From 58caf0d3d56449a079f2a981e2cfa1c98e69a3d1 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 13 May 2026 14:48:00 -0700 Subject: [PATCH] upgrade eval space reads with staleness/bad/change info, to expand process memory cache reading abilities to all evaluation spaces; unmapped address visualization (both memory view & single line eval) --- src/dbg_engine/dbg_engine_ctrl.c | 2 +- src/dbg_engine/dbg_engine_ctrl.h | 2 +- src/eval/eval_core.c | 2 +- src/eval/eval_core.h | 23 +++++- src/eval/eval_interpret.c | 82 +++++++++++++++---- src/eval/eval_interpret.h | 2 +- src/eval/eval_ir.c | 2 +- src/eval/eval_types.c | 4 +- .../eval_visualization_core.c | 17 +++- src/raddbg/raddbg_core.c | 18 +++- src/raddbg/raddbg_core.h | 2 +- src/raddbg/raddbg_main.c | 5 +- src/raddbg/raddbg_views.c | 15 +--- 13 files changed, 130 insertions(+), 46 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index b69a33b4..d5b75eb2 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -4512,7 +4512,7 @@ d_ctrl_eval_space_gen(E_Space space) } internal B32 -d_ctrl_eval_space_read(E_Space space, void *out, Rng1U64 range) +d_ctrl_eval_space_read(E_Space space, void *out, E_SpaceRangeInfo *out_range_info, Rng1U64 range) { B32 result = 0; switch(space.kind) diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index 0db01810..02b83e8a 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -554,7 +554,7 @@ internal DMN_Event *d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctr //- rjf: eval helpers internal U64 d_ctrl_eval_space_gen(E_Space space); -internal B32 d_ctrl_eval_space_read(E_Space space, void *out, Rng1U64 vaddr_range); +internal B32 d_ctrl_eval_space_read(E_Space space, void *out, E_SpaceRangeInfo *out_range_info, Rng1U64 vaddr_range); //- rjf: control thread eval scopes internal D_EvalScope *d_ctrl_thread__eval_scope_begin(Arena *arena, D_BreakpointList *user_bps, D_Entity *thread); diff --git a/src/eval/eval_core.c b/src/eval/eval_core.c index 694ddb27..6adc6193 100644 --- a/src/eval/eval_core.c +++ b/src/eval/eval_core.c @@ -1164,7 +1164,7 @@ e_value_eval_from_eval(E_Eval eval) MemoryZeroStruct(&eval.value); if(!e_type_key_match(type_key, e_type_key_zero()) && type_byte_size <= sizeof(E_Value) && - e_space_read(eval.space, &eval.value, value_vaddr_range)) + e_space_read(eval.space, &eval.value, 0, value_vaddr_range)) { eval.irtree.mode = E_Mode_Value; diff --git a/src/eval/eval_core.h b/src/eval/eval_core.h index a870fb59..82128bc2 100644 --- a/src/eval/eval_core.h +++ b/src/eval/eval_core.h @@ -757,8 +757,25 @@ struct E_AutoHookParams //////////////////////////////// //~ rjf: Evaluation Context +typedef U32 E_SpaceRangeFlags; +enum +{ + E_SpaceRangeFlag_AnyByteBad = (1<<0), + E_SpaceRangeFlag_AnyByteChanged = (1<<1), + E_SpaceRangeFlag_Stale = (1<<2), +}; + +typedef struct E_SpaceRangeInfo E_SpaceRangeInfo; +struct E_SpaceRangeInfo +{ + U64 *byte_bad_flags; + U64 *byte_changed_flags; + E_SpaceRangeFlags flags; +}; + typedef U64 E_SpaceGenFunction(E_Space space); -typedef B32 E_SpaceRWFunction(E_Space space, void *out, Rng1U64 offset_range); +typedef B32 E_SpaceReadFunction(E_Space space, void *out, E_SpaceRangeInfo *out_range_info, Rng1U64 offset_range); +typedef B32 E_SpaceWriteFunction(E_Space space, void *out, Rng1U64 offset_range); //- rjf: base context @@ -785,8 +802,8 @@ struct E_BaseCtx // rjf: space hooks E_SpaceGenFunction *space_gen; - E_SpaceRWFunction *space_read; - E_SpaceRWFunction *space_write; + E_SpaceReadFunction *space_read; + E_SpaceWriteFunction *space_write; }; //- rjf: ir generation context diff --git a/src/eval/eval_interpret.c b/src/eval/eval_interpret.c index 4dd1362e..5e64b380 100644 --- a/src/eval/eval_interpret.c +++ b/src/eval/eval_interpret.c @@ -46,7 +46,7 @@ e_space_gen(E_Space space) } internal B32 -e_space_read(E_Space space, void *out, Rng1U64 range) +e_space_read(E_Space space, void *out, E_SpaceRangeInfo *out_range_info, Rng1U64 range) { ProfBeginFunction(); B32 result = 0; @@ -77,6 +77,8 @@ e_space_read(E_Space space, void *out, Rng1U64 range) //- rjf: file reads case E_SpaceKind_File: { + Access *access = access_open(); + // rjf: unpack space/path U64 file_path_string_id = space.u64_0; String8 file_path = e_string_from_id(file_path_string_id); @@ -88,22 +90,70 @@ e_space_read(E_Space space, void *out, Rng1U64 range) containing_range.max += chunk_size-1; containing_range.max -= containing_range.max%chunk_size; - // rjf: map to hash + // rjf: map to hashes C_Key key = fs_key_from_path_range(file_path, containing_range, 0); - U128 hash = c_hash_from_key(key, 0); - - // rjf: look up from hash store - Access *access = access_open(); + U128 hash = {0}; + U128 prev_hash = {0}; + U64 desired_hash_count = 1; + if(out_range_info != 0 && out_range_info->byte_changed_flags != 0) { - String8 data = c_data_from_hash(access, hash); - Rng1U64 legal_range = r1u64(containing_range.min, containing_range.min + data.size); - Rng1U64 read_range = intersect_1u64(range, legal_range); - if(read_range.min < read_range.max) + desired_hash_count = 2; + } + { + U64 hashes_count = 0; + U128 hashes[2] = {0}; + for(U64 rewind_idx = 0; rewind_idx < C_KEY_HASH_HISTORY_COUNT && hashes_count < ArrayCount(hashes) && hashes_count < desired_hash_count; rewind_idx += 1) { - result = 1; - MemoryCopy(out, data.str + read_range.min - containing_range.min, dim_1u64(read_range)); + U128 h = c_hash_from_key(key, rewind_idx); + if(!u128_match(u128_zero(), h)) + { + hashes[hashes_count] = h; + hashes_count += 1; + } + } + hash = hashes[0]; + prev_hash = hashes[1]; + } + + // rjf: unpack hashes + String8 data = c_data_from_hash(access, hash); + String8 prev_data = c_data_from_hash(access, prev_hash); + + // rjf: unpack read range + Rng1U64 legal_range = r1u64(containing_range.min, containing_range.min + data.size); + Rng1U64 read_range = intersect_1u64(range, legal_range); + + // rjf: fill out byte bad flags + if(out_range_info != 0 && out_range_info->byte_bad_flags != 0) + { + // TODO(rjf): need to know whole space range here + } + + // rjf: fill out byte changed flags + if(out_range_info != 0 && out_range_info->byte_changed_flags != 0) + { + U64 num_bytes_read = dim_1u64(read_range); + if(data.size >= num_bytes_read && data.size == prev_data.size) + { + U64 byte_base_idx = read_range.min - containing_range.min; + for(U64 byte_idx = 0; byte_idx < num_bytes_read; byte_idx += 1) + { + if(data.str[byte_base_idx + byte_idx] != prev_data.str[byte_base_idx + byte_idx]) + { + out_range_info->byte_changed_flags[byte_idx/64] |= (1ull<<(byte_idx%64)); + out_range_info->flags |= E_SpaceRangeFlag_AnyByteChanged; + } + } } } + + // rjf: fill output data from data + if(read_range.min < read_range.max) + { + result = 1; + MemoryCopy(out, data.str + read_range.min - containing_range.min, dim_1u64(read_range)); + } + access_close(access); }break; @@ -128,7 +178,7 @@ e_space_read(E_Space space, void *out, Rng1U64 range) default: if(e_base_ctx->space_read != 0) { - result = e_base_ctx->space_read(space, out, range); + result = e_base_ctx->space_read(space, out, out_range_info, range); }break; } } @@ -277,7 +327,7 @@ e_interpret(String8 bytecode) { U64 addr = svals[0].u64; U64 size = imm.u64; - B32 good_read = e_space_read(selected_space, &nval, r1u64(addr, addr+size)); + B32 good_read = e_space_read(selected_space, &nval, 0, r1u64(addr, addr+size)); if(!good_read) { result.code = E_InterpretationCode_BadMemRead; @@ -299,7 +349,7 @@ e_interpret(String8 bytecode) Rng1U16 rng = arch_info->reg_code_rng_table[base_reg_code]; U64 off = (U64)rng.min + byte_off; U64 size = (U64)byte_size; - good_read = e_space_read(e_interpret_ctx->reg_space, &nval, r1u64(off, off+size)); + good_read = e_space_read(e_interpret_ctx->reg_space, &nval, 0, r1u64(off, off+size)); } if(!good_read) { @@ -312,7 +362,7 @@ e_interpret(String8 bytecode) { U64 off = svals[0].u64; U64 size = bit_size_from_arch(e_interpret_ctx->reg_arch)/8; - B32 good_read = e_space_read(e_interpret_ctx->reg_space, &nval, r1u64(off, off+size)); + B32 good_read = e_space_read(e_interpret_ctx->reg_space, &nval, 0, r1u64(off, off+size)); if(!good_read) { result.code = E_InterpretationCode_BadRegRead; diff --git a/src/eval/eval_interpret.h b/src/eval/eval_interpret.h index 83a82c59..92fb5a6c 100644 --- a/src/eval/eval_interpret.h +++ b/src/eval/eval_interpret.h @@ -34,7 +34,7 @@ internal void e_select_interpret_ctx(E_InterpretCtx *ctx, RDI_Parsed *primary_rd //~ rjf: Space Reading Helpers internal U64 e_space_gen(E_Space space); -internal B32 e_space_read(E_Space space, void *out, Rng1U64 range); +internal B32 e_space_read(E_Space space, void *out, E_SpaceRangeInfo *out_range_info, Rng1U64 range); internal B32 e_space_write(E_Space space, void *in, Rng1U64 range); //////////////////////////////// diff --git a/src/eval/eval_ir.c b/src/eval/eval_ir.c index 919905ba..33f26ec6 100644 --- a/src/eval/eval_ir.c +++ b/src/eval/eval_ir.c @@ -2386,7 +2386,7 @@ e_push_irtree_and_type_from_expr(Arena *arena, E_IRTreeAndType *root_parent, E_I U64 class_base_vaddr = interpret.value.u64; U64 vtable_vaddr = 0; U64 addr_size = e_type_byte_size_from_key(type_key); - if(e_space_read(interpret.space, &vtable_vaddr, r1u64(class_base_vaddr, class_base_vaddr+addr_size))) + if(e_space_read(interpret.space, &vtable_vaddr, 0, r1u64(class_base_vaddr, class_base_vaddr+addr_size))) { Arch arch = e_base_ctx->primary_module->arch; U32 dbg_info_num = 0; diff --git a/src/eval/eval_types.c b/src/eval/eval_types.c index 451ed179..682bef82 100644 --- a/src/eval/eval_types.c +++ b/src/eval/eval_types.c @@ -2587,7 +2587,7 @@ e_list_gather_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U U64 base_off = 0; U64 member_element_off = 0; U64 member_size = 0; - E_SpaceRWFunction *space_read = 0; + E_SpaceReadFunction *space_read = 0; { U64 key_read_off = 0; key_read_off += str8_deserial_read_struct(key, key_read_off, &process); @@ -2812,7 +2812,7 @@ E_TYPE_IREXT_FUNCTION_DEF(list) U64 base_off; U64 member_element_off; U64 member_size; - E_SpaceRWFunction *space_read; + E_SpaceReadFunction *space_read; } key_data = { diff --git a/src/eval_visualization/eval_visualization_core.c b/src/eval_visualization/eval_visualization_core.c index f41df3a5..93125efd 100644 --- a/src/eval_visualization/eval_visualization_core.c +++ b/src/eval_visualization/eval_visualization_core.c @@ -1880,7 +1880,7 @@ ev_string_iter_next(Arena *arena, EV_StringIter *it, String8 *out_string) U64 string_memory_addr = ptr_data->value_eval.value.u64; for(U64 try_size = string_buffer_size; try_size >= 16; try_size /= 2) { - B32 read_good = e_space_read(eval.space, string_buffer, r1u64(string_memory_addr, string_memory_addr+try_size)); + B32 read_good = e_space_read(eval.space, string_buffer, 0, r1u64(string_memory_addr, string_memory_addr+try_size)); if(read_good) { break; @@ -2107,6 +2107,21 @@ ev_string_iter_next(Arena *arena, EV_StringIter *it, String8 *out_string) { Temp scratch = scratch_begin(&arena, 1); String8 ptr_value_string = str8_from_u64(scratch.arena, ptr_data->value_eval.value.u64, 16, 0, 0); + if(params->flags & EV_StringFlag_ReadOnlyDisplayRules) + { + U8 byte = 0; + B32 addr_is_good = 0; + U64 byte_bad_flags = 0; + E_SpaceRangeInfo range_info = {.byte_bad_flags = &byte_bad_flags}; + if(e_space_read(ptr_data->value_eval.space, &byte, &range_info, r1u64(ptr_data->value_eval.value.u64, ptr_data->value_eval.value.u64+1))) + { + addr_is_good = !(byte_bad_flags & 1); + } + if(!addr_is_good) + { + ptr_value_string = str8f(scratch.arena, "%S (unmapped)", ptr_value_string); + } + } // // NOTE(rjf): currently, we are not using the string-generation radix parameter when // generating a pointer value - it is weird to want to change pointer value visualization diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 1a62a527..7b86947a 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -736,7 +736,7 @@ rd_eval_space_gen(E_Space space) } internal B32 -rd_eval_space_read(E_Space space, void *out, Rng1U64 range) +rd_eval_space_read(E_Space space, void *out, E_SpaceRangeInfo *out_range_info, Rng1U64 range) { Temp scratch = scratch_begin(0, 0); B32 result = 0; @@ -744,7 +744,7 @@ rd_eval_space_read(E_Space space, void *out, Rng1U64 range) { default: { - result = d_ctrl_eval_space_read(space, out, range); + result = d_ctrl_eval_space_read(space, out, out_range_info, range); }break; //- rjf: interior control entity reads (inside process address space or thread register block) @@ -762,6 +762,20 @@ rd_eval_space_read(E_Space space, void *out, Rng1U64 range) { result = 1; MemoryCopy(out, data.str, data.size); + if(out_range_info != 0 && out_range_info->byte_bad_flags) + { + MemoryCopy(out_range_info->byte_bad_flags, slice.byte_bad_flags, sizeof(U64) * (dim_1u64(range)+63)/64); + } + if(out_range_info != 0 && out_range_info->byte_changed_flags) + { + MemoryCopy(out_range_info->byte_changed_flags, slice.byte_changed_flags, sizeof(U64) * ((dim_1u64(range)+63)/64)); + } + if(out_range_info != 0) + { + out_range_info->flags |= E_SpaceRangeFlag_Stale*!!slice.stale; + out_range_info->flags |= E_SpaceRangeFlag_AnyByteChanged*!!slice.any_byte_changed; + out_range_info->flags |= E_SpaceRangeFlag_AnyByteBad*!!slice.any_byte_bad; + } } }break; case D_EntityKind_Thread: diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index f554f260..498300dc 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -658,7 +658,7 @@ internal String8 rd_cmd_name_from_eval(E_Eval eval); //- rjf: eval space reads/writes internal U64 rd_eval_space_gen(E_Space space); -internal B32 rd_eval_space_read(E_Space space, void *out, Rng1U64 range); +internal B32 rd_eval_space_read(E_Space space, void *out, E_SpaceRangeInfo *out_range_info, Rng1U64 range); internal B32 rd_eval_space_write(E_Space space, void *in, Rng1U64 range); //- rjf: asynchronous streamed reads -> hashes from spaces diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 5ccb838f..35d1afc4 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -4,9 +4,6 @@ //////////////////////////////// //~ rjf: post-0.9.26 TODO notes // -// [ ] single-line bad address visualization -// [ ] memory view show unmapped memory as non-zeros -// // [ ] "autos" collection, which can be evaluated // [ ] show "autos" inline in source code near thread? // [ ] memory_size(...) view for quickly evaluating memory sizes @@ -28,7 +25,6 @@ // [ ] no selected thread -> causing evaluation failures, e.g. with go-to-definition // //- evaluation space coverage pass -// [ ] eval space reads/writes -> needs staleness/badness info - replace ctrl layer, to apply to all spaces // [ ] need concrete ways of referring into a space at any offset - e.g. `process.memory + 0x1234`, `file:"foo".data + 0x1234`, `thread.regs + 0x80`, etc. // [ ] memory view needs to take advantage of above when peeking; ensure peeking works on files etc. // [ ] need to eliminate accelerators from evaluation context, and build them on the fly instead - @@ -273,6 +269,7 @@ // by default go to "untitled" in default user path // [x] memory view mutation controls // [x] step-over/step-into doesn't step successfully in many cases, just causes a continue +// [x] eval space reads/writes -> needs staleness/badness info - replace ctrl layer, to apply to all spaces //////////////////////////////// //~ rjf: Build Options diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 42c4137d..417f5c98 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -2869,7 +2869,7 @@ RD_VIEW_UI_FUNCTION_DEF(memory) String8 data = {0}; data.size = dim_1u64(range); data.str = push_array(scratch.arena, U8, data.size); - if(!e_space_read(eval.space, data.str, range)) + if(!e_space_read(eval.space, data.str, 0, range)) { log_user_errorf("Could not successfully read memory."); } @@ -3224,17 +3224,8 @@ RD_VIEW_UI_FUNCTION_DEF(memory) U64 *visible_memory_change_flags = push_array(scratch.arena, U64, (visible_memory_size+63)/64); U64 *visible_memory_bad_flags = push_array(scratch.arena, U64, (visible_memory_size+63)/64); { - e_space_read(eval.space, visible_memory, viz_range_bytes); - } - if(eval.space.kind == D_EvalSpaceKind_Entity) - { - D_Entity *entity = rd_ctrl_entity_from_eval_space(eval.space); - if(entity->kind == D_EntityKind_Process) - { - D_ProcessMemorySlice slice = d_process_memory_slice_from_vaddr_range(scratch.arena, entity->handle, viz_range_bytes, 0, 0); - visible_memory_change_flags = slice.byte_changed_flags; - visible_memory_bad_flags = slice.byte_bad_flags; - } + E_SpaceRangeInfo range_info = {visible_memory_bad_flags, visible_memory_change_flags}; + e_space_read(eval.space, visible_memory, &range_info, viz_range_bytes); } //////////////////////////////