diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2018-04-27 14:31:49 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2018-04-27 14:33:01 +0200 |
| commit | bcd6198db5f0e61a6b5c824bbc58c954c4287d56 (patch) | |
| tree | db4844ba54971b780f95392dc641b4cc58a3c286 /executor | |
| parent | 6bd89023819b08cd57c8ee342da966643c64ea77 (diff) | |
executor: support cover on 32-bit kernels
Detect kernel bitness and properly extract coverage on 32-bit kernels.
Diffstat (limited to 'executor')
| -rw-r--r-- | executor/executor.h | 103 | ||||
| -rw-r--r-- | executor/executor_bsd.cc | 15 | ||||
| -rw-r--r-- | executor/executor_linux.cc | 54 |
3 files changed, 101 insertions, 71 deletions
diff --git a/executor/executor.h b/executor/executor.h index 88075a11d..6210fa136 100644 --- a/executor/executor.h +++ b/executor/executor.h @@ -68,6 +68,7 @@ unsigned long long procid; int running; uint32 completed; bool collide; +bool is_kernel_64_bit = true; ALIGNED(64 << 10) char input_data[kMaxInput]; @@ -79,18 +80,13 @@ const uint64 arg_csum_inet = 0; const uint64 arg_csum_chunk_data = 0; const uint64 arg_csum_chunk_const = 1; -// TODO(dvyukov): for 32-bit kernel this needs to be uint32. -typedef uint64 cover_t; - struct thread_t { bool created; int id; osthread_t th; - // TODO(dvyukov): this assumes 64-bit kernel. This must be "kernel long" somehow. - cover_t* cover_data; - // Pointer to the size of coverage (stored as first word of memory). - cover_t* cover_size_ptr; - cover_t cover_buffer[1]; // fallback coverage buffer + char* cover_data; + char* cover_end; + uint64 cover_buffer[1]; // fallback coverage buffer event_t ready; event_t done; @@ -104,7 +100,7 @@ struct thread_t { long args[kMaxArgs]; long res; uint32 reserrno; - cover_t cover_size; + uint32 cover_size; bool fault_injected; int cover_fd; }; @@ -186,7 +182,7 @@ bool copyout(char* addr, uint64 size, uint64* res); void cover_open(); void cover_enable(thread_t* th); void cover_reset(thread_t* th); -cover_t read_cover_size(thread_t* th); +uint32 read_cover_size(thread_t* th); static uint32 hash(uint32 a); static bool dedup(uint32 sig); @@ -491,6 +487,42 @@ thread_t* schedule_call(int call_index, int call_num, bool colliding, uint64 cop return th; } +template <typename cover_t> +void write_coverage_signal(thread_t* th, uint32* signal_count_pos, uint32* cover_count_pos) +{ + // Write out feedback signals. + // Currently it is code edges computed as xor of two subsequent basic block PCs. + cover_t* cover_data = ((cover_t*)th->cover_data) + 1; + uint32 nsig = 0; + uint32 prev = 0; + for (uint32 i = 0; i < th->cover_size; i++) { + uint32 pc = cover_data[i]; + uint32 sig = pc ^ prev; + prev = hash(pc); + if (dedup(sig)) + continue; + write_output(sig); + nsig++; + } + // Write out number of signals. + *signal_count_pos = nsig; + + if (!flag_collect_cover) + return; + // Write out real coverage (basic block PCs). + uint32 cover_size = th->cover_size; + if (flag_dedup_cover) { + cover_t* end = cover_data + cover_size; + std::sort(cover_data, end); + cover_size = std::unique(cover_data, end) - cover_data; + } + // Truncate PCs to uint32 assuming that they fit into 32-bits. + // True for x86_64 and arm64 without KASLR. + for (uint32 i = 0; i < cover_size; i++) + write_output(cover_data[i]); + *cover_count_pos = cover_size; +} + 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); @@ -536,63 +568,34 @@ void handle_completion(thread_t* th) uint32* signal_count_pos = write_output(0); // filled in later uint32* cover_count_pos = write_output(0); // filled in later uint32* comps_count_pos = write_output(0); // filled in later - uint32 nsig = 0, cover_size = 0, comps_size = 0; if (flag_collect_comps) { // Collect only the comparisons - // TODO(dvyukov): this is broken for 32-bit kernels. - // cover_data is offsetted by cover_t, but kernel always offsetted it by uint64. uint32 ncomps = th->cover_size; - kcov_comparison_t* start = (kcov_comparison_t*)th->cover_data; + kcov_comparison_t* start = (kcov_comparison_t*)(th->cover_data + sizeof(uint64)); kcov_comparison_t* end = start + ncomps; - if ((cover_t*)end >= th->cover_data + kCoverSize) + if ((char*)end > th->cover_end) fail("too many comparisons %u", ncomps); std::sort(start, end); ncomps = std::unique(start, end) - start; + uint32 comps_size = 0; for (uint32 i = 0; i < ncomps; ++i) { if (start[i].ignore()) continue; comps_size++; start[i].write(); } + // Write out number of comparisons. + *comps_count_pos = comps_size; } else { - // Write out feedback signals. - // Currently it is code edges computed as xor of - // two subsequent basic block PCs. - uint32 prev = 0; - for (uint32 i = 0; i < th->cover_size; i++) { - uint32 pc = (uint32)th->cover_data[i]; - uint32 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) { - cover_t* start = th->cover_data; - cover_t* end = start + cover_size; - std::sort(start, end); - cover_size = std::unique(start, end) - start; - } - // Truncate PCs to uint32 assuming that they fit into 32-bits. - // True for x86_64 and arm64 without KASLR. - for (uint32 i = 0; i < cover_size; i++) - write_output((uint32)th->cover_data[i]); - } + if (is_kernel_64_bit) + write_coverage_signal<uint64>(th, signal_count_pos, cover_count_pos); + else + write_coverage_signal<uint32>(th, signal_count_pos, cover_count_pos); } - // 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("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, th->call_index, th->call_num, reserrno, + *signal_count_pos, *cover_count_pos, *comps_count_pos); completed++; write_completed(completed); } diff --git a/executor/executor_bsd.cc b/executor/executor_bsd.cc index 10b189008..83956f3a9 100644 --- a/executor/executor_bsd.cc +++ b/executor/executor_bsd.cc @@ -163,16 +163,17 @@ void cover_open() fail("open of /dev/kcov failed"); if (ioctl(th->cover_fd, KIOSETBUFSIZE, &kCoverSize)) fail("ioctl init trace write failed"); - size_t mmap_alloc_size = kCoverSize * sizeof(th->cover_data[0]); - uint64* mmap_ptr = (uint64*)mmap(NULL, mmap_alloc_size, - PROT_READ | PROT_WRITE, - MAP_SHARED, th->cover_fd, 0); + size_t mmap_alloc_size = kCoverSize * (is_kernel_64_bit ? 8 : 4); + char* mmap_ptr = (char*)mmap(NULL, mmap_alloc_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, th->cover_fd, 0); if (mmap_ptr == NULL) fail("cover mmap failed"); - th->cover_size_ptr = mmap_ptr; - th->cover_data = &mmap_ptr[1]; + th->cover_data = mmap_ptr; + th->cover_end = mmap_ptr + mmap_alloc_size; #else - th->cover_data = &th->cover_buffer[0]; + th->cover_data = (char*)&th->cover_buffer[0]; + th->cover_end = th->cover_data + sizeof(th->cover_buffer); #endif } } diff --git a/executor/executor_linux.cc b/executor/executor_linux.cc index 070d2bc94..d9177bce7 100644 --- a/executor/executor_linux.cc +++ b/executor/executor_linux.cc @@ -25,8 +25,8 @@ #include "syscalls_linux.h" -#define KCOV_INIT_TRACE _IOR('c', 1, cover_t) -#define KCOV_INIT_CMP _IOR('c', 2, cover_t) +#define KCOV_INIT_TRACE32 _IOR('c', 1, uint32) +#define KCOV_INIT_TRACE64 _IOR('c', 1, uint64) #define KCOV_ENABLE _IO('c', 100) #define KCOV_DISABLE _IO('c', 101) @@ -42,8 +42,11 @@ void* const kOutputDataAddr = (void*)0x1b2bc20000ull; uint32* output_data; uint32* output_pos; +static bool detect_kernel_bitness(); + int main(int argc, char** argv) { + is_kernel_64_bit = detect_kernel_bitness(); if (argc == 2 && strcmp(argv[1], "version") == 0) { puts(GOOS " " GOARCH " " SYZ_REVISION " " GIT_REVISION); return 0; @@ -137,15 +140,15 @@ void cover_open() th->cover_fd = open("/sys/kernel/debug/kcov", O_RDWR); if (th->cover_fd == -1) fail("open of /sys/kernel/debug/kcov failed"); - if (ioctl(th->cover_fd, KCOV_INIT_TRACE, kCoverSize)) + const int kcov_init_trace = is_kernel_64_bit ? KCOV_INIT_TRACE64 : KCOV_INIT_TRACE32; + if (ioctl(th->cover_fd, kcov_init_trace, kCoverSize)) fail("cover init trace write failed"); - size_t mmap_alloc_size = kCoverSize * sizeof(th->cover_data[0]); - void* mmap_ptr = mmap(NULL, mmap_alloc_size, - PROT_READ | PROT_WRITE, MAP_SHARED, th->cover_fd, 0); - if (mmap_ptr == MAP_FAILED) + size_t mmap_alloc_size = kCoverSize * (is_kernel_64_bit ? 8 : 4); + th->cover_data = (char*)mmap(NULL, mmap_alloc_size, + PROT_READ | PROT_WRITE, MAP_SHARED, th->cover_fd, 0); + th->cover_end = th->cover_data + mmap_alloc_size; + if (th->cover_data == MAP_FAILED) fail("cover mmap failed"); - th->cover_size_ptr = (cover_t*)mmap_ptr; - th->cover_data = &th->cover_size_ptr[1]; } } @@ -170,17 +173,18 @@ void cover_reset(thread_t* th) return; if (th == 0) th = current_thread; - __atomic_store_n(th->cover_size_ptr, 0, __ATOMIC_RELAXED); + *(uint64*)th->cover_data = 0; } -cover_t read_cover_size(thread_t* th) +uint32 read_cover_size(thread_t* th) { if (!flag_cover) return 0; - cover_t n = __atomic_load_n(th->cover_size_ptr, __ATOMIC_RELAXED); - debug("#%d: read cover size = %llu\n", th->id, (uint64)n); + // Note: this assumes little-endian kernel. + uint32 n = *(uint32*)th->cover_data; + debug("#%d: read cover size = %u\n", th->id, n); if (n >= kCoverSize) - fail("#%d: too much cover %llu", th->id, (uint64)n); + fail("#%d: too much cover %u", th->id, n); return n; } @@ -232,3 +236,25 @@ bool kcov_comparison_t::ignore() const } return false; } + +static bool detect_kernel_bitness() +{ + if (sizeof(void*) == 8) + return true; + // It turns out to be surprisingly hard to understand if the kernel underneath is 64-bits. + // A common method is to look at uname.machine. But it is produced in some involved ways, + // and we will need to know about all strings it returns and in the end it can be overriden + // during build and lie (and there are known precedents of this). + // So instead we look at size of addresses in /proc/kallsyms. + bool wide = true; + int fd = open("/proc/kallsyms", O_RDONLY); + if (fd != -1) { + char buf[16]; + if (read(fd, buf, sizeof(buf)) == sizeof(buf) && + (buf[8] == ' ' || buf[8] == '\t')) + wide = false; + close(fd); + } + debug("detected %d-bit kernel\n", wide ? 64 : 32); + return wide; +} |
