expand ctrl process memory cache access to allow waiting for fresh results, rather than any results; use when doing a ctrl process memory write, to prefer viewing less stale data on exact frame. also expand trap net generation with success bit, use to retry stepping commands across frames if process memory reads are hindered. abort after N retries.

This commit is contained in:
Ryan Fleury
2025-10-07 15:44:35 -07:00
parent 2cc7f45ec1
commit 783988c0ac
7 changed files with 89 additions and 67 deletions
+4 -12
View File
@@ -276,20 +276,12 @@ c_submit_data(C_Key key, Arena **data_arena, String8 data)
// rjf: push hash into key's history
if(key_node)
{
U128 last_hash = {0};
if(key_node->hash_history_gen >= 1)
if(key_node->hash_history_gen >= C_KEY_HASH_HISTORY_STRONG_REF_COUNT)
{
last_hash = key_node->hash_history[(key_node->hash_history_gen-1)%ArrayCount(key_node->hash_history)];
}
if(!u128_match(last_hash, hash))
{
if(key_node->hash_history_gen >= C_KEY_HASH_HISTORY_STRONG_REF_COUNT)
{
key_expired_hash = key_node->hash_history[(key_node->hash_history_gen-C_KEY_HASH_HISTORY_STRONG_REF_COUNT)%ArrayCount(key_node->hash_history)];
}
key_node->hash_history[key_node->hash_history_gen%ArrayCount(key_node->hash_history)] = hash;
key_node->hash_history_gen += 1;
key_expired_hash = key_node->hash_history[(key_node->hash_history_gen-C_KEY_HASH_HISTORY_STRONG_REF_COUNT)%ArrayCount(key_node->hash_history)];
}
key_node->hash_history[key_node->hash_history_gen%ArrayCount(key_node->hash_history)] = hash;
key_node->hash_history_gen += 1;
}
// rjf: key is new -> add this key to the associated root
+9 -9
View File
@@ -6114,7 +6114,7 @@ ctrl_memory_artifact_destroy(AC_Artifact artifact)
}
internal C_Key
ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, U64 endt_us, B32 *out_is_stale)
ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, B32 wait_for_fresh, U64 endt_us, B32 *out_is_stale)
{
ProfBeginFunction();
struct
@@ -6127,7 +6127,7 @@ ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32
String8 key = str8_struct(&key_data);
Access *access = access_open();
AC_Artifact artifact = ac_artifact_from_key(access, key, ctrl_memory_artifact_create, ctrl_memory_artifact_destroy, endt_us,
.flags = AC_Flag_HighPriority,
.flags = AC_Flag_HighPriority | (wait_for_fresh ? AC_Flag_WaitForFresh : 0),
.gen = ctrl_mem_gen(),
.slots_count = 2048,
.stale_out = out_is_stale,
@@ -6142,7 +6142,7 @@ ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32
//- rjf: process memory reading helpers
internal CTRL_ProcessMemorySlice
ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, U64 endt_us)
ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, B32 wait_for_fresh, U64 endt_us)
{
ProfBeginFunction();
CTRL_ProcessMemorySlice result = {0};
@@ -6168,7 +6168,7 @@ ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rn
{
U64 page_base_vaddr = page_range.min + page_idx*page_size;
B32 page_is_stale = 0;
C_Key page_key = ctrl_key_from_process_vaddr_range(process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0, endt_us, &page_is_stale);
C_Key page_key = ctrl_key_from_process_vaddr_range(process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0, wait_for_fresh, endt_us, &page_is_stale);
U128 page_hash = c_hash_from_key(page_key, 0);
U128 page_last_hash = c_hash_from_key(page_key, 1);
result.stale = (result.stale || page_is_stale);
@@ -6289,7 +6289,7 @@ ctrl_process_memory_read(CTRL_Handle process, Rng1U64 range, B32 *is_stale_out,
{
Temp scratch = scratch_begin(0, 0);
U64 needed_size = dim_1u64(range);
CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process, range, endt_us);
CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process, range, 0, endt_us);
B32 good = (slice.data.size >= needed_size && !slice.any_byte_bad);
if(good)
{
@@ -6322,15 +6322,15 @@ ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src)
// time.
if(result)
{
U64 endt_us = os_now_microseconds()+5000;
U64 endt_us = os_now_microseconds()+10000;
U64 page_size = os_get_system_info()->page_size; // TODO(rjf): @page_size_from_process
Rng1U64 page_range = r1u64(range.min/page_size, range.max/page_size);
Rng1U64 page_range = r1u64(range.min/page_size, (range.max+page_size-1)/page_size);
for EachInRange(page_idx, page_range)
{
Temp scratch = scratch_begin(0, 0);
ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process, r1u64(page_idx*page_size, (page_idx+1)*page_size), endt_us);
CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process, r1u64(page_idx*page_size, (page_idx+1)*page_size), 1, endt_us);
scratch_end(scratch);
if(os_now_microseconds() >= endt_us)
if(!slice.stale || os_now_microseconds() >= endt_us)
{
break;
}
+2 -2
View File
@@ -1004,10 +1004,10 @@ internal void ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg);
internal AC_Artifact ctrl_memory_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out);
internal void ctrl_memory_artifact_destroy(AC_Artifact artifact);
internal C_Key ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, U64 endt_us, B32 *out_is_stale);
internal C_Key ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, B32 wait_for_fresh, U64 endt_us, B32 *out_is_stale);
//- rjf: process memory reading helpers
internal CTRL_ProcessMemorySlice ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, U64 endt_us);
internal CTRL_ProcessMemorySlice ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, B32 wait_for_fresh, U64 endt_us);
internal B32 ctrl_process_memory_read(CTRL_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us);
#define ctrl_process_memory_read_struct(process, vaddr, is_stale_out, ptr, endt_us) ctrl_process_memory_read((process), r1u64((vaddr), (vaddr)+(sizeof(*(ptr)))), (is_stale_out), (ptr), (endt_us))
+53 -30
View File
@@ -290,11 +290,11 @@ d_cmd_list_push_new(Arena *arena, D_CmdList *cmds, D_CmdKind kind, D_CmdParams *
// - for any instructions which may change the stack pointer, traps are placed
// at them with the "save-stack-pointer | single-step-after" behaviors.
internal CTRL_TrapList
internal D_TrapNet
d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread)
{
Temp scratch = scratch_begin(&arena, 1);
CTRL_TrapList result = {0};
D_TrapNet result = {0};
// rjf: thread => unpacked info
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process);
@@ -305,13 +305,15 @@ d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread)
String8 machine_code = {0};
{
Rng1U64 rng = r1u64(ip_vaddr, ip_vaddr+max_instruction_size_from_arch(arch));
CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, rng, os_now_microseconds()+5000);
CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, rng, 0, os_now_microseconds()+5000);
machine_code = machine_code_slice.data;
}
// rjf: build traps if machine code was read successfully
if(machine_code.size != 0)
{
result.good = 1;
// rjf: decode instruction
DASM_Inst inst = dasm_inst_from_code(scratch.arena, arch, ip_vaddr, machine_code, DASM_Syntax_Intel);
@@ -319,7 +321,7 @@ d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread)
if(inst.flags & DASM_InstFlag_Call || inst.flags & DASM_InstFlag_Repeats)
{
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, ip_vaddr+inst.size};
ctrl_trap_list_push(arena, &result, &trap);
ctrl_trap_list_push(arena, &result.traps, &trap);
}
}
@@ -327,12 +329,12 @@ d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread)
return result;
}
internal CTRL_TrapList
internal D_TrapNet
d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
{
Temp scratch = scratch_begin(&arena, 1);
log_infof("step_over_line:\n{\n");
CTRL_TrapList result = {0};
D_TrapNet result = {0};
// rjf: thread => info
Arch arch = thread->arch;
@@ -380,7 +382,7 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
B32 good_machine_code = 0;
if(good_line_info)
{
CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, line_vaddr_rng, os_now_microseconds()+50000);
CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, line_vaddr_rng, 0, os_now_microseconds()+50000);
machine_code = machine_code_slice.data;
good_machine_code = (machine_code.size == dim_1u64(line_vaddr_rng) && !machine_code_slice.any_byte_bad);
LogInfoNamedBlockF("machine_code_slice")
@@ -469,7 +471,7 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
if(add)
{
CTRL_Trap trap = {flags, trap_addr};
ctrl_trap_list_push(arena, &result, &trap);
ctrl_trap_list_push(arena, &result.traps, &trap);
}
}
@@ -477,11 +479,17 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
if(good_line_info && good_machine_code)
{
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max};
ctrl_trap_list_push(arena, &result, &trap);
ctrl_trap_list_push(arena, &result.traps, &trap);
}
// rjf: good if we got machine code
if(good_machine_code)
{
result.good = 1;
}
// rjf: log
LogInfoNamedBlockF("traps") for(CTRL_TrapNode *n = result.first; n != 0; n = n->next)
LogInfoNamedBlockF("traps") for(CTRL_TrapNode *n = result.traps.first; n != 0; n = n->next)
{
log_infof("{flags:0x%x, vaddr:0x%I64x}\n", n->v.flags, n->v.vaddr);
}
@@ -491,11 +499,11 @@ d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread)
return result;
}
internal CTRL_TrapList
internal D_TrapNet
d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread)
{
Temp scratch = scratch_begin(&arena, 1);
CTRL_TrapList result = {0};
D_TrapNet result = {0};
// rjf: thread => info
Arch arch = thread->arch;
@@ -538,7 +546,7 @@ d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread)
B32 good_machine_code = 0;
if(good_line_info)
{
CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, line_vaddr_rng, os_now_microseconds()+5000);
CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, line_vaddr_rng, 0, os_now_microseconds()+5000);
machine_code = machine_code_slice.data;
good_machine_code = (machine_code.size == dim_1u64(line_vaddr_rng) && !machine_code_slice.any_byte_bad);
}
@@ -625,7 +633,7 @@ d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread)
if(add)
{
CTRL_Trap trap = {flags, trap_addr};
ctrl_trap_list_push(arena, &result, &trap);
ctrl_trap_list_push(arena, &result.traps, &trap);
}
}
@@ -633,7 +641,13 @@ d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread)
if(good_line_info && good_machine_code)
{
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max};
ctrl_trap_list_push(arena, &result, &trap);
ctrl_trap_list_push(arena, &result.traps, &trap);
}
// rjf: good if we got machine code
if(good_machine_code)
{
result.good = 1;
}
scratch_end(scratch);
@@ -981,7 +995,7 @@ d_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64
U64 tls_index = 0;
if(addr_size != 0)
{
CTRL_ProcessMemorySlice tls_index_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, tls_vaddr_range, 0);
CTRL_ProcessMemorySlice tls_index_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, tls_vaddr_range, 0, 0);
if(tls_index_slice.data.size >= addr_size)
{
tls_index = *(U64 *)tls_index_slice.data.str;
@@ -994,13 +1008,13 @@ d_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64
U64 thread_info_addr = root_vaddr;
U64 tls_addr_off = tls_index*addr_size;
U64 tls_addr_array = 0;
CTRL_ProcessMemorySlice tls_addr_array_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(thread_info_addr, thread_info_addr+addr_size), 0);
CTRL_ProcessMemorySlice tls_addr_array_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(thread_info_addr, thread_info_addr+addr_size), 0, 0);
String8 tls_addr_array_data = tls_addr_array_slice.data;
if(tls_addr_array_data.size >= 8)
{
MemoryCopy(&tls_addr_array, tls_addr_array_data.str, sizeof(U64));
}
CTRL_ProcessMemorySlice result_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(tls_addr_array + tls_addr_off, tls_addr_array + tls_addr_off + addr_size), 0);
CTRL_ProcessMemorySlice result_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(tls_addr_array + tls_addr_off, tls_addr_array + tls_addr_off + addr_size), 0, 0);
String8 result_data = result_slice.data;
if(result_data.size >= 8)
{
@@ -1791,15 +1805,14 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P
}
else
{
B32 good = 1;
CTRL_TrapList traps = {0};
D_TrapNet trap_net = {0};
switch(cmd->kind)
{
default: break;
case D_CmdKind_StepIntoInst: {}break;
case D_CmdKind_StepOverInst: {traps = d_trap_net_from_thread__step_over_inst(scratch.arena, thread);}break;
case D_CmdKind_StepIntoLine: {traps = d_trap_net_from_thread__step_into_line(scratch.arena, thread);}break;
case D_CmdKind_StepOverLine: {traps = d_trap_net_from_thread__step_over_line(scratch.arena, thread);}break;
case D_CmdKind_StepIntoInst: {trap_net.good = 1;}break;
case D_CmdKind_StepOverInst: {trap_net = d_trap_net_from_thread__step_over_inst(scratch.arena, thread);}break;
case D_CmdKind_StepIntoLine: {trap_net = d_trap_net_from_thread__step_into_line(scratch.arena, thread);}break;
case D_CmdKind_StepOverLine: {trap_net = d_trap_net_from_thread__step_over_line(scratch.arena, thread);}break;
case D_CmdKind_StepOut:
{
Access *access = access_open();
@@ -1812,32 +1825,42 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P
{
U64 vaddr = regs_rip_from_arch_block(thread->arch, callstack.concrete_frames[1]->regs);
CTRL_Trap trap = {CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck, vaddr};
ctrl_trap_list_push(scratch.arena, &traps, &trap);
ctrl_trap_list_push(scratch.arena, &trap_net.traps, &trap);
trap_net.good = 1;
}
else
{
log_user_error(str8_lit("Could not find the return address of the current callstack frame successfully."));
good = 0;
}
access_close(access);
}break;
}
if(good && traps.count != 0)
if(trap_net.good && trap_net.traps.count != 0)
{
need_run = 1;
run_kind = D_RunKind_Step;
run_thread = thread;
run_flags = 0;
run_traps = traps;
run_traps = trap_net.traps;
}
if(good && traps.count == 0)
else if(trap_net.good && trap_net.traps.count == 0)
{
need_run = 1;
run_kind = D_RunKind_SingleStep;
run_thread = thread;
run_flags = 0;
run_traps = traps;
run_traps = trap_net.traps;
}
else if(!trap_net.good && params->retry_idx < 1000)
{
D_CmdParams params_copy = *params;
params_copy.retry_idx += 1;
d_push_cmd(cmd->kind, &params_copy);
}
else if(!trap_net.good)
{
log_user_error(str8_lit("Could not successfully step."));
}
}
}break;
+14 -3
View File
@@ -69,6 +69,16 @@ struct D_PathMapArray
U64 count;
};
////////////////////////////////
//~ rjf: Trap Nets
typedef struct D_TrapNet D_TrapNet;
struct D_TrapNet
{
CTRL_TrapList traps;
B32 good;
};
////////////////////////////////
//~ rjf: Tick Output Types
@@ -187,6 +197,7 @@ struct D_CmdParams
U32 pid;
U32 rgba;
D_TargetArray targets;
U64 retry_idx;
};
typedef struct D_Cmd D_Cmd;
@@ -358,9 +369,9 @@ internal void d_cmd_list_push_new(Arena *arena, D_CmdList *cmds, D_CmdKind kind,
////////////////////////////////
//~ rjf: Stepping "Trap Net" Builders
internal CTRL_TrapList d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread);
internal CTRL_TrapList d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread);
internal CTRL_TrapList d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread);
internal D_TrapNet d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread);
internal D_TrapNet d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread);
internal D_TrapNet d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread);
////////////////////////////////
//~ rjf: Debug Info Lookups
+6 -10
View File
@@ -1805,7 +1805,7 @@ rd_eval_space_read(void *u, E_Space space, void *out, Rng1U64 range)
default:{}break;
case CTRL_EntityKind_Process:
{
CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, entity->handle, range, rd_state->frame_eval_memread_endt_us);
CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, entity->handle, range, 0, rd_state->frame_eval_memread_endt_us);
String8 data = slice.data;
if(data.size == dim_1u64(range))
{
@@ -2159,7 +2159,7 @@ rd_key_from_eval_space_range(E_Space space, Rng1U64 range, B32 zero_terminated)
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(space);
if(entity->kind == CTRL_EntityKind_Process)
{
result = ctrl_key_from_process_vaddr_range(entity->handle, range, zero_terminated, 0, 0);
result = ctrl_key_from_process_vaddr_range(entity->handle, range, zero_terminated, 0, 0, 0);
}
}break;
}
@@ -4641,7 +4641,7 @@ rd_view_ui(Rng2F32 rect)
CTRL_Entity *space_entity = rd_ctrl_entity_from_eval_space(cell->eval.space);
if(cell->eval.space.kind == RD_EvalSpaceKind_CtrlEntity && space_entity->kind == CTRL_EntityKind_Process)
{
CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, space_entity->handle, cell_vaddr_rng, rd_state->frame_eval_memread_endt_us);
CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, space_entity->handle, cell_vaddr_rng, 0, rd_state->frame_eval_memread_endt_us);
for(U64 idx = 0; idx < (slice.data.size+63)/64; idx += 1)
{
if(slice.byte_changed_flags[idx] != 0)
@@ -5047,7 +5047,7 @@ rd_view_ui(Rng2F32 rect)
cell->eval.space.kind == RD_EvalSpaceKind_CtrlEntity &&
row_info->callstack_thread == &ctrl_entity_nil &&
e_type_kind_from_key(cell->eval.irtree.type_key) != E_TypeKind_Function)
UI_FontSize(ui_top_font_size()*0.8f)
UI_FontSize(ui_top_font_size()*0.9f)
{
if(cell_width_px >= ui_top_font_size()*8.f)
{
@@ -7133,12 +7133,8 @@ rd_window_frame(void)
ev_key_set_expansion(rd_view_eval_view(), ev_key_root(), ev_key_make(ev_hash_from_key(ev_key_root()), 1), 1);
predicted_block_tree = ev_block_tree_from_eval(scratch.arena, rd_view_eval_view(), str8_zero(), hover_eval);
}
F32 row_height_px = ui_top_px_height();
U64 max_row_count = (U64)floor_f32(ui_top_font_size()*10.f / row_height_px);
if(ws->hover_eval_focused)
{
max_row_count *= 3;
}
F32 row_height_px = floor_f32(ui_top_font_size()*rd_setting_f32_from_name(str8_lit("row_height")));
U64 max_row_count = 12;
U64 needed_row_count = Min(max_row_count, predicted_block_tree.total_row_count);
F32 width_px = floor_f32(70.f*ui_top_font_size());
F32 height_px = needed_row_count*row_height_px;
+1 -1
View File
@@ -2973,7 +2973,7 @@ RD_VIEW_UI_FUNCTION_DEF(memory)
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(eval.space);
if(entity->kind == CTRL_EntityKind_Process)
{
CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, entity->handle, viz_range_bytes, 0);
CTRL_ProcessMemorySlice slice = ctrl_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;
}