// Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) #undef MARKUP_LAYER_COLOR #define MARKUP_LAYER_COLOR 0.70f, 0.50f, 0.25f //////////////////////////////// //~ rjf: Generated Code #include "dbg_engine/generated/dbg_engine.meta.c" //////////////////////////////// //~ rjf: Basic Helpers internal U64 d_hash_from_seed_string(U64 seed, String8 string) { U64 result = seed; for(U64 i = 0; i < string.size; i += 1) { result = ((result << 5) + result) + string.str[i]; } return result; } internal U64 d_hash_from_string(String8 string) { return d_hash_from_seed_string(5381, string); } internal U64 d_hash_from_seed_string__case_insensitive(U64 seed, String8 string) { U64 result = seed; for(U64 i = 0; i < string.size; i += 1) { result = ((result << 5) + result) + char_to_lower(string.str[i]); } return result; } internal U64 d_hash_from_string__case_insensitive(String8 string) { return d_hash_from_seed_string__case_insensitive(5381, string); } //////////////////////////////// //~ rjf: Handles internal D_Handle d_handle_zero(void) { D_Handle result = {0}; return result; } internal B32 d_handle_match(D_Handle a, D_Handle b) { return (a.u64[0] == b.u64[0] && a.u64[1] == b.u64[1]); } internal void d_handle_list_push_node(D_HandleList *list, D_HandleNode *node) { DLLPushBack(list->first, list->last, node); list->count += 1; } internal void d_handle_list_push(Arena *arena, D_HandleList *list, D_Handle handle) { D_HandleNode *n = push_array(arena, D_HandleNode, 1); n->handle = handle; d_handle_list_push_node(list, n); } internal void d_handle_list_remove(D_HandleList *list, D_HandleNode *node) { DLLRemove(list->first, list->last, node); list->count -= 1; } internal D_HandleNode * d_handle_list_find(D_HandleList *list, D_Handle handle) { D_HandleNode *result = 0; for(D_HandleNode *n = list->first; n != 0; n = n->next) { if(d_handle_match(n->handle, handle)) { result = n; break; } } return result; } internal D_HandleList d_handle_list_copy(Arena *arena, D_HandleList list) { D_HandleList result = {0}; for(D_HandleNode *n = list.first; n != 0; n = n->next) { d_handle_list_push(arena, &result, n->handle); } return result; } //////////////////////////////// //~ rjf: Registers Type Pure Functions internal void d_regs_copy_contents(Arena *arena, D_Regs *dst, D_Regs *src) { MemoryCopyStruct(dst, src); dst->entity_list = d_handle_list_copy(arena, src->entity_list); dst->file_path = push_str8_copy(arena, src->file_path); dst->lines = d_line_list_copy(arena, &src->lines); dst->dbgi_key = di_key_copy(arena, &src->dbgi_key); dst->string = push_str8_copy(arena, src->string); dst->params_tree = md_tree_copy(arena, src->params_tree); if(dst->entity_list.count == 0 && !d_handle_match(d_handle_zero(), dst->entity)) { d_handle_list_push(arena, &dst->entity_list, dst->entity); } } internal D_Regs * d_regs_copy(Arena *arena, D_Regs *src) { D_Regs *dst = push_array(arena, D_Regs, 1); d_regs_copy_contents(arena, dst, src); return dst; } //////////////////////////////// //~ rjf: State History Data Structure internal D_StateDeltaHistory * d_state_delta_history_alloc(void) { Arena *arena = arena_alloc(); D_StateDeltaHistory *hist = push_array(arena, D_StateDeltaHistory, 1); hist->arena = arena; for(Side side = (Side)0; side < Side_COUNT; side = (Side)(side+1)) { hist->side_arenas[side] = arena_alloc(); } return hist; } internal void d_state_delta_history_release(D_StateDeltaHistory *hist) { for(Side side = (Side)0; side < Side_COUNT; side = (Side)(side+1)) { arena_release(hist->side_arenas[side]); } arena_release(hist->arena); } internal void d_state_delta_history_batch_begin(D_StateDeltaHistory *hist) { if(hist == 0) { return; } if(hist->side_arenas[Side_Max] != 0) { arena_clear(hist->side_arenas[Side_Max]); hist->side_tops[Side_Max] = 0; } D_StateDeltaBatch *batch = push_array(hist->side_arenas[Side_Min], D_StateDeltaBatch, 1); SLLStackPush(hist->side_tops[Side_Min], batch); hist->batch_is_active = 1; } internal void d_state_delta_history_batch_end(D_StateDeltaHistory *hist) { if(hist == 0) { return; } hist->batch_is_active = 0; } internal void d_state_delta_history_push_delta_(D_StateDeltaHistory *hist, D_StateDeltaParams *params) { if(hist == 0) { return; } D_StateDeltaBatch *batch = hist->side_tops[Side_Min]; if(batch == 0 || hist->batch_is_active == 0) { return; } D_StateDeltaNode *n = push_array(hist->side_arenas[Side_Min], D_StateDeltaNode, 1); SLLQueuePush(batch->first, batch->last, n); n->v.guard_entity = d_handle_from_entity(params->guard_entity); n->v.vaddr = (U64)params->ptr; n->v.data = push_str8_copy(hist->arena, str8((U8*)params->ptr, params->size)); } internal void d_state_delta_history_wind(D_StateDeltaHistory *hist, Side side) { if(hist == 0) { return; } B32 done = 0; for(D_StateDeltaBatch *src_batch = hist->side_tops[side]; src_batch != 0 && !done; src_batch = hist->side_tops[side]) { U64 pop_pos = (U64)hist->side_tops[side] - (U64)hist->side_arenas[side]; SLLStackPop(hist->side_tops[side]); { D_StateDeltaBatch *dst_batch = push_array(hist->side_arenas[side_flip(side)], D_StateDeltaBatch, 1); SLLStackPush(hist->side_tops[side_flip(side)], dst_batch); for(D_StateDeltaNode *src_n = src_batch->first; src_n != 0; src_n = src_n->next) { D_StateDelta *src_delta = &src_n->v; B32 handle_is_good = (d_handle_match(src_delta->guard_entity, d_handle_zero()) || !d_entity_is_nil(d_entity_from_handle(src_delta->guard_entity))); if(handle_is_good) { D_StateDeltaNode *dst_n = push_array(hist->side_arenas[side_flip(side)], D_StateDeltaNode, 1); SLLQueuePush(dst_batch->first, dst_batch->last, dst_n); dst_n->v.vaddr = src_delta->vaddr; dst_n->v.data = push_str8_copy(hist->side_arenas[side_flip(side)], str8((U8 *)src_delta->vaddr, src_delta->data.size)); MemoryCopy((void *)src_delta->vaddr, src_delta->data.str, src_delta->data.size); done = 1; } } } arena_pop_to(hist->side_arenas[side], pop_pos); } } //////////////////////////////// //~ rjf: Sparse Tree Expansion State Data Structure //- rjf: keys internal D_ExpandKey d_expand_key_make(U64 parent_hash, U64 child_num) { D_ExpandKey key; { key.parent_hash = parent_hash; key.child_num = child_num; } return key; } internal D_ExpandKey d_expand_key_zero(void) { D_ExpandKey key = {0}; return key; } internal B32 d_expand_key_match(D_ExpandKey a, D_ExpandKey b) { return MemoryMatchStruct(&a, &b); } internal U64 df_hash_from_expand_key(D_ExpandKey key) { U64 data[] = { key.child_num, }; U64 hash = d_hash_from_seed_string(key.parent_hash, str8((U8 *)data, sizeof(data))); return hash; } //- rjf: table internal void d_expand_tree_table_init(Arena *arena, D_ExpandTreeTable *table, U64 slot_count) { MemoryZeroStruct(table); table->slots_count = slot_count; table->slots = push_array(arena, D_ExpandSlot, table->slots_count); } internal D_ExpandNode * d_expand_node_from_key(D_ExpandTreeTable *table, D_ExpandKey key) { U64 hash = df_hash_from_expand_key(key); U64 slot_idx = hash%table->slots_count; D_ExpandSlot *slot = &table->slots[slot_idx]; D_ExpandNode *node = 0; for(D_ExpandNode *n = slot->first; n != 0; n = n->hash_next) { if(d_expand_key_match(n->key, key)) { node = n; break; } } return node; } internal B32 d_expand_key_is_set(D_ExpandTreeTable *table, D_ExpandKey key) { D_ExpandNode *node = d_expand_node_from_key(table, key); return (node != 0 && node->expanded); } internal void d_expand_set_expansion(Arena *arena, D_ExpandTreeTable *table, D_ExpandKey parent_key, D_ExpandKey key, B32 expanded) { // rjf: map keys => nodes D_ExpandNode *parent_node = d_expand_node_from_key(table, parent_key); D_ExpandNode *node = d_expand_node_from_key(table, key); // rjf: make node if we don't have one, and we need one if(node == 0 && expanded) { node = table->free_node; if(node != 0) { table->free_node = table->free_node->next; MemoryZeroStruct(node); } else { node = push_array(arena, D_ExpandNode, 1); } // rjf: link into table U64 hash = df_hash_from_expand_key(key); U64 slot = hash % table->slots_count; DLLPushBack_NP(table->slots[slot].first, table->slots[slot].last, node, hash_next, hash_prev); // rjf: link into parent if(parent_node != 0) { D_ExpandNode *prev = 0; for(D_ExpandNode *n = parent_node->first; n != 0; n = n->next) { if(n->key.child_num < key.child_num) { prev = n; } else { break; } } DLLInsert_NP(parent_node->first, parent_node->last, prev, node, next, prev); node->parent = parent_node; } } // rjf: fill if(node != 0) { node->key = key; node->expanded = expanded; } // rjf: unlink node & free if we don't need it anymore if(expanded == 0 && node != 0 && node->first == 0) { // rjf: unlink from table U64 hash = df_hash_from_expand_key(key); U64 slot = hash % table->slots_count; DLLRemove_NP(table->slots[slot].first, table->slots[slot].last, node, hash_next, hash_prev); // rjf: unlink from tree if(parent_node != 0) { DLLRemove_NP(parent_node->first, parent_node->last, node, next, prev); } // rjf: free node->next = table->free_node; table->free_node = node; } } //////////////////////////////// //~ rjf: Config Type Functions internal void d_cfg_table_push_unparsed_string(Arena *arena, D_CfgTable *table, String8 string, D_CfgSrc source) { if(table->slot_count == 0) { table->slot_count = 64; table->slots = push_array(arena, D_CfgSlot, table->slot_count); } MD_TokenizeResult tokenize = md_tokenize_from_text(arena, string); MD_ParseResult parse = md_parse_from_text_tokens(arena, str8_lit(""), string, tokenize.tokens); for(MD_EachNode(tln, parse.root->first)) if(tln->string.size != 0) { // rjf: map string -> hash*slot String8 string = str8(tln->string.str, tln->string.size); U64 hash = d_hash_from_string__case_insensitive(string); U64 slot_idx = hash % table->slot_count; D_CfgSlot *slot = &table->slots[slot_idx]; // rjf: find existing value for this string D_CfgVal *val = 0; for(D_CfgVal *v = slot->first; v != 0; v = v->hash_next) { if(str8_match(v->string, string, StringMatchFlag_CaseInsensitive)) { val = v; break; } } // rjf: create new value if needed if(val == 0) { val = push_array(arena, D_CfgVal, 1); val->string = push_str8_copy(arena, string); val->insertion_stamp = table->insertion_stamp_counter; SLLStackPush_N(slot->first, val, hash_next); SLLQueuePush_N(table->first_val, table->last_val, val, linear_next); table->insertion_stamp_counter += 1; } // rjf: create new node within this value D_CfgTree *tree = push_array(arena, D_CfgTree, 1); SLLQueuePush_NZ(&d_nil_cfg_tree, val->first, val->last, tree, next); tree->source = source; tree->root = md_tree_copy(arena, tln); } } internal D_CfgTable d_cfg_table_from_inheritance(Arena *arena, D_CfgTable *src) { D_CfgTable dst_ = {0}; D_CfgTable *dst = &dst_; { dst->slot_count = src->slot_count; dst->slots = push_array(arena, D_CfgSlot, dst->slot_count); } for(D_CfgVal *src_val = src->first_val; src_val != 0 && src_val != &d_nil_cfg_val; src_val = src_val->linear_next) { D_ViewRuleSpec *spec = d_view_rule_spec_from_string(src_val->string); if(spec->info.flags & D_ViewRuleSpecInfoFlag_Inherited) { U64 hash = d_hash_from_string(spec->info.string); U64 dst_slot_idx = hash%dst->slot_count; D_CfgSlot *dst_slot = &dst->slots[dst_slot_idx]; D_CfgVal *dst_val = push_array(arena, D_CfgVal, 1); dst_val->first = src_val->first; dst_val->last = src_val->last; dst_val->string = src_val->string; dst_val->insertion_stamp = dst->insertion_stamp_counter; SLLStackPush_N(dst_slot->first, dst_val, hash_next); dst->insertion_stamp_counter += 1; } } return dst_; } internal D_CfgVal * d_cfg_val_from_string(D_CfgTable *table, String8 string) { D_CfgVal *result = &d_nil_cfg_val; if(table->slot_count != 0) { U64 hash = d_hash_from_string__case_insensitive(string); U64 slot_idx = hash % table->slot_count; D_CfgSlot *slot = &table->slots[slot_idx]; for(D_CfgVal *val = slot->first; val != 0; val = val->hash_next) { if(str8_match(val->string, string, StringMatchFlag_CaseInsensitive)) { result = val; break; } } } return result; } //////////////////////////////// //~ rjf: Debug Info Extraction Type Pure Functions internal D_LineList d_line_list_copy(Arena *arena, D_LineList *list) { D_LineList dst = {0}; for(D_LineNode *src_n = list->first; src_n != 0; src_n = src_n->next) { D_LineNode *dst_n = push_array(arena, D_LineNode, 1); MemoryCopyStruct(dst_n, src_n); dst_n->v.dbgi_key = di_key_copy(arena, &src_n->v.dbgi_key); SLLQueuePush(dst.first, dst.last, dst_n); dst.count += 1; } return dst; } //////////////////////////////// //~ rjf: Command Type Pure Functions //- rjf: specs internal B32 d_cmd_spec_is_nil(D_CmdSpec *spec) { return (spec == 0 || spec == &d_nil_cmd_spec); } internal void d_cmd_spec_list_push(Arena *arena, D_CmdSpecList *list, D_CmdSpec *spec) { D_CmdSpecNode *n = push_array(arena, D_CmdSpecNode, 1); n->spec = spec; SLLQueuePush(list->first, list->last, n); list->count += 1; } //- rjf: string -> command parsing internal String8 d_cmd_name_part_from_string(String8 string) { String8 result = string; for(U64 idx = 0; idx <= string.size; idx += 1) { if(idx == string.size || char_is_space(string.str[idx])) { result = str8_prefix(string, idx); break; } } return result; } internal String8 d_cmd_arg_part_from_string(String8 string) { String8 result = str8_lit(""); B32 found_space = 0; for(U64 idx = 0; idx <= string.size; idx += 1) { if(found_space && (idx == string.size || !char_is_space(string.str[idx]))) { result = str8_skip(string, idx); break; } else if(!found_space && (idx == string.size || char_is_space(string.str[idx]))) { found_space = 1; } } return result; } //- rjf: command parameter bundles internal D_CmdParams d_cmd_params_zero(void) { D_CmdParams p = {0}; return p; } internal String8 d_cmd_params_apply_spec_query(Arena *arena, D_CmdParams *params, D_CmdSpec *spec, String8 query) { String8 error = {0}; switch(spec->info.query.slot) { default: case D_CmdParamSlot_String: { params->string = push_str8_copy(arena, query); }break; case D_CmdParamSlot_FilePath: { String8TxtPtPair pair = str8_txt_pt_pair_from_string(query); params->file_path = push_str8_copy(arena, pair.string); params->text_point = pair.pt; }break; case D_CmdParamSlot_TextPoint: { U64 v = 0; if(try_u64_from_str8_c_rules(query, &v)) { params->text_point.column = 1; params->text_point.line = v; } else { error = str8_lit("Couldn't interpret as a line number."); } }break; case D_CmdParamSlot_VirtualAddr: goto use_numeric_eval; case D_CmdParamSlot_VirtualOff: goto use_numeric_eval; case D_CmdParamSlot_Index: goto use_numeric_eval; case D_CmdParamSlot_ID: goto use_numeric_eval; use_numeric_eval: { Temp scratch = scratch_begin(&arena, 1); E_Eval eval = e_eval_from_string(scratch.arena, query); if(eval.msgs.max_kind == E_MsgKind_Null) { E_TypeKind eval_type_kind = e_type_kind_from_key(e_type_unwrap(eval.type_key)); if(eval_type_kind == E_TypeKind_Ptr || eval_type_kind == E_TypeKind_LRef || eval_type_kind == E_TypeKind_RRef) { eval = e_value_eval_from_eval(eval); } U64 u64 = eval.value.u64; switch(spec->info.query.slot) { default:{}break; case D_CmdParamSlot_VirtualAddr: { params->vaddr = u64; }break; case D_CmdParamSlot_VirtualOff: { params->voff = u64; }break; case D_CmdParamSlot_Index: { params->index = u64; }break; case D_CmdParamSlot_UnwindIndex: { params->unwind_index = u64; }break; case D_CmdParamSlot_InlineDepth: { params->inline_depth = u64; }break; case D_CmdParamSlot_ID: { params->id = u64; }break; } } else { error = push_str8f(scratch.arena, "Couldn't evaluate \"%S\" as an address.", query); } scratch_end(scratch); }break; } return error; } //- rjf: command lists internal void d_cmd_list_push(Arena *arena, D_CmdList *cmds, D_CmdParams *params, D_CmdSpec *spec) { D_CmdNode *n = push_array(arena, D_CmdNode, 1); n->cmd.spec = spec; n->cmd.params = df_cmd_params_copy(arena, params); DLLPushBack(cmds->first, cmds->last, n); cmds->count += 1; } //- rjf: string -> core layer command kind internal D_CmdKind d_cmd_kind_from_string(String8 string) { D_CmdKind result = D_CmdKind_Null; for(U64 idx = 0; idx < ArrayCount(d_core_cmd_kind_spec_info_table); idx += 1) { if(str8_match(string, d_core_cmd_kind_spec_info_table[idx].string, StringMatchFlag_CaseInsensitive)) { result = (D_CmdKind)idx; break; } } return result; } //////////////////////////////// //~ rjf: Entity Functions //- rjf: nil internal B32 d_entity_is_nil(D_Entity *entity) { return (entity == 0 || entity == &d_nil_entity); } //- rjf: handle <-> entity conversions internal U64 d_index_from_entity(D_Entity *entity) { return (U64)(entity - d_state->entities_base); } internal D_Handle d_handle_from_entity(D_Entity *entity) { D_Handle handle = d_handle_zero(); if(!d_entity_is_nil(entity)) { handle.u64[0] = d_index_from_entity(entity); handle.u64[1] = entity->gen; } return handle; } internal D_Entity * d_entity_from_handle(D_Handle handle) { D_Entity *result = d_state->entities_base + handle.u64[0]; if(handle.u64[0] >= d_state->entities_count || result->gen != handle.u64[1]) { result = &d_nil_entity; } return result; } internal D_EntityList d_entity_list_from_handle_list(Arena *arena, D_HandleList handles) { D_EntityList result = {0}; for(D_HandleNode *n = handles.first; n != 0; n = n->next) { D_Entity *entity = d_entity_from_handle(n->handle); if(!d_entity_is_nil(entity)) { d_entity_list_push(arena, &result, entity); } } return result; } internal D_HandleList d_handle_list_from_entity_list(Arena *arena, D_EntityList entities) { D_HandleList result = {0}; for(D_EntityNode *n = entities.first; n != 0; n = n->next) { D_Handle handle = d_handle_from_entity(n->entity); d_handle_list_push(arena, &result, handle); } return result; } //- rjf: entity recursion iterators internal D_EntityRec d_entity_rec_depth_first(D_Entity *entity, D_Entity *subtree_root, U64 sib_off, U64 child_off) { D_EntityRec result = {0}; if(!d_entity_is_nil(*MemberFromOffset(D_Entity **, entity, child_off))) { result.next = *MemberFromOffset(D_Entity **, entity, child_off); result.push_count = 1; } else for(D_Entity *parent = entity; parent != subtree_root && !d_entity_is_nil(parent); parent = parent->parent) { if(parent != subtree_root && !d_entity_is_nil(*MemberFromOffset(D_Entity **, parent, sib_off))) { result.next = *MemberFromOffset(D_Entity **, parent, sib_off); break; } result.pop_count += 1; } return result; } //- rjf: ancestor/child introspection internal D_Entity * d_entity_child_from_kind(D_Entity *entity, D_EntityKind kind) { D_Entity *result = &d_nil_entity; for(D_Entity *child = entity->first; !d_entity_is_nil(child); child = child->next) { if(child->kind == kind) { result = child; break; } } return result; } internal D_Entity * d_entity_ancestor_from_kind(D_Entity *entity, D_EntityKind kind) { D_Entity *result = &d_nil_entity; for(D_Entity *p = entity->parent; !d_entity_is_nil(p); p = p->parent) { if(p->kind == kind) { result = p; break; } } return result; } internal D_EntityList d_push_entity_child_list_with_kind(Arena *arena, D_Entity *entity, D_EntityKind kind) { D_EntityList result = {0}; for(D_Entity *child = entity->first; !d_entity_is_nil(child); child = child->next) { if(child->kind == kind) { d_entity_list_push(arena, &result, child); } } return result; } internal D_Entity * d_entity_child_from_string_and_kind(D_Entity *parent, String8 string, D_EntityKind kind) { D_Entity *result = &d_nil_entity; for(D_Entity *child = parent->first; !d_entity_is_nil(child); child = child->next) { if(str8_match(child->string, string, 0) && child->kind == kind) { result = child; break; } } return result; } //- rjf: entity list building internal void d_entity_list_push(Arena *arena, D_EntityList *list, D_Entity *entity) { D_EntityNode *n = push_array(arena, D_EntityNode, 1); n->entity = entity; SLLQueuePush(list->first, list->last, n); list->count += 1; } internal D_EntityArray d_entity_array_from_list(Arena *arena, D_EntityList *list) { D_EntityArray result = {0}; result.count = list->count; result.v = push_array(arena, D_Entity *, result.count); U64 idx = 0; for(D_EntityNode *n = list->first; n != 0; n = n->next, idx += 1) { result.v[idx] = n->entity; } return result; } //- rjf: entity fuzzy list building internal D_EntityFuzzyItemArray d_entity_fuzzy_item_array_from_entity_list_needle(Arena *arena, D_EntityList *list, String8 needle) { Temp scratch = scratch_begin(&arena, 1); D_EntityArray array = d_entity_array_from_list(scratch.arena, list); D_EntityFuzzyItemArray result = d_entity_fuzzy_item_array_from_entity_array_needle(arena, &array, needle); return result; } internal D_EntityFuzzyItemArray d_entity_fuzzy_item_array_from_entity_array_needle(Arena *arena, D_EntityArray *array, String8 needle) { Temp scratch = scratch_begin(&arena, 1); D_EntityFuzzyItemArray result = {0}; result.count = array->count; result.v = push_array(arena, D_EntityFuzzyItem, result.count); U64 result_idx = 0; for(U64 src_idx = 0; src_idx < array->count; src_idx += 1) { D_Entity *entity = array->v[src_idx]; String8 display_string = d_display_string_from_entity(scratch.arena, entity); FuzzyMatchRangeList matches = fuzzy_match_find(arena, needle, display_string); if(matches.count >= matches.needle_part_count) { result.v[result_idx].entity = entity; result.v[result_idx].matches = matches; result_idx += 1; } else { String8 search_tags = d_search_tags_from_entity(scratch.arena, entity); if(search_tags.size != 0) { FuzzyMatchRangeList tag_matches = fuzzy_match_find(scratch.arena, needle, search_tags); if(tag_matches.count >= tag_matches.needle_part_count) { result.v[result_idx].entity = entity; result.v[result_idx].matches = matches; result_idx += 1; } } } } result.count = result_idx; scratch_end(scratch); return result; } //- rjf: full path building, from file/folder entities internal String8 d_full_path_from_entity(Arena *arena, D_Entity *entity) { String8 string = {0}; { Temp scratch = scratch_begin(&arena, 1); String8List strs = {0}; for(D_Entity *e = entity; !d_entity_is_nil(e); e = e->parent) { if(e->kind == D_EntityKind_File) { str8_list_push_front(scratch.arena, &strs, e->string); } } StringJoin join = {0}; join.sep = str8_lit("/"); string = str8_list_join(arena, &strs, &join); scratch_end(scratch); } return string; } //- rjf: display string entities, for referencing entities in ui internal String8 d_display_string_from_entity(Arena *arena, D_Entity *entity) { String8 result = {0}; switch(entity->kind) { default: { if(entity->string.size != 0) { result = push_str8_copy(arena, entity->string); } else { String8 kind_string = d_entity_kind_display_string_table[entity->kind]; result = push_str8f(arena, "%S $%I64u", kind_string, entity->id); } }break; case D_EntityKind_Target: { if(entity->string.size != 0) { result = push_str8_copy(arena, entity->string); } else { D_Entity *exe = d_entity_child_from_kind(entity, D_EntityKind_Executable); result = push_str8_copy(arena, exe->string); } }break; case D_EntityKind_Breakpoint: { if(entity->string.size != 0) { result = push_str8_copy(arena, entity->string); } else { D_Entity *loc = d_entity_child_from_kind(entity, D_EntityKind_Location); if(loc->flags & D_EntityFlag_HasTextPoint) { result = push_str8f(arena, "%S:%I64d:%I64d", str8_skip_last_slash(loc->string), loc->text_point.line, loc->text_point.column); } else if(loc->flags & D_EntityFlag_HasVAddr) { result = str8_from_u64(arena, loc->vaddr, 16, 16, 0); } else if(loc->string.size != 0) { result = push_str8_copy(arena, loc->string); } } }break; case D_EntityKind_Process: { D_Entity *main_mod_child = d_entity_child_from_kind(entity, D_EntityKind_Module); String8 main_mod_name = str8_skip_last_slash(main_mod_child->string); result = push_str8f(arena, "%S%s%sPID: %i%s", main_mod_name, main_mod_name.size != 0 ? " " : "", main_mod_name.size != 0 ? "(" : "", entity->ctrl_id, main_mod_name.size != 0 ? ")" : ""); }break; case D_EntityKind_Thread: { String8 name = entity->string; if(name.size == 0) { D_Entity *process = d_entity_ancestor_from_kind(entity, D_EntityKind_Process); D_Entity *first_thread = d_entity_child_from_kind(process, D_EntityKind_Thread); if(first_thread == entity) { name = str8_lit("Main Thread"); } } result = push_str8f(arena, "%S%s%sTID: %i%s", name, name.size != 0 ? " " : "", name.size != 0 ? "(" : "", entity->ctrl_id, name.size != 0 ? ")" : ""); }break; case D_EntityKind_Module: { result = push_str8_copy(arena, str8_skip_last_slash(entity->string)); }break; case D_EntityKind_RecentProject: { result = push_str8_copy(arena, str8_skip_last_slash(entity->string)); }break; } return result; } //- rjf: extra search tag strings for fuzzy filtering entities internal String8 d_search_tags_from_entity(Arena *arena, D_Entity *entity) { String8 result = {0}; if(entity->kind == D_EntityKind_Thread) { Temp scratch = scratch_begin(&arena, 1); D_Entity *process = d_entity_ancestor_from_kind(entity, D_EntityKind_Process); CTRL_Unwind unwind = d_query_cached_unwind_from_thread(entity); String8List strings = {0}; for(U64 frame_num = unwind.frames.count; frame_num > 0; frame_num -= 1) { CTRL_UnwindFrame *f = &unwind.frames.v[frame_num-1]; U64 rip_vaddr = regs_rip_from_arch_block(entity->arch, f->regs); D_Entity *module = d_module_from_process_vaddr(process, rip_vaddr); U64 rip_voff = d_voff_from_vaddr(module, rip_vaddr); DI_Key dbgi_key = d_dbgi_key_from_module(module); String8 procedure_name = d_symbol_name_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff, 0); if(procedure_name.size != 0) { str8_list_push(scratch.arena, &strings, procedure_name); } } StringJoin join = {0}; join.sep = str8_lit(","); result = str8_list_join(arena, &strings, &join); scratch_end(scratch); } return result; } //- rjf: entity -> color operations internal Vec4F32 d_hsva_from_entity(D_Entity *entity) { Vec4F32 result = {0}; if(entity->flags & D_EntityFlag_HasColor) { result = entity->color_hsva; } return result; } internal Vec4F32 d_rgba_from_entity(D_Entity *entity) { Vec4F32 result = {0}; if(entity->flags & D_EntityFlag_HasColor) { Vec3F32 hsv = v3f32(entity->color_hsva.x, entity->color_hsva.y, entity->color_hsva.z); Vec3F32 rgb = rgb_from_hsv(hsv); result = v4f32(rgb.x, rgb.y, rgb.z, entity->color_hsva.w); } return result; } //- rjf: entity -> expansion tree keys internal D_ExpandKey d_expand_key_from_entity(D_Entity *entity) { D_ExpandKey parent_key = d_parent_expand_key_from_entity(entity); D_ExpandKey key = d_expand_key_make(df_hash_from_expand_key(parent_key), (U64)entity); return key; } internal D_ExpandKey d_parent_expand_key_from_entity(D_Entity *entity) { D_ExpandKey parent_key = d_expand_key_make(5381, (U64)entity); return parent_key; } //- rjf: entity -> evaluation internal D_EntityEval * d_eval_from_entity(Arena *arena, D_Entity *entity) { D_EntityEval *eval = push_array(arena, D_EntityEval, 1); { D_Entity *loc = d_entity_child_from_kind(entity, D_EntityKind_Location); D_Entity *cnd = d_entity_child_from_kind(entity, D_EntityKind_Condition); String8 label_string = push_str8_copy(arena, entity->string); String8 loc_string = {0}; if(loc->flags & D_EntityFlag_HasTextPoint) { loc_string = push_str8f(arena, "%S:%I64u:%I64u", loc->string, loc->text_point.line, loc->text_point.column); } else if(loc->flags & D_EntityFlag_HasVAddr) { loc_string = push_str8f(arena, "0x%I64x", loc->vaddr); } String8 cnd_string = push_str8_copy(arena, cnd->string); eval->enabled = !entity->disabled; eval->hit_count = entity->u64; eval->label_off = (U64)((U8 *)label_string.str - (U8 *)eval); eval->location_off = (U64)((U8 *)loc_string.str - (U8 *)eval); eval->condition_off= (U64)((U8 *)cnd_string.str - (U8 *)eval); } return eval; } //////////////////////////////// //~ rjf: Name Allocation internal U64 d_name_bucket_idx_from_string_size(U64 size) { U64 size_rounded = u64_up_to_pow2(size+1); size_rounded = ClampBot((1<<4), size_rounded); U64 bucket_idx = 0; switch(size_rounded) { case 1<<4: {bucket_idx = 0;}break; case 1<<5: {bucket_idx = 1;}break; case 1<<6: {bucket_idx = 2;}break; case 1<<7: {bucket_idx = 3;}break; case 1<<8: {bucket_idx = 4;}break; case 1<<9: {bucket_idx = 5;}break; case 1<<10:{bucket_idx = 6;}break; default:{bucket_idx = ArrayCount(d_state->free_name_chunks)-1;}break; } return bucket_idx; } internal String8 d_name_alloc(String8 string) { if(string.size == 0) {return str8_zero();} U64 bucket_idx = d_name_bucket_idx_from_string_size(string.size); // rjf: loop -> find node, allocate if not there // // (we do a loop here so that all allocation logic goes through // the same path, such that we *always* pull off a free list, // rather than just using what was pushed onto an arena directly, // which is not undoable; the free lists we control, and are thus // trivially undoable) // D_NameChunkNode *node = 0; for(;node == 0;) { node = d_state->free_name_chunks[bucket_idx]; // rjf: pull from bucket free list if(node != 0) { if(bucket_idx == ArrayCount(d_state->free_name_chunks)-1) { node = 0; D_NameChunkNode *prev = 0; for(D_NameChunkNode *n = d_state->free_name_chunks[bucket_idx]; n != 0; prev = n, n = n->next) { if(n->size >= string.size+1) { if(prev == 0) { d_state_delta_history_push_struct_delta(d_state_delta_history(), &d_state->free_name_chunks[bucket_idx]); d_state->free_name_chunks[bucket_idx] = n->next; } else { d_state_delta_history_push_struct_delta(d_state_delta_history(), &prev->next); prev->next = n->next; } node = n; break; } } } else { d_state_delta_history_push_struct_delta(d_state_delta_history(), &d_state->free_name_chunks[bucket_idx]); SLLStackPop(d_state->free_name_chunks[bucket_idx]); } } // rjf: no found node -> allocate new, push onto associated free list if(node == 0) { U64 chunk_size = 0; if(bucket_idx < ArrayCount(d_state->free_name_chunks)-1) { chunk_size = 1<<(bucket_idx+4); } else { chunk_size = u64_up_to_pow2(string.size); } U8 *chunk_memory = push_array(d_state->arena, U8, chunk_size); D_NameChunkNode *chunk = (D_NameChunkNode *)chunk_memory; SLLStackPush(d_state->free_name_chunks[bucket_idx], chunk); } } // rjf: fill string & return String8 allocated_string = str8((U8 *)node, string.size); d_state_delta_history_push_delta(d_state_delta_history(), .ptr = allocated_string.str, .size = Max(allocated_string.size, sizeof(*node))); MemoryCopy((U8 *)node, string.str, string.size); return allocated_string; } internal void d_name_release(String8 string) { if(string.size == 0) {return;} U64 bucket_idx = d_name_bucket_idx_from_string_size(string.size); D_NameChunkNode *node = (D_NameChunkNode *)string.str; d_state_delta_history_push_delta(d_state_delta_history(), .ptr = node, .size = Max(node->size, sizeof(*node))); d_state_delta_history_push_struct_delta(d_state_delta_history(), &d_state->free_name_chunks[bucket_idx]); node->size = u64_up_to_pow2(string.size); SLLStackPush(d_state->free_name_chunks[bucket_idx], node); } //////////////////////////////// //~ rjf: Entity State Functions //- rjf: entity allocation + tree forming internal D_Entity * d_entity_alloc(D_Entity *parent, D_EntityKind kind) { B32 user_defined_lifetime = !!(d_entity_kind_flags_table[kind] & D_EntityKindFlag_UserDefinedLifetime); U64 free_list_idx = !!user_defined_lifetime; if(d_entity_is_nil(parent)) { parent = d_state->entities_root; } // rjf: empty free list -> push new if(!d_state->entities_free[free_list_idx]) { D_Entity *entity = push_array(d_state->entities_arena, D_Entity, 1); d_state->entities_count += 1; d_state->entities_free_count += 1; SLLStackPush(d_state->entities_free[free_list_idx], entity); } // rjf: pop new entity off free-list D_Entity *entity = d_state->entities_free[free_list_idx]; SLLStackPop(d_state->entities_free[free_list_idx]); d_state->entities_free_count -= 1; d_state->entities_active_count += 1; // rjf: zero entity { U64 gen = entity->gen; MemoryZeroStruct(entity); entity->gen = gen; } // rjf: set up alloc'd entity links entity->first = entity->last = entity->next = entity->prev = entity->parent = &d_nil_entity; entity->parent = parent; // rjf: stitch up parent links if(d_entity_is_nil(parent)) { d_state->entities_root = entity; } else { DLLPushBack_NPZ(&d_nil_entity, parent->first, parent->last, entity, next, prev); } // rjf: fill out metadata entity->kind = kind; d_state->entities_id_gen += 1; entity->id = d_state->entities_id_gen; entity->gen += 1; entity->alloc_time_us = os_now_microseconds(); entity->params_root = &md_nil_node; // rjf: initialize to deleted, record history, then "undelete" if this allocation can be undone if(user_defined_lifetime) { // TODO(rjf) } // rjf: dirtify caches d_state->kind_alloc_gens[kind] += 1; // rjf: log LogInfoNamedBlockF("new_entity") { log_infof("kind: \"%S\"\n", d_entity_kind_display_string_table[kind]); log_infof("id: $0x%I64x\n", entity->id); } return entity; } internal void d_entity_mark_for_deletion(D_Entity *entity) { if(!d_entity_is_nil(entity)) { entity->flags |= D_EntityFlag_MarkedForDeletion; } } internal void d_entity_release(D_Entity *entity) { Temp scratch = scratch_begin(0, 0); // rjf: unpack U64 free_list_idx = !!(d_entity_kind_flags_table[entity->kind] & D_EntityKindFlag_UserDefinedLifetime); // rjf: release whole tree typedef struct Task Task; struct Task { Task *next; D_Entity *e; }; Task start_task = {0, entity}; Task *first_task = &start_task; Task *last_task = &start_task; for(Task *task = first_task; task != 0; task = task->next) { for(D_Entity *child = task->e->first; !d_entity_is_nil(child); child = child->next) { Task *t = push_array(scratch.arena, Task, 1); t->e = child; SLLQueuePush(first_task, last_task, t); } LogInfoNamedBlockF("end_entity") { String8 name = d_display_string_from_entity(scratch.arena, task->e); log_infof("kind: \"%S\"\n", d_entity_kind_display_string_table[task->e->kind]); log_infof("id: $0x%I64x\n", task->e->id); log_infof("display_string: \"%S\"\n", name); } d_set_thread_freeze_state(task->e, 0); SLLStackPush(d_state->entities_free[free_list_idx], task->e); d_state->entities_free_count += 1; d_state->entities_active_count -= 1; task->e->gen += 1; if(task->e->string.size != 0) { d_name_release(task->e->string); } if(task->e->params_arena != 0) { arena_release(task->e->params_arena); } d_state->kind_alloc_gens[task->e->kind] += 1; } scratch_end(scratch); } internal void d_entity_change_parent(D_Entity *entity, D_Entity *old_parent, D_Entity *new_parent, D_Entity *prev_child) { Assert(entity->parent == old_parent); Assert(prev_child->parent == old_parent || d_entity_is_nil(prev_child)); // rjf: fix up links if(!d_entity_is_nil(old_parent)) { DLLRemove_NPZ(&d_nil_entity, old_parent->first, old_parent->last, entity, next, prev); } if(!d_entity_is_nil(new_parent)) { DLLInsert_NPZ(&d_nil_entity, new_parent->first, new_parent->last, prev_child, entity, next, prev); } entity->parent = new_parent; // rjf: notify d_state->kind_alloc_gens[entity->kind] += 1; } //- rjf: entity simple equipment internal void d_entity_equip_txt_pt(D_Entity *entity, TxtPt point) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->text_point, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->text_point = point; entity->flags |= D_EntityFlag_HasTextPoint; } internal void d_entity_equip_entity_handle(D_Entity *entity, D_Handle handle) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->entity_handle, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->entity_handle = handle; entity->flags |= D_EntityFlag_HasEntityHandle; } internal void d_entity_equip_disabled(D_Entity *entity, B32 value) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->disabled, .guard_entity = entity); entity->disabled = value; } internal void d_entity_equip_u64(D_Entity *entity, U64 u64) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->u64, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->u64 = u64; entity->flags |= D_EntityFlag_HasU64; } internal void d_entity_equip_color_rgba(D_Entity *entity, Vec4F32 rgba) { d_require_entity_nonnil(entity, return); Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); Vec3F32 hsv = hsv_from_rgb(rgb); Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); d_entity_equip_color_hsva(entity, hsva); } internal void d_entity_equip_color_hsva(D_Entity *entity, Vec4F32 hsva) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->color_hsva, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->color_hsva = hsva; entity->flags |= D_EntityFlag_HasColor; } internal void d_entity_equip_cfg_src(D_Entity *entity, D_CfgSrc cfg_src) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->cfg_src, .guard_entity = entity); entity->cfg_src = cfg_src; } internal void d_entity_equip_timestamp(D_Entity *entity, U64 timestamp) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->timestamp, .guard_entity = entity); entity->timestamp = timestamp; } //- rjf: control layer correllation equipment internal void d_entity_equip_ctrl_machine_id(D_Entity *entity, CTRL_MachineID machine_id) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->ctrl_machine_id, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->ctrl_machine_id = machine_id; entity->flags |= D_EntityFlag_HasCtrlMachineID; } internal void d_entity_equip_ctrl_handle(D_Entity *entity, DMN_Handle handle) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->ctrl_handle, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->ctrl_handle = handle; entity->flags |= D_EntityFlag_HasCtrlHandle; } internal void d_entity_equip_arch(D_Entity *entity, Arch arch) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->arch, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->arch = arch; entity->flags |= D_EntityFlag_HasArch; } internal void d_entity_equip_ctrl_id(D_Entity *entity, U32 id) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->ctrl_id, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->ctrl_id = id; entity->flags |= D_EntityFlag_HasCtrlID; } internal void d_entity_equip_stack_base(D_Entity *entity, U64 stack_base) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->stack_base, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->stack_base = stack_base; entity->flags |= D_EntityFlag_HasStackBase; } internal void d_entity_equip_vaddr_rng(D_Entity *entity, Rng1U64 range) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->vaddr_rng, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->vaddr_rng = range; entity->flags |= D_EntityFlag_HasVAddrRng; } internal void d_entity_equip_vaddr(D_Entity *entity, U64 vaddr) { d_require_entity_nonnil(entity, return); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->vaddr, .guard_entity = entity); d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->flags, .guard_entity = entity); entity->vaddr = vaddr; entity->flags |= D_EntityFlag_HasVAddr; } //- rjf: name equipment internal void d_entity_equip_name(D_Entity *entity, String8 name) { d_require_entity_nonnil(entity, return); if(entity->string.size != 0) { d_name_release(entity->string); } d_state_delta_history_push_struct_delta(d_state_delta_history(), &entity->string, .guard_entity = entity); if(name.size != 0) { entity->string = d_name_alloc(name); } else { entity->string = str8_zero(); } } internal void d_entity_equip_namef(D_Entity *entity, char *fmt, ...) { Temp scratch = scratch_begin(0, 0); va_list args; va_start(args, fmt); String8 string = push_str8fv(scratch.arena, fmt, args); va_end(args); d_entity_equip_name(entity, string); scratch_end(scratch); } //- rjf: params tree equipment internal void d_entity_equip_params(D_Entity *entity, MD_Node *params) { if(entity->params_arena == 0) { entity->params_arena = arena_alloc(); } arena_clear(entity->params_arena); entity->params_root = md_tree_copy(entity->params_arena, params); } internal void d_entity_equip_param(D_Entity *entity, String8 key, String8 value) { Temp scratch = scratch_begin(0, 0); //- rjf: try to incrementally add to existing tree B32 incrementally_added = 0; if(!md_node_is_nil(entity->params_root)) { MD_Node *params = entity->params_root; MD_Node *key_node = md_child_from_string(params, key, 0); if(md_node_is_nil(key_node)) { String8 key_copy = push_str8_copy(entity->params_arena, key); key_node = md_push_node(entity->params_arena, MD_NodeKind_Main, MD_NodeFlag_Identifier, key_copy, key_copy, 0); md_node_push_child(params, key_node); String8 value_copy = push_str8_copy(entity->params_arena, value); MD_TokenizeResult value_tokenize = md_tokenize_from_text(scratch.arena, value_copy); MD_ParseResult value_parse = md_parse_from_text_tokens(scratch.arena, str8_zero(), value_copy, value_tokenize.tokens); for(MD_EachNode(child, value_parse.root->first)) { child->parent = key_node; } key_node->first = value_parse.root->first; key_node->last = value_parse.root->last; incrementally_added = 1; } } //- rjf: not incrementally added -> fully rewrite parameter tree if(!incrementally_added) { MD_Node *params = md_tree_copy(scratch.arena, entity->params_root); MD_Node *key_node = md_child_from_string(params, key, 0); if(md_node_is_nil(key_node)) { key_node = md_push_node(scratch.arena, MD_NodeKind_Main, MD_NodeFlag_Identifier, key, key, 0); md_node_push_child(params, key_node); } MD_TokenizeResult value_tokenize = md_tokenize_from_text(scratch.arena, value); MD_ParseResult value_parse = md_parse_from_text_tokens(scratch.arena, str8_zero(), value, value_tokenize.tokens); for(MD_EachNode(child, value_parse.root->first)) { child->parent = key_node; } key_node->first = value_parse.root->first; key_node->last = value_parse.root->last; d_entity_equip_params(entity, params); } scratch_end(scratch); } //- rjf: opening folders/files & maintaining the entity model of the filesystem internal D_Entity * d_entity_from_path(String8 path, D_EntityFromPathFlags flags) { Temp scratch = scratch_begin(0, 0); PathStyle path_style = PathStyle_Relative; String8List path_parts = path_normalized_list_from_string(scratch.arena, path, &path_style); StringMatchFlags path_match_flags = path_match_flags_from_os(operating_system_from_context()); //- rjf: pass 1: open parts, ignore overrides D_Entity *file_no_override = &d_nil_entity; { D_Entity *parent = d_entity_root(); for(String8Node *path_part_n = path_parts.first; path_part_n != 0; path_part_n = path_part_n->next) { // rjf: find next child D_Entity *next_parent = &d_nil_entity; for(D_Entity *child = parent->first; !d_entity_is_nil(child); child = child->next) { B32 name_matches = str8_match(child->string, path_part_n->string, path_match_flags); if(name_matches && child->kind == D_EntityKind_File) { next_parent = child; break; } } // rjf: no next -> allocate one if(d_entity_is_nil(next_parent)) { if(flags & D_EntityFromPathFlag_OpenAsNeeded) { String8 parent_path = d_full_path_from_entity(scratch.arena, parent); String8 path = push_str8f(scratch.arena, "%S%s%S", parent_path, parent_path.size != 0 ? "/" : "", path_part_n->string); FileProperties file_properties = os_properties_from_file_path(path); if(file_properties.created != 0 || flags & D_EntityFromPathFlag_OpenMissing) { next_parent = d_entity_alloc(parent, D_EntityKind_File); d_entity_equip_name(next_parent, path_part_n->string); next_parent->timestamp = file_properties.modified; next_parent->flags |= D_EntityFlag_IsFolder * !!(file_properties.flags & FilePropertyFlag_IsFolder); next_parent->flags |= D_EntityFlag_IsMissing * !!(file_properties.created == 0); if(path_part_n->next != 0) { next_parent->flags |= D_EntityFlag_IsFolder; } } } else { parent = &d_nil_entity; break; } } // rjf: next parent -> follow it parent = next_parent; } file_no_override = (parent != d_entity_root() ? parent : &d_nil_entity); } //- rjf: pass 2: follow overrides D_Entity *file_overrides_applied = &d_nil_entity; if(flags & D_EntityFromPathFlag_AllowOverrides) { D_Entity *parent = d_entity_root(); for(String8Node *path_part_n = path_parts.first; path_part_n != 0; path_part_n = path_part_n->next) { // rjf: find next child D_Entity *next_parent = &d_nil_entity; for(D_Entity *child = parent->first; !d_entity_is_nil(child); child = child->next) { B32 name_matches = str8_match(child->string, path_part_n->string, path_match_flags); if(name_matches && child->kind == D_EntityKind_File) { next_parent = child; } } // rjf: no next -> allocate one if(d_entity_is_nil(next_parent)) { if(flags & D_EntityFromPathFlag_OpenAsNeeded) { String8 parent_path = d_full_path_from_entity(scratch.arena, parent); String8 path = push_str8f(scratch.arena, "%S%s%S", parent_path, parent_path.size != 0 ? "/" : "", path_part_n->string); FileProperties file_properties = os_properties_from_file_path(path); if(file_properties.created != 0 || flags & D_EntityFromPathFlag_OpenMissing) { next_parent = d_entity_alloc(parent, D_EntityKind_File); d_entity_equip_name(next_parent, path_part_n->string); next_parent->timestamp = file_properties.modified; next_parent->flags |= D_EntityFlag_IsFolder * !!(file_properties.flags & FilePropertyFlag_IsFolder); next_parent->flags |= D_EntityFlag_IsMissing * !!(file_properties.created == 0); if(path_part_n->next != 0) { next_parent->flags |= D_EntityFlag_IsFolder; } } } else { parent = &d_nil_entity; break; } } // rjf: next parent -> follow it parent = next_parent; } file_overrides_applied = (parent != d_entity_root() ? parent : &d_nil_entity);; } //- rjf: pick & return result D_Entity *result = (flags & D_EntityFromPathFlag_AllowOverrides) ? file_overrides_applied : file_no_override; if(flags & D_EntityFromPathFlag_AllowOverrides && result == file_overrides_applied && result->flags & D_EntityFlag_IsMissing) { result = file_no_override; } scratch_end(scratch); return result; } //- rjf: file path map override lookups internal String8List d_possible_overrides_from_file_path(Arena *arena, String8 file_path) { // NOTE(rjf): This path, given some target file path, scans all file path map // overrides, and collects the set of file paths which could've redirected // to the target file path given the set of file path maps. // // For example, if I have a rule saying D:/devel/ maps to C:/devel/, and I // feed in C:/devel/foo/bar.txt, then this path will construct // D:/devel/foo/bar.txt, as a possible option. // // It will also preserve C:/devel/foo/bar.txt in the resultant list, so that // overrideless files still work through this path, and both redirected // files and non-redirected files can go through the same path. // String8List result = {0}; str8_list_push(arena, &result, file_path); Temp scratch = scratch_begin(&arena, 1); PathStyle pth_style = PathStyle_Relative; String8List pth_parts = path_normalized_list_from_string(scratch.arena, file_path, &pth_style); { D_EntityList links = d_query_cached_entity_list_with_kind(D_EntityKind_FilePathMap); for(D_EntityNode *n = links.first; n != 0; n = n->next) { //- rjf: unpack link D_Entity *link = n->entity; D_Entity *src = d_entity_child_from_kind(link, D_EntityKind_Source); D_Entity *dst = d_entity_child_from_kind(link, D_EntityKind_Dest); PathStyle src_style = PathStyle_Relative; PathStyle dst_style = PathStyle_Relative; String8List src_parts = path_normalized_list_from_string(scratch.arena, src->string, &src_style); String8List dst_parts = path_normalized_list_from_string(scratch.arena, dst->string, &dst_style); //- rjf: determine if this link can possibly redirect to the target file path B32 dst_redirects_to_pth = 0; String8Node *non_redirected_pth_first = 0; if(dst_style == pth_style && dst_parts.first != 0 && pth_parts.first != 0) { dst_redirects_to_pth = 1; String8Node *dst_n = dst_parts.first; String8Node *pth_n = pth_parts.first; for(;dst_n != 0 && pth_n != 0; dst_n = dst_n->next, pth_n = pth_n->next) { if(!str8_match(dst_n->string, pth_n->string, StringMatchFlag_CaseInsensitive)) { dst_redirects_to_pth = 0; break; } non_redirected_pth_first = pth_n->next; } } //- rjf: if this link can redirect to this path via `src` -> `dst`, compute // possible full source path, by taking `src` and appending non-redirected // suffix (which did not show up in `dst`) if(dst_redirects_to_pth) { String8List candidate_parts = src_parts; for(String8Node *p = non_redirected_pth_first; p != 0; p = p->next) { str8_list_push(scratch.arena, &candidate_parts, p->string); } StringJoin join = {0}; join.sep = str8_lit("/"); String8 candidate_path = str8_list_join(arena, &candidate_parts, 0); str8_list_push(arena, &result, candidate_path); } } } scratch_end(scratch); return result; } //- rjf: top-level state queries internal D_Entity * d_entity_root(void) { return d_state->entities_root; } internal D_EntityList d_push_entity_list_with_kind(Arena *arena, D_EntityKind kind) { ProfBeginFunction(); D_EntityList result = {0}; for(D_Entity *entity = d_state->entities_root; !d_entity_is_nil(entity); entity = d_entity_rec_depth_first_pre(entity, &d_nil_entity).next) { if(entity->kind == kind) { d_entity_list_push(arena, &result, entity); } } ProfEnd(); return result; } internal D_Entity * d_entity_from_id(D_EntityID id) { D_Entity *result = &d_nil_entity; for(D_Entity *e = d_entity_root(); !d_entity_is_nil(e); e = d_entity_rec_depth_first_pre(e, &d_nil_entity).next) { if(e->id == id) { result = e; break; } } return result; } internal D_Entity * d_machine_entity_from_machine_id(CTRL_MachineID machine_id) { D_Entity *result = &d_nil_entity; for(D_Entity *e = d_entity_root(); !d_entity_is_nil(e); e = d_entity_rec_depth_first_pre(e, &d_nil_entity).next) { if(e->kind == D_EntityKind_Machine && e->ctrl_machine_id == machine_id) { result = e; break; } } if(d_entity_is_nil(result)) { result = d_entity_alloc(d_entity_root(), D_EntityKind_Machine); d_entity_equip_ctrl_machine_id(result, machine_id); } return result; } internal D_Entity * d_entity_from_ctrl_handle(CTRL_MachineID machine_id, DMN_Handle handle) { D_Entity *result = &d_nil_entity; if(handle.u64[0] != 0) { for(D_Entity *e = d_entity_root(); !d_entity_is_nil(e); e = d_entity_rec_depth_first_pre(e, &d_nil_entity).next) { if(e->flags & D_EntityFlag_HasCtrlMachineID && e->flags & D_EntityFlag_HasCtrlHandle && e->ctrl_machine_id == machine_id && MemoryMatchStruct(&e->ctrl_handle, &handle)) { result = e; break; } } } return result; } internal D_Entity * d_entity_from_ctrl_id(CTRL_MachineID machine_id, U32 id) { D_Entity *result = &d_nil_entity; if(id != 0) { for(D_Entity *e = d_entity_root(); !d_entity_is_nil(e); e = d_entity_rec_depth_first_pre(e, &d_nil_entity).next) { if(e->flags & D_EntityFlag_HasCtrlMachineID && e->flags & D_EntityFlag_HasCtrlID && e->ctrl_machine_id == machine_id && e->ctrl_id == id) { result = e; break; } } } return result; } internal D_Entity * d_entity_from_name_and_kind(String8 string, D_EntityKind kind) { D_Entity *result = &d_nil_entity; D_EntityList all_of_this_kind = d_query_cached_entity_list_with_kind(kind); for(D_EntityNode *n = all_of_this_kind.first; n != 0; n = n->next) { if(str8_match(n->entity->string, string, 0)) { result = n->entity; break; } } return result; } internal D_Entity * d_entity_from_u64_and_kind(U64 u64, D_EntityKind kind) { D_Entity *result = &d_nil_entity; D_EntityList all_of_this_kind = d_query_cached_entity_list_with_kind(kind); for(D_EntityNode *n = all_of_this_kind.first; n != 0; n = n->next) { if(n->entity->u64 == u64) { result = n->entity; break; } } return result; } //- rjf: entity freezing state internal void d_set_thread_freeze_state(D_Entity *thread, B32 frozen) { D_Handle thread_handle = d_handle_from_entity(thread); D_HandleNode *already_frozen_node = d_handle_list_find(&d_state->frozen_threads, thread_handle); B32 is_frozen = !!already_frozen_node; B32 should_be_frozen = frozen; // rjf: not frozen => frozen if(!is_frozen && should_be_frozen) { D_HandleNode *node = d_state->free_handle_node; if(node) { SLLStackPop(d_state->free_handle_node); } else { node = push_array(d_state->arena, D_HandleNode, 1); } node->handle = thread_handle; d_handle_list_push_node(&d_state->frozen_threads, node); } // rjf: frozen => not frozen if(is_frozen && !should_be_frozen) { d_handle_list_remove(&d_state->frozen_threads, already_frozen_node); SLLStackPush(d_state->free_handle_node, already_frozen_node); } } internal B32 d_entity_is_frozen(D_Entity *entity) { B32 is_frozen = !d_entity_is_nil(entity); for(D_Entity *e = entity; !d_entity_is_nil(e); e = d_entity_rec_depth_first_pre(e, entity).next) { if(e->kind == D_EntityKind_Thread) { B32 thread_is_frozen = !!d_handle_list_find(&d_state->frozen_threads, d_handle_from_entity(e)); if(!thread_is_frozen) { is_frozen = 0; break; } } } return is_frozen; } //////////////////////////////// //~ rjf: Command Stateful Functions internal void d_register_cmd_specs(D_CmdSpecInfoArray specs) { U64 registrar_idx = d_state->total_registrar_count; d_state->total_registrar_count += 1; for(U64 idx = 0; idx < specs.count; idx += 1) { // rjf: extract info from array slot D_CmdSpecInfo *info = &specs.v[idx]; // rjf: skip empties if(info->string.size == 0) { continue; } // rjf: determine hash/slot U64 hash = d_hash_from_string(info->string); U64 slot = hash % d_state->cmd_spec_table_size; // rjf: allocate node & push D_CmdSpec *spec = push_array(d_state->arena, D_CmdSpec, 1); SLLStackPush_N(d_state->cmd_spec_table[slot], spec, hash_next); // rjf: fill node D_CmdSpecInfo *info_copy = &spec->info; info_copy->string = push_str8_copy(d_state->arena, info->string); info_copy->description = push_str8_copy(d_state->arena, info->description); info_copy->search_tags = push_str8_copy(d_state->arena, info->search_tags); info_copy->display_name = push_str8_copy(d_state->arena, info->display_name); info_copy->flags = info->flags; info_copy->query = info->query; spec->registrar_index = registrar_idx; spec->ordering_index = idx; } } internal D_CmdSpec * d_cmd_spec_from_string(String8 string) { D_CmdSpec *result = &d_nil_cmd_spec; { U64 hash = d_hash_from_string(string); U64 slot = hash%d_state->cmd_spec_table_size; for(D_CmdSpec *n = d_state->cmd_spec_table[slot]; n != 0; n = n->hash_next) { if(str8_match(n->info.string, string, 0)) { result = n; break; } } } return result; } internal D_CmdSpec * d_cmd_spec_from_kind(D_CmdKind kind) { String8 string = d_core_cmd_kind_spec_info_table[kind].string; D_CmdSpec *result = d_cmd_spec_from_string(string); return result; } internal void d_cmd_spec_counter_inc(D_CmdSpec *spec) { if(!d_cmd_spec_is_nil(spec)) { spec->run_count += 1; } } internal D_CmdSpecList d_push_cmd_spec_list(Arena *arena) { D_CmdSpecList list = {0}; for(U64 idx = 0; idx < d_state->cmd_spec_table_size; idx += 1) { for(D_CmdSpec *spec = d_state->cmd_spec_table[idx]; spec != 0; spec = spec->hash_next) { d_cmd_spec_list_push(arena, &list, spec); } } return list; } //////////////////////////////// //~ rjf: View Rule Spec Stateful Functions internal void d_register_view_rule_specs(D_ViewRuleSpecInfoArray specs) { for(U64 idx = 0; idx < specs.count; idx += 1) { // rjf: extract info from array slot D_ViewRuleSpecInfo *info = &specs.v[idx]; // rjf: skip empties if(info->string.size == 0) { continue; } // rjf: determine hash/slot U64 hash = d_hash_from_string(info->string); U64 slot_idx = hash%d_state->view_rule_spec_table_size; // rjf: allocate node & push D_ViewRuleSpec *spec = push_array(d_state->arena, D_ViewRuleSpec, 1); SLLStackPush_N(d_state->view_rule_spec_table[slot_idx], spec, hash_next); // rjf: fill node D_ViewRuleSpecInfo *info_copy = &spec->info; MemoryCopyStruct(info_copy, info); info_copy->string = push_str8_copy(d_state->arena, info->string); info_copy->display_string = push_str8_copy(d_state->arena, info->display_string); info_copy->description = push_str8_copy(d_state->arena, info->description); } } internal D_ViewRuleSpec * d_view_rule_spec_from_string(String8 string) { D_ViewRuleSpec *spec = &d_nil_core_view_rule_spec; { U64 hash = d_hash_from_string(string); U64 slot_idx = hash%d_state->view_rule_spec_table_size; for(D_ViewRuleSpec *s = d_state->view_rule_spec_table[slot_idx]; s != 0; s = s->hash_next) { if(str8_match(string, s->info.string, 0)) { spec = s; break; } } } return spec; } //////////////////////////////// //~ rjf: Stepping "Trap Net" Builders // NOTE(rjf): Stepping Algorithm Overview (2024/01/17) // // The basic idea behind all stepping algorithms in the debugger are setting up // a "trap net". A "trap net" is just a collection of high-level traps that are // meant to "catch" a thread after letting it run. This trap net is submitted // when the debugger frontend sends a "run" command (it is just empty if doing // an actual 'run' or 'continue'). The debugger control thread then uses this // trap net to program a state machine, to appropriately respond to a variety // of debug events which it is passed from the OS. // // These are "high-level traps" because they can have specific behavioral info // attached to them. These are encoded via the `CTRL_TrapFlags` type, which // allow expression of the following behaviors: // // - end-stepping: when this trap is hit, it will end the stepping operation, // and the target will not continue. // - ignore-stack-pointer-check: when a trap in the trap net is hit, it will // by-default be ignored if the thread's stack pointer has changed. this // flag disables that behavior, for when the stack pointer is expected to // change (e.g. step-out). // - single-step-after-hit: when a trap with this flag is hit, the debugger // will immediately single-step the thread which hit it. // - save-stack-pointer: when a trap with this flag is hit, it will rewrite // the stack pointer which is used to compare against, when deciding // whether or not to filter a trap (based on stack pointer changes). // - begin-spoof-mode: this enables "spoof mode". "spoof mode" is a special // mode that disables the trap net entirely, and lets the thread run // freely - but it catches the thread not with a trap, but a false return // address. the debugger will overwrite a specific return address on the // stack. this address will be overwritten with an address which does NOT // point to a valid page, such that when the thread returns out of a // particular call frame, the debugger will receive a debug event, at // which point it can move the thread back to the correct return address, // and resume with the trap net enabled. this is used in "step over" // operations, because it avoids target <-> debugger "roundtrips" (e.g. // target being stopped, debugger being called with debug events, then // target resumes when debugger's control thread is done running) for // recursions. (it doesn't make a difference with non-recursive calls, // but the debugger can't detect the difference). // // Each stepping command prepares its trap net differently. // // --- Instruction Step Into -------------------------------------------------- // In this case, no trap net is prepared, and only a low-level single-step is // performed. // // --- Instruction Step Over -------------------------------------------------- // To build a trap net for an instruction-level step-over, the next instruction // at the thread's current instruction pointer is decoded. If it is a call // instruction, or if it is a repeating instruction, then a trap with the // 'end-stepping' behavior is placed at the instruction immediately following // the 'call' instruction. // // --- Line Step Into --------------------------------------------------------- // For a source-line step-into, the thread's instruction pointer is first used // to look up into the debug info's line info, to find the machine code in the // thread's current source line. Every instruction in this range is decoded. // Traps are then built in the following way: // // - 'call' instruction -> if can decode call destination address, place // "end-stepping | ignore-stack-pointer-check" trap at destination. if // can't, "end-stepping | single-step-after | ignore-stack-pointer-check" // trap at call. // - 'jmp' (both unconditional & conditional) -> if can decode jump destination // address, AND if jump leaves the line, place "end-stepping | ignore- // stack-pointer-check" trap at destination. if can't, "end-stepping | // single-step-after | ignore-stack-pointer-check" trap at jmp. if jump // stays within the line, do nothing. // - 'return' -> place "end-stepping | single-step-after" trap at return inst. // - "end-stepping" trap is placed at the first address after the line, to // catch all steps which simply proceed linearly through the instruction // stream. // // --- Line Step Over --------------------------------------------------------- // For a source-line step-over, the thread's instruction pointer is first used // to look up into the debug info's line info, to find the machine code in the // thread's current source line. Every instruction in this range is decoded. // Traps are then built in the following way: // // - 'call' instruction -> place "single-step-after | begin-spoof-mode" trap at // call instruction. // - 'jmp' (both unconditional & conditional) -> if can decode jump destination // address, AND if jump leaves the line, place "end-stepping" trap at // destination. if can't, "end-stepping | single-step-after" trap at jmp. // if jump stays within the line, do nothing. // - 'return' -> place "end-stepping | single-step-after" trap at return inst. // - "end-stepping" trap is placed at the first address after the line, to // catch all steps which simply proceed linearly through the instruction // stream. // - 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 d_trap_net_from_thread__step_over_inst(Arena *arena, D_Entity *thread) { Temp scratch = scratch_begin(&arena, 1); CTRL_TrapList result = {0}; // rjf: thread => unpacked info D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); Arch arch = d_arch_from_entity(thread); U64 ip_vaddr = ctrl_query_cached_rip_from_thread(d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); // rjf: ip => machine code String8 machine_code = {0}; { Rng1U64 rng = r1u64(ip_vaddr, ip_vaddr+max_instruction_size_from_arch(arch)); CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, rng, os_now_microseconds()+5000); machine_code = machine_code_slice.data; } // rjf: build traps if machine code was read successfully if(machine_code.size != 0) { // rjf: decode instruction DASM_Inst inst = dasm_inst_from_code(scratch.arena, arch, ip_vaddr, machine_code, DASM_Syntax_Intel); // rjf: call => run until call returns 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); } } scratch_end(scratch); return result; } internal CTRL_TrapList d_trap_net_from_thread__step_over_line(Arena *arena, D_Entity *thread) { Temp scratch = scratch_begin(&arena, 1); log_infof("step_over_line:\n{\n"); CTRL_TrapList result = {0}; // rjf: thread => info D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); D_Entity *module = d_module_from_thread(thread); DI_Key dbgi_key = d_dbgi_key_from_module(module); Arch arch = d_arch_from_entity(thread); U64 ip_vaddr = ctrl_query_cached_rip_from_thread(d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); log_infof("ip_vaddr: 0x%I64x\n", ip_vaddr); log_infof("dbgi_key: {%S, 0x%I64x}\n", dbgi_key.path, dbgi_key.min_timestamp); // rjf: ip => line vaddr range Rng1U64 line_vaddr_rng = {0}; { U64 ip_voff = d_voff_from_vaddr(module, ip_vaddr); D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, ip_voff); Rng1U64 line_voff_rng = {0}; if(lines.first != 0) { line_voff_rng = lines.first->v.voff_range; line_vaddr_rng = d_vaddr_range_from_voff_range(module, line_voff_rng); log_infof("line: {%S:%I64i}\n", lines.first->v.file_path, lines.first->v.pt.line); } log_infof("voff_range: {0x%I64x, 0x%I64x}\n", line_voff_rng.min, line_voff_rng.max); log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max); } // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range // // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging // is enabled. This is enabled by default normally. { U64 opl_line_voff_rng = d_voff_from_vaddr(module, line_vaddr_rng.max); D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, opl_line_voff_rng); if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) { line_vaddr_rng.max = d_vaddr_from_voff(module, lines.first->v.voff_range.max); } } // rjf: line vaddr range => did we find anything successfully? B32 good_line_info = (line_vaddr_rng.max != 0); // rjf: line vaddr range => line's machine code String8 machine_code = {0}; if(good_line_info) { CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, line_vaddr_rng, os_now_microseconds()+50000); machine_code = machine_code_slice.data; LogInfoNamedBlockF("machine_code_slice") { log_infof("stale: %i\n", machine_code_slice.stale); log_infof("any_byte_bad: %i\n", machine_code_slice.any_byte_bad); log_infof("any_byte_changed: %i\n", machine_code_slice.any_byte_changed); log_infof("bytes:\n[\n"); for(U64 idx = 0; idx < machine_code_slice.data.size; idx += 1) { log_infof("0x%x,", machine_code_slice.data.str[idx]); if(idx%16 == 15 || idx+1 == machine_code_slice.data.size) { log_infof("\n"); } } log_infof("]\n"); } } // rjf: machine code => ctrl flow analysis DASM_CtrlFlowInfo ctrl_flow_info = {0}; if(good_line_info) { ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, DASM_InstFlag_Call| DASM_InstFlag_Branch| DASM_InstFlag_UnconditionalJump| DASM_InstFlag_ChangesStackPointer| DASM_InstFlag_Return, arch, line_vaddr_rng.min, machine_code); LogInfoNamedBlockF("ctrl_flow_info") { LogInfoNamedBlockF("exit_points") for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) { log_infof("{vaddr:0x%I64x, jump_dest_vaddr:0x%I64x, inst_flags:%x}\n", n->v.vaddr, n->v.jump_dest_vaddr, n->v.inst_flags); } } } // rjf: push traps for all exit points if(good_line_info) for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) { DASM_CtrlFlowPoint *point = &n->v; CTRL_TrapFlags flags = 0; B32 add = 1; U64 trap_addr = point->vaddr; // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. if(point->inst_flags & (DASM_InstFlag_Branch| DASM_InstFlag_UnconditionalJump| DASM_InstFlag_Return)) { flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping); // rjf: omit if this jump stays inside of this line if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) { add = 0; } // rjf: trap @ destination, if we can - we can avoid a single-step this way. if(point->jump_dest_vaddr != 0) { trap_addr = point->jump_dest_vaddr; flags &= ~CTRL_TrapFlag_SingleStepAfterHit; } } // rjf: call => place spoof at return spot in stack, single-step after hitting else if(point->inst_flags & DASM_InstFlag_Call) { flags |= (CTRL_TrapFlag_BeginSpoofMode|CTRL_TrapFlag_SingleStepAfterHit); } // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping else if(point->inst_flags & DASM_InstFlag_ChangesStackPointer) { flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); } // rjf: add if appropriate if(add) { CTRL_Trap trap = {flags, trap_addr}; ctrl_trap_list_push(arena, &result, &trap); } } // rjf: push trap for natural linear flow if(good_line_info) { CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max}; ctrl_trap_list_push(arena, &result, &trap); } // rjf: log LogInfoNamedBlockF("traps") for(CTRL_TrapNode *n = result.first; n != 0; n = n->next) { log_infof("{flags:0x%x, vaddr:0x%I64x}\n", n->v.flags, n->v.vaddr); } scratch_end(scratch); log_infof("}\n\n"); return result; } internal CTRL_TrapList d_trap_net_from_thread__step_into_line(Arena *arena, D_Entity *thread) { Temp scratch = scratch_begin(&arena, 1); CTRL_TrapList result = {0}; // rjf: thread => info D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); D_Entity *module = d_module_from_thread(thread); DI_Key dbgi_key = d_dbgi_key_from_module(module); Arch arch = d_arch_from_entity(thread); U64 ip_vaddr = ctrl_query_cached_rip_from_thread(d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); // rjf: ip => line vaddr range Rng1U64 line_vaddr_rng = {0}; { U64 ip_voff = d_voff_from_vaddr(module, ip_vaddr); D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, ip_voff); Rng1U64 line_voff_rng = {0}; if(lines.first != 0) { line_voff_rng = lines.first->v.voff_range; line_vaddr_rng = d_vaddr_range_from_voff_range(module, line_voff_rng); } } // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range // // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging // is enabled. This is enabled by default normally. { U64 opl_line_voff_rng = d_voff_from_vaddr(module, line_vaddr_rng.max); D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, opl_line_voff_rng); if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) { line_vaddr_rng.max = d_vaddr_from_voff(module, lines.first->v.voff_range.max); } } // rjf: line vaddr range => did we find anything successfully? B32 good_line_info = (line_vaddr_rng.max != 0); // rjf: line vaddr range => line's machine code String8 machine_code = {0}; if(good_line_info) { CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, line_vaddr_rng, os_now_microseconds()+5000); machine_code = machine_code_slice.data; } // rjf: machine code => ctrl flow analysis DASM_CtrlFlowInfo ctrl_flow_info = {0}; if(good_line_info) { ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, DASM_InstFlag_Call| DASM_InstFlag_Branch| DASM_InstFlag_UnconditionalJump| DASM_InstFlag_ChangesStackPointer| DASM_InstFlag_Return, arch, line_vaddr_rng.min, machine_code); } // rjf: push traps for all exit points if(good_line_info) for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) { DASM_CtrlFlowPoint *point = &n->v; CTRL_TrapFlags flags = 0; B32 add = 1; U64 trap_addr = point->vaddr; // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. if(point->inst_flags & (DASM_InstFlag_Call| DASM_InstFlag_Branch| DASM_InstFlag_UnconditionalJump| DASM_InstFlag_Return)) { flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck); // rjf: omit if this jump stays inside of this line if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) { add = 0; } // rjf: trap @ destination, if we can - we can avoid a single-step this way. if(point->jump_dest_vaddr != 0) { trap_addr = point->jump_dest_vaddr; flags &= ~CTRL_TrapFlag_SingleStepAfterHit; } } // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping else if(point->inst_flags & DASM_InstFlag_ChangesStackPointer) { flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); } // rjf: add if appropriate if(add) { CTRL_Trap trap = {flags, trap_addr}; ctrl_trap_list_push(arena, &result, &trap); } } // rjf: push trap for natural linear flow if(good_line_info) { CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max}; ctrl_trap_list_push(arena, &result, &trap); } scratch_end(scratch); return result; } //////////////////////////////// //~ rjf: Modules & Debug Info Mappings //- rjf: module <=> debug info keys internal DI_Key d_dbgi_key_from_module(D_Entity *module) { D_Entity *debug_info_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); DI_Key key = {debug_info_path->string, debug_info_path->timestamp}; return key; } internal D_EntityList d_modules_from_dbgi_key(Arena *arena, DI_Key *dbgi_key) { D_EntityList list = {0}; D_EntityList all_modules = d_query_cached_entity_list_with_kind(D_EntityKind_Module); for(D_EntityNode *n = all_modules.first; n != 0; n = n->next) { D_Entity *module = n->entity; DI_Key module_dbgi_key = d_dbgi_key_from_module(module); if(di_key_match(&module_dbgi_key, dbgi_key)) { d_entity_list_push(arena, &list, module); } } return list; } //- rjf: voff <=> vaddr internal U64 d_base_vaddr_from_module(D_Entity *module) { U64 module_base_vaddr = module->vaddr; return module_base_vaddr; } internal U64 d_voff_from_vaddr(D_Entity *module, U64 vaddr) { U64 module_base_vaddr = d_base_vaddr_from_module(module); U64 voff = vaddr - module_base_vaddr; return voff; } internal U64 d_vaddr_from_voff(D_Entity *module, U64 voff) { U64 module_base_vaddr = d_base_vaddr_from_module(module); U64 vaddr = voff + module_base_vaddr; return vaddr; } internal Rng1U64 d_voff_range_from_vaddr_range(D_Entity *module, Rng1U64 vaddr_rng) { U64 rng_size = dim_1u64(vaddr_rng); Rng1U64 voff_rng = {0}; voff_rng.min = d_voff_from_vaddr(module, vaddr_rng.min); voff_rng.max = voff_rng.min + rng_size; return voff_rng; } internal Rng1U64 d_vaddr_range_from_voff_range(D_Entity *module, Rng1U64 voff_rng) { U64 rng_size = dim_1u64(voff_rng); Rng1U64 vaddr_rng = {0}; vaddr_rng.min = d_vaddr_from_voff(module, voff_rng.min); vaddr_rng.max = vaddr_rng.min + rng_size; return vaddr_rng; } //////////////////////////////// //~ rjf: Debug Info Lookups //- rjf: symbol lookups internal String8 d_symbol_name_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff, B32 decorated) { String8 result = {0}; { Temp scratch = scratch_begin(&arena, 1); DI_Scope *scope = di_scope_open(); RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); if(result.size == 0) { U64 scope_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_ScopeVMap, voff); RDI_Scope *scope = rdi_element_from_name_idx(rdi, Scopes, scope_idx); U64 proc_idx = scope->proc_idx; RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, proc_idx); E_TypeKey type = e_type_key_ext(E_TypeKind_Function, procedure->type_idx, e_parse_ctx_module_idx_from_rdi(rdi)); String8 name = {0}; name.str = rdi_string_from_idx(rdi, procedure->name_string_idx, &name.size); if(decorated && procedure->type_idx != 0) { String8List list = {0}; e_type_lhs_string_from_key(scratch.arena, type, &list, 0, 0); str8_list_push(scratch.arena, &list, name); e_type_rhs_string_from_key(scratch.arena, type, &list, 0); result = str8_list_join(arena, &list, 0); } else { result = push_str8_copy(arena, name); } } if(result.size == 0) { U64 global_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_GlobalVMap, voff); RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, global_idx); U64 name_size = 0; U8 *name_ptr = rdi_string_from_idx(rdi, global_var->name_string_idx, &name_size); result = push_str8_copy(arena, str8(name_ptr, name_size)); } di_scope_close(scope); scratch_end(scratch); } return result; } internal String8 d_symbol_name_from_process_vaddr(Arena *arena, D_Entity *process, U64 vaddr, B32 decorated) { ProfBeginFunction(); String8 result = {0}; { D_Entity *module = d_module_from_process_vaddr(process, vaddr); DI_Key dbgi_key = d_dbgi_key_from_module(module); U64 voff = d_voff_from_vaddr(module, vaddr); result = d_symbol_name_from_dbgi_key_voff(arena, &dbgi_key, voff, decorated); } ProfEnd(); return result; } //- rjf: symbol -> voff lookups internal U64 d_voff_from_dbgi_key_symbol_name(DI_Key *dbgi_key, String8 symbol_name) { ProfBeginFunction(); Temp scratch = scratch_begin(0, 0); DI_Scope *scope = di_scope_open(); U64 result = 0; { RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); RDI_NameMapKind name_map_kinds[] = { RDI_NameMapKind_GlobalVariables, RDI_NameMapKind_Procedures, }; if(rdi != &di_rdi_parsed_nil) { for(U64 name_map_kind_idx = 0; name_map_kind_idx < ArrayCount(name_map_kinds); name_map_kind_idx += 1) { RDI_NameMapKind name_map_kind = name_map_kinds[name_map_kind_idx]; RDI_NameMap *name_map = rdi_element_from_name_idx(rdi, NameMaps, name_map_kind); RDI_ParsedNameMap parsed_name_map = {0}; rdi_parsed_from_name_map(rdi, name_map, &parsed_name_map); RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &parsed_name_map, symbol_name.str, symbol_name.size); // rjf: node -> num U64 entity_num = 0; if(node != 0) { switch(node->match_count) { case 1: { entity_num = node->match_idx_or_idx_run_first + 1; }break; default: { U32 num = 0; U32 *run = rdi_matches_from_map_node(rdi, node, &num); if(num != 0) { entity_num = run[0]+1; } }break; } } // rjf: num -> voff U64 voff = 0; if(entity_num != 0) switch(name_map_kind) { default:{}break; case RDI_NameMapKind_GlobalVariables: { RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, entity_num-1); voff = global_var->voff; }break; case RDI_NameMapKind_Procedures: { RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, entity_num-1); RDI_Scope *scope = rdi_element_from_name_idx(rdi, Scopes, procedure->root_scope_idx); voff = *rdi_element_from_name_idx(rdi, ScopeVOffData, scope->voff_range_first); }break; } // rjf: nonzero voff -> break if(voff != 0) { result = voff; break; } } } } di_scope_close(scope); scratch_end(scratch); ProfEnd(); return result; } internal U64 d_type_num_from_dbgi_key_name(DI_Key *dbgi_key, String8 name) { ProfBeginFunction(); DI_Scope *scope = di_scope_open(); U64 result = 0; { RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); RDI_NameMap *name_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Types); RDI_ParsedNameMap parsed_name_map = {0}; rdi_parsed_from_name_map(rdi, name_map, &parsed_name_map); RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &parsed_name_map, name.str, name.size); U64 entity_num = 0; if(node != 0) { switch(node->match_count) { case 1: { entity_num = node->match_idx_or_idx_run_first + 1; }break; default: { U32 num = 0; U32 *run = rdi_matches_from_map_node(rdi, node, &num); if(num != 0) { entity_num = run[0]+1; } }break; } } result = entity_num; } di_scope_close(scope); ProfEnd(); return result; } //- rjf: voff -> line info internal D_LineList d_lines_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff) { Temp scratch = scratch_begin(&arena, 1); DI_Scope *scope = di_scope_open(); RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); D_LineList result = {0}; { //- rjf: gather line tables typedef struct LineTableNode LineTableNode; struct LineTableNode { LineTableNode *next; RDI_ParsedLineTable parsed_line_table; }; LineTableNode start_line_table = {0}; RDI_Unit *unit = rdi_unit_from_voff(rdi, voff); RDI_LineTable *unit_line_table = rdi_line_table_from_unit(rdi, unit); rdi_parsed_from_line_table(rdi, unit_line_table, &start_line_table.parsed_line_table); LineTableNode *top_line_table = 0; RDI_Scope *scope = rdi_scope_from_voff(rdi, voff); { for(RDI_Scope *s = scope; s->inline_site_idx != 0; s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) { RDI_InlineSite *inline_site = rdi_element_from_name_idx(rdi, InlineSites, s->inline_site_idx); if(inline_site->line_table_idx != 0) { LineTableNode *n = push_array(scratch.arena, LineTableNode, 1); SLLStackPush(top_line_table, n); RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, inline_site->line_table_idx); rdi_parsed_from_line_table(rdi, line_table, &n->parsed_line_table); } } } SLLStackPush(top_line_table, &start_line_table); //- rjf: gather lines in each line table Rng1U64 shallowest_voff_range = {0}; for(LineTableNode *line_table_n = top_line_table; line_table_n != 0; line_table_n = line_table_n->next) { RDI_ParsedLineTable parsed_line_table = line_table_n->parsed_line_table; U64 line_info_idx = rdi_line_info_idx_from_voff(&parsed_line_table, voff); if(line_info_idx < parsed_line_table.count) { RDI_Line *line = &parsed_line_table.lines[line_info_idx]; RDI_Column *column = (line_info_idx < parsed_line_table.col_count) ? &parsed_line_table.cols[line_info_idx] : 0; RDI_SourceFile *file = rdi_element_from_name_idx(rdi, SourceFiles, line->file_idx); String8List path_parts = {0}; for(RDI_FilePathNode *fpn = rdi_element_from_name_idx(rdi, FilePathNodes, file->file_path_node_idx); fpn != rdi_element_from_name_idx(rdi, FilePathNodes, 0); fpn = rdi_element_from_name_idx(rdi, FilePathNodes, fpn->parent_path_node)) { String8 path_part = {0}; path_part.str = rdi_string_from_idx(rdi, fpn->name_string_idx, &path_part.size); str8_list_push_front(scratch.arena, &path_parts, path_part); } StringJoin join = {0}; join.sep = str8_lit("/"); String8 file_normalized_full_path = str8_list_join(arena, &path_parts, &join); D_LineNode *n = push_array(arena, D_LineNode, 1); SLLQueuePush(result.first, result.last, n); result.count += 1; if(line->file_idx != 0 && file_normalized_full_path.size != 0) { n->v.file_path = file_normalized_full_path; } n->v.pt = txt_pt(line->line_num, column ? column->col_first : 1); n->v.voff_range = r1u64(parsed_line_table.voffs[line_info_idx], parsed_line_table.voffs[line_info_idx+1]); n->v.dbgi_key = *dbgi_key; if(line_table_n == top_line_table) { shallowest_voff_range = n->v.voff_range; } } } //- rjf: clamp all lines from all tables by shallowest (most unwound) range for(D_LineNode *n = result.first; n != 0; n = n->next) { n->v.voff_range = intersect_1u64(n->v.voff_range, shallowest_voff_range); } } di_scope_close(scope); scratch_end(scratch); return result; } //- rjf: file:line -> line info internal D_LineListArray d_lines_array_from_file_path_line_range(Arena *arena, String8 file_path, Rng1S64 line_num_range) { D_LineListArray array = {0}; { array.count = dim_1s64(line_num_range)+1; array.v = push_array(arena, D_LineList, array.count); } Temp scratch = scratch_begin(&arena, 1); DI_Scope *scope = di_scope_open(); DI_KeyList dbgi_keys = d_push_active_dbgi_key_list(scratch.arena); String8List overrides = d_possible_overrides_from_file_path(scratch.arena, file_path); for(String8Node *override_n = overrides.first; override_n != 0; override_n = override_n->next) { String8 file_path = override_n->string; String8 file_path_normalized = lower_from_str8(scratch.arena, file_path); for(DI_KeyNode *dbgi_key_n = dbgi_keys.first; dbgi_key_n != 0; dbgi_key_n = dbgi_key_n->next) { // rjf: binary -> rdi DI_Key key = dbgi_key_n->v; RDI_Parsed *rdi = di_rdi_from_key(scope, &key, 0); // rjf: file_path_normalized * rdi -> src_id B32 good_src_id = 0; U32 src_id = 0; if(rdi != &di_rdi_parsed_nil) { RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); RDI_ParsedNameMap map = {0}; rdi_parsed_from_name_map(rdi, mapptr, &map); RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); if(node != 0) { U32 id_count = 0; U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); if(id_count > 0) { good_src_id = 1; src_id = ids[0]; } } } // rjf: good src-id -> look up line info for visible range if(good_src_id) { RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); RDI_ParsedSourceLineMap line_map = {0}; rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); U64 line_idx = 0; for(S64 line_num = line_num_range.min; line_num <= line_num_range.max; line_num += 1, line_idx += 1) { D_LineList *list = &array.v[line_idx]; U32 voff_count = 0; U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); for(U64 idx = 0; idx < voff_count; idx += 1) { U64 base_voff = voffs[idx]; U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); RDI_ParsedLineTable unit_line_info = {0}; rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); if(unit_line_info.voffs != 0) { Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; D_LineNode *n = push_array(arena, D_LineNode, 1); n->v.voff_range = range; n->v.pt.line = (S64)actual_line; n->v.pt.column = 1; n->v.dbgi_key = key; SLLQueuePush(list->first, list->last, n); list->count += 1; } } } } // rjf: good src id -> push to relevant dbgi keys if(good_src_id) { di_key_list_push(arena, &array.dbgi_keys, &key); } } } di_scope_close(scope); scratch_end(scratch); return array; } internal D_LineList d_lines_from_file_path_line_num(Arena *arena, String8 file_path, S64 line_num) { D_LineListArray array = d_lines_array_from_file_path_line_range(arena, file_path, r1s64(line_num, line_num+1)); D_LineList list = {0}; if(array.count != 0) { list = array.v[0]; } return list; } //////////////////////////////// //~ rjf: Process/Thread/Module Info Lookups internal D_Entity * d_module_from_process_vaddr(D_Entity *process, U64 vaddr) { D_Entity *module = &d_nil_entity; for(D_Entity *child = process->first; !d_entity_is_nil(child); child = child->next) { if(child->kind == D_EntityKind_Module && contains_1u64(child->vaddr_rng, vaddr)) { module = child; break; } } return module; } internal D_Entity * d_module_from_thread(D_Entity *thread) { D_Entity *process = thread->parent; U64 rip = d_query_cached_rip_from_thread(thread); return d_module_from_process_vaddr(process, rip); } internal U64 d_tls_base_vaddr_from_process_root_rip(D_Entity *process, U64 root_vaddr, U64 rip_vaddr) { ProfBeginFunction(); U64 base_vaddr = 0; Temp scratch = scratch_begin(0, 0); if(!d_ctrl_targets_running()) { //- rjf: unpack module info D_Entity *module = d_module_from_process_vaddr(process, rip_vaddr); Rng1U64 tls_vaddr_range = ctrl_tls_vaddr_range_from_module(module->ctrl_machine_id, module->ctrl_handle); U64 addr_size = bit_size_from_arch(process->arch)/8; //- rjf: read module's TLS index U64 tls_index = 0; if(addr_size != 0) { CTRL_ProcessMemorySlice tls_index_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, tls_vaddr_range, 0); if(tls_index_slice.data.size >= addr_size) { tls_index = *(U64 *)tls_index_slice.data.str; } } //- rjf: PE path if(addr_size != 0) { 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_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, r1u64(thread_info_addr, thread_info_addr+addr_size), 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_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, r1u64(tls_addr_array + tls_addr_off, tls_addr_array + tls_addr_off + addr_size), 0); String8 result_data = result_slice.data; if(result_data.size >= 8) { MemoryCopy(&base_vaddr, result_data.str, sizeof(U64)); } } //- rjf: non-PE path (not implemented) #if 0 if(!bin_is_pe) { // TODO(rjf): not supported. old code from the prototype that Nick had sketched out: // TODO(nick): This code works only if the linked c runtime library is glibc. // Implement CRT detection here. U64 dtv_addr = UINT64_MAX; demon_read_memory(process->demon_handle, &dtv_addr, thread_info_addr, addr_size); /* union delta_thread_vector { size_t counter; struct { void *value; void *to_free; } pointer; }; */ U64 dtv_size = 16; U64 dtv_count = 0; demon_read_memory(process->demon_handle, &dtv_count, dtv_addr - dtv_size, addr_size); if (tls_index > 0 && tls_index < dtv_count) { demon_read_memory(process->demon_handle, &result, dtv_addr + dtv_size*tls_index, addr_size); } } #endif } scratch_end(scratch); ProfEnd(); return base_vaddr; } internal Arch d_arch_from_entity(D_Entity *entity) { return entity->arch; } internal E_String2NumMap * d_push_locals_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff) { RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); E_String2NumMap *result = e_push_locals_map_from_rdi_voff(arena, rdi, voff); return result; } internal E_String2NumMap * d_push_member_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff) { RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); E_String2NumMap *result = e_push_member_map_from_rdi_voff(arena, rdi, voff); return result; } internal B32 d_set_thread_rip(D_Entity *thread, U64 vaddr) { Temp scratch = scratch_begin(0, 0); void *block = ctrl_query_cached_reg_block_from_thread(scratch.arena, d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); regs_arch_block_write_rip(thread->arch, block, vaddr); B32 result = ctrl_thread_write_reg_block(thread->ctrl_machine_id, thread->ctrl_handle, block); // rjf: early mutation of unwind cache for immediate frontend effect if(result) { D_UnwindCache *cache = &d_state->unwind_cache; if(cache->slots_count != 0) { D_Handle thread_handle = d_handle_from_entity(thread); U64 hash = d_hash_from_string(str8_struct(&thread_handle)); U64 slot_idx = hash%cache->slots_count; D_UnwindCacheSlot *slot = &cache->slots[slot_idx]; for(D_UnwindCacheNode *n = slot->first; n != 0; n = n->next) { if(d_handle_match(n->thread, thread_handle) && n->unwind.frames.count != 0) { regs_arch_block_write_rip(thread->arch, n->unwind.frames.v[0].regs, vaddr); break; } } } } scratch_end(scratch); return result; } internal D_Entity * d_module_from_thread_candidates(D_Entity *thread, D_EntityList *candidates) { D_Entity *src_module = d_module_from_thread(thread); D_Entity *module = &d_nil_entity; D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); for(D_EntityNode *n = candidates->first; n != 0; n = n->next) { D_Entity *candidate_module = n->entity; D_Entity *candidate_process = d_entity_ancestor_from_kind(candidate_module, D_EntityKind_Process); if(candidate_process == process) { module = candidate_module; } if(candidate_module == src_module) { break; } } return module; } internal D_Unwind d_unwind_from_ctrl_unwind(Arena *arena, DI_Scope *di_scope, D_Entity *process, CTRL_Unwind *base_unwind) { Arch arch = d_arch_from_entity(process); D_Unwind result = {0}; result.frames.concrete_frame_count = base_unwind->frames.count; result.frames.total_frame_count = result.frames.concrete_frame_count; result.frames.v = push_array(arena, D_UnwindFrame, result.frames.concrete_frame_count); for(U64 idx = 0; idx < result.frames.concrete_frame_count; idx += 1) { CTRL_UnwindFrame *src = &base_unwind->frames.v[idx]; D_UnwindFrame *dst = &result.frames.v[idx]; U64 rip_vaddr = regs_rip_from_arch_block(arch, src->regs); D_Entity *module = d_module_from_process_vaddr(process, rip_vaddr); U64 rip_voff = d_voff_from_vaddr(module, rip_vaddr); DI_Key dbgi_key = d_dbgi_key_from_module(module); RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, 0); RDI_Scope *scope = rdi_scope_from_voff(rdi, rip_voff); // rjf: fill concrete frame info dst->regs = src->regs; dst->rdi = rdi; dst->procedure = rdi_element_from_name_idx(rdi, Procedures, scope->proc_idx); // rjf: push inline frames for(RDI_Scope *s = scope; s->inline_site_idx != 0; s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) { RDI_InlineSite *site = rdi_element_from_name_idx(rdi, InlineSites, s->inline_site_idx); D_UnwindInlineFrame *inline_frame = push_array(arena, D_UnwindInlineFrame, 1); DLLPushFront(dst->first_inline_frame, dst->last_inline_frame, inline_frame); inline_frame->inline_site = site; dst->inline_frame_count += 1; result.frames.inline_frame_count += 1; result.frames.total_frame_count += 1; } } return result; } //////////////////////////////// //~ rjf: Target Controls #if !defined(BLAKE2_H) #define HAVE_SSE2 #include "third_party/blake2/blake2.h" #include "third_party/blake2/blake2b.c" #endif //- rjf: state which parameterizes runs, but can be live-edited -> hash internal U128 d_hash_from_ctrl_param_state(void) { U128 result = {0}; Temp scratch = scratch_begin(0, 0); { // rjf: build data strings of all param data String8List strings = {0}; { for(D_HandleNode *n = d_state->frozen_threads.first; n != 0; n = n->next) { str8_list_push(scratch.arena, &strings, str8_struct(&n->handle)); } D_EntityList bps = d_query_cached_entity_list_with_kind(D_EntityKind_Breakpoint); for(D_EntityNode *n = bps.first; n != 0; n = n->next) { D_Entity *bp = n->entity; D_Entity *loc = d_entity_child_from_kind(bp, D_EntityKind_Location); D_Entity *cnd = d_entity_child_from_kind(bp, D_EntityKind_Condition); str8_list_push(scratch.arena, &strings, str8_struct(&bp->disabled)); str8_list_push(scratch.arena, &strings, loc->string); str8_list_push(scratch.arena, &strings, str8_struct(&loc->text_point)); str8_list_push(scratch.arena, &strings, str8_struct(&loc->vaddr)); str8_list_push(scratch.arena, &strings, str8_struct(&cnd->string)); } } // rjf: join & hash to produce result String8 string = str8_list_join(scratch.arena, &strings, 0); blake2b((U8 *)&result.u64[0], sizeof(result), string.str, string.size, 0, 0); } scratch_end(scratch); return result; } //- rjf: control message dispatching internal void d_push_ctrl_msg(CTRL_Msg *msg) { CTRL_Msg *dst = ctrl_msg_list_push(d_state->ctrl_msg_arena, &d_state->ctrl_msgs); ctrl_msg_deep_copy(d_state->ctrl_msg_arena, dst, msg); if(d_state->ctrl_soft_halt_issued == 0 && d_ctrl_targets_running()) { d_state->ctrl_soft_halt_issued = 1; ctrl_halt(); } } //- rjf: control thread running internal void d_ctrl_run(D_RunKind run, D_Entity *run_thread, CTRL_RunFlags flags, CTRL_TrapList *run_traps) { Temp scratch = scratch_begin(0, 0); // rjf: compute hash of all run-parameterization entities, store { d_state->ctrl_last_run_param_state_hash = d_hash_from_ctrl_param_state(); } // rjf: build run message CTRL_Msg msg = {(run == D_RunKind_Run || run == D_RunKind_Step) ? CTRL_MsgKind_Run : CTRL_MsgKind_SingleStep}; { D_EntityList user_bps = d_query_cached_entity_list_with_kind(D_EntityKind_Breakpoint); D_Entity *process = d_entity_ancestor_from_kind(run_thread, D_EntityKind_Process); msg.run_flags = flags; msg.machine_id = run_thread->ctrl_machine_id; msg.entity = run_thread->ctrl_handle; msg.parent = process->ctrl_handle; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); if(run_traps != 0) { MemoryCopyStruct(&msg.traps, run_traps); } for(D_EntityNode *user_bp_n = user_bps.first; user_bp_n != 0; user_bp_n = user_bp_n->next) { // rjf: unpack user breakpoint entity D_Entity *user_bp = user_bp_n->entity; if(user_bp->disabled) { continue; } D_Entity *loc = d_entity_child_from_kind(user_bp, D_EntityKind_Location); D_Entity *cnd = d_entity_child_from_kind(user_bp, D_EntityKind_Condition); // rjf: textual location -> add breakpoints for all possible override locations if(loc->flags & D_EntityFlag_HasTextPoint) { String8List overrides = d_possible_overrides_from_file_path(scratch.arena, loc->string); for(String8Node *n = overrides.first; n != 0; n = n->next) { CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_FileNameAndLineColNumber}; ctrl_user_bp.string = n->string; ctrl_user_bp.pt = loc->text_point; ctrl_user_bp.condition = cnd->string; ctrl_user_breakpoint_list_push(scratch.arena, &msg.user_bps, &ctrl_user_bp); } } // rjf: virtual address location -> add breakpoint for address else if(loc->flags & D_EntityFlag_HasVAddr) { CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_VirtualAddress}; ctrl_user_bp.u64 = loc->vaddr; ctrl_user_bp.condition = cnd->string; ctrl_user_breakpoint_list_push(scratch.arena, &msg.user_bps, &ctrl_user_bp); } // rjf: symbol name location -> add breakpoint for symbol name else if(loc->string.size != 0) { CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_SymbolNameAndOffset}; ctrl_user_bp.string = loc->string; ctrl_user_bp.condition = cnd->string; ctrl_user_breakpoint_list_push(scratch.arena, &msg.user_bps, &ctrl_user_bp); } } for(D_HandleNode *n = d_state->frozen_threads.first; n != 0; n = n->next) { D_Entity *thread = d_entity_from_handle(n->handle); if(!d_entity_is_nil(thread)) { CTRL_MachineIDHandlePair pair = {thread->ctrl_machine_id, thread->ctrl_handle}; ctrl_machine_id_handle_pair_list_push(scratch.arena, &msg.freeze_state_threads, &pair); } } msg.freeze_state_is_frozen = 1; } // rjf: push msg d_push_ctrl_msg(&msg); // rjf: copy run traps to scratch (needed, if the caller can pass `d_state->ctrl_last_run_traps`) CTRL_TrapList run_traps_copy = {0}; if(run_traps != 0) { run_traps_copy = ctrl_trap_list_copy(scratch.arena, run_traps); } // rjf: store last run info arena_clear(d_state->ctrl_last_run_arena); d_state->ctrl_last_run_kind = run; d_state->ctrl_last_run_frame_idx = d_frame_index(); d_state->ctrl_last_run_thread = d_handle_from_entity(run_thread); d_state->ctrl_last_run_flags = flags; d_state->ctrl_last_run_traps = ctrl_trap_list_copy(d_state->ctrl_last_run_arena, &run_traps_copy); d_state->ctrl_is_running = 1; // rjf: reset selected frame to top unwind d_state->base_regs.v.unwind_count = 0; d_state->base_regs.v.inline_depth = 0; scratch_end(scratch); } //- rjf: stopped info from the control thread internal CTRL_Event d_ctrl_last_stop_event(void) { return d_state->ctrl_last_stop_event; } //////////////////////////////// //~ rjf: Evaluation Context //- rjf: entity <-> eval space internal D_Entity * d_entity_from_eval_space(E_Space space) { D_Entity *entity = &d_nil_entity; if(space.u64[0] == 0 && space.u64[1] != 0) { entity = (D_Entity *)space.u64[1]; } return entity; } internal E_Space d_eval_space_from_entity(D_Entity *entity) { E_Space space = {0}; space.u64[1] = (U64)entity; return space; } //- rjf: eval space reads/writes internal B32 d_eval_space_read(void *u, E_Space space, void *out, Rng1U64 range) { B32 result = 0; D_Entity *entity = d_entity_from_eval_space(space); switch(entity->kind) { //- rjf: nil-space -> fall back to file system case D_EntityKind_Nil: { U128 key = space; U128 hash = hs_hash_from_key(key, 0); HS_Scope *scope = hs_scope_open(); { String8 data = hs_data_from_hash(scope, hash); Rng1U64 legal_range = r1u64(0, data.size); Rng1U64 read_range = intersect_1u64(range, legal_range); if(read_range.min < read_range.max) { result = 1; MemoryCopy(out, data.str + read_range.min, dim_1u64(read_range)); } } hs_scope_close(scope); }break; //- rjf: default -> evaluating a debugger entity; read from entity POD evaluation default: { Temp scratch = scratch_begin(0, 0); arena_push(scratch.arena, 0, 64); U64 pos_min = arena_pos(scratch.arena); D_EntityEval *eval = d_eval_from_entity(scratch.arena, entity); U64 pos_opl = arena_pos(scratch.arena); Rng1U64 legal_range = r1u64(0, pos_opl-pos_min); if(contains_1u64(legal_range, range.min)) { result = 1; U64 range_dim = dim_1u64(range); U64 bytes_to_read = Min(range_dim, (legal_range.max - range.min)); MemoryCopy(out, ((U8 *)eval) + range.min, bytes_to_read); if(bytes_to_read < range_dim) { MemoryZero((U8 *)out + bytes_to_read, range_dim - bytes_to_read); } } scratch_end(scratch); }break; //- rjf: process -> reading process memory case D_EntityKind_Process: { Temp scratch = scratch_begin(0, 0); CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, entity->ctrl_machine_id, entity->ctrl_handle, range, d_state->frame_eval_memread_endt_us); String8 data = slice.data; if(data.size == dim_1u64(range)) { result = 1; MemoryCopy(out, data.str, data.size); } scratch_end(scratch); }break; //- rjf: thread -> reading from thread register block case D_EntityKind_Thread: { Temp scratch = scratch_begin(0, 0); CTRL_Unwind unwind = d_query_cached_unwind_from_thread(entity); U64 frame_idx = e_interpret_ctx->reg_unwind_count; if(frame_idx < unwind.frames.count) { CTRL_UnwindFrame *f = &unwind.frames.v[frame_idx]; U64 regs_size = regs_block_size_from_arch(e_interpret_ctx->reg_arch); Rng1U64 legal_range = r1u64(0, regs_size); Rng1U64 read_range = intersect_1u64(legal_range, range); U64 read_size = dim_1u64(read_range); MemoryCopy(out, (U8 *)f->regs + read_range.min, read_size); result = (read_size == dim_1u64(range)); } scratch_end(scratch); }break; } return result; } internal B32 d_eval_space_write(void *u, E_Space space, void *in, Rng1U64 range) { B32 result = 0; D_Entity *entity = d_entity_from_eval_space(space); switch(entity->kind) { //- rjf: default -> making commits to entity evaluation default: { Temp scratch = scratch_begin(0, 0); D_EntityEval *eval = d_eval_from_entity(scratch.arena, entity); U64 range_dim = dim_1u64(range); if(range.min == OffsetOf(D_EntityEval, enabled) && range_dim >= 1) { result = 1; B32 new_enabled = !!((U8 *)in)[0]; d_entity_equip_disabled(entity, !new_enabled); } else if(range.min == eval->label_off && range_dim >= 1) { result = 1; String8 new_name = str8_cstring_capped((U8 *)in, (U8 *)in+range_dim); d_entity_equip_name(entity, new_name); } else if(range.min == eval->condition_off && range_dim >= 1) { result = 1; D_Entity *condition = d_entity_child_from_kind(entity, D_EntityKind_Condition); if(d_entity_is_nil(condition)) { condition = d_entity_alloc(entity, D_EntityKind_Condition); } String8 new_name = str8_cstring_capped((U8 *)in, (U8 *)in+range_dim); d_entity_equip_name(condition, new_name); } scratch_end(scratch); }break; //- rjf: process -> commit to process memory case D_EntityKind_Process: { result = ctrl_process_write(entity->ctrl_machine_id, entity->ctrl_handle, range, in); }break; //- rjf: thread -> commit to thread's register block case D_EntityKind_Thread: { CTRL_Unwind unwind = d_query_cached_unwind_from_thread(entity); U64 frame_idx = 0; if(frame_idx < unwind.frames.count) { Temp scratch = scratch_begin(0, 0); U64 regs_size = regs_block_size_from_arch(d_arch_from_entity(entity)); Rng1U64 legal_range = r1u64(0, regs_size); Rng1U64 write_range = intersect_1u64(legal_range, range); U64 write_size = dim_1u64(write_range); CTRL_UnwindFrame *f = &unwind.frames.v[frame_idx]; void *new_regs = push_array(scratch.arena, U8, regs_size); MemoryCopy(new_regs, f->regs, regs_size); MemoryCopy((U8 *)new_regs + write_range.min, in, write_size); result = ctrl_thread_write_reg_block(entity->ctrl_machine_id, entity->ctrl_handle, new_regs); scratch_end(scratch); } }break; } return result; } //- rjf: asynchronous streamed reads -> hashes from spaces internal U128 d_key_from_eval_space_range(E_Space space, Rng1U64 range, B32 zero_terminated) { U128 result = {0}; D_Entity *entity = d_entity_from_eval_space(space); switch(entity->kind) { //- rjf: nil space -> filesystem key encoded inside of `space` case D_EntityKind_Nil: { result = space; }break; //- rjf: process space -> query case D_EntityKind_Process: { result = ctrl_hash_store_key_from_process_vaddr_range(entity->ctrl_machine_id, entity->ctrl_handle, range, zero_terminated); }break; } return result; } //- rjf: space -> entire range internal Rng1U64 d_whole_range_from_eval_space(E_Space space) { Rng1U64 result = r1u64(0, 0); D_Entity *entity = d_entity_from_eval_space(space); switch(entity->kind) { //- rjf: nil space -> filesystem key encoded inside of `space` case D_EntityKind_Nil: { HS_Scope *scope = hs_scope_open(); U128 hash = {0}; for(U64 idx = 0; idx < 2; idx += 1) { hash = hs_hash_from_key(space, idx); if(!u128_match(hash, u128_zero())) { break; } } String8 data = hs_data_from_hash(scope, hash); result = r1u64(0, data.size); hs_scope_close(scope); }break; case D_EntityKind_Process: { result = r1u64(0, 0x7FFFFFFFFFFFull); }break; } return result; } //////////////////////////////// //~ rjf: Evaluation Views #if !defined(BLAKE2_H) #define HAVE_SSE2 #include "third_party/blake2/blake2.h" #include "third_party/blake2/blake2b.c" #endif internal D_EvalViewKey d_eval_view_key_make(U64 v0, U64 v1) { D_EvalViewKey v = {v0, v1}; return v; } internal D_EvalViewKey d_eval_view_key_from_string(String8 string) { D_EvalViewKey key = {0}; blake2b((U8 *)&key.u64[0], sizeof(key), string.str, string.size, 0, 0); return key; } internal D_EvalViewKey d_eval_view_key_from_stringf(char *fmt, ...) { Temp scratch = scratch_begin(0, 0); va_list args; va_start(args, fmt); String8 string = push_str8fv(scratch.arena, fmt, args); va_end(args); D_EvalViewKey key = d_eval_view_key_from_string(string); scratch_end(scratch); return key; } internal B32 d_eval_view_key_match(D_EvalViewKey a, D_EvalViewKey b) { return MemoryMatchStruct(&a, &b); } internal D_EvalView * d_eval_view_from_key(D_EvalViewKey key) { D_EvalView *eval_view = &d_nil_eval_view; { U64 slot_idx = key.u64[1]%d_state->eval_view_cache.slots_count; D_EvalViewSlot *slot = &d_state->eval_view_cache.slots[slot_idx]; for(D_EvalView *v = slot->first; v != &d_nil_eval_view && v != 0; v = v->hash_next) { if(d_eval_view_key_match(key, v->key)) { eval_view = v; break; } } if(eval_view == &d_nil_eval_view) { eval_view = push_array(d_state->arena, D_EvalView, 1); DLLPushBack_NPZ(&d_nil_eval_view, slot->first, slot->last, eval_view, hash_next, hash_prev); eval_view->key = key; eval_view->arena = arena_alloc(); d_expand_tree_table_init(eval_view->arena, &eval_view->expand_tree_table, 256); eval_view->view_rule_table.slot_count = 64; eval_view->view_rule_table.slots = push_array(eval_view->arena, D_EvalViewRuleCacheSlot, eval_view->view_rule_table.slot_count); } } return eval_view; } //- rjf: key -> view rules internal void d_eval_view_set_key_rule(D_EvalView *eval_view, D_ExpandKey key, String8 view_rule_string) { //- rjf: key -> hash * slot idx * slot String8 key_string = str8_struct(&key); U64 hash = d_hash_from_string(key_string); U64 slot_idx = hash%eval_view->view_rule_table.slot_count; D_EvalViewRuleCacheSlot *slot = &eval_view->view_rule_table.slots[slot_idx]; //- rjf: slot -> existing node D_EvalViewRuleCacheNode *existing_node = 0; for(D_EvalViewRuleCacheNode *n = slot->first; n != 0; n = n->hash_next) { if(d_expand_key_match(n->key, key)) { existing_node = n; break; } } //- rjf: existing node * new node -> node D_EvalViewRuleCacheNode *node = existing_node; if(node == 0) { node = push_array(eval_view->arena, D_EvalViewRuleCacheNode, 1); DLLPushBack_NP(slot->first, slot->last, node, hash_next, hash_prev); node->key = key; node->buffer_cap = 512; node->buffer = push_array(eval_view->arena, U8, node->buffer_cap); } //- rjf: mutate node if(node != 0) { node->buffer_string_size = ClampTop(view_rule_string.size, node->buffer_cap); MemoryCopy(node->buffer, view_rule_string.str, node->buffer_string_size); } } internal String8 d_eval_view_rule_from_key(D_EvalView *eval_view, D_ExpandKey key) { String8 result = {0}; //- rjf: key -> hash * slot idx * slot String8 key_string = str8_struct(&key); U64 hash = d_hash_from_string(key_string); U64 slot_idx = hash%eval_view->view_rule_table.slot_count; D_EvalViewRuleCacheSlot *slot = &eval_view->view_rule_table.slots[slot_idx]; //- rjf: slot -> existing node D_EvalViewRuleCacheNode *existing_node = 0; for(D_EvalViewRuleCacheNode *n = slot->first; n != 0; n = n->hash_next) { if(d_expand_key_match(n->key, key)) { existing_node = n; break; } } //- rjf: node -> result if(existing_node != 0) { result = str8(existing_node->buffer, existing_node->buffer_string_size); } return result; } //////////////////////////////// //~ rjf: Evaluation View Visualization & Interaction //- rjf: expr * view rule table -> expr internal E_Expr * d_expr_from_expr_cfg(Arena *arena, E_Expr *expr, D_CfgTable *cfg) { for(D_CfgVal *val = cfg->first_val; val != 0 && val != &d_nil_cfg_val; val = val->linear_next) { D_ViewRuleSpec *spec = d_view_rule_spec_from_string(val->string); if(spec->info.flags & D_ViewRuleSpecInfoFlag_ExprResolution) { expr = spec->info.expr_resolution(arena, expr, val->last->root); } } return expr; } //- rjf: evaluation value string builder helpers internal String8 d_string_from_ascii_value(Arena *arena, U8 val) { String8 result = {0}; switch(val) { case 0x00:{result = str8_lit("\\0");}break; case 0x07:{result = str8_lit("\\a");}break; case 0x08:{result = str8_lit("\\b");}break; case 0x0c:{result = str8_lit("\\f");}break; case 0x0a:{result = str8_lit("\\n");}break; case 0x0d:{result = str8_lit("\\r");}break; case 0x09:{result = str8_lit("\\t");}break; case 0x0b:{result = str8_lit("\\v");}break; case 0x3f:{result = str8_lit("\\?");}break; case '"': {result = str8_lit("\\\"");}break; case '\'':{result = str8_lit("\\'");}break; case '\\':{result = str8_lit("\\\\");}break; default: if(32 <= val && val < 255) { result = push_str8f(arena, "%c", val); }break; } return result; } internal String8 d_string_from_hresult_facility_code(U32 code) { String8 result = {0}; switch(code) { default:{}break; case 0x1:{result = str8_lit("RPC");}break; case 0x2:{result = str8_lit("DISPATCH");}break; case 0x3:{result = str8_lit("STORAGE");}break; case 0x4:{result = str8_lit("ITF");}break; case 0x7:{result = str8_lit("WIN32");}break; case 0x8:{result = str8_lit("WINDOWS");}break; case 0x9:{result = str8_lit("SECURITY|SSPI");}break; case 0xA:{result = str8_lit("CONTROL");}break; case 0xB:{result = str8_lit("CERT");}break; case 0xC:{result = str8_lit("INTERNET");}break; case 0xD:{result = str8_lit("MEDIASERVER");}break; case 0xE:{result = str8_lit("MSMQ");}break; case 0xF:{result = str8_lit("SETUPAPI");}break; case 0x10:{result = str8_lit("SCARD");}break; case 0x11:{result = str8_lit("COMPLUS");}break; case 0x12:{result = str8_lit("AAF");}break; case 0x13:{result = str8_lit("URT");}break; case 0x14:{result = str8_lit("ACS");}break; case 0x15:{result = str8_lit("DPLAY");}break; case 0x16:{result = str8_lit("UMI");}break; case 0x17:{result = str8_lit("SXS");}break; case 0x18:{result = str8_lit("WINDOWS_CE");}break; case 0x19:{result = str8_lit("HTTP");}break; case 0x1A:{result = str8_lit("USERMODE_COMMONLOG");}break; case 0x1B:{result = str8_lit("WER");}break; case 0x1F:{result = str8_lit("USERMODE_FILTER_MANAGER");}break; case 0x20:{result = str8_lit("BACKGROUNDCOPY");}break; case 0x21:{result = str8_lit("CONFIGURATION|WIA");}break; case 0x22:{result = str8_lit("STATE_MANAGEMENT");}break; case 0x23:{result = str8_lit("METADIRECTORY");}break; case 0x24:{result = str8_lit("WINDOWSUPDATE");}break; case 0x25:{result = str8_lit("DIRECTORYSERVICE");}break; case 0x26:{result = str8_lit("GRAPHICS");}break; case 0x27:{result = str8_lit("SHELL|NAP");}break; case 0x28:{result = str8_lit("TPM_SERVICES");}break; case 0x29:{result = str8_lit("TPM_SOFTWARE");}break; case 0x2A:{result = str8_lit("UI");}break; case 0x2B:{result = str8_lit("XAML");}break; case 0x2C:{result = str8_lit("ACTION_QUEUE");}break; case 0x30:{result = str8_lit("WINDOWS_SETUP|PLA");}break; case 0x31:{result = str8_lit("FVE");}break; case 0x32:{result = str8_lit("FWP");}break; case 0x33:{result = str8_lit("WINRM");}break; case 0x34:{result = str8_lit("NDIS");}break; case 0x35:{result = str8_lit("USERMODE_HYPERVISOR");}break; case 0x36:{result = str8_lit("CMI");}break; case 0x37:{result = str8_lit("USERMODE_VIRTUALIZATION");}break; case 0x38:{result = str8_lit("USERMODE_VOLMGR");}break; case 0x39:{result = str8_lit("BCD");}break; case 0x3A:{result = str8_lit("USERMODE_VHD");}break; case 0x3C:{result = str8_lit("SDIAG");}break; case 0x3D:{result = str8_lit("WINPE|WEBSERVICES");}break; case 0x3E:{result = str8_lit("WPN");}break; case 0x3F:{result = str8_lit("WINDOWS_STORE");}break; case 0x40:{result = str8_lit("INPUT");}break; case 0x42:{result = str8_lit("EAP");}break; case 0x50:{result = str8_lit("WINDOWS_DEFENDER");}break; case 0x51:{result = str8_lit("OPC");}break; case 0x52:{result = str8_lit("XPS");}break; case 0x53:{result = str8_lit("RAS");}break; case 0x54:{result = str8_lit("POWERSHELL|MBN");}break; case 0x55:{result = str8_lit("EAS");}break; case 0x62:{result = str8_lit("P2P_INT");}break; case 0x63:{result = str8_lit("P2P");}break; case 0x64:{result = str8_lit("DAF");}break; case 0x65:{result = str8_lit("BLUETOOTH_ATT");}break; case 0x66:{result = str8_lit("AUDIO");}break; case 0x6D:{result = str8_lit("VISUALCPP");}break; case 0x70:{result = str8_lit("SCRIPT");}break; case 0x71:{result = str8_lit("PARSE");}break; case 0x78:{result = str8_lit("BLB");}break; case 0x79:{result = str8_lit("BLB_CLI");}break; case 0x7A:{result = str8_lit("WSBAPP");}break; case 0x80:{result = str8_lit("BLBUI");}break; case 0x81:{result = str8_lit("USN");}break; case 0x82:{result = str8_lit("USERMODE_VOLSNAP");}break; case 0x83:{result = str8_lit("TIERING");}break; case 0x85:{result = str8_lit("WSB_ONLINE");}break; case 0x86:{result = str8_lit("ONLINE_ID");}break; case 0x99:{result = str8_lit("DLS");}break; case 0xA0:{result = str8_lit("SOS");}break; case 0xB0:{result = str8_lit("DEBUGGERS");}break; case 0xE7:{result = str8_lit("USERMODE_SPACES");}break; case 0x100:{result = str8_lit("DMSERVER|RESTORE|SPP");}break; case 0x101:{result = str8_lit("DEPLOYMENT_SERVICES_SERVER");}break; case 0x102:{result = str8_lit("DEPLOYMENT_SERVICES_IMAGING");}break; case 0x103:{result = str8_lit("DEPLOYMENT_SERVICES_MANAGEMENT");}break; case 0x104:{result = str8_lit("DEPLOYMENT_SERVICES_UTIL");}break; case 0x105:{result = str8_lit("DEPLOYMENT_SERVICES_BINLSVC");}break; case 0x107:{result = str8_lit("DEPLOYMENT_SERVICES_PXE");}break; case 0x108:{result = str8_lit("DEPLOYMENT_SERVICES_TFTP");}break; case 0x110:{result = str8_lit("DEPLOYMENT_SERVICES_TRANSPORT_MANAGEMENT");}break; case 0x116:{result = str8_lit("DEPLOYMENT_SERVICES_DRIVER_PROVISIONING");}break; case 0x121:{result = str8_lit("DEPLOYMENT_SERVICES_MULTICAST_SERVER");}break; case 0x122:{result = str8_lit("DEPLOYMENT_SERVICES_MULTICAST_CLIENT");}break; case 0x125:{result = str8_lit("DEPLOYMENT_SERVICES_CONTENT_PROVIDER");}break; case 0x131:{result = str8_lit("LINGUISTIC_SERVICES");}break; case 0x375:{result = str8_lit("WEB");}break; case 0x376:{result = str8_lit("WEB_SOCKET");}break; case 0x446:{result = str8_lit("AUDIOSTREAMING");}break; case 0x600:{result = str8_lit("ACCELERATOR");}break; case 0x701:{result = str8_lit("MOBILE");}break; case 0x7CC:{result = str8_lit("WMAAECMA");}break; case 0x801:{result = str8_lit("WEP");}break; case 0x802:{result = str8_lit("SYNCENGINE");}break; case 0x878:{result = str8_lit("DIRECTMUSIC");}break; case 0x879:{result = str8_lit("DIRECT3D10");}break; case 0x87A:{result = str8_lit("DXGI");}break; case 0x87B:{result = str8_lit("DXGI_DDI");}break; case 0x87C:{result = str8_lit("DIRECT3D11");}break; case 0x888:{result = str8_lit("LEAP");}break; case 0x889:{result = str8_lit("AUDCLNT");}break; case 0x898:{result = str8_lit("WINCODEC_DWRITE_DWM");}break; case 0x899:{result = str8_lit("DIRECT2D");}break; case 0x900:{result = str8_lit("DEFRAG");}break; case 0x901:{result = str8_lit("USERMODE_SDBUS");}break; case 0x902:{result = str8_lit("JSCRIPT");}break; case 0xA01:{result = str8_lit("PIDGENX");}break; } return result; } internal String8 d_string_from_hresult_code(U32 code) { String8 result = {0}; switch(code) { default:{}break; case 0x00000000: {result = str8_lit("S_OK: Operation successful");}break; case 0x00000001: {result = str8_lit("S_FALSE: Operation successful but returned no results");}break; case 0x80004004: {result = str8_lit("E_ABORT: Operation aborted");}break; case 0x80004005: {result = str8_lit("E_FAIL: Unspecified failure");}break; case 0x80004002: {result = str8_lit("E_NOINTERFACE: No such interface supported");}break; case 0x80004001: {result = str8_lit("E_NOTIMPL: Not implemented");}break; case 0x80004003: {result = str8_lit("E_POINTER: Pointer that is not valid");}break; case 0x8000FFFF: {result = str8_lit("E_UNEXPECTED: Unexpected failure");}break; case 0x80070005: {result = str8_lit("E_ACCESSDENIED: General access denied error");}break; case 0x80070006: {result = str8_lit("E_HANDLE: Handle that is not valid");}break; case 0x80070057: {result = str8_lit("E_INVALIDARG: One or more arguments are not valid");}break; case 0x8007000E: {result = str8_lit("E_OUTOFMEMORY: Failed to allocate necessary memory");}break; } return result; } internal String8 d_string_from_simple_typed_eval(Arena *arena, D_EvalVizStringFlags flags, U32 radix, E_Eval eval) { String8 result = {0}; E_TypeKey type_key = e_type_unwrap(eval.type_key); E_TypeKind type_kind = e_type_kind_from_key(type_key); U64 type_byte_size = e_type_byte_size_from_key(type_key); U8 digit_group_separator = 0; if(!(flags & D_EvalVizStringFlag_ReadOnlyDisplayRules)) { digit_group_separator = 0; } switch(type_kind) { default:{}break; case E_TypeKind_Handle: { result = str8_from_s64(arena, eval.value.s64, radix, 0, digit_group_separator); }break; case E_TypeKind_HResult: { if(flags & D_EvalVizStringFlag_ReadOnlyDisplayRules) { Temp scratch = scratch_begin(&arena, 1); U32 hresult_value = (U32)eval.value.u64; U32 is_error = !!(hresult_value & (1ull<<31)); U32 error_code = (hresult_value); U32 facility = (hresult_value & 0x7ff0000) >> 16; String8 value_string = str8_from_s64(scratch.arena, eval.value.u64, radix, 0, digit_group_separator); String8 facility_string = d_string_from_hresult_facility_code(facility); String8 error_string = d_string_from_hresult_code(error_code); result = push_str8f(arena, "%S%s%s%S%s%s%S%s", error_string, error_string.size != 0 ? " " : "", facility_string.size != 0 ? "[" : "", facility_string, facility_string.size != 0 ? "] ": "", error_string.size != 0 ? "(" : "", value_string, error_string.size != 0 ? ")" : ""); scratch_end(scratch); } else { result = str8_from_s64(arena, eval.value.u64, radix, 0, digit_group_separator); } }break; case E_TypeKind_Char8: case E_TypeKind_Char16: case E_TypeKind_Char32: case E_TypeKind_UChar8: case E_TypeKind_UChar16: case E_TypeKind_UChar32: { String8 char_str = d_string_from_ascii_value(arena, eval.value.s64); if(char_str.size != 0) { if(flags & D_EvalVizStringFlag_ReadOnlyDisplayRules) { String8 imm_string = str8_from_s64(arena, eval.value.s64, radix, 0, digit_group_separator); result = push_str8f(arena, "'%S' (%S)", char_str, imm_string); } else { result = push_str8f(arena, "'%S'", char_str); } } else { result = str8_from_s64(arena, eval.value.s64, radix, 0, digit_group_separator); } }break; case E_TypeKind_S8: case E_TypeKind_S16: case E_TypeKind_S32: case E_TypeKind_S64: { result = str8_from_s64(arena, eval.value.s64, radix, 0, digit_group_separator); }break; case E_TypeKind_U8: case E_TypeKind_U16: case E_TypeKind_U32: case E_TypeKind_U64: { U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; result = str8_from_u64(arena, eval.value.u64, radix, min_digits, digit_group_separator); }break; case E_TypeKind_U128: { Temp scratch = scratch_begin(&arena, 1); U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; String8 upper64 = str8_from_u64(scratch.arena, eval.value.u128.u64[0], radix, min_digits, digit_group_separator); String8 lower64 = str8_from_u64(scratch.arena, eval.value.u128.u64[1], radix, min_digits, digit_group_separator); result = push_str8f(arena, "%S:%S", upper64, lower64); scratch_end(scratch); }break; case E_TypeKind_F32: {result = push_str8f(arena, "%f", eval.value.f32);}break; case E_TypeKind_F64: {result = push_str8f(arena, "%f", eval.value.f64);}break; case E_TypeKind_Bool:{result = push_str8f(arena, "%s", eval.value.u64 ? "true" : "false");}break; case E_TypeKind_Ptr: {result = push_str8f(arena, "0x%I64x", eval.value.u64);}break; case E_TypeKind_LRef:{result = push_str8f(arena, "0x%I64x", eval.value.u64);}break; case E_TypeKind_RRef:{result = push_str8f(arena, "0x%I64x", eval.value.u64);}break; case E_TypeKind_Function:{result = push_str8f(arena, "0x%I64x", eval.value.u64);}break; case E_TypeKind_Enum: { Temp scratch = scratch_begin(&arena, 1); E_Type *type = e_type_from_key(scratch.arena, type_key); String8 constant_name = {0}; for(U64 val_idx = 0; val_idx < type->count; val_idx += 1) { if(eval.value.u64 == type->enum_vals[val_idx].val) { constant_name = type->enum_vals[val_idx].name; break; } } if(flags & D_EvalVizStringFlag_ReadOnlyDisplayRules) { if(constant_name.size != 0) { result = push_str8f(arena, "0x%I64x (%S)", eval.value.u64, constant_name); } else { result = push_str8f(arena, "0x%I64x (%I64u)", eval.value.u64, eval.value.u64); } } else if(constant_name.size != 0) { result = push_str8_copy(arena, constant_name); } else { result = push_str8f(arena, "0x%I64x (%I64u)", eval.value.u64, eval.value.u64); } scratch_end(scratch); }break; } return result; } internal String8 d_escaped_from_raw_string(Arena *arena, String8 raw) { Temp scratch = scratch_begin(&arena, 1); String8List parts = {0}; U64 start_split_idx = 0; for(U64 idx = 0; idx <= raw.size; idx += 1) { U8 byte = (idx < raw.size) ? raw.str[idx] : 0; B32 split = 1; String8 separator_replace = {0}; switch(byte) { default:{split = 0;}break; case 0: {}break; case '\a': {separator_replace = str8_lit("\\a");}break; case '\b': {separator_replace = str8_lit("\\b");}break; case '\f': {separator_replace = str8_lit("\\f");}break; case '\n': {separator_replace = str8_lit("\\n");}break; case '\r': {separator_replace = str8_lit("\\r");}break; case '\t': {separator_replace = str8_lit("\\t");}break; case '\v': {separator_replace = str8_lit("\\v");}break; case '\\': {separator_replace = str8_lit("\\\\");}break; case '"': {separator_replace = str8_lit("\\\"");}break; case '?': {separator_replace = str8_lit("\\?");}break; } if(split) { String8 substr = str8_substr(raw, r1u64(start_split_idx, idx)); start_split_idx = idx+1; str8_list_push(scratch.arena, &parts, substr); if(separator_replace.size != 0) { str8_list_push(scratch.arena, &parts, separator_replace); } } } StringJoin join = {0}; String8 result = str8_list_join(arena, &parts, &join); scratch_end(scratch); return result; } //- rjf: type info -> expandability/editablity internal B32 d_type_key_is_expandable(E_TypeKey type_key) { B32 result = 0; for(E_TypeKey t = type_key; !result; t = e_type_unwrap(e_type_direct_from_key(e_type_unwrap(t)))) { E_TypeKind kind = e_type_kind_from_key(t); if(kind == E_TypeKind_Null || kind == E_TypeKind_Function) { break; } if(kind == E_TypeKind_Struct || kind == E_TypeKind_Union || kind == E_TypeKind_Class || kind == E_TypeKind_Array || kind == E_TypeKind_Enum) { result = 1; } } return result; } internal B32 d_type_key_is_editable(E_TypeKey type_key) { B32 result = 0; for(E_TypeKey t = type_key; !result; t = e_type_unwrap(e_type_direct_from_key(e_type_unwrap(t)))) { E_TypeKind kind = e_type_kind_from_key(t); if(kind == E_TypeKind_Null || kind == E_TypeKind_Function) { break; } if((E_TypeKind_FirstBasic <= kind && kind <= E_TypeKind_LastBasic) || e_type_kind_is_pointer_or_ref(kind)) { result = 1; } } return result; } //- rjf: writing values back to child processes internal B32 d_commit_eval_value_string(E_Eval dst_eval, String8 string) { B32 result = 0; if(dst_eval.mode == E_Mode_Offset) { Temp scratch = scratch_begin(0, 0); E_TypeKey type_key = e_type_unwrap(dst_eval.type_key); E_TypeKind type_kind = e_type_kind_from_key(type_key); E_TypeKey direct_type_key = e_type_unwrap(e_type_direct_from_key(e_type_unwrap(dst_eval.type_key))); E_TypeKind direct_type_kind = e_type_kind_from_key(direct_type_key); String8 commit_data = {0}; B32 commit_at_ptr_dest = 0; if(E_TypeKind_FirstBasic <= type_kind && type_kind <= E_TypeKind_LastBasic) { E_Eval src_eval = e_eval_from_string(scratch.arena, string); commit_data = push_str8_copy(scratch.arena, str8_struct(&src_eval.value)); commit_data.size = Min(commit_data.size, e_type_byte_size_from_key(type_key)); } else if(type_kind == E_TypeKind_Ptr || type_kind == E_TypeKind_Array) { E_Eval src_eval = e_eval_from_string(scratch.arena, string); E_Eval src_eval_value = e_value_eval_from_eval(src_eval); E_TypeKind src_eval_value_type_kind = e_type_kind_from_key(src_eval_value.type_key); if(type_kind == E_TypeKind_Ptr && (e_type_kind_is_pointer_or_ref(src_eval_value_type_kind) || e_type_kind_is_integer(src_eval_value_type_kind)) && src_eval_value.value.u64 != 0 && src_eval_value.mode == E_Mode_Value) { commit_data = push_str8_copy(scratch.arena, str8_struct(&src_eval.value)); commit_data.size = Min(commit_data.size, e_type_byte_size_from_key(type_key)); } else if(direct_type_kind == E_TypeKind_Char8 || direct_type_kind == E_TypeKind_UChar8 || e_type_kind_is_integer(direct_type_kind)) { if(string.size >= 1 && string.str[0] == '"') { string = str8_skip(string, 1); } if(string.size >= 1 && string.str[string.size-1] == '"') { string = str8_chop(string, 1); } commit_data = e_raw_from_escaped_string(scratch.arena, string); commit_data.size += 1; if(type_kind == E_TypeKind_Ptr) { commit_at_ptr_dest = 1; } } } if(commit_data.size != 0 && e_type_byte_size_from_key(type_key) != 0) { U64 dst_offset = dst_eval.value.u64; if(dst_eval.mode == E_Mode_Offset && commit_at_ptr_dest) { E_Eval dst_value_eval = e_value_eval_from_eval(dst_eval); dst_offset = dst_value_eval.value.u64; } result = e_space_write(dst_eval.space, commit_data.str, r1u64(dst_offset, dst_offset + commit_data.size)); } scratch_end(scratch); } return result; } //- rjf: type helpers internal E_MemberArray d_filtered_data_members_from_members_cfg_table(Arena *arena, E_MemberArray members, D_CfgTable *cfg) { D_CfgVal *only = d_cfg_val_from_string(cfg, str8_lit("only")); D_CfgVal *omit = d_cfg_val_from_string(cfg, str8_lit("omit")); E_MemberArray filtered_members = members; if(only != &d_nil_cfg_val || omit != &d_nil_cfg_val) { Temp scratch = scratch_begin(&arena, 1); typedef struct DF_TypeMemberLooseNode DF_TypeMemberLooseNode; struct DF_TypeMemberLooseNode { DF_TypeMemberLooseNode *next; E_Member *member; }; DF_TypeMemberLooseNode *first_member = 0; DF_TypeMemberLooseNode *last_member = 0; U64 member_count = 0; MemoryZeroStruct(&filtered_members); for(U64 idx = 0; idx < members.count; idx += 1) { // rjf: check if included by 'only's B32 is_included = 1; for(D_CfgTree *r = only->first; r != &d_nil_cfg_tree; r = r->next) { is_included = 0; for(MD_EachNode(name_node, r->root->first)) { String8 name = name_node->string; if(str8_match(members.v[idx].name, name, 0)) { is_included = 1; goto end_inclusion_check; } } } end_inclusion_check:; // rjf: remove if excluded by 'omit's for(D_CfgTree *r = omit->first; r != &d_nil_cfg_tree; r = r->next) { for(MD_EachNode(name_node, r->root->first)) { String8 name = name_node->string; if(str8_match(members.v[idx].name, name, 0)) { is_included = 0; goto end_exclusion_check; } } } end_exclusion_check:; // rjf: push if included if(is_included) { DF_TypeMemberLooseNode *n = push_array(scratch.arena, DF_TypeMemberLooseNode, 1); n->member = &members.v[idx]; SLLQueuePush(first_member, last_member, n); member_count += 1; } } // rjf: bake { filtered_members.count = member_count; filtered_members.v = push_array_no_zero(arena, E_Member, filtered_members.count); U64 idx = 0; for(DF_TypeMemberLooseNode *n = first_member; n != 0; n = n->next, idx += 1) { MemoryCopyStruct(&filtered_members.v[idx], n->member); filtered_members.v[idx].name = push_str8_copy(arena, filtered_members.v[idx].name); filtered_members.v[idx].inheritance_key_chain = e_type_key_list_copy(arena, &filtered_members.v[idx].inheritance_key_chain); } } scratch_end(scratch); } return filtered_members; } internal D_EvalLinkBaseChunkList d_eval_link_base_chunk_list_from_eval(Arena *arena, E_TypeKey link_member_type_key, U64 link_member_off, E_Eval eval, U64 cap) { D_EvalLinkBaseChunkList list = {0}; for(E_Eval base_eval = eval, last_eval = zero_struct; list.count < cap;) { // rjf: check this ptr's validity if(base_eval.value.u64 == 0 || (base_eval.value.u64 == last_eval.value.u64 && base_eval.mode == last_eval.mode)) { break; } // rjf: gather { D_EvalLinkBaseChunkNode *chunk = list.last; if(chunk == 0 || chunk->count == ArrayCount(chunk->b)) { chunk = push_array_no_zero(arena, D_EvalLinkBaseChunkNode, 1); chunk->next = 0; chunk->count = 0; SLLQueuePush(list.first, list.last, chunk); } chunk->b[chunk->count].offset = base_eval.value.u64; chunk->count += 1; list.count += 1; } // rjf: grab link member E_Eval link_member_eval = { .value = {.u64 = base_eval.value.u64 + link_member_off}, .mode = E_Mode_Offset, .space = base_eval.space, .type_key = link_member_type_key, }; E_Eval link_member_value_eval = e_value_eval_from_eval(link_member_eval); // rjf: advance to next link last_eval = base_eval; base_eval.mode = E_Mode_Offset; base_eval.value.u64 = link_member_value_eval.value.u64; } return list; } internal D_EvalLinkBase d_eval_link_base_from_chunk_list_index(D_EvalLinkBaseChunkList *list, U64 idx) { D_EvalLinkBase result = zero_struct; U64 scan_idx = 0; for(D_EvalLinkBaseChunkNode *chunk = list->first; chunk != 0; chunk = chunk->next) { U64 chunk_idx_opl = scan_idx+chunk->count; if(scan_idx <= idx && idx < chunk_idx_opl) { result = chunk->b[idx - scan_idx]; } scan_idx = chunk_idx_opl; } return result; } internal D_EvalLinkBaseArray d_eval_link_base_array_from_chunk_list(Arena *arena, D_EvalLinkBaseChunkList *chunks) { D_EvalLinkBaseArray array = {0}; array.count = chunks->count; array.v = push_array_no_zero(arena, D_EvalLinkBase, array.count); U64 idx = 0; for(D_EvalLinkBaseChunkNode *n = chunks->first; n != 0; n = n->next) { MemoryCopy(array.v + idx, n->b, n->count * sizeof(D_EvalLinkBase)); idx += n->count; } return array; } //- rjf: viz block collection building internal D_EvalVizBlock * d_eval_viz_block_begin(Arena *arena, D_EvalVizBlockKind kind, D_ExpandKey parent_key, D_ExpandKey key, S32 depth) { D_EvalVizBlockNode *n = push_array(arena, D_EvalVizBlockNode, 1); n->v.kind = kind; n->v.parent_key = parent_key; n->v.key = key; n->v.depth = depth; n->v.expr = &e_expr_nil; n->v.cfg_table = &d_nil_cfg_table; return &n->v; } internal D_EvalVizBlock * d_eval_viz_block_split_and_continue(Arena *arena, D_EvalVizBlockList *list, D_EvalVizBlock *split_block, U64 split_idx) { U64 total_count = split_block->semantic_idx_range.max; split_block->visual_idx_range.max = split_block->semantic_idx_range.max = split_idx; d_eval_viz_block_end(list, split_block); D_EvalVizBlock *continue_block = d_eval_viz_block_begin(arena, split_block->kind, split_block->parent_key, split_block->key, split_block->depth); continue_block->string = split_block->string; continue_block->expr = split_block->expr; continue_block->visual_idx_range = continue_block->semantic_idx_range = r1u64(split_idx+1, total_count); continue_block->cfg_table = split_block->cfg_table; continue_block->link_bases = split_block->link_bases; continue_block->members = split_block->members; continue_block->enum_vals = split_block->enum_vals; continue_block->fzy_target = split_block->fzy_target; continue_block->fzy_backing_items = split_block->fzy_backing_items; return continue_block; } internal void d_eval_viz_block_end(D_EvalVizBlockList *list, D_EvalVizBlock *block) { D_EvalVizBlockNode *n = CastFromMember(D_EvalVizBlockNode, v, block); SLLQueuePush(list->first, list->last, n); list->count += 1; list->total_visual_row_count += dim_1u64(block->visual_idx_range); list->total_semantic_row_count += dim_1u64(block->semantic_idx_range); } internal void d_append_expr_eval_viz_blocks__rec(Arena *arena, D_EvalView *eval_view, D_ExpandKey parent_key, D_ExpandKey key, String8 string, E_Expr *expr, D_CfgTable *cfg_table, S32 depth, D_EvalVizBlockList *list_out) { ProfBeginFunction(); Temp scratch = scratch_begin(&arena, 1); //- rjf: apply expr resolution view rules expr = d_expr_from_expr_cfg(arena, expr, cfg_table); //- rjf: determine if this key is expanded D_ExpandNode *node = d_expand_node_from_key(&eval_view->expand_tree_table, key); B32 parent_is_expanded = (node != 0 && node->expanded); //- rjf: push block for expression root { D_EvalVizBlock *block = d_eval_viz_block_begin(arena, D_EvalVizBlockKind_Root, parent_key, key, depth); block->string = string; block->expr = expr; block->cfg_table = cfg_table; block->visual_idx_range = r1u64(key.child_num-1, key.child_num+0); block->semantic_idx_range = r1u64(key.child_num-1, key.child_num+0); d_eval_viz_block_end(list_out, block); } //- rjf: determine view rule to generate children blocks D_ViewRuleSpec *expand_view_rule_spec = d_view_rule_spec_from_string(str8_lit("default")); MD_Node *expand_view_rule_params = &md_nil_node; if(parent_is_expanded) { for(D_CfgVal *val = cfg_table->first_val; val != 0 && val != &d_nil_cfg_val; val = val->linear_next) { D_ViewRuleSpec *spec = d_view_rule_spec_from_string(val->string); if(spec->info.flags & D_ViewRuleSpecInfoFlag_VizBlockProd) { expand_view_rule_spec = spec; expand_view_rule_params = val->last->root; break; } } } //- rjf: do view rule children block generation, if we have an applicable view rule if(parent_is_expanded && expand_view_rule_spec != &d_nil_core_view_rule_spec) { expand_view_rule_spec->info.viz_block_prod(arena, eval_view, parent_key, key, node, string, expr, cfg_table, depth+1, expand_view_rule_params, list_out); } scratch_end(scratch); ProfEnd(); } internal D_EvalVizBlockList d_eval_viz_block_list_from_eval_view_expr_keys(Arena *arena, D_EvalView *eval_view, D_CfgTable *cfg_table, String8 expr, D_ExpandKey parent_key, D_ExpandKey key) { ProfBeginFunction(); D_EvalVizBlockList blocks = {0}; { E_TokenArray tokens = e_token_array_from_text(arena, expr); E_Parse parse = e_parse_expr_from_text_tokens(arena, expr, &tokens); U64 parse_opl = parse.last_token >= tokens.v + tokens.count ? expr.size : parse.last_token->range.min; U64 expr_comma_pos = str8_find_needle(expr, parse_opl, str8_lit(","), 0); U64 passthrough_pos = str8_find_needle(expr, parse_opl, str8_lit("--"), 0); String8List default_view_rules = {0}; if(expr_comma_pos < expr.size && expr_comma_pos < passthrough_pos) { String8 expr_extension = str8_substr(expr, r1u64(expr_comma_pos+1, passthrough_pos)); expr_extension = str8_skip_chop_whitespace(expr_extension); if(str8_match(expr_extension, str8_lit("x"), StringMatchFlag_CaseInsensitive)) { str8_list_pushf(arena, &default_view_rules, "hex"); } else if(str8_match(expr_extension, str8_lit("b"), StringMatchFlag_CaseInsensitive)) { str8_list_pushf(arena, &default_view_rules, "bin"); } else if(str8_match(expr_extension, str8_lit("o"), StringMatchFlag_CaseInsensitive)) { str8_list_pushf(arena, &default_view_rules, "oct"); } else if(expr_extension.size != 0) { str8_list_pushf(arena, &default_view_rules, "array:{%S}", expr_extension); } } if(passthrough_pos < expr.size) { String8 passthrough_view_rule = str8_skip_chop_whitespace(str8_skip(expr, passthrough_pos+2)); if(passthrough_view_rule.size != 0) { str8_list_push(arena, &default_view_rules, passthrough_view_rule); } } String8 view_rule_string = d_eval_view_rule_from_key(eval_view, key); D_CfgTable *cfg_table_inherited = push_array(arena, D_CfgTable, 1); *cfg_table_inherited = d_cfg_table_from_inheritance(arena, cfg_table); for(String8Node *n = default_view_rules.first; n != 0; n = n->next) { d_cfg_table_push_unparsed_string(arena, cfg_table_inherited, n->string, D_CfgSrc_User); } d_cfg_table_push_unparsed_string(arena, cfg_table_inherited, view_rule_string, D_CfgSrc_User); d_append_expr_eval_viz_blocks__rec(arena, eval_view, parent_key, key, expr, parse.expr, cfg_table_inherited, 0, &blocks); } ProfEnd(); return blocks; } internal void d_eval_viz_block_list_concat__in_place(D_EvalVizBlockList *dst, D_EvalVizBlockList *to_push) { if(dst->last == 0) { *dst = *to_push; } else if(to_push->first != 0) { dst->last->next = to_push->first; dst->last = to_push->last; dst->count += to_push->count; dst->total_visual_row_count += to_push->total_visual_row_count; dst->total_semantic_row_count += to_push->total_semantic_row_count; } MemoryZeroStruct(to_push); } internal S64 d_row_num_from_viz_block_list_key(D_EvalVizBlockList *blocks, D_ExpandKey key) { S64 row_num = 1; B32 found = 0; for(D_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) { D_EvalVizBlock *block = &n->v; if(key.parent_hash == block->key.parent_hash) { B32 this_block_contains_this_key = 0; { if(block->fzy_backing_items.v != 0) { U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&block->fzy_backing_items, key.child_num); this_block_contains_this_key = (item_num != 0 && contains_1u64(block->semantic_idx_range, item_num-1)); } else { this_block_contains_this_key = (block->semantic_idx_range.min+1 <= key.child_num && key.child_num < block->semantic_idx_range.max+1); } } if(this_block_contains_this_key) { found = 1; if(block->fzy_backing_items.v != 0) { U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&block->fzy_backing_items, key.child_num); row_num += item_num-1-block->semantic_idx_range.min; } else { row_num += key.child_num-1-block->semantic_idx_range.min; } break; } } if(!found) { row_num += (S64)dim_1u64(block->semantic_idx_range); } } if(!found) { row_num = 0; } return row_num; } internal D_ExpandKey d_key_from_viz_block_list_row_num(D_EvalVizBlockList *blocks, S64 row_num) { D_ExpandKey key = {0}; S64 scan_y = 1; for(D_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) { D_EvalVizBlock *vb = &n->v; Rng1S64 vb_row_num_range = r1s64(scan_y, scan_y + (S64)dim_1u64(vb->semantic_idx_range)); if(contains_1s64(vb_row_num_range, row_num)) { key = vb->key; if(vb->fzy_backing_items.v != 0) { U64 item_idx = (U64)((row_num - vb_row_num_range.min) + vb->semantic_idx_range.min); if(item_idx < vb->fzy_backing_items.count) { key.child_num = vb->fzy_backing_items.v[item_idx].idx; } } else { key.child_num = vb->semantic_idx_range.min + (row_num - vb_row_num_range.min) + 1; } break; } scan_y += dim_1s64(vb_row_num_range); } return key; } internal D_ExpandKey d_parent_key_from_viz_block_list_row_num(D_EvalVizBlockList *blocks, S64 row_num) { D_ExpandKey key = {0}; S64 scan_y = 1; for(D_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) { D_EvalVizBlock *vb = &n->v; Rng1S64 vb_row_num_range = r1s64(scan_y, scan_y + (S64)dim_1u64(vb->semantic_idx_range)); if(contains_1s64(vb_row_num_range, row_num)) { key = vb->parent_key; break; } scan_y += dim_1s64(vb_row_num_range); } return key; } //- rjf: viz block * index -> expression internal E_Expr * d_expr_from_eval_viz_block_index(Arena *arena, D_EvalVizBlock *block, U64 index) { E_Expr *result = block->expr; switch(block->kind) { default:{}break; case D_EvalVizBlockKind_Members: { E_MemberArray *members = &block->members; if(index < members->count) { E_Member *member = &members->v[index]; E_Expr *dot_expr = e_expr_ref_member_access(arena, block->expr, member->name); result = dot_expr; } }break; case D_EvalVizBlockKind_EnumMembers: { E_EnumValArray *enum_vals = &block->enum_vals; if(index < enum_vals->count) { E_EnumVal *val = &enum_vals->v[index]; E_Expr *dot_expr = e_expr_ref_member_access(arena, block->expr, val->name); result = dot_expr; } }break; case D_EvalVizBlockKind_Elements: { E_Expr *idx_expr = e_expr_ref_array_index(arena, block->expr, index); result = idx_expr; }break; case D_EvalVizBlockKind_DebugInfoTable: { // rjf: unpack row info FZY_Item *item = &block->fzy_backing_items.v[index]; D_ExpandKey parent_key = block->parent_key; D_ExpandKey key = block->key; key.child_num = block->fzy_backing_items.v[index].idx; // rjf: determine module to which this item belongs E_Module *module = e_parse_ctx->primary_module; U64 base_idx = 0; { for(U64 module_idx = 0; module_idx < e_parse_ctx->modules_count; module_idx += 1) { U64 all_items_count = 0; rdi_section_raw_table_from_kind(e_parse_ctx->modules[module_idx].rdi, block->fzy_target, &all_items_count); if(base_idx <= item->idx && item->idx < base_idx + all_items_count) { module = &e_parse_ctx->modules[module_idx]; break; } base_idx += all_items_count; } } // rjf: build expr E_Expr *item_expr = &e_expr_nil; { RDI_SectionKind section = block->fzy_target; U64 element_idx = block->fzy_backing_items.v[index].idx - base_idx; switch(section) { default:{}break; case RDI_SectionKind_Procedures: { RDI_Procedure *procedure = rdi_element_from_name_idx(module->rdi, Procedures, element_idx); RDI_Scope *scope = rdi_element_from_name_idx(module->rdi, Scopes, procedure->root_scope_idx); U64 voff = *rdi_element_from_name_idx(module->rdi, ScopeVOffData, scope->voff_range_first); E_OpList oplist = {0}; e_oplist_push_op(arena, &oplist, RDI_EvalOp_ModuleOff, e_value_u64(voff)); String8 bytecode = e_bytecode_from_oplist(arena, &oplist); U32 type_idx = procedure->type_idx; RDI_TypeNode *type_node = rdi_element_from_name_idx(module->rdi, TypeNodes, type_idx); E_TypeKey type_key = e_type_key_ext(e_type_kind_from_rdi(type_node->kind), type_idx, (U32)(module - e_parse_ctx->modules)); item_expr = e_push_expr(arena, E_ExprKind_LeafBytecode, 0); item_expr->mode = E_Mode_Value; item_expr->space = module->space; item_expr->type_key = type_key; item_expr->bytecode = bytecode; item_expr->string.str = rdi_string_from_idx(module->rdi, procedure->name_string_idx, &item_expr->string.size); }break; case RDI_SectionKind_GlobalVariables: { RDI_GlobalVariable *gvar = rdi_element_from_name_idx(module->rdi, GlobalVariables, element_idx); U64 voff = gvar->voff; E_OpList oplist = {0}; e_oplist_push_op(arena, &oplist, RDI_EvalOp_ModuleOff, e_value_u64(voff)); String8 bytecode = e_bytecode_from_oplist(arena, &oplist); U32 type_idx = gvar->type_idx; RDI_TypeNode *type_node = rdi_element_from_name_idx(module->rdi, TypeNodes, type_idx); E_TypeKey type_key = e_type_key_ext(e_type_kind_from_rdi(type_node->kind), type_idx, (U32)(module - e_parse_ctx->modules)); item_expr = e_push_expr(arena, E_ExprKind_LeafBytecode, 0); item_expr->mode = E_Mode_Offset; item_expr->space = module->space; item_expr->type_key = type_key; item_expr->bytecode = bytecode; item_expr->string.str = rdi_string_from_idx(module->rdi, gvar->name_string_idx, &item_expr->string.size); }break; case RDI_SectionKind_ThreadVariables: { RDI_ThreadVariable *tvar = rdi_element_from_name_idx(module->rdi, ThreadVariables, element_idx); E_OpList oplist = {0}; e_oplist_push_op(arena, &oplist, RDI_EvalOp_TLSOff, e_value_u64(tvar->tls_off)); String8 bytecode = e_bytecode_from_oplist(arena, &oplist); U32 type_idx = tvar->type_idx; RDI_TypeNode *type_node = rdi_element_from_name_idx(module->rdi, TypeNodes, type_idx); E_TypeKey type_key = e_type_key_ext(e_type_kind_from_rdi(type_node->kind), type_idx, (U32)(module - e_parse_ctx->modules)); item_expr = e_push_expr(arena, E_ExprKind_LeafBytecode, 0); item_expr->mode = E_Mode_Offset; item_expr->space = module->space; item_expr->type_key = type_key; item_expr->bytecode = bytecode; item_expr->string.str = rdi_string_from_idx(module->rdi, tvar->name_string_idx, &item_expr->string.size); }break; case RDI_SectionKind_UDTs: { RDI_UDT *udt = rdi_element_from_name_idx(module->rdi, UDTs, element_idx); RDI_TypeNode *type_node = rdi_element_from_name_idx(module->rdi, TypeNodes, udt->self_type_idx); E_TypeKey type_key = e_type_key_ext(e_type_kind_from_rdi(type_node->kind), udt->self_type_idx, (U32)(module - e_parse_ctx->modules)); item_expr = e_push_expr(arena, E_ExprKind_TypeIdent, 0); item_expr->type_key = type_key; }break; } } result = item_expr; }break; } return result; } //- rjf: viz row list building internal D_EvalVizRow * d_eval_viz_row_list_push_new(Arena *arena, D_EvalView *eval_view, D_EvalVizWindowedRowList *rows, D_EvalVizBlock *block, D_ExpandKey key, E_Expr *expr) { // rjf: push D_EvalVizRow *row = push_array(arena, D_EvalVizRow, 1); SLLQueuePush(rows->first, rows->last, row); rows->count += 1; // rjf: pick cfg table; resolve expression if needed D_CfgTable *cfg_table = 0; switch(block->kind) { default: { cfg_table = push_array(arena, D_CfgTable, 1); *cfg_table = d_cfg_table_from_inheritance(arena, block->cfg_table); String8 row_view_rules = d_eval_view_rule_from_key(eval_view, key); if(row_view_rules.size != 0) { d_cfg_table_push_unparsed_string(arena, cfg_table, row_view_rules, D_CfgSrc_User); } expr = d_expr_from_expr_cfg(arena, expr, cfg_table); }break; case D_EvalVizBlockKind_Root: case D_EvalVizBlockKind_Canvas: { cfg_table = block->cfg_table; }break; } // rjf: determine row ui hook to use for this row DF_ViewRuleSpec *value_ui_rule_spec = &df_nil_view_rule_spec; MD_Node *value_ui_rule_params = &md_nil_node; for(D_CfgVal *val = cfg_table->first_val; val != 0 && val != &d_nil_cfg_val; val = val->linear_next) { DF_ViewRuleSpec *spec = df_view_rule_spec_from_string(val->string); if(spec->info.flags & DF_ViewRuleSpecInfoFlag_RowUI) { value_ui_rule_spec = spec; value_ui_rule_params = val->last->root; break; } } // rjf: determine block ui hook to use for this row DF_ViewRuleSpec *expand_ui_rule_spec = &df_nil_view_rule_spec; MD_Node *expand_ui_rule_params = &md_nil_node; if(block->kind == D_EvalVizBlockKind_Canvas) { for(D_CfgVal *val = cfg_table->first_val; val != 0 && val != &d_nil_cfg_val; val = val->linear_next) { DF_ViewRuleSpec *spec = df_view_rule_spec_from_string(val->string); if(spec->info.flags & DF_ViewRuleSpecInfoFlag_ViewUI) { expand_ui_rule_spec = spec; expand_ui_rule_params = val->last->root; break; } } } // rjf: fill row->depth = block->depth; row->parent_key = block->parent_key; row->key = key; row->size_in_rows = 1; row->string = block->string; row->expr = expr; if(row->expr->kind == E_ExprKind_MemberAccess) { Temp scratch = scratch_begin(&arena, 1); E_IRTreeAndType irtree = e_irtree_and_type_from_expr(scratch.arena, row->expr->first); E_TypeKey type = irtree.type_key; E_MemberArray data_members = e_type_data_members_from_key(scratch.arena, type); E_Member *member = e_type_member_from_array_name(&data_members, row->expr->last->string); if(member != 0) { row->member = e_type_member_copy(arena, member); } scratch_end(scratch); } row->cfg_table = cfg_table; row->value_ui_rule_spec = value_ui_rule_spec; row->value_ui_rule_params = value_ui_rule_params; row->expand_ui_rule_spec = expand_ui_rule_spec; row->expand_ui_rule_params = expand_ui_rule_params; return row; } internal D_EvalVizWindowedRowList d_eval_viz_windowed_row_list_from_viz_block_list(Arena *arena, D_EvalView *eval_view, Rng1S64 visible_range, D_EvalVizBlockList *blocks) { ProfBeginFunction(); Temp scratch = scratch_begin(&arena, 1); ////////////////////////////// //- rjf: produce windowed rows, per block // U64 visual_idx_off = 0; U64 semantic_idx_off = 0; D_EvalVizWindowedRowList list = {0}; for(D_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) { D_EvalVizBlock *block = &n->v; ////////////////////////////// //- rjf: extract block info // U64 block_num_visual_rows = dim_1u64(block->visual_idx_range); U64 block_num_semantic_rows = dim_1u64(block->semantic_idx_range); Rng1S64 block_visual_range = r1s64(visual_idx_off, visual_idx_off + block_num_visual_rows); Rng1S64 block_semantic_range = r1s64(semantic_idx_off, semantic_idx_off + block_num_semantic_rows); ////////////////////////////// //- rjf: get skip/chop of block's index range // U64 num_skipped_visual = 0; U64 num_chopped_visual = 0; { if(visible_range.min > block_visual_range.min) { num_skipped_visual = (visible_range.min - block_visual_range.min); num_skipped_visual = Min(num_skipped_visual, block_num_visual_rows); } if(visible_range.max < block_visual_range.max) { num_chopped_visual = (block_visual_range.max - visible_range.max); num_chopped_visual = Min(num_chopped_visual, block_num_visual_rows); } } ////////////////////////////// //- rjf: get visible idx range & invisible counts // Rng1U64 visible_idx_range = block->visual_idx_range; { visible_idx_range.min += num_skipped_visual; visible_idx_range.max -= num_chopped_visual; } ////////////////////////////// //- rjf: sum & advance // list.count_before_visual += num_skipped_visual; if(block_num_visual_rows != 0) { list.count_before_semantic += block_num_semantic_rows * num_skipped_visual / block_num_visual_rows; } visual_idx_off += block_num_visual_rows; semantic_idx_off += block_num_semantic_rows; ////////////////////////////// //- rjf: produce rows, depending on block's kind // if(visible_idx_range.max > visible_idx_range.min) switch(block->kind) { default:{}break; ////////////////////////////// //- rjf: single rows, piping in info from the originating block // case D_EvalVizBlockKind_Null: case D_EvalVizBlockKind_Root: { d_eval_viz_row_list_push_new(arena, eval_view, &list, block, block->key, block->expr); }break; ////////////////////////////// //- rjf: canvas -> produce blank row, sized by the idx range specified in the block // case D_EvalVizBlockKind_Canvas: if(num_skipped_visual < block_num_visual_rows) { D_ExpandKey key = d_expand_key_make(df_hash_from_expand_key(block->parent_key), 1); D_EvalVizRow *row = d_eval_viz_row_list_push_new(arena, eval_view, &list, block, key, block->expr); row->size_in_rows = dim_1u64(intersect_1u64(visible_idx_range, r1u64(0, dim_1u64(block->visual_idx_range)))); row->skipped_size_in_rows= (visible_idx_range.min > block->visual_idx_range.min) ? visible_idx_range.min - block->visual_idx_range.min : 0; row->chopped_size_in_rows= (visible_idx_range.max < block->visual_idx_range.max) ? block->visual_idx_range.max - visible_idx_range.max : 0; }break; ////////////////////////////// //- rjf: all elements of a debug info table -> produce rows for visible range // case D_EvalVizBlockKind_DebugInfoTable: for(U64 idx = visible_idx_range.min; idx < visible_idx_range.max; idx += 1) { FZY_Item *item = &block->fzy_backing_items.v[idx]; D_ExpandKey parent_key = block->parent_key; D_ExpandKey key = block->key; key.child_num = block->fzy_backing_items.v[idx].idx; E_Expr *row_expr = d_expr_from_eval_viz_block_index(arena, block, idx); d_eval_viz_row_list_push_new(arena, eval_view, &list, block, key, row_expr); }break; ////////////////////////////// //- rjf: members/elements/enum-members // case D_EvalVizBlockKind_Members: case D_EvalVizBlockKind_EnumMembers: case D_EvalVizBlockKind_Elements: { for(U64 idx = visible_idx_range.min; idx < visible_idx_range.max; idx += 1) { D_ExpandKey key = d_expand_key_make(df_hash_from_expand_key(block->parent_key), idx+1); E_Expr *expr = d_expr_from_eval_viz_block_index(arena, block, idx); d_eval_viz_row_list_push_new(arena, eval_view, &list, block, key, expr); } }break; } } scratch_end(scratch); ProfEnd(); return list; } //- rjf: viz row -> strings internal String8 d_expr_string_from_viz_row(Arena *arena, D_EvalVizRow *row) { String8 result = row->string; if(result.size == 0) switch(row->expr->kind) { default: { result = e_string_from_expr(arena, row->expr); }break; case E_ExprKind_ArrayIndex: { result = push_str8f(arena, "[%S]", e_string_from_expr(arena, row->expr->last)); }break; case E_ExprKind_MemberAccess: { result = push_str8f(arena, ".%S", e_string_from_expr(arena, row->expr->last)); }break; } return result; } //- rjf: viz row -> expandability/editability internal B32 d_viz_row_is_expandable(D_EvalVizRow *row) { B32 result = 0; { // rjf: determine if view rules force expandability if(!result) { for(D_CfgVal *val = row->cfg_table->first_val; val != 0 && val != &d_nil_cfg_val; val = val->linear_next) { D_ViewRuleSpec *spec = d_view_rule_spec_from_string(val->string); if(spec->info.flags & D_ViewRuleSpecInfoFlag_Expandable) { result = 1; break; } } } // rjf: determine if type info force expandability if(!result) { Temp scratch = scratch_begin(0, 0); E_IRTreeAndType irtree = e_irtree_and_type_from_expr(scratch.arena, row->expr); result = d_type_key_is_expandable(irtree.type_key); scratch_end(scratch); } } return result; } internal B32 d_viz_row_is_editable(D_EvalVizRow *row) { B32 result = 0; Temp scratch = scratch_begin(0, 0); E_IRTreeAndType irtree = e_irtree_and_type_from_expr(scratch.arena, row->expr); result = d_type_key_is_editable(irtree.type_key); scratch_end(scratch); return result; } //- rjf: view rule config tree info extraction internal U64 d_base_offset_from_eval(E_Eval eval) { if(e_type_kind_is_pointer_or_ref(e_type_kind_from_key(eval.type_key))) { eval = e_value_eval_from_eval(eval); } return eval.value.u64; } internal E_Value d_value_from_params(MD_Node *params) { Temp scratch = scratch_begin(0, 0); String8 expr = md_string_from_children(scratch.arena, params); E_Eval eval = e_eval_from_string(scratch.arena, expr); E_Eval value_eval = e_value_eval_from_eval(eval); scratch_end(scratch); return value_eval.value; } internal E_TypeKey d_type_key_from_params(MD_Node *params) { Temp scratch = scratch_begin(0, 0); String8 expr = md_string_from_children(scratch.arena, params); E_TokenArray tokens = e_token_array_from_text(scratch.arena, expr); E_Parse parse = e_parse_type_from_text_tokens(scratch.arena, expr, &tokens); E_TypeKey type_key = e_type_from_expr(parse.expr); scratch_end(scratch); return type_key; } internal E_Value d_value_from_params_key(MD_Node *params, String8 key) { Temp scratch = scratch_begin(0, 0); MD_Node *key_node = md_child_from_string(params, key, 0); String8 expr = md_string_from_children(scratch.arena, key_node); E_Eval eval = e_eval_from_string(scratch.arena, expr); E_Eval value_eval = e_value_eval_from_eval(eval); scratch_end(scratch); return value_eval.value; } internal Rng1U64 d_range_from_eval_params(E_Eval eval, MD_Node *params) { Temp scratch = scratch_begin(0, 0); U64 size = d_value_from_params_key(params, str8_lit("size")).u64; E_TypeKey type_key = e_type_unwrap(eval.type_key); E_TypeKind type_kind = e_type_kind_from_key(type_key); E_TypeKey direct_type_key = e_type_unwrap(e_type_direct_from_key(eval.type_key)); E_TypeKind direct_type_kind = e_type_kind_from_key(direct_type_key); if(size == 0 && e_type_kind_is_pointer_or_ref(type_kind) && (direct_type_kind == E_TypeKind_Struct || direct_type_kind == E_TypeKind_Union || direct_type_kind == E_TypeKind_Class || direct_type_kind == E_TypeKind_Array)) { size = e_type_byte_size_from_key(e_type_direct_from_key(e_type_unwrap(eval.type_key))); } if(size == 0 && eval.mode == E_Mode_Offset && (type_kind == E_TypeKind_Struct || type_kind == E_TypeKind_Union || type_kind == E_TypeKind_Class || type_kind == E_TypeKind_Array)) { size = e_type_byte_size_from_key(e_type_unwrap(eval.type_key)); } if(size == 0) { size = 16384; } Rng1U64 result = {0}; result.min = d_base_offset_from_eval(eval); result.max = result.min + size; scratch_end(scratch); return result; } internal TXT_LangKind d_lang_kind_from_eval_params(E_Eval eval, MD_Node *params) { TXT_LangKind lang_kind = TXT_LangKind_Null; if(eval.expr->kind == E_ExprKind_LeafFilePath) { lang_kind = txt_lang_kind_from_extension(str8_skip_last_dot(eval.expr->string)); } else { MD_Node *lang_node = md_child_from_string(params, str8_lit("lang"), 0); String8 lang_kind_string = lang_node->first->string; lang_kind = txt_lang_kind_from_extension(lang_kind_string); } return lang_kind; } internal Arch d_arch_from_eval_params(E_Eval eval, MD_Node *params) { Arch arch = Arch_Null; MD_Node *arch_node = md_child_from_string(params, str8_lit("arch"), 0); String8 arch_kind_string = arch_node->first->string; if(str8_match(arch_kind_string, str8_lit("x64"), StringMatchFlag_CaseInsensitive)) { arch = Arch_x64; } return arch; } internal Vec2S32 d_dim2s32_from_eval_params(E_Eval eval, MD_Node *params) { Vec2S32 dim = v2s32(1, 1); { dim.x = d_value_from_params_key(params, str8_lit("w")).s32; dim.y = d_value_from_params_key(params, str8_lit("h")).s32; } return dim; } internal R_Tex2DFormat d_tex2dformat_from_eval_params(E_Eval eval, MD_Node *params) { R_Tex2DFormat result = R_Tex2DFormat_RGBA8; { MD_Node *fmt_node = md_child_from_string(params, str8_lit("fmt"), 0); for(EachNonZeroEnumVal(R_Tex2DFormat, fmt)) { if(str8_match(r_tex2d_format_display_string_table[fmt], fmt_node->first->string, StringMatchFlag_CaseInsensitive)) { result = fmt; break; } } } return result; } //- rjf: eval -> entity internal D_Entity * d_entity_from_eval_string(String8 string) { D_Entity *entity = &d_nil_entity; { Temp scratch = scratch_begin(0, 0); E_Eval eval = e_eval_from_string(scratch.arena, string); entity = d_entity_from_eval_space(eval.space); scratch_end(scratch); } return entity; } internal String8 d_eval_string_from_entity(Arena *arena, D_Entity *entity) { String8 eval_string = push_str8f(arena, "macro:`$%I64u`", entity->id); return eval_string; } //- rjf: eval <-> file path internal String8 d_file_path_from_eval_string(Arena *arena, String8 string) { String8 result = {0}; { Temp scratch = scratch_begin(&arena, 1); E_Eval eval = e_eval_from_string(scratch.arena, string); if(eval.expr->kind == E_ExprKind_LeafFilePath) { result = d_cfg_raw_from_escaped_string(arena, eval.expr->string); } scratch_end(scratch); } return result; } internal String8 d_eval_string_from_file_path(Arena *arena, String8 string) { Temp scratch = scratch_begin(&arena, 1); String8 string_escaped = d_cfg_escaped_from_raw_string(scratch.arena, string); String8 result = push_str8f(arena, "file:\"%S\"", string_escaped); scratch_end(scratch); return result; } //////////////////////////////// //~ rjf: Main State Accessors/Mutators //- rjf: frame data internal F32 d_dt(void) { return d_state->dt; } internal U64 d_frame_index(void) { return d_state->frame_index; } internal Arena * d_frame_arena(void) { return d_state->frame_arenas[d_state->frame_index%ArrayCount(d_state->frame_arenas)]; } internal F64 d_time_in_seconds(void) { return d_state->time_in_seconds; } //- rjf: interaction registers internal D_Regs * d_regs(void) { D_Regs *regs = &d_state->top_regs->v; return regs; } internal D_Regs * d_base_regs(void) { D_Regs *regs = &d_state->base_regs.v; return regs; } internal D_Regs * d_push_regs(void) { D_Regs *top = d_regs(); D_RegsNode *n = push_array(d_frame_arena(), D_RegsNode, 1); MemoryCopyStruct(&n->v, top); SLLStackPush(d_state->top_regs, n); return &n->v; } internal D_Regs * d_pop_regs(void) { D_Regs *regs = &d_state->top_regs->v; SLLStackPop(d_state->top_regs); if(d_state->top_regs == 0) { d_state->top_regs = &d_state->base_regs; } return regs; } //- rjf: undo/redo history internal D_StateDeltaHistory * d_state_delta_history(void) { return d_state->hist; } //- rjf: control state internal D_RunKind d_ctrl_last_run_kind(void) { return d_state->ctrl_last_run_kind; } internal U64 d_ctrl_last_run_frame_idx(void) { return d_state->ctrl_last_run_frame_idx; } internal B32 d_ctrl_targets_running(void) { return d_state->ctrl_is_running; } //- rjf: config paths internal String8 d_cfg_path_from_src(D_CfgSrc src) { return d_state->cfg_paths[src]; } //- rjf: config state internal D_CfgTable * d_cfg_table(void) { return &d_state->cfg_table; } //- rjf: config serialization internal String8 d_cfg_escaped_from_raw_string(Arena *arena, String8 string) { Temp scratch = scratch_begin(&arena, 1); String8List parts = {0}; U64 split_start_idx = 0; for(U64 idx = 0; idx <= string.size; idx += 1) { U8 byte = (idx < string.size ? string.str[idx] : 0); if(byte == 0 || byte == '\"' || byte == '\\') { String8 part = str8_substr(string, r1u64(split_start_idx, idx)); str8_list_push(scratch.arena, &parts, part); switch(byte) { default:{}break; case '\"':{str8_list_push(scratch.arena, &parts, str8_lit("\\\""));}break; case '\\':{str8_list_push(scratch.arena, &parts, str8_lit("\\\\"));}break; } split_start_idx = idx+1; } } StringJoin join = {0}; String8 result = str8_list_join(arena, &parts, &join); scratch_end(scratch); return result; } internal String8 d_cfg_raw_from_escaped_string(Arena *arena, String8 string) { Temp scratch = scratch_begin(&arena, 1); String8List parts = {0}; U64 split_start_idx = 0; U64 extra_advance = 0; for(U64 idx = 0; idx <= string.size; ((idx += 1+extra_advance), extra_advance=0)) { U8 byte = (idx < string.size ? string.str[idx] : 0); if(byte == 0 || byte == '\\') { String8 part = str8_substr(string, r1u64(split_start_idx, idx)); str8_list_push(scratch.arena, &parts, part); if(byte == '\\' && idx+1 < string.size) { switch(string.str[idx+1]) { default:{}break; case '"': {extra_advance = 1; str8_list_push(scratch.arena, &parts, str8_lit("\""));}break; case '\\':{extra_advance = 1; str8_list_push(scratch.arena, &parts, str8_lit("\\"));}break; } } split_start_idx = idx+1+extra_advance; } } StringJoin join = {0}; String8 result = str8_list_join(arena, &parts, &join); scratch_end(scratch); return result; } internal String8List d_cfg_strings_from_core(Arena *arena, String8 root_path, D_CfgSrc source) { ProfBeginFunction(); local_persist char *spaces = " "; local_persist char *slashes= "////////////////////////////////////////////////////////////////////////////////"; String8List strs = {0}; //- rjf: write all entities { for(EachEnumVal(D_EntityKind, k)) { D_EntityKindFlags k_flags = d_entity_kind_flags_table[k]; if(!(k_flags & D_EntityKindFlag_IsSerializedToConfig)) { continue; } B32 first = 1; D_EntityList entities = d_query_cached_entity_list_with_kind(k); for(D_EntityNode *n = entities.first; n != 0; n = n->next) { D_Entity *entity = n->entity; if(entity->cfg_src != source) { continue; } if(first) { first = 0; String8 title_name = d_entity_kind_name_lower_plural_table[k]; str8_list_pushf(arena, &strs, "/// %S %.*s\n\n", title_name, (int)Max(0, 79 - (title_name.size + 5)), slashes); } D_EntityRec rec = {0}; S64 depth = 0; for(D_Entity *e = entity; !d_entity_is_nil(e); e = rec.next) { //- rjf: get next iteration rec = d_entity_rec_depth_first_pre(e, entity); //- rjf: unpack entity info typedef U32 EntityInfoFlags; enum { EntityInfoFlag_HasName = (1<<0), EntityInfoFlag_HasDisabled = (1<<1), EntityInfoFlag_HasTxtPt = (1<<2), EntityInfoFlag_HasVAddr = (1<<3), EntityInfoFlag_HasColor = (1<<4), EntityInfoFlag_HasChildren = (1<<5), }; String8 entity_name_escaped = e->string; if(d_entity_kind_flags_table[e->kind] & D_EntityKindFlag_NameIsPath) { Temp scratch = scratch_begin(&arena, 1); String8 path_normalized = path_normalized_from_string(scratch.arena, e->string); entity_name_escaped = path_relative_dst_from_absolute_dst_src(arena, path_normalized, root_path); scratch_end(scratch); } else { entity_name_escaped = d_cfg_escaped_from_raw_string(arena, e->string); } EntityInfoFlags info_flags = 0; if(entity_name_escaped.size != 0) { info_flags |= EntityInfoFlag_HasName; } if(!!e->disabled) { info_flags |= EntityInfoFlag_HasDisabled; } if(e->flags & D_EntityFlag_HasTextPoint) { info_flags |= EntityInfoFlag_HasTxtPt; } if(e->flags & D_EntityFlag_HasVAddr) { info_flags |= EntityInfoFlag_HasVAddr; } if(e->flags & D_EntityFlag_HasColor) { info_flags |= EntityInfoFlag_HasColor; } if(!d_entity_is_nil(e->first)) { info_flags |= EntityInfoFlag_HasChildren; } //- rjf: write entity info B32 opened_brace = 0; switch(info_flags) { //- rjf: default path -> entity has lots of stuff, so write all info generically default: { opened_brace = 1; // rjf: write entity title str8_list_pushf(arena, &strs, "%S:\n{\n", d_entity_kind_name_lower_table[e->kind]); // rjf: write this entity's info if(entity_name_escaped.size != 0) { str8_list_pushf(arena, &strs, "name: \"%S\"\n", entity_name_escaped); } if(e->disabled) { str8_list_pushf(arena, &strs, "disabled: 1\n"); } if(e->flags & D_EntityFlag_HasColor) { Vec4F32 hsva = d_hsva_from_entity(e); Vec4F32 rgba = rgba_from_hsva(hsva); U32 rgba_hex = u32_from_rgba(rgba); str8_list_pushf(arena, &strs, "color: 0x%x\n", rgba_hex); } if(e->flags & D_EntityFlag_HasTextPoint) { str8_list_pushf(arena, &strs, "line: %I64d\n", e->text_point.line); } if(e->flags & D_EntityFlag_HasVAddr) { str8_list_pushf(arena, &strs, "vaddr: (0x%I64x)\n", e->vaddr); } }break; //- rjf: single-line fast-paths case EntityInfoFlag_HasName: {str8_list_pushf(arena, &strs, "%S: \"%S\"\n", d_entity_kind_name_lower_table[e->kind], entity_name_escaped);}break; case EntityInfoFlag_HasName|EntityInfoFlag_HasTxtPt: {str8_list_pushf(arena, &strs, "%S: (\"%S\":%I64d)\n", d_entity_kind_name_lower_table[e->kind], entity_name_escaped, e->text_point.line);}break; case EntityInfoFlag_HasVAddr: {str8_list_pushf(arena, &strs, "%S: (0x%I64x)\n", d_entity_kind_name_lower_table[e->kind], e->vaddr);}break; //- rjf: empty case 0: {}break; } // rjf: push depth += rec.push_count; // rjf: pop if(rec.push_count == 0) { for(S64 pop_idx = 0; pop_idx < rec.pop_count + opened_brace; pop_idx += 1) { if(depth > 0) { depth -= 1; } str8_list_pushf(arena, &strs, "}\n"); } } // rjf: separate top-level entities with extra newline if(d_entity_is_nil(rec.next) && (rec.pop_count != 0 || n->next == 0)) { str8_list_pushf(arena, &strs, "\n"); } } } } } //- rjf: write exception code filters if(source == D_CfgSrc_Project) { str8_list_push(arena, &strs, str8_lit("/// exception code filters ////////////////////////////////////////////////////\n")); str8_list_push(arena, &strs, str8_lit("\n")); str8_list_push(arena, &strs, str8_lit("exception_code_filters:\n")); str8_list_push(arena, &strs, str8_lit("{\n")); for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1); k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) { String8 name = ctrl_exception_code_kind_lowercase_code_string_table[k]; B32 value = !!(d_state->ctrl_exception_code_filters[k/64] & (1ull<<(k%64))); str8_list_pushf(arena, &strs, " %S: %i\n", name, value); } str8_list_push(arena, &strs, str8_lit("}\n\n")); } ProfEnd(); return strs; } internal void d_cfg_push_write_string(D_CfgSrc src, String8 string) { str8_list_push(d_state->cfg_write_arenas[src], &d_state->cfg_write_data[src], push_str8_copy(d_state->cfg_write_arenas[src], string)); } //- rjf: current path internal String8 d_current_path(void) { return d_state->current_path; } //- rjf: entity kind cache internal D_EntityList d_query_cached_entity_list_with_kind(D_EntityKind kind) { ProfBeginFunction(); D_EntityListCache *cache = &d_state->kind_caches[kind]; // rjf: build cached list if we're out-of-date if(cache->alloc_gen != d_state->kind_alloc_gens[kind]) { cache->alloc_gen = d_state->kind_alloc_gens[kind]; if(cache->arena == 0) { cache->arena = arena_alloc(); } arena_clear(cache->arena); cache->list = d_push_entity_list_with_kind(cache->arena, kind); } // rjf: grab & return cached list D_EntityList result = cache->list; ProfEnd(); return result; } //- rjf: active entity based queries internal DI_KeyList d_push_active_dbgi_key_list(Arena *arena) { DI_KeyList dbgis = {0}; D_EntityList modules = d_query_cached_entity_list_with_kind(D_EntityKind_Module); for(D_EntityNode *n = modules.first; n != 0; n = n->next) { D_Entity *module = n->entity; DI_Key key = d_dbgi_key_from_module(module); di_key_list_push(arena, &dbgis, &key); } return dbgis; } internal D_EntityList d_push_active_target_list(Arena *arena) { D_EntityList active_targets = {0}; D_EntityList all_targets = d_query_cached_entity_list_with_kind(D_EntityKind_Target); for(D_EntityNode *n = all_targets.first; n != 0; n = n->next) { if(!n->entity->disabled) { d_entity_list_push(arena, &active_targets, n->entity); } } return active_targets; } //- rjf: expand key based entity queries internal D_Entity * d_entity_from_expand_key_and_kind(D_ExpandKey key, D_EntityKind kind) { D_Entity *result = &d_nil_entity; D_EntityList list = d_query_cached_entity_list_with_kind(kind); for(D_EntityNode *n = list.first; n != 0; n = n->next) { D_Entity *entity = n->entity; if(d_expand_key_match(d_expand_key_from_entity(entity), key)) { result = entity; break; } } return result; } //- rjf: per-run caches internal CTRL_Unwind d_query_cached_unwind_from_thread(D_Entity *thread) { Temp scratch = scratch_begin(0, 0); CTRL_Unwind result = {0}; if(thread->kind == D_EntityKind_Thread) { U64 reg_gen = ctrl_reg_gen(); U64 mem_gen = ctrl_mem_gen(); D_UnwindCache *cache = &d_state->unwind_cache; D_Handle handle = d_handle_from_entity(thread); U64 hash = d_hash_from_string(str8_struct(&handle)); U64 slot_idx = hash%cache->slots_count; D_UnwindCacheSlot *slot = &cache->slots[slot_idx]; D_UnwindCacheNode *node = 0; for(D_UnwindCacheNode *n = slot->first; n != 0; n = n->next) { if(d_handle_match(handle, n->thread)) { node = n; break; } } if(node == 0) { node = cache->free_node; if(node != 0) { SLLStackPop(cache->free_node); } else { node = push_array_no_zero(d_state->arena, D_UnwindCacheNode, 1); } MemoryZeroStruct(node); DLLPushBack(slot->first, slot->last, node); node->arena = arena_alloc(); node->thread = handle; } if(node->reggen != reg_gen || node->memgen != mem_gen) { CTRL_Unwind new_unwind = ctrl_unwind_from_thread(scratch.arena, d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle, os_now_microseconds()+100); if(!(new_unwind.flags & (CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale)) && new_unwind.frames.count != 0) { node->unwind = ctrl_unwind_deep_copy(node->arena, thread->arch, &new_unwind); node->reggen = reg_gen; node->memgen = mem_gen; } } result = node->unwind; } scratch_end(scratch); return result; } internal U64 d_query_cached_rip_from_thread(D_Entity *thread) { U64 result = d_query_cached_rip_from_thread_unwind(thread, 0); return result; } internal U64 d_query_cached_rip_from_thread_unwind(D_Entity *thread, U64 unwind_count) { U64 result = 0; if(unwind_count == 0) { result = ctrl_query_cached_rip_from_thread(d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); } else { CTRL_Unwind unwind = d_query_cached_unwind_from_thread(thread); if(unwind.frames.count != 0) { result = regs_rip_from_arch_block(thread->arch, unwind.frames.v[unwind_count%unwind.frames.count].regs); } } return result; } internal U64 d_query_cached_tls_base_vaddr_from_process_root_rip(D_Entity *process, U64 root_vaddr, U64 rip_vaddr) { U64 result = 0; for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->tls_base_caches); cache_idx += 1) { D_RunTLSBaseCache *cache = &d_state->tls_base_caches[(d_state->tls_base_cache_gen+cache_idx)%ArrayCount(d_state->tls_base_caches)]; if(cache_idx == 0 && cache->slots_count == 0) { cache->slots_count = 256; cache->slots = push_array(cache->arena, D_RunTLSBaseCacheSlot, cache->slots_count); } else if(cache->slots_count == 0) { break; } D_Handle handle = d_handle_from_entity(process); U64 hash = d_hash_from_seed_string(d_hash_from_string(str8_struct(&handle)), str8_struct(&rip_vaddr)); U64 slot_idx = hash%cache->slots_count; D_RunTLSBaseCacheSlot *slot = &cache->slots[slot_idx]; D_RunTLSBaseCacheNode *node = 0; for(D_RunTLSBaseCacheNode *n = slot->first; n != 0; n = n->hash_next) { if(d_handle_match(n->process, handle) && n->root_vaddr == root_vaddr && n->rip_vaddr == rip_vaddr) { node = n; break; } } if(node == 0) { U64 tls_base_vaddr = d_tls_base_vaddr_from_process_root_rip(process, root_vaddr, rip_vaddr); if(tls_base_vaddr != 0) { node = push_array(cache->arena, D_RunTLSBaseCacheNode, 1); SLLQueuePush_N(slot->first, slot->last, node, hash_next); node->process = handle; node->root_vaddr = root_vaddr; node->rip_vaddr = rip_vaddr; node->tls_base_vaddr = tls_base_vaddr; } } if(node != 0 && node->tls_base_vaddr != 0) { result = node->tls_base_vaddr; break; } } return result; } internal E_String2NumMap * d_query_cached_locals_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff) { ProfBeginFunction(); E_String2NumMap *map = &e_string2num_map_nil; for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->locals_caches); cache_idx += 1) { D_RunLocalsCache *cache = &d_state->locals_caches[(d_state->locals_cache_gen+cache_idx)%ArrayCount(d_state->locals_caches)]; if(cache_idx == 0 && cache->table_size == 0) { cache->table_size = 256; cache->table = push_array(cache->arena, D_RunLocalsCacheSlot, cache->table_size); } else if(cache->table_size == 0) { break; } U64 hash = di_hash_from_key(dbgi_key); U64 slot_idx = hash % cache->table_size; D_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; D_RunLocalsCacheNode *node = 0; for(D_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) { if(di_key_match(&n->dbgi_key, dbgi_key) && n->voff == voff) { node = n; break; } } if(node == 0) { DI_Scope *scope = di_scope_open(); E_String2NumMap *map = d_push_locals_map_from_dbgi_key_voff(cache->arena, scope, dbgi_key, voff); if(map->slots_count != 0) { node = push_array(cache->arena, D_RunLocalsCacheNode, 1); node->dbgi_key = di_key_copy(cache->arena, dbgi_key); node->voff = voff; node->locals_map = map; SLLQueuePush_N(slot->first, slot->last, node, hash_next); } di_scope_close(scope); } if(node != 0 && node->locals_map->slots_count != 0) { map = node->locals_map; break; } } ProfEnd(); return map; } internal E_String2NumMap * d_query_cached_member_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff) { ProfBeginFunction(); E_String2NumMap *map = &e_string2num_map_nil; for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->member_caches); cache_idx += 1) { D_RunLocalsCache *cache = &d_state->member_caches[(d_state->member_cache_gen+cache_idx)%ArrayCount(d_state->member_caches)]; if(cache_idx == 0 && cache->table_size == 0) { cache->table_size = 256; cache->table = push_array(cache->arena, D_RunLocalsCacheSlot, cache->table_size); } else if(cache->table_size == 0) { break; } U64 hash = di_hash_from_key(dbgi_key); U64 slot_idx = hash % cache->table_size; D_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; D_RunLocalsCacheNode *node = 0; for(D_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) { if(di_key_match(&n->dbgi_key, dbgi_key) && n->voff == voff) { node = n; break; } } if(node == 0) { DI_Scope *scope = di_scope_open(); E_String2NumMap *map = d_push_member_map_from_dbgi_key_voff(cache->arena, scope, dbgi_key, voff); if(map->slots_count != 0) { node = push_array(cache->arena, D_RunLocalsCacheNode, 1); node->dbgi_key = di_key_copy(cache->arena, dbgi_key); node->voff = voff; node->locals_map = map; SLLQueuePush_N(slot->first, slot->last, node, hash_next); } di_scope_close(scope); } if(node != 0 && node->locals_map->slots_count != 0) { map = node->locals_map; break; } } ProfEnd(); return map; } //- rjf: top-level command dispatch internal void d_push_cmd(D_CmdSpec *spec, D_CmdParams *params) { // rjf: log if(params->os_event == 0 || params->os_event->kind != OS_EventKind_MouseMove) { Temp scratch = scratch_begin(0, 0); D_Entity *entity = d_entity_from_handle(params->entity); log_infof("df_cmd:\n{\n", spec->info.string); log_infof("spec: \"%S\"\n", spec->info.string); #define HandleParamPrint(mem_name) if(!d_handle_match(d_handle_zero(), params->mem_name)) { log_infof("%s: [0x%I64x, 0x%I64x]\n", #mem_name, params->mem_name.u64[0], params->mem_name.u64[1]); } HandleParamPrint(window); HandleParamPrint(panel); HandleParamPrint(dest_panel); HandleParamPrint(prev_view); HandleParamPrint(view); if(!d_entity_is_nil(entity)) { String8 entity_name = d_display_string_from_entity(scratch.arena, entity); log_infof("entity: \"%S\"\n", entity_name); } U64 idx = 0; for(D_HandleNode *n = params->entity_list.first; n != 0; n = n->next, idx += 1) { D_Entity *entity = d_entity_from_handle(n->handle); if(!d_entity_is_nil(entity)) { String8 entity_name = d_display_string_from_entity(scratch.arena, entity); log_infof("entity_list[%I64u]: \"%S\"\n", idx, entity_name); } } if(!d_cmd_spec_is_nil(params->cmd_spec)) { log_infof("cmd_spec: \"%S\"\n", params->cmd_spec->info.string); } if(params->string.size != 0) { log_infof("string: \"%S\"\n", params->string); } if(params->file_path.size != 0) { log_infof("file_path: \"%S\"\n", params->file_path); } if(params->text_point.line != 0) { log_infof("text_point: [line:%I64d, col:%I64d]\n", params->text_point.line, params->text_point.column); } if(params->vaddr != 0) { log_infof("vaddr: 0x%I64x\n", params->vaddr); } if(params->voff != 0) { log_infof("voff: 0x%I64x\n", params->voff); } if(params->index != 0) { log_infof("index: 0x%I64x\n", params->index); } if(params->unwind_index != 0) { log_infof("unwind_index: 0x%I64x\n", params->unwind_index); } if(params->inline_depth != 0) { log_infof("inline_depth: 0x%I64x\n", params->inline_depth); } if(params->id != 0) { log_infof("id: 0x%I64x\n", params->id); } if(params->os_event != 0) { String8 kind_string = str8_lit(""); switch(params->os_event->kind) { default:{}break; case OS_EventKind_Press: {kind_string = str8_lit("press");}break; case OS_EventKind_Release: {kind_string = str8_lit("release");}break; case OS_EventKind_MouseMove: {kind_string = str8_lit("mousemove");}break; case OS_EventKind_Text: {kind_string = str8_lit("text");}break; case OS_EventKind_Scroll: {kind_string = str8_lit("scroll");}break; case OS_EventKind_WindowLoseFocus:{kind_string = str8_lit("losefocus");}break; case OS_EventKind_WindowClose: {kind_string = str8_lit("closewindow");}break; case OS_EventKind_FileDrop: {kind_string = str8_lit("filedrop");}break; case OS_EventKind_Wakeup: {kind_string = str8_lit("wakeup");}break; } log_infof("os_event->kind: %S\n", kind_string); } #undef HandleParamPrint log_infof("}\n\n"); scratch_end(scratch); } d_cmd_list_push(d_state->root_cmd_arena, &d_state->root_cmds, params, spec); } internal void d_error(String8 string) { d_cmd(D_CmdKind_Error, .string = string); } internal void d_errorf(char *fmt, ...) { Temp scratch = scratch_begin(0, 0); va_list args; va_start(args, fmt); String8 string = push_str8fv(scratch.arena, fmt, args); d_error(string); scratch_end(scratch); } //////////////////////////////// //~ rjf: Message Functions internal D_MsgKind d_msg_kind_from_string(String8 string) { D_MsgKind result = D_MsgKind_Null; for(EachNonZeroEnumVal(D_MsgKind, k)) { if(str8_match(string, d_msg_kind_name_lower_table[k], 0)) { result = k; break; } } return result; } internal void d_msg_(D_MsgKind kind, D_Regs *regs) { D_MsgNode *n = push_array(d_state->msgs_arena, D_MsgNode, 1); SLLQueuePush(d_state->msgs.first, d_state->msgs.last, n); d_state->msgs.count += 1; n->v.kind = kind; n->v.regs = d_regs_copy(d_state->msgs_arena, regs); } //////////////////////////////// //~ rjf: Main Layer Top-Level Calls internal void d_init(CmdLine *cmdln, D_StateDeltaHistory *hist) { Arena *arena = arena_alloc(); d_state = push_array(arena, D_State, 1); d_state->arena = arena; for(U64 idx = 0; idx < ArrayCount(d_state->frame_arenas); idx += 1) { d_state->frame_arenas[idx] = arena_alloc(); } d_state->root_cmd_arena = arena_alloc(); d_state->output_log_key = hs_hash_from_data(str8_lit("df_output_log_key")); d_state->entities_arena = arena_alloc(.reserve_size = GB(64), .commit_size = KB(64)); d_state->entities_root = &d_nil_entity; d_state->entities_base = push_array(d_state->entities_arena, D_Entity, 0); d_state->entities_count = 0; d_state->ctrl_msg_arena = arena_alloc(); d_state->ctrl_entity_store = ctrl_entity_store_alloc(); d_state->ctrl_stop_arena = arena_alloc(); d_state->entities_root = d_entity_alloc(&d_nil_entity, D_EntityKind_Root); d_state->cmd_spec_table_size = 1024; d_state->cmd_spec_table = push_array(arena, D_CmdSpec *, d_state->cmd_spec_table_size); d_state->view_rule_spec_table_size = 1024; d_state->view_rule_spec_table = push_array(arena, D_ViewRuleSpec *, d_state->view_rule_spec_table_size); d_state->seconds_til_autosave = 0.5f; d_state->hist = hist; d_state->top_regs = &d_state->base_regs; d_state->msgs_arena = arena_alloc(); // rjf: set up initial exception filtering rules for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) { if(ctrl_exception_code_kind_default_enable_table[k]) { d_state->ctrl_exception_code_filters[k/64] |= 1ull<<(k%64); } } // rjf: set up initial entities { D_Entity *local_machine = d_entity_alloc(d_state->entities_root, D_EntityKind_Machine); d_entity_equip_ctrl_machine_id(local_machine, CTRL_MachineID_Local); d_entity_equip_name(local_machine, str8_lit("This PC")); } // rjf: register core commands { D_CmdSpecInfoArray array = {d_core_cmd_kind_spec_info_table, ArrayCount(d_core_cmd_kind_spec_info_table)}; d_register_cmd_specs(array); } // rjf: register core view rules { D_ViewRuleSpecInfoArray array = {d_core_view_rule_spec_info_table, ArrayCount(d_core_view_rule_spec_info_table)}; d_register_view_rule_specs(array); } // rjf: set up caches d_state->unwind_cache.slots_count = 1024; d_state->unwind_cache.slots = push_array(arena, D_UnwindCacheSlot, d_state->unwind_cache.slots_count); for(U64 idx = 0; idx < ArrayCount(d_state->tls_base_caches); idx += 1) { d_state->tls_base_caches[idx].arena = arena_alloc(); } for(U64 idx = 0; idx < ArrayCount(d_state->locals_caches); idx += 1) { d_state->locals_caches[idx].arena = arena_alloc(); } for(U64 idx = 0; idx < ArrayCount(d_state->member_caches); idx += 1) { d_state->member_caches[idx].arena = arena_alloc(); } // rjf: set up eval view cache d_state->eval_view_cache.slots_count = 4096; d_state->eval_view_cache.slots = push_array(arena, D_EvalViewSlot, d_state->eval_view_cache.slots_count); // rjf: set up run state d_state->ctrl_last_run_arena = arena_alloc(); // rjf: set up config reading state { Temp scratch = scratch_begin(0, 0); // rjf: unpack command line arguments String8 user_cfg_path = cmd_line_string(cmdln, str8_lit("user")); String8 project_cfg_path = cmd_line_string(cmdln, str8_lit("project")); if(project_cfg_path.size == 0) { project_cfg_path = cmd_line_string(cmdln, str8_lit("profile")); } { String8 user_program_data_path = os_get_process_info()->user_program_data_path; String8 user_data_folder = push_str8f(scratch.arena, "%S/%S", user_program_data_path, str8_lit("raddbg")); os_make_directory(user_data_folder); if(user_cfg_path.size == 0) { user_cfg_path = push_str8f(scratch.arena, "%S/default.raddbg_user", user_data_folder); } if(project_cfg_path.size == 0) { project_cfg_path = push_str8f(scratch.arena, "%S/default.raddbg_project", user_data_folder); } } // rjf: set up config path state String8 cfg_src_paths[D_CfgSrc_COUNT] = {user_cfg_path, project_cfg_path}; for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { d_state->cfg_path_arenas[src] = arena_alloc(); d_cmd(d_cfg_src_load_cmd_kind_table[src], .file_path = path_normalized_from_string(scratch.arena, cfg_src_paths[src])); } // rjf: set up config table arena d_state->cfg_arena = arena_alloc(); scratch_end(scratch); } // rjf: set up config write state for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { d_state->cfg_write_arenas[src] = arena_alloc(); } // rjf: set up initial browse path { Temp scratch = scratch_begin(0, 0); String8 current_path = os_get_current_path(scratch.arena); String8 current_path_with_slash = push_str8f(scratch.arena, "%S/", current_path); d_state->current_path_arena = arena_alloc(); d_state->current_path = push_str8_copy(d_state->current_path_arena, current_path_with_slash); scratch_end(scratch); } } internal D_CmdList d_gather_root_cmds(Arena *arena) { D_CmdList cmds = {0}; for(D_CmdNode *n = d_state->root_cmds.first; n != 0; n = n->next) { d_cmd_list_push(arena, &cmds, &n->cmd.params, n->cmd.spec); } return cmds; } internal void d_tick(Arena *arena, DI_Scope *di_scope, D_CmdList *cmds, F32 dt) { ProfBeginFunction(); d_state->frame_index += 1; arena_clear(d_frame_arena()); d_state->frame_eval_memread_endt_us = os_now_microseconds() + 5000; d_state->dt = dt; d_state->time_in_seconds += dt; d_state->top_regs = &d_state->base_regs; d_regs_copy_contents(d_frame_arena(), &d_state->top_regs->v, &d_state->top_regs->v); ////////////////////////////// //- rjf: sync with ctrl thread // ProfScope("sync with ctrl thread") { Temp scratch = scratch_begin(&arena, 1); //- rjf: grab next reggen/memgen U64 new_mem_gen = ctrl_mem_gen(); U64 new_reg_gen = ctrl_reg_gen(); //- rjf: consume & process events CTRL_EventList events = ctrl_c2u_pop_events(scratch.arena); ctrl_entity_store_apply_events(d_state->ctrl_entity_store, &events); for(CTRL_EventNode *event_n = events.first; event_n != 0; event_n = event_n->next) { CTRL_Event *event = &event_n->v; log_infof("ctrl_event:\n{\n"); log_infof("kind: \"%S\"\n", ctrl_string_from_event_kind(event->kind)); log_infof("entity_id: %u\n", event->entity_id); switch(event->kind) { default:{}break; //- rjf: errors case CTRL_EventKind_Error: { log_user_error(event->string); }break; //- rjf: starts/stops case CTRL_EventKind_Started: { d_state->ctrl_is_running = 1; }break; case CTRL_EventKind_Stopped: { B32 should_snap = !(d_state->ctrl_soft_halt_issued); d_state->ctrl_is_running = 0; d_state->ctrl_soft_halt_issued = 0; D_Entity *stop_thread = d_entity_from_ctrl_handle(event->machine_id, event->entity); // rjf: gather stop info { arena_clear(d_state->ctrl_stop_arena); MemoryCopyStruct(&d_state->ctrl_last_stop_event, event); d_state->ctrl_last_stop_event.string = push_str8_copy(d_state->ctrl_stop_arena, d_state->ctrl_last_stop_event.string); } // rjf: select & snap to thread causing stop if(should_snap && stop_thread->kind == D_EntityKind_Thread) { log_infof("stop_thread: \"%S\"\n", d_display_string_from_entity(scratch.arena, stop_thread)); D_CmdParams params = d_cmd_params_zero(); params.entity = d_handle_from_entity(stop_thread); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_SelectThread)); } // rjf: if no stop-causing thread, and if selected thread, snap to selected if(should_snap && d_entity_is_nil(stop_thread)) { D_Entity *selected_thread = d_entity_from_handle(d_regs()->thread); if(!d_entity_is_nil(selected_thread)) { D_CmdParams params = d_cmd_params_zero(); params.entity = d_handle_from_entity(selected_thread); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_FindThread)); } } // rjf: thread hit user breakpoint -> increment breakpoint hit count if(should_snap && event->cause == CTRL_EventCause_UserBreakpoint) { U64 stop_thread_vaddr = ctrl_query_cached_rip_from_thread(d_state->ctrl_entity_store, stop_thread->ctrl_machine_id, stop_thread->ctrl_handle); D_Entity *process = d_entity_ancestor_from_kind(stop_thread, D_EntityKind_Process); D_Entity *module = d_module_from_process_vaddr(process, stop_thread_vaddr); DI_Key dbgi_key = d_dbgi_key_from_module(module); U64 stop_thread_voff = d_voff_from_vaddr(module, stop_thread_vaddr); D_EntityList user_bps = d_query_cached_entity_list_with_kind(D_EntityKind_Breakpoint); for(D_EntityNode *n = user_bps.first; n != 0; n = n->next) { D_Entity *bp = n->entity; D_Entity *loc = d_entity_child_from_kind(bp, D_EntityKind_Location); D_LineList loc_lines = d_lines_from_file_path_line_num(scratch.arena, loc->string, loc->text_point.line); if(loc_lines.first != 0) { for(D_LineNode *n = loc_lines.first; n != 0; n = n->next) { if(contains_1u64(n->v.voff_range, stop_thread_voff)) { bp->u64 += 1; break; } } } else if(loc->flags & D_EntityFlag_HasVAddr && stop_thread_vaddr == loc->vaddr) { bp->u64 += 1; } else if(loc->string.size != 0) { U64 symb_voff = d_voff_from_dbgi_key_symbol_name(&dbgi_key, loc->string); if(symb_voff == stop_thread_voff) { bp->u64 += 1; } } } } // rjf: exception or unexpected trap -> push error if(event->cause == CTRL_EventCause_InterruptedByException || event->cause == CTRL_EventCause_InterruptedByTrap) { D_CmdParams params = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_Error)); } // rjf: kill all entities which are marked to die on stop { D_Entity *request = d_entity_from_id(event->msg_id); if(d_entity_is_nil(request)) { for(D_Entity *entity = d_entity_root(); !d_entity_is_nil(entity); entity = d_entity_rec_depth_first_pre(entity, d_entity_root()).next) { if(entity->flags & D_EntityFlag_DiesOnRunStop) { d_entity_mark_for_deletion(entity); } } } } }break; //- rjf: entity creation/deletion case CTRL_EventKind_NewProc: { // rjf: the first process? -> clear session output & reset all bp hit counts D_EntityList existing_processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); if(existing_processes.count == 0) { MTX_Op op = {r1u64(0, 0xffffffffffffffffull), str8_lit("[new session]\n")}; mtx_push_op(d_state->output_log_key, op); D_EntityList bps = d_query_cached_entity_list_with_kind(D_EntityKind_Breakpoint); for(D_EntityNode *n = bps.first; n != 0; n = n->next) { n->entity->u64 = 0; } } // rjf: create entity D_Entity *machine = d_machine_entity_from_machine_id(event->machine_id); D_Entity *entity = d_entity_alloc(machine, D_EntityKind_Process); d_entity_equip_u64(entity, event->msg_id); d_entity_equip_ctrl_machine_id(entity, event->machine_id); d_entity_equip_ctrl_handle(entity, event->entity); d_entity_equip_ctrl_id(entity, event->entity_id); d_entity_equip_arch(entity, event->arch); }break; case CTRL_EventKind_NewThread: { // rjf: create entity D_Entity *parent = d_entity_from_ctrl_handle(event->machine_id, event->parent); D_Entity *entity = d_entity_alloc(parent, D_EntityKind_Thread); d_entity_equip_ctrl_machine_id(entity, event->machine_id); d_entity_equip_ctrl_handle(entity, event->entity); d_entity_equip_arch(entity, event->arch); d_entity_equip_ctrl_id(entity, event->entity_id); d_entity_equip_stack_base(entity, event->stack_base); d_entity_equip_vaddr(entity, event->rip_vaddr); if(event->string.size != 0) { d_entity_equip_name(entity, event->string); } // rjf: find any pending thread names correllating with this TID -> equip name if found match { D_EntityList pending_thread_names = d_query_cached_entity_list_with_kind(D_EntityKind_PendingThreadName); for(D_EntityNode *n = pending_thread_names.first; n != 0; n = n->next) { D_Entity *pending_thread_name = n->entity; if(event->machine_id == pending_thread_name->ctrl_machine_id && event->entity_id == pending_thread_name->ctrl_id) { d_entity_mark_for_deletion(pending_thread_name); d_entity_equip_name(entity, pending_thread_name->string); break; } } } // rjf: determine index in process U64 thread_idx_in_process = 0; for(D_Entity *child = parent->first; !d_entity_is_nil(child); child = child->next) { if(child == entity) { break; } if(child->kind == D_EntityKind_Thread) { thread_idx_in_process += 1; } } // rjf: build default thread color table Vec4F32 thread_colors[] = { df_rgba_from_theme_color(DF_ThemeColor_Thread0), df_rgba_from_theme_color(DF_ThemeColor_Thread1), df_rgba_from_theme_color(DF_ThemeColor_Thread2), df_rgba_from_theme_color(DF_ThemeColor_Thread3), df_rgba_from_theme_color(DF_ThemeColor_Thread4), df_rgba_from_theme_color(DF_ThemeColor_Thread5), df_rgba_from_theme_color(DF_ThemeColor_Thread6), df_rgba_from_theme_color(DF_ThemeColor_Thread7), }; // rjf: pick color Vec4F32 thread_color = thread_colors[thread_idx_in_process % ArrayCount(thread_colors)]; // rjf: equip color d_entity_equip_color_rgba(entity, thread_color); // rjf: automatically select if we don't have a selected thread D_Entity *selected_thread = d_entity_from_handle(d_state->base_regs.v.thread); if(d_entity_is_nil(selected_thread)) { d_state->base_regs.v.thread = d_handle_from_entity(entity); } // rjf: do initial snap D_EntityList already_existing_processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); B32 do_initial_snap = (already_existing_processes.count == 1 && thread_idx_in_process == 0); if(do_initial_snap) { D_CmdParams params = d_cmd_params_zero(); params.entity = d_handle_from_entity(entity); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_SelectThread)); } }break; case CTRL_EventKind_NewModule: { // rjf: grab process D_Entity *parent = d_entity_from_ctrl_handle(event->machine_id, event->parent); // rjf: determine if this is the first module B32 is_first = 0; if(d_entity_is_nil(d_entity_child_from_kind(parent, D_EntityKind_Module))) { is_first = 1; } // rjf: create module entity D_Entity *module = d_entity_alloc(parent, D_EntityKind_Module); d_entity_equip_ctrl_machine_id(module, event->machine_id); d_entity_equip_ctrl_handle(module, event->entity); d_entity_equip_arch(module, event->arch); d_entity_equip_name(module, event->string); d_entity_equip_vaddr_rng(module, event->vaddr_rng); d_entity_equip_vaddr(module, event->rip_vaddr); d_entity_equip_timestamp(module, event->timestamp); // rjf: is first -> find target, equip process & module & first thread with target color if(is_first) { D_EntityList targets = d_query_cached_entity_list_with_kind(D_EntityKind_Target); for(D_EntityNode *n = targets.first; n != 0; n = n->next) { D_Entity *target = n->entity; D_Entity *exe = d_entity_child_from_kind(target, D_EntityKind_Executable); String8 exe_name = exe->string; String8 exe_name_normalized = path_normalized_from_string(scratch.arena, exe_name); String8 module_name_normalized = path_normalized_from_string(scratch.arena, module->string); if(str8_match(exe_name_normalized, module_name_normalized, StringMatchFlag_CaseInsensitive) && target->flags & D_EntityFlag_HasColor) { D_Entity *first_thread = d_entity_child_from_kind(parent, D_EntityKind_Thread); Vec4F32 rgba = d_rgba_from_entity(target); d_entity_equip_color_rgba(parent, rgba); d_entity_equip_color_rgba(first_thread, rgba); d_entity_equip_color_rgba(module, rgba); break; } } } }break; case CTRL_EventKind_EndProc: { U32 pid = event->entity_id; D_Entity *process = d_entity_from_ctrl_handle(event->machine_id, event->entity); d_entity_mark_for_deletion(process); }break; case CTRL_EventKind_EndThread: { D_Entity *thread = d_entity_from_ctrl_handle(event->machine_id, event->entity); d_set_thread_freeze_state(thread, 0); d_entity_mark_for_deletion(thread); }break; case CTRL_EventKind_EndModule: { D_Entity *module = d_entity_from_ctrl_handle(event->machine_id, event->entity); d_entity_mark_for_deletion(module); }break; //- rjf: debug info changes case CTRL_EventKind_ModuleDebugInfoPathChange: { D_Entity *module = d_entity_from_ctrl_handle(event->machine_id, event->entity); D_Entity *debug_info = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); if(d_entity_is_nil(debug_info)) { debug_info = d_entity_alloc(module, D_EntityKind_DebugInfoPath); } d_entity_equip_name(debug_info, event->string); d_entity_equip_timestamp(debug_info, event->timestamp); }break; //- rjf: debug strings case CTRL_EventKind_DebugString: { MTX_Op op = {r1u64(max_U64, max_U64), event->string}; mtx_push_op(d_state->output_log_key, op); }break; case CTRL_EventKind_ThreadName: { String8 string = event->string; D_Entity *entity = d_entity_from_ctrl_handle(event->machine_id, event->entity); if(event->entity_id != 0) { entity = d_entity_from_ctrl_id(event->machine_id, event->entity_id); } if(d_entity_is_nil(entity)) { D_Entity *process = d_entity_from_ctrl_handle(event->machine_id, event->parent); if(!d_entity_is_nil(process)) { entity = d_entity_alloc(process, D_EntityKind_PendingThreadName); d_entity_equip_name(entity, string); d_entity_equip_ctrl_machine_id(entity, event->machine_id); d_entity_equip_ctrl_id(entity, event->entity_id); } } if(!d_entity_is_nil(entity)) { d_entity_equip_name(entity, string); } }break; //- rjf: memory case CTRL_EventKind_MemReserve:{}break; case CTRL_EventKind_MemCommit:{}break; case CTRL_EventKind_MemDecommit:{}break; case CTRL_EventKind_MemRelease:{}break; } log_infof("}\n\n"); } //- rjf: clear tls base cache if((d_state->tls_base_cache_reggen_idx != new_reg_gen || d_state->tls_base_cache_memgen_idx != new_mem_gen) && !d_ctrl_targets_running()) { d_state->tls_base_cache_gen += 1; D_RunTLSBaseCache *cache = &d_state->tls_base_caches[d_state->tls_base_cache_gen%ArrayCount(d_state->tls_base_caches)]; arena_clear(cache->arena); cache->slots_count = 0; cache->slots = 0; d_state->tls_base_cache_reggen_idx = new_reg_gen; d_state->tls_base_cache_memgen_idx = new_mem_gen; } //- rjf: clear locals cache if(d_state->locals_cache_reggen_idx != new_reg_gen && !d_ctrl_targets_running()) { d_state->locals_cache_gen += 1; D_RunLocalsCache *cache = &d_state->locals_caches[d_state->locals_cache_gen%ArrayCount(d_state->locals_caches)]; arena_clear(cache->arena); cache->table_size = 0; cache->table = 0; d_state->locals_cache_reggen_idx = new_reg_gen; } //- rjf: clear members cache if(d_state->member_cache_reggen_idx != new_reg_gen && !d_ctrl_targets_running()) { d_state->member_cache_gen += 1; D_RunLocalsCache *cache = &d_state->member_caches[d_state->member_cache_gen%ArrayCount(d_state->member_caches)]; arena_clear(cache->arena); cache->table_size = 0; cache->table = 0; d_state->member_cache_reggen_idx = new_reg_gen; } scratch_end(scratch); } ////////////////////////////// //- rjf: eliminate entities that are marked for deletion // ProfScope("eliminate deleted entities") { for(D_Entity *entity = d_entity_root(), *next = 0; !d_entity_is_nil(entity); entity = next) { next = d_entity_rec_depth_first_pre(entity, &d_nil_entity).next; if(entity->flags & D_EntityFlag_MarkedForDeletion) { B32 undoable = (d_entity_kind_flags_table[entity->kind] & D_EntityKindFlag_UserDefinedLifetime); // rjf: fixup next entity to iterate to next = d_entity_rec_depth_first(entity, &d_nil_entity, OffsetOf(D_Entity, next), OffsetOf(D_Entity, next)).next; // rjf: eliminate root entity if we're freeing it if(entity == d_state->entities_root) { d_state->entities_root = &d_nil_entity; } // rjf: unhook & release this entity tree d_entity_change_parent(entity, entity->parent, &d_nil_entity, &d_nil_entity); d_entity_release(entity); } } } ////////////////////////////// //- rjf: garbage collect eliminated thread unwinds // for(U64 slot_idx = 0; slot_idx < d_state->unwind_cache.slots_count; slot_idx += 1) { D_UnwindCacheSlot *slot = &d_state->unwind_cache.slots[slot_idx]; for(D_UnwindCacheNode *n = slot->first, *next = 0; n != 0; n = next) { next = n->next; if(d_entity_is_nil(d_entity_from_handle(n->thread))) { DLLRemove(slot->first, slot->last, n); arena_release(n->arena); SLLStackPush(d_state->unwind_cache.free_node, n); } } } ////////////////////////////// //- rjf: sync with di parsers // ProfScope("sync with di parsers") { Temp scratch = scratch_begin(&arena, 1); DI_EventList events = di_p2u_pop_events(scratch.arena, 0); for(DI_EventNode *n = events.first; n != 0; n = n->next) { DI_Event *event = &n->v; switch(event->kind) { default:{}break; case DI_EventKind_ConversionStarted: { D_Entity *task = d_entity_alloc(d_entity_root(), D_EntityKind_ConversionTask); d_entity_equip_name(task, event->string); }break; case DI_EventKind_ConversionEnded: { D_Entity *task = d_entity_from_name_and_kind(event->string, D_EntityKind_ConversionTask); if(!d_entity_is_nil(task)) { d_entity_mark_for_deletion(task); } }break; } } scratch_end(scratch); } ////////////////////////////// //- rjf: start/stop telemetry captures // ProfScope("start/stop telemetry captures") { if(!ProfIsCapturing() && DEV_telemetry_capture) { ProfBeginCapture("raddbg"); } if(ProfIsCapturing() && !DEV_telemetry_capture) { ProfEndCapture(); } } ////////////////////////////// //- rjf: process messages // ProfScope("process messages") { Temp scratch = scratch_begin(&arena, 1); for(D_MsgNode *n = d_state->msgs.first; n != 0; n = n->next) { temp_end(scratch); D_Msg *msg = &n->v; D_Regs *regs = msg->regs; D_RegsScope { d_regs_copy_contents(scratch.arena, d_regs(), regs); switch(msg->kind) { default:{}break; //- rjf: [low level target operations] target launching case D_MsgKind_LaunchAndRun: case D_MsgKind_LaunchAndStepInto: { // rjf: get list of targets to launch D_EntityList targets = d_entity_list_from_handle_list(scratch.arena, regs->entity_list); // rjf: no targets => assume all active targets if(targets.count == 0) { targets = d_push_active_target_list(scratch.arena); } // rjf: launch if(targets.count != 0) { for(D_EntityNode *n = targets.first; n != 0; n = n->next) { // rjf: extract data from target D_Entity *target = n->entity; String8 name = d_entity_child_from_kind(target, D_EntityKind_Executable)->string; String8 args = d_entity_child_from_kind(target, D_EntityKind_Arguments)->string; String8 path = d_entity_child_from_kind(target, D_EntityKind_WorkingDirectory)->string; String8 entry= d_entity_child_from_kind(target, D_EntityKind_EntryPoint)->string; name = str8_skip_chop_whitespace(name); args = str8_skip_chop_whitespace(args); path = str8_skip_chop_whitespace(path); entry = str8_skip_chop_whitespace(entry); if(path.size == 0) { path = os_get_current_path(scratch.arena); } // rjf: build launch options String8List cmdln_strings = {0}; { str8_list_push(scratch.arena, &cmdln_strings, name); { U64 start_split_idx = 0; B32 quoted = 0; for(U64 idx = 0; idx <= args.size; idx += 1) { U8 byte = idx < args.size ? args.str[idx] : 0; if(byte == '"') { quoted ^= 1; } B32 splitter_found = (!quoted && (byte == 0 || char_is_space(byte))); if(splitter_found) { String8 string = str8_substr(args, r1u64(start_split_idx, idx)); if(string.size > 0) { str8_list_push(scratch.arena, &cmdln_strings, string); } start_split_idx = idx+1; } } } } // rjf: push message to launch { CTRL_Msg msg = {CTRL_MsgKind_Launch}; msg.path = path; msg.cmd_line_string_list = cmdln_strings; msg.env_inherit = 1; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); str8_list_push(scratch.arena, &msg.entry_points, entry); d_push_ctrl_msg(&msg); } } // rjf: run d_ctrl_run(D_RunKind_Run, &d_nil_entity, CTRL_RunFlag_StopOnEntryPoint * (msg->kind == D_MsgKind_LaunchAndStepInto), 0); } // rjf: no targets -> error if(targets.count == 0) { log_user_errorf("No active targets exist; cannot launch."); } }break; //- rjf: [low level target operations] killing case D_MsgKind_Kill: { D_EntityList processes = d_entity_list_from_handle_list(scratch.arena, regs->entity_list); // rjf: no processes => kill everything if(processes.count == 0) { processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); } // rjf: kill processes if(processes.count != 0) { for(D_EntityNode *n = processes.first; n != 0; n = n->next) { D_Entity *process = n->entity; CTRL_Msg msg = {CTRL_MsgKind_Kill}; { msg.exit_code = 1; msg.machine_id = process->ctrl_machine_id; msg.entity = process->ctrl_handle; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); } d_push_ctrl_msg(&msg); } } // rjf: no processes -> error if(processes.count == 0) { log_user_errorf("No attached running processes exist; cannot kill."); } }break; case D_MsgKind_KillAll: { d_msg(D_MsgKind_Kill); }break; //- rjf: [low level target operations] attaching / detaching case D_MsgKind_Attach: { U32 pid = regs->pid; if(pid != 0) { CTRL_Msg msg = {CTRL_MsgKind_Attach}; msg.entity_id = pid; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); d_push_ctrl_msg(&msg); } }break; case D_MsgKind_Detach: { for(D_HandleNode *n = regs->entity_list.first; n != 0; n = n->next) { D_Entity *entity = d_entity_from_handle(n->handle); if(entity->kind == D_EntityKind_Process) { CTRL_Msg msg = {CTRL_MsgKind_Detach}; msg.machine_id = entity->ctrl_machine_id; msg.entity = entity->ctrl_handle; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); d_push_ctrl_msg(&msg); } } }break; //- rjf: [low level target operations] continuing from halted state case D_MsgKind_Continue: { B32 good_to_run = 0; D_EntityList machines = d_query_cached_entity_list_with_kind(D_EntityKind_Machine); for(D_EntityNode *n = machines.first; n != 0; n = n->next) { D_Entity *machine = n->entity; if(!d_entity_is_frozen(machine)) { good_to_run = 1; break; } } if(good_to_run) { d_ctrl_run(D_RunKind_Run, &d_nil_entity, 0, 0); } else { log_user_errorf("Cannot run with all threads frozen."); } }break; //- rjf: [low level target operations] stepping case D_MsgKind_StepIntoInst: case D_MsgKind_StepOverInst: case D_MsgKind_StepIntoLine: case D_MsgKind_StepOverLine: case D_MsgKind_StepOut: { D_Entity *thread = d_entity_from_handle(regs->thread); if(d_ctrl_targets_running()) { if(d_ctrl_last_run_kind() == D_RunKind_Run) { log_user_errorf("Must halt before stepping."); } } else if(d_entity_is_frozen(thread)) { log_user_errorf("Must thaw selected thread before stepping."); } else { B32 good = 1; CTRL_TrapList traps = {0}; switch(msg->kind) { default:{}break; case D_MsgKind_StepIntoInst: {}break; case D_MsgKind_StepOverInst: {traps = d_trap_net_from_thread__step_over_inst(scratch.arena, thread);}break; case D_MsgKind_StepIntoLine: {traps = d_trap_net_from_thread__step_into_line(scratch.arena, thread);}break; case D_MsgKind_StepOverLine: {traps = d_trap_net_from_thread__step_over_line(scratch.arena, thread);}break; case D_MsgKind_StepOut: { // rjf: thread => full unwind CTRL_Unwind unwind = ctrl_unwind_from_thread(scratch.arena, d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle, os_now_microseconds()+10000); // rjf: use first unwind frame to generate trap if(unwind.flags == 0 && unwind.frames.count > 1) { U64 vaddr = regs_rip_from_arch_block(thread->arch, unwind.frames.v[1].regs); CTRL_Trap trap = {CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck, vaddr}; ctrl_trap_list_push(scratch.arena, &traps, &trap); } else { log_user_errorf("Could not find the return address of the current callstack frame successfully."); good = 0; } }break; } if(good && traps.count != 0) { d_ctrl_run(D_RunKind_Step, thread, 0, &traps); } if(good && traps.count == 0) { d_ctrl_run(D_RunKind_SingleStep, thread, 0, &traps); } } }break; //- rjf: [low level target operations] halting case D_MsgKind_Halt: { if(d_ctrl_targets_running()) { ctrl_halt(); } }break; case D_MsgKind_SoftHaltRefresh: { if(d_ctrl_targets_running()) { d_ctrl_run(d_state->ctrl_last_run_kind, d_entity_from_handle(d_state->ctrl_last_run_thread), d_state->ctrl_last_run_flags, &d_state->ctrl_last_run_traps); } }break; //- rjf: [low level target operations] setting thread rip case D_MsgKind_SetThreadIP: { D_Entity *thread = d_entity_from_handle(regs->thread); U64 vaddr = regs->vaddr_range.min; void *block = ctrl_query_cached_reg_block_from_thread(scratch.arena, d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); regs_arch_block_write_rip(thread->arch, block, vaddr); B32 result = ctrl_thread_write_reg_block(thread->ctrl_machine_id, thread->ctrl_handle, block); // rjf: early mutation of unwind cache for immediate frontend effect if(result) { D_UnwindCache *cache = &d_state->unwind_cache; if(cache->slots_count != 0) { D_Handle thread_handle = d_handle_from_entity(thread); U64 hash = d_hash_from_string(str8_struct(&thread_handle)); U64 slot_idx = hash%cache->slots_count; D_UnwindCacheSlot *slot = &cache->slots[slot_idx]; for(D_UnwindCacheNode *n = slot->first; n != 0; n = n->next) { if(d_handle_match(n->thread, thread_handle) && n->unwind.frames.count != 0) { regs_arch_block_write_rip(thread->arch, n->unwind.frames.v[0].regs, vaddr); break; } } } } }break; //- rjf: high level target operations [running] case D_MsgKind_RunToLine: { String8 file_path = regs->file_path; TxtPt pt = regs->cursor; D_Entity *bp = d_entity_alloc(d_entity_root(), D_EntityKind_Breakpoint); d_entity_equip_cfg_src(bp, D_CfgSrc_Transient); bp->flags |= D_EntityFlag_DiesOnRunStop; D_Entity *loc = d_entity_alloc(bp, D_EntityKind_Location); d_entity_equip_name(loc, file_path); d_entity_equip_txt_pt(loc, pt); d_msg(D_MsgKind_Run); }break; case D_MsgKind_RunToAddress: { D_Entity *bp = d_entity_alloc(d_entity_root(), D_EntityKind_Breakpoint); d_entity_equip_cfg_src(bp, D_CfgSrc_Transient); bp->flags |= D_EntityFlag_DiesOnRunStop; D_Entity *loc = d_entity_alloc(bp, D_EntityKind_Location); d_entity_equip_vaddr(loc, regs->vaddr_range.min); d_msg(D_MsgKind_Run); }break; case D_MsgKind_Run: { D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); if(processes.count != 0) { d_msg(D_MsgKind_Continue); } else if(!d_ctrl_targets_running()) { d_msg(D_MsgKind_LaunchAndRun); } }break; case D_MsgKind_Restart: { // rjf: kill all { d_msg(D_MsgKind_KillAll); } // rjf: gather targets corresponding to all launched processes D_EntityList targets = {0}; { D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); for(D_EntityNode *n = processes.first; n != 0; n = n->next) { D_Entity *process = n->entity; D_Entity *target = d_entity_from_handle(process->entity_handle); if(!d_entity_is_nil(target)) { d_entity_list_push(scratch.arena, &targets, target); } } } // rjf: re-launch targets { d_msg(D_MsgKind_LaunchAndRun, .entity_list = d_handle_list_from_entity_list(scratch.arena, targets)); } }break; //- rjf: high level target operations [stepping] case D_MsgKind_StepInto: case D_MsgKind_StepOver: { D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); if(processes.count != 0) { D_MsgKind step_msg_kind = (msg->kind == D_MsgKind_StepInto ? D_MsgKind_StepIntoLine : D_MsgKind_StepOverLine); if(regs->prefer_disasm) { step_msg_kind = (msg->kind == D_MsgKind_StepInto ? D_MsgKind_StepIntoInst : D_MsgKind_StepOverInst); } d_msg(step_msg_kind); } else if(!d_ctrl_targets_running()) { D_EntityList targets = d_push_active_target_list(scratch.arena); d_msg(D_MsgKind_LaunchAndStepInto, .entity_list = d_handle_list_from_entity_list(scratch.arena, targets)); } }break; //- rjf: thread / frame selection case D_MsgKind_SelectThread: { D_Entity *thread = d_entity_from_handle(regs->thread); D_Entity *module = d_module_from_thread(thread); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); d_state->base_regs.v.unwind_count = 0; d_state->base_regs.v.inline_depth = 0; d_state->base_regs.v.thread = d_handle_from_entity(thread); d_state->base_regs.v.module = d_handle_from_entity(module); d_state->base_regs.v.process = d_handle_from_entity(process); }break; case D_MsgKind_SelectUnwind: { DI_Scope *di_scope = di_scope_open(); D_Entity *thread = d_entity_from_handle(d_state->base_regs.v.thread); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); CTRL_Unwind base_unwind = d_query_cached_unwind_from_thread(thread); D_Unwind rich_unwind = d_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind); if(regs->unwind_count < rich_unwind.frames.concrete_frame_count) { D_UnwindFrame *frame = &rich_unwind.frames.v[regs->unwind_count]; d_state->base_regs.v.unwind_count = regs->unwind_count; d_state->base_regs.v.inline_depth = 0; if(regs->inline_depth <= frame->inline_frame_count) { d_state->base_regs.v.inline_depth = regs->inline_depth; } } di_scope_close(di_scope); }break; case D_MsgKind_UpOneFrame: case D_MsgKind_DownOneFrame: { DI_Scope *di_scope = di_scope_open(); D_Entity *thread = d_entity_from_handle(d_state->base_regs.v.thread); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); CTRL_Unwind base_unwind = d_query_cached_unwind_from_thread(thread); D_Unwind rich_unwind = d_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind); U64 crnt_unwind_idx = d_state->base_regs.v.unwind_count; U64 crnt_inline_dpt = d_state->base_regs.v.inline_depth; U64 next_unwind_idx = crnt_unwind_idx; U64 next_inline_dpt = crnt_inline_dpt; if(crnt_unwind_idx < rich_unwind.frames.concrete_frame_count) { D_UnwindFrame *f = &rich_unwind.frames.v[crnt_unwind_idx]; switch(msg->kind) { default:{}break; case D_MsgKind_UpOneFrame: { if(crnt_inline_dpt < f->inline_frame_count) { next_inline_dpt += 1; } else if(crnt_unwind_idx > 0) { next_unwind_idx -= 1; next_inline_dpt = 0; } }break; case D_MsgKind_DownOneFrame: { if(crnt_inline_dpt > 0) { next_inline_dpt -= 1; } else if(crnt_unwind_idx < rich_unwind.frames.concrete_frame_count) { next_unwind_idx += 1; next_inline_dpt = (f+1)->inline_frame_count; } }break; } } d_msg(D_MsgKind_SelectUnwind, .unwind_count = next_unwind_idx, .inline_depth = next_inline_dpt); di_scope_close(di_scope); }break; //- rjf: thread freezing/thawing case D_MsgKind_FreezeThread: {d_msg(D_MsgKind_FreezeEntity, .entity = regs->thread);}break; case D_MsgKind_ThawThread: {d_msg(D_MsgKind_ThawEntity, .entity = regs->thread);}break; case D_MsgKind_FreezeProcess: {d_msg(D_MsgKind_FreezeEntity, .entity = regs->process);}break; case D_MsgKind_ThawProcess: {d_msg(D_MsgKind_ThawEntity, .entity = regs->process);}break; case D_MsgKind_FreezeMachine: {d_msg(D_MsgKind_FreezeEntity, .entity = regs->machine);}break; case D_MsgKind_ThawMachine: {d_msg(D_MsgKind_ThawEntity, .entity = regs->machine);}break; case D_MsgKind_FreezeLocalMachine: { D_Entity *machine = d_machine_entity_from_machine_id(CTRL_MachineID_Local); d_msg(D_MsgKind_FreezeEntity, .entity = d_handle_from_entity(machine)); }break; case D_MsgKind_ThawLocalMachine: { D_Entity *machine = d_machine_entity_from_machine_id(CTRL_MachineID_Local); d_msg(D_MsgKind_ThawEntity, .entity = d_handle_from_entity(machine)); }break; //- rjf: path overrides (TODO(rjf)) case D_MsgKind_SetPathMapSrc:{}break; case D_MsgKind_SetPathMapDst:{}break; case D_MsgKind_SetFileReplacementPath:{}break; //- rjf: auto view rules (TODO(rjf)) case D_MsgKind_SetAutoViewRuleType:{}break; case D_MsgKind_SetAutoViewRuleViewRule:{}break; //- rjf: general entity operations case D_MsgKind_EnableEntity: case D_MsgKind_EnableBreakpoint: case D_MsgKind_EnableTarget: { D_Entity *entity = d_entity_from_handle(regs->entity); d_entity_equip_disabled(entity, 0); }break; case D_MsgKind_DisableEntity: case D_MsgKind_DisableBreakpoint: case D_MsgKind_DisableTarget: { D_Entity *entity = d_entity_from_handle(regs->entity); d_entity_equip_disabled(entity, 1); }break; case D_MsgKind_FreezeEntity: case D_MsgKind_ThawEntity: { B32 should_freeze = (msg->kind == D_MsgKind_FreezeEntity); D_Entity *root = d_entity_from_handle(regs->entity); for(D_Entity *e = root; !d_entity_is_nil(e); e = d_entity_rec_depth_first_pre(e, root).next) { if(e->kind == D_EntityKind_Thread) { d_set_thread_freeze_state(e, should_freeze); } } }break; case D_MsgKind_RemoveEntity: case D_MsgKind_RemoveBreakpoint: case D_MsgKind_RemoveTarget: { D_Entity *entity = d_entity_from_handle(regs->entity); D_EntityKindFlags kind_flags = d_entity_kind_flags_table[entity->kind]; if(kind_flags & D_EntityKindFlag_CanDelete) { d_entity_mark_for_deletion(entity); } }break; case D_MsgKind_NameEntity: { D_Entity *entity = d_entity_from_handle(regs->entity); String8 string = regs->string; d_entity_equip_name(entity, string); }break; case D_MsgKind_DuplicateEntity: { D_Entity *src = d_entity_from_handle(regs->entity); if(!d_entity_is_nil(src)) { typedef struct Task Task; struct Task { Task *next; D_Entity *src_n; D_Entity *dst_parent; }; Task starter_task = {0, src, src->parent}; Task *first_task = &starter_task; Task *last_task = &starter_task; for(Task *task = first_task; task != 0; task = task->next) { D_Entity *src_n = task->src_n; D_Entity *dst_n = d_entity_alloc(task->dst_parent, task->src_n->kind); if(src_n->flags & D_EntityFlag_HasTextPoint) {d_entity_equip_txt_pt(dst_n, src_n->text_point);} if(src_n->flags & D_EntityFlag_HasU64) {d_entity_equip_u64(dst_n, src_n->u64);} if(src_n->flags & D_EntityFlag_HasColor) {d_entity_equip_color_hsva(dst_n, d_hsva_from_entity(src_n));} if(src_n->flags & D_EntityFlag_HasVAddrRng) {d_entity_equip_vaddr_rng(dst_n, src_n->vaddr_rng);} if(src_n->flags & D_EntityFlag_HasVAddr) {d_entity_equip_vaddr(dst_n, src_n->vaddr);} if(src_n->disabled) {d_entity_equip_disabled(dst_n, 1);} if(src_n->string.size != 0) {d_entity_equip_name(dst_n, src_n->string);} dst_n->cfg_src = src_n->cfg_src; for(D_Entity *src_child = task->src_n->first; !d_entity_is_nil(src_child); src_child = src_child->next) { Task *child_task = push_array(scratch.arena, Task, 1); child_task->src_n = src_child; child_task->dst_parent = dst_n; SLLQueuePush(first_task, last_task, child_task); } } } }break; case D_MsgKind_RelocateEntity: { D_Entity *entity = d_entity_from_handle(regs->entity); D_Entity *location = d_entity_child_from_kind(entity, D_EntityKind_Location); if(d_entity_is_nil(location)) { location = d_entity_alloc(entity, D_EntityKind_Location); } location->flags &= ~D_EntityFlag_HasTextPoint; location->flags &= ~D_EntityFlag_HasVAddr; if(regs->cursor.line != 0) { d_entity_equip_txt_pt(location, regs->cursor); } if(regs->vaddr_range.min != 0) { d_entity_equip_vaddr(location, regs->vaddr_range.min); } if(regs->file_path.size != 0) { d_entity_equip_name(location, regs->file_path); } }break; //- rjf: breakpoints case D_MsgKind_AddBreakpoint: case D_MsgKind_ToggleBreakpoint: { String8 file_path = regs->file_path; TxtPt pt = regs->cursor; String8 string = regs->string; U64 vaddr = regs->vaddr_range.min; B32 removed_already_existing = 0; if(msg->kind == D_MsgKind_ToggleBreakpoint) { D_EntityList bps = d_query_cached_entity_list_with_kind(D_EntityKind_Breakpoint); for(D_EntityNode *n = bps.first; n != 0; n = n->next) { D_Entity *bp = n->entity; D_Entity *loc = d_entity_child_from_kind(bp, D_EntityKind_Location); if((loc->flags & D_EntityFlag_HasTextPoint && path_match_normalized(loc->string, file_path) && loc->text_point.line == pt.line) || (loc->flags & D_EntityFlag_HasVAddr && loc->vaddr == vaddr) || (!(loc->flags & D_EntityFlag_HasTextPoint) && str8_match(loc->string, string, 0))) { d_entity_mark_for_deletion(bp); removed_already_existing = 1; break; } } } if(!removed_already_existing) { D_Entity *bp = d_entity_alloc(d_entity_root(), D_EntityKind_Breakpoint); d_entity_equip_cfg_src(bp, D_CfgSrc_Project); D_Entity *loc = d_entity_alloc(bp, D_EntityKind_Location); if(file_path.size != 0 && pt.line != 0) { d_entity_equip_name(loc, file_path); d_entity_equip_txt_pt(loc, pt); } else if(string.size != 0) { d_entity_equip_name(loc, string); } else if(vaddr != 0) { d_entity_equip_vaddr(loc, vaddr); } } }break; case D_MsgKind_AddAddressBreakpoint: case D_MsgKind_AddFunctionBreakpoint: { d_msg(D_MsgKind_AddBreakpoint); }break; //- rjf: watch pins case D_MsgKind_AddWatchPin: case D_MsgKind_ToggleWatchPin: { String8 file_path = regs->file_path; TxtPt pt = regs->cursor; String8 string = regs->string; U64 vaddr = regs->vaddr_range.min; B32 removed_already_existing = 0; if(msg->kind == D_MsgKind_ToggleWatchPin) { D_EntityList wps = d_query_cached_entity_list_with_kind(D_EntityKind_WatchPin); for(D_EntityNode *n = wps.first; n != 0; n = n->next) { D_Entity *wp = n->entity; D_Entity *loc = d_entity_child_from_kind(wp, D_EntityKind_Location); if((loc->flags & D_EntityFlag_HasTextPoint && path_match_normalized(loc->string, file_path) && loc->text_point.line == pt.line) || (loc->flags & D_EntityFlag_HasVAddr && loc->vaddr == vaddr) || (!(loc->flags & D_EntityFlag_HasTextPoint) && str8_match(loc->string, string, 0))) { d_entity_mark_for_deletion(wp); removed_already_existing = 1; break; } } } if(!removed_already_existing) { D_Entity *wp = d_entity_alloc(d_entity_root(), D_EntityKind_WatchPin); d_entity_equip_name(wp, string); d_entity_equip_cfg_src(wp, D_CfgSrc_Project); D_Entity *loc = d_entity_alloc(wp, D_EntityKind_Location); if(file_path.size != 0 && pt.line != 0) { d_entity_equip_name(loc, file_path); d_entity_equip_txt_pt(loc, pt); } else if(vaddr != 0) { d_entity_equip_vaddr(loc, vaddr); } } }break; //- rjf: watch expressions case D_MsgKind_ToggleWatchExpression: if(regs->string.size != 0) { D_Entity *existing_watch = d_entity_from_name_and_kind(regs->string, D_EntityKind_Watch); if(d_entity_is_nil(existing_watch)) { D_Entity *watch = &d_nil_entity; D_StateDeltaHistoryBatch(d_state_delta_history()) { watch = d_entity_alloc(d_entity_root(), D_EntityKind_Watch); } d_entity_equip_cfg_src(watch, D_CfgSrc_Project); d_entity_equip_name(watch, regs->string); } else { d_entity_mark_for_deletion(existing_watch); } }break; //- rjf: at-cursor operations case D_MsgKind_ToggleBreakpointAtCursor:{d_msg(D_MsgKind_ToggleBreakpoint);}break; case D_MsgKind_ToggleWatchPinAtCursor:{d_msg(D_MsgKind_ToggleWatchPin);}break; case D_MsgKind_GoToNameAtCursor: case D_MsgKind_ToggleWatchExpressionAtCursor: { // rjf: get expr String8 expr = {0}; { HS_Scope *hs_scope = hs_scope_open(); TXT_Scope *txt_scope = txt_scope_open(); U128 text_key = regs->text_key; TXT_LangKind lang_kind = regs->lang_kind; TxtRng range = txt_rng(regs->cursor, regs->mark); U128 hash = {0}; TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, text_key, lang_kind, &hash); String8 data = hs_data_from_hash(hs_scope, hash); Rng1U64 expr_off_range = {0}; if(range.min.column != range.max.column) { expr_off_range = r1u64(txt_off_from_info_pt(&info, range.min), txt_off_from_info_pt(&info, range.max)); } else { expr_off_range = txt_expr_off_range_from_info_data_pt(&info, data, range.min); } expr = push_str8_copy(scratch.arena, str8_substr(data, expr_off_range)); txt_scope_close(txt_scope); hs_scope_close(hs_scope); } // rjf: push command for this expr // TODO(rjf): @msgs // d_msg(msg->kind == D_MsgKind_GoToNameAtCursor ? D_MsgKind_GoToName : // msg->kind == D_MsgKind_ToggleWatchExpressionAtCursor ? D_MsgKind_ToggleWatchExpression : // D_MsgKind_GoToName, .string = expr); }break; {}break; case D_MsgKind_RunToCursor: { String8 file_path = regs->file_path; if(file_path.size != 0) { d_msg(D_MsgKind_RunToLine); } else { d_msg(D_MsgKind_RunToAddress); } }break; case D_MsgKind_SetNextStatement: { D_Entity *thread = d_entity_from_handle(regs->thread); String8 file_path = regs->file_path; U64 new_rip_vaddr = regs->vaddr_range.min; if(file_path.size != 0) { D_LineList *lines = ®s->lines; for(D_LineNode *n = lines->first; n != 0; n = n->next) { D_EntityList modules = d_modules_from_dbgi_key(scratch.arena, &n->v.dbgi_key); D_Entity *module = d_module_from_thread_candidates(thread, &modules); if(!d_entity_is_nil(module)) { new_rip_vaddr = d_vaddr_from_voff(module, n->v.voff_range.min); break; } } } d_msg(D_MsgKind_SetThreadIP, .thread = regs->thread, .vaddr_range = r1u64(new_rip_vaddr, 0)); }break; //- rjf: targets case D_MsgKind_AddTarget: { D_Entity *entity = d_entity_alloc(d_entity_root(), D_EntityKind_Target); d_entity_equip_disabled(entity, 1); d_entity_equip_cfg_src(entity, D_CfgSrc_Project); D_Entity *exe = d_entity_alloc(entity, D_EntityKind_Executable); d_entity_equip_name(exe, regs->file_path); String8 working_dir = str8_chop_last_slash(regs->file_path); if(working_dir.size != 0) { String8 working_dir_path = push_str8f(scratch.arena, "%S/", working_dir); D_Entity *execution_path = d_entity_alloc(entity, D_EntityKind_WorkingDirectory); d_entity_equip_name(execution_path, working_dir_path); } d_msg(D_MsgKind_SelectTarget, .entity = d_handle_from_entity(entity)); }break; case D_MsgKind_SelectTarget: { D_Entity *entity = d_entity_from_handle(regs->entity); if(entity->kind == D_EntityKind_Target) { D_EntityList all_targets = d_query_cached_entity_list_with_kind(D_EntityKind_Target); B32 is_selected = !entity->disabled; for(D_EntityNode *n = all_targets.first; n != 0; n = n->next) { D_Entity *target = n->entity; d_entity_equip_disabled(target, 1); } if(!is_selected) { d_entity_equip_disabled(entity, 0); } } }break; case D_MsgKind_RetryEndedProcess: { D_Entity *ended_process = d_entity_from_handle(regs->entity); D_Entity *target = d_entity_from_handle(ended_process->entity_handle); if(target->kind == D_EntityKind_Target) { d_msg(D_MsgKind_LaunchAndRun, .entity = d_handle_from_entity(target)); } else if(d_entity_is_nil(target)) { log_user_errorf("The ended process' corresponding target is missing."); } else if(d_entity_is_nil(ended_process)) { log_user_errorf("Invalid ended process."); } }break; //- rjf: meta case D_MsgKind_RegisterAsJITDebugger: { #if OS_WINDOWS char filename_cstr[MAX_PATH] = {0}; GetModuleFileName(0, filename_cstr, sizeof(filename_cstr)); String8 debugger_binary_path = str8_cstring(filename_cstr); String8 name8 = str8_lit("Debugger"); String8 data8 = push_str8f(scratch.arena, "%S --jit_pid:%%ld --jit_code:%%ld --jit_addr:0x%%p", debugger_binary_path); String16 name16 = str16_from_8(scratch.arena, name8); String16 data16 = str16_from_8(scratch.arena, data8); B32 likely_not_in_admin_mode = 0; { HKEY reg_key = 0; LSTATUS status = 0; status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, ®_key); likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED); status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2); RegCloseKey(reg_key); } { HKEY reg_key = 0; LSTATUS status = 0; status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, ®_key); likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED); status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2); RegCloseKey(reg_key); } if(likely_not_in_admin_mode) { log_user_errorf("Could not register as the just-in-time debugger, access was denied; try running the debugger as administrator."); } #else log_user_errorf("Registering as the just-in-time debugger is currently not supported on this system."); #endif }break; case D_MsgKind_LogMarker: { log_infof("\"#MARKER\""); }break; } } } arena_clear(d_state->msgs_arena); MemoryZeroStruct(&d_state->msgs); scratch_end(scratch); } ////////////////////////////// //- rjf: clear root level commands // { arena_clear(d_state->root_cmd_arena); MemoryZeroStruct(&d_state->root_cmds); } ////////////////////////////// //- rjf: autosave // { d_state->seconds_til_autosave -= dt; if(d_state->seconds_til_autosave <= 0.f) { D_CmdParams params = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_WriteUserData)); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_WriteProjectData)); d_state->seconds_til_autosave = 5.f; } } ////////////////////////////// //- rjf: process top-level commands // ProfScope("process top-level commands") { Temp scratch = scratch_begin(&arena, 1); for(D_CmdNode *cmd_node = cmds->first; cmd_node != 0; cmd_node = cmd_node->next) { temp_end(scratch); // rjf: unpack command D_Cmd *cmd = &cmd_node->cmd; D_CmdParams params = cmd->params; D_CmdKind core_cmd_kind = d_cmd_kind_from_string(cmd->spec->info.string); d_cmd_spec_counter_inc(cmd->spec); // rjf: process command switch(core_cmd_kind) { default:{}break; //- rjf: command fast paths case D_CmdKind_RunCommand: { D_CmdSpec *spec = params.cmd_spec; if(spec != cmd->spec && !d_cmd_spec_is_nil(spec)) { d_cmd_spec_counter_inc(spec); if(!(spec->info.query.flags & D_CmdQueryFlag_Required)) { d_cmd_list_push(arena, cmds, ¶ms, spec); } } }break; //- rjf: low-level target control operations case D_CmdKind_LaunchAndRun: case D_CmdKind_LaunchAndInit: { // rjf: get list of targets to launch D_EntityList targets = d_entity_list_from_handle_list(scratch.arena, params.entity_list); // rjf: no targets => assume all active targets if(targets.count == 0) { targets = d_push_active_target_list(scratch.arena); } // rjf: launch if(targets.count != 0) { for(D_EntityNode *n = targets.first; n != 0; n = n->next) { // rjf: extract data from target D_Entity *target = n->entity; String8 name = d_entity_child_from_kind(target, D_EntityKind_Executable)->string; String8 args = d_entity_child_from_kind(target, D_EntityKind_Arguments)->string; String8 path = d_entity_child_from_kind(target, D_EntityKind_WorkingDirectory)->string; String8 entry= d_entity_child_from_kind(target, D_EntityKind_EntryPoint)->string; name = str8_skip_chop_whitespace(name); args = str8_skip_chop_whitespace(args); path = str8_skip_chop_whitespace(path); entry = str8_skip_chop_whitespace(entry); if(path.size == 0) { path = os_get_current_path(scratch.arena); } // rjf: build launch options String8List cmdln_strings = {0}; { str8_list_push(scratch.arena, &cmdln_strings, name); { U64 start_split_idx = 0; B32 quoted = 0; for(U64 idx = 0; idx <= args.size; idx += 1) { U8 byte = idx < args.size ? args.str[idx] : 0; if(byte == '"') { quoted ^= 1; } B32 splitter_found = (!quoted && (byte == 0 || char_is_space(byte))); if(splitter_found) { String8 string = str8_substr(args, r1u64(start_split_idx, idx)); if(string.size > 0) { str8_list_push(scratch.arena, &cmdln_strings, string); } start_split_idx = idx+1; } } } } // rjf: push message to launch { CTRL_Msg msg = {CTRL_MsgKind_Launch}; msg.path = path; msg.cmd_line_string_list = cmdln_strings; msg.env_inherit = 1; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); str8_list_push(scratch.arena, &msg.entry_points, entry); d_push_ctrl_msg(&msg); } } // rjf: run d_ctrl_run(D_RunKind_Run, &d_nil_entity, CTRL_RunFlag_StopOnEntryPoint * (core_cmd_kind == D_CmdKind_LaunchAndInit), 0); } // rjf: no targets -> error if(targets.count == 0) { D_CmdParams p = params; p.string = str8_lit("No active targets exist; cannot launch."); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Error)); } }break; case D_CmdKind_Kill: { D_EntityList processes = d_entity_list_from_handle_list(scratch.arena, params.entity_list); // rjf: no processes => kill everything if(processes.count == 0) { processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); } // rjf: kill processes if(processes.count != 0) { for(D_EntityNode *n = processes.first; n != 0; n = n->next) { D_Entity *process = n->entity; CTRL_Msg msg = {CTRL_MsgKind_Kill}; { msg.exit_code = 1; msg.machine_id = process->ctrl_machine_id; msg.entity = process->ctrl_handle; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); } d_push_ctrl_msg(&msg); } } // rjf: no processes -> error if(processes.count == 0) { D_CmdParams p = params; p.string = str8_lit("No attached running processes exist; cannot kill."); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Error)); } }break; case D_CmdKind_KillAll: { D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); if(processes.count != 0) { for(D_EntityNode *n = processes.first; n != 0; n = n->next) { D_Entity *process = n->entity; CTRL_Msg msg = {CTRL_MsgKind_Kill}; { msg.exit_code = 1; msg.machine_id = process->ctrl_machine_id; msg.entity = process->ctrl_handle; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); } d_push_ctrl_msg(&msg); } } if(processes.count == 0) { D_CmdParams p = params; p.string = str8_lit("No attached running processes exist; cannot kill."); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Error)); } }break; case D_CmdKind_Detach: { for(D_HandleNode *n = params.entity_list.first; n != 0; n = n->next) { D_Entity *entity = d_entity_from_handle(n->handle); if(entity->kind == D_EntityKind_Process) { CTRL_Msg msg = {CTRL_MsgKind_Detach}; msg.machine_id = entity->ctrl_machine_id; msg.entity = entity->ctrl_handle; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); d_push_ctrl_msg(&msg); } } }break; case D_CmdKind_Continue: { B32 good_to_run = 0; D_EntityList machines = d_query_cached_entity_list_with_kind(D_EntityKind_Machine); for(D_EntityNode *n = machines.first; n != 0; n = n->next) { D_Entity *machine = n->entity; if(!d_entity_is_frozen(machine)) { good_to_run = 1; break; } } if(good_to_run) { d_ctrl_run(D_RunKind_Run, &d_nil_entity, 0, 0); } else { D_CmdParams p = params; p.string = str8_lit("Cannot run with all threads frozen."); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Error)); } }break; case D_CmdKind_StepIntoInst: case D_CmdKind_StepOverInst: case D_CmdKind_StepIntoLine: case D_CmdKind_StepOverLine: case D_CmdKind_StepOut: { D_Entity *thread = d_entity_from_handle(params.entity); if(d_ctrl_targets_running()) { if(d_ctrl_last_run_kind() == D_RunKind_Run) { D_CmdParams p = params; p.string = str8_lit("Must halt before stepping."); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Error)); } } else if(d_entity_is_frozen(thread)) { D_CmdParams p = params; p.string = str8_lit("Must thaw selected thread before stepping."); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Error)); } else { B32 good = 1; CTRL_TrapList traps = {0}; switch(core_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_StepOut: { // rjf: thread => full unwind CTRL_Unwind unwind = ctrl_unwind_from_thread(scratch.arena, d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle, os_now_microseconds()+10000); // rjf: use first unwind frame to generate trap if(unwind.flags == 0 && unwind.frames.count > 1) { U64 vaddr = regs_rip_from_arch_block(thread->arch, unwind.frames.v[1].regs); CTRL_Trap trap = {CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck, vaddr}; ctrl_trap_list_push(scratch.arena, &traps, &trap); } else { D_CmdParams p = params; p.string = str8_lit("Could not find the return address of the current callstack frame successfully."); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Error)); good = 0; } }break; } if(good && traps.count != 0) { d_ctrl_run(D_RunKind_Step, thread, 0, &traps); } if(good && traps.count == 0) { d_ctrl_run(D_RunKind_SingleStep, thread, 0, &traps); } } }break; case D_CmdKind_Halt: if(d_ctrl_targets_running()) { ctrl_halt(); }break; case D_CmdKind_SoftHaltRefresh: { if(d_ctrl_targets_running()) { d_ctrl_run(d_state->ctrl_last_run_kind, d_entity_from_handle(d_state->ctrl_last_run_thread), d_state->ctrl_last_run_flags, &d_state->ctrl_last_run_traps); } }break; case D_CmdKind_SetThreadIP: { D_Entity *thread = d_entity_from_handle(params.entity); U64 vaddr = params.vaddr; if(thread->kind == D_EntityKind_Thread && vaddr != 0) { d_set_thread_rip(thread, vaddr); } }break; //- rjf: high-level composite target control operations case D_CmdKind_RunToLine: { String8 file_path = params.file_path; TxtPt point = params.text_point; D_Entity *bp = d_entity_alloc(d_entity_root(), D_EntityKind_Breakpoint); d_entity_equip_cfg_src(bp, D_CfgSrc_Transient); bp->flags |= D_EntityFlag_DiesOnRunStop; D_Entity *loc = d_entity_alloc(bp, D_EntityKind_Location); d_entity_equip_name(loc, file_path); d_entity_equip_txt_pt(loc, point); D_CmdParams p = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Run)); }break; case D_CmdKind_RunToAddress: { D_Entity *bp = d_entity_alloc(d_entity_root(), D_EntityKind_Breakpoint); d_entity_equip_cfg_src(bp, D_CfgSrc_Transient); bp->flags |= D_EntityFlag_DiesOnRunStop; D_Entity *loc = d_entity_alloc(bp, D_EntityKind_Location); d_entity_equip_vaddr(loc, params.vaddr); D_CmdParams p = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Run)); }break; case D_CmdKind_Run: { D_CmdParams params = d_cmd_params_zero(); D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); if(processes.count != 0) { D_CmdParams params = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_Continue)); } else if(!d_ctrl_targets_running()) { D_CmdParams params = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_LaunchAndRun)); } }break; case D_CmdKind_Restart: { // rjf: kill all { D_CmdParams params = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_KillAll)); } // rjf: gather targets corresponding to all launched processes D_EntityList targets = {0}; { D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); for(D_EntityNode *n = processes.first; n != 0; n = n->next) { D_Entity *process = n->entity; D_Entity *target = d_entity_from_handle(process->entity_handle); if(!d_entity_is_nil(target)) { d_entity_list_push(scratch.arena, &targets, target); } } } // rjf: re-launch targets { D_CmdParams params = d_cmd_params_zero(); params.entity_list = d_handle_list_from_entity_list(scratch.arena, targets); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_LaunchAndRun)); } }break; case D_CmdKind_StepInto: case D_CmdKind_StepOver: { D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process); if(processes.count != 0) { D_CmdKind step_cmd_kind = (core_cmd_kind == D_CmdKind_StepInto ? D_CmdKind_StepIntoLine : D_CmdKind_StepOverLine); B32 prefer_dasm = params.prefer_dasm; if(prefer_dasm) { step_cmd_kind = (core_cmd_kind == D_CmdKind_StepInto ? D_CmdKind_StepIntoInst : D_CmdKind_StepOverInst); } d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(step_cmd_kind)); } else if(!d_ctrl_targets_running()) { D_EntityList targets = d_push_active_target_list(scratch.arena); D_CmdParams p = params; p.entity_list = d_handle_list_from_entity_list(scratch.arena, targets); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_LaunchAndInit)); } }break; //- rjf: debug control context management operations case D_CmdKind_SelectThread: { D_Entity *thread = d_entity_from_handle(params.entity); D_Entity *module = d_module_from_thread(thread); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); d_state->base_regs.v.unwind_count = 0; d_state->base_regs.v.inline_depth = 0; d_state->base_regs.v.thread = d_handle_from_entity(thread); d_state->base_regs.v.module = d_handle_from_entity(module); d_state->base_regs.v.process = d_handle_from_entity(process); }break; case D_CmdKind_SelectUnwind: { DI_Scope *di_scope = di_scope_open(); D_Entity *thread = d_entity_from_handle(d_state->base_regs.v.thread); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); CTRL_Unwind base_unwind = d_query_cached_unwind_from_thread(thread); D_Unwind rich_unwind = d_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind); if(params.unwind_index < rich_unwind.frames.concrete_frame_count) { D_UnwindFrame *frame = &rich_unwind.frames.v[params.unwind_index]; d_state->base_regs.v.unwind_count = params.unwind_index; d_state->base_regs.v.inline_depth = 0; if(params.inline_depth <= frame->inline_frame_count) { d_state->base_regs.v.inline_depth = params.inline_depth; } } di_scope_close(di_scope); }break; case D_CmdKind_UpOneFrame: case D_CmdKind_DownOneFrame: { DI_Scope *di_scope = di_scope_open(); D_Entity *thread = d_entity_from_handle(d_state->base_regs.v.thread); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); CTRL_Unwind base_unwind = d_query_cached_unwind_from_thread(thread); D_Unwind rich_unwind = d_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind); U64 crnt_unwind_idx = d_state->base_regs.v.unwind_count; U64 crnt_inline_dpt = d_state->base_regs.v.inline_depth; U64 next_unwind_idx = crnt_unwind_idx; U64 next_inline_dpt = crnt_inline_dpt; if(crnt_unwind_idx < rich_unwind.frames.concrete_frame_count) { D_UnwindFrame *f = &rich_unwind.frames.v[crnt_unwind_idx]; switch(core_cmd_kind) { default:{}break; case D_CmdKind_UpOneFrame: { if(crnt_inline_dpt < f->inline_frame_count) { next_inline_dpt += 1; } else if(crnt_unwind_idx > 0) { next_unwind_idx -= 1; next_inline_dpt = 0; } }break; case D_CmdKind_DownOneFrame: { if(crnt_inline_dpt > 0) { next_inline_dpt -= 1; } else if(crnt_unwind_idx < rich_unwind.frames.concrete_frame_count) { next_unwind_idx += 1; next_inline_dpt = (f+1)->inline_frame_count; } }break; } } D_CmdParams p = params; p.unwind_index = next_unwind_idx; p.inline_depth = next_inline_dpt; d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_SelectUnwind)); di_scope_close(di_scope); }break; case D_CmdKind_FreezeThread: case D_CmdKind_ThawThread: case D_CmdKind_FreezeProcess: case D_CmdKind_ThawProcess: case D_CmdKind_FreezeMachine: case D_CmdKind_ThawMachine: { D_CmdKind disptch_kind = ((core_cmd_kind == D_CmdKind_FreezeThread || core_cmd_kind == D_CmdKind_FreezeProcess || core_cmd_kind == D_CmdKind_FreezeMachine) ? D_CmdKind_FreezeEntity : D_CmdKind_ThawEntity); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(disptch_kind)); }break; case D_CmdKind_FreezeLocalMachine: { CTRL_MachineID machine_id = CTRL_MachineID_Local; D_CmdParams params = d_cmd_params_zero(); params.entity = d_handle_from_entity(d_machine_entity_from_machine_id(machine_id)); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_FreezeMachine)); }break; case D_CmdKind_ThawLocalMachine: { CTRL_MachineID machine_id = CTRL_MachineID_Local; D_CmdParams params = d_cmd_params_zero(); params.entity = d_handle_from_entity(d_machine_entity_from_machine_id(machine_id)); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_ThawMachine)); }break; //- rjf: undo/redo case D_CmdKind_Undo: { d_state_delta_history_wind(d_state->hist, Side_Min); }break; case D_CmdKind_Redo: { d_state_delta_history_wind(d_state->hist, Side_Max); }break; //- rjf: files case D_CmdKind_SetCurrentPath: { arena_clear(d_state->current_path_arena); d_state->current_path = push_str8_copy(d_state->current_path_arena, params.file_path); }break; //- rjf: config path saving/loading/applying case D_CmdKind_OpenRecentProject: { D_Entity *entity = d_entity_from_handle(params.entity); if(entity->kind == D_EntityKind_RecentProject) { D_CmdParams p = d_cmd_params_zero(); p.file_path = entity->string; d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_OpenProject)); } }break; case D_CmdKind_OpenUser: case D_CmdKind_OpenProject: { B32 load_cfg[D_CfgSrc_COUNT] = {0}; for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { load_cfg[src] = (core_cmd_kind == d_cfg_src_load_cmd_kind_table[src]); } //- rjf: normalize path String8 new_path = path_normalized_from_string(scratch.arena, params.file_path); //- rjf: path -> data FileProperties props = {0}; String8 data = {0}; { OS_Handle file = os_file_open(OS_AccessFlag_ShareRead|OS_AccessFlag_Read, new_path); props = os_properties_from_file(file); data = os_string_from_file_range(scratch.arena, file, r1u64(0, props.size)); os_file_close(file); } //- rjf: investigate file path/data B32 file_is_okay = 1; if(props.modified != 0 && data.size != 0 && !str8_match(str8_prefix(data, 9), str8_lit("// raddbg"), 0)) { file_is_okay = 0; } //- rjf: set new config paths if(file_is_okay) { for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { if(load_cfg[src]) { arena_clear(d_state->cfg_path_arenas[src]); d_state->cfg_paths[src] = push_str8_copy(d_state->cfg_path_arenas[src], new_path); } } } //- rjf: get config file properties FileProperties cfg_props[D_CfgSrc_COUNT] = {0}; if(file_is_okay) { for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { String8 path = d_cfg_path_from_src(src); cfg_props[src] = os_properties_from_file_path(path); } } //- rjf: load files String8 cfg_data[D_CfgSrc_COUNT] = {0}; U64 cfg_timestamps[D_CfgSrc_COUNT] = {0}; if(file_is_okay) { for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { String8 path = d_cfg_path_from_src(src); OS_Handle file = os_file_open(OS_AccessFlag_ShareRead|OS_AccessFlag_Read, path); FileProperties props = os_properties_from_file(file); String8 data = os_string_from_file_range(scratch.arena, file, r1u64(0, props.size)); if(data.size != 0) { cfg_data[src] = data; cfg_timestamps[src] = props.modified; } os_file_close(file); } } //- rjf: determine if we need to save config B32 cfg_save[D_CfgSrc_COUNT] = {0}; if(file_is_okay) { for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { cfg_save[src] = (load_cfg[src] && cfg_props[src].created == 0); } } //- rjf: determine if we need to reload config B32 cfg_load[D_CfgSrc_COUNT] = {0}; B32 cfg_load_any = 0; if(file_is_okay) { for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { cfg_load[src] = (load_cfg[src] && ((cfg_save[src] == 0 && d_state->cfg_cached_timestamp[src] != cfg_timestamps[src]) || cfg_props[src].created == 0)); cfg_load_any = cfg_load_any || cfg_load[src]; } } //- rjf: load => build new config table if(cfg_load_any) { arena_clear(d_state->cfg_arena); MemoryZeroStruct(&d_state->cfg_table); for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { d_cfg_table_push_unparsed_string(d_state->cfg_arena, &d_state->cfg_table, cfg_data[src], src); } } //- rjf: load => dispatch apply // // NOTE(rjf): must happen before `save`. we need to create a default before saving, which // occurs in the 'apply' path. // if(file_is_okay) { for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { if(cfg_load[src]) { D_CmdKind cmd_kind = d_cfg_src_apply_cmd_kind_table[src]; D_CmdParams params = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(cmd_kind)); d_state->cfg_cached_timestamp[src] = cfg_timestamps[src]; } } } //- rjf: save => dispatch write if(file_is_okay) { for(D_CfgSrc src = (D_CfgSrc)0; src < D_CfgSrc_COUNT; src = (D_CfgSrc)(src+1)) { if(cfg_save[src]) { D_CmdKind cmd_kind = d_cfg_src_write_cmd_kind_table[src]; D_CmdParams params = d_cmd_params_zero(); d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(cmd_kind)); } } } //- rjf: bad file -> alert user if(!file_is_okay) { D_CmdParams p = params; p.string = push_str8f(scratch.arena, "\"%S\" appears to refer to an existing file which is not a RADDBG config file. This would overwrite the file.", new_path); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_Error)); } }break; //- rjf: loading/applying stateful config changes case D_CmdKind_ApplyUserData: case D_CmdKind_ApplyProjectData: { D_CfgTable *table = d_cfg_table(); //- rjf: get config source D_CfgSrc src = D_CfgSrc_User; for(D_CfgSrc s = (D_CfgSrc)0; s < D_CfgSrc_COUNT; s = (D_CfgSrc)(s+1)) { if(core_cmd_kind == d_cfg_src_apply_cmd_kind_table[s]) { src = s; break; } } //- rjf: get paths String8 cfg_path = d_cfg_path_from_src(src); String8 cfg_folder = str8_chop_last_slash(cfg_path); //- rjf: keep track of recent projects if(src == D_CfgSrc_Project) { D_EntityList recent_projects = d_query_cached_entity_list_with_kind(D_EntityKind_RecentProject); D_Entity *recent_project = &d_nil_entity; for(D_EntityNode *n = recent_projects.first; n != 0; n = n->next) { if(path_match_normalized(cfg_path, n->entity->string)) { recent_project = n->entity; break; } } if(d_entity_is_nil(recent_project)) { recent_project = d_entity_alloc(d_entity_root(), D_EntityKind_RecentProject); d_entity_equip_name(recent_project, path_normalized_from_string(scratch.arena, cfg_path)); d_entity_equip_cfg_src(recent_project, D_CfgSrc_User); } } //- rjf: eliminate all existing entities which are derived from config { for(EachEnumVal(D_EntityKind, k)) { D_EntityKindFlags k_flags = d_entity_kind_flags_table[k]; if(k_flags & D_EntityKindFlag_IsSerializedToConfig) { D_EntityList entities = d_query_cached_entity_list_with_kind(k); for(D_EntityNode *n = entities.first; n != 0; n = n->next) { if(n->entity->cfg_src == src) { d_entity_mark_for_deletion(n->entity); } } } } } //- rjf: apply all entities { for(EachEnumVal(D_EntityKind, k)) { D_EntityKindFlags k_flags = d_entity_kind_flags_table[k]; if(k_flags & D_EntityKindFlag_IsSerializedToConfig) { D_CfgVal *k_val = d_cfg_val_from_string(table, d_entity_kind_name_lower_table[k]); for(D_CfgTree *k_tree = k_val->first; k_tree != &d_nil_cfg_tree; k_tree = k_tree->next) { if(k_tree->source != src) { continue; } D_Entity *entity = d_entity_alloc(d_entity_root(), k); d_entity_equip_cfg_src(entity, k_tree->source); // rjf: iterate config tree typedef struct Task Task; struct Task { Task *next; D_Entity *entity; MD_Node *n; }; Task start_task = {0, entity, k_tree->root}; Task *first_task = &start_task; Task *last_task = first_task; for(Task *t = first_task; t != 0; t = t->next) { MD_Node *node = t->n; for(MD_EachNode(child, node->first)) { // rjf: standalone string literals under an entity -> name if(child->flags & MD_NodeFlag_StringLiteral && child->first == &md_nil_node) { String8 string = d_cfg_raw_from_escaped_string(scratch.arena, child->string); if(d_entity_kind_flags_table[t->entity->kind] & D_EntityKindFlag_NameIsPath) { string = path_absolute_dst_from_relative_dst_src(scratch.arena, string, cfg_folder); } d_entity_equip_name(t->entity, string); } // rjf: standalone string literals under an entity, with a numeric child -> name & text location if(child->flags & MD_NodeFlag_StringLiteral && child->first->flags & MD_NodeFlag_Numeric && child->first->first == &md_nil_node) { String8 string = d_cfg_raw_from_escaped_string(scratch.arena, child->string); if(d_entity_kind_flags_table[t->entity->kind] & D_EntityKindFlag_NameIsPath) { string = path_absolute_dst_from_relative_dst_src(scratch.arena, string, cfg_folder); } d_entity_equip_name(t->entity, string); S64 line = 0; try_s64_from_str8_c_rules(child->first->string, &line); TxtPt pt = txt_pt(line, 1); d_entity_equip_txt_pt(t->entity, pt); } // rjf: standalone hex literals under an entity -> vaddr if(child->flags & MD_NodeFlag_Numeric && child->first == &md_nil_node && str8_match(str8_substr(child->string, r1u64(0, 2)), str8_lit("0x"), 0)) { U64 vaddr = 0; try_u64_from_str8_c_rules(child->string, &vaddr); d_entity_equip_vaddr(t->entity, vaddr); } // rjf: specifically named entity equipment if((str8_match(child->string, str8_lit("name"), StringMatchFlag_CaseInsensitive) || str8_match(child->string, str8_lit("label"), StringMatchFlag_CaseInsensitive)) && child->first != &md_nil_node) { String8 string = d_cfg_raw_from_escaped_string(scratch.arena, child->first->string); if(d_entity_kind_flags_table[t->entity->kind] & D_EntityKindFlag_NameIsPath) { string = path_absolute_dst_from_relative_dst_src(scratch.arena, string, cfg_folder); } d_entity_equip_name(t->entity, string); } if((str8_match(child->string, str8_lit("active"), StringMatchFlag_CaseInsensitive) || str8_match(child->string, str8_lit("enabled"), StringMatchFlag_CaseInsensitive)) && child->first != &md_nil_node) { d_entity_equip_disabled(t->entity, !str8_match(child->first->string, str8_lit("1"), 0)); } if(str8_match(child->string, str8_lit("disabled"), StringMatchFlag_CaseInsensitive) && child->first != &md_nil_node) { d_entity_equip_disabled(t->entity, str8_match(child->first->string, str8_lit("1"), 0)); } if(str8_match(child->string, str8_lit("hsva"), StringMatchFlag_CaseInsensitive) && child->first != &md_nil_node) { Vec4F32 hsva = {0}; hsva.x = (F32)f64_from_str8(child->first->string); hsva.y = (F32)f64_from_str8(child->first->next->string); hsva.z = (F32)f64_from_str8(child->first->next->next->string); hsva.w = (F32)f64_from_str8(child->first->next->next->next->string); d_entity_equip_color_hsva(t->entity, hsva); } if(str8_match(child->string, str8_lit("color"), StringMatchFlag_CaseInsensitive) && child->first != &md_nil_node) { Vec4F32 rgba = rgba_from_hex_string_4f32(child->first->string); Vec4F32 hsva = hsva_from_rgba(rgba); d_entity_equip_color_hsva(t->entity, hsva); } if(str8_match(child->string, str8_lit("line"), StringMatchFlag_CaseInsensitive) && child->first != &md_nil_node) { S64 line = 0; try_s64_from_str8_c_rules(child->first->string, &line); TxtPt pt = txt_pt(line, 1); d_entity_equip_txt_pt(t->entity, pt); } if((str8_match(child->string, str8_lit("vaddr"), StringMatchFlag_CaseInsensitive) || str8_match(child->string, str8_lit("addr"), StringMatchFlag_CaseInsensitive)) && child->first != &md_nil_node) { U64 vaddr = 0; try_u64_from_str8_c_rules(child->first->string, &vaddr); d_entity_equip_vaddr(t->entity, vaddr); } // rjf: sub-entity -> create new task D_EntityKind sub_entity_kind = D_EntityKind_Nil; for(EachEnumVal(D_EntityKind, k2)) { if(child->flags & MD_NodeFlag_Identifier && child->first != &md_nil_node && (str8_match(child->string, d_entity_kind_name_lower_table[k2], StringMatchFlag_CaseInsensitive) || (k2 == D_EntityKind_Executable && str8_match(child->string, str8_lit("exe"), StringMatchFlag_CaseInsensitive)))) { Task *task = push_array(scratch.arena, Task, 1); task->next = t->next; task->entity = d_entity_alloc(t->entity, k2); task->n = child; t->next = task; break; } } } } } } } } //- rjf: apply exception code filters D_CfgVal *filter_tables = d_cfg_val_from_string(table, str8_lit("exception_code_filters")); for(D_CfgTree *table = filter_tables->first; table != &d_nil_cfg_tree; table = table->next) { for(MD_EachNode(rule, table->root->first)) { String8 name = rule->string; String8 val_string = rule->first->string; U64 val = 0; if(try_u64_from_str8_c_rules(val_string, &val)) { CTRL_ExceptionCodeKind kind = CTRL_ExceptionCodeKind_Null; for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1); k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) { if(str8_match(name, ctrl_exception_code_kind_lowercase_code_string_table[k], 0)) { kind = k; break; } } if(kind != CTRL_ExceptionCodeKind_Null) { if(val) { d_state->ctrl_exception_code_filters[kind/64] |= (1ull<<(kind%64)); } else { d_state->ctrl_exception_code_filters[kind/64] &= ~(1ull<<(kind%64)); } } } } } }break; //- rjf: writing config changes case D_CmdKind_WriteUserData: case D_CmdKind_WriteProjectData: { D_CfgSrc src = D_CfgSrc_User; for(D_CfgSrc s = (D_CfgSrc)0; s < D_CfgSrc_COUNT; s = (D_CfgSrc)(s+1)) { if(core_cmd_kind == d_cfg_src_write_cmd_kind_table[s]) { src = s; break; } } arena_clear(d_state->cfg_write_arenas[src]); MemoryZeroStruct(&d_state->cfg_write_data[src]); String8 path = d_cfg_path_from_src(src); String8List strs = d_cfg_strings_from_core(scratch.arena, path, src); String8 header = push_str8f(scratch.arena, "// raddbg %s file\n\n", d_cfg_src_string_table[src].str); str8_list_push_front(scratch.arena, &strs, header); String8 data = str8_list_join(scratch.arena, &strs, 0); String8 data_indented = indented_from_string(scratch.arena, data); d_state->cfg_write_issued[src] = 1; d_cfg_push_write_string(src, data_indented); }break; //- rjf: override file links case D_CmdKind_SetFileOverrideLinkSrc: case D_CmdKind_SetFileOverrideLinkDst: { // rjf: unpack args D_Entity *map = d_entity_from_handle(params.entity); String8 path = path_normalized_from_string(scratch.arena, params.file_path); String8 path_folder = str8_chop_last_slash(path); String8 path_file = str8_skip_last_slash(path); // rjf: src -> move map & commit name; dst -> open destination file & refer to it in map switch(core_cmd_kind) { default:{}break; case D_CmdKind_SetFileOverrideLinkSrc: { D_Entity *map_parent = (params.file_path.size != 0) ? d_entity_from_path(path_folder, D_EntityFromPathFlag_OpenAsNeeded|D_EntityFromPathFlag_OpenMissing) : d_entity_root(); if(d_entity_is_nil(map)) { map = d_entity_alloc(map_parent, D_EntityKind_FilePathMap); } else { d_entity_change_parent(map, map->parent, map_parent, &d_nil_entity); } d_entity_equip_name(map, path_file); }break; case D_CmdKind_SetFileOverrideLinkDst: { if(d_entity_is_nil(map)) { map = d_entity_alloc(d_entity_root(), D_EntityKind_FilePathMap); } D_Entity *map_dst_entity = &d_nil_entity; if(params.file_path.size != 0) { map_dst_entity = d_entity_from_path(path, D_EntityFromPathFlag_All); } d_entity_equip_entity_handle(map, d_handle_from_entity(map_dst_entity)); }break; } // rjf: empty src/dest -> delete if(!d_entity_is_nil(map) && map->string.size == 0 && d_entity_is_nil(d_entity_from_handle(map->entity_handle))) { d_entity_mark_for_deletion(map); } }break; case D_CmdKind_SetFileReplacementPath: { // NOTE(rjf): // // C:/foo/bar/baz.c // D:/foo/bar/baz.c // -> override C: -> D: // // C:/1/2/foo/bar.c // C:/2/3/foo/bar.c // -> override C:/1/2 -> C:2/3 // // C:/foo/bar/baz.c // D:/1/2/3.c // -> override C:/foo/bar/baz.c -> D:/1/2/3.c //- rjf: unpack String8 src_path = params.string; String8 dst_path = params.file_path; #if 0 // TODO(rjf): //- rjf: grab src file & chosen replacement D_Entity *file = d_entity_from_handle(params.entity); D_Entity *replacement = d_entity_from_path(params.file_path, D_EntityFromPathFlag_OpenAsNeeded|D_EntityFromPathFlag_OpenMissing); //- rjf: find D_Entity *first_diff_src = file; D_Entity *first_diff_dst = replacement; for(;!d_entity_is_nil(first_diff_src) && !d_entity_is_nil(first_diff_dst);) { if(!str8_match(first_diff_src->string, first_diff_dst->string, StringMatchFlag_CaseInsensitive) || first_diff_src->parent->kind != D_EntityKind_File || first_diff_src->parent->parent->kind != D_EntityKind_File || first_diff_dst->parent->kind != D_EntityKind_File || first_diff_dst->parent->parent->kind != D_EntityKind_File) { break; } first_diff_src = first_diff_src->parent; first_diff_dst = first_diff_dst->parent; } //- rjf: override first different if(!d_entity_is_nil(first_diff_src) && !d_entity_is_nil(first_diff_dst)) { D_Entity *link = d_entity_child_from_string_and_kind(first_diff_src->parent, first_diff_src->name, D_EntityKind_FilePathMap); if(d_entity_is_nil(link)) { link = d_entity_alloc(first_diff_src->parent, D_EntityKind_FilePathMap); d_entity_equip_name(link, first_diff_src->name); } d_entity_equip_entity_handle(link, d_handle_from_entity(first_diff_dst)); } #endif }break; //- rjf: auto view rules case D_CmdKind_SetAutoViewRuleType: case D_CmdKind_SetAutoViewRuleViewRule: { D_Entity *map = d_entity_from_handle(params.entity); if(d_entity_is_nil(map)) { map = d_entity_alloc(d_entity_root(), D_EntityKind_AutoViewRule); d_entity_equip_cfg_src(map, D_CfgSrc_Project); } D_Entity *src = d_entity_child_from_kind(map, D_EntityKind_Source); if(d_entity_is_nil(src)) { src = d_entity_alloc(map, D_EntityKind_Source); } D_Entity *dst = d_entity_child_from_kind(map, D_EntityKind_Dest); if(d_entity_is_nil(dst)) { dst = d_entity_alloc(map, D_EntityKind_Dest); } if(map->kind == D_EntityKind_AutoViewRule) { D_Entity *edit_child = (core_cmd_kind == D_CmdKind_SetAutoViewRuleType ? src : dst); d_entity_equip_name(edit_child, params.string); } if(src->string.size == 0 && dst->string.size == 0) { d_entity_mark_for_deletion(map); } { D_AutoViewRuleMapCache *cache = &d_state->auto_view_rule_cache; if(cache->arena == 0) { cache->arena = arena_alloc(); } arena_clear(cache->arena); cache->slots_count = 1024; cache->slots = push_array(cache->arena, D_AutoViewRuleSlot, cache->slots_count); D_EntityList maps = d_query_cached_entity_list_with_kind(D_EntityKind_AutoViewRule); for(D_EntityNode *n = maps.first; n != 0; n = n->next) { D_Entity *map = n->entity; D_Entity *src = d_entity_child_from_kind(map, D_EntityKind_Source); D_Entity *dst = d_entity_child_from_kind(map, D_EntityKind_Dest); String8 type = src->string; String8 view_rule = dst->string; U64 hash = d_hash_from_string(type); U64 slot_idx = hash%cache->slots_count; D_AutoViewRuleSlot *slot = &cache->slots[slot_idx]; D_AutoViewRuleNode *node = push_array(cache->arena, D_AutoViewRuleNode, 1); node->type = push_str8_copy(cache->arena, type); node->view_rule = push_str8_copy(cache->arena, view_rule); SLLQueuePush(slot->first, slot->last, node); } } }break; //- rjf: general entity operations case D_CmdKind_EnableEntity: case D_CmdKind_EnableBreakpoint: case D_CmdKind_EnableTarget: { D_Entity *entity = d_entity_from_handle(params.entity); D_StateDeltaHistoryBatch(d_state_delta_history()) { d_entity_equip_disabled(entity, 0); } }break; case D_CmdKind_DisableEntity: case D_CmdKind_DisableBreakpoint: case D_CmdKind_DisableTarget: { D_Entity *entity = d_entity_from_handle(params.entity); D_StateDeltaHistoryBatch(d_state_delta_history()) { d_entity_equip_disabled(entity, 1); } }break; case D_CmdKind_FreezeEntity: case D_CmdKind_ThawEntity: { B32 should_freeze = (core_cmd_kind == D_CmdKind_FreezeEntity); D_Entity *root = d_entity_from_handle(params.entity); for(D_Entity *e = root; !d_entity_is_nil(e); e = d_entity_rec_depth_first_pre(e, root).next) { if(e->kind == D_EntityKind_Thread) { d_set_thread_freeze_state(e, should_freeze); } } }break; case D_CmdKind_RemoveEntity: case D_CmdKind_RemoveBreakpoint: case D_CmdKind_RemoveTarget: { D_Entity *entity = d_entity_from_handle(params.entity); D_EntityKindFlags kind_flags = d_entity_kind_flags_table[entity->kind]; if(kind_flags & D_EntityKindFlag_CanDelete) { d_entity_mark_for_deletion(entity); } }break; case D_CmdKind_NameEntity: { D_Entity *entity = d_entity_from_handle(params.entity); String8 string = params.string; d_entity_equip_name(entity, string); }break; case D_CmdKind_EditEntity:{}break; case D_CmdKind_DuplicateEntity: { D_Entity *src = d_entity_from_handle(params.entity); if(!d_entity_is_nil(src)) { typedef struct Task Task; struct Task { Task *next; D_Entity *src_n; D_Entity *dst_parent; }; Task starter_task = {0, src, src->parent}; Task *first_task = &starter_task; Task *last_task = &starter_task; for(Task *task = first_task; task != 0; task = task->next) { D_Entity *src_n = task->src_n; if(task == first_task) { d_state_delta_history_batch_begin(d_state_delta_history()); } D_Entity *dst_n = d_entity_alloc(task->dst_parent, task->src_n->kind); if(task == first_task) { d_state_delta_history_batch_end(d_state_delta_history()); } if(src_n->flags & D_EntityFlag_HasTextPoint) {d_entity_equip_txt_pt(dst_n, src_n->text_point);} if(src_n->flags & D_EntityFlag_HasU64) {d_entity_equip_u64(dst_n, src_n->u64);} if(src_n->flags & D_EntityFlag_HasColor) {d_entity_equip_color_hsva(dst_n, d_hsva_from_entity(src_n));} if(src_n->flags & D_EntityFlag_HasVAddrRng) {d_entity_equip_vaddr_rng(dst_n, src_n->vaddr_rng);} if(src_n->flags & D_EntityFlag_HasVAddr) {d_entity_equip_vaddr(dst_n, src_n->vaddr);} if(src_n->disabled) {d_entity_equip_disabled(dst_n, 1);} if(src_n->string.size != 0) {d_entity_equip_name(dst_n, src_n->string);} dst_n->cfg_src = src_n->cfg_src; for(D_Entity *src_child = task->src_n->first; !d_entity_is_nil(src_child); src_child = src_child->next) { Task *child_task = push_array(scratch.arena, Task, 1); child_task->src_n = src_child; child_task->dst_parent = dst_n; SLLQueuePush(first_task, last_task, child_task); } } } }break; case D_CmdKind_RelocateEntity: { D_Entity *entity = d_entity_from_handle(params.entity); D_Entity *location = d_entity_child_from_kind(entity, D_EntityKind_Location); if(d_entity_is_nil(location)) { location = d_entity_alloc(entity, D_EntityKind_Location); } location->flags &= ~D_EntityFlag_HasTextPoint; location->flags &= ~D_EntityFlag_HasVAddr; if(params.text_point.line != 0) { d_entity_equip_txt_pt(location, params.text_point); } if(params.vaddr != 0) { d_entity_equip_vaddr(location, params.vaddr); } if(params.file_path.size != 0) { d_entity_equip_name(location, params.file_path); } }break; //- rjf: breakpoints case D_CmdKind_AddBreakpoint: case D_CmdKind_ToggleBreakpoint: { String8 file_path = params.file_path; TxtPt pt = params.text_point; String8 string = params.string; U64 vaddr = params.vaddr; B32 removed_already_existing = 0; if(core_cmd_kind == D_CmdKind_ToggleBreakpoint) { D_EntityList bps = d_query_cached_entity_list_with_kind(D_EntityKind_Breakpoint); for(D_EntityNode *n = bps.first; n != 0; n = n->next) { D_Entity *bp = n->entity; D_Entity *loc = d_entity_child_from_kind(bp, D_EntityKind_Location); if((loc->flags & D_EntityFlag_HasTextPoint && path_match_normalized(loc->string, file_path) && loc->text_point.line == pt.line) || (loc->flags & D_EntityFlag_HasVAddr && loc->vaddr == vaddr) || (!(loc->flags & D_EntityFlag_HasTextPoint) && str8_match(loc->string, string, 0))) { d_entity_mark_for_deletion(bp); removed_already_existing = 1; break; } } } if(!removed_already_existing) { D_Entity *bp = d_entity_alloc(d_entity_root(), D_EntityKind_Breakpoint); d_entity_equip_cfg_src(bp, D_CfgSrc_Project); D_Entity *loc = d_entity_alloc(bp, D_EntityKind_Location); if(file_path.size != 0 && pt.line != 0) { d_entity_equip_name(loc, file_path); d_entity_equip_txt_pt(loc, pt); } else if(string.size != 0) { d_entity_equip_name(loc, string); } else if(vaddr != 0) { d_entity_equip_vaddr(loc, vaddr); } } }break; case D_CmdKind_AddAddressBreakpoint: case D_CmdKind_AddFunctionBreakpoint: { d_cmd_list_push(arena, cmds, ¶ms, d_cmd_spec_from_kind(D_CmdKind_AddBreakpoint)); }break; //- rjf: watch pins case D_CmdKind_AddWatchPin: case D_CmdKind_ToggleWatchPin: { String8 file_path = params.file_path; TxtPt pt = params.text_point; String8 string = params.string; U64 vaddr = params.vaddr; B32 removed_already_existing = 0; if(core_cmd_kind == D_CmdKind_ToggleWatchPin) { D_EntityList wps = d_query_cached_entity_list_with_kind(D_EntityKind_WatchPin); for(D_EntityNode *n = wps.first; n != 0; n = n->next) { D_Entity *wp = n->entity; D_Entity *loc = d_entity_child_from_kind(wp, D_EntityKind_Location); if((loc->flags & D_EntityFlag_HasTextPoint && path_match_normalized(loc->string, file_path) && loc->text_point.line == pt.line) || (loc->flags & D_EntityFlag_HasVAddr && loc->vaddr == vaddr) || (!(loc->flags & D_EntityFlag_HasTextPoint) && str8_match(loc->string, string, 0))) { d_entity_mark_for_deletion(wp); removed_already_existing = 1; break; } } } if(!removed_already_existing) { D_Entity *wp = d_entity_alloc(d_entity_root(), D_EntityKind_WatchPin); d_entity_equip_name(wp, string); d_entity_equip_cfg_src(wp, D_CfgSrc_Project); D_Entity *loc = d_entity_alloc(wp, D_EntityKind_Location); if(file_path.size != 0 && pt.line != 0) { d_entity_equip_name(loc, file_path); d_entity_equip_txt_pt(loc, pt); } else if(vaddr != 0) { d_entity_equip_vaddr(loc, vaddr); } } }break; //- rjf: watches case D_CmdKind_ToggleWatchExpression: if(params.string.size != 0) { D_Entity *existing_watch = d_entity_from_name_and_kind(params.string, D_EntityKind_Watch); if(d_entity_is_nil(existing_watch)) { D_Entity *watch = &d_nil_entity; D_StateDeltaHistoryBatch(d_state_delta_history()) { watch = d_entity_alloc(d_entity_root(), D_EntityKind_Watch); } d_entity_equip_cfg_src(watch, D_CfgSrc_Project); d_entity_equip_name(watch, cmd->params.string); } else { d_entity_mark_for_deletion(existing_watch); } }break; //- rjf: cursor operations case D_CmdKind_ToggleBreakpointAtCursor: { D_Regs *regs = d_regs(); D_CmdParams p = d_cmd_params_zero(); p.file_path = regs->file_path; p.text_point = regs->cursor; p.vaddr = regs->vaddr_range.min; d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_ToggleBreakpoint)); }break; case D_CmdKind_ToggleWatchPinAtCursor: { D_Regs *regs = d_regs(); D_CmdParams p = d_cmd_params_zero(); p.file_path = regs->file_path; p.text_point = regs->cursor; p.vaddr = regs->vaddr_range.min; p.string = params.string; d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_ToggleWatchPin)); }break; case D_CmdKind_GoToNameAtCursor: case D_CmdKind_ToggleWatchExpressionAtCursor: { HS_Scope *hs_scope = hs_scope_open(); TXT_Scope *txt_scope = txt_scope_open(); D_Regs *regs = d_regs(); U128 text_key = regs->text_key; TXT_LangKind lang_kind = regs->lang_kind; TxtRng range = txt_rng(regs->cursor, regs->mark); U128 hash = {0}; TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, text_key, lang_kind, &hash); String8 data = hs_data_from_hash(hs_scope, hash); Rng1U64 expr_off_range = {0}; if(range.min.column != range.max.column) { expr_off_range = r1u64(txt_off_from_info_pt(&info, range.min), txt_off_from_info_pt(&info, range.max)); } else { expr_off_range = txt_expr_off_range_from_info_data_pt(&info, data, range.min); } String8 expr = str8_substr(data, expr_off_range); D_CmdParams p = d_cmd_params_zero(); p.string = expr; d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(core_cmd_kind == D_CmdKind_GoToNameAtCursor ? D_CmdKind_GoToName : core_cmd_kind == D_CmdKind_ToggleWatchExpressionAtCursor ? D_CmdKind_ToggleWatchExpression : D_CmdKind_GoToName)); txt_scope_close(txt_scope); hs_scope_close(hs_scope); }break; case D_CmdKind_RunToCursor: { String8 file_path = d_regs()->file_path; if(file_path.size != 0) { d_cmd(D_CmdKind_RunToLine, .file_path = file_path, .text_point = d_regs()->cursor); } else { d_cmd(D_CmdKind_RunToAddress, .vaddr = d_regs()->vaddr_range.min); } }break; case D_CmdKind_SetNextStatement: { D_Entity *thread = d_entity_from_handle(d_regs()->thread); String8 file_path = d_regs()->file_path; U64 new_rip_vaddr = d_regs()->vaddr_range.min; if(file_path.size != 0) { D_LineList *lines = &d_regs()->lines; for(D_LineNode *n = lines->first; n != 0; n = n->next) { D_EntityList modules = d_modules_from_dbgi_key(scratch.arena, &n->v.dbgi_key); D_Entity *module = d_module_from_thread_candidates(thread, &modules); if(!d_entity_is_nil(module)) { new_rip_vaddr = d_vaddr_from_voff(module, n->v.voff_range.min); break; } } } D_CmdParams p = d_cmd_params_zero(); p.entity = d_handle_from_entity(thread); p.vaddr = new_rip_vaddr; d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_SetThreadIP)); }break; //- rjf: targets case D_CmdKind_AddTarget: { // rjf: build target D_Entity *entity = &d_nil_entity; D_StateDeltaHistoryBatch(d_state_delta_history()) { entity = d_entity_alloc(d_entity_root(), D_EntityKind_Target); } d_entity_equip_disabled(entity, 1); d_entity_equip_cfg_src(entity, D_CfgSrc_Project); D_Entity *exe = d_entity_alloc(entity, D_EntityKind_Executable); d_entity_equip_name(exe, params.file_path); String8 working_dir = str8_chop_last_slash(params.file_path); if(working_dir.size != 0) { String8 working_dir_path = push_str8f(scratch.arena, "%S/", working_dir); D_Entity *execution_path = d_entity_alloc(entity, D_EntityKind_WorkingDirectory); d_entity_equip_name(execution_path, working_dir_path); } D_CmdParams p = params; p.entity = d_handle_from_entity(entity); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_EditTarget)); d_cmd_list_push(arena, cmds, &p, d_cmd_spec_from_kind(D_CmdKind_SelectTarget)); }break; case D_CmdKind_SelectTarget: { D_Entity *entity = d_entity_from_handle(params.entity); if(entity->kind == D_EntityKind_Target) { D_StateDeltaHistoryBatch(d_state_delta_history()) { D_EntityList all_targets = d_query_cached_entity_list_with_kind(D_EntityKind_Target); B32 is_selected = !entity->disabled; for(D_EntityNode *n = all_targets.first; n != 0; n = n->next) { D_Entity *target = n->entity; d_entity_equip_disabled(target, 1); } if(!is_selected) { d_entity_equip_disabled(entity, 0); } } } }break; //- rjf: ended processes case D_CmdKind_RetryEndedProcess: { D_Entity *ended_process = d_entity_from_handle(params.entity); D_Entity *target = d_entity_from_handle(ended_process->entity_handle); if(target->kind == D_EntityKind_Target) { d_cmd(D_CmdKind_LaunchAndRun, .entity = d_handle_from_entity(target)); } else if(d_entity_is_nil(target)) { d_cmd(D_CmdKind_Error, .string = str8_lit("The ended process' corresponding target is missing.")); } else if(d_entity_is_nil(ended_process)) { d_cmd(D_CmdKind_Error, .string = str8_lit("Invalid ended process.")); } }break; //- rjf: attaching case D_CmdKind_Attach: { U64 pid = params.id; if(pid != 0) { CTRL_Msg msg = {CTRL_MsgKind_Attach}; msg.entity_id = (U32)pid; MemoryCopyArray(msg.exception_code_filters, d_state->ctrl_exception_code_filters); d_push_ctrl_msg(&msg); } }break; //- rjf: jit-debugger registration case D_CmdKind_RegisterAsJITDebugger: { #if OS_WINDOWS char filename_cstr[MAX_PATH] = {0}; GetModuleFileName(0, filename_cstr, sizeof(filename_cstr)); String8 debugger_binary_path = str8_cstring(filename_cstr); String8 name8 = str8_lit("Debugger"); String8 data8 = push_str8f(scratch.arena, "%S --jit_pid:%%ld --jit_code:%%ld --jit_addr:0x%%p", debugger_binary_path); String16 name16 = str16_from_8(scratch.arena, name8); String16 data16 = str16_from_8(scratch.arena, data8); B32 likely_not_in_admin_mode = 0; { HKEY reg_key = 0; LSTATUS status = 0; status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, ®_key); likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED); status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2); RegCloseKey(reg_key); } { HKEY reg_key = 0; LSTATUS status = 0; status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, ®_key); likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED); status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2); RegCloseKey(reg_key); } if(likely_not_in_admin_mode) { d_cmd(D_CmdKind_Error, .string = str8_lit("Could not register as the just-in-time debugger, access was denied; try running the debugger as administrator.")); } #else D_CmdParams p = params; p.string = str8_lit("Registering as the just-in-time debugger is currently not supported on this system."); d_push_cmd(&p, d_cmd_spec_from_kind(D_CmdKind_Error)); #endif }break; //- rjf: developer commands case D_CmdKind_LogMarker: { log_infof("\"#MARKER\""); }break; } } scratch_end(scratch); } ////////////////////////////// //- rjf: unpack eval-dependent info // D_Entity *process = d_entity_from_handle(d_regs()->process); D_Entity *thread = d_entity_from_handle(d_regs()->thread); Arch arch = d_arch_from_entity(thread); U64 unwind_count = d_regs()->unwind_count; U64 rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, unwind_count); CTRL_Unwind unwind = d_query_cached_unwind_from_thread(thread); D_Entity *module = d_module_from_process_vaddr(process, rip_vaddr); U64 rip_voff = d_voff_from_vaddr(module, rip_vaddr); U64 tls_root_vaddr = ctrl_query_cached_tls_root_vaddr_from_thread(d_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); D_EntityList all_modules = d_query_cached_entity_list_with_kind(D_EntityKind_Module); U64 eval_modules_count = Max(1, all_modules.count); E_Module *eval_modules = push_array(arena, E_Module, eval_modules_count); E_Module *eval_modules_primary = &eval_modules[0]; eval_modules_primary->rdi = &di_rdi_parsed_nil; eval_modules_primary->vaddr_range = r1u64(0, max_U64); DI_Key primary_dbgi_key = {0}; { U64 eval_module_idx = 0; for(D_EntityNode *n = all_modules.first; n != 0; n = n->next, eval_module_idx += 1) { D_Entity *m = n->entity; DI_Key dbgi_key = d_dbgi_key_from_module(m); eval_modules[eval_module_idx].arch = d_arch_from_entity(m); eval_modules[eval_module_idx].rdi = di_rdi_from_key(di_scope, &dbgi_key, 0); eval_modules[eval_module_idx].vaddr_range = m->vaddr_rng; eval_modules[eval_module_idx].space = d_eval_space_from_entity(d_entity_ancestor_from_kind(m, D_EntityKind_Process)); if(module == m) { eval_modules_primary = &eval_modules[eval_module_idx]; } } } U64 rdis_count = Max(1, all_modules.count); RDI_Parsed **rdis = push_array(arena, RDI_Parsed *, rdis_count); rdis[0] = &di_rdi_parsed_nil; U64 rdis_primary_idx = 0; Rng1U64 *rdis_vaddr_ranges = push_array(arena, Rng1U64, rdis_count); { U64 idx = 0; for(D_EntityNode *n = all_modules.first; n != 0; n = n->next, idx += 1) { DI_Key dbgi_key = d_dbgi_key_from_module(n->entity); rdis[idx] = di_rdi_from_key(di_scope, &dbgi_key, 0); rdis_vaddr_ranges[idx] = n->entity->vaddr_rng; if(n->entity == module) { primary_dbgi_key = dbgi_key; rdis_primary_idx = idx; } } } ////////////////////////////// //- rjf: build eval type context // E_TypeCtx *type_ctx = push_array(arena, E_TypeCtx, 1); { E_TypeCtx *ctx = type_ctx; ctx->ip_vaddr = rip_vaddr; ctx->ip_voff = rip_voff; ctx->modules = eval_modules; ctx->modules_count = eval_modules_count; ctx->primary_module = eval_modules_primary; } e_select_type_ctx(type_ctx); ////////////////////////////// //- rjf: build eval parse context // E_ParseCtx *parse_ctx = push_array(arena, E_ParseCtx, 1); ProfScope("build eval parse context") { E_ParseCtx *ctx = parse_ctx; ctx->ip_vaddr = rip_vaddr; ctx->ip_voff = rip_voff; ctx->ip_thread_space = d_eval_space_from_entity(thread); ctx->modules = eval_modules; ctx->modules_count = eval_modules_count; ctx->primary_module = eval_modules_primary; ctx->regs_map = ctrl_string2reg_from_arch(ctx->primary_module->arch); ctx->reg_alias_map = ctrl_string2alias_from_arch(ctx->primary_module->arch); ctx->locals_map = d_query_cached_locals_map_from_dbgi_key_voff(&primary_dbgi_key, rip_voff); ctx->member_map = d_query_cached_member_map_from_dbgi_key_voff(&primary_dbgi_key, rip_voff); } e_select_parse_ctx(parse_ctx); ////////////////////////////// //- rjf: build eval IR context // E_IRCtx *ir_ctx = push_array(arena, E_IRCtx, 1); { E_IRCtx *ctx = ir_ctx; ctx->macro_map = push_array(arena, E_String2ExprMap, 1); ctx->macro_map[0] = e_string2expr_map_make(arena, 512); //- rjf: add macros for constants { // rjf: pid -> current process' ID if(!d_entity_is_nil(process)) { E_Expr *expr = e_push_expr(arena, E_ExprKind_LeafU64, 0); expr->value.u64 = process->ctrl_id; e_string2expr_map_insert(arena, ctx->macro_map, str8_lit("pid"), expr); } // rjf: tid -> current thread's ID if(!d_entity_is_nil(thread)) { E_Expr *expr = e_push_expr(arena, E_ExprKind_LeafU64, 0); expr->value.u64 = thread->ctrl_id; e_string2expr_map_insert(arena, ctx->macro_map, str8_lit("tid"), expr); } } //- rjf: add macros for entities { Temp scratch = scratch_begin(&arena, 1); E_MemberList entity_members = {0}; { e_member_list_push_new(scratch.arena, &entity_members, .name = str8_lit("Enabled"), .off = 0, .type_key = e_type_key_basic(E_TypeKind_S64)); e_member_list_push_new(scratch.arena, &entity_members, .name = str8_lit("Hit Count"),.off = 0+8, .type_key = e_type_key_basic(E_TypeKind_U64)); e_member_list_push_new(scratch.arena, &entity_members, .name = str8_lit("Label"), .off = 0+8+8, .type_key = e_type_key_cons_ptr(arch_from_context(), e_type_key_basic(E_TypeKind_Char8))); e_member_list_push_new(scratch.arena, &entity_members, .name = str8_lit("Location"), .off = 0+8+8+8, .type_key = e_type_key_cons_ptr(arch_from_context(), e_type_key_basic(E_TypeKind_Char8))); e_member_list_push_new(scratch.arena, &entity_members, .name = str8_lit("Condition"),.off = 0+8+8+8+8,.type_key = e_type_key_cons_ptr(arch_from_context(), e_type_key_basic(E_TypeKind_Char8))); } E_MemberArray entity_members_array = e_member_array_from_list(scratch.arena, &entity_members); E_TypeKey entity_type = e_type_key_cons(.arch = arch_from_context(), .kind = E_TypeKind_Struct, .name = str8_lit("Entity"), .members = entity_members_array.v, .count = entity_members_array.count); D_EntityKind evallable_kinds[] = { D_EntityKind_Breakpoint, D_EntityKind_WatchPin, D_EntityKind_Target, }; for(U64 idx = 0; idx < ArrayCount(evallable_kinds); idx += 1) { D_EntityList entities = d_query_cached_entity_list_with_kind(evallable_kinds[idx]); for(D_EntityNode *n = entities.first; n != 0; n = n->next) { D_Entity *entity = n->entity; E_Expr *expr = e_push_expr(arena, E_ExprKind_LeafOffset, 0); expr->space = d_eval_space_from_entity(entity); expr->mode = E_Mode_Offset; expr->type_key = entity_type; e_string2expr_map_insert(arena, ctx->macro_map, push_str8f(arena, "$%I64u", entity->id), expr); if(entity->string.size != 0) { e_string2expr_map_insert(arena, ctx->macro_map, entity->string, expr); } } } scratch_end(scratch); } //- rjf: add macros for all watches which define identifiers D_EntityList watches = d_query_cached_entity_list_with_kind(D_EntityKind_Watch); for(D_EntityNode *n = watches.first; n != 0; n = n->next) { D_Entity *watch = n->entity; String8 expr = watch->string; E_TokenArray tokens = e_token_array_from_text(arena, expr); E_Parse parse = e_parse_expr_from_text_tokens(arena, expr, &tokens); if(parse.msgs.max_kind == E_MsgKind_Null) { e_push_leaf_ident_exprs_from_expr__in_place(arena, ctx->macro_map, parse.expr); } } } e_select_ir_ctx(ir_ctx); ////////////////////////////// //- rjf: build eval interpretation context // E_InterpretCtx *interpret_ctx = push_array(arena, E_InterpretCtx, 1); { E_InterpretCtx *ctx = interpret_ctx; ctx->space_read = d_eval_space_read; ctx->space_write = d_eval_space_write; ctx->primary_space = eval_modules_primary->space; ctx->reg_arch = eval_modules_primary->arch; ctx->reg_space = d_eval_space_from_entity(thread); ctx->reg_unwind_count = unwind_count; ctx->module_base = push_array(arena, U64, 1); ctx->module_base[0] = module->vaddr_rng.min; ctx->tls_base = push_array(arena, U64, 1); ctx->tls_base[0] = d_query_cached_tls_base_vaddr_from_process_root_rip(process, tls_root_vaddr, rip_vaddr); } e_select_interpret_ctx(interpret_ctx); ////////////////////////////// //- rjf: compute state parameterization hash for ctrl runs, if ctrl is running - // if the hash doesn't match the one for the last run, we need to soft-halt and // re-send down the parameterization state // if(d_ctrl_targets_running()) { U128 state_hash = d_hash_from_ctrl_param_state(); if(!u128_match(state_hash, d_state->ctrl_last_run_param_state_hash)) { d_cmd(D_CmdKind_SoftHaltRefresh); } } ////////////////////////////// //- rjf: send messages to control // if(d_state->ctrl_msgs.count != 0) { if(ctrl_u2c_push_msgs(&d_state->ctrl_msgs, os_now_microseconds()+100)) { MemoryZeroStruct(&d_state->ctrl_msgs); arena_clear(d_state->ctrl_msg_arena); } } ProfEnd(); }