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