| // Copyright 2017 syzkaller project authors. All rights reserved. |
| // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. |
| |
| #include <algorithm> |
| #include <errno.h> |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| |
| #ifndef GIT_REVISION |
| #define GIT_REVISION "unknown" |
| #endif |
| |
| #ifndef GOOS |
| #define GOOS "unknown" |
| #endif |
| |
| // Note: zircon max fd is 256. |
| const int kInPipeFd = 250; // remapped from stdin |
| const int kOutPipeFd = 251; // remapped from stdout |
| |
| const int kMaxInput = 2 << 20; |
| const int kMaxOutput = 16 << 20; |
| const int kCoverSize = 64 << 13; // TODO: check |
| const int kMaxArgs = 9; |
| const int kMaxThreads = 16; |
| const int kMaxCommands = 16 << 10; |
| |
| const uint64_t instr_eof = -1; |
| const uint64_t instr_copyin = -2; |
| const uint64_t instr_copyout = -3; |
| |
| const uint64_t arg_const = 0; |
| const uint64_t arg_result = 1; |
| const uint64_t arg_data = 2; |
| const uint64_t arg_csum = 3; |
| |
| enum sandbox_type { |
| sandbox_none, |
| sandbox_setuid, |
| sandbox_namespace, |
| }; |
| |
| bool flag_cover; |
| bool flag_threaded; |
| bool flag_collide; |
| bool flag_sandbox_privs; |
| sandbox_type flag_sandbox; |
| bool flag_enable_tun; |
| bool flag_enable_fault_injection; |
| |
| bool flag_collect_cover; |
| bool flag_dedup_cover; |
| |
| // If true, then executor should write the comparisons data to fuzzer. |
| bool flag_collect_comps; |
| |
| // Inject fault into flag_fault_nth-th operation in flag_fault_call-th syscall. |
| bool flag_inject_fault; |
| int flag_fault_call; |
| int flag_fault_nth; |
| |
| int flag_pid; |
| |
| int running; |
| uint32_t completed; |
| bool collide; |
| |
| ALIGNED(64 << 10) |
| char input_data[kMaxInput]; |
| |
| // We use the default value instead of results of failed syscalls. |
| // -1 is an invalid fd and an invalid address and deterministic, |
| // so good enough for our purposes. |
| const uint64_t default_value = -1; |
| |
| // Checksum kinds. |
| const uint64_t arg_csum_inet = 0; |
| |
| // Checksum chunk kinds. |
| const uint64_t arg_csum_chunk_data = 0; |
| const uint64_t arg_csum_chunk_const = 1; |
| |
| struct thread_t { |
| bool created; |
| int id; |
| osthread_t th; |
| // TODO(dvyukov): this assumes 64-bit kernel. This must be "kernel long" somehow. |
| uint64_t* cover_data; |
| // Pointer to the size of coverage (stored as first word of memory). |
| uint64_t* cover_size_ptr; |
| uint64_t cover_buffer[1]; // fallback coverage buffer |
| |
| event_t ready; |
| event_t done; |
| uint64_t* copyout_pos; |
| bool handled; |
| int call_n; |
| int call_index; |
| int call_num; |
| int num_args; |
| long args[kMaxArgs]; |
| long res; |
| uint32_t reserrno; |
| uint64_t cover_size; |
| bool fault_injected; |
| int cover_fd; |
| }; |
| |
| thread_t threads[kMaxThreads]; |
| |
| struct res_t { |
| bool executed; |
| uint64_t val; |
| }; |
| |
| res_t results[kMaxCommands]; |
| |
| const uint64_t kInMagic = 0xbadc0ffeebadface; |
| const uint32_t kOutMagic = 0xbadf00d; |
| |
| struct handshake_req { |
| uint64_t magic; |
| uint64_t flags; // env flags |
| uint64_t pid; |
| }; |
| |
| struct handshake_reply { |
| uint32_t magic; |
| }; |
| |
| struct execute_req { |
| uint64_t magic; |
| uint64_t env_flags; |
| uint64_t exec_flags; |
| uint64_t pid; |
| uint64_t fault_call; |
| uint64_t fault_nth; |
| uint64_t prog_size; |
| }; |
| |
| struct execute_reply { |
| uint32_t magic; |
| uint32_t done; |
| uint32_t status; |
| }; |
| |
| enum { |
| KCOV_CMP_CONST = 1, |
| KCOV_CMP_SIZE1 = 0, |
| KCOV_CMP_SIZE2 = 2, |
| KCOV_CMP_SIZE4 = 4, |
| KCOV_CMP_SIZE8 = 6, |
| KCOV_CMP_SIZE_MASK = 6, |
| }; |
| |
| struct kcov_comparison_t { |
| uint64_t type; |
| uint64_t arg1; |
| uint64_t arg2; |
| uint64_t pc; |
| |
| bool ignore() const; |
| void write(); |
| bool operator==(const struct kcov_comparison_t& other) const; |
| bool operator<(const struct kcov_comparison_t& other) const; |
| }; |
| |
| long execute_syscall(call_t* c, long a0, long a1, long a2, long a3, long a4, long a5, long a6, long a7, long a8); |
| thread_t* schedule_call(int n, int call_index, int call_num, uint64_t num_args, uint64_t* args, uint64_t* pos); |
| void handle_completion(thread_t* th); |
| void execute_call(thread_t* th); |
| void thread_create(thread_t* th, int id); |
| void* worker_thread(void* arg); |
| uint32_t* write_output(uint32_t v); |
| void write_completed(uint32_t completed); |
| uint64_t read_input(uint64_t** input_posp, bool peek = false); |
| uint64_t read_arg(uint64_t** input_posp); |
| uint64_t read_result(uint64_t** input_posp); |
| void copyin(char* addr, uint64_t val, uint64_t size, uint64_t bf_off, uint64_t bf_len); |
| uint64_t copyout(char* addr, uint64_t size); |
| void cover_open(); |
| void cover_enable(thread_t* th); |
| void cover_reset(thread_t* th); |
| uint64_t read_cover_size(thread_t* th); |
| static uint32_t hash(uint32_t a); |
| static bool dedup(uint32_t sig); |
| |
| void setup_control_pipes() |
| { |
| if (dup2(0, kInPipeFd) < 0) |
| fail("dup2(0, kInPipeFd) failed"); |
| if (dup2(1, kOutPipeFd) < 0) |
| fail("dup2(1, kOutPipeFd) failed"); |
| if (dup2(2, 1) < 0) |
| fail("dup2(2, 1) failed"); |
| if (close(0)) |
| fail("close(0) failed"); |
| } |
| |
| void parse_env_flags(uint64_t flags) |
| { |
| flag_debug = flags & (1 << 0); |
| flag_cover = flags & (1 << 1); |
| flag_threaded = flags & (1 << 2); |
| flag_collide = flags & (1 << 3); |
| |
| flag_threaded = 0; |
| flag_collide = 0; |
| |
| flag_sandbox = sandbox_none; |
| if (flags & (1 << 4)) |
| flag_sandbox = sandbox_setuid; |
| else if (flags & (1 << 5)) |
| flag_sandbox = sandbox_namespace; |
| if (!flag_threaded) |
| flag_collide = false; |
| flag_enable_tun = flags & (1 << 6); |
| flag_enable_fault_injection = flags & (1 << 7); |
| } |
| |
| void receive_handshake() |
| { |
| handshake_req req = {}; |
| int n = read(kInPipeFd, &req, sizeof(req)); |
| if (n != sizeof(req)) |
| fail("handshake read failed: %d", n); |
| if (req.magic != kInMagic) |
| fail("bad handshake magic 0x%llx", req.magic); |
| parse_env_flags(req.flags); |
| flag_pid = req.pid; |
| } |
| |
| void reply_handshake() |
| { |
| handshake_reply reply = {}; |
| reply.magic = kOutMagic; |
| if (write(kOutPipeFd, &reply, sizeof(reply)) != sizeof(reply)) |
| fail("control pipe write failed"); |
| } |
| |
| void receive_execute(bool need_prog) |
| { |
| execute_req req; |
| if (read(kInPipeFd, &req, sizeof(req)) != (ssize_t)sizeof(req)) |
| fail("control pipe read failed"); |
| if (req.magic != kInMagic) |
| fail("bad execute request magic 0x%llx", req.magic); |
| if (req.prog_size > kMaxInput) |
| fail("bad execute prog size 0x%llx", req.prog_size); |
| parse_env_flags(req.env_flags); |
| flag_pid = req.pid; |
| flag_collect_cover = req.exec_flags & (1 << 0); |
| flag_dedup_cover = req.exec_flags & (1 << 1); |
| flag_inject_fault = req.exec_flags & (1 << 2); |
| flag_collect_comps = req.exec_flags & (1 << 3); |
| flag_fault_call = req.fault_call; |
| flag_fault_nth = req.fault_nth; |
| debug("exec opts: pid=%d threaded=%d collide=%d cover=%d comps=%d dedup=%d fault=%d/%d/%d prog=%llu\n", |
| flag_pid, flag_threaded, flag_collide, flag_collect_cover, flag_collect_comps, |
| flag_dedup_cover, flag_inject_fault, flag_fault_call, flag_fault_nth, |
| req.prog_size); |
| if (req.prog_size == 0) { |
| if (need_prog) |
| fail("need_prog: no program"); |
| return; |
| } |
| uint64_t pos = 0; |
| for (;;) { |
| ssize_t rv = read(kInPipeFd, input_data + pos, sizeof(input_data) - pos); |
| if (rv < 0) |
| fail("read failed"); |
| pos += rv; |
| if (rv == 0 || pos >= req.prog_size) |
| break; |
| } |
| if (pos != req.prog_size) |
| fail("bad input size %d, want %d", pos, req.prog_size); |
| } |
| |
| void reply_execute(int status) |
| { |
| execute_reply reply = {}; |
| reply.magic = kOutMagic; |
| reply.done = true; |
| reply.status = status; |
| if (write(kOutPipeFd, &reply, sizeof(reply)) != sizeof(reply)) |
| fail("control pipe write failed"); |
| } |
| |
| // execute_one executes program stored in input_data. |
| void execute_one() |
| { |
| retry: |
| uint64_t* input_pos = (uint64_t*)input_data; |
| write_output(0); // Number of executed syscalls (updated later). |
| |
| if (!collide && !flag_threaded) |
| cover_enable(&threads[0]); |
| |
| int call_index = 0; |
| for (int n = 0;; n++) { |
| uint64_t call_num = read_input(&input_pos); |
| if (call_num == instr_eof) |
| break; |
| if (call_num == instr_copyin) { |
| char* addr = (char*)read_input(&input_pos); |
| uint64_t typ = read_input(&input_pos); |
| uint64_t size = read_input(&input_pos); |
| debug("copyin to %p\n", addr); |
| switch (typ) { |
| case arg_const: { |
| uint64_t arg = read_input(&input_pos); |
| uint64_t bf_off = read_input(&input_pos); |
| uint64_t bf_len = read_input(&input_pos); |
| copyin(addr, arg, size, bf_off, bf_len); |
| break; |
| } |
| case arg_result: { |
| uint64_t val = read_result(&input_pos); |
| copyin(addr, val, size, 0, 0); |
| break; |
| } |
| case arg_data: { |
| NONFAILING(memcpy(addr, input_pos, size)); |
| // Read out the data. |
| for (uint64_t i = 0; i < (size + 7) / 8; i++) |
| read_input(&input_pos); |
| break; |
| } |
| case arg_csum: { |
| debug("checksum found at %llx\n", addr); |
| char* csum_addr = addr; |
| uint64_t csum_size = size; |
| uint64_t csum_kind = read_input(&input_pos); |
| switch (csum_kind) { |
| case arg_csum_inet: { |
| if (csum_size != 2) { |
| fail("inet checksum must be 2 bytes, not %lu", size); |
| } |
| debug("calculating checksum for %llx\n", csum_addr); |
| struct csum_inet csum; |
| csum_inet_init(&csum); |
| uint64_t chunks_num = read_input(&input_pos); |
| uint64_t chunk; |
| for (chunk = 0; chunk < chunks_num; chunk++) { |
| uint64_t chunk_kind = read_input(&input_pos); |
| uint64_t chunk_value = read_input(&input_pos); |
| uint64_t chunk_size = read_input(&input_pos); |
| switch (chunk_kind) { |
| case arg_csum_chunk_data: |
| debug("#%d: data chunk, addr: %llx, size: %llu\n", chunk, chunk_value, chunk_size); |
| NONFAILING(csum_inet_update(&csum, (const uint8_t*)chunk_value, chunk_size)); |
| break; |
| case arg_csum_chunk_const: |
| if (chunk_size != 2 && chunk_size != 4 && chunk_size != 8) { |
| fail("bad checksum const chunk size %lld\n", chunk_size); |
| } |
| // Here we assume that const values come to us big endian. |
| debug("#%d: const chunk, value: %llx, size: %llu\n", chunk, chunk_value, chunk_size); |
| csum_inet_update(&csum, (const uint8_t*)&chunk_value, chunk_size); |
| break; |
| default: |
| fail("bad checksum chunk kind %lu", chunk_kind); |
| } |
| } |
| int16_t csum_value = csum_inet_digest(&csum); |
| debug("writing inet checksum %hx to %llx\n", csum_value, csum_addr); |
| NONFAILING(copyin(csum_addr, csum_value, 2, 0, 0)); |
| break; |
| } |
| default: |
| fail("bad checksum kind %lu", csum_kind); |
| } |
| break; |
| } |
| default: |
| fail("bad argument type %lu", typ); |
| } |
| continue; |
| } |
| if (call_num == instr_copyout) { |
| read_input(&input_pos); // addr |
| read_input(&input_pos); // size |
| // The copyout will happen when/if the call completes. |
| continue; |
| } |
| |
| // Normal syscall. |
| if (call_num >= syscall_count) |
| fail("invalid command number %lu", call_num); |
| uint64_t num_args = read_input(&input_pos); |
| if (num_args > kMaxArgs) |
| fail("command has bad number of arguments %lu", num_args); |
| uint64_t args[kMaxArgs] = {}; |
| for (uint64_t i = 0; i < num_args; i++) |
| args[i] = read_arg(&input_pos); |
| for (uint64_t i = num_args; i < 6; i++) |
| args[i] = 0; |
| thread_t* th = schedule_call(n, call_index++, call_num, num_args, args, input_pos); |
| |
| if (collide && (call_index % 2) == 0) { |
| // Don't wait for every other call. |
| // We already have results from the previous execution. |
| } else if (flag_threaded) { |
| // Wait for call completion. |
| // Note: sys knows about this 20ms timeout when it generates |
| // timespec/timeval values. |
| const uint64_t timeout_ms = flag_debug ? 500 : 20; |
| if (event_timedwait(&th->done, timeout_ms)) |
| handle_completion(th); |
| // Check if any of previous calls have completed. |
| // Give them some additional time, because they could have been |
| // just unblocked by the current call. |
| if (running < 0) |
| fail("running = %d", running); |
| if (running > 0) { |
| bool last = read_input(&input_pos, true) == instr_eof; |
| sleep_ms(last ? 10 : 1); |
| for (int i = 0; i < kMaxThreads; i++) { |
| th = &threads[i]; |
| if (!th->handled && event_isset(&th->done)) |
| handle_completion(th); |
| } |
| } |
| } else { |
| // Execute directly. |
| if (th != &threads[0]) |
| fail("using non-main thread in non-thread mode"); |
| execute_call(th); |
| handle_completion(th); |
| } |
| } |
| |
| if (flag_collide && !flag_inject_fault && !collide) { |
| debug("enabling collider\n"); |
| collide = true; |
| goto retry; |
| } |
| } |
| |
| thread_t* schedule_call(int n, int call_index, int call_num, uint64_t num_args, uint64_t* args, uint64_t* pos) |
| { |
| // Find a spare thread to execute the call. |
| int i; |
| for (i = 0; i < kMaxThreads; i++) { |
| thread_t* th = &threads[i]; |
| if (!th->created) |
| thread_create(th, i); |
| if (event_isset(&th->done)) { |
| if (!th->handled) |
| handle_completion(th); |
| break; |
| } |
| } |
| if (i == kMaxThreads) |
| exitf("out of threads"); |
| thread_t* th = &threads[i]; |
| debug("scheduling call %d [%s] on thread %d\n", call_index, syscalls[call_num].name, th->id); |
| if (event_isset(&th->ready) || !event_isset(&th->done) || !th->handled) |
| fail("bad thread state in schedule: ready=%d done=%d handled=%d", |
| event_isset(&th->ready), event_isset(&th->done), th->handled); |
| th->copyout_pos = pos; |
| event_reset(&th->done); |
| th->handled = false; |
| th->call_n = n; |
| th->call_index = call_index; |
| th->call_num = call_num; |
| th->num_args = num_args; |
| for (int i = 0; i < kMaxArgs; i++) |
| th->args[i] = args[i]; |
| event_set(&th->ready); |
| running++; |
| return th; |
| } |
| |
| void handle_completion(thread_t* th) |
| { |
| debug("completion of call %d [%s] on thread %d\n", th->call_index, syscalls[th->call_num].name, th->id); |
| if (event_isset(&th->ready) || !event_isset(&th->done) || th->handled) |
| fail("bad thread state in completion: ready=%d done=%d handled=%d", |
| event_isset(&th->ready), event_isset(&th->done), th->handled); |
| if (th->res != (long)-1) { |
| if (th->call_n >= kMaxCommands) |
| fail("result idx %ld overflows kMaxCommands", th->call_n); |
| results[th->call_n].executed = true; |
| results[th->call_n].val = th->res; |
| for (bool done = false; !done;) { |
| th->call_n++; |
| uint64_t call_num = read_input(&th->copyout_pos); |
| switch (call_num) { |
| case instr_copyout: { |
| char* addr = (char*)read_input(&th->copyout_pos); |
| uint64_t size = read_input(&th->copyout_pos); |
| uint64_t val = copyout(addr, size); |
| if (th->call_n >= kMaxCommands) |
| fail("result idx %ld overflows kMaxCommands", th->call_n); |
| results[th->call_n].executed = true; |
| results[th->call_n].val = val; |
| debug("copyout from %p\n", addr); |
| break; |
| } |
| default: |
| done = true; |
| break; |
| } |
| } |
| } |
| if (!collide) { |
| write_output(th->call_index); |
| write_output(th->call_num); |
| uint32_t reserrno = th->res != -1 ? 0 : th->reserrno; |
| write_output(reserrno); |
| write_output(th->fault_injected); |
| uint32_t* signal_count_pos = write_output(0); // filled in later |
| uint32_t* cover_count_pos = write_output(0); // filled in later |
| uint32_t* comps_count_pos = write_output(0); // filled in later |
| uint32_t nsig = 0, cover_size = 0, comps_size = 0; |
| |
| if (flag_collect_comps) { |
| // Collect only the comparisons |
| uint32_t ncomps = th->cover_size; |
| kcov_comparison_t* start = (kcov_comparison_t*)th->cover_data; |
| kcov_comparison_t* end = start + ncomps; |
| if ((uint64_t*)end >= th->cover_data + kCoverSize) |
| fail("too many comparisons %u", ncomps); |
| std::sort(start, end); |
| ncomps = std::unique(start, end) - start; |
| for (uint32_t i = 0; i < ncomps; ++i) { |
| if (start[i].ignore()) |
| continue; |
| comps_size++; |
| start[i].write(); |
| } |
| } else { |
| // Write out feedback signals. |
| // Currently it is code edges computed as xor of |
| // two subsequent basic block PCs. |
| uint32_t prev = 0; |
| for (uint32_t i = 0; i < th->cover_size; i++) { |
| uint32_t pc = (uint32_t)th->cover_data[i]; |
| uint32_t sig = pc ^ prev; |
| prev = hash(pc); |
| if (dedup(sig)) |
| continue; |
| write_output(sig); |
| nsig++; |
| } |
| if (flag_collect_cover) { |
| // Write out real coverage (basic block PCs). |
| cover_size = th->cover_size; |
| if (flag_dedup_cover) { |
| uint64_t* start = (uint64_t*)th->cover_data; |
| uint64_t* end = start + cover_size; |
| std::sort(start, end); |
| cover_size = std::unique(start, end) - start; |
| } |
| // Truncate PCs to uint32_t assuming that they fit into 32-bits. |
| // True for x86_64 and arm64 without KASLR. |
| for (uint32_t i = 0; i < cover_size; i++) |
| write_output((uint32_t)th->cover_data[i]); |
| } |
| } |
| // Write out real coverage (basic block PCs). |
| *cover_count_pos = cover_size; |
| // Write out number of comparisons |
| *comps_count_pos = comps_size; |
| // Write out number of signals |
| *signal_count_pos = nsig; |
| |
| debug("th->cover_size: %d, cover_size: %d\n", (int)th->cover_size, (int)cover_size); |
| |
| debug("out #%u: index=%u num=%u errno=%d sig=%u cover=%u comps=%u\n", |
| completed, th->call_index, th->call_num, reserrno, nsig, |
| cover_size, comps_size); |
| completed++; |
| write_completed(completed); |
| } |
| th->handled = true; |
| running--; |
| } |
| |
| void thread_create(thread_t* th, int id) |
| { |
| th->created = true; |
| th->id = id; |
| th->handled = true; |
| event_init(&th->ready); |
| event_init(&th->done); |
| event_set(&th->done); |
| if (flag_threaded) |
| thread_start(&th->th, worker_thread, th); |
| } |
| |
| void* worker_thread(void* arg) |
| { |
| thread_t* th = (thread_t*)arg; |
| |
| cover_enable(th); |
| for (;;) { |
| event_wait(&th->ready); |
| execute_call(th); |
| } |
| return 0; |
| } |
| |
| void execute_call(thread_t* th) |
| { |
| event_reset(&th->ready); |
| call_t* call = &syscalls[th->call_num]; |
| debug("#%d: %s(", th->id, call->name); |
| for (int i = 0; i < th->num_args; i++) { |
| if (i != 0) |
| debug(", "); |
| debug("0x%lx", th->args[i]); |
| } |
| debug(")\n"); |
| |
| int fail_fd = -1; |
| if (flag_inject_fault && th->call_index == flag_fault_call) { |
| if (collide) |
| fail("both collide and fault injection are enabled"); |
| debug("injecting fault into %d-th operation\n", flag_fault_nth); |
| fail_fd = inject_fault(flag_fault_nth); |
| } |
| |
| cover_reset(th); |
| errno = 0; |
| th->res = execute_syscall(call, th->args[0], th->args[1], th->args[2], |
| th->args[3], th->args[4], th->args[5], |
| th->args[6], th->args[7], th->args[8]); |
| th->reserrno = errno; |
| th->cover_size = read_cover_size(th); |
| th->fault_injected = false; |
| |
| debug("collected cover size for %s: %d\n", call->name, (int)th->cover_size); |
| |
| if (flag_inject_fault && th->call_index == flag_fault_call) { |
| th->fault_injected = fault_injected(fail_fd); |
| debug("fault injected: %d\n", th->fault_injected); |
| } |
| |
| if (th->res == -1) |
| debug("#%d: %s = errno(%d)\n", th->id, call->name, th->reserrno); |
| else |
| debug("#%d: %s = 0x%lx\n", th->id, call->name, th->res); |
| event_set(&th->done); |
| } |
| |
| static uint32_t hash(uint32_t a) |
| { |
| a = (a ^ 61) ^ (a >> 16); |
| a = a + (a << 3); |
| a = a ^ (a >> 4); |
| a = a * 0x27d4eb2d; |
| a = a ^ (a >> 15); |
| return a; |
| } |
| |
| const uint32_t dedup_table_size = 8 << 10; |
| uint32_t dedup_table[dedup_table_size]; |
| |
| // Poorman's best-effort hashmap-based deduplication. |
| // The hashmap is global which means that we deduplicate across different calls. |
| // This is OK because we are interested only in new signals. |
| static bool dedup(uint32_t sig) |
| { |
| for (uint32_t i = 0; i < 4; i++) { |
| uint32_t pos = (sig + i) % dedup_table_size; |
| if (dedup_table[pos] == sig) |
| return true; |
| if (dedup_table[pos] == 0) { |
| dedup_table[pos] = sig; |
| return false; |
| } |
| } |
| dedup_table[sig % dedup_table_size] = sig; |
| return false; |
| } |
| |
| void copyin(char* addr, uint64_t val, uint64_t size, uint64_t bf_off, uint64_t bf_len) |
| { |
| NONFAILING(switch (size) { |
| case 1: |
| STORE_BY_BITMASK(uint8_t, addr, val, bf_off, bf_len); |
| break; |
| case 2: |
| STORE_BY_BITMASK(uint16_t, addr, val, bf_off, bf_len); |
| break; |
| case 4: |
| STORE_BY_BITMASK(uint32_t, addr, val, bf_off, bf_len); |
| break; |
| case 8: |
| STORE_BY_BITMASK(uint64_t, addr, val, bf_off, bf_len); |
| break; |
| default: |
| fail("copyin: bad argument size %lu", size); |
| }); |
| } |
| |
| uint64_t copyout(char* addr, uint64_t size) |
| { |
| uint64_t res = default_value; |
| NONFAILING(switch (size) { |
| case 1: |
| res = *(uint8_t*)addr; |
| break; |
| case 2: |
| res = *(uint16_t*)addr; |
| break; |
| case 4: |
| res = *(uint32_t*)addr; |
| break; |
| case 8: |
| res = *(uint64_t*)addr; |
| break; |
| default: |
| fail("copyout: bad argument size %lu", size); |
| }); |
| return res; |
| } |
| |
| uint64_t read_arg(uint64_t** input_posp) |
| { |
| uint64_t typ = read_input(input_posp); |
| uint64_t size = read_input(input_posp); |
| (void)size; |
| uint64_t arg = 0; |
| switch (typ) { |
| case arg_const: { |
| arg = read_input(input_posp); |
| // Bitfields can't be args of a normal syscall, so just ignore them. |
| read_input(input_posp); // bit field offset |
| read_input(input_posp); // bit field length |
| break; |
| } |
| case arg_result: { |
| arg = read_result(input_posp); |
| break; |
| } |
| default: |
| fail("bad argument type %lu", typ); |
| } |
| return arg; |
| } |
| |
| uint64_t read_result(uint64_t** input_posp) |
| { |
| uint64_t idx = read_input(input_posp); |
| uint64_t op_div = read_input(input_posp); |
| uint64_t op_add = read_input(input_posp); |
| if (idx >= kMaxCommands) |
| fail("command refers to bad result %ld", idx); |
| uint64_t arg = default_value; |
| if (results[idx].executed) { |
| arg = results[idx].val; |
| if (op_div != 0) |
| arg = arg / op_div; |
| arg += op_add; |
| } |
| return arg; |
| } |
| |
| uint64_t read_input(uint64_t** input_posp, bool peek) |
| { |
| uint64_t* input_pos = *input_posp; |
| if ((char*)input_pos >= input_data + kMaxInput) |
| fail("input command overflows input"); |
| if (!peek) |
| *input_posp = input_pos + 1; |
| return *input_pos; |
| } |
| |
| void kcov_comparison_t::write() |
| { |
| // Write order: type arg1 arg2 pc. |
| write_output((uint32_t)type); |
| |
| // KCOV converts all arguments of size x first to uintx_t and then to |
| // uint64_t. We want to properly extend signed values, e.g we want |
| // int8_t c = 0xfe to be represented as 0xfffffffffffffffe. |
| // Note that uint8_t c = 0xfe will be represented the same way. |
| // This is ok because during hints processing we will anyways try |
| // the value 0x00000000000000fe. |
| switch (type & KCOV_CMP_SIZE_MASK) { |
| case KCOV_CMP_SIZE1: |
| arg1 = (uint64_t)(int64_t)(int8_t)arg1; |
| arg2 = (uint64_t)(int64_t)(int8_t)arg2; |
| break; |
| case KCOV_CMP_SIZE2: |
| arg1 = (uint64_t)(int64_t)(int16_t)arg1; |
| arg2 = (uint64_t)(int64_t)(int16_t)arg2; |
| break; |
| case KCOV_CMP_SIZE4: |
| arg1 = (uint64_t)(int64_t)(int32_t)arg1; |
| arg2 = (uint64_t)(int64_t)(int32_t)arg2; |
| break; |
| } |
| bool is_size_8 = (type & KCOV_CMP_SIZE_MASK) == KCOV_CMP_SIZE8; |
| if (!is_size_8) { |
| write_output((uint32_t)arg1); |
| write_output((uint32_t)arg2); |
| return; |
| } |
| // If we have 64 bits arguments then write them in Little-endian. |
| write_output((uint32_t)(arg1 & 0xFFFFFFFF)); |
| write_output((uint32_t)(arg1 >> 32)); |
| write_output((uint32_t)(arg2 & 0xFFFFFFFF)); |
| write_output((uint32_t)(arg2 >> 32)); |
| } |
| |
| bool kcov_comparison_t::operator==(const struct kcov_comparison_t& other) const |
| { |
| // We don't check for PC equality now, because it is not used. |
| return type == other.type && arg1 == other.arg1 && arg2 == other.arg2; |
| } |
| |
| bool kcov_comparison_t::operator<(const struct kcov_comparison_t& other) const |
| { |
| if (type != other.type) |
| return type < other.type; |
| if (arg1 != other.arg1) |
| return arg1 < other.arg1; |
| // We don't check for PC equality now, because it is not used. |
| return arg2 < other.arg2; |
| } |