mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-06-13 07:32:23 -07:00
eliminate old caches from ctrl layer
This commit is contained in:
+206
-727
@@ -1458,15 +1458,6 @@ ctrl_init(void)
|
||||
e_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2alias_tables[arch], alias_names[idx], idx);
|
||||
}
|
||||
}
|
||||
ctrl_state->process_memory_cache.slots_count = 256;
|
||||
ctrl_state->process_memory_cache.slots = push_array(arena, CTRL_ProcessMemoryCacheSlot, ctrl_state->process_memory_cache.slots_count);
|
||||
ctrl_state->process_memory_cache.stripes_count = os_get_system_info()->logical_processor_count;
|
||||
ctrl_state->process_memory_cache.stripes = push_array(arena, CTRL_ProcessMemoryCacheStripe, ctrl_state->process_memory_cache.stripes_count);
|
||||
for(U64 idx = 0; idx < ctrl_state->process_memory_cache.stripes_count; idx += 1)
|
||||
{
|
||||
ctrl_state->process_memory_cache.stripes[idx].rw_mutex = rw_mutex_alloc();
|
||||
ctrl_state->process_memory_cache.stripes[idx].cv = cond_var_alloc();
|
||||
}
|
||||
ctrl_state->thread_reg_cache.slots_count = 1024;
|
||||
ctrl_state->thread_reg_cache.slots = push_array(arena, CTRL_ThreadRegCacheSlot, ctrl_state->thread_reg_cache.slots_count);
|
||||
ctrl_state->thread_reg_cache.stripes_count = os_get_system_info()->logical_processor_count;
|
||||
@@ -1485,9 +1476,6 @@ ctrl_init(void)
|
||||
ctrl_state->module_image_info_cache.stripes[idx].arena = arena_alloc();
|
||||
ctrl_state->module_image_info_cache.stripes[idx].rw_mutex = rw_mutex_alloc();
|
||||
}
|
||||
ctrl_state->call_stack_tree_cache.tree.root = &ctrl_call_stack_tree_node_nil;
|
||||
ctrl_state->call_stack_tree_cache.cv = cond_var_alloc();
|
||||
ctrl_state->call_stack_tree_cache.rw_mutex = rw_mutex_alloc();
|
||||
ctrl_state->u2c_ring_size = KB(64);
|
||||
ctrl_state->u2c_ring_base = push_array_no_zero(arena, U8, ctrl_state->u2c_ring_size);
|
||||
ctrl_state->u2c_ring_mutex = mutex_alloc();
|
||||
@@ -1520,12 +1508,6 @@ ctrl_init(void)
|
||||
ctrl_state->exception_code_filters[k/64] |= 1ull<<(k%64);
|
||||
}
|
||||
}
|
||||
ctrl_state->mem_req_mutex = mutex_alloc();
|
||||
ctrl_state->mem_req_arena = arena_alloc();
|
||||
ctrl_state->u2ms_ring_size = KB(64);
|
||||
ctrl_state->u2ms_ring_base = push_array(arena, U8, ctrl_state->u2ms_ring_size);
|
||||
ctrl_state->u2ms_ring_mutex = mutex_alloc();
|
||||
ctrl_state->u2ms_ring_cv = cond_var_alloc();
|
||||
ctrl_state->ctrl_thread_log = log_alloc();
|
||||
ctrl_state->ctrl_thread = thread_launch(ctrl_thread__entry_point, 0);
|
||||
}
|
||||
@@ -1539,466 +1521,6 @@ ctrl_set_wakeup_hook(CTRL_WakeupFunctionType *wakeup_hook)
|
||||
ctrl_state->wakeup_hook = wakeup_hook;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Process Memory Functions
|
||||
|
||||
//- rjf: process memory cache key reading
|
||||
|
||||
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_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache;
|
||||
|
||||
//- rjf: unpack process key
|
||||
U64 process_hash = ctrl_hash_from_handle(process);
|
||||
U64 process_slot_idx = process_hash%cache->slots_count;
|
||||
U64 process_stripe_idx = process_slot_idx%cache->stripes_count;
|
||||
CTRL_ProcessMemoryCacheSlot *process_slot = &cache->slots[process_slot_idx];
|
||||
CTRL_ProcessMemoryCacheStripe *process_stripe = &cache->stripes[process_stripe_idx];
|
||||
|
||||
//- rjf: get the content root for this process; construct process node if it
|
||||
// doesn't exist
|
||||
C_Root root = {0};
|
||||
{
|
||||
for(B32 write_mode = 0; write_mode <= 1; write_mode += 1)
|
||||
{
|
||||
B32 node_found = 0;
|
||||
RWMutexScope(process_stripe->rw_mutex, write_mode)
|
||||
{
|
||||
for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next)
|
||||
{
|
||||
if(ctrl_handle_match(n->handle, process))
|
||||
{
|
||||
node_found = 1;
|
||||
root = n->root;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(write_mode && !node_found)
|
||||
{
|
||||
node_found = 1;
|
||||
Arena *node_arena = arena_alloc();
|
||||
CTRL_ProcessMemoryCacheNode *node = push_array(node_arena, CTRL_ProcessMemoryCacheNode, 1);
|
||||
DLLPushBack(process_slot->first, process_slot->last, node);
|
||||
node->arena = node_arena;
|
||||
node->handle = process;
|
||||
node->root = c_root_alloc();
|
||||
node->range_hash_slots_count = 1024;
|
||||
node->range_hash_slots = push_array(node_arena, CTRL_ProcessMemoryRangeHashSlot, node->range_hash_slots_count);
|
||||
root = node->root;
|
||||
}
|
||||
if(node_found)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: form ID for this process memory query
|
||||
C_ID id = {0};
|
||||
{
|
||||
id.u128[0].u64[0] = vaddr_range.min & 0x00ffffffffffffffull;
|
||||
id.u128[0].u64[1] = vaddr_range.max & 0x00ffffffffffffffull;
|
||||
if(zero_terminated)
|
||||
{
|
||||
id.u128[0].u64[0] |= (1ull << 63);
|
||||
}
|
||||
}
|
||||
U64 range_hash = u64_hash_from_str8(str8_struct(&id));
|
||||
|
||||
//- rjf: form full key
|
||||
C_Key key = c_key_make(root, id);
|
||||
|
||||
//- rjf: loop: try to look for current results, request if not there, wait if we can, repeat until we can't
|
||||
U64 mem_gen = ctrl_mem_gen();
|
||||
B32 key_is_stale = 0;
|
||||
for(;;)
|
||||
{
|
||||
//- rjf: step 1: [read-only] try to look for current results for key's ID; wait if working & retry
|
||||
B32 id_exists = 0;
|
||||
B32 id_stale = 0;
|
||||
B32 id_working = 0;
|
||||
MutexScopeR(process_stripe->rw_mutex) for(;;)
|
||||
{
|
||||
for(CTRL_ProcessMemoryCacheNode *process_n = process_slot->first; process_n != 0; process_n = process_n->next)
|
||||
{
|
||||
if(ctrl_handle_match(process_n->handle, process))
|
||||
{
|
||||
U64 range_slot_idx = range_hash%process_n->range_hash_slots_count;
|
||||
CTRL_ProcessMemoryRangeHashSlot *range_slot = &process_n->range_hash_slots[range_slot_idx];
|
||||
for(CTRL_ProcessMemoryRangeHashNode *n = range_slot->first; n != 0; n = n->next)
|
||||
{
|
||||
if(c_id_match(n->id, id))
|
||||
{
|
||||
id_exists = 1;
|
||||
id_stale = (n->mem_gen < mem_gen);
|
||||
id_working = (n->working_count != 0);
|
||||
goto end_fast_lookup;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end_fast_lookup:;
|
||||
if(!id_stale || !id_working || os_now_microseconds() >= endt_us)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
cond_var_wait_rw_r(process_stripe->cv, process_stripe->rw_mutex, endt_us);
|
||||
}
|
||||
}
|
||||
key_is_stale = id_stale;
|
||||
|
||||
//- rjf: step 2: if the ID exists and is not stale, then we're done;
|
||||
// the hash store contains the most up-to-date representation of the
|
||||
// process memory for this key.
|
||||
if(id_exists && !id_stale)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//- rjf: step 3: if the ID does not exist in the process' cache, then we
|
||||
// need to build a node for it. if that, or if the ID is stale, then also
|
||||
// request that that range is streamed & wait for its result (for as long
|
||||
// as we have.)
|
||||
B32 requested = 0;
|
||||
if(!id_exists || (id_exists && id_stale && !id_working))
|
||||
{
|
||||
B32 node_needs_stream = 0;
|
||||
MutexScopeW(process_stripe->rw_mutex)
|
||||
{
|
||||
for(CTRL_ProcessMemoryCacheNode *process_n = process_slot->first; process_n != 0; process_n = process_n->next)
|
||||
{
|
||||
if(ctrl_handle_match(process_n->handle, process))
|
||||
{
|
||||
U64 range_slot_idx = range_hash%process_n->range_hash_slots_count;
|
||||
CTRL_ProcessMemoryRangeHashSlot *range_slot = &process_n->range_hash_slots[range_slot_idx];
|
||||
CTRL_ProcessMemoryRangeHashNode *range_n = 0;
|
||||
for(CTRL_ProcessMemoryRangeHashNode *n = range_slot->first; n != 0; n = n->next)
|
||||
{
|
||||
if(c_id_match(n->id, id))
|
||||
{
|
||||
range_n = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(range_n == 0)
|
||||
{
|
||||
range_n = push_array(process_n->arena, CTRL_ProcessMemoryRangeHashNode, 1);
|
||||
SLLQueuePush(range_slot->first, range_slot->last, range_n);
|
||||
range_n->vaddr_range = vaddr_range;
|
||||
range_n->zero_terminated = zero_terminated;
|
||||
range_n->id = id;
|
||||
node_needs_stream = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
node_needs_stream = (range_n->mem_gen < mem_gen && range_n->working_count == 0);
|
||||
}
|
||||
if(node_needs_stream)
|
||||
{
|
||||
range_n->working_count += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(node_needs_stream)
|
||||
{
|
||||
if(ctrl_u2ms_enqueue_req(key, process, vaddr_range, zero_terminated, endt_us))
|
||||
{
|
||||
// NOTE(rjf): debugging
|
||||
#if 0
|
||||
raddbg_log("[0x%I64x, 0x%I64x) push: (gen: %I64u)\n", vaddr_range.min, vaddr_range.max, mem_gen);
|
||||
#endif
|
||||
async_push_work(ctrl_mem_stream_work);
|
||||
requested = 1;
|
||||
}
|
||||
else MutexScopeW(process_stripe->rw_mutex)
|
||||
{
|
||||
for(CTRL_ProcessMemoryCacheNode *process_n = process_slot->first; process_n != 0; process_n = process_n->next)
|
||||
{
|
||||
if(ctrl_handle_match(process_n->handle, process))
|
||||
{
|
||||
U64 range_slot_idx = range_hash%process_n->range_hash_slots_count;
|
||||
CTRL_ProcessMemoryRangeHashSlot *range_slot = &process_n->range_hash_slots[range_slot_idx];
|
||||
CTRL_ProcessMemoryRangeHashNode *range_n = 0;
|
||||
for(CTRL_ProcessMemoryRangeHashNode *n = range_slot->first; n != 0; n = n->next)
|
||||
{
|
||||
if(c_id_match(n->id, id))
|
||||
{
|
||||
n->working_count -= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: step 4: if we didn't request, and if we aren't working, then exit
|
||||
if(!requested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//- rjf: step 5: exit if out of time
|
||||
if(os_now_microseconds() >= endt_us)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(out_is_stale)
|
||||
{
|
||||
*out_is_stale = key_is_stale;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
//- rjf: process memory cache reading helpers
|
||||
|
||||
internal CTRL_ProcessMemorySlice
|
||||
ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, U64 endt_us)
|
||||
{
|
||||
ProfBeginFunction();
|
||||
CTRL_ProcessMemorySlice result = {0};
|
||||
if(range.max > range.min &&
|
||||
dim_1u64(range) <= MB(256) &&
|
||||
range.min <= 0x000FFFFFFFFFFFFFull &&
|
||||
range.max <= 0x000FFFFFFFFFFFFFull)
|
||||
{
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
Access *access = access_open();
|
||||
CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache;
|
||||
|
||||
//- rjf: unpack address range, prepare per-touched-page info
|
||||
U64 page_size = KB(4);
|
||||
Rng1U64 page_range = r1u64(AlignDownPow2(range.min, page_size), AlignPow2(range.max, page_size));
|
||||
U64 page_count = dim_1u64(page_range)/page_size;
|
||||
U128 *page_hashes = push_array(scratch.arena, U128, page_count);
|
||||
U128 *page_last_hashes = push_array(scratch.arena, U128, page_count);
|
||||
|
||||
//- rjf: gather hashes & last-hashes for each page
|
||||
ProfScope("gather hashes & last-hashes for each page")
|
||||
{
|
||||
for(U64 page_idx = 0; page_idx < page_count; page_idx += 1)
|
||||
{
|
||||
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_new(process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0, 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);
|
||||
page_hashes[page_idx] = page_hash;
|
||||
page_last_hashes[page_idx] = page_last_hash;
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: setup output buffers
|
||||
void *read_out = push_array(arena, U8, dim_1u64(range));
|
||||
U64 *byte_bad_flags = push_array(arena, U64, (dim_1u64(range)+63)/64);
|
||||
U64 *byte_changed_flags = push_array(arena, U64, (dim_1u64(range)+63)/64);
|
||||
|
||||
//- rjf: iterate pages, fill output
|
||||
ProfScope("iterate pages, fill output")
|
||||
{
|
||||
U64 write_off = 0;
|
||||
for(U64 page_idx = 0; page_idx < page_count; page_idx += 1)
|
||||
{
|
||||
// rjf: read data for this page
|
||||
String8 data = c_data_from_hash(access, page_hashes[page_idx]);
|
||||
Rng1U64 data_vaddr_range = r1u64(page_range.min + page_idx*page_size, page_range.min + page_idx*page_size+data.size);
|
||||
|
||||
// rjf: skip/chop bytes which are irrelevant for the actual requested read
|
||||
String8 in_range_data = data;
|
||||
if(page_idx == page_count-1 && data_vaddr_range.max > range.max)
|
||||
{
|
||||
in_range_data = str8_chop(in_range_data, data_vaddr_range.max-range.max);
|
||||
}
|
||||
if(page_idx == 0 && range.min > data_vaddr_range.min)
|
||||
{
|
||||
in_range_data = str8_skip(in_range_data, range.min-data_vaddr_range.min);
|
||||
}
|
||||
|
||||
// rjf: write this chunk
|
||||
MemoryCopy((U8*)read_out+write_off, in_range_data.str, in_range_data.size);
|
||||
|
||||
// rjf; if this page's data doesn't fill the entire range, mark
|
||||
// missing bytes as bad
|
||||
if(data.size < page_size) ProfScope("mark missing bytes as bad")
|
||||
{
|
||||
Rng1U64 invalid_range = r1u64(data_vaddr_range.min+data.size, data_vaddr_range.min + page_size);
|
||||
Rng1U64 in_range_invalid_range = intersect_1u64(invalid_range, range);
|
||||
for(U64 invalid_vaddr = in_range_invalid_range.min;
|
||||
invalid_vaddr < in_range_invalid_range.max;
|
||||
invalid_vaddr += 1)
|
||||
{
|
||||
U64 idx_in_range = invalid_vaddr - range.min;
|
||||
byte_bad_flags[idx_in_range/64] |= (1ull<<(idx_in_range%64));
|
||||
}
|
||||
}
|
||||
|
||||
// rjf: if this page's hash & last_hash don't match, diff each byte &
|
||||
// fill out changed flags
|
||||
if(!u128_match(page_hashes[page_idx], page_last_hashes[page_idx])) ProfScope("hashes don't match; diff each byte")
|
||||
{
|
||||
String8 last_data = c_data_from_hash(access, page_last_hashes[page_idx]);
|
||||
String8 in_range_last_data = last_data;
|
||||
if(page_idx == page_count-1 && data_vaddr_range.max > range.max)
|
||||
{
|
||||
in_range_last_data = str8_chop(in_range_last_data, data_vaddr_range.max-range.max);
|
||||
}
|
||||
if(page_idx == 0 && range.min > data_vaddr_range.min)
|
||||
{
|
||||
in_range_last_data = str8_skip(in_range_last_data, range.min-data_vaddr_range.min);
|
||||
}
|
||||
for(U64 idx = 0; idx < in_range_data.size; idx += 1)
|
||||
{
|
||||
U8 last_byte = idx < in_range_last_data.size ? in_range_last_data.str[idx] : 0;
|
||||
U8 now_byte = idx < in_range_data.size ? in_range_data.str[idx] : 0;
|
||||
if(last_byte != now_byte)
|
||||
{
|
||||
U64 idx_in_read_out = write_off+idx;
|
||||
byte_changed_flags[idx_in_read_out/64] |= (1ull<<(idx_in_read_out%64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rjf: increment past this chunk
|
||||
U64 bytes_to_skip = page_size;
|
||||
if(page_idx == 0 && range.min > data_vaddr_range.min)
|
||||
{
|
||||
bytes_to_skip -= (range.min-data_vaddr_range.min);
|
||||
}
|
||||
write_off += bytes_to_skip;
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: fill result
|
||||
result.data.str = (U8*)read_out;
|
||||
result.data.size = dim_1u64(range);
|
||||
result.byte_bad_flags = byte_bad_flags;
|
||||
result.byte_changed_flags = byte_changed_flags;
|
||||
if(byte_bad_flags != 0)
|
||||
{
|
||||
for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1)
|
||||
{
|
||||
result.any_byte_bad = result.any_byte_bad || !!result.byte_bad_flags[idx];
|
||||
}
|
||||
}
|
||||
if(byte_changed_flags != 0)
|
||||
{
|
||||
for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1)
|
||||
{
|
||||
result.any_byte_changed = result.any_byte_changed || !!result.byte_changed_flags[idx];
|
||||
}
|
||||
}
|
||||
|
||||
access_close(access);
|
||||
scratch_end(scratch);
|
||||
}
|
||||
ProfEnd();
|
||||
return result;
|
||||
}
|
||||
|
||||
internal B32
|
||||
ctrl_process_memory_read(CTRL_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us)
|
||||
{
|
||||
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);
|
||||
B32 good = (slice.data.size >= needed_size && !slice.any_byte_bad);
|
||||
if(good)
|
||||
{
|
||||
MemoryCopy(out, slice.data.str, needed_size);
|
||||
}
|
||||
if(slice.stale && is_stale_out)
|
||||
{
|
||||
*is_stale_out = 1;
|
||||
}
|
||||
scratch_end(scratch);
|
||||
return good;
|
||||
}
|
||||
|
||||
//- rjf: process memory writing
|
||||
|
||||
internal B32
|
||||
ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src)
|
||||
{
|
||||
ProfBeginFunction();
|
||||
B32 result = dmn_process_write(process.dmn_handle, range, src);
|
||||
|
||||
//- rjf: success -> bump generation
|
||||
if(result)
|
||||
{
|
||||
ins_atomic_u64_inc_eval(&ctrl_state->mem_gen);
|
||||
}
|
||||
|
||||
//- rjf: success -> wait for cache updates, for small regions - prefer relatively seamless
|
||||
// writes within calling frame's "view" of the memory, at the expense of a small amount of
|
||||
// time.
|
||||
if(result)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
U64 endt_us = os_now_microseconds()+5000;
|
||||
|
||||
//- rjf: gather tasks for all affected cached regions
|
||||
typedef struct Task Task;
|
||||
struct Task
|
||||
{
|
||||
Task *next;
|
||||
CTRL_Handle process;
|
||||
Rng1U64 range;
|
||||
};
|
||||
Task *first_task = 0;
|
||||
Task *last_task = 0;
|
||||
CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache;
|
||||
for(U64 slot_idx = 0; slot_idx < cache->slots_count; slot_idx += 1)
|
||||
{
|
||||
U64 stripe_idx = slot_idx%cache->stripes_count;
|
||||
CTRL_ProcessMemoryCacheSlot *slot = &cache->slots[slot_idx];
|
||||
CTRL_ProcessMemoryCacheStripe *stripe = &cache->stripes[stripe_idx];
|
||||
MutexScopeW(stripe->rw_mutex)
|
||||
{
|
||||
for(CTRL_ProcessMemoryCacheNode *proc_n = slot->first; proc_n != 0; proc_n = proc_n->next)
|
||||
{
|
||||
for(U64 range_hash_idx = 0; range_hash_idx < proc_n->range_hash_slots_count; range_hash_idx += 1)
|
||||
{
|
||||
CTRL_ProcessMemoryRangeHashSlot *range_slot = &proc_n->range_hash_slots[range_hash_idx];
|
||||
for(CTRL_ProcessMemoryRangeHashNode *n = range_slot->first; n != 0; n = n->next)
|
||||
{
|
||||
Rng1U64 intersection_w_range = intersect_1u64(range, n->vaddr_range);
|
||||
if(dim_1u64(intersection_w_range) != 0 && dim_1u64(n->vaddr_range) <= KB(64))
|
||||
{
|
||||
Task *task = push_array(scratch.arena, Task, 1);
|
||||
task->process = proc_n->handle;
|
||||
task->range = n->vaddr_range;
|
||||
SLLQueuePush(first_task, last_task, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: for all tasks, wait for up-to-date results
|
||||
for(Task *task = first_task; task != 0; task = task->next)
|
||||
{
|
||||
Temp temp = temp_begin(scratch.arena);
|
||||
ctrl_process_memory_slice_from_vaddr_range(temp.arena, task->process, task->range, endt_us);
|
||||
temp_end(temp);
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
ProfEnd();
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Thread Register Functions
|
||||
|
||||
@@ -4828,27 +4350,6 @@ ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg,
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: clear process memory cache, if we've just started a lone process
|
||||
if(event->kind == DMN_EventKind_CreateProcess && ctrl_state->process_counter == 1)
|
||||
{
|
||||
CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache;
|
||||
for(U64 slot_idx = 0; slot_idx < cache->slots_count; slot_idx += 1)
|
||||
{
|
||||
U64 stripe_idx = slot_idx%cache->stripes_count;
|
||||
CTRL_ProcessMemoryCacheSlot *slot = &cache->slots[slot_idx];
|
||||
CTRL_ProcessMemoryCacheStripe *stripe = &cache->stripes[stripe_idx];
|
||||
MutexScopeW(stripe->rw_mutex)
|
||||
{
|
||||
for(CTRL_ProcessMemoryCacheNode *n = slot->first, *next = 0; n != 0; n = next)
|
||||
{
|
||||
next = n->next;
|
||||
arena_clear(n->arena);
|
||||
}
|
||||
}
|
||||
MemoryZeroStruct(slot);
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: out of queued up demon events -> clear event arena
|
||||
if(ctrl_state->first_dmn_event_node == 0)
|
||||
{
|
||||
@@ -6629,230 +6130,6 @@ ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg)
|
||||
ProfEnd();
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Asynchronous Memory Streaming Functions
|
||||
|
||||
//- rjf: user -> memory stream communication
|
||||
|
||||
internal B32
|
||||
ctrl_u2ms_enqueue_req(C_Key key, CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, U64 endt_us)
|
||||
{
|
||||
B32 good = 0;
|
||||
MutexScope(ctrl_state->u2ms_ring_mutex) for(;;)
|
||||
{
|
||||
U64 unconsumed_size = ctrl_state->u2ms_ring_write_pos-ctrl_state->u2ms_ring_read_pos;
|
||||
U64 available_size = ctrl_state->u2ms_ring_size-unconsumed_size;
|
||||
if(available_size >= sizeof(key)+sizeof(process)+sizeof(vaddr_range)+sizeof(zero_terminated))
|
||||
{
|
||||
good = 1;
|
||||
ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &key);
|
||||
ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &process);
|
||||
ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &vaddr_range);
|
||||
ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &zero_terminated);
|
||||
break;
|
||||
}
|
||||
if(os_now_microseconds() >= endt_us) {break;}
|
||||
cond_var_wait(ctrl_state->u2ms_ring_cv, ctrl_state->u2ms_ring_mutex, endt_us);
|
||||
}
|
||||
cond_var_broadcast(ctrl_state->u2ms_ring_cv);
|
||||
return good;
|
||||
}
|
||||
|
||||
internal void
|
||||
ctrl_u2ms_dequeue_req(C_Key *out_key, CTRL_Handle *out_process, Rng1U64 *out_vaddr_range, B32 *out_zero_terminated)
|
||||
{
|
||||
MutexScope(ctrl_state->u2ms_ring_mutex) for(;;)
|
||||
{
|
||||
U64 unconsumed_size = ctrl_state->u2ms_ring_write_pos-ctrl_state->u2ms_ring_read_pos;
|
||||
if(unconsumed_size >= sizeof(*out_key)+sizeof(*out_process)+sizeof(*out_vaddr_range)+sizeof(*out_zero_terminated))
|
||||
{
|
||||
ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_key);
|
||||
ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_process);
|
||||
ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_vaddr_range);
|
||||
ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_zero_terminated);
|
||||
break;
|
||||
}
|
||||
cond_var_wait(ctrl_state->u2ms_ring_cv, ctrl_state->u2ms_ring_mutex, max_U64);
|
||||
}
|
||||
cond_var_broadcast(ctrl_state->u2ms_ring_cv);
|
||||
}
|
||||
|
||||
//- rjf: entry point
|
||||
|
||||
ASYNC_WORK_DEF(ctrl_mem_stream_work)
|
||||
{
|
||||
#define CTRL_MEM_STREAM_WORK_DEBUG 0
|
||||
ProfBeginFunction();
|
||||
CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache;
|
||||
|
||||
//- rjf: unpack next request
|
||||
C_Key key = {0};
|
||||
CTRL_Handle process = {0};
|
||||
Rng1U64 vaddr_range = {0};
|
||||
B32 zero_terminated = 0;
|
||||
ctrl_u2ms_dequeue_req(&key, &process, &vaddr_range, &zero_terminated);
|
||||
ProfBegin("memory stream request");
|
||||
|
||||
//- rjf: unpack process key
|
||||
U64 process_hash = ctrl_hash_from_handle(process);
|
||||
U64 process_slot_idx = process_hash%cache->slots_count;
|
||||
U64 process_stripe_idx = process_slot_idx%cache->stripes_count;
|
||||
CTRL_ProcessMemoryCacheSlot *process_slot = &cache->slots[process_slot_idx];
|
||||
CTRL_ProcessMemoryCacheStripe *process_stripe = &cache->stripes[process_stripe_idx];
|
||||
|
||||
//- rjf: unpack address range hash cache key
|
||||
U64 range_hash = u64_hash_from_str8(str8_struct(&key.id));
|
||||
|
||||
//- rjf: clamp vaddr range
|
||||
Rng1U64 vaddr_range_clamped = vaddr_range;
|
||||
{
|
||||
vaddr_range_clamped.max = Max(vaddr_range_clamped.max, vaddr_range_clamped.min);
|
||||
U64 max_size_cap = Min(max_U64-vaddr_range_clamped.min, GB(1));
|
||||
vaddr_range_clamped.max = Min(vaddr_range_clamped.max, vaddr_range_clamped.min+max_size_cap);
|
||||
}
|
||||
|
||||
//- rjf: task was taken -> read memory
|
||||
U64 range_size = 0;
|
||||
Arena *range_arena = 0;
|
||||
void *range_base = 0;
|
||||
U64 zero_terminated_size = 0;
|
||||
U64 pre_read_mem_gen = ctrl_mem_gen();
|
||||
B32 pre_run_state = ins_atomic_u64_eval(&ctrl_state->ctrl_thread_run_state);
|
||||
#if CTRL_MEM_STREAM_WORK_DEBUG
|
||||
Log *log = log_alloc();
|
||||
log_select(log);
|
||||
log_scope_begin();
|
||||
#endif
|
||||
{
|
||||
range_size = dim_1u64(vaddr_range_clamped);
|
||||
U64 page_size = os_get_system_info()->page_size;
|
||||
U64 arena_size = AlignPow2(range_size + ARENA_HEADER_SIZE, page_size);
|
||||
range_arena = arena_alloc(.reserve_size = range_size+ARENA_HEADER_SIZE, .commit_size = range_size+ARENA_HEADER_SIZE);
|
||||
if(range_arena == 0)
|
||||
{
|
||||
range_size = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
range_base = push_array_no_zero(range_arena, U8, range_size);
|
||||
U64 bytes_read = 0;
|
||||
U64 retry_count = 0;
|
||||
U64 retry_limit = range_size > page_size ? 64 : 0;
|
||||
for(Rng1U64 vaddr_range_clamped_retry = vaddr_range_clamped;
|
||||
retry_count <= retry_limit;
|
||||
retry_count += 1)
|
||||
{
|
||||
bytes_read = dmn_process_read(process.dmn_handle, vaddr_range_clamped_retry, range_base);
|
||||
if(bytes_read == 0 && vaddr_range_clamped_retry.max > vaddr_range_clamped_retry.min)
|
||||
{
|
||||
U64 diff = (vaddr_range_clamped_retry.max-vaddr_range_clamped_retry.min)/2;
|
||||
vaddr_range_clamped_retry.max -= diff;
|
||||
vaddr_range_clamped_retry.max = AlignDownPow2(vaddr_range_clamped_retry.max, page_size);
|
||||
if(diff == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(bytes_read == 0)
|
||||
{
|
||||
arena_release(range_arena);
|
||||
range_base = 0;
|
||||
range_size = 0;
|
||||
range_arena = 0;
|
||||
}
|
||||
else if(bytes_read < range_size)
|
||||
{
|
||||
MemoryZero((U8 *)range_base + bytes_read, range_size-bytes_read);
|
||||
}
|
||||
zero_terminated_size = range_size;
|
||||
if(zero_terminated)
|
||||
{
|
||||
for(U64 idx = 0; idx < bytes_read; idx += 1)
|
||||
{
|
||||
if(((U8 *)range_base)[idx] == 0)
|
||||
{
|
||||
zero_terminated_size = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
U64 post_read_mem_gen = ctrl_mem_gen();
|
||||
B32 post_run_state = ins_atomic_u64_eval(&ctrl_state->ctrl_thread_run_state);
|
||||
// NOTE(rjf): debugging
|
||||
#if CTRL_MEM_STREAM_WORK_DEBUG
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
String8 sample_data_str = str8_lit("no data");
|
||||
if(range_base != 0)
|
||||
{
|
||||
String8 sample_data = str8((U8*)range_base + 0x100, 16);
|
||||
String8List sample_data_strs = numeric_str8_list_from_data(scratch.arena, 16, sample_data, 1);
|
||||
sample_data_str = str8_list_join(scratch.arena, &sample_data_strs, &(StringJoin){.sep = str8_lit(", ")});
|
||||
}
|
||||
LogScopeResult log = log_scope_end(scratch.arena);
|
||||
raddbg_log("[0x%I64x, 0x%I64x) { pre_gen: %I64u, post_gen: %I64u, pre_run: %i, post_run: %i, bytes: [%S], info:```%S``` }\n", vaddr_range.min, vaddr_range.max, pre_read_mem_gen, post_read_mem_gen, pre_run_state, post_run_state, sample_data_str, log.strings[LogMsgKind_Info]);
|
||||
scratch_end(scratch);
|
||||
}
|
||||
#endif
|
||||
|
||||
//- rjf: read successful -> submit to hash store
|
||||
U128 hash = {0};
|
||||
if(range_base != 0 && pre_read_mem_gen == post_read_mem_gen)
|
||||
{
|
||||
hash = c_submit_data(key, &range_arena, str8((U8*)range_base, zero_terminated_size));
|
||||
}
|
||||
else if(range_arena != 0)
|
||||
{
|
||||
arena_release(range_arena);
|
||||
}
|
||||
|
||||
//- rjf: commit new info to cache
|
||||
MutexScopeW(process_stripe->rw_mutex)
|
||||
{
|
||||
for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next)
|
||||
{
|
||||
if(ctrl_handle_match(n->handle, process))
|
||||
{
|
||||
U64 range_slot_idx = range_hash%n->range_hash_slots_count;
|
||||
CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx];
|
||||
for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next)
|
||||
{
|
||||
if(c_id_match(range_n->id, key.id))
|
||||
{
|
||||
if(pre_read_mem_gen == post_read_mem_gen)
|
||||
{
|
||||
range_n->mem_gen = post_read_mem_gen;
|
||||
}
|
||||
range_n->working_count -= 1;
|
||||
goto commit__break_all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
commit__break_all:;
|
||||
}
|
||||
|
||||
//- rjf: broadcast changes
|
||||
cond_var_broadcast(process_stripe->cv);
|
||||
if(!u128_match(u128_zero(), hash))
|
||||
{
|
||||
if(ctrl_state->wakeup_hook != 0)
|
||||
{
|
||||
ctrl_state->wakeup_hook();
|
||||
}
|
||||
}
|
||||
ProfEnd();
|
||||
ProfEnd();
|
||||
return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Process Memory Artifact Cache Hooks / Lookups
|
||||
|
||||
@@ -6995,7 +6272,7 @@ ctrl_memory_artifact_destroy(AC_Artifact artifact)
|
||||
}
|
||||
|
||||
internal C_Key
|
||||
ctrl_key_from_process_vaddr_range_new(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, U64 endt_us, B32 *out_is_stale)
|
||||
{
|
||||
ProfBeginFunction();
|
||||
struct
|
||||
@@ -7020,6 +6297,208 @@ ctrl_key_from_process_vaddr_range_new(CTRL_Handle process, Rng1U64 vaddr_range,
|
||||
return content_key;
|
||||
}
|
||||
|
||||
//- 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)
|
||||
{
|
||||
ProfBeginFunction();
|
||||
CTRL_ProcessMemorySlice result = {0};
|
||||
if(range.max > range.min &&
|
||||
dim_1u64(range) <= MB(256) &&
|
||||
range.min <= 0x000FFFFFFFFFFFFFull &&
|
||||
range.max <= 0x000FFFFFFFFFFFFFull)
|
||||
{
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
Access *access = access_open();
|
||||
|
||||
//- rjf: unpack address range, prepare per-touched-page info
|
||||
U64 page_size = KB(4);
|
||||
Rng1U64 page_range = r1u64(AlignDownPow2(range.min, page_size), AlignPow2(range.max, page_size));
|
||||
U64 page_count = dim_1u64(page_range)/page_size;
|
||||
U128 *page_hashes = push_array(scratch.arena, U128, page_count);
|
||||
U128 *page_last_hashes = push_array(scratch.arena, U128, page_count);
|
||||
|
||||
//- rjf: gather hashes & last-hashes for each page
|
||||
ProfScope("gather hashes & last-hashes for each page")
|
||||
{
|
||||
for(U64 page_idx = 0; page_idx < page_count; page_idx += 1)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
page_hashes[page_idx] = page_hash;
|
||||
page_last_hashes[page_idx] = page_last_hash;
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: setup output buffers
|
||||
void *read_out = push_array(arena, U8, dim_1u64(range));
|
||||
U64 *byte_bad_flags = push_array(arena, U64, (dim_1u64(range)+63)/64);
|
||||
U64 *byte_changed_flags = push_array(arena, U64, (dim_1u64(range)+63)/64);
|
||||
|
||||
//- rjf: iterate pages, fill output
|
||||
ProfScope("iterate pages, fill output")
|
||||
{
|
||||
U64 write_off = 0;
|
||||
for(U64 page_idx = 0; page_idx < page_count; page_idx += 1)
|
||||
{
|
||||
// rjf: read data for this page
|
||||
String8 data = c_data_from_hash(access, page_hashes[page_idx]);
|
||||
Rng1U64 data_vaddr_range = r1u64(page_range.min + page_idx*page_size, page_range.min + page_idx*page_size+data.size);
|
||||
|
||||
// rjf: skip/chop bytes which are irrelevant for the actual requested read
|
||||
String8 in_range_data = data;
|
||||
if(page_idx == page_count-1 && data_vaddr_range.max > range.max)
|
||||
{
|
||||
in_range_data = str8_chop(in_range_data, data_vaddr_range.max-range.max);
|
||||
}
|
||||
if(page_idx == 0 && range.min > data_vaddr_range.min)
|
||||
{
|
||||
in_range_data = str8_skip(in_range_data, range.min-data_vaddr_range.min);
|
||||
}
|
||||
|
||||
// rjf: write this chunk
|
||||
MemoryCopy((U8*)read_out+write_off, in_range_data.str, in_range_data.size);
|
||||
|
||||
// rjf; if this page's data doesn't fill the entire range, mark
|
||||
// missing bytes as bad
|
||||
if(data.size < page_size) ProfScope("mark missing bytes as bad")
|
||||
{
|
||||
Rng1U64 invalid_range = r1u64(data_vaddr_range.min+data.size, data_vaddr_range.min + page_size);
|
||||
Rng1U64 in_range_invalid_range = intersect_1u64(invalid_range, range);
|
||||
for(U64 invalid_vaddr = in_range_invalid_range.min;
|
||||
invalid_vaddr < in_range_invalid_range.max;
|
||||
invalid_vaddr += 1)
|
||||
{
|
||||
U64 idx_in_range = invalid_vaddr - range.min;
|
||||
byte_bad_flags[idx_in_range/64] |= (1ull<<(idx_in_range%64));
|
||||
}
|
||||
}
|
||||
|
||||
// rjf: if this page's hash & last_hash don't match, diff each byte &
|
||||
// fill out changed flags
|
||||
if(!u128_match(page_hashes[page_idx], page_last_hashes[page_idx])) ProfScope("hashes don't match; diff each byte")
|
||||
{
|
||||
String8 last_data = c_data_from_hash(access, page_last_hashes[page_idx]);
|
||||
String8 in_range_last_data = last_data;
|
||||
if(page_idx == page_count-1 && data_vaddr_range.max > range.max)
|
||||
{
|
||||
in_range_last_data = str8_chop(in_range_last_data, data_vaddr_range.max-range.max);
|
||||
}
|
||||
if(page_idx == 0 && range.min > data_vaddr_range.min)
|
||||
{
|
||||
in_range_last_data = str8_skip(in_range_last_data, range.min-data_vaddr_range.min);
|
||||
}
|
||||
for(U64 idx = 0; idx < in_range_data.size; idx += 1)
|
||||
{
|
||||
U8 last_byte = idx < in_range_last_data.size ? in_range_last_data.str[idx] : 0;
|
||||
U8 now_byte = idx < in_range_data.size ? in_range_data.str[idx] : 0;
|
||||
if(last_byte != now_byte)
|
||||
{
|
||||
U64 idx_in_read_out = write_off+idx;
|
||||
byte_changed_flags[idx_in_read_out/64] |= (1ull<<(idx_in_read_out%64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rjf: increment past this chunk
|
||||
U64 bytes_to_skip = page_size;
|
||||
if(page_idx == 0 && range.min > data_vaddr_range.min)
|
||||
{
|
||||
bytes_to_skip -= (range.min-data_vaddr_range.min);
|
||||
}
|
||||
write_off += bytes_to_skip;
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: fill result
|
||||
result.data.str = (U8*)read_out;
|
||||
result.data.size = dim_1u64(range);
|
||||
result.byte_bad_flags = byte_bad_flags;
|
||||
result.byte_changed_flags = byte_changed_flags;
|
||||
if(byte_bad_flags != 0)
|
||||
{
|
||||
for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1)
|
||||
{
|
||||
result.any_byte_bad = result.any_byte_bad || !!result.byte_bad_flags[idx];
|
||||
}
|
||||
}
|
||||
if(byte_changed_flags != 0)
|
||||
{
|
||||
for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1)
|
||||
{
|
||||
result.any_byte_changed = result.any_byte_changed || !!result.byte_changed_flags[idx];
|
||||
}
|
||||
}
|
||||
|
||||
access_close(access);
|
||||
scratch_end(scratch);
|
||||
}
|
||||
ProfEnd();
|
||||
return result;
|
||||
}
|
||||
|
||||
internal B32
|
||||
ctrl_process_memory_read(CTRL_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us)
|
||||
{
|
||||
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);
|
||||
B32 good = (slice.data.size >= needed_size && !slice.any_byte_bad);
|
||||
if(good)
|
||||
{
|
||||
MemoryCopy(out, slice.data.str, needed_size);
|
||||
}
|
||||
if(slice.stale && is_stale_out)
|
||||
{
|
||||
*is_stale_out = 1;
|
||||
}
|
||||
scratch_end(scratch);
|
||||
return good;
|
||||
}
|
||||
|
||||
//- rjf: process memory writing
|
||||
|
||||
internal B32
|
||||
ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src)
|
||||
{
|
||||
ProfBeginFunction();
|
||||
B32 result = dmn_process_write(process.dmn_handle, range, src);
|
||||
|
||||
//- rjf: success -> bump generation
|
||||
if(result)
|
||||
{
|
||||
ins_atomic_u64_inc_eval(&ctrl_state->mem_gen);
|
||||
}
|
||||
|
||||
//- rjf: success -> wait for cache updates, for small regions - prefer relatively seamless
|
||||
// writes within calling frame's "view" of the memory, at the expense of a small amount of
|
||||
// time.
|
||||
if(result)
|
||||
{
|
||||
U64 endt_us = os_now_microseconds()+5000;
|
||||
U64 page_size = os_get_system_info()->page_size;
|
||||
Rng1U64 page_range = r1u64(range.min/page_size, range.max/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);
|
||||
scratch_end(scratch);
|
||||
if(os_now_microseconds() >= endt_us)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProfEnd();
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Call Stack Artifact Cache Hooks / Lookups
|
||||
|
||||
@@ -7200,7 +6679,7 @@ ctrl_call_stack_artifact_destroy(AC_Artifact artifact)
|
||||
}
|
||||
|
||||
internal CTRL_CallStack
|
||||
ctrl_call_stack_from_thread_new(Access *access, CTRL_Handle thread_handle, B32 high_priority, U64 endt_us)
|
||||
ctrl_call_stack_from_thread(Access *access, CTRL_Handle thread_handle, B32 high_priority, U64 endt_us)
|
||||
{
|
||||
CTRL_CallStack result = {0};
|
||||
{
|
||||
@@ -7254,7 +6733,7 @@ ctrl_call_stack_tree_artifact_create(String8 key, U64 gen, U64 *requested_gen, B
|
||||
{
|
||||
for EachIndex(idx, threads_count)
|
||||
{
|
||||
call_stacks[idx] = ctrl_call_stack_from_thread_new(access, threads[idx], 0, 0);
|
||||
call_stacks[idx] = ctrl_call_stack_from_thread(access, threads[idx], 0, 0);
|
||||
if(call_stacks[idx].concrete_frames_count == 0)
|
||||
{
|
||||
stale = 1;
|
||||
@@ -7350,7 +6829,7 @@ ctrl_call_stack_tree_artifact_destroy(AC_Artifact artifact)
|
||||
}
|
||||
|
||||
internal CTRL_CallStackTree
|
||||
ctrl_call_stack_tree_new(Access *access, U64 endt_us)
|
||||
ctrl_call_stack_tree(Access *access, U64 endt_us)
|
||||
{
|
||||
CTRL_CallStackTree result = {&ctrl_call_stack_tree_node_nil};
|
||||
{
|
||||
|
||||
+12
-138
@@ -535,68 +535,7 @@ struct CTRL_EventList
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Process Memory Cache Types
|
||||
|
||||
typedef struct CTRL_ProcessMemoryRangeHashNode CTRL_ProcessMemoryRangeHashNode;
|
||||
struct CTRL_ProcessMemoryRangeHashNode
|
||||
{
|
||||
CTRL_ProcessMemoryRangeHashNode *next;
|
||||
|
||||
// rjf: key
|
||||
Rng1U64 vaddr_range;
|
||||
B32 zero_terminated;
|
||||
C_ID id;
|
||||
|
||||
// rjf: staleness info
|
||||
U64 mem_gen;
|
||||
|
||||
// rjf: metadata
|
||||
U64 working_count;
|
||||
U64 last_time_requested_us;
|
||||
U64 last_user_clock_idx_touched;
|
||||
};
|
||||
|
||||
typedef struct CTRL_ProcessMemoryRangeHashSlot CTRL_ProcessMemoryRangeHashSlot;
|
||||
struct CTRL_ProcessMemoryRangeHashSlot
|
||||
{
|
||||
CTRL_ProcessMemoryRangeHashNode *first;
|
||||
CTRL_ProcessMemoryRangeHashNode *last;
|
||||
};
|
||||
|
||||
typedef struct CTRL_ProcessMemoryCacheNode CTRL_ProcessMemoryCacheNode;
|
||||
struct CTRL_ProcessMemoryCacheNode
|
||||
{
|
||||
CTRL_ProcessMemoryCacheNode *next;
|
||||
CTRL_ProcessMemoryCacheNode *prev;
|
||||
Arena *arena;
|
||||
CTRL_Handle handle;
|
||||
C_Root root;
|
||||
U64 range_hash_slots_count;
|
||||
CTRL_ProcessMemoryRangeHashSlot *range_hash_slots;
|
||||
};
|
||||
|
||||
typedef struct CTRL_ProcessMemoryCacheSlot CTRL_ProcessMemoryCacheSlot;
|
||||
struct CTRL_ProcessMemoryCacheSlot
|
||||
{
|
||||
CTRL_ProcessMemoryCacheNode *first;
|
||||
CTRL_ProcessMemoryCacheNode *last;
|
||||
};
|
||||
|
||||
typedef struct CTRL_ProcessMemoryCacheStripe CTRL_ProcessMemoryCacheStripe;
|
||||
struct CTRL_ProcessMemoryCacheStripe
|
||||
{
|
||||
RWMutex rw_mutex;
|
||||
CondVar cv;
|
||||
};
|
||||
|
||||
typedef struct CTRL_ProcessMemoryCache CTRL_ProcessMemoryCache;
|
||||
struct CTRL_ProcessMemoryCache
|
||||
{
|
||||
U64 slots_count;
|
||||
CTRL_ProcessMemoryCacheSlot *slots;
|
||||
U64 stripes_count;
|
||||
CTRL_ProcessMemoryCacheStripe *stripes;
|
||||
};
|
||||
//~ rjf: Process Memory Types
|
||||
|
||||
typedef struct CTRL_ProcessMemorySlice CTRL_ProcessMemorySlice;
|
||||
struct CTRL_ProcessMemorySlice
|
||||
@@ -688,22 +627,6 @@ struct CTRL_ModuleImageInfoCache
|
||||
CTRL_ModuleImageInfoCacheStripe *stripes;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Call Stack Tree Cache Types
|
||||
|
||||
typedef struct CTRL_CallStackTreeCache CTRL_CallStackTreeCache;
|
||||
struct CTRL_CallStackTreeCache
|
||||
{
|
||||
Arena *arena;
|
||||
CTRL_CallStackTree tree;
|
||||
CondVar cv;
|
||||
RWMutex rw_mutex;
|
||||
U64 reg_gen;
|
||||
U64 mem_gen;
|
||||
U64 scope_touch_count;
|
||||
U64 request_count;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Touched Debug Info Directory Cache
|
||||
|
||||
@@ -753,22 +676,6 @@ typedef CTRL_WAKEUP_FUNCTION_DEF(CTRL_WakeupFunctionType);
|
||||
////////////////////////////////
|
||||
//~ rjf: Main State Types
|
||||
|
||||
typedef struct CTRL_MemRequest CTRL_MemRequest;
|
||||
struct CTRL_MemRequest
|
||||
{
|
||||
C_Key key;
|
||||
CTRL_Handle process;
|
||||
Rng1U64 vaddr_range;
|
||||
B32 zero_terminated;
|
||||
};
|
||||
|
||||
typedef struct CTRL_MemRequestNode CTRL_MemRequestNode;
|
||||
struct CTRL_MemRequestNode
|
||||
{
|
||||
CTRL_MemRequestNode *next;
|
||||
CTRL_MemRequest v;
|
||||
};
|
||||
|
||||
typedef struct CTRL_State CTRL_State;
|
||||
struct CTRL_State
|
||||
{
|
||||
@@ -780,10 +687,8 @@ struct CTRL_State
|
||||
E_String2NumMap arch_string2alias_tables[Arch_COUNT];
|
||||
|
||||
// rjf: caches
|
||||
CTRL_ProcessMemoryCache process_memory_cache;
|
||||
CTRL_ThreadRegCache thread_reg_cache;
|
||||
CTRL_ModuleImageInfoCache module_image_info_cache;
|
||||
CTRL_CallStackTreeCache call_stack_tree_cache;
|
||||
|
||||
// rjf: generations
|
||||
U64 run_gen;
|
||||
@@ -830,21 +735,6 @@ struct CTRL_State
|
||||
CTRL_ModuleReqCacheNode **module_req_cache_slots;
|
||||
String8List msg_user_bp_touched_files;
|
||||
String8List msg_user_bp_touched_symbols;
|
||||
|
||||
// rjf: memory requests
|
||||
Mutex mem_req_mutex;
|
||||
Arena *mem_req_arena;
|
||||
CTRL_MemRequestNode *first_mem_req;
|
||||
CTRL_MemRequestNode *last_mem_req;
|
||||
U64 mem_req_count;
|
||||
|
||||
// rjf: user -> memstream ring buffer
|
||||
U64 u2ms_ring_size;
|
||||
U8 *u2ms_ring_base;
|
||||
U64 u2ms_ring_write_pos;
|
||||
U64 u2ms_ring_read_pos;
|
||||
Mutex u2ms_ring_mutex;
|
||||
CondVar u2ms_ring_cv;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
@@ -1003,20 +893,6 @@ internal void ctrl_init(void);
|
||||
|
||||
internal void ctrl_set_wakeup_hook(CTRL_WakeupFunctionType *wakeup_hook);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Process Memory Functions
|
||||
|
||||
//- rjf: process memory cache key reading
|
||||
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);
|
||||
|
||||
//- rjf: process memory cache reading helpers
|
||||
internal CTRL_ProcessMemorySlice ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, 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))
|
||||
|
||||
//- rjf: process memory writing
|
||||
internal B32 ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Thread Register Functions
|
||||
|
||||
@@ -1123,35 +999,33 @@ internal void ctrl_thread__detach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg);
|
||||
internal void ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg);
|
||||
internal void ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Asynchronous Memory Streaming Functions
|
||||
|
||||
//- rjf: user -> memory stream communication
|
||||
internal B32 ctrl_u2ms_enqueue_req(C_Key key, CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, U64 endt_us);
|
||||
internal void ctrl_u2ms_dequeue_req(C_Key *out_key, CTRL_Handle *out_process, Rng1U64 *out_vaddr_range, B32 *out_zero_terminated);
|
||||
|
||||
//- rjf: entry point
|
||||
ASYNC_WORK_DEF(ctrl_mem_stream_work);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Process Memory Artifact Cache Hooks / Lookups
|
||||
|
||||
internal AC_Artifact ctrl_memory_artifact_create(String8 key, U64 gen, U64 *requested_gen, B32 *retry_out);
|
||||
internal void ctrl_memory_artifact_destroy(AC_Artifact artifact);
|
||||
internal C_Key ctrl_key_from_process_vaddr_range_new(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, 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 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))
|
||||
|
||||
//- rjf: process memory writing
|
||||
internal B32 ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Call Stack Artifact Cache Hooks / Lookups
|
||||
|
||||
internal AC_Artifact ctrl_call_stack_artifact_create(String8 key, U64 gen, U64 *requested_gen, B32 *retry_out);
|
||||
internal void ctrl_call_stack_artifact_destroy(AC_Artifact artifact);
|
||||
internal CTRL_CallStack ctrl_call_stack_from_thread_new(Access *access, CTRL_Handle thread_handle, B32 high_priority, U64 endt_us);
|
||||
internal CTRL_CallStack ctrl_call_stack_from_thread(Access *access, CTRL_Handle thread_handle, B32 high_priority, U64 endt_us);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Call Stack Tree Artifact Cache Hooks / Lookups
|
||||
|
||||
internal AC_Artifact ctrl_call_stack_tree_artifact_create(String8 key, U64 gen, U64 *requested_gen, B32 *retry_out);
|
||||
internal void ctrl_call_stack_tree_artifact_destroy(AC_Artifact artifact);
|
||||
internal CTRL_CallStackTree ctrl_call_stack_tree_new(Access *access, U64 endt_us);
|
||||
internal CTRL_CallStackTree ctrl_call_stack_tree(Access *access, U64 endt_us);
|
||||
|
||||
#endif // CTRL_CORE_H
|
||||
|
||||
@@ -1209,7 +1209,7 @@ d_query_cached_rip_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count)
|
||||
else
|
||||
{
|
||||
Access *access = access_open();
|
||||
CTRL_CallStack callstack = ctrl_call_stack_from_thread_new(access, thread->handle, 1, 0);
|
||||
CTRL_CallStack callstack = ctrl_call_stack_from_thread(access, thread->handle, 1, 0);
|
||||
if(callstack.concrete_frames_count != 0)
|
||||
{
|
||||
result = regs_rip_from_arch_block(thread->arch, callstack.concrete_frames[unwind_count%callstack.concrete_frames_count]->regs);
|
||||
@@ -1889,7 +1889,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P
|
||||
Access *access = access_open();
|
||||
|
||||
// rjf: thread => call stack
|
||||
CTRL_CallStack callstack = ctrl_call_stack_from_thread_new(access, thread->handle, 1, os_now_microseconds()+10000);
|
||||
CTRL_CallStack callstack = ctrl_call_stack_from_thread(access, thread->handle, 1, os_now_microseconds()+10000);
|
||||
|
||||
// rjf: use first unwind frame to generate trap
|
||||
if(callstack.concrete_frames_count > 1)
|
||||
|
||||
@@ -1817,7 +1817,7 @@ rd_eval_space_read(void *u, E_Space space, void *out, Rng1U64 range)
|
||||
case CTRL_EntityKind_Thread:
|
||||
{
|
||||
Access *access = access_open();
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread_new(access, entity->handle, 1, rd_state->frame_eval_memread_endt_us);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread(access, entity->handle, 1, rd_state->frame_eval_memread_endt_us);
|
||||
U64 concrete_frame_idx = e_interpret_ctx->reg_unwind_count;
|
||||
if(concrete_frame_idx < call_stack.concrete_frames_count)
|
||||
{
|
||||
@@ -2160,7 +2160,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_new(entity->handle, range, zero_terminated, 0, 0);
|
||||
result = ctrl_key_from_process_vaddr_range(entity->handle, range, zero_terminated, 0, 0);
|
||||
}
|
||||
}break;
|
||||
}
|
||||
@@ -6469,7 +6469,7 @@ rd_window_frame(void)
|
||||
Vec4F32 symbol_color = ui_color_from_name(str8_lit("code_symbol"));
|
||||
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(ctrl_entity, CTRL_EntityKind_Process);
|
||||
B32 call_stack_high_priority = ctrl_handle_match(ctrl_entity->handle, rd_base_regs()->thread);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread_new(access, ctrl_entity->handle, call_stack_high_priority, call_stack_high_priority ? rd_state->frame_eval_memread_endt_us : 0);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread(access, ctrl_entity->handle, call_stack_high_priority, call_stack_high_priority ? rd_state->frame_eval_memread_endt_us : 0);
|
||||
if(call_stack.frames_count != 0)
|
||||
{
|
||||
ui_spacer(ui_em(1.5f, 1.f));
|
||||
@@ -16258,7 +16258,7 @@ rd_frame(void)
|
||||
{
|
||||
Access *access = access_open();
|
||||
CTRL_Entity *thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, rd_base_regs()->thread);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread_new(access, thread->handle, 1, os_now_microseconds()+10000);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread(access, thread->handle, 1, os_now_microseconds()+10000);
|
||||
CTRL_CallStackFrame *frame = ctrl_call_stack_frame_from_unwind_and_inline_depth(&call_stack, rd_regs()->unwind_count, rd_regs()->inline_depth);
|
||||
if(frame == 0)
|
||||
{
|
||||
@@ -16277,7 +16277,7 @@ rd_frame(void)
|
||||
{
|
||||
Access *access = access_open();
|
||||
CTRL_Entity *thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, rd_base_regs()->thread);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread_new(access, thread->handle, 1, os_now_microseconds()+10000);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread(access, thread->handle, 1, os_now_microseconds()+10000);
|
||||
CTRL_CallStackFrame *current_frame = ctrl_call_stack_frame_from_unwind_and_inline_depth(&call_stack, rd_regs()->unwind_count, rd_regs()->inline_depth);
|
||||
CTRL_CallStackFrame *next_frame = current_frame;
|
||||
if(current_frame != 0) switch(kind)
|
||||
|
||||
@@ -968,7 +968,7 @@ E_TYPE_IREXT_FUNCTION_DEF(call_stack)
|
||||
B32 call_stack_high_priority = ctrl_handle_match(entity->handle, rd_base_regs()->thread);
|
||||
accel->arch = entity->arch;
|
||||
accel->process = ctrl_process_from_entity(entity)->handle;
|
||||
accel->call_stack = ctrl_call_stack_from_thread_new(rd_state->frame_access, entity->handle, call_stack_high_priority, call_stack_high_priority ? rd_state->frame_eval_memread_endt_us : 0);
|
||||
accel->call_stack = ctrl_call_stack_from_thread(rd_state->frame_access, entity->handle, call_stack_high_priority, call_stack_high_priority ? rd_state->frame_eval_memread_endt_us : 0);
|
||||
}
|
||||
scratch_end(scratch);
|
||||
}
|
||||
@@ -1571,7 +1571,7 @@ E_TYPE_EXPAND_INFO_FUNCTION_DEF(call_stack_tree)
|
||||
if(!rd_state->got_frame_call_stack_tree)
|
||||
{
|
||||
rd_state->got_frame_call_stack_tree = 1;
|
||||
rd_state->frame_call_stack_tree = ctrl_call_stack_tree_new(rd_state->frame_access, 0);
|
||||
rd_state->frame_call_stack_tree = ctrl_call_stack_tree(rd_state->frame_access, 0);
|
||||
}
|
||||
RD_CallStackTreeExpandAccel *accel = push_array(arena, RD_CallStackTreeExpandAccel, 1);
|
||||
accel->node = &ctrl_call_stack_tree_node_nil;
|
||||
|
||||
@@ -1030,7 +1030,7 @@ rd_watch_row_info_from_row(Arena *arena, EV_Row *row)
|
||||
info.callstack_thread = entity;
|
||||
U64 frame_num = ev_block_num_from_id(block, key.child_id);
|
||||
B32 call_stack_high_priority = ctrl_handle_match(entity->handle, rd_base_regs()->thread);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread_new(access, entity->handle, call_stack_high_priority, call_stack_high_priority ? rd_state->frame_eval_memread_endt_us : 0);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread(access, entity->handle, call_stack_high_priority, call_stack_high_priority ? rd_state->frame_eval_memread_endt_us : 0);
|
||||
if(1 <= frame_num && frame_num <= call_stack.frames_count)
|
||||
{
|
||||
CTRL_CallStackFrame *f = &call_stack.frames[frame_num-1];
|
||||
@@ -2933,7 +2933,7 @@ RD_VIEW_UI_FUNCTION_DEF(memory)
|
||||
Access *access = access_open();
|
||||
CTRL_Entity *selected_thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, rd_regs()->thread);
|
||||
CTRL_Entity *selected_process = ctrl_entity_ancestor_from_kind(selected_thread, CTRL_EntityKind_Process);
|
||||
CTRL_CallStack selected_call_stack = ctrl_call_stack_from_thread_new(access, selected_thread->handle, 1, 0);
|
||||
CTRL_CallStack selected_call_stack = ctrl_call_stack_from_thread(access, selected_thread->handle, 1, 0);
|
||||
CTRL_Entity *eval_process = &ctrl_entity_nil;
|
||||
if(eval.space.kind == RD_EvalSpaceKind_CtrlEntity)
|
||||
{
|
||||
|
||||
@@ -552,7 +552,7 @@ rd_title_fstrs_from_ctrl_entity(Arena *arena, CTRL_Entity *entity, B32 include_e
|
||||
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(entity, CTRL_EntityKind_Process);
|
||||
Arch arch = entity->arch;
|
||||
B32 call_stack_high_priority = ctrl_handle_match(entity->handle, rd_base_regs()->thread);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread_new(access, entity->handle, call_stack_high_priority, call_stack_high_priority ? rd_state->frame_eval_memread_endt_us : 0);
|
||||
CTRL_CallStack call_stack = ctrl_call_stack_from_thread(access, entity->handle, call_stack_high_priority, call_stack_high_priority ? rd_state->frame_eval_memread_endt_us : 0);
|
||||
B32 did_first_known = 0;
|
||||
for(U64 idx = 0, limit = 10;
|
||||
idx < call_stack.frames_count && idx < limit;
|
||||
|
||||
Reference in New Issue
Block a user