From 74e0017e92de0b3d51230c020d5ffa4c2bd2badc Mon Sep 17 00:00:00 2001 From: Netanel Komm Date: Tue, 21 Apr 2026 00:32:54 +0300 Subject: [PATCH] syz-verifier, executor, pkg/flatrpc: implement memory comparison This commit introduces a differential memory comparison engine to detect memory divergences across different kernel versions in syz-verifier. syz-verifier: Isolate the memory policy engine into a dedicated memcmp.go module to track and evaluate mismatches. The verifier now sets the new MemCmp execution flag when requesting program execution to instruct the executor to provide memory data. executor: Introduce ptrace interception into the execution loop to capture baseline and final memory hashes. Add 2 hook points into the child that raise SIGSTOP, which the parent catches via ptrace - one when the child starts and one when it is about to exit. This allows the parent to collect memory information about the child to send back to the verifier safely. Gate memory comparison related operations behind flag_memcmp to avoid incurring overhead for other tools that rely on the executor. pkg/flatrpc: Integrate said MemCmp flag into ExecFlags via FlatBuffers. --- executor/common.h | 45 +++++- executor/executor.cc | 166 +++++++++++++++++++++- executor/executor_runner.h | 3 +- executor/snapshot.h | 2 +- pkg/flatrpc/flatrpc.fbs | 11 ++ pkg/flatrpc/flatrpc.go | 278 +++++++++++++++++++++++++++++++++++-- pkg/flatrpc/flatrpc.h | 247 +++++++++++++++++++++++++++----- syz-verifier/memcmp.go | 203 +++++++++++++++++++++++++++ syz-verifier/verifier.go | 6 + 9 files changed, 911 insertions(+), 50 deletions(-) create mode 100644 syz-verifier/memcmp.go diff --git a/executor/common.h b/executor/common.h index e7e02ffb22c0..c4f391e7081a 100644 --- a/executor/common.h +++ b/executor/common.h @@ -80,6 +80,7 @@ static unsigned long long procid; #include #if GOOS_linux +#include #include #endif @@ -655,6 +656,14 @@ static void loop(void) #endif #if SYZ_EXECUTOR close(kOutPipeFd); +#if GOOS_linux + if (flag_memcmp) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) { + perror("ptrace TRACEME"); + doexit(1); + } + } +#endif #endif execute_one(); #if !SYZ_EXECUTOR && SYZ_HAVE_CLOSE_FDS && !SYZ_THREADED @@ -680,11 +689,43 @@ static void loop(void) #if SYZ_EXECUTOR uint64 last_executed = start; uint32 executed_calls = output_data->completed.load(std::memory_order_relaxed); +#if GOOS_linux + int times_stopped = 0; +#endif #endif for (;;) { sleep_ms(10); - if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid) - break; + if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid) { +#if SYZ_EXECUTOR +#if GOOS_linux + if (flag_memcmp && WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) { + times_stopped++; + const bool is_snapshot = (times_stopped == 1); + const bool is_after = (times_stopped == 2); + if (!output_data || (!is_snapshot && !is_after)) { + if (ptrace(PTRACE_CONT, pid, NULL, NULL) == -1) + debug("ptrace CONT failed for pid %d: %s\n", pid, strerror(errno)); + continue; + } + uint64 hash_start_time = current_time_ms(); + memory_region* out = is_snapshot ? output_data->snapshot_vmas : output_data->after_vmas; + uint32 cnt = collect_child_vmas(pid, out, kMaxVmas); + uint64 hash_duration = current_time_ms() - hash_start_time; + if (is_snapshot) { + output_data->snapshot_vmas_count = cnt; + } else { + output_data->after_vmas_count = cnt; + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != -1) { + last_executed = current_time_ms(); + start += hash_duration; + } + continue; + } else +#endif +#endif + break; + } #if SYZ_EXECUTOR // Even though the test process executes exit at the end // and execution time of each syscall is bounded by syscall_timeout_ms (~50ms), diff --git a/executor/executor.cc b/executor/executor.cc index 3ba21e846933..b12d827da97b 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -22,6 +22,10 @@ #include #endif +#if GOOS_linux +#include +#endif + #include "defs.h" #include "pkg/flatrpc/flatrpc.h" @@ -94,6 +98,21 @@ static NORETURN void doexit(int status); static NORETURN void doexit_thread(int status); #endif +struct memory_region { + uint64 start; + uint64 end; + uint32 perms; + uint32 memory_hash; + char name[128]; + bool readable; +}; + +// Memory hash calculation for child processes +#if GOOS_linux +static uint32 hash_child_memory_region(int mem_fd, uint64 start, uint64 size); +static uint32 collect_child_vmas(int child_pid, memory_region* out, uint32 max); +#endif + // Print debug output that is visible when running syz-manager/execprog with -debug flag. // Debug output is supposed to be relatively high-level (syscalls executed, return values, timing, etc) // and is intended mostly for end users. If you need to debug lower-level details, use debug_verbose @@ -140,6 +159,7 @@ bool IsSet(T flags, T f) // prog execution with neither signal nor coverage. Likely 64kb will be enough in that case. const uint32 kMaxCalls = 64; +const uint32 kMaxVmas = 256; struct alignas(8) OutputData { std::atomic size; @@ -147,6 +167,10 @@ struct alignas(8) OutputData { std::atomic completed; std::atomic num_calls; std::atomic>> result_offset; + std::atomic snapshot_vmas_count; + std::atomic after_vmas_count; + memory_region snapshot_vmas[kMaxVmas]; + memory_region after_vmas[kMaxVmas]; struct { // Call index in the test program (they may be out-of-order is some syscalls block). int index; @@ -161,6 +185,8 @@ struct alignas(8) OutputData { completed.store(0, std::memory_order_relaxed); num_calls.store(0, std::memory_order_relaxed); result_offset.store(0, std::memory_order_relaxed); + snapshot_vmas_count.store(0, std::memory_order_relaxed); + after_vmas_count.store(0, std::memory_order_relaxed); } }; @@ -273,6 +299,7 @@ static bool flag_nic_vf; static bool flag_vhci_injection; static bool flag_wifi; static bool flag_delay_kcov_mmap; +static bool flag_memcmp; static bool flag_collect_cover; static bool flag_collect_signal; @@ -489,7 +516,7 @@ static bool coverage_filter(uint64 pc); static rpc::ComparisonRaw convert(const kcov_comparison_t& cmp); static flatbuffers::span finish_output(OutputData* output, int proc_id, uint64 req_id, uint32 num_calls, uint64 elapsed, uint64 freshness, uint32 status, bool hanged, - const std::vector* process_output); + const std::vector* process_output, bool memcmp_enabled); static void parse_execute(const execute_req& req); static void parse_handshake(const handshake_req& req); @@ -870,6 +897,7 @@ void parse_execute(const execute_req& req) flag_dedup_cover = req.exec_flags & (uint64)rpc::ExecFlag::DedupCover; flag_comparisons = req.exec_flags & (uint64)rpc::ExecFlag::CollectComps; flag_threaded = req.exec_flags & (uint64)rpc::ExecFlag::Threaded; + flag_memcmp = req.exec_flags & (uint64)rpc::ExecFlag::MemCmp; all_call_signal = req.all_call_signal; all_extra_signal = req.all_extra_signal; @@ -971,6 +999,12 @@ void execute_one() memset(&call_props, 0, sizeof(call_props)); read_input(&input_pos); // total number of calls + +#if GOOS_linux + if (flag_memcmp) + raise(SIGSTOP); // Capture "before" snapshot right before starting the syscall execution. +#endif + for (;;) { uint64 call_num = read_input(&input_pos); if (call_num == instr_eof) @@ -1155,6 +1189,11 @@ void execute_one() } } +#if GOOS_linux + if (flag_memcmp) + raise(SIGSTOP); // Capture "after" snapshot right after all execution finishes. +#endif + #if SYZ_HAVE_CLOSE_FDS close_fds(); #endif @@ -1451,8 +1490,101 @@ void write_extra_output() cover_reset(&extra_cov); } +#if GOOS_linux +static uint32 hash_child_memory_region(pid_t child_pid, uint64 start, uint64 size) +{ + uint8 buf[8192]; + uint64 off = 0; + uint32 h = 0; + uint32 word_index = 0; + + while (off < size) { + uint64 to_read = std::min(sizeof(buf), size - off); + + struct iovec local = {buf, (size_t)to_read}; + struct iovec remote = {reinterpret_cast(start + off), (size_t)to_read}; + + ssize_t n = process_vm_readv(child_pid, &local, 1, &remote, 1, 0); + if (n <= 0) + break; + + size_t i = 0; + for (; i + 4 <= static_cast(n); i += 4) { + uint32 v = 0; + memcpy(&v, buf + i, sizeof(v)); + h ^= hash(v + word_index++); + } + if (i < static_cast(n)) { + uint32 v = 0; + for (size_t j = 0; i + j < static_cast(n); j++) + v |= static_cast(buf[i + j]) << (8 * j); + h ^= hash(v + word_index++); + } + off += static_cast(n); + } + return h; +} + +static uint32 collect_child_vmas(pid_t child_pid, memory_region* out, uint32 max) +{ + char maps_path[64]; + snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", child_pid); + FILE* maps = fopen(maps_path, "r"); + if (!maps) + return 0; + + uint32 count = 0; + char line[1024]; + + while (count < max && fgets(line, sizeof(line), maps)) { + if (strstr(line, "(deleted)") || strstr(line, "kcov") || + strstr(line, "[vvar]") || strstr(line, "[vvar_vclock]")) + continue; + + uint64 start = 0, end = 0; + char perms[8] = {}; + char name_buf[128] = {}; + + if (sscanf(line, "%llx-%llx %7s %*s %*s %*s %127[^\n]", &start, &end, perms, name_buf) < 3) + continue; + + if (perms[0] != 'r') + continue; + + memory_region& r = out[count]; + r.start = start; + r.end = end; + r.readable = true; + + r.perms = 0; + if (perms[0] == 'r') + r.perms |= 1; + if (perms[1] == 'w') + r.perms |= 2; + if (perms[2] == 'x') + r.perms |= 4; + if (perms[3] == 'p') + r.perms |= 8; + if (perms[3] == 's') + r.perms |= 16; + + r.name[0] = '\0'; + if (name_buf[0] != '\0') { + strncpy(r.name, name_buf, sizeof(r.name) - 1); + r.name[sizeof(r.name) - 1] = '\0'; + } + + r.memory_hash = hash_child_memory_region(child_pid, r.start, r.end - r.start); + count++; + } + + fclose(maps); + return count; +} +#endif + flatbuffers::span finish_output(OutputData* output, int proc_id, uint64 req_id, uint32 num_calls, uint64 elapsed, - uint64 freshness, uint32 status, bool hanged, const std::vector* process_output) + uint64 freshness, uint32 status, bool hanged, const std::vector* process_output, bool memcmp_enabled) { // In snapshot mode the output size is fixed and output_size is always initialized, so use it. int out_size = flag_snapshot ? output_size : output->size.load(std::memory_order_relaxed) ? @@ -1478,7 +1610,34 @@ flatbuffers::span finish_output(OutputData* output, int proc_id, uint64 } calls[call.index] = call.offset; } - auto prog_info_off = rpc::CreateProgInfoRawDirect(fbb, &calls, &extra, 0, elapsed, freshness); + flatbuffers::Offset prog_info_off; + if (memcmp_enabled) { + uint32 snapshot_vmas_count = output->snapshot_vmas_count.load(std::memory_order_relaxed); + uint32 after_vmas_count = output->after_vmas_count.load(std::memory_order_relaxed); + if (snapshot_vmas_count > kMaxVmas) + snapshot_vmas_count = kMaxVmas; + if (after_vmas_count > kMaxVmas) + after_vmas_count = kMaxVmas; + std::vector> snapshot_vmas; + snapshot_vmas.reserve(snapshot_vmas_count); + for (uint32 i = 0; i < snapshot_vmas_count; i++) { + const auto& r = output->snapshot_vmas[i]; + auto name_off = fbb.CreateString(r.name); + snapshot_vmas.push_back(rpc::CreateVmaRaw(fbb, r.start, r.end, r.perms, r.memory_hash, name_off)); + } + std::vector> after_vmas; + after_vmas.reserve(after_vmas_count); + for (uint32 i = 0; i < after_vmas_count; i++) { + const auto& r = output->after_vmas[i]; + auto name_off = fbb.CreateString(r.name); + after_vmas.push_back(rpc::CreateVmaRaw(fbb, r.start, r.end, r.perms, r.memory_hash, name_off)); + } + prog_info_off = rpc::CreateProgInfoRawDirect(fbb, &calls, &extra, 0, + &snapshot_vmas, &after_vmas, elapsed, freshness); + } else { + prog_info_off = rpc::CreateProgInfoRawDirect(fbb, &calls, &extra, 0, + nullptr, nullptr, elapsed, freshness); + } flatbuffers::Offset error_off = 0; if (status == kFailStatus) error_off = fbb.CreateString("process failed"); @@ -1486,6 +1645,7 @@ flatbuffers::span finish_output(OutputData* output, int proc_id, uint64 auto output_off = output->result_offset.load(std::memory_order_relaxed); if (output_off.IsNull() && process_output) output_off = fbb.CreateVector(*process_output); + auto exec_off = rpc::CreateExecResultRaw(fbb, req_id, proc_id, output_off, hanged, error_off, prog_info_off); auto msg_off = rpc::CreateExecutorMessageRaw(fbb, rpc::ExecutorMessagesRaw::ExecResult, flatbuffers::Offset(exec_off.o)); diff --git a/executor/executor_runner.h b/executor/executor_runner.h index f26d446bfa65..3a72cb4b36c3 100644 --- a/executor/executor_runner.h +++ b/executor/executor_runner.h @@ -465,7 +465,8 @@ class Proc uint32 num_calls = 0; if (msg_->type == rpc::RequestType::Program) num_calls = read_input(&prog_data); - auto data = finish_output(resp_mem_, id_, msg_->id, num_calls, elapsed, freshness_++, status, hanged, output); + bool memcmp_enabled = (bool)(static_cast(msg_->exec_opts->exec_flags()) & static_cast(rpc::ExecFlag::MemCmp)); + auto data = finish_output(resp_mem_, id_, msg_->id, num_calls, elapsed, freshness_++, status, hanged, output, memcmp_enabled); conn_.Send(data.data(), data.size()); resp_mem_->Reset(); diff --git a/executor/snapshot.h b/executor/snapshot.h index 71c0b3940fa4..83d48d59dd5f 100644 --- a/executor/snapshot.h +++ b/executor/snapshot.h @@ -260,7 +260,7 @@ NORETURN static void SnapshotDone(bool failed) debug("SnapshotDone\n"); CoverAccessScope scope(nullptr); uint32 num_calls = output_data->num_calls.load(std::memory_order_relaxed); - auto data = finish_output(output_data, 0, 0, num_calls, 0, 0, failed ? kFailStatus : 0, false, nullptr); + auto data = finish_output(output_data, 0, 0, num_calls, 0, 0, failed ? kFailStatus : 0, false, nullptr, flag_memcmp); ivs.hdr->output_offset = data.data() - reinterpret_cast(ivs.hdr); ivs.hdr->output_size = data.size(); SnapshotSetState(failed ? rpc::SnapshotState::Failed : rpc::SnapshotState::Executed); diff --git a/pkg/flatrpc/flatrpc.fbs b/pkg/flatrpc/flatrpc.fbs index b8542bf9e215..d729f6f92761 100644 --- a/pkg/flatrpc/flatrpc.fbs +++ b/pkg/flatrpc/flatrpc.fbs @@ -162,6 +162,7 @@ enum ExecFlag : uint64 (bit_flags) { DedupCover, // deduplicate coverage in executor CollectComps, // collect KCOV comparisons Threaded, // use multiple threads to mitigate blocked syscalls + MemCmp, // collect memory comparison data } struct ExecOptsRaw { @@ -240,12 +241,22 @@ struct ComparisonRaw { is_const :bool; } +table VmaRaw { + start :uint64; + end :uint64; + perms :uint32; // bitmask: r=1, w=2, x=4, p(private)=8, s(shared)=16 + memory_hash :uint32; + name :string; +} + table ProgInfoRaw { calls :[CallInfoRaw]; // Contains signal and cover collected from background threads. // The raw version is exported by executor, and them merged into extra on the host. extra_raw :[CallInfoRaw]; extra :CallInfoRaw; + snapshot_vmas :[VmaRaw]; + after_vmas :[VmaRaw]; // Total execution time of the program in nanoseconds. elapsed :uint64; // Number of programs executed in the same process before this one. diff --git a/pkg/flatrpc/flatrpc.go b/pkg/flatrpc/flatrpc.go index 9f86b077adc8..96bb8ab4f251 100644 --- a/pkg/flatrpc/flatrpc.go +++ b/pkg/flatrpc/flatrpc.go @@ -403,6 +403,7 @@ const ( ExecFlagDedupCover ExecFlag = 4 ExecFlagCollectComps ExecFlag = 8 ExecFlagThreaded ExecFlag = 16 + ExecFlagMemCmp ExecFlag = 32 ) var EnumNamesExecFlag = map[ExecFlag]string{ @@ -411,6 +412,7 @@ var EnumNamesExecFlag = map[ExecFlag]string{ ExecFlagDedupCover: "DedupCover", ExecFlagCollectComps: "CollectComps", ExecFlagThreaded: "Threaded", + ExecFlagMemCmp: "MemCmp", } var EnumValuesExecFlag = map[string]ExecFlag{ @@ -419,6 +421,7 @@ var EnumValuesExecFlag = map[string]ExecFlag{ "DedupCover": ExecFlagDedupCover, "CollectComps": ExecFlagCollectComps, "Threaded": ExecFlagThreaded, + "MemCmp": ExecFlagMemCmp, } func (v ExecFlag) String() string { @@ -3024,12 +3027,169 @@ func CreateComparisonRaw(builder *flatbuffers.Builder, pc uint64, op1 uint64, op return builder.Offset() } +type VmaRawT struct { + Start uint64 `json:"start"` + End uint64 `json:"end"` + Perms uint32 `json:"perms"` + MemoryHash uint32 `json:"memory_hash"` + Name string `json:"name"` +} + +func (t *VmaRawT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + if t == nil { + return 0 + } + nameOffset := flatbuffers.UOffsetT(0) + if t.Name != "" { + nameOffset = builder.CreateString(t.Name) + } + VmaRawStart(builder) + VmaRawAddStart(builder, t.Start) + VmaRawAddEnd(builder, t.End) + VmaRawAddPerms(builder, t.Perms) + VmaRawAddMemoryHash(builder, t.MemoryHash) + VmaRawAddName(builder, nameOffset) + return VmaRawEnd(builder) +} + +func (rcv *VmaRaw) UnPackTo(t *VmaRawT) { + t.Start = rcv.Start() + t.End = rcv.End() + t.Perms = rcv.Perms() + t.MemoryHash = rcv.MemoryHash() + t.Name = string(rcv.Name()) +} + +func (rcv *VmaRaw) UnPack() *VmaRawT { + if rcv == nil { + return nil + } + t := &VmaRawT{} + rcv.UnPackTo(t) + return t +} + +type VmaRaw struct { + _tab flatbuffers.Table +} + +func GetRootAsVmaRaw(buf []byte, offset flatbuffers.UOffsetT) *VmaRaw { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &VmaRaw{} + x.Init(buf, n+offset) + return x +} + +func FinishVmaRawBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsVmaRaw(buf []byte, offset flatbuffers.UOffsetT) *VmaRaw { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &VmaRaw{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedVmaRawBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *VmaRaw) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *VmaRaw) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *VmaRaw) Start() uint64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.GetUint64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *VmaRaw) MutateStart(n uint64) bool { + return rcv._tab.MutateUint64Slot(4, n) +} + +func (rcv *VmaRaw) End() uint64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.GetUint64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *VmaRaw) MutateEnd(n uint64) bool { + return rcv._tab.MutateUint64Slot(6, n) +} + +func (rcv *VmaRaw) Perms() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *VmaRaw) MutatePerms(n uint32) bool { + return rcv._tab.MutateUint32Slot(8, n) +} + +func (rcv *VmaRaw) MemoryHash() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *VmaRaw) MutateMemoryHash(n uint32) bool { + return rcv._tab.MutateUint32Slot(10, n) +} + +func (rcv *VmaRaw) Name() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func VmaRawStart(builder *flatbuffers.Builder) { + builder.StartObject(5) +} +func VmaRawAddStart(builder *flatbuffers.Builder, start uint64) { + builder.PrependUint64Slot(0, start, 0) +} +func VmaRawAddEnd(builder *flatbuffers.Builder, end uint64) { + builder.PrependUint64Slot(1, end, 0) +} +func VmaRawAddPerms(builder *flatbuffers.Builder, perms uint32) { + builder.PrependUint32Slot(2, perms, 0) +} +func VmaRawAddMemoryHash(builder *flatbuffers.Builder, memoryHash uint32) { + builder.PrependUint32Slot(3, memoryHash, 0) +} +func VmaRawAddName(builder *flatbuffers.Builder, name flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(name), 0) +} +func VmaRawEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} + type ProgInfoRawT struct { - Calls []*CallInfoRawT `json:"calls"` - ExtraRaw []*CallInfoRawT `json:"extra_raw"` - Extra *CallInfoRawT `json:"extra"` - Elapsed uint64 `json:"elapsed"` - Freshness uint64 `json:"freshness"` + Calls []*CallInfoRawT `json:"calls"` + ExtraRaw []*CallInfoRawT `json:"extra_raw"` + Extra *CallInfoRawT `json:"extra"` + SnapshotVmas []*VmaRawT `json:"snapshot_vmas"` + AfterVmas []*VmaRawT `json:"after_vmas"` + Elapsed uint64 `json:"elapsed"` + Freshness uint64 `json:"freshness"` } func (t *ProgInfoRawT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT { @@ -3063,10 +3223,38 @@ func (t *ProgInfoRawT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT { extraRawOffset = builder.EndVector(extraRawLength) } extraOffset := t.Extra.Pack(builder) + snapshotVmasOffset := flatbuffers.UOffsetT(0) + if t.SnapshotVmas != nil { + snapshotVmasLength := len(t.SnapshotVmas) + snapshotVmasOffsets := make([]flatbuffers.UOffsetT, snapshotVmasLength) + for j := 0; j < snapshotVmasLength; j++ { + snapshotVmasOffsets[j] = t.SnapshotVmas[j].Pack(builder) + } + ProgInfoRawStartSnapshotVmasVector(builder, snapshotVmasLength) + for j := snapshotVmasLength - 1; j >= 0; j-- { + builder.PrependUOffsetT(snapshotVmasOffsets[j]) + } + snapshotVmasOffset = builder.EndVector(snapshotVmasLength) + } + afterVmasOffset := flatbuffers.UOffsetT(0) + if t.AfterVmas != nil { + afterVmasLength := len(t.AfterVmas) + afterVmasOffsets := make([]flatbuffers.UOffsetT, afterVmasLength) + for j := 0; j < afterVmasLength; j++ { + afterVmasOffsets[j] = t.AfterVmas[j].Pack(builder) + } + ProgInfoRawStartAfterVmasVector(builder, afterVmasLength) + for j := afterVmasLength - 1; j >= 0; j-- { + builder.PrependUOffsetT(afterVmasOffsets[j]) + } + afterVmasOffset = builder.EndVector(afterVmasLength) + } ProgInfoRawStart(builder) ProgInfoRawAddCalls(builder, callsOffset) ProgInfoRawAddExtraRaw(builder, extraRawOffset) ProgInfoRawAddExtra(builder, extraOffset) + ProgInfoRawAddSnapshotVmas(builder, snapshotVmasOffset) + ProgInfoRawAddAfterVmas(builder, afterVmasOffset) ProgInfoRawAddElapsed(builder, t.Elapsed) ProgInfoRawAddFreshness(builder, t.Freshness) return ProgInfoRawEnd(builder) @@ -3088,6 +3276,20 @@ func (rcv *ProgInfoRaw) UnPackTo(t *ProgInfoRawT) { t.ExtraRaw[j] = x.UnPack() } t.Extra = rcv.Extra(nil).UnPack() + snapshotVmasLength := rcv.SnapshotVmasLength() + t.SnapshotVmas = make([]*VmaRawT, snapshotVmasLength) + for j := 0; j < snapshotVmasLength; j++ { + x := VmaRaw{} + rcv.SnapshotVmas(&x, j) + t.SnapshotVmas[j] = x.UnPack() + } + afterVmasLength := rcv.AfterVmasLength() + t.AfterVmas = make([]*VmaRawT, afterVmasLength) + for j := 0; j < afterVmasLength; j++ { + x := VmaRaw{} + rcv.AfterVmas(&x, j) + t.AfterVmas[j] = x.UnPack() + } t.Elapsed = rcv.Elapsed() t.Freshness = rcv.Freshness() } @@ -3189,8 +3391,48 @@ func (rcv *ProgInfoRaw) Extra(obj *CallInfoRaw) *CallInfoRaw { return nil } -func (rcv *ProgInfoRaw) Elapsed() uint64 { +func (rcv *ProgInfoRaw) SnapshotVmas(obj *VmaRaw, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *ProgInfoRaw) SnapshotVmasLength() int { o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *ProgInfoRaw) AfterVmas(obj *VmaRaw, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *ProgInfoRaw) AfterVmasLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *ProgInfoRaw) Elapsed() uint64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) if o != 0 { return rcv._tab.GetUint64(o + rcv._tab.Pos) } @@ -3198,11 +3440,11 @@ func (rcv *ProgInfoRaw) Elapsed() uint64 { } func (rcv *ProgInfoRaw) MutateElapsed(n uint64) bool { - return rcv._tab.MutateUint64Slot(10, n) + return rcv._tab.MutateUint64Slot(14, n) } func (rcv *ProgInfoRaw) Freshness() uint64 { - o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + o := flatbuffers.UOffsetT(rcv._tab.Offset(16)) if o != 0 { return rcv._tab.GetUint64(o + rcv._tab.Pos) } @@ -3210,11 +3452,11 @@ func (rcv *ProgInfoRaw) Freshness() uint64 { } func (rcv *ProgInfoRaw) MutateFreshness(n uint64) bool { - return rcv._tab.MutateUint64Slot(12, n) + return rcv._tab.MutateUint64Slot(16, n) } func ProgInfoRawStart(builder *flatbuffers.Builder) { - builder.StartObject(5) + builder.StartObject(7) } func ProgInfoRawAddCalls(builder *flatbuffers.Builder, calls flatbuffers.UOffsetT) { builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(calls), 0) @@ -3231,11 +3473,23 @@ func ProgInfoRawStartExtraRawVector(builder *flatbuffers.Builder, numElems int) func ProgInfoRawAddExtra(builder *flatbuffers.Builder, extra flatbuffers.UOffsetT) { builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(extra), 0) } +func ProgInfoRawAddSnapshotVmas(builder *flatbuffers.Builder, snapshotVmas flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(snapshotVmas), 0) +} +func ProgInfoRawStartSnapshotVmasVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} +func ProgInfoRawAddAfterVmas(builder *flatbuffers.Builder, afterVmas flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(afterVmas), 0) +} +func ProgInfoRawStartAfterVmasVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} func ProgInfoRawAddElapsed(builder *flatbuffers.Builder, elapsed uint64) { - builder.PrependUint64Slot(3, elapsed, 0) + builder.PrependUint64Slot(5, elapsed, 0) } func ProgInfoRawAddFreshness(builder *flatbuffers.Builder, freshness uint64) { - builder.PrependUint64Slot(4, freshness, 0) + builder.PrependUint64Slot(6, freshness, 0) } func ProgInfoRawEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { return builder.EndObject() diff --git a/pkg/flatrpc/flatrpc.h b/pkg/flatrpc/flatrpc.h index 6807434e6119..ee04264050d4 100644 --- a/pkg/flatrpc/flatrpc.h +++ b/pkg/flatrpc/flatrpc.h @@ -83,6 +83,10 @@ struct CallInfoRawT; struct ComparisonRaw; +struct VmaRaw; +struct VmaRawBuilder; +struct VmaRawT; + struct ProgInfoRaw; struct ProgInfoRawBuilder; struct ProgInfoRawT; @@ -641,49 +645,34 @@ enum class ExecFlag : uint64_t { DedupCover = 4ULL, CollectComps = 8ULL, Threaded = 16ULL, + MemCmp = 32ULL, NONE = 0, - ANY = 31ULL + ANY = 63ULL }; FLATBUFFERS_DEFINE_BITMASK_OPERATORS(ExecFlag, uint64_t) -inline const ExecFlag (&EnumValuesExecFlag())[5] { +inline const ExecFlag (&EnumValuesExecFlag())[6] { static const ExecFlag values[] = { ExecFlag::CollectSignal, ExecFlag::CollectCover, ExecFlag::DedupCover, ExecFlag::CollectComps, - ExecFlag::Threaded + ExecFlag::Threaded, + ExecFlag::MemCmp }; return values; } -inline const char * const *EnumNamesExecFlag() { - static const char * const names[17] = { - "CollectSignal", - "CollectCover", - "", - "DedupCover", - "", - "", - "", - "CollectComps", - "", - "", - "", - "", - "", - "", - "", - "Threaded", - nullptr - }; - return names; -} - inline const char *EnumNameExecFlag(ExecFlag e) { - if (::flatbuffers::IsOutRange(e, ExecFlag::CollectSignal, ExecFlag::Threaded)) return ""; - const size_t index = static_cast(e) - static_cast(ExecFlag::CollectSignal); - return EnumNamesExecFlag()[index]; + switch (e) { + case ExecFlag::CollectSignal: return "CollectSignal"; + case ExecFlag::CollectCover: return "CollectCover"; + case ExecFlag::DedupCover: return "DedupCover"; + case ExecFlag::CollectComps: return "CollectComps"; + case ExecFlag::Threaded: return "Threaded"; + case ExecFlag::MemCmp: return "MemCmp"; + default: return ""; + } } enum class CallFlag : uint8_t { @@ -2338,11 +2327,127 @@ inline ::flatbuffers::Offset CreateCallInfoRawDirect( ::flatbuffers::Offset CreateCallInfoRaw(::flatbuffers::FlatBufferBuilder &_fbb, const CallInfoRawT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct VmaRawT : public ::flatbuffers::NativeTable { + typedef VmaRaw TableType; + uint64_t start = 0; + uint64_t end = 0; + uint32_t perms = 0; + uint32_t memory_hash = 0; + std::string name{}; +}; + +struct VmaRaw FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef VmaRawT NativeTableType; + typedef VmaRawBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_START = 4, + VT_END = 6, + VT_PERMS = 8, + VT_MEMORY_HASH = 10, + VT_NAME = 12 + }; + uint64_t start() const { + return GetField(VT_START, 0); + } + uint64_t end() const { + return GetField(VT_END, 0); + } + uint32_t perms() const { + return GetField(VT_PERMS, 0); + } + uint32_t memory_hash() const { + return GetField(VT_MEMORY_HASH, 0); + } + const ::flatbuffers::String *name() const { + return GetPointer(VT_NAME); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_START, 8) && + VerifyField(verifier, VT_END, 8) && + VerifyField(verifier, VT_PERMS, 4) && + VerifyField(verifier, VT_MEMORY_HASH, 4) && + VerifyOffset(verifier, VT_NAME) && + verifier.VerifyString(name()) && + verifier.EndTable(); + } + VmaRawT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(VmaRawT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const VmaRawT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct VmaRawBuilder { + typedef VmaRaw Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_start(uint64_t start) { + fbb_.AddElement(VmaRaw::VT_START, start, 0); + } + void add_end(uint64_t end) { + fbb_.AddElement(VmaRaw::VT_END, end, 0); + } + void add_perms(uint32_t perms) { + fbb_.AddElement(VmaRaw::VT_PERMS, perms, 0); + } + void add_memory_hash(uint32_t memory_hash) { + fbb_.AddElement(VmaRaw::VT_MEMORY_HASH, memory_hash, 0); + } + void add_name(::flatbuffers::Offset<::flatbuffers::String> name) { + fbb_.AddOffset(VmaRaw::VT_NAME, name); + } + explicit VmaRawBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateVmaRaw( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint64_t start = 0, + uint64_t end = 0, + uint32_t perms = 0, + uint32_t memory_hash = 0, + ::flatbuffers::Offset<::flatbuffers::String> name = 0) { + VmaRawBuilder builder_(_fbb); + builder_.add_end(end); + builder_.add_start(start); + builder_.add_name(name); + builder_.add_memory_hash(memory_hash); + builder_.add_perms(perms); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateVmaRawDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint64_t start = 0, + uint64_t end = 0, + uint32_t perms = 0, + uint32_t memory_hash = 0, + const char *name = nullptr) { + auto name__ = name ? _fbb.CreateString(name) : 0; + return rpc::CreateVmaRaw( + _fbb, + start, + end, + perms, + memory_hash, + name__); +} + +::flatbuffers::Offset CreateVmaRaw(::flatbuffers::FlatBufferBuilder &_fbb, const VmaRawT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct ProgInfoRawT : public ::flatbuffers::NativeTable { typedef ProgInfoRaw TableType; std::vector> calls{}; std::vector> extra_raw{}; std::unique_ptr extra{}; + std::vector> snapshot_vmas{}; + std::vector> after_vmas{}; uint64_t elapsed = 0; uint64_t freshness = 0; ProgInfoRawT() = default; @@ -2358,8 +2463,10 @@ struct ProgInfoRaw FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { VT_CALLS = 4, VT_EXTRA_RAW = 6, VT_EXTRA = 8, - VT_ELAPSED = 10, - VT_FRESHNESS = 12 + VT_SNAPSHOT_VMAS = 10, + VT_AFTER_VMAS = 12, + VT_ELAPSED = 14, + VT_FRESHNESS = 16 }; const ::flatbuffers::Vector<::flatbuffers::Offset> *calls() const { return GetPointer> *>(VT_CALLS); @@ -2370,6 +2477,12 @@ struct ProgInfoRaw FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const rpc::CallInfoRaw *extra() const { return GetPointer(VT_EXTRA); } + const ::flatbuffers::Vector<::flatbuffers::Offset> *snapshot_vmas() const { + return GetPointer> *>(VT_SNAPSHOT_VMAS); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *after_vmas() const { + return GetPointer> *>(VT_AFTER_VMAS); + } uint64_t elapsed() const { return GetField(VT_ELAPSED, 0); } @@ -2386,6 +2499,12 @@ struct ProgInfoRaw FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { verifier.VerifyVectorOfTables(extra_raw()) && VerifyOffset(verifier, VT_EXTRA) && verifier.VerifyTable(extra()) && + VerifyOffset(verifier, VT_SNAPSHOT_VMAS) && + verifier.VerifyVector(snapshot_vmas()) && + verifier.VerifyVectorOfTables(snapshot_vmas()) && + VerifyOffset(verifier, VT_AFTER_VMAS) && + verifier.VerifyVector(after_vmas()) && + verifier.VerifyVectorOfTables(after_vmas()) && VerifyField(verifier, VT_ELAPSED, 8) && VerifyField(verifier, VT_FRESHNESS, 8) && verifier.EndTable(); @@ -2408,6 +2527,12 @@ struct ProgInfoRawBuilder { void add_extra(::flatbuffers::Offset extra) { fbb_.AddOffset(ProgInfoRaw::VT_EXTRA, extra); } + void add_snapshot_vmas(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> snapshot_vmas) { + fbb_.AddOffset(ProgInfoRaw::VT_SNAPSHOT_VMAS, snapshot_vmas); + } + void add_after_vmas(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> after_vmas) { + fbb_.AddOffset(ProgInfoRaw::VT_AFTER_VMAS, after_vmas); + } void add_elapsed(uint64_t elapsed) { fbb_.AddElement(ProgInfoRaw::VT_ELAPSED, elapsed, 0); } @@ -2430,11 +2555,15 @@ inline ::flatbuffers::Offset CreateProgInfoRaw( ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> calls = 0, ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> extra_raw = 0, ::flatbuffers::Offset extra = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> snapshot_vmas = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> after_vmas = 0, uint64_t elapsed = 0, uint64_t freshness = 0) { ProgInfoRawBuilder builder_(_fbb); builder_.add_freshness(freshness); builder_.add_elapsed(elapsed); + builder_.add_after_vmas(after_vmas); + builder_.add_snapshot_vmas(snapshot_vmas); builder_.add_extra(extra); builder_.add_extra_raw(extra_raw); builder_.add_calls(calls); @@ -2446,15 +2575,21 @@ inline ::flatbuffers::Offset CreateProgInfoRawDirect( const std::vector<::flatbuffers::Offset> *calls = nullptr, const std::vector<::flatbuffers::Offset> *extra_raw = nullptr, ::flatbuffers::Offset extra = 0, + const std::vector<::flatbuffers::Offset> *snapshot_vmas = nullptr, + const std::vector<::flatbuffers::Offset> *after_vmas = nullptr, uint64_t elapsed = 0, uint64_t freshness = 0) { auto calls__ = calls ? _fbb.CreateVector<::flatbuffers::Offset>(*calls) : 0; auto extra_raw__ = extra_raw ? _fbb.CreateVector<::flatbuffers::Offset>(*extra_raw) : 0; + auto snapshot_vmas__ = snapshot_vmas ? _fbb.CreateVector<::flatbuffers::Offset>(*snapshot_vmas) : 0; + auto after_vmas__ = after_vmas ? _fbb.CreateVector<::flatbuffers::Offset>(*after_vmas) : 0; return rpc::CreateProgInfoRaw( _fbb, calls__, extra_raw__, extra, + snapshot_vmas__, + after_vmas__, elapsed, freshness); } @@ -3534,6 +3669,44 @@ inline ::flatbuffers::Offset CreateCallInfoRaw(::flatbuffers::FlatB _comps); } +inline VmaRawT *VmaRaw::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::unique_ptr(new VmaRawT()); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void VmaRaw::UnPackTo(VmaRawT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = start(); _o->start = _e; } + { auto _e = end(); _o->end = _e; } + { auto _e = perms(); _o->perms = _e; } + { auto _e = memory_hash(); _o->memory_hash = _e; } + { auto _e = name(); if (_e) _o->name = _e->str(); } +} + +inline ::flatbuffers::Offset VmaRaw::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const VmaRawT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return CreateVmaRaw(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset CreateVmaRaw(::flatbuffers::FlatBufferBuilder &_fbb, const VmaRawT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const VmaRawT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _start = _o->start; + auto _end = _o->end; + auto _perms = _o->perms; + auto _memory_hash = _o->memory_hash; + auto _name = _o->name.empty() ? 0 : _fbb.CreateString(_o->name); + return rpc::CreateVmaRaw( + _fbb, + _start, + _end, + _perms, + _memory_hash, + _name); +} + inline ProgInfoRawT::ProgInfoRawT(const ProgInfoRawT &o) : extra((o.extra) ? new rpc::CallInfoRawT(*o.extra) : nullptr), elapsed(o.elapsed), @@ -3542,12 +3715,18 @@ inline ProgInfoRawT::ProgInfoRawT(const ProgInfoRawT &o) for (const auto &calls_ : o.calls) { calls.emplace_back((calls_) ? new rpc::CallInfoRawT(*calls_) : nullptr); } extra_raw.reserve(o.extra_raw.size()); for (const auto &extra_raw_ : o.extra_raw) { extra_raw.emplace_back((extra_raw_) ? new rpc::CallInfoRawT(*extra_raw_) : nullptr); } + snapshot_vmas.reserve(o.snapshot_vmas.size()); + for (const auto &snapshot_vmas_ : o.snapshot_vmas) { snapshot_vmas.emplace_back((snapshot_vmas_) ? new rpc::VmaRawT(*snapshot_vmas_) : nullptr); } + after_vmas.reserve(o.after_vmas.size()); + for (const auto &after_vmas_ : o.after_vmas) { after_vmas.emplace_back((after_vmas_) ? new rpc::VmaRawT(*after_vmas_) : nullptr); } } inline ProgInfoRawT &ProgInfoRawT::operator=(ProgInfoRawT o) FLATBUFFERS_NOEXCEPT { std::swap(calls, o.calls); std::swap(extra_raw, o.extra_raw); std::swap(extra, o.extra); + std::swap(snapshot_vmas, o.snapshot_vmas); + std::swap(after_vmas, o.after_vmas); std::swap(elapsed, o.elapsed); std::swap(freshness, o.freshness); return *this; @@ -3565,6 +3744,8 @@ inline void ProgInfoRaw::UnPackTo(ProgInfoRawT *_o, const ::flatbuffers::resolve { auto _e = calls(); if (_e) { _o->calls.resize(_e->size()); for (::flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { if(_o->calls[_i]) { _e->Get(_i)->UnPackTo(_o->calls[_i].get(), _resolver); } else { _o->calls[_i] = std::unique_ptr(_e->Get(_i)->UnPack(_resolver)); }; } } else { _o->calls.resize(0); } } { auto _e = extra_raw(); if (_e) { _o->extra_raw.resize(_e->size()); for (::flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { if(_o->extra_raw[_i]) { _e->Get(_i)->UnPackTo(_o->extra_raw[_i].get(), _resolver); } else { _o->extra_raw[_i] = std::unique_ptr(_e->Get(_i)->UnPack(_resolver)); }; } } else { _o->extra_raw.resize(0); } } { auto _e = extra(); if (_e) { if(_o->extra) { _e->UnPackTo(_o->extra.get(), _resolver); } else { _o->extra = std::unique_ptr(_e->UnPack(_resolver)); } } else if (_o->extra) { _o->extra.reset(); } } + { auto _e = snapshot_vmas(); if (_e) { _o->snapshot_vmas.resize(_e->size()); for (::flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { if(_o->snapshot_vmas[_i]) { _e->Get(_i)->UnPackTo(_o->snapshot_vmas[_i].get(), _resolver); } else { _o->snapshot_vmas[_i] = std::unique_ptr(_e->Get(_i)->UnPack(_resolver)); }; } } else { _o->snapshot_vmas.resize(0); } } + { auto _e = after_vmas(); if (_e) { _o->after_vmas.resize(_e->size()); for (::flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { if(_o->after_vmas[_i]) { _e->Get(_i)->UnPackTo(_o->after_vmas[_i].get(), _resolver); } else { _o->after_vmas[_i] = std::unique_ptr(_e->Get(_i)->UnPack(_resolver)); }; } } else { _o->after_vmas.resize(0); } } { auto _e = elapsed(); _o->elapsed = _e; } { auto _e = freshness(); _o->freshness = _e; } } @@ -3580,6 +3761,8 @@ inline ::flatbuffers::Offset CreateProgInfoRaw(::flatbuffers::FlatB auto _calls = _o->calls.size() ? _fbb.CreateVector<::flatbuffers::Offset> (_o->calls.size(), [](size_t i, _VectorArgs *__va) { return CreateCallInfoRaw(*__va->__fbb, __va->__o->calls[i].get(), __va->__rehasher); }, &_va ) : 0; auto _extra_raw = _o->extra_raw.size() ? _fbb.CreateVector<::flatbuffers::Offset> (_o->extra_raw.size(), [](size_t i, _VectorArgs *__va) { return CreateCallInfoRaw(*__va->__fbb, __va->__o->extra_raw[i].get(), __va->__rehasher); }, &_va ) : 0; auto _extra = _o->extra ? CreateCallInfoRaw(_fbb, _o->extra.get(), _rehasher) : 0; + auto _snapshot_vmas = _o->snapshot_vmas.size() ? _fbb.CreateVector<::flatbuffers::Offset> (_o->snapshot_vmas.size(), [](size_t i, _VectorArgs *__va) { return CreateVmaRaw(*__va->__fbb, __va->__o->snapshot_vmas[i].get(), __va->__rehasher); }, &_va ) : 0; + auto _after_vmas = _o->after_vmas.size() ? _fbb.CreateVector<::flatbuffers::Offset> (_o->after_vmas.size(), [](size_t i, _VectorArgs *__va) { return CreateVmaRaw(*__va->__fbb, __va->__o->after_vmas[i].get(), __va->__rehasher); }, &_va ) : 0; auto _elapsed = _o->elapsed; auto _freshness = _o->freshness; return rpc::CreateProgInfoRaw( @@ -3587,6 +3770,8 @@ inline ::flatbuffers::Offset CreateProgInfoRaw(::flatbuffers::FlatB _calls, _extra_raw, _extra, + _snapshot_vmas, + _after_vmas, _elapsed, _freshness); } diff --git a/syz-verifier/memcmp.go b/syz-verifier/memcmp.go new file mode 100644 index 000000000000..9871c8308b91 --- /dev/null +++ b/syz-verifier/memcmp.go @@ -0,0 +1,203 @@ +// Copyright 2026 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. + +package main + +import ( + "fmt" + "strings" + + "github.com/google/syzkaller/pkg/flatrpc" + "github.com/google/syzkaller/pkg/hash" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/prog" +) + +type VmaKind uint32 + +// Only VMAs we are not ignoring (those are already discarded by the executor, +// as there is no point in collecting them) +const ( + VmaKindUnknown VmaKind = iota + VmaKindStack + VmaKindHeap + VmaKindExecutorBinary + VmaKindAnonymousPage + VmaKindVdso + VmaKindSyzkallerMmap +) + +func (k VmaKind) String() string { + switch k { + case VmaKindStack: + return "Stack" + case VmaKindHeap: + return "Heap" + case VmaKindExecutorBinary: + return "ExecutorBinary" + case VmaKindAnonymousPage: + return "AnonymousPage" + case VmaKindVdso: + return "Vdso" + case VmaKindSyzkallerMmap: + return "SyzkallerMmap" + default: + return "Unknown" + } +} + +func classifyVMA(name string, start uint64) VmaKind { + if name != "" { + switch name { + case "[stack]": + return VmaKindStack + case "[heap]": + return VmaKindHeap + case "[vdso]": + return VmaKindVdso + } + if strings.Contains(name, "/syz-executor") { + return VmaKindExecutorBinary + } + return VmaKindUnknown + } + if (start & 0xFFF000000000) == 0x200000000000 { + return VmaKindSyzkallerMmap + } + return VmaKindAnonymousPage +} + +type ComparisonStrategy int + +const ( + StrategyIgnore ComparisonStrategy = iota + StrategySelfCheck + StrategyCrossCompare +) + +func getComparisonStrategy(kind VmaKind) ComparisonStrategy { + switch kind { + // Disabled for MVP - easily re-enablable later. + // case VmaKindStack, VmaKindHeap, VmaKindVdso, VmaKindExecutorBinary: + // + // return StrategySelfCheck + case VmaKindSyzkallerMmap: // MVP currently focuses only on syzkaller scratchpad (0x2000...) + return StrategyCrossCompare + // case VmaKindAnonymousPage: + // + // return StrategyCrossCompare + default: + return StrategyIgnore + } +} + +type VmaState struct { + Hash uint32 + Kind VmaKind +} + +func mapVMAs(rawVMAs []*flatrpc.VmaRawT) map[uint64]VmaState { + vmaMap := make(map[uint64]VmaState, len(rawVMAs)) + for _, raw := range rawVMAs { + vmaMap[raw.Start] = VmaState{ + Hash: raw.MemoryHash, + Kind: classifyVMA(raw.Name, raw.Start), + } + } + return vmaMap +} + +func (vrf *Verifier) verifyMemoryMismatches(info0, info1 *flatrpc.ProgInfoRawT, kernelName0, kernelName1 string) (bool, string) { + snap0 := mapVMAs(info0.SnapshotVmas) + snap1 := mapVMAs(info1.SnapshotVmas) + after0 := mapVMAs(info0.AfterVmas) + after1 := mapVMAs(info1.AfterVmas) + + mismatchFound := false + var report strings.Builder + + for start, vma0 := range snap0 { + strategy := getComparisonStrategy(vma0.Kind) + + if strategy == StrategyIgnore { + continue + } + + if strategy == StrategySelfCheck { + if a0, exists := after0[start]; exists { + if vma0.Hash != a0.Hash { + mismatchFound = true + report.WriteString(fmt.Sprintf("Self-Check Mismatch (%s): %s mutated during execution.\n", kernelName0, vma0.Kind)) + } + } + if vma1, exists := snap1[start]; exists { + if a1, existsAfter := after1[start]; existsAfter { + if vma1.Hash != a1.Hash { + mismatchFound = true + report.WriteString(fmt.Sprintf("Self-Check Mismatch (%s): %s mutated during execution.\n", kernelName1, vma1.Kind)) + } + } + } + continue + } + + if strategy == StrategyCrossCompare { + a0, exists0 := after0[start] + a1, exists1 := after1[start] + + if exists0 && exists1 { + if a0.Hash != a1.Hash { + mismatchFound = true + report.WriteString(fmt.Sprintf("Cross-Kernel Mismatch: %s at 0x%x diverged.\n", vma0.Kind, start)) + report.WriteString(fmt.Sprintf("\t%s Hash: 0x%x\n", kernelName0, a0.Hash)) + report.WriteString(fmt.Sprintf("\t%s Hash: 0x%x\n", kernelName1, a1.Hash)) + } + } + } + } + + return mismatchFound, report.String() +} + +// logMemoryMismatchSequence is a temporary, isolated logger to avoid merge conflicts. +func (vrf *Verifier) logMemoryMismatchSequence(p *prog.Prog, info0, info1 *flatrpc.ProgInfoRawT, name0, name1 string, details string) { + var reportBody strings.Builder + writeLine := func(format string, args ...any) { + line := fmt.Sprintf(format, args...) + log.Logf(0, "%s", line) + reportBody.WriteString(line) + reportBody.WriteByte('\n') + } + + writeLine("") + writeLine("========== VMA MEMORY MISMATCH DETECTED ==========") + writeLine("Between: Kernel 0 (%s) and Kernel 1 (%s)", name0, name1) + writeLine("") + for _, devLine := range strings.Split(strings.TrimSpace(details), "\n") { + writeLine("%s", devLine) + } + writeLine("") + writeLine("Complete Program Sequence:") + writeLine("-------------------------------------------") + + progLines := strings.Split(strings.TrimSpace(string(p.Serialize())), "\n") + for callIdx, call := range p.Calls { + callStr := call.Meta.CallName + "(...)" + if callIdx < len(progLines) { + callStr = progLines[callIdx] + } + writeLine(" [%d] %s", callIdx, callStr) + + // For memory mismatches, we only print flags to see if the call succeeded/failed generally. + if info0 != nil && info1 != nil && callIdx < len(info0.Calls) && callIdx < len(info1.Calls) { + writeLine(" ┌─ %s: flags=0x%x", name0, uint8(info0.Calls[callIdx].Flags)) + writeLine(" └─ %s: flags=0x%x", name1, uint8(info1.Calls[callIdx].Flags)) + } + writeLine("") + } + writeLine("-------------------------------------------") + + progHash := hash.String(p.Serialize()) + title := fmt.Sprintf("syz-verifier memory mismatch: %s vs %s (%s)", name0, name1, progHash) + vrf.saveMismatchReport(title, reportBody.String()) +} diff --git a/syz-verifier/verifier.go b/syz-verifier/verifier.go index d57a9f6c847c..cab08e7c407c 100644 --- a/syz-verifier/verifier.go +++ b/syz-verifier/verifier.go @@ -393,6 +393,11 @@ func (vrf *Verifier) compareResults(prog *prog.Prog, responses []*queue.Result) vrf.kernels[0].cfg.Name, vrf.kernels[i].cfg.Name, firstMismatchCall) vrf.saveMismatchReport(title, reportBody.String()) } + + hasMemMismatch, memDetails := vrf.verifyMemoryMismatches(res0.Info, res.Info, vrf.kernels[0].cfg.Name, vrf.kernels[i].cfg.Name) + if hasMemMismatch { + vrf.logMemoryMismatchSequence(prog, res0.Info, res.Info, vrf.kernels[0].cfg.Name, vrf.kernels[i].cfg.Name, memDetails) + } } } @@ -492,6 +497,7 @@ func (kernel *Kernel) MachineChecked(features flatrpc.Feature, kernel.features <- features log.Logf(0, "kernel %s: configuring source", kernel.cfg.Name) opts := fuzzer.DefaultExecOpts(kernel.cfg, features, kernel.debug) + opts.ExecFlags |= flatrpc.ExecFlagMemCmp kernel.source = queue.DefaultOpts(kernel.source, opts) kernel.mu.Lock() kernel.queueConfigured = true