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)

This commit is contained in:
Ryan Fleury
2026-05-13 14:48:00 -07:00
parent e6d83f7450
commit 58caf0d3d5
13 changed files with 130 additions and 46 deletions
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
+20 -3
View File
@@ -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
+66 -16
View File
@@ -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;
+1 -1
View File
@@ -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);
////////////////////////////////
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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 =
{
@@ -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
+16 -2
View File
@@ -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:
+1 -1
View File
@@ -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
+1 -4
View File
@@ -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
+3 -12
View File
@@ -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);
}
//////////////////////////////