aboutsummaryrefslogtreecommitdiffstats
path: root/executor
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2018-04-27 14:31:49 +0200
committerDmitry Vyukov <dvyukov@google.com>2018-04-27 14:33:01 +0200
commitbcd6198db5f0e61a6b5c824bbc58c954c4287d56 (patch)
treedb4844ba54971b780f95392dc641b4cc58a3c286 /executor
parent6bd89023819b08cd57c8ee342da966643c64ea77 (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.h103
-rw-r--r--executor/executor_bsd.cc15
-rw-r--r--executor/executor_linux.cc54
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;
+}