From 11cb826181e566a12d006421c2ccce23c939903c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 29 Jul 2026 12:30:40 -0700 Subject: [PATCH 001/251] plug eval2 into watch debug tooltips --- project.4coder | 2 +- project.raddbg | 2 +- src/dbg_engine/dbg_engine_core.c | 87 ++++++++++++++++ src/dbg_engine/dbg_engine_core.h | 19 ++++ src/eval2/eval2.c | 49 ++++++++- src/eval2/eval2.h | 2 +- src/eval2/eval2.mdesk | 6 ++ src/eval2/generated/eval2.meta.c | 52 ++++++++++ src/eval2/generated/eval2.meta.h | 1 + src/raddbg/raddbg_core.c | 173 +++++++++++++++++++++++++++++++ 10 files changed, 389 insertions(+), 4 deletions(-) diff --git a/project.4coder b/project.4coder index f205a915..0b10da9c 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build torture && build ryan_scratch && pushd build && torture eval2/* && ryan_scratch", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/project.raddbg b/project.raddbg index 73a545e7..afa0b2b8 100644 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: "../raddebugger" arguments: "--user:C:/devel/raddebugger/build/raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -66,7 +67,6 @@ target: { executable: "build/ryan_scratch.exe" working_directory: build - enabled: 1 } target: { diff --git a/src/dbg_engine/dbg_engine_core.c b/src/dbg_engine/dbg_engine_core.c index 02a05907..fe6cf195 100644 --- a/src/dbg_engine/dbg_engine_core.c +++ b/src/dbg_engine/dbg_engine_core.c @@ -6,6 +6,93 @@ #include "generated/dbg_engine.meta.c" +//////////////////////////////// +//~ rjf: @eval2 Bundled Evaluation Path + +internal D_Eval +d_eval_from_string(Arena *arena, String8 string) +{ + D_Eval eval = {0}; + { + E2_MsgList msgs = {0}; + + //- rjf: string -> expr + E2_Expr *expr = &e2_expr_nil; + { + E2_ParseState state = {0}; + B32 identifier_is_type = 0; + for(;;) + { + E2_Parse parse = e2_parse_from_string(arena, &state, identifier_is_type, E2_LangKind_CLike, string); + identifier_is_type = 0; + expr = parse.expr; + e2_msg_list_concat_in_place(&msgs, &parse.msgs); + if(e2_parse_status_is_terminal(parse.status)) + { + break; + } + } + } + + //- rjf: expr -> irtree + E2_IRNode *irtree = &e2_irnode_nil; + { + E2_IRNode *resolve_result = &e2_irnode_nil; + E2_Val compile_time_eval_result = {0}; + E2_CompileState state = {0}; + for(;;) + { + E2_Compile compile = e2_compile_from_expr(arena, &state, resolve_result, compile_time_eval_result, expr); + resolve_result = &e2_irnode_nil; + MemoryZeroStruct(&compile_time_eval_result); + irtree = compile.irtree; + e2_msg_list_concat_in_place(&msgs, &compile.msgs); + switch(compile.status) + { + default:{}break; + case E2_CompileStatus_MissedIdentifierResolution: + { + resolve_result = e2_irnode_const_u64_or_smaller(arena, 123); + }break; + } + if(e2_compile_status_is_terminal(compile.status)) + { + break; + } + } + } + + //- rjf: ir tree -> bytecode + String8 bytecode = e2_bytecode_from_irnode(arena, irtree); + + //- rjf: bytecode -> value + E2_Val val = {0}; + { + E2_InterpState state = {0}; + E2_SpaceMap space_map = {0}; + for(;;) + { + E2_Interp interp = e2_interp_from_bytecode(arena, &state, &space_map, bytecode); + val = interp.val; + e2_msg_list_concat_in_place(&msgs, &interp.msgs); + if(e2_interp_status_is_terminal(interp.status)) + { + break; + } + } + } + + //- rjf: fill + eval.string = string; + eval.expr = expr; + eval.irtree = irtree; + eval.bytecode = bytecode; + eval.val = val; + eval.msgs = msgs; + } + return eval; +} + //////////////////////////////// //~ rjf: Layer Initialization diff --git a/src/dbg_engine/dbg_engine_core.h b/src/dbg_engine/dbg_engine_core.h index bc80c2d1..c40df17d 100644 --- a/src/dbg_engine/dbg_engine_core.h +++ b/src/dbg_engine/dbg_engine_core.h @@ -4,6 +4,20 @@ #ifndef DBG_ENGINE_CORE_H #define DBG_ENGINE_CORE_H +//////////////////////////////// +//~ rjf: @eval2 Evaluation Bundle + +typedef struct D_Eval D_Eval; +struct D_Eval +{ + String8 string; + E2_Expr *expr; + E2_IRNode *irtree; + String8 bytecode; + E2_Val val; + E2_MsgList msgs; +}; + //////////////////////////////// //~ rjf: ID Types @@ -318,6 +332,11 @@ struct D_TrapNet B32 good_read; }; +//////////////////////////////// +//~ rjf: @eval2 Bundled Evaluation Path + +internal D_Eval d_eval_from_string(Arena *arena, String8 string); + //////////////////////////////// //~ rjf: Layer Initialization diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 99d18520..28468088 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -199,6 +199,22 @@ e2_msgf(Arena *arena, E2_MsgList *msgs, Rng1U64 src_range, char *fmt, ...) return msg; } +internal void +e2_msg_list_concat_in_place(E2_MsgList *dst, E2_MsgList *src) +{ + if(dst->last != 0 && src->first != 0) + { + dst->last->next = src->first; + dst->last = src->last; + dst->count += src->count; + } + else if(src->first != 0) + { + MemoryCopyStruct(dst, src); + } + MemoryZeroStruct(src); +} + //////////////////////////////// //~ rjf: Assets @@ -2594,7 +2610,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, internal E2_Compile e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_result, E2_Val compile_time_eval_result, E2_Expr *expr) { - E2_Compile compile = {E2_CompileStatus_Error}; + E2_Compile compile = {E2_CompileStatus_Error, .irtree = &e2_irnode_nil, .params_irtree = &e2_irnode_nil}; { E2_IRNode *next_resolve_result = resolve_result; @@ -3720,6 +3736,7 @@ e2_interp_from_bytecode(Arena *arena, E2_InterpState *state, E2_SpaceMap *space_ { good = 0; interp.status = E2_InterpStatus_BadRegCode; + e2_msgf(arena, &interp.msgs, r1u64(state->bytecode_off, state->bytecode_off+1), "Bad register code (%i).", base_reg_code); } }break; case RDI_EvalOp_FrameOff: {ctx_flags = E2_CtxFlag_HasFrameBase; ctx_base_addr = state->selected_ctx.frame_base_addr;}goto optional_base_off; @@ -3735,6 +3752,16 @@ e2_interp_from_bytecode(Arena *arena, E2_InterpState *state, E2_SpaceMap *space_ else { good = 0; + String8 missing_msg = {0}; + switch(ctx_flags) + { + default:{}break; + case E2_CtxFlag_HasFrameBase: {missing_msg = s("Missing frame base address.");}break; + case E2_CtxFlag_HasModuleBase:{missing_msg = s("Missing module base address.");}break; + case E2_CtxFlag_HasTLSBase: {missing_msg = s("Missing thread-local-storage base address.");}break; + case E2_CtxFlag_HasCFA: {missing_msg = s("Missing canonical frame address.");}break; + } + e2_msg(arena, &interp.msgs, r1u64(state->bytecode_off, state->bytecode_off+1), missing_msg); interp.status = E2_InterpStatus_MissingCtxFlag; interp.missing_ctx_flags |= ctx_flags; } @@ -3979,6 +4006,7 @@ BinOp(U, u64, u64, symbol); { good = 0; interp.status = E2_InterpStatus_BadOffset; + e2_msgf(arena, &interp.msgs, r1u64(state->bytecode_off, state->bytecode_off+1), "Couldn't read range [0x%I64x, 0x%I64x) (end of range is 0x%I64x).", offset, offset+bytes_to_read, popped_vals[1].string.size); } }break; @@ -4029,6 +4057,25 @@ BinOp(U, u64, u64, symbol); }break; } + //- rjf: push generalized failure messages + switch(interp.status) + { + default:{}break; + case E2_InterpStatus_DivideByZero: + { + e2_msgf(arena, &interp.msgs, r1u64(state->bytecode_off, state->bytecode_off+1), "Divide by zero."); + }break; + case E2_InterpStatus_UnsupportedOp: + { + e2_msgf(arena, &interp.msgs, r1u64(state->bytecode_off, state->bytecode_off+1), "Unsupported opcode (%i).", op); + }break; + case E2_InterpStatus_BadOpTypes: + { + String8 opcode_name = rdi_string_from_eval_op(scratch.arena, op); + e2_msgf(arena, &interp.msgs, r1u64(state->bytecode_off, state->bytecode_off+1), "%S opcode cannot be applied to these types.", opcode_name); + }break; + } + //- rjf: if opcode successful -> do stack pops if(good) for EachIndex(pop_idx, pop_count) { diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index c8736d19..f7ec4d96 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -64,7 +64,6 @@ typedef enum E2_CompileStatus //- rjf: caller-provided info E2_CompileStatus_MissedIdentifierResolution, E2_CompileStatus_NewIdentifierDefinition, - E2_CompileStatus_NewCtxID, E2_CompileStatus_MemberAccess, E2_CompileStatus_IndexAccess, E2_CompileStatus_Call, @@ -559,6 +558,7 @@ internal E2_IRNode *e2_irtree_from_identifier(E2_IdentifierMap *map, String8 nam internal E2_Msg *e2_msg(Arena *arena, E2_MsgList *msgs, Rng1U64 src_range, String8 string); internal E2_Msg *e2_msgf(Arena *arena, E2_MsgList *msgs, Rng1U64 src_range, char *fmt, ...); +internal void e2_msg_list_concat_in_place(E2_MsgList *dst, E2_MsgList *src); //////////////////////////////// //~ rjf: Assets diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index 70d13964..363500a3 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -65,6 +65,12 @@ E2_ExprKindTable: COUNT, } +@data(String8) e2_expr_kind_string_table: +{ + `{0}` + @expand(E2_ExprKindTable a) `str8_lit_comp("$(a.name)")` +} + @data(B8) e2_expr_kind_allow_type_operands_table: { 0, diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index 7affddae..52c664ea 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,6 +4,58 @@ //- GENERATED CODE C_LINKAGE_BEGIN +String8 e2_expr_kind_string_table[48] = +{ +{0}, +str8_lit_comp("Identifier"), +str8_lit_comp("MacroArg"), +str8_lit_comp("TypeIdentifier"), +str8_lit_comp("Numeric"), +str8_lit_comp("StringLiteral"), +str8_lit_comp("CharLiteral"), +str8_lit_comp("Ptr"), +str8_lit_comp("Array"), +str8_lit_comp("Function"), +str8_lit_comp("Const"), +str8_lit_comp("Volatile"), +str8_lit_comp("Unsigned"), +str8_lit_comp("Signed"), +str8_lit_comp("Dot"), +str8_lit_comp("Index"), +str8_lit_comp("Call"), +str8_lit_comp("DerefAsm"), +str8_lit_comp("SizeOf"), +str8_lit_comp("TypeOf"), +str8_lit_comp("Cast"), +str8_lit_comp("Deref"), +str8_lit_comp("Address"), +str8_lit_comp("Pos"), +str8_lit_comp("Neg"), +str8_lit_comp("LogNot"), +str8_lit_comp("BitNot"), +str8_lit_comp("Mul"), +str8_lit_comp("Div"), +str8_lit_comp("Mod"), +str8_lit_comp("Add"), +str8_lit_comp("Sub"), +str8_lit_comp("LShift"), +str8_lit_comp("RShift"), +str8_lit_comp("Less"), +str8_lit_comp("LtEq"), +str8_lit_comp("Grtr"), +str8_lit_comp("GrEq"), +str8_lit_comp("EqEq"), +str8_lit_comp("NtEq"), +str8_lit_comp("BitAnd"), +str8_lit_comp("BitXor"), +str8_lit_comp("BitOr"), +str8_lit_comp("LogAnd"), +str8_lit_comp("LogOr"), +str8_lit_comp("Define"), +str8_lit_comp("Macro"), +str8_lit_comp("Cond"), +}; + B8 e2_expr_kind_allow_type_operands_table[48] = { 0, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index 2bc7ea5b..8e160b52 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -163,6 +163,7 @@ E2_ExprKindParseInfo *expr_kind_parse_infos; }; C_LINKAGE_BEGIN +extern String8 e2_expr_kind_string_table[48]; extern B8 e2_expr_kind_allow_type_operands_table[48]; extern B8 e2_expr_kind_is_type_expr_table[48]; extern B8 e2_expr_kind_is_first_operand_type_maybe_table[48]; diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 52c1a7c3..16147d7d 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -4368,6 +4368,179 @@ rd_view_ui(Rng2F32 rect) rd_set_autocomp_regs(cell->eval, .ui_key = line_edit_key, .string = input, .cursor = cell_edit_state->cursor); } + // rjf: @eval2 show tooltip for eval2 evaluation +#if 0 + if(ui_hovering(sig) && row_depth == 0) UI_Tooltip RD_Font(RD_FontSlot_Code) + { + String8 expr = cell->eval.string; + D_Eval eval = d_eval_from_string(scratch.arena, expr); + String8 indent = s(" "); + + // rjf: log string + ui_label(expr); + + // rjf: log expression tree + if(eval.expr != &e2_expr_nil) + { + ui_spacer(ui_em(1.f, 1.f)); + UI_TagF("weak") ui_label(s("--- Expression Tree ---")); + typedef struct Task Task; + struct Task + { + Task *next; + E2_Expr *expr; + E2_ExprNode *last_child; + S64 depth; + }; + Task start_task = {0, eval.expr}; + Task *top_task = &start_task; + Task *free_task = 0; + for(;top_task != 0;) + { + E2_Expr *expr = top_task->expr; + + // rjf: do this expr node + if(top_task->last_child == 0) + { + String8 expr_kind_name = e2_expr_kind_string_table[expr->kind]; + ui_labelf("%.*s%S", top_task->depth, indent.str, expr_kind_name); + } + + // rjf: try to push a child + B32 pushed_child = 0; + { + E2_ExprNode *next_child = (top_task->last_child ? top_task->last_child->next : expr->first_child); + top_task->last_child = next_child; + if(next_child != 0) + { + Task *task = free_task; + if(task != 0) + { + SLLStackPop(free_task); + } + else + { + task = push_array_no_zero(scratch.arena, Task, 1); + } + MemoryZeroStruct(task); + task->expr = next_child->v; + task->depth = top_task->depth+1; + SLLStackPush(top_task, task); + pushed_child = 1; + } + } + + // rjf: if we didn't push a child -> we're done + if(!pushed_child) + { + Task *popped = top_task; + SLLStackPop(top_task); + SLLStackPush(free_task, popped); + } + } + } + + // rjf: log IR tree + if(eval.irtree != &e2_irnode_nil) + { + ui_spacer(ui_em(1.f, 1.f)); + UI_TagF("weak") ui_label(s("--- IR Tree ---")); + typedef struct Task Task; + struct Task + { + Task *next; + E2_IRNode *irnode; + E2_IRNodePtrNode *last_child; + S64 depth; + }; + Task start_task = {0, eval.irtree}; + Task *top_task = &start_task; + Task *free_task = 0; + for(;top_task != 0;) + { + E2_IRNode *irnode = top_task->irnode; + + // rjf: do this irnode + if(top_task->last_child == 0) + { + String8 type_string = e2_string_from_type_key(scratch.arena, irnode->type_key); + String8 op_name = {0}; + if(irnode->op < RDI_EvalOp_COUNT) + { + op_name = rdi_string_from_eval_op(scratch.arena, irnode->op); + } + else + { + op_name = str8f(scratch.arena, "#%x", irnode->op); + } + ui_labelf("%.*s%S (type: %S)", top_task->depth, indent.str, op_name, type_string); + } + + // rjf: try to push a child + B32 pushed_child = 0; + { + E2_IRNodePtrNode *next_child = (top_task->last_child ? top_task->last_child->next : irnode->first_child); + top_task->last_child = next_child; + if(next_child != 0) + { + Task *task = free_task; + if(task != 0) + { + SLLStackPop(free_task); + } + else + { + task = push_array_no_zero(scratch.arena, Task, 1); + } + MemoryZeroStruct(task); + task->irnode = next_child->v; + task->depth = top_task->depth+1; + SLLStackPush(top_task, task); + pushed_child = 1; + } + } + + // rjf: if we didn't push a child -> we're done + if(!pushed_child) + { + Task *popped = top_task; + SLLStackPop(top_task); + SLLStackPush(free_task, popped); + } + } + } + + // rjf: log bytecode + if(eval.bytecode.size != 0) + { + ui_spacer(ui_em(1.f, 1.f)); + UI_TagF("weak") ui_label(s("--- Bytecode ---")); + String8 bytecode_string = rdi_string_from_bytecode(scratch.arena, rdi_arch_from_arch(Arch_CURRENT), eval.bytecode); + ui_label(bytecode_string); + } + + // rjf: log value + { + ui_spacer(ui_em(1.f, 1.f)); + UI_TagF("weak") ui_label(s("--- Value ---")); + ui_labelf("U64: %I64u (0x%I64x)", eval.val.u64, eval.val.u64); + ui_labelf("F32: %f", eval.val.f32); + ui_labelf("F64: %f", eval.val.f64); + } + + // rjf: log messages + if(eval.msgs.count != 0) + { + ui_spacer(ui_em(1.f, 1.f)); + UI_TagF("weak") ui_label(s("--- Messages ---")); + for EachNode(n, E2_Msg, eval.msgs.first) + { + ui_label(n->string); + } + } + } +#endif + // rjf: apply locks { CFG_Node *cfg = row_info->group_cfg_child; From a6755d6191963bdc263be7c3e8a7d3073f74fe86 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 4 Aug 2026 15:38:28 -0700 Subject: [PATCH 002/251] fix linux build, fix small gl rendering issues --- project.4coder | 2 +- src/raddbg/raddbg_core.c | 12 ++++++------ src/render/opengl/generated/render_opengl.meta.h | 2 +- src/render/opengl/render_opengl.c | 2 +- src/render/opengl/render_opengl.h | 1 + src/render/opengl/render_opengl.mdesk | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/project.4coder b/project.4coder index 0b10da9c..8c4d76eb 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 16147d7d..d7c4e9f9 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2756,9 +2756,9 @@ rd_view_ui(Rng2F32 rect) D_Entity *entity = rd_ctrl_entity_from_eval_space(space); B32 is_memory_eval = (space.kind == D_EvalSpaceKind_Entity && entity->kind == D_EntityKind_Process); row_can_be_locked = (is_memory_eval && - (row->eval.irtree.mode == E_Mode_Offset) || - (row->eval.irtree.mode == E_Mode_Value && - e_type_kind_is_pointer_or_ref(e_type_kind_from_key(e_type_key_unwrap(row->eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative))))); + (row->eval.irtree.mode == E_Mode_Offset || + (row->eval.irtree.mode == E_Mode_Value && + e_type_kind_is_pointer_or_ref(e_type_kind_from_key(e_type_key_unwrap(row->eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative)))))); } // rjf: determine if row is locked @@ -4136,9 +4136,9 @@ rd_view_ui(Rng2F32 rect) D_Entity *entity = rd_ctrl_entity_from_eval_space(space); B32 is_memory_eval = (space.kind == D_EvalSpaceKind_Entity && entity->kind == D_EntityKind_Process); cell_can_be_locked = (is_memory_eval && - (cell->eval.irtree.mode == E_Mode_Offset) || - (cell->eval.irtree.mode == E_Mode_Value && - e_type_kind_is_pointer_or_ref(e_type_kind_from_key(e_type_key_unwrap(cell->eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative))))); + ((cell->eval.irtree.mode == E_Mode_Offset) || + (cell->eval.irtree.mode == E_Mode_Value && + e_type_kind_is_pointer_or_ref(e_type_kind_from_key(e_type_key_unwrap(cell->eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative)))))); } // rjf: determine if this cell is locked diff --git a/src/render/opengl/generated/render_opengl.meta.h b/src/render/opengl/generated/render_opengl.meta.h index f4851da7..9edd4b86 100644 --- a/src/render/opengl/generated/render_opengl.meta.h +++ b/src/render/opengl/generated/render_opengl.meta.h @@ -260,7 +260,7 @@ str8_lit_comp( " \n" " // rjf: sample for corners\n" " float corner_sdf_s = rect_sdf(sdf_sample_pos, rect_half_size, corner_radius);\n" -" float corner_sdf_t = smoothstep(0, 2, corner_sdf_s);\n" +" float corner_sdf_t = 1-smoothstep(0, 2, corner_sdf_s);\n" " \n" " // rjf: weight output color by sdf\n" " // this is doing alpha testing, leave blurring only where mostly opaque pixels are\n" diff --git a/src/render/opengl/render_opengl.c b/src/render/opengl/render_opengl.c index d9814293..4d06297e 100644 --- a/src/render/opengl/render_opengl.c +++ b/src/render/opengl/render_opengl.c @@ -418,7 +418,7 @@ r_window_begin_frame(WM_Window os, R_Handle r) glBindFramebufferScope(GL_FRAMEBUFFER, targets[idx]->fbo) glBindTextureScope(GL_TEXTURE_2D, targets[idx]->color_texture) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (S32)client_rect_dim.x, (S32)client_rect_dim.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, (S32)client_rect_dim.x, (S32)client_rect_dim.y, 0, GL_RGBA, GL_FLOAT, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, targets[idx]->color_texture, 0); } } diff --git a/src/render/opengl/render_opengl.h b/src/render/opengl/render_opengl.h index e64ec04c..f499cd1e 100644 --- a/src/render/opengl/render_opengl.h +++ b/src/render/opengl/render_opengl.h @@ -52,6 +52,7 @@ typedef ptrdiff_t GLintptr; #define GL_COLOR_ATTACHMENT31 0x8CFF #define GL_R8 0x8229 +#define GL_RGBA32F 0x8814 #define GL_ARRAY_BUFFER 0x8892 #define GL_STREAM_DRAW 0x88E0 diff --git a/src/render/opengl/render_opengl.mdesk b/src/render/opengl/render_opengl.mdesk index c2665929..4c9d8eff 100644 --- a/src/render/opengl/render_opengl.mdesk +++ b/src/render/opengl/render_opengl.mdesk @@ -282,7 +282,7 @@ void main(void) // rjf: sample for corners float corner_sdf_s = rect_sdf(sdf_sample_pos, rect_half_size, corner_radius); - float corner_sdf_t = smoothstep(0, 2, corner_sdf_s); + float corner_sdf_t = 1-smoothstep(0, 2, corner_sdf_s); // rjf: weight output color by sdf // this is doing alpha testing, leave blurring only where mostly opaque pixels are From a0a16ea11ef82d9f9fa7a24c154c1c7fbfbf3cdd Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 4 Aug 2026 18:51:01 -0700 Subject: [PATCH 003/251] fix eh_frame unwinder - switching to synchronous process reads exposed losing info about a missed address read for unwinding, so unwinds never succeeded --- src/eh_frame/unwind/eh_frame_unwind.c | 1 + src/eval_visualization/eval_visualization_core.c | 3 ++- src/linux/demon/linux_demon.c | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/eh_frame/unwind/eh_frame_unwind.c b/src/eh_frame/unwind/eh_frame_unwind.c index 0fedf232..47a5dba6 100644 --- a/src/eh_frame/unwind/eh_frame_unwind.c +++ b/src/eh_frame/unwind/eh_frame_unwind.c @@ -133,6 +133,7 @@ eh_uwnd_step(Arch arch, MemoryMap *memory_map, UWND_ModuleInfo *module_info, U64 // rjf: determine CIE's full address range Rng1U64 cie_vaddr_range = {0}; DW_Format cie_fmt = DW_Format_Null; + if(!done) { // rjf: read initial length data String8 initial_length_data = {0}; diff --git a/src/eval_visualization/eval_visualization_core.c b/src/eval_visualization/eval_visualization_core.c index 35605bec..93287c5b 100644 --- a/src/eval_visualization/eval_visualization_core.c +++ b/src/eval_visualization/eval_visualization_core.c @@ -1764,7 +1764,8 @@ ev_string_iter_next(Arena *arena, EV_StringIter *it, String8 *out_string) { ptr_data->addr_is_good = 1; } - else if(e_space_read(ptr_data->value_eval.space, &byte, &range_info, r1u64(ptr_data->value_eval.value.u64, ptr_data->value_eval.value.u64+1))) + else if(e_space_read(ptr_data->value_eval.space, &byte, &range_info, r1u64(ptr_data->value_eval.value.u64, ptr_data->value_eval.value.u64+1)) && + !(range_info.flags & E_SpaceRangeFlag_Stale)) { ptr_data->addr_is_good = !(byte_bad_flags & 1); } diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 152e3951..4c85d7dc 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -690,7 +690,7 @@ lnx_dmn_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase, B32 is_rebased U64 bloom_word = (elf_class == ELF_Class_64) ? 8 : 4; U64 buckets_vaddr = dynamic_info.gnu_hash_vaddr + sizeof(gnu_header) + bloom_size * bloom_word; U64 chain_vaddr = buckets_vaddr + (U64)nbuckets * sizeof(U32); - + // find largest symbol index referenced by any bucket U32 max_bucket = 0; for(U32 b = 0; b < nbuckets; b += 1) @@ -702,7 +702,7 @@ lnx_dmn_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase, B32 is_rebased } if(entry > max_bucket) { max_bucket = entry; } } - + if(max_bucket < symoffset) { // no symbols in hash; total count is symoffset @@ -735,7 +735,7 @@ lnx_dmn_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase, B32 is_rebased } } } - + // scan symbol table for the rendezvous symbol if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count) { From e6e0992c63c29d949c5bf7bac556c6e0ea5a78d7 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 5 Aug 2026 12:09:32 -0600 Subject: [PATCH 004/251] lnx_wm: x11 borderless / redraw-while-resizing --- .../window_manager/linux_window_manager.c | 312 +++++++++++++++--- .../window_manager/linux_window_manager.h | 33 ++ 2 files changed, 299 insertions(+), 46 deletions(-) diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index fc2a6147..dda872d4 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -19,6 +19,81 @@ lnx_window_from_x11window(Window window) return result; } +internal int +lnx_moveresize_code_from_pos(LNX_WM_Window *window, Vec2F32 pos, B32 *out_is_in_client_area) +{ + B32 in_non_client_area = 0; + int moveresize_code = 0; + if(window->custom_border) + { + WM_Window handle = {(U64)window}; + Rng2F32 window_rect = wm_client_rect_from_window(handle); + Rng2F32 inside_edges = pad_2f32(window_rect, -window->custom_edge_thickness); + Rng2F32 inside_edges_and_title_bar = inside_edges; + inside_edges_and_title_bar.y0 += window->custom_title_bar_thickness; + if(!contains_2f32(inside_edges_and_title_bar, pos)) + { + in_non_client_area = 1; + for EachNode(n, LNX_WM_WindowClientArea, window->first_client_area) + { + if(contains_2f32(n->rect, pos)) + { + in_non_client_area = 0; + break; + } + } + } + if(in_non_client_area) + { + Rng2F32 title_bar_rect = r2f32p(window_rect.x0 + window->custom_edge_thickness, + window_rect.y0 + window->custom_edge_thickness, + window_rect.x1 - window->custom_edge_thickness, + window_rect.y0 + window->custom_title_bar_thickness); + if(contains_2f32(title_bar_rect, pos)) + { + moveresize_code = _NET_WM_MOVERESIZE_MOVE; + } + else if(contains_2f32(window_rect, pos) && !contains_2f32(inside_edges, pos)) + { + if(pos.x <= inside_edges.x0 && pos.y <= inside_edges.y0) + { + moveresize_code = _NET_WM_MOVERESIZE_SIZE_TOPLEFT; + } + else if(pos.x <= inside_edges.x0 && pos.y >= inside_edges.y1) + { + moveresize_code = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; + } + else if(pos.x >= inside_edges.x1 && pos.y <= inside_edges.y0) + { + moveresize_code = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT; + } + else if(pos.x >= inside_edges.x1 && pos.y >= inside_edges.y1) + { + moveresize_code = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; + } + else if(pos.x <= inside_edges.x0) + { + moveresize_code = _NET_WM_MOVERESIZE_SIZE_LEFT; + } + else if(pos.x >= inside_edges.x1) + { + moveresize_code = _NET_WM_MOVERESIZE_SIZE_RIGHT; + } + else if(pos.y <= inside_edges.y0) + { + moveresize_code = _NET_WM_MOVERESIZE_SIZE_TOP; + } + else if(pos.y >= inside_edges.y1) + { + moveresize_code = _NET_WM_MOVERESIZE_SIZE_BOTTOM; + } + } + } + } + out_is_in_client_area[0] = !in_non_client_area; + return moveresize_code; +} + //////////////////////////////// //~ rjf: @per_os_impl Main Initialization API (Implemented Per-OS) @@ -154,7 +229,9 @@ wm_window_open(Rng2F32 rect, WM_WindowFlags flags, String8 title) ButtonReleaseMask| KeyPressMask| KeyReleaseMask| + StructureNotifyMask| FocusChangeMask); + XSetWindowBackgroundPixmap(lnx_wm_state->display, w->window, None); Atom protocols[] = { lnx_wm_state->wm_delete_window_atom, @@ -181,6 +258,24 @@ wm_window_open(Rng2F32 rect, WM_WindowFlags flags, String8 title) XStoreName(lnx_wm_state->display, w->window, (char *)title_copy.str); scratch_end(scratch); + //- rjf: set custom window border info + if(flags & WM_WindowFlag_CustomBorder) + { + w->custom_border = 1; + typedef struct Hints Hints; + struct Hints + { + U32 flags; + U32 functions; + U32 decorations; + S32 inputMode; + U32 status; + }; + Hints hints = {.flags = 2, .decorations = 0}; + Atom property = XInternAtom(lnx_wm_state->display, "_MOTIF_WM_HINTS", 1); + XChangeProperty(lnx_wm_state->display, w->window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } + //- rjf: convert to handle & return WM_Window handle = {(U64)w}; return handle; @@ -307,28 +402,47 @@ internal void wm_window_clear_custom_border_data(WM_Window handle) { if(wm_window_match(handle, wm_window_zero())) {return;} - // TODO(rjf) + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + for(LNX_WM_WindowClientArea *n = w->first_client_area, *next = 0; n != 0; n = next) + { + next = n->next; + SLLStackPush(lnx_wm_state->free_client_area, n); + } + w->first_client_area = w->last_client_area = 0; } internal void wm_window_push_custom_title_bar(WM_Window handle, F32 thickness) { if(wm_window_match(handle, wm_window_zero())) {return;} - // TODO(rjf) + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + w->custom_title_bar_thickness = thickness; } internal void wm_window_push_custom_edges(WM_Window handle, F32 thickness) { if(wm_window_match(handle, wm_window_zero())) {return;} - // TODO(rjf) + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + w->custom_edge_thickness = thickness; } internal void wm_window_push_custom_title_bar_client_area(WM_Window handle, Rng2F32 rect) { if(wm_window_match(handle, wm_window_zero())) {return;} - // TODO(rjf) + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + LNX_WM_WindowClientArea *n = lnx_wm_state->free_client_area; + if(n != 0) + { + SLLStackPop(lnx_wm_state->free_client_area); + } + else + { + n = push_array(lnx_wm_state->arena, LNX_WM_WindowClientArea, 1); + } + n->rect = rect; + SLLQueuePush(w->first_client_area, w->last_client_area, n); } internal Rng2F32 @@ -336,9 +450,17 @@ wm_rect_from_window(WM_Window handle) { if(wm_window_match(handle, wm_window_zero())) {return r2f32p(0, 0, 0, 0);} LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; - XWindowAttributes atts = {0}; - Status s = XGetWindowAttributes(lnx_wm_state->display, w->window, &atts); - Rng2F32 result = r2f32p((F32)atts.x, (F32)atts.y, (F32)atts.x + (F32)atts.width, (F32)atts.y + (F32)atts.height); + Rng2F32 result = {0}; + if(w->resize_draw) + { + result = w->last_synced_rect; + } + else + { + XWindowAttributes atts = {0}; + Status s = XGetWindowAttributes(lnx_wm_state->display, w->window, &atts); + result = r2f32p((F32)atts.x, (F32)atts.y, (F32)atts.x + (F32)atts.width, (F32)atts.y + (F32)atts.height); + } return result; } @@ -346,9 +468,18 @@ internal Rng2F32 wm_client_rect_from_window(WM_Window handle) { LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; - XWindowAttributes atts = {0}; - Status s = XGetWindowAttributes(lnx_wm_state->display, w->window, &atts); - Rng2F32 result = r2f32p(0, 0, (F32)atts.width, (F32)atts.height); + Rng2F32 result = {0}; + if(w->resize_draw) + { + Vec2F32 dim = dim_2f32(w->last_synced_rect); + result = r2f32p(0, 0, dim.x, dim.y); + } + else + { + XWindowAttributes atts = {0}; + Status s = XGetWindowAttributes(lnx_wm_state->display, w->window, &atts); + result = r2f32p(0, 0, (F32)atts.width, (F32)atts.height); + } return result; } @@ -460,8 +591,11 @@ wm_get_events(Arena *arena, B32 wait) } while(XPending(lnx_wm_state->display)) { + //- rjf: get next event XEvent evt = {0}; XNextEvent(lnx_wm_state->display, &evt); + + //- rjf: do event response B32 set_mouse_cursor = 0; switch(evt.type) { @@ -588,40 +722,77 @@ wm_get_events(Arena *arena, B32 wait) case ButtonPress: case ButtonRelease: { - // rjf: determine flags - WM_Modifiers modifiers = 0; - if(evt.xbutton.state & ShiftMask) { modifiers |= WM_Modifier_Shift; } - if(evt.xbutton.state & ControlMask) { modifiers |= WM_Modifier_Ctrl; } - if(evt.xbutton.state & Mod1Mask) { modifiers |= WM_Modifier_Alt; } - - // rjf: map button -> WM_Key - WM_Key key = WM_Key_Null; - switch(evt.xbutton.button) - { - default:{}break; - case Button1:{key = WM_Key_LeftMouseButton;}break; - case Button2:{key = WM_Key_MiddleMouseButton;}break; - case Button3:{key = WM_Key_RightMouseButton;}break; - } - - // rjf: push event LNX_WM_Window *window = lnx_window_from_x11window(evt.xbutton.window); - if(key != WM_Key_Null) + + // rjf: determine if this is outside client area, & where it is + B32 in_client_area = 0; + int moveresize_code = lnx_moveresize_code_from_pos(window, v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y), &in_client_area); + + // rjf: if in non-client area -> redirect to default x11 server behavior + if(!in_client_area) { - WM_Event *e = wm_event_list_push_new(arena, &evts, evt.type == ButtonPress ? WM_EventKind_Press : WM_EventKind_Release); - e->window.u64[0] = (U64)window; - e->modifiers = modifiers; - e->key = key; - e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); + // rjf: ungrab pointer + XUngrabPointer(lnx_wm_state->display, evt.xbutton.time); + + // rjf: get atom for moving/resizing event kind + Atom net_wm_moveresize = XInternAtom(lnx_wm_state->display, "_NET_WM_MOVERESIZE", 0); + + // rjf: fill event + XEvent xev; + MemoryZeroStruct(&xev); + { + xev.xclient.type = ClientMessage; + xev.xclient.display = lnx_wm_state->display; + xev.xclient.window = window->window; + xev.xclient.message_type = net_wm_moveresize; + xev.xclient.format = 32; + xev.xclient.data.l[0] = evt.xbutton.x_root; + xev.xclient.data.l[1] = evt.xbutton.y_root; + xev.xclient.data.l[2] = moveresize_code; + } + + // rjf: send event + XSendEvent(lnx_wm_state->display, DefaultRootWindow(lnx_wm_state->display), 0, SubstructureRedirectMask|SubstructureNotifyMask, &xev); + XFlush(lnx_wm_state->display); } - else if(evt.xbutton.button == Button4 || - evt.xbutton.button == Button5) + + // rjf: otherwise -> pipe the event to the application + else { - WM_Event *e = wm_event_list_push_new(arena, &evts, WM_EventKind_Scroll); - e->window.u64[0] = (U64)window; - e->modifiers = modifiers; - e->delta = v2f32(0, evt.xbutton.button == Button4 ? -1.f : +1.f); - e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); + // rjf: determine flags + WM_Modifiers modifiers = 0; + if(evt.xbutton.state & ShiftMask) { modifiers |= WM_Modifier_Shift; } + if(evt.xbutton.state & ControlMask) { modifiers |= WM_Modifier_Ctrl; } + if(evt.xbutton.state & Mod1Mask) { modifiers |= WM_Modifier_Alt; } + + // rjf: map button -> WM_Key + WM_Key key = WM_Key_Null; + switch(evt.xbutton.button) + { + default:{}break; + case Button1:{key = WM_Key_LeftMouseButton;}break; + case Button2:{key = WM_Key_MiddleMouseButton;}break; + case Button3:{key = WM_Key_RightMouseButton;}break; + } + + // rjf: push event + if(key != WM_Key_Null) + { + WM_Event *e = wm_event_list_push_new(arena, &evts, evt.type == ButtonPress ? WM_EventKind_Press : WM_EventKind_Release); + e->window.u64[0] = (U64)window; + e->modifiers = modifiers; + e->key = key; + e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); + } + else if(evt.xbutton.button == Button4 || + evt.xbutton.button == Button5) + { + WM_Event *e = wm_event_list_push_new(arena, &evts, WM_EventKind_Scroll); + e->window.u64[0] = (U64)window; + e->modifiers = modifiers; + e->delta = v2f32(0, evt.xbutton.button == Button4 ? -1.f : +1.f); + e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); + } } }break; @@ -634,6 +805,26 @@ wm_get_events(Arena *arena, B32 wait) e->pos.x = (F32)evt.xmotion.x; e->pos.y = (F32)evt.xmotion.y; set_mouse_cursor = 1; + if(window->custom_border) + { + B32 in_client_area; + int moveresize_code = lnx_moveresize_code_from_pos(window, e->pos, &in_client_area); + if(!in_client_area) + { + switch(moveresize_code) + { + default:{}break; + case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:{lnx_wm_state->last_set_cursor = WM_Cursor_DownRight;}break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:{lnx_wm_state->last_set_cursor = WM_Cursor_DownRight;}break; + case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:{lnx_wm_state->last_set_cursor = WM_Cursor_UpRight;}break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:{lnx_wm_state->last_set_cursor = WM_Cursor_UpRight;}break; + case _NET_WM_MOVERESIZE_SIZE_LEFT:{lnx_wm_state->last_set_cursor = WM_Cursor_LeftRight;}break; + case _NET_WM_MOVERESIZE_SIZE_RIGHT:{lnx_wm_state->last_set_cursor = WM_Cursor_LeftRight;}break; + case _NET_WM_MOVERESIZE_SIZE_TOP:{lnx_wm_state->last_set_cursor = WM_Cursor_UpDown;}break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOM:{lnx_wm_state->last_set_cursor = WM_Cursor_UpDown;}break; + } + } + } }break; //- rjf: window focus/unfocus @@ -647,6 +838,36 @@ wm_get_events(Arena *arena, B32 wait) e->window.u64[0] = (U64)window; }break; + //- rjf: window moves & resizes + case ConfigureNotify: + { + LNX_WM_Window *window = lnx_window_from_x11window(evt.xconfigure.window); + if(!window->resize_draw) + { + window->last_synced_rect = r2f32p((F32)evt.xconfigure.x, (F32)evt.xconfigure.y, (F32)evt.xconfigure.x + (F32)evt.xconfigure.width, (F32)evt.xconfigure.y + (F32)evt.xconfigure.height); + } + }break; + + //- rjf: re-paints + case Expose: + { + LNX_WM_Window *window = lnx_window_from_x11window(evt.xexpose.window); + if(XPending(lnx_wm_state->display) == 0) + { + window->resize_draw = window->waiting_for_resize; + update(); + window->resize_draw = 0; + if(window->waiting_for_resize) + { + window->waiting_for_resize = 0; + XSyncValue sync_value; + XSyncIntsToValue(&sync_value, window->counter_value & 0xffffffffu, (int)((window->counter_value & 0xffffffff00000000ull) >> 32)); + XSyncSetCounter(lnx_wm_state->display, window->counter_xid, sync_value); + XFlush(lnx_wm_state->display); + } + } + }break; + //- rjf: client messages case ClientMessage: { @@ -661,17 +882,16 @@ wm_get_events(Arena *arena, B32 wait) LNX_WM_Window *window = lnx_window_from_x11window(evt.xclient.window); if(window != 0) { - window->counter_value = 0; - window->counter_value |= evt.xclient.data.l[2]; - window->counter_value |= (evt.xclient.data.l[3] << 32); - XSyncValue value; - XSyncIntToValue(&value, window->counter_value); - XSyncSetCounter(lnx_wm_state->display, window->counter_xid, value); + U32 counter_low = (U32)evt.xclient.data.l[2]; + U32 counter_hi = (U32)evt.xclient.data.l[3]; + window->counter_value = (counter_low | ((U64)counter_hi << 32)); + window->waiting_for_resize = 1; } } }break; } + //- rjf: set mouse cursor if(set_mouse_cursor) { Window root_window = 0; diff --git a/src/linux/window_manager/linux_window_manager.h b/src/linux/window_manager/linux_window_manager.h index 7ef8cb56..b6f2eb04 100644 --- a/src/linux/window_manager/linux_window_manager.h +++ b/src/linux/window_manager/linux_window_manager.h @@ -16,9 +16,32 @@ #include #include +//////////////////////////////// +//~ rjf: X definitions + +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + //////////////////////////////// //~ rjf: Window State +typedef struct LNX_WM_WindowClientArea LNX_WM_WindowClientArea; +struct LNX_WM_WindowClientArea +{ + LNX_WM_WindowClientArea *next; + Rng2F32 rect; +}; + typedef struct LNX_WM_Window LNX_WM_Window; struct LNX_WM_Window { @@ -28,6 +51,14 @@ struct LNX_WM_Window XIC xic; XID counter_xid; U64 counter_value; + B32 resize_draw; + Rng2F32 last_synced_rect; + B32 waiting_for_resize; + B32 custom_border; + F32 custom_title_bar_thickness; + F32 custom_edge_thickness; + LNX_WM_WindowClientArea *first_client_area; + LNX_WM_WindowClientArea *last_client_area; }; //////////////////////////////// @@ -52,6 +83,7 @@ struct LNX_WM_State Visual *window_visual; int window_depth; Colormap window_colormap; + LNX_WM_WindowClientArea *free_client_area; }; //////////////////////////////// @@ -63,5 +95,6 @@ global LNX_WM_State *lnx_wm_state = 0; //~ rjf: Helpers internal LNX_WM_Window *lnx_window_from_x11window(Window window); +internal int lnx_moveresize_code_from_pos(LNX_WM_Window *window, Vec2F32 pos, B32 *out_is_in_client_area); #endif // LINUX_WINDOW_MANAGER_H From 914c1946b0f1354754000f4b72f1ef506d810384 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 5 Aug 2026 12:47:29 -0600 Subject: [PATCH 005/251] fix overly-aggressive visualization of unmapped addresses (while read is stale); fix unaligned allocations of xsave blocks in linux_demon; fix some edges in custom window borders in x11 --- src/eval_visualization/eval_visualization_core.c | 5 ++--- src/linux/demon/linux_demon.c | 8 ++++---- src/linux/window_manager/linux_window_manager.c | 8 ++++++-- src/render/opengl/linux/render_opengl_linux.h | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/eval_visualization/eval_visualization_core.c b/src/eval_visualization/eval_visualization_core.c index 93287c5b..31272f0f 100644 --- a/src/eval_visualization/eval_visualization_core.c +++ b/src/eval_visualization/eval_visualization_core.c @@ -1764,10 +1764,9 @@ ev_string_iter_next(Arena *arena, EV_StringIter *it, String8 *out_string) { ptr_data->addr_is_good = 1; } - else if(e_space_read(ptr_data->value_eval.space, &byte, &range_info, r1u64(ptr_data->value_eval.value.u64, ptr_data->value_eval.value.u64+1)) && - !(range_info.flags & E_SpaceRangeFlag_Stale)) + else if(e_space_read(ptr_data->value_eval.space, &byte, &range_info, r1u64(ptr_data->value_eval.value.u64, ptr_data->value_eval.value.u64+1))) { - ptr_data->addr_is_good = !(byte_bad_flags & 1); + ptr_data->addr_is_good = (range_info.flags & E_SpaceRangeFlag_Stale) || !(byte_bad_flags & 1); } } if(ptr_data->did_redirect) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 4c85d7dc..5f2183ba 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1526,7 +1526,7 @@ lnx_dmn_thread_read_reg_block(LNX_DMN_Thread *thread) // get xsave if(x64_is_xsave_supported()) { - void *xsave_raw = push_array(scratch.arena, U8, process_ctx->xsave_size); + void *xsave_raw = push_array_aligned(scratch.arena, U8, process_ctx->xsave_size, 64); int ptrace_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_len = process_ctx->xsave_size, .iov_base = xsave_raw })); if(ptrace_result < 0) { goto exit; } xsave = xsave_raw; @@ -1536,7 +1536,7 @@ lnx_dmn_thread_read_reg_block(LNX_DMN_Thread *thread) // get fxsave if(fxsave == 0) { - fxsave = push_array(scratch.arena, X64_FXSave, 1); + fxsave = push_array_aligned(scratch.arena, X64_FXSave, 1, 64); int ptrace_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_len = sizeof(*fxsave), .iov_base = fxsave })); if(ptrace_result < 0) { goto exit; } fxsave = 0; @@ -1739,8 +1739,8 @@ lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread) if(x64_is_xsave_supported()) { - U8 *xsave_raw = push_array(scratch.arena, U8, process_ctx->xsave_size); - X64_XSave *dst = (X64_XSave *)xsave_raw; + U8 *xsave_raw = push_array_aligned(scratch.arena, U8, process_ctx->xsave_size, 64); + X64_XSave *dst = (X64_XSave *)xsave_raw; dst->fxsave = dst_fxsave; dst->header.xstate_bv |= X64_XStateComponentFlag_FP; dst->header.xstate_bv |= X64_XStateComponentFlag_SSE; diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index dda872d4..341cd13c 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -48,7 +48,7 @@ lnx_moveresize_code_from_pos(LNX_WM_Window *window, Vec2F32 pos, B32 *out_is_in_ Rng2F32 title_bar_rect = r2f32p(window_rect.x0 + window->custom_edge_thickness, window_rect.y0 + window->custom_edge_thickness, window_rect.x1 - window->custom_edge_thickness, - window_rect.y0 + window->custom_title_bar_thickness); + window_rect.y0 + window->custom_edge_thickness + window->custom_title_bar_thickness); if(contains_2f32(title_bar_rect, pos)) { moveresize_code = _NET_WM_MOVERESIZE_MOVE; @@ -87,6 +87,10 @@ lnx_moveresize_code_from_pos(LNX_WM_Window *window, Vec2F32 pos, B32 *out_is_in_ { moveresize_code = _NET_WM_MOVERESIZE_SIZE_BOTTOM; } + else + { + in_non_client_area = 0; + } } } } @@ -807,7 +811,7 @@ wm_get_events(Arena *arena, B32 wait) set_mouse_cursor = 1; if(window->custom_border) { - B32 in_client_area; + B32 in_client_area = 0; int moveresize_code = lnx_moveresize_code_from_pos(window, e->pos, &in_client_area); if(!in_client_area) { diff --git a/src/render/opengl/linux/render_opengl_linux.h b/src/render/opengl/linux/render_opengl_linux.h index 3cab3328..823e104e 100644 --- a/src/render/opengl/linux/render_opengl_linux.h +++ b/src/render/opengl/linux/render_opengl_linux.h @@ -14,7 +14,7 @@ //~ rjf: Decide On Backend #if !defined(R_OPENGL_LINUX_BACKEND) -# define R_OPENGL_LINUX_BACKEND R_OPENGL_LINUX_BACKEND_GLX +# define R_OPENGL_LINUX_BACKEND R_OPENGL_LINUX_BACKEND_EGL #endif //////////////////////////////// From 2f4aa6567e17f60002869c405270651207ef64f3 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 5 Aug 2026 13:54:48 -0600 Subject: [PATCH 006/251] x11 icons; fix edit box clipping / scrolling in decorated cells --- build.sh | 6 +- .../generated/linux_window_manager.meta.c | 5 + .../generated/linux_window_manager.meta.h | 139 ++++++++++++++++ .../window_manager/linux_window_manager.c | 149 +++++++++++++++++- .../window_manager/linux_window_manager.h | 7 + .../window_manager/linux_window_manager.mdesk | 4 + src/raddbg/raddbg_widgets.c | 9 +- 7 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 src/linux/window_manager/generated/linux_window_manager.meta.c create mode 100644 src/linux/window_manager/generated/linux_window_manager.meta.h create mode 100644 src/linux/window_manager/linux_window_manager.mdesk diff --git a/build.sh b/build.sh index 8dccc60f..241e6f4a 100755 --- a/build.sh +++ b/build.sh @@ -5,7 +5,6 @@ cd "$(dirname "$0")" # --- Unpack Arguments -------------------------------------------------------- for arg in "$@"; do declare $arg='1'; done if [[ "$#" == "0" ]]; then raddbg='1'; fi - cc_sanitize="" if [[ "${asan:-0}" == "1" ]]; then echo "[asan enabled]" @@ -39,6 +38,9 @@ else cc_render="-lGL -lEGL" fi +# --- Icons ------------------------------------------------------------------- +cc_icon="-DLNX_WM_ICON=1" + # --- Choose Compile/Link Lines ----------------------------------------------- if [[ "${gcc:-0}" == "1" ]]; then compiler="${CC:-gcc} $cc_cflags_gcc"; echo "[gcc compile]"; elif [[ "${clang:-1}" == "1" ]]; then compiler="${CC:-clang} $cc_cflags_clang"; echo "[clang compile]"; @@ -62,7 +64,7 @@ fi # --- Build Everything (@build_targets) --------------------------------------- cd build -if [[ "${raddbg:-0}" == "1" ]]; then didbuild=1 && $compile ../src/raddbg/raddbg_main.c $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg; fi +if [[ "${raddbg:-0}" == "1" ]]; then didbuild=1 && $compile ../src/raddbg/raddbg_main.c $cc_icon $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg; fi if [[ "${raddbg_non_graphical:-0}" == "1" ]]; then didbuild=1 && $compile ../src/raddbg/raddbg_main.c -DWM_STUB=1 -DR_BACKEND=R_BACKEND_STUB $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg_non_graphical; fi if [[ "${radbin:-0}" == "1" ]]; then didbuild=1 && $compile ../src/radbin/radbin_main.c $cc_link -o radbin; fi if [[ "${radlink:-0}" == "1" ]]; then didbuild=1 && $compile ../src/linker/lnk.c $cc_link -o radlink; fi diff --git a/src/linux/window_manager/generated/linux_window_manager.meta.c b/src/linux/window_manager/generated/linux_window_manager.meta.c new file mode 100644 index 00000000..0378fad9 --- /dev/null +++ b/src/linux/window_manager/generated/linux_window_manager.meta.c @@ -0,0 +1,5 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + diff --git a/src/linux/window_manager/generated/linux_window_manager.meta.h b/src/linux/window_manager/generated/linux_window_manager.meta.h new file mode 100644 index 00000000..653eff4d --- /dev/null +++ b/src/linux/window_manager/generated/linux_window_manager.meta.h @@ -0,0 +1,139 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +#ifndef LINUX_WINDOW_MANAGER_META_H +#define LINUX_WINDOW_MANAGER_META_H + +C_LINKAGE_BEGIN +read_only global U8 lnx_wm_icon_file_bytes__data[] = +{ +0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x20,0x00,0x02,0x1e,0x00,0x00,0x16,0x00,0x00,0x00,0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x08,0x06,0x00,0x00,0x00,0x5c,0x72,0xa8,0x66,0x00,0x00,0x1d,0xc9,0x49,0x44,0x41,0x54,0x78, +0x9c,0xed,0xdd,0x7b,0x7c,0x14,0xe5,0xbd,0xc7,0xf1,0xcf,0x6e,0xb2,0x9b,0x04,0x12,0x20,0x10,0xae,0xe2,0x25,0x52,0x06,0x05,0xe5,0x12,0x14,0x6f,0xc7,0x56,0x99,0x5a,0xd4,0xaa,0xd5,0xda,0x8b,0xb7,0xa2,0x55,0x4f,0x6d,0x6b,0xab,0xb5,0xf6,0x72,0x7a,0xb1,0xb5,0x1e,0x41,0xce,0xd1,0xb6,0x5a,0x4f,0xeb,0xa9,0x56,0x8b,0x37,0xb4,0x5a, +0xfb,0xaa,0x56,0x5a,0xa4,0x1d,0xb5,0x20,0x2d,0x95,0x8a,0x8a,0xf5,0xa0,0x83,0x8a,0x37,0x10,0x08,0x90,0x70,0x0b,0xe4,0xb6,0x7b,0xfe,0x98,0x8d,0x44,0x48,0x42,0x36,0xf3,0xcc,0xfc,0x66,0x77,0x7e,0xef,0xd7,0x8b,0x57,0xbc,0x24,0xcf,0xf3,0xcb,0x32,0xcf,0x77,0x9f,0x99,0x9d,0x79,0x1e,0x50,0x4a,0x29,0xa5,0x94,0x52,0x4a,0x29,0xa5, +0x94,0x52,0x4a,0x29,0xa5,0x94,0x52,0x4a,0x29,0xa5,0x94,0x52,0x4a,0x29,0xa5,0x54,0xe1,0x4a,0x48,0x17,0xa0,0x82,0xe5,0x58,0x54,0x01,0xd5,0xc0,0xa0,0x4e,0x5f,0x3b,0xff,0x33,0x40,0x23,0xd0,0x90,0xfb,0xda,0xf9,0x9f,0x1b,0x6c,0x97,0x6d,0x61,0xd7,0xac,0xc2,0xa3,0x01,0x50,0xa0,0x1c,0x8b,0xa1,0xc0,0x54,0x60,0x0a,0x70,0x30,0x7b, +0x0f,0xec,0x6a,0x60,0x20,0x50,0xe2,0xb3,0xab,0x76,0x60,0x0b,0x9d,0x42,0x81,0xdd,0x41,0xf1,0x06,0xf0,0x3c,0xf0,0x9c,0xed,0x52,0xef,0xb3,0x1f,0x25,0x40,0x03,0xa0,0x00,0x38,0x16,0xfb,0x03,0x75,0x78,0x83,0xbd,0x2e,0xf7,0x67,0x3f,0xd1,0xa2,0xf6,0xb6,0x06,0x58,0xde,0xf9,0x8f,0xed,0xf2,0xae,0x6c,0x49,0x6a,0x5f,0x34,0x00,0x22, +0xc4,0xb1,0x48,0xe0,0xbd,0x9b,0xd7,0x75,0xfa,0x33,0x05,0x18,0x2a,0x59,0x97,0x0f,0xf5,0x78,0x33,0x84,0xce,0xa1,0xf0,0xba,0x6c,0x49,0xaa,0x33,0x0d,0x80,0x88,0x70,0x2c,0x7e,0x0f,0x9c,0x80,0x37,0x6d,0x2f,0x66,0x5b,0x80,0xa7,0x6d,0x97,0x33,0xa5,0x0b,0x51,0x1a,0x00,0x91,0xe0,0x58,0xd4,0xe2,0x9d,0x4f,0xc7,0x49,0xad,0xed,0xf2, +0xa6,0x74,0x11,0x71,0x97,0x94,0x2e,0x40,0x01,0x30,0x5d,0xba,0x00,0x01,0x71,0xfc,0x9d,0x23,0x47,0x03,0x20,0x1a,0xe2,0x38,0x18,0xe2,0xf8,0x3b,0x47,0x8e,0x06,0x40,0x34,0x9c,0x28,0x5d,0x80,0x80,0x38,0xfe,0xce,0x91,0xa3,0x01,0x20,0xcc,0xb1,0x38,0x04,0x18,0x29,0x5d,0x87,0x80,0x51,0x8e,0xc5,0x38,0xe9,0x22,0xe2,0x4e,0x03,0x40, +0x5e,0x9c,0xa7,0xc2,0x71,0xfe,0xdd,0x23,0x41,0x03,0x40,0x5e,0x9c,0x07,0x41,0x9c,0x7f,0xf7,0x48,0xd0,0x8f,0x01,0x05,0xe5,0x6e,0xfc,0xa9,0x07,0x86,0x48,0xd7,0x22,0x64,0x23,0x30,0xcc,0x76,0xc9,0x4a,0x17,0x12,0x57,0x3a,0x03,0x90,0x35,0x91,0xf8,0x0e,0x7e,0x80,0x1a,0xe0,0x70,0xe9,0x22,0xe2,0x4c,0x03,0x40,0x96,0x4e,0x81,0xf5, +0x35,0x10,0xa5,0x01,0x20,0x4b,0x0f,0x7e,0x7d,0x0d,0x44,0xe9,0x35,0x00,0x21,0x8e,0x45,0x09,0xb0,0x19,0x18,0x20,0x5d,0x8b,0xb0,0x2d,0xc0,0x10,0xdb,0xa5,0x5d,0xba,0x90,0x38,0xd2,0x19,0x80,0x9c,0xa9,0xe8,0xe0,0x07,0xef,0xe1,0xa7,0x3a,0xe9,0x22,0xe2,0x4a,0x03,0x40,0x8e,0x4e,0x7d,0x77,0xd3,0xd7,0x42,0x88,0x06,0x80,0x1c,0x3d, +0xe8,0x77,0xd3,0xd7,0x42,0x88,0x5e,0x03,0x10,0xe0,0x58,0xa4,0xf1,0x96,0xd6,0xea,0x27,0x5d,0x4b,0x44,0x34,0x01,0x83,0x6c,0x97,0x56,0xe9,0x42,0xe2,0x46,0x67,0x00,0x32,0x8e,0x42,0x07,0x7f,0x67,0xfd,0xf0,0x5e,0x13,0x15,0x32,0x0d,0x00,0x19,0x3a,0xe5,0xdd,0x9b,0xbe,0x26,0x02,0x34,0x00,0x64,0xe8,0xc1,0xbe,0x37,0x7d,0x4d,0x04, +0xe8,0x35,0x80,0x90,0x39,0x16,0x15,0x78,0x4b,0x6a,0xa7,0xa5,0x6b,0x89,0x98,0x66,0xa0,0xda,0x76,0xd9,0x29,0x5d,0x48,0x9c,0xe8,0x0c,0x20,0x7c,0xc7,0xa1,0x83,0xbf,0x2b,0x65,0xc0,0xb1,0xd2,0x45,0xc4,0x8d,0x06,0x40,0xf8,0x74,0xaa,0xdb,0x3d,0x7d,0x6d,0x42,0xa6,0x01,0x10,0x3e,0x3d,0xc8,0xbb,0xa7,0xaf,0x4d,0xc8,0xf4,0x1a,0x40, +0x88,0x1c,0x8b,0x01,0x78,0xf7,0xff,0xfb,0xdd,0xae,0xab,0x58,0xb5,0x01,0x83,0x75,0x3f,0xc2,0xf0,0xe8,0x0c,0x20,0x5c,0xc7,0xa3,0x83,0xbf,0x27,0xa5,0x78,0xaf,0x91,0x0a,0x89,0xce,0x00,0x42,0xe0,0x58,0x94,0x03,0x5f,0x00,0xbe,0x0d,0x8c,0x12,0x2e,0x27,0xea,0xd6,0x02,0x73,0x80,0x3b,0x6c,0x97,0x5d,0xd2,0xc5,0x14,0x3b,0x0d,0x80, +0x00,0xe5,0x3e,0xf2,0xfb,0x22,0xf0,0x2d,0x60,0x84,0x70,0x39,0x85,0xe6,0x3d,0xe0,0x46,0xe0,0x7f,0xf5,0xa3,0xc1,0xe0,0x68,0x00,0x04,0xc0,0xb1,0xe8,0x0f,0x7c,0x19,0xb8,0x1a,0x18,0x2e,0x5c,0x4e,0xa1,0x5b,0x0f,0xdc,0x04,0xdc,0x66,0xbb,0xec,0x90,0x2e,0xa6,0xd8,0x68,0x00,0x18,0xe4,0x58,0x54,0x02,0x5f,0x01,0xbe,0x4e,0xe1,0xee, +0xe8,0x1b,0x55,0xf5,0xc0,0x4f,0x80,0xff,0xb1,0x5d,0xb6,0x4b,0x17,0x53,0x2c,0x34,0x00,0x0c,0xc8,0x5d,0xdd,0xbf,0x02,0xf8,0x1a,0xf1,0x5e,0xe4,0x33,0x0c,0x9b,0x80,0x9b,0x81,0x9f,0xd9,0x2e,0x5b,0xa5,0x8b,0x29,0x74,0x05,0x15,0x00,0x8e,0x45,0x12,0xf8,0x11,0xb0,0x1a,0x58,0x60,0xbb,0xac,0x15,0xae,0x27,0x0d,0x7c,0x13,0xf8,0x06, +0x30,0x48,0xb2,0x96,0x18,0x6a,0x00,0x7e,0x0c,0xdc,0x68,0xbb,0xb4,0x48,0x16,0xe2,0x58,0x8c,0x02,0x4e,0x06,0x0e,0x02,0xae,0xb5,0x5d,0x32,0x92,0xf5,0xe4,0xa3,0xd0,0x02,0xe0,0x66,0xe0,0xca,0x4e,0xff,0xe9,0x5f,0xc0,0x02,0xe0,0x09,0x60,0xb1,0xed,0xd2,0x1c,0x62,0x2d,0x75,0xc0,0x5c,0x74,0x59,0x6b,0x69,0x2f,0x01,0x17,0xda,0x2e, +0xcf,0x87,0xd5,0xa1,0x63,0x51,0x86,0xf7,0x71,0xe5,0x0c,0xbc,0x81,0x7f,0x58,0xa7,0xff,0x7d,0xb3,0xed,0x72,0x55,0x58,0xb5,0xf8,0x55,0x30,0x01,0xe0,0x58,0x5c,0x89,0x37,0xf5,0xeb,0x4e,0x13,0xf0,0x34,0xb9,0x40,0xb0,0x5d,0xdc,0x80,0xea,0x48,0x01,0xd7,0x00,0xdf,0xc1,0xfb,0xdc,0x5a,0xc9,0x6b,0x03,0x6e,0x00,0xfe,0x33,0xa8,0x45, +0x45,0x1c,0x0b,0x8b,0xdd,0x03,0xfe,0x04,0x7a,0x5e,0xcf,0xe1,0x4a,0xdb,0xe5,0x67,0x41,0xd4,0x61,0x5a,0x41,0x04,0x80,0x63,0x71,0x26,0xf0,0x08,0xf9,0xdd,0xb8,0xb4,0x1a,0x6f,0x66,0xb0,0x00,0x78,0xd2,0xc4,0xdd,0x65,0x8e,0xc5,0x14,0xbc,0x77,0xfd,0x89,0x7e,0xdb,0x52,0x81,0x58,0x01,0x5c,0x64,0x62,0x36,0xe0,0x58,0x54,0x01,0x36, +0xde,0xa0,0x9f,0x01,0xd4,0xe6,0xf1,0xe3,0x19,0xe0,0x93,0xb6,0xcb,0xa3,0x7e,0xeb,0x08,0x5a,0xe4,0x03,0xc0,0xb1,0x98,0x86,0xf7,0xce,0x5e,0xe1,0xa3,0x99,0x56,0xe0,0x6f,0xec,0x0e,0x84,0x17,0xf2,0xd9,0x8e,0x2a,0xf7,0xae,0xff,0x7d,0xe0,0xbb,0xe8,0xbb,0x7e,0xd4,0xb5,0x02,0xb3,0x81,0x59,0xf9,0xcc,0x06,0x72,0xdb,0xb4,0x4d,0x61, +0xf7,0xbb,0xfc,0x31,0x40,0xca,0x47,0x1d,0x4d,0xc0,0x09,0xb6,0xcb,0x32,0x1f,0x6d,0x04,0x2e,0xd2,0x01,0xe0,0x58,0xd4,0x02,0x4b,0x81,0x61,0x86,0x9b,0x5e,0x0f,0x2c,0xc4,0x0b,0x84,0x85,0xb6,0x4b,0x7d,0x0f,0x35,0x4c,0xc6,0x7b,0xd7,0x9f,0x64,0xb8,0x06,0x15,0xac,0x17,0xf1,0xae,0x0d,0xbc,0xd8,0xdd,0x37,0x38,0x16,0x43,0x81,0x8f, +0xe1,0x0d,0xf8,0x93,0x30,0x7f,0xcf,0xc6,0x06,0xe0,0x68,0xdb,0x65,0xb5,0xe1,0x76,0x8d,0x89,0x6c,0x00,0x38,0x16,0xd5,0x78,0xef,0xda,0x87,0x04,0xdc,0x55,0x16,0x58,0xce,0xee,0xd9,0xc1,0xdf,0x6d,0x97,0xb6,0xdc,0xbb,0xfe,0xf7,0xf0,0xde,0xf5,0xfd,0xbc,0x13,0x28,0x39,0xad,0xc0,0x2c,0x60,0xb6,0xed,0xd2,0xea,0x58,0x94,0xe2,0xbd, +0xb3,0x9f,0x8c,0xf7,0x4e,0x5f,0x47,0xf0,0x63,0xe0,0x15,0xe0,0x58,0xdb,0xa5,0x21,0xe0,0x7e,0xfa,0x24,0x92,0x01,0x90,0xbb,0xca,0xba,0x10,0xf8,0xb0,0x40,0xf7,0x5b,0x01,0x07,0xef,0x9c,0x6f,0xb2,0x40,0xff,0xca,0xbc,0x17,0xf0,0xae,0x09,0xd9,0xc8,0x6c,0xc6,0xb2,0x08,0x38,0x49,0xfa,0xe3,0xca,0xae,0x44,0x2e,0x00,0x72,0xe7,0x62, +0xf7,0x03,0xe7,0x4a,0xd7,0xa2,0x94,0x41,0xf3,0x80,0x0b,0xa2,0xb6,0x15,0x7a,0x14,0x1f,0x07,0xbe,0x1e,0x1d,0xfc,0xaa,0xf8,0x9c,0x07,0xfc,0xa7,0x74,0x11,0x7b,0x8a,0xd4,0x0c,0xc0,0xb1,0xb8,0x14,0xb8,0x43,0xba,0x0e,0xa5,0x02,0x74,0xa9,0xed,0x72,0xa7,0x74,0x11,0x1d,0x22,0x13,0x00,0x8e,0xc5,0x47,0x81,0x3f,0xa1,0x1f,0xb3,0xa9, +0xe2,0xd6,0x06,0x9c,0x62,0xbb,0xfc,0x45,0xba,0x10,0x88,0x48,0x00,0xe4,0xae,0xf8,0xbf,0x0c,0x8c,0x94,0xae,0x45,0xa9,0x10,0xbc,0x07,0x4c,0x88,0xc2,0x27,0x03,0x51,0xb9,0x06,0xf0,0x33,0x74,0xf0,0xab,0xf8,0x18,0x09,0xdc,0x22,0x5d,0x04,0x44,0x60,0x06,0xe0,0x58,0x9c,0x0e,0x3c,0x26,0x5d,0x87,0x52,0x02,0x4e,0xb7,0x5d,0x1e,0x97, +0x2c,0x40,0x34,0x00,0x74,0xea,0xaf,0x62,0x4e,0xfc,0x54,0x40,0xfa,0x14,0xe0,0x16,0x74,0xf0,0xab,0xf8,0x1a,0x49,0xcf,0x4f,0xb8,0x06,0x4e,0x6c,0x06,0xa0,0x53,0x7f,0xa5,0xde,0x27,0x76,0x2a,0x20,0x12,0x00,0x3a,0xf5,0x57,0xea,0x03,0xd6,0xe2,0x9d,0x0a,0x34,0x86,0xdd,0xb1,0xd4,0x29,0x80,0x4e,0xfd,0x95,0xda,0x6d,0x14,0x42,0x9f, +0x0a,0x84,0x3e,0x03,0xd0,0xa9,0xbf,0x52,0xdd,0x0a,0xfd,0x54,0x20,0xd4,0x00,0xd0,0xa9,0xbf,0x52,0x3d,0x0a,0xfd,0x54,0x20,0xec,0x53,0x00,0x9d,0xfa,0x2b,0xd5,0xbd,0xd0,0x4f,0x05,0x42,0x9b,0x01,0x38,0x16,0xa7,0x01,0x7f,0x08,0xab,0x3f,0xa5,0x0a,0x58,0x68,0xa7,0x02,0xa1,0x04,0x40,0x6e,0x81,0x8f,0x55,0xc0,0xfe,0x61,0xf4,0xa7, +0x54,0x81,0x7b,0x07,0xf8,0x50,0x18,0x0b,0x88,0x84,0x75,0x0a,0x70,0x09,0x3a,0xf8,0x95,0xea,0xad,0xfd,0xf1,0xc6,0x4c,0xe0,0x02,0x9f,0x01,0xe4,0x76,0xcf,0x79,0x0d,0x0d,0x00,0xa5,0xf2,0xf1,0x36,0x30,0x36,0xe8,0x59,0x40,0x18,0x33,0x80,0xcf,0xa3,0x83,0x5f,0xa9,0x7c,0x1d,0x00,0x5c,0x14,0x74,0x27,0x81,0xce,0x00,0x72,0x2b,0xeb, +0xae,0x02,0x0e,0x0c,0xb2,0x1f,0xa5,0x8a,0xd4,0x5b,0x78,0xb3,0x80,0x40,0x76,0x3b,0x82,0xe0,0x67,0x00,0x17,0xa2,0x83,0x5f,0xa9,0xbe,0x3a,0x10,0x98,0x19,0x64,0x07,0x81,0xcd,0x00,0x72,0x6b,0xb0,0xbb,0xe4,0xb7,0xa5,0x92,0x52,0xea,0x83,0x56,0x03,0x96,0xed,0xd2,0x16,0x44,0xe3,0x41,0xce,0x00,0x66,0xa2,0x83,0x5f,0x29,0xbf,0x6a, +0x81,0xcf,0x05,0xd5,0x78,0x20,0x33,0x80,0xdc,0xbb,0xff,0xab,0xc0,0xc1,0x41,0xb4,0xaf,0x54,0xcc,0xbc,0x0e,0x8c,0xb3,0x5d,0xda,0x4d,0x37,0x1c,0xd4,0x0c,0xe0,0x7c,0x74,0xf0,0x2b,0x65,0xca,0x18,0xbc,0x31,0x65,0x9c,0xf1,0x19,0x80,0x63,0x51,0x82,0xb7,0x1f,0xda,0x87,0x4c,0xb7,0xad,0x54,0x8c,0xad,0x02,0x0e,0x35,0x3d,0x0b,0x08, +0x62,0x0d,0xfe,0xf3,0xd0,0xc1,0xdf,0xa3,0x64,0x2a,0xcd,0x90,0xe9,0xa7,0x33,0xea,0xd3,0x97,0x52,0x7d,0xe4,0x47,0x00,0xc8,0x66,0xb3,0x40,0x16,0x32,0x19,0xef,0x9f,0x73,0x7f,0xb2,0xd9,0xcc,0xfb,0xff,0x4c,0x36,0x4b,0x36,0x93,0xf1,0xbe,0x2f,0x9b,0xf5,0xbe,0xaf,0xf3,0xf7,0x93,0xfb,0xff,0xdd,0xfc,0x7c,0xc7,0xf7,0x43,0x76,0xf7, +0xcf,0x7c,0xa0,0xbf,0x3d,0xfa,0xee,0xa2,0x2f,0xb2,0x59,0xb2,0x74,0xf5,0xb3,0xbd,0xab,0xb5,0xf3,0xcf,0xaf,0x7b,0xf4,0x3e,0x36,0x3e,0xa9,0x4f,0x86,0xf7,0xd2,0x58,0xbc,0x1d,0xb3,0xee,0x33,0xd9,0xa8,0xd1,0x19,0x80,0x63,0x91,0x04,0x56,0x02,0x96,0xc9,0x76,0x8b,0x59,0xbf,0x03,0xc7,0x32,0xf2,0x33,0x97,0x32,0xf2,0xac,0x8b,0x48, +0x0f,0x31,0xbd,0x0b,0x7a,0xb4,0x65,0xdb,0x5a,0x59,0xf1,0xc5,0x33,0xd8,0xb4,0x78,0x81,0x74,0x29,0x85,0xe2,0x55,0x60,0xbc,0xed,0x92,0x31,0xd5,0xa0,0xe9,0x00,0x38,0x07,0x78,0xc0,0x64,0x9b,0x71,0x91,0x4c,0xa5,0x19,0x72,0xe2,0x69,0x1c,0x70,0xf1,0x37,0x18,0x38,0xe5,0x18,0xe9,0x72,0x42,0x93,0x69,0xde,0xc5,0x8b,0x97,0x9e,0x42, +0xc3,0xb3,0x4f,0x4b,0x97,0x52,0x28,0xce,0xb5,0x5d,0x1e,0x34,0xd5,0x98,0xe9,0x8b,0x80,0xa1,0x3c,0xc0,0x50,0x8c,0x32,0xad,0x2d,0xd4,0x2f,0xfc,0x1d,0xcb,0xcf,0x3f,0x9e,0x55,0x37,0x7c,0x9d,0x4c,0xf3,0x2e,0xe9,0x92,0x42,0x91,0x2c,0x2b,0x67,0xe2,0xed,0x8f,0x33,0x60,0xf2,0xd1,0xd2,0xa5,0x14,0x8a,0x8b,0x4d,0x36,0x66,0x6c,0x06, +0xe0,0x58,0x0c,0xc3,0x5b,0xd1,0xa4,0xc4,0x54,0x9b,0x71,0xd6,0xef,0xe0,0x43,0x18,0x3f,0x67,0x2e,0x03,0x26,0x1d,0x25,0x5d,0x4a,0x28,0xda,0xb6,0x36,0xb2,0x7c,0xe6,0x89,0x6c,0x5f,0xf9,0x82,0x74,0x29,0x51,0xd7,0x0e,0x8c,0xb4,0x5d,0xea,0x4d,0x34,0x66,0x72,0x06,0xf0,0x69,0x74,0xf0,0x1b,0xd3,0xf4,0xc6,0x2b,0x3c,0x77,0xee,0xbf, +0xf1,0xfa,0x8f,0xbf,0x43,0xa6,0x35,0xf0,0xc7,0xc2,0xc5,0x95,0x0e,0x18,0xc4,0x94,0x5f,0xff,0x99,0xfe,0x1f,0x1a,0x2f,0x5d,0x4a,0xd4,0x95,0xe0,0x8d,0x35,0x23,0x4c,0x06,0xc0,0x39,0x06,0xdb,0x52,0x40,0xb6,0xbd,0x8d,0xb7,0x6e,0x9f,0xc3,0xb2,0xb3,0xa6,0xd2,0xb4,0xfa,0x55,0xe9,0x72,0x02,0x97,0xaa,0xae,0x61,0xca,0xdc,0xbf,0x50, +0x71,0xc0,0x18,0xe9,0x52,0xa2,0xce,0xd8,0x58,0x33,0x72,0x0a,0xe0,0x58,0x8c,0xc6,0x7b,0x7e,0x59,0x7c,0xaf,0xc1,0x62,0x55,0x36,0x7c,0x3f,0xea,0xe6,0x2d,0xa6,0x62,0x74,0xf1,0xdf,0x5d,0xbd,0x6b,0xed,0x5b,0x2c,0x3f,0xff,0xc3,0xec,0x5a,0xfb,0xb6,0x74,0x29,0x51,0x95,0x05,0xf6,0xb7,0x5d,0xd6,0xf8,0x6d,0xc8,0x54,0x00,0x5c,0x0d, +0xdc,0x64,0xa2,0xad,0x42,0x52,0x5a,0x99,0xa0,0xfa,0xb0,0x34,0xe5,0x43,0x4b,0x28,0xab,0x4e,0x92,0xae,0x4e,0x52,0x36,0x38,0x49,0x32,0x95,0xa0,0x79,0x73,0x86,0x96,0xcd,0x19,0x9a,0x1b,0xda,0x69,0xde,0x94,0xa1,0x71,0x65,0x2b,0xbb,0xea,0xfd,0xdd,0xc3,0x51,0x31,0xba,0x96,0xba,0x79,0x8b,0x29,0x1b,0xbe,0x9f,0xa1,0xdf,0x20,0xba, +0x9a,0xde,0x5a,0xc5,0xf2,0xf3,0x3e,0x4c,0xcb,0xc6,0x75,0xd2,0xa5,0x44,0xd5,0xd5,0xb6,0xcb,0x4f,0xfc,0x36,0x62,0x2a,0x00,0x96,0x01,0x47,0x98,0x68,0x2b,0xea,0xca,0x6b,0x4a,0xa8,0x99,0x96,0x66,0xe8,0xb4,0x32,0x06,0x8d,0x4f,0x93,0xc8,0xe3,0xaa,0xc7,0xf6,0xd5,0x6d,0xd4,0x3f,0xdb,0x4c,0xfd,0xb2,0x66,0xb6,0xbf,0xd9,0xe6,0xe5, +0x78,0x9e,0xfa,0xd5,0x8e,0xa3,0xee,0xfe,0x45,0xb1,0xb8,0x67,0x60,0xc7,0xaa,0x97,0x59,0xfe,0xb9,0x13,0x68,0x6d,0xd8,0x28,0x5d,0x4a,0x14,0x2d,0xb3,0x5d,0xa6,0xf9,0x6d,0xc4,0x77,0x00,0x38,0x16,0x63,0xf0,0x96,0xfc,0x2a,0x6a,0xfd,0x46,0x95,0x30,0xe6,0x82,0x4a,0x86,0x1e,0x55,0x66,0xa4,0xbd,0x6d,0xaf,0xb7,0xf1,0xda,0xbd,0xdb, +0x69,0x78,0x29,0xff,0x0b,0x7c,0x95,0xe3,0x26,0x52,0x77,0xdf,0xd3,0x94,0x0e,0xa8,0x36,0x52,0x4b,0x94,0x6d,0x7b,0x79,0x39,0xcf,0x5f,0x38,0x9d,0xb6,0x6d,0x5b,0xa4,0x4b,0x89,0xa2,0x31,0xb6,0xcb,0x1b,0x7e,0x1a,0x30,0x71,0x11,0xb0,0xa8,0x2f,0xfe,0xa5,0xab,0x93,0x8c,0xbb,0xac,0x8a,0xa3,0x6e,0x19,0x62,0x6c,0xf0,0x03,0x54,0x8d, +0x29,0x65,0xca,0xb5,0x83,0x98,0x7c,0xcd,0x20,0x2a,0x0f,0xca,0xef,0x8e,0xec,0xed,0xaf,0xae,0xe0,0x85,0x4b,0x4e,0x8e,0xc5,0xbd,0x02,0x55,0x13,0xea,0x98,0x74,0xc7,0x1f,0x29,0xe9,0x57,0x29,0x5d,0x4a,0x14,0xf9,0x1e,0x7b,0xbe,0x3f,0xb6,0xbb,0x70,0x08,0xbf,0x00,0x8a,0x72,0x3e,0x3a,0xfc,0xdf,0xca,0x99,0x72,0xed,0x20,0x06,0x8e, +0x4b,0x91,0x08,0xe8,0xf2,0x66,0xc5,0x88,0x12,0xf6,0xfb,0x58,0x05,0xc9,0x74,0x82,0xc6,0x7f,0xb5,0xf4,0xfa,0xb4,0xa0,0x79,0xfd,0x1a,0x48,0x24,0xa8,0x3e,0x7a,0x7a,0x30,0x85,0x45,0x48,0xf9,0xc8,0xfd,0x19,0x38,0xf9,0x68,0x36,0x2c,0x78,0x98,0x6c,0x5b,0x20,0xeb,0x62,0x14,0xaa,0x61,0xf7,0x6c,0xe2,0x36,0x3f,0x0d,0xf8,0x3a,0xac, +0x1d,0x8b,0x09,0xc0,0xbf,0xfc,0xb4,0x11,0x45,0x89,0x04,0xd4,0x9e,0xdb,0x9f,0x83,0xce,0xee,0x1f,0x6a,0xbf,0x9b,0x9e,0x6b,0xe1,0xe5,0x9f,0x6e,0xa1,0x6d,0x67,0xef,0x52,0x20,0x99,0x2e,0x63,0xda,0xe3,0x2f,0xd1,0xef,0xc0,0xb1,0x01,0x57,0x16,0x0d,0x9b,0xfe,0xfa,0x47,0x5e,0xba,0xfc,0xac,0x58,0xdc,0x17,0x91,0x87,0x09,0xb6,0xcb, +0xff,0xf5,0xf5,0x87,0xfd,0x9e,0x02,0x14,0xdd,0xf4,0xbf,0xa4,0x3c,0xc1,0x61,0xdf,0x1a,0x18,0xfa,0xe0,0x07,0x18,0x32,0x35,0xcd,0xd4,0x39,0xd5,0x54,0x0c,0xef,0xdd,0xc4,0x2c,0xd3,0xd2,0x8c,0x7b,0xdd,0x57,0x02,0xae,0x2a,0x3a,0x86,0x7c,0xe4,0x54,0xc6,0xff,0xf8,0x7e,0x12,0x25,0x41,0x3c,0xc4,0x5a,0xb0,0x7c,0x8d,0x41,0xbf,0x01, +0xf0,0x59,0x9f,0x3f,0x1f,0x29,0x89,0x12,0x98,0xf8,0x9d,0x81,0x0c,0x9d,0x66,0xee,0x5c,0x3f,0x5f,0xfd,0x47,0x97,0x52,0x77,0x7d,0x35,0x65,0x83,0x7b,0xf7,0x57,0xb3,0xf9,0x99,0x85,0x6c,0x58,0xf0,0x70,0xc0,0x55,0x45,0xc7,0xb0,0x19,0x9f,0xe2,0xd0,0x1b,0xee,0x22,0x91,0x94,0xda,0xd9,0x3e,0x72,0x7c,0x8d,0xc1,0x3e,0xbf,0x8a,0x8e, +0x45,0x1d,0xde,0x33,0xca,0x45,0xc3,0xba,0xa4,0x8a,0xea,0xc3,0xd2,0xd2,0x65,0x50,0x36,0x38,0xc9,0xe1,0xdf,0x1e,0x48,0x32,0xdd,0xbb,0x33,0xb4,0x55,0xb3,0xaf,0xa2,0xbd,0x69,0x7b,0xc0,0x55,0x45,0xc7,0x88,0x4f,0x7c,0x0e,0xeb,0x07,0x3f,0x27,0xb0,0x0b,0x33,0x85,0xc5,0xca,0x8d,0xc5,0x3e,0xe9,0xf3,0x45,0xc0,0x0b,0x87,0xd0,0x04, +0x9c,0x08,0x8c,0xee,0x6b,0x1b,0x51,0xb2,0xdf,0xc9,0x15,0xd4,0x7e,0x26,0xfc,0x69,0x7f,0x77,0xca,0x06,0x97,0x50,0x31,0xbc,0x84,0xfa,0x7f,0x34,0xef,0xf3,0x7b,0xdb,0x77,0x6c,0xa3,0x75,0xd3,0x06,0x4a,0xaa,0x06,0xd0,0xb2,0x61,0x2d,0xad,0x9b,0xd6,0xd3,0xda,0xb0,0x91,0xb6,0xad,0x0d,0xb4,0x6d,0xdf,0x4a,0x66,0xe7,0x0e,0x32,0xcd, +0xbb,0xc8,0xb6,0xb5,0x92,0xcd,0x64,0x48,0x24,0x13,0x05,0xff,0x0e,0x3a,0xe0,0xf0,0x23,0x28,0xe9,0x5f,0xc5,0xe6,0x25,0x7f,0x96,0x2e,0x45,0xda,0x52,0xe0,0xd6,0x7b,0x36,0xb1,0xef,0x03,0xa5,0x0b,0x7e,0x2f,0x02,0x0e,0x00,0xfe,0x04,0x1c,0xeb,0xa7,0x1d,0x69,0x03,0xac,0x14,0x53,0x67,0x55,0x93,0x88,0xe0,0x98,0x58,0x75,0xd7,0x76, +0xde,0x99,0xdf,0x64,0xbc,0xdd,0x44,0x32,0x49,0xa2,0x34,0x45,0x22,0x95,0x26,0x99,0x4a,0xbf,0xff,0x35,0x99,0x4a,0x93,0x48,0x7f,0xf0,0xbf,0x75,0xf7,0x3d,0x89,0xd2,0xd4,0xee,0x7f,0xcf,0x7d,0x4d,0x0d,0x1a,0xc2,0x7e,0xe7,0x5f,0x4e,0xa2,0x24,0x9c,0xe7,0xc2,0x56,0x7c,0xf1,0x74,0x36,0x3e,0x15,0xca,0x46,0xba,0x51,0xb4,0x04,0x38, +0xc5,0x76,0xd9,0xd6,0xd7,0x06,0x7c,0x5d,0x4d,0xb1,0x5d,0xb6,0x3a,0x16,0x33,0x80,0xf9,0xc0,0x87,0xfd,0xb4,0x25,0x26,0x01,0x63,0x2f,0xaa,0x8c,0xe4,0xe0,0x07,0xa8,0x3d,0xa7,0x3f,0xeb,0x16,0xed,0xa2,0x75,0x9b,0xb1,0x45,0x60,0x00,0xc8,0x66,0x32,0x64,0x5b,0x9a,0xa1,0xa5,0xd9,0xd8,0x22,0x73,0x65,0xc3,0x46,0x31,0xe9,0xf6,0xf9, +0xa1,0x0d,0xfe,0x77,0xef,0xbd,0x95,0x8d,0x4f,0xcf,0x0f,0xa5,0xaf,0x08,0xfa,0x2b,0x70,0x9a,0xed,0xe2,0xeb,0xdc,0xcf,0xf7,0x61,0x9f,0x2b,0xe0,0x14,0xe0,0x49,0xbf,0x6d,0x49,0x18,0x3a,0xad,0x8c,0x81,0xe3,0x52,0xd2,0x65,0x74,0xab,0xb4,0x5f,0x82,0x03,0xcf,0xee,0x27,0x5d,0xc6,0x3e,0xf5,0x1f,0x7b,0x18,0x53,0x1f,0x5a,0x4a,0xe5, +0xa1,0x93,0x43,0xe9,0xef,0xdd,0x7b,0x6f,0xc5,0x9d,0x75,0x65,0x6e,0x2d,0xc4,0xd8,0x71,0x80,0x53,0xfd,0x0e,0x7e,0x30,0xf4,0x38,0xb0,0xed,0xd2,0x04,0x9c,0x06,0x2c,0x34,0xd1,0x5e,0x58,0x12,0x25,0x30,0xe6,0x82,0xe8,0xdf,0x61,0x36,0xfa,0x94,0x0a,0xca,0x87,0x45,0x77,0xa9,0x85,0xea,0x63,0x6c,0xa6,0x3e,0xb8,0x84,0xf2,0x91,0xe1, +0xec,0x01,0x1b,0xf3,0xc1,0xff,0x04,0x70,0x7a,0x6e,0xcc,0xf9,0x66,0x6c,0xe2,0x6b,0xbb,0xec,0x04,0xce,0x00,0xfe,0x68,0xaa,0xcd,0xa0,0x0d,0x3f,0xbe,0x9c,0x7e,0xa3,0xa2,0x3b,0xb0,0x3a,0x24,0x4b,0x13,0x1c,0xf4,0xc9,0x68,0xce,0x02,0x46,0x9c,0x39,0x93,0xc9,0x77,0x2e,0xa0,0xb4,0x72,0x40,0x28,0xfd,0xc5,0x7c,0xf0,0xcf,0x07,0x3e, +0x91,0x1b,0x6b,0x46,0x18,0x3d,0xf3,0xb5,0x5d,0x9a,0x81,0xb3,0x80,0x82,0x58,0xeb,0x79,0xd8,0x31,0x72,0x9f,0xf7,0xe7,0x6b,0xe8,0xd1,0x65,0x79,0x3d,0x79,0x18,0xb8,0x44,0x82,0xda,0xcb,0x7f,0xc0,0xf8,0xff,0xba,0x3b,0xb4,0x1b,0x73,0x62,0x3e,0xf8,0x1f,0x05,0x3e,0x99,0x1b,0x63,0xc6,0x18,0xbf,0xf4,0x65,0xbb,0xb4,0x00,0x9f,0x02, +0x1e,0x31,0xdd,0xb6,0x49,0x25,0xe5,0x09,0x06,0x4f,0x92,0xff,0xcc,0xbf,0xb7,0x52,0x55,0xc9,0xc8,0x5c,0xab,0x48,0x94,0xa6,0x38,0x74,0xd6,0x9d,0xd4,0x5e,0xf1,0xa3,0xd0,0xfa,0x8c,0xf9,0xe0,0x7f,0x04,0xf8,0x74,0x6e,0x6c,0x19,0x15,0xc8,0xb5,0xef,0xdc,0x7e,0xe6,0xe7,0x00,0x0f,0x05,0xd1,0xbe,0x09,0x83,0x27,0xa7,0x49,0xa6,0x0a, +0xeb,0x46,0x12,0xc9,0x3b,0x14,0x3b,0x94,0x56,0x0e,0x60,0xd2,0xed,0xf3,0x19,0x79,0xf6,0xe7,0x43,0xeb,0x33,0xe6,0x83,0xff,0x37,0xc0,0x39,0xb9,0x31,0x65,0x5c,0x60,0x1f,0x7e,0xe5,0xb6,0x33,0x3e,0x0f,0xb8,0x3f,0xa8,0x3e,0xfc,0xa8,0x39,0x52,0x7e,0x30,0xe5,0xab,0x46,0x38,0x00,0xca,0x46,0x8c,0xa6,0x6e,0xde,0x62,0x06,0x1f,0x77, +0x52,0x68,0x7d,0xc6,0x7c,0xf0,0xdf,0x07,0x9c,0x1f,0xd4,0xd6,0xe0,0x10,0xec,0xf6,0xe0,0xe4,0xf6,0x31,0x9b,0x09,0xcc,0x0d,0xb2,0x9f,0xbe,0xa8,0xdc,0xbf,0xf0,0x1e,0x28,0xa9,0x18,0x5e,0xd2,0xeb,0xdb,0x83,0x4d,0xab,0x1c,0x37,0x91,0x23,0x1e,0x5a,0x4a,0xe5,0xb8,0x89,0xa1,0xf5,0x19,0xf3,0xc1,0xff,0x6b,0xe0,0xc2,0x20,0x76,0x04, +0xee,0x2c,0xf0,0xdb,0x5f,0x72,0xdb,0x18,0x5d,0x0c,0xdc,0x11,0x74,0x5f,0xf9,0xe8,0xed,0xc3,0x36,0x51,0x53,0x56,0x1d,0x7e,0xdd,0x83,0x8f,0x3b,0x89,0xa9,0x0f,0x3c,0x13,0xea,0x5a,0x84,0x31,0x1f,0xfc,0x77,0x00,0x97,0x98,0xdc,0x02,0xac,0x3b,0xa1,0x1c,0x4d,0xb6,0x4b,0x16,0xb8,0x0c,0xf8,0x45,0x18,0xfd,0xed,0x4b,0xa2,0x04,0x52, +0x83,0x0a,0x34,0x00,0x42,0x0e,0xae,0x91,0x67,0x7f,0xde,0x5b,0x91,0xa7,0x7f,0x55,0x68,0x7d,0xc6,0x7c,0xf0,0xff,0x02,0xb8,0x2c,0x37,0x66,0x02,0x17,0xda,0x3c,0x38,0xf7,0x0b,0x5d,0xee,0x58,0xb4,0x02,0x57,0x86,0xd5,0x6f,0x57,0xd2,0x03,0x93,0x05,0xfb,0x20,0x59,0x3a,0xac,0x19,0x40,0x22,0x41,0xed,0x57,0xaf,0xa5,0xf6,0xf2,0x1f, +0x84,0xd3,0x5f,0x4e,0xcc,0x07,0xff,0xcd,0xb6,0xcb,0x55,0x61,0x76,0x18,0xfa,0xdb,0xa0,0xed,0xf2,0x35,0x84,0x97,0x10,0x4f,0x55,0x15,0xe6,0xbb,0x3f,0x40,0x6a,0x40,0xf0,0xb5,0x27,0x53,0x69,0xc6,0xcf,0x99,0xab,0x83,0x3f,0x5c,0x37,0x86,0x3d,0xf8,0x41,0x20,0x00,0x00,0x6c,0x97,0x6f,0x02,0x37,0x48,0xf4,0x0d,0x18,0x7f,0xb0,0x26, +0x4c,0xad,0x5b,0x83,0xad,0xbd,0xb4,0x6a,0x20,0x93,0x7e,0xf5,0x27,0x46,0x9c,0x39,0x33,0xd0,0x7e,0xf6,0x14,0xf3,0xc1,0x3f,0xdb,0x76,0xf9,0x96,0x44,0xc7,0x62,0x6f,0x85,0xb6,0xcb,0x77,0x81,0xeb,0x24,0xfa,0x6e,0xd9,0x92,0xe9,0xd3,0x9a,0xfc,0x51,0xd0,0xd2,0x10,0x5c,0x00,0x94,0x8f,0xdc,0x9f,0xa9,0x0f,0x3c,0x13,0xfa,0x42,0xa3, +0x31,0x1f,0xfc,0x3f,0xb2,0x5d,0xbe,0x27,0xd5,0xb9,0xe8,0x5c,0xd8,0x76,0xf9,0x21,0x70,0x4d,0xd8,0xfd,0x66,0xdb,0x73,0x21,0x50,0x80,0x9a,0x03,0x0a,0x80,0xca,0x43,0x27,0x33,0xf5,0xa1,0xa5,0xf4,0x1f,0x7b,0x58,0x20,0xed,0x77,0x27,0xe6,0x83,0xff,0xfb,0xb6,0xcb,0xb5,0x92,0x05,0x88,0x9f,0x0c,0xdb,0x2e,0xd7,0x03,0xb7,0x84,0xdd, +0x6f,0xf3,0xe6,0xc2,0x0c,0x80,0x20,0x66,0x00,0x43,0x8e,0x3f,0x99,0xa9,0xf3,0x16,0x53,0x36,0x6c,0x94,0xf1,0xb6,0x7b,0x12,0xf3,0xc1,0x7f,0xb3,0xed,0x32,0x4b,0xba,0x08,0xf1,0x00,0xc8,0x79,0x27,0xec,0x0e,0x77,0xbc,0x5b,0x78,0xeb,0xcb,0xef,0xda,0xd0,0x4e,0x7b,0xb3,0xd9,0xc1,0x32,0xea,0xd3,0x97,0x32,0xf1,0x97,0x8f,0x87,0xbe, +0xf1,0x46,0xcc,0x07,0x3f,0x08,0x1c,0xf3,0x5d,0x89,0x4a,0x00,0x0c,0x0a,0xbb,0xc3,0x8d,0xcb,0x8c,0x3e,0x54,0x15,0x8a,0x8d,0xcb,0x0c,0x3e,0x0b,0x92,0x48,0x70,0xf0,0xd7,0xae,0xe7,0x90,0xeb,0xef,0x08,0x6d,0x05,0x9f,0x0e,0x3a,0xf8,0x01,0x81,0x63,0xbe,0x2b,0x51,0xb9,0x1f,0x36,0xf4,0x4d,0xee,0x36,0x3d,0xdf,0x42,0xa6,0x2d,0x4b, +0xb2,0xb4,0x70,0x6e,0x08,0xa8,0x7f,0xd6,0x4c,0x68,0x25,0x53,0x69,0x0e,0x99,0x7d,0x27,0x23,0xce,0xb8,0xc0,0x48,0x7b,0xf9,0xd0,0xc1,0xff,0xbe,0x48,0x6c,0xec,0x18,0x95,0x19,0x40,0xe8,0x2f,0x46,0xfb,0xce,0x2c,0x0d,0x2b,0x02,0x79,0xc0,0x2a,0x10,0x6d,0xdb,0xb3,0x34,0xae,0xf4,0x3f,0x03,0x28,0x1d,0x30,0x88,0x49,0x77,0x2e,0xd0, +0xc1,0x2f,0x4f,0x03,0xa0,0x13,0x91,0x17,0x63,0xc3,0xdf,0x0b,0x67,0x73,0xcd,0xfa,0x67,0x9b,0xc9,0xfa,0x7c,0x2c,0xa4,0x7c,0xd4,0x81,0x4c,0x7d,0x60,0x09,0xd5,0x47,0x9d,0x68,0xa6,0xa8,0x3c,0xe8,0xe0,0xdf,0x8b,0x06,0x40,0x27,0x22,0xe7,0x43,0xeb,0x17,0x35,0xb3,0x73,0x5d,0xa0,0x0f,0x5b,0x19,0x91,0x6d,0x87,0x37,0x7f,0xbb,0xc3, +0x57,0x1b,0x55,0x13,0xea,0x38,0xe2,0xe1,0xa5,0xf4,0xff,0xd0,0x78,0x43,0x55,0xf5,0x9e,0x0e,0xfe,0x2e,0x45,0xe2,0x1a,0x40,0x54,0x02,0x40,0x24,0x0d,0x33,0x6d,0x59,0x5e,0xbf,0x3f,0xfa,0x3b,0xea,0xac,0x59,0xb0,0x93,0x9d,0xeb,0xfb,0x1e,0x54,0x43,0x3e,0x72,0x2a,0x75,0xf7,0x2f,0x22,0x5d,0x33,0xc2,0x60,0x55,0xbd,0xa3,0x83,0xbf, +0x5b,0x3a,0x03,0xe8,0x44,0xec,0xc5,0xd8,0xf0,0xf7,0x66,0xb6,0xbe,0x16,0xdd,0x6b,0x01,0xed,0x3b,0xb3,0xac,0xf6,0xf1,0xee,0x3f,0xea,0xb3,0x5f,0x60,0xe2,0x6d,0x8f,0x51,0x52,0x11,0xfe,0xae,0x47,0x3a,0xf8,0x7b,0xa4,0x01,0xd0,0x89,0xdc,0x74,0x28,0x0b,0xaf,0xcd,0xdd,0x1e,0xd9,0x63,0x74,0xf5,0xc3,0x3b,0xfa,0x74,0xff,0x7f,0x22, +0x99,0x64,0xcc,0xd7,0x67,0x73,0xc8,0x75,0xbf,0x0c,0xfd,0x63,0x3e,0xd0,0xc1,0xdf,0x0b,0x1a,0x00,0x00,0x8e,0x45,0x05,0x20,0xba,0xd6,0x55,0xe3,0xca,0x56,0x5e,0x9b,0x1b,0xbd,0x53,0x81,0x0d,0x7f,0x6f,0xe6,0x9d,0xc7,0xf2,0x5f,0xfe,0x3d,0x99,0x2e,0x63,0xfc,0x8d,0xf7,0x71,0xe0,0x65,0xdf,0x09,0xa0,0xaa,0x7d,0xd3,0xc1,0xdf,0x2b, +0x65,0x8e,0x45,0xb9,0x74,0x11,0xe2,0x01,0x40,0x44,0x92,0xf0,0x9d,0xf9,0x4d,0xbc,0xf7,0x64,0x74,0x3e,0x15,0xd8,0xb6,0xba,0x8d,0x95,0xb7,0x6e,0xcd,0x7b,0x0c,0xa5,0x06,0x0e,0x66,0xf2,0x5d,0x0b,0x19,0x7e,0xda,0xb9,0xc1,0x14,0xb6,0x0f,0x3a,0xf8,0xf3,0x22,0x7e,0xec,0x6b,0x00,0x74,0xc8,0xc2,0xab,0xb7,0x6f,0xa3,0x71,0xa5,0xfc, +0xf5,0x80,0x96,0xc6,0x0c,0x2f,0xcd,0xd9,0x92,0xf7,0x6d,0xbf,0x15,0xa3,0x6b,0x99,0xfa,0xe0,0x12,0x06,0x1d,0x29,0xb3,0x4d,0xa3,0x0e,0xfe,0xbc,0x89,0x1f,0xfb,0x51,0x08,0x80,0xc8,0xc8,0xb4,0x66,0x59,0x31,0xbb,0x91,0x4d,0xcb,0x8d,0x2f,0xbf,0xde,0x6b,0x4d,0xef,0xb5,0xb3,0xfc,0x07,0x0d,0xec,0xda,0x98,0xdf,0x55,0xff,0xaa,0xc3, +0x8e,0x60,0xea,0x43,0x4b,0xe9,0x77,0xf0,0x21,0x01,0x55,0xd6,0x33,0x1d,0xfc,0x85,0x29,0x0a,0x01,0xd0,0x20,0x5d,0x40,0x67,0x6d,0x4d,0x59,0x56,0xdc,0xd0,0xc8,0xdb,0x7d,0x38,0xf7,0xf6,0x6b,0xf3,0x8b,0x2d,0xfc,0xf3,0x3f,0x36,0xd3,0xb4,0x26,0xbf,0xc1,0x5f,0x33,0xfd,0x74,0xea,0xee,0xfb,0x2b,0xe9,0x21,0xc3,0x02,0xaa,0xac,0x67, +0x3a,0xf8,0xfb,0x4c,0xfc,0xd8,0xd7,0x00,0xe8,0x42,0x36,0x03,0xaf,0xdd,0xbd,0x9d,0x95,0xff,0xb3,0x95,0x4c,0x6b,0x38,0x07,0xf5,0x3b,0xf3,0x9b,0x78,0x71,0x56,0x23,0x6d,0xdb,0xf3,0xeb,0x6f,0xbf,0xf3,0xbe,0xcc,0xe1,0x3f,0xff,0x3d,0x25,0x15,0x32,0x7b,0x07,0xae,0xfb,0xfd,0x3d,0x3a,0xf8,0xfb,0x4e,0xfc,0xd8,0x8f,0xc4,0x93,0x30, +0x8e,0xc5,0x2e,0x84,0x3f,0x09,0xe8,0x4e,0x79,0x4d,0x09,0xb5,0xe7,0xf4,0x67,0xe4,0x09,0xe5,0x81,0xbc,0x5a,0x8d,0x2f,0xb7,0xf2,0xda,0xbd,0xdb,0xd9,0xba,0x2a,0xbf,0x6b,0x0f,0x89,0x64,0x92,0x31,0x57,0xcf,0xe1,0x80,0x4b,0xbf,0x69,0xbe,0xa8,0x3c,0xb4,0x6c,0x5c,0xc7,0xf2,0xcf,0x9d,0x48,0xd3,0x1b,0xaf,0x88,0xd6,0x51,0x80,0x9a, +0x6d,0x57,0xfe,0x53,0x80,0xa8,0x04,0xc0,0x3a,0x60,0xb8,0x74,0x1d,0x3d,0xa9,0x3c,0xb0,0x94,0x31,0x17,0x54,0x32,0xa4,0xce,0xcc,0x7e,0x82,0x3b,0xde,0x6e,0xe3,0xb5,0x7b,0xb7,0xb3,0xe9,0xf9,0x96,0xbc,0x97,0x27,0x4b,0x96,0x95,0x33,0xfe,0xbf,0xee,0x66,0xd8,0x29,0x9f,0x31,0x52,0x8b,0x5f,0x1a,0x02,0x7d,0xb2,0xce,0x76,0x19,0x29, +0x5d,0x44,0x54,0x02,0x60,0x25,0x20,0x73,0xf5,0x2a,0x4f,0xfd,0x46,0x95,0x50,0x73,0x64,0x19,0x43,0xa7,0x95,0x79,0x9b,0x75,0xe6,0xf1,0x0a,0x36,0xad,0x69,0xa7,0xfe,0xd9,0x66,0xea,0x9f,0x6d,0x66,0xdb,0xaa,0xd6,0x3e,0xcd,0x9a,0x4b,0xab,0x06,0x32,0xf1,0xb6,0xc7,0xc4,0xae,0xf4,0x77,0x47,0x43,0x20,0x6f,0x2b,0x6d,0x97,0xf0,0x1f, +0xcc,0xd8,0x43,0x54,0x02,0xe0,0x6f,0xc0,0x31,0xd2,0x75,0xe4,0x2b,0x3d,0x28,0xc9,0xe0,0x89,0x69,0xca,0x6a,0x92,0x94,0x0d,0x2e,0xa1,0xac,0x3a,0x49,0xba,0x3a,0x49,0x32,0x95,0xa0,0x79,0x73,0x3b,0x2d,0x0d,0x19,0x9a,0x37,0x67,0x68,0x6e,0xc8,0xd0,0xf8,0x72,0x0b,0x4d,0x6b,0xfd,0x3d,0x78,0x94,0xaa,0xae,0x61,0xf2,0x9d,0x4f,0x50, +0x35,0xa1,0xce,0xd0,0x6f,0x60,0x96,0x86,0x40,0x5e,0xfe,0x66,0xbb,0x1c,0x27,0x5d,0x44,0x54,0x16,0x04,0x69,0x94,0x2e,0xa0,0x2f,0x5a,0x1a,0x33,0xac,0x5b,0x14,0xce,0xcd,0x43,0x65,0xc3,0x46,0x31,0xf9,0xd7,0x7f,0x16,0x79,0x9a,0xaf,0xb7,0xd2,0x35,0x23,0xa8,0xbb,0xf7,0x29,0x0d,0x81,0xde,0x89,0xc4,0x31,0x1f,0x85,0x4f,0x01,0x20, +0x02,0x57,0x43,0xa3,0xac,0x62,0x74,0x2d,0x75,0xf3,0x16,0x47,0x7a,0xf0,0x77,0xe8,0x08,0x01,0xa9,0xfb,0x11,0x0a,0x48,0x24,0x8e,0x79,0x0d,0x80,0x28,0x4b,0x24,0x18,0x7e,0xea,0x67,0x39,0xe2,0x91,0x65,0x54,0xec,0x7f,0xb0,0x74,0x35,0xbd,0xa6,0x21,0xd0,0x2b,0x91,0x38,0xe6,0x35,0x00,0x22,0x2a,0x35,0x78,0x28,0x87,0xdd,0xf2,0x10, +0x13,0x7e,0xfa,0x20,0xa9,0x41,0x43,0xa4,0xcb,0xc9,0x9b,0x86,0xc0,0x3e,0x45,0xe2,0x98,0x8f,0x4a,0x00,0x44,0xe2,0x7c,0x28,0x2a,0x86,0xce,0x38,0x9b,0xa3,0xe7,0xbf,0xcc,0xb0,0x19,0x9f,0x92,0x2e,0xc5,0x17,0x0d,0x81,0x1e,0x45,0xe2,0x98,0x8f,0x4a,0x00,0xec,0x2f,0x5d,0x80,0xb4,0x44,0x32,0x49,0xcd,0xf4,0x33,0xa8,0x9b,0xb7,0x98, +0xc3,0x7f,0xf6,0x5b,0x52,0x83,0x87,0x4a,0x97,0x64,0x84,0x86,0x40,0xb7,0x22,0x71,0xcc,0x8b,0x7f,0x0c,0xe8,0x58,0x5c,0x83,0xd0,0x1e,0x81,0x51,0x90,0x2c,0x2b,0x67,0xc4,0x99,0x33,0x39,0xe0,0xe2,0xab,0xe9,0x77,0x90,0x25,0x5d,0x4e,0x60,0xf4,0x23,0xc2,0x2e,0x5d,0x93,0xdb,0x19,0x4b,0x8c,0x68,0x00,0x38,0x16,0xd7,0x21,0xb0,0x37, +0xa0,0xb4,0x54,0x75,0x0d,0xd5,0x47,0x9d,0xc8,0xe0,0x63,0x3f,0x4a,0xcd,0x47,0xcf,0x14,0x7b,0x88,0x27,0x6c,0x1a,0x02,0x5d,0xba,0x2e,0xb7,0x47,0xa6,0x08,0xb1,0x00,0x70,0x2c,0x66,0x03,0x32,0x4b,0xd6,0x98,0x92,0x48,0xf4,0xfa,0x21,0x98,0x44,0x49,0x09,0x55,0xe3,0xeb,0xa8,0x3e,0xf6,0xa3,0x54,0x1d,0x3a,0xd9,0xfb,0xd9,0x8e,0x36, +0xf6,0xfc,0xde,0xae,0xfe,0x5a,0xba,0xf8,0x3e,0x12,0x09,0x12,0x25,0x25,0x24,0x52,0x69,0x92,0xa5,0x29,0xef,0x6b,0x2a,0x9d,0xfb,0xba,0xc7,0xbf,0xef,0xf1,0xff,0x25,0x96,0x09,0x03,0x0d,0x81,0x6e,0xcc,0x96,0xda,0x21,0x58,0x24,0x00,0x1c,0x8b,0x9b,0x80,0xab,0x25,0xfa,0x56,0x9e,0x44,0x32,0x49,0xa2,0x53,0x28,0x54,0x1f,0x3d,0x9d, +0x09,0x3f,0x99,0x47,0xa2,0x24,0xf8,0x7b,0xc3,0x34,0x04,0xba,0x74,0x93,0xed,0x12,0xfa,0x93,0x5d,0xa1,0x07,0x80,0x63,0x71,0x0b,0x70,0x45,0xd8,0xfd,0xaa,0x7d,0xab,0xb1,0x3f,0xc1,0xe1,0xb7,0xfe,0x56,0x43,0x40,0xce,0x2d,0xb6,0xcb,0xd7,0xc2,0xec,0x30,0xb4,0x00,0x70,0x2c,0x12,0xc0,0xcf,0x81,0x2f,0x85,0xd5,0xa7,0xca,0x9f,0x86, +0x80,0xb8,0x5f,0x00,0x5f,0xb1,0xdd,0x7c,0x9f,0x11,0xed,0x9b,0x50,0x02,0xc0,0xb1,0x48,0x02,0xbf,0x04,0x2e,0x0d,0xa3,0x3f,0xe5,0x8f,0x86,0x80,0xb8,0x3b,0x80,0xcb,0xc2,0x08,0x81,0xc0,0x03,0x20,0x37,0xf8,0xef,0x02,0x2e,0x0c,0xba,0x2f,0x65,0x8e,0x86,0x80,0xb8,0xb9,0xc0,0x25,0xb6,0x4b,0xfe,0x9b,0x42,0xe4,0x21,0xd0,0x00,0x70, +0x2c,0x4a,0x80,0xbb,0x81,0xf3,0x83,0xec,0x47,0x05,0x43,0x43,0x40,0xdc,0xfd,0xc0,0x85,0xb6,0x4b,0x60,0x1b,0x58,0x06,0x16,0x00,0x8e,0x45,0x29,0xde,0x2f,0x10,0x8d,0x65,0x6b,0x54,0x9f,0x68,0x08,0x88,0xfb,0x0d,0x70,0x81,0xed,0xd2,0x16,0x44,0xe3,0x81,0x04,0x80,0x63,0x91,0x02,0x1e,0x04,0x3e,0x19,0x44,0xfb,0x2a,0x5c,0x1a,0x02, +0xe2,0x1e,0x01,0xce,0xb5,0x5d,0x8c,0x6f,0x5a,0x61,0x3c,0x00,0x1c,0x8b,0x34,0xf0,0x30,0x70,0x86,0xe9,0xb6,0x95,0x1c,0x0d,0x01,0x71,0x8f,0x02,0x9f,0xb1,0x5d,0x8c,0x6e,0x5a,0x61,0x34,0x00,0x1c,0x8b,0x32,0xe0,0x77,0xc0,0xa9,0x26,0xdb,0x55,0xd1,0xa0,0x21,0x20,0x6e,0x3e,0x70,0xb6,0xed,0xd2,0x6c,0xaa,0x41,0x63,0x01,0x90,0xdb, +0xe4,0xf3,0xf7,0xc0,0xc7,0x4c,0xb5,0xa9,0xa2,0x47,0x43,0x40,0xdc,0x42,0xe0,0x4c,0xdb,0x65,0xa7,0x89,0xc6,0x8c,0x04,0x80,0x63,0xd1,0x0f,0xf8,0x03,0x30,0xdd,0x44,0x7b,0x2a,0xda,0x34,0x04,0xc4,0x3d,0x09,0x9c,0x6e,0xbb,0xf8,0xde,0xbe,0xca,0x77,0x00,0x38,0x16,0x95,0x78,0x53,0x93,0x68,0xad,0x53,0xad,0x02,0xa5,0x21,0x20,0x6e, +0x11,0xf0,0x71,0xdb,0xc5,0xd7,0xbe,0xf6,0xbe,0x02,0xc0,0xb1,0x18,0x00,0xfc,0x09,0x38,0xd6,0x4f,0x3b,0xaa,0x30,0x69,0x08,0x88,0x5b,0x02,0x9c,0x62,0xbb,0x6c,0xeb,0x6b,0x03,0x7d,0x0e,0x00,0xc7,0x62,0x20,0xf0,0x04,0x70,0x54,0x5f,0xdb,0x50,0x85,0x4f,0x43,0x40,0xdc,0x3f,0x80,0x19,0xb6,0xcb,0x96,0xbe,0xfc,0xb0,0x9f,0x25,0xc1, +0xc6,0xa0,0x83,0x3f,0xf6,0x36,0x3a,0x8f,0xf2,0xd2,0x57,0x3f,0x45,0xb6,0x3d,0x90,0xfb,0x54,0x3e,0x40,0x97,0x17,0xeb,0xd2,0x51,0x78,0x63,0xb1,0x4f,0xfc,0x9e,0x02,0xb8,0xc0,0x58,0x3f,0x6d,0xa8,0xe2,0xa0,0x33,0x01,0x31,0xae,0xed,0x32,0xae,0xaf,0x3f,0xec,0x77,0x51,0xd0,0xdf,0xf8,0xfc,0x79,0x55,0x24,0x74,0x26,0x20,0xc6,0xd7, +0x18,0xf4,0x1b,0x00,0x0f,0xf8,0xfc,0x79,0x55,0x44,0x34,0x04,0x44,0xf8,0x1a,0x83,0xbe,0x02,0xc0,0x76,0xf9,0x3f,0xe0,0x5f,0x7e,0xda,0x50,0xc5,0x25,0xec,0x10,0x18,0x79,0x56,0xac,0x9f,0x32,0x7f,0xc9,0x76,0x59,0xe9,0xa7,0x01,0x13,0xfb,0x02,0x3c,0x68,0xa0,0x0d,0x55,0x44,0xc2,0x0c,0x81,0x4c,0xab,0xd1,0x5b,0xe3,0x0b,0x8d,0xef, +0xb1,0xa7,0x01,0xa0,0x02,0x11,0x56,0x08,0x64,0x35,0x00,0x7c,0xf1,0x1d,0x00,0xb6,0xcb,0xeb,0xc0,0x3f,0xfd,0xb6,0xa3,0x8a,0x4f,0x18,0x21,0x10,0xe3,0x19,0xc0,0x32,0xdb,0xe5,0x0d,0xbf,0x8d,0x98,0xda,0x1a,0x4c,0x67,0x01,0xaa,0x4b,0x41,0x87,0x40,0xb6,0xd5,0xf8,0x23,0xf2,0x85,0xc2,0xc8,0x98,0x33,0x15,0x00,0xbf,0x81,0x70,0x56, +0x31,0x55,0x85,0x27,0xc8,0x10,0x88,0xe9,0x0c,0x20,0x8b,0xa1,0x8f,0xe0,0x8d,0x04,0x80,0xed,0xf2,0x2e,0xde,0x7d,0xc9,0x4a,0x75,0x29,0xa8,0x10,0x88,0xe9,0x35,0x80,0x67,0x6c,0x97,0x35,0x26,0x1a,0x32,0xb9,0x3b,0xb0,0x9e,0x06,0xa8,0x1e,0x05,0x11,0x02,0x31,0x9d,0x01,0x18,0x1b,0x6b,0x26,0x03,0xe0,0x61,0x08,0x6e,0xf5,0x52,0x55, +0x1c,0x4c,0x87,0x40,0xb6,0x2d,0x76,0xd7,0x00,0xda,0xf1,0xc6,0x9a,0x11,0xc6,0x02,0xc0,0x76,0xd9,0x00,0x3c,0x65,0xaa,0x3d,0x55,0xbc,0x4c,0x86,0x40,0x0c,0x67,0x00,0x4f,0xda,0x2e,0xf5,0xa6,0x1a,0x33,0x39,0x03,0x00,0xb8,0xd3,0x70,0x7b,0xaa,0x48,0x99,0x0a,0x81,0x18,0x5e,0x03,0xb8,0xcb,0x64,0x63,0xa6,0x03,0xe0,0x21,0xe0,0x55, +0xc3,0x6d,0xaa,0x22,0x65,0x22,0x04,0x32,0x2d,0xb1,0x0a,0x80,0x57,0xf1,0xc6,0x98,0x31,0x46,0x03,0x20,0xb7,0x8d,0xd1,0x2c,0x93,0x6d,0xaa,0xe2,0xe6,0x37,0x04,0x62,0x76,0x0d,0xe0,0x7a,0xd3,0x5b,0x85,0x99,0x9e,0x01,0x00,0xcc,0x03,0x56,0x05,0xd0,0xae,0x2a,0x52,0x7e,0x42,0x20,0x46,0xa7,0x00,0xab,0x08,0xe0,0xe9,0x5b,0xe3,0x01, +0x90,0xdb,0xc7,0x4c,0x67,0x01,0x2a,0x2f,0x7d,0x0d,0x81,0x18,0x5d,0x04,0xbc,0x3e,0x88,0x3d,0x02,0x83,0x98,0x01,0x80,0xb7,0x27,0xe0,0xeb,0x01,0xb5,0xad,0x8a,0x54,0x5f,0x42,0x20,0x26,0x01,0xf0,0x3a,0xde,0x98,0x32,0x2e,0x90,0x00,0xc8,0x6d,0x64,0x38,0x3b,0x88,0xb6,0x55,0x71,0xcb,0x37,0x04,0x62,0x72,0x0a,0x30,0x2b,0xa8,0x1d, +0x82,0x83,0x9a,0x01,0x00,0xdc,0x03,0xac,0x0e,0xb0,0x7d,0x55,0xa4,0xf2,0x09,0x81,0x18,0x3c,0x0c,0xf4,0x06,0x70,0x6f,0x50,0x8d,0x07,0x16,0x00,0x3a,0x0b,0x50,0x7e,0xf4,0x36,0x04,0x62,0x70,0x0a,0x30,0x3b,0xa8,0xad,0xc1,0x21,0xd8,0x19,0x00,0xc0,0xdd,0xc0,0x5b,0x01,0xf7,0xa1,0x8a,0x54,0x6f,0x42,0xa0,0xc8,0x03,0xe0,0x4d,0xbc, +0x99,0x74,0x60,0x02,0x0d,0x80,0xdc,0x7e,0xe6,0x37,0x04,0xd9,0x87,0x2a,0x6e,0xfb,0x0a,0x81,0x22,0xbf,0x06,0x70,0x43,0x6e,0x0c,0x05,0x26,0xe8,0x19,0x00,0xc0,0xaf,0x81,0x77,0x42,0xe8,0x47,0x15,0xa9,0x9e,0x42,0xa0,0x88,0x6f,0x04,0x7a,0x1b,0x6f,0xec,0x04,0x2a,0xf0,0x00,0xb0,0x5d,0x5a,0x80,0x39,0x41,0xf7,0xa3,0x8a,0x5b,0x77, +0x21,0x50,0xc4,0xa7,0x00,0x73,0x82,0x7e,0xf7,0x87,0x70,0x66,0x00,0xe0,0x3d,0x24,0xa4,0xb3,0x00,0xe5,0x4b,0x57,0x21,0x50,0xa4,0x33,0x80,0x77,0x08,0xe9,0xc1,0xba,0x50,0x02,0xc0,0x76,0x69,0x06,0xbe,0x1c,0x46,0x5f,0xaa,0xb8,0x75,0x0e,0x81,0x6c,0x7b,0x3b,0xd9,0xf6,0xa2,0x5c,0x82,0xe2,0x4b,0xb9,0x99,0x73,0xe0,0xc2,0x9a,0x01, +0x60,0xbb,0x3c,0x4e,0x80,0x9f,0x67,0xaa,0xf8,0xe8,0x08,0x81,0xf6,0x9d,0x3b,0xa4,0x4b,0x09,0xc2,0x3d,0xb6,0xcb,0xfc,0xb0,0x3a,0xf3,0xb5,0x39,0x68,0xbe,0x1c,0x8b,0x6a,0xe0,0x65,0x60,0x64,0x98,0xfd,0xaa,0xe2,0x34,0xf8,0xb8,0x93,0xd8,0xbc,0xe4,0xcf,0xd2,0x65,0x98,0xb4,0x16,0x98,0x60,0xbb,0x34,0x86,0xd5,0x61,0xa8,0x01,0x00, +0xe0,0x58,0x9c,0x06,0xfc,0x21,0xec,0x7e,0x95,0x2a,0x00,0xa7,0xe7,0x66,0xca,0xa1,0x09,0xed,0x14,0xa0,0x83,0x9e,0x0a,0x28,0xd5,0xa5,0x7b,0xc2,0x1e,0xfc,0x20,0x10,0x00,0x39,0x57,0x02,0xef,0x09,0xf5,0xad,0x54,0xd4,0xac,0xc5,0x1b,0x13,0xa1,0x13,0x09,0x00,0xdb,0xa5,0x01,0xf8,0x82,0x44,0xdf,0x4a,0x45,0xd0,0x65,0x61,0x9e,0xf7, +0x77,0x26,0x35,0x03,0xd0,0x53,0x01,0xa5,0x3c,0x22,0x53,0xff,0x0e,0x62,0x01,0x90,0xa3,0xa7,0x02,0x2a,0xce,0xc4,0xa6,0xfe,0x1d,0x44,0x03,0x20,0x77,0x2a,0x70,0x99,0x64,0x0d,0x4a,0x09,0x12,0x9b,0xfa,0x77,0x90,0x9e,0x01,0x60,0xbb,0xfc,0x01,0xb8,0x4f,0xba,0x0e,0xa5,0x42,0x76,0xaf,0xe4,0xd4,0xbf,0x83,0x78,0x00,0xe4,0x5c,0x81, +0x9e,0x0a,0xa8,0xf8,0x78,0x0f,0xe1,0xa9,0x7f,0x87,0x48,0x04,0x40,0xee,0x54,0x60,0x26,0x04,0xb7,0xf2,0x89,0x52,0x11,0xd1,0x06,0xcc,0xcc,0x1d,0xf3,0xe2,0x22,0x11,0x00,0x00,0xb6,0xcb,0x5f,0x80,0x2f,0x49,0xd7,0xa1,0x54,0xc0,0xbe,0x98,0x3b,0xd6,0x23,0x21,0x32,0x01,0x00,0x60,0xbb,0xfc,0x0a,0x5d,0x47,0x50,0x15,0xaf,0x59,0xb6, +0x1b,0xad,0xfd,0x33,0x23,0x15,0x00,0x39,0xdf,0xc7,0xdb,0x5d,0x48,0xa9,0x62,0x72,0xbf,0xed,0xf2,0x7d,0xe9,0x22,0xf6,0x14,0xb9,0x00,0xb0,0x5d,0xb2,0xc0,0xc5,0xc0,0x22,0xa1,0x12,0xb6,0x00,0xbf,0x03,0x5e,0x10,0xea,0x5f,0x99,0xf7,0x02,0xde,0xdf,0xe9,0x16,0xa1,0xfe,0xff,0x8a,0x77,0x4c,0x47,0x4e,0xe4,0x02,0x00,0xde,0x5f,0x40, +0xe4,0x4c,0xe0,0x95,0x10,0xba,0xcb,0x02,0xff,0xc4,0xdb,0xce,0xec,0x78,0xa0,0xc6,0x76,0x39,0x1b,0x98,0x06,0x5c,0x0b,0xc1,0x2f,0xcb,0xa4,0x02,0xd3,0x0a,0xfc,0x10,0x38,0x32,0xf7,0x77,0x5a,0x83,0xf7,0x77,0x3c,0x0b,0xef,0xef,0x3c,0x1b,0x42,0x0d,0xaf,0x00,0x67,0x85,0xb5,0xc0,0x47,0xbe,0x42,0x7f,0x1c,0x38,0x1f,0x8e,0x45,0x2d, +0xb0,0x14,0x18,0x66,0xb8,0xe9,0xf5,0xc0,0x13,0xb9,0x3f,0x0b,0x6d,0x97,0x8d,0x3d,0xd4,0x30,0x19,0x98,0x0b,0x4c,0x32,0x5c,0x83,0x0a,0xd6,0x0b,0xc0,0x45,0xb6,0xcb,0x8b,0xdd,0x7d,0x83,0x63,0x31,0x14,0x38,0x09,0x38,0x19,0xf8,0x18,0x30,0xdc,0x70,0x0d,0xeb,0x81,0xa3,0x6d,0x97,0x37,0x0d,0xb7,0x6b,0x4c,0xa4,0x03,0x00,0xc0,0xb1, +0x98,0x06,0x3c,0x05,0xf4,0xf3,0xd1,0x4c,0x2b,0xb0,0x04,0x6f,0xc0,0x2f,0x00,0x5e,0xcc,0x9d,0x6a,0xf4,0xb6,0x86,0x14,0xf0,0x3d,0xe0,0xbb,0x40,0xca,0x47,0x1d,0x2a,0x78,0xad,0x78,0xef,0xf0,0xb3,0xf3,0x59,0x54,0xd3,0xb1,0x48,0x00,0x93,0x81,0x19,0x78,0x81,0x70,0x2c,0xfe,0xfe,0xae,0x9b,0x80,0x13,0x6c,0x97,0x65,0x3e,0xda,0x08, +0x5c,0xe4,0x03,0x00,0xc0,0xb1,0xf8,0x04,0xde,0x39,0x5c,0x3e,0xa7,0x2c,0x6f,0xb0,0x7b,0xc0,0x3f,0x65,0xbb,0x6c,0x33,0x50,0xc7,0x64,0xbc,0xcd,0x4e,0x26,0xfa,0x6d,0x4b,0x05,0xe2,0x45,0xbc,0x77,0x7d,0xdf,0xd7,0x6f,0x1c,0x8b,0x2a,0x60,0x3a,0x5e,0x20,0xcc,0x00,0x0e,0xce,0xe3,0xc7,0x33,0x78,0xd3,0xfe,0xc7,0xfc,0xd6,0x11,0xb4, +0x82,0x08,0x00,0x00,0xc7,0xe2,0x4a,0xe0,0xe6,0x1e,0xbe,0x65,0x07,0xf0,0x34,0xde,0x80,0x7f,0xc2,0x76,0x59,0x15,0x50,0x1d,0x29,0xbc,0x4f,0x2a,0xbe,0x0b,0x94,0x06,0xd1,0x87,0xca,0x5b,0xc7,0x36,0x74,0xd7,0x07,0xb5,0x94,0xb6,0x63,0x31,0x96,0xdd,0xb3,0x83,0x13,0x80,0xfe,0x3d,0x7c,0xfb,0x15,0xb6,0xcb,0xad,0x41,0xd4,0x61,0x5a, +0xc1,0x04,0x00,0x80,0x63,0x71,0x33,0x1f,0xbc,0x85,0xf2,0x25,0x72,0x03,0x1e,0x58,0x1c,0xe6,0x85,0x16,0xc7,0xa2,0x0e,0xef,0xda,0xc0,0xe1,0x61,0xf5,0xa9,0xba,0xb4,0x02,0xef,0x5d,0xff,0xf9,0xb0,0x3a,0x74,0x2c,0xd2,0x78,0x17,0x13,0x3b,0x02,0xa1,0xf3,0x31,0x70,0xb3,0xed,0x72,0x55,0x58,0xb5,0xf8,0x55,0x68,0x01,0x90,0xc4,0xbb, +0x32,0xbf,0x1a,0xef,0x5d,0x7e,0xad,0x70,0x3d,0x69,0xe0,0x9b,0xc0,0xd5,0x40,0xb5,0x64,0x2d,0x31,0xd4,0x00,0xdc,0x04,0xdc,0x18,0xc6,0x06,0x1a,0x3d,0x71,0x2c,0x46,0xe1,0x85,0x41,0x2d,0x70,0xad,0xed,0x92,0x91,0xac,0x27,0x1f,0x05,0x15,0x00,0x51,0xe5,0x58,0x0c,0x00,0xbe,0x0a,0x5c,0x05,0x0c,0x11,0x2e,0xa7,0xd8,0x6d,0x02,0x7e, +0x0a,0xdc,0x6a,0xbb,0x6c,0x95,0x2e,0xa6,0xd0,0x69,0x00,0x18,0xe4,0x58,0x54,0x02,0x97,0xe3,0xcd,0x08,0x86,0x0a,0x97,0x53,0x6c,0xea,0x81,0x1f,0x03,0x3f,0xb7,0x5d,0xb6,0x4b,0x17,0x53,0x2c,0x34,0x00,0x02,0xe0,0x58,0xf4,0xc7,0x7b,0xb0,0xe9,0x1b,0x98,0xff,0x6c,0x39,0x6e,0xd6,0x03,0x37,0x02,0xb7,0xd9,0x2e,0x4d,0xd2,0xc5,0x14, +0x1b,0x0d,0x80,0x00,0x39,0x16,0x15,0x78,0x2b,0x1e,0x7d,0x0b,0xdd,0x0c,0x25,0x5f,0xef,0x01,0xff,0x0d,0xfc,0xd2,0x76,0xd9,0x29,0x5d,0x4c,0xb1,0xd2,0x00,0x08,0x81,0x63,0x51,0x0e,0xfc,0x3b,0xf0,0x6d,0x60,0x3f,0xe1,0x72,0xa2,0x6e,0x0d,0xde,0x6e,0xd2,0xbf,0xb2,0x5d,0x76,0x49,0x17,0x53,0xec,0x34,0x00,0x42,0xe4,0x58,0x7c,0x1c, +0xe4,0x97,0x81,0x8a,0xb8,0x8f,0xdb,0x2e,0x7f,0x94,0x2e,0x22,0x2e,0x22,0xf9,0x30,0x50,0x11,0x5b,0x84,0xae,0x7a,0xd4,0x93,0x36,0x60,0xb1,0x74,0x11,0x71,0xa2,0x01,0x10,0xa2,0xdc,0xed,0xc8,0xcf,0x49,0xd7,0x11,0x61,0xff,0x34,0x71,0xcb,0xb6,0xea,0x3d,0x0d,0x80,0xf0,0x3d,0x29,0x5d,0x40,0x84,0xe9,0x6b,0x13,0x32,0x0d,0x80,0xf0, +0xe9,0x41,0xde,0x3d,0x7d,0x6d,0x42,0xa6,0x01,0x10,0xbe,0x25,0x10,0xcd,0xc5,0x21,0x84,0x35,0x03,0x7f,0x93,0x2e,0x22,0x6e,0x34,0x00,0x42,0x96,0xfb,0x4c,0x7b,0xa9,0x74,0x1d,0x11,0xb4,0x54,0x3f,0xef,0x0f,0x9f,0x06,0x80,0x0c,0x9d,0xea,0xee,0x4d,0x5f,0x13,0x01,0x1a,0x00,0x32,0xf4,0x60,0xdf,0x9b,0xbe,0x26,0x02,0x34,0x00,0x64, +0xfc,0x03,0xf4,0xbe,0xf6,0x4e,0x9a,0xf0,0x5e,0x13,0x15,0x32,0x0d,0x00,0x01,0xb9,0x85,0x4b,0x96,0x48,0xd7,0x11,0x21,0xcf,0x48,0x3f,0xd3,0x1f,0x57,0x1a,0x00,0x72,0x74,0xca,0xbb,0x9b,0xbe,0x16,0x42,0x34,0x00,0xe4,0xe8,0x41,0xbf,0x9b,0xbe,0x16,0x42,0x34,0x00,0xe4,0x3c,0x07,0xba,0xa2,0x0d,0xde,0x6e,0x3d,0xcb,0xa5,0x8b,0x88, +0x2b,0x0d,0x00,0x21,0xb6,0x4b,0x3b,0x72,0xdb,0x9f,0x45,0xc9,0xa2,0xdc,0x6b,0xa1,0x04,0x68,0x00,0xc8,0xd2,0xa9,0xaf,0xbe,0x06,0xa2,0x34,0x00,0x64,0xe9,0xc1,0xaf,0xaf,0x81,0x28,0x0d,0x00,0x59,0x2b,0xf0,0x56,0xb9,0x8d,0xab,0x8d,0x78,0x7b,0x3b,0x28,0x21,0x1a,0x00,0x82,0x72,0xfb,0x13,0x3e,0x2d,0x5d,0x87,0xa0,0xa7,0xf3,0xd9, +0xa3,0x51,0x99,0xa7,0x01,0x20,0x2f,0xce,0x53,0xe0,0x38,0xff,0xee,0x91,0xa0,0x01,0x20,0x2f,0xce,0x83,0x20,0xce,0xbf,0x7b,0x24,0xe8,0xa2,0xa0,0x11,0xe0,0x58,0xac,0x25,0x7e,0xcb,0x86,0xaf,0xb5,0x5d,0x5d,0x21,0x59,0x9a,0xce,0x00,0xa2,0xe1,0x29,0xe9,0x02,0x04,0xc4,0xf1,0x77,0x8e,0x1c,0x0d,0x80,0x68,0x88,0xe3,0x54,0x38,0x8e, +0xbf,0x73,0xe4,0x68,0x00,0x44,0x43,0x1c,0x07,0x83,0x23,0x5d,0x80,0xd2,0x00,0x88,0x04,0xdb,0x65,0x35,0xf0,0x28,0xde,0x7d,0xf1,0xc5,0xae,0x11,0x78,0xd4,0x76,0x79,0x4b,0xba,0x10,0xa5,0x17,0x01,0x23,0xc5,0xb1,0x48,0xe0,0xed,0x31,0x5f,0xb7,0xc7,0x9f,0x42,0xdd,0x69,0x78,0x03,0xf0,0x3c,0xde,0xc3,0x3e,0xcb,0x81,0xe5,0xb6,0xcb, +0x1b,0xb2,0x25,0xa9,0xce,0x34,0x00,0x0a,0x80,0x63,0x31,0x1a,0x2f,0x08,0xa6,0xb0,0x3b,0x14,0x46,0x8b,0x16,0xb5,0xb7,0x77,0xe9,0x34,0xd0,0xf1,0x06,0xfb,0x1a,0xd9,0x92,0xd4,0xbe,0x68,0x00,0x14,0x28,0xc7,0x62,0x28,0xbb,0xc3,0xa0,0x16,0x18,0x94,0xfb,0x53,0xdd,0xe9,0xeb,0x40,0xa0,0xd4,0x67,0x57,0x6d,0x78,0xa7,0x26,0x0d,0x78, +0xd3,0xf7,0x8e,0xaf,0x8d,0xc0,0x6a,0x76,0x0f,0xf6,0x7a,0x9f,0xfd,0x28,0x01,0x1a,0x00,0x45,0xce,0xb1,0xa8,0xe4,0x83,0xa1,0xb0,0x67,0x50,0xc0,0xde,0x03,0xfb,0xfd,0xc1,0x6e,0xbb,0x6c,0x0f,0xbb,0x66,0xa5,0x94,0x52,0x4a,0x29,0xa5,0x94,0x52,0x4a,0x29,0xa5,0x94,0x52,0x4a,0x29,0xa5,0x94,0x52,0x4a,0x29,0xa5,0x94,0x52,0x4a,0x29, +0xd5,0x1b,0xff,0x0f,0x5c,0x56,0xd9,0x0b,0x64,0x90,0x3b,0x82,0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae,0x42,0x60,0x82, +}; + +read_only global String8 lnx_wm_icon_file_bytes = {lnx_wm_icon_file_bytes__data, sizeof(lnx_wm_icon_file_bytes__data)}; + +C_LINKAGE_END + +#endif // LINUX_WINDOW_MANAGER_META_H diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 341cd13c..e36c76ce 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -1,6 +1,11 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/linux_window_manager.meta.c" + //////////////////////////////// //~ rjf: Helpers @@ -101,6 +106,13 @@ lnx_moveresize_code_from_pos(LNX_WM_Window *window, Vec2F32 pos, B32 *out_is_in_ //////////////////////////////// //~ rjf: @per_os_impl Main Initialization API (Implemented Per-OS) +#if LNX_WM_ICON && !defined(STBI_INCLUDE_STB_IMAGE_H) +# define STB_IMAGE_IMPLEMENTATION +# define STBI_ONLY_PNG +# define STBI_ONLY_BMP +# include "third_party/stb/stb_image.h" +#endif + internal void wm_init(void) { @@ -148,9 +160,122 @@ wm_init(void) } } - // create wakeup event for polling + //- rjf: load icon +#if LNX_WM_ICON + { + // rjf: unpack icon image data + { + Temp scratch = scratch_begin(0, 0); + String8 data = lnx_wm_icon_file_bytes; + U8 *ptr = data.str; + U8 *opl = ptr+data.size; + + // rjf: read header +#pragma pack(push, 1) + typedef struct ICO_Header ICO_Header; + struct ICO_Header + { + U16 reserved_padding; // must be 0 + U16 image_type; // if 1 -> ICO, if 2 -> CUR + U16 num_images; + }; + typedef struct ICO_Entry ICO_Entry; + struct ICO_Entry + { + U8 image_width_px; + U8 image_height_px; + U8 num_colors; + U8 reserved_padding; // should be 0 + union + { + U16 ico_color_planes; // in ICO + U16 cur_hotspot_x_px; // in CUR + }; + union + { + U16 ico_bits_per_pixel; // in ICO + U16 cur_hotspot_y_px; // in CUR + }; + U32 image_data_size; + U32 image_data_off; + }; +#pragma pack(pop) + ICO_Header hdr = {0}; + if(ptr+sizeof(hdr) < opl) + { + MemoryCopy(&hdr, ptr, sizeof(hdr)); + ptr += sizeof(hdr); + } + + // rjf: read image entries + U64 entries_count = hdr.num_images; + ICO_Entry *entries = push_array(scratch.arena, ICO_Entry, hdr.num_images); + { + U64 bytes_to_read = sizeof(ICO_Entry)*entries_count; + bytes_to_read = Min(bytes_to_read, opl-ptr); + MemoryCopy(entries, ptr, bytes_to_read); + ptr += bytes_to_read; + } + + // rjf: find largest image + ICO_Entry *best_entry = 0; + U64 best_entry_area = 0; + for(U64 idx = 0; idx < entries_count; idx += 1) + { + ICO_Entry *entry = &entries[idx]; + U64 width = entry->image_width_px; + if(width == 0) { width = 256; } + U64 height = entry->image_height_px; + if(height == 0) { height = 256; } + U64 entry_area = width*height; + if(entry_area > best_entry_area) + { + best_entry = entry; + best_entry_area = entry_area; + } + } + + // rjf: deserialize raw image data from best entry's offset + U8 *image_data = 0; + Vec2S32 image_dim = {0}; + if(best_entry != 0) + { + U8 *file_data_ptr = data.str + best_entry->image_data_off; + U64 file_data_size = best_entry->image_data_size; + int width = 0; + int height = 0; + int components = 0; + image_data = stbi_load_from_memory(file_data_ptr, file_data_size, &width, &height, &components, 4); + image_dim.x = width; + image_dim.y = height; + } + + // rjf: swizzle to ARGB, store + { + U64 width = (U64)image_dim.x; + U64 height = (U64)image_dim.y; + U64 pixel_count = width*height; + lnx_wm_state->icon_image_data_count = 2 + pixel_count; + lnx_wm_state->icon_image_data = push_array(arena, long, lnx_wm_state->icon_image_data_count); + lnx_wm_state->icon_image_data[0] = width; + lnx_wm_state->icon_image_data[1] = height; + for EachIndex(pixel_idx, pixel_count) + { + U32 *src_pixel = (U32 *)image_data + pixel_idx; + long *dst_pixel = &(lnx_wm_state->icon_image_data + 2)[pixel_idx]; + dst_pixel[0] |= (src_pixel[0] & 0x000000ffu) << 24; + dst_pixel[0] |= (src_pixel[0] & 0xffffff00u) >> 8; + } + } + + stbi_image_free(image_data); + scratch_end(scratch); + } + } +#endif + + //- rjf: create wakeup event for polling lnx_wm_state->wakeup_fd = eventfd(0, EFD_CLOEXEC); - Assert(lnx_wm_state->wakeup_fd > 0); } //////////////////////////////// @@ -249,6 +374,26 @@ wm_window_open(Rng2F32 rect, WM_WindowFlags flags, String8 title) } XChangeProperty(lnx_wm_state->display, w->window, lnx_wm_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1); + //- rjf: set icon + { + Atom net_wm_icon = XInternAtom(lnx_wm_state->display, "_NET_WM_ICON", 0); + XChangeProperty(lnx_wm_state->display, w->window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (U8 *)lnx_wm_state->icon_image_data, lnx_wm_state->icon_image_data_count); + XFlush(lnx_wm_state->display); + } + + //- rjf: ignore .desktop files + { + XClassHint *class_hint = XAllocClassHint(); + if(class_hint) + { + class_hint->res_name = "custom_raddbg_class"; + class_hint->res_class = "CustomRaddbgClass"; + XSetClassHint(lnx_wm_state->display, w->window, class_hint); + XFree(class_hint); + XFlush(lnx_wm_state->display); + } + } + //- rjf: create xic w->xic = XCreateIC(lnx_wm_state->xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, diff --git a/src/linux/window_manager/linux_window_manager.h b/src/linux/window_manager/linux_window_manager.h index b6f2eb04..cb435810 100644 --- a/src/linux/window_manager/linux_window_manager.h +++ b/src/linux/window_manager/linux_window_manager.h @@ -84,8 +84,15 @@ struct LNX_WM_State int window_depth; Colormap window_colormap; LNX_WM_WindowClientArea *free_client_area; + long *icon_image_data; + U64 icon_image_data_count; }; +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/linux_window_manager.meta.h" + //////////////////////////////// //~ rjf: Globals diff --git a/src/linux/window_manager/linux_window_manager.mdesk b/src/linux/window_manager/linux_window_manager.mdesk new file mode 100644 index 00000000..66a125f8 --- /dev/null +++ b/src/linux/window_manager/linux_window_manager.mdesk @@ -0,0 +1,4 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +@embed_file lnx_wm_icon_file_bytes: "../data/logo.ico" diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index a1e797c4..19e0ea19 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -3510,6 +3510,7 @@ rd_cell(RD_CellParams *params, String8 string) //- rjf: build line edit container box // UI_Box *edit_box = &ui_nil_box; + F32 editable_edit_box_dim = dim_2f32(box->rect).x; if((is_focus_active || is_focus_active_disabled) || build_line_edit) UI_Parent(box) { @@ -3519,7 +3520,8 @@ rd_cell(RD_CellParams *params, String8 string) { if(is_editing) { - edit_box_size = ui_px(floor_f32(dim_2f32(box->rect).x*0.5f), 1.f); + editable_edit_box_dim = floor_f32(dim_2f32(box->rect).x*0.5f); + edit_box_size = ui_px(editable_edit_box_dim, 1.f); } else { @@ -3604,7 +3606,7 @@ rd_cell(RD_CellParams *params, String8 string) { UI_Parent(edit_box) UI_PrefWidth(ui_children_sum(0)) { - scrollable_box = ui_build_box_from_stringf(is_focus_active*(UI_BoxFlag_AllowOverflowX), "scroll_box_%p", params->edit_buffer); + scrollable_box = ui_build_box_from_stringf(is_focus_active*(UI_BoxFlag_AllowOverflowX|UI_BoxFlag_Clip), "scroll_box_%p", params->edit_buffer); } } @@ -4171,7 +4173,8 @@ rd_cell(RD_CellParams *params, String8 string) // if(scrollable_box != &ui_nil_box) { - F32 visible_dim_px = dim_2f32(box->rect).x - expander_size_px - ui_top_font_size()*params->depth; + F32 edit_box_dim_px = editable_edit_box_dim; + F32 visible_dim_px = edit_box_dim_px - expander_size_px - ui_top_font_size()*params->depth; if(visible_dim_px > 0) { Rng1F32 cursor_range_px = r1f32(cursor_off-ui_top_font_size()*2.f, cursor_off+ui_top_font_size()*1.f); From d7685be114652cf13bba2720cc436350ff2e523a Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 5 Aug 2026 14:00:17 -0600 Subject: [PATCH 007/251] do not assume non-empty config file destinations are okay if we don't have a config path established - will cause overwrites on 'opens' --- src/raddbg/raddbg_core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index d7c4e9f9..261f7afe 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -13868,10 +13868,10 @@ rd_frame(void) B32 file_is_okay = 0; { String8 stored_path = (kind == RD_CmdKind_OpenUser ? rd_state->user_path : rd_state->project_path); - file_is_okay = ((file_props.size == 0 && file_props.created == 0) || - str8_match(file_path, stored_path, 0) || - stored_path.size == 0 || - str8_match(str8_prefix(file_data, 9), str8_lit("// raddbg"), 0)); + file_is_okay = (((file_props.size == 0 && file_props.created == 0) || + str8_match(file_path, stored_path, 0) || + stored_path.size == 0) && + (file_data.size == 0 || str8_match(str8_prefix(file_data, 9), str8_lit("// raddbg"), 0))); } //- rjf: determine file's version From e1d79d07b2bb07c111e4cd75bddfdd85479e5dac Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 5 Aug 2026 14:33:05 -0600 Subject: [PATCH 008/251] menu bar visual simplifications --- src/raddbg/raddbg_core.c | 75 +++++++++++++++++++++++--------------- src/ui/generated/ui.meta.c | 6 +++ src/ui/generated/ui.meta.h | 11 ++++++ src/ui/ui.mdesk | 3 ++ src/ui/ui_core.c | 1 + src/ui/ui_core.h | 8 ++++ 6 files changed, 75 insertions(+), 29 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 261f7afe..55edc856 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -7616,7 +7616,10 @@ rd_window_frame(void) } // rjf: make ui - for(U64 idx = 0; idx < ArrayCount(items); idx += 1) + UI_TagF("implicit") + UI_VisualMargin(ui_top_font_size()*0.45f) + UI_CornerRadius(ui_top_font_size()*0.5f) + for(U64 idx = 0; idx < ArrayCount(items); idx += 1) { ui_set_next_fastpath_codepoint(items[idx].codepoint); B32 alt_fastpath_key = 0; @@ -7628,24 +7631,27 @@ rd_window_frame(void) { ui_set_next_flags(UI_BoxFlag_DrawTextFastpathCodepoint); } - UI_Signal sig = rd_menu_bar_button(items[idx].name); - wm_window_push_custom_title_bar_client_area(ws->os, sig.box->rect); - if(menu_open) + UI_TagF(!ui_ctx_menu_is_open(items[idx].menu_key) ? "weak" : "") { - if((ui_hovering(sig) && !ui_ctx_menu_is_open(items[idx].menu_key)) || (open_menu_idx_prime == idx && open_menu_idx_prime != open_menu_idx)) + UI_Signal sig = rd_menu_bar_button(items[idx].name); + wm_window_push_custom_title_bar_client_area(ws->os, sig.box->rect); + if(menu_open) { - ui_ctx_menu_open(items[idx].menu_key, sig.box->key, v2f32(0, sig.box->rect.y1-sig.box->rect.y0)); + if((ui_hovering(sig) && !ui_ctx_menu_is_open(items[idx].menu_key)) || (open_menu_idx_prime == idx && open_menu_idx_prime != open_menu_idx)) + { + ui_ctx_menu_open(items[idx].menu_key, sig.box->key, v2f32(0, sig.box->rect.y1-sig.box->rect.y0)); + } } - } - else if(ui_pressed(sig) || alt_fastpath_key) - { - if(ui_ctx_menu_is_open(items[idx].menu_key)) + else if(ui_pressed(sig) || alt_fastpath_key) { - ui_ctx_menu_close(); - } - else - { - ui_ctx_menu_open(items[idx].menu_key, sig.box->key, v2f32(0, sig.box->rect.y1-sig.box->rect.y0)); + if(ui_ctx_menu_is_open(items[idx].menu_key)) + { + ui_ctx_menu_close(); + } + else + { + ui_ctx_menu_open(items[idx].menu_key, sig.box->key, v2f32(0, sig.box->rect.y1-sig.box->rect.y0)); + } } } } @@ -7661,6 +7667,9 @@ rd_window_frame(void) UI_PrefWidth(ui_px(dim_2f32(top_bar_rect).y, 1)) RD_Font(RD_FontSlot_Icons) UI_FontSize(ui_top_font_size()*0.85f) + UI_TagF("implicit") + UI_CornerRadius(ui_top_font_size()*1.f) + UI_VisualMargin(ui_top_font_size()*0.5f) { Temp scratch = scratch_begin(0, 0); CFG_NodePtrList targets = cfg_node_top_level_list_from_string(scratch.arena, str8_lit("target")); @@ -7748,11 +7757,12 @@ rd_window_frame(void) // rjf: loaded project viz if(do_user_prof) + UI_VisualMargin(ui_top_font_size()*0.5f) + UI_CornerRadius(ui_top_font_size()*0.5f) { ui_set_next_pref_width(ui_children_sum(1)); ui_set_next_child_layout_axis(Axis2_X); UI_Box *prof_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| - UI_BoxFlag_DrawBorder| UI_BoxFlag_DrawBackground| UI_BoxFlag_DrawHotEffects| UI_BoxFlag_DrawActiveEffects, @@ -7805,6 +7815,10 @@ rd_window_frame(void) } // rjf: min/max/close buttons + UI_TagF("implicit") + UI_TagF("weak") + UI_VisualMargin(ui_top_font_size()*0.5f) + UI_CornerRadius(ui_top_font_size()*0.9f) { UI_Signal min_sig = {0}; UI_Signal max_sig = {0}; @@ -7818,9 +7832,10 @@ rd_window_frame(void) max_sig = rd_icon_buttonf(wm_window_is_maximized(ws->os) ? RD_IconKind_WindowRestore : RD_IconKind_Window, 0, "##maximize"); } UI_PrefWidth(ui_px(button_dim, 1.f)) - UI_TagF("bad_pop") + UI_FontSize(ui_top_font_size()*0.85f) + // UI_TagF("bad_pop") { - cls_sig = rd_icon_buttonf(RD_IconKind_X, 0, "##close"); + cls_sig = rd_icon_buttonf(RD_IconKind_X, 0, "##close"); } if(ui_clicked(min_sig)) { @@ -9390,17 +9405,19 @@ rd_window_frame(void) // rjf: draw background if(box->flags & UI_BoxFlag_DrawBackground) { + Rng2F32 box_bg_rect = pad_2f32(box->rect, -box->visual_margin); + // rjf: hot effect extension (drop shadow) if(box->flags & UI_BoxFlag_DrawHotEffects) { - Rng2F32 drop_shadow_rect = shift_2f32(pad_2f32(box->rect, 8), v2f32(4, 4)); + Rng2F32 drop_shadow_rect = shift_2f32(pad_2f32(box_bg_rect, 8), v2f32(4, 4)); Vec4F32 color = drop_shadow_color; color.w *= t*box_background_color.w; dr_rect(drop_shadow_rect, color, 0.8f, 0, 8.f); } // rjf: draw background - R_Rect2DInst *inst = dr_rect(pad_2f32(box->rect, 1.f), box_background_color, 0, 0, border_softness*1.f); + R_Rect2DInst *inst = dr_rect(pad_2f32(box_bg_rect, 1.f), box_background_color, 0, 0, border_softness*1.f); MemoryCopyArray(inst->corner_radii, box_corner_radii); // rjf: hot effect extension @@ -9414,7 +9431,7 @@ rd_window_frame(void) { Vec4F32 color = hover_color; color.w *= 0.015f; - R_Rect2DInst *inst = dr_rect(pad_2f32(box->rect, 1.f), v4f32(0, 0, 0, 0), 0, 0, border_softness*1.f); + R_Rect2DInst *inst = dr_rect(pad_2f32(box_bg_rect, 1.f), v4f32(0, 0, 0, 0), 0, 0, border_softness*1.f); inst->colors[Corner_00] = color; inst->colors[Corner_10] = color; inst->colors[Corner_01] = color; @@ -9423,7 +9440,7 @@ rd_window_frame(void) } // rjf: soft circle around mouse - if(box->hot_t > 0.01f) DR_ClipScope(intersect_2f32(box->rect, dr_top_clip())) + if(box->hot_t > 0.01f && dim_2f32(box->rect).x > box->font_size*8.f) DR_ClipScope(intersect_2f32(box_bg_rect, dr_top_clip())) { Vec4F32 color = hover_color; color.w *= 0.025f; @@ -9447,15 +9464,15 @@ rd_window_frame(void) shadow_color.w *= 0.5f*box->active_t; Vec2F32 shadow_size = { - (box->rect.x1 - box->rect.x0)*0.60f*box->active_t, - (box->rect.y1 - box->rect.y0)*0.60f*box->active_t, + (box_bg_rect.x1 - box_bg_rect.x0)*0.60f*box->active_t, + (box_bg_rect.y1 - box_bg_rect.y0)*0.60f*box->active_t, }; shadow_size.x = Clamp(0, shadow_size.x, box->font_size*2.f); shadow_size.y = Clamp(0, shadow_size.y, box->font_size*2.f); // rjf: top -> bottom dark effect { - R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x0, box->rect.y0, box->rect.x1, box->rect.y0 + shadow_size.y), v4f32(0, 0, 0, 0), 0, 0, 1.f); + R_Rect2DInst *inst = dr_rect(r2f32p(box_bg_rect.x0, box_bg_rect.y0, box_bg_rect.x1, box_bg_rect.y0 + shadow_size.y), v4f32(0, 0, 0, 0), 0, 0, 1.f); inst->colors[Corner_00] = inst->colors[Corner_10] = shadow_color; inst->colors[Corner_01] = inst->colors[Corner_11] = v4f32(0.f, 0.f, 0.f, 0.0f); MemoryCopyArray(inst->corner_radii, box_corner_radii); @@ -9463,7 +9480,7 @@ rd_window_frame(void) // rjf: bottom -> top light effect { - R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x0, box->rect.y1 - shadow_size.y, box->rect.x1, box->rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f); + R_Rect2DInst *inst = dr_rect(r2f32p(box_bg_rect.x0, box_bg_rect.y1 - shadow_size.y, box_bg_rect.x1, box_bg_rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f); inst->colors[Corner_00] = inst->colors[Corner_10] = v4f32(0, 0, 0, 0); inst->colors[Corner_01] = inst->colors[Corner_11] = v4f32(1.0f, 1.0f, 1.0f, 0.08f*box->active_t); MemoryCopyArray(inst->corner_radii, box_corner_radii); @@ -9471,7 +9488,7 @@ rd_window_frame(void) // rjf: left -> right dark effect { - R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x0, box->rect.y0, box->rect.x0 + shadow_size.x, box->rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f); + R_Rect2DInst *inst = dr_rect(r2f32p(box_bg_rect.x0, box_bg_rect.y0, box_bg_rect.x0 + shadow_size.x, box_bg_rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f); inst->colors[Corner_10] = inst->colors[Corner_11] = v4f32(0.f, 0.f, 0.f, 0.f); inst->colors[Corner_00] = shadow_color; inst->colors[Corner_01] = shadow_color; @@ -9480,7 +9497,7 @@ rd_window_frame(void) // rjf: right -> left dark effect { - R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x1 - shadow_size.x, box->rect.y0, box->rect.x1, box->rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f); + R_Rect2DInst *inst = dr_rect(r2f32p(box_bg_rect.x1 - shadow_size.x, box_bg_rect.y0, box_bg_rect.x1, box_bg_rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f); inst->colors[Corner_00] = inst->colors[Corner_01] = v4f32(0.f, 0.f, 0.f, 0.f); inst->colors[Corner_10] = shadow_color; inst->colors[Corner_11] = shadow_color; @@ -9617,7 +9634,7 @@ rd_window_frame(void) if(b->flags & UI_BoxFlag_DrawBorder) { Vec4F32 border_color = b->border_color; - Rng2F32 b_border_rect = pad_2f32(b->rect, 1.f); + Rng2F32 b_border_rect = pad_2f32(b->rect, 1.f - b->visual_margin); R_Rect2DInst *inst = dr_rect(b_border_rect, border_color, 0, 1.f, border_softness*1.f); MemoryCopyArray(inst->corner_radii, b_corner_radii); diff --git a/src/ui/generated/ui.meta.c b/src/ui/generated/ui.meta.c index 790b5877..6bd3067e 100644 --- a/src/ui/generated/ui.meta.c +++ b/src/ui/generated/ui.meta.c @@ -36,6 +36,7 @@ #define UI_CornerRadius01(v) DeferLoop(ui_push_corner_radius_01(v), ui_pop_corner_radius_01()) #define UI_CornerRadius10(v) DeferLoop(ui_push_corner_radius_10(v), ui_pop_corner_radius_10()) #define UI_CornerRadius11(v) DeferLoop(ui_push_corner_radius_11(v), ui_pop_corner_radius_11()) +#define UI_VisualMargin(v) DeferLoop(ui_push_visual_margin(v), ui_pop_visual_margin()) #define UI_BlurSize(v) DeferLoop(ui_push_blur_size(v), ui_pop_blur_size()) #define UI_TextPadding(v) DeferLoop(ui_push_text_padding(v), ui_pop_text_padding()) #define UI_TextAlignment(v) DeferLoop(ui_push_text_alignment(v), ui_pop_text_alignment()) @@ -71,6 +72,7 @@ internal F32 ui_top_corner_radius_00(void) { UI_StackTopImpl(ui_state, CornerRad internal F32 ui_top_corner_radius_01(void) { UI_StackTopImpl(ui_state, CornerRadius01, corner_radius_01) } internal F32 ui_top_corner_radius_10(void) { UI_StackTopImpl(ui_state, CornerRadius10, corner_radius_10) } internal F32 ui_top_corner_radius_11(void) { UI_StackTopImpl(ui_state, CornerRadius11, corner_radius_11) } +internal F32 ui_top_visual_margin(void) { UI_StackTopImpl(ui_state, VisualMargin, visual_margin) } internal F32 ui_top_blur_size(void) { UI_StackTopImpl(ui_state, BlurSize, blur_size) } internal F32 ui_top_text_padding(void) { UI_StackTopImpl(ui_state, TextPadding, text_padding) } internal UI_TextAlign ui_top_text_alignment(void) { UI_StackTopImpl(ui_state, TextAlignment, text_alignment) } @@ -105,6 +107,7 @@ internal F32 ui_bottom_corner_radius_00(void) { UI_StackBottomImpl(ui_state, Cor internal F32 ui_bottom_corner_radius_01(void) { UI_StackBottomImpl(ui_state, CornerRadius01, corner_radius_01) } internal F32 ui_bottom_corner_radius_10(void) { UI_StackBottomImpl(ui_state, CornerRadius10, corner_radius_10) } internal F32 ui_bottom_corner_radius_11(void) { UI_StackBottomImpl(ui_state, CornerRadius11, corner_radius_11) } +internal F32 ui_bottom_visual_margin(void) { UI_StackBottomImpl(ui_state, VisualMargin, visual_margin) } internal F32 ui_bottom_blur_size(void) { UI_StackBottomImpl(ui_state, BlurSize, blur_size) } internal F32 ui_bottom_text_padding(void) { UI_StackBottomImpl(ui_state, TextPadding, text_padding) } internal UI_TextAlign ui_bottom_text_alignment(void) { UI_StackBottomImpl(ui_state, TextAlignment, text_alignment) } @@ -139,6 +142,7 @@ internal F32 ui_push_corner_radius_00(F32 v) { UI_StackPushImpl(ui_state, Corner internal F32 ui_push_corner_radius_01(F32 v) { UI_StackPushImpl(ui_state, CornerRadius01, corner_radius_01, F32, v) } internal F32 ui_push_corner_radius_10(F32 v) { UI_StackPushImpl(ui_state, CornerRadius10, corner_radius_10, F32, v) } internal F32 ui_push_corner_radius_11(F32 v) { UI_StackPushImpl(ui_state, CornerRadius11, corner_radius_11, F32, v) } +internal F32 ui_push_visual_margin(F32 v) { UI_StackPushImpl(ui_state, VisualMargin, visual_margin, F32, v) } internal F32 ui_push_blur_size(F32 v) { UI_StackPushImpl(ui_state, BlurSize, blur_size, F32, v) } internal F32 ui_push_text_padding(F32 v) { UI_StackPushImpl(ui_state, TextPadding, text_padding, F32, v) } internal UI_TextAlign ui_push_text_alignment(UI_TextAlign v) { UI_StackPushImpl(ui_state, TextAlignment, text_alignment, UI_TextAlign, v) } @@ -173,6 +177,7 @@ internal F32 ui_pop_corner_radius_00(void) { UI_StackPopImpl(ui_state, CornerRad internal F32 ui_pop_corner_radius_01(void) { UI_StackPopImpl(ui_state, CornerRadius01, corner_radius_01) } internal F32 ui_pop_corner_radius_10(void) { UI_StackPopImpl(ui_state, CornerRadius10, corner_radius_10) } internal F32 ui_pop_corner_radius_11(void) { UI_StackPopImpl(ui_state, CornerRadius11, corner_radius_11) } +internal F32 ui_pop_visual_margin(void) { UI_StackPopImpl(ui_state, VisualMargin, visual_margin) } internal F32 ui_pop_blur_size(void) { UI_StackPopImpl(ui_state, BlurSize, blur_size) } internal F32 ui_pop_text_padding(void) { UI_StackPopImpl(ui_state, TextPadding, text_padding) } internal UI_TextAlign ui_pop_text_alignment(void) { UI_StackPopImpl(ui_state, TextAlignment, text_alignment) } @@ -207,6 +212,7 @@ internal F32 ui_set_next_corner_radius_00(F32 v) { UI_StackSetNextImpl(ui_state, internal F32 ui_set_next_corner_radius_01(F32 v) { UI_StackSetNextImpl(ui_state, CornerRadius01, corner_radius_01, F32, v) } internal F32 ui_set_next_corner_radius_10(F32 v) { UI_StackSetNextImpl(ui_state, CornerRadius10, corner_radius_10, F32, v) } internal F32 ui_set_next_corner_radius_11(F32 v) { UI_StackSetNextImpl(ui_state, CornerRadius11, corner_radius_11, F32, v) } +internal F32 ui_set_next_visual_margin(F32 v) { UI_StackSetNextImpl(ui_state, VisualMargin, visual_margin, F32, v) } internal F32 ui_set_next_blur_size(F32 v) { UI_StackSetNextImpl(ui_state, BlurSize, blur_size, F32, v) } internal F32 ui_set_next_text_padding(F32 v) { UI_StackSetNextImpl(ui_state, TextPadding, text_padding, F32, v) } internal UI_TextAlign ui_set_next_text_alignment(UI_TextAlign v) { UI_StackSetNextImpl(ui_state, TextAlignment, text_alignment, UI_TextAlign, v) } diff --git a/src/ui/generated/ui.meta.h b/src/ui/generated/ui.meta.h index fbed7a1a..72d9fd59 100644 --- a/src/ui/generated/ui.meta.h +++ b/src/ui/generated/ui.meta.h @@ -38,6 +38,7 @@ typedef struct UI_CornerRadius00Node UI_CornerRadius00Node; struct UI_CornerRadi typedef struct UI_CornerRadius01Node UI_CornerRadius01Node; struct UI_CornerRadius01Node{UI_CornerRadius01Node *next; F32 v;}; typedef struct UI_CornerRadius10Node UI_CornerRadius10Node; struct UI_CornerRadius10Node{UI_CornerRadius10Node *next; F32 v;}; typedef struct UI_CornerRadius11Node UI_CornerRadius11Node; struct UI_CornerRadius11Node{UI_CornerRadius11Node *next; F32 v;}; +typedef struct UI_VisualMarginNode UI_VisualMarginNode; struct UI_VisualMarginNode{UI_VisualMarginNode *next; F32 v;}; typedef struct UI_BlurSizeNode UI_BlurSizeNode; struct UI_BlurSizeNode{UI_BlurSizeNode *next; F32 v;}; typedef struct UI_TextPaddingNode UI_TextPaddingNode; struct UI_TextPaddingNode{UI_TextPaddingNode *next; F32 v;}; typedef struct UI_TextAlignmentNode UI_TextAlignmentNode; struct UI_TextAlignmentNode{UI_TextAlignmentNode *next; UI_TextAlign v;}; @@ -76,6 +77,7 @@ UI_CornerRadius00Node corner_radius_00_nil_stack_top;\ UI_CornerRadius01Node corner_radius_01_nil_stack_top;\ UI_CornerRadius10Node corner_radius_10_nil_stack_top;\ UI_CornerRadius11Node corner_radius_11_nil_stack_top;\ +UI_VisualMarginNode visual_margin_nil_stack_top;\ UI_BlurSizeNode blur_size_nil_stack_top;\ UI_TextPaddingNode text_padding_nil_stack_top;\ UI_TextAlignmentNode text_alignment_nil_stack_top;\ @@ -113,6 +115,7 @@ state->corner_radius_00_nil_stack_top.v = 0;\ state->corner_radius_01_nil_stack_top.v = 0;\ state->corner_radius_10_nil_stack_top.v = 0;\ state->corner_radius_11_nil_stack_top.v = 0;\ +state->visual_margin_nil_stack_top.v = 0;\ state->blur_size_nil_stack_top.v = 0;\ state->text_padding_nil_stack_top.v = 0;\ state->text_alignment_nil_stack_top.v = UI_TextAlign_Left;\ @@ -152,6 +155,7 @@ struct { UI_CornerRadius00Node *top; F32 bottom_val; UI_CornerRadius00Node *free struct { UI_CornerRadius01Node *top; F32 bottom_val; UI_CornerRadius01Node *free; U64 gen; B32 auto_pop; } corner_radius_01_stack;\ struct { UI_CornerRadius10Node *top; F32 bottom_val; UI_CornerRadius10Node *free; U64 gen; B32 auto_pop; } corner_radius_10_stack;\ struct { UI_CornerRadius11Node *top; F32 bottom_val; UI_CornerRadius11Node *free; U64 gen; B32 auto_pop; } corner_radius_11_stack;\ +struct { UI_VisualMarginNode *top; F32 bottom_val; UI_VisualMarginNode *free; U64 gen; B32 auto_pop; } visual_margin_stack;\ struct { UI_BlurSizeNode *top; F32 bottom_val; UI_BlurSizeNode *free; U64 gen; B32 auto_pop; } blur_size_stack;\ struct { UI_TextPaddingNode *top; F32 bottom_val; UI_TextPaddingNode *free; U64 gen; B32 auto_pop; } text_padding_stack;\ struct { UI_TextAlignmentNode *top; UI_TextAlign bottom_val; UI_TextAlignmentNode *free; U64 gen; B32 auto_pop; } text_alignment_stack;\ @@ -189,6 +193,7 @@ state->corner_radius_00_stack.top = &state->corner_radius_00_nil_stack_top; stat state->corner_radius_01_stack.top = &state->corner_radius_01_nil_stack_top; state->corner_radius_01_stack.bottom_val = 0; state->corner_radius_01_stack.free = 0; state->corner_radius_01_stack.auto_pop = 0;\ state->corner_radius_10_stack.top = &state->corner_radius_10_nil_stack_top; state->corner_radius_10_stack.bottom_val = 0; state->corner_radius_10_stack.free = 0; state->corner_radius_10_stack.auto_pop = 0;\ state->corner_radius_11_stack.top = &state->corner_radius_11_nil_stack_top; state->corner_radius_11_stack.bottom_val = 0; state->corner_radius_11_stack.free = 0; state->corner_radius_11_stack.auto_pop = 0;\ +state->visual_margin_stack.top = &state->visual_margin_nil_stack_top; state->visual_margin_stack.bottom_val = 0; state->visual_margin_stack.free = 0; state->visual_margin_stack.auto_pop = 0;\ state->blur_size_stack.top = &state->blur_size_nil_stack_top; state->blur_size_stack.bottom_val = 0; state->blur_size_stack.free = 0; state->blur_size_stack.auto_pop = 0;\ state->text_padding_stack.top = &state->text_padding_nil_stack_top; state->text_padding_stack.bottom_val = 0; state->text_padding_stack.free = 0; state->text_padding_stack.auto_pop = 0;\ state->text_alignment_stack.top = &state->text_alignment_nil_stack_top; state->text_alignment_stack.bottom_val = UI_TextAlign_Left; state->text_alignment_stack.free = 0; state->text_alignment_stack.auto_pop = 0;\ @@ -226,6 +231,7 @@ if(state->corner_radius_00_stack.auto_pop) { ui_pop_corner_radius_00(); state->c if(state->corner_radius_01_stack.auto_pop) { ui_pop_corner_radius_01(); state->corner_radius_01_stack.auto_pop = 0; }\ if(state->corner_radius_10_stack.auto_pop) { ui_pop_corner_radius_10(); state->corner_radius_10_stack.auto_pop = 0; }\ if(state->corner_radius_11_stack.auto_pop) { ui_pop_corner_radius_11(); state->corner_radius_11_stack.auto_pop = 0; }\ +if(state->visual_margin_stack.auto_pop) { ui_pop_visual_margin(); state->visual_margin_stack.auto_pop = 0; }\ if(state->blur_size_stack.auto_pop) { ui_pop_blur_size(); state->blur_size_stack.auto_pop = 0; }\ if(state->text_padding_stack.auto_pop) { ui_pop_text_padding(); state->text_padding_stack.auto_pop = 0; }\ if(state->text_alignment_stack.auto_pop) { ui_pop_text_alignment(); state->text_alignment_stack.auto_pop = 0; }\ @@ -262,6 +268,7 @@ internal F32 ui_top_corner_radius_00(void); internal F32 ui_top_corner_radius_01(void); internal F32 ui_top_corner_radius_10(void); internal F32 ui_top_corner_radius_11(void); +internal F32 ui_top_visual_margin(void); internal F32 ui_top_blur_size(void); internal F32 ui_top_text_padding(void); internal UI_TextAlign ui_top_text_alignment(void); @@ -297,6 +304,7 @@ internal F32 ui_bottom_corner_radius_00(void); internal F32 ui_bottom_corner_radius_01(void); internal F32 ui_bottom_corner_radius_10(void); internal F32 ui_bottom_corner_radius_11(void); +internal F32 ui_bottom_visual_margin(void); internal F32 ui_bottom_blur_size(void); internal F32 ui_bottom_text_padding(void); internal UI_TextAlign ui_bottom_text_alignment(void); @@ -332,6 +340,7 @@ internal F32 ui_push_corner_radius_00(F32 v); internal F32 ui_push_corner_radius_01(F32 v); internal F32 ui_push_corner_radius_10(F32 v); internal F32 ui_push_corner_radius_11(F32 v); +internal F32 ui_push_visual_margin(F32 v); internal F32 ui_push_blur_size(F32 v); internal F32 ui_push_text_padding(F32 v); internal UI_TextAlign ui_push_text_alignment(UI_TextAlign v); @@ -367,6 +376,7 @@ internal F32 ui_pop_corner_radius_00(void); internal F32 ui_pop_corner_radius_01(void); internal F32 ui_pop_corner_radius_10(void); internal F32 ui_pop_corner_radius_11(void); +internal F32 ui_pop_visual_margin(void); internal F32 ui_pop_blur_size(void); internal F32 ui_pop_text_padding(void); internal UI_TextAlign ui_pop_text_alignment(void); @@ -402,6 +412,7 @@ internal F32 ui_set_next_corner_radius_00(F32 v); internal F32 ui_set_next_corner_radius_01(F32 v); internal F32 ui_set_next_corner_radius_10(F32 v); internal F32 ui_set_next_corner_radius_11(F32 v); +internal F32 ui_set_next_visual_margin(F32 v); internal F32 ui_set_next_blur_size(F32 v); internal F32 ui_set_next_text_padding(F32 v); internal UI_TextAlign ui_set_next_text_alignment(UI_TextAlign v); diff --git a/src/ui/ui.mdesk b/src/ui/ui.mdesk index b8636dac..b68e5f63 100644 --- a/src/ui/ui.mdesk +++ b/src/ui/ui.mdesk @@ -58,6 +58,9 @@ UI_StackTable: { CornerRadius10 corner_radius_10 F32 0 } { CornerRadius11 corner_radius_11 F32 0 } + //- rjf: visual margin + { VisualMargin visual_margin F32 0 } + //- rjf: blur size { BlurSize blur_size F32 0 } diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index 3f0e2a61..73b5ccbe 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -2524,6 +2524,7 @@ ui_build_box_from_key(UI_BoxFlags flags, UI_Key key) box->corner_radii[Corner_01] = ui_state->corner_radius_01_stack.top->v; box->corner_radii[Corner_10] = ui_state->corner_radius_10_stack.top->v; box->corner_radii[Corner_11] = ui_state->corner_radius_11_stack.top->v; + box->visual_margin = ui_state->visual_margin_stack.top->v; box->blur_size = ui_state->blur_size_stack.top->v; box->transparency = ui_state->transparency_stack.top->v; box->squish = ui_state->squish_stack.top->v; diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index b5aaa508..c04c92b1 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -402,6 +402,7 @@ struct UI_Box F32 tab_size; FNT_RasterFlags text_raster_flags; F32 corner_radii[Corner_COUNT]; + F32 visual_margin; F32 blur_size; F32 transparency; F32 squish; @@ -993,6 +994,7 @@ internal F32 ui_top_corner_radius_00(void); internal F32 ui_top_corner_radius_01(void); internal F32 ui_top_corner_radius_10(void); internal F32 ui_top_corner_radius_11(void); +internal F32 ui_top_visual_margin(void); internal F32 ui_top_blur_size(void); internal F32 ui_top_text_padding(void); internal UI_TextAlign ui_top_text_alignment(void); @@ -1028,6 +1030,7 @@ internal F32 ui_bottom_corner_radius_00(void); internal F32 ui_bottom_corner_radius_01(void); internal F32 ui_bottom_corner_radius_10(void); internal F32 ui_bottom_corner_radius_11(void); +internal F32 ui_bottom_visual_margin(void); internal F32 ui_bottom_blur_size(void); internal F32 ui_bottom_text_padding(void); internal UI_TextAlign ui_bottom_text_alignment(void); @@ -1063,6 +1066,7 @@ internal F32 ui_push_corner_radius_00(F32 v); internal F32 ui_push_corner_radius_01(F32 v); internal F32 ui_push_corner_radius_10(F32 v); internal F32 ui_push_corner_radius_11(F32 v); +internal F32 ui_push_visual_margin(F32 v); internal F32 ui_push_blur_size(F32 v); internal F32 ui_push_text_padding(F32 v); internal UI_TextAlign ui_push_text_alignment(UI_TextAlign v); @@ -1098,6 +1102,7 @@ internal F32 ui_pop_corner_radius_00(void); internal F32 ui_pop_corner_radius_01(void); internal F32 ui_pop_corner_radius_10(void); internal F32 ui_pop_corner_radius_11(void); +internal F32 ui_pop_visual_margin(void); internal F32 ui_pop_blur_size(void); internal F32 ui_pop_text_padding(void); internal UI_TextAlign ui_pop_text_alignment(void); @@ -1133,6 +1138,7 @@ internal F32 ui_set_next_corner_radius_00(F32 v); internal F32 ui_set_next_corner_radius_01(F32 v); internal F32 ui_set_next_corner_radius_10(F32 v); internal F32 ui_set_next_corner_radius_11(F32 v); +internal F32 ui_set_next_visual_margin(F32 v); internal F32 ui_set_next_blur_size(F32 v); internal F32 ui_set_next_text_padding(F32 v); internal UI_TextAlign ui_set_next_text_alignment(UI_TextAlign v); @@ -1174,6 +1180,7 @@ internal F32 ui_top_px_height(void); #define UI_Tag(v) DeferLoop(ui_push_tag(v), ui_pop_tag()) #define UI_BackgroundColor(v) DeferLoop(ui_push_background_color(v), ui_pop_background_color()) #define UI_TextColor(v) DeferLoop(ui_push_text_color(v), ui_pop_text_color()) +#define UI_BorderColor(v) DeferLoop(ui_push_border_color(v), ui_pop_border_color()) #define UI_Squish(v) DeferLoop(ui_push_squish(v), ui_pop_squish()) #define UI_HoverCursor(v) DeferLoop(ui_push_hover_cursor(v), ui_pop_hover_cursor()) #define UI_Font(v) DeferLoop(ui_push_font(v), ui_pop_font()) @@ -1184,6 +1191,7 @@ internal F32 ui_top_px_height(void); #define UI_CornerRadius01(v) DeferLoop(ui_push_corner_radius_01(v), ui_pop_corner_radius_01()) #define UI_CornerRadius10(v) DeferLoop(ui_push_corner_radius_10(v), ui_pop_corner_radius_10()) #define UI_CornerRadius11(v) DeferLoop(ui_push_corner_radius_11(v), ui_pop_corner_radius_11()) +#define UI_VisualMargin(v) DeferLoop(ui_push_visual_margin(v), ui_pop_visual_margin()) #define UI_BlurSize(v) DeferLoop(ui_push_blur_size(v), ui_pop_blur_size()) #define UI_TextPadding(v) DeferLoop(ui_push_text_padding(v), ui_pop_text_padding()) #define UI_TextAlignment(v) DeferLoop(ui_push_text_alignment(v), ui_pop_text_alignment()) From 9d655c01903913c52c21c687db90413cfe364821 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 5 Aug 2026 15:39:43 -0600 Subject: [PATCH 009/251] more adjustments --- project.4coder | 2 +- .../window_manager/linux_window_manager.c | 15 +-------- src/raddbg/raddbg_core.c | 25 ++++++++++---- src/raddbg/raddbg_views.c | 5 +-- src/raddbg/raddbg_widgets.c | 3 +- src/ui/generated/ui.meta.c | 18 ++++++---- src/ui/generated/ui.meta.h | 33 ++++++++++++------- src/ui/ui.mdesk | 3 +- src/ui/ui_core.c | 3 +- src/ui/ui_core.h | 12 ++++--- 10 files changed, 71 insertions(+), 48 deletions(-) diff --git a/project.4coder b/project.4coder index 8c4d76eb..07a58411 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && wsl ./build.sh raddbg", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index e36c76ce..52983f50 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -381,19 +381,6 @@ wm_window_open(Rng2F32 rect, WM_WindowFlags flags, String8 title) XFlush(lnx_wm_state->display); } - //- rjf: ignore .desktop files - { - XClassHint *class_hint = XAllocClassHint(); - if(class_hint) - { - class_hint->res_name = "custom_raddbg_class"; - class_hint->res_class = "CustomRaddbgClass"; - XSetClassHint(lnx_wm_state->display, w->window, class_hint); - XFree(class_hint); - XFlush(lnx_wm_state->display); - } - } - //- rjf: create xic w->xic = XCreateIC(lnx_wm_state->xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, @@ -843,7 +830,7 @@ wm_get_events(Arena *arena, B32 wait) for(U64 off = 0; off < text_size;) { UnicodeDecode decode = utf8_decode(text+off, text_size-off); - if(decode.codepoint != 0 && decode.codepoint != 127 && (decode.codepoint >= 32 || decode.codepoint == '\t')) + if(decode.codepoint != 0 && decode.codepoint != 127 && decode.codepoint >= 32) { WM_Event *e = wm_event_list_push_new(arena, &evts, WM_EventKind_Text); e->window.u64[0] = (U64)window; diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 55edc856..5dc4a9e3 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -8948,11 +8948,14 @@ rd_window_frame(void) } if(tab_is_selected && panel_tree.focused == panel) { - UI_PrefWidth(ui_px(tab_close_width_px, 1.f)) UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_px(tab_close_width_px, 1.f)) + UI_TextAlignment(UI_TextAlign_Center) RD_Font(RD_FontSlot_Icons) UI_FontSize(ui_top_font_size()*0.75f) UI_TagF(".") UI_TagF("tab") UI_TagF("weak") UI_TagF("implicit") - UI_CornerRadius(0) + UI_VisualMarginX(ceil_f32(ui_top_font_size()*0.5f)) + UI_VisualMarginY(floor_f32(tab_bar_vheight - tab_close_width_px + ui_top_font_size()*0.25f)) + UI_CornerRadius(ui_top_font_size()*1.f) { UI_Box *edit_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| UI_BoxFlag_DrawBorder| @@ -8978,12 +8981,14 @@ rd_window_frame(void) } } } - UI_PrefWidth(ui_px(tab_close_width_px, 1.f)) UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_px(tab_close_width_px, 1.f)) + UI_TextAlignment(UI_TextAlign_Center) RD_Font(RD_FontSlot_Icons) UI_FontSize(ui_top_font_size()*0.75f) UI_TagF(".") UI_TagF("tab") UI_TagF("weak") UI_TagF("implicit") - UI_CornerRadius00(0) - UI_CornerRadius01(0) + UI_VisualMarginX(ceil_f32(ui_top_font_size()*0.5f)) + UI_VisualMarginY(floor_f32(tab_bar_vheight - tab_close_width_px + ui_top_font_size()*0.25f)) + UI_CornerRadius(ui_top_font_size()*1.f) { UI_Box *close_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| UI_BoxFlag_DrawBorder| @@ -9405,7 +9410,10 @@ rd_window_frame(void) // rjf: draw background if(box->flags & UI_BoxFlag_DrawBackground) { - Rng2F32 box_bg_rect = pad_2f32(box->rect, -box->visual_margin); + Rng2F32 box_bg_rect = r2f32p(box->rect.x0 + box->visual_margin.x, + box->rect.y0 + box->visual_margin.y, + box->rect.x1 - box->visual_margin.x, + box->rect.y1 - box->visual_margin.y); // rjf: hot effect extension (drop shadow) if(box->flags & UI_BoxFlag_DrawHotEffects) @@ -9634,7 +9642,10 @@ rd_window_frame(void) if(b->flags & UI_BoxFlag_DrawBorder) { Vec4F32 border_color = b->border_color; - Rng2F32 b_border_rect = pad_2f32(b->rect, 1.f - b->visual_margin); + Rng2F32 b_border_rect = r2f32p(b->rect.x0 - 1.f + b->visual_margin.x, + b->rect.y0 - 1.f + b->visual_margin.y, + b->rect.x1 + 1.f - b->visual_margin.x, + b->rect.y1 + 1.f - b->visual_margin.y); R_Rect2DInst *inst = dr_rect(b_border_rect, border_color, 0, 1.f, border_softness*1.f); MemoryCopyArray(inst->corner_radii, b_corner_radii); diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index c067bf4c..706578bc 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -4355,11 +4355,12 @@ RD_VIEW_UI_FUNCTION_DEF(memory) if(ui_right_clicked(sig)) { rd_cmd(RD_CmdKind_PushQuery, - .expr = s("query:memory_commands"), + .expr = str8f(scratch.arena, "query:config.$%I64x", rd_regs()->view), .do_implicit_root = 1, .do_lister = 1, .activate_with_single_click = 1, - .ui_key = sig.box->key); + .ui_key = ui_get_selected_state()->root->key, + .off_px = ui_mouse(),); } // rjf: ctrl+scroll -> change font size diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 19e0ea19..11e17644 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -2097,9 +2097,10 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred } rd_cmd(RD_CmdKind_FocusPanel); rd_cmd(RD_CmdKind_PushQuery, - .expr = commands_expr, + .expr = str8f(scratch.arena, "%S, query:config.$%I64x", commands_expr, rd_regs()->view), .do_implicit_root = 1, .do_lister = 1, + .small_size = 1, .activate_with_single_click = 1, .ui_key = ui_get_selected_state()->root->key, .off_px = ui_mouse(), diff --git a/src/ui/generated/ui.meta.c b/src/ui/generated/ui.meta.c index 6bd3067e..4fd5c7fc 100644 --- a/src/ui/generated/ui.meta.c +++ b/src/ui/generated/ui.meta.c @@ -36,7 +36,8 @@ #define UI_CornerRadius01(v) DeferLoop(ui_push_corner_radius_01(v), ui_pop_corner_radius_01()) #define UI_CornerRadius10(v) DeferLoop(ui_push_corner_radius_10(v), ui_pop_corner_radius_10()) #define UI_CornerRadius11(v) DeferLoop(ui_push_corner_radius_11(v), ui_pop_corner_radius_11()) -#define UI_VisualMargin(v) DeferLoop(ui_push_visual_margin(v), ui_pop_visual_margin()) +#define UI_VisualMarginX(v) DeferLoop(ui_push_visual_margin_x(v), ui_pop_visual_margin_x()) +#define UI_VisualMarginY(v) DeferLoop(ui_push_visual_margin_y(v), ui_pop_visual_margin_y()) #define UI_BlurSize(v) DeferLoop(ui_push_blur_size(v), ui_pop_blur_size()) #define UI_TextPadding(v) DeferLoop(ui_push_text_padding(v), ui_pop_text_padding()) #define UI_TextAlignment(v) DeferLoop(ui_push_text_alignment(v), ui_pop_text_alignment()) @@ -72,7 +73,8 @@ internal F32 ui_top_corner_radius_00(void) { UI_StackTopImpl(ui_state, CornerRad internal F32 ui_top_corner_radius_01(void) { UI_StackTopImpl(ui_state, CornerRadius01, corner_radius_01) } internal F32 ui_top_corner_radius_10(void) { UI_StackTopImpl(ui_state, CornerRadius10, corner_radius_10) } internal F32 ui_top_corner_radius_11(void) { UI_StackTopImpl(ui_state, CornerRadius11, corner_radius_11) } -internal F32 ui_top_visual_margin(void) { UI_StackTopImpl(ui_state, VisualMargin, visual_margin) } +internal F32 ui_top_visual_margin_x(void) { UI_StackTopImpl(ui_state, VisualMarginX, visual_margin_x) } +internal F32 ui_top_visual_margin_y(void) { UI_StackTopImpl(ui_state, VisualMarginY, visual_margin_y) } internal F32 ui_top_blur_size(void) { UI_StackTopImpl(ui_state, BlurSize, blur_size) } internal F32 ui_top_text_padding(void) { UI_StackTopImpl(ui_state, TextPadding, text_padding) } internal UI_TextAlign ui_top_text_alignment(void) { UI_StackTopImpl(ui_state, TextAlignment, text_alignment) } @@ -107,7 +109,8 @@ internal F32 ui_bottom_corner_radius_00(void) { UI_StackBottomImpl(ui_state, Cor internal F32 ui_bottom_corner_radius_01(void) { UI_StackBottomImpl(ui_state, CornerRadius01, corner_radius_01) } internal F32 ui_bottom_corner_radius_10(void) { UI_StackBottomImpl(ui_state, CornerRadius10, corner_radius_10) } internal F32 ui_bottom_corner_radius_11(void) { UI_StackBottomImpl(ui_state, CornerRadius11, corner_radius_11) } -internal F32 ui_bottom_visual_margin(void) { UI_StackBottomImpl(ui_state, VisualMargin, visual_margin) } +internal F32 ui_bottom_visual_margin_x(void) { UI_StackBottomImpl(ui_state, VisualMarginX, visual_margin_x) } +internal F32 ui_bottom_visual_margin_y(void) { UI_StackBottomImpl(ui_state, VisualMarginY, visual_margin_y) } internal F32 ui_bottom_blur_size(void) { UI_StackBottomImpl(ui_state, BlurSize, blur_size) } internal F32 ui_bottom_text_padding(void) { UI_StackBottomImpl(ui_state, TextPadding, text_padding) } internal UI_TextAlign ui_bottom_text_alignment(void) { UI_StackBottomImpl(ui_state, TextAlignment, text_alignment) } @@ -142,7 +145,8 @@ internal F32 ui_push_corner_radius_00(F32 v) { UI_StackPushImpl(ui_state, Corner internal F32 ui_push_corner_radius_01(F32 v) { UI_StackPushImpl(ui_state, CornerRadius01, corner_radius_01, F32, v) } internal F32 ui_push_corner_radius_10(F32 v) { UI_StackPushImpl(ui_state, CornerRadius10, corner_radius_10, F32, v) } internal F32 ui_push_corner_radius_11(F32 v) { UI_StackPushImpl(ui_state, CornerRadius11, corner_radius_11, F32, v) } -internal F32 ui_push_visual_margin(F32 v) { UI_StackPushImpl(ui_state, VisualMargin, visual_margin, F32, v) } +internal F32 ui_push_visual_margin_x(F32 v) { UI_StackPushImpl(ui_state, VisualMarginX, visual_margin_x, F32, v) } +internal F32 ui_push_visual_margin_y(F32 v) { UI_StackPushImpl(ui_state, VisualMarginY, visual_margin_y, F32, v) } internal F32 ui_push_blur_size(F32 v) { UI_StackPushImpl(ui_state, BlurSize, blur_size, F32, v) } internal F32 ui_push_text_padding(F32 v) { UI_StackPushImpl(ui_state, TextPadding, text_padding, F32, v) } internal UI_TextAlign ui_push_text_alignment(UI_TextAlign v) { UI_StackPushImpl(ui_state, TextAlignment, text_alignment, UI_TextAlign, v) } @@ -177,7 +181,8 @@ internal F32 ui_pop_corner_radius_00(void) { UI_StackPopImpl(ui_state, CornerRad internal F32 ui_pop_corner_radius_01(void) { UI_StackPopImpl(ui_state, CornerRadius01, corner_radius_01) } internal F32 ui_pop_corner_radius_10(void) { UI_StackPopImpl(ui_state, CornerRadius10, corner_radius_10) } internal F32 ui_pop_corner_radius_11(void) { UI_StackPopImpl(ui_state, CornerRadius11, corner_radius_11) } -internal F32 ui_pop_visual_margin(void) { UI_StackPopImpl(ui_state, VisualMargin, visual_margin) } +internal F32 ui_pop_visual_margin_x(void) { UI_StackPopImpl(ui_state, VisualMarginX, visual_margin_x) } +internal F32 ui_pop_visual_margin_y(void) { UI_StackPopImpl(ui_state, VisualMarginY, visual_margin_y) } internal F32 ui_pop_blur_size(void) { UI_StackPopImpl(ui_state, BlurSize, blur_size) } internal F32 ui_pop_text_padding(void) { UI_StackPopImpl(ui_state, TextPadding, text_padding) } internal UI_TextAlign ui_pop_text_alignment(void) { UI_StackPopImpl(ui_state, TextAlignment, text_alignment) } @@ -212,7 +217,8 @@ internal F32 ui_set_next_corner_radius_00(F32 v) { UI_StackSetNextImpl(ui_state, internal F32 ui_set_next_corner_radius_01(F32 v) { UI_StackSetNextImpl(ui_state, CornerRadius01, corner_radius_01, F32, v) } internal F32 ui_set_next_corner_radius_10(F32 v) { UI_StackSetNextImpl(ui_state, CornerRadius10, corner_radius_10, F32, v) } internal F32 ui_set_next_corner_radius_11(F32 v) { UI_StackSetNextImpl(ui_state, CornerRadius11, corner_radius_11, F32, v) } -internal F32 ui_set_next_visual_margin(F32 v) { UI_StackSetNextImpl(ui_state, VisualMargin, visual_margin, F32, v) } +internal F32 ui_set_next_visual_margin_x(F32 v) { UI_StackSetNextImpl(ui_state, VisualMarginX, visual_margin_x, F32, v) } +internal F32 ui_set_next_visual_margin_y(F32 v) { UI_StackSetNextImpl(ui_state, VisualMarginY, visual_margin_y, F32, v) } internal F32 ui_set_next_blur_size(F32 v) { UI_StackSetNextImpl(ui_state, BlurSize, blur_size, F32, v) } internal F32 ui_set_next_text_padding(F32 v) { UI_StackSetNextImpl(ui_state, TextPadding, text_padding, F32, v) } internal UI_TextAlign ui_set_next_text_alignment(UI_TextAlign v) { UI_StackSetNextImpl(ui_state, TextAlignment, text_alignment, UI_TextAlign, v) } diff --git a/src/ui/generated/ui.meta.h b/src/ui/generated/ui.meta.h index 72d9fd59..aa40485a 100644 --- a/src/ui/generated/ui.meta.h +++ b/src/ui/generated/ui.meta.h @@ -38,7 +38,8 @@ typedef struct UI_CornerRadius00Node UI_CornerRadius00Node; struct UI_CornerRadi typedef struct UI_CornerRadius01Node UI_CornerRadius01Node; struct UI_CornerRadius01Node{UI_CornerRadius01Node *next; F32 v;}; typedef struct UI_CornerRadius10Node UI_CornerRadius10Node; struct UI_CornerRadius10Node{UI_CornerRadius10Node *next; F32 v;}; typedef struct UI_CornerRadius11Node UI_CornerRadius11Node; struct UI_CornerRadius11Node{UI_CornerRadius11Node *next; F32 v;}; -typedef struct UI_VisualMarginNode UI_VisualMarginNode; struct UI_VisualMarginNode{UI_VisualMarginNode *next; F32 v;}; +typedef struct UI_VisualMarginXNode UI_VisualMarginXNode; struct UI_VisualMarginXNode{UI_VisualMarginXNode *next; F32 v;}; +typedef struct UI_VisualMarginYNode UI_VisualMarginYNode; struct UI_VisualMarginYNode{UI_VisualMarginYNode *next; F32 v;}; typedef struct UI_BlurSizeNode UI_BlurSizeNode; struct UI_BlurSizeNode{UI_BlurSizeNode *next; F32 v;}; typedef struct UI_TextPaddingNode UI_TextPaddingNode; struct UI_TextPaddingNode{UI_TextPaddingNode *next; F32 v;}; typedef struct UI_TextAlignmentNode UI_TextAlignmentNode; struct UI_TextAlignmentNode{UI_TextAlignmentNode *next; UI_TextAlign v;}; @@ -77,7 +78,8 @@ UI_CornerRadius00Node corner_radius_00_nil_stack_top;\ UI_CornerRadius01Node corner_radius_01_nil_stack_top;\ UI_CornerRadius10Node corner_radius_10_nil_stack_top;\ UI_CornerRadius11Node corner_radius_11_nil_stack_top;\ -UI_VisualMarginNode visual_margin_nil_stack_top;\ +UI_VisualMarginXNode visual_margin_x_nil_stack_top;\ +UI_VisualMarginYNode visual_margin_y_nil_stack_top;\ UI_BlurSizeNode blur_size_nil_stack_top;\ UI_TextPaddingNode text_padding_nil_stack_top;\ UI_TextAlignmentNode text_alignment_nil_stack_top;\ @@ -115,7 +117,8 @@ state->corner_radius_00_nil_stack_top.v = 0;\ state->corner_radius_01_nil_stack_top.v = 0;\ state->corner_radius_10_nil_stack_top.v = 0;\ state->corner_radius_11_nil_stack_top.v = 0;\ -state->visual_margin_nil_stack_top.v = 0;\ +state->visual_margin_x_nil_stack_top.v = 0;\ +state->visual_margin_y_nil_stack_top.v = 0;\ state->blur_size_nil_stack_top.v = 0;\ state->text_padding_nil_stack_top.v = 0;\ state->text_alignment_nil_stack_top.v = UI_TextAlign_Left;\ @@ -155,7 +158,8 @@ struct { UI_CornerRadius00Node *top; F32 bottom_val; UI_CornerRadius00Node *free struct { UI_CornerRadius01Node *top; F32 bottom_val; UI_CornerRadius01Node *free; U64 gen; B32 auto_pop; } corner_radius_01_stack;\ struct { UI_CornerRadius10Node *top; F32 bottom_val; UI_CornerRadius10Node *free; U64 gen; B32 auto_pop; } corner_radius_10_stack;\ struct { UI_CornerRadius11Node *top; F32 bottom_val; UI_CornerRadius11Node *free; U64 gen; B32 auto_pop; } corner_radius_11_stack;\ -struct { UI_VisualMarginNode *top; F32 bottom_val; UI_VisualMarginNode *free; U64 gen; B32 auto_pop; } visual_margin_stack;\ +struct { UI_VisualMarginXNode *top; F32 bottom_val; UI_VisualMarginXNode *free; U64 gen; B32 auto_pop; } visual_margin_x_stack;\ +struct { UI_VisualMarginYNode *top; F32 bottom_val; UI_VisualMarginYNode *free; U64 gen; B32 auto_pop; } visual_margin_y_stack;\ struct { UI_BlurSizeNode *top; F32 bottom_val; UI_BlurSizeNode *free; U64 gen; B32 auto_pop; } blur_size_stack;\ struct { UI_TextPaddingNode *top; F32 bottom_val; UI_TextPaddingNode *free; U64 gen; B32 auto_pop; } text_padding_stack;\ struct { UI_TextAlignmentNode *top; UI_TextAlign bottom_val; UI_TextAlignmentNode *free; U64 gen; B32 auto_pop; } text_alignment_stack;\ @@ -193,7 +197,8 @@ state->corner_radius_00_stack.top = &state->corner_radius_00_nil_stack_top; stat state->corner_radius_01_stack.top = &state->corner_radius_01_nil_stack_top; state->corner_radius_01_stack.bottom_val = 0; state->corner_radius_01_stack.free = 0; state->corner_radius_01_stack.auto_pop = 0;\ state->corner_radius_10_stack.top = &state->corner_radius_10_nil_stack_top; state->corner_radius_10_stack.bottom_val = 0; state->corner_radius_10_stack.free = 0; state->corner_radius_10_stack.auto_pop = 0;\ state->corner_radius_11_stack.top = &state->corner_radius_11_nil_stack_top; state->corner_radius_11_stack.bottom_val = 0; state->corner_radius_11_stack.free = 0; state->corner_radius_11_stack.auto_pop = 0;\ -state->visual_margin_stack.top = &state->visual_margin_nil_stack_top; state->visual_margin_stack.bottom_val = 0; state->visual_margin_stack.free = 0; state->visual_margin_stack.auto_pop = 0;\ +state->visual_margin_x_stack.top = &state->visual_margin_x_nil_stack_top; state->visual_margin_x_stack.bottom_val = 0; state->visual_margin_x_stack.free = 0; state->visual_margin_x_stack.auto_pop = 0;\ +state->visual_margin_y_stack.top = &state->visual_margin_y_nil_stack_top; state->visual_margin_y_stack.bottom_val = 0; state->visual_margin_y_stack.free = 0; state->visual_margin_y_stack.auto_pop = 0;\ state->blur_size_stack.top = &state->blur_size_nil_stack_top; state->blur_size_stack.bottom_val = 0; state->blur_size_stack.free = 0; state->blur_size_stack.auto_pop = 0;\ state->text_padding_stack.top = &state->text_padding_nil_stack_top; state->text_padding_stack.bottom_val = 0; state->text_padding_stack.free = 0; state->text_padding_stack.auto_pop = 0;\ state->text_alignment_stack.top = &state->text_alignment_nil_stack_top; state->text_alignment_stack.bottom_val = UI_TextAlign_Left; state->text_alignment_stack.free = 0; state->text_alignment_stack.auto_pop = 0;\ @@ -231,7 +236,8 @@ if(state->corner_radius_00_stack.auto_pop) { ui_pop_corner_radius_00(); state->c if(state->corner_radius_01_stack.auto_pop) { ui_pop_corner_radius_01(); state->corner_radius_01_stack.auto_pop = 0; }\ if(state->corner_radius_10_stack.auto_pop) { ui_pop_corner_radius_10(); state->corner_radius_10_stack.auto_pop = 0; }\ if(state->corner_radius_11_stack.auto_pop) { ui_pop_corner_radius_11(); state->corner_radius_11_stack.auto_pop = 0; }\ -if(state->visual_margin_stack.auto_pop) { ui_pop_visual_margin(); state->visual_margin_stack.auto_pop = 0; }\ +if(state->visual_margin_x_stack.auto_pop) { ui_pop_visual_margin_x(); state->visual_margin_x_stack.auto_pop = 0; }\ +if(state->visual_margin_y_stack.auto_pop) { ui_pop_visual_margin_y(); state->visual_margin_y_stack.auto_pop = 0; }\ if(state->blur_size_stack.auto_pop) { ui_pop_blur_size(); state->blur_size_stack.auto_pop = 0; }\ if(state->text_padding_stack.auto_pop) { ui_pop_text_padding(); state->text_padding_stack.auto_pop = 0; }\ if(state->text_alignment_stack.auto_pop) { ui_pop_text_alignment(); state->text_alignment_stack.auto_pop = 0; }\ @@ -268,7 +274,8 @@ internal F32 ui_top_corner_radius_00(void); internal F32 ui_top_corner_radius_01(void); internal F32 ui_top_corner_radius_10(void); internal F32 ui_top_corner_radius_11(void); -internal F32 ui_top_visual_margin(void); +internal F32 ui_top_visual_margin_x(void); +internal F32 ui_top_visual_margin_y(void); internal F32 ui_top_blur_size(void); internal F32 ui_top_text_padding(void); internal UI_TextAlign ui_top_text_alignment(void); @@ -304,7 +311,8 @@ internal F32 ui_bottom_corner_radius_00(void); internal F32 ui_bottom_corner_radius_01(void); internal F32 ui_bottom_corner_radius_10(void); internal F32 ui_bottom_corner_radius_11(void); -internal F32 ui_bottom_visual_margin(void); +internal F32 ui_bottom_visual_margin_x(void); +internal F32 ui_bottom_visual_margin_y(void); internal F32 ui_bottom_blur_size(void); internal F32 ui_bottom_text_padding(void); internal UI_TextAlign ui_bottom_text_alignment(void); @@ -340,7 +348,8 @@ internal F32 ui_push_corner_radius_00(F32 v); internal F32 ui_push_corner_radius_01(F32 v); internal F32 ui_push_corner_radius_10(F32 v); internal F32 ui_push_corner_radius_11(F32 v); -internal F32 ui_push_visual_margin(F32 v); +internal F32 ui_push_visual_margin_x(F32 v); +internal F32 ui_push_visual_margin_y(F32 v); internal F32 ui_push_blur_size(F32 v); internal F32 ui_push_text_padding(F32 v); internal UI_TextAlign ui_push_text_alignment(UI_TextAlign v); @@ -376,7 +385,8 @@ internal F32 ui_pop_corner_radius_00(void); internal F32 ui_pop_corner_radius_01(void); internal F32 ui_pop_corner_radius_10(void); internal F32 ui_pop_corner_radius_11(void); -internal F32 ui_pop_visual_margin(void); +internal F32 ui_pop_visual_margin_x(void); +internal F32 ui_pop_visual_margin_y(void); internal F32 ui_pop_blur_size(void); internal F32 ui_pop_text_padding(void); internal UI_TextAlign ui_pop_text_alignment(void); @@ -412,7 +422,8 @@ internal F32 ui_set_next_corner_radius_00(F32 v); internal F32 ui_set_next_corner_radius_01(F32 v); internal F32 ui_set_next_corner_radius_10(F32 v); internal F32 ui_set_next_corner_radius_11(F32 v); -internal F32 ui_set_next_visual_margin(F32 v); +internal F32 ui_set_next_visual_margin_x(F32 v); +internal F32 ui_set_next_visual_margin_y(F32 v); internal F32 ui_set_next_blur_size(F32 v); internal F32 ui_set_next_text_padding(F32 v); internal UI_TextAlign ui_set_next_text_alignment(UI_TextAlign v); diff --git a/src/ui/ui.mdesk b/src/ui/ui.mdesk index b68e5f63..0fc67d8c 100644 --- a/src/ui/ui.mdesk +++ b/src/ui/ui.mdesk @@ -59,7 +59,8 @@ UI_StackTable: { CornerRadius11 corner_radius_11 F32 0 } //- rjf: visual margin - { VisualMargin visual_margin F32 0 } + { VisualMarginX visual_margin_x F32 0 } + { VisualMarginY visual_margin_y F32 0 } //- rjf: blur size { BlurSize blur_size F32 0 } diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index 73b5ccbe..bc4f58a4 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -2524,7 +2524,8 @@ ui_build_box_from_key(UI_BoxFlags flags, UI_Key key) box->corner_radii[Corner_01] = ui_state->corner_radius_01_stack.top->v; box->corner_radii[Corner_10] = ui_state->corner_radius_10_stack.top->v; box->corner_radii[Corner_11] = ui_state->corner_radius_11_stack.top->v; - box->visual_margin = ui_state->visual_margin_stack.top->v; + box->visual_margin.x = ui_state->visual_margin_x_stack.top->v; + box->visual_margin.y = ui_state->visual_margin_y_stack.top->v; box->blur_size = ui_state->blur_size_stack.top->v; box->transparency = ui_state->transparency_stack.top->v; box->squish = ui_state->squish_stack.top->v; diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index c04c92b1..5694122c 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -402,7 +402,7 @@ struct UI_Box F32 tab_size; FNT_RasterFlags text_raster_flags; F32 corner_radii[Corner_COUNT]; - F32 visual_margin; + Vec2F32 visual_margin; F32 blur_size; F32 transparency; F32 squish; @@ -1066,7 +1066,8 @@ internal F32 ui_push_corner_radius_00(F32 v); internal F32 ui_push_corner_radius_01(F32 v); internal F32 ui_push_corner_radius_10(F32 v); internal F32 ui_push_corner_radius_11(F32 v); -internal F32 ui_push_visual_margin(F32 v); +internal F32 ui_push_visual_margin_x(F32 v); +internal F32 ui_push_visual_margin_y(F32 v); internal F32 ui_push_blur_size(F32 v); internal F32 ui_push_text_padding(F32 v); internal UI_TextAlign ui_push_text_alignment(UI_TextAlign v); @@ -1102,7 +1103,8 @@ internal F32 ui_pop_corner_radius_00(void); internal F32 ui_pop_corner_radius_01(void); internal F32 ui_pop_corner_radius_10(void); internal F32 ui_pop_corner_radius_11(void); -internal F32 ui_pop_visual_margin(void); +internal F32 ui_pop_visual_margin_x(void); +internal F32 ui_pop_visual_margin_y(void); internal F32 ui_pop_blur_size(void); internal F32 ui_pop_text_padding(void); internal UI_TextAlign ui_pop_text_alignment(void); @@ -1191,7 +1193,8 @@ internal F32 ui_top_px_height(void); #define UI_CornerRadius01(v) DeferLoop(ui_push_corner_radius_01(v), ui_pop_corner_radius_01()) #define UI_CornerRadius10(v) DeferLoop(ui_push_corner_radius_10(v), ui_pop_corner_radius_10()) #define UI_CornerRadius11(v) DeferLoop(ui_push_corner_radius_11(v), ui_pop_corner_radius_11()) -#define UI_VisualMargin(v) DeferLoop(ui_push_visual_margin(v), ui_pop_visual_margin()) +#define UI_VisualMarginX(v) DeferLoop(ui_push_visual_margin_x(v), ui_pop_visual_margin_x()) +#define UI_VisualMarginY(v) DeferLoop(ui_push_visual_margin_y(v), ui_pop_visual_margin_y()) #define UI_BlurSize(v) DeferLoop(ui_push_blur_size(v), ui_pop_blur_size()) #define UI_TextPadding(v) DeferLoop(ui_push_text_padding(v), ui_pop_text_padding()) #define UI_TextAlignment(v) DeferLoop(ui_push_text_alignment(v), ui_pop_text_alignment()) @@ -1207,6 +1210,7 @@ internal F32 ui_top_px_height(void); #define UI_Focus(kind) DeferLoop((ui_push_focus_hot(kind), ui_push_focus_active(kind)), (ui_pop_focus_hot(), ui_pop_focus_active())) #define UI_FlagsAdd(v) DeferLoop(ui_push_flags(ui_top_flags()|(v)), ui_pop_flags()) #define UI_TagF(...) DeferLoop(ui_push_tagf(__VA_ARGS__), ui_pop_tag()) +#define UI_VisualMargin(v) DeferLoop((ui_push_visual_margin_x(v), ui_push_visual_margin_y(v)), (ui_pop_visual_margin_x(), ui_pop_visual_margin_y())) //- rjf: tooltip #define UI_TooltipBase DeferLoop(ui_tooltip_begin_base(), ui_tooltip_end_base()) From 6a33efe18285dfb09b6f18de1ba017a7642ad935 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 5 Aug 2026 15:47:46 -0600 Subject: [PATCH 010/251] command metadata adjustments --- src/raddbg/generated/raddbg.meta.c | 2 +- src/raddbg/raddbg.mdesk | 2 +- src/raddbg/raddbg_views.c | 2 +- src/raddbg/raddbg_widgets.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 7b4e96f5..140901eb 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -440,7 +440,7 @@ RD_VocabInfo rd_vocab_info_table[372] = RD_NameSchemaInfo rd_name_schema_info_table[39] = { -{str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @description(\"Controls whether or not a movement trail of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n}\n")}, +{str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n}\n")}, {str8_lit_comp("project"), 0, str8_lit_comp("@expand_commands(edit_project_theme) x:\n{\n @display_name('Project Name') 'name': string,\n @default(2) @display_name('Project Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: visualizers\n @display_name('Display Pointer Addresses Before Contents') @description(\"When visualizing pointers, always shows the address first, before showing contents at the pointer's address.\")\n @default(0) display_pointer_addresses_before_contents: bool,\n @display_name('Use Default C++ STL Type Visualizers') @description(\"Enables the built-in type views for C++ STL types.\")\n @default(1) use_default_stl_type_views: bool,\n @display_name('Use Default Unreal Engine Type Visualizers') @description(\"Enables the built-in type views for Unreal Engine types.\")\n @default(1) use_default_ue_type_views: bool,\n\n //- rjf: theme\n @default(\"None\") @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme': string,\n @no_expand @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme_colors': set,\n\n //- rjf: exception settings\n @default(1) @display_name(\"Break On Win32 Control-C Exceptions\") @description(\"Code: 0x40010005\")\n win32_ctrl_c: bool;\n @default(1) @display_name(\"Break On Win32 Control-Break Exceptions\") @description(\"Code: 0x40010008\")\n win32_ctrl_break: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Originate Error Exceptions\") @description(\"Code: 0x40080201\")\n win32_win_rt_originate_error: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Transform Error Exceptions\") @description(\"Code: 0x40080202\")\n win32_win_rt_transform_error: bool;\n @default(0) @display_name(\"Break On Win32 RPC Call Cancelled Exceptions\") @description(\"Code: 0x0000071a\")\n win32_rpc_call_cancelled: bool;\n @default(0) @display_name(\"Break On Win32 Data Type Misalignment Exceptions\") @description(\"Code: 0x80000002\")\n win32_datatype_misalignment: bool;\n @default(1) @display_name(\"Break On Win32 Access Violation Exceptions\") @description(\"Code: 0xc0000005\")\n win32_access_violation: bool;\n @default(0) @display_name(\"Break On Win32 In Page Error Exceptions\") @description(\"Code: 0xc0000006\")\n win32_in_page_error: bool;\n @default(1) @display_name(\"Break On Win32 Invalid Handle Specified Exceptions\") @description(\"Code: 0xc0000008\")\n win32_invalid_handle: bool;\n @default(0) @display_name(\"Break On Win32 Not Enough Quota Exceptions\") @description(\"Code: 0xc0000017\")\n win32_not_enough_quota: bool;\n @default(0) @display_name(\"Break On Win32 Illegal Instruction Exceptions\") @description(\"Code: 0xc000001d\")\n win32_illegal_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Cannot Continue From Exception Exceptions\") @description(\"Code: 0xc0000025\")\n win32_cannot_continue_exception: bool;\n @default(0) @display_name(\"Break On Win32 Invalid Exception Disposition Returned By Handler Exceptions\") @description(\"Code: 0xc0000026\")\n win32_invalid_exception_disposition: bool;\n @default(0) @display_name(\"Break On Win32 Array Bounds Exceeded Exceptions\") @description(\"Code: 0xc000008c\")\n win32_array_bounds_exceeded: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Denormal Operand Exceptions\") @description(\"Code: 0xc000008d\")\n win32_floating_point_denormal_operand: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Division By Zero Exceptions\") @description(\"Code: 0xc000008e\")\n win32_floating_point_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Inexact Result Exceptions\") @description(\"Code: 0xc000008f\")\n win32_floating_point_inexact_result: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Invalid Operation Exceptions\") @description(\"Code: 0xc0000090\")\n win32_floating_point_invalid_operation: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Overflow Exceptions\") @description(\"Code: 0xc0000091\")\n win32_floating_point_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Stack Check Exceptions\") @description(\"Code: 0xc0000092\")\n win32_floating_point_stack_check: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Underflow Exceptions\") @description(\"Code: 0xc0000093\")\n win32_floating_point_underflow: bool;\n @default(0) @display_name(\"Break On Win32 Integer Division By Zero Exceptions\") @description(\"Code: 0xc0000094\")\n win32_integer_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Integer Overflow Exceptions\") @description(\"Code: 0xc0000095\")\n win32_integer_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Privileged Instruction Exceptions\") @description(\"Code: 0xc0000096\")\n win32_privileged_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Stack Overflow Exceptions\") @description(\"Code: 0xc00000fd\")\n win32_stack_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Unable To Locate DLL Exceptions\") @description(\"Code: 0xc0000135\")\n win32_unable_to_locate_dll: bool;\n @default(0) @display_name(\"Break On Win32 Ordinal Not Found Exceptions\") @description(\"Code: 0xc0000138\")\n win32_ordinal_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Entry Point Not Found Exceptions\") @description(\"Code: 0xc0000139\")\n win32_entry_point_not_found: bool;\n @default(0) @display_name(\"Break On Win32 DLL Initialization Failed Exceptions\") @description(\"Code: 0xc0000142\")\n win32_dll_initialization_failed: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Faults Exceptions\") @description(\"Code: 0xc00002b4\")\n win32_floating_point_sse_multiple_faults: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Traps Exceptions\") @description(\"Code: 0xc00002b5\")\n win32_floating_point_sse_multiple_traps: bool;\n @default(1) @display_name(\"Break On Win32 Assertion Failed Exceptions\") @description(\"Code: 0xc0000420\")\n win32_assertion_failed: bool;\n @default(0) @display_name(\"Break On Win32 Module Not Found Exceptions\") @description(\"Code: 0xc06d007e\")\n win32_module_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Procedure Not Found Exceptions\") @description(\"Code: 0xc06d007f\")\n win32_procedure_not_found: bool;\n @default(1) @display_name(\"Break On Win32 Sanitizer Error Detected Exceptions\") @description(\"Code: 0xe073616e\")\n win32_sanitizer_error_detected: bool;\n @default(0) @display_name(\"Break On Win32 Sanitizer Raw Access Violation Exceptions\") @description(\"Code: 0xe0736171\")\n win32_sanitizer_raw_access_violation: bool;\n @default(1) @display_name(\"Break On Win32 DirectX Debug Layer Exceptions\") @description(\"Code: 0x0000087a\")\n win32_directx_debug_layer: bool;\n}\n")}, {str8_lit_comp("theme_color"), 0, str8_lit_comp("@collection_commands(add_theme_color, fork_theme, save_theme, save_and_set_theme)\n@row_commands(duplicate_cfg, remove_cfg)\nx:\n{\n @display_name('Tags') tags: string,\n @display_name('Value') value: @color @hex u32,\n}\n")}, {str8_lit_comp("window"), 0, str8_lit_comp("x:\n{\n //- rjf: text rasterization settings\n @default(1) @display_name('Smooth UI Text') @description(\"Controls whether or not UI text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_ui_text': bool,\n @default(1) @display_name('Hint UI Text') @description(\"Controls whether or not UI text is hinted, for better text readability at small sizes.\")\n 'hint_ui_text': bool,\n @default(0) @display_name('Smooth Code Text') @description(\"Controls whether or not code text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_code_text': bool,\n @default(1) @display_name('Hint Code Text') @description(\"Controls whether or not code text is hinted, for better text readability at small sizes.\")\n 'hint_code_text': bool,\n @default(11) @display_name('Window Font Size') @description(\"Controls the window's default font size. Does not apply to tabs with their own font size set.\")\n 'font_size': @range[6, 72] u64,\n\n //- rjf: size settings\n @default(3.f) @display_name('Window Row Height') @description(\"Controls the window's default row height, in multiples of the font size. Does not apply to tabs with their own row height set.\")\n 'row_height': @range[1.75f, 5.f] f32,\n @default(3.f) @description(\"Controls the height of tabs, in multiples of the font size.\")\n 'tab_height': @range[1.75f, 5.f] f32,\n\n //- rjf: theme settings\n @default(1) @display_name('Use Project Theme') @description(\"Prefer using the project theme for this window, if any. If off, only the user's theme settings will be used.\")\n 'use_project_theme': bool,\n}\n")}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 0d9817f2..eb44fc9a 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -274,7 +274,7 @@ RD_VocabTable: 'cursor_scope_end_annotations': bool, //- rjf: cursor decorations - @default(1) @display_name('Cursor Trail') @description("Controls whether or not a movement trail of the cursor is drawn.") + @default(1) @display_name('Cursor Trail') @expand_if("$.animations") @description("Controls whether or not a movement trail animation of the cursor is drawn.") 'cursor_trail': bool, //- rjf: thread & breakpoint decorations diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 706578bc..11c8a90c 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -4360,7 +4360,7 @@ RD_VIEW_UI_FUNCTION_DEF(memory) .do_lister = 1, .activate_with_single_click = 1, .ui_key = ui_get_selected_state()->root->key, - .off_px = ui_mouse(),); + .off_px = ui_mouse()); } // rjf: ctrl+scroll -> change font size diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 11e17644..af3027c7 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -1320,7 +1320,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred B32 do_bp_glow = rd_setting_b32_from_name(s("breakpoint_glow")); B32 do_scope_lines = rd_setting_b32_from_name(s("cursor_scope_lines")); B32 do_scope_end_annotations = rd_setting_b32_from_name(s("cursor_scope_end_annotations")); - B32 do_cursor_trail = rd_setting_b32_from_name(s("cursor_trail")); + B32 do_cursor_trail = rd_setting_b32_from_name(s("animations")) && rd_setting_b32_from_name(s("cursor_trail")); B32 do_autos = rd_setting_b32_from_name(s("show_autos_in_src_and_disasm")); Vec4F32 pop_color = {0}; UI_TagF("pop") From 5b5573c098b3fa3c7661af9d2c81347d05611129 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 10:14:43 -0600 Subject: [PATCH 011/251] merge tab + menu w/ open tab command, flatten source files listing into tab menu --- project.raddbg | 7 ++- src/raddbg/generated/raddbg.meta.c | 4 +- src/raddbg/raddbg.mdesk | 4 +- src/raddbg/raddbg_core.c | 72 ++++++++++++++++++------------ 4 files changed, 54 insertions(+), 33 deletions(-) mode change 100644 => 100755 project.raddbg diff --git a/project.raddbg b/project.raddbg old mode 100644 new mode 100755 index afa0b2b8..36f1e2c7 --- a/project.raddbg +++ b/project.raddbg @@ -7,7 +7,6 @@ target: working_directory: "../raddebugger" arguments: "--user:C:/devel/raddebugger/build/raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { @@ -73,3 +72,9 @@ target: executable: "../../Program Files/SuperTux/bin/supertux2.exe" working_directory: "../../Program Files/SuperTux/bin" } +target: +{ + executable: "build/raddbg" + working_directory: "build/" + enabled: 1 +} diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 140901eb..ae94b5c5 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -688,7 +688,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[259] = { str8_lit_comp("prev_tab"), str8_lit_comp("Focuses the previous tab on the active panel."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("move_tab_right"), str8_lit_comp("Moves the selected tab right one slot."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("move_tab_left"), str8_lit_comp("Moves the selected tab left one slot."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, -{ str8_lit_comp("open_tab"), str8_lit_comp("Opens a new tab."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CmdName, str8_lit_comp("query:tab_commands")}}, +{ str8_lit_comp("open_tab"), str8_lit_comp("Opens a new tab."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("build_tab"), str8_lit_comp("Opens a new tab with the parameterized view specification."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("duplicate_tab"), str8_lit_comp("Duplicates a tab."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Tab, str8_lit_comp("")}}, { str8_lit_comp("copy_tab_full_path"), str8_lit_comp("Copies the full path of the file being viewed by this tab."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Tab, str8_lit_comp("")}}, @@ -699,7 +699,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[259] = { str8_lit_comp("tab_settings"), str8_lit_comp("Opens settings for a tab."), str8_lit_comp("view,options"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("set_current_path"), str8_lit_comp("Sets the debugger's current path, which is used as a starting point when browsing for files."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("open"), str8_lit_comp("Opens a file."), str8_lit_comp("code,source,file"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*1)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, -{ str8_lit_comp("open_source_file_from_debug_info"), str8_lit_comp("Opens a source file found within loaded debug info."), str8_lit_comp("code,source,file"), str8_lit_comp("{tab_commands}"), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:source_files")}}, +{ str8_lit_comp("open_source_file_from_debug_info"), str8_lit_comp("Opens a source file found within loaded debug info."), str8_lit_comp("code,source,file"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:source_files")}}, { str8_lit_comp("switch_to_partner_file"), str8_lit_comp("Switches to the focused file's partner; or from header to implementation or vice versa."), str8_lit_comp("code,source,file"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("show_file_in_explorer"), str8_lit_comp("Opens the operating system's file explorer and shows the selected file."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("go_to_disassembly"), str8_lit_comp("Goes to the disassembly, if any, for a given source code line."), str8_lit_comp("code,source,disassembly,disasm"), str8_lit_comp("{text_pt_commands}"), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index eb44fc9a..2f38fd18 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1039,7 +1039,7 @@ RD_CmdTable: // | | | | {PrevTab 0 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "prev_tab" "Focus Previous Tab" "Focuses the previous tab on the active panel." "" "" } {MoveTabRight 0 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "move_tab_right" "Move Tab Right" "Moves the selected tab right one slot." "" "" } {MoveTabLeft 0 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "move_tab_left" "Move Tab Left" "Moves the selected tab left one slot." "" "" } - {OpenTab 0 1 1 "query:tab_commands" CmdName 0 0 0 0 0 1 1 Null "open_tab" "Open New Tab" "Opens a new tab." "" "" } + {OpenTab 0 1 0 "" Null 0 0 0 0 0 0 0 Null "open_tab" "Open New Tab" "Opens a new tab." "" "" } {BuildTab 0 0 0 "" Null 0 0 0 0 0 0 0 Null "build_tab" "Build Tab" "Opens a new tab with the parameterized view specification." "" "" } {DuplicateTab 0 1 1 "" Tab 0 0 0 0 0 0 0 Duplicate "duplicate_tab" "Duplicate Tab" "Duplicates a tab." "" "" } {CopyTabFullPath 0 0 0 "" Tab 0 0 0 0 0 0 0 Clipboard "copy_tab_full_path" "Copy Full Path" "Copies the full path of the file being viewed by this tab." "" "" } @@ -1052,7 +1052,7 @@ RD_CmdTable: // | | | | //- rjf: files {SetCurrentPath 0 0 1 "" Null 0 0 0 0 0 0 0 FileOutline "set_current_path" "Set Current Path" "Sets the debugger's current path, which is used as a starting point when browsing for files." "" "" } {Open 0 1 1 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 FileOutline "open" "Open" "Opens a file." "code,source,file" "" } - {OpenSourceFileFromDebugInfo 0 1 0 "query:source_files" Cfg 0 0 0 0 0 1 1 FileOutline "open_source_file_from_debug_info" "Open Source File From Debug Info" "Opens a source file found within loaded debug info." "code,source,file" "{tab_commands}" } + {OpenSourceFileFromDebugInfo 0 1 0 "query:source_files" Cfg 0 0 0 0 0 1 1 FileOutline "open_source_file_from_debug_info" "Open Source File From Debug Info" "Opens a source file found within loaded debug info." "code,source,file" "" } {SwitchToPartnerFile 0 1 1 "" Null 0 0 0 0 0 0 0 FileOutline "switch_to_partner_file" "Switch To Partner File" "Switches to the focused file's partner; or from header to implementation or vice versa." "code,source,file" "" } {ShowFileInExplorer 0 1 1 "" Null 0 0 0 0 0 0 0 FolderClosedFilled "show_file_in_explorer" "Show File In Explorer" "Opens the operating system's file explorer and shows the selected file." "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 5dc4a9e3..cbedf632 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2533,7 +2533,7 @@ rd_view_ui(Rng2F32 rect) case E_SpaceKind_FileSystem: { String8 file = rd_file_path_from_eval(scratch.arena, eval); - rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = file, .vaddr = 0); + rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = file, .vaddr = 0, .prefer_new_tab = cfg_node_child_from_string(view, s("prefer_new_tab")) != &cfg_nil_node); }break; case RD_EvalSpaceKind_MetaCfg: { @@ -9094,25 +9094,25 @@ rd_window_frame(void) { ui_spacer(ui_px(1.f, 1.f)); } - UI_CornerRadius00(panel->tab_side == Side_Min ? corner_radius : 0) - UI_CornerRadius10(panel->tab_side == Side_Min ? corner_radius : 0) - UI_CornerRadius01(panel->tab_side == Side_Max ? corner_radius : 0) - UI_CornerRadius11(panel->tab_side == Side_Max ? corner_radius : 0) - RD_Font(RD_FontSlot_Icons) - UI_FontSize(ui_top_font_size()) - UI_TagF("implicit") - UI_TagF("weak") { - UI_Box *add_new_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText| - UI_BoxFlag_DrawBorder| - UI_BoxFlag_DrawBackground| - UI_BoxFlag_DrawHotEffects| - UI_BoxFlag_DrawActiveEffects| - UI_BoxFlag_Clickable| - UI_BoxFlag_DisableTextTrunc, - "%S##add_new_tab_button_%p", - rd_icon_kind_text_table[RD_IconKind_Add], - panel->cfg); + UI_Box *add_new_box = &ui_nil_box; + RD_Font(RD_FontSlot_Icons) + UI_CornerRadius((tab_bar_vheight - tab_bar_vheight/4.f) / 3.f) + UI_VisualMargin(tab_bar_vheight/4.f) + UI_TagF("implicit") + UI_TagF("weak") + { + add_new_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects| + UI_BoxFlag_Clickable| + UI_BoxFlag_DisableTextTrunc, + "%S##add_new_tab_button_%p", + rd_icon_kind_text_table[RD_IconKind_Add], + panel->cfg); + } UI_Signal sig = ui_signal_from_box(add_new_box); if(ui_pressed(sig)) { @@ -9124,14 +9124,17 @@ rd_window_frame(void) } else { - rd_cmd(RD_CmdKind_PushQuery, - .expr = str8_lit("query:tab_commands"), - .panel = panel->cfg->id, - .do_implicit_root = 1, - .small_size = 1, - .do_lister = 1, - .activate_with_single_click = 1, - .ui_key = add_new_box->key); + rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_OpenTab].string); + } + } + if(ui_hovering(sig)) UI_Tooltip + { + ui_state->tooltip_anchor_key = add_new_box->key; + ui_set_next_pref_width(ui_children_sum(1)); + UI_Row + { + ui_labelf("Open New Tab"); + rd_cmd_binding_buttons(rd_cmd_kind_info_table[RD_CmdKind_OpenTab].string, s(""), 1, RD_CmdBindingButtonFlag_NoEdit); } } } @@ -13468,9 +13471,14 @@ rd_frame(void) rd_cmd(RD_CmdKind_PushQuery, .expr = expr, .do_implicit_root = 1, .do_lister = 1, .do_big_rows = 1, .view = tab->id, .tab = tab->id); }break; + //- rjf: tab opening + case RD_CmdKind_OpenTab: + { + rd_cmd(RD_CmdKind_PushQuery, .expr = s("query:tab_commands, query:source_files"), .do_implicit_root = 1, .do_lister = 1, .prefer_new_tab = 1); + }break; + //- rjf: command fast paths case RD_CmdKind_RunCommand: - case RD_CmdKind_OpenTab: { RD_CmdKindInfo *info = rd_cmd_kind_info_from_string(cmd->regs->cmd_name); @@ -16398,6 +16406,14 @@ rd_frame(void) { cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("activate_with_single_click")); } + if(!rd_regs()->prefer_new_tab) + { + cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, s("prefer_new_tab"))); + } + else + { + cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("prefer_new_tab")); + } } // rjf: choose initial input string From 32ef9095e01e540cb2c975041fd82b311fc04324 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 10:27:50 -0600 Subject: [PATCH 012/251] apply global animations toggle to all cursor trails --- src/raddbg/raddbg_widgets.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index af3027c7..825afec7 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -3308,7 +3308,7 @@ rd_cell(RD_CellParams *params, String8 string) { ProfBeginFunction(); Temp scratch = scratch_begin(0, 0); - B32 do_cursor_trail = rd_setting_b32_from_name(str8_lit("cursor_trail")); + B32 do_cursor_trail = rd_setting_b32_from_name(s("animations")) && rd_setting_b32_from_name(str8_lit("cursor_trail")); ////////////////////////////// //- rjf: unpack visual metrics From 867b64f7a22013ccf9e32e4f97e7a6596f11ae19 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 12:53:05 -0600 Subject: [PATCH 013/251] split shell operation abstraction from graphical window manager abstraction - do basic integration of common linux utilities for implementing shell operations - also add 'sh_install_or_uninstall_self' for doing shell-specific operations related to installing or uninstalling an application. hook up to config state in raddbg --- data/logo.png | Bin 0 -> 7061 bytes project.4coder | 2 +- src/base/base_arena.c | 18 +- src/linux/shell/generated/linux_shell.meta.c | 5 + src/linux/shell/generated/linux_shell.meta.h | 129 ++++++++++++ src/linux/shell/linux_shell.c | 189 ++++++++++++++++++ src/linux/shell/linux_shell.h | 12 ++ src/linux/shell/linux_shell.mdesk | 4 + .../window_manager/linux_window_manager.c | 44 +--- src/raddbg/generated/raddbg.meta.c | 2 +- src/raddbg/raddbg.mdesk | 3 + src/raddbg/raddbg_core.c | 19 +- src/raddbg/raddbg_core.h | 3 + src/raddbg/raddbg_main.c | 42 ++-- src/render/d3d11/render_d3d11.c | 10 +- .../linux/egl/render_opengl_linux_egl.c | 16 +- .../linux/glx/render_opengl_linux_glx.c | 12 +- src/shell/shell.c | 2 + src/shell/shell.h | 16 ++ src/shell/shell_inc.c | 11 + src/shell/shell_inc.h | 16 ++ src/win32/shell/win32_shell.c | 78 ++++++++ src/win32/shell/win32_shell.h | 7 + .../window_manager/win32_window_manager.c | 73 ------- src/window_manager/window_manager.h | 12 -- src/window_manager/window_manager_stub.c | 27 --- 26 files changed, 551 insertions(+), 201 deletions(-) create mode 100644 data/logo.png create mode 100644 src/linux/shell/generated/linux_shell.meta.c create mode 100644 src/linux/shell/generated/linux_shell.meta.h create mode 100644 src/linux/shell/linux_shell.c create mode 100644 src/linux/shell/linux_shell.h create mode 100644 src/linux/shell/linux_shell.mdesk create mode 100644 src/shell/shell.c create mode 100644 src/shell/shell.h create mode 100644 src/shell/shell_inc.c create mode 100644 src/shell/shell_inc.h create mode 100644 src/win32/shell/win32_shell.c create mode 100644 src/win32/shell/win32_shell.h diff --git a/data/logo.png b/data/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..10f903efdd569c4680db6941f4b62693ee19a255 GIT binary patch literal 7061 zcmZu$c|26__rJ5)$G&9jq{#<`kv(gKtjU&L2uW138#6>@kL+ZWHCdB2nG9L7CHvBh zeaV_V6TjOZzyH3k`?~kJ_nh;b=iKw0*Ylj`eO{Ov>oU^w&;tO#sIRAO1^^K76#~#w zgN@yzlDl97ch@x11c2&928uJ)Kkj2QT`kB&w`4f@A>^WGW&{AiC;-4d1%N*w3A+LS zPox20%?SXMGXUV6Z%)(At005=j)AT=aQ5%Qw-l#<6gpqM+m8W&f&JeN0kU$q0f1*+ zUt7~WaB?FzD1&39@oV^u>WnH(R$3NOhELFYij4XXM=CR5dc|&^xy|Sl6uqdO-S+TrcKjN(r&M; zq6`74z9`*~o*e;go-cOsS6{QM{Zw zNUU#ORWp=`%_lstW%Z{TMAEtil~_83+1~PS;ShvvLASV3AJ;gM*sjF!6F@F)-0OX1 zuH9(wBH>e=V|4eqHB}_mqjeBAI`XNgZ_E^5rfX6<`i^BXt4(*!6Nx?ge3S=es_Qw> zx~saK<@_io*q&NwXs)vE>krJEN$F$lmMuxmC<15PkP-tDi_ln>bOqEFSaLrkL5?0% zT4@G zq{I$pQ>yjyD@l+NxFzB#LZiEqo3;;f*C za`cf1jl#-1!mhxL;0`ShCVW-r8S9XN*Avq#fCs#XG39QQ7AIDaTX-itmdVl()Ow-v zjtD62-=Pgk1D$6V3etxY#CS*|5OZ!+HYdFW5Y-D|JF|QNJ&{15fz+m$_#}u}Hz#kt z61I)!EI{NhC0y_OxcDWF!h`eXRHh|-Vmm(RA`;iNI5jlwk7IFpkXEYT|3s$TsHEu{ zI;AJx_;vxGBq?lUEiyg+nMg9LBUAx;R%{!I1}f=V&u=r3(fDSOWX>{dHdci(#fbhH zg3#9{MTMcG@N8|a{Ia}?N`Hy1R z-V|3n!OIaD;e2PQl;4@mL%uO|SRS=o3n&2Ly<=}gcDSqMBh)iCDwn1Er5i1%lbXqb zG*|WSGGD&*K|B&kkZ25Sk`!<$c`_m>+Siy&MQCg+p`MfJ0f=mWJ@Cqri5GF~h?HVC zU?QO1_g9ZYaz~{n_0Rh*v|Da9{HaeyT6r37ScQ%KKp4VNu4atZQ?-oVk5fs^wC>*% zu9KaJt`!KU&JIV$UP#m8kyRzsp$0K~fmq~@>exJs`_Vu05athMnDq&NciqmWq z=Y`YE_z{90(KQnx^mxb0+G<1fcW4QRbfwXgZ7E_wN7LF_f79Bt!ixizFm~*VX#K}m zufKfarry>xG&YXMF=PKSrYIvdPC5iXRt(Z8`5rN9O!MqAzKo`iIMf@ULn?kielsLX z7yDQRw*l{~$IZ!C7R$wiiXiSy-V%NapP8;Y%$=j^S|tctK<=NdsdF{xHj2<^R7ZEW zps`!AXQpH-zicB8`AdYC(cS890O@%v)Ed5Lj-eIYz92ow7(hLZrtvLnmkQN3A#+&( zt7T{0WaQ%0ZDAKg%5ta|ApSQh8rsi9l_LP17TICQW2Qo#)Id#FrJn9y!(p$@S*=R7 zE4;6YhVEmK(2~7#bG*T`gZ7Xq2_W@UtHE82rRp{X)ZzQxl&~p`y%8>KcZH*>9QwRd zktfhHO!8?+G!&BEE=ZLIgbmkgQH1hf%~RFb=l0jIyB9FRlSYCDvOg<<_cM>vdVr^0 z&j+xW;|l0{34jsbVR$K{0$UDlf>v3M+C$ghoN|b`sC|5gT?*wFB5@Lw*jiasdM^_u zUcU$4%Z(eMr_A(ecf8XeGT^*;aS|p#{_R_gIg6$Agr+~?wChEu>>^%j&_BGWa)(l& zKj|o{3mJCFsIe8amiFaOX77OgY!)RKx+?dIGHmGkFX2t?XD~M_MXmIkE-O!dTZGd?xnWrE2Q*T^oJ(VrxD0M~ueOzk(XL<7U>gbuz{I28Qs|BBrd+QSpb6tEE ztqaTw#wXpU#zyP%9FO;i?}K!FqU%#I1p&Rm;~zOSu3l%cMzjw*ZoDO4{<{*?V;w9I ze);9)nb0SjrD=D$>;e&9?tP{rwTa9)NVNV?Ikm6&_VYiUG-VOj67DYVVy-RcpByiJ zN_laqS1?5MiBC(BsE1%N5|y|lx%FteVRuAbMeOMk>Yn2tn%#cIqzej^EY~}+>Tyyw zz4+$5w?C3z-iSDO_*&u_u){s0SLwlKCb;P2Q8gui$hZ<^9I1OW7iud%EW5RS&BL1V zS~>ir7OKjaVpDGcTr{9GzTKH;4qUE1n|qsGDLOH2zP)TaT$+=A*lO50LsPO_Z_+iC z^(z`Ee&_SZ;N8b4ldae7)AP^1H>?vQ)JeWNoY#seDapAf?{>Y1Y>cBp6I%3JTlq{$ z3(q_bb4xPiW0QYIo%!vjaps}7;v&au=478x}HcJtNC^p{1-8)Cwx652xK*Wn0R{ zuo!t+-q^HVMiUnX9>`!D32K<#S%Xz& zk*pL_{mxsiG__%knYP}GM>kHG-z?3JiLII6+`j$!;rFXQRoyu*@s1h%CKXpY|9&Xn zDs(OdOQm{zu9SA@ECPLfdgy}t9F5p7+xEd&lm!?DCa|W-fK({ zc&JIcSM!+)lS2e*)AzlbA2<2Zm?rIe9G;2aU3s$Tw<(B9DqihN77+GV{`AyIRO9c7 z(S`t*sO6<+8rYbZZ|TFwM?Ns;K8thRkGrV%u~zSZG`R8QI(EbG1W{o4Bv@)+RfZ$_)vCowhe?dsH3hW!oVa`5{k^*Xi-i;M0K z&*6b`*c}UsGgXao=6at~*GP9}jW&9RF9s;pwYvtvSQ1c;tAzam7 zJ{Nu1Wd#gu4PsR+cQ#jO23EDs`_c|Jv%G2e!Fs_wZ|J++#tX}6u~mUL2c#E*l!;Lz zUQdVSEYsT;vn(s|K^x*(zeWdZ@)Odc8ATO*L^8*<-W6t=qxFTjw<(;pKYpIDtXY3; znvgn~0hWMz)rPe8giysPeS$H8fpGfTxX3!gnO&T=!Ia%#(~G>j8s8|m36F%ww87h0 zBWAnCNRhL&xUTv$Z(DVCG5YY`fL|dEpBPkfAF|?-RJWooHTWXD9K!l5=kgf&JTvZ3 ze|Jn8xgX?m*6^ITH=8Ott$MbGw&Et*FcqZcfCgU!-=*)Hv^ZWjnd}@~QAjNYP22 z^sg{ix{jIY=3%W43^h?8*XZGZ8e8~j5G%KfCzBO7)(x7CbHOgRuyt?JaQ(l%?WXL_m-Sb zzZC-`=j^{>)m5FQB9+w6o&6xtrG51+gjk~n;W_ZDt4kiyF)(wcgutRhqMPPeuLbDw zb){K{cT%U3-kp1mM|p7Ly4t8RMgkglcPLRzZFBUwCDRKQM942qwu^bDl$bX|#)!`$ z%Bm)m(sm!_L`bnsRvY_2gBVurBZ``2-dUVnS52q0VB*!N)%Ceo)c58$mMDq|3FKBh z?`RvjJOW6vdt7KSI^w9;sjE#qbP)S*Rp8#?{1>G}t252@-6KA5kw`w;n8lEDLf2{U zXM?sUiMC{ZQGF2eAjGlmA@6tklPUWkf-@pyfzftM1T=aJ(m$}s<&=N~UHKmwN@h~f zR$z(nZPaBX+?`(wnlt$!@!L-_1yssJh8oBQb=E15E9b|~$zANzzz^End={$p!x znf9nZ;+Fxw!7tN1@D(3w#8iJBt@G*fnTqXEmOb*38k}$VZ~=3Y;gMlC+m7RJ=HNkD zLB>=bt{3g=Z+pC!8(ACxVnPm_$IttWe;zCu=s2SxE(93!Pk*-=mp)eYji2c>`e=h9 zYqABQu|ulQq}O+p{MPH!+kFJBcu`|Y6F?^oF}gdD%rzBN+n-IHlt_>NhSQb4KPmZU zCRm?xATuGIN%lKGjpjXQoS~WCi#i-lC)G5ij%q*X82YtPjcEF%G^Ox!yYYy>J4k_7 z0~E58RhVszyDh8mY%2l|UH9(}Jm3yb^p-f%qx3IPMt#5SR@M1Dsy~=PC13h4Lg#0Y z5dNTiI>QolO5%&dq4^A1Z*`g%q{f_p$5c)s(l!dkCANd>nfE zhc(_Ig{nBkUS(5?dx+H+vM%Iw1Rs#@?hnD&!MUw=#0R7OaxBR>|2r< z)&rA4MhPGDcfMIJ+1qxe$z}HaN#FR$Occnw_H5<=%=3y7_9{xIXYM^r0DyB zOJz2xed_E(!q>&j7p?b(x$g;Ja`n<5u(j5gq(B;a0l-Y(`i}-I_N)o#`axCy@A2OC zC6kikLg5ed8-9PJ#!n84X;T$H1dw}*1zsJg=A2XnvZDlC7??(@_0i(iKFcjQE4w8=OcX}CXi*ULy zIMdf+(PY3HHXvDOVD#XCeR}UUgMr{Zr;TPKq?8F)9G5KFgn4kI>y{lWZzdsu(x)qk3D!UOBC^lJs z?%HPf#LN2QN$=+tswF`y7Ma**kA7YnfDHd9SZAcabq`z^AALM(^@19!CX$8MTmdwA zEXPx=V1`Y`m%AHZ?M?KlTsxl$VhPkq{Ifk3>rcMTBPieAKK<4lSn(GPL27&%fGosT zyftxM*wgXj4NEQ_xQ4r(mAn8XzkfXEde9oztYzcZN#wUY@cjHxm1&)E<>=`1o0u5ZXQqv3 z`R4q=IWAH=U2wZE`=bJxr)X?o+zUeL>&+$&{EXBWxR!7a^{{F6S^ldHCDFeVm6*;$ zrO#lNb2)t|$cw@Am~7t`oc=TG$*%Pj%gNC>_bW1mWA~OmJ%yW_N?AM=^pcCA-rcN)j`zF{TQVi&az`PSm z*dPpy44@&I1o=kTQK56PI5!MD;Xnf*E+eh~$jpzJeR2FKmMVS9A!ND4(G7~m`O8u% zlBD&6M?no*1Q-FiW|E0D6mltoE_0*GU(163C-Mt!d{ey#?g~}ubs=O%>&Lnygapnb zuF~h=PgCf+;5AG7`=lvbMeBy)f;!^eMQs#6GG;b}4`{ixrM ztW6Z1Rfbi`ABWN_+Wi$}Poi6hPsh$9Qu-=nz!8}U9|&UmiG~`eqyPuVS8h~%0xx)A zDz(VsE5N)2b=-GBZMzT)zQ}+-xB~wKtXG~U{Uf`}`xZFYeE0uHMtq`;Gga>b4N2JX zajJE$!RV^x^d&omUBFbMPOmV8KVd5!P5UHh@GaIml{Jlv9|_ISP~XRyULbQ9IS+;F zs^@IH!6*KAs+Ed;>st8bJ|mg6!?6p_x5(OwAV?oGZ+|v(RCI%?CLZqFE-^5sbQoI# zNtjh#T3th-iRp!9Q}=QZQQNMiD{uw+d9cpES-0!V92A|QY8Ai|4@$5(A8N`e#3ze0 zfBeDmS~h#%Vh7a`)bHDTWIESFZEfh;Wg5 z`(@#OSDk^;>(T6~Ln0&;IYpT8G!%^VUYECm@U;I5b<~UqG*YlI#MM`oj;|G9pf}c~ zA%X}9YRSuMJJbpQn1MX;T%FH`cmgAty)5}_3boUQJ0{ZX_QE$feJB%|+ThyHD?scC zZXmTsL=a_4V1|a>^`tT3#X1kc4b6CBLo+lW%n;P#$(oN96|kEA)ST*hPPPWl63BD; z5&NVL^lvhN$>-o|yn-|Lk|-UGu#1>mwp_I12Gcph){gIMrc=wI6S7U!P&St~SA#?uVL@Ou6fdWP|-i7RU6{`5eWn^WansL)34zGC&Aa{s|{DrVtM?p{J3B zt@G6Qd@M_U>S={E;3ejZ`>j#3cj!(9hJ@sH)g#TLxMw+%f9+iWu5(^uLZR*H#_6XO z=zo&(3qFhN!gg8=zICa)XskSH+l0K_!+eu$=dq{^;z!v8(x7t08{bT)jLk`P*_AdS zd!yh0VPX0grP19Z54>Le0s~whvJ8>sztRo-u&$>90_(}6K3PKzXLBDxFugZ3nK?Ma~*ZP z!e)m^3B2XWfcbsfsdS+FN5r1T5^*51OGr~p-ccKhmAm>WbVyaW!g(D;()T`wCPx+@f62uDP?D=iq>Mvb^!mvj_ zJ`qM>ajJErJ#n8Hizj@E?zU5)kswD2#CIx=@(|t7uE!1WWJfte`6WF6H9a4Hw4UHWKF2mN3VIcsP@(C7s*zHLXC4eqLuHdPNCCx zGwz!zx&jZ-5oTnOGLe;c&lg_VfatG3u|RV+3DPh@tDKCfUR78){aHh@rkbXm_RP2< z&W8r1keYO>oQV}0+4-(L>4Yv?xnXd%vZKj z3?37Y(9plk!HdK)XEJWu@wGX#5wibD4=zLGQydnwL$j5dNK!D<(y|O%BsSSzh82x% zTO{F^C#MEII<&ihP#NYy?O?QNK@f2@;Ex-i1>*U_FqVJlKef>B0Tn!_x8ByMtcf#N zi0~G`b1v7ZKW3LLw+6M{aO}|N0$x#Ka3Bv$RbbV)FX5%PwISpRe9ocKd-=1ktMXG? zw?MoWHJ;7-|4SAN&`b1d&^Z}Pa;qEBio3{C&yq?Aa2WYl_qgHwui&AFIORSrnQ{Z5 z(uQdelN8M5&x55jujLKZECy5(QIHuw{Ww;kmd?<=fX|d^um^qD8Vui z<`$84S@A}G8I&&?oO0`PDe&O7_+a1arUP5ROkvgjZhO^DrzgRx~`*EBxg<9w4|Ewy3m8Q0Min^WU1Vj1R SaVb~~1N3!_wJWroBK{9vq9;87 literal 0 HcmV?d00001 diff --git a/project.4coder b/project.4coder index 07a58411..f2ae9f7c 100644 --- a/project.4coder +++ b/project.4coder @@ -68,7 +68,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: running target - .f3 = { .win = "raddbg_stable --ipc run", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f3 = { .win = "raddbg_stable --ipc run", .linux = "cd build && ./raddbg && cd ..", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f3 = { .win = "build torture", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f3 = { .win = "wsl ./build/raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f3 = { .win = "pushd build && raddbg --user:dev.raddbg_user && popd", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/base/base_arena.c b/src/base/base_arena.c index 881944b3..2e9b3e00 100644 --- a/src/base/base_arena.c +++ b/src/base/base_arena.c @@ -29,7 +29,7 @@ internal Arena * arena_alloc_(ArenaParams *params) { ProfBeginFunction(); - + U64 reserve_size = params->reserve_size; U64 commit_size = params->commit_size; @@ -69,13 +69,13 @@ arena_alloc_(ArenaParams *params) } // rjf: panic on arena creation failure -#if OS_FEATURE_GRAPHICAL if(Unlikely(base == 0)) { - wm_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); +#if defined(SHELL_H) + sh_message(1, s("Fatal Allocation Failure"), s("Unexpected memory allocation failure.")); +#endif abort_self(1); } -#endif // rjf: extract arena header & fill AsanUnpoisonMemoryRegion(base, ARENA_HEADER_SIZE); @@ -143,7 +143,7 @@ internal void arena_release(Arena *arena) { ProfBeginFunction(); - + #if PROFILE_TELEMETRY { Arena *base_arena = arena; @@ -176,7 +176,7 @@ arena_release(Arena *arena) AsanUnpoisonMemoryRegion(n, n->cmt); release_memory(n, n->res); } - + ProfEnd(); } @@ -295,13 +295,13 @@ arena_push(Arena *arena, U64 size, U64 align, B32 zero) #endif // rjf: panic on failure -#if OS_FEATURE_GRAPHICAL if(Unlikely(result == 0)) { - wm_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); +#if defined(SHELL_H) + sh_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); +#endif abort_self(1); } -#endif return result; } diff --git a/src/linux/shell/generated/linux_shell.meta.c b/src/linux/shell/generated/linux_shell.meta.c new file mode 100644 index 00000000..0378fad9 --- /dev/null +++ b/src/linux/shell/generated/linux_shell.meta.c @@ -0,0 +1,5 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + diff --git a/src/linux/shell/generated/linux_shell.meta.h b/src/linux/shell/generated/linux_shell.meta.h new file mode 100644 index 00000000..38dfff03 --- /dev/null +++ b/src/linux/shell/generated/linux_shell.meta.h @@ -0,0 +1,129 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +#ifndef LINUX_SHELL_META_H +#define LINUX_SHELL_META_H + +C_LINKAGE_BEGIN +read_only global U8 lnx_sh_logo_file_bytes__data[] = +{ +0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x08,0x06,0x00,0x00,0x00,0x5c,0x72,0xa8,0x66,0x00,0x00,0x00,0x04,0x67,0x41,0x4d,0x41,0x00,0x00,0xb1,0x8f,0x0b,0xfc,0x61,0x05,0x00,0x00,0x00,0x01,0x73,0x52,0x47,0x42,0x01,0xd9,0xc9,0x2c,0x7f,0x00,0x00, +0x00,0x20,0x63,0x48,0x52,0x4d,0x00,0x00,0x7a,0x26,0x00,0x00,0x80,0x84,0x00,0x00,0xfa,0x00,0x00,0x00,0x80,0xe8,0x00,0x00,0x75,0x30,0x00,0x00,0xea,0x60,0x00,0x00,0x3a,0x98,0x00,0x00,0x17,0x70,0x9c,0xba,0x51,0x3c,0x00,0x00,0x00,0x06,0x62,0x4b,0x47,0x44,0x00,0xff,0x00,0xff,0x00,0xff,0xa0,0xbd,0xa7,0x93,0x00,0x00,0x00,0x09, +0x70,0x48,0x59,0x73,0x00,0x00,0x0b,0x13,0x00,0x00,0x0b,0x13,0x01,0x00,0x9a,0x9c,0x18,0x00,0x00,0x1a,0xec,0x49,0x44,0x41,0x54,0x78,0xda,0xed,0x9d,0x79,0x98,0x14,0xd5,0xb9,0xc6,0x7f,0xdd,0x3d,0xdd,0x3d,0x03,0x33,0x30,0x33,0x0c,0xab,0xa0,0x8e,0x48,0xa3,0xa0,0x2c,0x83,0x02,0xea,0xd5,0x28,0xa5,0xa2,0x46,0xdc,0x35,0xee,0x1a, +0xe5,0x46,0x13,0x13,0xd1,0x18,0xcd,0xd5,0xa8,0x31,0x06,0x90,0x5c,0x35,0x41,0x8d,0x7a,0xd5,0xa8,0xb8,0x80,0x5b,0xcc,0xe3,0x86,0xa2,0xa6,0x5d,0x40,0x0c,0x11,0x45,0x01,0x09,0x5a,0xa0,0xb8,0x81,0xc0,0x00,0x33,0xc0,0x30,0xd0,0xb3,0x74,0xdf,0x3f,0xba,0xd4,0x51,0x01,0xa7,0xe9,0xee,0x5a,0xba,0xde,0xdf,0xf3,0xcc,0x33,0x2c,0xd3, +0x53,0xa7,0xbe,0xf3,0xbd,0x6f,0x7d,0xe7,0x54,0xd5,0x39,0x20,0x84,0x10,0x42,0x08,0x21,0x84,0x10,0x42,0x08,0x21,0x84,0x10,0x42,0x08,0x21,0x84,0x10,0x42,0x08,0x21,0x84,0x10,0x42,0x08,0xef,0x12,0x50,0x08,0x0a,0x9b,0x78,0x8c,0x32,0xa0,0x02,0x28,0x6f,0xf3,0xbd,0xed,0x9f,0x01,0xea,0x81,0x3a,0xeb,0x7b,0xdb,0x3f,0xd7,0x19,0x26, +0x1b,0x15,0x45,0x19,0x80,0x70,0x9f,0xb0,0xbb,0x02,0xc3,0x80,0xa1,0xc0,0x6e,0x5b,0x11,0x76,0x05,0xd0,0x19,0x08,0x65,0x79,0xa8,0x56,0x60,0x7d,0x5b,0x53,0x68,0x63,0x14,0x1f,0x03,0xef,0x02,0xef,0x18,0x26,0xb5,0xea,0x15,0x19,0x80,0xc8,0x8f,0xd8,0xfb,0x00,0x35,0x96,0xd8,0x6b,0xac,0xaf,0x9d,0x5c,0xd6,0xcc,0xe5,0xc0,0xbc,0xb6, +0x5f,0x86,0xc9,0x17,0xea,0x3d,0x19,0x80,0x68,0xbf,0xd0,0x03,0xd6,0xd5,0xbc,0xa6,0xcd,0xd7,0x50,0xa0,0xab,0x47,0x4f,0xa9,0xd6,0xaa,0x10,0xda,0x9a,0xc2,0x47,0xea,0x69,0x19,0x80,0xf8,0xbe,0xf8,0x9f,0x02,0x0e,0xb6,0xca,0xf6,0x42,0x66,0x3d,0xf0,0x9a,0x61,0x72,0x9c,0x7a,0x5d,0x06,0x20,0xd2,0xe2,0xaf,0xb6,0xc6,0xd3,0x7e,0xa2, +0xda,0x30,0xf9,0x44,0xbd,0xef,0x2c,0x41,0x85,0xc0,0x15,0x8c,0xd2,0x39,0x0b,0x19,0x80,0x0c,0x40,0xe7,0x2c,0x64,0x00,0x3e,0xe4,0x10,0x9d,0xb3,0x90,0x01,0xf8,0x73,0xfc,0xbf,0x07,0xd0,0xd3,0x87,0xa7,0xde,0x2b,0x1e,0xa3,0xbf,0x32,0x40,0x06,0xa0,0xf2,0x5f,0xe7,0x2e,0x64,0x00,0x12,0x81,0xce,0x5d,0xd8,0x8d,0x6e,0x03,0x3a,0x5b, +0xfe,0x07,0x48,0x3f,0x2c,0xd3,0xc5,0xa7,0x21,0x58,0x03,0x74,0x33,0x4c,0x52,0xca,0x06,0x55,0x00,0x7e,0x64,0x90,0x8f,0xc5,0x0f,0x50,0x05,0xec,0xad,0x34,0x90,0x01,0xa8,0x04,0x56,0x0c,0x84,0x0c,0x40,0xc9,0xaf,0x18,0x08,0xcd,0x01,0xf8,0x63,0xfc,0x1f,0x02,0xd6,0x01,0x9d,0x7c,0x1e,0x8a,0xf5,0x40,0x17,0xc3,0xa4,0x55,0x59,0xa1, +0x0a,0xc0,0x4f,0x0c,0x93,0xf8,0x81,0xf4,0xcb,0x4f,0x35,0x0a,0x83,0x0c,0x40,0xa5,0xaf,0x62,0x21,0x64,0x00,0x4a,0x7a,0xc5,0x42,0x68,0x0e,0xa0,0xb0,0xc7,0xff,0x11,0xd2,0x4b,0x6b,0x75,0x50,0x34,0x00,0x68,0x04,0xca,0x0d,0x93,0x66,0x85,0x42,0x15,0x80,0x1f,0x18,0x21,0xf1,0x7f,0x8b,0x0e,0x56,0x4c,0x84,0x0c,0x40,0x25,0xaf,0x62, +0x22,0x64,0x00,0x4a,0x76,0xc5,0x44,0x68,0x0e,0xa0,0x00,0xc7,0xff,0x25,0xa4,0x97,0xd4,0x8e,0x28,0x1a,0xdf,0x22,0x01,0x54,0x18,0x26,0x9b,0x15,0x0a,0x55,0x00,0x85,0xcc,0x01,0x12,0xff,0x56,0x89,0x02,0xfb,0x2b,0x0c,0x32,0x00,0x95,0xba,0x8a,0x8d,0x90,0x01,0x28,0xc9,0x15,0x1b,0xa1,0x39,0x80,0xc2,0x1a,0xff,0x77,0x22,0xfd,0xfc, +0x7f,0x48,0xd1,0xd8,0x2a,0x2d,0x40,0xa5,0xf6,0x23,0x54,0x05,0x50,0xa8,0x1c,0x28,0xf1,0x6f,0x97,0x22,0x2b,0x46,0x42,0x15,0x40,0x41,0x5d,0xf9,0x8b,0x81,0x9f,0x01,0xbf,0x05,0x7a,0x29,0x22,0xdb,0x65,0x05,0x30,0x09,0xb8,0xc7,0x30,0xd9,0xa2,0x70,0xc8,0x00,0xbc,0x2c,0xfc,0x12,0xe0,0x42,0xe0,0x0a,0xa0,0x87,0x22,0x92,0x11,0x5f, +0x02,0x37,0x02,0xff,0xa7,0x5b,0x83,0x32,0x00,0xaf,0x09,0xbf,0x23,0xf0,0x0b,0xe0,0x32,0xa0,0xbb,0x22,0x92,0x15,0xab,0x80,0x9b,0x80,0x3b,0x0d,0x93,0x4d,0x0a,0x87,0x0c,0xc0,0xcd,0xc2,0x2f,0x05,0x7e,0x09,0xfc,0x1a,0xef,0xee,0xe8,0xeb,0x56,0x6a,0x81,0x3f,0x03,0x7f,0x35,0x4c,0x1a,0x14,0x0e,0x19,0x80,0x9b,0x84,0xdf,0x09,0xb8, +0x18,0xb8,0x04,0x7f,0x2f,0xf2,0x69,0x07,0x6b,0x81,0xc9,0xc0,0xad,0x86,0xc9,0x06,0x85,0xc3,0x47,0x06,0x10,0x8f,0x11,0x04,0xfe,0x00,0x2c,0x03,0x66,0x18,0x26,0x2b,0x1c,0x6e,0x4f,0x04,0xb8,0x1c,0xf8,0x0d,0x50,0xae,0x74,0xb2,0x95,0x3a,0xe0,0x66,0xe0,0x46,0xc3,0xa4,0xc9,0xe1,0x3c,0xe8,0x05,0x1c,0x01,0xec,0x0a,0x5c,0x67,0x98, +0x24,0x65,0x00,0xf9,0x09,0xf4,0x64,0x60,0x5c,0x9b,0x7f,0x7a,0x1f,0x98,0x01,0xbc,0x08,0xcc,0x32,0x4c,0x12,0x36,0xb6,0xa5,0x06,0x98,0x82,0x96,0xb5,0x76,0x9a,0x85,0xc0,0x39,0x86,0xc9,0xbb,0x36,0xf6,0x7d,0x94,0xf4,0xed,0xca,0xd1,0x96,0xf0,0xf7,0x6a,0xf3,0xdf,0x93,0x0d,0x93,0x4b,0x65,0x00,0xb9,0x0f,0xfa,0x38,0xab,0xf4,0xdb, +0x16,0x8d,0xc0,0x6b,0x5f,0x19,0x82,0x61,0x62,0xe6,0xa9,0x1d,0x61,0xe0,0x1a,0xe0,0x4a,0xd2,0xf7,0xad,0x85,0xf3,0xb4,0x00,0x37,0x00,0x7f,0xcc,0xd7,0xa2,0x22,0xf1,0x18,0xb1,0x36,0x82,0x3f,0x98,0xed,0xaf,0xe7,0x30,0xce,0x30,0xb9,0x55,0x06,0x90,0xbb,0xe0,0x1f,0x07,0x3c,0x49,0x66,0x0f,0x2e,0x2d,0xb3,0x2a,0x83,0x19,0xc0,0x2b, +0xb9,0x78,0xba,0x2c,0x1e,0x63,0xa8,0x75,0xd5,0x1f,0x24,0xcd,0xb9,0x92,0x05,0xc0,0xb9,0xb9,0xa8,0x06,0xe2,0x31,0xca,0x00,0xc3,0x12,0xfd,0x68,0xa0,0x3a,0x83,0x8f,0x27,0x81,0x13,0x0c,0x93,0xa7,0x65,0x00,0xd9,0x77,0xc4,0x70,0xeb,0xca,0x5e,0x92,0xc5,0xaf,0x69,0x06,0xde,0x6c,0x63,0x08,0xef,0x65,0xb2,0x1d,0x95,0x75,0xd5,0xbf, +0x1a,0xb8,0x4a,0x57,0x7d,0xd7,0xd3,0x0c,0x4c,0x04,0x26,0x64,0x52,0x0d,0x58,0xdb,0xb4,0x0d,0x6d,0x73,0x95,0xdf,0x0f,0x08,0x67,0xd1,0x8e,0x46,0xe0,0x60,0xc3,0x64,0xae,0x0c,0x60,0xc7,0xc5,0x5f,0x0d,0xcc,0x01,0xba,0xe5,0xf8,0x57,0xaf,0x02,0x5e,0xb2,0x0c,0xe1,0x25,0xc3,0xa4,0x76,0x3b,0x6d,0x18,0x62,0x5d,0xf5,0x07,0x4b,0x5b, +0x9e,0x62,0xbe,0x35,0x37,0x30,0x7f,0x3b,0x7d,0xdb,0x15,0x38,0xdc,0x12,0xfc,0x61,0xe4,0xfe,0x99,0x8d,0xd5,0xc0,0x48,0xc3,0x64,0x99,0x0c,0x20,0x73,0xf1,0x57,0x58,0x57,0xed,0x3d,0xf2,0x7c,0xa8,0x14,0x30,0xaf,0x4d,0x75,0xf0,0x2f,0xc3,0xa4,0xc5,0xba,0xea,0xff,0xce,0xba,0xea,0x87,0xa5,0x27,0xcf,0x56,0x03,0x13,0x80,0x89,0x86, +0x49,0x73,0x3c,0x46,0x91,0x75,0x65,0x3f,0xc2,0xba,0xd2,0xd7,0xd8,0xa0,0x81,0x0f,0x80,0xfd,0x0d,0x93,0x3a,0x19,0x40,0xfb,0xc5,0x1f,0xb5,0xae,0xd0,0x07,0x39,0x70,0xf8,0x0d,0x40,0xdc,0x1a,0xf3,0x0d,0x91,0x86,0x0a,0x82,0xf7,0x48,0xcf,0x09,0x19,0x38,0xb3,0x19,0xcb,0x4c,0xe0,0x30,0xa7,0x6f,0x57,0x7a,0xc2,0x00,0xac,0xb1,0xd8, +0x54,0xe0,0x34,0xe5,0xad,0x28,0x20,0xa6,0x01,0x67,0xba,0x6d,0x2b,0x74,0x37,0xbe,0x0e,0x3c,0x5e,0xe2,0x17,0x05,0xc8,0xe9,0xc0,0x1f,0x55,0x01,0x6c,0xff,0xea,0x3f,0x16,0xb8,0x47,0xb9,0x22,0x0a,0x98,0xb1,0x86,0xc9,0xbd,0x32,0x80,0xef,0x8b,0xff,0x50,0xe0,0x05,0x74,0x9b,0x4d,0x14,0x36,0x2d,0xc0,0x91,0x86,0xc9,0x3f,0x65,0x00, +0xdf,0x88,0xbf,0x02,0x58,0x04,0xf4,0x54,0x7e,0x08,0x1f,0xf0,0x25,0x30,0xd0,0x0d,0x77,0x06,0xdc,0x32,0x07,0x70,0xab,0xc4,0x2f,0x7c,0x44,0x4f,0xe0,0x16,0x55,0x00,0xe9,0xab,0xff,0x18,0xe0,0x19,0xe5,0x84,0xf0,0x21,0x63,0x0c,0x93,0xe7,0x7c,0x6b,0x00,0x2a,0xfd,0x85,0x86,0x02,0xce,0x0e,0x05,0x9c,0x1e,0x02,0xdc,0x22,0xf1,0x0b, +0x9f,0x0f,0x05,0x26,0xfb,0xb2,0x02,0x50,0xe9,0x2f,0x84,0xf3,0x43,0x81,0x80,0x43,0xe2,0x57,0xe9,0x2f,0xc4,0x37,0xac,0xb0,0x86,0x02,0xf5,0x7e,0x19,0x02,0xa8,0xf4,0x17,0xe2,0x1b,0x7a,0xe1,0xd0,0x5d,0x01,0xdb,0x2b,0x00,0x95,0xfe,0x42,0xb8,0x67,0x28,0x10,0xb0,0x59,0xfc,0x2a,0xfd,0x85,0x70,0xd1,0x50,0xc0,0xee,0x21,0x80,0x4a, +0x7f,0x21,0x5c,0x34,0x14,0xb0,0xad,0x02,0x88,0xc7,0x38,0x1a,0x78,0x56,0x7d,0x2c,0x84,0x7b,0x86,0x02,0x01,0x9b,0xc4,0x1f,0x05,0x96,0x00,0x7d,0xd4,0xb7,0x42,0xfc,0x20,0x9f,0x03,0xbb,0xdb,0xb1,0x80,0x88,0x5d,0x43,0x80,0xf3,0x25,0x7e,0x21,0xda,0x4d,0x1f,0x4b,0x33,0xde,0xaf,0x00,0xac,0xdd,0x73,0x96,0xca,0x00,0x84,0xc8,0x88, +0xcf,0x80,0x7e,0xf9,0xae,0x02,0xec,0xa8,0x00,0x7e,0x2a,0xf1,0x0b,0x91,0x31,0x3b,0x03,0xe7,0x7a,0xba,0x02,0xb0,0x56,0xd6,0x5d,0x02,0xec,0xa2,0xfe,0x14,0x22,0x63,0x3e,0xb5,0xaa,0x80,0xe6,0x7c,0x1d,0x20,0xdf,0x15,0xc0,0x39,0x12,0xbf,0x10,0x3b,0xcc,0x2e,0xc0,0xd9,0x9e,0xac,0x00,0xac,0x35,0xd8,0x4d,0x32,0xdb,0x52,0x49,0x08, +0xf1,0x6d,0x96,0x01,0x31,0xc3,0xa4,0xc5,0x6b,0x15,0xc0,0xd9,0x12,0xbf,0x10,0x59,0x53,0x0d,0x9c,0xe5,0xa9,0x0a,0xc0,0xba,0xfa,0x7f,0x08,0xec,0xa6,0xfe,0x13,0x22,0x6b,0x3e,0x02,0xfa,0x1b,0x26,0xad,0x5e,0xa9,0x00,0xce,0x90,0xf8,0x85,0xc8,0x19,0x7d,0x2d,0x4d,0xb9,0xbf,0x02,0x88,0xc7,0x08,0x91,0xde,0x0f,0x6d,0x77,0xf5,0x9b, +0x10,0x39,0x63,0x09,0xb0,0x67,0xae,0xab,0x80,0x7c,0xac,0xc1,0x7f,0xba,0xc4,0xff,0x03,0x65,0x57,0x38,0x42,0x97,0x51,0x63,0xe8,0x75,0xf2,0x58,0x2a,0xf6,0xfd,0x11,0x00,0xa9,0x54,0x0a,0x48,0x41,0x32,0x99,0xfe,0xb3,0xf5,0x95,0x4a,0x25,0xbf,0xfe,0x33,0xa9,0x14,0xa9,0x64,0x32,0xfd,0x73,0xa9,0x54,0xfa,0xe7,0xda,0xfe,0x3c,0xd6, +0xff,0x6f,0xe3,0xf3,0x5f,0xfd,0x3c,0xa4,0xbe,0xf9,0xcc,0xb7,0x8e,0xf7,0x9d,0x63,0x6f,0xe5,0x58,0xa4,0x52,0xa4,0xd8,0xda,0x67,0xdb,0xd7,0xd6,0xb6,0x9f,0x5f,0xf9,0xf4,0xc3,0xac,0x79,0x45,0x6f,0x86,0xb7,0x93,0x7e,0xa4,0x77,0xcc,0x7a,0xd8,0xb5,0x15,0x40,0x3c,0x46,0x10,0x58,0x0c,0xc4,0xd4,0x5f,0xed,0xa3,0xc3,0x2e,0xfd,0xe8, +0x79,0xca,0x58,0x7a,0x1e,0x7f,0x2e,0x91,0x2e,0xdd,0x7c,0x75,0xee,0xa9,0x96,0x66,0x16,0x5c,0x78,0x0c,0x6b,0x67,0xcd,0x50,0x22,0xb4,0x8f,0x0f,0x81,0x01,0x86,0x49,0xd2,0xad,0x06,0x70,0x2a,0xf0,0x88,0xfa,0x69,0x07,0xab,0x82,0x43,0x8e,0x66,0xe7,0xf3,0x7e,0x43,0xe7,0xa1,0xfb,0xf9,0xe6,0xbc,0x93,0x89,0x2d,0xcc,0x1f,0x7b,0x24, +0x75,0x6f,0xbd,0xa6,0x24,0x68,0x1f,0xa7,0x19,0x26,0x8f,0xe6,0x2c,0xef,0x72,0xdc,0xb8,0xf3,0xd5,0x3f,0x3b,0x28,0x84,0xe6,0x26,0x6a,0x5f,0xfa,0x07,0xf3,0xce,0x38,0x90,0x25,0x37,0xfc,0x9a,0x64,0x62,0x8b,0x3f,0x8c,0x2f,0x5a,0xcc,0xa0,0xbb,0x9f,0xa3,0xd3,0x90,0x91,0x4a,0x82,0xf6,0x71,0x9e,0x2b,0x87,0x00,0xf1,0x18,0xdd,0x48, +0xaf,0x68,0x12,0x52,0x1f,0xe5,0x60,0x68,0xb0,0xdb,0x1e,0x0c,0x98,0x34,0x85,0x4e,0x83,0x47,0xf8,0xe2,0x7c,0x5b,0x36,0xd4,0x33,0xef,0xec,0x43,0x68,0x58,0xfc,0x9e,0x3a,0x7f,0xfb,0xb4,0x02,0x3d,0x0d,0x93,0x5a,0xb7,0x55,0x00,0x27,0x4b,0xfc,0xb9,0xa3,0xf1,0xe3,0x0f,0x78,0xe7,0xb4,0xff,0xe2,0xa3,0x9b,0xaf,0x24,0xd9,0xdc,0x54, +0xf0,0xe7,0x5b,0xd4,0xa9,0x9c,0xa1,0xf7,0xbf,0x4c,0xc7,0xdd,0x07,0xa8,0xf3,0xb7,0x4f,0xc8,0xd2,0x9a,0xeb,0x86,0x00,0xa7,0xaa,0x6f,0x72,0x4b,0xaa,0xb5,0x85,0x4f,0xef,0x9e,0xc4,0xdc,0xe3,0x87,0xd1,0xb8,0xec,0xc3,0x82,0x3f,0xdf,0x70,0x45,0x15,0x43,0xa7,0xfc,0x93,0x92,0x9d,0xfb,0xaa,0xf3,0x6d,0xd2,0x5a,0x4e,0x86,0x00,0xf1, +0x18,0xbd,0x49,0xbf,0xbf,0x1c,0x50,0xdf,0xe4,0x87,0x68,0xf7,0x9d,0xa8,0x99,0x36,0x8b,0x92,0xde,0x85,0xff,0x74,0xf5,0x96,0x15,0x9f,0x32,0xef,0x8c,0x83,0xd8,0xb2,0xe2,0x33,0x75,0xfc,0x36,0xae,0x0d,0x40,0x1f,0xc3,0x64,0xb9,0x5b,0x0c,0xe0,0x32,0xe0,0x26,0xbf,0xf5,0x42,0x51,0x69,0x80,0x8a,0xbd,0x22,0x14,0x77,0x0d,0x11,0xad, +0x08,0x12,0xa9,0x08,0x12,0xad,0x0c,0x12,0x0c,0x07,0x48,0xac,0x4b,0xd2,0xb4,0x2e,0x49,0xa2,0xae,0x95,0xc4,0xda,0x24,0xf5,0x8b,0x9b,0xd9,0x52,0x9b,0xdd,0x33,0x1c,0x25,0xbd,0xab,0xa9,0x99,0x36,0x8b,0x68,0xf7,0x9d,0x0a,0x7f,0x08,0xf4,0xe9,0x12,0xe6,0x9d,0x7e,0x10,0x4d,0x6b,0x56,0x4a,0xee,0x5b,0xe7,0x32,0xc3,0xe4,0xcf,0x6e, +0x31,0x80,0xb9,0xc0,0x3e,0x7e,0x88,0x7a,0x71,0x55,0x88,0xaa,0xe1,0x11,0xba,0x0e,0x8f,0x52,0x3e,0x20,0x42,0x20,0x83,0x59,0x8f,0x86,0x65,0x2d,0xd4,0xbe,0x95,0xa0,0x76,0x6e,0x82,0x86,0x4f,0x5a,0xd2,0x3e,0x9e,0x21,0x1d,0xaa,0xfb,0x53,0x33,0x75,0xa6,0x2f,0x9e,0x19,0xd8,0xb4,0x64,0x11,0xf3,0xce,0x3a,0x98,0xe6,0xba,0x35,0x92, +0xfb,0xf7,0x99,0x6b,0x98,0x0c,0x77,0xdc,0x00,0xe2,0x31,0xfa,0x92,0x5e,0xf2,0xab,0xa0,0xe9,0xd0,0x2b,0x44,0xdf,0x33,0x4b,0xe9,0x3a,0x22,0x9a,0x93,0xdf,0xb7,0xf1,0xa3,0x16,0x96,0x3e,0xd4,0x40,0xdd,0xc2,0xcc,0x27,0xf8,0x4a,0xfb,0x0f,0xa2,0xe6,0xe1,0xd7,0x28,0xea,0x54,0x51,0xf0,0x59,0xbe,0x71,0xd1,0x3c,0xde,0x3d,0x67,0x14, +0x2d,0x1b,0xd7,0x4b,0xf2,0xdf,0xa7,0xaf,0x61,0xf2,0x71,0x36,0xbf,0x20,0x17,0x93,0x80,0x05,0x3d,0xf9,0x17,0xa9,0x08,0xd2,0xff,0x82,0x32,0x46,0xdc,0xd2,0x25,0x67,0xe2,0x07,0x28,0xeb,0x5b,0xc4,0xd0,0xeb,0xca,0x19,0x72,0x4d,0x39,0xa5,0xbb,0x66,0xf6,0x44,0x76,0xc3,0x87,0x0b,0x78,0xef,0xfc,0x23,0x7c,0xf1,0xac,0x40,0xd9,0xc0, +0x1a,0x06,0xdf,0xf3,0x3c,0xa1,0x0e,0xa5,0x92,0x7b,0x1e,0xb4,0x97,0xf5,0x6d,0xbb,0x73,0xba,0x70,0x07,0x50,0x90,0xf5,0x68,0xf7,0xff,0x2a,0x66,0xe8,0x75,0xe5,0x74,0xee,0x1f,0x26,0x90,0xa7,0xe9,0xcd,0x92,0x1e,0x21,0x76,0x3a,0xbc,0x84,0x60,0x24,0x40,0xfd,0xfb,0x4d,0xed,0x1e,0x16,0x24,0x56,0x2d,0x87,0x40,0x80,0x8a,0x91,0xa3, +0x0a,0x7f,0xd8,0xd5,0xb3,0x0f,0x9d,0x87,0x8c,0x64,0xf5,0x8c,0x27,0x48,0xb5,0xb4,0x48,0xf6,0xdf,0xd0,0xed,0xc1,0xb5,0xdc,0xe9,0xd8,0x10,0x20,0x1e,0x63,0x20,0xf0,0x7e,0xa1,0x45,0x35,0x10,0x80,0xea,0xd3,0x3a,0xb2,0xeb,0x89,0x1d,0x6d,0x3d,0xee,0xda,0x77,0x9a,0x58,0xf4,0x97,0xf5,0xb4,0x6c,0x6e,0x9f,0x0b,0x04,0x23,0x51,0x86, +0x3f,0xb7,0x90,0x0e,0xbb,0xf4,0xf3,0x45,0xb6,0xaf,0x7d,0xfd,0x79,0x16,0x5e,0x74,0xbc,0x2f,0x9e,0x8b,0xc8,0x80,0x81,0x86,0xc9,0x7f,0x9c,0x1a,0x02,0x14,0x5c,0xf9,0x1f,0x2a,0x0e,0xb0,0xd7,0x15,0x9d,0x6d,0x17,0x3f,0x40,0x97,0x61,0x11,0x86,0x4d,0xaa,0xa0,0xa4,0x7b,0xfb,0x0a,0xb3,0x64,0x53,0x02,0xf3,0xfa,0x5f,0xfa,0x26,0xd3, +0xbb,0xfc,0xe8,0x28,0x06,0xdc,0x3c,0x95,0x40,0xa8,0x48,0xb2,0xcf,0x91,0x06,0xb3,0x35,0x80,0x9f,0x14,0xd4,0x95,0x3f,0x04,0x83,0xae,0xec,0x4c,0xd7,0xe1,0x51,0xc7,0xda,0xd0,0xb1,0x77,0x11,0x35,0xe3,0x2b,0x88,0x56,0xb6,0xaf,0x6b,0xd6,0xbd,0xf1,0x12,0xab,0x67,0x3c,0xe1,0x9f,0x9a,0x77,0xf4,0x49,0xec,0x79,0xc3,0x7d,0x04,0x82, +0x41,0x49,0x3f,0x07,0x1a,0xdc,0xe1,0x28,0xc6,0x63,0xd4,0x90,0x7e,0x47,0xb9,0x60,0x88,0x9d,0x5f,0x46,0xc5,0x5e,0x11,0xc7,0xdb,0x11,0xad,0x0c,0xb2,0xf7,0x6f,0x3b,0x13,0x8c,0xb4,0x6f,0x84,0xb6,0x64,0xe2,0xa5,0xb4,0x36,0x36,0xf8,0x26,0xe3,0x7b,0x1c,0x7b,0x16,0xb1,0x6b,0x6f,0x27,0x6f,0x13,0x33,0x1e,0x4b,0x5b,0x4b,0x8b,0x3b, +0x56,0xf1,0xee,0xe8,0x07,0xcf,0xe9,0x42,0x23,0x70,0x08,0xd0,0xbb,0x10,0xa2,0xb8,0xd3,0x11,0x25,0x54,0x9f,0xd2,0xd1,0x35,0xed,0x89,0x56,0x86,0x28,0xe9,0x1e,0xa2,0xf6,0xdf,0x89,0x1f,0xfc,0xd9,0xd6,0x4d,0x1b,0x69,0x5e,0xbb,0x9a,0x50,0x59,0x27,0x9a,0x56,0xaf,0xa0,0x79,0xed,0x2a,0x9a,0xeb,0xd6,0xd0,0xb2,0xa1,0x8e,0x96,0x86, +0x0d,0x24,0x37,0x6f,0x22,0x99,0xd8,0x42,0xaa,0xa5,0x99,0x54,0x32,0x49,0x20,0x18,0xf0,0xfc,0x15,0xb4,0xd3,0xde,0xfb,0x10,0xea,0x58,0xc6,0xba,0xd9,0x2f,0xfb,0xdd,0x00,0xe6,0x00,0xb7,0x3d,0xb8,0x96,0xc4,0x8e,0x7c,0x38,0xdb,0x49,0xc0,0x4e,0xc0,0x0b,0xc0,0xfe,0x9e,0x4e,0xa6,0x58,0x98,0x61,0x13,0x2a,0x08,0xb8,0x50,0x13,0x4b, +0xee,0x6b,0xe0,0xf3,0xe9,0x8d,0xb9,0x1f,0xee,0x04,0x83,0x04,0x8a,0xc2,0x04,0xc2,0x11,0x82,0xe1,0xc8,0xd7,0xdf,0x83,0xe1,0x08,0x81,0xc8,0xb7,0xff,0x6d,0x5b,0x3f,0x13,0x28,0x0a,0x7f,0xf3,0x77,0xeb,0x7b,0xb8,0xbc,0x0b,0x3b,0x9d,0x71,0x11,0x81,0x90,0x3d,0xef,0x85,0x2d,0xb8,0x70,0x0c,0x6b,0x5e,0x7d,0xce,0xaf,0xe2,0x9f,0x0d, +0x1c,0x69,0x98,0x6c,0xdc,0xd1,0x5f,0x90,0xd5,0x6c,0x8a,0x61,0xb2,0x21,0x1e,0x63,0x34,0x30,0x1d,0x38,0xc8,0x9b,0x03,0x7f,0xe8,0x77,0x6e,0xa9,0x2b,0xc5,0x0f,0x50,0x7d,0x6a,0x47,0x56,0xce,0xdc,0x42,0xf3,0xc6,0x64,0x4e,0x7f,0x6f,0x2a,0x99,0x24,0xd5,0x94,0x80,0xa6,0x44,0xce,0x16,0x99,0x8b,0x76,0xeb,0xc5,0xe0,0xbb,0xa7,0xdb, +0x26,0xfe,0x2f,0x1e,0xba,0x8d,0x35,0xaf,0x4d,0xf7,0xab,0xf8,0x5f,0x07,0x8e,0x36,0x4c,0xb2,0x1a,0xfb,0x65,0x9d,0xf6,0x56,0x03,0x8e,0x04,0x5e,0xf1,0x62,0x14,0xbb,0x0e,0x8f,0xd2,0xb9,0x7f,0xd8,0xb5,0xed,0x2b,0xea,0x10,0x60,0x97,0x13,0x3b,0xb8,0x3e,0x8e,0x1d,0xfb,0xed,0xc5,0xb0,0xc7,0xe7,0x50,0xba,0xe7,0x10,0xdb,0xc4,0x6f, +0x4e,0x18,0x67,0xad,0x85,0xe8,0x3b,0xe2,0xc0,0x51,0xd9,0x8a,0x3f,0x27,0x06,0x60,0x99,0x40,0x23,0x70,0x34,0xf0,0x92,0xa7,0x2e,0xfe,0x21,0xe8,0x7b,0xa6,0xfb,0x9f,0x30,0xeb,0x7d,0x64,0x09,0xc5,0xdd,0xdc,0xbb,0xd4,0x42,0xc5,0x7e,0x06,0xc3,0x1e,0x9d,0x4d,0x71,0xcf,0x3e,0x12,0x7f,0xfe,0x79,0x11,0x18,0x63,0x69,0x0e,0x57,0x18, +0x80,0x65,0x02,0x9b,0x81,0x63,0x80,0xe7,0xbd,0x12,0xc9,0xee,0x07,0x16,0xd3,0xa1,0x97,0xfb,0xd7,0x30,0x09,0x16,0x05,0xd8,0xf5,0x04,0x77,0x56,0x01,0x3d,0x8e,0x3b,0x9b,0x21,0xf7,0xce,0xa0,0xa8,0xb4,0x93,0xc4,0x9f,0x7f,0xa6,0x03,0xc7,0x5a,0x5a,0xc3,0x55,0x06,0x60,0x99,0x40,0x02,0x38,0x1e,0xf0,0xc4,0x5a,0xcf,0xdd,0xf6,0x8b, +0x7a,0xa6,0xe7,0xbb,0x8e,0x8c,0x66,0xf4,0xe6,0x61,0xfe,0xcb,0xa7,0x00,0xd5,0x17,0x5d,0xcb,0x80,0x3f,0x3d,0x60,0xdb,0x83,0x39,0x3e,0x17,0xff,0xd3,0xc0,0x09,0x96,0xc6,0x70,0xa5,0x01,0x58,0x26,0xd0,0x04,0x9c,0x04,0x3c,0xe9,0xe6,0x68,0x86,0x8a,0x03,0x54,0x0e,0x8e,0x78,0xa6,0xf7,0xc3,0x65,0x41,0xd7,0xcc,0x55,0x04,0x8a,0xc2, +0xec,0x39,0xe1,0x5e,0xaa,0x2f,0xfe,0x83,0x6d,0xc7,0xf4,0xb9,0xf8,0x9f,0x04,0x4e,0xb6,0xb4,0x85,0xab,0x0d,0xc0,0x32,0x81,0x66,0xd2,0x8f,0x28,0x3e,0xee,0xd6,0x88,0x56,0x0e,0x89,0x10,0x0c,0x7b,0xeb,0x41,0x12,0x27,0x9f,0x50,0xfc,0x8a,0xa2,0xd2,0x4e,0x0c,0xbe,0x7b,0x3a,0x3d,0x4f,0xfc,0xa9,0xc4,0x6f,0x0f,0x8f,0x01,0xa7,0x5a, +0x9a,0xc2,0x13,0x06,0x60,0x99,0x40,0x0b,0xe9,0x5d,0x82,0xa6,0xba,0x31,0xaa,0x55,0xfb,0x46,0x3d,0x97,0x09,0x55,0x0e,0x1b,0x40,0xb4,0x47,0x6f,0x6a,0xa6,0xcd,0xa2,0xf2,0x80,0xc3,0x24,0x7e,0x7b,0x78,0x18,0x38,0x23,0x5f,0x5b,0x83,0xe7,0xd5,0x00,0x2c,0x13,0x68,0x25,0xbd,0x4d,0xf8,0x14,0xb7,0x45,0xb6,0xb4,0x8f,0xf7,0x5e,0x28, +0x29,0xe9,0x1e,0x6a,0xf7,0xe3,0xc1,0x39,0x8f,0x57,0xff,0x41,0xec,0xf3,0xf8,0x1c,0x4a,0xfb,0x0f,0x92,0xf8,0xed,0xe1,0x7e,0xe0,0x9c,0x7c,0xec,0x08,0x6c,0x9b,0x01,0x58,0x26,0x90,0x24,0xbd,0x99,0xc1,0x3d,0x6e,0x8a,0x6e,0x7b,0x5f,0xb6,0x71,0x1b,0xd1,0x0a,0xfb,0xdb,0x5d,0x79,0xc0,0x61,0x0c,0x7b,0xe4,0x0d,0x5b,0xd7,0x22,0xf4, +0xb9,0xf8,0xef,0x01,0xce,0xcf,0xe5,0x16,0x60,0x8e,0x19,0x80,0x65,0x02,0x29,0xe0,0x02,0xe0,0x0e,0x37,0x44,0x37,0x10,0x82,0x70,0xb9,0x47,0x0d,0xc0,0x66,0xe3,0xea,0x79,0xe2,0x4f,0xd3,0x2b,0xf2,0x74,0x2c,0x93,0xf8,0xed,0xe1,0x0e,0xe0,0x02,0x4b,0x33,0x79,0xc7,0xb6,0x3a,0xd8,0x3a,0xa1,0x8b,0xe2,0x31,0x9a,0x81,0x71,0x4e,0x46, +0x38,0xd2,0x39,0xe8,0xd9,0x17,0xc9,0x22,0x76,0x55,0x00,0x81,0x00,0xd5,0xbf,0xba,0x8e,0xea,0x8b,0xae,0xb5,0xf5,0xfc,0x7c,0x2e,0xfe,0xc9,0x86,0xc9,0xa5,0x76,0x1e,0xd0,0xf6,0xcb,0xa0,0x61,0x72,0x09,0x0e,0x2f,0x21,0x1e,0x2e,0xf3,0xee,0x9b,0x70,0xe1,0x4e,0xf9,0x6f,0x7b,0x30,0x1c,0x61,0xc0,0xa4,0x29,0x12,0xbf,0xbd,0xdc,0x68, +0xb7,0xf8,0x1d,0x31,0x00,0xcb,0x04,0x2e,0x07,0x6e,0x70,0x2a,0xd2,0xb9,0x7e,0xb1,0xc6,0xd6,0xb6,0x6f,0xc8,0x6f,0xdb,0x8b,0xca,0x3a,0x33,0xf8,0x6f,0x2f,0xd0,0xe3,0xb8,0xb3,0x25,0x7e,0xfb,0x98,0x68,0x98,0x5c,0xe1,0xc4,0x81,0x1d,0xbb,0x14,0x1a,0x26,0x57,0x01,0xd7,0x3b,0x71,0xec,0xa6,0xf5,0x49,0xf0,0x68,0x9e,0x35,0xd5,0xe5, +0xcf,0x00,0x8a,0x7b,0xf6,0x61,0xd8,0x23,0x6f,0xd8,0xbe,0xd0,0xa8,0xcf,0xc5,0xff,0x07,0xc3,0xe4,0x77,0x4e,0x1d,0xdc,0xd1,0x5a,0xd8,0x30,0xf9,0x3d,0x70,0x8d,0xdd,0xc7,0x4d,0xb5,0x5a,0x26,0xe0,0x41,0x12,0x79,0x32,0x80,0xd2,0x3d,0x87,0x30,0xec,0xf1,0x39,0x74,0xec,0xb7,0x97,0xc4,0x6f,0x1f,0x57,0x1b,0x26,0xd7,0x39,0xd9,0x00, +0xc7,0x07,0xc3,0x86,0xc9,0x78,0xe0,0x16,0xdb,0x85,0xb4,0xce,0x9b,0x06,0x90,0x8f,0x0a,0xa0,0xcb,0x81,0x47,0x30,0x6c,0xda,0x2c,0xa2,0xdd,0x7a,0x49,0xfc,0xf6,0x31,0xd9,0x30,0x99,0xe0,0x74,0x23,0xdc,0x32,0x1b,0xf6,0xb9,0xdd,0x07,0xdc,0xf4,0x85,0xf7,0xd6,0x97,0xdf,0xb2,0xba,0x95,0xd6,0x44,0x6e,0xc5,0xd2,0xeb,0xe4,0xb1,0x0c, +0xba,0xeb,0x39,0xdb,0x37,0xde,0xf0,0xb9,0xf8,0x1d,0xc9,0x79,0x37,0x1b,0x40,0xb9,0xdd,0x07,0x5c,0x33,0x37,0xe1,0xb9,0x8c,0x59,0x33,0x37,0x87,0xef,0x82,0x04,0x02,0xec,0x76,0xc9,0x78,0xf6,0x18,0x7f,0x8f,0x6d,0x2b,0xf8,0x48,0xfc,0xce,0xe6,0xfc,0xd6,0x70,0xcb,0xf3,0xb0,0xb6,0x6f,0x72,0xb7,0xf6,0xdd,0x26,0x92,0x2d,0x29,0x82, +0x45,0xde,0x79,0x20,0xa0,0xf6,0xad,0xdc,0x98,0x56,0x30,0x1c,0x61,0x8f,0x89,0xf7,0xd2,0xe3,0x98,0x33,0x6d,0x3f,0x07,0x89,0xdf,0xb9,0x9c,0x77,0x73,0x05,0x60,0x7b,0x30,0x5a,0x37,0xa7,0xa8,0x5b,0xd0,0xec,0x99,0x6c,0x69,0x69,0x48,0x51,0xbf,0x38,0xfb,0x0a,0xa0,0xa8,0x53,0x39,0x83,0xef,0x9d,0x21,0xf1,0xcb,0x00,0xfc,0x6d,0x00, +0x00,0xab,0xff,0xe5,0x9d,0xcd,0x35,0x6b,0xdf,0x4a,0x90,0xca,0xf2,0xb5,0x90,0xe2,0x5e,0xbb,0x30,0xec,0x91,0xd9,0x54,0x8c,0x38,0x44,0xe2,0x97,0x01,0xf8,0x7b,0x0e,0x00,0x60,0xd5,0xcc,0x04,0x9b,0x57,0xb6,0xba,0x3e,0x53,0x52,0xad,0xf0,0xc9,0xdf,0x37,0x65,0xf5,0x3b,0xca,0x06,0xd6,0xb0,0xcf,0x13,0x73,0xe8,0xb8,0xfb,0x00,0x89, +0x5f,0x73,0x00,0xaa,0x00,0x00,0x92,0x2d,0x29,0x3e,0x9a,0xea,0xfe,0x1d,0x75,0x96,0xcf,0xd8,0xcc,0xe6,0x55,0x3b,0x6e,0x54,0x5d,0x7e,0x74,0x14,0x35,0x53,0x67,0x12,0xa9,0xea,0x21,0xf1,0xab,0x02,0x90,0x01,0x7c,0x7b,0x18,0x90,0x60,0xc3,0x52,0xf7,0xce,0x05,0xb4,0x6e,0x4e,0xb1,0x2c,0x8b,0xab,0x7f,0xaf,0x9f,0xfc,0x8c,0x41,0x77, +0x3e,0x43,0xa8,0xa4,0xa3,0xc4,0x2f,0x03,0xd0,0x10,0xe0,0xfb,0xf5,0x35,0x2c,0x9d,0xd2,0xe0,0xda,0x1c,0x5d,0xf6,0xc4,0xa6,0x1d,0x7a,0xfe,0x3f,0x10,0x0c,0xd2,0xf7,0xd7,0x13,0xd9,0xe3,0xfa,0xbb,0x6c,0xbf,0xcd,0x27,0xf1,0xcb,0x00,0xda,0x4d,0x3c,0x46,0x09,0xe0,0xe8,0x5a,0x57,0xf5,0x8b,0x9b,0x59,0x3a,0xc5,0x7d,0x43,0x81,0xd5, +0xff,0x4a,0xf0,0xf9,0x33,0x99,0x2f,0xff,0x1e,0x8c,0x44,0x19,0x70,0xe3,0xc3,0xec,0x72,0xc1,0x95,0x8e,0xb4,0x5b,0xe2,0x6f,0x17,0xd1,0x78,0x8c,0x62,0x55,0x00,0x2e,0x71,0xc2,0xcf,0xa7,0x37,0xf2,0xe5,0x2b,0xee,0xb9,0x2b,0xb0,0x71,0x59,0x0b,0x8b,0x6f,0xdb,0x90,0xb1,0x86,0xc2,0x9d,0x2b,0x19,0x72,0xdf,0x4b,0x74,0x3f,0xfa,0x34, +0x89,0x5f,0x55,0x80,0x0c,0x20,0x93,0xa1,0xc0,0x87,0x77,0x6f,0xa4,0x7e,0xb1,0xf3,0xf3,0x01,0x4d,0xf5,0x49,0x16,0x4e,0x5a,0x9f,0xf1,0x63,0xbf,0x25,0xbd,0xab,0x19,0xf6,0xe8,0x6c,0xca,0xf7,0x3d,0x48,0xe2,0x97,0x01,0x78,0x6a,0x0e,0xc0,0x15,0x24,0x9b,0x53,0x2c,0x98,0x58,0xcf,0xda,0x79,0x4d,0x8e,0xb5,0xa1,0xf1,0xcb,0x56,0xe6, +0x5d,0x5b,0xc7,0x96,0x35,0x99,0xcd,0xfa,0x97,0xed,0xb5,0x0f,0xc3,0x1e,0x9f,0x43,0x87,0xdd,0xf6,0x90,0xf8,0x85,0xa7,0x0c,0xa0,0xce,0x4d,0x01,0x69,0x69,0x4c,0xb1,0xe0,0x86,0x7a,0x3e,0x7b,0xa6,0xd1,0xf6,0x63,0xaf,0x9b,0xdf,0xc4,0xdb,0xff,0xb3,0x8e,0xc6,0xe5,0x99,0x89,0xbf,0x6a,0xd4,0x18,0x6a,0x1e,0x7e,0x9d,0x48,0x97,0x6e, +0x12,0xbf,0xb7,0xa8,0x93,0x01,0xb8,0xcc,0x00,0x00,0x52,0x49,0x58,0xfa,0x40,0x03,0x8b,0xff,0xba,0x81,0x64,0xb3,0x3d,0x49,0xfd,0xf9,0xf4,0x46,0xe6,0x4f,0xa8,0xa7,0xa5,0x21,0xb3,0xe3,0xed,0x74,0xfa,0x2f,0xd8,0xfb,0xf6,0xa7,0x08,0x95,0x38,0xb3,0x77,0xe0,0xca,0xa7,0x1e,0x94,0xf8,0x3d,0x9c,0xfb,0xae,0x78,0x13,0x26,0x1e,0x63, +0x0b,0x0e,0xdf,0x09,0xd8,0x16,0xc5,0x55,0x21,0xaa,0x4f,0xed,0x48,0xcf,0x83,0x8b,0xf3,0x12,0xad,0xfa,0x45,0xcd,0x2c,0x7d,0xa8,0x81,0x0d,0x4b,0x32,0x9b,0x7b,0x08,0x04,0x83,0xf4,0xbd,0x6c,0x12,0x3b,0x8f,0xbd,0xdc,0xd9,0xf9,0x8a,0x35,0x2b,0x99,0x77,0xd6,0x21,0x34,0x7e,0xfc,0x81,0xe4,0x9c,0x19,0x09,0xc3,0x74,0xfe,0x2e,0x80, +0x5b,0x0c,0x60,0x25,0xd0,0xdd,0xcd,0xbd,0x55,0xba,0x4b,0x11,0x7d,0xcf,0x2c,0xa5,0x4b,0x4d,0x6e,0xf6,0x13,0xdc,0xf4,0x59,0x0b,0x4b,0x1f,0x6a,0x60,0xed,0xbb,0x4d,0x19,0x2f,0x4f,0x16,0x8c,0x16,0x33,0xe0,0x4f,0x0f,0xd0,0xed,0xc8,0x53,0x5c,0x11,0x1b,0x99,0xc0,0x8e,0x15,0x4f,0x86,0x49,0x4f,0x19,0x40,0xda,0x00,0x16,0x03,0x7b, +0x78,0xa1,0xd7,0x3a,0xf4,0x0a,0x51,0xb5,0x6f,0x94,0xae,0xc3,0xa3,0xe9,0xcd,0x3a,0x33,0x88,0x60,0xe3,0xf2,0x56,0x6a,0xdf,0x4a,0x50,0xfb,0x56,0x82,0x8d,0x4b,0x9a,0x77,0xa8,0x6a,0x2e,0x2a,0xeb,0xcc,0xa0,0x3b,0x9f,0x71,0x6c,0xa6,0x5f,0x26,0x90,0x33,0x16,0x1b,0x26,0x03,0x64,0x00,0x69,0x03,0x78,0x13,0xd8,0xcf,0x6b,0x3d,0x18, +0x29,0x0f,0x52,0x39,0x28,0x42,0xb4,0x2a,0x48,0xb4,0x32,0x44,0xb4,0x22,0x48,0xa4,0x22,0x48,0x30,0x1c,0x20,0xb1,0xae,0x95,0xa6,0xba,0x24,0x89,0x75,0x49,0x12,0x75,0x49,0xea,0x17,0x35,0xd1,0xb8,0x22,0xbb,0x17,0x8f,0xc2,0x15,0x55,0x0c,0xb9,0xf7,0x45,0xca,0x06,0xd6,0xb8,0x32,0x1e,0x32,0x81,0x8c,0x78,0xd3,0x30,0x39,0xc0,0xe9, +0x46,0xb8,0x65,0x41,0x90,0x7a,0x2f,0xf6,0x60,0x53,0x7d,0x92,0x95,0x33,0xed,0x79,0x78,0x28,0xda,0xad,0x17,0x43,0xee,0x7f,0xd9,0x91,0xb7,0xf9,0xda,0x6d,0x88,0x55,0x3d,0xa8,0x79,0xe8,0x55,0x99,0x80,0x87,0x72,0xde,0x2d,0xcf,0x01,0xd4,0x29,0x1f,0xb6,0x4d,0x49,0xef,0x6a,0x6a,0xa6,0xcd,0x72,0xb5,0xf8,0xbf,0x6b,0x02,0x4e,0x3d, +0x8f,0xe0,0x21,0xea,0x64,0x00,0x32,0x80,0x1f,0x18,0xa0,0x05,0xe8,0x7e,0xd4,0x4f,0xd8,0xe7,0xc9,0xb9,0x94,0xf4,0xd9,0xcd,0x3b,0x43,0x23,0x99,0x80,0x0c,0x40,0x06,0x90,0x1d,0xe1,0xca,0xae,0xec,0x75,0xcb,0xe3,0x0c,0xfc,0xcb,0xa3,0x84,0xcb,0xbb,0x78,0xae,0xfd,0x32,0x01,0x19,0x40,0xc1,0xcf,0x01,0xe4,0x8b,0xae,0xa3,0x4f,0x64, +0xe4,0xf4,0x45,0x74,0x1b,0x7d,0x92,0xa7,0xcf,0x43,0x26,0xa0,0x39,0x80,0xf6,0xd2,0xc7,0xf7,0xd5,0x7e,0x30,0x48,0xd5,0xa8,0x63,0xa8,0x99,0x36,0x8b,0xbd,0x6f,0xfd,0x3b,0xe1,0xca,0xae,0x05,0x71,0x5e,0x32,0x01,0x77,0xe7,0xbc,0xe3,0xb7,0x01,0xe3,0x31,0xae,0xc1,0xa1,0x3d,0x02,0x5d,0xe1,0xc0,0xd1,0x62,0x7a,0x1c,0x77,0x36,0x3b, +0x9f,0x77,0x19,0x1d,0x76,0x8d,0x15,0xec,0x79,0xea,0x16,0xe1,0x56,0xb9,0xc6,0xda,0x19,0xcb,0x9f,0x06,0x10,0x8f,0x71,0x3d,0x0e,0xec,0x0d,0xe8,0xf8,0xf8,0xbe,0xa2,0x8a,0x8a,0x11,0x87,0x50,0xb9,0xff,0xa1,0x54,0x1d,0x7a,0x9c,0x63,0x2f,0xf1,0xc8,0x04,0x5c,0xc1,0xf5,0xd6,0x1e,0x99,0xfe,0x32,0x80,0x78,0x8c,0x89,0xc0,0x95,0x9e, +0xee,0xba,0x40,0xa0,0xdd,0x2f,0xc1,0x04,0x42,0x21,0xca,0x06,0xd4,0x50,0xb1,0xff,0xa1,0x94,0xed,0x39,0x24,0xfd,0xd9,0xaf,0x7e,0xc7,0xf7,0x3a,0x25,0xb0,0xf5,0x63,0x6d,0xe5,0xdf,0x02,0xa1,0x10,0x81,0x70,0x84,0x60,0x51,0x38,0xfd,0x3d,0x1c,0xb1,0xbe,0x7f,0xe7,0xef,0xdf,0xf9,0x7f,0x27,0x96,0x09,0x93,0x09,0x6c,0x93,0x89,0x4e, +0xed,0x10,0x1c,0x70,0x48,0xfc,0x37,0x01,0x97,0xa9,0xdf,0x9d,0x9d,0x73,0x08,0xb4,0x31,0x85,0x8a,0x91,0xa3,0x18,0xf8,0xe7,0x69,0x04,0x42,0x45,0x32,0x01,0x67,0xb8,0xc9,0x30,0xb9,0xbc,0xe0,0x0d,0x20,0x1e,0xe3,0x16,0xe0,0x62,0xf5,0xb7,0xfb,0xa8,0x32,0x8e,0x65,0xef,0xdb,0xfe,0x2e,0x13,0x70,0x8e,0x5b,0x0c,0x93,0x4b,0x0a,0xd2, +0x00,0xe2,0x31,0x02,0xc0,0xed,0xc0,0xcf,0xd5,0xcf,0x32,0x01,0x99,0xc0,0x36,0xb9,0x03,0xf8,0xa5,0x61,0x92,0x2a,0x18,0x03,0x88,0xc7,0x08,0x02,0x77,0x01,0x63,0xd5,0xbf,0x32,0x01,0x99,0xc0,0x0f,0x72,0x0f,0x70,0x81,0x1d,0x26,0x10,0xb0,0x49,0xfc,0xf7,0x01,0xe7,0xa8,0x5f,0x65,0x02,0x32,0x81,0x76,0x33,0x05,0x38,0xdf,0x30,0x49, +0x7a,0xd6,0x00,0xe2,0x31,0x42,0xc0,0x03,0xc0,0x19,0xea,0x4f,0x99,0x80,0x4c,0x20,0x63,0xa6,0x02,0xe7,0x18,0x26,0xad,0x9e,0x33,0x80,0x78,0x8c,0x22,0xeb,0x04,0x4e,0x51,0x3f,0xca,0x04,0x64,0x02,0x3b,0xcc,0x63,0xc0,0x99,0x86,0x49,0x8b,0x67,0x0c,0x20,0x1e,0x23,0x0c,0x3c,0x0a,0x9c,0xa0,0xfe,0x93,0x09,0xc8,0x04,0xb2,0xe6,0x49, +0xe0,0x34,0xc3,0xa4,0xd9,0xf5,0x06,0x10,0x8f,0x11,0x01,0x9e,0x00,0x8e,0x51,0xbf,0xc9,0x04,0x64,0x02,0x39,0xe3,0x69,0xe0,0x14,0xc3,0xa4,0xc9,0xb5,0x06,0x10,0x8f,0x11,0x05,0xfe,0x01,0x1c,0xa5,0xfe,0x92,0x09,0xc8,0x04,0x72,0xce,0x74,0xe0,0x44,0xc3,0x24,0xe1,0x3a,0x03,0xb0,0x36,0xf9,0x7c,0x0a,0x38,0x5c,0xfd,0x24,0x13,0x90, +0x09,0xe4,0x8d,0x97,0x80,0xe3,0x0c,0x93,0xcd,0xae,0x31,0x80,0x78,0x8c,0x0e,0xc0,0xb3,0xc0,0x28,0xf5,0x8f,0x4c,0x40,0x26,0x90,0x77,0x5e,0x01,0xc6,0x18,0x26,0x8d,0x8e,0x1b,0x40,0x3c,0x46,0xa9,0x55,0x9a,0x1c,0xa4,0x7e,0x91,0x09,0xc8,0x04,0x6c,0x63,0x26,0xf0,0x63,0xc3,0xa4,0xc1,0x31,0x03,0x88,0xc7,0xe8,0x04,0xbc,0x00,0xec, +0xaf,0xfe,0x90,0x09,0xc8,0x04,0x6c,0x67,0x36,0x70,0xa4,0x61,0xb2,0xd1,0x76,0x03,0x88,0xc7,0xe8,0x0c,0xbc,0x08,0x8c,0x50,0x3f,0xc8,0x04,0x64,0x02,0x8e,0xf1,0x6f,0x60,0xb4,0x61,0xb2,0x7e,0x47,0x3e,0x9c,0xcd,0x92,0x60,0x7d,0x25,0x7e,0xb1,0x26,0xfe,0x34,0x0b,0x7f,0x75,0x12,0xa9,0xd6,0x96,0xbc,0x1f,0x4b,0xcb,0x8b,0x6d,0x95, +0x11,0x96,0x16,0x1d,0x19,0x02,0x98,0x40,0x3f,0xf5,0x81,0x50,0x25,0xe0,0x18,0xa6,0x61,0xd2,0x7f,0x47,0x3f,0x9c,0xed,0xa2,0xa0,0x8f,0x29,0xfe,0x42,0x95,0x80,0xa3,0x64,0xa5,0xc1,0x6c,0x0d,0xe0,0x11,0xc5,0x5f,0xc8,0x04,0x1c,0xe5,0x11,0xc7,0x0c,0xc0,0x30,0xf9,0x0f,0xf0,0xbe,0x52,0x5f,0x38,0x65,0x02,0x3d,0x8f,0xf7,0xf5,0x5b, +0xe6,0x0b,0x0d,0x93,0xc5,0x4e,0x56,0x00,0x90,0x7e,0xe9,0x47,0x08,0x47,0x4c,0x20,0xd9,0xdc,0xe4,0xe7,0x50,0x67,0xad,0x3d,0x19,0x80,0xf0,0xb4,0x09,0xa4,0x64,0x00,0xce,0x1a,0x80,0x61,0xf2,0x11,0xf0,0xb6,0x52,0x5e,0x38,0x61,0x02,0x3e,0xae,0x00,0xe6,0x1a,0x26,0x1f,0xbb,0xa1,0x02,0x50,0x15,0x20,0x1c,0x33,0x81,0x54,0x73,0xb3, +0x5f,0x43,0x9b,0x13,0xcd,0xe5,0xca,0x00,0x1e,0x03,0x7b,0x56,0x31,0x15,0x32,0x01,0x55,0x00,0xa4,0xc8,0xd1,0x2d,0xf8,0x9c,0x18,0x80,0x61,0xf2,0x05,0xe9,0xe7,0x92,0x85,0xb0,0xd5,0x04,0x7c,0x3a,0x07,0xf0,0x86,0x61,0xb2,0xdc,0x4d,0x15,0x80,0x86,0x01,0xc2,0x11,0x13,0xf0,0x69,0x05,0x90,0x33,0xad,0xe5,0xd2,0x00,0x9e,0x80,0xfc, +0xad,0x5e,0x2a,0x64,0x02,0x5b,0xad,0x00,0x5a,0x7c,0x37,0x07,0xd0,0x6a,0x69,0xcd,0x5d,0x06,0x60,0x98,0xac,0x06,0x5e,0x55,0x8a,0x0b,0x3b,0x4d,0xc0,0x87,0x15,0xc0,0x2b,0x86,0x49,0xad,0x1b,0x2b,0x00,0x80,0x7b,0x95,0xde,0xc2,0x4e,0x13,0xf0,0xe1,0x1c,0xc0,0x7d,0xb9,0xfc,0x65,0xb9,0x36,0x80,0xc7,0x81,0x0f,0x95,0xde,0xc2,0x2e, +0x13,0x48,0x36,0xf9,0xca,0x00,0x3e,0xb4,0x34,0xe6,0x4e,0x03,0xb0,0xb6,0x31,0x9a,0xa0,0xd4,0x16,0x76,0x99,0x80,0xcf,0xe6,0x00,0xc6,0xe7,0x7a,0xab,0xb0,0x60,0x1e,0x1a,0x39,0x0d,0x58,0xa2,0xd4,0x16,0x76,0x98,0x80,0x8f,0x86,0x00,0x4b,0xc8,0xc3,0xdb,0xb7,0x39,0x37,0x00,0x6b,0x1f,0x33,0x55,0x01,0xc2,0x16,0x13,0xf0,0xd1,0x24, +0xe0,0xf8,0x7c,0xec,0x11,0x18,0xcc,0x53,0x63,0xa7,0x02,0x1f,0x29,0xad,0x45,0xbe,0x4d,0xc0,0x27,0x06,0xf0,0x91,0xa5,0x29,0x3c,0x61,0x00,0xd6,0x46,0x86,0x13,0x95,0xd2,0x22,0xdf,0x26,0xe0,0x93,0x21,0xc0,0x84,0x7c,0xed,0x10,0x1c,0xcc,0x63,0xa3,0x1f,0x04,0x96,0x29,0xa5,0x45,0x3e,0x4d,0xc0,0x07,0x2f,0x03,0x7d,0x0c,0x3c,0x94, +0xaf,0x5f,0x9e,0x37,0x03,0x50,0x15,0x20,0xec,0x30,0x01,0x1f,0x0c,0x01,0x26,0xe6,0x6b,0x6b,0xf0,0x7c,0x57,0x00,0x00,0x0f,0x00,0x9f,0x2a,0x9d,0x45,0xbe,0x4c,0xa0,0xc0,0x0d,0xe0,0x13,0xab,0x92,0xc6,0x93,0x06,0x60,0xed,0x67,0x7e,0x83,0x52,0x59,0xe4,0xcb,0x04,0x0a,0x7c,0x0e,0xe0,0x06,0x4b,0x43,0xde,0x34,0x00,0x8b,0xfb,0x81, +0xcf,0x95,0xca,0x22,0x1f,0x26,0x50,0xc0,0x0f,0x02,0x7d,0x66,0x69,0x07,0x4f,0x1b,0x80,0x61,0xd2,0x04,0x4c,0x52,0x1a,0x8b,0x7c,0x98,0x40,0x01,0x0f,0x01,0x26,0xe5,0xfb,0xea,0x6f,0x57,0x05,0x00,0xe9,0x97,0x84,0x54,0x05,0x88,0x9c,0x9b,0x40,0x81,0x56,0x00,0x9f,0x63,0xd3,0x8b,0x75,0xb6,0x18,0x80,0x61,0x92,0x00,0x7e,0xa1,0x14, +0x16,0xb9,0x34,0x81,0x54,0x6b,0x2b,0xa9,0xd6,0x82,0x5c,0x82,0xe2,0xe7,0x56,0xe5,0x5c,0x18,0x06,0x60,0x99,0xc0,0x73,0xe4,0xf1,0x7e,0xa6,0xf0,0x9f,0x09,0xb4,0x6e,0xde,0x54,0x88,0xa7,0xf7,0xa0,0x61,0x32,0xdd,0xae,0x83,0x05,0xec,0x3c,0xb3,0x78,0x8c,0x0a,0x60,0x11,0xd0,0x53,0x69,0x2c,0xb2,0xa5,0xf2,0x80,0xc3,0x58,0x37,0xfb, +0xe5,0x42,0x3a,0xa5,0x15,0xc0,0x40,0xc3,0xa4,0xbe,0x20,0x0d,0xc0,0x32,0x81,0xa3,0x81,0x67,0x95,0xbe,0x42,0x7c,0x8f,0x31,0x56,0xa5,0x6c,0x1b,0x41,0xbb,0xcf,0x50,0x43,0x01,0x21,0xb6,0x59,0xfa,0x3f,0x67,0xf7,0x41,0x83,0x0e,0x9d,0xec,0x38,0xe0,0x4b,0xf5,0xb9,0x10,0x5f,0x97,0xfe,0xe3,0x9c,0x38,0xb0,0x23,0x06,0x60,0x98,0xd4, +0x01,0x3f,0x53,0xbf,0x0b,0x01,0xc0,0x05,0x76,0x8e,0xfb,0xdd,0x50,0x01,0x68,0x28,0x20,0x84,0x83,0xa5,0xbf,0xe3,0x06,0xa0,0xa1,0x80,0x10,0xce,0x95,0xfe,0xae,0x30,0x00,0x6b,0x28,0x70,0x81,0xf2,0x40,0xa8,0xf4,0xf7,0x67,0x05,0x80,0x61,0xf2,0x2c,0xf0,0xb0,0x72,0x41,0xf8,0x8c,0x87,0x9c,0x2c,0xfd,0x5d,0x63,0x00,0x16,0x17,0x6b, +0x28,0x20,0x7c,0xc4,0x97,0x4e,0x97,0xfe,0xae,0x32,0x00,0x6b,0x28,0x70,0x36,0xe4,0x6f,0xe5,0x13,0x21,0x5c,0x42,0x0b,0x70,0xb6,0x95,0xf3,0x32,0x80,0x36,0x26,0xf0,0x4f,0xe0,0xe7,0xca,0x0f,0x51,0xe0,0x5c,0x68,0xe5,0x3a,0x32,0x80,0xef,0x9b,0xc0,0xdf,0xd0,0x3a,0x82,0xa2,0x70,0x99,0x60,0x98,0xee,0xda,0x3f,0x33,0xe8,0xc2,0x20, +0x5d,0x4d,0x7a,0x77,0x21,0x21,0x0a,0x89,0xa9,0x86,0xc9,0xd5,0x6e,0x6b,0x94,0xeb,0x0c,0xc0,0x30,0x49,0x01,0xe7,0x01,0x33,0x1d,0x6a,0xc2,0x7a,0xe0,0x1f,0xc0,0x7b,0xca,0xd9,0x82,0xe1,0x3d,0xab,0x4f,0xd7,0x3b,0x74,0xfc,0xd7,0xad,0x9c,0x46,0x06,0xd0,0x3e,0x13,0x48,0x00,0xc7,0x01,0x1f,0xd8,0x70,0xb8,0x14,0xf0,0x36,0xe9,0xed, +0xcc,0x0e,0x04,0xaa,0x0c,0x93,0x13,0x81,0xe1,0xc0,0x75,0x40,0xb3,0xf4,0xe3,0x59,0x9a,0x81,0xdf,0x03,0xfb,0x5a,0x7d,0x5a,0x65,0xf5,0xf1,0x04,0xab,0xcf,0x53,0x36,0xb4,0xe1,0x03,0xe0,0x78,0xbb,0x16,0xf8,0xc8,0x94,0x80,0x9b,0x7b,0x2f,0x1e,0xa3,0x1a,0x98,0x03,0x74,0xcb,0xf1,0xaf,0x5e,0x05,0xbc,0x68,0x7d,0xbd,0x64,0x98,0xac, +0xd9,0x4e,0x1b,0x86,0x00,0x53,0x80,0xc1,0xd2,0x93,0xe7,0xae,0xfa,0xe7,0x1a,0x26,0xf3,0xb7,0xd3,0xb7,0x5d,0x81,0xc3,0x80,0x23,0x80,0xc3,0x81,0xee,0x79,0xc8,0xb3,0x91,0x86,0xc9,0x27,0x6e,0x0d,0x52,0xc0,0xed,0xbd,0x18,0x8f,0x31,0x1c,0x78,0x15,0xe8,0x90,0xe5,0x95,0x60,0xb6,0x25,0xf8,0x19,0xc0,0x7c,0x6b,0xa8,0xd1,0xde,0x36, +0x84,0x81,0xdf,0x01,0x57,0x01,0x61,0x69,0xcb,0xf5,0x57,0xfd,0x09,0xa4,0x37,0xd4,0x68,0xce,0xa0,0x8f,0x03,0xc0,0x10,0x60,0xb4,0x65,0x08,0xfb,0x67,0xd9,0xd7,0x8d,0xc0,0xc1,0x86,0xc9,0x5c,0x37,0x07,0x2b,0xe0,0x85,0x1e,0x8d,0xc7,0x38,0xd6,0x1a,0xc3,0x65,0x32,0x64,0xf9,0xb8,0x8d,0xe0,0x5f,0x35,0x4c,0x36,0xe6,0xa0,0x1d,0x43, +0x48,0x6f,0x76,0x32,0x48,0x3a,0x73,0x25,0xf3,0xad,0xab,0xfe,0x7b,0x39,0xe8,0xeb,0x32,0x60,0x94,0x65,0x08,0xa3,0x81,0xdd,0x32,0xf8,0x78,0xd2,0x2a,0xfb,0x9f,0x71,0x7b,0xc0,0x02,0x5e,0xe9,0xd9,0x78,0x8c,0x71,0xc0,0xe4,0xed,0xfc,0xc8,0x26,0xe0,0x35,0x4b,0xf0,0x2f,0x1a,0x26,0x4b,0xf2,0xd4,0x8e,0x30,0xe9,0x3b,0x15,0x57,0x01, +0x45,0xd2,0x9c,0x2b,0xf8,0x6a,0x1b,0xba,0xf1,0xf9,0x5a,0x4a,0x3b,0x1e,0xa3,0x5f,0x9b,0xea,0xe0,0x60,0xa0,0xe3,0x76,0x7e,0xfc,0x62,0xc3,0xe4,0x36,0x2f,0x04,0x2e,0xe0,0xa5,0x5e,0x8e,0xc7,0x98,0xcc,0xb7,0x1f,0xa1,0x5c,0xf8,0x95,0xe0,0x81,0x59,0x76,0x4e,0xb4,0xc4,0x63,0xd4,0x58,0x73,0x03,0x7b,0x4b,0x7f,0x8e,0xb2,0xc0,0xba, +0xea,0xbf,0x6b,0x63,0xdf,0x47,0x48,0x4f,0x26,0x7e,0x65,0x08,0x6d,0x73,0x60,0xb2,0x61,0x72,0xa9,0x57,0x82,0xe7,0x35,0x03,0x08,0x92,0x9e,0x99,0x5f,0x66,0x5d,0xe5,0x57,0x38,0xdc,0x9e,0x08,0x70,0x39,0x70,0x19,0x50,0x21,0x2d,0xda,0x4a,0x1d,0x70,0x13,0x70,0xa3,0x1d,0x1b,0x68,0xfc,0x40,0x1e,0xf4,0xb2,0xcc,0xa0,0x1a,0xb8,0xce, +0x30,0x49,0xca,0x00,0x7c,0x44,0x3c,0x46,0x27,0xe0,0x57,0xc0,0xa5,0x40,0x17,0x45,0x24,0xaf,0xac,0x05,0xfe,0x02,0xdc,0x66,0x98,0x6c,0x50,0x38,0x64,0x00,0x6e,0x32,0x82,0x52,0xe0,0x22,0xab,0x22,0xe8,0xaa,0x88,0xe4,0x94,0x5a,0xe0,0x66,0xe0,0x76,0xc3,0xa4,0x41,0xe1,0x90,0x01,0xb8,0xd9,0x08,0x3a,0x92,0x7e,0xb1,0xe9,0x37,0xe4, +0xfe,0xde,0xb2,0xdf,0x58,0x05,0xdc,0x08,0xdc,0x69,0x98,0x34,0x2a,0x1c,0x32,0x00,0x2f,0x19,0x41,0x09,0xe9,0x15,0x8f,0xae,0x40,0x9b,0xa1,0x64,0xca,0x97,0xc0,0xff,0x02,0x77,0x19,0x26,0x9b,0x15,0x0e,0x19,0x80,0x97,0x8d,0xa0,0x18,0xf8,0x6f,0xe0,0xb7,0xc0,0x4e,0x8a,0xc8,0x76,0x59,0x4e,0x7a,0x37,0xe9,0xbf,0x19,0x26,0x5b,0x14, +0x0e,0x19,0x40,0x21,0x19,0xc1,0x8f,0xc1,0xf9,0x65,0xa0,0x5c,0xce,0x8f,0x0d,0x93,0xe7,0x15,0x06,0x7b,0x08,0x2a,0x04,0xb6,0x32,0x13,0xad,0x7a,0xb4,0x3d,0x5a,0x80,0x59,0x0a,0x83,0x0c,0xa0,0x20,0xb1,0x1e,0x47,0x7e,0x47,0x91,0xd8,0x26,0x6f,0xe7,0xe2,0x91,0x6d,0x21,0x03,0x70,0x33,0xaf,0x28,0x04,0x8a,0x8d,0x0c,0x40,0x49,0x2e, +0x14,0x1b,0x19,0x80,0x0f,0x99,0x0d,0xee,0x5c,0x1c,0xc2,0x61,0x12,0xc0,0x9b,0x0a,0x83,0x0c,0xa0,0xd0,0xe7,0x01,0x36,0x93,0x5e,0xe4,0x44,0x7c,0x9b,0x39,0xba,0xdf,0x2f,0x03,0x50,0xa9,0xab,0x98,0x08,0x19,0x80,0x92,0x5d,0x31,0x11,0x32,0x80,0xc2,0xe5,0xdf,0xa0,0xe7,0xda,0xdb,0xd0,0x68,0xc5,0x44,0xc8,0x00,0x7c,0x31,0x0f,0xd0, +0x44,0x7a,0x32,0x50,0xa4,0x79,0xc3,0xe9,0x77,0xfa,0x65,0x00,0x42,0x25,0xaf,0x62,0x21,0x03,0x10,0x4a,0x7a,0xc5,0x42,0x06,0x20,0xf2,0xcf,0x3b,0xa0,0x15,0x6d,0x48,0xef,0xd6,0x33,0x4f,0x61,0x90,0x01,0xf8,0x6d,0x1e,0xa0,0x15,0xe7,0xb6,0x3f,0x73,0x13,0x33,0xad,0x58,0x08,0x19,0x80,0x4a,0x5f,0xc5,0x40,0xc8,0x00,0x94,0xfc,0x8a, +0x81,0x90,0x01,0xf8,0x80,0x05,0xa4,0x57,0xb9,0xf5,0x2b,0x6b,0x48,0xef,0xed,0x20,0x64,0x00,0xbe,0x9c,0x07,0x48,0x91,0xde,0xcd,0xc8,0xaf,0xbc,0x96,0xc9,0x1e,0x8d,0x42,0x06,0xa0,0x12,0x58,0xe7,0x2e,0x64,0x00,0x12,0x81,0xce,0x5d,0xe4,0x02,0x2d,0x0a,0xea,0x02,0xe2,0x31,0x56,0xe0,0xbf,0x65,0xc3,0x57,0x18,0xa6,0x56,0x48,0x56, +0x05,0x20,0x00,0x5e,0xd5,0x39,0x0b,0x19,0x80,0x4a,0x61,0x9d,0xb3,0x90,0x01,0x48,0x0c,0xfe,0x18,0xf9,0xa8,0xdb,0x65,0x00,0x02,0x30,0x4c,0x96,0x01,0x4f,0x93,0x7e,0x2e,0xbe,0xd0,0xa9,0x07,0x9e,0x36,0x4c,0x3e,0x55,0xcf,0x3b,0x8f,0x26,0x01,0xdd,0x74,0x49,0x8c,0x11,0x20,0xbd,0xc7,0x7c,0xcd,0x77,0xbe,0xbc,0xba,0xd3,0xf0,0x6a, +0xe0,0x5d,0xd2,0x2f,0xfb,0xcc,0x03,0xe6,0x19,0x26,0x1f,0xab,0xa7,0x65,0x00,0x22,0x33,0x63,0xe8,0x6d,0x19,0xc1,0xd0,0x36,0xa6,0xd0,0xdb,0x65,0xcd,0xfc,0xa2,0xad,0xd0,0x2d,0xb1,0x2f,0x57,0xef,0xc9,0x00,0x44,0x7e,0x4c,0xa1,0x6b,0x1b,0x33,0xa8,0x06,0xca,0xad,0xaf,0x8a,0x36,0xdf,0x3b,0x03,0x45,0x59,0x1e,0xaa,0xc5,0x1a,0x9a, +0xd4,0x59,0xe5,0xfb,0x57,0xdf,0xeb,0x81,0x65,0x6d,0xc4,0x5e,0xab,0x5e,0x91,0x01,0x08,0xf7,0x19,0x45,0xe9,0x77,0x4c,0xe1,0xbb,0x46,0xc1,0x56,0x84,0xfd,0xb5,0xd8,0x0d,0x93,0x06,0x45,0x51,0x08,0x21,0x84,0x10,0x42,0x08,0x21,0x84,0x10,0x42,0x08,0x21,0x84,0x10,0x42,0x08,0x21,0x84,0x10,0x42,0x08,0x21,0x84,0x10,0xc2,0x8d,0xfc, +0x3f,0x5c,0x56,0xd9,0x0b,0xad,0xff,0xf9,0xa9,0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae,0x42,0x60,0x82, +}; + +read_only global String8 lnx_sh_logo_file_bytes = {lnx_sh_logo_file_bytes__data, sizeof(lnx_sh_logo_file_bytes__data)}; + +C_LINKAGE_END + +#endif // LINUX_SHELL_META_H diff --git a/src/linux/shell/linux_shell.c b/src/linux/shell/linux_shell.c new file mode 100644 index 00000000..7e7d7713 --- /dev/null +++ b/src/linux/shell/linux_shell.c @@ -0,0 +1,189 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/linux_shell.meta.c" + +//////////////////////////////// +//~ rjf: @per_os_impl Shell Operations + +internal void +sh_message(B32 error, String8 title, String8 message) +{ + B32 done = 0; + B32 text_is_long = (str8_find_needle(message, 0, s("\n"), 0) < message.size); + + //- rjf: try zenity + if(!done) + { + Temp scratch = scratch_begin(0, 0); + if(text_is_long) + { + String8 cmd = str8f(scratch.arena, "echo \"%S\" | zenity --text-info --no-markup --title=\"%S\"", escaped_from_raw_str8(scratch.arena, message), title); + FILE *f = popen((char *)cmd.str, "r"); + done = (f != 0); + pclose(f); + } + else + { + String8 cmd = str8f(scratch.arena, "zenity %s --no-markup --title=\"%S\" --text=\"%S\"", error ? "--error" : "--info", title, escaped_from_raw_str8(scratch.arena, message)); + FILE *f = popen((char *)cmd.str, "r"); + done = (f != 0); + pclose(f); + } + scratch_end(scratch); + } + + //- rjf: fall back to stderr + if(!done) + { + if(error) + { + fprintf(stderr, "[X] "); + } + fprintf(stderr, "%.*s\n", str8_varg(title)); + fprintf(stderr, "%.*s\n\n", str8_varg(message)); + } +} + +internal String8 +sh_pick_file(Arena *arena, String8 title, String8 initial_path) +{ + String8 result = {0}; + B32 done = 0; + + //- rjf: try zenity + if(!done) + { + Temp scratch = scratch_begin(&arena, 1); + String8 cmd = str8f(scratch.arena, "zenity --file-selection --filename=\"%S\" --title=\"%S\"", initial_path, title); + FILE *f = popen((char *)cmd.str, "r"); + done = (f != 0); + if(done) + { + U64 output_buffer_size = 4096; + char *output_buffer = push_array(scratch.arena, char, output_buffer_size); + char *path = fgets(output_buffer, output_buffer_size, f); + if(path != 0) + { + result = str8_copy(arena, str8_chop(str8_cstring_capped(path, output_buffer+output_buffer_size), 1)); + } + } + pclose(f); + scratch_end(scratch); + } + + return result; +} + +internal void +sh_show_in_file_browser(String8 path) +{ + B32 done = 0; + + //- rjf: try xdg-open + if(!done) + { + Temp scratch = scratch_begin(0, 0); + String8 cmd = str8f(scratch.arena, "xdg-open %S", str8_chop_last_slash(path)); + FILE *f = popen((char *)cmd.str, "r"); + pclose(f); + scratch_end(scratch); + } + + //- rjf: fallback -> tell user + if(!done) + { + Temp scratch = scratch_begin(0, 0); + sh_message(1, s("Error"), s("Could not find a way to pick a file using the locally installed system utilities.")); + scratch_end(scratch); + } +} + +internal void +sh_open_in_browser(String8 url) +{ + B32 done = 0; + + //- rjf: try xdg-open + if(!done) + { + Temp scratch = scratch_begin(0, 0); + String8 cmd = str8f(scratch.arena, "xdg-open %S", url); + FILE *f = popen((char *)cmd.str, "r"); + done = (f != 0); + pclose(f); + scratch_end(scratch); + } + + //- rjf: fallback -> tell user + if(!done) + { + Temp scratch = scratch_begin(0, 0); + sh_message(1, s("Error"), str8f(scratch.arena, "Could not find a way to open %S in a locally installed browser.", url)); + scratch_end(scratch); + } +} + +internal void +sh_install_or_uninstall_self(B32 install) +{ + Temp scratch = scratch_begin(0, 0); + + //- rjf: get xdg local application install info path + String8 application_name = str8_chop_last_dot(str8_skip_last_slash(get_process_info()->binary_file_path)); + char *xdg_data_home_cstring = getenv("XDG_DATA_HOME"); + String8 desktop_entries_folder_name = str8_cstring(xdg_data_home_cstring); + if(desktop_entries_folder_name.size == 0) + { + char *home = getenv("HOME"); + desktop_entries_folder_name = str8f(scratch.arena, "%s/.local/share", home); + } + String8 desktop_file_path = str8f(scratch.arena, "%S/applications/%S.desktop", desktop_entries_folder_name, application_name); + + //- rjf: get icon image path + B32 has_icon = 0; + String8 icon_path = {0}; +#if LNX_WM_ICON + { + has_icon = 1; + icon_path = str8f(scratch.arena, "%S/%S.png", get_process_info()->user_program_config_data_path, application_name); + } +#endif + + //- rjf: install -> write files + if(install) + { + //- rjf: write icon + write_data_to_file_path(icon_path, lnx_sh_logo_file_bytes); + + //- rjf: write .desktop file + String8 desktop_file_data = str8f(scratch.arena, + "[Desktop Entry]\n" + "Type=Application\n" + "Version=" BUILD_VERSION_STRING_LITERAL "\n" + "Name=" BUILD_TITLE "\n" + "Comment=" BUILD_TITLE_STRING_LITERAL "\n" + "Exec=%S\n" + "%s%S%s" // (optional icon) + "Terminal=%s\n" + "Categories=Utility;Development;", + get_process_info()->binary_file_path, + has_icon ? "Icon=" : "", + has_icon ? icon_path : s(""), + has_icon ? "\n" : "", + BUILD_CONSOLE_INTERFACE ? "true" : "false"); + write_data_to_file_path(desktop_file_path, desktop_file_data); + } + + //- rjf: uninstall -> delete files + else + { + delete_file_at_path(icon_path); + delete_file_at_path(desktop_file_path); + } + + scratch_end(scratch); +} diff --git a/src/linux/shell/linux_shell.h b/src/linux/shell/linux_shell.h new file mode 100644 index 00000000..69377066 --- /dev/null +++ b/src/linux/shell/linux_shell.h @@ -0,0 +1,12 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef LINUX_SHELL_H +#define LINUX_SHELL_H + +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/linux_shell.meta.h" + +#endif // LINUX_SHELL_H diff --git a/src/linux/shell/linux_shell.mdesk b/src/linux/shell/linux_shell.mdesk new file mode 100644 index 00000000..f30b4be9 --- /dev/null +++ b/src/linux/shell/linux_shell.mdesk @@ -0,0 +1,4 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +@embed_file lnx_sh_logo_file_bytes: "../data/logo.png" diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 52983f50..6f37d82d 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -381,6 +381,15 @@ wm_window_open(Rng2F32 rect, WM_WindowFlags flags, String8 title) XFlush(lnx_wm_state->display); } + //- rjf: set class + { + XClassHint *class_hints = XAllocClassHint(); + class_hints->res_name = "raddbg"; + class_hints->res_class = "RADDBG"; + XSetClassHint(lnx_wm_state->display, w->window, class_hints); + XFree(class_hints); + } + //- rjf: create xic w->xic = XCreateIC(lnx_wm_state->xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, @@ -1097,38 +1106,3 @@ wm_set_cursor(WM_Cursor cursor) { lnx_wm_state->last_set_cursor = cursor; } - -//////////////////////////////// -//~ rjf: @per_os_impl Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -wm_graphical_message(B32 error, String8 title, String8 message) -{ - if(error) - { - fprintf(stderr, "[X] "); - } - fprintf(stderr, "%.*s\n", str8_varg(title)); - fprintf(stderr, "%.*s\n\n", str8_varg(message)); -} - -internal String8 -wm_graphical_pick_file(Arena *arena, String8 title, String8 initial_path) -{ - return str8_zero(); -} - -//////////////////////////////// -//~ rjf: @per_os_impl Shell Operations - -internal void -wm_show_in_filesystem_ui(String8 path) -{ - // TODO(rjf) -} - -internal void -wm_open_in_browser(String8 url) -{ - // TODO(rjf) -} diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index ae94b5c5..fa039843 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -440,7 +440,7 @@ RD_VocabInfo rd_vocab_info_table[372] = RD_NameSchemaInfo rd_name_schema_info_table[39] = { -{str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n}\n")}, +{str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n\n //- rjf: system installation\n @display_name('Install To System') @description(\"Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.\") 'install_to_system': bool,\n}\n")}, {str8_lit_comp("project"), 0, str8_lit_comp("@expand_commands(edit_project_theme) x:\n{\n @display_name('Project Name') 'name': string,\n @default(2) @display_name('Project Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: visualizers\n @display_name('Display Pointer Addresses Before Contents') @description(\"When visualizing pointers, always shows the address first, before showing contents at the pointer's address.\")\n @default(0) display_pointer_addresses_before_contents: bool,\n @display_name('Use Default C++ STL Type Visualizers') @description(\"Enables the built-in type views for C++ STL types.\")\n @default(1) use_default_stl_type_views: bool,\n @display_name('Use Default Unreal Engine Type Visualizers') @description(\"Enables the built-in type views for Unreal Engine types.\")\n @default(1) use_default_ue_type_views: bool,\n\n //- rjf: theme\n @default(\"None\") @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme': string,\n @no_expand @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme_colors': set,\n\n //- rjf: exception settings\n @default(1) @display_name(\"Break On Win32 Control-C Exceptions\") @description(\"Code: 0x40010005\")\n win32_ctrl_c: bool;\n @default(1) @display_name(\"Break On Win32 Control-Break Exceptions\") @description(\"Code: 0x40010008\")\n win32_ctrl_break: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Originate Error Exceptions\") @description(\"Code: 0x40080201\")\n win32_win_rt_originate_error: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Transform Error Exceptions\") @description(\"Code: 0x40080202\")\n win32_win_rt_transform_error: bool;\n @default(0) @display_name(\"Break On Win32 RPC Call Cancelled Exceptions\") @description(\"Code: 0x0000071a\")\n win32_rpc_call_cancelled: bool;\n @default(0) @display_name(\"Break On Win32 Data Type Misalignment Exceptions\") @description(\"Code: 0x80000002\")\n win32_datatype_misalignment: bool;\n @default(1) @display_name(\"Break On Win32 Access Violation Exceptions\") @description(\"Code: 0xc0000005\")\n win32_access_violation: bool;\n @default(0) @display_name(\"Break On Win32 In Page Error Exceptions\") @description(\"Code: 0xc0000006\")\n win32_in_page_error: bool;\n @default(1) @display_name(\"Break On Win32 Invalid Handle Specified Exceptions\") @description(\"Code: 0xc0000008\")\n win32_invalid_handle: bool;\n @default(0) @display_name(\"Break On Win32 Not Enough Quota Exceptions\") @description(\"Code: 0xc0000017\")\n win32_not_enough_quota: bool;\n @default(0) @display_name(\"Break On Win32 Illegal Instruction Exceptions\") @description(\"Code: 0xc000001d\")\n win32_illegal_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Cannot Continue From Exception Exceptions\") @description(\"Code: 0xc0000025\")\n win32_cannot_continue_exception: bool;\n @default(0) @display_name(\"Break On Win32 Invalid Exception Disposition Returned By Handler Exceptions\") @description(\"Code: 0xc0000026\")\n win32_invalid_exception_disposition: bool;\n @default(0) @display_name(\"Break On Win32 Array Bounds Exceeded Exceptions\") @description(\"Code: 0xc000008c\")\n win32_array_bounds_exceeded: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Denormal Operand Exceptions\") @description(\"Code: 0xc000008d\")\n win32_floating_point_denormal_operand: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Division By Zero Exceptions\") @description(\"Code: 0xc000008e\")\n win32_floating_point_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Inexact Result Exceptions\") @description(\"Code: 0xc000008f\")\n win32_floating_point_inexact_result: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Invalid Operation Exceptions\") @description(\"Code: 0xc0000090\")\n win32_floating_point_invalid_operation: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Overflow Exceptions\") @description(\"Code: 0xc0000091\")\n win32_floating_point_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Stack Check Exceptions\") @description(\"Code: 0xc0000092\")\n win32_floating_point_stack_check: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Underflow Exceptions\") @description(\"Code: 0xc0000093\")\n win32_floating_point_underflow: bool;\n @default(0) @display_name(\"Break On Win32 Integer Division By Zero Exceptions\") @description(\"Code: 0xc0000094\")\n win32_integer_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Integer Overflow Exceptions\") @description(\"Code: 0xc0000095\")\n win32_integer_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Privileged Instruction Exceptions\") @description(\"Code: 0xc0000096\")\n win32_privileged_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Stack Overflow Exceptions\") @description(\"Code: 0xc00000fd\")\n win32_stack_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Unable To Locate DLL Exceptions\") @description(\"Code: 0xc0000135\")\n win32_unable_to_locate_dll: bool;\n @default(0) @display_name(\"Break On Win32 Ordinal Not Found Exceptions\") @description(\"Code: 0xc0000138\")\n win32_ordinal_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Entry Point Not Found Exceptions\") @description(\"Code: 0xc0000139\")\n win32_entry_point_not_found: bool;\n @default(0) @display_name(\"Break On Win32 DLL Initialization Failed Exceptions\") @description(\"Code: 0xc0000142\")\n win32_dll_initialization_failed: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Faults Exceptions\") @description(\"Code: 0xc00002b4\")\n win32_floating_point_sse_multiple_faults: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Traps Exceptions\") @description(\"Code: 0xc00002b5\")\n win32_floating_point_sse_multiple_traps: bool;\n @default(1) @display_name(\"Break On Win32 Assertion Failed Exceptions\") @description(\"Code: 0xc0000420\")\n win32_assertion_failed: bool;\n @default(0) @display_name(\"Break On Win32 Module Not Found Exceptions\") @description(\"Code: 0xc06d007e\")\n win32_module_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Procedure Not Found Exceptions\") @description(\"Code: 0xc06d007f\")\n win32_procedure_not_found: bool;\n @default(1) @display_name(\"Break On Win32 Sanitizer Error Detected Exceptions\") @description(\"Code: 0xe073616e\")\n win32_sanitizer_error_detected: bool;\n @default(0) @display_name(\"Break On Win32 Sanitizer Raw Access Violation Exceptions\") @description(\"Code: 0xe0736171\")\n win32_sanitizer_raw_access_violation: bool;\n @default(1) @display_name(\"Break On Win32 DirectX Debug Layer Exceptions\") @description(\"Code: 0x0000087a\")\n win32_directx_debug_layer: bool;\n}\n")}, {str8_lit_comp("theme_color"), 0, str8_lit_comp("@collection_commands(add_theme_color, fork_theme, save_theme, save_and_set_theme)\n@row_commands(duplicate_cfg, remove_cfg)\nx:\n{\n @display_name('Tags') tags: string,\n @display_name('Value') value: @color @hex u32,\n}\n")}, {str8_lit_comp("window"), 0, str8_lit_comp("x:\n{\n //- rjf: text rasterization settings\n @default(1) @display_name('Smooth UI Text') @description(\"Controls whether or not UI text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_ui_text': bool,\n @default(1) @display_name('Hint UI Text') @description(\"Controls whether or not UI text is hinted, for better text readability at small sizes.\")\n 'hint_ui_text': bool,\n @default(0) @display_name('Smooth Code Text') @description(\"Controls whether or not code text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_code_text': bool,\n @default(1) @display_name('Hint Code Text') @description(\"Controls whether or not code text is hinted, for better text readability at small sizes.\")\n 'hint_code_text': bool,\n @default(11) @display_name('Window Font Size') @description(\"Controls the window's default font size. Does not apply to tabs with their own font size set.\")\n 'font_size': @range[6, 72] u64,\n\n //- rjf: size settings\n @default(3.f) @display_name('Window Row Height') @description(\"Controls the window's default row height, in multiples of the font size. Does not apply to tabs with their own row height set.\")\n 'row_height': @range[1.75f, 5.f] f32,\n @default(3.f) @description(\"Controls the height of tabs, in multiples of the font size.\")\n 'tab_height': @range[1.75f, 5.f] f32,\n\n //- rjf: theme settings\n @default(1) @display_name('Use Project Theme') @description(\"Prefer using the project theme for this window, if any. If off, only the user's theme settings will be used.\")\n 'use_project_theme': bool,\n}\n")}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 2f38fd18..4c3b98e9 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -325,6 +325,9 @@ RD_VocabTable: //- rjf: update check @default(1) @display_name('Check For Updates') @description("Enables a small network request to check if there is a newer release version available.") 'check_for_updates': bool, + + //- rjf: system installation + @display_name('Install To System') @description("Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.") 'install_to_system': bool, } ``` } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index cbedf632..e7ae1924 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -7549,7 +7549,7 @@ rd_window_frame(void) UI_Signal sig = ui_button(str8_lit("Submit request, issue, or bug report")); if(ui_clicked(sig)) { - wm_open_in_browser(url); + sh_open_in_browser(url); } } ui_spacer(ui_em(0.5f, 1.f)); @@ -8018,7 +8018,7 @@ rd_window_frame(void) ui_box_equip_display_fstrs(label, &fstrs); if(ui_clicked(sig)) { - wm_open_in_browser(s("https://github.com/EpicGames/raddebugger/releases/latest")); + sh_open_in_browser(s("https://github.com/EpicGames/raddebugger/releases/latest")); } } @@ -13201,6 +13201,17 @@ rd_frame(void) rd_state->eval_viz_base_string_flags |= EV_StringFlag_DisplayAddressUnmappedStatus; } + //////////////////////////// + //- rjf: do installation/uninstallation of app + // + B32 installed = rd_setting_b32_from_name(s("install_to_system")); + B32 last_installed = rd_state->installed; + if(installed != last_installed) + { + sh_install_or_uninstall_self(installed); + rd_state->installed = installed; + } + //////////////////////////// //- rjf: send update check if needed // @@ -13503,7 +13514,7 @@ rd_frame(void) { cmd_title = info->string; } - String8 file_path = wm_graphical_pick_file(scratch.arena, cmd_title, current_path_string); + String8 file_path = sh_pick_file(scratch.arena, cmd_title, current_path_string); file_path = path_normalized_from_string(scratch.arena, file_path); if(file_path.size != 0) { @@ -15084,7 +15095,7 @@ rd_frame(void) if(rd_regs()->file_path.size != 0) { String8 full_path = rd_regs()->file_path; - wm_show_in_filesystem_ui(full_path); + sh_show_in_file_browser(full_path); }break; //- rjf: source <-> disasm diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index 5d314d7e..2294b6be 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -390,6 +390,9 @@ struct RD_State S32 frame_depth; U64 frame_eval_memread_endt_us; + // rjf: installation setting state + B32 installed; + // rjf: update checker B32 sent_update_check; B32 got_update_check; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index efb1e756..f7b47811 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -387,6 +387,7 @@ #include "minidump/minidump_parse.h" #include "mdesk/mdesk.h" #include "window_manager/window_manager_inc.h" +#include "shell/shell_inc.h" #include "http/http_inc.h" #include "symbol_server/symbol_server_inc.h" #include "config/config_inc.h" @@ -448,6 +449,7 @@ #include "minidump/minidump_parse.c" #include "mdesk/mdesk.c" #include "window_manager/window_manager_inc.c" +#include "shell/shell_inc.c" #include "http/http_inc.c" #include "symbol_server/symbol_server_inc.c" #include "config/config_inc.c" @@ -964,26 +966,26 @@ entry_point(CmdLine *cmd_line) //- rjf: help message box case ExecMode_Help: { - wm_graphical_message(0, - str8_lit("The RAD Debugger - Help"), - str8_lit("The following options may be used when starting the RAD Debugger from the command line:\n\n" - "--user:\n" - "Use to specify the location of a user file which should be used. User files are used to store settings for users, including window and panel setups, path mapping, and visual settings. If this file does not exist, it will be created as necessary. This file will be autosaved as user-related changes are made.\n\n" - "--project:\n" - "Use to specify the location of a project file which should be used. Project files are used to store settings for users and projects. If this file does not exist, it will be created as necessary. This file will be autosaved as project-related changes are made.\n\n" - "--auto_step\n" - "This will step into all active targets after the debugger initially starts.\n\n" - "--auto_run\n" - "This will run all active targets after the debugger initially starts.\n\n" - "--quit_after_success (or -q)\n" - "This will close the debugger automatically after all processes exit, if they all exited successfully (with code 0), and ran with no interruptions.\n\n" - "--ipc \n" - "This will launch the debugger in the non-graphical IPC mode, which is used to communicate with another running instance of the debugger. The debugger instance will launch, send the specified command, then immediately terminate. This may be used by editors or other programs to control the debugger.\n\n" - "--gen_crash_dump\n" - "Generate mini dump on crash.\n\n" - "--logs:\n" - "Overrides default path to the folder with logs.\n" - )); + sh_message(0, + str8_lit("The RAD Debugger - Help"), + str8_lit("The following options may be used when starting the RAD Debugger from the command line:\n\n" + "--user:\n" + "Use to specify the location of a user file which should be used. User files are used to store settings for users, including window and panel setups, path mapping, and visual settings. If this file does not exist, it will be created as necessary. This file will be autosaved as user-related changes are made.\n\n" + "--project:\n" + "Use to specify the location of a project file which should be used. Project files are used to store settings for users and projects. If this file does not exist, it will be created as necessary. This file will be autosaved as project-related changes are made.\n\n" + "--auto_step\n" + "This will step into all active targets after the debugger initially starts.\n\n" + "--auto_run\n" + "This will run all active targets after the debugger initially starts.\n\n" + "--quit_after_success (or -q)\n" + "This will close the debugger automatically after all processes exit, if they all exited successfully (with code 0), and ran with no interruptions.\n\n" + "--ipc \n" + "This will launch the debugger in the non-graphical IPC mode, which is used to communicate with another running instance of the debugger. The debugger instance will launch, send the specified command, then immediately terminate. This may be used by editors or other programs to control the debugger.\n\n" + "--gen_crash_dump\n" + "Generate mini dump on crash.\n\n" + "--logs:\n" + "Overrides default path to the folder with logs.\n" + )); }break; } diff --git a/src/render/d3d11/render_d3d11.c b/src/render/d3d11/render_d3d11.c index deb28141..121aebde 100644 --- a/src/render/d3d11/render_d3d11.c +++ b/src/render/d3d11/render_d3d11.c @@ -198,7 +198,7 @@ r_init(CmdLine *cmdln) { char buffer[256] = {0}; raddbg_snprintf(buffer, sizeof(buffer), "D3D11 device creation failure (%lx). The process is terminating.", error); - wm_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); + sh_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); abort_self(1); } ProfEnd(); @@ -370,7 +370,7 @@ r_init(CmdLine *cmdln) { errors = str8((U8 *)vshad_source_errors->lpVtbl->GetBufferPointer(vshad_source_errors), (U64)vshad_source_errors->lpVtbl->GetBufferSize(vshad_source_errors)); - wm_graphical_message(1, str8_lit("Vertex Shader Compilation Failure"), errors); + sh_message(1, str8_lit("Vertex Shader Compilation Failure"), errors); } else { @@ -425,7 +425,7 @@ r_init(CmdLine *cmdln) { errors = str8((U8 *)pshad_source_errors->lpVtbl->GetBufferPointer(pshad_source_errors), (U64)pshad_source_errors->lpVtbl->GetBufferSize(pshad_source_errors)); - wm_graphical_message(1, str8_lit("Pixel Shader Compilation Failure"), errors); + sh_message(1, str8_lit("Pixel Shader Compilation Failure"), errors); } else { @@ -534,7 +534,7 @@ r_window_equip(WM_Window handle) { char buffer[256] = {0}; raddbg_snprintf(buffer, sizeof(buffer), "DXGI swap chain creation failure (%lx). The process is terminating.", error); - wm_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); + sh_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); abort_self(1); } @@ -1052,7 +1052,7 @@ r_window_end_frame(WM_Window window, R_Handle window_equip) { char buffer[256] = {0}; raddbg_snprintf(buffer, sizeof(buffer), "D3D11 present failure (%lx). The process is terminating.", error); - wm_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); + sh_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); abort_self(1); } d_ctx->lpVtbl->ClearState(d_ctx); diff --git a/src/render/opengl/linux/egl/render_opengl_linux_egl.c b/src/render/opengl/linux/egl/render_opengl_linux_egl.c index 4b05a425..481cf082 100644 --- a/src/render/opengl/linux/egl/render_opengl_linux_egl.c +++ b/src/render/opengl/linux/egl/render_opengl_linux_egl.c @@ -23,7 +23,7 @@ r_ogl_os_init(CmdLine *cmdln) r_ogl_lnx_state->display = eglGetDisplay((EGLNativeDisplayType)lnx_wm_state->display); if(r_ogl_lnx_state->display == EGL_NO_DISPLAY) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Failed to get EGL display.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Failed to get EGL display.")); abort_self(1); } } @@ -33,14 +33,14 @@ r_ogl_os_init(CmdLine *cmdln) EGLint egl_version_minor = 0; if(!eglInitialize(r_ogl_lnx_state->display, &egl_version_major, &egl_version_minor)) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't initialize EGL display.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't initialize EGL display.")); abort_self(1); } if(egl_version_major < 1 || (egl_version_major == 1 && egl_version_minor < 5)) { Temp scratch = scratch_begin(0, 0); String8 message = push_str8f(scratch.arena, "Unsupported EGL version (%i.%i, need at least 1.5)", egl_version_major, egl_version_minor); - wm_graphical_message(1, str8_lit("Fatal Error"), message); + sh_message(1, str8_lit("Fatal Error"), message); abort_self(1); scratch_end(scratch); } @@ -48,7 +48,7 @@ r_ogl_os_init(CmdLine *cmdln) //- rjf: pick GL API if(!eglBindAPI(EGL_OPENGL_API)) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't initialize EGL API to OpenGL.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't initialize EGL API to OpenGL.")); abort_self(1); } @@ -71,7 +71,7 @@ r_ogl_os_init(CmdLine *cmdln) EGLint configs_count = 0; if(!eglChooseConfig(r_ogl_lnx_state->display, config_filter, configs, ArrayCount(configs), &configs_count) || configs_count == 0) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't choose EGL configuration.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't choose EGL configuration.")); abort_self(1); } int screen = DefaultScreen(lnx_wm_state->display); @@ -97,7 +97,7 @@ r_ogl_os_init(CmdLine *cmdln) } if(vi == 0 || r_ogl_lnx_state->config == 0) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't find an EGL config with a matching X11 visual.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't find an EGL config with a matching X11 visual.")); abort_self(1); } lnx_wm_state->window_visual = vi->visual; @@ -125,7 +125,7 @@ r_ogl_os_init(CmdLine *cmdln) r_ogl_lnx_state->context = eglCreateContext(r_ogl_lnx_state->display, r_ogl_lnx_state->config, EGL_NO_CONTEXT, options); if(r_ogl_lnx_state->context == EGL_NO_CONTEXT) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't create OpenGL context with EGL.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't create OpenGL context with EGL.")); abort_self(1); } } @@ -168,7 +168,7 @@ r_ogl_os_window_equip(WM_Window window) } if(w->surface == EGL_NO_SURFACE) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't create EGL surface.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Couldn't create EGL surface.")); abort_self(1); } } diff --git a/src/render/opengl/linux/glx/render_opengl_linux_glx.c b/src/render/opengl/linux/glx/render_opengl_linux_glx.c index 993cdf32..3d7a870a 100644 --- a/src/render/opengl/linux/glx/render_opengl_linux_glx.c +++ b/src/render/opengl/linux/glx/render_opengl_linux_glx.c @@ -20,7 +20,7 @@ r_ogl_os_init(CmdLine *cmdln) { Temp scratch = scratch_begin(0, 0); String8 message = push_str8f(scratch.arena, "Unsupported GLX version (%i.%i, need at least 1.3)", glx_version_major, glx_version_minor); - wm_graphical_message(1, str8_lit("Fatal Error"), message); + sh_message(1, str8_lit("Fatal Error"), message); abort_self(1); scratch_end(scratch); } @@ -45,25 +45,25 @@ r_ogl_os_init(CmdLine *cmdln) GLXFBConfig *framebuffer_configs = glXChooseFBConfig(lnx_wm_state->display, DefaultScreen(lnx_wm_state->display), framebuffer_config_options, &framebuffer_configs_count); if(framebuffer_configs == 0) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Could not find a suitable framebuffer configuration.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Could not find a suitable framebuffer configuration.")); abort_self(1); } GLXFBConfig framebuffer_config = framebuffer_configs[0]; XFree(framebuffer_configs); - + //- extract visual/colormap from chosen fbconfig, publish to os layer { XVisualInfo *vi = glXGetVisualFromFBConfig(lnx_wm_state->display, framebuffer_config); if(vi == 0) { - wm_graphical_message(1, str8_lit("Fatal Error"), str8_lit("Could not get visual from GLX framebuffer config.")); + sh_message(1, str8_lit("Fatal Error"), str8_lit("Could not get visual from GLX framebuffer config.")); abort_self(1); } lnx_wm_state->window_visual = vi->visual; lnx_wm_state->window_depth = vi->depth; lnx_wm_state->window_colormap = XCreateColormap(lnx_wm_state->display, - XRootWindow(lnx_wm_state->display, vi->screen), - vi->visual, AllocNone); + XRootWindow(lnx_wm_state->display, vi->screen), + vi->visual, AllocNone); XFree(vi); } diff --git a/src/shell/shell.c b/src/shell/shell.c new file mode 100644 index 00000000..ae447aab --- /dev/null +++ b/src/shell/shell.c @@ -0,0 +1,2 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) diff --git a/src/shell/shell.h b/src/shell/shell.h new file mode 100644 index 00000000..c5828db0 --- /dev/null +++ b/src/shell/shell.h @@ -0,0 +1,16 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef SHELL_H +#define SHELL_H + +//////////////////////////////// +//~ rjf: @per_os_impl Shell Operations + +internal void sh_message(B32 error, String8 title, String8 message); +internal String8 sh_pick_file(Arena *arena, String8 title, String8 initial_path); +internal void sh_show_in_file_browser(String8 path); +internal void sh_open_in_browser(String8 url); +internal void sh_install_or_uninstall_self(B32 install); + +#endif // SHELL_H diff --git a/src/shell/shell_inc.c b/src/shell/shell_inc.c new file mode 100644 index 00000000..ea89cbfa --- /dev/null +++ b/src/shell/shell_inc.c @@ -0,0 +1,11 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "shell.c" +#if OS_WINDOWS +# include "win32/shell/win32_shell.c" +#elif OS_LINUX +# include "linux/shell/linux_shell.c" +#else +# error Shell functions not implemented for this operating system. +#endif diff --git a/src/shell/shell_inc.h b/src/shell/shell_inc.h new file mode 100644 index 00000000..7decdaeb --- /dev/null +++ b/src/shell/shell_inc.h @@ -0,0 +1,16 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef SHELL_INC_H +#define SHELL_INC_H + +#include "shell.h" +#if OS_WINDOWS +# include "win32/shell/win32_shell.h" +#elif OS_LINUX +# include "linux/shell/linux_shell.h" +#else +# error Shell functions not implemented for this operating system. +#endif + +#endif // SHELL_INC_H diff --git a/src/win32/shell/win32_shell.c b/src/win32/shell/win32_shell.c new file mode 100644 index 00000000..dffb4bff --- /dev/null +++ b/src/win32/shell/win32_shell.c @@ -0,0 +1,78 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: @per_os_impl Shell Operations + +internal void +sh_message(B32 error, String8 title, String8 message) +{ + Temp scratch = scratch_begin(0, 0); + String16 title16 = str16_from_8(scratch.arena, title); + String16 message16 = str16_from_8(scratch.arena, message); + MessageBoxW(0, (WCHAR *)message16.str, (WCHAR *)title16.str, MB_OK|(!!error*MB_ICONERROR)); + scratch_end(scratch); +} + +internal String8 +sh_pick_file(Arena *arena, String8 title, String8 initial_path) +{ + String8 result = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + U64 buffer_size = 4096; + U16 *buffer = push_array(scratch.arena, U16, buffer_size); + OPENFILENAMEW params = {sizeof(params)}; + { + params.lpstrTitle = (WCHAR *)str16_from_8(scratch.arena, title).str; + params.lpstrFile = (WCHAR *)buffer; + params.nMaxFile = buffer_size; + params.lpstrInitialDir = (WCHAR *)str16_from_8(scratch.arena, initial_path).str; + } + if(GetOpenFileNameW(¶ms)) + { + result = str8_from_16(arena, str16_cstring((U16 *)buffer)); + } + scratch_end(scratch); + } + return result; +} + +internal void +sh_show_in_file_browser(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + for(U64 idx = 0; idx < path_copy.size; idx += 1) + { + if(path_copy.str[idx] == '/') + { + path_copy.str[idx] = '\\'; + } + } + String16 path16 = str16_from_8(scratch.arena, path_copy); + SFGAOF flags = 0; + PIDLIST_ABSOLUTE list = 0; + if(path16.size != 0 && SUCCEEDED(SHParseDisplayName(path16.str, 0, &list, 0, &flags))) + { + HRESULT hr = SHOpenFolderAndSelectItems(list, 0, 0, 0); + CoTaskMemFree(list); + (void)hr; + } + scratch_end(scratch); +} + +internal void +sh_open_in_browser(String8 url) +{ + Temp scratch = scratch_begin(0, 0); + String16 url16 = str16_from_8(scratch.arena, url); + ShellExecuteW(0, L"open", (WCHAR *)url16.str, 0, 0, SW_SHOWNORMAL); + scratch_end(scratch); +} + +internal void +sh_install_or_uninstall_self(B32 install) +{ + // TODO(rjf) +} diff --git a/src/win32/shell/win32_shell.h b/src/win32/shell/win32_shell.h new file mode 100644 index 00000000..13b36c07 --- /dev/null +++ b/src/win32/shell/win32_shell.h @@ -0,0 +1,7 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef WIN32_SHELL_H +#define WIN32_SHELL_H + +#endif // WIN32_SHELL_H diff --git a/src/win32/window_manager/win32_window_manager.c b/src/win32/window_manager/win32_window_manager.c index c107ef58..8719f5ec 100644 --- a/src/win32/window_manager/win32_window_manager.c +++ b/src/win32/window_manager/win32_window_manager.c @@ -1618,76 +1618,3 @@ hcursor = curs; }break; w32_wm_state->hCursor = hcursor; } } - -//////////////////////////////// -//~ rjf: @per_os_impl Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -wm_graphical_message(B32 error, String8 title, String8 message) -{ - Temp scratch = scratch_begin(0, 0); - String16 title16 = str16_from_8(scratch.arena, title); - String16 message16 = str16_from_8(scratch.arena, message); - MessageBoxW(0, (WCHAR *)message16.str, (WCHAR *)title16.str, MB_OK|(!!error*MB_ICONERROR)); - scratch_end(scratch); -} - -internal String8 -wm_graphical_pick_file(Arena *arena, String8 title, String8 initial_path) -{ - String8 result = {0}; - { - Temp scratch = scratch_begin(&arena, 1); - U64 buffer_size = 4096; - U16 *buffer = push_array(scratch.arena, U16, buffer_size); - OPENFILENAMEW params = {sizeof(params)}; - { - params.lpstrTitle = (WCHAR *)str16_from_8(scratch.arena, title).str; - params.lpstrFile = (WCHAR *)buffer; - params.nMaxFile = buffer_size; - params.lpstrInitialDir = (WCHAR *)str16_from_8(scratch.arena, initial_path).str; - } - if(GetOpenFileNameW(¶ms)) - { - result = str8_from_16(arena, str16_cstring((U16 *)buffer)); - } - scratch_end(scratch); - } - return result; -} - -//////////////////////////////// -//~ rjf: @per_os_impl Shell Operations - -internal void -wm_show_in_filesystem_ui(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = push_str8_copy(scratch.arena, path); - for(U64 idx = 0; idx < path_copy.size; idx += 1) - { - if(path_copy.str[idx] == '/') - { - path_copy.str[idx] = '\\'; - } - } - String16 path16 = str16_from_8(scratch.arena, path_copy); - SFGAOF flags = 0; - PIDLIST_ABSOLUTE list = 0; - if(path16.size != 0 && SUCCEEDED(SHParseDisplayName(path16.str, 0, &list, 0, &flags))) - { - HRESULT hr = SHOpenFolderAndSelectItems(list, 0, 0, 0); - CoTaskMemFree(list); - (void)hr; - } - scratch_end(scratch); -} - -internal void -wm_open_in_browser(String8 url) -{ - Temp scratch = scratch_begin(0, 0); - String16 url16 = str16_from_8(scratch.arena, url); - ShellExecuteW(0, L"open", (WCHAR *)url16.str, 0, 0, SW_SHOWNORMAL); - scratch_end(scratch); -} diff --git a/src/window_manager/window_manager.h b/src/window_manager/window_manager.h index 804e5468..d2d5dd98 100644 --- a/src/window_manager/window_manager.h +++ b/src/window_manager/window_manager.h @@ -231,16 +231,4 @@ internal Vec2F32 wm_mouse_from_window(WM_Window window); internal void wm_set_cursor(WM_Cursor cursor); -//////////////////////////////// -//~ rjf: @per_os_impl Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void wm_graphical_message(B32 error, String8 title, String8 message); -internal String8 wm_graphical_pick_file(Arena *arena, String8 title, String8 initial_path); - -//////////////////////////////// -//~ rjf: @per_os_impl Shell Operations - -internal void wm_show_in_filesystem_ui(String8 path); -internal void wm_open_in_browser(String8 url); - #endif // WINDOW_MANAGER_H diff --git a/src/window_manager/window_manager_stub.c b/src/window_manager/window_manager_stub.c index cc81368e..6910ba2d 100644 --- a/src/window_manager/window_manager_stub.c +++ b/src/window_manager/window_manager_stub.c @@ -249,30 +249,3 @@ internal void wm_set_cursor(WM_Cursor cursor) { } - -//////////////////////////////// -//~ rjf: @per_os_impl Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -wm_graphical_message(B32 error, String8 title, String8 message) -{ -} - -internal String8 -wm_graphical_pick_file(Arena *arena, String8 title, String8 initial_path) -{ - return str8_zero(); -} - -//////////////////////////////// -//~ rjf: @per_os_impl Shell Operations - -internal void -wm_show_in_filesystem_ui(String8 path) -{ -} - -internal void -wm_open_in_browser(String8 url) -{ -} From fd33f61a04ebe50164889b4472d0bded5f7374a8 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 14:01:19 -0600 Subject: [PATCH 014/251] add symlink in local bin path for linux install --- project.raddbg | 2 +- src/linux/shell/linux_shell.c | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/project.raddbg b/project.raddbg index 36f1e2c7..8b516f0f 100755 --- a/project.raddbg +++ b/project.raddbg @@ -75,6 +75,6 @@ target: target: { executable: "build/raddbg" - working_directory: "build/" + working_directory: build enabled: 1 } diff --git a/src/linux/shell/linux_shell.c b/src/linux/shell/linux_shell.c index 7e7d7713..6b935fea 100644 --- a/src/linux/shell/linux_shell.c +++ b/src/linux/shell/linux_shell.c @@ -132,13 +132,19 @@ sh_install_or_uninstall_self(B32 install) { Temp scratch = scratch_begin(0, 0); + //- rjf: unpack context + char *home = getenv("HOME"); + + //- rjf: get local user binary path + String8 bin_path = str8f(scratch.arena, "%s/.local/bin", home); + String8 symlink_path = str8f(scratch.arena, "%S/raddbg", bin_path); + //- rjf: get xdg local application install info path String8 application_name = str8_chop_last_dot(str8_skip_last_slash(get_process_info()->binary_file_path)); char *xdg_data_home_cstring = getenv("XDG_DATA_HOME"); String8 desktop_entries_folder_name = str8_cstring(xdg_data_home_cstring); if(desktop_entries_folder_name.size == 0) { - char *home = getenv("HOME"); desktop_entries_folder_name = str8f(scratch.arena, "%s/.local/share", home); } String8 desktop_file_path = str8f(scratch.arena, "%S/applications/%S.desktop", desktop_entries_folder_name, application_name); @@ -153,7 +159,7 @@ sh_install_or_uninstall_self(B32 install) } #endif - //- rjf: install -> write files + //- rjf: install -> write info if(install) { //- rjf: write icon @@ -176,6 +182,10 @@ sh_install_or_uninstall_self(B32 install) has_icon ? "\n" : "", BUILD_CONSOLE_INTERFACE ? "true" : "false"); write_data_to_file_path(desktop_file_path, desktop_file_data); + + //- rjf: make symlink in binary path to this exe + make_directory(str8_chop_last_slash(symlink_path)); + symlink((char *)get_process_info()->binary_file_path.str, (char *)symlink_path.str); } //- rjf: uninstall -> delete files @@ -183,6 +193,7 @@ sh_install_or_uninstall_self(B32 install) { delete_file_at_path(icon_path); delete_file_at_path(desktop_file_path); + unlink((char *)symlink_path.str); } scratch_end(scratch); From eff08276073a832615591ec7671d4f8721611414 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 14:14:51 -0600 Subject: [PATCH 015/251] hook up initial installation detection, in case program is manually installed externally --- src/linux/shell/linux_shell.c | 74 +++++++++++++++++++++-------------- src/raddbg/raddbg_core.c | 5 ++- src/shell/shell.h | 2 +- 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/linux/shell/linux_shell.c b/src/linux/shell/linux_shell.c index 6b935fea..06ce8320 100644 --- a/src/linux/shell/linux_shell.c +++ b/src/linux/shell/linux_shell.c @@ -127,9 +127,10 @@ sh_open_in_browser(String8 url) } } -internal void -sh_install_or_uninstall_self(B32 install) +internal B32 +sh_install_or_uninstall_self(B32 write, B32 install) { + B32 install_state = 0; Temp scratch = scratch_begin(0, 0); //- rjf: unpack context @@ -159,42 +160,55 @@ sh_install_or_uninstall_self(B32 install) } #endif - //- rjf: install -> write info - if(install) + //- rjf: if writing -> do mutation + if(write) { - //- rjf: write icon - write_data_to_file_path(icon_path, lnx_sh_logo_file_bytes); + //- rjf: install -> write info + if(install) + { + //- rjf: write icon + write_data_to_file_path(icon_path, lnx_sh_logo_file_bytes); + + //- rjf: write .desktop file + String8 desktop_file_data = str8f(scratch.arena, + "[Desktop Entry]\n" + "Type=Application\n" + "Version=" BUILD_VERSION_STRING_LITERAL "\n" + "Name=" BUILD_TITLE "\n" + "Comment=" BUILD_TITLE_STRING_LITERAL "\n" + "Exec=%S\n" + "%s%S%s" // (optional icon) + "Terminal=%s\n" + "Categories=Utility;Development;", + get_process_info()->binary_file_path, + has_icon ? "Icon=" : "", + has_icon ? icon_path : s(""), + has_icon ? "\n" : "", + BUILD_CONSOLE_INTERFACE ? "true" : "false"); + write_data_to_file_path(desktop_file_path, desktop_file_data); + + //- rjf: make symlink in binary path to this exe + make_directory(str8_chop_last_slash(symlink_path)); + symlink((char *)get_process_info()->binary_file_path.str, (char *)symlink_path.str); + } - //- rjf: write .desktop file - String8 desktop_file_data = str8f(scratch.arena, - "[Desktop Entry]\n" - "Type=Application\n" - "Version=" BUILD_VERSION_STRING_LITERAL "\n" - "Name=" BUILD_TITLE "\n" - "Comment=" BUILD_TITLE_STRING_LITERAL "\n" - "Exec=%S\n" - "%s%S%s" // (optional icon) - "Terminal=%s\n" - "Categories=Utility;Development;", - get_process_info()->binary_file_path, - has_icon ? "Icon=" : "", - has_icon ? icon_path : s(""), - has_icon ? "\n" : "", - BUILD_CONSOLE_INTERFACE ? "true" : "false"); - write_data_to_file_path(desktop_file_path, desktop_file_data); + //- rjf: uninstall -> delete files + else + { + delete_file_at_path(icon_path); + delete_file_at_path(desktop_file_path); + unlink((char *)symlink_path.str); + } - //- rjf: make symlink in binary path to this exe - make_directory(str8_chop_last_slash(symlink_path)); - symlink((char *)get_process_info()->binary_file_path.str, (char *)symlink_path.str); + install_state = install; } - //- rjf: uninstall -> delete files + //- rjf: do reading -> check if installation state exists else { - delete_file_at_path(icon_path); - delete_file_at_path(desktop_file_path); - unlink((char *)symlink_path.str); + install_state = file_path_exists(desktop_file_path); } scratch_end(scratch); + return install_state; } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index e7ae1924..2f32ef27 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -11486,6 +11486,9 @@ rd_init(CmdLine *cmdln) scratch_end(scratch); } + // rjf: check initial installation status + rd_state->installed = sh_install_or_uninstall_self(0, 0); + ProfEnd(); scratch_end(scratch); } @@ -13208,7 +13211,7 @@ rd_frame(void) B32 last_installed = rd_state->installed; if(installed != last_installed) { - sh_install_or_uninstall_self(installed); + sh_install_or_uninstall_self(1, installed); rd_state->installed = installed; } diff --git a/src/shell/shell.h b/src/shell/shell.h index c5828db0..4c1a2dc7 100644 --- a/src/shell/shell.h +++ b/src/shell/shell.h @@ -11,6 +11,6 @@ internal void sh_message(B32 error, String8 title, String8 message); internal String8 sh_pick_file(Arena *arena, String8 title, String8 initial_path); internal void sh_show_in_file_browser(String8 path); internal void sh_open_in_browser(String8 url); -internal void sh_install_or_uninstall_self(B32 install); +internal B32 sh_install_or_uninstall_self(B32 write, B32 install); #endif // SHELL_H From 13f9ddc18a39bc62a80908b0e01df22922d6230d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 14:24:20 -0600 Subject: [PATCH 016/251] set initial position of cursor trail correctly --- src/raddbg/raddbg_widgets.c | 4 ++-- src/ui/ui_basic_widgets.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 825afec7..e9769997 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -2864,9 +2864,9 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred { Vec2F32 advance = fnt_dim_from_tag_size_string(line_box->font, line_box->font_size, 0, params->tab_size, str8_prefix(line_string, *cursor - line_range.min)); F32 cursor_y = text_container_box->rect.y0 + line_idx*params->line_height_px - params->font_size*0.125f; - F32 cursor_y__animated = ui_anim(ui_key_from_stringf(text_container_box->key, "cursor_y_px"), cursor_y); + F32 cursor_y__animated = ui_anim(ui_key_from_stringf(text_container_box->key, "cursor_y_px"), cursor_y, .initial = cursor_y); F32 cursor_off_pixels = advance.x; - F32 cursor_off_pixels__animated = ui_anim(ui_key_from_stringf(text_container_box->key, "cursor_off_px"), cursor_off_pixels); + F32 cursor_off_pixels__animated = ui_anim(ui_key_from_stringf(text_container_box->key, "cursor_off_px"), cursor_off_pixels, .initial = cursor_off_pixels); F32 cursor_thickness = ClampBot(1.f, floor_f32(line_box->font_size/10.f)); Rng2F32 cursor_rect = { diff --git a/src/ui/ui_basic_widgets.c b/src/ui/ui_basic_widgets.c index 93d8ea06..bdcc7886 100644 --- a/src/ui/ui_basic_widgets.c +++ b/src/ui/ui_basic_widgets.c @@ -151,7 +151,7 @@ internal UI_BOX_CUSTOM_DRAW(ui_line_edit_draw) U64 cursor = draw_data->cursor; U64 mark = draw_data->mark; F32 cursor_pixel_off = fnt_dim_from_tag_size_string(font, font_size, 0, tab_size, str8_prefix(edited_string, cursor)).x; - F32 cursor_pixel_off__animated = ui_anim(ui_key_from_stringf(box->key, "cursor_off_px"), cursor_pixel_off); + F32 cursor_pixel_off__animated = ui_anim(ui_key_from_stringf(box->key, "cursor_off_px"), cursor_pixel_off, .initial = cursor_pixel_off); F32 mark_pixel_off = fnt_dim_from_tag_size_string(font, font_size, 0, tab_size, str8_prefix(edited_string, mark)).x; F32 cursor_thickness = ClampBot(1.f, floor_f32(font_size/10.f)); Rng2F32 cursor_rect = From e423b31c80c1f6a8432fdb4b66c419718f767d83 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 14:33:16 -0600 Subject: [PATCH 017/251] adjustments --- src/raddbg/raddbg_core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 2f32ef27..081c5de9 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -5956,13 +5956,13 @@ rd_window_frame(void) //////////////////////////// //- rjf: @window_ui_part calculate top-level rectangles/sizes // + F32 window_edge_px = ui_top_font_size()*0.2f; Rng2F32 window_rect = wm_client_rect_from_window(ws->os); Vec2F32 window_rect_dim = dim_2f32(window_rect); F32 top_bar_dim_px = floor_f32(ui_top_font_size()*3.f); Rng2F32 top_bar_rect = r2f32p(window_rect.x0, window_rect.y0, window_rect.x0+window_rect_dim.x+1, window_rect.y0+top_bar_dim_px); Rng2F32 bottom_bar_rect = r2f32p(window_rect.x0, window_rect_dim.y - top_bar_dim_px, window_rect.x0+window_rect_dim.x, window_rect.y0+window_rect_dim.y); - Rng2F32 content_rect = r2f32p(window_rect.x0, top_bar_rect.y1, window_rect.x0+window_rect_dim.x, bottom_bar_rect.y0); - F32 window_edge_px = wm_dpi_from_window(ws->os)*0.035f; + Rng2F32 content_rect = r2f32p(window_rect.x0, top_bar_rect.y1 + window_edge_px*1.5f, window_rect.x0+window_rect_dim.x, bottom_bar_rect.y0); content_rect = pad_2f32(content_rect, -window_edge_px); //////////////////////////// @@ -7252,7 +7252,7 @@ rd_window_frame(void) wm_window_clear_custom_border_data(ws->os); wm_window_push_custom_edges(ws->os, window_edge_px); wm_window_push_custom_title_bar(ws->os, dim_2f32(top_bar_rect).y); - ui_set_next_flags(UI_BoxFlag_DefaultFocusNav|UI_BoxFlag_DisableFocusOverlay); + ui_set_next_flags(UI_BoxFlag_DefaultFocusNav|UI_BoxFlag_DisableFocusOverlay|UI_BoxFlag_DrawDropShadow); UI_Focus((ws->menu_bar_focused && window_is_focused && !ui_any_ctx_menu_is_open()) ? UI_FocusKind_On : UI_FocusKind_Null) UI_TagF("menu_bar") UI_Pane(top_bar_rect, str8_lit("###top_bar")) @@ -8399,7 +8399,7 @@ rd_window_frame(void) panel_rect_pct.y0*content_rect_dim.y, panel_rect_pct.x1*content_rect_dim.x, panel_rect_pct.y1*content_rect_dim.y); - panel_rect = pad_2f32(panel_rect, floor_f32(-ui_top_font_size()*0.15f)); + panel_rect = pad_2f32(panel_rect, floor_f32(-ui_top_font_size()*0.25f)); panel_rect = r2f32p(round_f32(panel_rect.x0), round_f32(panel_rect.y0), round_f32(panel_rect.x1), round_f32(panel_rect.y1)); F32 tab_bar_rheight = floor_f32(ui_top_font_size()*3.5f); F32 tab_bar_vheight = floor_f32(ui_top_font_size()*rd_setting_f32_from_name(str8_lit("tab_height"))); From 50ecf0e68aa831c9504318e49ff94aad5acea998 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 15:13:28 -0600 Subject: [PATCH 018/251] linux/window_manager: window maximizing/minimizing/fullscreening --- .../window_manager/linux_window_manager.c | 152 ++++++++++++++++-- 1 file changed, 143 insertions(+), 9 deletions(-) diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 6f37d82d..48b83c99 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -488,45 +488,179 @@ internal B32 wm_window_is_fullscreen(WM_Window handle) { if(wm_window_match(handle, wm_window_zero())) {return 0;} - // TODO(rjf) - return 0; + B32 is_fullscreen = 0; + { + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + + // rjf: get atoms + Atom wm_state = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE", 0); + Atom fullscreen = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE_FULLSCREEN", 0); + + // rjf: get window properties + unsigned long props_count = 0; + Atom *props = 0; + { + Atom actual_type; + int actual_format; + unsigned long bytes_after = 0; + int result = XGetWindowProperty(lnx_wm_state->display, w->window, wm_state, 0, 1024, 0, AnyPropertyType, + &actual_type, &actual_format, &props_count, &bytes_after, + (unsigned char **)&props); + } + + // rjf: find if horizontal / vertical fullscreen properties are set + { + for(unsigned long idx = 0; idx < props_count; idx += 1) + { + if(props[idx] == fullscreen) + { + is_fullscreen = 1; + break; + } + } + } + + // rjf: free properties + XFree(props); + } + return is_fullscreen; } internal void wm_window_set_fullscreen(WM_Window handle, B32 fullscreen) { if(wm_window_match(handle, wm_window_zero())) {return;} - // TODO(rjf) + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + XEvent evt; + MemoryZeroStruct(&evt); + { + evt.type = ClientMessage; + evt.xclient.window = w->window; + evt.xclient.message_type = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE", 0); + evt.xclient.format = 32; + evt.xclient.data.l[0] = !!fullscreen; + evt.xclient.data.l[1] = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE_FULLSCREEN", 0); + evt.xclient.data.l[3] = 1; + } + XSendEvent(lnx_wm_state->display, DefaultRootWindow(lnx_wm_state->display), 0, + SubstructureNotifyMask | SubstructureRedirectMask, &evt); + XFlush(lnx_wm_state->display); } internal B32 wm_window_is_maximized(WM_Window handle) { if(wm_window_match(handle, wm_window_zero())) {return 0;} - // TODO(rjf) - return 0; + B32 is_maximized = 0; + { + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + + // rjf: get atoms + Atom wm_state = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE", 0); + Atom x_max = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0); + Atom y_max = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE_MAXIMIZED_VERT", 0); + + // rjf: get window properties + unsigned long props_count = 0; + Atom *props = 0; + { + Atom actual_type; + int actual_format; + unsigned long bytes_after = 0; + int result = XGetWindowProperty(lnx_wm_state->display, w->window, wm_state, 0, 1024, 0, AnyPropertyType, + &actual_type, &actual_format, &props_count, &bytes_after, + (unsigned char **)&props); + } + + // rjf: find if horizontal / vertical maximized properties are set + B32 x_max_set = 0; + B32 y_max_set = 0; + { + for(unsigned long idx = 0; idx < props_count; idx += 1) + { + if(props[idx] == x_max) { x_max_set = 1; } + if(props[idx] == y_max) { y_max_set = 1; } + } + } + + // rjf: free properties + XFree(props); + + // rjf: maximized -> both vert/horizontal set + is_maximized = (x_max_set && y_max_set); + } + return is_maximized; } internal void wm_window_set_maximized(WM_Window handle, B32 maximized) { if(wm_window_match(handle, wm_window_zero())) {return;} - // TODO(rjf) + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + XEvent evt; + MemoryZeroStruct(&evt); + { + evt.type = ClientMessage; + evt.xclient.window = w->window; + evt.xclient.message_type = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE", 0); + evt.xclient.format = 32; + evt.xclient.data.l[0] = !!maximized; + evt.xclient.data.l[1] = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0); + evt.xclient.data.l[2] = XInternAtom(lnx_wm_state->display, "_NET_WM_STATE_MAXIMIZED_VERT", 0); + evt.xclient.data.l[3] = 1; + } + XSendEvent(lnx_wm_state->display, DefaultRootWindow(lnx_wm_state->display), 0, + SubstructureNotifyMask | SubstructureRedirectMask, &evt); + XFlush(lnx_wm_state->display); } internal B32 wm_window_is_minimized(WM_Window handle) { if(wm_window_match(handle, wm_window_zero())) {return 0;} - // TODO(rjf) - return 0; + B32 is_minimized = 0; + { + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + Atom wm_state = XInternAtom(lnx_wm_state->display, "WM_STATE", 0); + + // rjf: get window property data + unsigned long props_count = 0; + U8 *prop_data = 0; + { + Atom actual_type; + int actual_format; + unsigned long bytes_after = 0; + int result = XGetWindowProperty(lnx_wm_state->display, w->window, wm_state, 0, 1024, 0, AnyPropertyType, + &actual_type, &actual_format, &props_count, &bytes_after, + (unsigned char **)&prop_data); + } + + // rjf: is_minimized -> first unsigned long of property data == IconicState + if(prop_data != 0) + { + is_minimized = (*(unsigned long *)prop_data) == IconicState; + } + + // rjf: free properties + XFree(prop_data); + } + return is_minimized; } internal void wm_window_set_minimized(WM_Window handle, B32 minimized) { if(wm_window_match(handle, wm_window_zero())) {return;} - // TODO(rjf) + LNX_WM_Window *w = (LNX_WM_Window *)handle.u64[0]; + if(minimized) + { + XIconifyWindow(lnx_wm_state->display, w->window, DefaultScreen(lnx_wm_state->display)); + } + else + { + XMapWindow(lnx_wm_state->display, w->window); + } + XFlush(lnx_wm_state->display); } internal void From 9c1b243472429e471348896f09f3c19b83c2af85 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 15:18:42 -0600 Subject: [PATCH 019/251] remove dumb fudge factor --- src/raddbg/raddbg_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 081c5de9..14b199ff 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -5960,7 +5960,7 @@ rd_window_frame(void) Rng2F32 window_rect = wm_client_rect_from_window(ws->os); Vec2F32 window_rect_dim = dim_2f32(window_rect); F32 top_bar_dim_px = floor_f32(ui_top_font_size()*3.f); - Rng2F32 top_bar_rect = r2f32p(window_rect.x0, window_rect.y0, window_rect.x0+window_rect_dim.x+1, window_rect.y0+top_bar_dim_px); + Rng2F32 top_bar_rect = r2f32p(window_rect.x0, window_rect.y0, window_rect.x0+window_rect_dim.x, window_rect.y0+top_bar_dim_px); Rng2F32 bottom_bar_rect = r2f32p(window_rect.x0, window_rect_dim.y - top_bar_dim_px, window_rect.x0+window_rect_dim.x, window_rect.y0+window_rect_dim.y); Rng2F32 content_rect = r2f32p(window_rect.x0, top_bar_rect.y1 + window_edge_px*1.5f, window_rect.x0+window_rect_dim.x, bottom_bar_rect.y0); content_rect = pad_2f32(content_rect, -window_edge_px); From 5ed398a8b9ab5196aac812b096eda2e26961b1ba Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 6 Aug 2026 15:35:56 -0700 Subject: [PATCH 020/251] fix windows build --- src/win32/shell/win32_shell.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/win32/shell/win32_shell.c b/src/win32/shell/win32_shell.c index dffb4bff..a99487b2 100644 --- a/src/win32/shell/win32_shell.c +++ b/src/win32/shell/win32_shell.c @@ -71,8 +71,9 @@ sh_open_in_browser(String8 url) scratch_end(scratch); } -internal void -sh_install_or_uninstall_self(B32 install) +internal B32 +sh_install_or_uninstall_self(B32 write, B32 install) { // TODO(rjf) + return 0; } From 7388b757d118c138439c38a2024b3443015b5d39 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 7 Aug 2026 10:17:01 -0600 Subject: [PATCH 021/251] demon: eliminate redundant access scopes --- src/dbg_engine/dbg_engine_ctrl.c | 11 +- src/demon/{demon_core.c => demon.c} | 0 src/demon/{demon_core.h => demon.h} | 25 +-- src/demon/{demon_core.mdesk => demon.mdesk} | 3 + src/demon/demon_inc.c | 2 +- src/demon/demon_inc.h | 2 +- src/linux/demon/linux_demon.c | 1 - src/win32/demon/win32_demon.c | 230 ++++++++++---------- 8 files changed, 123 insertions(+), 151 deletions(-) rename src/demon/{demon_core.c => demon.c} (100%) rename src/demon/{demon_core.h => demon.h} (90%) rename src/demon/{demon_core.mdesk => demon.mdesk} (87%) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index f33189d3..ae97d9c6 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -2476,14 +2476,11 @@ d_ctrl_thread__entry_point(void *p) ProfScope("update thread register cache") { D_EntityArray threads = d_entity_array_from_kind(D_EntityKind_Thread); - X64_RegBlock *blocks = push_array(scratch.arena, X64_RegBlock, threads.count); + for EachIndex(idx, threads.count) { - for EachIndex(idx, threads.count) - { - Temp scratch = scratch_begin(0, 0); - d_cached_reg_block_from_thread(scratch.arena, threads.v[idx]->handle); - scratch_end(scratch); - } + Temp scratch = scratch_begin(0, 0); + d_cached_reg_block_from_thread(scratch.arena, threads.v[idx]->handle); + scratch_end(scratch); } } diff --git a/src/demon/demon_core.c b/src/demon/demon.c similarity index 100% rename from src/demon/demon_core.c rename to src/demon/demon.c diff --git a/src/demon/demon_core.h b/src/demon/demon.h similarity index 90% rename from src/demon/demon_core.h rename to src/demon/demon.h index eba11daf..7c9d8a5e 100644 --- a/src/demon/demon_core.h +++ b/src/demon/demon.h @@ -1,8 +1,8 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#ifndef DEMON_CORE_H -#define DEMON_CORE_H +#ifndef DEMON_H +#define DEMON_H //////////////////////////////// //~ rjf: Control-Thread-Only Context @@ -18,17 +18,6 @@ struct DMN_CtrlCtx U64 u64[1]; }; -//////////////////////////////// -//~ Dynamic Linker Types - -typedef U32 DMN_TlsModel; -enum -{ - DMN_TlsModel_Null, - DMN_TlsModel_WinodwsNt, - DMN_TlsModel_Gnu -}; - //////////////////////////////// //~ rjf: Handle Types @@ -129,9 +118,6 @@ struct DMN_Event U64 user_data; B32 exception_repeated; - // rjf: process info - DMN_TlsModel tls_model; - // rjf: module info DMN_ModuleInfo *module_info; @@ -247,11 +233,6 @@ internal DMN_HandleArray dmn_handle_array_copy(Arena *arena, DMN_HandleArray *sr //- rjf: event list building internal DMN_Event *dmn_event_list_push(Arena *arena, DMN_EventList *list); -//////////////////////////////// -//~ rjf: Process Reading Helper Functions (Helpers, Implemented Once) - -internal String8 dmn_data_from_process_vaddr_range(Arena *arena, DMN_Handle process, Rng1U64 vrange); - //////////////////////////////// //~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) @@ -302,4 +283,4 @@ internal void dmn_process_iter_begin(DMN_ProcessIter *iter); internal B32 dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out); internal void dmn_process_iter_end(DMN_ProcessIter *iter); -#endif // DEMON_CORE_H +#endif // DEMON_H diff --git a/src/demon/demon_core.mdesk b/src/demon/demon.mdesk similarity index 87% rename from src/demon/demon_core.mdesk rename to src/demon/demon.mdesk index 50acceef..26e8825f 100644 --- a/src/demon/demon_core.mdesk +++ b/src/demon/demon.mdesk @@ -1,3 +1,6 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + //////////////////////////////// //~ rjf: Event Kind Tables diff --git a/src/demon/demon_inc.c b/src/demon/demon_inc.c index 771aa2e6..d0e7b432 100644 --- a/src/demon/demon_inc.c +++ b/src/demon/demon_inc.c @@ -1,7 +1,7 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#include "demon/demon_core.c" +#include "demon/demon.c" #if OS_WINDOWS # include "win32/demon/win32_demon.c" diff --git a/src/demon/demon_inc.h b/src/demon/demon_inc.h index b5e693f1..cc93d4d7 100644 --- a/src/demon/demon_inc.h +++ b/src/demon/demon_inc.h @@ -4,7 +4,7 @@ #ifndef DEMON_INC_H #define DEMON_INC_H -#include "demon/demon_core.h" +#include "demon/demon.h" #if OS_WINDOWS # include "win32/demon/win32_demon.h" diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 5f2183ba..c5ce4360 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1953,7 +1953,6 @@ lnx_dmn_push_event_create_process(Arena *arena, DMN_EventList *events, LNX_DMN_P e->process = lnx_dmn_handle_from_process(process); e->arch = process->ctx->arch; e->code = process->pid; - e->tls_model = DMN_TlsModel_Gnu; // TODO: use dynamic linker path to figure out correct enum here } internal void diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index aae97c39..6944e420 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -1365,118 +1365,117 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) { Temp scratch = scratch_begin(0, 0); U32 result = 0; - DMN_AccessScope + + //- rjf: produce exe / arguments string + String8 cmd = {0}; + if(params->cmd_line.first != 0) { - //- rjf: produce exe / arguments string - String8 cmd = {0}; - if(params->cmd_line.first != 0) + String8List args = {0}; + String8 exe_path = params->cmd_line.first->string; + String8List exe_path_parts = str8_split_path(scratch.arena, exe_path); + exe_path = str8_list_join(scratch.arena, &exe_path_parts, &(StringJoin){.sep = str8_lit("\\")}); + str8_list_pushf(scratch.arena, &args, "\"%S\"", exe_path); + for(String8Node *n = params->cmd_line.first->next; n != 0; n = n->next) { - String8List args = {0}; - String8 exe_path = params->cmd_line.first->string; - String8List exe_path_parts = str8_split_path(scratch.arena, exe_path); - exe_path = str8_list_join(scratch.arena, &exe_path_parts, &(StringJoin){.sep = str8_lit("\\")}); - str8_list_pushf(scratch.arena, &args, "\"%S\"", exe_path); - for(String8Node *n = params->cmd_line.first->next; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &args, n->string); - } - StringJoin join_params = {0}; - join_params.sep = str8_lit(" "); - cmd = str8_list_join(scratch.arena, &args, &join_params); - } - - //- rjf: produce environment strings - String8 env = {0}; - { - String8List all_opts = params->env; - if(params->inherit_env != 0) - { - MemoryZeroStruct(&all_opts); - str8_list_push(scratch.arena, &all_opts, str8_lit("_NO_DEBUG_HEAP=1")); - for(String8Node *n = params->env.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - for(String8Node *n = w32_dmn_shared->env_strings.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - } - StringJoin join_params2 = {0}; - join_params2.sep = str8_lit("\0"); - join_params2.post = str8_lit("\0"); - env = str8_list_join(scratch.arena, &all_opts, &join_params2); - } - - //- rjf: produce utf-16 strings - String16 cmd16 = str16_from_8(scratch.arena, cmd); - String16 dir16 = str16_from_8(scratch.arena, params->path); - String16 env16 = str16_from_8(scratch.arena, env); - - //- rjf: launch - DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; - if(params->debug_subprocesses) - { - creation_flags |= DEBUG_PROCESS; - } - else - { - creation_flags |= DEBUG_ONLY_THIS_PROCESS; - } - BOOL inherit_handles = 0; - STARTUPINFOW startup_info = {sizeof(startup_info)}; - if(!file_match(params->stdout_file, file_zero())) - { - HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0]; - startup_info.hStdOutput = stdout_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - if(!file_match(params->stderr_file, file_zero())) - { - HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0]; - startup_info.hStdError = stderr_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - if(!file_match(params->stdin_file, file_zero())) - { - HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0]; - startup_info.hStdInput = stdin_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - PROCESS_INFORMATION process_info = {0}; - if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 1, creation_flags, (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) - { - // check if we are 32-bit app, and just close it immediately - BOOL is_wow = 0; - IsWow64Process(process_info.hProcess, &is_wow); - if(is_wow) - { - log_user_errorf("Only 64-bit applications can be debugged currently."); - DebugActiveProcessStop(process_info.dwProcessId); - TerminateProcess(process_info.hProcess,0xffffffff); - } - else - { - result = process_info.dwProcessId; - w32_dmn_shared->new_process_pending = 1; - } - CloseHandle(process_info.hProcess); - CloseHandle(process_info.hThread); - } - else - { - DWORD error = GetLastError(); - LPWSTR message = 0; - FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, error, MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), (LPWSTR)&message, 0, 0); - String8 message8 = message ? str8_from_16(scratch.arena, str16_cstring(message)) : str8_lit("unknown error"); - LocalFree(message); - - log_user_errorf("There was an error starting %S: %S", params->cmd_line.first->string, message8); + str8_list_push(scratch.arena, &args, n->string); } + StringJoin join_params = {0}; + join_params.sep = str8_lit(" "); + cmd = str8_list_join(scratch.arena, &args, &join_params); } + + //- rjf: produce environment strings + String8 env = {0}; + { + String8List all_opts = params->env; + if(params->inherit_env != 0) + { + MemoryZeroStruct(&all_opts); + str8_list_push(scratch.arena, &all_opts, str8_lit("_NO_DEBUG_HEAP=1")); + for(String8Node *n = params->env.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + for(String8Node *n = w32_dmn_shared->env_strings.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + } + StringJoin join_params2 = {0}; + join_params2.sep = str8_lit("\0"); + join_params2.post = str8_lit("\0"); + env = str8_list_join(scratch.arena, &all_opts, &join_params2); + } + + //- rjf: produce utf-16 strings + String16 cmd16 = str16_from_8(scratch.arena, cmd); + String16 dir16 = str16_from_8(scratch.arena, params->path); + String16 env16 = str16_from_8(scratch.arena, env); + + //- rjf: launch + DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; + if(params->debug_subprocesses) + { + creation_flags |= DEBUG_PROCESS; + } + else + { + creation_flags |= DEBUG_ONLY_THIS_PROCESS; + } + BOOL inherit_handles = 0; + STARTUPINFOW startup_info = {sizeof(startup_info)}; + if(!file_match(params->stdout_file, file_zero())) + { + HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0]; + startup_info.hStdOutput = stdout_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + if(!file_match(params->stderr_file, file_zero())) + { + HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0]; + startup_info.hStdError = stderr_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + if(!file_match(params->stdin_file, file_zero())) + { + HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0]; + startup_info.hStdInput = stdin_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + PROCESS_INFORMATION process_info = {0}; + if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 1, creation_flags, (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) + { + // check if we are 32-bit app, and just close it immediately + BOOL is_wow = 0; + IsWow64Process(process_info.hProcess, &is_wow); + if(is_wow) + { + log_user_errorf("Only 64-bit applications can be debugged currently."); + DebugActiveProcessStop(process_info.dwProcessId); + TerminateProcess(process_info.hProcess,0xffffffff); + } + else + { + result = process_info.dwProcessId; + w32_dmn_shared->new_process_pending = 1; + } + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + } + else + { + DWORD error = GetLastError(); + LPWSTR message = 0; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, error, MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), (LPWSTR)&message, 0, 0); + String8 message8 = message ? str8_from_16(scratch.arena, str16_cstring(message)) : str8_lit("unknown error"); + LocalFree(message); + + log_user_errorf("There was an error starting %S: %S", params->cmd_line.first->string, message8); + } + scratch_end(scratch); return result; } @@ -1485,7 +1484,7 @@ internal B32 dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) { B32 result = 0; - DMN_AccessScope if(DebugActiveProcess((DWORD)pid)) + if(DebugActiveProcess((DWORD)pid)) { result = 1; w32_dmn_shared->new_process_pending = 1; @@ -1514,13 +1513,10 @@ internal B32 dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code) { B32 result = 0; - DMN_AccessScope + W32_DMN_Entity *process_entity = w32_dmn_entity_from_handle(process); + if(TerminateProcess(process_entity->handle, exit_code)) { - W32_DMN_Entity *process_entity = w32_dmn_entity_from_handle(process); - if(TerminateProcess(process_entity->handle, exit_code)) - { - result = 1; - } + result = 1; } return result; } @@ -1529,7 +1525,6 @@ internal B32 dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process) { B32 result = 0; - DMN_AccessScope { W32_DMN_Entity *process_entity = w32_dmn_entity_from_handle(process); @@ -1567,7 +1562,6 @@ internal DMN_EventList dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { DMN_EventList events = {0}; - dmn_access_open(); ////////////////////////////// //- rjf: determine event generation path @@ -2148,7 +2142,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->process = w32_dmn_handle_from_entity(process); e->arch = module_info->arch; e->code = evt.dwProcessId; - e->tls_model = DMN_TlsModel_WinodwsNt; } // rjf: create thread @@ -3151,7 +3144,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) }break; } - dmn_access_close(); return events; } From 8c2efc5adf2b24cfedbf30e9238fdd55cf05a82c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 7 Aug 2026 13:23:42 -0600 Subject: [PATCH 022/251] linux/demon flattening / clean-up checkpoint - eliminate redundant helpers, single-use functions, obsolete code --- obsolete/linux/demon/linux_demon_obsolete.c | 54 + src/linux/demon/generated/linux_demon.meta.c | 40 + src/linux/demon/generated/linux_demon.meta.h | 32 + src/linux/demon/linux_demon.c | 1909 ++++++++---------- src/linux/demon/linux_demon.h | 362 ++-- src/linux/demon/linux_demon.mdesk | 37 + src/stap/stap_parse.c | 16 +- src/stap/stap_parse.h | 14 +- src/stap/stap_test.c | 10 +- 9 files changed, 1220 insertions(+), 1254 deletions(-) create mode 100644 obsolete/linux/demon/linux_demon_obsolete.c create mode 100644 src/linux/demon/generated/linux_demon.meta.c create mode 100644 src/linux/demon/generated/linux_demon.meta.h create mode 100644 src/linux/demon/linux_demon.mdesk diff --git a/obsolete/linux/demon/linux_demon_obsolete.c b/obsolete/linux/demon/linux_demon_obsolete.c new file mode 100644 index 00000000..2489e8fe --- /dev/null +++ b/obsolete/linux/demon/linux_demon_obsolete.c @@ -0,0 +1,54 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal LNX_DMN_Process * +lnx_dmn_event_attach(Arena *arena, DMN_EventList *events, pid_t pid) +{ + Temp scratch = scratch_begin(&arena, 1); + + // create process + LNX_DMN_Process *process = lnx_dmn_event_create_process(arena, events, pid, 0, LNX_DMN_CreateProcessFlag_DebugSubprocesses|LNX_DMN_CreateProcessFlag_Rebased); + + // extract threads from /proc/pid/task + { + String8 task_path = str8f(scratch.arena, "/proc/%d/task", pid); + DIR *task_dirp = opendir((char *)task_path.str); + if(task_dirp) + { + for(;;) + { + struct dirent *dirent = readdir(task_dirp); + if(dirent == 0) { break; } + + String8 tid_str = str8_cstring_capped(dirent->d_name, dirent->d_name + NAME_MAX); + if(str8_match(tid_str, str8_lit(".."), 0) || str8_match(tid_str, str8_lit("."), 0)) { continue; } + U64 tid_64 = u64_from_str8(tid_str, 10); + pid_t tid = (pid_t)tid_64; + AssertAlways(tid == tid_64); + + if(tid == pid) { continue; } // main thread was created during create process sequence + lnx_dmn_event_create_thread(arena, events, process, tid); + } + + LNX_RETRY_ON_EINTR(closedir(task_dirp)); + } + } + + // extract modules from r_debug + { + B32 is_64bit = process->ctx->dl_class == ELF_Class_64; + GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->ctx->rdebug_vaddr, lnx_dmn_machine_op_mem_read, &process->fd); + U64 name_space_id = 0; + for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) + { + lnx_dmn_event_load_module(arena, events, process->first_thread, name_space_id, rdebug_n->v.r_map); + name_space_id += 1; + } + } + + // handshake complete + lnx_dmn_push_event_handshake_complete(arena, events, process); + + scratch_end(scratch); + return process; +} \ No newline at end of file diff --git a/src/linux/demon/generated/linux_demon.meta.c b/src/linux/demon/generated/linux_demon.meta.c new file mode 100644 index 00000000..7d499867 --- /dev/null +++ b/src/linux/demon/generated/linux_demon.meta.c @@ -0,0 +1,40 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +C_LINKAGE_BEGIN +U8 lnx_dmn_probe_kind_args_count_table[12] = +{ +0, +2, +2, +2, +3, +2, +3, +2, +2, +3, +3, +3, +}; + +String8 lnx_dmn_probe_kind_string_table[12] = +{ +{0}, +str8_lit_comp("init_start"), +str8_lit_comp("init_complete"), +str8_lit_comp("reloc_start"), +str8_lit_comp("reloc_complete"), +str8_lit_comp("map_start"), +str8_lit_comp("map_complete"), +str8_lit_comp("unmap_start"), +str8_lit_comp("unmap_complete"), +str8_lit_comp("longjmp"), +str8_lit_comp("longjmp_target"), +str8_lit_comp("setjmp"), +}; + +C_LINKAGE_END + diff --git a/src/linux/demon/generated/linux_demon.meta.h b/src/linux/demon/generated/linux_demon.meta.h new file mode 100644 index 00000000..75b995e8 --- /dev/null +++ b/src/linux/demon/generated/linux_demon.meta.h @@ -0,0 +1,32 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +#ifndef LINUX_DEMON_META_H +#define LINUX_DEMON_META_H + +typedef enum LNX_DMN_ProbeKind +{ +LNX_DMN_ProbeKind_Null, +LNX_DMN_ProbeKind_InitStart, +LNX_DMN_ProbeKind_InitComplete, +LNX_DMN_ProbeKind_RelocStart, +LNX_DMN_ProbeKind_RelocComplete, +LNX_DMN_ProbeKind_MapStart, +LNX_DMN_ProbeKind_MapComplete, +LNX_DMN_ProbeKind_UnmapStart, +LNX_DMN_ProbeKind_UnmapComplete, +LNX_DMN_ProbeKind_LongJmp, +LNX_DMN_ProbeKind_LongJmpTarget, +LNX_DMN_ProbeKind_SetJmp, +LNX_DMN_ProbeKind_COUNT, +} LNX_DMN_ProbeKind; + +C_LINKAGE_BEGIN +extern U8 lnx_dmn_probe_kind_args_count_table[12]; +extern String8 lnx_dmn_probe_kind_string_table[12]; + +C_LINKAGE_END + +#endif // LINUX_DEMON_META_H diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index c5ce4360..7bc3d9f6 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -2,13 +2,12 @@ // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// -//~ Includes +//~ rjf: Generated Code -#include -#include -#include +#include "generated/linux_demon.meta.c" //////////////////////////////// +//~ rjf: Memory R/W internal U64 lnx_dmn_size_from_fd(int memory_fd, U64 cap) @@ -101,48 +100,15 @@ lnx_dmn_ptrace_seize(pid_t pid) return LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE)); } -internal String8 -lnx_dmn_exe_path_from_pid(Arena *arena, pid_t pid) -{ - Temp scratch = scratch_begin(&arena, 1); - - String8 exe_link_path = str8f(scratch.arena, "/proc/%d/exe", pid); - String8List parts = {0}; - int readlink_result = 0; - for(S64 r = 0, cap = PATH_MAX; r < 4; cap *= 2, r += 1) - { - U8 *buffer = push_array(arena, U8, cap); - readlink_result = readlink((char *)exe_link_path.str, (char *)buffer, cap); - - if(readlink_result < 0) - { - break; - } - - str8_list_push(scratch.arena, &parts, str8(buffer, readlink_result)); - - if(readlink_result < cap) - { - break; - } - } - - String8 result = str8_list_join(arena, &parts, 0); - scratch_end(scratch); - return result; -} - internal String8 lnx_dmn_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base) { - Temp scratch = scratch_begin(&arena, 1); - String8 dl_path = {0}; - + Temp scratch = scratch_begin(&arena, 1); int maps_fd = LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/maps", pid).str, O_RDONLY)); if(maps_fd != -1) { - // read entire /proc/pid/maps + // rjf: read entire /proc/pid/maps U64 maps_size = lnx_dmn_size_from_fd(maps_fd, MB(1)); U8 *maps_ptr = push_array(scratch.arena, U8, maps_size); U64 read_size = lnx_dmn_read(maps_fd, r1u64(0, maps_size), maps_ptr); @@ -569,213 +535,6 @@ lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 return result; } -internal LNX_DMN_DynamicInfo -lnx_dmn_dynamic_info_from_memory(int memory_fd, ELF_Class elf_class, U64 rebase, U64 dynamic_vaddr) -{ - LNX_DMN_DynamicInfo dynamic_info = {0}; - for(U64 dynamic_cursor = dynamic_vaddr; ; dynamic_cursor += elf_dyn_size_from_class(elf_class)) - { - // rjf: read next dyn entry - ELF_Dyn64 dyn = {0}; - if(elf_read_dyn(lnx_dmn_machine_op_mem_read, &memory_fd, dynamic_cursor, elf_class, &dyn) != MachineOpResult_Ok) { Assert(0 && "unable to read dynamic"); } - - // rjf: break on zero - if(dyn.tag == ELF_DynTag_Null) { break; } - - // extract reuiqred values out of dynamic section - if(dyn.tag == ELF_DynTag_Strtab) - { - dynamic_info.strtab_vaddr = rebase + dyn.val; - } - else if(dyn.tag == ELF_DynTag_Strsz) - { - dynamic_info.strtab_size = dyn.val; - } - else if(dyn.tag == ELF_DynTag_Symtab) - { - dynamic_info.symtab_vaddr = rebase + dyn.val; - } - else if(dyn.tag == ELF_DynTag_Syment) - { - dynamic_info.symtab_entry_size = dyn.val; - } - else if(dyn.tag == ELF_DynTag_Hash) - { - dynamic_info.hash_vaddr = rebase + dyn.val; - } - else if(dyn.tag == ELF_DynTag_GNU_Hash) - { - dynamic_info.gnu_hash_vaddr = rebase + dyn.val; - } - } - return dynamic_info; -} - -internal U64 -lnx_dmn_find_dynamic_phdr(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) -{ - U64 result = max_U64; - - for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize) - { - ELF_Phdr64 phdr = {0}; - if(elf_read_phdr(lnx_dmn_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok) - { - Assert(0 && "unable to read a program header"); - } - - if(phdr.p_type == ELF_PType_Dynamic) - { - result = rebase + phdr.p_vaddr; - break; - } - } - - return result; -} - -internal U64 -lnx_dmn_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase, B32 is_rebased) -{ - Temp scratch = scratch_begin(0, 0); - - U64 rdebug_vaddr = 0; - - // load DL's header - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(lnx_dmn_machine_op_mem_read, &memory_fd, loader_vbase, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); goto exit; } - - U64 rebase = ehdr.e_type == ELF_Type_Dyn ? loader_vbase : 0; - ELF_Class elf_class = ehdr.e_ident[ELF_Identifier_Class]; - - // find dynamic program header - U64 phdr_vaddr = loader_vbase + ehdr.e_phoff; - U64 dynamic_vaddr = lnx_dmn_find_dynamic_phdr(memory_fd, elf_class, rebase, phdr_vaddr, ehdr.e_phentsize, ehdr.e_phentsize); - - // extract necessary info out of dynamic program header - U64 dynamic_info_rebase = is_rebased ? 0 : rebase; - LNX_DMN_DynamicInfo dynamic_info = lnx_dmn_dynamic_info_from_memory(memory_fd, elf_class, dynamic_info_rebase, dynamic_vaddr); - - // extract symbol table count from available options - U64 symbol_count = 0; - if(dynamic_info.hash_vaddr) - { - U64 hash_entry_size = 4; - if(elf_class == ELF_Class_64 && (ehdr.e_machine == ELF_MachineKind_ALPHA || ehdr.e_machine == ELF_MachineKind_S390 || ehdr.e_machine == ELF_MachineKind_S390_OLD)) - { - hash_entry_size = 8; - } - - U64 chain_count = 0; - if(lnx_dmn_read(memory_fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size) - { - symbol_count = chain_count; - } - else - { - Assert(0 && "failed to read hash table's chain count out of HASH"); - } - } - else if(dynamic_info.gnu_hash_vaddr) - { - // GNU_HASH header: u32 nbuckets, u32 symoffset, u32 bloom_size, u32 bloom_shift, - // followed by bloom[bloom_size] (word size = 4 on 32-bit ELF, 8 on 64-bit), - // buckets[nbuckets] (u32), chains[...] (u32, terminated by entry w/ LSB set). - U32 gnu_header[4] = {0}; - if(lnx_dmn_read(memory_fd, r1u64(dynamic_info.gnu_hash_vaddr, dynamic_info.gnu_hash_vaddr + sizeof(gnu_header)), gnu_header) == sizeof(gnu_header)) - { - U32 nbuckets = gnu_header[0]; - U32 symoffset = gnu_header[1]; - U32 bloom_size = gnu_header[2]; - U64 bloom_word = (elf_class == ELF_Class_64) ? 8 : 4; - U64 buckets_vaddr = dynamic_info.gnu_hash_vaddr + sizeof(gnu_header) + bloom_size * bloom_word; - U64 chain_vaddr = buckets_vaddr + (U64)nbuckets * sizeof(U32); - - // find largest symbol index referenced by any bucket - U32 max_bucket = 0; - for(U32 b = 0; b < nbuckets; b += 1) - { - U32 entry = 0; - if(lnx_dmn_read(memory_fd, r1u64(buckets_vaddr + b*sizeof(U32), buckets_vaddr + (b+1)*sizeof(U32)), &entry) != sizeof(U32)) - { - break; - } - if(entry > max_bucket) { max_bucket = entry; } - } - - if(max_bucket < symoffset) - { - // no symbols in hash; total count is symoffset - symbol_count = symoffset; - } - else - { - // walk chain from max_bucket until terminator (entry w/ LSB set) - U64 idx = max_bucket; - for(;;) - { - U32 chain_entry = 0; - U64 entry_vaddr = chain_vaddr + (idx - symoffset) * sizeof(U32); - if(lnx_dmn_read(memory_fd, r1u64(entry_vaddr, entry_vaddr + sizeof(U32)), &chain_entry) != sizeof(U32)) - { - break; - } - if(chain_entry & 1) - { - symbol_count = idx + 1; - break; - } - idx += 1; - if(idx - max_bucket > (1u<<24)) - { - // sanity bound: don't loop forever on a corrupt chain - break; - } - } - } - } - } - - // scan symbol table for the rendezvous symbol - if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count) - { - for EachIndex(symbol_idx, symbol_count) - { - ELF_Sym64 symbol = {0}; - if(elf_read_symbol(lnx_dmn_machine_op_mem_read, &memory_fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol) != MachineOpResult_Ok) - { - Assert(0 && "failed to read symbol table"); - break; - } - - Temp temp = temp_begin(scratch.arena); - - String8 symbol_name = {0}; - if(symbol.st_name < dynamic_info.strtab_size) - { - U64 cap = dynamic_info.strtab_size - symbol.st_name; - symbol_name = lnx_dmn_read_string_capped(temp.arena, memory_fd, dynamic_info.strtab_vaddr + symbol.st_name, cap); - } - - if(str8_match(symbol_name, str8_lit("_r_debug"), 0)) - { - ELF_SymType symbol_type = ELF_ST_TYPE(symbol.st_info); - if(symbol_type == ELF_SymType_Object && symbol.st_size > 0) - { - rdebug_vaddr = rebase + symbol.st_value; - break; - } - } - - temp_end(temp); - } - } - - exit:; - scratch_end(scratch); - return rdebug_vaddr; -} - internal LNX_DMN_ProbeList lnx_dmn_read_probes(Arena *arena, int fd, U64 offset, U64 image_base) { @@ -959,94 +718,6 @@ lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *pa return process; } -internal LNX_DMN_ProcessCtx * -lnx_dmn_process_ctx_alloc(LNX_DMN_Process *process, B32 is_rebased) -{ - LNX_DMN_ProcessCtx *ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; - - ELF_Hdr64 exe_ehdr = lnx_dmn_ehdr_from_pid(process->pid); - LNX_DMN_Auxv auxv = lnx_dmn_auxv_from_pid(process->pid, exe_ehdr.e_ident[ELF_Identifier_Class]); - Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); - U64 rdebug_vaddr = lnx_dmn_rdebug_vaddr_from_memory(process->fd, auxv.base, is_rebased); - U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); - U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - Rng1U64 image_vrange = lnx_dmn_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); - Arena *ctx_arena = arena_alloc(); - - ELF_Class dl_class; - { - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(lnx_dmn_machine_op_mem_read, &process->fd, auxv.base, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); } - dl_class = ehdr.e_ident[ELF_Identifier_Class]; - } - - // query xsave layout - U64 xcr0 = 0; - U64 xsave_size = 0; - X64_XSaveLayout xsave_layout = {0}; - if(arch == Arch_x64) - { - X64_XSave xsave = {0}; - if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, process->pid, (void *)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)) - { - // Linux stores xcr0 bits in fxstate padding, - // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 - xcr0 = *(U64 *)((U8 *)&xsave + 464); - xsave_size = x64_get_xsave_size(); - xsave_layout = x64_get_xsave_layout(xcr0); - } - else { Assert(0 && "failed to get xstate"); } - } - - // gather probes - LNX_DMN_Probe **known_probes = push_array(ctx_arena, LNX_DMN_Probe *, LNX_DMN_ProbeType_Count); - { - Temp scratch = scratch_begin(0, 0); - - String8 dl_path = lnx_dmn_dl_path_from_pid(scratch.arena, process->pid, auxv.base); - int dl_fd = LNX_RETRY_ON_EINTR(open((char *)dl_path.str, O_RDONLY)); - - LNX_DMN_ProbeList probes = {0}; - if(dl_fd >= 0) - { - probes = lnx_dmn_read_probes(ctx_arena, dl_fd, 0, auxv.base); - LNX_RETRY_ON_EINTR(close(dl_fd)); - } - - for EachNode(n, LNX_DMN_ProbeNode, probes.first) - { - LNX_DMN_Probe *p = &n->v; - if(str8_match(p->provider, str8_lit("rtld"), 0)) - { -#define X(_N,_A,_S) if(str8_match(p->name, str8_lit(_S), 0)) { AssertAlways(p->args.count == _A); known_probes[LNX_DMN_ProbeType_##_N] = p; continue ; } - LNX_DMN_Probe_XList -#undef X - } - } - - scratch_end(scratch); - } - - ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; - ctx->arena = arena_alloc(); - ctx->arch = arch; - ctx->rdebug_vaddr = rdebug_vaddr; - ctx->dl_class = dl_class; - ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000); - ctx->probes = known_probes; - ctx->xcr0 = xcr0; - ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); - ctx->xsave_layout = xsave_layout; - - // create main module - LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, process->fd, base_vaddr, auxv.execfn, 1, 1); - - // glibc has a shortcut mapping for the main module - hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); - - return ctx; -} - internal LNX_DMN_Thread * lnx_dmn_thread_alloc(LNX_DMN_Process *process, LNX_DMN_ThreadState thread_state, pid_t tid) { @@ -1183,26 +854,18 @@ lnx_dmn_process_release(LNX_DMN_Process *process) // release the context if(process->ctx) { - lnx_dmn_process_ctx_release(process->ctx); + process->ctx->ref_count -= 1; + if(process->ctx->ref_count == 0) + { + Arena *arena = process->ctx->arena; + lnx_dmn_entity_release((LNX_DMN_Entity *)process->ctx); + } } // release process entity lnx_dmn_entity_release((LNX_DMN_Entity *)process); } -internal void -lnx_dmn_process_ctx_release(LNX_DMN_ProcessCtx *ctx) -{ - Assert(ctx->ref_count > 0); - ctx->ref_count -= 1; - - if(ctx->ref_count == 0) - { - arena_release(ctx->arena); - lnx_dmn_entity_release((LNX_DMN_Entity *)ctx); - } -} - internal void lnx_dmn_thread_release(LNX_DMN_Thread *thread) { @@ -1268,12 +931,11 @@ lnx_dmn_process_ctx_clone(LNX_DMN_Process *new_owner, LNX_DMN_ProcessCtx *ctx) result->xsave_layout = ctx->xsave_layout; // clone probes - result->probes = push_array(result->arena, LNX_DMN_Probe *, LNX_DMN_ProbeType_Count); - for EachIndex(probe_idx, LNX_DMN_ProbeType_Count) + result->probes = push_array(result->arena, LNX_DMN_Probe *, LNX_DMN_ProbeKind_COUNT); + for EachNonZeroEnumVal(LNX_DMN_ProbeKind, k) { - LNX_DMN_Probe *dst = result->probes[probe_idx]; - LNX_DMN_Probe *src = ctx->probes[probe_idx]; - + LNX_DMN_Probe *dst = result->probes[k]; + LNX_DMN_Probe *src = ctx->probes[k]; dst->provider = str8_copy(result->arena, src->provider); dst->name = str8_copy(result->arena, src->name); dst->args_string = str8_copy(result->arena, src->args_string); @@ -1349,12 +1011,6 @@ lnx_dmn_handle_from_process(LNX_DMN_Process *process) return lnx_dmn_handle_from_entity((LNX_DMN_Entity *)process); } -internal DMN_Handle -lnx_dmn_handle_from_process_ctx(LNX_DMN_ProcessCtx *process_ctx) -{ - return lnx_dmn_handle_from_entity((LNX_DMN_Entity *)process_ctx); -} - internal DMN_Handle lnx_dmn_handle_from_thread(LNX_DMN_Thread *thread) { @@ -1389,12 +1045,6 @@ lnx_dmn_process_from_handle(DMN_Handle process_handle) return (LNX_DMN_Process *)lnx_dmn_entity_from_handle(process_handle, LNX_DMN_EntityKind_Process); } -internal LNX_DMN_ProcessCtx * -lnx_dmn_process_ctx_from_handle(DMN_Handle process_ctx_handle) -{ - return (LNX_DMN_ProcessCtx *)lnx_dmn_entity_from_handle(process_ctx_handle, LNX_DMN_EntityKind_ProcessCtx); -} - internal LNX_DMN_Thread * lnx_dmn_thread_from_handle(DMN_Handle thread_handle) { @@ -1407,34 +1057,6 @@ lnx_dmn_module_from_handle(DMN_Handle module_handle) return (LNX_DMN_Module *)lnx_dmn_entity_from_handle(module_handle, LNX_DMN_EntityKind_Module); } -internal void -lnx_dmn_process_trap_probes(LNX_DMN_Process *process) -{ - for EachIndex(i, LNX_DMN_ProbeType_Count) - { - if(process->ctx->probes[i] == 0) { continue; } - - DMN_Trap *trap = push_array(process->ctx->arena, DMN_Trap, 1); - trap->process = lnx_dmn_handle_from_process(process); - trap->vaddr = process->ctx->probes[i]->pc; - trap->id = i; - - LNX_DMN_ActiveTrap *active_trap = lnx_dmn_set_trap(process->ctx->arena, trap); - SLLQueuePush(process->ctx->first_probe_trap, process->ctx->last_probe_trap, active_trap); - - if(BUILD_DEBUG && process->ctx->arch == Arch_x64) - { - Assert(active_trap->swap_bytes.size == 1 && active_trap->swap_bytes.str[0] == 0x90); - } - } -} - -internal void -lnx_dmn_process_untrap_probes(LNX_DMN_Process *process) -{ - NotImplemented; -} - internal U64 lnx_dmn_thread_read_ip(LNX_DMN_Thread *thread) { @@ -1443,14 +1065,6 @@ lnx_dmn_thread_read_ip(LNX_DMN_Thread *thread) return result; } -internal U64 -lnx_dmn_thread_read_sp(LNX_DMN_Thread *thread) -{ - ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); - U64 result = arch_sp_from_reg_block(arch_info, thread->reg_block); - return result; -} - internal void lnx_dmn_thread_write_ip(LNX_DMN_Thread *thread, U64 ip) { @@ -1459,14 +1073,6 @@ lnx_dmn_thread_write_ip(LNX_DMN_Thread *thread, U64 ip) thread->is_reg_block_dirty = 1; } -internal void -lnx_dmn_thread_write_sp(LNX_DMN_Thread *thread, U64 sp) -{ - ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); - arch_reg_block_write_sp(arch_info, thread->reg_block, sp); - thread->is_reg_block_dirty = 1; -} - internal B32 lnx_dmn_thread_read_reg_block(LNX_DMN_Thread *thread) { @@ -1901,6 +1507,9 @@ lnx_dmn_tls_root_vaddr_from_reg_block(int fd, Arch arch, void *reg_block) return result; } +//////////////////////////////// +//~ List Helpers + internal void lnx_dmn_thread_ptr_list_push_node(LNX_DMN_ThreadPtrList *list, LNX_DMN_ThreadPtrNode *n) { @@ -1934,35 +1543,8 @@ lnx_dmn_module_ptr_list_push(Arena *arena, LNX_DMN_ModulePtrList *list, LNX_DMN_ return n; } -internal LNX_DMN_ProcessPtrNode * -lnx_dmn_process_ptr_list_push(Arena *arena, LNX_DMN_ProcessPtrList *list, LNX_DMN_Process *v) -{ - LNX_DMN_ProcessPtrNode *n = push_array(arena, LNX_DMN_ProcessPtrNode, 1); - n->v = v; - SLLQueuePush(list->first, list->last, n); - list->count += 1; - return n; -} - -internal void -lnx_dmn_push_event_create_process(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process) -{ - // push create process event - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_CreateProcess; - e->process = lnx_dmn_handle_from_process(process); - e->arch = process->ctx->arch; - e->code = process->pid; -} - -internal void -lnx_dmn_push_event_exit_process(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_ExitProcess; - e->process = lnx_dmn_handle_from_process(process); - e->code = process->main_thread_exit_code; -} +//////////////////////////////// +//~ Debug Event Pushers internal void lnx_dmn_push_event_create_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread) @@ -1977,23 +1559,13 @@ lnx_dmn_push_event_create_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Th e->tls_root_vaddr = thread->dtv_base_vaddr; } -internal void -lnx_dmn_push_event_exit_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 exit_code) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_ExitThread; - e->process = lnx_dmn_handle_from_process(thread->process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->code = exit_code; -} - internal void lnx_dmn_push_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, LNX_DMN_Module *module) { - // TODO: reporting this module breaks ctrl thread LNX_DMN_Process *process = thread->process; DMN_ModuleInfo *module_info = lnx_dmn_module_info_from_process_module(arena, process->pid, process->fd, module->base_vaddr, module->name_vaddr, module->is_main); String8 module_name = module_info->module_path; + // TODO: reporting this module breaks ctrl thread if(!str8_match(module_name, str8_lit("linux-vdso.so.1"), 0)) { DMN_Event *e = dmn_event_list_push(arena, events); @@ -2018,117 +1590,8 @@ lnx_dmn_push_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Pr e->module = lnx_dmn_handle_from_module(module); } -internal void -lnx_dmn_push_event_handshake_complete(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_HandshakeComplete; - e->process = lnx_dmn_handle_from_process(process); - e->thread = lnx_dmn_handle_from_thread(process->first_thread); - e->arch = process->ctx->arch; -} - -internal void -lnx_dmn_push_event_breakpoint(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 address) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Breakpoint; - e->process = lnx_dmn_handle_from_process(thread->process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = address; - e->address = address; -} - -internal void -lnx_dmn_push_event_single_step(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_SingleStep; - e->process = lnx_dmn_handle_from_process(thread->process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_thread_read_ip(thread); - e->address = e->instruction_pointer; -} - -internal void -lnx_dmn_push_event_exception(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 signo) -{ - local_persist B8 is_repeatable[] = - { - 0, // null - 1, // 1 SIGHUP - 1, // 2 SIGINT - 1, // 3 SIGQUIT - 1, // 4 SIGTRAP - 1, // 5 SIGABRT - 1, // 6 SIGIOT - 1, // 7 SIGBUS - 1, // 8 SIGFPE - 1, // 9 SIGKILL - 1, // 10 SIGUSR1 - 1, // 11 SIGSEGV - 1, // 12 SIGUSR2 - 1, // 13 SIGPIPE - 1, // 14 SIGALRM - 1, // 15 SIGTERM - 1, // 16 SIGTKFLT - 0, // 17 SIGCHLD - 0, // 18 SIGCONT - 1, // 19 SIGSTOP - 1, // 20 SIGTSP - 1, // 21 SIGTTIN - 1, // 22 SIGTTOU - 0, // 23 SIGURG - 1, // 24 SIGXCPU - 1, // 25 SIGXFSZ - 1, // 26 SIGVTALRM - 1, // 27 SIGPROF - 0, // 28 SIGWINCH - 1, // 29 SIGIO - 1, // 30 SIGPWR - 1, // 31 SIGSYS - 1, // 32 SIGUNUSED - }; - - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Exception; - e->process = lnx_dmn_handle_from_process(thread->process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_thread_read_ip(thread); - e->address = e->instruction_pointer; - e->code = signo; - e->exception_repeated = signo < ArrayCount(is_repeatable) ? is_repeatable[signo] : 0; - - if(signo == SIGSEGV) - { - siginfo_t si = {0}; - LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, thread->tid, 0, &si)); - e->address = (U64)si.si_addr; - } -} - -internal void -lnx_dmn_push_event_halt(Arena *arena, DMN_EventList *events) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Halt; -} - -internal void -lnx_dmn_push_event_not_attached(Arena *arena, DMN_EventList *events) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Error; - e->error_kind = DMN_ErrorKind_NotAttached; -} - -internal LNX_DMN_Thread * -lnx_dmn_event_create_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process, pid_t tid) -{ - LNX_DMN_Thread *thread = lnx_dmn_thread_alloc(process, LNX_DMN_ThreadState_Stopped, tid); - lnx_dmn_push_event_create_thread(arena, events, thread); - return thread; -} +//////////////////////////////// +//~ Debug Event internal void lnx_dmn_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code) @@ -2143,7 +1606,13 @@ lnx_dmn_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 ex } // push exit event - lnx_dmn_push_event_exit_thread(arena, events, thread, exit_code); + { + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_ExitThread; + e->process = lnx_dmn_handle_from_process(thread->process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->code = exit_code; + } // release entity lnx_dmn_thread_release(thread); @@ -2151,7 +1620,22 @@ lnx_dmn_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 ex // auto exit process on last thread if(process->thread_count == 0) { - lnx_dmn_event_exit_process(arena, events, process->pid); + // push module events + for EachNode(module, LNX_DMN_Module, process->ctx->first_module) + { + lnx_dmn_push_event_unload_module(arena, events, process, module); + } + + // push process exit event + { + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_ExitProcess; + e->process = lnx_dmn_handle_from_process(process); + e->code = process->main_thread_exit_code; + } + + // release process + lnx_dmn_process_release(process); } } @@ -2160,27 +1644,303 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX { LNX_DMN_Process *process = lnx_dmn_process_alloc(pid, LNX_DMN_ProcessState_Normal, parent_process, !!(flags & LNX_DMN_CreateProcessFlag_DebugSubprocesses), !!(flags & LNX_DMN_CreateProcessFlag_Cow)); + //- rjf: get process context - create if this isn't cloning parent process + LNX_DMN_ProcessCtx *ctx = 0; if(flags & LNX_DMN_CreateProcessFlag_ClonedMemory) { - process->ctx = parent_process->ctx; + ctx = parent_process->ctx; } else { - process->ctx = lnx_dmn_process_ctx_alloc(process, !!(flags & LNX_DMN_CreateProcessFlag_Rebased)); + ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; + + ELF_Hdr64 exe_ehdr = lnx_dmn_ehdr_from_pid(process->pid); + LNX_DMN_Auxv auxv = lnx_dmn_auxv_from_pid(process->pid, exe_ehdr.e_ident[ELF_Identifier_Class]); + Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); + U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); + U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + Rng1U64 image_vrange = lnx_dmn_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); + Arena *ctx_arena = arena_alloc(); + + ELF_Hdr64 dl_ehdr = {0}; + elf_read_ehdr(lnx_dmn_machine_op_mem_read, &process->fd, auxv.base, &dl_ehdr); + ELF_Class dl_class = dl_ehdr.e_ident[ELF_Identifier_Class]; + + // rjf: compute rdebug vaddr + U64 rdebug_vaddr = 0; + { + Temp scratch = scratch_begin(0, 0); + + // load DL's header + ELF_Hdr64 ehdr = {0}; + elf_read_ehdr(lnx_dmn_machine_op_mem_read, &process->fd, auxv.base, &ehdr); + + U64 rebase = dl_ehdr.e_type == ELF_Type_Dyn ? auxv.base : 0; + ELF_Class elf_class = ehdr.e_ident[ELF_Identifier_Class]; + + // rjf: find dynamic program header + U64 dynamic_vaddr = 0; + { + U64 phdr_vaddr = auxv.base + dl_ehdr.e_phoff; + for(U64 ph_cursor = phdr_vaddr, ph_opl = (phdr_vaddr + ehdr.e_phentsize * ehdr.e_phentsize); ph_cursor < ph_opl; ph_cursor += ehdr.e_phentsize) + { + ELF_Phdr64 phdr = {0}; + elf_read_phdr(lnx_dmn_machine_op_mem_read, &process->fd, ph_cursor, elf_class, &phdr); + if(phdr.p_type == ELF_PType_Dynamic) + { + dynamic_vaddr = rebase + phdr.p_vaddr; + break; + } + } + } + + // rjf: get dynamic info for this module + // extract necessary info out of dynamic program header + LNX_DMN_DynamicInfo dynamic_info = {0}; + { + B32 is_rebased = !!(flags & LNX_DMN_CreateProcessFlag_Rebased); + U64 dynamic_info_rebase = is_rebased ? 0 : rebase; + for(U64 dynamic_cursor = dynamic_vaddr; ; dynamic_cursor += elf_dyn_size_from_class(elf_class)) + { + // rjf: read next dyn entry + ELF_Dyn64 dyn = {0}; + if(elf_read_dyn(lnx_dmn_machine_op_mem_read, &process->fd, dynamic_cursor, elf_class, &dyn) != MachineOpResult_Ok) { Assert(0 && "unable to read dynamic"); } + + // rjf: break on zero + if(dyn.tag == ELF_DynTag_Null) { break; } + + // rjf: extract info from dynamic section + switch(dyn.tag) + { + default:{}break; + case ELF_DynTag_Strtab: {dynamic_info.strtab_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_Strsz: {dynamic_info.strtab_size = dyn.val;}break; + case ELF_DynTag_Symtab: {dynamic_info.symtab_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_Syment: {dynamic_info.symtab_entry_size = dyn.val;}break; + case ELF_DynTag_Hash: {dynamic_info.hash_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_GNU_Hash: {dynamic_info.gnu_hash_vaddr = dynamic_info_rebase + dyn.val;}break; + } + } + } + + // extract symbol table count from available options + U64 symbol_count = 0; + if(dynamic_info.hash_vaddr) + { + U64 hash_entry_size = 4; + if(elf_class == ELF_Class_64 && (ehdr.e_machine == ELF_MachineKind_ALPHA || ehdr.e_machine == ELF_MachineKind_S390 || ehdr.e_machine == ELF_MachineKind_S390_OLD)) + { + hash_entry_size = 8; + } + + U64 chain_count = 0; + if(lnx_dmn_read(process->fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size) + { + symbol_count = chain_count; + } + else + { + Assert(0 && "failed to read hash table's chain count out of HASH"); + } + } + else if(dynamic_info.gnu_hash_vaddr) + { + // GNU_HASH header: u32 nbuckets, u32 symoffset, u32 bloom_size, u32 bloom_shift, + // followed by bloom[bloom_size] (word size = 4 on 32-bit ELF, 8 on 64-bit), + // buckets[nbuckets] (u32), chains[...] (u32, terminated by entry w/ LSB set). + U32 gnu_header[4] = {0}; + if(lnx_dmn_read(process->fd, r1u64(dynamic_info.gnu_hash_vaddr, dynamic_info.gnu_hash_vaddr + sizeof(gnu_header)), gnu_header) == sizeof(gnu_header)) + { + U32 nbuckets = gnu_header[0]; + U32 symoffset = gnu_header[1]; + U32 bloom_size = gnu_header[2]; + U64 bloom_word = (elf_class == ELF_Class_64) ? 8 : 4; + U64 buckets_vaddr = dynamic_info.gnu_hash_vaddr + sizeof(gnu_header) + bloom_size * bloom_word; + U64 chain_vaddr = buckets_vaddr + (U64)nbuckets * sizeof(U32); + + // find largest symbol index referenced by any bucket + U32 max_bucket = 0; + for(U32 b = 0; b < nbuckets; b += 1) + { + U32 entry = 0; + if(lnx_dmn_read(process->fd, r1u64(buckets_vaddr + b*sizeof(U32), buckets_vaddr + (b+1)*sizeof(U32)), &entry) != sizeof(U32)) + { + break; + } + if(entry > max_bucket) { max_bucket = entry; } + } + + if(max_bucket < symoffset) + { + // no symbols in hash; total count is symoffset + symbol_count = symoffset; + } + else + { + // walk chain from max_bucket until terminator (entry w/ LSB set) + U64 idx = max_bucket; + for(;;) + { + U32 chain_entry = 0; + U64 entry_vaddr = chain_vaddr + (idx - symoffset) * sizeof(U32); + if(lnx_dmn_read(process->fd, r1u64(entry_vaddr, entry_vaddr + sizeof(U32)), &chain_entry) != sizeof(U32)) + { + break; + } + if(chain_entry & 1) + { + symbol_count = idx + 1; + break; + } + idx += 1; + if(idx - max_bucket > (1u<<24)) + { + // sanity bound: don't loop forever on a corrupt chain + break; + } + } + } + } + } + + // scan symbol table for the rendezvous symbol + if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count) + { + for EachIndex(symbol_idx, symbol_count) + { + ELF_Sym64 symbol = {0}; + if(elf_read_symbol(lnx_dmn_machine_op_mem_read, &process->fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol) != MachineOpResult_Ok) + { + Assert(0 && "failed to read symbol table"); + break; + } + + Temp temp = temp_begin(scratch.arena); + + String8 symbol_name = {0}; + if(symbol.st_name < dynamic_info.strtab_size) + { + U64 cap = dynamic_info.strtab_size - symbol.st_name; + symbol_name = lnx_dmn_read_string_capped(temp.arena, process->fd, dynamic_info.strtab_vaddr + symbol.st_name, cap); + } + + if(str8_match(symbol_name, str8_lit("_r_debug"), 0)) + { + ELF_SymType symbol_type = ELF_ST_TYPE(symbol.st_info); + if(symbol_type == ELF_SymType_Object && symbol.st_size > 0) + { + rdebug_vaddr = rebase + symbol.st_value; + break; + } + } + + temp_end(temp); + } + } + + scratch_end(scratch); + } + + // query xsave layout + U64 xcr0 = 0; + U64 xsave_size = 0; + X64_XSaveLayout xsave_layout = {0}; + if(arch == Arch_x64) + { + X64_XSave xsave = {0}; + if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, process->pid, (void *)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)) + { + // Linux stores xcr0 bits in fxstate padding, + // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 + xcr0 = *(U64 *)((U8 *)&xsave + 464); + xsave_size = x64_get_xsave_size(); + xsave_layout = x64_get_xsave_layout(xcr0); + } + else { Assert(0 && "failed to get xstate"); } + } + + // gather probes + LNX_DMN_Probe **known_probes = push_array(ctx_arena, LNX_DMN_Probe *, LNX_DMN_ProbeKind_COUNT); + { + Temp scratch = scratch_begin(0, 0); + + String8 dl_path = lnx_dmn_dl_path_from_pid(scratch.arena, process->pid, auxv.base); + int dl_fd = LNX_RETRY_ON_EINTR(open((char *)dl_path.str, O_RDONLY)); + + LNX_DMN_ProbeList probes = {0}; + if(dl_fd >= 0) + { + probes = lnx_dmn_read_probes(ctx_arena, dl_fd, 0, auxv.base); + LNX_RETRY_ON_EINTR(close(dl_fd)); + } + + for EachNode(n, LNX_DMN_ProbeNode, probes.first) + { + LNX_DMN_Probe *p = &n->v; + if(str8_match(p->provider, s("rtld"), 0)) + { + for EachNonZeroEnumVal(LNX_DMN_ProbeKind, k) + { + if(str8_match(p->name, lnx_dmn_probe_kind_string_table[k], 0)) + { + known_probes[k] = p; + break; + } + } + } + } + + scratch_end(scratch); + } + + ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; + ctx->arena = arena_alloc(); + ctx->arch = arch; + ctx->rdebug_vaddr = rdebug_vaddr; + ctx->dl_class = dl_class; + ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000); + ctx->probes = known_probes; + ctx->xcr0 = xcr0; + ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); + ctx->xsave_layout = xsave_layout; + + // create main module + LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, process->fd, base_vaddr, auxv.execfn, 1, 1); + + // glibc has a shortcut mapping for the main module + hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); } + + //- rjf: set process' context + process->ctx = ctx; process->ctx->ref_count += 1; - // install probes in a process that does not have a cloned memory + //- rjf: install probes - only in processes without cloned memory if(!(flags & LNX_DMN_CreateProcessFlag_ClonedMemory)) { - lnx_dmn_process_trap_probes(process); + for EachNonZeroEnumVal(LNX_DMN_ProbeKind, k) + { + if(process->ctx->probes[k] == 0) { continue; } + DMN_Trap *trap = push_array(process->ctx->arena, DMN_Trap, 1); + trap->process = lnx_dmn_handle_from_process(process); + trap->vaddr = process->ctx->probes[k]->pc; + trap->id = k; + LNX_DMN_ActiveTrap *active_trap = lnx_dmn_set_trap(process->ctx->arena, trap); + SLLQueuePush(process->ctx->first_probe_trap, process->ctx->last_probe_trap, active_trap); + } } // create main thread lnx_dmn_thread_alloc(process, LNX_DMN_ThreadState_Stopped, pid); // push events - lnx_dmn_push_event_create_process(arena, events, process); + { + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_CreateProcess; + e->process = lnx_dmn_handle_from_process(process); + e->arch = process->ctx->arch; + e->code = process->pid; + } for EachNode(thread, LNX_DMN_Thread, process->first_thread) { lnx_dmn_push_event_create_thread(arena, events, thread); @@ -2189,28 +1949,15 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX { lnx_dmn_push_event_load_module(arena, events, process->first_thread, module); } - lnx_dmn_push_event_handshake_complete(arena, events, process); - - return process; -} - -internal void -lnx_dmn_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid) -{ - LNX_DMN_Process *process = lnx_dmn_process_from_pid(pid); - AssertAlways(process->thread_count == 0); - - // push module events - for EachNode(module, LNX_DMN_Module, process->ctx->first_module) { - lnx_dmn_push_event_unload_module(arena, events, process, module); + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_HandshakeComplete; + e->process = lnx_dmn_handle_from_process(process); + e->thread = lnx_dmn_handle_from_thread(process->first_thread); + e->arch = process->ctx->arch; } - // push process exit event - lnx_dmn_push_event_exit_process(arena, events, process); - - // release process - lnx_dmn_process_release(process); + return process; } internal void @@ -2243,326 +1990,38 @@ lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *t } } -internal void -lnx_dmn_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process, U64 rdebug_vaddr) -{ - Temp scratch = scratch_begin(&arena, 1); - - LNX_DMN_ProcessCtx *ctx = process->ctx; - - // flag every module as inactive - for EachNode(module, LNX_DMN_Module, ctx->first_module) - { - module->is_live = 0; - } - - // mark live modules - B32 is_64bit = ctx->dl_class == ELF_Class_64; - GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, lnx_dmn_machine_op_mem_read, &process->fd); - for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) - { - GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, lnx_dmn_machine_op_mem_read, &process->fd); - for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first) - { - LNX_DMN_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, link_map_n->v.addr_vaddr); - module->is_live = 1; - } - } - - // collect unloaded modules - LNX_DMN_ModulePtrList to_release = {0}; - for EachNode(module, LNX_DMN_Module, ctx->first_module) - { - if(!module->is_live) - { - lnx_dmn_module_ptr_list_push(scratch.arena, &to_release, module); - } - } - - // clone process context - if(to_release.count > 0) - { - if(process->is_cow) - { - process->is_cow = 0; - process->ctx = lnx_dmn_process_ctx_clone(process, process->ctx); - } - } - - // push events and clean up unloaded modules - for EachNode(module_n, LNX_DMN_ModulePtrNode, to_release.first) - { - lnx_dmn_push_event_unload_module(arena, events, process, module_n->v); - lnx_dmn_module_release(process->ctx, module_n->v); - } - - scratch_end(scratch); -} - -internal void -lnx_dmn_event_breakpoint(Arena *arena, DMN_EventList *events, LNX_DMN_ActiveTrap *user_traps, pid_t tid) -{ - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(tid); - LNX_DMN_Process *process = thread->process; - U64 ip = lnx_dmn_thread_read_ip(thread); - - // is this user trap? - LNX_DMN_ActiveTrap *hit_user_trap = 0; - { - DMN_Handle process_handle = lnx_dmn_handle_from_process(process); - for EachNode(active_trap, LNX_DMN_ActiveTrap, user_traps) - { - if(MemoryCompare(&active_trap->trap->process, &process_handle, sizeof(DMN_Handle)) == 0) - { - if(active_trap->trap->vaddr == ip-1) - { - hit_user_trap = active_trap; - break; - } - } - } - } - - // is this a probe trap? - LNX_DMN_ProbeType probe_type = LNX_DMN_ProbeType_Null; - if(hit_user_trap == 0) - { - for EachNode(active_trap, LNX_DMN_ActiveTrap, process->ctx->first_probe_trap) - { - if(active_trap->trap->vaddr == ip-1) - { - probe_type = active_trap->trap->id; - break; - } - } - } - - if(probe_type == LNX_DMN_ProbeType_InitComplete) - { - B32 is_init_completed = 0; - - LNX_DMN_Probe *probe = process->ctx->probes[LNX_DMN_ProbeType_InitComplete]; - U64 name_space_id = 0, rdebug_addr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id)) { goto init_complete_exit; } - if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &rdebug_addr)) { goto init_complete_exit; } - - GNU_RDebugInfo64 rdebug = {0}; - if(gnu_read_r_debug(lnx_dmn_machine_op_mem_read, &process->fd, rdebug_addr, process->ctx->arch, &rdebug) != MachineOpResult_Ok) { goto init_complete_exit; } - if(rdebug.r_version < 1) { goto init_complete_exit; } - - lnx_dmn_event_load_module(arena, events, thread, name_space_id, rdebug.r_map); - - is_init_completed = 1; - init_complete_exit:; - AssertAlways(is_init_completed); - } - else if(probe_type == LNX_DMN_ProbeType_RelocComplete) - { - B32 is_reloc_completed = 0; - - LNX_DMN_Probe *probe = process->ctx->probes[LNX_DMN_ProbeType_RelocComplete]; - U64 name_space_id = 0, new_link_map_addr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id)) { goto reloc_complete_exit; } - if(!stap_read_arg_u(probe->args.v[2], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &new_link_map_addr)) { goto reloc_complete_exit; } - - lnx_dmn_event_load_module(arena, events, thread, name_space_id, new_link_map_addr); - - is_reloc_completed = 1; - reloc_complete_exit:; - AssertAlways(is_reloc_completed); - } - else if(probe_type == LNX_DMN_ProbeType_UnmapComplete) - { - B32 is_unmap_completed = 0; - - LNX_DMN_Probe *probe = process->ctx->probes[LNX_DMN_ProbeType_UnmapComplete]; - U64 name_space_id = 0, rdebug_vaddr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id)) { goto unmap_complete_exit; } - if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &rdebug_vaddr)) { goto unmap_complete_exit; } - - lnx_dmn_event_unload_module(arena, events, process, rdebug_vaddr); - - is_unmap_completed = 1; - unmap_complete_exit:; - AssertAlways(is_unmap_completed); - } - - if(probe_type == LNX_DMN_ProbeType_Null) - { - // rollback IP on user traps - if(hit_user_trap) - { - U64 ip = lnx_dmn_thread_read_ip(thread); - lnx_dmn_thread_write_ip(thread, ip - 1); - } - - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Breakpoint; - e->process = lnx_dmn_handle_from_process(process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_thread_read_ip(thread); - } -} - -internal void -lnx_dmn_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid) -{ - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(tid); - - B32 is_valid = 1; - U64 address = 0; - switch(thread->process->ctx->arch) - { - default:{}break; - case Arch_x64: - { - X64_RegBlock *regs_x64 = thread->reg_block; - if(regs_x64->dr6 & X64_DebugStatusFlag_B0) - { - address = regs_x64->dr0; - } - else if(regs_x64->dr6 & X64_DebugStatusFlag_B1) - { - address = regs_x64->dr1; - } - else if(regs_x64->dr6 & X64_DebugStatusFlag_B2) - { - address = regs_x64->dr2; - } - else if(regs_x64->dr6 & X64_DebugStatusFlag_B3) - { - address = regs_x64->dr3; - } - else - { - is_valid = 0; - } - }break; - } - - if(is_valid) - { - lnx_dmn_push_event_breakpoint(arena, events, thread, address); - } -} - -internal void -lnx_dmn_event_halt(Arena *arena, DMN_EventList *events) -{ - lnx_dmn_push_event_halt(arena, events); -} - -internal void -lnx_dmn_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid) -{ - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(tid); - - // clear single step flag - lnx_dmn_set_single_step_flag(thread, 0); - - // push event - lnx_dmn_push_event_single_step(arena, events, thread); -} - -internal void -lnx_dmn_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo) -{ - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(tid); - - thread->pass_through_signal = 1; - thread->pass_through_signo = signo; - - lnx_dmn_push_event_exception(arena, events, thread, signo); -} - -internal LNX_DMN_Process * -lnx_dmn_event_attach(Arena *arena, DMN_EventList *events, pid_t pid) -{ - Temp scratch = scratch_begin(&arena, 1); - - // create process - LNX_DMN_Process *process = lnx_dmn_event_create_process(arena, events, pid, 0, LNX_DMN_CreateProcessFlag_DebugSubprocesses|LNX_DMN_CreateProcessFlag_Rebased); - - // extract threads from /proc/pid/task - { - String8 task_path = str8f(scratch.arena, "/proc/%d/task", pid); - DIR *task_dirp = opendir((char *)task_path.str); - if(task_dirp) - { - for(;;) - { - struct dirent *dirent = readdir(task_dirp); - if(dirent == 0) { break; } - - String8 tid_str = str8_cstring_capped(dirent->d_name, dirent->d_name + NAME_MAX); - if(str8_match(tid_str, str8_lit(".."), 0) || str8_match(tid_str, str8_lit("."), 0)) { continue; } - U64 tid_64 = u64_from_str8(tid_str, 10); - pid_t tid = (pid_t)tid_64; - AssertAlways(tid == tid_64); - - if(tid == pid) { continue; } // main thread was created during create process sequence - lnx_dmn_event_create_thread(arena, events, process, tid); - } - - LNX_RETRY_ON_EINTR(closedir(task_dirp)); - } - } - - // extract modules from r_debug - { - B32 is_64bit = process->ctx->dl_class == ELF_Class_64; - GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->ctx->rdebug_vaddr, lnx_dmn_machine_op_mem_read, &process->fd); - U64 name_space_id = 0; - for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) - { - lnx_dmn_event_load_module(arena, events, process->first_thread, name_space_id, rdebug_n->v.r_map); - name_space_id += 1; - } - } - - // handshake complete - lnx_dmn_push_event_handshake_complete(arena, events, process); - - scratch_end(scratch); - return process; -} - //////////////////////////////// //~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) internal void dmn_init(void) { - if(lnx_dmn_state == 0) + Arena *arena = arena_alloc(); + lnx_dmn_state = push_array(arena, LNX_DMN_State, 1); + lnx_dmn_state->arena = arena; + lnx_dmn_state->access_mutex = mutex_alloc(); + lnx_dmn_state->entities_arena = arena_alloc(.reserve_size = GB(32), .commit_size = KB(64), .flags = ArenaFlag_NoChain); + lnx_dmn_state->entities_base = push_array(lnx_dmn_state->entities_arena, LNX_DMN_Entity, 0); + lnx_dmn_state->tid_ht = hash_table_init(lnx_dmn_state->arena, 0x2000); + lnx_dmn_state->pid_ht = hash_table_init(lnx_dmn_state->arena, 0x400); + lnx_dmn_state->halter_mutex = mutex_alloc(); + lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Null); + + // find offsets of TLS index and TLS offset in the link_map struct + // + // TODO: assuming that target is using same libc version as debugger { - local_persist LNX_DMN_State state; - lnx_dmn_state = &state; - - lnx_dmn_state->arena = arena_alloc(); - lnx_dmn_state->access_mutex = mutex_alloc(); - lnx_dmn_state->entities_arena = arena_alloc(.reserve_size = GB(32), .commit_size = KB(64), .flags = ArenaFlag_NoChain); - lnx_dmn_state->entities_base = push_array(lnx_dmn_state->entities_arena, LNX_DMN_Entity, 0); - lnx_dmn_state->tid_ht = hash_table_init(lnx_dmn_state->arena, 0x2000); - lnx_dmn_state->pid_ht = hash_table_init(lnx_dmn_state->arena, 0x400); - lnx_dmn_state->halter_mutex = mutex_alloc(); - lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Null); - - // find offsets of TLS index and TLS offset in the link_map struct - // - // TODO: assuming that target is using same libc version as debugger + LNX_DMN_DbDesc *tls_modid_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_modid"); + LNX_DMN_DbDesc *tls_offset_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_offset"); + if(tls_modid_desc && tls_offset_desc) { - LNX_DMN_DbDesc *tls_modid_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_modid"); - LNX_DMN_DbDesc *tls_offset_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_offset"); - if(tls_modid_desc && tls_offset_desc) + if(tls_modid_desc->bit_size <= 64 && tls_offset_desc->bit_size <= 64) { - if(tls_modid_desc->bit_size <= 64 && tls_offset_desc->bit_size <= 64) - { - lnx_dmn_state->tls_modid_desc = *tls_modid_desc; - lnx_dmn_state->tls_offset_desc = *tls_offset_desc; - lnx_dmn_state->is_tls_detected = 1; - } - else { Assert(0 && "invalid TLS desc"); } + lnx_dmn_state->tls_modid_desc = *tls_modid_desc; + lnx_dmn_state->tls_offset_desc = *tls_offset_desc; + lnx_dmn_state->is_tls_detected = 1; } + else { Assert(0 && "invalid TLS desc"); } } } } @@ -2601,8 +2060,8 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) { Temp scratch = scratch_begin(0, 0); - // setup target command line - U64 argc = params->cmd_line.node_count + 1; + //- rjf: setup target command line + U64 argc = params->cmd_line.node_count + 1; char **argv = push_array(scratch.arena, char *, argc); { U64 idx = 0; @@ -2613,7 +2072,7 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) } } - // setup target environment + //- rjf: setup target environment U64 envc = lnx_state.default_env_count + params->env.node_count + 1; char **envp = push_array(scratch.arena, char *, envc); { @@ -2629,13 +2088,13 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) } } - // create zero-terminated work directory path + //- rjf: create zero-terminated work directory path char *work_dir_path = (char *)str8_copy(scratch.arena, params->path).str; - // fork process + //- rjf: fork process pid_t pid = fork(); - // child process + //- rjf: child process if(pid == 0) { // wait for seize @@ -2650,7 +2109,8 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) child_exit:; exit(0); } - // parent process + + //- rjf: parent process else if(pid > 0) { // try to seize child process @@ -2725,13 +2185,26 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) Temp scratch = scratch_begin(&arena, 1); DMN_EventList events = {0}; + // TODO(rjf): wait... we can't take the halter mutex here??? + // callers of dmn_halt need to be able to call *while* a run + // is happening! + // + // TODO(rjf): *after further reading* oh, I see, it is dropped + // before we do a wait_pid... we should try to clean this up? + // rather than a mutex dance that spans this whole path... + // mutex_take(lnx_dmn_state->halter_mutex); - // wait for signals from the running threads + ////////////////////////////// + //- rjf: wait for signals from the running threads + // if(lnx_dmn_state->process_count > 0) { - // write traps to memory - LNX_DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0; + //////////////////////////// + //- rjf: write all traps + // + LNX_DMN_ActiveTrap *active_trap_first = 0; + LNX_DMN_ActiveTrap *active_trap_last = 0; { HashTable *process_ht = hash_table_init(scratch.arena, lnx_dmn_state->process_count); for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) @@ -2769,7 +2242,9 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } - // enable single stepping + //////////////////////////// + //- rjf: enable single stepping + // if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) { LNX_DMN_Thread *single_step_thread = lnx_dmn_thread_from_handle(ctrls->single_step_thread); @@ -2777,13 +2252,11 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { lnx_dmn_set_single_step_flag(single_step_thread, 1); } - else - { - Assert(0 && "invalid single_step_thread handle"); - } } - // schedule threads to run + //////////////////////////// + //- rjf: schedule threads to run + // LNX_DMN_ThreadPtrList running_threads = {0}; { for EachNode(process, LNX_DMN_Process, lnx_dmn_state->first_process) @@ -2802,6 +2275,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } + //- rjf: schedule threads in this process for EachNode(thread, LNX_DMN_Thread, process->first_thread) { //- rjf: determine if this thread is frozen @@ -2830,6 +2304,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) is_frozen ^= 1; } } + // rjf: single-step? freeze if not the single-step thread. else { @@ -2887,122 +2362,131 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } - // hash running threads tids + //////////////////////////// + //- rjf: hash running threads tids + // HashTable *running_threads_ht = hash_table_init(scratch.arena, running_threads.count * 2); for EachNode(n, LNX_DMN_ThreadPtrNode, running_threads.first) { hash_table_push_u64_raw(scratch.arena, running_threads_ht, n->v->tid, n); } - B32 is_halt_done = 0; + //////////////////////////// + //- rjf: wait the next signal & generate events + // + B32 is_halt_done = 0; LNX_DMN_ThreadPtrList stopped_threads = {0}; do { - // wait for a signal - int status = 0; - pid_t wait_id = 0; + ////////////////////////// + //- rjf: wait for a signal + // + int status = 0; + pid_t wait_id = 0; { mutex_drop(lnx_dmn_state->halter_mutex); - for(;;) - { - wait_id = LNX_RETRY_ON_EINTR(waitpid(-1, &status, __WALL|__WNOTHREAD)); - if(wait_id == -1) { InvalidPath; } // TODO: graceful exit - break; - } + wait_id = LNX_RETRY_ON_EINTR(waitpid(-1, &status, __WALL|__WNOTHREAD)); mutex_take(lnx_dmn_state->halter_mutex); } - // unpack status + ////////////////////////// + //- rjf: unpack wait_id + // + LNX_DMN_Process *process = lnx_dmn_process_from_pid(wait_id); + LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); + + ////////////////////////// + //- rjf: unpack signal's status + // int wifexited = WIFEXITED(status); int wifsignaled = WIFSIGNALED(status); int wifstopped = WIFSTOPPED(status); int wstopsig = WSTOPSIG(status); int event_code = (status >> 16); - // intercept initing processes + ////////////////////////// + //- rjf: intercept initing processes + // + if(process && process->state != LNX_DMN_ProcessState_Normal) { - LNX_DMN_Process *process = lnx_dmn_process_from_pid(wait_id); - - if(process && process->state != LNX_DMN_ProcessState_Normal) + switch(process->state) { - switch(process->state) + case LNX_DMN_ProcessState_Null: + case LNX_DMN_ProcessState_Normal: { - case LNX_DMN_ProcessState_Null: - case LNX_DMN_ProcessState_Normal: - { - InvalidPath; - } break; - case LNX_DMN_ProcessState_Launch: - { - if(wstopsig == SIGSTOP) - { - if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, process->pid, 0, 0)) >= 0) - { - process->state = LNX_DMN_ProcessState_WaitForExec; - goto wait_for_signal; - } - else { Assert(0 && "failed to resume tracee"); } - } - // else { Assert(0 && "unexpected signal"); } - } break; - case LNX_DMN_ProcessState_WaitForExec: - { - if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) - { - LNX_DMN_CreateProcessFlags create_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; - lnx_dmn_process_release(process); - lnx_dmn_event_create_process(arena, &events, wait_id, 0, create_flags); - goto wait_for_signal; - } - //else { Assert(0 && "unexpected signal"); } - } break; - case LNX_DMN_ProcessState_ExecFailedDoExit: - { - if(wifexited) - { - lnx_dmn_process_release(process); - goto wait_for_signal; - } - else { Assert(0 && "unexpected signal"); } - } break; - case LNX_DMN_ProcessState_Attach: - { - if(wstopsig == SIGSTOP) - { - LNX_DMN_CreateProcessFlags create_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; - lnx_dmn_process_release(process); - lnx_dmn_event_create_process(arena, &events, wait_id, 0, create_flags); - goto wait_for_signal; - } - else { Assert(0 && "unexpected signal"); } - } break; - } - - // shutdown process + }break; + case LNX_DMN_ProcessState_Launch: { - if(LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)) >= 0) + if(wstopsig == SIGSTOP) { - process->state = LNX_DMN_ProcessState_ExecFailedDoExit; + if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, process->pid, 0, 0)) >= 0) + { + process->state = LNX_DMN_ProcessState_WaitForExec; + goto wait_for_signal; + } + else { Assert(0 && "failed to resume tracee"); } } - else + // else { Assert(0 && "unexpected signal"); } + } break; + case LNX_DMN_ProcessState_WaitForExec: + { + if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) { - //Assert(0 && "failed to send kill signal to the tracee"); + LNX_DMN_CreateProcessFlags create_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; lnx_dmn_process_release(process); + lnx_dmn_event_create_process(arena, &events, wait_id, 0, create_flags); + goto wait_for_signal; } - } - - wait_for_signal:; - continue; + //else { Assert(0 && "unexpected signal"); } + } break; + case LNX_DMN_ProcessState_ExecFailedDoExit: + { + if(wifexited) + { + lnx_dmn_process_release(process); + goto wait_for_signal; + } + else { Assert(0 && "unexpected signal"); } + } break; + case LNX_DMN_ProcessState_Attach: + { + if(wstopsig == SIGSTOP) + { + LNX_DMN_CreateProcessFlags create_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; + lnx_dmn_process_release(process); + lnx_dmn_event_create_process(arena, &events, wait_id, 0, create_flags); + goto wait_for_signal; + } + else { Assert(0 && "unexpected signal"); } + } break; } + + // shutdown process + { + if(LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)) >= 0) + { + process->state = LNX_DMN_ProcessState_ExecFailedDoExit; + } + else + { + //Assert(0 && "failed to send kill signal to the tracee"); + lnx_dmn_process_release(process); + } + } + + wait_for_signal:; + continue; } - if(wifstopped || wifsignaled || wifexited) + ////////////////////////// + //- rjf: any thread stop -> interrupt all threads + // + if(wifexited || wifsignaled || wifstopped) { LNX_DMN_ThreadPtrNode *thread_n = hash_table_search_u64_raw(running_threads_ht, wait_id); if(thread_n) { LNX_DMN_Thread *thread = thread_n->v; - AssertAlways(thread->state == LNX_DMN_ThreadState_Running); // remove mapping hash_table_purge_u64(running_threads_ht, thread->tid); @@ -3033,52 +2517,271 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } - // normal child exit via _exit or exit() - if(wifexited) + ////////////////////////// + //- rjf: wifexited / wifsignaled -> thread exits + // + // wifexited -> normal process exit via _exit or exit() + // wifsignaled -> exit because child did not handle a signal + // + if(wifexited || wifsignaled) { - lnx_dmn_event_exit_thread(arena, &events, wait_id, WEXITSTATUS(status)); + U64 exit_code = wifexited ? WEXITSTATUS(status) : WTERMSIG(status); + lnx_dmn_event_exit_thread(arena, &events, wait_id, exit_code); } - // exit because child did not handle a signal - else if(wifsignaled) - { - lnx_dmn_event_exit_thread(arena, &events, wait_id, WTERMSIG(status)); - } - // thread or group stop + ////////////////////////// + //- rjf: wifstopped -> thread or group stop + // else if(wifstopped) { - // read thread registers + //////////////////////// + //- rjf: read stop thread's new registers + // + if(thread && thread->state != LNX_DMN_ThreadState_PendingCreation) { - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); - if(thread && thread->state != LNX_DMN_ThreadState_PendingCreation) - { - thread->is_reg_block_dirty = !lnx_dmn_thread_read_reg_block(thread); - Assert(!thread->is_reg_block_dirty); - } + thread->is_reg_block_dirty = !lnx_dmn_thread_read_reg_block(thread); } + //////////////////////// + //- rjf: trap signal + // if(wstopsig == SIGTRAP) { switch(event_code) { + //////////////////// + //- rjf: 0 -> general trap path + // case 0: { - // translate signal code to DEMON event + // translate signal code to event siginfo_t siginfo = {0}; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0) { switch(siginfo.si_code) { - case SI_KERNEL: { lnx_dmn_event_breakpoint(arena, &events, active_trap_first, wait_id); } break; - case TRAP_BRKPT: { lnx_dmn_event_breakpoint(arena, &events, active_trap_first, wait_id); } break; - case TRAP_TRACE: { lnx_dmn_event_single_step(arena, &events, wait_id); } break; - case TRAP_HWBKPT: { lnx_dmn_event_data_breakpoint(arena, &events, wait_id); } break; + //- rjf: regular traps or breakpoints + case SI_KERNEL: + case TRAP_BRKPT: + { + U64 ip = lnx_dmn_thread_read_ip(thread); + + // is this user trap? + LNX_DMN_ActiveTrap *hit_user_trap = 0; + { + DMN_Handle process_handle = lnx_dmn_handle_from_process(process); + for EachNode(active_trap, LNX_DMN_ActiveTrap, active_trap_first) + { + if(MemoryCompare(&active_trap->trap->process, &process_handle, sizeof(DMN_Handle)) == 0) + { + if(active_trap->trap->vaddr == ip-1) + { + hit_user_trap = active_trap; + break; + } + } + } + } + + // is this a probe trap? + LNX_DMN_ProbeKind probe_kind = LNX_DMN_ProbeKind_Null; + if(hit_user_trap == 0) + { + for EachNode(active_trap, LNX_DMN_ActiveTrap, process->ctx->first_probe_trap) + { + if(active_trap->trap->vaddr == ip-1) + { + probe_kind = active_trap->trap->id; + break; + } + } + } + + if(probe_kind == LNX_DMN_ProbeKind_InitComplete) + { + B32 is_init_completed = 0; + + LNX_DMN_Probe *probe = process->ctx->probes[LNX_DMN_ProbeKind_InitComplete]; + U64 name_space_id = 0, rdebug_addr = 0; + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id)) { goto init_complete_exit; } + if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &rdebug_addr)) { goto init_complete_exit; } + + GNU_RDebugInfo64 rdebug = {0}; + if(gnu_read_r_debug(lnx_dmn_machine_op_mem_read, &process->fd, rdebug_addr, process->ctx->arch, &rdebug) != MachineOpResult_Ok) { goto init_complete_exit; } + if(rdebug.r_version < 1) { goto init_complete_exit; } + + lnx_dmn_event_load_module(arena, &events, thread, name_space_id, rdebug.r_map); + + is_init_completed = 1; + init_complete_exit:; + AssertAlways(is_init_completed); + } + else if(probe_kind == LNX_DMN_ProbeKind_RelocComplete) + { + B32 is_reloc_completed = 0; + + LNX_DMN_Probe *probe = process->ctx->probes[LNX_DMN_ProbeKind_RelocComplete]; + U64 name_space_id = 0, new_link_map_addr = 0; + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id)) { goto reloc_complete_exit; } + if(!stap_read_arg_u(probe->args.v[2], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &new_link_map_addr)) { goto reloc_complete_exit; } + + lnx_dmn_event_load_module(arena, &events, thread, name_space_id, new_link_map_addr); + + is_reloc_completed = 1; + reloc_complete_exit:; + AssertAlways(is_reloc_completed); + } + else if(probe_kind == LNX_DMN_ProbeKind_UnmapComplete) + { + B32 is_unmap_completed = 0; + + LNX_DMN_Probe *probe = process->ctx->probes[LNX_DMN_ProbeKind_UnmapComplete]; + U64 name_space_id = 0, rdebug_vaddr = 0; + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id)) { goto unmap_complete_exit; } + if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &rdebug_vaddr)) { goto unmap_complete_exit; } + + { + Temp scratch = scratch_begin(&arena, 1); + + LNX_DMN_ProcessCtx *ctx = process->ctx; + + // flag every module as inactive + for EachNode(module, LNX_DMN_Module, ctx->first_module) + { + module->is_live = 0; + } + + // mark live modules + B32 is_64bit = ctx->dl_class == ELF_Class_64; + GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, lnx_dmn_machine_op_mem_read, &process->fd); + for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) + { + GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, lnx_dmn_machine_op_mem_read, &process->fd); + for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first) + { + LNX_DMN_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, link_map_n->v.addr_vaddr); + module->is_live = 1; + } + } + + // collect unloaded modules + LNX_DMN_ModulePtrList to_release = {0}; + for EachNode(module, LNX_DMN_Module, ctx->first_module) + { + if(!module->is_live) + { + lnx_dmn_module_ptr_list_push(scratch.arena, &to_release, module); + } + } + + // clone process context + if(to_release.count > 0) + { + if(process->is_cow) + { + process->is_cow = 0; + process->ctx = lnx_dmn_process_ctx_clone(process, process->ctx); + } + } + + // push events and clean up unloaded modules + for EachNode(module_n, LNX_DMN_ModulePtrNode, to_release.first) + { + lnx_dmn_push_event_unload_module(arena, &events, process, module_n->v); + lnx_dmn_module_release(process->ctx, module_n->v); + } + + scratch_end(scratch); + } + + is_unmap_completed = 1; + unmap_complete_exit:; + AssertAlways(is_unmap_completed); + } + + if(probe_kind == LNX_DMN_ProbeKind_Null) + { + // rollback IP on user traps + if(hit_user_trap) + { + U64 ip = lnx_dmn_thread_read_ip(thread); + lnx_dmn_thread_write_ip(thread, ip - 1); + } + + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Breakpoint; + e->process = lnx_dmn_handle_from_process(process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->instruction_pointer = lnx_dmn_thread_read_ip(thread); + } + }break; + + //- rjf: single-step completions + case TRAP_TRACE: + { + lnx_dmn_set_single_step_flag(thread, 0); + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_SingleStep; + e->process = lnx_dmn_handle_from_process(thread->process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->instruction_pointer = lnx_dmn_thread_read_ip(thread); + e->address = e->instruction_pointer; + }break; + + //- rjf: data breakpoints + case TRAP_HWBKPT: + { + B32 is_valid = 1; + U64 address = 0; + switch(thread->process->ctx->arch) + { + default:{}break; + case Arch_x64: + { + X64_RegBlock *regs_x64 = thread->reg_block; + if(regs_x64->dr6 & X64_DebugStatusFlag_B0) + { + address = regs_x64->dr0; + } + else if(regs_x64->dr6 & X64_DebugStatusFlag_B1) + { + address = regs_x64->dr1; + } + else if(regs_x64->dr6 & X64_DebugStatusFlag_B2) + { + address = regs_x64->dr2; + } + else if(regs_x64->dr6 & X64_DebugStatusFlag_B3) + { + address = regs_x64->dr3; + } + else + { + is_valid = 0; + } + }break; + } + + if(is_valid) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Breakpoint; + e->process = lnx_dmn_handle_from_process(thread->process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->instruction_pointer = address; + e->address = address; + } + }break; case TRAP_BRANCH: { NotImplemented; }break; case TRAP_UNK: { NotImplemented; }break; default: { InvalidPath; } break; } } else { Assert(0 && "failed to get signal info"); } }break; + + //////////////////// + //- rjf: fork / vforks + // case PTRACE_EVENT_FORK: { NotImplemented; @@ -3091,6 +2794,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { NotImplemented; }break; + + //////////////////// + //- rjf: clone + // case PTRACE_EVENT_CLONE: { // kernel stopped the parent just before scheduling the child to @@ -3107,6 +2814,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } else { Assert(0 && "failed to get new tid"); } }break; + + //////////////////// + //- rjf: exec + // case PTRACE_EVENT_EXEC: { LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); @@ -3125,14 +2836,25 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) else { Assert(0 && "failed to detach"); } } }break; + + //////////////////// + //- rjf: exit event -> exit tracing not enabled, so we don't really have anything to do here. + // case PTRACE_EVENT_EXIT: { - AssertAlways(0 && "exit tracing is disabled, this event should not be delivered"); }break; + + //////////////////// + //- TODO(rjf): seccomp? + // case PTRACE_EVENT_SECCOMP: { NotImplemented; }break; + + //////////////////// + //- rjf: thread stops + // case PTRACE_EVENT_STOP: { LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); @@ -3140,16 +2862,20 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { LNX_DMN_Process *process = thread->process; lnx_dmn_thread_release(thread); - thread = lnx_dmn_event_create_thread(arena, &events, process, wait_id); + thread = lnx_dmn_thread_alloc(process, LNX_DMN_ThreadState_Stopped, wait_id); + lnx_dmn_push_event_create_thread(arena, &events, thread); } else { AssertAlways(thread->state == LNX_DMN_ThreadState_Stopped); } }break; - default: { Assert(0 && "unexpected ptrace code"); } break; } } + + //////////////////////// + //- rjf: sigstop - potentially halt + // else if(wstopsig == SIGSTOP) { siginfo_t siginfo = {0}; @@ -3166,19 +2892,85 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } } + + //////////////////////// + //- rjf: catchall exception case + // else { - lnx_dmn_event_exception(arena, &events, wait_id, wstopsig); + LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); + thread->pass_through_signal = 1; + thread->pass_through_signo = wstopsig; + local_persist B8 is_repeatable[] = + { + 0, // null + 1, // 1 SIGHUP + 1, // 2 SIGINT + 1, // 3 SIGQUIT + 1, // 4 SIGTRAP + 1, // 5 SIGABRT + 1, // 6 SIGIOT + 1, // 7 SIGBUS + 1, // 8 SIGFPE + 1, // 9 SIGKILL + 1, // 10 SIGUSR1 + 1, // 11 SIGSEGV + 1, // 12 SIGUSR2 + 1, // 13 SIGPIPE + 1, // 14 SIGALRM + 1, // 15 SIGTERM + 1, // 16 SIGTKFLT + 0, // 17 SIGCHLD + 0, // 18 SIGCONT + 1, // 19 SIGSTOP + 1, // 20 SIGTSP + 1, // 21 SIGTTIN + 1, // 22 SIGTTOU + 0, // 23 SIGURG + 1, // 24 SIGXCPU + 1, // 25 SIGXFSZ + 1, // 26 SIGVTALRM + 1, // 27 SIGPROF + 0, // 28 SIGWINCH + 1, // 29 SIGIO + 1, // 30 SIGPWR + 1, // 31 SIGSYS + 1, // 32 SIGUNUSED + }; + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Exception; + e->process = lnx_dmn_handle_from_process(thread->process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->instruction_pointer = lnx_dmn_thread_read_ip(thread); + e->address = e->instruction_pointer; + e->code = wstopsig; + e->exception_repeated = wstopsig < ArrayCount(is_repeatable) ? is_repeatable[wstopsig] : 0; + if(wstopsig == SIGSEGV) + { + siginfo_t si = {0}; + LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, thread->tid, 0, &si)); + e->address = (U64)si.si_addr; + } } } - else { Assert(0 && "unexpected stop code"); } + + ////////////////////////// + //- rjf: otherwise -> unexpected stop code. + // + else + { + // TODO(rjf) + } } while(running_threads.count > 0 || lnx_dmn_state->process_pending_creation > 0 || lnx_dmn_state->threads_pending_creation > 0); - // finalize halter state + //////////////////////////// + //- rjf: do halt + // if(is_halt_done) { // push event - lnx_dmn_event_halt(arena, &events); + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Halt; // reset state lnx_dmn_state->halter_tid = 0; @@ -3187,29 +2979,36 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) lnx_dmn_state->is_halting = 0; } - // restore original instruction bytes + //////////////////////////// + //- rjf: unset all traps - restore original bytes + // for EachNode(active_trap, LNX_DMN_ActiveTrap, active_trap_first) { - // skip process that exited during the wait - LNX_DMN_Process *process = lnx_dmn_process_from_handle(active_trap->trap->process); - if(!process) { continue; } - - if(!dmn_process_write(active_trap->trap->process, r1u64(active_trap->trap->vaddr, active_trap->trap->vaddr + active_trap->swap_bytes.size), active_trap->swap_bytes.str) && - active_trap->good) + if(active_trap->good) { - // TODO(rjf): log an error here? - we wrote the trap successfully, but we did not remove it successfully, - // implying a change in address mapping. this is maybe not even a bug, since the address is simply now - // unmapped. + LNX_DMN_Process *process = lnx_dmn_process_from_handle(active_trap->trap->process); + // NOTE(rjf): this process may have been killed during the wait - skip such processes + if(process != 0) + { + dmn_process_write(active_trap->trap->process, r1u64(active_trap->trap->vaddr, active_trap->trap->vaddr + active_trap->swap_bytes.size), active_trap->swap_bytes.str); + } } } } + ////////////////////////////// + //- rjf: no events, no threads -> not attached + // if(events.count == 0 && lnx_dmn_state->process_count == 0) { - lnx_dmn_push_event_not_attached(arena, &events); + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Error; + e->error_kind = DMN_ErrorKind_NotAttached; } + // TODO(rjf): WRONG, see `take` mutex_drop(lnx_dmn_state->halter_mutex); + scratch_end(scratch); return events; } @@ -3220,18 +3019,19 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) internal void dmn_halt(U64 code, U64 user_data) { - mutex_take(lnx_dmn_state->halter_mutex); - if(lnx_dmn_state->process_count) + MutexScope(lnx_dmn_state->halter_mutex) { - lnx_dmn_state->halter_tid = gettid(); - lnx_dmn_state->halt_code = code; - lnx_dmn_state->halt_user_data = user_data; - for EachNode(process, LNX_DMN_Process, lnx_dmn_state->first_process) + if(lnx_dmn_state->process_count) { - if(LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)) < 0) { Assert(0 && "failed to send SIGSTOP"); } + lnx_dmn_state->halter_tid = gettid(); + lnx_dmn_state->halt_code = code; + lnx_dmn_state->halt_user_data = user_data; + for EachNode(process, LNX_DMN_Process, lnx_dmn_state->first_process) + { + if(LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)) < 0) { Assert(0 && "failed to send SIGSTOP"); } + } } } - mutex_drop(lnx_dmn_state->halter_mutex); } //////////////////////////////// @@ -3269,34 +3069,39 @@ dmn_access_close(void) internal U64 dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) { + // TODO(rjf) return 0; } internal void dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size) { + // TODO(rjf) } internal void dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size) { + // TODO(rjf) } internal void dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size) { + // TODO(rjf) } internal void dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, AccessFlags flags) { + // TODO(rjf) } internal U64 dmn_process_read(DMN_Handle process_handle, Rng1U64 range, void *dst) { - LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); U64 result = 0; + LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); if(process) { result = lnx_dmn_read(process->fd, range, dst); @@ -3307,8 +3112,8 @@ dmn_process_read(DMN_Handle process_handle, Rng1U64 range, void *dst) internal B32 dmn_process_write(DMN_Handle process_handle, Rng1U64 range, void *src) { - LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); B32 result = 0; + LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); if(process) { result = lnx_dmn_write(process->fd, range, src); @@ -3438,7 +3243,29 @@ dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info if(got_pid) { pid_t pid = u64_from_str8(pid_string, 10); - String8 name = lnx_dmn_exe_path_from_pid(arena, pid); + String8 name = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + String8 exe_link_path = str8f(scratch.arena, "/proc/%d/exe", pid); + String8List parts = {0}; + int readlink_result = 0; + for(S64 r = 0, cap = PATH_MAX; r < 4; cap *= 2, r += 1) + { + U8 *buffer = push_array(scratch.arena, U8, cap); + readlink_result = readlink((char *)exe_link_path.str, (char *)buffer, cap); + if(readlink_result < 0) + { + break; + } + str8_list_push(scratch.arena, &parts, str8(buffer, readlink_result)); + if(readlink_result < cap) + { + break; + } + } + name = str8_list_join(arena, &parts, 0); + scratch_end(scratch); + } if(name.size == 0) { name = str8_lit("(unknown process)"); diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index 64a40edc..b152fb10 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -4,6 +4,18 @@ #ifndef LINUX_DEMON_H #define LINUX_DEMON_H +//////////////////////////////// +//~ rjf: Linux Includes + +#include +#include +#include + +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/linux_demon.meta.h" + //////////////////////////////// //~ rjf: Register Layouts // @@ -62,70 +74,54 @@ struct LNX_DMN_UserX64 U8 u_comm[32]; U64 u_debugreg[8]; }; + StaticAssert(sizeof(LNX_DMN_UserX64) == 912, lnx_user_x64_size_check); //////////////////////////////// -//~ TLS +//~ rjf: TLS Descriptions -typedef struct LNX_DMN_DbDesc +typedef struct LNX_DMN_DbDesc LNX_DMN_DbDesc; +struct LNX_DMN_DbDesc { U32 bit_size; U32 count; U32 offset; -} LNX_DMN_DbDesc; +}; //////////////////////////////// -//~ SDT Probes +//~ rjf: SDT (Statically-Defined Tracing) Probes -typedef struct LNX_DMN_Probe +typedef struct LNX_DMN_Probe LNX_DMN_Probe; +struct LNX_DMN_Probe { - String8 provider; - String8 name; - String8 args_string; + String8 provider; + String8 name; + String8 args_string; STAP_ArgArray args; - U64 pc; - U64 semaphore; -} LNX_DMN_Probe; + U64 pc; + U64 semaphore; +}; -typedef struct LNX_DMN_ProbeNode +typedef struct LNX_DMN_ProbeNode LNX_DMN_ProbeNode; +struct LNX_DMN_ProbeNode { + LNX_DMN_ProbeNode *next; LNX_DMN_Probe v; - struct LNX_DMN_ProbeNode *next; -} LNX_DMN_ProbeNode; +}; -typedef struct LNX_DMN_ProbeList +typedef struct LNX_DMN_ProbeList LNX_DMN_ProbeList; +struct LNX_DMN_ProbeList { - U64 count; LNX_DMN_ProbeNode *first; LNX_DMN_ProbeNode *last; -} LNX_DMN_ProbeList; - -#define LNX_DMN_Probe_XList \ -X(InitStart, 2, "init_start") \ -X(InitComplete, 2, "init_complete") \ -X(RelocStart, 2, "reloc_start") \ -X(RelocComplete, 3, "reloc_complete") \ -X(MapStart, 2, "map_start") \ -X(MapComplete, 3, "map_complete") \ -X(UnmapStart, 2, "unmap_start") \ -X(UnmapComplete, 2, "unmap_complete") \ -X(LongJmp, 3, "longjmp") \ -X(LongJmpTarget, 3, "longjmp_target") \ -X(SetJmp, 3, "setjmp") - -typedef enum -{ - LNX_DMN_ProbeType_Null, -#define X(_N,...) LNX_DMN_ProbeType_##_N, - LNX_DMN_Probe_XList -#undef X - LNX_DMN_ProbeType_Count, -} LNX_DMN_ProbeType; + U64 count; +}; //////////////////////////////// -//~ Process Info +//~ rjf: Process Info -typedef struct LNX_DMN_Auxv +typedef struct LNX_DMN_Auxv LNX_DMN_Auxv; +struct LNX_DMN_Auxv { U64 base; U64 phnum; @@ -133,9 +129,10 @@ typedef struct LNX_DMN_Auxv U64 phdr; U64 execfn; U64 pagesz; -} LNX_DMN_Auxv; +}; -typedef struct LNX_DMN_DynamicInfo +typedef struct LNX_DMN_DynamicInfo LNX_DMN_DynamicInfo; +struct LNX_DMN_DynamicInfo { U64 hash_vaddr; U64 gnu_hash_vaddr; @@ -143,10 +140,10 @@ typedef struct LNX_DMN_DynamicInfo U64 strtab_size; U64 symtab_vaddr; U64 symtab_entry_size; -} LNX_DMN_DynamicInfo; +}; //////////////////////////////// -//~ Entities +//~ rjf: Entities typedef enum LNX_DMN_EntityKind { @@ -155,7 +152,8 @@ typedef enum LNX_DMN_EntityKind LNX_DMN_EntityKind_ProcessCtx, LNX_DMN_EntityKind_Thread, LNX_DMN_EntityKind_Module, -} LNX_DMN_EntityKind; +} +LNX_DMN_EntityKind; typedef enum LNX_DMN_ThreadState { @@ -164,40 +162,46 @@ typedef enum LNX_DMN_ThreadState LNX_DMN_ThreadState_Stopped, LNX_DMN_ThreadState_Exited, LNX_DMN_ThreadState_PendingCreation, -} LNX_DMN_ThreadState; +} +LNX_DMN_ThreadState; -typedef struct LNX_DMN_Thread +typedef struct LNX_DMN_Thread LNX_DMN_Thread; +struct LNX_DMN_Thread { - pid_t tid; - LNX_DMN_ThreadState state; + LNX_DMN_Thread *next; + LNX_DMN_Thread *prev; + pid_t tid; + LNX_DMN_ThreadState state; struct LNX_DMN_Process *process; - void *reg_block; - B32 is_reg_block_dirty; - B32 pass_through_signal; - U64 pass_through_signo; - U64 orig_rax; - U64 dtv_base_vaddr; - - struct LNX_DMN_Thread *next; - struct LNX_DMN_Thread *prev; -} LNX_DMN_Thread; + void *reg_block; + B32 is_reg_block_dirty; + B32 pass_through_signal; + U64 pass_through_signo; + U64 orig_rax; + U64 dtv_base_vaddr; +}; -typedef struct LNX_DMN_ThreadPtrNode +typedef struct LNX_DMN_ThreadPtrNode LNX_DMN_ThreadPtrNode; +struct LNX_DMN_ThreadPtrNode { + LNX_DMN_ThreadPtrNode *next; + LNX_DMN_ThreadPtrNode *prev; LNX_DMN_Thread *v; - struct LNX_DMN_ThreadPtrNode *next; - struct LNX_DMN_ThreadPtrNode *prev; -} LNX_DMN_ThreadPtrNode; +}; -typedef struct LNX_DMN_ThreadPtrList +typedef struct LNX_DMN_ThreadPtrList LNX_DMN_ThreadPtrList; +struct LNX_DMN_ThreadPtrList { - U64 count; LNX_DMN_ThreadPtrNode *first; LNX_DMN_ThreadPtrNode *last; -} LNX_DMN_ThreadPtrList; + U64 count; +}; -typedef struct LNX_DMN_Module +typedef struct LNX_DMN_Module LNX_DMN_Module; +struct LNX_DMN_Module { + LNX_DMN_Module *next; + LNX_DMN_Module *prev; U64 name_vaddr; U64 base_vaddr; U64 name_space_id; @@ -207,27 +211,26 @@ typedef struct LNX_DMN_Module U64 phcount; U64 tls_index; U64 tls_offset; - B8 is_live; - B8 is_main; - - struct LNX_DMN_Module *next; - struct LNX_DMN_Module *prev; -} LNX_DMN_Module; + B8 is_live; + B8 is_main; +}; -typedef struct LNX_DMN_ModulePtrNode +typedef struct LNX_DMN_ModulePtrNode LNX_DMN_ModulePtrNode; +struct LNX_DMN_ModulePtrNode { + LNX_DMN_ModulePtrNode *next; LNX_DMN_Module *v; - struct LNX_DMN_ModulePtrNode *next; -} LNX_DMN_ModulePtrNode; +}; -typedef struct LNX_DMN_ModulePtrList +typedef struct LNX_DMN_ModulePtrList LNX_DMN_ModulePtrList; +struct LNX_DMN_ModulePtrList { - U64 count; LNX_DMN_ModulePtrNode *first; LNX_DMN_ModulePtrNode *last; -} LNX_DMN_ModulePtrList; + U64 count; +}; -typedef enum +typedef enum LNX_DMN_ProcessState { LNX_DMN_ProcessState_Null, LNX_DMN_ProcessState_Launch, @@ -235,48 +238,51 @@ typedef enum LNX_DMN_ProcessState_WaitForExec, LNX_DMN_ProcessState_ExecFailedDoExit, LNX_DMN_ProcessState_Normal, -} LNX_DMN_ProcessState; +} +LNX_DMN_ProcessState; -typedef enum +typedef enum LNX_DMN_CreateProcessFlags { LNX_DMN_CreateProcessFlag_DebugSubprocesses = (1 << 0), LNX_DMN_CreateProcessFlag_Rebased = (1 << 1), LNX_DMN_CreateProcessFlag_Cow = (1 << 2), LNX_DMN_CreateProcessFlag_ClonedMemory = (1 << 3), -} LNX_DMN_CreateProcessFlags; +} +LNX_DMN_CreateProcessFlags; -typedef struct LNX_DMN_Process +typedef struct LNX_DMN_Process LNX_DMN_Process; +struct LNX_DMN_Process { - pid_t pid; - int fd; - LNX_DMN_ProcessState state; - B32 debug_subprocesses; - B32 is_cow; - B32 vfork_with_spoof; - U64 thread_count; - LNX_DMN_Thread *first_thread; - LNX_DMN_Thread *last_thread; - U64 main_thread_exit_code; - struct LNX_DMN_Process *parent_process; + LNX_DMN_Process *parent_process; struct LNX_DMN_ProcessCtx *ctx; - - - struct LNX_DMN_Process *next; - struct LNX_DMN_Process *prev; -} LNX_DMN_Process; + LNX_DMN_Process *next; + LNX_DMN_Process *prev; + pid_t pid; + int fd; + LNX_DMN_ProcessState state; + B32 debug_subprocesses; + B32 is_cow; + B32 vfork_with_spoof; + U64 thread_count; + LNX_DMN_Thread *first_thread; + LNX_DMN_Thread *last_thread; + U64 main_thread_exit_code; +}; -typedef struct LNX_DMN_ProcessPtrNode +typedef struct LNX_DMN_ProcessPtrNode LNX_DMN_ProcessPtrNode; +struct LNX_DMN_ProcessPtrNode { + LNX_DMN_ProcessPtrNode *next; LNX_DMN_Process *v; - struct LNX_DMN_ProcessPtrNode *next; -} LNX_DMN_ProcessPtrNode; +}; -typedef struct LNX_DMN_ProcessPtrList +typedef struct LNX_DMN_ProcessPtrList LNX_DMN_ProcessPtrList; +struct LNX_DMN_ProcessPtrList { - U64 count; LNX_DMN_ProcessPtrNode *first; LNX_DMN_ProcessPtrNode *last; -} LNX_DMN_ProcessPtrList; + U64 count; +}; typedef struct LNX_DMN_ActiveTrap LNX_DMN_ActiveTrap; struct LNX_DMN_ActiveTrap @@ -287,64 +293,65 @@ struct LNX_DMN_ActiveTrap String8 swap_bytes; }; -typedef struct LNX_DMN_ProcessCtx +typedef struct LNX_DMN_ProcessCtx LNX_DMN_ProcessCtx; +struct LNX_DMN_ProcessCtx { - Arena *arena; - Arch arch; - U64 rdebug_vaddr; - ELF_Class dl_class; - HashTable *loaded_modules_ht; - LNX_DMN_Probe **probes; - LNX_DMN_ActiveTrap *first_probe_trap; - LNX_DMN_ActiveTrap *last_probe_trap; - LNX_DMN_Module *first_module; - LNX_DMN_Module *last_module; - U64 module_count; - U64 ref_count; - + Arena *arena; + Arch arch; + U64 rdebug_vaddr; + ELF_Class dl_class; + HashTable *loaded_modules_ht; + LNX_DMN_Probe **probes; + LNX_DMN_ActiveTrap *first_probe_trap; + LNX_DMN_ActiveTrap *last_probe_trap; + LNX_DMN_Module *first_module; + LNX_DMN_Module *last_module; + U64 module_count; + U64 ref_count; String8List free_reg_blocks; String8List free_reg_block_nodes; // x64 - U64 xcr0; - U64 xsave_size; + U64 xcr0; + U64 xsave_size; X64_XSaveLayout xsave_layout; -} LNX_DMN_ProcessCtx; +}; -typedef struct LNX_DMN_Entity +typedef struct LNX_DMN_Entity LNX_DMN_Entity; +struct LNX_DMN_Entity { union { - LNX_DMN_Process process; + LNX_DMN_Process process; LNX_DMN_ProcessCtx process_ctx; - LNX_DMN_Thread thread; - LNX_DMN_Module module; - struct - { - struct LNX_DMN_Entity *next; - }; + LNX_DMN_Thread thread; + LNX_DMN_Module module; + LNX_DMN_Entity *next; }; - U32 gen; + U32 gen; LNX_DMN_EntityKind kind; -} LNX_DMN_Entity; +}; -typedef struct LNX_DMN_EntityNode +typedef struct LNX_DMN_EntityNode LNX_DMN_EntityNode; +struct LNX_DMN_EntityNode { + LNX_DMN_EntityNode *next; LNX_DMN_Entity *v; - struct LNX_DMN_EntityNode *next; -} LNX_DMN_EntityNode; +}; -typedef struct LNX_DMN_EntityList +typedef struct LNX_DMN_EntityList LNX_DMN_EntityList; +struct LNX_DMN_EntityList { - U64 count; LNX_DMN_EntityNode *first; LNX_DMN_EntityNode *last; -} LNX_DMN_EntityList; + U64 count; +}; //////////////////////////////// -//~ Global State +//~ rjf: Top-Level State Bundle -typedef struct LNX_DMN_State +typedef struct LNX_DMN_State LNX_DMN_State; +struct LNX_DMN_State { Arena *arena; @@ -353,16 +360,16 @@ typedef struct LNX_DMN_State B32 access_run_state; // rjf: entity storage - Arena *entities_arena; + Arena *entities_arena; LNX_DMN_Entity *entities_base; LNX_DMN_Entity *free_entity; - U64 entities_count; + U64 entities_count; HashTable *tid_ht; // thread id -> thread entity HashTable *pid_ht; // process id -> process entity // process tracking - U64 process_count; + U64 process_count; LNX_DMN_Process *first_process; LNX_DMN_Process *last_process; @@ -373,27 +380,28 @@ typedef struct LNX_DMN_State // halter Mutex halter_mutex; pid_t halter_tid; - U64 halt_code; - U64 halt_user_data; - B32 is_halting; + U64 halt_code; + U64 halt_user_data; + B32 is_halting; // TLS - B32 is_tls_detected; + B32 is_tls_detected; LNX_DMN_DbDesc tls_modid_desc; LNX_DMN_DbDesc tls_offset_desc; -} LNX_DMN_State; +}; //////////////////////////////// -//~ Globals +//~ rjf: Globals -global B32 lnx_dmn_ctrl_thread; -global LNX_DMN_State *lnx_dmn_state; +global LNX_DMN_State *lnx_dmn_state = 0; +thread_static B32 lnx_dmn_ctrl_thread = 0; //////////////////////////////// -//~ Memory R/W +//~ rjf: Memory R/W Helpers -internal U64 lnx_dmn_read(int memory_fd, Rng1U64 range, void *dst); -internal B32 lnx_dmn_write(int memory_fd, Rng1U64 range, void *src); +internal U64 lnx_dmn_size_from_fd(int memory_fd, U64 cap); +internal U64 lnx_dmn_read(int memory_fd, Rng1U64 range, void *dst); +internal B32 lnx_dmn_write(int memory_fd, Rng1U64 range, void *src); internal String8 lnx_dmn_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size); internal String8 lnx_dmn_read_string(Arena *arena, int memory_fd, U64 base_vaddr); #define lnx_dmn_read_struct(fd, vaddr, ptr) lnx_dmn_read((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) @@ -407,17 +415,15 @@ internal LNX_DMN_ActiveTrap *lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap); //////////////////////////////// //~ ELF/GNU info -internal Rng1U64 lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); -internal LNX_DMN_DynamicInfo lnx_dmn_dynamic_info_from_memory(int memory_fd, ELF_Class elf_Class, U64 rebase, U64 dynamic_vaddr); -internal U64 lnx_dmn_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vaddr, B32 is_rebased); +internal Rng1U64 lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); //////////////////////////////// -//~ Process Info +//~ rjf: Process Info Parsing Functions -internal String8 lnx_dmn_exe_path_from_pid(Arena *arena, pid_t pid); internal String8 lnx_dmn_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base); internal ELF_Hdr64 lnx_dmn_ehdr_from_pid(pid_t pid); internal LNX_DMN_Auxv lnx_dmn_auxv_from_pid(pid_t pid, ELF_Class elf_class); + internal LNX_DMN_Thread * lnx_dmn_thread_from_pid(pid_t pid); internal LNX_DMN_Process * lnx_dmn_process_from_pid(pid_t pid); @@ -427,19 +433,17 @@ internal LNX_DMN_Process * lnx_dmn_process_from_pid(pid_t pid); internal DMN_ModuleInfo *lnx_dmn_module_info_from_process_module(Arena *arena, pid_t pid, int memory_fd, U64 base_vaddr, U64 link_map_vaddr, B32 is_main); //////////////////////////////// -//~ Entity +//~ rjf: Entity Functions // alloc internal LNX_DMN_Entity * lnx_dmn_entity_alloc(LNX_DMN_EntityKind kind); internal LNX_DMN_Process * lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *parent_process, B32 debug_subprocess, B32 is_cow); -internal LNX_DMN_ProcessCtx * lnx_dmn_process_ctx_alloc(LNX_DMN_Process *process, B32 is_rebased); internal LNX_DMN_Thread * lnx_dmn_thread_alloc(LNX_DMN_Process *process, LNX_DMN_ThreadState thread_state, pid_t tid); internal LNX_DMN_Module * lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main); // release internal void lnx_dmn_entity_release(LNX_DMN_Entity *entity); internal void lnx_dmn_process_release(LNX_DMN_Process *process); -internal void lnx_dmn_process_ctx_release(LNX_DMN_ProcessCtx *process_ctx); internal void lnx_dmn_thread_release(LNX_DMN_Thread *thread); internal void lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_Module *module); @@ -450,30 +454,20 @@ internal LNX_DMN_Module * lnx_dmn_module_clone(LNX_DMN_ProcessCtx *process_c // entity -> handle internal DMN_Handle lnx_dmn_handle_from_entity(LNX_DMN_Entity *entity); internal DMN_Handle lnx_dmn_handle_from_process(LNX_DMN_Process *process); -internal DMN_Handle lnx_dmn_handle_from_process_ctx(LNX_DMN_ProcessCtx *process_ctx); internal DMN_Handle lnx_dmn_handle_from_thread(LNX_DMN_Thread *thread); internal DMN_Handle lnx_dmn_handle_from_module(LNX_DMN_Module *module); // handle -> entity internal LNX_DMN_Entity * lnx_dmn_entity_from_handle(DMN_Handle handle, LNX_DMN_EntityKind expected_kind); internal LNX_DMN_Process * lnx_dmn_process_from_handle(DMN_Handle process_handle); -internal LNX_DMN_ProcessCtx * lnx_dmn_process_ctx_from_handle(DMN_Handle process_ctx_handle); internal LNX_DMN_Thread * lnx_dmn_thread_from_handle(DMN_Handle thread_handle); internal LNX_DMN_Module * lnx_dmn_module_from_handle(DMN_Handle module_handle); -//////////////////////////////// -//~ Process Helpers - -internal void lnx_dmn_process_trap_probes(LNX_DMN_Process *process); -internal void lnx_dmn_process_untrap_probes(LNX_DMN_Process *process); - //////////////////////////////// //~ Thread Helpers internal U64 lnx_dmn_thread_read_ip(LNX_DMN_Thread *thread); -internal U64 lnx_dmn_thread_read_sp(LNX_DMN_Thread *thread); internal void lnx_dmn_thread_write_ip(LNX_DMN_Thread *thread, U64 ip); -internal void lnx_dmn_thread_write_sp(LNX_DMN_Thread *thread, U64 sp); internal B32 lnx_dmn_thread_read_reg_block(LNX_DMN_Thread *thread); internal B32 lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread); internal B32 lnx_dmn_set_single_step_flag(LNX_DMN_Thread *thread, B32 is_on); @@ -484,39 +478,21 @@ internal U64 lnx_dmn_tls_root_vaddr_from_reg_block(int fd, Arch arch, void *reg internal void lnx_dmn_thread_ptr_list_push_node(LNX_DMN_ThreadPtrList *list, LNX_DMN_ThreadPtrNode *n); internal LNX_DMN_ThreadPtrNode * lnx_dmn_thread_ptr_list_push(Arena *arena, LNX_DMN_ThreadPtrList *list, LNX_DMN_Thread *v); +internal void lnx_dmn_thread_ptr_list_remove(LNX_DMN_ThreadPtrList *list, LNX_DMN_ThreadPtrNode *n); internal LNX_DMN_ModulePtrNode * lnx_dmn_module_ptr_list_push(Arena *arena, LNX_DMN_ModulePtrList *list, LNX_DMN_Module *v); -internal LNX_DMN_ProcessPtrNode * lnx_dmn_process_ptr_list_push(Arena *arena, LNX_DMN_ProcessPtrList *list, LNX_DMN_Process *v); //////////////////////////////// //~ Debug Event Pushers -internal void lnx_dmn_push_event_create_process(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process); -internal void lnx_dmn_push_event_exit_process(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process); internal void lnx_dmn_push_event_create_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread); -internal void lnx_dmn_push_event_exit_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 exit_code); internal void lnx_dmn_push_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, LNX_DMN_Module *module); internal void lnx_dmn_push_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process, LNX_DMN_Module *module); -internal void lnx_dmn_push_event_handshake_complete(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process); -internal void lnx_dmn_push_event_breakpoint(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 address); -internal void lnx_dmn_push_event_single_step(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread); -internal void lnx_dmn_push_event_exception(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 signo); -internal void lnx_dmn_push_event_halt(Arena *arena, DMN_EventList *events); -internal void lnx_dmn_push_event_not_attached(Arena *arena, DMN_EventList *events); //////////////////////////////// //~ Debug Event -internal LNX_DMN_Thread * lnx_dmn_event_create_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process, pid_t tid); internal void lnx_dmn_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code); internal LNX_DMN_Process * lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX_DMN_Process *parent_process, LNX_DMN_CreateProcessFlags flags); -internal void lnx_dmn_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid); internal void lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr); -internal void lnx_dmn_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process, U64 rdebug_vaddr); -internal void lnx_dmn_event_breakpoint(Arena *arena, DMN_EventList *events, LNX_DMN_ActiveTrap *user_traps, pid_t tid); -internal void lnx_dmn_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid); -internal void lnx_dmn_event_halt(Arena *arena, DMN_EventList *events); -internal void lnx_dmn_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid); -internal void lnx_dmn_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo); -internal LNX_DMN_Process * lnx_dmn_event_attach(Arena *arena, DMN_EventList *events, pid_t pid); #endif // LINUX_DEMON_H diff --git a/src/linux/demon/linux_demon.mdesk b/src/linux/demon/linux_demon.mdesk new file mode 100644 index 00000000..e6f7b0a9 --- /dev/null +++ b/src/linux/demon/linux_demon.mdesk @@ -0,0 +1,37 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +@table(name args_count string} +LNX_DMN_ProbeTable: +{ + {InitStart 2 "init_start"} + {InitComplete 2 "init_complete"} + {RelocStart 2 "reloc_start"} + {RelocComplete 3 "reloc_complete"} + {MapStart 2 "map_start"} + {MapComplete 3 "map_complete"} + {UnmapStart 2 "unmap_start"} + {UnmapComplete 2 "unmap_complete"} + {LongJmp 3 "longjmp"} + {LongJmpTarget 3 "longjmp_target"} + {SetJmp 3 "setjmp"} +} + +@enum LNX_DMN_ProbeKind: +{ + Null + @expand(LNX_DMN_ProbeTable row) `$(row.name)` + COUNT +} + +@data(U8) lnx_dmn_probe_kind_args_count_table: +{ + 0, + @expand(LNX_DMN_ProbeTable row) `$(row.args_count)` +} + +@data(String8) lnx_dmn_probe_kind_string_table: +{ + `{0}`, + @expand(LNX_DMN_ProbeTable row) `str8_lit_comp("$(row.string)")` +} diff --git a/src/stap/stap_parse.c b/src/stap/stap_parse.c index 0dfd55a8..f7c0bcff 100644 --- a/src/stap/stap_parse.c +++ b/src/stap/stap_parse.c @@ -235,7 +235,7 @@ stap_parse_args_x64(String8 string, STAP_Arg *arg_out) } // fill out memory ref portion - arg.type = STAP_ArgType_MemoryRef; + arg.kind = STAP_ArgKind_MemoryRef; arg.memory_ref.disp = disp; arg.memory_ref.base.reg_code = base_reg_code; arg.memory_ref.index.reg_code = index_reg_code; @@ -274,7 +274,7 @@ stap_parse_args_x64(String8 string, STAP_Arg *arg_out) goto operand_parse_exit; } - arg.type = STAP_ArgType_Imm; + arg.kind = STAP_ArgKind_Imm; arg.imm = imm; } // %reg @@ -291,7 +291,7 @@ stap_parse_args_x64(String8 string, STAP_Arg *arg_out) goto operand_parse_exit; } - arg.type = STAP_ArgType_Reg; + arg.kind = STAP_ArgKind_Reg; arg.reg.reg_code = reg_code; } else { goto operand_parse_exit; @@ -353,21 +353,21 @@ stap_read_arg(STAP_Arg arg, AssertAlways(arg.value_size <= 8); ARCH_Info *arch_info = arch_info_from_arch(arch); B32 is_value_read = 0; - switch(arg.type) + switch(arg.kind) { default:{}break; - case STAP_ArgType_Null:{}break; - case STAP_ArgType_Imm: + case STAP_ArgKind_Null:{}break; + case STAP_ArgKind_Imm: { MemoryCopy(raw_value, &arg.imm, arg.value_size); is_value_read = 1; }break; - case STAP_ArgType_Reg: + case STAP_ArgKind_Reg: if(0 < arg.reg.reg_code && arg.reg.reg_code < arch_info->reg_code_count) { is_value_read = arch_reg_block_read_range(arch_info, reg_block, arch_info->reg_code_rng_table[arg.reg.reg_code], raw_value); }break; - case STAP_ArgType_MemoryRef: + case STAP_ArgKind_MemoryRef: { U64 base = 0; if(0 < arg.memory_ref.base.reg_code && arg.memory_ref.base.reg_code < arch_info->reg_code_count) diff --git a/src/stap/stap_parse.h b/src/stap/stap_parse.h index 301e66e0..df64e263 100644 --- a/src/stap/stap_parse.h +++ b/src/stap/stap_parse.h @@ -4,13 +4,13 @@ #ifndef STAP_PARSE_H #define STAP_PARSE_H -typedef enum STAP_ArgType +typedef enum STAP_ArgKind { - STAP_ArgType_Null, - STAP_ArgType_Imm, - STAP_ArgType_Reg, - STAP_ArgType_MemoryRef, -} STAP_ArgType; + STAP_ArgKind_Null, + STAP_ArgKind_Imm, + STAP_ArgKind_Reg, + STAP_ArgKind_MemoryRef, +} STAP_ArgKind; typedef enum STAP_ArgValueType { @@ -24,7 +24,7 @@ typedef struct STAP_Arg { STAP_ArgValueType value_type; U64 value_size; - STAP_ArgType type; + STAP_ArgKind kind; union { U64 imm; struct { diff --git a/src/stap/stap_test.c b/src/stap/stap_test.c index 155a4562..9b1619db 100644 --- a/src/stap/stap_test.c +++ b/src/stap/stap_test.c @@ -25,7 +25,7 @@ entry_point(CmdLine *cmd_line) if (error.size != 0) { goto exit; } AssertAlways(arg.value_type == STAP_ArgValueType_S); AssertAlways(arg.value_size == 2); - AssertAlways(arg.type == STAP_ArgType_Imm); + AssertAlways(arg.type == STAP_ArgKind_Imm); AssertAlways((S64)arg.imm == -123); string = str8_lit("1@$43"); @@ -33,7 +33,7 @@ entry_point(CmdLine *cmd_line) if (error.size != 0) { goto exit; } AssertAlways(arg.value_type == STAP_ArgValueType_U); AssertAlways(arg.value_size == 1); - AssertAlways(arg.type == STAP_ArgType_Imm); + AssertAlways(arg.type == STAP_ArgKind_Imm); AssertAlways((S64)arg.imm == 43); string = str8_lit("4f@%eax"); @@ -41,7 +41,7 @@ entry_point(CmdLine *cmd_line) if (error.size != 0) { goto exit; } AssertAlways(arg.value_type == STAP_ArgValueType_F); AssertAlways(arg.value_size == 4); - AssertAlways(arg.type == STAP_ArgType_Reg); + AssertAlways(arg.type == STAP_ArgKind_Reg); AssertAlways(arg.reg.is_alias == 1); AssertAlways(arg.reg.reg_code == REGS_AliasCodeX64_eax); @@ -50,7 +50,7 @@ entry_point(CmdLine *cmd_line) if (error.size != 0) { goto exit; } AssertAlways(arg.value_size == 4); AssertAlways(arg.value_type == STAP_ArgValueType_U); - AssertAlways(arg.type == STAP_ArgType_MemoryRef); + AssertAlways(arg.type == STAP_ArgKind_MemoryRef); AssertAlways(arg.memory_ref.disp == 0); AssertAlways(arg.memory_ref.base.reg_code == REGS_RegCodeX64_rdi); AssertAlways(!arg.memory_ref.base.is_alias); @@ -63,7 +63,7 @@ entry_point(CmdLine *cmd_line) if (error.size != 0) { goto exit; } AssertAlways(arg.value_size == 4); AssertAlways(arg.value_type == STAP_ArgValueType_U); - AssertAlways(arg.type == STAP_ArgType_MemoryRef); + AssertAlways(arg.type == STAP_ArgKind_MemoryRef); AssertAlways(arg.memory_ref.disp == -22); AssertAlways(arg.memory_ref.base.reg_code == REGS_RegCodeX64_rdi); AssertAlways(!arg.memory_ref.base.is_alias); From 38e321df79a2c5270a2ecf94c4c6a5ada9fc1b13 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 7 Aug 2026 14:13:30 -0600 Subject: [PATCH 023/251] linux/demon: more flattening & extra layer elimination --- src/elf/elf.c | 154 ++++---- src/linux/demon/linux_demon.c | 677 +++++++++++++++++----------------- src/linux/demon/linux_demon.h | 8 +- src/win32/demon/win32_demon.c | 1 - 4 files changed, 418 insertions(+), 422 deletions(-) diff --git a/src/elf/elf.c b/src/elf/elf.c index e9e67346..4bb43547 100644 --- a/src/elf/elf.c +++ b/src/elf/elf.c @@ -127,9 +127,9 @@ internal String8 elf_string_from_class(Arena *arena, ELF_Class v) { switch (v) { - case ELF_Class_None: return str8_lit("None"); - case ELF_Class_32: return str8_lit("32Bit"); - case ELF_Class_64: return str8_lit("64Bit"); + case ELF_Class_None: return str8_lit("None"); + case ELF_Class_32: return str8_lit("32Bit"); + case ELF_Class_64: return str8_lit("64Bit"); } return push_str8f(arena, "%#x", v); } @@ -141,12 +141,12 @@ arch_from_elf_machine(ELF_MachineKind e_machine) { Arch arch = Arch_Null; switch (e_machine) { - case ELF_MachineKind_None: arch = Arch_Null; break; - case ELF_MachineKind_AARCH64: arch = Arch_arm32; break; - case ELF_MachineKind_ARM: arch = Arch_arm32; break; - case ELF_MachineKind_386: arch = Arch_x86; break; - case ELF_MachineKind_X86_64: arch = Arch_x64; break; - default: NotImplemented; break; + case ELF_MachineKind_None: arch = Arch_Null; break; + case ELF_MachineKind_AARCH64: arch = Arch_arm32; break; + case ELF_MachineKind_ARM: arch = Arch_arm32; break; + case ELF_MachineKind_386: arch = Arch_x86; break; + case ELF_MachineKind_X86_64: arch = Arch_x64; break; + default: NotImplemented; break; } return arch; } @@ -158,10 +158,10 @@ elf_phdr_size_from_class(ELF_Class elf_class) { U64 result = 0; switch (elf_class) { - case ELF_Class_None: break; - case ELF_Class_32: { result = sizeof(ELF_Phdr32); } break; - case ELF_Class_64: { result = sizeof(ELF_Phdr64); } break; - default: { NotImplemented; } break; + case ELF_Class_None: break; + case ELF_Class_32: { result = sizeof(ELF_Phdr32); } break; + case ELF_Class_64: { result = sizeof(ELF_Phdr64); } break; + default: { NotImplemented; } break; } return result; } @@ -171,10 +171,10 @@ elf_dyn_size_from_class(ELF_Class elf_class) { U64 result = 0; switch (elf_class) { - case ELF_Class_None: break; - case ELF_Class_32: { result = sizeof(ELF_Dyn32); } break; - case ELF_Class_64: { result = sizeof(ELF_Dyn64); } break; - default: { NotImplemented; } break; + case ELF_Class_None: break; + case ELF_Class_32: { result = sizeof(ELF_Dyn32); } break; + case ELF_Class_64: { result = sizeof(ELF_Dyn64); } break; + default: { NotImplemented; } break; } return result; } @@ -189,18 +189,18 @@ elf_read_ehdr(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Hdr6 if (result == MachineOpResult_Ok) { if (str8_match(str8_prefix(str8_array_fixed(e_ident), elf_magic_string.size), elf_magic_string, 0)) { switch (e_ident[ELF_Identifier_Class]) { - default: { InvalidPath; }break; - case ELF_Class_None: { }break; - case ELF_Class_32: { - ELF_Hdr32 ehdr32 = {0}; - result = mem_read(addr, &ehdr32, sizeof(ehdr32), mem_read_ud); - if (result == MachineOpResult_Ok) { - *ehdr_out = elf_hdr64_from_hdr32(ehdr32); - } - } break; - case ELF_Class_64: { - result = mem_read(addr, ehdr_out, sizeof(*ehdr_out), mem_read_ud); - } break; + default: { InvalidPath; }break; + case ELF_Class_None: { }break; + case ELF_Class_32: { + ELF_Hdr32 ehdr32 = {0}; + result = mem_read(addr, &ehdr32, sizeof(ehdr32), mem_read_ud); + if (result == MachineOpResult_Ok) { + *ehdr_out = elf_hdr64_from_hdr32(ehdr32); + } + } break; + case ELF_Class_64: { + result = mem_read(addr, ehdr_out, sizeof(*ehdr_out), mem_read_ud); + } break; } } } @@ -212,18 +212,18 @@ elf_read_phdr(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Clas { MachineOpResult result = MachineOpResult_Fail; switch (elf_class) { - case ELF_Class_None: break; - case ELF_Class_32: { - ELF_Phdr32 phdr32 = {0}; - result = mem_read(addr, &phdr32, sizeof(phdr32), mem_read_ud); - if (result == MachineOpResult_Ok) { - *phdr_out = elf_phdr64_from_phdr32(phdr32); - } - } break; - case ELF_Class_64: { - result = mem_read(addr, phdr_out, sizeof(*phdr_out), mem_read_ud); - } break; - default: { NotImplemented; } break; + case ELF_Class_None: break; + case ELF_Class_32: { + ELF_Phdr32 phdr32 = {0}; + result = mem_read(addr, &phdr32, sizeof(phdr32), mem_read_ud); + if (result == MachineOpResult_Ok) { + *phdr_out = elf_phdr64_from_phdr32(phdr32); + } + } break; + case ELF_Class_64: { + result = mem_read(addr, phdr_out, sizeof(*phdr_out), mem_read_ud); + } break; + default: { NotImplemented; } break; } return result; } @@ -233,18 +233,18 @@ elf_read_shdr(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Clas { MachineOpResult result = MachineOpResult_Fail; switch (elf_class) { - case ELF_Class_None: break; - case ELF_Class_32: { - ELF_Shdr32 shdr32 = {0}; - result = mem_read(addr, &shdr32, sizeof(shdr32), mem_read_ud); - if (result == MachineOpResult_Ok) { - *shdr_out = elf_shdr64_from_shdr32(shdr32); - } - } break; - case ELF_Class_64: { - result = mem_read(addr, shdr_out, sizeof(*shdr_out), mem_read_ud); - } break; - default: { NotImplemented; } break; + case ELF_Class_None: break; + case ELF_Class_32: { + ELF_Shdr32 shdr32 = {0}; + result = mem_read(addr, &shdr32, sizeof(shdr32), mem_read_ud); + if (result == MachineOpResult_Ok) { + *shdr_out = elf_shdr64_from_shdr32(shdr32); + } + } break; + case ELF_Class_64: { + result = mem_read(addr, shdr_out, sizeof(*shdr_out), mem_read_ud); + } break; + default: { NotImplemented; } break; } return result; } @@ -254,18 +254,18 @@ elf_read_dyn(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class { MachineOpResult result = MachineOpResult_Fail; switch (elf_class) { - case ELF_Class_None: {} break; - case ELF_Class_32: { - ELF_Dyn32 dyn32 = {0}; - result = mem_read(addr, &dyn32, sizeof(dyn32), mem_read_ud); - if (result == MachineOpResult_Fail) { - *dyn_out = elf_dyn64_from_dyn32(dyn32); - } - } break; - case ELF_Class_64: { - result = mem_read(addr, dyn_out, sizeof(*dyn_out), mem_read_ud); - } break; - default: { NotImplemented; } break; + case ELF_Class_None: {} break; + case ELF_Class_32: { + ELF_Dyn32 dyn32 = {0}; + result = mem_read(addr, &dyn32, sizeof(dyn32), mem_read_ud); + if (result == MachineOpResult_Fail) { + *dyn_out = elf_dyn64_from_dyn32(dyn32); + } + } break; + case ELF_Class_64: { + result = mem_read(addr, dyn_out, sizeof(*dyn_out), mem_read_ud); + } break; + default: { NotImplemented; } break; } return result; } @@ -275,18 +275,18 @@ elf_read_symbol(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Cl { MachineOpResult result = MachineOpResult_Fail; switch (elf_class) { - case ELF_Class_None: {} break; - case ELF_Class_32: { - ELF_Sym32 symbol32 = {0}; - result = mem_read(addr, &symbol32, sizeof(symbol32), mem_read_ud); - if (result == MachineOpResult_Ok) { - *symbol_out = elf_sym64_from_sym32(symbol32); - } - }break; - case ELF_Class_64: { - result = mem_read(addr, symbol_out, sizeof(*symbol_out), mem_read_ud); - }break; - default: { NotImplemented; } break; + case ELF_Class_None: {} break; + case ELF_Class_32: { + ELF_Sym32 symbol32 = {0}; + result = mem_read(addr, &symbol32, sizeof(symbol32), mem_read_ud); + if (result == MachineOpResult_Ok) { + *symbol_out = elf_sym64_from_sym32(symbol32); + } + }break; + case ELF_Class_64: { + result = mem_read(addr, symbol_out, sizeof(*symbol_out), mem_read_ud); + }break; + default: { NotImplemented; } break; } return result; } diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 7bc3d9f6..bf76be60 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -7,42 +7,29 @@ #include "generated/linux_demon.meta.c" //////////////////////////////// -//~ rjf: Memory R/W - -internal U64 -lnx_dmn_size_from_fd(int memory_fd, U64 cap) -{ - U8 temp[4096]; - size_t cursor = 0; - while(cursor < cap) - { - ssize_t actual_read = LNX_RETRY_ON_EINTR(pread(memory_fd, temp, sizeof(temp), cursor)); - if(actual_read <= 0) { break; } - cursor += (U64)actual_read; - } - return (U64)cursor; -} +//~ rjf: Memory R/W Helpers internal U64 lnx_dmn_read(int memory_fd, Rng1U64 range, void *dst) { - size_t cursor = 0, size = dim_1u64(range); - while(cursor < size) + size_t cursor = 0; + size_t size = (size_t)dim_1u64(range); + for(;cursor < size;) { - size_t to_read = size - cursor; + size_t to_read = size - cursor; ssize_t actual_read = LNX_RETRY_ON_EINTR(pread(memory_fd, (U8 *)dst + cursor, to_read, range.min + cursor)); - if(actual_read < 0) { break; } - if(actual_read == 0) { break; } + if(actual_read <= 0) { break; } cursor += actual_read; } - return cursor; + return (U64)cursor; } internal B32 lnx_dmn_write(int memory_fd, Rng1U64 range, void *src) { - size_t cursor = 0, size = dim_1u64(range); - while(cursor < size) + size_t cursor = 0; + size_t size = (size_t)dim_1u64(range); + for(;cursor < size;) { size_t to_write = size - cursor; ssize_t actual_write = LNX_RETRY_ON_EINTR(pwrite(memory_fd, (U8 *)src + cursor, to_write, range.min + cursor)); @@ -100,152 +87,6 @@ lnx_dmn_ptrace_seize(pid_t pid) return LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE)); } -internal String8 -lnx_dmn_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base) -{ - String8 dl_path = {0}; - Temp scratch = scratch_begin(&arena, 1); - int maps_fd = LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/maps", pid).str, O_RDONLY)); - if(maps_fd != -1) - { - // rjf: read entire /proc/pid/maps - U64 maps_size = lnx_dmn_size_from_fd(maps_fd, MB(1)); - U8 *maps_ptr = push_array(scratch.arena, U8, maps_size); - U64 read_size = lnx_dmn_read(maps_fd, r1u64(0, maps_size), maps_ptr); - - // split map file on lines - String8List lines = str8_split_by_string_chars(scratch.arena, str8(maps_ptr, maps_size), str8_lit("\n"), 0); - - // scan each line until a virtual mapping whose low part matches the DL base address is found - for EachNode(n, String8Node, lines.first) - { - String8 line = n->string; - - // split the string while respecting escape sequences - String8List parts = {0}; - for EachIndex(cursor, line.size) - { - if(parts.node_count > 5) { break; } - if(line.str[cursor] == ' ') { continue; } - - // scan forward to the closing delimiter - U64 token_start = cursor; - for(; cursor < line.size; cursor += 1) - { - if(line.str[cursor] == '\\') - { - cursor += 1; - continue; - } - if(line.str[cursor] == ' ') { break; } - } - - // push sub-string to the list - str8_list_push(scratch.arena, &parts, str8_substr(line, r1u64(token_start, cursor))); - } - - // was line parsed correctly? - if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; } - - // parse map virtual range - String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); - if(vaddr_list.node_count != 2) { Assert(0 && "failed to parse virtual range portion of map line"); continue; } - - // does the low part match DL base address? - U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16); - if(lo_vaddr == auxv_base) - { - dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; - dl_path = push_str8_copy(arena, dl_path); - break; - } - } - - LNX_RETRY_ON_EINTR(close(maps_fd)); - } - else { Assert(0 && "failed to open DL fd"); } - - scratch_end(scratch); - Assert(dl_path.size); - return dl_path; -} - -internal ELF_Hdr64 -lnx_dmn_ehdr_from_pid(pid_t pid) -{ - Temp scratch = scratch_begin(0, 0); - - ELF_Hdr64 exe = {0}; - B32 is_read = 0; - - char *exe_path = (char *)str8f(scratch.arena, "/proc/%d/exe", pid).str; - int exe_fd = LNX_RETRY_ON_EINTR(open(exe_path, O_RDONLY)); - - if(exe_fd >= 0) - { - is_read = elf_read_ehdr(lnx_dmn_machine_op_mem_read, &exe_fd, 0, &exe); - LNX_RETRY_ON_EINTR(close(exe_fd)); - } - - Assert(is_read); - scratch_end(scratch); - return exe; -} - -internal LNX_DMN_Auxv -lnx_dmn_auxv_from_pid(pid_t pid, ELF_Class elf_class) -{ - Temp scratch = scratch_begin(0, 0); - LNX_DMN_Auxv result = {0}; - - // rjf: open aux data - String8 auxv_path = str8f(scratch.arena, "/proc/%d/auxv", pid); - int auxv_fd = LNX_RETRY_ON_EINTR(open((char *)auxv_path.str, O_RDONLY)); - - // rjf: scan aux data - if(auxv_fd >= 0) - { - for(;;) - { - // rjf: read next aux - ELF_Auxv64 auxv = {0}; - switch(elf_class) - { - case ELF_Class_None:{}break; - case ELF_Class_32: - { - ELF_Auxv32 auxv32 = {0}; - if(read(auxv_fd, &auxv32, sizeof(auxv32)) != sizeof(auxv32)) { goto brkloop; } - auxv = elf_auxv64_from_auxv32(auxv32); - }break; - case ELF_Class_64: - { - if(read(auxv_fd, &auxv, sizeof(auxv)) != sizeof(auxv)) { goto brkloop; } - }break; - default:{NotImplemented;}break; - } - - // rjf: fill result - switch(auxv.a_type) - { - default:{}break; - case ELF_AuxType_Null: goto brkloop; break; - case ELF_AuxType_Base: result.base = auxv.a_val; break; - case ELF_AuxType_Phnum: result.phnum = auxv.a_val; break; - case ELF_AuxType_Phent: result.phent = auxv.a_val; break; - case ELF_AuxType_Phdr: result.phdr = auxv.a_val; break; - case ELF_AuxType_ExecFn: result.execfn = auxv.a_val; break; - case ELF_AuxType_Pagesz: result.pagesz = auxv.a_val; break; - } - } - brkloop:; - LNX_RETRY_ON_EINTR(close(auxv_fd)); - } - - scratch_end(scratch); - return result; -} - internal LNX_DMN_Thread * lnx_dmn_thread_from_pid(pid_t tid) { @@ -535,132 +376,6 @@ lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 return result; } -internal LNX_DMN_ProbeList -lnx_dmn_read_probes(Arena *arena, int fd, U64 offset, U64 image_base) -{ - Temp scratch = scratch_begin(&arena, 1); - - LNX_DMN_ProbeList probes = {0}; - - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(lnx_dmn_machine_op_mem_read, &fd, offset, &ehdr) != MachineOpResult_Ok) { goto exit; } - - U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; - ELF_Shdr64 strtab_shdr = {0}; - if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr) != MachineOpResult_Ok) { goto exit; } - - B32 found_probes = 0; - B32 found_probes_base = 0; - ELF_Shdr64 text_shdr = {0}; - ELF_Shdr64 stapsdt_base_shdr = {0}; - ELF_Shdr64 stapsdt_shdr = {0}; - for(U64 shdr_off = offset + ehdr.e_shoff, shdr_opl = shdr_off + ehdr.e_shentsize * ehdr.e_shnum; - shdr_off < shdr_opl; - shdr_off += ehdr.e_shentsize) { - ELF_Shdr64 shdr = {0}; - if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr) != MachineOpResult_Ok) { goto exit; } - - if(shdr.sh_type == ELF_ShType_Note) - { - U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; - U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; - String8 name = lnx_dmn_read_string_capped(scratch.arena, fd, name_offset, name_cap); - - if(str8_match(name, str8_lit(".note.stapsdt"), 0)) - { - stapsdt_shdr = shdr; - found_probes = 1; - } - } - else if(shdr.sh_type == ELF_ShType_ProgBits) - { - U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; - U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; - String8 name = lnx_dmn_read_string_capped(scratch.arena, fd, name_offset, name_cap); - - if(str8_match(name, str8_lit(".stapsdt.base"), 0)) - { - stapsdt_base_shdr = shdr; - found_probes_base = 1; - } else if(str8_match(name, str8_lit(".text"), 0)) - { - text_shdr = shdr; - } - } - - if(found_probes && found_probes_base) { break; } - } - - if(!found_probes || !found_probes_base) { goto exit; } - - U64 probes_base = stapsdt_base_shdr.sh_addr; - - Rng1U64 note_range = shift_1u64(r1u64(stapsdt_shdr.sh_offset, stapsdt_shdr.sh_offset + stapsdt_shdr.sh_size), offset); - void *raw_note = push_array(arena, U8, stapsdt_shdr.sh_size); - U64 note_read_size = lnx_dmn_read(fd, note_range, raw_note); - if(note_read_size != dim_1u64(note_range)) { goto exit; } - - Arch arch = arch_from_elf_machine(ehdr.e_machine); - ELF_NoteList note = elf_parse_note(scratch.arena, str8(raw_note, dim_1u64(note_range)), ehdr.e_ident[ELF_Identifier_Class], ehdr.e_machine); - - for EachNode(n, ELF_NoteNode, note.first) - { - ELF_Note *note = &n->v; - if(!str8_match(note->owner, str8_lit("stapsdt"), 0)) { continue; } - if(note->type != ELF_NoteType_STapSdt) { continue; } - - LNX_DMN_Probe probe = {0}; - { - U64 cursor = 0; - U64 addr_size = ehdr.e_ident[ELF_Identifier_Class] == ELF_Class_64 ? 8 : 4; - - U64 pc = 0; - U64 pc_size = str8_deserial_read(note->desc, cursor, &pc, addr_size, addr_size); - if (pc_size == 0) { goto exit; } - cursor += pc_size; - - U64 base_addr = 0; - U64 base_addr_size = str8_deserial_read(note->desc, cursor, &base_addr, addr_size, addr_size); - if (base_addr_size == 0) { goto exit; } - cursor += base_addr_size; - - U64 semaphore = 0; - U64 semaphore_size = str8_deserial_read(note->desc, cursor, &semaphore, addr_size, addr_size); - if (semaphore_size == 0) { goto exit; } - cursor += semaphore_size; - - String8 provider = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += provider.size + 1; - if (cursor > note->desc.size) { goto exit; } - - String8 name = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += name.size + 1; - if (cursor > note->desc.size) { goto exit; } - - String8 args = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += args.size + 1; - if (cursor > note->desc.size) { goto exit; } - - U64 probe_rebase = image_base + (base_addr - probes_base); - - probe.provider = provider; - probe.name = name; - probe.args = stap_arg_array_from_string(arena, arch, args); - probe.pc = pc + probe_rebase; - probe.semaphore = semaphore ? semaphore + probe_rebase : 0; - } - - LNX_DMN_ProbeNode *n = push_array(arena, LNX_DMN_ProbeNode, 1); - n->v = probe; - SLLQueuePush(probes.first, probes.last, n); - probes.count += 1; - } - - exit:; - scratch_end(scratch); - return probes; -} - internal STAP_MEMORY_READ(lnx_dmn_stap_memory_read) { @@ -965,29 +680,17 @@ lnx_dmn_process_ctx_clone(LNX_DMN_Process *new_owner, LNX_DMN_ProcessCtx *ctx) // clone modules for EachNode(module, LNX_DMN_Module, ctx->first_module) { - lnx_dmn_module_clone(result, module); + LNX_DMN_Module *dst = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Module)->module; + MemoryCopyStruct(dst, module); + dst->next = dst->prev = 0; + hash_table_push_u64_raw(result->arena, result->loaded_modules_ht, dst->base_vaddr, dst); + DLLPushBack(result->first_module, result->last_module, dst); + result->module_count += 1; } return result; } -internal LNX_DMN_Module * -lnx_dmn_module_clone(LNX_DMN_ProcessCtx *process_ctx, LNX_DMN_Module *module) -{ - LNX_DMN_Module *result = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Module)->module; - *result = *module; - result->next = result->prev = 0; - - // clone base addr mapping - hash_table_push_u64_raw(process_ctx->arena, process_ctx->loaded_modules_ht, result->base_vaddr, result); - - // push module to the list - DLLPushBack(process_ctx->first_module, process_ctx->last_module, module); - process_ctx->module_count += 1; - - return result; -} - internal DMN_Handle lnx_dmn_handle_from_entity(LNX_DMN_Entity *entity) { @@ -1644,7 +1347,9 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX { LNX_DMN_Process *process = lnx_dmn_process_alloc(pid, LNX_DMN_ProcessState_Normal, parent_process, !!(flags & LNX_DMN_CreateProcessFlag_DebugSubprocesses), !!(flags & LNX_DMN_CreateProcessFlag_Cow)); + ////////////////////////////// //- rjf: get process context - create if this isn't cloning parent process + // LNX_DMN_ProcessCtx *ctx = 0; if(flags & LNX_DMN_CreateProcessFlag_ClonedMemory) { @@ -1652,21 +1357,107 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX } else { + //- rjf: allocate ctx ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; - ELF_Hdr64 exe_ehdr = lnx_dmn_ehdr_from_pid(process->pid); - LNX_DMN_Auxv auxv = lnx_dmn_auxv_from_pid(process->pid, exe_ehdr.e_ident[ELF_Identifier_Class]); - Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); - U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); - U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - Rng1U64 image_vrange = lnx_dmn_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); - Arena *ctx_arena = arena_alloc(); + //- rjf: read main executable ELF header + ELF_Hdr64 exe_ehdr = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + char *exe_path = (char *)str8f(scratch.arena, "/proc/%d/exe", pid).str; + int exe_fd = LNX_RETRY_ON_EINTR(open(exe_path, O_RDONLY)); + if(exe_fd >= 0) + { + U8 e_ident[ELF_Identifier_Max] = {0}; + lnx_dmn_read(exe_fd, r1u64(0, sizeof(e_ident)), e_ident); + if(MemoryMatch(e_ident, elf_magic_string.str, elf_magic_string.size)) + { + switch(e_ident[ELF_Identifier_Class]) + { + default:{}break; + case ELF_Class_None:{}break; + case ELF_Class_32: + { + ELF_Hdr32 ehdr32 = {0}; + lnx_dmn_read(exe_fd, r1u64(0, sizeof(ehdr32)), &ehdr32); + exe_ehdr = elf_hdr64_from_hdr32(ehdr32); + }break; + case ELF_Class_64: + { + lnx_dmn_read(exe_fd, r1u64(0, sizeof(exe_ehdr)), &exe_ehdr); + }break; + } + } + } + LNX_RETRY_ON_EINTR(close(exe_fd)); + scratch_end(scratch); + } + //- rjf: read auxv info for pid + LNX_DMN_Auxv auxv = {0}; + { + Temp scratch = scratch_begin(0, 0); + + // rjf: open aux data + String8 auxv_path = str8f(scratch.arena, "/proc/%d/auxv", pid); + int auxv_fd = LNX_RETRY_ON_EINTR(open((char *)auxv_path.str, O_RDONLY)); + + // rjf: scan aux data + if(auxv_fd >= 0) + { + for(U64 off = 0;;) + { + // rjf: read next aux + ELF_Auxv64 auxv_entry = {0}; + switch(exe_ehdr.e_ident[ELF_Identifier_Class]) + { + default: + case ELF_Class_None:{}break; + case ELF_Class_32: + { + ELF_Auxv32 auxv32 = {0}; + off += lnx_dmn_read_struct(auxv_fd, off, &auxv32); + auxv_entry = elf_auxv64_from_auxv32(auxv32); + }break; + case ELF_Class_64: + { + off += lnx_dmn_read_struct(auxv_fd, off, &auxv_entry); + }break; + } + + // rjf: fill result + switch(auxv_entry.a_type) + { + default:{}break; + case ELF_AuxType_Null: goto brkloop; break; + case ELF_AuxType_Base: auxv.base = auxv_entry.a_val; break; + case ELF_AuxType_Phnum: auxv.phnum = auxv_entry.a_val; break; + case ELF_AuxType_Phent: auxv.phent = auxv_entry.a_val; break; + case ELF_AuxType_Phdr: auxv.phdr = auxv_entry.a_val; break; + case ELF_AuxType_ExecFn: auxv.execfn = auxv_entry.a_val; break; + case ELF_AuxType_Pagesz: auxv.pagesz = auxv_entry.a_val; break; + } + } + brkloop:; + LNX_RETRY_ON_EINTR(close(auxv_fd)); + } + + scratch_end(scratch); + } + + //- rjf: unpack context + Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); + U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); + U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + Rng1U64 image_vrange = lnx_dmn_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); + Arena *ctx_arena = arena_alloc(); + + //- rjf: read dynamically loaded ELF header ELF_Hdr64 dl_ehdr = {0}; elf_read_ehdr(lnx_dmn_machine_op_mem_read, &process->fd, auxv.base, &dl_ehdr); ELF_Class dl_class = dl_ehdr.e_ident[ELF_Identifier_Class]; - // rjf: compute rdebug vaddr + //- rjf: compute rdebug vaddr U64 rdebug_vaddr = 0; { Temp scratch = scratch_begin(0, 0); @@ -1841,9 +1632,9 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX scratch_end(scratch); } - // query xsave layout - U64 xcr0 = 0; - U64 xsave_size = 0; + //- query xsave layout + U64 xcr0 = 0; + U64 xsave_size = 0; X64_XSaveLayout xsave_layout = {0}; if(arch == Arch_x64) { @@ -1852,11 +1643,14 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX { // Linux stores xcr0 bits in fxstate padding, // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 - xcr0 = *(U64 *)((U8 *)&xsave + 464); - xsave_size = x64_get_xsave_size(); + xcr0 = *(U64 *)((U8 *)&xsave + 464); + xsave_size = x64_get_xsave_size(); xsave_layout = x64_get_xsave_layout(xcr0); } - else { Assert(0 && "failed to get xstate"); } + else + { + Assert(0 && "failed to get xstate"); + } } // gather probes @@ -1864,16 +1658,217 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX { Temp scratch = scratch_begin(0, 0); - String8 dl_path = lnx_dmn_dl_path_from_pid(scratch.arena, process->pid, auxv.base); - int dl_fd = LNX_RETRY_ON_EINTR(open((char *)dl_path.str, O_RDONLY)); + // rjf: get dl path for this pid + String8 dl_path = {0}; + { + int maps_fd = LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/maps", pid).str, O_RDONLY)); + if(maps_fd != -1) + { + // rjf: determine how big /proc/pid/maps is (this is a "special file", so we have to compute the size + // by reading the whole thing - very very good + // + // TODO(rjf): probably just read it once into a list & join? + // + U64 maps_size = 0; + { + U8 temp[4096]; + size_t cap = MB(1); + size_t cursor = 0; + for(;cursor < cap;) + { + ssize_t actual_read = LNX_RETRY_ON_EINTR(pread(maps_fd, temp, sizeof(temp), cursor)); + if(actual_read <= 0) { break; } + cursor += (U64)actual_read; + } + maps_size = (U64)cursor; + } + + // rjf: read entire /proc/pid/maps + U8 *maps_ptr = push_array(scratch.arena, U8, maps_size); + U64 read_size = lnx_dmn_read(maps_fd, r1u64(0, maps_size), maps_ptr); + + // split map file on lines + String8List lines = str8_split_by_string_chars(scratch.arena, str8(maps_ptr, maps_size), str8_lit("\n"), 0); + + // scan each line until a virtual mapping whose low part matches the DL base address is found + for EachNode(n, String8Node, lines.first) + { + String8 line = n->string; + + // split the string while respecting escape sequences + String8List parts = {0}; + for EachIndex(cursor, line.size) + { + if(parts.node_count > 5) { break; } + if(line.str[cursor] == ' ') { continue; } + + // scan forward to the closing delimiter + U64 token_start = cursor; + for(; cursor < line.size; cursor += 1) + { + if(line.str[cursor] == '\\') + { + cursor += 1; + continue; + } + if(line.str[cursor] == ' ') { break; } + } + + // push sub-string to the list + str8_list_push(scratch.arena, &parts, str8_substr(line, r1u64(token_start, cursor))); + } + + // was line parsed correctly? + if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; } + + // parse map virtual range + String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); + if(vaddr_list.node_count != 2) { Assert(0 && "failed to parse virtual range portion of map line"); continue; } + + // does the low part match DL base address? + U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16); + if(lo_vaddr == auxv.base) + { + dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; + dl_path = push_str8_copy(arena, dl_path); + break; + } + } + + LNX_RETRY_ON_EINTR(close(maps_fd)); + } + else + { + Assert(0 && "failed to open DL fd"); + } + Assert(dl_path.size); + } + // rjf: read probes for this dl + int dl_fd = LNX_RETRY_ON_EINTR(open((char *)dl_path.str, O_RDONLY)); LNX_DMN_ProbeList probes = {0}; if(dl_fd >= 0) { - probes = lnx_dmn_read_probes(ctx_arena, dl_fd, 0, auxv.base); + U64 offset = 0; + ELF_Hdr64 ehdr = {0}; + if(elf_read_ehdr(lnx_dmn_machine_op_mem_read, &dl_fd, offset, &ehdr) != MachineOpResult_Ok) { goto exit; } + + U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; + ELF_Shdr64 strtab_shdr = {0}; + if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &dl_fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr) != MachineOpResult_Ok) { goto exit; } + + B32 found_probes = 0; + B32 found_probes_base = 0; + ELF_Shdr64 text_shdr = {0}; + ELF_Shdr64 stapsdt_base_shdr = {0}; + ELF_Shdr64 stapsdt_shdr = {0}; + for(U64 shdr_off = offset + ehdr.e_shoff, shdr_opl = shdr_off + ehdr.e_shentsize * ehdr.e_shnum; + shdr_off < shdr_opl; + shdr_off += ehdr.e_shentsize) { + ELF_Shdr64 shdr = {0}; + if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &dl_fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr) != MachineOpResult_Ok) { goto exit; } + + if(shdr.sh_type == ELF_ShType_Note) + { + U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; + U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; + String8 name = lnx_dmn_read_string_capped(scratch.arena, dl_fd, name_offset, name_cap); + + if(str8_match(name, str8_lit(".note.stapsdt"), 0)) + { + stapsdt_shdr = shdr; + found_probes = 1; + } + } + else if(shdr.sh_type == ELF_ShType_ProgBits) + { + U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; + U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; + String8 name = lnx_dmn_read_string_capped(scratch.arena, dl_fd, name_offset, name_cap); + + if(str8_match(name, str8_lit(".stapsdt.base"), 0)) + { + stapsdt_base_shdr = shdr; + found_probes_base = 1; + } else if(str8_match(name, str8_lit(".text"), 0)) + { + text_shdr = shdr; + } + } + + if(found_probes && found_probes_base) { break; } + } + + if(!found_probes || !found_probes_base) { goto exit; } + + U64 probes_base = stapsdt_base_shdr.sh_addr; + + Rng1U64 note_range = shift_1u64(r1u64(stapsdt_shdr.sh_offset, stapsdt_shdr.sh_offset + stapsdt_shdr.sh_size), offset); + void *raw_note = push_array(arena, U8, stapsdt_shdr.sh_size); + U64 note_read_size = lnx_dmn_read(dl_fd, note_range, raw_note); + if(note_read_size != dim_1u64(note_range)) { goto exit; } + + Arch arch = arch_from_elf_machine(ehdr.e_machine); + ELF_NoteList note = elf_parse_note(scratch.arena, str8(raw_note, dim_1u64(note_range)), ehdr.e_ident[ELF_Identifier_Class], ehdr.e_machine); + + for EachNode(n, ELF_NoteNode, note.first) + { + ELF_Note *note = &n->v; + if(!str8_match(note->owner, str8_lit("stapsdt"), 0)) { continue; } + if(note->type != ELF_NoteType_STapSdt) { continue; } + + LNX_DMN_Probe probe = {0}; + { + U64 cursor = 0; + U64 addr_size = ehdr.e_ident[ELF_Identifier_Class] == ELF_Class_64 ? 8 : 4; + + U64 pc = 0; + U64 pc_size = str8_deserial_read(note->desc, cursor, &pc, addr_size, addr_size); + if (pc_size == 0) { goto exit; } + cursor += pc_size; + + U64 base_addr = 0; + U64 base_addr_size = str8_deserial_read(note->desc, cursor, &base_addr, addr_size, addr_size); + if (base_addr_size == 0) { goto exit; } + cursor += base_addr_size; + + U64 semaphore = 0; + U64 semaphore_size = str8_deserial_read(note->desc, cursor, &semaphore, addr_size, addr_size); + if (semaphore_size == 0) { goto exit; } + cursor += semaphore_size; + + String8 provider = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += provider.size + 1; + if (cursor > note->desc.size) { goto exit; } + + String8 name = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += name.size + 1; + if (cursor > note->desc.size) { goto exit; } + + String8 args = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += args.size + 1; + if (cursor > note->desc.size) { goto exit; } + + U64 probe_rebase = auxv.base + (base_addr - probes_base); + + probe.provider = provider; + probe.name = name; + probe.args = stap_arg_array_from_string(arena, arch, args); + probe.pc = pc + probe_rebase; + probe.semaphore = semaphore ? semaphore + probe_rebase : 0; + } + + LNX_DMN_ProbeNode *n = push_array(arena, LNX_DMN_ProbeNode, 1); + n->v = probe; + SLLQueuePush(probes.first, probes.last, n); + probes.count += 1; + } + + exit:; LNX_RETRY_ON_EINTR(close(dl_fd)); } + // rjf: store probes for EachNode(n, LNX_DMN_ProbeNode, probes.first) { LNX_DMN_Probe *p = &n->v; @@ -1893,8 +1888,9 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX scratch_end(scratch); } + //- rjf: build context ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; - ctx->arena = arena_alloc(); + ctx->arena = ctx_arena; ctx->arch = arch; ctx->rdebug_vaddr = rdebug_vaddr; ctx->dl_class = dl_class; @@ -1904,18 +1900,22 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); ctx->xsave_layout = xsave_layout; - // create main module + //- create main module LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, process->fd, base_vaddr, auxv.execfn, 1, 1); - // glibc has a shortcut mapping for the main module + //- glibc has a shortcut mapping for the main module hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); } + ////////////////////////////// //- rjf: set process' context + // process->ctx = ctx; process->ctx->ref_count += 1; - //- rjf: install probes - only in processes without cloned memory + ////////////////////////////// + //- rjf: install probe traps - only needed in processes without cloned memory + // if(!(flags & LNX_DMN_CreateProcessFlag_ClonedMemory)) { for EachNonZeroEnumVal(LNX_DMN_ProbeKind, k) @@ -1930,10 +1930,14 @@ lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX } } - // create main thread + ////////////////////////////// + //- rjf: create main thread + // lnx_dmn_thread_alloc(process, LNX_DMN_ThreadState_Stopped, pid); - // push events + ////////////////////////////// + //- rjf: push events + // { DMN_Event *e = dmn_event_list_push(arena, events); e->kind = DMN_EventKind_CreateProcess; @@ -1964,7 +1968,6 @@ internal void lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr) { LNX_DMN_Process *process = thread->process; - GNU_LinkMap64 map = {0}; for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr) { diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index b152fb10..dd2c97ae 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -357,7 +357,7 @@ struct LNX_DMN_State // rjf: access locking mechanism Mutex access_mutex; - B32 access_run_state; + B32 access_run_state; // rjf: entity storage Arena *entities_arena; @@ -399,7 +399,6 @@ thread_static B32 lnx_dmn_ctrl_thread = 0; //////////////////////////////// //~ rjf: Memory R/W Helpers -internal U64 lnx_dmn_size_from_fd(int memory_fd, U64 cap); internal U64 lnx_dmn_read(int memory_fd, Rng1U64 range, void *dst); internal B32 lnx_dmn_write(int memory_fd, Rng1U64 range, void *src); internal String8 lnx_dmn_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size); @@ -420,10 +419,6 @@ internal Rng1U64 lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class //////////////////////////////// //~ rjf: Process Info Parsing Functions -internal String8 lnx_dmn_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base); -internal ELF_Hdr64 lnx_dmn_ehdr_from_pid(pid_t pid); -internal LNX_DMN_Auxv lnx_dmn_auxv_from_pid(pid_t pid, ELF_Class elf_class); - internal LNX_DMN_Thread * lnx_dmn_thread_from_pid(pid_t pid); internal LNX_DMN_Process * lnx_dmn_process_from_pid(pid_t pid); @@ -449,7 +444,6 @@ internal void lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_Module *mo // clone internal LNX_DMN_ProcessCtx * lnx_dmn_process_ctx_clone(LNX_DMN_Process *process, LNX_DMN_ProcessCtx *ctx); -internal LNX_DMN_Module * lnx_dmn_module_clone(LNX_DMN_ProcessCtx *process_ctx, LNX_DMN_Module *module); // entity -> handle internal DMN_Handle lnx_dmn_handle_from_entity(LNX_DMN_Entity *entity); diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index 6944e420..8e3d6fb0 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -1472,7 +1472,6 @@ dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, error, MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), (LPWSTR)&message, 0, 0); String8 message8 = message ? str8_from_16(scratch.arena, str16_cstring(message)) : str8_lit("unknown error"); LocalFree(message); - log_user_errorf("There was an error starting %S: %S", params->cmd_line.first->string, message8); } From 05de22df76d0c498e2c94e652bd92f2547767b03 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 7 Aug 2026 14:43:15 -0600 Subject: [PATCH 024/251] linux/demon: eliminate unneeded fields, h/c matching, reading etc. --- src/linux/demon/linux_demon.c | 82 ++++++++++++++++++++--------------- src/linux/demon/linux_demon.h | 61 ++++++++++++-------------- 2 files changed, 75 insertions(+), 68 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index bf76be60..7e7f38d0 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -87,18 +87,6 @@ lnx_dmn_ptrace_seize(pid_t pid) return LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE)); } -internal LNX_DMN_Thread * -lnx_dmn_thread_from_pid(pid_t tid) -{ - return hash_table_search_u64_raw(lnx_dmn_state->tid_ht, tid); -} - -internal LNX_DMN_Process * -lnx_dmn_process_from_pid(pid_t pid) -{ - return hash_table_search_u64_raw(lnx_dmn_state->pid_ht, pid); -} - //////////////////////////////// //~ rjf: Module Info Parsing @@ -351,6 +339,9 @@ lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap) return result; } +//////////////////////////////// +//~ rjf: ELF/GNU Parsing + internal Rng1U64 lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) { @@ -376,13 +367,10 @@ lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 return result; } -internal -STAP_MEMORY_READ(lnx_dmn_stap_memory_read) -{ - LNX_DMN_Process *process = raw_ctx; - U64 bytes_read = lnx_dmn_read(process->fd, r1u64(addr, addr + read_size), buffer); - return bytes_read == read_size; -} +//////////////////////////////// +//~ rjf: Entity Functions + +//- rjf: base allocation / deallocation internal LNX_DMN_Entity * lnx_dmn_entity_alloc(LNX_DMN_EntityKind kind) @@ -403,6 +391,17 @@ lnx_dmn_entity_alloc(LNX_DMN_EntityKind kind) return entity; } +internal void +lnx_dmn_entity_release(LNX_DMN_Entity *entity) +{ + U32 gen = entity->gen + 1; + MemoryZeroStruct(entity); + entity->gen = gen; + SLLStackPush(lnx_dmn_state->free_entity, entity); +} + +//- rjf: specialized allocation / deallocation helpers + internal LNX_DMN_Process * lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *parent_process, B32 debug_subprocesses, B32 is_cow) { @@ -518,9 +517,6 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 module->name_vaddr = name_vaddr; module->name_space_id = name_space_id; module->size = dim_1u64(module_vrange); - module->phvaddr = base_vaddr + module_ehdr.e_phoff; - module->phcount = module_ehdr.e_phnum; - module->phentsize = module_ehdr.e_phentsize; module->tls_index = tls_index; module->tls_offset = tls_offset; module->is_main = is_main; @@ -536,15 +532,6 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 return module; } -internal void -lnx_dmn_entity_release(LNX_DMN_Entity *entity) -{ - U32 gen = entity->gen + 1; - MemoryZeroStruct(entity); - entity->gen = gen; - SLLStackPush(lnx_dmn_state->free_entity, entity); -} - internal void lnx_dmn_process_release(LNX_DMN_Process *process) { @@ -631,6 +618,8 @@ lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_Module *module) lnx_dmn_entity_release((LNX_DMN_Entity *)module); } +//- rjf: context cloning + internal LNX_DMN_ProcessCtx * lnx_dmn_process_ctx_clone(LNX_DMN_Process *new_owner, LNX_DMN_ProcessCtx *ctx) { @@ -691,6 +680,8 @@ lnx_dmn_process_ctx_clone(LNX_DMN_Process *new_owner, LNX_DMN_ProcessCtx *ctx) return result; } +//- rjf: entity <-> handle + internal DMN_Handle lnx_dmn_handle_from_entity(LNX_DMN_Entity *entity) { @@ -731,7 +722,7 @@ lnx_dmn_entity_from_handle(DMN_Handle handle, LNX_DMN_EntityKind expected_kind) { LNX_DMN_Entity *result = 0; U32 index = handle.u32[0]; - U32 gen = handle.u32[1]; + U32 gen = handle.u32[1]; if(index < lnx_dmn_state->entities_count && lnx_dmn_state->entities_base[index].gen == gen) { if(lnx_dmn_state->entities_base[index].kind == expected_kind) @@ -760,6 +751,23 @@ lnx_dmn_module_from_handle(DMN_Handle module_handle) return (LNX_DMN_Module *)lnx_dmn_entity_from_handle(module_handle, LNX_DMN_EntityKind_Module); } +//- rjf: entity <-> pid + +internal LNX_DMN_Thread * +lnx_dmn_thread_from_pid(pid_t tid) +{ + return hash_table_search_u64_raw(lnx_dmn_state->tid_ht, tid); +} + +internal LNX_DMN_Process * +lnx_dmn_process_from_pid(pid_t pid) +{ + return hash_table_search_u64_raw(lnx_dmn_state->pid_ht, pid); +} + +//////////////////////////////// +//~ rjf: Thread Helpers + internal U64 lnx_dmn_thread_read_ip(LNX_DMN_Thread *thread) { @@ -2182,6 +2190,14 @@ dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process_handle) return result; } +internal +STAP_MEMORY_READ(lnx_dmn_stap_memory_read) +{ + LNX_DMN_Process *process = raw_ctx; + U64 bytes_read = lnx_dmn_read(process->fd, r1u64(addr, addr + read_size), buffer); + return bytes_read == read_size; +} + internal DMN_EventList dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { @@ -2823,7 +2839,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // case PTRACE_EVENT_EXEC: { - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); LNX_DMN_Process *process = thread->process; if(process->debug_subprocesses) { @@ -2860,7 +2875,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // case PTRACE_EVENT_STOP: { - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); if(thread->state == LNX_DMN_ThreadState_PendingCreation) { LNX_DMN_Process *process = thread->process; diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index dd2c97ae..b69553df 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -206,9 +206,6 @@ struct LNX_DMN_Module U64 base_vaddr; U64 name_space_id; U64 size; - U64 phvaddr; - U64 phentsize; - U64 phcount; U64 tls_index; U64 tls_offset; B8 is_live; @@ -406,59 +403,55 @@ internal String8 lnx_dmn_read_string(Arena *arena, int memory_fd, U64 base_vaddr #define lnx_dmn_read_struct(fd, vaddr, ptr) lnx_dmn_read((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) #define lnx_dmn_write_struct(fd, vaddr, ptr) lnx_dmn_write((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) -//////////////////////////////// -//~ rjf: Trap Setting - -internal LNX_DMN_ActiveTrap *lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap); - -//////////////////////////////// -//~ ELF/GNU info - -internal Rng1U64 lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); - -//////////////////////////////// -//~ rjf: Process Info Parsing Functions - -internal LNX_DMN_Thread * lnx_dmn_thread_from_pid(pid_t pid); -internal LNX_DMN_Process * lnx_dmn_process_from_pid(pid_t pid); - //////////////////////////////// //~ rjf: Module Info Parsing internal DMN_ModuleInfo *lnx_dmn_module_info_from_process_module(Arena *arena, pid_t pid, int memory_fd, U64 base_vaddr, U64 link_map_vaddr, B32 is_main); +//////////////////////////////// +//~ rjf: Trap Setting + +internal LNX_DMN_ActiveTrap *lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap); + +//////////////////////////////// +//~ rjf: ELF/GNU Parsing + +internal Rng1U64 lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); + //////////////////////////////// //~ rjf: Entity Functions -// alloc -internal LNX_DMN_Entity * lnx_dmn_entity_alloc(LNX_DMN_EntityKind kind); -internal LNX_DMN_Process * lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *parent_process, B32 debug_subprocess, B32 is_cow); -internal LNX_DMN_Thread * lnx_dmn_thread_alloc(LNX_DMN_Process *process, LNX_DMN_ThreadState thread_state, pid_t tid); -internal LNX_DMN_Module * lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main); - -// release +//- rjf: base allocation / deallocation +internal LNX_DMN_Entity *lnx_dmn_entity_alloc(LNX_DMN_EntityKind kind); internal void lnx_dmn_entity_release(LNX_DMN_Entity *entity); + +//- rjf: specialized allocation / deallocation helpers +internal LNX_DMN_Process *lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *parent_process, B32 debug_subprocess, B32 is_cow); +internal LNX_DMN_Thread *lnx_dmn_thread_alloc(LNX_DMN_Process *process, LNX_DMN_ThreadState thread_state, pid_t tid); +internal LNX_DMN_Module *lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main); internal void lnx_dmn_process_release(LNX_DMN_Process *process); internal void lnx_dmn_thread_release(LNX_DMN_Thread *thread); internal void lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_Module *module); -// clone +//- rjf: context cloning internal LNX_DMN_ProcessCtx * lnx_dmn_process_ctx_clone(LNX_DMN_Process *process, LNX_DMN_ProcessCtx *ctx); -// entity -> handle +//- rjf: entity <-> handle internal DMN_Handle lnx_dmn_handle_from_entity(LNX_DMN_Entity *entity); internal DMN_Handle lnx_dmn_handle_from_process(LNX_DMN_Process *process); internal DMN_Handle lnx_dmn_handle_from_thread(LNX_DMN_Thread *thread); internal DMN_Handle lnx_dmn_handle_from_module(LNX_DMN_Module *module); +internal LNX_DMN_Entity *lnx_dmn_entity_from_handle(DMN_Handle handle, LNX_DMN_EntityKind expected_kind); +internal LNX_DMN_Process *lnx_dmn_process_from_handle(DMN_Handle process_handle); +internal LNX_DMN_Thread *lnx_dmn_thread_from_handle(DMN_Handle thread_handle); +internal LNX_DMN_Module *lnx_dmn_module_from_handle(DMN_Handle module_handle); -// handle -> entity -internal LNX_DMN_Entity * lnx_dmn_entity_from_handle(DMN_Handle handle, LNX_DMN_EntityKind expected_kind); -internal LNX_DMN_Process * lnx_dmn_process_from_handle(DMN_Handle process_handle); -internal LNX_DMN_Thread * lnx_dmn_thread_from_handle(DMN_Handle thread_handle); -internal LNX_DMN_Module * lnx_dmn_module_from_handle(DMN_Handle module_handle); +//- rjf: entity <-> pid +internal LNX_DMN_Thread *lnx_dmn_thread_from_pid(pid_t pid); +internal LNX_DMN_Process *lnx_dmn_process_from_pid(pid_t pid); //////////////////////////////// -//~ Thread Helpers +//~ rjf: Thread Helpers internal U64 lnx_dmn_thread_read_ip(LNX_DMN_Thread *thread); internal void lnx_dmn_thread_write_ip(LNX_DMN_Thread *thread, U64 ip); From d622fdf4fd742e23dfdd12a7164a141ce5fd86a8 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sat, 8 Aug 2026 21:04:22 -0700 Subject: [PATCH 025/251] eliminate helpers for thread/process exits & process creation - just pull them out into single spot in the signal handling loop --- src/linux/demon/linux_demon.c | 1477 +++++++++++++++++---------------- src/linux/demon/linux_demon.h | 2 - 2 files changed, 747 insertions(+), 732 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 7e7f38d0..3fc457ae 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1304,674 +1304,6 @@ lnx_dmn_push_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Pr //////////////////////////////// //~ Debug Event -internal void -lnx_dmn_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code) -{ - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(tid); - LNX_DMN_Process *process = thread->process; - - // store main thread's exit code - if(thread->tid == thread->process->pid) - { - thread->process->main_thread_exit_code = exit_code; - } - - // push exit event - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_ExitThread; - e->process = lnx_dmn_handle_from_process(thread->process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->code = exit_code; - } - - // release entity - lnx_dmn_thread_release(thread); - - // auto exit process on last thread - if(process->thread_count == 0) - { - // push module events - for EachNode(module, LNX_DMN_Module, process->ctx->first_module) - { - lnx_dmn_push_event_unload_module(arena, events, process, module); - } - - // push process exit event - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_ExitProcess; - e->process = lnx_dmn_handle_from_process(process); - e->code = process->main_thread_exit_code; - } - - // release process - lnx_dmn_process_release(process); - } -} - -internal LNX_DMN_Process * -lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX_DMN_Process *parent_process, LNX_DMN_CreateProcessFlags flags) -{ - LNX_DMN_Process *process = lnx_dmn_process_alloc(pid, LNX_DMN_ProcessState_Normal, parent_process, !!(flags & LNX_DMN_CreateProcessFlag_DebugSubprocesses), !!(flags & LNX_DMN_CreateProcessFlag_Cow)); - - ////////////////////////////// - //- rjf: get process context - create if this isn't cloning parent process - // - LNX_DMN_ProcessCtx *ctx = 0; - if(flags & LNX_DMN_CreateProcessFlag_ClonedMemory) - { - ctx = parent_process->ctx; - } - else - { - //- rjf: allocate ctx - ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; - - //- rjf: read main executable ELF header - ELF_Hdr64 exe_ehdr = {0}; - { - Temp scratch = scratch_begin(&arena, 1); - char *exe_path = (char *)str8f(scratch.arena, "/proc/%d/exe", pid).str; - int exe_fd = LNX_RETRY_ON_EINTR(open(exe_path, O_RDONLY)); - if(exe_fd >= 0) - { - U8 e_ident[ELF_Identifier_Max] = {0}; - lnx_dmn_read(exe_fd, r1u64(0, sizeof(e_ident)), e_ident); - if(MemoryMatch(e_ident, elf_magic_string.str, elf_magic_string.size)) - { - switch(e_ident[ELF_Identifier_Class]) - { - default:{}break; - case ELF_Class_None:{}break; - case ELF_Class_32: - { - ELF_Hdr32 ehdr32 = {0}; - lnx_dmn_read(exe_fd, r1u64(0, sizeof(ehdr32)), &ehdr32); - exe_ehdr = elf_hdr64_from_hdr32(ehdr32); - }break; - case ELF_Class_64: - { - lnx_dmn_read(exe_fd, r1u64(0, sizeof(exe_ehdr)), &exe_ehdr); - }break; - } - } - } - LNX_RETRY_ON_EINTR(close(exe_fd)); - scratch_end(scratch); - } - - //- rjf: read auxv info for pid - LNX_DMN_Auxv auxv = {0}; - { - Temp scratch = scratch_begin(0, 0); - - // rjf: open aux data - String8 auxv_path = str8f(scratch.arena, "/proc/%d/auxv", pid); - int auxv_fd = LNX_RETRY_ON_EINTR(open((char *)auxv_path.str, O_RDONLY)); - - // rjf: scan aux data - if(auxv_fd >= 0) - { - for(U64 off = 0;;) - { - // rjf: read next aux - ELF_Auxv64 auxv_entry = {0}; - switch(exe_ehdr.e_ident[ELF_Identifier_Class]) - { - default: - case ELF_Class_None:{}break; - case ELF_Class_32: - { - ELF_Auxv32 auxv32 = {0}; - off += lnx_dmn_read_struct(auxv_fd, off, &auxv32); - auxv_entry = elf_auxv64_from_auxv32(auxv32); - }break; - case ELF_Class_64: - { - off += lnx_dmn_read_struct(auxv_fd, off, &auxv_entry); - }break; - } - - // rjf: fill result - switch(auxv_entry.a_type) - { - default:{}break; - case ELF_AuxType_Null: goto brkloop; break; - case ELF_AuxType_Base: auxv.base = auxv_entry.a_val; break; - case ELF_AuxType_Phnum: auxv.phnum = auxv_entry.a_val; break; - case ELF_AuxType_Phent: auxv.phent = auxv_entry.a_val; break; - case ELF_AuxType_Phdr: auxv.phdr = auxv_entry.a_val; break; - case ELF_AuxType_ExecFn: auxv.execfn = auxv_entry.a_val; break; - case ELF_AuxType_Pagesz: auxv.pagesz = auxv_entry.a_val; break; - } - } - brkloop:; - LNX_RETRY_ON_EINTR(close(auxv_fd)); - } - - scratch_end(scratch); - } - - //- rjf: unpack context - Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); - U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); - U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - Rng1U64 image_vrange = lnx_dmn_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); - Arena *ctx_arena = arena_alloc(); - - //- rjf: read dynamically loaded ELF header - ELF_Hdr64 dl_ehdr = {0}; - elf_read_ehdr(lnx_dmn_machine_op_mem_read, &process->fd, auxv.base, &dl_ehdr); - ELF_Class dl_class = dl_ehdr.e_ident[ELF_Identifier_Class]; - - //- rjf: compute rdebug vaddr - U64 rdebug_vaddr = 0; - { - Temp scratch = scratch_begin(0, 0); - - // load DL's header - ELF_Hdr64 ehdr = {0}; - elf_read_ehdr(lnx_dmn_machine_op_mem_read, &process->fd, auxv.base, &ehdr); - - U64 rebase = dl_ehdr.e_type == ELF_Type_Dyn ? auxv.base : 0; - ELF_Class elf_class = ehdr.e_ident[ELF_Identifier_Class]; - - // rjf: find dynamic program header - U64 dynamic_vaddr = 0; - { - U64 phdr_vaddr = auxv.base + dl_ehdr.e_phoff; - for(U64 ph_cursor = phdr_vaddr, ph_opl = (phdr_vaddr + ehdr.e_phentsize * ehdr.e_phentsize); ph_cursor < ph_opl; ph_cursor += ehdr.e_phentsize) - { - ELF_Phdr64 phdr = {0}; - elf_read_phdr(lnx_dmn_machine_op_mem_read, &process->fd, ph_cursor, elf_class, &phdr); - if(phdr.p_type == ELF_PType_Dynamic) - { - dynamic_vaddr = rebase + phdr.p_vaddr; - break; - } - } - } - - // rjf: get dynamic info for this module - // extract necessary info out of dynamic program header - LNX_DMN_DynamicInfo dynamic_info = {0}; - { - B32 is_rebased = !!(flags & LNX_DMN_CreateProcessFlag_Rebased); - U64 dynamic_info_rebase = is_rebased ? 0 : rebase; - for(U64 dynamic_cursor = dynamic_vaddr; ; dynamic_cursor += elf_dyn_size_from_class(elf_class)) - { - // rjf: read next dyn entry - ELF_Dyn64 dyn = {0}; - if(elf_read_dyn(lnx_dmn_machine_op_mem_read, &process->fd, dynamic_cursor, elf_class, &dyn) != MachineOpResult_Ok) { Assert(0 && "unable to read dynamic"); } - - // rjf: break on zero - if(dyn.tag == ELF_DynTag_Null) { break; } - - // rjf: extract info from dynamic section - switch(dyn.tag) - { - default:{}break; - case ELF_DynTag_Strtab: {dynamic_info.strtab_vaddr = dynamic_info_rebase + dyn.val;}break; - case ELF_DynTag_Strsz: {dynamic_info.strtab_size = dyn.val;}break; - case ELF_DynTag_Symtab: {dynamic_info.symtab_vaddr = dynamic_info_rebase + dyn.val;}break; - case ELF_DynTag_Syment: {dynamic_info.symtab_entry_size = dyn.val;}break; - case ELF_DynTag_Hash: {dynamic_info.hash_vaddr = dynamic_info_rebase + dyn.val;}break; - case ELF_DynTag_GNU_Hash: {dynamic_info.gnu_hash_vaddr = dynamic_info_rebase + dyn.val;}break; - } - } - } - - // extract symbol table count from available options - U64 symbol_count = 0; - if(dynamic_info.hash_vaddr) - { - U64 hash_entry_size = 4; - if(elf_class == ELF_Class_64 && (ehdr.e_machine == ELF_MachineKind_ALPHA || ehdr.e_machine == ELF_MachineKind_S390 || ehdr.e_machine == ELF_MachineKind_S390_OLD)) - { - hash_entry_size = 8; - } - - U64 chain_count = 0; - if(lnx_dmn_read(process->fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size) - { - symbol_count = chain_count; - } - else - { - Assert(0 && "failed to read hash table's chain count out of HASH"); - } - } - else if(dynamic_info.gnu_hash_vaddr) - { - // GNU_HASH header: u32 nbuckets, u32 symoffset, u32 bloom_size, u32 bloom_shift, - // followed by bloom[bloom_size] (word size = 4 on 32-bit ELF, 8 on 64-bit), - // buckets[nbuckets] (u32), chains[...] (u32, terminated by entry w/ LSB set). - U32 gnu_header[4] = {0}; - if(lnx_dmn_read(process->fd, r1u64(dynamic_info.gnu_hash_vaddr, dynamic_info.gnu_hash_vaddr + sizeof(gnu_header)), gnu_header) == sizeof(gnu_header)) - { - U32 nbuckets = gnu_header[0]; - U32 symoffset = gnu_header[1]; - U32 bloom_size = gnu_header[2]; - U64 bloom_word = (elf_class == ELF_Class_64) ? 8 : 4; - U64 buckets_vaddr = dynamic_info.gnu_hash_vaddr + sizeof(gnu_header) + bloom_size * bloom_word; - U64 chain_vaddr = buckets_vaddr + (U64)nbuckets * sizeof(U32); - - // find largest symbol index referenced by any bucket - U32 max_bucket = 0; - for(U32 b = 0; b < nbuckets; b += 1) - { - U32 entry = 0; - if(lnx_dmn_read(process->fd, r1u64(buckets_vaddr + b*sizeof(U32), buckets_vaddr + (b+1)*sizeof(U32)), &entry) != sizeof(U32)) - { - break; - } - if(entry > max_bucket) { max_bucket = entry; } - } - - if(max_bucket < symoffset) - { - // no symbols in hash; total count is symoffset - symbol_count = symoffset; - } - else - { - // walk chain from max_bucket until terminator (entry w/ LSB set) - U64 idx = max_bucket; - for(;;) - { - U32 chain_entry = 0; - U64 entry_vaddr = chain_vaddr + (idx - symoffset) * sizeof(U32); - if(lnx_dmn_read(process->fd, r1u64(entry_vaddr, entry_vaddr + sizeof(U32)), &chain_entry) != sizeof(U32)) - { - break; - } - if(chain_entry & 1) - { - symbol_count = idx + 1; - break; - } - idx += 1; - if(idx - max_bucket > (1u<<24)) - { - // sanity bound: don't loop forever on a corrupt chain - break; - } - } - } - } - } - - // scan symbol table for the rendezvous symbol - if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count) - { - for EachIndex(symbol_idx, symbol_count) - { - ELF_Sym64 symbol = {0}; - if(elf_read_symbol(lnx_dmn_machine_op_mem_read, &process->fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol) != MachineOpResult_Ok) - { - Assert(0 && "failed to read symbol table"); - break; - } - - Temp temp = temp_begin(scratch.arena); - - String8 symbol_name = {0}; - if(symbol.st_name < dynamic_info.strtab_size) - { - U64 cap = dynamic_info.strtab_size - symbol.st_name; - symbol_name = lnx_dmn_read_string_capped(temp.arena, process->fd, dynamic_info.strtab_vaddr + symbol.st_name, cap); - } - - if(str8_match(symbol_name, str8_lit("_r_debug"), 0)) - { - ELF_SymType symbol_type = ELF_ST_TYPE(symbol.st_info); - if(symbol_type == ELF_SymType_Object && symbol.st_size > 0) - { - rdebug_vaddr = rebase + symbol.st_value; - break; - } - } - - temp_end(temp); - } - } - - scratch_end(scratch); - } - - //- query xsave layout - U64 xcr0 = 0; - U64 xsave_size = 0; - X64_XSaveLayout xsave_layout = {0}; - if(arch == Arch_x64) - { - X64_XSave xsave = {0}; - if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, process->pid, (void *)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)) - { - // Linux stores xcr0 bits in fxstate padding, - // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 - xcr0 = *(U64 *)((U8 *)&xsave + 464); - xsave_size = x64_get_xsave_size(); - xsave_layout = x64_get_xsave_layout(xcr0); - } - else - { - Assert(0 && "failed to get xstate"); - } - } - - // gather probes - LNX_DMN_Probe **known_probes = push_array(ctx_arena, LNX_DMN_Probe *, LNX_DMN_ProbeKind_COUNT); - { - Temp scratch = scratch_begin(0, 0); - - // rjf: get dl path for this pid - String8 dl_path = {0}; - { - int maps_fd = LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/maps", pid).str, O_RDONLY)); - if(maps_fd != -1) - { - // rjf: determine how big /proc/pid/maps is (this is a "special file", so we have to compute the size - // by reading the whole thing - very very good - // - // TODO(rjf): probably just read it once into a list & join? - // - U64 maps_size = 0; - { - U8 temp[4096]; - size_t cap = MB(1); - size_t cursor = 0; - for(;cursor < cap;) - { - ssize_t actual_read = LNX_RETRY_ON_EINTR(pread(maps_fd, temp, sizeof(temp), cursor)); - if(actual_read <= 0) { break; } - cursor += (U64)actual_read; - } - maps_size = (U64)cursor; - } - - // rjf: read entire /proc/pid/maps - U8 *maps_ptr = push_array(scratch.arena, U8, maps_size); - U64 read_size = lnx_dmn_read(maps_fd, r1u64(0, maps_size), maps_ptr); - - // split map file on lines - String8List lines = str8_split_by_string_chars(scratch.arena, str8(maps_ptr, maps_size), str8_lit("\n"), 0); - - // scan each line until a virtual mapping whose low part matches the DL base address is found - for EachNode(n, String8Node, lines.first) - { - String8 line = n->string; - - // split the string while respecting escape sequences - String8List parts = {0}; - for EachIndex(cursor, line.size) - { - if(parts.node_count > 5) { break; } - if(line.str[cursor] == ' ') { continue; } - - // scan forward to the closing delimiter - U64 token_start = cursor; - for(; cursor < line.size; cursor += 1) - { - if(line.str[cursor] == '\\') - { - cursor += 1; - continue; - } - if(line.str[cursor] == ' ') { break; } - } - - // push sub-string to the list - str8_list_push(scratch.arena, &parts, str8_substr(line, r1u64(token_start, cursor))); - } - - // was line parsed correctly? - if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; } - - // parse map virtual range - String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); - if(vaddr_list.node_count != 2) { Assert(0 && "failed to parse virtual range portion of map line"); continue; } - - // does the low part match DL base address? - U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16); - if(lo_vaddr == auxv.base) - { - dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; - dl_path = push_str8_copy(arena, dl_path); - break; - } - } - - LNX_RETRY_ON_EINTR(close(maps_fd)); - } - else - { - Assert(0 && "failed to open DL fd"); - } - Assert(dl_path.size); - } - - // rjf: read probes for this dl - int dl_fd = LNX_RETRY_ON_EINTR(open((char *)dl_path.str, O_RDONLY)); - LNX_DMN_ProbeList probes = {0}; - if(dl_fd >= 0) - { - U64 offset = 0; - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(lnx_dmn_machine_op_mem_read, &dl_fd, offset, &ehdr) != MachineOpResult_Ok) { goto exit; } - - U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; - ELF_Shdr64 strtab_shdr = {0}; - if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &dl_fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr) != MachineOpResult_Ok) { goto exit; } - - B32 found_probes = 0; - B32 found_probes_base = 0; - ELF_Shdr64 text_shdr = {0}; - ELF_Shdr64 stapsdt_base_shdr = {0}; - ELF_Shdr64 stapsdt_shdr = {0}; - for(U64 shdr_off = offset + ehdr.e_shoff, shdr_opl = shdr_off + ehdr.e_shentsize * ehdr.e_shnum; - shdr_off < shdr_opl; - shdr_off += ehdr.e_shentsize) { - ELF_Shdr64 shdr = {0}; - if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &dl_fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr) != MachineOpResult_Ok) { goto exit; } - - if(shdr.sh_type == ELF_ShType_Note) - { - U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; - U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; - String8 name = lnx_dmn_read_string_capped(scratch.arena, dl_fd, name_offset, name_cap); - - if(str8_match(name, str8_lit(".note.stapsdt"), 0)) - { - stapsdt_shdr = shdr; - found_probes = 1; - } - } - else if(shdr.sh_type == ELF_ShType_ProgBits) - { - U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; - U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; - String8 name = lnx_dmn_read_string_capped(scratch.arena, dl_fd, name_offset, name_cap); - - if(str8_match(name, str8_lit(".stapsdt.base"), 0)) - { - stapsdt_base_shdr = shdr; - found_probes_base = 1; - } else if(str8_match(name, str8_lit(".text"), 0)) - { - text_shdr = shdr; - } - } - - if(found_probes && found_probes_base) { break; } - } - - if(!found_probes || !found_probes_base) { goto exit; } - - U64 probes_base = stapsdt_base_shdr.sh_addr; - - Rng1U64 note_range = shift_1u64(r1u64(stapsdt_shdr.sh_offset, stapsdt_shdr.sh_offset + stapsdt_shdr.sh_size), offset); - void *raw_note = push_array(arena, U8, stapsdt_shdr.sh_size); - U64 note_read_size = lnx_dmn_read(dl_fd, note_range, raw_note); - if(note_read_size != dim_1u64(note_range)) { goto exit; } - - Arch arch = arch_from_elf_machine(ehdr.e_machine); - ELF_NoteList note = elf_parse_note(scratch.arena, str8(raw_note, dim_1u64(note_range)), ehdr.e_ident[ELF_Identifier_Class], ehdr.e_machine); - - for EachNode(n, ELF_NoteNode, note.first) - { - ELF_Note *note = &n->v; - if(!str8_match(note->owner, str8_lit("stapsdt"), 0)) { continue; } - if(note->type != ELF_NoteType_STapSdt) { continue; } - - LNX_DMN_Probe probe = {0}; - { - U64 cursor = 0; - U64 addr_size = ehdr.e_ident[ELF_Identifier_Class] == ELF_Class_64 ? 8 : 4; - - U64 pc = 0; - U64 pc_size = str8_deserial_read(note->desc, cursor, &pc, addr_size, addr_size); - if (pc_size == 0) { goto exit; } - cursor += pc_size; - - U64 base_addr = 0; - U64 base_addr_size = str8_deserial_read(note->desc, cursor, &base_addr, addr_size, addr_size); - if (base_addr_size == 0) { goto exit; } - cursor += base_addr_size; - - U64 semaphore = 0; - U64 semaphore_size = str8_deserial_read(note->desc, cursor, &semaphore, addr_size, addr_size); - if (semaphore_size == 0) { goto exit; } - cursor += semaphore_size; - - String8 provider = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += provider.size + 1; - if (cursor > note->desc.size) { goto exit; } - - String8 name = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += name.size + 1; - if (cursor > note->desc.size) { goto exit; } - - String8 args = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += args.size + 1; - if (cursor > note->desc.size) { goto exit; } - - U64 probe_rebase = auxv.base + (base_addr - probes_base); - - probe.provider = provider; - probe.name = name; - probe.args = stap_arg_array_from_string(arena, arch, args); - probe.pc = pc + probe_rebase; - probe.semaphore = semaphore ? semaphore + probe_rebase : 0; - } - - LNX_DMN_ProbeNode *n = push_array(arena, LNX_DMN_ProbeNode, 1); - n->v = probe; - SLLQueuePush(probes.first, probes.last, n); - probes.count += 1; - } - - exit:; - LNX_RETRY_ON_EINTR(close(dl_fd)); - } - - // rjf: store probes - for EachNode(n, LNX_DMN_ProbeNode, probes.first) - { - LNX_DMN_Probe *p = &n->v; - if(str8_match(p->provider, s("rtld"), 0)) - { - for EachNonZeroEnumVal(LNX_DMN_ProbeKind, k) - { - if(str8_match(p->name, lnx_dmn_probe_kind_string_table[k], 0)) - { - known_probes[k] = p; - break; - } - } - } - } - - scratch_end(scratch); - } - - //- rjf: build context - ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; - ctx->arena = ctx_arena; - ctx->arch = arch; - ctx->rdebug_vaddr = rdebug_vaddr; - ctx->dl_class = dl_class; - ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000); - ctx->probes = known_probes; - ctx->xcr0 = xcr0; - ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); - ctx->xsave_layout = xsave_layout; - - //- create main module - LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, process->fd, base_vaddr, auxv.execfn, 1, 1); - - //- glibc has a shortcut mapping for the main module - hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); - } - - ////////////////////////////// - //- rjf: set process' context - // - process->ctx = ctx; - process->ctx->ref_count += 1; - - ////////////////////////////// - //- rjf: install probe traps - only needed in processes without cloned memory - // - if(!(flags & LNX_DMN_CreateProcessFlag_ClonedMemory)) - { - for EachNonZeroEnumVal(LNX_DMN_ProbeKind, k) - { - if(process->ctx->probes[k] == 0) { continue; } - DMN_Trap *trap = push_array(process->ctx->arena, DMN_Trap, 1); - trap->process = lnx_dmn_handle_from_process(process); - trap->vaddr = process->ctx->probes[k]->pc; - trap->id = k; - LNX_DMN_ActiveTrap *active_trap = lnx_dmn_set_trap(process->ctx->arena, trap); - SLLQueuePush(process->ctx->first_probe_trap, process->ctx->last_probe_trap, active_trap); - } - } - - ////////////////////////////// - //- rjf: create main thread - // - lnx_dmn_thread_alloc(process, LNX_DMN_ThreadState_Stopped, pid); - - ////////////////////////////// - //- rjf: push events - // - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_CreateProcess; - e->process = lnx_dmn_handle_from_process(process); - e->arch = process->ctx->arch; - e->code = process->pid; - } - for EachNode(thread, LNX_DMN_Thread, process->first_thread) - { - lnx_dmn_push_event_create_thread(arena, events, thread); - } - for EachNode(module, LNX_DMN_Module, process->ctx->first_module) - { - lnx_dmn_push_event_load_module(arena, events, process->first_thread, module); - } - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_HandshakeComplete; - e->process = lnx_dmn_handle_from_process(process); - e->thread = lnx_dmn_handle_from_thread(process->first_thread); - e->arch = process->ctx->arch; - } - - return process; -} - internal void lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr) { @@ -2204,13 +1536,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) Temp scratch = scratch_begin(&arena, 1); DMN_EventList events = {0}; - // TODO(rjf): wait... we can't take the halter mutex here??? - // callers of dmn_halt need to be able to call *while* a run - // is happening! - // - // TODO(rjf): *after further reading* oh, I see, it is dropped - // before we do a wait_pid... we should try to clean this up? - // rather than a mutex dance that spans this whole path... + ////////////////////////////// + //- rjf: prevent halts temporarily, while debuggee state can change - + // we will resume before actually waiting for a signal, so that a halt + // can correctly interrupt debuggee execution. // mutex_take(lnx_dmn_state->halter_mutex); @@ -2220,7 +1549,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(lnx_dmn_state->process_count > 0) { //////////////////////////// - //- rjf: write all traps + //- rjf: write all trap instructions // LNX_DMN_ActiveTrap *active_trap_first = 0; LNX_DMN_ActiveTrap *active_trap_last = 0; @@ -2395,10 +1724,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // B32 is_halt_done = 0; LNX_DMN_ThreadPtrList stopped_threads = {0}; - do + for(;;) { ////////////////////////// - //- rjf: wait for a signal + //- rjf: wait for a signal (unblock halts) // int status = 0; pid_t wait_id = 0; @@ -2424,16 +1753,21 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) int event_code = (status >> 16); ////////////////////////// - //- rjf: intercept initing processes + //- rjf: handle signals from initializing processes // + B32 signal_handled = 0; + B32 process_done = 0; + B32 process_new = 0; + LNX_DMN_CreateProcessFlags process_new_flags = 0; + B32 thread_done = 0; + U64 thread_done_exit_code = 0; + B32 thread_new = 0; if(process && process->state != LNX_DMN_ProcessState_Normal) { + //- rjf: advance process initialization state switch(process->state) { - case LNX_DMN_ProcessState_Null: - case LNX_DMN_ProcessState_Normal: - { - }break; + default:{}break; case LNX_DMN_ProcessState_Launch: { if(wstopsig == SIGSTOP) @@ -2441,46 +1775,40 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, process->pid, 0, 0)) >= 0) { process->state = LNX_DMN_ProcessState_WaitForExec; - goto wait_for_signal; + signal_handled = 1; + } + else + { + log_infof("ptrace error: Failed to resume tracee."); } - else { Assert(0 && "failed to resume tracee"); } } - // else { Assert(0 && "unexpected signal"); } - } break; + }break; case LNX_DMN_ProcessState_WaitForExec: + if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) { - if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) - { - LNX_DMN_CreateProcessFlags create_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; - lnx_dmn_process_release(process); - lnx_dmn_event_create_process(arena, &events, wait_id, 0, create_flags); - goto wait_for_signal; - } - //else { Assert(0 && "unexpected signal"); } - } break; + process_done = 1; + process_new = 1; + process_new_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; + signal_handled = 1; + }break; case LNX_DMN_ProcessState_ExecFailedDoExit: + if(wifexited) { - if(wifexited) - { - lnx_dmn_process_release(process); - goto wait_for_signal; - } - else { Assert(0 && "unexpected signal"); } - } break; + process_done = 1; + signal_handled = 1; + }break; case LNX_DMN_ProcessState_Attach: + if(wstopsig == SIGSTOP) { - if(wstopsig == SIGSTOP) - { - LNX_DMN_CreateProcessFlags create_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; - lnx_dmn_process_release(process); - lnx_dmn_event_create_process(arena, &events, wait_id, 0, create_flags); - goto wait_for_signal; - } - else { Assert(0 && "unexpected signal"); } - } break; + process_done = 1; + process_new = 1; + process_new_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; + signal_handled = 1; + }break; } - // shutdown process + //- rjf: if signal was unhandled at this point -> kill process + if(!signal_handled) { if(LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)) >= 0) { @@ -2488,19 +1816,16 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } else { - //Assert(0 && "failed to send kill signal to the tracee"); - lnx_dmn_process_release(process); + log_infof("ptrace error: Couldn't successfully kill process; releasing debugger state."); + process_done = 1; } } - - wait_for_signal:; - continue; } ////////////////////////// //- rjf: any thread stop -> interrupt all threads // - if(wifexited || wifsignaled || wifstopped) + if(!signal_handled && (wifexited || wifsignaled || wifstopped)) { LNX_DMN_ThreadPtrNode *thread_n = hash_table_search_u64_raw(running_threads_ht, wait_id); if(thread_n) @@ -2542,17 +1867,20 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // wifexited -> normal process exit via _exit or exit() // wifsignaled -> exit because child did not handle a signal // - if(wifexited || wifsignaled) + if(!signal_handled && (wifexited || wifsignaled)) { - U64 exit_code = wifexited ? WEXITSTATUS(status) : WTERMSIG(status); - lnx_dmn_event_exit_thread(arena, &events, wait_id, exit_code); + thread_done = 1; + thread_done_exit_code = wifexited ? WEXITSTATUS(status) : WTERMSIG(status); + signal_handled = 1; } ////////////////////////// //- rjf: wifstopped -> thread or group stop // - else if(wifstopped) + else if(!signal_handled && wifstopped) { + signal_handled = 1; + //////////////////////// //- rjf: read stop thread's new registers // @@ -2842,14 +2170,17 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) LNX_DMN_Process *process = thread->process; if(process->debug_subprocesses) { - lnx_dmn_event_exit_thread(arena, &events, wait_id, 0); - lnx_dmn_event_create_process(arena, &events, wait_id, 0, LNX_DMN_CreateProcessFlag_DebugSubprocesses); + thread_done = 1; + thread_done_exit_code = 0; + process_new = 1; + process_new_flags = LNX_DMN_CreateProcessFlag_DebugSubprocesses; } else { if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, wait_id, 0, 0)) >= 0) { - lnx_dmn_event_exit_thread(arena, &events, wait_id, 0); + thread_done = 1; + thread_done_exit_code = 0; } else { Assert(0 && "failed to detach"); } } @@ -2972,13 +2303,697 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } ////////////////////////// - //- rjf: otherwise -> unexpected stop code. + //- rjf: signal not handled -> log // - else + if(!signal_handled) { // TODO(rjf) } - } while(running_threads.count > 0 || lnx_dmn_state->process_pending_creation > 0 || lnx_dmn_state->threads_pending_creation > 0); + + ////////////////////////// + //- rjf: thread done -> release + // + if(thread_done) + { + // rjf: store main thread's exit code + if(thread->tid == thread->process->pid) + { + thread->process->main_thread_exit_code = thread_done_exit_code; + } + + // rjf: push exit event + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_ExitThread; + e->process = lnx_dmn_handle_from_process(thread->process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->code = (U32)thread_done_exit_code; + } + + // rjf: release thread storage + lnx_dmn_thread_release(thread); + + // rjf: last thread in process? -> exit process too + if(thread->process->thread_count == 0) + { + // rjf: push module events + for EachNode(module, LNX_DMN_Module, thread->process->ctx->first_module) + { + lnx_dmn_push_event_unload_module(arena, &events, thread->process, module); + } + + // rjf: push process exit event + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_ExitProcess; + e->process = lnx_dmn_handle_from_process(thread->process); + e->code = thread->process->main_thread_exit_code; + } + + // rjf: release process storage + lnx_dmn_process_release(thread->process); + } + } + + ////////////////////////// + //- rjf: process done -> release + // + if(process_done) + { + lnx_dmn_process_release(process); + } + + ////////////////////////// + //- rjf: new process -> create + // + if(process_new) + { + LNX_DMN_Process *parent_process = 0; + LNX_DMN_Process *new_process = lnx_dmn_process_alloc(wait_id, LNX_DMN_ProcessState_Normal, 0, !!(process_new_flags & LNX_DMN_CreateProcessFlag_DebugSubprocesses), !!(process_new_flags & LNX_DMN_CreateProcessFlag_Cow)); + + //////////////////////// + //- rjf: get process context - create if this isn't cloning parent process + // + LNX_DMN_ProcessCtx *ctx = 0; + if(parent_process != 0 && process_new_flags & LNX_DMN_CreateProcessFlag_ClonedMemory) + { + ctx = parent_process->ctx; + } + else + { + //- rjf: allocate ctx + ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; + + //- rjf: read main executable ELF header + ELF_Hdr64 exe_ehdr = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + char *exe_path = (char *)str8f(scratch.arena, "/proc/%d/exe", wait_id).str; + int exe_fd = LNX_RETRY_ON_EINTR(open(exe_path, O_RDONLY)); + if(exe_fd >= 0) + { + U8 e_ident[ELF_Identifier_Max] = {0}; + lnx_dmn_read(exe_fd, r1u64(0, sizeof(e_ident)), e_ident); + if(MemoryMatch(e_ident, elf_magic_string.str, elf_magic_string.size)) + { + switch(e_ident[ELF_Identifier_Class]) + { + default:{}break; + case ELF_Class_None:{}break; + case ELF_Class_32: + { + ELF_Hdr32 ehdr32 = {0}; + lnx_dmn_read(exe_fd, r1u64(0, sizeof(ehdr32)), &ehdr32); + exe_ehdr = elf_hdr64_from_hdr32(ehdr32); + }break; + case ELF_Class_64: + { + lnx_dmn_read(exe_fd, r1u64(0, sizeof(exe_ehdr)), &exe_ehdr); + }break; + } + } + } + LNX_RETRY_ON_EINTR(close(exe_fd)); + scratch_end(scratch); + } + + //- rjf: read auxv info for pid + LNX_DMN_Auxv auxv = {0}; + { + Temp scratch = scratch_begin(0, 0); + + // rjf: open aux data + String8 auxv_path = str8f(scratch.arena, "/proc/%d/auxv", wait_id); + int auxv_fd = LNX_RETRY_ON_EINTR(open((char *)auxv_path.str, O_RDONLY)); + + // rjf: scan aux data + if(auxv_fd >= 0) + { + for(U64 off = 0;;) + { + // rjf: read next aux + ELF_Auxv64 auxv_entry = {0}; + switch(exe_ehdr.e_ident[ELF_Identifier_Class]) + { + default: + case ELF_Class_None:{}break; + case ELF_Class_32: + { + ELF_Auxv32 auxv32 = {0}; + off += lnx_dmn_read_struct(auxv_fd, off, &auxv32); + auxv_entry = elf_auxv64_from_auxv32(auxv32); + }break; + case ELF_Class_64: + { + off += lnx_dmn_read_struct(auxv_fd, off, &auxv_entry); + }break; + } + + // rjf: fill result + switch(auxv_entry.a_type) + { + default:{}break; + case ELF_AuxType_Null: goto brkloop; break; + case ELF_AuxType_Base: auxv.base = auxv_entry.a_val; break; + case ELF_AuxType_Phnum: auxv.phnum = auxv_entry.a_val; break; + case ELF_AuxType_Phent: auxv.phent = auxv_entry.a_val; break; + case ELF_AuxType_Phdr: auxv.phdr = auxv_entry.a_val; break; + case ELF_AuxType_ExecFn: auxv.execfn = auxv_entry.a_val; break; + case ELF_AuxType_Pagesz: auxv.pagesz = auxv_entry.a_val; break; + } + } + brkloop:; + LNX_RETRY_ON_EINTR(close(auxv_fd)); + } + + scratch_end(scratch); + } + + //- rjf: unpack context + Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); + U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); + U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + Rng1U64 image_vrange = lnx_dmn_compute_image_vrange(new_process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); + Arena *ctx_arena = arena_alloc(); + + //- rjf: read dynamically loaded ELF header + ELF_Hdr64 dl_ehdr = {0}; + elf_read_ehdr(lnx_dmn_machine_op_mem_read, &new_process->fd, auxv.base, &dl_ehdr); + ELF_Class dl_class = dl_ehdr.e_ident[ELF_Identifier_Class]; + + //- rjf: compute rdebug vaddr + U64 rdebug_vaddr = 0; + { + Temp scratch = scratch_begin(0, 0); + + // load DL's header + ELF_Hdr64 ehdr = {0}; + elf_read_ehdr(lnx_dmn_machine_op_mem_read, &new_process->fd, auxv.base, &ehdr); + + U64 rebase = dl_ehdr.e_type == ELF_Type_Dyn ? auxv.base : 0; + ELF_Class elf_class = ehdr.e_ident[ELF_Identifier_Class]; + + // rjf: find dynamic program header + U64 dynamic_vaddr = 0; + { + U64 phdr_vaddr = auxv.base + dl_ehdr.e_phoff; + for(U64 ph_cursor = phdr_vaddr, ph_opl = (phdr_vaddr + ehdr.e_phentsize * ehdr.e_phentsize); ph_cursor < ph_opl; ph_cursor += ehdr.e_phentsize) + { + ELF_Phdr64 phdr = {0}; + elf_read_phdr(lnx_dmn_machine_op_mem_read, &new_process->fd, ph_cursor, elf_class, &phdr); + if(phdr.p_type == ELF_PType_Dynamic) + { + dynamic_vaddr = rebase + phdr.p_vaddr; + break; + } + } + } + + // rjf: get dynamic info for this module + // extract necessary info out of dynamic program header + LNX_DMN_DynamicInfo dynamic_info = {0}; + { + B32 is_rebased = !!(process_new_flags & LNX_DMN_CreateProcessFlag_Rebased); + U64 dynamic_info_rebase = is_rebased ? 0 : rebase; + for(U64 dynamic_cursor = dynamic_vaddr; ; dynamic_cursor += elf_dyn_size_from_class(elf_class)) + { + // rjf: read next dyn entry + ELF_Dyn64 dyn = {0}; + if(elf_read_dyn(lnx_dmn_machine_op_mem_read, &new_process->fd, dynamic_cursor, elf_class, &dyn) != MachineOpResult_Ok) { Assert(0 && "unable to read dynamic"); } + + // rjf: break on zero + if(dyn.tag == ELF_DynTag_Null) { break; } + + // rjf: extract info from dynamic section + switch(dyn.tag) + { + default:{}break; + case ELF_DynTag_Strtab: {dynamic_info.strtab_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_Strsz: {dynamic_info.strtab_size = dyn.val;}break; + case ELF_DynTag_Symtab: {dynamic_info.symtab_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_Syment: {dynamic_info.symtab_entry_size = dyn.val;}break; + case ELF_DynTag_Hash: {dynamic_info.hash_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_GNU_Hash: {dynamic_info.gnu_hash_vaddr = dynamic_info_rebase + dyn.val;}break; + } + } + } + + // extract symbol table count from available options + U64 symbol_count = 0; + if(dynamic_info.hash_vaddr) + { + U64 hash_entry_size = 4; + if(elf_class == ELF_Class_64 && (ehdr.e_machine == ELF_MachineKind_ALPHA || ehdr.e_machine == ELF_MachineKind_S390 || ehdr.e_machine == ELF_MachineKind_S390_OLD)) + { + hash_entry_size = 8; + } + + U64 chain_count = 0; + if(lnx_dmn_read(new_process->fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size) + { + symbol_count = chain_count; + } + else + { + Assert(0 && "failed to read hash table's chain count out of HASH"); + } + } + else if(dynamic_info.gnu_hash_vaddr) + { + // GNU_HASH header: u32 nbuckets, u32 symoffset, u32 bloom_size, u32 bloom_shift, + // followed by bloom[bloom_size] (word size = 4 on 32-bit ELF, 8 on 64-bit), + // buckets[nbuckets] (u32), chains[...] (u32, terminated by entry w/ LSB set). + U32 gnu_header[4] = {0}; + if(lnx_dmn_read(new_process->fd, r1u64(dynamic_info.gnu_hash_vaddr, dynamic_info.gnu_hash_vaddr + sizeof(gnu_header)), gnu_header) == sizeof(gnu_header)) + { + U32 nbuckets = gnu_header[0]; + U32 symoffset = gnu_header[1]; + U32 bloom_size = gnu_header[2]; + U64 bloom_word = (elf_class == ELF_Class_64) ? 8 : 4; + U64 buckets_vaddr = dynamic_info.gnu_hash_vaddr + sizeof(gnu_header) + bloom_size * bloom_word; + U64 chain_vaddr = buckets_vaddr + (U64)nbuckets * sizeof(U32); + + // find largest symbol index referenced by any bucket + U32 max_bucket = 0; + for(U32 b = 0; b < nbuckets; b += 1) + { + U32 entry = 0; + if(lnx_dmn_read(new_process->fd, r1u64(buckets_vaddr + b*sizeof(U32), buckets_vaddr + (b+1)*sizeof(U32)), &entry) != sizeof(U32)) + { + break; + } + if(entry > max_bucket) { max_bucket = entry; } + } + + if(max_bucket < symoffset) + { + // no symbols in hash; total count is symoffset + symbol_count = symoffset; + } + else + { + // walk chain from max_bucket until terminator (entry w/ LSB set) + U64 idx = max_bucket; + for(;;) + { + U32 chain_entry = 0; + U64 entry_vaddr = chain_vaddr + (idx - symoffset) * sizeof(U32); + if(lnx_dmn_read(new_process->fd, r1u64(entry_vaddr, entry_vaddr + sizeof(U32)), &chain_entry) != sizeof(U32)) + { + break; + } + if(chain_entry & 1) + { + symbol_count = idx + 1; + break; + } + idx += 1; + if(idx - max_bucket > (1u<<24)) + { + // sanity bound: don't loop forever on a corrupt chain + break; + } + } + } + } + } + + // scan symbol table for the rendezvous symbol + if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count) + { + for EachIndex(symbol_idx, symbol_count) + { + ELF_Sym64 symbol = {0}; + if(elf_read_symbol(lnx_dmn_machine_op_mem_read, &new_process->fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol) != MachineOpResult_Ok) + { + Assert(0 && "failed to read symbol table"); + break; + } + + Temp temp = temp_begin(scratch.arena); + + String8 symbol_name = {0}; + if(symbol.st_name < dynamic_info.strtab_size) + { + U64 cap = dynamic_info.strtab_size - symbol.st_name; + symbol_name = lnx_dmn_read_string_capped(temp.arena, new_process->fd, dynamic_info.strtab_vaddr + symbol.st_name, cap); + } + + if(str8_match(symbol_name, str8_lit("_r_debug"), 0)) + { + ELF_SymType symbol_type = ELF_ST_TYPE(symbol.st_info); + if(symbol_type == ELF_SymType_Object && symbol.st_size > 0) + { + rdebug_vaddr = rebase + symbol.st_value; + break; + } + } + + temp_end(temp); + } + } + + scratch_end(scratch); + } + + //- query xsave layout + U64 xcr0 = 0; + U64 xsave_size = 0; + X64_XSaveLayout xsave_layout = {0}; + if(arch == Arch_x64) + { + X64_XSave xsave = {0}; + if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, new_process->pid, (void *)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)) + { + // Linux stores xcr0 bits in fxstate padding, + // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 + xcr0 = *(U64 *)((U8 *)&xsave + 464); + xsave_size = x64_get_xsave_size(); + xsave_layout = x64_get_xsave_layout(xcr0); + } + else + { + Assert(0 && "failed to get xstate"); + } + } + + // gather probes + LNX_DMN_Probe **known_probes = push_array(ctx_arena, LNX_DMN_Probe *, LNX_DMN_ProbeKind_COUNT); + { + Temp scratch = scratch_begin(0, 0); + + // rjf: get dl path for this pid + String8 dl_path = {0}; + { + int maps_fd = LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/maps", wait_id).str, O_RDONLY)); + if(maps_fd != -1) + { + // rjf: determine how big /proc/pid/maps is (this is a "special file", so we have to compute the size + // by reading the whole thing - very very good + // + // TODO(rjf): probably just read it once into a list & join? + // + U64 maps_size = 0; + { + U8 temp[4096]; + size_t cap = MB(1); + size_t cursor = 0; + for(;cursor < cap;) + { + ssize_t actual_read = LNX_RETRY_ON_EINTR(pread(maps_fd, temp, sizeof(temp), cursor)); + if(actual_read <= 0) { break; } + cursor += (U64)actual_read; + } + maps_size = (U64)cursor; + } + + // rjf: read entire /proc/pid/maps + U8 *maps_ptr = push_array(scratch.arena, U8, maps_size); + U64 read_size = lnx_dmn_read(maps_fd, r1u64(0, maps_size), maps_ptr); + + // split map file on lines + String8List lines = str8_split_by_string_chars(scratch.arena, str8(maps_ptr, maps_size), str8_lit("\n"), 0); + + // scan each line until a virtual mapping whose low part matches the DL base address is found + for EachNode(n, String8Node, lines.first) + { + String8 line = n->string; + + // split the string while respecting escape sequences + String8List parts = {0}; + for EachIndex(cursor, line.size) + { + if(parts.node_count > 5) { break; } + if(line.str[cursor] == ' ') { continue; } + + // scan forward to the closing delimiter + U64 token_start = cursor; + for(; cursor < line.size; cursor += 1) + { + if(line.str[cursor] == '\\') + { + cursor += 1; + continue; + } + if(line.str[cursor] == ' ') { break; } + } + + // push sub-string to the list + str8_list_push(scratch.arena, &parts, str8_substr(line, r1u64(token_start, cursor))); + } + + // was line parsed correctly? + if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; } + + // parse map virtual range + String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); + if(vaddr_list.node_count != 2) { Assert(0 && "failed to parse virtual range portion of map line"); continue; } + + // does the low part match DL base address? + U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16); + if(lo_vaddr == auxv.base) + { + dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; + dl_path = push_str8_copy(arena, dl_path); + break; + } + } + + LNX_RETRY_ON_EINTR(close(maps_fd)); + } + else + { + Assert(0 && "failed to open DL fd"); + } + Assert(dl_path.size); + } + + // rjf: read probes for this dl + int dl_fd = LNX_RETRY_ON_EINTR(open((char *)dl_path.str, O_RDONLY)); + LNX_DMN_ProbeList probes = {0}; + if(dl_fd >= 0) + { + U64 offset = 0; + ELF_Hdr64 ehdr = {0}; + if(elf_read_ehdr(lnx_dmn_machine_op_mem_read, &dl_fd, offset, &ehdr) != MachineOpResult_Ok) { goto exit; } + + U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; + ELF_Shdr64 strtab_shdr = {0}; + if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &dl_fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr) != MachineOpResult_Ok) { goto exit; } + + B32 found_probes = 0; + B32 found_probes_base = 0; + ELF_Shdr64 text_shdr = {0}; + ELF_Shdr64 stapsdt_base_shdr = {0}; + ELF_Shdr64 stapsdt_shdr = {0}; + for(U64 shdr_off = offset + ehdr.e_shoff, shdr_opl = shdr_off + ehdr.e_shentsize * ehdr.e_shnum; + shdr_off < shdr_opl; + shdr_off += ehdr.e_shentsize) { + ELF_Shdr64 shdr = {0}; + if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &dl_fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr) != MachineOpResult_Ok) { goto exit; } + + if(shdr.sh_type == ELF_ShType_Note) + { + U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; + U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; + String8 name = lnx_dmn_read_string_capped(scratch.arena, dl_fd, name_offset, name_cap); + + if(str8_match(name, str8_lit(".note.stapsdt"), 0)) + { + stapsdt_shdr = shdr; + found_probes = 1; + } + } + else if(shdr.sh_type == ELF_ShType_ProgBits) + { + U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; + U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; + String8 name = lnx_dmn_read_string_capped(scratch.arena, dl_fd, name_offset, name_cap); + + if(str8_match(name, str8_lit(".stapsdt.base"), 0)) + { + stapsdt_base_shdr = shdr; + found_probes_base = 1; + } else if(str8_match(name, str8_lit(".text"), 0)) + { + text_shdr = shdr; + } + } + + if(found_probes && found_probes_base) { break; } + } + + if(!found_probes || !found_probes_base) { goto exit; } + + U64 probes_base = stapsdt_base_shdr.sh_addr; + + Rng1U64 note_range = shift_1u64(r1u64(stapsdt_shdr.sh_offset, stapsdt_shdr.sh_offset + stapsdt_shdr.sh_size), offset); + void *raw_note = push_array(arena, U8, stapsdt_shdr.sh_size); + U64 note_read_size = lnx_dmn_read(dl_fd, note_range, raw_note); + if(note_read_size != dim_1u64(note_range)) { goto exit; } + + Arch arch = arch_from_elf_machine(ehdr.e_machine); + ELF_NoteList note = elf_parse_note(scratch.arena, str8(raw_note, dim_1u64(note_range)), ehdr.e_ident[ELF_Identifier_Class], ehdr.e_machine); + + for EachNode(n, ELF_NoteNode, note.first) + { + ELF_Note *note = &n->v; + if(!str8_match(note->owner, str8_lit("stapsdt"), 0)) { continue; } + if(note->type != ELF_NoteType_STapSdt) { continue; } + + LNX_DMN_Probe probe = {0}; + { + U64 cursor = 0; + U64 addr_size = ehdr.e_ident[ELF_Identifier_Class] == ELF_Class_64 ? 8 : 4; + + U64 pc = 0; + U64 pc_size = str8_deserial_read(note->desc, cursor, &pc, addr_size, addr_size); + if (pc_size == 0) { goto exit; } + cursor += pc_size; + + U64 base_addr = 0; + U64 base_addr_size = str8_deserial_read(note->desc, cursor, &base_addr, addr_size, addr_size); + if (base_addr_size == 0) { goto exit; } + cursor += base_addr_size; + + U64 semaphore = 0; + U64 semaphore_size = str8_deserial_read(note->desc, cursor, &semaphore, addr_size, addr_size); + if (semaphore_size == 0) { goto exit; } + cursor += semaphore_size; + + String8 provider = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += provider.size + 1; + if (cursor > note->desc.size) { goto exit; } + + String8 name = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += name.size + 1; + if (cursor > note->desc.size) { goto exit; } + + String8 args = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += args.size + 1; + if (cursor > note->desc.size) { goto exit; } + + U64 probe_rebase = auxv.base + (base_addr - probes_base); + + probe.provider = provider; + probe.name = name; + probe.args = stap_arg_array_from_string(arena, arch, args); + probe.pc = pc + probe_rebase; + probe.semaphore = semaphore ? semaphore + probe_rebase : 0; + } + + LNX_DMN_ProbeNode *n = push_array(arena, LNX_DMN_ProbeNode, 1); + n->v = probe; + SLLQueuePush(probes.first, probes.last, n); + probes.count += 1; + } + + exit:; + LNX_RETRY_ON_EINTR(close(dl_fd)); + } + + // rjf: store probes + for EachNode(n, LNX_DMN_ProbeNode, probes.first) + { + LNX_DMN_Probe *p = &n->v; + if(str8_match(p->provider, s("rtld"), 0)) + { + for EachNonZeroEnumVal(LNX_DMN_ProbeKind, k) + { + if(str8_match(p->name, lnx_dmn_probe_kind_string_table[k], 0)) + { + known_probes[k] = p; + break; + } + } + } + } + + scratch_end(scratch); + } + + //- rjf: build context + ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; + ctx->arena = ctx_arena; + ctx->arch = arch; + ctx->rdebug_vaddr = rdebug_vaddr; + ctx->dl_class = dl_class; + ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000); + ctx->probes = known_probes; + ctx->xcr0 = xcr0; + ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); + ctx->xsave_layout = xsave_layout; + + //- create main module + LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, new_process->fd, base_vaddr, auxv.execfn, 1, 1); + + //- glibc has a shortcut mapping for the main module + hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); + } + + //////////////////////// + //- rjf: set process' context + // + new_process->ctx = ctx; + new_process->ctx->ref_count += 1; + + //////////////////////// + //- rjf: install probe traps - only needed in processes without cloned memory + // + if(!(process_new_flags & LNX_DMN_CreateProcessFlag_ClonedMemory)) + { + for EachNonZeroEnumVal(LNX_DMN_ProbeKind, k) + { + if(new_process->ctx->probes[k] == 0) { continue; } + DMN_Trap *trap = push_array(new_process->ctx->arena, DMN_Trap, 1); + trap->process = lnx_dmn_handle_from_process(new_process); + trap->vaddr = new_process->ctx->probes[k]->pc; + trap->id = k; + LNX_DMN_ActiveTrap *active_trap = lnx_dmn_set_trap(new_process->ctx->arena, trap); + SLLQueuePush(new_process->ctx->first_probe_trap, new_process->ctx->last_probe_trap, active_trap); + } + } + + //////////////////////// + //- rjf: create main thread + // + lnx_dmn_thread_alloc(new_process, LNX_DMN_ThreadState_Stopped, wait_id); + + //////////////////////// + //- rjf: push events + // + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_CreateProcess; + e->process = lnx_dmn_handle_from_process(new_process); + e->arch = new_process->ctx->arch; + e->code = new_process->pid; + } + for EachNode(thread, LNX_DMN_Thread, new_process->first_thread) + { + lnx_dmn_push_event_create_thread(arena, &events, thread); + } + for EachNode(module, LNX_DMN_Module, new_process->ctx->first_module) + { + lnx_dmn_push_event_load_module(arena, &events, new_process->first_thread, module); + } + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_HandshakeComplete; + e->process = lnx_dmn_handle_from_process(new_process); + e->thread = lnx_dmn_handle_from_thread(new_process->first_thread); + e->arch = new_process->ctx->arch; + } + } + + ////////////////////////// + //- rjf: break if all threads are stopped, no pending processes, no pending threads + // + if(running_threads.count == 0 && lnx_dmn_state->process_pending_creation == 0 && lnx_dmn_state->threads_pending_creation == 0) + { + break; + } + } //////////////////////////// //- rjf: do halt @@ -3023,7 +3038,9 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->error_kind = DMN_ErrorKind_NotAttached; } - // TODO(rjf): WRONG, see `take` + ////////////////////////////// + //- rjf: re-allow halts + // mutex_drop(lnx_dmn_state->halter_mutex); scratch_end(scratch); diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index b69553df..f569c12f 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -478,8 +478,6 @@ internal void lnx_dmn_push_event_unload_module(Arena *arena, DMN_EventList *even //////////////////////////////// //~ Debug Event -internal void lnx_dmn_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code); -internal LNX_DMN_Process * lnx_dmn_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, LNX_DMN_Process *parent_process, LNX_DMN_CreateProcessFlags flags); internal void lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr); #endif // LINUX_DEMON_H From 980a9908d187a800e484da802c6a4b3d8e786f7b Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sun, 9 Aug 2026 20:27:00 -0700 Subject: [PATCH 026/251] linux_demon: fix kill regressions from flattening --- src/linux/demon/linux_demon.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 3fc457ae..f2e58ac0 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1810,6 +1810,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) //- rjf: if signal was unhandled at this point -> kill process if(!signal_handled) { + signal_handled = 1; if(LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)) >= 0) { process->state = LNX_DMN_ProcessState_ExecFailedDoExit; @@ -2313,19 +2314,21 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) ////////////////////////// //- rjf: thread done -> release // - if(thread_done) + if(thread_done && thread) { + LNX_DMN_Process *thread_process = thread->process; + // rjf: store main thread's exit code - if(thread->tid == thread->process->pid) + if(thread->tid == thread_process->pid) { - thread->process->main_thread_exit_code = thread_done_exit_code; + thread_process->main_thread_exit_code = thread_done_exit_code; } // rjf: push exit event { DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_ExitThread; - e->process = lnx_dmn_handle_from_process(thread->process); + e->process = lnx_dmn_handle_from_process(thread_process); e->thread = lnx_dmn_handle_from_thread(thread); e->code = (U32)thread_done_exit_code; } @@ -2334,31 +2337,31 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) lnx_dmn_thread_release(thread); // rjf: last thread in process? -> exit process too - if(thread->process->thread_count == 0) + if(thread_process->thread_count == 0) { // rjf: push module events - for EachNode(module, LNX_DMN_Module, thread->process->ctx->first_module) + for EachNode(module, LNX_DMN_Module, thread_process->ctx->first_module) { - lnx_dmn_push_event_unload_module(arena, &events, thread->process, module); + lnx_dmn_push_event_unload_module(arena, &events, thread_process, module); } // rjf: push process exit event { DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_ExitProcess; - e->process = lnx_dmn_handle_from_process(thread->process); - e->code = thread->process->main_thread_exit_code; + e->process = lnx_dmn_handle_from_process(thread_process); + e->code = thread_process->main_thread_exit_code; } // rjf: release process storage - lnx_dmn_process_release(thread->process); + lnx_dmn_process_release(thread_process); } } ////////////////////////// //- rjf: process done -> release // - if(process_done) + if(process_done && process) { lnx_dmn_process_release(process); } From 9f83e388d2cd0814606cbe14f2d6e48bcab4c0d2 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 10 Aug 2026 11:56:42 -0600 Subject: [PATCH 027/251] linux_demon: more reading, flattening, elimination of unnecessary helpers / layers --- src/linux/demon/linux_demon.c | 421 ++++++++++++++++++---------------- src/linux/demon/linux_demon.h | 23 +- 2 files changed, 223 insertions(+), 221 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index f2e58ac0..31fbfff3 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -432,50 +432,6 @@ lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *pa return process; } -internal LNX_DMN_Thread * -lnx_dmn_thread_alloc(LNX_DMN_Process *process, LNX_DMN_ThreadState thread_state, pid_t tid) -{ - void *reg_block; - if(process->ctx->free_reg_blocks.node_count) - { - String8Node *n = str8_list_pop_front(&process->ctx->free_reg_blocks); - reg_block = n->string.str; - str8_list_push_node(&process->ctx->free_reg_block_nodes, n); - } - else - { - ARCH_Info *arch_info = arch_info_from_arch(process->ctx->arch); - U64 reg_block_size = arch_info->reg_block_size; - reg_block = push_array(process->ctx->arena, U8, reg_block_size); - } - - LNX_DMN_Thread *thread = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Thread)->thread; - thread->tid = tid; - thread->state = thread_state; - thread->process = process; - thread->reg_block = reg_block; - thread->dtv_base_vaddr = lnx_dmn_tls_root_vaddr_from_reg_block(process->fd, process->ctx->arch, reg_block); - if(thread_state == LNX_DMN_ThreadState_Stopped) - { - thread->is_reg_block_dirty = !lnx_dmn_thread_read_reg_block(thread); - } - - // add thread to the list - DLLPushBack(process->first_thread, process->last_thread, thread); - process->thread_count += 1; - - // push tid -> thread mapping - hash_table_push_u64_raw(lnx_dmn_state->arena, lnx_dmn_state->tid_ht, thread->tid, thread); - - // update global thread counter - if(thread_state == LNX_DMN_ThreadState_PendingCreation) - { - lnx_dmn_state->threads_pending_creation += 1; - } - - return thread; -} - internal LNX_DMN_Module * lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main) { @@ -532,42 +488,6 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 return module; } -internal void -lnx_dmn_process_release(LNX_DMN_Process *process) -{ - // update global state - AssertAlways(lnx_dmn_state->process_count > 0); - DLLRemove(lnx_dmn_state->first_process, lnx_dmn_state->last_process, process); - lnx_dmn_state->process_count -= 1; - - // update pending process tracker - if(process->state != LNX_DMN_ProcessState_Normal) - { - Assert(lnx_dmn_state->process_pending_creation > 0); - lnx_dmn_state->process_pending_creation -= 1; - } - - // close memory handle - if(LNX_RETRY_ON_EINTR(close(process->fd)) < 0) { Assert(0 && "failed to close memory descriptor"); } - - // remove pid mapping - hash_table_purge_u64(lnx_dmn_state->pid_ht, process->pid); - - // release the context - if(process->ctx) - { - process->ctx->ref_count -= 1; - if(process->ctx->ref_count == 0) - { - Arena *arena = process->ctx->arena; - lnx_dmn_entity_release((LNX_DMN_Entity *)process->ctx); - } - } - - // release process entity - lnx_dmn_entity_release((LNX_DMN_Entity *)process); -} - internal void lnx_dmn_thread_release(LNX_DMN_Thread *thread) { @@ -1221,29 +1141,6 @@ lnx_dmn_tls_root_vaddr_from_reg_block(int fd, Arch arch, void *reg_block) //////////////////////////////// //~ List Helpers -internal void -lnx_dmn_thread_ptr_list_push_node(LNX_DMN_ThreadPtrList *list, LNX_DMN_ThreadPtrNode *n) -{ - DLLPushBack(list->first, list->last, n); - list->count += 1; -} - -internal LNX_DMN_ThreadPtrNode * -lnx_dmn_thread_ptr_list_push(Arena *arena, LNX_DMN_ThreadPtrList *list, LNX_DMN_Thread *v) -{ - LNX_DMN_ThreadPtrNode *n = push_array(arena, LNX_DMN_ThreadPtrNode, 1); - n->v = v; - lnx_dmn_thread_ptr_list_push_node(list, n); - return n; -} - -internal void -lnx_dmn_thread_ptr_list_remove(LNX_DMN_ThreadPtrList *list, LNX_DMN_ThreadPtrNode *n) -{ - DLLRemove(list->first, list->last, n); - list->count -= 1; -} - internal LNX_DMN_ModulePtrNode * lnx_dmn_module_ptr_list_push(Arena *arena, LNX_DMN_ModulePtrList *list, LNX_DMN_Module *v) { @@ -1605,7 +1502,16 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) //////////////////////////// //- rjf: schedule threads to run // - LNX_DMN_ThreadPtrList running_threads = {0}; + typedef struct ThreadNode ThreadNode; + struct ThreadNode + { + ThreadNode *next; + LNX_DMN_Thread *v; + }; + ThreadNode *first_scheduled_thread = 0; + ThreadNode *last_scheduled_thread = 0; + U64 scheduled_thread_count = 0; + U64 running_thread_count = 0; { for EachNode(process, LNX_DMN_Process, lnx_dmn_state->first_process) { @@ -1684,9 +1590,13 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // resume thread if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, thread->tid, 0, sig_code)) >= 0) { - thread->state = LNX_DMN_ThreadState_Running; + thread->state = LNX_DMN_ThreadState_Running; thread->is_reg_block_dirty = 1; - lnx_dmn_thread_ptr_list_push(scratch.arena, &running_threads, thread); + ThreadNode *n = push_array(scratch.arena, ThreadNode, 1); + SLLQueuePush(first_scheduled_thread, last_scheduled_thread, n); + n->v = thread; + scheduled_thread_count += 1; + running_thread_count += 1; } else { @@ -1710,20 +1620,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } - //////////////////////////// - //- rjf: hash running threads tids - // - HashTable *running_threads_ht = hash_table_init(scratch.arena, running_threads.count * 2); - for EachNode(n, LNX_DMN_ThreadPtrNode, running_threads.first) - { - hash_table_push_u64_raw(scratch.arena, running_threads_ht, n->v->tid, n); - } - //////////////////////////// //- rjf: wait the next signal & generate events // B32 is_halt_done = 0; - LNX_DMN_ThreadPtrList stopped_threads = {0}; for(;;) { ////////////////////////// @@ -1738,14 +1638,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } ////////////////////////// - //- rjf: unpack wait_id + //- rjf: unpack signal // LNX_DMN_Process *process = lnx_dmn_process_from_pid(wait_id); LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); - - ////////////////////////// - //- rjf: unpack signal's status - // int wifexited = WIFEXITED(status); int wifsignaled = WIFSIGNALED(status); int wifstopped = WIFSTOPPED(status); @@ -1753,15 +1649,33 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) int event_code = (status >> 16); ////////////////////////// - //- rjf: handle signals from initializing processes + //- rjf: set up signal handling outputs (top-level handling bit) // B32 signal_handled = 0; + + ////////////////////////// + //- rjf: set up signal handling outputs (process exits / creation) + // B32 process_done = 0; B32 process_new = 0; LNX_DMN_CreateProcessFlags process_new_flags = 0; + + ////////////////////////// + //- rjf: set up signal handling outputs (thread exits / creation / stops) + // B32 thread_done = 0; + B32 thread_done_report_events = 0; U64 thread_done_exit_code = 0; B32 thread_new = 0; + LNX_DMN_ThreadState thread_new_state = LNX_DMN_ThreadState_Null; + B32 thread_new_report_events = 0; + LNX_DMN_Process *thread_new_parent = process; + B32 thread_stop = 0; + B32 thread_initial_stop = 0; + + ////////////////////////// + //- rjf: handle signals from initializing processes + // if(process && process->state != LNX_DMN_ProcessState_Normal) { //- rjf: advance process initialization state @@ -1824,23 +1738,15 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } ////////////////////////// - //- rjf: any thread stop -> interrupt all threads + //- rjf: any thread stop -> update thread state, halt other threads if needed // if(!signal_handled && (wifexited || wifsignaled || wifstopped)) { - LNX_DMN_ThreadPtrNode *thread_n = hash_table_search_u64_raw(running_threads_ht, wait_id); - if(thread_n) + B32 thread_running = (thread && thread->state == LNX_DMN_ThreadState_Running); + + // rjf: update thread running state + if(thread) { - LNX_DMN_Thread *thread = thread_n->v; - - // remove mapping - hash_table_purge_u64(running_threads_ht, thread->tid); - - // move thread to the stopped list - lnx_dmn_thread_ptr_list_remove(&running_threads, thread_n); - lnx_dmn_thread_ptr_list_push_node(&stopped_threads, thread_n); - - // update thread state if(wifstopped && !wifsignaled && !wifexited) { thread->state = LNX_DMN_ThreadState_Stopped; @@ -1849,17 +1755,29 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { thread->state = LNX_DMN_ThreadState_Exited; } - else { InvalidPath; } - - // stop all other threads - if(stopped_threads.count == 1) + } + + // rjf: if this is the first thread to stop out of all we scheduled -> interrupt + // all other scheduled threads + if(scheduled_thread_count != 0 && running_thread_count == scheduled_thread_count) + { + for EachNode(n, ThreadNode, first_scheduled_thread) { - for EachNode(n, LNX_DMN_ThreadPtrNode, running_threads.first) + if(n->v != thread && n->v->state == LNX_DMN_ThreadState_Running) { - if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, n->v->tid, 0, 0)) < 0) { Assert(0 && "failed to interrupt process"); } + if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, n->v->tid, 0, 0)) < 0) + { + log_infof("ptrace error: Couldn't send PTRACE_INTERRUPT to TID %I64u.", n->v->tid); + } } } } + + // rjf: decrement running thread count + if(thread_running) + { + running_thread_count -= 1; + } } ////////////////////////// @@ -1872,6 +1790,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { thread_done = 1; thread_done_exit_code = wifexited ? WEXITSTATUS(status) : WTERMSIG(status); + thread_done_report_events = 1; signal_handled = 1; } @@ -1897,6 +1816,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { switch(event_code) { + default:{}break; + //////////////////// //- rjf: 0 -> general trap path // @@ -2148,19 +2069,22 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // case PTRACE_EVENT_CLONE: { - // kernel stopped the parent just before scheduling the child to + // NOTE: kernel stopped the parent just before scheduling the child to // give us a chance to prepare to trace it; next event for the child // will be a PTRACE_EVENT_STOP - - pid_t new_tid; + pid_t new_tid = 0; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid)) >= 0) { - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); - - // create a new partially inited thread - lnx_dmn_thread_alloc(thread->process, LNX_DMN_ThreadState_PendingCreation, new_tid); + thread_new = 1; + thread_new_report_events = 0; + thread_new_state = LNX_DMN_ThreadState_PendingCreation; + thread_new_parent = thread->process; + lnx_dmn_state->threads_pending_creation += 1; + } + else + { + log_infof("ptrace error: PTRACE_EVENT_CLONE reported, but ptrace did not successfully report the new TID."); } - else { Assert(0 && "failed to get new tid"); } }break; //////////////////// @@ -2172,6 +2096,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(process->debug_subprocesses) { thread_done = 1; + thread_done_report_events = 1; thread_done_exit_code = 0; process_new = 1; process_new_flags = LNX_DMN_CreateProcessFlag_DebugSubprocesses; @@ -2181,42 +2106,35 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, wait_id, 0, 0)) >= 0) { thread_done = 1; + thread_done_report_events = 1; thread_done_exit_code = 0; } else { Assert(0 && "failed to detach"); } } }break; - //////////////////// - //- rjf: exit event -> exit tracing not enabled, so we don't really have anything to do here. - // - case PTRACE_EVENT_EXIT: - { - }break; - - //////////////////// - //- TODO(rjf): seccomp? - // - case PTRACE_EVENT_SECCOMP: - { - NotImplemented; - }break; - //////////////////// //- rjf: thread stops // case PTRACE_EVENT_STOP: { + // rjf: stop event for pending threads -> thread is created & stopped if(thread->state == LNX_DMN_ThreadState_PendingCreation) { - LNX_DMN_Process *process = thread->process; - lnx_dmn_thread_release(thread); - thread = lnx_dmn_thread_alloc(process, LNX_DMN_ThreadState_Stopped, wait_id); - lnx_dmn_push_event_create_thread(arena, &events, thread); - } - else - { - AssertAlways(thread->state == LNX_DMN_ThreadState_Stopped); + thread->state = LNX_DMN_ThreadState_Stopped; + lnx_dmn_state->threads_pending_creation -= 1; + thread_stop = 1; + thread_initial_stop = 1; + + // rjf: report new thread + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_CreateThread; + e->process = lnx_dmn_handle_from_process(thread->process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->arch = thread->process->ctx->arch; + e->code = thread->tid; + // TODO(rjf): e->stack_pointer = ???; + e->tls_root_vaddr = thread->dtv_base_vaddr; } }break; } @@ -2249,7 +2167,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); thread->pass_through_signal = 1; - thread->pass_through_signo = wstopsig; + thread->pass_through_signo = wstopsig; local_persist B8 is_repeatable[] = { 0, // null @@ -2325,6 +2243,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } // rjf: push exit event + if(thread_done_report_events) { DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_ExitThread; @@ -2340,12 +2259,16 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(thread_process->thread_count == 0) { // rjf: push module events - for EachNode(module, LNX_DMN_Module, thread_process->ctx->first_module) + if(thread_done_report_events) { - lnx_dmn_push_event_unload_module(arena, &events, thread_process, module); + for EachNode(module, LNX_DMN_Module, thread_process->ctx->first_module) + { + lnx_dmn_push_event_unload_module(arena, &events, thread_process, module); + } } // rjf: push process exit event + if(thread_done_report_events) { DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_ExitProcess; @@ -2353,8 +2276,9 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->code = thread_process->main_thread_exit_code; } - // rjf: release process storage - lnx_dmn_process_release(thread_process); + // rjf: turn on process-end path + process = thread_process; + process_done = 1; } } @@ -2363,16 +2287,34 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // if(process_done && process) { - lnx_dmn_process_release(process); + DLLRemove(lnx_dmn_state->first_process, lnx_dmn_state->last_process, process); + lnx_dmn_state->process_count -= 1; + if(process->state != LNX_DMN_ProcessState_Normal) + { + lnx_dmn_state->process_pending_creation -= 1; + } + LNX_RETRY_ON_EINTR(close(process->fd)); + hash_table_purge_u64(lnx_dmn_state->pid_ht, process->pid); + if(process->ctx) + { + process->ctx->ref_count -= 1; + if(process->ctx->ref_count == 0) + { + Arena *arena = process->ctx->arena; + lnx_dmn_entity_release((LNX_DMN_Entity *)process->ctx); + } + } + lnx_dmn_entity_release((LNX_DMN_Entity *)process); } ////////////////////////// //- rjf: new process -> create // + LNX_DMN_Process *new_process = 0; if(process_new) { LNX_DMN_Process *parent_process = 0; - LNX_DMN_Process *new_process = lnx_dmn_process_alloc(wait_id, LNX_DMN_ProcessState_Normal, 0, !!(process_new_flags & LNX_DMN_CreateProcessFlag_DebugSubprocesses), !!(process_new_flags & LNX_DMN_CreateProcessFlag_Cow)); + new_process = lnx_dmn_process_alloc(wait_id, LNX_DMN_ProcessState_Normal, 0, !!(process_new_flags & LNX_DMN_CreateProcessFlag_DebugSubprocesses), !!(process_new_flags & LNX_DMN_CreateProcessFlag_Cow)); //////////////////////// //- rjf: get process context - create if this isn't cloning parent process @@ -2958,12 +2900,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } //////////////////////// - //- rjf: create main thread - // - lnx_dmn_thread_alloc(new_process, LNX_DMN_ThreadState_Stopped, wait_id); - - //////////////////////// - //- rjf: push events + //- rjf: report process creation // { DMN_Event *e = dmn_event_list_push(arena, &events); @@ -2972,42 +2909,128 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->arch = new_process->ctx->arch; e->code = new_process->pid; } - for EachNode(thread, LNX_DMN_Thread, new_process->first_thread) + + //////////////////////// + //- rjf: create main thread + // { - lnx_dmn_push_event_create_thread(arena, &events, thread); + thread_new = 1; + thread_new_state = LNX_DMN_ThreadState_Stopped; + thread_new_parent = new_process; + thread_new_report_events = 1; } + } + + ////////////////////////// + //- rjf: new thread -> create + // + if(thread_new) + { + // rjf: allocate + LNX_DMN_Thread *new_thread = 0; + { + // rjf: allocate reg block + void *reg_block = 0; + if(process->ctx->free_reg_blocks.node_count) + { + String8Node *n = str8_list_pop_front(&process->ctx->free_reg_blocks); + reg_block = n->string.str; + str8_list_push_node(&process->ctx->free_reg_block_nodes, n); + } + else + { + ARCH_Info *arch_info = arch_info_from_arch(process->ctx->arch); + U64 reg_block_size = arch_info->reg_block_size; + reg_block = push_array(process->ctx->arena, U8, reg_block_size); + } + + // rjf: allocate thread + new_thread = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Thread)->thread; + new_thread->tid = wait_id; + new_thread->state = thread_new_state; + new_thread->process = thread_new_parent; + new_thread->reg_block = reg_block; + + // rjf: hook thread to process + DLLPushBack(thread_new_parent->first_thread, thread_new_parent->last_thread, new_thread); + thread_new_parent->thread_count += 1; + + // rjf: map tid -> thread mapping + hash_table_push_u64_raw(lnx_dmn_state->arena, lnx_dmn_state->tid_ht, new_thread->tid, new_thread); + } + + // rjf: report + if(thread_new_report_events) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_CreateThread; + e->process = lnx_dmn_handle_from_process(new_thread->process); + e->thread = lnx_dmn_handle_from_thread(new_thread); + e->arch = new_thread->process->ctx->arch; + e->code = new_thread->tid; + // TODO(rjf): e->stack_pointer = ???; + e->tls_root_vaddr = new_thread->dtv_base_vaddr; + } + + // rjf: if thread is stopped -> turn on initial stop path + if(thread_new_state == LNX_DMN_ThreadState_Stopped) + { + thread = new_thread; + thread_stop = 1; + thread_initial_stop = 1; + } + } + + ////////////////////////// + //- rjf: thread stop -> read registers. if initial stop, form base addr to DTV (TLS) + // + if(thread_stop) + { + thread->is_reg_block_dirty = !lnx_dmn_thread_read_reg_block(thread); + if(thread_initial_stop) + { + thread->dtv_base_vaddr = lnx_dmn_tls_root_vaddr_from_reg_block(thread->process->fd, thread->process->ctx->arch, thread->reg_block); + } + } + + ////////////////////////// + //- rjf: process was created -> report events for module loads + // + if(new_process) + { for EachNode(module, LNX_DMN_Module, new_process->ctx->first_module) { lnx_dmn_push_event_load_module(arena, &events, new_process->first_thread, module); } - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_HandshakeComplete; - e->process = lnx_dmn_handle_from_process(new_process); - e->thread = lnx_dmn_handle_from_thread(new_process->first_thread); - e->arch = new_process->ctx->arch; - } + } + + ////////////////////////// + //- rjf: process was created -> report event for handshake completion + // + if(new_process) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_HandshakeComplete; + e->process = lnx_dmn_handle_from_process(new_process); + e->thread = lnx_dmn_handle_from_thread(new_process->first_thread); } ////////////////////////// //- rjf: break if all threads are stopped, no pending processes, no pending threads // - if(running_threads.count == 0 && lnx_dmn_state->process_pending_creation == 0 && lnx_dmn_state->threads_pending_creation == 0) + if(running_thread_count == 0 && lnx_dmn_state->process_pending_creation == 0 && lnx_dmn_state->threads_pending_creation == 0) { break; } } //////////////////////////// - //- rjf: do halt + //- rjf: halted -> push halt event, reset halter state // if(is_halt_done) { - // push event DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_Halt; - - // reset state lnx_dmn_state->halter_tid = 0; lnx_dmn_state->halt_code = 0; lnx_dmn_state->halt_user_data = 0; diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index f569c12f..e4b7989b 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -181,22 +181,6 @@ struct LNX_DMN_Thread U64 dtv_base_vaddr; }; -typedef struct LNX_DMN_ThreadPtrNode LNX_DMN_ThreadPtrNode; -struct LNX_DMN_ThreadPtrNode -{ - LNX_DMN_ThreadPtrNode *next; - LNX_DMN_ThreadPtrNode *prev; - LNX_DMN_Thread *v; -}; - -typedef struct LNX_DMN_ThreadPtrList LNX_DMN_ThreadPtrList; -struct LNX_DMN_ThreadPtrList -{ - LNX_DMN_ThreadPtrNode *first; - LNX_DMN_ThreadPtrNode *last; - U64 count; -}; - typedef struct LNX_DMN_Module LNX_DMN_Module; struct LNX_DMN_Module { @@ -427,9 +411,7 @@ internal void lnx_dmn_entity_release(LNX_DMN_Entity *entity); //- rjf: specialized allocation / deallocation helpers internal LNX_DMN_Process *lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *parent_process, B32 debug_subprocess, B32 is_cow); -internal LNX_DMN_Thread *lnx_dmn_thread_alloc(LNX_DMN_Process *process, LNX_DMN_ThreadState thread_state, pid_t tid); internal LNX_DMN_Module *lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main); -internal void lnx_dmn_process_release(LNX_DMN_Process *process); internal void lnx_dmn_thread_release(LNX_DMN_Thread *thread); internal void lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_Module *module); @@ -463,10 +445,7 @@ internal U64 lnx_dmn_tls_root_vaddr_from_reg_block(int fd, Arch arch, void *reg //////////////////////////////// //~ List Helpers -internal void lnx_dmn_thread_ptr_list_push_node(LNX_DMN_ThreadPtrList *list, LNX_DMN_ThreadPtrNode *n); -internal LNX_DMN_ThreadPtrNode * lnx_dmn_thread_ptr_list_push(Arena *arena, LNX_DMN_ThreadPtrList *list, LNX_DMN_Thread *v); -internal void lnx_dmn_thread_ptr_list_remove(LNX_DMN_ThreadPtrList *list, LNX_DMN_ThreadPtrNode *n); -internal LNX_DMN_ModulePtrNode * lnx_dmn_module_ptr_list_push(Arena *arena, LNX_DMN_ModulePtrList *list, LNX_DMN_Module *v); +internal LNX_DMN_ModulePtrNode *lnx_dmn_module_ptr_list_push(Arena *arena, LNX_DMN_ModulePtrList *list, LNX_DMN_Module *v); //////////////////////////////// //~ Debug Event Pushers From 6b30c395d70a051e81b5826024f46a6626e3be69 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 10 Aug 2026 14:20:22 -0600 Subject: [PATCH 028/251] linux_demon: eliminate active-trap layer; do pre-trap instruction memory read in one batch before doing write, robustly handles duplicates; eliminate deduplication inside linux demon (ultimately we want to deduplicate traps sent by the dbg engine, but we should just dedup at that layer, long before the set of traps ever reaches the demon backend) --- src/linux/demon/linux_demon.c | 152 ++++++++++++++++++---------------- src/linux/demon/linux_demon.h | 5 +- src/win32/demon/win32_demon.c | 25 +++++- 3 files changed, 104 insertions(+), 78 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 31fbfff3..e505b482 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -689,7 +689,7 @@ lnx_dmn_process_from_pid(pid_t pid) //~ rjf: Thread Helpers internal U64 -lnx_dmn_thread_read_ip(LNX_DMN_Thread *thread) +lnx_dmn_ip_from_thread(LNX_DMN_Thread *thread) { ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); U64 result = arch_ip_from_reg_block(arch_info, thread->reg_block); @@ -1154,19 +1154,6 @@ lnx_dmn_module_ptr_list_push(Arena *arena, LNX_DMN_ModulePtrList *list, LNX_DMN_ //////////////////////////////// //~ Debug Event Pushers -internal void -lnx_dmn_push_event_create_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_CreateThread; - e->process = lnx_dmn_handle_from_process(thread->process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->arch = thread->process->ctx->arch; - e->code = thread->tid; - // TODO(rjf): e->stack_pointer = ???; - e->tls_root_vaddr = thread->dtv_base_vaddr; -} - internal void lnx_dmn_push_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, LNX_DMN_Module *module) { @@ -1445,44 +1432,42 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // if(lnx_dmn_state->process_count > 0) { + ARCH_Info *arch_info = arch_info_from_arch(Arch_CURRENT); + //////////////////////////// - //- rjf: write all trap instructions + //- rjf: read initial bytes at all trap locations // - LNX_DMN_ActiveTrap *active_trap_first = 0; - LNX_DMN_ActiveTrap *active_trap_last = 0; + U64 bytes_per_trap = arch_info->trap_instruction.size; + U8 *trap_swap_bytes = push_array(scratch.arena, U8, ctrls->traps.trap_count * bytes_per_trap); { - HashTable *process_ht = hash_table_init(scratch.arena, lnx_dmn_state->process_count); + U64 trap_idx = 0; for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) { for EachIndex(n_idx, n->count) { - // skip hardware breakpoints DMN_Trap *trap = n->v+n_idx; - if(trap->flags) { continue; } - - HashTable *active_trap_ht = hash_table_search_u64_raw(process_ht, trap->process.u64[0]); - if(active_trap_ht == 0) + if(trap->flags == 0) { - active_trap_ht = hash_table_init(scratch.arena, ctrls->traps.trap_count); - hash_table_push_u64_raw(scratch.arena, process_ht, trap->process.u64[0], active_trap_ht); + dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr + bytes_per_trap), trap_swap_bytes + trap_idx*bytes_per_trap); + } + trap_idx += 1; + } + } + } + + //////////////////////////// + //- rjf: write all trap instructions + // + { + for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) + { + for EachIndex(n_idx, n->count) + { + DMN_Trap *trap = n->v+n_idx; + if(trap->flags == 0) + { + dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr + bytes_per_trap), arch_info->trap_instruction.str); } - - // TODO: ctrl sends down duplicate traps - LNX_DMN_ActiveTrap *is_set = hash_table_search_u64_raw(active_trap_ht, trap->vaddr); - if(is_set) { continue; } - - // TODO: ctrl sends down traps for exited process - LNX_DMN_Process *process = lnx_dmn_process_from_handle(trap->process); - if(!process) { continue; } - - // trap instruction - LNX_DMN_ActiveTrap *active_trap = lnx_dmn_set_trap(scratch.arena, trap); - - // add trap to the active list - SLLQueuePush(active_trap_first, active_trap_last, active_trap); - - // add (address -> trap) - hash_table_push_u64_raw(scratch.arena, active_trap_ht, trap->vaddr, active_trap); } } } @@ -1823,7 +1808,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // case 0: { - // translate signal code to event siginfo_t siginfo = {0}; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0) { @@ -1833,26 +1817,29 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) case SI_KERNEL: case TRAP_BRKPT: { - U64 ip = lnx_dmn_thread_read_ip(thread); + //- rjf: unpack thread IP / process + U64 ip = lnx_dmn_ip_from_thread(thread); + DMN_Handle process_handle = lnx_dmn_handle_from_process(process); - // is this user trap? - LNX_DMN_ActiveTrap *hit_user_trap = 0; + //- rjf: IP -> find associated user trap + DMN_Trap *hit_user_trap = 0; { - DMN_Handle process_handle = lnx_dmn_handle_from_process(process); - for EachNode(active_trap, LNX_DMN_ActiveTrap, active_trap_first) + for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) { - if(MemoryCompare(&active_trap->trap->process, &process_handle, sizeof(DMN_Handle)) == 0) + for EachIndex(n_idx, n->count) { - if(active_trap->trap->vaddr == ip-1) + DMN_Trap *trap = n->v+n_idx; + if(trap->flags == 0 && dmn_handle_match(trap->process, process_handle) && trap->vaddr == ip - bytes_per_trap) { - hit_user_trap = active_trap; - break; + hit_user_trap = trap; + goto dbl_break_find_user_trap; } } } + dbl_break_find_user_trap:; } - // is this a probe trap? + //- rjf: no user trap -> correllate w/ probe LNX_DMN_ProbeKind probe_kind = LNX_DMN_ProbeKind_Null; if(hit_user_trap == 0) { @@ -1866,6 +1853,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } + //- rjf: probe trap: (module) initialization complete if(probe_kind == LNX_DMN_ProbeKind_InitComplete) { B32 is_init_completed = 0; @@ -1885,6 +1873,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) init_complete_exit:; AssertAlways(is_init_completed); } + + //- rjf: probe trap: (module) relocation complete else if(probe_kind == LNX_DMN_ProbeKind_RelocComplete) { B32 is_reloc_completed = 0; @@ -1900,6 +1890,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) reloc_complete_exit:; AssertAlways(is_reloc_completed); } + + //- rjf: probe trap: (module) unmap complete else if(probe_kind == LNX_DMN_ProbeKind_UnmapComplete) { B32 is_unmap_completed = 0; @@ -1968,20 +1960,23 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) AssertAlways(is_unmap_completed); } + //- rjf: no associated probe -> either trap or breakpoint if(probe_kind == LNX_DMN_ProbeKind_Null) { - // rollback IP on user traps + // rjf: on user breakpoint trap -> roll back IP if(hit_user_trap) { - U64 ip = lnx_dmn_thread_read_ip(thread); + U64 ip = lnx_dmn_ip_from_thread(thread); lnx_dmn_thread_write_ip(thread, ip - 1); } + // rjf: generate event DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_Breakpoint; + e->kind = hit_user_trap ? DMN_EventKind_Breakpoint : DMN_EventKind_Trap; e->process = lnx_dmn_handle_from_process(process); e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_thread_read_ip(thread); + e->instruction_pointer = lnx_dmn_ip_from_thread(thread); + e->user_data = hit_user_trap ? hit_user_trap->id : 0; } }break; @@ -1993,7 +1988,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->kind = DMN_EventKind_SingleStep; e->process = lnx_dmn_handle_from_process(thread->process); e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_thread_read_ip(thread); + e->instruction_pointer = lnx_dmn_ip_from_thread(thread); e->address = e->instruction_pointer; }break; @@ -2045,7 +2040,11 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) case TRAP_UNK: { NotImplemented; }break; default: { InvalidPath; } break; } - } else { Assert(0 && "failed to get signal info"); } + } + else + { + log_infof("ptrace error: Failed to get signal info via PTRACE_GETSIGINFO."); + } }break; //////////////////// @@ -2208,7 +2207,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->kind = DMN_EventKind_Exception; e->process = lnx_dmn_handle_from_process(thread->process); e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_thread_read_ip(thread); + e->instruction_pointer = lnx_dmn_ip_from_thread(thread); e->address = e->instruction_pointer; e->code = wstopsig; e->exception_repeated = wstopsig < ArrayCount(is_repeatable) ? is_repeatable[wstopsig] : 0; @@ -3040,15 +3039,18 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) //////////////////////////// //- rjf: unset all traps - restore original bytes // - for EachNode(active_trap, LNX_DMN_ActiveTrap, active_trap_first) { - if(active_trap->good) + U64 trap_idx = 0; + for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) { - LNX_DMN_Process *process = lnx_dmn_process_from_handle(active_trap->trap->process); - // NOTE(rjf): this process may have been killed during the wait - skip such processes - if(process != 0) + for EachIndex(n_idx, n->count) { - dmn_process_write(active_trap->trap->process, r1u64(active_trap->trap->vaddr, active_trap->trap->vaddr + active_trap->swap_bytes.size), active_trap->swap_bytes.str); + DMN_Trap *trap = n->v+n_idx; + if(trap->flags == 0) + { + dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr + bytes_per_trap), trap_swap_bytes + trap_idx*bytes_per_trap); + } + trap_idx += 1; } } } @@ -3161,10 +3163,13 @@ internal U64 dmn_process_read(DMN_Handle process_handle, Rng1U64 range, void *dst) { U64 result = 0; - LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); - if(process) + DMN_AccessScope { - result = lnx_dmn_read(process->fd, range, dst); + LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); + if(process) + { + result = lnx_dmn_read(process->fd, range, dst); + } } return result; } @@ -3173,10 +3178,13 @@ internal B32 dmn_process_write(DMN_Handle process_handle, Rng1U64 range, void *src) { B32 result = 0; - LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); - if(process) + DMN_AccessScope { - result = lnx_dmn_write(process->fd, range, src); + LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); + if(process) + { + result = lnx_dmn_write(process->fd, range, src); + } } return result; } diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index e4b7989b..3f699bf4 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -435,7 +435,7 @@ internal LNX_DMN_Process *lnx_dmn_process_from_pid(pid_t pid); //////////////////////////////// //~ rjf: Thread Helpers -internal U64 lnx_dmn_thread_read_ip(LNX_DMN_Thread *thread); +internal U64 lnx_dmn_ip_from_thread(LNX_DMN_Thread *thread); internal void lnx_dmn_thread_write_ip(LNX_DMN_Thread *thread, U64 ip); internal B32 lnx_dmn_thread_read_reg_block(LNX_DMN_Thread *thread); internal B32 lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread); @@ -450,13 +450,12 @@ internal LNX_DMN_ModulePtrNode *lnx_dmn_module_ptr_list_push(Arena *arena, LNX_D //////////////////////////////// //~ Debug Event Pushers -internal void lnx_dmn_push_event_create_thread(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread); internal void lnx_dmn_push_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, LNX_DMN_Module *module); internal void lnx_dmn_push_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process, LNX_DMN_Module *module); //////////////////////////////// //~ Debug Event -internal void lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr); +internal void lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr); #endif // LINUX_DEMON_H diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index 8e3d6fb0..360d60a1 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -1685,10 +1685,30 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } + ////////////////////////// + //- rjf: read all initial memory at trap locations + // + U8 *trap_swap_bytes = push_array(scratch.arena, U8, ctrls->traps.trap_count); + { + U64 trap_idx = 0; + for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next) + { + for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1) + { + DMN_Trap *trap = n->v+n_idx; + if(trap->flags == 0) + { + // TODO(rjf): assumes x86 + trap_swap_bytes[trap_idx] = 0xCC; + dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr+1), trap_swap_bytes+trap_idx); + } + } + } + } + ////////////////////////// //- rjf: write all traps into memory // - U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, ctrls->traps.trap_count); ProfScope("write all traps into memory") { U64 trap_idx = 0; @@ -1699,8 +1719,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) DMN_Trap *trap = n->v+n_idx; if(trap->flags == 0) { - trap_swap_bytes[trap_idx] = 0xCC; - dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr+1), trap_swap_bytes+trap_idx); + // TODO(rjf): assumes x86 U8 int3 = 0xCC; dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &int3); } From 45aeca0bf99081a5259d74572338139d74c44735 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 10 Aug 2026 14:40:43 -0600 Subject: [PATCH 029/251] linux_demon: intrinsic links in threads/processes for pid mapping --- src/linux/demon/linux_demon.c | 137 ++++++++++++++++++++++------------ src/linux/demon/linux_demon.h | 42 ++++++++--- 2 files changed, 121 insertions(+), 58 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index e505b482..5d574fd1 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -425,8 +425,13 @@ lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *pa DLLPushBack(lnx_dmn_state->first_process, lnx_dmn_state->last_process, process); lnx_dmn_state->process_count += 1; - // push pid -> LNX_DMN_Process mapping - hash_table_push_u64_raw(lnx_dmn_state->arena, lnx_dmn_state->pid_ht, pid, process); + // rjf: record in id -> process table + { + U64 key = (U64)pid; + U64 hash = u64_hash_from_str8(str8_struct(&key)); + U64 slot_idx = hash%lnx_dmn_state->process_from_pid_slots_count; + DLLPushBack_NP(lnx_dmn_state->process_from_pid_slots[slot_idx].first, lnx_dmn_state->process_from_pid_slots[slot_idx].last, process, pid_next, pid_prev); + } scratch_end(scratch); return process; @@ -488,42 +493,6 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 return module; } -internal void -lnx_dmn_thread_release(LNX_DMN_Thread *thread) -{ - LNX_DMN_Process *process = thread->process; - - // purge tid mapping - hash_table_purge_u64(lnx_dmn_state->tid_ht, thread->tid); - - // update global thread counter - if(thread->state == LNX_DMN_ThreadState_PendingCreation) - { - AssertAlways(lnx_dmn_state->threads_pending_creation > 0); - lnx_dmn_state->threads_pending_creation -= 1; - } - - // remove thread from the list - Assert(process->thread_count > 0); - DLLRemove(process->first_thread, process->last_thread, thread); - process->thread_count -= 1; - - // push reg block to the free list - String8Node *reg_block_node; - if(process->ctx->free_reg_block_nodes.node_count) - { - reg_block_node = str8_list_pop_front(&process->ctx->free_reg_block_nodes); - } - else - { - reg_block_node = push_array(process->ctx->arena, String8Node, 1); - } - reg_block_node->string = str8(thread->reg_block, 0); - str8_list_push_node(&process->ctx->free_reg_blocks, reg_block_node); - - lnx_dmn_entity_release((LNX_DMN_Entity *)thread); -} - internal void lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_Module *module) { @@ -676,13 +645,37 @@ lnx_dmn_module_from_handle(DMN_Handle module_handle) internal LNX_DMN_Thread * lnx_dmn_thread_from_pid(pid_t tid) { - return hash_table_search_u64_raw(lnx_dmn_state->tid_ht, tid); + LNX_DMN_Thread *result = 0; + U64 key = (U64)tid; + U64 hash = u64_hash_from_str8(str8_struct(&key)); + U64 slot_idx = hash%lnx_dmn_state->thread_from_tid_slots_count; + for(LNX_DMN_Thread *t = lnx_dmn_state->thread_from_tid_slots[slot_idx].first; t != 0; t = t->tid_next) + { + if(t->tid == tid) + { + result = t; + break; + } + } + return result; } internal LNX_DMN_Process * lnx_dmn_process_from_pid(pid_t pid) { - return hash_table_search_u64_raw(lnx_dmn_state->pid_ht, pid); + LNX_DMN_Process *result = 0; + U64 key = (U64)pid; + U64 hash = u64_hash_from_str8(str8_struct(&key)); + U64 slot_idx = hash%lnx_dmn_state->process_from_pid_slots_count; + for(LNX_DMN_Process *p = lnx_dmn_state->process_from_pid_slots[slot_idx].first; p != 0; p = p->pid_next) + { + if(p->pid == pid) + { + result = p; + break; + } + } + return result; } //////////////////////////////// @@ -1229,14 +1222,21 @@ dmn_init(void) lnx_dmn_state->access_mutex = mutex_alloc(); lnx_dmn_state->entities_arena = arena_alloc(.reserve_size = GB(32), .commit_size = KB(64), .flags = ArenaFlag_NoChain); lnx_dmn_state->entities_base = push_array(lnx_dmn_state->entities_arena, LNX_DMN_Entity, 0); - lnx_dmn_state->tid_ht = hash_table_init(lnx_dmn_state->arena, 0x2000); - lnx_dmn_state->pid_ht = hash_table_init(lnx_dmn_state->arena, 0x400); lnx_dmn_state->halter_mutex = mutex_alloc(); lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Null); + // rjf: set up id <-> entity tables + { + lnx_dmn_state->process_from_pid_slots_count = 4096; + lnx_dmn_state->process_from_pid_slots = push_array(arena, LNX_DMN_ProcessSlot, lnx_dmn_state->process_from_pid_slots_count); + lnx_dmn_state->thread_from_tid_slots_count = 16384; + lnx_dmn_state->thread_from_tid_slots = push_array(arena, LNX_DMN_ThreadSlot, lnx_dmn_state->thread_from_tid_slots_count); + } + // find offsets of TLS index and TLS offset in the link_map struct // // TODO: assuming that target is using same libc version as debugger + // { LNX_DMN_DbDesc *tls_modid_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_modid"); LNX_DMN_DbDesc *tls_offset_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_offset"); @@ -1248,7 +1248,6 @@ dmn_init(void) lnx_dmn_state->tls_offset_desc = *tls_offset_desc; lnx_dmn_state->is_tls_detected = 1; } - else { Assert(0 && "invalid TLS desc"); } } } } @@ -2252,7 +2251,41 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } // rjf: release thread storage - lnx_dmn_thread_release(thread); + { + // update global thread counter + if(thread->state == LNX_DMN_ThreadState_PendingCreation) + { + lnx_dmn_state->threads_pending_creation -= 1; + } + + // rjf: remove thread from per-process thread list + DLLRemove(thread_process->first_thread, thread_process->last_thread, thread); + thread_process->thread_count -= 1; + + // rjf: remove thread from id -> thread table + { + U64 key = (U64)thread->tid; + U64 hash = u64_hash_from_str8(str8_struct(&key)); + U64 slot_idx = hash%lnx_dmn_state->thread_from_tid_slots_count; + DLLRemove_NP(lnx_dmn_state->thread_from_tid_slots[slot_idx].first, lnx_dmn_state->thread_from_tid_slots[slot_idx].last, thread, tid_next, tid_prev); + } + + // rjf: release reg block + String8Node *reg_block_node; + if(thread_process->ctx->free_reg_block_nodes.node_count) + { + reg_block_node = str8_list_pop_front(&thread_process->ctx->free_reg_block_nodes); + } + else + { + reg_block_node = push_array(thread_process->ctx->arena, String8Node, 1); + } + reg_block_node->string = str8(thread->reg_block, 0); + str8_list_push_node(&thread_process->ctx->free_reg_blocks, reg_block_node); + + // rjf: release entity record + lnx_dmn_entity_release((LNX_DMN_Entity *)thread); + } // rjf: last thread in process? -> exit process too if(thread_process->thread_count == 0) @@ -2293,7 +2326,12 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) lnx_dmn_state->process_pending_creation -= 1; } LNX_RETRY_ON_EINTR(close(process->fd)); - hash_table_purge_u64(lnx_dmn_state->pid_ht, process->pid); + { + U64 key = (U64)process->pid; + U64 hash = u64_hash_from_str8(str8_struct(&key)); + U64 slot_idx = hash%lnx_dmn_state->process_from_pid_slots_count; + DLLRemove_NP(lnx_dmn_state->process_from_pid_slots[slot_idx].first, lnx_dmn_state->process_from_pid_slots[slot_idx].last, process, pid_next, pid_prev); + } if(process->ctx) { process->ctx->ref_count -= 1; @@ -2954,8 +2992,13 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) DLLPushBack(thread_new_parent->first_thread, thread_new_parent->last_thread, new_thread); thread_new_parent->thread_count += 1; - // rjf: map tid -> thread mapping - hash_table_push_u64_raw(lnx_dmn_state->arena, lnx_dmn_state->tid_ht, new_thread->tid, new_thread); + // rjf: map id -> thread + { + U64 key = (U64)wait_id; + U64 hash = u64_hash_from_str8(str8_struct(&key)); + U64 slot_idx = hash%lnx_dmn_state->thread_from_tid_slots_count; + DLLPushBack_NP(lnx_dmn_state->thread_from_tid_slots[slot_idx].first, lnx_dmn_state->thread_from_tid_slots[slot_idx].last, new_thread, tid_next, tid_prev); + } } // rjf: report diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index 3f699bf4..a13187e6 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -170,6 +170,8 @@ struct LNX_DMN_Thread { LNX_DMN_Thread *next; LNX_DMN_Thread *prev; + LNX_DMN_Thread *tid_next; + LNX_DMN_Thread *tid_prev; pid_t tid; LNX_DMN_ThreadState state; struct LNX_DMN_Process *process; @@ -181,6 +183,13 @@ struct LNX_DMN_Thread U64 dtv_base_vaddr; }; +typedef struct LNX_DMN_ThreadSlot LNX_DMN_ThreadSlot; +struct LNX_DMN_ThreadSlot +{ + LNX_DMN_Thread *first; + LNX_DMN_Thread *last; +}; + typedef struct LNX_DMN_Module LNX_DMN_Module; struct LNX_DMN_Module { @@ -234,10 +243,12 @@ LNX_DMN_CreateProcessFlags; typedef struct LNX_DMN_Process LNX_DMN_Process; struct LNX_DMN_Process { - LNX_DMN_Process *parent_process; - struct LNX_DMN_ProcessCtx *ctx; LNX_DMN_Process *next; LNX_DMN_Process *prev; + LNX_DMN_Process *pid_next; + LNX_DMN_Process *pid_prev; + LNX_DMN_Process *parent_process; + struct LNX_DMN_ProcessCtx *ctx; pid_t pid; int fd; LNX_DMN_ProcessState state; @@ -250,6 +261,13 @@ struct LNX_DMN_Process U64 main_thread_exit_code; }; +typedef struct LNX_DMN_ProcessSlot LNX_DMN_ProcessSlot; +struct LNX_DMN_ProcessSlot +{ + LNX_DMN_Process *first; + LNX_DMN_Process *last; +}; + typedef struct LNX_DMN_ProcessPtrNode LNX_DMN_ProcessPtrNode; struct LNX_DMN_ProcessPtrNode { @@ -340,32 +358,35 @@ struct LNX_DMN_State Mutex access_mutex; B32 access_run_state; - // rjf: entity storage + // rjf: main entity storage Arena *entities_arena; LNX_DMN_Entity *entities_base; LNX_DMN_Entity *free_entity; U64 entities_count; - HashTable *tid_ht; // thread id -> thread entity - HashTable *pid_ht; // process id -> process entity + // rjf: id -> entity tables + LNX_DMN_ProcessSlot *process_from_pid_slots; + U64 process_from_pid_slots_count; + LNX_DMN_ThreadSlot *thread_from_tid_slots; + U64 thread_from_tid_slots_count; - // process tracking - U64 process_count; + // rjf: process entity list LNX_DMN_Process *first_process; LNX_DMN_Process *last_process; + U64 process_count; - // process/thread creation tracking + // rjf: pending process/thread counts U64 process_pending_creation; U64 threads_pending_creation; - // halter + // rjf: halting state Mutex halter_mutex; pid_t halter_tid; U64 halt_code; U64 halt_user_data; B32 is_halting; - // TLS + // rjf: TLS B32 is_tls_detected; LNX_DMN_DbDesc tls_modid_desc; LNX_DMN_DbDesc tls_offset_desc; @@ -412,7 +433,6 @@ internal void lnx_dmn_entity_release(LNX_DMN_Entity *entity); //- rjf: specialized allocation / deallocation helpers internal LNX_DMN_Process *lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *parent_process, B32 debug_subprocess, B32 is_cow); internal LNX_DMN_Module *lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main); -internal void lnx_dmn_thread_release(LNX_DMN_Thread *thread); internal void lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_Module *module); //- rjf: context cloning From 8f8e14c348071fe3a84e962c3ee9d46e59b35bf5 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 10 Aug 2026 14:56:29 -0600 Subject: [PATCH 030/251] linux_demon: intrinsic links for addr -> module table --- src/linux/demon/linux_demon.c | 179 ++++++++++++++++++++++------------ src/linux/demon/linux_demon.h | 16 ++- 2 files changed, 127 insertions(+), 68 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 5d574fd1..5dd9575c 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -440,70 +440,91 @@ lnx_dmn_process_alloc(pid_t pid, LNX_DMN_ProcessState state, LNX_DMN_Process *pa internal LNX_DMN_Module * lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main) { - LNX_DMN_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, base_vaddr); - if(module) { goto exit; } - - // parse out module's ELF header - ELF_Hdr64 module_ehdr = {0}; - if(elf_read_ehdr(lnx_dmn_machine_op_mem_read, &memory_fd, base_vaddr, &module_ehdr) != MachineOpResult_Ok) { goto exit; } - - // gather info about module - U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; - Rng1U64 module_vrange = lnx_dmn_compute_image_vrange(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); - - // read TLS index and TLS offset - U64 tls_index = max_U64; - U64 tls_offset = max_U64; - if(is_main) + //- rjf: find this module, if it is already loaded + LNX_DMN_Module *module = 0; + U64 hash = u64_hash_from_str8(str8_struct(&base_vaddr)); + U64 slot_idx = hash%ctx->module_slots_count; { - tls_index = 1; - tls_offset = 0; - } - else - { - if(lnx_dmn_state->is_tls_detected) + for(LNX_DMN_Module *m = ctx->module_slots[slot_idx].first; m != 0; m = m->hash_next) { - Rng1U64 tls_modid_range = r1u64(lnx_dmn_state->tls_modid_desc.offset, lnx_dmn_state->tls_modid_desc.offset + lnx_dmn_state->tls_modid_desc.bit_size / 8); - Rng1U64 tls_offset_range = r1u64(lnx_dmn_state->tls_offset_desc.offset, lnx_dmn_state->tls_offset_desc.offset + lnx_dmn_state->tls_offset_desc.bit_size / 8); - tls_modid_range = shift_1u64(tls_modid_range, base_vaddr); - tls_offset_range = shift_1u64(tls_offset_range, base_vaddr); - if(!lnx_dmn_read(memory_fd, tls_modid_range, &tls_index)) { Assert(0 && "failed to read TLS index"); } - if(!lnx_dmn_read(memory_fd, tls_offset_range, &tls_offset)) { Assert(0 && "failed to read TLS offset"); } + if(m->base_vaddr == base_vaddr) + { + module = m; + break; + } } } - module = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Module)->module; - module->base_vaddr = base_vaddr; - module->name_vaddr = name_vaddr; - module->name_space_id = name_space_id; - module->size = dim_1u64(module_vrange); - module->tls_index = tls_index; - module->tls_offset = tls_offset; - module->is_main = is_main; - - // add module to the list - DLLPushBack(ctx->first_module, ctx->last_module, module); - ctx->module_count += 1; - - // push base address -> module mapping - hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, base_vaddr, module); - - exit:; + //- rjf: load this module if it's not already loaded + if(module == 0) + { + // parse out module's ELF header + ELF_Hdr64 module_ehdr = {0}; + elf_read_ehdr(lnx_dmn_machine_op_mem_read, &memory_fd, base_vaddr, &module_ehdr); + + // gather info about module + U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; + Rng1U64 module_vrange = lnx_dmn_compute_image_vrange(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); + + // read TLS index and TLS offset + U64 tls_index = max_U64; + U64 tls_offset = max_U64; + if(is_main) + { + tls_index = 1; + tls_offset = 0; + } + else + { + if(lnx_dmn_state->is_tls_detected) + { + Rng1U64 tls_modid_range = r1u64(lnx_dmn_state->tls_modid_desc.offset, lnx_dmn_state->tls_modid_desc.offset + lnx_dmn_state->tls_modid_desc.bit_size / 8); + Rng1U64 tls_offset_range = r1u64(lnx_dmn_state->tls_offset_desc.offset, lnx_dmn_state->tls_offset_desc.offset + lnx_dmn_state->tls_offset_desc.bit_size / 8); + tls_modid_range = shift_1u64(tls_modid_range, base_vaddr); + tls_offset_range = shift_1u64(tls_offset_range, base_vaddr); + if(!lnx_dmn_read(memory_fd, tls_modid_range, &tls_index)) { Assert(0 && "failed to read TLS index"); } + if(!lnx_dmn_read(memory_fd, tls_offset_range, &tls_offset)) { Assert(0 && "failed to read TLS offset"); } + } + } + + module = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Module)->module; + module->base_vaddr = base_vaddr; + module->name_vaddr = name_vaddr; + module->name_space_id = name_space_id; + module->size = dim_1u64(module_vrange); + module->tls_index = tls_index; + module->tls_offset = tls_offset; + module->is_main = is_main; + + // add module to the list + DLLPushBack_NP(ctx->first_module, ctx->last_module, module, order_next, order_prev); + ctx->module_count += 1; + + // rjf: link into base_vaddr -> module table + { + DLLPushBack_NP(ctx->module_slots[slot_idx].first, ctx->module_slots[slot_idx].last, module, hash_next, hash_prev); + } + } return module; } internal void lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_Module *module) { - // remove module from the list - Assert(ctx->module_count > 0); - DLLRemove(ctx->first_module, ctx->last_module, module); + // rjf: unlink module from ordered list + DLLRemove_NP(ctx->first_module, ctx->last_module, module, order_next, order_prev); ctx->module_count -= 1; - // purge base addr -> module mapping - hash_table_purge_u64(ctx->loaded_modules_ht, module->base_vaddr); + // rjf: unlink from module table + { + U64 hash = u64_hash_from_str8(str8_struct(&module->base_vaddr)); + U64 slot_idx = hash%ctx->module_slots_count; + LNX_DMN_ModuleSlot *slot = &ctx->module_slots[slot_idx]; + DLLRemove_NP(slot->first, slot->last, module, hash_next, hash_prev); + } + // rjf: release entity record lnx_dmn_entity_release((LNX_DMN_Entity *)module); } @@ -518,7 +539,8 @@ lnx_dmn_process_ctx_clone(LNX_DMN_Process *new_owner, LNX_DMN_ProcessCtx *ctx) result->arch = ctx->arch; result->rdebug_vaddr = ctx->rdebug_vaddr; result->dl_class = ctx->dl_class; - result->loaded_modules_ht = hash_table_init(result->arena, ctx->loaded_modules_ht->cap); + result->module_slots_count = ctx->module_slots_count; + result->module_slots = push_array(result->arena, LNX_DMN_ModuleSlot, result->module_slots_count); result->xcr0 = ctx->xcr0; result->xsave_size = ctx->xsave_size; result->xsave_layout = ctx->xsave_layout; @@ -556,14 +578,15 @@ lnx_dmn_process_ctx_clone(LNX_DMN_Process *new_owner, LNX_DMN_ProcessCtx *ctx) } // clone modules - for EachNode(module, LNX_DMN_Module, ctx->first_module) + for(LNX_DMN_Module *module = ctx->first_module; module != 0; module = module->order_next) { LNX_DMN_Module *dst = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Module)->module; MemoryCopyStruct(dst, module); - dst->next = dst->prev = 0; - hash_table_push_u64_raw(result->arena, result->loaded_modules_ht, dst->base_vaddr, dst); - DLLPushBack(result->first_module, result->last_module, dst); + DLLPushBack_NP(result->first_module, result->last_module, dst, order_next, order_prev); result->module_count += 1; + U64 hash = u64_hash_from_str8(str8_struct(&dst->base_vaddr)); + U64 slot_idx = hash%result->module_slots_count; + DLLPushBack_NP(result->module_slots[slot_idx].first, result->module_slots[slot_idx].last, dst, hash_next, hash_prev); } return result; @@ -1192,7 +1215,19 @@ lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *t if(gnu_read_link_map(lnx_dmn_machine_op_mem_read, &process->fd, map_vaddr, process->ctx->dl_class, &map) != MachineOpResult_Ok) { break; } // was module already loaded? - LNX_DMN_Module *module = hash_table_search_u64_raw(process->ctx->loaded_modules_ht, map.addr_vaddr); + LNX_DMN_Module *module = 0; + { + U64 hash = u64_hash_from_str8(str8_struct(&map.addr_vaddr)); + U64 slot_idx = hash%process->ctx->module_slots_count; + for(LNX_DMN_Module *m = process->ctx->module_slots[slot_idx].first; m != 0; m = m->hash_next) + { + if(m->base_vaddr == map.addr_vaddr) + { + module = m; + break; + } + } + } if(module) { continue; } // clone process ctx @@ -1906,27 +1941,35 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) LNX_DMN_ProcessCtx *ctx = process->ctx; // flag every module as inactive - for EachNode(module, LNX_DMN_Module, ctx->first_module) + for(LNX_DMN_Module *module = ctx->first_module; module != 0; module = module->order_next) { module->is_live = 0; } // mark live modules - B32 is_64bit = ctx->dl_class == ELF_Class_64; + B32 is_64bit = ctx->dl_class == ELF_Class_64; GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, lnx_dmn_machine_op_mem_read, &process->fd); for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) { GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, lnx_dmn_machine_op_mem_read, &process->fd); for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first) { - LNX_DMN_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, link_map_n->v.addr_vaddr); - module->is_live = 1; + U64 hash = u64_hash_from_str8(str8_struct(&link_map_n->v.addr_vaddr)); + U64 slot_idx = hash%ctx->module_slots_count; + for(LNX_DMN_Module *m = ctx->module_slots[slot_idx].first; m != 0; m = m->hash_next) + { + if(m->base_vaddr == link_map_n->v.addr_vaddr) + { + m->is_live = 1; + break; + } + } } } // collect unloaded modules LNX_DMN_ModulePtrList to_release = {0}; - for EachNode(module, LNX_DMN_Module, ctx->first_module) + for(LNX_DMN_Module *module = ctx->first_module; module != 0; module = module->order_next) { if(!module->is_live) { @@ -2293,7 +2336,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // rjf: push module events if(thread_done_report_events) { - for EachNode(module, LNX_DMN_Module, thread_process->ctx->first_module) + for(LNX_DMN_Module *module = thread_process->ctx->first_module; module != 0; module = module->order_next) { lnx_dmn_push_event_unload_module(arena, &events, thread_process, module); } @@ -2900,7 +2943,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) ctx->arch = arch; ctx->rdebug_vaddr = rdebug_vaddr; ctx->dl_class = dl_class; - ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000); + ctx->module_slots_count= 4096; + ctx->module_slots = push_array(ctx_arena, LNX_DMN_ModuleSlot, ctx->module_slots_count); ctx->probes = known_probes; ctx->xcr0 = xcr0; ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); @@ -2910,7 +2954,12 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, new_process->fd, base_vaddr, auxv.execfn, 1, 1); //- glibc has a shortcut mapping for the main module - hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); + { + U64 vaddr = 0; + U64 hash = u64_hash_from_str8(str8_struct(&vaddr)); + U64 slot_idx = hash%ctx->module_slots_count; + DLLPushBack_NP(ctx->module_slots[slot_idx].first, ctx->module_slots[slot_idx].last, main_module, hash_next, hash_prev); + } } //////////////////////// @@ -3040,7 +3089,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // if(new_process) { - for EachNode(module, LNX_DMN_Module, new_process->ctx->first_module) + for(LNX_DMN_Module *module = new_process->ctx->first_module; module != 0; module = module->order_next) { lnx_dmn_push_event_load_module(arena, &events, new_process->first_thread, module); } diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index a13187e6..c02aa7f4 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -193,8 +193,10 @@ struct LNX_DMN_ThreadSlot typedef struct LNX_DMN_Module LNX_DMN_Module; struct LNX_DMN_Module { - LNX_DMN_Module *next; - LNX_DMN_Module *prev; + LNX_DMN_Module *order_next; + LNX_DMN_Module *order_prev; + LNX_DMN_Module *hash_next; + LNX_DMN_Module *hash_prev; U64 name_vaddr; U64 base_vaddr; U64 name_space_id; @@ -205,6 +207,13 @@ struct LNX_DMN_Module B8 is_main; }; +typedef struct LNX_DMN_ModuleSlot LNX_DMN_ModuleSlot; +struct LNX_DMN_ModuleSlot +{ + LNX_DMN_Module *first; + LNX_DMN_Module *last; +}; + typedef struct LNX_DMN_ModulePtrNode LNX_DMN_ModulePtrNode; struct LNX_DMN_ModulePtrNode { @@ -299,13 +308,14 @@ struct LNX_DMN_ProcessCtx Arch arch; U64 rdebug_vaddr; ELF_Class dl_class; - HashTable *loaded_modules_ht; LNX_DMN_Probe **probes; LNX_DMN_ActiveTrap *first_probe_trap; LNX_DMN_ActiveTrap *last_probe_trap; LNX_DMN_Module *first_module; LNX_DMN_Module *last_module; U64 module_count; + U64 module_slots_count; + LNX_DMN_ModuleSlot *module_slots; U64 ref_count; String8List free_reg_blocks; String8List free_reg_block_nodes; From 70c0888bc33e9da330139baa9200240a8db54dc7 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 10 Aug 2026 15:21:39 -0600 Subject: [PATCH 031/251] linux window manager: key state / modifiers polling --- .../window_manager/linux_window_manager.c | 308 ++++++++++++++---- .../window_manager/linux_window_manager.h | 2 + 2 files changed, 238 insertions(+), 72 deletions(-) diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 48b83c99..9caca2e9 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -103,6 +103,226 @@ lnx_moveresize_code_from_pos(LNX_WM_Window *window, Vec2F32 pos, B32 *out_is_in_ return moveresize_code; } +internal KeySym +lnx_wm_keysym_from_key(WM_Key key) +{ + KeySym ks = 0; + switch(key) + { + default:{}break; + case WM_Key_Esc:{ks = XK_Escape;}break; + case WM_Key_F1:{ks = XK_F1;}break; + case WM_Key_F2:{ks = XK_F2;}break; + case WM_Key_F3:{ks = XK_F3;}break; + case WM_Key_F4:{ks = XK_F4;}break; + case WM_Key_F5:{ks = XK_F5;}break; + case WM_Key_F6:{ks = XK_F6;}break; + case WM_Key_F7:{ks = XK_F7;}break; + case WM_Key_F8:{ks = XK_F8;}break; + case WM_Key_F9:{ks = XK_F9;}break; + case WM_Key_F10:{ks = XK_F10;}break; + case WM_Key_F11:{ks = XK_F11;}break; + case WM_Key_F12:{ks = XK_F12;}break; + case WM_Key_F13:{ks = XK_F13;}break; + case WM_Key_F14:{ks = XK_F14;}break; + case WM_Key_F15:{ks = XK_F15;}break; + case WM_Key_F16:{ks = XK_F16;}break; + case WM_Key_F17:{ks = XK_F17;}break; + case WM_Key_F18:{ks = XK_F18;}break; + case WM_Key_F19:{ks = XK_F19;}break; + case WM_Key_F20:{ks = XK_F20;}break; + case WM_Key_F21:{ks = XK_F21;}break; + case WM_Key_F22:{ks = XK_F22;}break; + case WM_Key_F23:{ks = XK_F23;}break; + case WM_Key_F24:{ks = XK_F24;}break; + case WM_Key_Tick:{ks = '`';}break; + case WM_Key_0:{ks = XK_0;}break; + case WM_Key_1:{ks = XK_1;}break; + case WM_Key_2:{ks = XK_2;}break; + case WM_Key_3:{ks = XK_3;}break; + case WM_Key_4:{ks = XK_4;}break; + case WM_Key_5:{ks = XK_5;}break; + case WM_Key_6:{ks = XK_6;}break; + case WM_Key_7:{ks = XK_7;}break; + case WM_Key_8:{ks = XK_8;}break; + case WM_Key_9:{ks = XK_9;}break; + case WM_Key_Minus:{ks = '-';}break; + case WM_Key_Equal:{ks = '=';}break; + case WM_Key_Backspace:{ks = XK_BackSpace;}break; + case WM_Key_Tab:{ks = XK_Tab;}break; + case WM_Key_Q:{ks = XK_Q;}break; + case WM_Key_W:{ks = XK_W;}break; + case WM_Key_E:{ks = XK_E;}break; + case WM_Key_R:{ks = XK_R;}break; + case WM_Key_T:{ks = XK_T;}break; + case WM_Key_Y:{ks = XK_Y;}break; + case WM_Key_U:{ks = XK_U;}break; + case WM_Key_I:{ks = XK_I;}break; + case WM_Key_O:{ks = XK_O;}break; + case WM_Key_P:{ks = XK_P;}break; + case WM_Key_LeftBracket:{ks = '[';}break; + case WM_Key_RightBracket:{ks = ']';}break; + case WM_Key_BackSlash:{ks = '\\';}break; + case WM_Key_CapsLock:{ks = XK_Caps_Lock;}break; + case WM_Key_A:{ks = XK_Alt_L;}break; + case WM_Key_S:{ks = XK_Shift_L;}break; + case WM_Key_D:{ks = XK_Delete;}break; + case WM_Key_F:{ks = XK_F24;}break; + case WM_Key_G:{ks = XK_G;}break; + case WM_Key_H:{ks = XK_Home;}break; + case WM_Key_J:{ks = XK_J;}break; + case WM_Key_K:{ks = XK_K;}break; + case WM_Key_L:{ks = XK_L;}break; + case WM_Key_Semicolon:{ks = ';';}break; + case WM_Key_Quote:{ks = '\'';}break; + case WM_Key_Return:{ks = XK_Return;}break; + case WM_Key_Shift:{ks = XK_Shift_L;}break; + case WM_Key_Z:{ks = XK_Z;}break; + case WM_Key_X:{ks = XK_X;}break; + case WM_Key_C:{ks = XK_Caps_Lock;}break; + case WM_Key_V:{ks = XK_V;}break; + case WM_Key_B:{ks = XK_B;}break; + case WM_Key_N:{ks = XK_N;}break; + case WM_Key_M:{ks = '-';}break; + case WM_Key_Comma:{ks = ',';}break; + case WM_Key_Period:{ks = '.';}break; + case WM_Key_Slash:{ks = '/';}break; + case WM_Key_Ctrl:{ks = XK_Control_L;}break; + case WM_Key_Alt:{ks = XK_Alt_L;}break; + case WM_Key_Space:{ks = ' ';}break; + case WM_Key_Menu:{ks = XK_Menu;}break; + case WM_Key_ScrollLock:{ks = XK_Scroll_Lock;}break; + case WM_Key_Pause:{ks = XK_Pause;}break; + case WM_Key_Insert:{ks = XK_Insert;}break; + case WM_Key_Home:{ks = XK_Home;}break; + case WM_Key_PageUp:{ks = XK_Page_Up;}break; + case WM_Key_Delete:{ks = XK_Delete;}break; + case WM_Key_End:{ks = XK_End;}break; + case WM_Key_PageDown:{ks = XK_Page_Down;}break; + case WM_Key_Up:{ks = XK_Up;}break; + case WM_Key_Left:{ks = XK_Left;}break; + case WM_Key_Down:{ks = XK_Down;}break; + case WM_Key_Right:{ks = XK_Right;}break; + case WM_Key_NumLock:{ks = XK_Num_Lock;}break; + case WM_Key_NumSlash:{ks = XK_KP_Divide;}break; + case WM_Key_NumStar:{ks = XK_KP_Multiply;}break; + case WM_Key_NumMinus:{ks = XK_KP_Subtract;}break; + case WM_Key_NumPlus:{ks = XK_KP_Add;}break; + case WM_Key_NumPeriod:{ks = XK_KP_Decimal;}break; + case WM_Key_Num0:{ks = XK_KP_0;}break; + case WM_Key_Num1:{ks = XK_KP_1;}break; + case WM_Key_Num2:{ks = XK_KP_2;}break; + case WM_Key_Num3:{ks = XK_KP_3;}break; + case WM_Key_Num4:{ks = XK_KP_4;}break; + case WM_Key_Num5:{ks = XK_KP_5;}break; + case WM_Key_Num6:{ks = XK_KP_6;}break; + case WM_Key_Num7:{ks = XK_KP_7;}break; + case WM_Key_Num8:{ks = XK_KP_8;}break; + case WM_Key_Num9:{ks = XK_KP_9;}break; + } + return ks; +} + +internal WM_Key +lnx_wm_key_from_keysym(KeySym ks, B32 *out_is_right_sided) +{ + WM_Key key = WM_Key_Null; + B32 is_right_sided = 0; + switch(ks) + { + default: + { + if(0){} + else if(XK_F1 <= ks && ks <= XK_F24) { key = (WM_Key)(WM_Key_F1 + (ks - XK_F1)); } + else if('0' <= ks && ks <= '9') { key = WM_Key_0 + (ks-'0'); } + }break; + case XK_Escape:{key = WM_Key_Esc;};break; + case XK_BackSpace:{key = WM_Key_Backspace;}break; + case XK_Delete:{key = WM_Key_Delete;}break; + case XK_Return:{key = WM_Key_Return;}break; + case XK_Pause:{key = WM_Key_Pause;}break; + case XK_Tab:{key = WM_Key_Tab;}break; + case XK_Left:{key = WM_Key_Left;}break; + case XK_Right:{key = WM_Key_Right;}break; + case XK_Up:{key = WM_Key_Up;}break; + case XK_Down:{key = WM_Key_Down;}break; + case XK_Home:{key = WM_Key_Home;}break; + case XK_End:{key = WM_Key_End;}break; + case XK_Page_Up:{key = WM_Key_PageUp;}break; + case XK_Page_Down:{key = WM_Key_PageDown;}break; + case XK_Alt_L:{ key = WM_Key_Alt; }break; + case XK_Alt_R:{ key = WM_Key_Alt; is_right_sided = 1;}break; + case XK_Shift_L:{ key = WM_Key_Shift; }break; + case XK_Shift_R:{ key = WM_Key_Shift; is_right_sided = 1;}break; + case XK_Control_L:{ key = WM_Key_Ctrl; }break; + case XK_Control_R:{ key = WM_Key_Ctrl; is_right_sided = 1;}break; + case XK_Caps_Lock:{key = WM_Key_CapsLock;}break; + case XK_Menu:{key = WM_Key_Menu;}break; + case XK_Scroll_Lock:{key = WM_Key_ScrollLock;}break; + case XK_Insert:{key = WM_Key_Insert;}break; + case XK_Num_Lock:{key = WM_Key_NumLock;}break; + case XK_KP_Divide:{key = WM_Key_NumSlash;}break; + case XK_KP_Multiply:{key = WM_Key_NumStar;}break; + case XK_KP_Subtract:{key = WM_Key_NumMinus;}break; + case XK_KP_Add:{key = WM_Key_NumPlus;}break; + case XK_KP_Decimal:{key = WM_Key_NumPeriod;}break; + case XK_KP_0:{key = WM_Key_Num0;}break; + case XK_KP_1:{key = WM_Key_Num1;}break; + case XK_KP_2:{key = WM_Key_Num2;}break; + case XK_KP_3:{key = WM_Key_Num3;}break; + case XK_KP_4:{key = WM_Key_Num4;}break; + case XK_KP_5:{key = WM_Key_Num5;}break; + case XK_KP_6:{key = WM_Key_Num6;}break; + case XK_KP_7:{key = WM_Key_Num7;}break; + case XK_KP_8:{key = WM_Key_Num8;}break; + case XK_KP_9:{key = WM_Key_Num9;}break; + case '-':{key = WM_Key_Minus;}break; + case '=':{key = WM_Key_Equal;}break; + case '[':{key = WM_Key_LeftBracket;}break; + case ']':{key = WM_Key_RightBracket;}break; + case ';':{key = WM_Key_Semicolon;}break; + case '\'':{key = WM_Key_Quote;}break; + case '.':{key = WM_Key_Period;}break; + case ',':{key = WM_Key_Comma;}break; + case '/':{key = WM_Key_Slash;}break; + case '\\':{key = WM_Key_BackSlash;}break; + case '\t':{key = WM_Key_Tab;}break; + case 'a':case 'A':{key = WM_Key_A;}break; + case 'b':case 'B':{key = WM_Key_B;}break; + case 'c':case 'C':{key = WM_Key_C;}break; + case 'd':case 'D':{key = WM_Key_D;}break; + case 'e':case 'E':{key = WM_Key_E;}break; + case 'f':case 'F':{key = WM_Key_F;}break; + case 'g':case 'G':{key = WM_Key_G;}break; + case 'h':case 'H':{key = WM_Key_H;}break; + case 'i':case 'I':{key = WM_Key_I;}break; + case 'j':case 'J':{key = WM_Key_J;}break; + case 'k':case 'K':{key = WM_Key_K;}break; + case 'l':case 'L':{key = WM_Key_L;}break; + case 'm':case 'M':{key = WM_Key_M;}break; + case 'n':case 'N':{key = WM_Key_N;}break; + case 'o':case 'O':{key = WM_Key_O;}break; + case 'p':case 'P':{key = WM_Key_P;}break; + case 'q':case 'Q':{key = WM_Key_Q;}break; + case 'r':case 'R':{key = WM_Key_R;}break; + case 's':case 'S':{key = WM_Key_S;}break; + case 't':case 'T':{key = WM_Key_T;}break; + case 'u':case 'U':{key = WM_Key_U;}break; + case 'v':case 'V':{key = WM_Key_V;}break; + case 'w':case 'W':{key = WM_Key_W;}break; + case 'x':case 'X':{key = WM_Key_X;}break; + case 'y':case 'Y':{key = WM_Key_Y;}break; + case 'z':case 'Z':{key = WM_Key_Z;}break; + case ' ':{key = WM_Key_Space;}break; + case '`':{key = WM_Key_Tick;}break; + } + if(out_is_right_sided) + { + out_is_right_sided[0] = is_right_sided; + } + return key; +} + //////////////////////////////// //~ rjf: @per_os_impl Main Initialization API (Implemented Per-OS) @@ -898,74 +1118,7 @@ wm_get_events(Arena *arena, B32 wait) // rjf: map keysym -> WM_Key B32 is_right_sided = 0; - WM_Key key = WM_Key_Null; - switch(keysym) - { - default: - { - if(0){} - else if(XK_F1 <= keysym && keysym <= XK_F24) { key = (WM_Key)(WM_Key_F1 + (keysym - XK_F1)); } - else if('0' <= keysym && keysym <= '9') { key = WM_Key_0 + (keysym-'0'); } - }break; - case XK_Escape:{key = WM_Key_Esc;};break; - case XK_BackSpace:{key = WM_Key_Backspace;}break; - case XK_Delete:{key = WM_Key_Delete;}break; - case XK_Return:{key = WM_Key_Return;}break; - case XK_Pause:{key = WM_Key_Pause;}break; - case XK_Tab:{key = WM_Key_Tab;}break; - case XK_Left:{key = WM_Key_Left;}break; - case XK_Right:{key = WM_Key_Right;}break; - case XK_Up:{key = WM_Key_Up;}break; - case XK_Down:{key = WM_Key_Down;}break; - case XK_Home:{key = WM_Key_Home;}break; - case XK_End:{key = WM_Key_End;}break; - case XK_Page_Up:{key = WM_Key_PageUp;}break; - case XK_Page_Down:{key = WM_Key_PageDown;}break; - case XK_Alt_L:{ key = WM_Key_Alt; }break; - case XK_Alt_R:{ key = WM_Key_Alt; is_right_sided = 1;}break; - case XK_Shift_L:{ key = WM_Key_Shift; }break; - case XK_Shift_R:{ key = WM_Key_Shift; is_right_sided = 1;}break; - case XK_Control_L:{ key = WM_Key_Ctrl; }break; - case XK_Control_R:{ key = WM_Key_Ctrl; is_right_sided = 1;}break; - case '-':{key = WM_Key_Minus;}break; - case '=':{key = WM_Key_Equal;}break; - case '[':{key = WM_Key_LeftBracket;}break; - case ']':{key = WM_Key_RightBracket;}break; - case ';':{key = WM_Key_Semicolon;}break; - case '\'':{key = WM_Key_Quote;}break; - case '.':{key = WM_Key_Period;}break; - case ',':{key = WM_Key_Comma;}break; - case '/':{key = WM_Key_Slash;}break; - case '\\':{key = WM_Key_BackSlash;}break; - case '\t':{key = WM_Key_Tab;}break; - case 'a':case 'A':{key = WM_Key_A;}break; - case 'b':case 'B':{key = WM_Key_B;}break; - case 'c':case 'C':{key = WM_Key_C;}break; - case 'd':case 'D':{key = WM_Key_D;}break; - case 'e':case 'E':{key = WM_Key_E;}break; - case 'f':case 'F':{key = WM_Key_F;}break; - case 'g':case 'G':{key = WM_Key_G;}break; - case 'h':case 'H':{key = WM_Key_H;}break; - case 'i':case 'I':{key = WM_Key_I;}break; - case 'j':case 'J':{key = WM_Key_J;}break; - case 'k':case 'K':{key = WM_Key_K;}break; - case 'l':case 'L':{key = WM_Key_L;}break; - case 'm':case 'M':{key = WM_Key_M;}break; - case 'n':case 'N':{key = WM_Key_N;}break; - case 'o':case 'O':{key = WM_Key_O;}break; - case 'p':case 'P':{key = WM_Key_P;}break; - case 'q':case 'Q':{key = WM_Key_Q;}break; - case 'r':case 'R':{key = WM_Key_R;}break; - case 's':case 'S':{key = WM_Key_S;}break; - case 't':case 'T':{key = WM_Key_T;}break; - case 'u':case 'U':{key = WM_Key_U;}break; - case 'v':case 'V':{key = WM_Key_V;}break; - case 'w':case 'W':{key = WM_Key_W;}break; - case 'x':case 'X':{key = WM_Key_X;}break; - case 'y':case 'Y':{key = WM_Key_Y;}break; - case 'z':case 'Z':{key = WM_Key_Z;}break; - case ' ':{key = WM_Key_Space;}break; - } + WM_Key key = lnx_wm_key_from_keysym(keysym, &is_right_sided); // rjf: push text event if(evt.type == KeyPress && text_size != 0) @@ -1198,15 +1351,26 @@ wm_get_events(Arena *arena, B32 wait) internal WM_Modifiers wm_get_modifiers(void) { - // TODO(rjf) - return 0; + WM_Modifiers mods = 0; + if(wm_key_is_down(WM_Key_Shift)) { mods |= WM_Modifier_Shift; } + if(wm_key_is_down(WM_Key_Alt)) { mods |= WM_Modifier_Alt; } + if(wm_key_is_down(WM_Key_Ctrl)) { mods |= WM_Modifier_Ctrl; } + return mods; } internal B32 wm_key_is_down(WM_Key key) { - // TODO(rjf) - return 0; + B32 result = 0; + { + char keys[32]; + XQueryKeymap(lnx_wm_state->display, keys); + KeySym sym = lnx_wm_keysym_from_key(key); + KeyCode code = XKeysymToKeycode(lnx_wm_state->display, sym); + B32 is_down = !!(keys[code/8] & (1u<<(code%8))); + result = is_down; + } + return result; } internal Vec2F32 diff --git a/src/linux/window_manager/linux_window_manager.h b/src/linux/window_manager/linux_window_manager.h index cb435810..ae8f61db 100644 --- a/src/linux/window_manager/linux_window_manager.h +++ b/src/linux/window_manager/linux_window_manager.h @@ -103,5 +103,7 @@ global LNX_WM_State *lnx_wm_state = 0; internal LNX_WM_Window *lnx_window_from_x11window(Window window); internal int lnx_moveresize_code_from_pos(LNX_WM_Window *window, Vec2F32 pos, B32 *out_is_in_client_area); +internal KeySym lnx_wm_keysym_from_key(WM_Key key); +internal WM_Key lnx_wm_key_from_keysym(KeySym ks, B32 *out_is_right_sided); #endif // LINUX_WINDOW_MANAGER_H From 41976ca1da86617c63d06362ceef579dcebc9cf8 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 11 Aug 2026 08:14:17 -0700 Subject: [PATCH 032/251] do not use binary mode for generated files --- src/metagen/metagen_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metagen/metagen_main.c b/src/metagen/metagen_main.c index 198a3409..ec314b9a 100644 --- a/src/metagen/metagen_main.c +++ b/src/metagen/metagen_main.c @@ -549,7 +549,7 @@ entry_point(CmdLine *cmdline) c_path = push_str8f(mg_arena, "%S/%S", layer_generated_folder, str8_skip_last_slash(layer->c_name_override)); } { - FILE *h = fopen((char *)h_path.str, "wb"); + FILE *h = fopen((char *)h_path.str, "w"); fprintf(h, "// Copyright (c) Epic Games Tools\n"); fprintf(h, "// Licensed under the MIT license (https://opensource.org/license/mit/)\n\n"); if(layer->h_header.first == 0) @@ -605,7 +605,7 @@ entry_point(CmdLine *cmdline) fclose(h); } { - FILE *c = fopen((char *)c_path.str, "wb"); + FILE *c = fopen((char *)c_path.str, "w"); fprintf(c, "// Copyright (c) Epic Games Tools\n"); fprintf(c, "// Licensed under the MIT license (https://opensource.org/license/mit/)\n\n"); if(layer->c_header.first == 0) From a016caea59ce1ab5976a12ac92f408cb21e3448a Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 11 Aug 2026 08:22:57 -0700 Subject: [PATCH 033/251] fix incorrect zero-rip unwind check --- src/dbg_engine/dbg_engine_ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index ae97d9c6..a613378e 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1867,7 +1867,7 @@ d_unwind_from_thread(Arena *arena, D_Handle thread, U64 endt_us) U64 start_sp = arch_sp_from_reg_block(arch_info, regs_block); //- rjf: cancel on zero rip (except the top frame) - if(start_ip == 0 && frame_node_count > 0) + if(start_ip == 0 && frame_node_count > 1) { break; } From 7cc61e75e4061002764f85e0b3b91e721734a19f Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 11 Aug 2026 08:24:55 -0700 Subject: [PATCH 034/251] fix use of old functions in spall profiling code --- src/base/base_profile.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/base/base_profile.c b/src/base/base_profile.c index c614f08e..db894303 100644 --- a/src/base/base_profile.c +++ b/src/base/base_profile.c @@ -8,22 +8,22 @@ spall_begin(char *fmt, ...) if(spall_buffer.data == 0) { spall_buffer.length = MB(1); - spall_buffer.data = os_reserve(spall_buffer.length); - os_commit(spall_buffer.data, spall_buffer.length); + spall_buffer.data = reserve_memory(spall_buffer.length); + commit_memory(spall_buffer.data, spall_buffer.length); spall_buffer_init(&spall_profile, &spall_buffer); } if(spall_pid == 0) { - spall_pid = os_get_process_info()->pid; + spall_pid = get_process_info()->pid; } if(spall_tid == 0) { - spall_tid = os_tid(); + spall_tid = tid(); } Temp scratch = scratch_begin(0, 0); va_list args; va_start(args, fmt); - String8 string = push_str8fv(scratch.arena, fmt, args); + String8 string = str8fv(scratch.arena, fmt, args); spall_buffer_begin_ex(&spall_profile, &spall_buffer, string.str, string.size, now_time_us(), spall_tid, spall_pid); va_end(args); scratch_end(scratch); From b8679b4a9a513cd0516bfaeaa9a6b56891e63530 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 11 Aug 2026 09:31:52 -0700 Subject: [PATCH 035/251] linux_demon: more helper flattening / elimination --- project.raddbg | 8 +- src/linux/demon/linux_demon.c | 171 +++++++++++++++------------------- src/linux/demon/linux_demon.h | 25 ----- 3 files changed, 81 insertions(+), 123 deletions(-) diff --git a/project.raddbg b/project.raddbg index 8b516f0f..c4ce262e 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: "../raddebugger" arguments: "--user:C:/devel/raddebugger/build/raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -76,5 +77,10 @@ target: { executable: "build/raddbg" working_directory: build - enabled: 1 +} +target: +{ + executable: "../scratch/test.exe" + working_directory: "../scratch" + entry_point: "main::main" } diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 5dd9575c..b23e5876 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1154,19 +1154,6 @@ lnx_dmn_tls_root_vaddr_from_reg_block(int fd, Arch arch, void *reg_block) return result; } -//////////////////////////////// -//~ List Helpers - -internal LNX_DMN_ModulePtrNode * -lnx_dmn_module_ptr_list_push(Arena *arena, LNX_DMN_ModulePtrList *list, LNX_DMN_Module *v) -{ - LNX_DMN_ModulePtrNode *n = push_array(arena, LNX_DMN_ModulePtrNode, 1); - n->v = v; - SLLQueuePush(list->first, list->last, n); - list->count += 1; - return n; -} - //////////////////////////////// //~ Debug Event Pushers @@ -1201,50 +1188,6 @@ lnx_dmn_push_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Pr e->module = lnx_dmn_handle_from_module(module); } -//////////////////////////////// -//~ Debug Event - -internal void -lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr) -{ - LNX_DMN_Process *process = thread->process; - GNU_LinkMap64 map = {0}; - for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr) - { - // read out new link map item - if(gnu_read_link_map(lnx_dmn_machine_op_mem_read, &process->fd, map_vaddr, process->ctx->dl_class, &map) != MachineOpResult_Ok) { break; } - - // was module already loaded? - LNX_DMN_Module *module = 0; - { - U64 hash = u64_hash_from_str8(str8_struct(&map.addr_vaddr)); - U64 slot_idx = hash%process->ctx->module_slots_count; - for(LNX_DMN_Module *m = process->ctx->module_slots[slot_idx].first; m != 0; m = m->hash_next) - { - if(m->base_vaddr == map.addr_vaddr) - { - module = m; - break; - } - } - } - if(module) { continue; } - - // clone process ctx - if(process->is_cow) - { - process->is_cow = 0; - process->ctx = lnx_dmn_process_ctx_clone(process, process->ctx); - } - - // alloc module - module = lnx_dmn_module_alloc(process->ctx, process->fd, map.addr_vaddr, map.name_vaddr, name_space_id, 0); - - // push load module event - lnx_dmn_push_event_load_module(arena, events, thread, module); - } -} - //////////////////////////////// //~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) @@ -1887,42 +1830,67 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } - //- rjf: probe trap: (module) initialization complete - if(probe_kind == LNX_DMN_ProbeKind_InitComplete) + //- rjf: probe trap: (module) initialization / relocation complete + if(probe_kind == LNX_DMN_ProbeKind_InitComplete || probe_kind == LNX_DMN_ProbeKind_RelocComplete) { - B32 is_init_completed = 0; + // rjf: unpack probe arguments + LNX_DMN_Probe *probe = process->ctx->probes[probe_kind]; + U64 name_space_id = 0; + U64 addr = 0; + stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id); + stap_read_arg_u(probe->args.v[probe_kind == LNX_DMN_ProbeKind_InitComplete ? 1 : 2], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &addr); - LNX_DMN_Probe *probe = process->ctx->probes[LNX_DMN_ProbeKind_InitComplete]; - U64 name_space_id = 0, rdebug_addr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id)) { goto init_complete_exit; } - if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &rdebug_addr)) { goto init_complete_exit; } + // rjf: init complete probe -> read rdebug for link map addr + U64 new_link_map_vaddr = addr; + if(probe_kind == LNX_DMN_ProbeKind_InitComplete) + { + GNU_RDebugInfo64 rdebug = {0}; + gnu_read_r_debug(lnx_dmn_machine_op_mem_read, &process->fd, addr, process->ctx->arch, &rdebug); + new_link_map_vaddr = rdebug.r_map; + } - GNU_RDebugInfo64 rdebug = {0}; - if(gnu_read_r_debug(lnx_dmn_machine_op_mem_read, &process->fd, rdebug_addr, process->ctx->arch, &rdebug) != MachineOpResult_Ok) { goto init_complete_exit; } - if(rdebug.r_version < 1) { goto init_complete_exit; } - - lnx_dmn_event_load_module(arena, &events, thread, name_space_id, rdebug.r_map); - - is_init_completed = 1; - init_complete_exit:; - AssertAlways(is_init_completed); - } - - //- rjf: probe trap: (module) relocation complete - else if(probe_kind == LNX_DMN_ProbeKind_RelocComplete) - { - B32 is_reloc_completed = 0; - - LNX_DMN_Probe *probe = process->ctx->probes[LNX_DMN_ProbeKind_RelocComplete]; - U64 name_space_id = 0, new_link_map_addr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &name_space_id)) { goto reloc_complete_exit; } - if(!stap_read_arg_u(probe->args.v[2], process->ctx->arch, thread->reg_block, lnx_dmn_stap_memory_read, process, &new_link_map_addr)) { goto reloc_complete_exit; } - - lnx_dmn_event_load_module(arena, &events, thread, name_space_id, new_link_map_addr); - - is_reloc_completed = 1; - reloc_complete_exit:; - AssertAlways(is_reloc_completed); + // rjf: new module loaded + { + LNX_DMN_Process *process = thread->process; + GNU_LinkMap64 map = {0}; + for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr) + { + // read out new link map item + if(gnu_read_link_map(lnx_dmn_machine_op_mem_read, &process->fd, map_vaddr, process->ctx->dl_class, &map) != MachineOpResult_Ok) { break; } + + // was module already loaded? + LNX_DMN_Module *module = 0; + { + U64 hash = u64_hash_from_str8(str8_struct(&map.addr_vaddr)); + U64 slot_idx = hash%process->ctx->module_slots_count; + for(LNX_DMN_Module *m = process->ctx->module_slots[slot_idx].first; m != 0; m = m->hash_next) + { + if(m->base_vaddr == map.addr_vaddr) + { + module = m; + break; + } + } + } + + // rjf: load module + if(module == 0) + { + // clone process ctx + if(process->is_cow) + { + process->is_cow = 0; + process->ctx = lnx_dmn_process_ctx_clone(process, process->ctx); + } + + // alloc module + module = lnx_dmn_module_alloc(process->ctx, process->fd, map.addr_vaddr, map.name_vaddr, name_space_id, 0); + + // rjf: report + lnx_dmn_push_event_load_module(arena, &events, thread, module); + } + } + } } //- rjf: probe trap: (module) unmap complete @@ -1968,30 +1936,39 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } // collect unloaded modules - LNX_DMN_ModulePtrList to_release = {0}; + typedef struct ModuleNode ModuleNode; + struct ModuleNode + { + ModuleNode *next; + LNX_DMN_Module *m; + }; + ModuleNode *first_module_to_release = 0; + ModuleNode *last_module_to_release = 0; for(LNX_DMN_Module *module = ctx->first_module; module != 0; module = module->order_next) { if(!module->is_live) { - lnx_dmn_module_ptr_list_push(scratch.arena, &to_release, module); + ModuleNode *n = push_array(scratch.arena, ModuleNode, 1); + SLLQueuePush(first_module_to_release, last_module_to_release, n); + n->m = module; } } // clone process context - if(to_release.count > 0) + if(first_module_to_release != 0) { if(process->is_cow) { process->is_cow = 0; - process->ctx = lnx_dmn_process_ctx_clone(process, process->ctx); + process->ctx = lnx_dmn_process_ctx_clone(process, process->ctx); } } // push events and clean up unloaded modules - for EachNode(module_n, LNX_DMN_ModulePtrNode, to_release.first) + for EachNode(module_n, ModuleNode, first_module_to_release) { - lnx_dmn_push_event_unload_module(arena, &events, process, module_n->v); - lnx_dmn_module_release(process->ctx, module_n->v); + lnx_dmn_push_event_unload_module(arena, &events, process, module_n->m); + lnx_dmn_module_release(process->ctx, module_n->m); } scratch_end(scratch); diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index c02aa7f4..f0010032 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -214,21 +214,6 @@ struct LNX_DMN_ModuleSlot LNX_DMN_Module *last; }; -typedef struct LNX_DMN_ModulePtrNode LNX_DMN_ModulePtrNode; -struct LNX_DMN_ModulePtrNode -{ - LNX_DMN_ModulePtrNode *next; - LNX_DMN_Module *v; -}; - -typedef struct LNX_DMN_ModulePtrList LNX_DMN_ModulePtrList; -struct LNX_DMN_ModulePtrList -{ - LNX_DMN_ModulePtrNode *first; - LNX_DMN_ModulePtrNode *last; - U64 count; -}; - typedef enum LNX_DMN_ProcessState { LNX_DMN_ProcessState_Null, @@ -472,20 +457,10 @@ internal B32 lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread); internal B32 lnx_dmn_set_single_step_flag(LNX_DMN_Thread *thread, B32 is_on); internal U64 lnx_dmn_tls_root_vaddr_from_reg_block(int fd, Arch arch, void *reg_block); -//////////////////////////////// -//~ List Helpers - -internal LNX_DMN_ModulePtrNode *lnx_dmn_module_ptr_list_push(Arena *arena, LNX_DMN_ModulePtrList *list, LNX_DMN_Module *v); - //////////////////////////////// //~ Debug Event Pushers internal void lnx_dmn_push_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, LNX_DMN_Module *module); internal void lnx_dmn_push_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process, LNX_DMN_Module *module); -//////////////////////////////// -//~ Debug Event - -internal void lnx_dmn_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr); - #endif // LINUX_DEMON_H From 1770d853086efb59b67328752c73f9211a01b6ce Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 11 Aug 2026 11:01:58 -0700 Subject: [PATCH 036/251] linux_demon: more helper flattening / elimination --- src/dwarf/dwarf_parse_elf.c | 4 +- src/elf/elf.c | 274 ++++++++++++++++------------- src/elf/elf.h | 316 +++++++++++++++++++--------------- src/elf/elf_parse.c | 7 +- src/linux/demon/linux_demon.c | 147 ++++++++++++---- src/linux/demon/linux_demon.h | 5 +- src/radbin/radbin.c | 2 +- 7 files changed, 459 insertions(+), 296 deletions(-) diff --git a/src/dwarf/dwarf_parse_elf.c b/src/dwarf/dwarf_parse_elf.c index a837c483..89b76f65 100644 --- a/src/dwarf/dwarf_parse_elf.c +++ b/src/dwarf/dwarf_parse_elf.c @@ -52,11 +52,11 @@ dw_raw_from_elf_bin(Arena *arena, String8 data, ELF_Bin *bin) // rjf: read compressed-section header ELF_Chdr64 chdr64 = {0}; U64 chdr_size = 0; - if(ELF_HdrIs64Bit(bin->hdr.e_ident)) + if(bin->hdr.e_ident[ELF_Identifier_Class] == ELF_Class_64) { chdr_size = str8_deserial_read_struct(section_data__maybe_compressed, 0, &chdr64); } - else if(ELF_HdrIs32Bit(bin->hdr.e_ident)) + else if(bin->hdr.e_ident[ELF_Identifier_Class] == ELF_Class_32) { ELF_Chdr32 chdr32 = {0}; chdr_size = str8_deserial_read_struct(section_data__maybe_compressed, 0, &chdr32); diff --git a/src/elf/elf.c b/src/elf/elf.c index 4bb43547..c92ac9c6 100644 --- a/src/elf/elf.c +++ b/src/elf/elf.c @@ -122,46 +122,65 @@ elf_auxv64_from_auxv32(ELF_Auxv32 auxv32) } //////////////////////////////// - -internal String8 -elf_string_from_class(Arena *arena, ELF_Class v) -{ - switch (v) { - case ELF_Class_None: return str8_lit("None"); - case ELF_Class_32: return str8_lit("32Bit"); - case ELF_Class_64: return str8_lit("64Bit"); - } - return push_str8f(arena, "%#x", v); -} - -//////////////////////////////// +//~ rjf: ELF -> Codebase enum Conversions internal Arch arch_from_elf_machine(ELF_MachineKind e_machine) { Arch arch = Arch_Null; - switch (e_machine) { - case ELF_MachineKind_None: arch = Arch_Null; break; - case ELF_MachineKind_AARCH64: arch = Arch_arm32; break; - case ELF_MachineKind_ARM: arch = Arch_arm32; break; - case ELF_MachineKind_386: arch = Arch_x86; break; - case ELF_MachineKind_X86_64: arch = Arch_x64; break; - default: NotImplemented; break; + switch((ELF_MachineKindEnum)e_machine) + { + default:{}break; + case ELF_MachineKind_None: {arch = Arch_Null;}break; + case ELF_MachineKind_AARCH64: {arch = Arch_arm32;}break; + case ELF_MachineKind_ARM: {arch = Arch_arm32;}break; + case ELF_MachineKind_386: {arch = Arch_x86;}break; + case ELF_MachineKind_X86_64: {arch = Arch_x64;}break; } return arch; } //////////////////////////////// +//~ rjf: ELF Class -> Structure Size + +internal U64 +elf_hdr_size_from_class(ELF_Class elf_class) +{ + U64 result = 0; + switch((ELF_ClassEnum)elf_class) + { + case ELF_Class_COUNT: + case ELF_Class_None:{}break; + case ELF_Class_32:{result = sizeof(ELF_Hdr32);} break; + case ELF_Class_64:{result = sizeof(ELF_Hdr64);} break; + } + return result; +} internal U64 elf_phdr_size_from_class(ELF_Class elf_class) { U64 result = 0; - switch (elf_class) { - case ELF_Class_None: break; - case ELF_Class_32: { result = sizeof(ELF_Phdr32); } break; - case ELF_Class_64: { result = sizeof(ELF_Phdr64); } break; - default: { NotImplemented; } break; + switch((ELF_ClassEnum)elf_class) + { + case ELF_Class_COUNT: + case ELF_Class_None:{}break; + case ELF_Class_32:{result = sizeof(ELF_Phdr32);} break; + case ELF_Class_64:{result = sizeof(ELF_Phdr64);} break; + } + return result; +} + +internal U64 +elf_shdr_size_from_class(ELF_Class elf_class) +{ + U64 result = 0; + switch((ELF_ClassEnum)elf_class) + { + case ELF_Class_COUNT: + case ELF_Class_None:{}break; + case ELF_Class_32:{result = sizeof(ELF_Shdr32);} break; + case ELF_Class_64:{result = sizeof(ELF_Shdr64);} break; } return result; } @@ -170,124 +189,149 @@ internal U64 elf_dyn_size_from_class(ELF_Class elf_class) { U64 result = 0; - switch (elf_class) { - case ELF_Class_None: break; - case ELF_Class_32: { result = sizeof(ELF_Dyn32); } break; - case ELF_Class_64: { result = sizeof(ELF_Dyn64); } break; - default: { NotImplemented; } break; + switch((ELF_ClassEnum)elf_class) + { + case ELF_Class_COUNT: + case ELF_Class_None:{}break; + case ELF_Class_32:{result = sizeof(ELF_Dyn32);}break; + case ELF_Class_64:{result = sizeof(ELF_Dyn64);}break; + } + return result; +} + +internal U64 +elf_sym_size_from_class(ELF_Class elf_class) +{ + U64 result = 0; + switch((ELF_ClassEnum)elf_class) + { + case ELF_Class_COUNT: + case ELF_Class_None:{}break; + case ELF_Class_32:{result = sizeof(ELF_Sym32);}break; + case ELF_Class_64:{result = sizeof(ELF_Sym64);}break; } return result; } //////////////////////////////// +//~ rjf: Optional Class Conversion Readers -internal MachineOpResult -elf_read_ehdr(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Hdr64 *ehdr_out) +internal ELF_Hdr64 +elf_hdr64_from_class_data(ELF_Class elf_class, String8 data) { - U8 e_ident[ELF_Identifier_Max] = {0}; - MachineOpResult result = mem_read(addr, &e_ident, sizeof(e_ident), mem_read_ud); - if (result == MachineOpResult_Ok) { - if (str8_match(str8_prefix(str8_array_fixed(e_ident), elf_magic_string.size), elf_magic_string, 0)) { - switch (e_ident[ELF_Identifier_Class]) { - default: { InvalidPath; }break; - case ELF_Class_None: { }break; - case ELF_Class_32: { - ELF_Hdr32 ehdr32 = {0}; - result = mem_read(addr, &ehdr32, sizeof(ehdr32), mem_read_ud); - if (result == MachineOpResult_Ok) { - *ehdr_out = elf_hdr64_from_hdr32(ehdr32); - } - } break; - case ELF_Class_64: { - result = mem_read(addr, ehdr_out, sizeof(*ehdr_out), mem_read_ud); - } break; - } + ELF_Hdr64 result = {0}; + U64 needed_size = elf_hdr_size_from_class(elf_class); + if(data.size >= needed_size) + { + switch(elf_class) + { + default:{}break; + case ELF_Class_32: + { + ELF_Hdr32 result32 = *(ELF_Hdr32 *)data.str; + result = elf_hdr64_from_hdr32(result32); + }break; + case ELF_Class_64: + { + result = *(ELF_Hdr64 *)data.str; + }break; } } return result; } -internal MachineOpResult -elf_read_phdr(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class elf_class, ELF_Phdr64 *phdr_out) +internal ELF_Phdr64 +elf_phdr64_from_class_data(ELF_Class elf_class, String8 data) { - MachineOpResult result = MachineOpResult_Fail; - switch (elf_class) { - case ELF_Class_None: break; - case ELF_Class_32: { - ELF_Phdr32 phdr32 = {0}; - result = mem_read(addr, &phdr32, sizeof(phdr32), mem_read_ud); - if (result == MachineOpResult_Ok) { - *phdr_out = elf_phdr64_from_phdr32(phdr32); - } - } break; - case ELF_Class_64: { - result = mem_read(addr, phdr_out, sizeof(*phdr_out), mem_read_ud); - } break; - default: { NotImplemented; } break; + ELF_Phdr64 result = {0}; + U64 needed_size = elf_phdr_size_from_class(elf_class); + if(data.size >= needed_size) + { + switch(elf_class) + { + default:{}break; + case ELF_Class_32: + { + ELF_Phdr32 result32 = *(ELF_Phdr32 *)data.str; + result = elf_phdr64_from_phdr32(result32); + }break; + case ELF_Class_64: + { + result = *(ELF_Phdr64 *)data.str; + }break; + } } return result; } -internal MachineOpResult -elf_read_shdr(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class elf_class, ELF_Shdr64 *shdr_out) +internal ELF_Shdr64 +elf_shdr64_from_class_data(ELF_Class elf_class, String8 data) { - MachineOpResult result = MachineOpResult_Fail; - switch (elf_class) { - case ELF_Class_None: break; - case ELF_Class_32: { - ELF_Shdr32 shdr32 = {0}; - result = mem_read(addr, &shdr32, sizeof(shdr32), mem_read_ud); - if (result == MachineOpResult_Ok) { - *shdr_out = elf_shdr64_from_shdr32(shdr32); - } - } break; - case ELF_Class_64: { - result = mem_read(addr, shdr_out, sizeof(*shdr_out), mem_read_ud); - } break; - default: { NotImplemented; } break; + ELF_Shdr64 result = {0}; + U64 needed_size = elf_shdr_size_from_class(elf_class); + if(data.size >= needed_size) + { + switch(elf_class) + { + default:{}break; + case ELF_Class_32: + { + ELF_Shdr32 result32 = *(ELF_Shdr32 *)data.str; + result = elf_shdr64_from_shdr32(result32); + }break; + case ELF_Class_64: + { + result = *(ELF_Shdr64 *)data.str; + }break; + } } return result; } -internal MachineOpResult -elf_read_dyn(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class elf_class, ELF_Dyn64 *dyn_out) +internal ELF_Dyn64 +elf_dyn64_from_class_data(ELF_Class elf_class, String8 data) { - MachineOpResult result = MachineOpResult_Fail; - switch (elf_class) { - case ELF_Class_None: {} break; - case ELF_Class_32: { - ELF_Dyn32 dyn32 = {0}; - result = mem_read(addr, &dyn32, sizeof(dyn32), mem_read_ud); - if (result == MachineOpResult_Fail) { - *dyn_out = elf_dyn64_from_dyn32(dyn32); - } - } break; - case ELF_Class_64: { - result = mem_read(addr, dyn_out, sizeof(*dyn_out), mem_read_ud); - } break; - default: { NotImplemented; } break; + ELF_Dyn64 result = {0}; + U64 needed_size = elf_dyn_size_from_class(elf_class); + if(data.size >= needed_size) + { + switch(elf_class) + { + default:{}break; + case ELF_Class_32: + { + ELF_Dyn32 result32 = *(ELF_Dyn32 *)data.str; + result = elf_dyn64_from_dyn32(result32); + }break; + case ELF_Class_64: + { + result = *(ELF_Dyn64 *)data.str; + }break; + } } return result; } -internal MachineOpResult -elf_read_symbol(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class elf_class, ELF_Sym64 *symbol_out) +internal ELF_Sym64 +elf_sym64_from_class_data(ELF_Class elf_class, String8 data) { - MachineOpResult result = MachineOpResult_Fail; - switch (elf_class) { - case ELF_Class_None: {} break; - case ELF_Class_32: { - ELF_Sym32 symbol32 = {0}; - result = mem_read(addr, &symbol32, sizeof(symbol32), mem_read_ud); - if (result == MachineOpResult_Ok) { - *symbol_out = elf_sym64_from_sym32(symbol32); - } - }break; - case ELF_Class_64: { - result = mem_read(addr, symbol_out, sizeof(*symbol_out), mem_read_ud); - }break; - default: { NotImplemented; } break; + ELF_Sym64 result = {0}; + U64 needed_size = elf_sym_size_from_class(elf_class); + if(data.size >= needed_size) + { + switch(elf_class) + { + default:{}break; + case ELF_Class_32: + { + ELF_Sym32 result32 = *(ELF_Sym32 *)data.str; + result = elf_sym64_from_sym32(result32); + }break; + case ELF_Class_64: + { + result = *(ELF_Sym64 *)data.str; + }break; + } } return result; } - diff --git a/src/elf/elf.h b/src/elf/elf.h index 1a32fbce..fb4e343e 100644 --- a/src/elf/elf.h +++ b/src/elf/elf.h @@ -4,17 +4,21 @@ #ifndef ELF_H #define ELF_H +//////////////////////////////// +//~ rjf: Primary ELF File enums/structures + typedef U8 ELF_Class; -enum +typedef enum ELF_ClassEnum { ELF_Class_None = 0, ELF_Class_32 = 1, ELF_Class_64 = 2, - ELF_Class_Count = 3 -}; + ELF_Class_COUNT = 3 +} +ELF_ClassEnum; typedef U8 ELF_OsAbi; -enum +typedef enum ELF_OsAbiEnum { ELF_OsAbi_None, ELF_OsAbi_SYSV, @@ -28,17 +32,19 @@ enum ELF_OsAbi_TRU64, ELF_OsAbi_ARM = 97, ELF_OsAbi_STANDALONE = 255, -}; +} +ELF_OsAbiEnum; typedef U8 ELF_Version; -enum +typedef enum ELF_VersionEnum { ELF_Version_None, ELF_Version_Current, -}; +} +ELF_VersionEnum; typedef U16 ELF_MachineKind; -enum +typedef enum ELF_MachineKindEnum { ELF_MachineKind_None = 0, ELF_MachineKind_M32 = 1, @@ -96,18 +102,20 @@ enum ELF_MachineKind_K1OM = 181, ELF_MachineKind_RISCV = 243, ELF_MachineKind_S390_OLD = 0xA390, -}; +} +ELF_MachineKindEnum; typedef U8 ELF_Data; -enum +typedef enum ELF_DataEnum { ELF_Data_None = 0, ELF_Data_2LSB = 1, ELF_Data_2MSB = 2, -}; +} +ELF_DataEnum; typedef U16 ELF_Type; -enum +typedef enum ELF_TypeEnum { ELF_Type_None = 0, ELF_Type_Rel = 1, @@ -118,10 +126,11 @@ enum ELF_Type_HiOs = 0xff00, ELF_Type_LoProc = 0xff00, ELF_Type_HiProc = 0xffff -}; +} +ELF_TypeEnum; typedef U32 ELF_PType; -enum +typedef enum ELF_PTypeEnum { ELF_PType_Null = 0, ELF_PType_Load = 1, @@ -141,18 +150,20 @@ enum ELF_PType_GnuStack = ELF_PType_LoOs + 0x474e551, // frame unwind information ELF_PType_GnuRelro = ELF_PType_LoOs + 0x474e552, // stack flags ELF_PType_GnuProperty = ELF_PType_LoOs + 0x474e553, // read-only after relocations -}; +} +ELF_PTypeEnum; typedef U32 ELF_PFlag; -enum +typedef enum ELF_PFlagEnum { ELF_PFlag_Exec = (1 << 0), ELF_PFlag_Write = (1 << 1), ELF_PFlag_Read = (1 << 2), -}; +} +ELF_PFlagEnum; typedef U32 ELF_ShType; -enum +typedef enum ELF_ShTypeEnum { ELF_ShType_Null = 0, ELF_ShType_ProgBits = 1, @@ -187,10 +198,11 @@ enum ELF_ShType_GNU_versym = ELF_ShType_SUNW_versym, ELF_ShType_Proc, ELF_ShType_User, -}; +} +ELF_ShTypeEnum; typedef U32 ELF_SectionIndex; -enum +typedef enum ELF_SectionIndexEnum { ELF_SectionIndex_Undef = 0, // Symbol with section index is undefined and must be resolved by the link editor @@ -214,7 +226,8 @@ enum ELF_SectionIndex_TIC6X_Common = ELF_SectionIndex_LoReserve, ELF_SectionIndex_MIPS_SUndefined = 0xff04, -}; +} +ELF_SectionIndexEnum; typedef U32 ELF_SectionFlags; enum @@ -240,7 +253,7 @@ enum #define ELF_SectionFlag_Extract_MaskOs(f) (U8)(((f) >> ELF_SectionFlag_MaskOs_Shift) & ELF_SectionFlag_MaskOs_Mask) #define ELF_SectionFlag_Extract_MaskProc(f) (U8)(((f) >> ELF_SectionFlag_MaskProc_shift) & ELF_SectionFlag_MaskProc_Mask) typedef U32 ELF_AuxType; -enum +typedef enum ELF_AuxTypeEnum { ELF_AuxType_Null = 0, ELF_AuxType_Phdr = 3, // program headers @@ -276,10 +289,11 @@ enum ELF_AuxType_L2_CacheGeometry = 45, ELF_AuxType_L3_CacheSize = 46, ELF_AuxType_L3_CacheGeometry = 47, -}; +} +ELF_AuxTypeEnum; typedef U32 ELF_DynTag; -enum +typedef enum ELF_DynTagEnum { ELF_DynTag_Null = 0, @@ -358,7 +372,8 @@ enum ELF_DynTag_VerSym = 0x6ffffff0, ELF_DynTag_LoProc = 0x70000000, ELF_DynTag_HiProc = 0x7fffffff, -}; +} +ELF_DynTagEnum; typedef U32 ELF_DynFlag; enum @@ -532,9 +547,6 @@ enum ELF_NoteType_STapSdt = 3, // System Tap probes }; -#define ELF_HdrIs64Bit(e_ident) (e_ident[ELF_Identifier_Class] == ELF_Class_64) -#define ELF_HdrIs32Bit(e_ident) (e_ident[ELF_Identifier_Class] == ELF_Class_32) - typedef enum ELF_Identifier { ELF_Identifier_Mag0 = 0, @@ -547,76 +559,82 @@ typedef enum ELF_Identifier ELF_Identifier_OsAbi = 7, ELF_Identfiier_AbiBersion = 8, ELF_Identifier_Max = 16, -} ELF_Identifier; +} +ELF_Identifier; read_only global U8 elf_magic[] = {0x7f, 'E', 'L', 'F'}; read_only global String8 elf_magic_string = {elf_magic, sizeof(elf_magic)}; -typedef struct ELF_Hdr64 +typedef struct ELF_Hdr64 ELF_Hdr64; +struct ELF_Hdr64 { - U8 e_ident[ELF_Identifier_Max]; - ELF_Type e_type; + U8 e_ident[ELF_Identifier_Max]; + ELF_Type e_type; ELF_MachineKind e_machine; - U32 e_version; - U64 e_entry; - U64 e_phoff; - U64 e_shoff; - U32 e_flags; - U16 e_ehsize; - U16 e_phentsize; - U16 e_phnum; - U16 e_shentsize; - U16 e_shnum; - U16 e_shstrndx; -} ELF_Hdr64; + U32 e_version; + U64 e_entry; + U64 e_phoff; + U64 e_shoff; + U32 e_flags; + U16 e_ehsize; + U16 e_phentsize; + U16 e_phnum; + U16 e_shentsize; + U16 e_shnum; + U16 e_shstrndx; +}; -typedef struct ELF_Hdr32 +typedef struct ELF_Hdr32 ELF_Hdr32; +struct ELF_Hdr32 { - U8 e_ident[ELF_Identifier_Max]; - ELF_Type e_type; + U8 e_ident[ELF_Identifier_Max]; + ELF_Type e_type; ELF_MachineKind e_machine; - U32 e_version; - U32 e_entry; - U32 e_phoff; - U32 e_shoff; - U32 e_flags; - U16 e_ehsize; - U16 e_phentsize; - U16 e_phnum; - U16 e_shentsize; - U16 e_shnum; - U16 e_shstrndx; -} ELF_Hdr32; + U32 e_version; + U32 e_entry; + U32 e_phoff; + U32 e_shoff; + U32 e_flags; + U16 e_ehsize; + U16 e_phentsize; + U16 e_phnum; + U16 e_shentsize; + U16 e_shnum; + U16 e_shstrndx; +}; -typedef struct ELF_Shdr64 +typedef struct ELF_Shdr64 ELF_Shdr64; +struct ELF_Shdr64 { - U32 sh_name; + U32 sh_name; ELF_ShType sh_type; - U64 sh_flags; - U64 sh_addr; - U64 sh_offset; - U64 sh_size; - U32 sh_link; - U32 sh_info; - U64 sh_addralign; - U64 sh_entsize; -} ELF_Shdr64; + U64 sh_flags; + U64 sh_addr; + U64 sh_offset; + U64 sh_size; + U32 sh_link; + U32 sh_info; + U64 sh_addralign; + U64 sh_entsize; +}; -typedef struct ELF_Shdr32 +typedef struct ELF_Shdr32 ELF_Shdr32; +struct ELF_Shdr32 { - U32 sh_name; + U32 sh_name; ELF_ShType sh_type; - U32 sh_flags; - U32 sh_addr; - U32 sh_offset; - U32 sh_size; - U32 sh_link; - U32 sh_info; - U32 sh_addralign; - U32 sh_entsize; -} ELF_Shdr32; + U32 sh_flags; + U32 sh_addr; + U32 sh_offset; + U32 sh_size; + U32 sh_link; + U32 sh_info; + U32 sh_addralign; + U32 sh_entsize; +}; -typedef struct ELF_Phdr64 +typedef struct ELF_Phdr64 ELF_Phdr64; +struct ELF_Phdr64 { U32 p_type; U32 p_flags; @@ -626,9 +644,10 @@ typedef struct ELF_Phdr64 U64 p_filesz; U64 p_memsz; U64 p_align; -} ELF_Phdr64; +}; -typedef struct ELF_Phdr32 +typedef struct ELF_Phdr32 ELF_Phdr32; +struct ELF_Phdr32 { U32 p_type; U32 p_offset; @@ -638,46 +657,53 @@ typedef struct ELF_Phdr32 U32 p_memsz; U32 p_flags; U32 p_align; -} ELF_Phdr32; +}; //////////////////////////////// -// Auxiliary Vectors - +//~ Auxiliary Vectors +// // these appear in /proc//auxv of a process, they are not in elf files +// -typedef struct ELF_Auxv32 +typedef struct ELF_Auxv32 ELF_Auxv32; +struct ELF_Auxv32 { U32 a_type; U32 a_val; -} ELF_Auxv32; +}; -typedef struct ELF_Auxv64 +typedef struct ELF_Auxv64 ELF_Auxv64; +struct ELF_Auxv64 { U64 a_type; U64 a_val; -} ELF_Auxv64; +}; //////////////////////////////// -// Dynamic Structures - +//~ Dynamic Structures +// // these appear in the virtual address space of a process, they are not in elf files +// -typedef struct ELF_Dyn32 +typedef struct ELF_Dyn32 ELF_Dyn32; +struct ELF_Dyn32 { U32 tag; U32 val; -} ELF_Dyn32; +}; -typedef struct ELF_Dyn64 +typedef struct ELF_Dyn64 ELF_Dyn64; +struct ELF_Dyn64 { U64 tag; U64 val; -} ELF_Dyn64; +}; //////////////////////////////// -// Imports and Exports +//~ Imports and Exports -typedef struct +typedef struct ELF_Sym32 ELF_Sym32; +struct ELF_Sym32 { U32 st_name; // Holds index into files string table. U32 st_value; // Depending on the context, this may be address, size, etc. @@ -685,9 +711,10 @@ typedef struct U8 st_info; // Contains symbols type and binding. U8 st_other; // Reserved for future use, currenly zero. U16 st_shndx; // Section index to which symbol is relevant. -} ELF_Sym32; +}; -typedef struct +typedef struct ELF_Sym64 ELF_Sym64; +struct ELF_Sym64 { U32 st_name; U8 st_info; @@ -695,38 +722,42 @@ typedef struct U16 st_shndx; U64 st_value; U64 st_size; -} ELF_Sym64; +}; #define ELF_ST_INFO(b,t) (((b) << 4) + ((t) & 0xF)) #define ELF_ST_BIND(x) ((x) >> 4) #define ELF_ST_TYPE(x) ((x) & 0xF) #define ELF_ST_VISIBILITY(v) ((v) & 0x3) -typedef struct +typedef struct ELF_Rel32 ELF_Rel32; +struct ELF_Rel32 { U32 r_offset; U32 r_info; -} ELF_Rel32; +}; -typedef struct +typedef struct ELF_Rela32 ELF_Rela32; +struct ELF_Rela32 { U32 r_offset; U32 r_info; S32 r_addend; -} ELF_Rela32; +}; -typedef struct +typedef struct ELF_Rel64 ELF_Rel64; +struct ELF_Rel64 { U64 r_offset; U64 r_info; -} ELF_Rel64; +}; -typedef struct +typedef struct ELF_Rela64 ELF_Rela64; +struct ELF_Rela64 { U64 r_offset; U64 r_info; S64 r_addend; -} ELF_Rela64; +}; #define ELF32_R_SYM(x) ((x) >> 8) #define ELF32_R_TYPE(x) ((x) & 0xFF) @@ -740,7 +771,8 @@ typedef struct #define ELF_EXTERNAL_VERSYM_MASK 0x7FFF // Appears in .gnu.verdef (SHT_GNU_verdef) -typedef struct +typedef struct ELF_ExternalVerdef ELF_ExternalVerdef; +struct ELF_ExternalVerdef { U16 vd_version; U16 vd_flags; @@ -749,43 +781,47 @@ typedef struct U32 vd_hash; U32 vd_aux; U32 vd_next; -} ELF_ExternalVerdef; +}; // Appears in .gnu.verdef (SHT_GNU_verdef) -typedef struct +typedef struct ELF_ExternalVerdaux ELF_ExternalVerdaux; +struct ELF_ExternalVerdaux { U32 vda_name; U32 vda_next; -} ELF_ExternalVerdaux; +}; // Appears in .gnu.verneed (SHT_GNU_verneed) -typedef struct +typedef struct ELF_ExternalVerneed ELF_ExternalVerneed; +struct ELF_ExternalVerneed { U16 vn_version; U16 vn_cnt; U32 vn_file; U32 vn_aux; U32 vn_next; -} ELF_ExternalVerneed; +}; // Appears in .gnu.verneed (SHT_GNU_verneed) -typedef struct +typedef struct ELF_ExternalVernaux ELF_ExternalVernaux; +struct ELF_ExternalVernaux { U32 vna_hash; U16 vna_flags; U16 vna_other; U32 vna_name; U32 vna_next; -} ELF_ExternalVernaux; +}; // Appears in .gnu.version (SHT_GNU_versym) -typedef struct +typedef struct ELF_ExternalVersym ELF_ExternalVersym; +struct ELF_ExternalVersym { U16 vs_vers; -} ELF_ExternalVersym; +}; //////////////////////////////// -// Extensions +//~ Extensions typedef U8 ELF_CompressType; enum ELF_CompressTypeEnum @@ -801,53 +837,57 @@ enum ELF_CompressTypeEnum ELF_CompressType_HiProc = 0x7fffffff, }; -typedef struct ELF_Chdr32 +typedef struct ELF_Chdr32 ELF_Chdr32; +struct ELF_Chdr32 { U32 ch_type; U32 ch_size; U32 ch_addr_align; -} ELF_Chdr32; +}; -typedef struct ELF_Chdr64 +typedef struct ELF_Chdr64 ELF_Chdr64; +struct ELF_Chdr64 { U32 ch_type; U32 ch_reserved; U64 ch_size; U64 ch_addr_align; -} ELF_Chdr64; +}; //////////////////////////////// +//~ rjf: 32 => 64 bit conversions internal ELF_Hdr64 elf_hdr64_from_hdr32(ELF_Hdr32 h32); internal ELF_Shdr64 elf_shdr64_from_shdr32(ELF_Shdr32 h32); internal ELF_Phdr64 elf_phdr64_from_phdr32(ELF_Phdr32 h32); -internal ELF_Dyn64 elf_dyn64_from_dyn32 (ELF_Dyn32 h32); -internal ELF_Sym64 elf_sym64_from_sym32 (ELF_Sym32 sym32); -internal ELF_Rel64 elf_rel64_from_rel32 (ELF_Rel32 rel32); +internal ELF_Dyn64 elf_dyn64_from_dyn32(ELF_Dyn32 h32); +internal ELF_Sym64 elf_sym64_from_sym32(ELF_Sym32 sym32); +internal ELF_Rel64 elf_rel64_from_rel32(ELF_Rel32 rel32); internal ELF_Rela64 elf_rela64_from_rela32(ELF_Rela32 rela32); internal ELF_Chdr64 elf_chdr64_from_chdr32(ELF_Chdr32 chdr32); internal ELF_Auxv64 elf_auxv64_from_auxv32(ELF_Auxv32 auxv32); //////////////////////////////// - -internal String8 elf_string_from_class(Arena *arena, ELF_Class v); - -//////////////////////////////// +//~ rjf: ELF -> Codebase enum Conversions internal Arch arch_from_elf_machine(ELF_MachineKind machine); //////////////////////////////// +//~ rjf: ELF Class -> Structure Size +internal U64 elf_hdr_size_from_class(ELF_Class elf_class); internal U64 elf_phdr_size_from_class(ELF_Class elf_class); +internal U64 elf_shdr_size_from_class(ELF_Class elf_class); internal U64 elf_dyn_size_from_class(ELF_Class elf_class); +internal U64 elf_sym_size_from_class(ELF_Class elf_class); //////////////////////////////// -//~ Compat Readers +//~ rjf: Optional Class Conversion Readers -internal MachineOpResult elf_read_ehdr (MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Hdr64 *ehdr_out); -internal MachineOpResult elf_read_phdr (MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class elf_class, ELF_Phdr64 *phdr_out); -internal MachineOpResult elf_read_shdr (MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class elf_class, ELF_Shdr64 *shdr_out); -internal MachineOpResult elf_read_dyn (MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class elf_class, ELF_Dyn64 *dyn_out); -internal MachineOpResult elf_read_symbol(MachineOp_MemRead *mem_read, void *mem_read_ud, U64 addr, ELF_Class elf_class, ELF_Sym64 *symbol_out); +internal ELF_Hdr64 elf_hdr64_from_class_data(ELF_Class elf_class, String8 data); +internal ELF_Phdr64 elf_phdr64_from_class_data(ELF_Class elf_class, String8 data); +internal ELF_Shdr64 elf_shdr64_from_class_data(ELF_Class elf_class, String8 data); +internal ELF_Dyn64 elf_dyn64_from_class_data(ELF_Class elf_class, String8 data); +internal ELF_Sym64 elf_sym64_from_class_data(ELF_Class elf_class, String8 data); #endif // ELF_H diff --git a/src/elf/elf_parse.c b/src/elf/elf_parse.c index 297965ff..a77820db 100644 --- a/src/elf/elf_parse.c +++ b/src/elf/elf_parse.c @@ -172,8 +172,8 @@ internal ELF_NoteList elf_parse_note(Arena *arena, String8 raw_note, ELF_Class elf_class, ELF_MachineKind e_machine) { ELF_NoteList result = {0}; - - for (U64 cursor = 0; cursor < raw_note.size; ) { + for(U64 cursor = 0; cursor < raw_note.size; ) + { U32 owner_size; U64 owner_size_size = str8_deserial_read_struct(raw_note, cursor, &owner_size); if (owner_size_size == 0) { goto exit; } @@ -206,9 +206,6 @@ elf_parse_note(Arena *arena, String8 raw_note, ELF_Class elf_class, ELF_MachineK SLLQueuePush(result.first, result.last, n); result.count += 1; } - exit:; return result; } - - diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index b23e5876..fd50fe08 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -40,6 +40,16 @@ lnx_dmn_write(int memory_fd, Rng1U64 range, void *src) return is_written; } +internal String8 +lnx_dmn_data_from_memory_range(Arena *arena, int memory_fd, Rng1U64 range) +{ + String8 result = {0}; + result.size = dim_1u64(range); + result.str = push_array(arena, U8, result.size); + lnx_dmn_read(memory_fd, range, result.str); + return result; +} + internal String8 lnx_dmn_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size) { @@ -340,21 +350,24 @@ lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap) } //////////////////////////////// -//~ rjf: ELF/GNU Parsing +//~ rjf: In-Memory ELF Parsing Helpers internal Rng1U64 -lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) +lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) { - Rng1U64 result = { .min = max_U64 }; - - for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize) + Rng1U64 result = {.min = max_U64}; + for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); + ph_cursor < ph_opl; + ph_cursor += e_phentsize) { ELF_Phdr64 phdr = {0}; - if(elf_read_phdr(lnx_dmn_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok) { - Assert(0 && "unable to read a program header"); + Temp scratch = scratch_begin(0, 0); + U64 phdr_size = elf_phdr_size_from_class(elf_class); + String8 phdr_data = lnx_dmn_data_from_memory_range(scratch.arena, memory_fd, r1u64(ph_cursor, ph_cursor+phdr_size)); + phdr = elf_phdr64_from_class_data(elf_class, phdr_data); + scratch_end(scratch); } - if(phdr.p_type == ELF_PType_Load) { U64 min = rebase + phdr.p_vaddr; @@ -363,7 +376,6 @@ lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 result.max = Max(result.max, max); } } - return result; } @@ -458,14 +470,25 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 //- rjf: load this module if it's not already loaded if(module == 0) { - // parse out module's ELF header + Temp scratch = scratch_begin(0, 0); + + // rjf: read module's ELF identifier + U8 module_eident[ELF_Identifier_Max] = {0}; + lnx_dmn_read(memory_fd, r1u64(base_vaddr, base_vaddr + sizeof(module_eident)), module_eident); + ELF_Class module_eclass = module_eident[ELF_Identifier_Class]; + + // rjf: read module's ELF header ELF_Hdr64 module_ehdr = {0}; - elf_read_ehdr(lnx_dmn_machine_op_mem_read, &memory_fd, base_vaddr, &module_ehdr); + { + U64 ehdr_size = elf_hdr_size_from_class(module_eclass); + String8 ehdr_data = lnx_dmn_data_from_memory_range(scratch.arena, memory_fd, r1u64(base_vaddr, base_vaddr+ehdr_size)); + module_ehdr = elf_hdr64_from_class_data(module_eclass, ehdr_data); + } // gather info about module - U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; - Rng1U64 module_vrange = lnx_dmn_compute_image_vrange(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); + U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; + Rng1U64 module_vrange = lnx_dmn_vaddr_range_from_phdrs(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); // read TLS index and TLS offset U64 tls_index = max_U64; @@ -505,6 +528,8 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 { DLLPushBack_NP(ctx->module_slots[slot_idx].first, ctx->module_slots[slot_idx].last, module, hash_next, hash_prev); } + + scratch_end(scratch); } return module; } @@ -2475,25 +2500,42 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - Rng1U64 image_vrange = lnx_dmn_compute_image_vrange(new_process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); + Rng1U64 image_vrange = lnx_dmn_vaddr_range_from_phdrs(new_process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); Arena *ctx_arena = arena_alloc(); + //- rjf: read dynamically loaded ELF identifier + U8 dl_eident[ELF_Identifier_Max] = {0}; + lnx_dmn_read(new_process->fd, r1u64(auxv.base, auxv.base+sizeof(dl_eident)), dl_eident); + ELF_Class dl_class = dl_eident[ELF_Identifier_Class]; + //- rjf: read dynamically loaded ELF header ELF_Hdr64 dl_ehdr = {0}; - elf_read_ehdr(lnx_dmn_machine_op_mem_read, &new_process->fd, auxv.base, &dl_ehdr); - ELF_Class dl_class = dl_ehdr.e_ident[ELF_Identifier_Class]; + { + U64 ehdr_size = elf_hdr_size_from_class(dl_class); + String8 ehdr_data = lnx_dmn_data_from_memory_range(scratch.arena, new_process->fd, r1u64(auxv.base, auxv.base+ehdr_size)); + dl_ehdr = elf_hdr64_from_class_data(dl_class, ehdr_data); + } //- rjf: compute rdebug vaddr U64 rdebug_vaddr = 0; { Temp scratch = scratch_begin(0, 0); - // load DL's header - ELF_Hdr64 ehdr = {0}; - elf_read_ehdr(lnx_dmn_machine_op_mem_read, &new_process->fd, auxv.base, &ehdr); + // rjf: load DL's elf identifier + U8 eident[ELF_Identifier_Max] = {0}; + lnx_dmn_read(new_process->fd, r1u64(auxv.base, auxv.base+sizeof(eident)), eident); + ELF_Class elf_class = eident[ELF_Identifier_Class]; + // rjf: load DL's header + ELF_Hdr64 ehdr = {0}; + { + U64 ehdr_size = elf_hdr_size_from_class(elf_class); + String8 ehdr_data = lnx_dmn_data_from_memory_range(scratch.arena, new_process->fd, r1u64(auxv.base, auxv.base+ehdr_size)); + ehdr = elf_hdr64_from_class_data(elf_class, ehdr_data); + } + + // rjf: unpack header U64 rebase = dl_ehdr.e_type == ELF_Type_Dyn ? auxv.base : 0; - ELF_Class elf_class = ehdr.e_ident[ELF_Identifier_Class]; // rjf: find dynamic program header U64 dynamic_vaddr = 0; @@ -2502,7 +2544,13 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) for(U64 ph_cursor = phdr_vaddr, ph_opl = (phdr_vaddr + ehdr.e_phentsize * ehdr.e_phentsize); ph_cursor < ph_opl; ph_cursor += ehdr.e_phentsize) { ELF_Phdr64 phdr = {0}; - elf_read_phdr(lnx_dmn_machine_op_mem_read, &new_process->fd, ph_cursor, elf_class, &phdr); + { + Temp scratch = scratch_begin(0, 0); + U64 phdr_size = elf_phdr_size_from_class(elf_class); + String8 phdr_data = lnx_dmn_data_from_memory_range(scratch.arena, new_process->fd, r1u64(ph_cursor, ph_cursor+phdr_size)); + phdr = elf_phdr64_from_class_data(elf_class, phdr_data); + scratch_end(scratch); + } if(phdr.p_type == ELF_PType_Dynamic) { dynamic_vaddr = rebase + phdr.p_vaddr; @@ -2521,7 +2569,13 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { // rjf: read next dyn entry ELF_Dyn64 dyn = {0}; - if(elf_read_dyn(lnx_dmn_machine_op_mem_read, &new_process->fd, dynamic_cursor, elf_class, &dyn) != MachineOpResult_Ok) { Assert(0 && "unable to read dynamic"); } + { + Temp scratch = scratch_begin(&arena, 1); + U64 dyn_size = elf_dyn_size_from_class(elf_class); + String8 dyn_data = lnx_dmn_data_from_memory_range(scratch.arena, new_process->fd, r1u64(dynamic_cursor, dynamic_cursor+dyn_size)); + dyn = elf_dyn64_from_class_data(elf_class, dyn_data); + scratch_end(scratch); + } // rjf: break on zero if(dyn.tag == ELF_DynTag_Null) { break; } @@ -2626,10 +2680,13 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) for EachIndex(symbol_idx, symbol_count) { ELF_Sym64 symbol = {0}; - if(elf_read_symbol(lnx_dmn_machine_op_mem_read, &new_process->fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol) != MachineOpResult_Ok) { - Assert(0 && "failed to read symbol table"); - break; + Temp temp = temp_begin(scratch.arena); + U64 sym_size = elf_sym_size_from_class(elf_class); + U64 sym_vaddr = dynamic_info.symtab_vaddr + symbol_idx*dynamic_info.symtab_entry_size; + String8 sym_data = lnx_dmn_data_from_memory_range(temp.arena, new_process->fd, r1u64(sym_vaddr, sym_vaddr+sym_size)); + symbol = elf_sym64_from_class_data(elf_class, sym_data); + temp_end(temp); } Temp temp = temp_begin(scratch.arena); @@ -2776,12 +2833,29 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(dl_fd >= 0) { U64 offset = 0; - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(lnx_dmn_machine_op_mem_read, &dl_fd, offset, &ehdr) != MachineOpResult_Ok) { goto exit; } - U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; - ELF_Shdr64 strtab_shdr = {0}; - if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &dl_fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr) != MachineOpResult_Ok) { goto exit; } + U8 eident[ELF_Identifier_Max] = {0}; + lnx_dmn_read(dl_fd, r1u64(offset, offset+sizeof(eident)), eident); + ELF_Class eclass = eident[ELF_Identifier_Class]; + + ELF_Hdr64 ehdr = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + U64 ehdr_size = elf_hdr_size_from_class(eclass); + String8 ehdr_data = lnx_dmn_data_from_memory_range(scratch.arena, dl_fd, r1u64(offset, offset+ehdr_size)); + ehdr = elf_hdr64_from_class_data(eclass, ehdr_data); + scratch_end(scratch); + } + + U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; + ELF_Shdr64 strtab_shdr = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + U64 shdr_size = elf_shdr_size_from_class(eclass); + String8 shdr_data = lnx_dmn_data_from_memory_range(scratch.arena, dl_fd, r1u64(strtab_shdr_offset, strtab_shdr_offset+shdr_size)); + strtab_shdr = elf_shdr64_from_class_data(eclass, shdr_data); + scratch_end(scratch); + } B32 found_probes = 0; B32 found_probes_base = 0; @@ -2790,9 +2864,16 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) ELF_Shdr64 stapsdt_shdr = {0}; for(U64 shdr_off = offset + ehdr.e_shoff, shdr_opl = shdr_off + ehdr.e_shentsize * ehdr.e_shnum; shdr_off < shdr_opl; - shdr_off += ehdr.e_shentsize) { + shdr_off += ehdr.e_shentsize) + { ELF_Shdr64 shdr = {0}; - if(elf_read_shdr(lnx_dmn_machine_op_mem_read, &dl_fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr) != MachineOpResult_Ok) { goto exit; } + { + Temp scratch = scratch_begin(&arena, 1); + U64 shdr_size = elf_shdr_size_from_class(eclass); + String8 shdr_data = lnx_dmn_data_from_memory_range(scratch.arena, dl_fd, r1u64(shdr_off, shdr_off+shdr_size)); + strtab_shdr = elf_shdr64_from_class_data(eclass, shdr_data); + scratch_end(scratch); + } if(shdr.sh_type == ELF_ShType_Note) { diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index f0010032..797dd998 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -398,6 +398,7 @@ thread_static B32 lnx_dmn_ctrl_thread = 0; internal U64 lnx_dmn_read(int memory_fd, Rng1U64 range, void *dst); internal B32 lnx_dmn_write(int memory_fd, Rng1U64 range, void *src); +internal String8 lnx_dmn_data_from_memory_range(Arena *arena, int memory_fd, Rng1U64 range); internal String8 lnx_dmn_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size); internal String8 lnx_dmn_read_string(Arena *arena, int memory_fd, U64 base_vaddr); #define lnx_dmn_read_struct(fd, vaddr, ptr) lnx_dmn_read((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) @@ -414,9 +415,9 @@ internal DMN_ModuleInfo *lnx_dmn_module_info_from_process_module(Arena *arena, p internal LNX_DMN_ActiveTrap *lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap); //////////////////////////////// -//~ rjf: ELF/GNU Parsing +//~ rjf: In-Memory ELF Parsing Helpers -internal Rng1U64 lnx_dmn_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); +internal Rng1U64 lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); //////////////////////////////// //~ rjf: Entity Functions diff --git a/src/radbin/radbin.c b/src/radbin/radbin.c index a89b0fb8..43d735f2 100644 --- a/src/radbin/radbin.c +++ b/src/radbin/radbin.c @@ -259,7 +259,7 @@ rb_thread_entry_point(void *p) identifier_maybe[ELF_Identifier_Mag3] == 'F'); if(is_elf_magic) { - file_format = ELF_HdrIs64Bit(identifier_maybe) ? RB_FileFormat_ELF64 : RB_FileFormat_ELF32; + file_format = (identifier_maybe[ELF_Identifier_Class] == ELF_Class_64) ? RB_FileFormat_ELF64 : RB_FileFormat_ELF32; } } From 338a8226a1dcc860855ac463cb94e2b27079804b Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 11 Aug 2026 11:08:04 -0700 Subject: [PATCH 037/251] linux_demon: fix incorrect shdr read --- src/linux/demon/linux_demon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index fd50fe08..17e7e379 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -2871,7 +2871,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) Temp scratch = scratch_begin(&arena, 1); U64 shdr_size = elf_shdr_size_from_class(eclass); String8 shdr_data = lnx_dmn_data_from_memory_range(scratch.arena, dl_fd, r1u64(shdr_off, shdr_off+shdr_size)); - strtab_shdr = elf_shdr64_from_class_data(eclass, shdr_data); + shdr = elf_shdr64_from_class_data(eclass, shdr_data); scratch_end(scratch); } From 94392b49757992f2f76e30e8ba02ca330c412097 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 11 Aug 2026 14:02:40 -0700 Subject: [PATCH 038/251] linux_demon: checkpoint --- src/elf/elf.c | 38 +++ src/elf/elf.h | 2 + src/linux/demon/linux_demon.c | 554 +++++++++++++++------------------- src/linux/demon/linux_demon.h | 1 + src/stap/stap_parse.c | 38 --- src/stap/stap_parse.h | 4 + src/win32/demon/win32_demon.c | 14 +- 7 files changed, 305 insertions(+), 346 deletions(-) diff --git a/src/elf/elf.c b/src/elf/elf.c index c92ac9c6..34da98d7 100644 --- a/src/elf/elf.c +++ b/src/elf/elf.c @@ -213,6 +213,20 @@ elf_sym_size_from_class(ELF_Class elf_class) return result; } +internal U64 +elf_auxv_size_from_class(ELF_Class elf_class) +{ + U64 result = 0; + switch((ELF_ClassEnum)elf_class) + { + case ELF_Class_COUNT: + case ELF_Class_None:{}break; + case ELF_Class_32:{result = sizeof(ELF_Auxv32);}break; + case ELF_Class_64:{result = sizeof(ELF_Auxv64);}break; + } + return result; +} + //////////////////////////////// //~ rjf: Optional Class Conversion Readers @@ -335,3 +349,27 @@ elf_sym64_from_class_data(ELF_Class elf_class, String8 data) } return result; } + +internal ELF_Auxv64 +elf_auxv64_from_class_data(ELF_Class elf_class, String8 data) +{ + ELF_Auxv64 result = {0}; + U64 needed_size = elf_auxv_size_from_class(elf_class); + if(data.size >= needed_size) + { + switch(elf_class) + { + default:{}break; + case ELF_Class_32: + { + ELF_Auxv32 result32 = *(ELF_Auxv32 *)data.str; + result = elf_auxv64_from_auxv32(result32); + }break; + case ELF_Class_64: + { + result = *(ELF_Auxv64 *)data.str; + }break; + } + } + return result; +} diff --git a/src/elf/elf.h b/src/elf/elf.h index fb4e343e..13a3311e 100644 --- a/src/elf/elf.h +++ b/src/elf/elf.h @@ -880,6 +880,7 @@ internal U64 elf_phdr_size_from_class(ELF_Class elf_class); internal U64 elf_shdr_size_from_class(ELF_Class elf_class); internal U64 elf_dyn_size_from_class(ELF_Class elf_class); internal U64 elf_sym_size_from_class(ELF_Class elf_class); +internal U64 elf_auxv_size_from_class(ELF_Class elf_class); //////////////////////////////// //~ rjf: Optional Class Conversion Readers @@ -889,5 +890,6 @@ internal ELF_Phdr64 elf_phdr64_from_class_data(ELF_Class elf_class, String8 data internal ELF_Shdr64 elf_shdr64_from_class_data(ELF_Class elf_class, String8 data); internal ELF_Dyn64 elf_dyn64_from_class_data(ELF_Class elf_class, String8 data); internal ELF_Sym64 elf_sym64_from_class_data(ELF_Class elf_class, String8 data); +internal ELF_Auxv64 elf_auxv64_from_class_data(ELF_Class elf_class, String8 data); #endif // ELF_H diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 17e7e379..98a1bc5f 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -352,6 +352,20 @@ lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap) //////////////////////////////// //~ rjf: In-Memory ELF Parsing Helpers +internal ELF_Hdr64 +lnx_dmn_elf_hdr64_from_vaddr(int memory_fd, U64 vaddr) +{ + Temp scratch = scratch_begin(0, 0); + U8 ident[ELF_Identifier_Max] = {0}; + lnx_dmn_read(memory_fd, r1u64(vaddr, vaddr+sizeof(ident)), ident); + ELF_Class elf_class = ident[ELF_Identifier_Class]; + U64 hdr_size = elf_hdr_size_from_class(elf_class); + String8 data = lnx_dmn_data_from_memory_range(scratch.arena, memory_fd, r1u64(vaddr, vaddr+hdr_size)); + ELF_Hdr64 hdr64 = elf_hdr64_from_class_data(elf_class, data); + scratch_end(scratch); + return hdr64; +} + internal Rng1U64 lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) { @@ -472,45 +486,34 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 { Temp scratch = scratch_begin(0, 0); - // rjf: read module's ELF identifier - U8 module_eident[ELF_Identifier_Max] = {0}; - lnx_dmn_read(memory_fd, r1u64(base_vaddr, base_vaddr + sizeof(module_eident)), module_eident); - ELF_Class module_eclass = module_eident[ELF_Identifier_Class]; - // rjf: read module's ELF header - ELF_Hdr64 module_ehdr = {0}; - { - U64 ehdr_size = elf_hdr_size_from_class(module_eclass); - String8 ehdr_data = lnx_dmn_data_from_memory_range(scratch.arena, memory_fd, r1u64(base_vaddr, base_vaddr+ehdr_size)); - module_ehdr = elf_hdr64_from_class_data(module_eclass, ehdr_data); - } + ELF_Hdr64 module_ehdr = lnx_dmn_elf_hdr64_from_vaddr(memory_fd, base_vaddr); + ELF_Class module_eclass = module_ehdr.e_ident[ELF_Identifier_Class]; - // gather info about module + // rjf: unpack module's vaddr range U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; Rng1U64 module_vrange = lnx_dmn_vaddr_range_from_phdrs(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); - // read TLS index and TLS offset - U64 tls_index = max_U64; + // rjf: read TLS index and TLS offset + U64 tls_index = max_U64; U64 tls_offset = max_U64; if(is_main) { - tls_index = 1; + tls_index = 1; tls_offset = 0; } - else + else if(lnx_dmn_state->is_tls_detected) { - if(lnx_dmn_state->is_tls_detected) - { - Rng1U64 tls_modid_range = r1u64(lnx_dmn_state->tls_modid_desc.offset, lnx_dmn_state->tls_modid_desc.offset + lnx_dmn_state->tls_modid_desc.bit_size / 8); - Rng1U64 tls_offset_range = r1u64(lnx_dmn_state->tls_offset_desc.offset, lnx_dmn_state->tls_offset_desc.offset + lnx_dmn_state->tls_offset_desc.bit_size / 8); - tls_modid_range = shift_1u64(tls_modid_range, base_vaddr); - tls_offset_range = shift_1u64(tls_offset_range, base_vaddr); - if(!lnx_dmn_read(memory_fd, tls_modid_range, &tls_index)) { Assert(0 && "failed to read TLS index"); } - if(!lnx_dmn_read(memory_fd, tls_offset_range, &tls_offset)) { Assert(0 && "failed to read TLS offset"); } - } + Rng1U64 tls_modid_range = r1u64(lnx_dmn_state->tls_modid_desc.offset, lnx_dmn_state->tls_modid_desc.offset + lnx_dmn_state->tls_modid_desc.bit_size/8); + Rng1U64 tls_offset_range = r1u64(lnx_dmn_state->tls_offset_desc.offset, lnx_dmn_state->tls_offset_desc.offset + lnx_dmn_state->tls_offset_desc.bit_size/8); + tls_modid_range = shift_1u64(tls_modid_range, base_vaddr); + tls_offset_range = shift_1u64(tls_offset_range, base_vaddr); + lnx_dmn_read(memory_fd, tls_modid_range, &tls_index); + lnx_dmn_read(memory_fd, tls_offset_range, &tls_offset); } + // rjf: allocate & fill module module = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Module)->module; module->base_vaddr = base_vaddr; module->name_vaddr = name_vaddr; @@ -520,7 +523,7 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 module->tls_offset = tls_offset; module->is_main = is_main; - // add module to the list + // rjf: link into context's module list DLLPushBack_NP(ctx->first_module, ctx->last_module, module, order_next, order_prev); ctx->module_count += 1; @@ -623,16 +626,12 @@ internal DMN_Handle lnx_dmn_handle_from_entity(LNX_DMN_Entity *entity) { DMN_Handle handle = {0}; - U64 index = IntFromPtr(entity - lnx_dmn_state->entities_base); + U64 index = (U64)(entity - lnx_dmn_state->entities_base); if(index <= max_U32) { handle.u32[0] = index; handle.u32[1] = entity->gen; } - else - { - Assert(0 && "failed to make a handle for the entity"); - } return handle; } @@ -939,90 +938,89 @@ lnx_dmn_thread_read_reg_block(LNX_DMN_Thread *thread) internal B32 lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread) { - AssertAlways(thread->state == LNX_DMN_ThreadState_Stopped); - B32 is_reg_block_written = 0; - - switch(thread->process->ctx->arch) + if(thread->state == LNX_DMN_ThreadState_Stopped) { - case Arch_Null: {} break; - case Arch_x64: + switch(thread->process->ctx->arch) { - LNX_DMN_ProcessCtx *process_ctx = thread->process->ctx; - X64_RegBlock *src = thread->reg_block; - - // general purpose registers + case Arch_arm64: + case Arch_arm32: + case Arch_x86: + case Arch_COUNT: + case Arch_Null:{}break; + case Arch_x64: { - LNX_DMN_GprsX64 dst; - dst.r15 = src->r15; - dst.r14 = src->r14; - dst.r13 = src->r13; - dst.r12 = src->r12; - dst.rbp = src->rbp; - dst.rbx = src->rbx; - dst.r11 = src->r11; - dst.r10 = src->r10; - dst.r9 = src->r9; - dst.r8 = src->r8; - dst.rax = src->rax; - dst.rcx = src->rcx; - dst.rdx = src->rdx; - dst.rsi = src->rsi; - dst.rdi = src->rdi; - dst.orig_rax = thread->orig_rax; - dst.rip = src->rip; - dst.cs = src->cs; - dst.rflags = src->rflags; - dst.rsp = src->rsp; - dst.ss = src->ss; - dst.fsbase = src->fsbase; - dst.gsbase = src->gsbase; - dst.ds = src->ds; - dst.es = src->es; - dst.fs = src->fs; - dst.gs = src->gs; - int ptrace_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = &dst, .iov_len = sizeof(dst) })); - if(ptrace_result < 0) { goto exit; } - } - - // xsave - { - Temp scratch = scratch_begin(0, 0); + LNX_DMN_ProcessCtx *process_ctx = thread->process->ctx; + X64_RegBlock *src = thread->reg_block; + B32 good = 1; - X64_FXSave dst_fxsave = {0}; + // rjf: write general purpose registers { - dst_fxsave.fcw = src->fcw; - dst_fxsave.fsw = src->fsw; - dst_fxsave.ftw = src->ftw; - dst_fxsave.fop = src->fop; - dst_fxsave.fip = src->fip; - dst_fxsave.fdp = src->fdp; - dst_fxsave.mxcsr = src->mxcsr; - dst_fxsave.mxcsr_mask = src->mxcsr_mask; - - U128 *st_d = (U128 *)dst_fxsave.st_space; - U80 *st_s = &src->st0; - for EachIndex(n, 8) + LNX_DMN_GprsX64 dst; + dst.r15 = src->r15; + dst.r14 = src->r14; + dst.r13 = src->r13; + dst.r12 = src->r12; + dst.rbp = src->rbp; + dst.rbx = src->rbx; + dst.r11 = src->r11; + dst.r10 = src->r10; + dst.r9 = src->r9; + dst.r8 = src->r8; + dst.rax = src->rax; + dst.rcx = src->rcx; + dst.rdx = src->rdx; + dst.rsi = src->rsi; + dst.rdi = src->rdi; + dst.orig_rax = thread->orig_rax; + dst.rip = src->rip; + dst.cs = src->cs; + dst.rflags = src->rflags; + dst.rsp = src->rsp; + dst.ss = src->ss; + dst.fsbase = src->fsbase; + dst.gsbase = src->gsbase; + dst.ds = src->ds; + dst.es = src->es; + dst.fs = src->fs; + dst.gs = src->gs; + int ptrace_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = &dst, .iov_len = sizeof(dst) })); + if(ptrace_result < 0) { - MemoryCopy(&st_d[n], &st_s[n], sizeof(U80)); - } - - U128 *xmm_d = (U128 *)dst_fxsave.xmm_space; - U512 *xmm_s = &src->zmm0; - for EachIndex(n, 16) - { - MemoryCopy(&xmm_d[n], &xmm_s[n], sizeof(U128)); + good = 0; } } - if(x64_is_xsave_supported()) + // rjf: write xsave registers + if(good && x64_is_xsave_supported()) { + Temp scratch = scratch_begin(0, 0); U8 *xsave_raw = push_array_aligned(scratch.arena, U8, process_ctx->xsave_size, 64); X64_XSave *dst = (X64_XSave *)xsave_raw; - dst->fxsave = dst_fxsave; + { + dst->fxsave.fcw = src->fcw; + dst->fxsave.fsw = src->fsw; + dst->fxsave.ftw = src->ftw; + dst->fxsave.fop = src->fop; + dst->fxsave.fip = src->fip; + dst->fxsave.fdp = src->fdp; + dst->fxsave.mxcsr = src->mxcsr; + dst->fxsave.mxcsr_mask = src->mxcsr_mask; + U128 *st_d = (U128 *)dst->fxsave.st_space; + U80 *st_s = &src->st0; + for EachIndex(n, 8) + { + MemoryCopy(&st_d[n], &st_s[n], sizeof(U80)); + } + U128 *xmm_d = (U128 *)dst->fxsave.xmm_space; + U512 *xmm_s = &src->zmm0; + for EachIndex(n, 16) + { + MemoryCopy(&xmm_d[n], &xmm_s[n], sizeof(U128)); + } + } dst->header.xstate_bv |= X64_XStateComponentFlag_FP; dst->header.xstate_bv |= X64_XStateComponentFlag_SSE; - if(process_ctx->xsave_layout.avx_offset) { if(process_ctx->xsave_layout.avx_offset + sizeof(U128) * 16 <= process_ctx->xsave_size) @@ -1036,7 +1034,6 @@ lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread) dst->header.xstate_bv |= X64_XStateComponentFlag_AVX; } } - if(process_ctx->xsave_layout.opmask_offset) { if(process_ctx->xsave_layout.opmask_offset + sizeof(U64) * 8 <= process_ctx->xsave_size) @@ -1049,9 +1046,7 @@ lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread) } dst->header.xstate_bv |= X64_XStateComponentFlag_OPMASK; } - else { Assert(0 && "invalid xsave size"); goto exit; } } - if(process_ctx->xsave_layout.zmm_h_offset) { if(process_ctx->xsave_layout.zmm_h_offset + sizeof(U256) * 16 <= process_ctx->xsave_size) @@ -1064,9 +1059,7 @@ lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread) } dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM_H; } - else { Assert(0 && "invalid xsave size"); goto exit; } } - if(process_ctx->xsave_layout.zmm_offset) { if(process_ctx->xsave_layout.zmm_offset + sizeof(U512) * 16 <= process_ctx->xsave_size) @@ -1079,9 +1072,7 @@ lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread) } dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM; } - else { Assert(0 && "invalid xsave size"); goto exit; } } - if(process_ctx->xsave_layout.cet_u_offset) { if(process_ctx->xsave_layout.cet_u_offset + sizeof(U64) * 2 <= process_ctx->xsave_size) @@ -1091,43 +1082,39 @@ lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread) cet_u[1] = src->cetssp; dst->header.xstate_bv |= X64_XStateComponentFlag_CETU; } - else { Assert(0 && "invalid xsave size"); goto exit; } } - - // xsave - Assert(dst->header.xcomp_bv == 0); // must always be zero + dst->header.xcomp_bv = 0; int ptrace_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_base = dst, .iov_len = process_ctx->xsave_size })); - if(ptrace_result < 0) { goto exit; } + if(ptrace_result < 0) + { + log_infof("ptrace error: PTRACE_SETREGSET failed, when trying to write xsave registers."); + } + scratch_end(scratch); } - scratch_end(scratch); - } - - // debug registers - { - src->dr7 |= (1 << 10); - - U64 *dr_s = &src->dr0; - for EachIndex(n, 8) + // rjf: write debug registers + if(good) { - if(n != 4 && n != 5) + src->dr7 |= (1 << 10); + U64 *dr_s = &src->dr0; + for EachIndex(n, 8) { - U64 offset = OffsetOf(LNX_DMN_UserX64, u_debugreg[n]); - int ptrace_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_POKEUSER, thread->tid, PtrFromInt(offset), (void*)(uintptr_t)dr_s[n])); - if(ptrace_result < 0) { goto exit; } + if(n != 4 && n != 5) + { + U64 offset = OffsetOf(LNX_DMN_UserX64, u_debugreg[n]); + int ptrace_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_POKEUSER, thread->tid, PtrFromInt(offset), (void*)(uintptr_t)dr_s[n])); + if(ptrace_result < 0) + { + log_infof("ptrace error: PTRACE_POKEUSER failed, when trying to write debug registers."); + } + } } } - } - - is_reg_block_written = 1; - } break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: { NotImplemented; }break; - default: { InvalidPath; } break; + + is_reg_block_written = good; + }break; + } } - - exit:; return is_reg_block_written; } @@ -1137,7 +1124,11 @@ lnx_dmn_set_single_step_flag(LNX_DMN_Thread *thread, B32 is_on) B32 is_flag_set = 0; switch(thread->process->ctx->arch) { - case Arch_Null: {} break; + case Arch_x86: + case Arch_arm32: + case Arch_arm64: + case Arch_COUNT: + case Arch_Null:{}break; case Arch_x64: { X64_RegBlock *reg_block = thread->reg_block; @@ -1145,13 +1136,8 @@ lnx_dmn_set_single_step_flag(LNX_DMN_Thread *thread, B32 is_on) else { reg_block->rflags &= ~X64_RFlag_Trap; } thread->is_reg_block_dirty = 1; is_flag_set = 1; - } break; - case Arch_x86: - case Arch_arm32: - case Arch_arm64: { NotImplemented; }break; - default: { InvalidPath; } break; + }break; } - Assert(is_flag_set); return is_flag_set; } @@ -2408,10 +2394,14 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } else { + ////////////////////// //- rjf: allocate ctx + // ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; - //- rjf: read main executable ELF header + ////////////////////// + //- rjf: read executable ELF header + // ELF_Hdr64 exe_ehdr = {0}; { Temp scratch = scratch_begin(&arena, 1); @@ -2419,35 +2409,18 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) int exe_fd = LNX_RETRY_ON_EINTR(open(exe_path, O_RDONLY)); if(exe_fd >= 0) { - U8 e_ident[ELF_Identifier_Max] = {0}; - lnx_dmn_read(exe_fd, r1u64(0, sizeof(e_ident)), e_ident); - if(MemoryMatch(e_ident, elf_magic_string.str, elf_magic_string.size)) - { - switch(e_ident[ELF_Identifier_Class]) - { - default:{}break; - case ELF_Class_None:{}break; - case ELF_Class_32: - { - ELF_Hdr32 ehdr32 = {0}; - lnx_dmn_read(exe_fd, r1u64(0, sizeof(ehdr32)), &ehdr32); - exe_ehdr = elf_hdr64_from_hdr32(ehdr32); - }break; - case ELF_Class_64: - { - lnx_dmn_read(exe_fd, r1u64(0, sizeof(exe_ehdr)), &exe_ehdr); - }break; - } - } + exe_ehdr = lnx_dmn_elf_hdr64_from_vaddr(exe_fd, 0); } LNX_RETRY_ON_EINTR(close(exe_fd)); scratch_end(scratch); } + ////////////////////// //- rjf: read auxv info for pid + // LNX_DMN_Auxv auxv = {0}; { - Temp scratch = scratch_begin(0, 0); + Temp scratch = scratch_begin(&arena, 1); // rjf: open aux data String8 auxv_path = str8f(scratch.arena, "/proc/%d/auxv", wait_id); @@ -2460,27 +2433,19 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { // rjf: read next aux ELF_Auxv64 auxv_entry = {0}; - switch(exe_ehdr.e_ident[ELF_Identifier_Class]) { - default: - case ELF_Class_None:{}break; - case ELF_Class_32: - { - ELF_Auxv32 auxv32 = {0}; - off += lnx_dmn_read_struct(auxv_fd, off, &auxv32); - auxv_entry = elf_auxv64_from_auxv32(auxv32); - }break; - case ELF_Class_64: - { - off += lnx_dmn_read_struct(auxv_fd, off, &auxv_entry); - }break; + Temp temp = temp_begin(scratch.arena); + U64 auxv_size = elf_auxv_size_from_class(exe_ehdr.e_ident[ELF_Identifier_Class]); + String8 auxv_data = lnx_dmn_data_from_memory_range(temp.arena, auxv_fd, r1u64(off, off+auxv_size)); + auxv_entry = elf_auxv64_from_class_data(exe_ehdr.e_ident[ELF_Identifier_Class], auxv_data); + off += auxv_size; + temp_end(temp); } // rjf: fill result switch(auxv_entry.a_type) { default:{}break; - case ELF_AuxType_Null: goto brkloop; break; case ELF_AuxType_Base: auxv.base = auxv_entry.a_val; break; case ELF_AuxType_Phnum: auxv.phnum = auxv_entry.a_val; break; case ELF_AuxType_Phent: auxv.phent = auxv_entry.a_val; break; @@ -2488,8 +2453,13 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) case ELF_AuxType_ExecFn: auxv.execfn = auxv_entry.a_val; break; case ELF_AuxType_Pagesz: auxv.pagesz = auxv_entry.a_val; break; } + + // rjf: terminate on null entries + if(auxv_entry.a_type == ELF_AuxType_Null) + { + break; + } } - brkloop:; LNX_RETRY_ON_EINTR(close(auxv_fd)); } @@ -2499,40 +2469,17 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) //- rjf: unpack context Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); - U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + U64 rebase = (exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0); Rng1U64 image_vrange = lnx_dmn_vaddr_range_from_phdrs(new_process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); - Arena *ctx_arena = arena_alloc(); - - //- rjf: read dynamically loaded ELF identifier - U8 dl_eident[ELF_Identifier_Max] = {0}; - lnx_dmn_read(new_process->fd, r1u64(auxv.base, auxv.base+sizeof(dl_eident)), dl_eident); - ELF_Class dl_class = dl_eident[ELF_Identifier_Class]; //- rjf: read dynamically loaded ELF header - ELF_Hdr64 dl_ehdr = {0}; - { - U64 ehdr_size = elf_hdr_size_from_class(dl_class); - String8 ehdr_data = lnx_dmn_data_from_memory_range(scratch.arena, new_process->fd, r1u64(auxv.base, auxv.base+ehdr_size)); - dl_ehdr = elf_hdr64_from_class_data(dl_class, ehdr_data); - } + ELF_Hdr64 dl_ehdr = lnx_dmn_elf_hdr64_from_vaddr(new_process->fd, auxv.base); + ELF_Class dl_class = dl_ehdr.e_ident[ELF_Identifier_Class]; //- rjf: compute rdebug vaddr U64 rdebug_vaddr = 0; { - Temp scratch = scratch_begin(0, 0); - - // rjf: load DL's elf identifier - U8 eident[ELF_Identifier_Max] = {0}; - lnx_dmn_read(new_process->fd, r1u64(auxv.base, auxv.base+sizeof(eident)), eident); - ELF_Class elf_class = eident[ELF_Identifier_Class]; - - // rjf: load DL's header - ELF_Hdr64 ehdr = {0}; - { - U64 ehdr_size = elf_hdr_size_from_class(elf_class); - String8 ehdr_data = lnx_dmn_data_from_memory_range(scratch.arena, new_process->fd, r1u64(auxv.base, auxv.base+ehdr_size)); - ehdr = elf_hdr64_from_class_data(elf_class, ehdr_data); - } + Temp scratch = scratch_begin(&arena, 1); // rjf: unpack header U64 rebase = dl_ehdr.e_type == ELF_Type_Dyn ? auxv.base : 0; @@ -2540,15 +2487,17 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // rjf: find dynamic program header U64 dynamic_vaddr = 0; { - U64 phdr_vaddr = auxv.base + dl_ehdr.e_phoff; - for(U64 ph_cursor = phdr_vaddr, ph_opl = (phdr_vaddr + ehdr.e_phentsize * ehdr.e_phentsize); ph_cursor < ph_opl; ph_cursor += ehdr.e_phentsize) + U64 phdr_vaddr = auxv.base + dl_ehdr.e_phoff; + for(U64 ph_cursor = phdr_vaddr, ph_opl = (phdr_vaddr + dl_ehdr.e_phentsize*dl_ehdr.e_phentsize); + ph_cursor < ph_opl; + ph_cursor += dl_ehdr.e_phentsize) { ELF_Phdr64 phdr = {0}; { Temp scratch = scratch_begin(0, 0); - U64 phdr_size = elf_phdr_size_from_class(elf_class); + U64 phdr_size = elf_phdr_size_from_class(dl_class); String8 phdr_data = lnx_dmn_data_from_memory_range(scratch.arena, new_process->fd, r1u64(ph_cursor, ph_cursor+phdr_size)); - phdr = elf_phdr64_from_class_data(elf_class, phdr_data); + phdr = elf_phdr64_from_class_data(dl_class, phdr_data); scratch_end(scratch); } if(phdr.p_type == ELF_PType_Dynamic) @@ -2559,21 +2508,20 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } - // rjf: get dynamic info for this module - // extract necessary info out of dynamic program header + // rjf: parse dynamic info for this module LNX_DMN_DynamicInfo dynamic_info = {0}; { B32 is_rebased = !!(process_new_flags & LNX_DMN_CreateProcessFlag_Rebased); U64 dynamic_info_rebase = is_rebased ? 0 : rebase; - for(U64 dynamic_cursor = dynamic_vaddr; ; dynamic_cursor += elf_dyn_size_from_class(elf_class)) + for(U64 dynamic_cursor = dynamic_vaddr;; dynamic_cursor += elf_dyn_size_from_class(dl_class)) { // rjf: read next dyn entry ELF_Dyn64 dyn = {0}; { Temp scratch = scratch_begin(&arena, 1); - U64 dyn_size = elf_dyn_size_from_class(elf_class); + U64 dyn_size = elf_dyn_size_from_class(dl_class); String8 dyn_data = lnx_dmn_data_from_memory_range(scratch.arena, new_process->fd, r1u64(dynamic_cursor, dynamic_cursor+dyn_size)); - dyn = elf_dyn64_from_class_data(elf_class, dyn_data); + dyn = elf_dyn64_from_class_data(dl_class, dyn_data); scratch_end(scratch); } @@ -2594,103 +2542,106 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } - // extract symbol table count from available options + // rjf: parse symbol table count for this module U64 symbol_count = 0; - if(dynamic_info.hash_vaddr) { - U64 hash_entry_size = 4; - if(elf_class == ELF_Class_64 && (ehdr.e_machine == ELF_MachineKind_ALPHA || ehdr.e_machine == ELF_MachineKind_S390 || ehdr.e_machine == ELF_MachineKind_S390_OLD)) + // rjf: base hash implementation + if(dynamic_info.hash_vaddr) { - hash_entry_size = 8; + U64 hash_entry_size = 4; + if(dl_class == ELF_Class_64 && (dl_ehdr.e_machine == ELF_MachineKind_ALPHA || dl_ehdr.e_machine == ELF_MachineKind_S390 || dl_ehdr.e_machine == ELF_MachineKind_S390_OLD)) + { + hash_entry_size = 8; + } + U64 chain_count = 0; + if(lnx_dmn_read(new_process->fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size) + { + symbol_count = chain_count; + } } - U64 chain_count = 0; - if(lnx_dmn_read(new_process->fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size) + // rjf: GNU hash table implementation + else if(dynamic_info.gnu_hash_vaddr) { - symbol_count = chain_count; - } - else - { - Assert(0 && "failed to read hash table's chain count out of HASH"); - } - } - else if(dynamic_info.gnu_hash_vaddr) - { - // GNU_HASH header: u32 nbuckets, u32 symoffset, u32 bloom_size, u32 bloom_shift, - // followed by bloom[bloom_size] (word size = 4 on 32-bit ELF, 8 on 64-bit), - // buckets[nbuckets] (u32), chains[...] (u32, terminated by entry w/ LSB set). - U32 gnu_header[4] = {0}; - if(lnx_dmn_read(new_process->fd, r1u64(dynamic_info.gnu_hash_vaddr, dynamic_info.gnu_hash_vaddr + sizeof(gnu_header)), gnu_header) == sizeof(gnu_header)) - { - U32 nbuckets = gnu_header[0]; - U32 symoffset = gnu_header[1]; - U32 bloom_size = gnu_header[2]; - U64 bloom_word = (elf_class == ELF_Class_64) ? 8 : 4; - U64 buckets_vaddr = dynamic_info.gnu_hash_vaddr + sizeof(gnu_header) + bloom_size * bloom_word; - U64 chain_vaddr = buckets_vaddr + (U64)nbuckets * sizeof(U32); - - // find largest symbol index referenced by any bucket - U32 max_bucket = 0; - for(U32 b = 0; b < nbuckets; b += 1) + // GNU_HASH header: u32 nbuckets, u32 symoffset, u32 bloom_size, u32 bloom_shift, + // followed by bloom[bloom_size] (word size = 4 on 32-bit ELF, 8 on 64-bit), + // buckets[nbuckets] (u32), chains[...] (u32, terminated by entry w/ LSB set). + U32 gnu_header[4] = {0}; + if(lnx_dmn_read(new_process->fd, r1u64(dynamic_info.gnu_hash_vaddr, dynamic_info.gnu_hash_vaddr + sizeof(gnu_header)), gnu_header) == sizeof(gnu_header)) { - U32 entry = 0; - if(lnx_dmn_read(new_process->fd, r1u64(buckets_vaddr + b*sizeof(U32), buckets_vaddr + (b+1)*sizeof(U32)), &entry) != sizeof(U32)) + // rjf: unpack header + U32 nbuckets = gnu_header[0]; + U32 symoffset = gnu_header[1]; + U32 bloom_size = gnu_header[2]; + U64 bloom_word = (dl_class == ELF_Class_64) ? 8 : 4; + U64 buckets_vaddr = dynamic_info.gnu_hash_vaddr + sizeof(gnu_header) + bloom_size * bloom_word; + U64 chain_vaddr = buckets_vaddr + (U64)nbuckets * sizeof(U32); + + // rjf: find largest symbol index referenced by any bucket + U32 max_bucket = 0; + for(U32 b = 0; b < nbuckets; b += 1) { - break; + U32 entry = 0; + if(lnx_dmn_read(new_process->fd, r1u64(buckets_vaddr + b*sizeof(U32), buckets_vaddr + (b+1)*sizeof(U32)), &entry) != sizeof(U32)) + { + break; + } + if(entry > max_bucket) { max_bucket = entry; } } - if(entry > max_bucket) { max_bucket = entry; } - } - - if(max_bucket < symoffset) - { - // no symbols in hash; total count is symoffset - symbol_count = symoffset; - } - else - { - // walk chain from max_bucket until terminator (entry w/ LSB set) - U64 idx = max_bucket; - for(;;) + + // rjf: no symbols in hash table - total count is symoffset + if(max_bucket < symoffset) { - U32 chain_entry = 0; - U64 entry_vaddr = chain_vaddr + (idx - symoffset) * sizeof(U32); - if(lnx_dmn_read(new_process->fd, r1u64(entry_vaddr, entry_vaddr + sizeof(U32)), &chain_entry) != sizeof(U32)) + symbol_count = symoffset; + } + + // rjf: else -> walk chain from max_bucket until terminator (entry w/ LSB set) + else + { + U64 idx = max_bucket; + for(;;) { - break; - } - if(chain_entry & 1) - { - symbol_count = idx + 1; - break; - } - idx += 1; - if(idx - max_bucket > (1u<<24)) - { - // sanity bound: don't loop forever on a corrupt chain - break; + U32 chain_entry = 0; + U64 entry_vaddr = chain_vaddr + (idx - symoffset) * sizeof(U32); + if(lnx_dmn_read(new_process->fd, r1u64(entry_vaddr, entry_vaddr + sizeof(U32)), &chain_entry) != sizeof(U32)) + { + break; + } + if(chain_entry & 1) + { + symbol_count = idx + 1; + break; + } + idx += 1; + if(idx - max_bucket > (1u<<24)) + { + break; + } } } } } } - // scan symbol table for the rendezvous symbol + // rjf: scan symbol table for the rendezvous symbol if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count) { for EachIndex(symbol_idx, symbol_count) { + Temp temp = temp_begin(scratch.arena); + + // rjf: read next symbol ELF_Sym64 symbol = {0}; { Temp temp = temp_begin(scratch.arena); - U64 sym_size = elf_sym_size_from_class(elf_class); + U64 sym_size = elf_sym_size_from_class(dl_class); U64 sym_vaddr = dynamic_info.symtab_vaddr + symbol_idx*dynamic_info.symtab_entry_size; String8 sym_data = lnx_dmn_data_from_memory_range(temp.arena, new_process->fd, r1u64(sym_vaddr, sym_vaddr+sym_size)); - symbol = elf_sym64_from_class_data(elf_class, sym_data); + symbol = elf_sym64_from_class_data(dl_class, sym_data); temp_end(temp); } - Temp temp = temp_begin(scratch.arena); - + // rjf: read symbol's name String8 symbol_name = {0}; if(symbol.st_name < dynamic_info.strtab_size) { @@ -2698,24 +2649,30 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) symbol_name = lnx_dmn_read_string_capped(temp.arena, new_process->fd, dynamic_info.strtab_vaddr + symbol.st_name, cap); } - if(str8_match(symbol_name, str8_lit("_r_debug"), 0)) + // rjf: _r_debug -> this symbol defines the rdebug_vaddr + B32 got_rdebug = 0; + if(str8_match(symbol_name, s("_r_debug"), 0)) { ELF_SymType symbol_type = ELF_ST_TYPE(symbol.st_info); if(symbol_type == ELF_SymType_Object && symbol.st_size > 0) { + got_rdebug = 1; rdebug_vaddr = rebase + symbol.st_value; - break; } } temp_end(temp); + if(got_rdebug) + { + break; + } } } scratch_end(scratch); } - //- query xsave layout + //- rjf: [x64] query xsave layout U64 xcr0 = 0; U64 xsave_size = 0; X64_XSaveLayout xsave_layout = {0}; @@ -2732,11 +2689,12 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } else { - Assert(0 && "failed to get xstate"); + log_infof("ptrace error: PTRACE_GETREGSET failed, when trying to determine xsave layout."); } } - // gather probes + //- rjf: gather probes in this process + Arena *ctx_arena = arena_alloc(); LNX_DMN_Probe **known_probes = push_array(ctx_arena, LNX_DMN_Probe *, LNX_DMN_ProbeKind_COUNT); { Temp scratch = scratch_begin(0, 0); @@ -2834,18 +2792,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { U64 offset = 0; - U8 eident[ELF_Identifier_Max] = {0}; - lnx_dmn_read(dl_fd, r1u64(offset, offset+sizeof(eident)), eident); - ELF_Class eclass = eident[ELF_Identifier_Class]; - - ELF_Hdr64 ehdr = {0}; - { - Temp scratch = scratch_begin(&arena, 1); - U64 ehdr_size = elf_hdr_size_from_class(eclass); - String8 ehdr_data = lnx_dmn_data_from_memory_range(scratch.arena, dl_fd, r1u64(offset, offset+ehdr_size)); - ehdr = elf_hdr64_from_class_data(eclass, ehdr_data); - scratch_end(scratch); - } + ELF_Hdr64 ehdr = lnx_dmn_elf_hdr64_from_vaddr(dl_fd, offset); + ELF_Class eclass = ehdr.e_ident[ELF_Identifier_Class]; U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; ELF_Shdr64 strtab_shdr = {0}; @@ -2995,7 +2943,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) scratch_end(scratch); } - //- rjf: build context + //- rjf: allocate & fill context ctx = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; ctx->arena = ctx_arena; ctx->arch = arch; @@ -3008,10 +2956,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); ctx->xsave_layout = xsave_layout; - //- create main module + //- rjf: create main module LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, new_process->fd, base_vaddr, auxv.execfn, 1, 1); - //- glibc has a shortcut mapping for the main module + //- rjf: glibc has a shortcut mapping for the main module; ensure 0 base address goes to it { U64 vaddr = 0; U64 hash = u64_hash_from_str8(str8_struct(&vaddr)); diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index 797dd998..0e95a32f 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -417,6 +417,7 @@ internal LNX_DMN_ActiveTrap *lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap); //////////////////////////////// //~ rjf: In-Memory ELF Parsing Helpers +internal ELF_Hdr64 lnx_dmn_elf_hdr64_from_vaddr(int memory_fd, U64 vaddr); internal Rng1U64 lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); //////////////////////////////// diff --git a/src/stap/stap_parse.c b/src/stap/stap_parse.c index f7c0bcff..e7fa528c 100644 --- a/src/stap/stap_parse.c +++ b/src/stap/stap_parse.c @@ -7,44 +7,6 @@ stap_is_scale_valid(U64 scale) return IsPow2(scale) && scale <= 8; } -internal U64 -stap_parse_digit(U8 *begin, U8 *end, U64 *digit_out) -{ - U8 *ptr = begin; - if (ptr < end && *ptr == '-') { - ptr += 1; - } - for (;ptr < end; ptr += 1) { - if (!char_is_digit(*ptr, 10)) { break; } - } - String8 digit_str = str8(begin, (U64)(ptr - begin)); - if (digit_out) { - *digit_out = u64_from_str8(digit_str, 10); - } - return digit_str.size; -} - -internal U64 -stap_skip_whitespace(U8 *begin, U8 *end) -{ - U8 *ptr = begin; - for (; ptr < end && *ptr == ' '; ptr += 1); - U64 size = (U64)(ptr - begin); - return size; -} - -internal U64 -stap_parse_ident(U8 *begin, U8 *end, String8 *ident_out) -{ - U8 *ptr = begin; - for (; ptr < end; ptr += 1) { - if (!char_is_alpha(*ptr) && !char_is_digit(*ptr, 10)) { break; } - } - String8 ident = str8(begin, (U64)(ptr - begin)); - if (ident_out) { *ident_out = ident; } - return ident.size; -} - internal U64 stap_size_from_arg(String8 string) { diff --git a/src/stap/stap_parse.h b/src/stap/stap_parse.h index df64e263..96890c81 100644 --- a/src/stap/stap_parse.h +++ b/src/stap/stap_parse.h @@ -67,6 +67,10 @@ typedef STAP_MEMORY_READ(STAP_MemoryRead); //////////////////////////////// +internal B32 stap_is_scale_valid(U64 scale); +internal U64 stap_size_from_arg(String8 string); +internal String8List stap_list_from_string(Arena *arena, String8 string); + internal String8 stap_parse_args_x64(String8 string, STAP_Arg *arg_out); internal STAP_ArgArray stap_arg_array_from_string(Arena *arena, Arch arch, String8 string); diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index 360d60a1..8972c3e8 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -9,9 +9,13 @@ internal DMN_Handle w32_dmn_handle_from_entity(W32_DMN_Entity *entity) { - U32 idx = (U32)(entity - w32_dmn_shared->entities_base); - U32 gen = entity->gen; - DMN_Handle handle = {idx, gen}; + DMN_Handle handle = {0}; + U64 index = (U64)(entity - w32_dmn_shared->entities_base); + if(index <= max_U32) + { + handle.u32[0] = (U32)index; + handle.u32[1] = entity->gen; + } return handle; } @@ -1677,7 +1681,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(single_step_thread_ctx != 0) { U64 rflags = single_step_thread_ctx->EFlags|0x2; - U64 new_rflags = rflags | 0x100; + U64 new_rflags = rflags|X64_RFlag_Trap; single_step_thread_ctx->EFlags = new_rflags; SetThreadContext(thread->handle, single_step_thread_ctx); } @@ -3100,7 +3104,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(single_step_thread_ctx != 0) { U64 rflags = single_step_thread_ctx->EFlags|0x2; - U64 new_rflags = rflags & ~0x100; + U64 new_rflags = rflags & ~X64_RFlag_Trap; single_step_thread_ctx->EFlags = new_rflags; SetThreadContext(thread->handle, single_step_thread_ctx); } From baef2f07bfb5cd1d94fad651c7760358a8aef13e Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 12 Aug 2026 09:48:54 -0700 Subject: [PATCH 039/251] fix torture --- project.4coder | 2 +- project.raddbg | 64 ++++++-------------------------------- src/torture/torture_main.c | 2 ++ 3 files changed, 12 insertions(+), 56 deletions(-) diff --git a/project.4coder b/project.4coder index f2ae9f7c..c992f764 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && wsl ./build.sh raddbg", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin && pushd build && pushd test && radbin --rdi a.out && radbin --dump a.rdi --out:a.dump && popd && popd", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/project.raddbg b/project.raddbg index c4ce262e..fb942e89 100755 --- a/project.raddbg +++ b/project.raddbg @@ -4,17 +4,14 @@ name: "The RAD Debugger" target: { executable: "build/raddbg.exe" - working_directory: "../raddebugger" - arguments: "--user:C:/devel/raddebugger/build/raddbg_test.user" + working_directory: build + arguments: "--user:raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { - executable: "build/raddbg.exe" - working_directory: "../raddebugger" - arguments: "--user:test123" - debug_subprocesses: 0 + executable: "build/metagen.exe" + working_directory: build } target: { @@ -26,41 +23,7 @@ target: { executable: "build/torture.exe" working_directory: build - arguments: "eval2/*" -} -target: -{ - executable: "build/metagen.exe" - working_directory: build -} -target: -{ - executable: "build/radbin.exe" - working_directory: build - arguments: "--rdi raddbg" -} -target: -{ - executable: "build/release_version/raddbg.exe" - working_directory: "build/release_version" -} -target: -{ - executable: "build/radbin.exe" - working_directory: build - arguments: "--rdi fat.so --capture" -} -target: -{ - executable: "build/radbin.exe" - working_directory: build - arguments: "--rdi fat.debug --capture" -} -target: -{ - executable: "build/radbin.exe" - working_directory: build - arguments: "--rdi raddbg" + arguments: * } watch_pin: expression: tag_hash_slots target: @@ -70,17 +33,8 @@ target: } target: { - executable: "../../Program Files/SuperTux/bin/supertux2.exe" - working_directory: "../../Program Files/SuperTux/bin" -} -target: -{ - executable: "build/raddbg" - working_directory: build -} -target: -{ - executable: "../scratch/test.exe" - working_directory: "../scratch" - entry_point: "main::main" + executable: "build/radbin.exe" + working_directory: "build/" + enabled: 1 + arguments: "--rdi raddbg" } diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 672f7d8b..9d9e58e8 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -32,6 +32,7 @@ #include "mdesk/mdesk.h" #include "metagen/metagen.h" #include "window_manager/window_manager_inc.h" +#include "shell/shell_inc.h" #include "http/http_inc.h" #include "symbol_server/symbol_server_inc.h" #include "config/config_inc.h" @@ -114,6 +115,7 @@ #include "mdesk/mdesk.c" #include "metagen/metagen.c" #include "window_manager/window_manager_inc.c" +#include "shell/shell_inc.c" #include "http/http_inc.c" #include "symbol_server/symbol_server_inc.c" #include "config/config_inc.c" From a47be13d707e246b05647fee722a38c225ccbb44 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 12 Aug 2026 10:01:40 -0700 Subject: [PATCH 040/251] linux_demon: do not assume nonzero threads --- src/linux/demon/linux_demon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 98a1bc5f..133656bf 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -2148,7 +2148,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) case PTRACE_EVENT_STOP: { // rjf: stop event for pending threads -> thread is created & stopped - if(thread->state == LNX_DMN_ThreadState_PendingCreation) + if(thread != 0 && thread->state == LNX_DMN_ThreadState_PendingCreation) { thread->state = LNX_DMN_ThreadState_Stopped; lnx_dmn_state->threads_pending_creation -= 1; From 8372582608dae72982b9d82dc11aaf55ea07c0e5 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 12 Aug 2026 15:48:49 -0700 Subject: [PATCH 041/251] rdi_from_dwarf: first pass at entry-point-independent deterministic type graph hashing --- project.raddbg | 4 +- src/rdi_from_dwarf/rdi_from_dwarf.c | 193 +++++++++++++++++++++------- src/rdi_from_dwarf/rdi_from_dwarf.h | 15 +++ 3 files changed, 166 insertions(+), 46 deletions(-) diff --git a/project.raddbg b/project.raddbg index fb942e89..2b803237 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -35,6 +36,5 @@ target: { executable: "build/radbin.exe" working_directory: "build/" - enabled: 1 - arguments: "--rdi raddbg" + arguments: "--rdi test/a.out" } diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index a9661251..cd7be489 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -4,6 +4,13 @@ //////////////////////////////// //~ rjf: Helpers +internal int +d2r_tree_hash_is_less_than(D2R_TreeHash *a, D2R_TreeHash *b) +{ + int is_less_than = (a->hash < b->hash); + return is_less_than; +} + internal int d2r_unique_tag_node_is_less_than(D2R_UniqueTagNode **l, D2R_UniqueTagNode **r) { @@ -1167,7 +1174,6 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) struct D2R_ParentTagNode { D2R_ParentTagNode *next; - U64 container_ancestor_info_off; U64 hash_seed; }; D2R_ParentTagNode *top_parent = 0; @@ -1301,18 +1307,6 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) n_hash_seed = hash; } - // rjf: determine container ancestor info off - U64 container_ancestor_info_off = top_parent ? top_parent->container_ancestor_info_off : 0; - if(tag.kind == DW_TagKind_StructureType || - tag.kind == DW_TagKind_UnionType || - tag.kind == DW_TagKind_ClassType || - tag.kind == DW_TagKind_SubProgram || - tag.kind == DW_TagKind_LexicalBlock || - tag.kind == DW_TagKind_Namespace) - { - container_ancestor_info_off = start_off; - } - // rjf: push node for this parent D2R_ParentTagNode *n = free_parent; if(n != 0) @@ -1326,7 +1320,6 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) MemoryZeroStruct(n); SLLStackPush(top_parent, n); n->hash_seed = n_hash_seed; - n->container_ancestor_info_off = container_ancestor_info_off; } // rjf: pop parents @@ -1429,9 +1422,6 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) struct D2R_ParentTagNode { D2R_ParentTagNode *next; - DW_TagKind tag_kind; - U64 info_off; - U64 hash_seed; B32 disable_dedup; }; D2R_ParentTagNode *top_parent = 0; @@ -1521,21 +1511,6 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) tags_to_dedup_count += 1; } - // rjf: compute hash seed for this tag - U64 hash_seed = (top_parent ? top_parent->hash_seed : 0); - if(tag.kind == DW_TagKind_SubProgram || - tag.kind == DW_TagKind_StructureType || - tag.kind == DW_TagKind_UnionType || - tag.kind == DW_TagKind_ClassType || - tag.kind == DW_TagKind_Namespace) - { - DW2_Attrib *name_attrib = dw2_attrib_from_kind(&tag, DW_AttribKind_Name); - if(name_attrib != &dw2_attrib_nil) - { - hash_seed = u64_hash_from_str8(name_attrib->val.string); - } - } - // rjf: if we have a sibling offset encoded, and if we do not need to dedup children, // then skip if(!children_should_be_deduped && sibling_encoded && sibling_off >= off) @@ -1559,9 +1534,6 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) n = push_array(work_scratch.arena, D2R_ParentTagNode, 1); } SLLStackPush(top_parent, n); - n->tag_kind = tag.kind; - n->info_off = start_off; - n->hash_seed = hash_seed; n->disable_dedup = !children_should_be_deduped; } if(tag.kind == DW_TagKind_Null) @@ -1585,11 +1557,11 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) } } - //- rjf: produce tree hash for all tags we need to deduplicate & gather + //- rjf: produce tree + reference hash for all tags we need to deduplicate & gather // - // the tree hash is formed by combining the tag hashes for all dependency tags + // the tree hash is formed by combining the tag hashes under a single tag + // the tree+ref hash is formed by combining the tree hashes across a graph of references // - U64 total_tag_count_this_work = 0; for(D2R_TagNode *tag_n = first_tag_to_dedup; tag_n != 0; tag_n = tag_n->next) { Temp dedup_root_scratch = scratch_begin(&scratch.arena, 1); @@ -1597,11 +1569,144 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) //- rjf: unpack tag node U64 start_off = tag_n->info_off; + //- rjf: gather the set of tree hashes in the type graph pointed into by `tag_n` +#if 0 + D2R_TreeHashNode *first_tree_hash = 0; + D2R_TreeHashNode *last_tree_hash = 0; + U64 tree_hash_count = 0; + { + typedef struct Task Task; + struct Task + { + Task *next; + U64 unit_idx; + U64 start_off; + }; + Task start_task = {0, origin_unit_idx, start_off}; + Task *first_task = &start_task; + Task *last_task = 0; + Task *free_task = 0; + for(;first_task != 0;) + { + // rjf: unpack this task's unit info + U64 unit_idx = first_task->unit_idx; + Rng1U64 unit_info_tag_range = unit_info_tag_ranges[unit_idx]; + DW2_ParseCtx *unit_parse_ctx = &unit_parse_ctxs[unit_idx]; + + // rjf: combine all tag hashes in this tag tree + S64 depth = 0; + U64 tree_hash = 0; + for(U64 off = first_task->start_off; contains_1u64(unit_info_tag_range, off) && (depth > 0 || off == first_task->start_off);) + { + U64 start_off = off; + Temp tag_scratch = scratch_begin(&dedup_root_scratch.arena, 1); + + // rjf: look up this tag hash + U64 tag_hash = 0; + { + U64 off_hash = u64_hash_from_str8(str8_struct(&start_off)); + U64 slot_idx = off_hash%tag_hash_slots_count; + for(D2R_TagHashNode *n = tag_hash_slots[slot_idx]; n != 0; n = n->next) + { + if(n->info_off == start_off) + { + tag_hash = n->hash; + break; + } + } + } + + // rjf: combine this tag hash into the tree hash + tree_hash = u64_hash_from_seed_str8(tree_hash, str8_struct(&tag_hash)); + + // rjf: read the next tag + DW2_Tag tag = {0}; + off += dw2_read_tag(tag_scratch.arena, raw, unit_parse_ctx, raw->sec[DW_SectionKind_Info].data, off, &tag); + + // rjf: find type reference in this tag, use to generate additional tasks + DW2_Attrib *type_attrib = dw2_attrib_from_kind(&tag, DW_AttribKind_Type); + if(type_attrib != &dw2_attrib_nil) + { + U64 type_ref_info_off = dw2_reference_info_off_from_form_val(unit_parse_ctx, &type_attrib->val); + U64 type_ref_unit_idx = unit_idx; + if(!contains_1u64(unit_info_tag_range, type_ref_info_off)) + { + U64 new_unit_num = rng1u64_array_num_from_value__binary_search(&unit_info_tag_ranges_array, type_ref_info_off); + if(0 < new_unit_num && new_unit_num <= unit_count) + { + type_ref_unit_idx = new_unit_num-1; + } + } + Task *t = free_task; + if(t) + { + SLLStackPop(free_task); + } + else + { + t = push_array(dedup_root_scratch.arena, Task, 1); + } + SLLQueuePush(first_task, last_task, t); + t->unit_idx = type_ref_unit_idx; + t->start_off = type_ref_info_off; + } + + scratch_end(tag_scratch); + if(off == start_off) + { + break; + } + } + + // rjf: push this tree hash + D2R_TreeHashNode *n = push_array(dedup_root_scratch.arena, D2R_TreeHashNode, 1); + SLLQueuePush(first_tree_hash, last_tree_hash, n); + n->v.info_off = start_off; + n->v.hash = tree_hash; + tree_hash_count += 1; + + // rjf: pop this task off the queue + Task *popped = first_task; + first_task = first_task->next; + SLLStackPush(free_task, popped); + } + } + + //- rjf: flatten & sort the hash pieces deterministically + D2R_TreeHash *tree_hashes_sorted = 0; + { + D2R_TreeHash *unsorted = push_array(scratch.arena, D2R_TreeHash, tree_hash_count); + U64 idx = 0; + for EachNode(n, D2R_TreeHashNode, first_tree_hash) + { + unsorted[idx] = n->v; + idx += 1; + } + radsort(unsorted, tree_hash_count, d2r_tree_hash_is_less_than); + tree_hashes_sorted = unsorted; + } + + //- rjf: form combined hash of all pieces in deterministic order + U64 combined_hash = 0; + for EachIndex(tree_hash_idx, tree_hash_count) + { + combined_hash = u64_hash_from_seed_str8(combined_hash, str8_struct(&tree_hashes_sorted[tree_hash_idx].hash)); + } + + //- rjf: walk the sorted hash pieces - for each piece, it's full hash is + // the combined hash, + an identifier of which node it came from. + for EachIndex(tree_hash_idx, tree_hash_count) + { + U64 tree_and_ref_hash = u64_hash_from_seed_str8(combined_hash, str8_struct(&tree_hash_idx)); + // TODO(rjf): cache mapping of `tree_hashes_sorted[tree_hash_idx].info_off -> tree_and_ref_hash` + } +#endif + //- rjf: compute tree hash U64 hash = 0; D2R_UniqueTagKind unique_tag_kind = D2R_UniqueTagKind_Type; - U64 container_ancestor_info_off = 0; U64 dependency_count = 0; + U64 container_ancestor_info_off = 0; { typedef struct TagTask TagTask; struct TagTask @@ -1731,11 +1836,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) // rjf: unpack type reference in this tag DW2_Attrib *type_attrib = &dw2_attrib_nil; U64 type_info_off = 0; - if(include_tag_in_hash && - tag.kind != DW_TagKind_PointerType && - tag.kind != DW_TagKind_ReferenceType && - tag.kind != DW_TagKind_RValueReferenceType && - tag.kind != DW_TagKind_PtrToMemberType) + if(include_tag_in_hash) { type_attrib = dw2_attrib_from_kind(&tag, DW_AttribKind_Type); type_info_off = dw2_reference_info_off_from_form_val(unit_parse_ctx, &type_attrib->val); @@ -2427,6 +2528,10 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) dst_type->kind = rdi_type_kind; dst_type->direct_type = direct_type; dst_type->byte_size = arch_addr_size; + if(direct_type == 0) + { + int x = 0; + } }break; case DW_TagKind_RestrictType: { diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.h b/src/rdi_from_dwarf/rdi_from_dwarf.h index 83e0adb2..8ffa7469 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.h +++ b/src/rdi_from_dwarf/rdi_from_dwarf.h @@ -19,6 +19,20 @@ struct D2R_TagHashNode //////////////////////////////// //~ rjf: Unique Tag Tree Deduplication Types +typedef struct D2R_TreeHash D2R_TreeHash; +struct D2R_TreeHash +{ + U64 info_off; + U64 hash; +}; + +typedef struct D2R_TreeHashNode D2R_TreeHashNode; +struct D2R_TreeHashNode +{ + D2R_TreeHashNode *next; + D2R_TreeHash v; +}; + typedef enum D2R_UniqueTagKind { D2R_UniqueTagKind_Type, @@ -75,6 +89,7 @@ struct D2R_ConvertParams //////////////////////////////// //~ rjf: Helpers +internal int d2r_tree_hash_is_less_than(D2R_TreeHash *a, D2R_TreeHash *b); internal int d2r_unique_tag_node_is_less_than(D2R_UniqueTagNode **l, D2R_UniqueTagNode **r); //////////////////////////////// From 604a244473e659d4283c7ec064afae696d3dab95 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 13 Aug 2026 11:05:36 -0700 Subject: [PATCH 042/251] rdi_from_dwarf: turn off aggressive type hash caching for now - need to solve cyclical graph hashing problem later when it is more pressing... also fix local-UDTs not being gathered/converted --- src/rdi_from_dwarf/rdi_from_dwarf.c | 158 +++------------------------- 1 file changed, 16 insertions(+), 142 deletions(-) diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index cd7be489..a17a535a 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -1469,7 +1469,8 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) if(tag.kind == DW_TagKind_Namespace || tag.kind == DW_TagKind_ClassType || tag.kind == DW_TagKind_StructureType || - tag.kind == DW_TagKind_UnionType) + tag.kind == DW_TagKind_UnionType || + tag.kind == DW_TagKind_SubProgram) { children_should_be_deduped = 1; } @@ -1569,139 +1570,6 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) //- rjf: unpack tag node U64 start_off = tag_n->info_off; - //- rjf: gather the set of tree hashes in the type graph pointed into by `tag_n` -#if 0 - D2R_TreeHashNode *first_tree_hash = 0; - D2R_TreeHashNode *last_tree_hash = 0; - U64 tree_hash_count = 0; - { - typedef struct Task Task; - struct Task - { - Task *next; - U64 unit_idx; - U64 start_off; - }; - Task start_task = {0, origin_unit_idx, start_off}; - Task *first_task = &start_task; - Task *last_task = 0; - Task *free_task = 0; - for(;first_task != 0;) - { - // rjf: unpack this task's unit info - U64 unit_idx = first_task->unit_idx; - Rng1U64 unit_info_tag_range = unit_info_tag_ranges[unit_idx]; - DW2_ParseCtx *unit_parse_ctx = &unit_parse_ctxs[unit_idx]; - - // rjf: combine all tag hashes in this tag tree - S64 depth = 0; - U64 tree_hash = 0; - for(U64 off = first_task->start_off; contains_1u64(unit_info_tag_range, off) && (depth > 0 || off == first_task->start_off);) - { - U64 start_off = off; - Temp tag_scratch = scratch_begin(&dedup_root_scratch.arena, 1); - - // rjf: look up this tag hash - U64 tag_hash = 0; - { - U64 off_hash = u64_hash_from_str8(str8_struct(&start_off)); - U64 slot_idx = off_hash%tag_hash_slots_count; - for(D2R_TagHashNode *n = tag_hash_slots[slot_idx]; n != 0; n = n->next) - { - if(n->info_off == start_off) - { - tag_hash = n->hash; - break; - } - } - } - - // rjf: combine this tag hash into the tree hash - tree_hash = u64_hash_from_seed_str8(tree_hash, str8_struct(&tag_hash)); - - // rjf: read the next tag - DW2_Tag tag = {0}; - off += dw2_read_tag(tag_scratch.arena, raw, unit_parse_ctx, raw->sec[DW_SectionKind_Info].data, off, &tag); - - // rjf: find type reference in this tag, use to generate additional tasks - DW2_Attrib *type_attrib = dw2_attrib_from_kind(&tag, DW_AttribKind_Type); - if(type_attrib != &dw2_attrib_nil) - { - U64 type_ref_info_off = dw2_reference_info_off_from_form_val(unit_parse_ctx, &type_attrib->val); - U64 type_ref_unit_idx = unit_idx; - if(!contains_1u64(unit_info_tag_range, type_ref_info_off)) - { - U64 new_unit_num = rng1u64_array_num_from_value__binary_search(&unit_info_tag_ranges_array, type_ref_info_off); - if(0 < new_unit_num && new_unit_num <= unit_count) - { - type_ref_unit_idx = new_unit_num-1; - } - } - Task *t = free_task; - if(t) - { - SLLStackPop(free_task); - } - else - { - t = push_array(dedup_root_scratch.arena, Task, 1); - } - SLLQueuePush(first_task, last_task, t); - t->unit_idx = type_ref_unit_idx; - t->start_off = type_ref_info_off; - } - - scratch_end(tag_scratch); - if(off == start_off) - { - break; - } - } - - // rjf: push this tree hash - D2R_TreeHashNode *n = push_array(dedup_root_scratch.arena, D2R_TreeHashNode, 1); - SLLQueuePush(first_tree_hash, last_tree_hash, n); - n->v.info_off = start_off; - n->v.hash = tree_hash; - tree_hash_count += 1; - - // rjf: pop this task off the queue - Task *popped = first_task; - first_task = first_task->next; - SLLStackPush(free_task, popped); - } - } - - //- rjf: flatten & sort the hash pieces deterministically - D2R_TreeHash *tree_hashes_sorted = 0; - { - D2R_TreeHash *unsorted = push_array(scratch.arena, D2R_TreeHash, tree_hash_count); - U64 idx = 0; - for EachNode(n, D2R_TreeHashNode, first_tree_hash) - { - unsorted[idx] = n->v; - idx += 1; - } - radsort(unsorted, tree_hash_count, d2r_tree_hash_is_less_than); - tree_hashes_sorted = unsorted; - } - - //- rjf: form combined hash of all pieces in deterministic order - U64 combined_hash = 0; - for EachIndex(tree_hash_idx, tree_hash_count) - { - combined_hash = u64_hash_from_seed_str8(combined_hash, str8_struct(&tree_hashes_sorted[tree_hash_idx].hash)); - } - - //- rjf: walk the sorted hash pieces - for each piece, it's full hash is - // the combined hash, + an identifier of which node it came from. - for EachIndex(tree_hash_idx, tree_hash_count) - { - U64 tree_and_ref_hash = u64_hash_from_seed_str8(combined_hash, str8_struct(&tree_hash_idx)); - // TODO(rjf): cache mapping of `tree_hashes_sorted[tree_hash_idx].info_off -> tree_and_ref_hash` - } -#endif - //- rjf: compute tree hash U64 hash = 0; D2R_UniqueTagKind unique_tag_kind = D2R_UniqueTagKind_Type; @@ -1720,6 +1588,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) U64 off; S64 depth; U64 hash; + B32 is_member; }; TagTask start_task = {0, DW_TagKind_Null, 0, 0, origin_unit_idx, start_off, start_off, 0}; TagTask *top_task = &start_task; @@ -1730,6 +1599,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) U64 already_computed_hash = 0; U64 already_computed_dependency_count = 0; B32 found_already_computed_hash = 0; +#if 0 { U64 off_hash = u64_hash_from_str8(str8_struct(&top_task->start_off)); U64 off_slot_idx = off_hash%unit_deduped_tag_maps[top_task->unit_idx].slots_count; @@ -1745,6 +1615,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) } } } +#endif //- rjf: if we found an already-computed hash, just write it into this task as its result if(found_already_computed_hash) @@ -1861,7 +1732,10 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) // rjf: if this tag has a non-recursive type reference -> push a new task to hash the type. if(!type_ref_is_recursive && type_info_off != 0) { - top_task->dependency_count += 1; + if(tag.kind != DW_TagKind_Member) + { + top_task->dependency_count += 1; + } U64 unit_idx = top_task->unit_idx; if(!contains_1u64(unit_info_tag_range, type_info_off)) { @@ -1881,16 +1755,17 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) task = push_array_no_zero(dedup_root_scratch.arena, TagTask, 1); } MemoryZeroStruct(task); - SLLStackPush(top_task, task); task->unit_idx = unit_idx; task->start_off = task->off = type_info_off; + task->is_member = (tag.kind == DW_TagKind_Member); + SLLStackPush(top_task, task); } scratch_end(tag_scratch); } //- rjf: if we're done, and if we computed the hash on this lane -> record in the per-unit (offset -> hash) table - if(done && !found_already_computed_hash) + if(done && !found_already_computed_hash && top_task == &start_task) { U64 info_off_hash = u64_hash_from_str8(str8_struct(&top_task->start_off)); U64 info_off_slot_idx = info_off_hash%unit_deduped_tag_maps[top_task->unit_idx].slots_count; @@ -1918,7 +1793,10 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) if(top_task != 0) { top_task->hash = u64_hash_from_seed_str8(top_task->hash, str8_struct(&popped->hash)); - top_task->dependency_count += popped->dependency_count; + if(!popped->is_member) + { + top_task->dependency_count += popped->dependency_count; + } } } } @@ -2528,10 +2406,6 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) dst_type->kind = rdi_type_kind; dst_type->direct_type = direct_type; dst_type->byte_size = arch_addr_size; - if(direct_type == 0) - { - int x = 0; - } }break; case DW_TagKind_RestrictType: { From 08d25fa39d59de395a1c9d9dd18060fc082617be Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 13 Aug 2026 12:28:06 -0700 Subject: [PATCH 043/251] linux_demon: fix incorrect arena buckets for probe parsing, fix zero process being incorrectly used (snap process to thread process, if wait_id does not refer to any process itself; text: fix treating R"'s as starting a herestring, if ending a non-herestring --- src/linux/demon/linux_demon.c | 26 +++++++++++++++++--------- src/text/text.c | 6 +++++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 133656bf..3633f869 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1615,6 +1615,10 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // LNX_DMN_Process *process = lnx_dmn_process_from_pid(wait_id); LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); + if(process == 0 && thread != 0) + { + process = thread->process; + } int wifexited = WIFEXITED(status); int wifsignaled = WIFSIGNALED(status); int wifstopped = WIFSTOPPED(status); @@ -1640,6 +1644,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) B32 thread_done_report_events = 0; U64 thread_done_exit_code = 0; B32 thread_new = 0; + pid_t thread_new_pid = 0; LNX_DMN_ThreadState thread_new_state = LNX_DMN_ThreadState_Null; B32 thread_new_report_events = 0; LNX_DMN_Process *thread_new_parent = process; @@ -1718,7 +1723,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) B32 thread_running = (thread && thread->state == LNX_DMN_ThreadState_Running); // rjf: update thread running state - if(thread) + if(thread_running) { if(wifstopped && !wifsignaled && !wifexited) { @@ -2105,6 +2110,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid)) >= 0) { thread_new = 1; + thread_new_pid = new_tid; thread_new_report_events = 0; thread_new_state = LNX_DMN_ThreadState_PendingCreation; thread_new_parent = thread->process; @@ -2697,7 +2703,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) Arena *ctx_arena = arena_alloc(); LNX_DMN_Probe **known_probes = push_array(ctx_arena, LNX_DMN_Probe *, LNX_DMN_ProbeKind_COUNT); { - Temp scratch = scratch_begin(0, 0); + Temp scratch = scratch_begin(&arena, 1); // rjf: get dl path for this pid String8 dl_path = {0}; @@ -2771,7 +2777,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(lo_vaddr == auxv.base) { dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; - dl_path = push_str8_copy(arena, dl_path); + dl_path = push_str8_copy(scratch.arena, dl_path); break; } } @@ -2859,7 +2865,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) U64 probes_base = stapsdt_base_shdr.sh_addr; Rng1U64 note_range = shift_1u64(r1u64(stapsdt_shdr.sh_offset, stapsdt_shdr.sh_offset + stapsdt_shdr.sh_size), offset); - void *raw_note = push_array(arena, U8, stapsdt_shdr.sh_size); + void *raw_note = push_array(ctx_arena, U8, stapsdt_shdr.sh_size); U64 note_read_size = lnx_dmn_read(dl_fd, note_range, raw_note); if(note_read_size != dim_1u64(note_range)) { goto exit; } @@ -2908,12 +2914,12 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) probe.provider = provider; probe.name = name; - probe.args = stap_arg_array_from_string(arena, arch, args); + probe.args = stap_arg_array_from_string(ctx_arena, arch, args); probe.pc = pc + probe_rebase; probe.semaphore = semaphore ? semaphore + probe_rebase : 0; } - LNX_DMN_ProbeNode *n = push_array(arena, LNX_DMN_ProbeNode, 1); + LNX_DMN_ProbeNode *n = push_array(ctx_arena, LNX_DMN_ProbeNode, 1); n->v = probe; SLLQueuePush(probes.first, probes.last, n); probes.count += 1; @@ -3010,6 +3016,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) thread_new_state = LNX_DMN_ThreadState_Stopped; thread_new_parent = new_process; thread_new_report_events = 1; + thread_new_pid = wait_id; } } @@ -3038,7 +3045,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // rjf: allocate thread new_thread = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Thread)->thread; - new_thread->tid = wait_id; + new_thread->tid = thread_new_pid; new_thread->state = thread_new_state; new_thread->process = thread_new_parent; new_thread->reg_block = reg_block; @@ -3049,7 +3056,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // rjf: map id -> thread { - U64 key = (U64)wait_id; + U64 key = (U64)thread_new_pid; U64 hash = u64_hash_from_str8(str8_struct(&key)); U64 slot_idx = hash%lnx_dmn_state->thread_from_tid_slots_count; DLLPushBack_NP(lnx_dmn_state->thread_from_tid_slots[slot_idx].first, lnx_dmn_state->thread_from_tid_slots[slot_idx].last, new_thread, tid_next, tid_prev); @@ -3188,7 +3195,8 @@ dmn_halt(U64 code, U64 user_data) lnx_dmn_state->halt_user_data = user_data; for EachNode(process, LNX_DMN_Process, lnx_dmn_state->first_process) { - if(LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)) < 0) { Assert(0 && "failed to send SIGSTOP"); } + int kill_result = LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)); + int x = 0; } } } diff --git a/src/text/text.c b/src/text/text.c index f7f7ff3f..dd5a0cb0 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -3936,7 +3936,11 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 default:{}break; case TXT_TokenKind_String: { - if(herestring_marker.size == 0 && byte == '"') + if(byte == 'R' && next_byte == '"') + { + active_token_end_off = off+2; + } + else if(herestring_marker.size == 0 && byte == '"') { active_token_end_off = off+1; } From 0c2f79d2f93ff28fd491cd4ccfe34e3f4b1038cc Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 21 Jul 2026 11:27:08 -0700 Subject: [PATCH 044/251] update natvis --- src/linker/linker.natvis | 27 +++++++++++++++++++++++++++ src/natvis/base.natvis | 11 +++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/linker/linker.natvis b/src/linker/linker.natvis index 84296c9c..0cc2d7f8 100644 --- a/src/linker/linker.natvis +++ b/src/linker/linker.natvis @@ -240,5 +240,32 @@ + + {{ count={count} tombstones={tombstone_count} }} + + count + tombstone_count + root + + + + + live + tombstone + + v.key + v.value + + radix children + + children[0] + children[1] + children[2] + children[3] + + + + + diff --git a/src/natvis/base.natvis b/src/natvis/base.natvis index 8009134e..3e854654 100644 --- a/src/natvis/base.natvis +++ b/src/natvis/base.natvis @@ -18,18 +18,18 @@ node_count first next - this + string - - {{ count={count} pointer={strings} }} + + {{ count={count} pointer={v} }} count count - strings + v @@ -142,7 +142,7 @@ - {{ name={string} hash={hash} value_string={value_string} value_stirngs={value_strings} }} + {{ name={string} hash={hash} value_string={value_string} value_strings={value_strings} }} @@ -211,7 +211,6 @@ kind next - next parent {first_child} From 6a34ee168f79e547de13d6cec5df00e0ad8996d6 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 22 Jul 2026 00:08:20 -0700 Subject: [PATCH 045/251] update /OPT:ICF * pass over fold stats diagnostic * implement /RAD_ICF_HASH_KIND * rename color space -> scope --- src/linker/lnk.c | 316 ++++++++++++++++++++---------------- src/linker/lnk_config.c | 11 ++ src/linker/lnk_config.h | 2 + src/linker/lnk_debug_info.c | 2 +- src/linker/lnk_hasher.c | 27 ++- src/linker/lnk_hasher.h | 25 +-- 6 files changed, 224 insertions(+), 159 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index be253f18..4b72899f 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -185,6 +185,12 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) // By default keep full type names, override when TPI/IPI streams overflow. "/RAD_PDB_HASH_TYPE_NAMES:NONE", + + // TODO: The ICF algorithm requires a cryptographic hash to establish + // equivalence. With xxHash and similar non-cryptographic hashes, + // the algorithm must compare each section property before + // deciding whether sections are truly identical. + "/RAD_ICF_HASH_KIND:BLAKE3", }; char *push_opts[] = { @@ -3003,35 +3009,25 @@ lnk_opt_ref(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj ProfEnd(); } -typedef enum LNK_ICF_ColorSpace -{ - LNK_ICF_ColorSpace_Null, - LNK_ICF_ColorSpace_Code, - LNK_ICF_ColorSpace_Unwind, - LNK_ICF_ColorSpace_VFTable, - LNK_ICF_ColorSpace_ConstData, // string literals, float consts, const tables (/Gw and /GF) - LNK_ICF_ColorSpace_COUNT, -} LNK_ICF_ColorSpace; +#define LNK_ICF_Scope_XList \ + X(Null) \ + X(Code) \ + X(Unwind) \ + X(VFTable) \ + X(ConstData) // string literals, float consts, const tables (/Gw and /GF) -internal String8 -lnk_string_from_icf_color_space(LNK_ICF_ColorSpace color_space) +typedef enum LNK_ICF_Scope { - String8 result = str8_lit("Unknown"); - switch (color_space) { - case LNK_ICF_ColorSpace_Null: { result = str8_lit("Null"); } break; - case LNK_ICF_ColorSpace_Code: { result = str8_lit("Code"); } break; - case LNK_ICF_ColorSpace_Unwind: { result = str8_lit("Unwind"); } break; - case LNK_ICF_ColorSpace_VFTable: { result = str8_lit("VFTables"); } break; - case LNK_ICF_ColorSpace_ConstData: { result = str8_lit("ConstData"); } break; - case LNK_ICF_ColorSpace_COUNT: { result = str8_lit("Unknown"); } break; - } - return result; -} +#define X(ID) LNK_ICF_Scope_##ID, + LNK_ICF_Scope_XList + LNK_ICF_Scope_COUNT +#undef X +} LNK_ICF_Scope; -internal LNK_ICF_ColorSpace -lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) +internal LNK_ICF_Scope +lnk_icf_scope_from_section(LNK_Obj *obj, U32 sect_idx) { - LNK_ICF_ColorSpace result = LNK_ICF_ColorSpace_Null; + LNK_ICF_Scope result = LNK_ICF_Scope_Null; // * section flags filter * COFF_SectionFlags expected_flags = COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_MemRead; @@ -3041,7 +3037,7 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) } if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntCode) { - result = LNK_ICF_ColorSpace_Code; + result = LNK_ICF_Scope_Code; goto exit; } @@ -3054,7 +3050,7 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) // * include unwind info metadata * // if (str8_match(section_name, str8_lit(".xdata"), 0) || str8_match(section_name, str8_lit(".pdata"), 0)) { - result = LNK_ICF_ColorSpace_Unwind; + result = LNK_ICF_Scope_Unwind; goto exit; } @@ -3070,7 +3066,7 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) // * include virtual function tables * // if (str8_starts_with(comdat_name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))) { - result = LNK_ICF_ColorSpace_VFTable; + result = LNK_ICF_Scope_VFTable; goto exit; } @@ -3092,7 +3088,7 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) select == COFF_ComdatSelect_SameSize || select == COFF_ComdatSelect_ExactMatch || select == COFF_ComdatSelect_Largest) { - result = LNK_ICF_ColorSpace_ConstData; + result = LNK_ICF_Scope_ConstData; goto exit; } } @@ -3100,6 +3096,7 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) } exit:; + Assert(result == LNK_ICF_Scope_Null || obj->comdats[sect_idx] != max_U32); // TODO: are symlinks on COMDATs optional? return result; } @@ -3143,7 +3140,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 color_slot_idx; U32 obj_idx; U32 sect_idx; - LNK_ICF_ColorSpace color_space; + LNK_ICF_Scope scope; } Contrib; // reuse both tables without clearing them between refinement rounds @@ -3168,8 +3165,9 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) Temp scratch = scratch_begin(&arena,1); Temp scratch2 = scratch_begin(&scratch.arena,1); // retain relocation metadata through temporary associated-section traversals - LNK_OptTask *task = raw_task; - LNK_Obj **objs = task->objs; + LNK_OptTask *task = raw_task; + LNK_Obj **objs = task->objs; + LNK_HashKind icf_hash_kind = task->config->icf_hash_kind; if (task->config->llvm_addrsig == LNK_SwitchState_Yes) { ProfBegin("Flag significant sections"); @@ -3235,7 +3233,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 obj_idx = task->obj_indices[task_id].v[i]; LNK_Obj *obj = objs[obj_idx]; for EachIndex(sect_idx, obj->header.section_count_no_null) { - if (lnk_icf_color_space_from_section(obj, sect_idx)) { + if (lnk_icf_scope_from_section(obj, sect_idx)) { contrib_counts[obj_idx] += 1; } } @@ -3243,54 +3241,50 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) ProfEnd(); barrier_wait(tp->barrier); - U64 contrib_count = sum_array_u64(task->objs_count, contrib_counts); - U64 *noncontrib_offsets = 0; - U64 *contrib_offsets = 0; - U64 **color_map = 0; - Contrib *contribs = 0; - Rng1U64 *contrib_ranges = 0; - U64 *split_counts = 0; - U64 *split_offsets = 0; - U32 *is_part_stable = 0; - U64 *next_color = 0; + struct Shared { + U64 contrib_count; + U64 *noncontrib_offsets; + U64 *contrib_offsets; + U64 **color_map; + Contrib *contribs; + Rng1U64 *contrib_ranges; + U64 *split_counts; + U64 *split_offsets; + U32 *is_part_stable; + U64 *next_color; + } shared = { + .contrib_count = sum_array_u64(task->objs_count, contrib_counts) + }; if (task_id == 0) { ProfBegin("Init"); - noncontrib_offsets = push_array(scratch.arena, U64, task->objs_count); + shared.noncontrib_offsets = push_array(scratch.arena, U64, task->objs_count); U64 noncontrib_count = 0; for EachIndex(obj_idx, task->objs_count) { - noncontrib_offsets[obj_idx] = noncontrib_count; + shared.noncontrib_offsets[obj_idx] = noncontrib_count; noncontrib_count += objs[obj_idx]->header.section_count_no_null - contrib_counts[obj_idx]; } - color_map = push_array(scratch.arena, U64 *, task->objs_count); + shared.color_map = push_array(scratch.arena, U64 *, task->objs_count); for EachIndex(obj_idx, task->objs_count) { LNK_Obj *obj = objs[obj_idx]; - color_map[obj_idx] = push_array(scratch.arena, U64, obj->header.section_count_no_null); + shared.color_map[obj_idx] = push_array(scratch.arena, U64, obj->header.section_count_no_null); } - contrib_offsets = offsets_from_counts_array_u64(scratch.arena, contrib_counts, task->objs_count); - contribs = push_array(scratch.arena, Contrib, contrib_count); - contrib_ranges = tp_divide_work(scratch.arena, contrib_count, tp->worker_count); - split_counts = push_array(scratch.arena, U64, tp->worker_count); - split_offsets = push_array(scratch.arena, U64, tp->worker_count + 1); - is_part_stable = push_array(scratch.arena, U32, 1); - next_color = push_array(scratch.arena, U64, 1); - *next_color = LNK_ICF_ColorSpace_COUNT + noncontrib_count; + shared.contrib_offsets = offsets_from_counts_array_u64(scratch.arena, contrib_counts, task->objs_count); + shared.contribs = push_array(scratch.arena, Contrib, shared.contrib_count); + shared.contrib_ranges = tp_divide_work(scratch.arena, shared.contrib_count, tp->worker_count); + shared.split_counts = push_array(scratch.arena, U64, tp->worker_count); + shared.split_offsets = push_array(scratch.arena, U64, tp->worker_count + 1); + shared.is_part_stable = push_array(scratch.arena, U32, 1); + shared.next_color = push_array(scratch.arena, U64, 1); + *shared.next_color = LNK_ICF_Scope_COUNT + noncontrib_count; - lnk_log(LNK_Log_Debug, " Contrib count: %S", str8_from_count(scratch.arena, contrib_count)); + lnk_log(LNK_Log_Debug, " Contrib count: %S", str8_from_count(scratch.arena, shared.contrib_count)); ProfEnd(); } - tp_broadcast(&contrib_offsets); - tp_broadcast(&noncontrib_offsets); - tp_broadcast(&color_map); - tp_broadcast(&contribs); - tp_broadcast(&contrib_ranges); - tp_broadcast(&split_counts); - tp_broadcast(&split_offsets); - tp_broadcast(&is_part_stable); - tp_broadcast(&next_color); + tp_broadcast(&shared); ProfBegin("Compute Hashes"); HashMap reloc_target_hm = {0}; // cache source-symbol resolution so refinement only reads colors and hashes @@ -3300,20 +3294,20 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 cursor = 0; U64 noncontrib_cursor = 0; for EachIndex(sect_idx, obj->header.section_count_no_null) { - LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(obj, sect_idx); - if (color_space == LNK_ICF_ColorSpace_Null) { + LNK_ICF_Scope scope = lnk_icf_scope_from_section(obj, sect_idx); + if (scope == LNK_ICF_Scope_Null) { // assign colors in object order to avoid a contended atomic allocator - color_map[obj_idx][sect_idx] = LNK_ICF_ColorSpace_COUNT + noncontrib_offsets[obj_idx] + noncontrib_cursor++; + shared.color_map[obj_idx][sect_idx] = LNK_ICF_Scope_COUNT + shared.noncontrib_offsets[obj_idx] + noncontrib_cursor++; continue; } // compute contribution index - U64 contrib_idx = contrib_offsets[obj_idx] + cursor++; - Contrib *contrib = &contribs[contrib_idx]; + U64 contrib_idx = shared.contrib_offsets[obj_idx] + cursor++; + Contrib *contrib = &shared.contribs[contrib_idx]; *contrib = (Contrib){ .obj_idx = safe_cast_u32(obj->input_idx), .sect_idx = safe_cast_u32(sect_idx), - .color_space = color_space, + .scope = scope, }; Temp temp = temp_begin(scratch.arena); @@ -3331,8 +3325,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) contrib->reloc_targets = push_array(scratch2.arena, RelocTarget *, contrib->reloc_count); } - blake3_hasher hasher; blake3_hasher_init(&hasher); - blake3_hasher_update(&hasher, &color_space, sizeof(color_space)); + LNK_Hasher hasher; lnk_hasher_init(&hasher, icf_hash_kind); + lnk_hasher_update_struct(&hasher, &scope); U64 reloc_cursor = 0; for EachNode(associated_n, U32Node, associated_sections.first) { @@ -3340,8 +3334,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) String8 associated_data = str8_substr(obj->data, r1u64s(associated_header->foff, associated_header->fsize)); COFF_RelocArray associated_relocs = lnk_coff_relocs_from_section_header(obj, associated_header); - blake3_hasher_update(&hasher, associated_data.str, associated_data.size); - blake3_hasher_update(&hasher, &associated_relocs.count, sizeof(associated_relocs.count)); + lnk_hasher_update_string(&hasher, associated_data); + lnk_hasher_update_struct(&hasher, &associated_relocs.count); for EachIndex(reloc_idx, associated_relocs.count) { COFF_Reloc *r = &associated_relocs.v[reloc_idx]; @@ -3377,7 +3371,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) } } - target->color = &color_map[target_obj->input_idx][target_sect - 1]; + target->color = &shared.color_map[target_obj->input_idx][target_sect - 1]; } break; default: { target->static_id = Compose64Bit(target_ref.obj->input_idx, target_ref.symbol_idx); @@ -3392,17 +3386,17 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) } contrib->reloc_targets[reloc_cursor++] = target; - blake3_hasher_update(&hasher, &r->apply_off, sizeof(r->apply_off)); - blake3_hasher_update(&hasher, &r->type, sizeof(r->type)); - blake3_hasher_update(&hasher, &target->interp, sizeof(target->interp)); - blake3_hasher_update(&hasher, &target->value, sizeof(target->value)); + lnk_hasher_update_struct(&hasher, &r->apply_off); + lnk_hasher_update_struct(&hasher, &r->type); + lnk_hasher_update_struct(&hasher, &target->interp); + lnk_hasher_update_struct(&hasher, &target->value); } } Assert(reloc_cursor == contrib->reloc_count); - blake3_hasher_finalize(&hasher, (U8 *)&contrib->static_hash, sizeof(contrib->static_hash)); + contrib->static_hash = lnk_hasher_digest128(&hasher); // seed foldable sections with their immutable content and relocation shape - color_map[obj_idx][sect_idx] = hash_map_hasher(str8_struct(&contrib->static_hash)) | (1ull << 63); + shared.color_map[obj_idx][sect_idx] = hash_map_hasher(str8_struct(&contrib->static_hash)) | (1ull << 63); temp_end(temp); } } @@ -3414,7 +3408,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 *table_generation = 0; if (task_id == 0) { ProfBegin("Alloc hash tables"); - color_table.slots_count = u64_up_to_pow2(Max(2, contrib_count*2)); + color_table.slots_count = u64_up_to_pow2(Max(2, shared.contrib_count*2)); color_table.slots = push_array(scratch.arena, ColorHashSlot, color_table.slots_count); old_color_table.slots_count = color_table.slots_count; old_color_table.slots = push_array(scratch.arena, OldColorHashSlot, old_color_table.slots_count); @@ -3438,7 +3432,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) if (task_id == 0) { // reset color status tracker - *is_part_stable = 1; + *shared.is_part_stable = 1; // update hash tables generations Assert(*table_generation < (max_U64 >> 2)); @@ -3452,19 +3446,19 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 ready_state = (table_generation_value << 2) | 2; ProfBegin("Compute colored hashes"); - for EachInRange(contrib_idx, contrib_ranges[task_id]) { - Contrib *contrib = &contribs[contrib_idx]; - contrib->key.old_color = color_map[contrib->obj_idx][contrib->sect_idx]; + for EachInRange(contrib_idx, shared.contrib_ranges[task_id]) { + Contrib *contrib = &shared.contribs[contrib_idx]; + contrib->key.old_color = shared.color_map[contrib->obj_idx][contrib->sect_idx]; - blake3_hasher hasher; blake3_hasher_init(&hasher); - blake3_hasher_update(&hasher, &contrib->static_hash, sizeof(contrib->static_hash)); + // apply colors from section references + LNK_Hasher hasher; lnk_hasher_init(&hasher, icf_hash_kind); + lnk_hasher_update_struct(&hasher, &contrib->static_hash); for EachIndex(reloc_idx, contrib->reloc_count) { RelocTarget *target = contrib->reloc_targets[reloc_idx]; U64 target_id = target->color ? *target->color : target->static_id; - blake3_hasher_update(&hasher, &target_id, sizeof(target_id)); + lnk_hasher_update_struct(&hasher, &target_id); } - U128 hash; - blake3_hasher_finalize(&hasher, (U8 *)&hash, sizeof(hash)); + U128 hash = lnk_hasher_digest128(&hasher); // insert the colored hash into the concurrent table contrib->key.hash = hash; @@ -3508,10 +3502,10 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // publish one old-color record per color group ProfBegin("Index color groups"); - Rng1U64 contrib_range = contrib_ranges[task_id]; + Rng1U64 contrib_range = shared.contrib_ranges[task_id]; for EachInRange(contrib_idx, contrib_range) { - Contrib *contrib = &contribs[contrib_idx]; - ColorHashSlot *slot = &color_table.slots[contrib->color_slot_idx]; + Contrib *contrib = &shared.contribs[contrib_idx]; + ColorHashSlot *slot = &color_table.slots[contrib->color_slot_idx]; if (ins_atomic_u64_eval(&slot->first_contrib_idx) == contrib_idx) { Assert(old_color_table.slots_count > 0 && (old_color_table.slots_count & (old_color_table.slots_count - 1)) == 0); U64 old_color = slot->key.old_color; @@ -3555,16 +3549,16 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) ProfBegin("Count color splits"); U64 split_count = 0; for EachInRange(contrib_idx, contrib_range) { - Contrib *contrib = &contribs[contrib_idx]; - ColorHashSlot *color_slot = &color_table.slots[contrib->color_slot_idx]; + Contrib *contrib = &shared.contribs[contrib_idx]; + ColorHashSlot *color_slot = &color_table.slots[contrib->color_slot_idx]; if (ins_atomic_u64_eval(&color_slot->first_contrib_idx) != contrib_idx) { continue; } - OldColorHashSlot *old_color_slot = &old_color_table.slots[color_slot->old_color_slot_idx]; + OldColorHashSlot *old_color_slot = &old_color_table.slots[color_slot->old_color_slot_idx]; if (ins_atomic_u64_eval(&old_color_slot->first_contrib_idx) != contrib_idx) { split_count += 1; } } - split_counts[task_id] = split_count; + shared.split_counts[task_id] = split_count; ProfEnd(); barrier_wait(tp->barrier); @@ -3572,16 +3566,16 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) if (task_id == 0) { ProfBegin("Prefix color splits"); - U64 start_next_color = *next_color; + U64 start_next_color = *shared.next_color; U64 total_split_count = 0; for EachIndex(worker_id, tp->worker_count) { - split_offsets[worker_id] = total_split_count; - total_split_count += split_counts[worker_id]; + shared.split_offsets[worker_id] = total_split_count; + total_split_count += shared.split_counts[worker_id]; } - split_offsets[tp->worker_count] = start_next_color; + shared.split_offsets[tp->worker_count] = start_next_color; - *next_color += total_split_count; - *is_part_stable = (total_split_count == 0); + *shared.next_color += total_split_count; + *shared.is_part_stable = (total_split_count == 0); lnk_log(LNK_Log_Debug, " Round %llu found %S splits", iter_count, str8_from_count(scratch.arena, total_split_count)); @@ -3591,13 +3585,13 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // assign old and split colors in deterministic contribution order ProfBegin("Assign colors"); - U64 next_split_color = split_offsets[tp->worker_count] + split_offsets[task_id]; + U64 next_split_color = shared.split_offsets[tp->worker_count] + shared.split_offsets[task_id]; for EachInRange(contrib_idx, contrib_range) { - Contrib *contrib = &contribs[contrib_idx]; - ColorHashSlot *color_slot = &color_table.slots[contrib->color_slot_idx]; + Contrib *contrib = &shared.contribs[contrib_idx]; + ColorHashSlot *color_slot = &color_table.slots[contrib->color_slot_idx]; if (ins_atomic_u64_eval(&color_slot->first_contrib_idx) != contrib_idx) { continue; } - OldColorHashSlot *old_color_slot = &old_color_table.slots[color_slot->old_color_slot_idx]; + OldColorHashSlot *old_color_slot = &old_color_table.slots[color_slot->old_color_slot_idx]; if (ins_atomic_u64_eval(&old_color_slot->first_contrib_idx) == contrib_idx) { color_slot->color = color_slot->key.old_color; } else { @@ -3610,11 +3604,11 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // update colors for this worker's contributions ProfBegin("Update color map"); for EachIndex(i, task->obj_indices[task_id].count) { - U64 obj_idx = task->obj_indices[task_id].v[i]; - Rng1U64 obj_contrib_range = r1u64(contrib_offsets[obj_idx], contrib_offsets[obj_idx] + contrib_counts[obj_idx]); + U64 obj_idx = task->obj_indices[task_id].v[i]; + Rng1U64 obj_contrib_range = r1u64(shared.contrib_offsets[obj_idx], shared.contrib_offsets[obj_idx] + contrib_counts[obj_idx]); for EachInRange(contrib_idx, obj_contrib_range) { - Contrib *contrib = &contribs[contrib_idx]; - color_map[contrib->obj_idx][contrib->sect_idx] = color_table.slots[contrib->color_slot_idx].color; + Contrib *contrib = &shared.contribs[contrib_idx]; + shared.color_map[contrib->obj_idx][contrib->sect_idx] = color_table.slots[contrib->color_slot_idx].color; } } ProfEnd(); @@ -3623,7 +3617,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) ProfEnd(); // round prof // stop iterating when partitions stabilize - if (*is_part_stable) { break; } + if (*shared.is_part_stable) { break; } } barrier_wait(tp->barrier); @@ -3631,33 +3625,42 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // step 3: flag folded sections for removal // - typedef struct { U64 count; U64 size; } FoldStats; + typedef struct { U64 count; U64 size; U64 live_count; U64 live_size; } FoldStats; FoldStats *fold_stats = 0; if (task_id == 0 && lnk_get_log_status(LNK_Log_Debug)) { - fold_stats = push_array(scratch.arena, FoldStats, tp->worker_count * LNK_ICF_ColorSpace_COUNT); + fold_stats = push_array(scratch.arena, FoldStats, tp->worker_count * LNK_ICF_Scope_COUNT); } tp_broadcast(&fold_stats); ProfBegin("Flag Folds"); - FoldStats *local_fold_stats = fold_stats ? fold_stats + (task_id * LNK_ICF_ColorSpace_COUNT) : 0; - for EachInRange(contrib_idx, contrib_ranges[task_id]) { - Contrib *contrib = &contribs[contrib_idx]; + for EachInRange(contrib_idx, shared.contrib_ranges[task_id]) { + Contrib *contrib = &shared.contribs[contrib_idx]; ColorHashSlot *color_slot = &color_table.slots[contrib->color_slot_idx]; - Contrib *leader = &contribs[ins_atomic_u64_eval(&color_slot->first_contrib_idx)]; - if (leader == contrib) { continue; } - + Contrib *leader = &shared.contribs[ins_atomic_u64_eval(&color_slot->first_contrib_idx)]; + LNK_Obj *contrib_obj = objs[contrib->obj_idx]; LNK_Obj *leader_obj = objs[leader->obj_idx]; - if (local_fold_stats) { - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(contrib_obj, contrib->sect_idx + 1); - LNK_ICF_ColorSpace color_space = contrib->color_space; - local_fold_stats[color_space].count += 1; - local_fold_stats[color_space].size += section_header->fsize; + if (fold_stats) { + FoldStats *st = fold_stats + (task_id * LNK_ICF_Scope_COUNT); + U64 fsize = lnk_coff_section_header_from_section_number(contrib_obj, contrib->sect_idx + 1)->fsize; + if (leader == contrib) { + st[leader->scope].live_count += 1; + st[leader->scope].live_size += fsize; + } else { + st[contrib->scope].count += 1; + st[contrib->scope].size += fsize; + } } - U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->section_flags[contrib->sect_idx]); - COFF_SectionFlags *leader_flags = &leader_obj->section_flags[leader->sect_idx]; + if (leader == contrib) { + continue; + } + + // TODO: double-check if discarded section headers should be updated with leaders' alignment, + // leaders at this point are resolved + U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->section_flags[contrib->sect_idx]); + COFF_SectionFlags *leader_flags = &leader_obj->section_flags[leader->sect_idx]; for (COFF_SectionFlags old_flags = ins_atomic_u32_eval((U32 *)leader_flags);;) { U64 leader_align = coff_align_size_from_section_flags(old_flags); if (leader_align >= contrib_align) { break; } @@ -3665,15 +3668,18 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) COFF_SectionFlags new_flags = old_flags; new_flags &= ~(COFF_SectionFlag_AlignMask << COFF_SectionFlag_AlignShift); new_flags |= coff_section_flag_from_align_size(contrib_align); + COFF_SectionFlags observed = ins_atomic_u32_eval_cond_assign((U32 *)leader_flags, new_flags, old_flags); if (observed == old_flags) { break; } old_flags = observed; } - Assert(leader_obj->comdats[leader->sect_idx] != max_U32); - contrib_obj->symlinks[contrib->sect_idx + 1] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->comdats[leader->sect_idx] }; + // remove folded section from the image contrib_obj->section_flags[contrib->sect_idx] |= COFF_SectionFlag_LnkRemove; + // update folded section COMDAT symlink to point to the leader section + contrib_obj->symlinks[contrib->sect_idx + 1] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->comdats[leader->sect_idx] }; + #if LNK_PARANOID String8 section_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib->sect_idx+1); String8 leader_name = lnk_obj_section_name_from_section_number(leader_obj, leader->sect_idx+1); @@ -3684,22 +3690,48 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) barrier_wait(tp->barrier); if (task_id == 0 && fold_stats) { - FoldStats total_stats[LNK_ICF_ColorSpace_COUNT] = {0}; + FoldStats stats[LNK_ICF_Scope_COUNT] = {0}; for EachIndex(worker_id, tp->worker_count) { - FoldStats *worker_stats = fold_stats + (worker_id * LNK_ICF_ColorSpace_COUNT); - for (LNK_ICF_ColorSpace color_space = (LNK_ICF_ColorSpace)(LNK_ICF_ColorSpace_Null + 1); color_space < LNK_ICF_ColorSpace_COUNT; color_space += 1) { - total_stats[color_space].count += worker_stats[color_space].count; - total_stats[color_space].size += worker_stats[color_space].size; + FoldStats *w = fold_stats + (worker_id * LNK_ICF_Scope_COUNT); + for EachEnumVal(LNK_ICF_Scope, scope) { + stats[scope].count += w[scope].count; + stats[scope].size += w[scope].size; + stats[scope].live_size += w[scope].live_size; + stats[scope].live_count += w[scope].live_count; } } - U64 total_count = 0, total_size = 0; - for (LNK_ICF_ColorSpace color_space = (LNK_ICF_ColorSpace)(LNK_ICF_ColorSpace_Null + 1); color_space < LNK_ICF_ColorSpace_COUNT; color_space += 1) { - lnk_log(LNK_Log_Debug, " %-8S: %M, %.*s sections", lnk_string_from_icf_color_space(color_space), total_stats[color_space].size, str8_varg(str8_from_count(scratch.arena, total_stats[color_space].count))); - total_count += total_stats[color_space].count; - total_size += total_stats[color_space].size; + FoldStats total = {0}; + for EachEnumVal(LNK_ICF_Scope, scope) { + if (scope == LNK_ICF_Scope_Null) { continue; } + + // scope -> string + String8 scope_str = str8_lit("Unknown"); + switch (scope) { +#define X(ID) case LNK_ICF_Scope_##ID: scope_str = str8_lit(Stringify(ID)); break; + LNK_ICF_Scope_XList +#undef X + } + + lnk_log(LNK_Log_Debug, " %-9S: removed %M, %.*s sections; live %M, %.*s sections", + scope_str, + stats[scope].size, + str8_varg(str8_from_count(scratch.arena, stats[scope].count)), + stats[scope].live_size, + str8_varg(str8_from_count(scratch.arena, stats[scope].live_count))); + + total.count += stats[scope].count; + total.size += stats[scope].size; + total.live_count += stats[scope].live_count; + total.live_size += stats[scope].live_size; } - lnk_log(LNK_Log_Debug, " %-8s: %M, %.*s sections", "Total", total_size, str8_varg(str8_from_count(scratch.arena, total_count))); + + lnk_log(LNK_Log_Debug, " %-9s: removed %M, %.*s sections; live %M, %.*s sections", + "Total", + total.size, + str8_varg(str8_from_count(scratch.arena, total.count)), + total.live_size, + str8_varg(str8_from_count(scratch.arena, total.live_count))); } barrier_wait(tp->barrier); diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index eb08ca3b..985e91c6 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -98,6 +98,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Rad_SharedThreadPool, 0, "RAD_SHARED_THREAD_POOL", "[:STRING]", "Default value \"" LNK_DEFAULT_THREAD_POOL_NAME "\"" }, { LNK_CmdSwitch_Rad_SharedThreadPoolMaxWorkers, 0, "RAD_SHARED_THREAD_POOL_MAX_WORKERS", ":#", "Set maximum number of workers in a thread pool." }, { LNK_CmdSwitch_Rad_SortImports, 0, "RAD_SORT_IMPORTS", "[:NO]", "Sort static and delayed import tables by their order of appearance in libs, without assuming link order." }, + { LNK_CmdSwitch_Rad_IcfHashKind, 0, "RAD_ICF_HASH_KIND", "{BLAKE3|XXHASH}", "Sets hashing algorithm for /OPT:ICF." }, { LNK_CmdSwitch_Rad_Ignore, 0, "RAD_IGNORE", ":#", "Ignore the specified RAD linker warning." }, { LNK_CmdSwitch_Rad_ImageAltPath, 0, "RAD_IMAGEALTPATH", ":FILENAME", "Alternative name for the image" }, { LNK_CmdSwitch_Rad_WriteTempFiles, 0, "RAD_WRITE_TEMP_FILES", "[:NO]", "When speicifed linker writes image and debug info to temporary files and renames after link is done." }, @@ -2325,6 +2326,16 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List lnk_cmd_switch_parse_u32(obj, cmd_switch, value_strings, &config->time_stamp, 0); } break; + case LNK_CmdSwitch_Rad_IcfHashKind: { + // TODO: dedup + String8 alg = {0}; + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &alg)) { + if (str8_matchi(alg, str8_lit("BLAKE3"))) { config->icf_hash_kind = LNK_HashKind_BLAKE3; } + else if (str8_matchi(alg, str8_lit("XXHASH"))) { config->icf_hash_kind = LNK_HashKind_XXHash; } + else { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unknown hash alg: %S", alg); } + } + } break; + case LNK_CmdSwitch_Rad_DebugTypeHash: { String8 alg = {0}; if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &alg)) { diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index 9fd5057f..d6e8b1f1 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -114,6 +114,7 @@ typedef enum LNK_CmdSwitch_Rad_EnvLib, LNK_CmdSwitch_Rad_Exe, LNK_CmdSwitch_Rad_Guid, + LNK_CmdSwitch_Rad_IcfHashKind, LNK_CmdSwitch_Rad_Ignore, LNK_CmdSwitch_Rad_ImageAltPath, LNK_CmdSwitch_Rad_LargePages, @@ -410,6 +411,7 @@ typedef struct LNK_Config U64 unresolved_symbol_limit; U64 unresolved_symbol_ref_limit; LNK_SwitchState map_lines_for_unresolved_symbols; + LNK_HashKind icf_hash_kind; LNK_HashKind debug_types_hash; String8 type_server_name; LNK_SwitchState type_server; diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index e217d057..6e196cc4 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -1353,7 +1353,7 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf lnk_hasher_update_struct(&hasher, leaf_header_ptr); // finalize the type hash - U64 hash = lnk_hasher_digest(&hasher); + U64 hash = lnk_hasher_digest64(&hasher); Assert(hash != 0); Assert(input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] == 0 || diff --git a/src/linker/lnk_hasher.c b/src/linker/lnk_hasher.c index d11467b1..2b315ee6 100644 --- a/src/linker/lnk_hasher.c +++ b/src/linker/lnk_hasher.c @@ -1,7 +1,7 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -internal void +LNK_HASHER_FN void lnk_hasher_init(LNK_Hasher *hasher, LNK_HashKind kind) { hasher->kind = kind; @@ -12,7 +12,7 @@ lnk_hasher_init(LNK_Hasher *hasher, LNK_HashKind kind) } } -internal void +LNK_HASHER_FN void lnk_hasher_update(LNK_Hasher *hasher, void *data, U64 size) { switch (hasher->kind) { @@ -22,8 +22,8 @@ lnk_hasher_update(LNK_Hasher *hasher, void *data, U64 size) } } -internal U64 -lnk_hasher_digest(LNK_Hasher *hasher) +LNK_HASHER_FN U64 +lnk_hasher_digest64(LNK_Hasher *hasher) { U64 hash = 0; switch (hasher->kind) { @@ -34,7 +34,22 @@ lnk_hasher_digest(LNK_Hasher *hasher) return hash; } -internal String8 +LNK_HASHER_FN U128 +lnk_hasher_digest128(LNK_Hasher *hasher) +{ + U128 hash = {0}; + switch (hasher->kind) { + case LNK_HashKind_BLAKE3: blake3_hasher_finalize(&hasher->u.blake3, (U8 *)&hash, sizeof(hash)); break; + case LNK_HashKind_XXHash: { + XXH128_hash_t *out = (XXH128_hash_t *)&hash; + *out = XXH3_128bits_digest(&hasher->u.xxhash); + } break; + default: InvalidPath; break; + } + return hash; +} + +LNK_HASHER_FN String8 lnk_string_hash_kind(LNK_HashKind hash_kind) { #define X(NAME) case LNK_HashKind_##NAME: return str8_lit(Stringify(NAME)); @@ -45,7 +60,7 @@ lnk_string_hash_kind(LNK_HashKind hash_kind) return str8_zero(); } -internal LNK_HashKind +LNK_HASHER_FN LNK_HashKind lnk_hash_kind_from_llvm(LLVM_GHashAlgEnum v) { switch (v) { diff --git a/src/linker/lnk_hasher.h b/src/linker/lnk_hasher.h index a07aa321..59c40df1 100644 --- a/src/linker/lnk_hasher.h +++ b/src/linker/lnk_hasher.h @@ -18,20 +18,25 @@ typedef enum typedef struct LNK_Hasher { LNK_HashKind kind; - union - { + union { blake3_hasher blake3; XXH3_state_t xxhash; } u; } LNK_Hasher; -internal void lnk_hasher_init (LNK_Hasher *hasher, LNK_HashKind kind); -internal void lnk_hasher_update(LNK_Hasher *hasher, void *data, U64 size); -#define lnk_hasher_update_struct(hasher, ptr) lnk_hasher_update(hasher, ptr, sizeof(*ptr)) -internal U64 lnk_hasher_digest(LNK_Hasher *hasher); +#if BUILD_DEBUG +#define LNK_HASHER_FN internal +#else +#define LNK_HASHER_FN always_inline +#endif -internal String8 lnk_string_hash_kind(LNK_HashKind hash_kind); - -// LLVM extension -internal LNK_HashKind lnk_hash_kind_from_llvm(LLVM_GHashAlgEnum v); +LNK_HASHER_FN void lnk_hasher_init (LNK_Hasher *hasher, LNK_HashKind kind); +LNK_HASHER_FN void lnk_hasher_update (LNK_Hasher *hasher, void *data, U64 size); +LNK_HASHER_FN U64 lnk_hasher_digest64 (LNK_Hasher *hasher); +LNK_HASHER_FN U128 lnk_hasher_digest128(LNK_Hasher *hasher); +LNK_HASHER_FN String8 lnk_string_hash_kind(LNK_HashKind kind); +#define lnk_hasher_update_string(hasher, string) lnk_hasher_update(hasher, (string).str, (string).size) +#define lnk_hasher_update_struct(hasher, ptr) lnk_hasher_update(hasher, ptr, sizeof(*ptr)) +// @LLVM-ext +LNK_HASHER_FN LNK_HashKind lnk_hash_kind_from_llvm(LLVM_GHashAlgEnum v); From 72c3dfd30c8e0c70ae62aa320f471be2f29f18e0 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 22 Jul 2026 22:17:04 -0700 Subject: [PATCH 046/251] improve diagnostics for the missing entry point --- src/base/base_core.h | 2 +- src/linker/lnk.c | 34 +++++++++++++++++++++++++++++++--- src/linker/lnk_config.c | 18 +++++++++--------- src/pe/pe.c | 20 +++++++++++++++++++- src/pe/pe.h | 37 +++++++++++++++++++++---------------- 5 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/base/base_core.h b/src/base/base_core.h index a5b2a964..0cb16243 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -199,7 +199,7 @@ #define EachNonZeroEnumVal(type, it) (type it = (type)1; it < type##_COUNT; it = (type)(it+1)) #define EachInRange(it, range) (U64 it = (range).min; it < (range).max; it += 1) #define EachNode(it, T, first) (T *it = first; it != 0; it = it->next) -#define EachBit(it, flags) (U64 (_i_) = (flags), it = (flags) & -(flags); (_i_) != 0; (_i_) &= ((_i_) - 1), it = (flags) & -(flags)) +#define EachBit(it, flags) (U64 (_i_) = (flags), it = (_i_) & -(_i_); (_i_) != 0; (_i_) &= ((_i_) - 1), it = (_i_) & -(_i_)) //////////////////////////////// //~ rjf: Memory Operation Macros diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 4b72899f..e66833b9 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1605,7 +1605,7 @@ lnk_load_inputs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer } // by default terminal server is enabled for windows and console applications - if (~config->flags & LNK_ConfigFlag_NoTsAware && ~config->file_characteristics & PE_ImageFileCharacteristic_FILE_DLL) { + if (~config->flags & LNK_ConfigFlag_NoTsAware && ~config->file_characteristics & PE_ImageFileCharacteristic_DLL) { if (config->subsystem == PE_WindowsSubsystem_WINDOWS_GUI || config->subsystem == PE_WindowsSubsystem_WINDOWS_CUI) { config->dll_characteristics |= PE_DllCharacteristic_TERMINAL_SERVER_AWARE; } @@ -2397,7 +2397,35 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer // was entry point resolved? // if (config->entry_point_name.size == 0 || link->try_to_resolve_entry_point) { - lnk_error(LNK_Error_EntryPoint, "unable to find entry point symbol"); + String8 machine_str = coff_string_from_machine_type(config->machine); + String8 subsystem_str = pe_string_from_subsystem(config->subsystem); + String8Array entry_points = pe_get_entry_point_names(config->machine, config->subsystem, config->file_characteristics); + + String8List list = {0}; + for EachIndex(i, entry_points.count) { str8_list_push(scratch.arena, &list, entry_points.v[i]); } + String8 default_entries = str8_list_join(scratch.arena, &list, &(StringJoin){.sep = ", "}); + + lnk_error(LNK_Error_EntryPoint, + "failed to infer entry point symbol from the inputs\n" + " Machine: %S\n" + " Subsystem: %S\n" + " Subsystem Ver: %llu.%llu\n" + " File Chars: %S\n" + " DLL Chars: %S\n" + " Default Entries: %S\n" + " User Entry: \"%S\"\n" + " Input Obj Count: %S\n" + " Input Lib Count: %S", + machine_str.size ? machine_str : str8_lit("Unknown"), // Machine + subsystem_str.size ? subsystem_str : str8_lit("Unknown"), // Version + config->subsystem_ver.major, config->subsystem_ver.minor, // Subsystem + pe_string_from_file_characteristics(scratch.arena, config->file_characteristics), // File Chars + pe_string_from_dll_characteristics(scratch.arena, config->dll_characteristics), // DLL Chars + default_entries.size ? default_entries : str8_lit("None"), // Default Entry Points + config->entry_point_name, // /ENTRY + str8_from_count(scratch.arena, config->input_list[LNK_Input_Obj].node_count), // Input Objects Count + str8_from_count(scratch.arena, config->input_list[LNK_Input_Lib].node_count) // Input Libs Count + ); } // @@ -6597,7 +6625,7 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) // // Import Library // - if (config->build_imp_lib && (config->file_characteristics & PE_ImageFileCharacteristic_FILE_DLL)) { + if (config->build_imp_lib && (config->file_characteristics & PE_ImageFileCharacteristic_DLL)) { ProfBegin("Build Import Library"); lnk_timer_begin(LNK_Timer_Lib); String8 linker_debug_symbols = lnk_make_linker_debug_symbols(scratch.arena, config->machine); diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 985e91c6..0aba8f5e 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -897,9 +897,9 @@ lnk_get_base_addr(LNK_Config *config) { U64 base_addr = config->user_base_addr; if (base_addr == 0) { - if (config->file_characteristics & PE_ImageFileCharacteristic_FILE_DLL) { + if (config->file_characteristics & PE_ImageFileCharacteristic_DLL) { base_addr = coff_default_dll_base_from_machine(config->machine); - } else if (config->file_characteristics & PE_ImageFileCharacteristic_EXE) { + } else if (config->file_characteristics & PE_ImageFileCharacteristic_EXECUTABLE_IMAGE) { if ((~config->file_characteristics & PE_ImageFileCharacteristic_LARGE_ADDRESS_AWARE) && config->machine == COFF_MachineType_X64) { base_addr = coff_default_exe_base_from_machine(COFF_MachineType_X86); } else { @@ -1397,7 +1397,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Dll: { - config->file_characteristics |= PE_ImageFileCharacteristic_FILE_DLL; + config->file_characteristics |= PE_ImageFileCharacteristic_DLL; } break; case LNK_CmdSwitch_DynamicBase: { @@ -2112,7 +2112,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_Exe: { - lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->file_characteristics, PE_ImageFileCharacteristic_EXE); + lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->file_characteristics, PE_ImageFileCharacteristic_EXECUTABLE_IMAGE); } break; case LNK_CmdSwitch_Rad_Guid: { @@ -2638,7 +2638,7 @@ lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj) } if (stmt == LNK_DefFileStmt_Library) { - config->file_characteristics |= PE_ImageFileCharacteristic_FILE_DLL; + config->file_characteristics |= PE_ImageFileCharacteristic_DLL; } if (name.size != 0) { @@ -2775,7 +2775,7 @@ lnk_config_init(LNK_CmdLine cmd_line) // set default manifest resource id if (config->manifest_resource_id == 0) { - if (config->file_characteristics & PE_ImageFileCharacteristic_FILE_DLL) { + if (config->file_characteristics & PE_ImageFileCharacteristic_DLL) { config->manifest_resource_id = push_u64(arena, 2); } else { config->manifest_resource_id = push_u64(arena, 1); @@ -2844,7 +2844,7 @@ lnk_config_init(LNK_CmdLine cmd_line) } // warn about unused large address aware flag - if ((~config->file_characteristics & PE_ImageFileCharacteristic_LARGE_ADDRESS_AWARE) && (config->file_characteristics & PE_ImageFileCharacteristic_FILE_DLL)) { + if ((~config->file_characteristics & PE_ImageFileCharacteristic_LARGE_ADDRESS_AWARE) && (config->file_characteristics & PE_ImageFileCharacteristic_DLL)) { lnk_error(LNK_Warning_NoLargeAddressAwarenessForDll, "/LARGEADDRESSAWARE:NO has no effect when specified together with /DLL"); } @@ -2867,7 +2867,7 @@ lnk_config_init(LNK_CmdLine cmd_line) // set flags for /FIXED if (config->flags & LNK_ConfigFlag_Fixed) { - config->file_characteristics |= PE_ImageFileCharacteristic_STRIPPED; + config->file_characteristics |= PE_ImageFileCharacteristic_RELOCS_STRIPPED; config->dll_characteristics &= ~PE_DllCharacteristic_DYNAMIC_BASE; } // if we don't have a fixed image and dynamic base switch @@ -2886,7 +2886,7 @@ lnk_config_init(LNK_CmdLine cmd_line) // handle empty /OUT if (!config->out_path.size) { String8 name = str8_list_first(&config->input_list[LNK_Input_Obj]); - String8 ext = (config->file_characteristics & PE_ImageFileCharacteristic_FILE_DLL) ? str8_lit("dll") : str8_lit("exe"); + String8 ext = (config->file_characteristics & PE_ImageFileCharacteristic_DLL) ? str8_lit("dll") : str8_lit("exe"); config->out_path = path_replace_file_extension(scratch.arena, name, ext); } diff --git a/src/pe/pe.c b/src/pe/pe.c index 85d9ed17..85449aeb 100644 --- a/src/pe/pe.c +++ b/src/pe/pe.c @@ -351,6 +351,24 @@ pe_string_from_load_config_guard_flags(Arena *arena, PE_LoadConfigGuardFlags fla return result; } +internal String8 +pe_string_from_file_characteristics(Arena *arena, PE_ImageFileCharacteristics file_chars) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List result = {0}; + for EachBit(i, file_chars) { + switch (i) { +#define X(ID, VAL, DISPLAY_STRING) case (1 << VAL): str8_list_push(scratch.arena, &result, str8_lit(DISPLAY_STRING)); break; + PE_ImageFileCharacteristic_XList +#undef X + default: str8_list_pushf(scratch.arena, &result, "%llu", i); + } + } + String8 string = str8_list_join(arena, &result, &(StringJoin){.sep=str8_lit(", ")}); + scratch_end(scratch); + return string; +} + internal String8 pe_string_from_dll_characteristics(Arena *arena, PE_DllCharacteristics dll_chars) { @@ -915,7 +933,7 @@ pe_get_entry_point_names(COFF_MachineType machine, { String8Array entry_point_names = {0}; - if (file_characteristics & PE_ImageFileCharacteristic_FILE_DLL) { + if (file_characteristics & PE_ImageFileCharacteristic_DLL) { if (machine == COFF_MachineType_X86) { read_only static String8 dll_entry_point_arr[] = { str8_lit_comp("__DllMainCRTStartup@12"), diff --git a/src/pe/pe.h b/src/pe/pe.h index 378d71ed..be8a34df 100644 --- a/src/pe/pe.h +++ b/src/pe/pe.h @@ -53,25 +53,30 @@ enum PE_WindowsSubsystem_COUNT = 14 }; +#define PE_ImageFileCharacteristic_XList \ + X(RELOCS_STRIPPED , 0 , "Relocations Stripped" ) \ + X(EXECUTABLE_IMAGE , 1 , "Executable" ) \ + X(LINE_NUMS_STRIPPED , 2 , "Line Numbers Stripped" ) \ + X(LOCAL_SYMS_STRIPPED , 3 , "Local Symbols Stripped" ) \ + X(AGGRESSIVE_WS_TRIM , 4 , "Aggressive Working Set Trim") \ + X(LARGE_ADDRESS_AWARE , 5 , "Large Address Aware" ) \ + X(RESERVED , 6 , "Reserved" ) \ + X(BYTES_REVERSED_LO , 7 , "Bytes Reversed Low" ) \ + X(32BIT_MACHINE , 8 , "32-Bit Machine" ) \ + X(DEBUG_STRIPPED , 9 , "Debug Information Stripped" ) \ + X(REMOVABLE_RUN_FROM_SWAP, 10, "Removable: Run from Swap" ) \ + X(NET_RUN_FROM_SWAP , 11, "Network: Run from Swap" ) \ + X(SYSTEM , 12, "System File" ) \ + X(DLL , 13, "DLL" ) \ + X(UP_SYSTEM_ONLY , 14, "Uniprocessor System Only" ) \ + X(BYTES_REVERSED_HI , 15, "Bytes Reversed High" ) + typedef U16 PE_ImageFileCharacteristics; enum { - PE_ImageFileCharacteristic_STRIPPED = (1 << 0), - PE_ImageFileCharacteristic_EXE = (1 << 1), - PE_ImageFileCharacteristic_NUMS_STRIPPED = (1 << 2), - PE_ImageFileCharacteristic_PE_STRIPPED = (1 << 3), - PE_ImageFileCharacteristic_AGGRESIVE_WS_TRIM = (1 << 4), - PE_ImageFileCharacteristic_LARGE_ADDRESS_AWARE = (1 << 5), - PE_ImageFileCharacteristic_UNUSED1 = (1 << 6), - PE_ImageFileCharacteristic_BYTES_RESERVED_LO = (1 << 7), - PE_ImageFileCharacteristic_32BIT_MACHINE = (1 << 8), - PE_ImageFileCharacteristic_DEBUG_STRIPPED = (1 << 9), - PE_ImageFileCharacteristic_FILE_REMOVABLE_RUN_FROM_SWAP = (1 << 10), - PE_ImageFileCharacteristic_NET_RUN_FROM_SWAP = (1 << 11), - PE_ImageFileCharacteristic_FILE_SYSTEM = (1 << 12), - PE_ImageFileCharacteristic_FILE_DLL = (1 << 13), - PE_ImageFileCharacteristic_FILE_UP_SYSTEM_ONLY = (1 << 14), - PE_ImageFileCharacteristic_BYTES_RESERVED_HI = (1 << 15), +#define X(ID, VAL, ...) PE_ImageFileCharacteristic_##ID = (1 << VAL), + PE_ImageFileCharacteristic_XList +#undef X }; typedef U16 PE_DllCharacteristics; From 1086d1d350e4925b63e279d24d2c42654105efb4 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 13 Aug 2026 13:39:00 -0700 Subject: [PATCH 047/251] reduce arena footprint by using initial reserve and commit size parameters `current` points to the most recently allocated arena. If that arena was allocated by a large push, lets say 2 GiB, while the arena's initial reserve size was 1 GiB, the next block would be allocated with a 2 GiB reserve. --- src/base/base_arena.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/base/base_arena.c b/src/base/base_arena.c index 2e9b3e00..d4376c10 100644 --- a/src/base/base_arena.c +++ b/src/base/base_arena.c @@ -227,8 +227,8 @@ arena_push(Arena *arena, U64 size, U64 align, B32 zero) if(new_block == 0) { - U64 res_size = current->res_size; - U64 cmt_size = current->cmt_size; + U64 res_size = arena->res_size; + U64 cmt_size = arena->cmt_size; if(size + ARENA_HEADER_SIZE > res_size) { res_size = AlignPow2(size + ARENA_HEADER_SIZE, align); @@ -236,9 +236,9 @@ arena_push(Arena *arena, U64 size, U64 align, B32 zero) } new_block = arena_alloc(.reserve_size = res_size, .commit_size = cmt_size, - .flags = current->flags, - .allocation_site_file = current->allocation_site_file, - .allocation_site_line = current->allocation_site_line); + .flags = arena->flags, + .allocation_site_file = arena->allocation_site_file, + .allocation_site_line = arena->allocation_site_line); size_to_zero = 0; } From 3057d0b7da6eddc81201959ee38afbaa2cafe89f Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 13 Aug 2026 13:41:20 -0700 Subject: [PATCH 048/251] minor fix --- src/linker/lnk_hasher.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linker/lnk_hasher.h b/src/linker/lnk_hasher.h index 59c40df1..b4a6512f 100644 --- a/src/linker/lnk_hasher.h +++ b/src/linker/lnk_hasher.h @@ -27,7 +27,7 @@ typedef struct LNK_Hasher #if BUILD_DEBUG #define LNK_HASHER_FN internal #else -#define LNK_HASHER_FN always_inline +#define LNK_HASHER_FN force_inline #endif LNK_HASHER_FN void lnk_hasher_init (LNK_Hasher *hasher, LNK_HashKind kind); From e6363453840c5e487a788b2895bc25de4f23acfc Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 14 Aug 2026 13:01:39 -0700 Subject: [PATCH 049/251] update memfun.h --- src/third_party/martins_memfun/memfun.h | 2889 +++++++++++++++++------ 1 file changed, 2147 insertions(+), 742 deletions(-) diff --git a/src/third_party/martins_memfun/memfun.h b/src/third_party/martins_memfun/memfun.h index b8ee0eb3..433dec1d 100644 --- a/src/third_party/martins_memfun/memfun.h +++ b/src/third_party/martins_memfun/memfun.h @@ -1,5 +1,9 @@ #pragma once +// +// https://github.com/mmozeiko/overflow/tree/main/memfun +// + #include #include #include @@ -48,33 +52,46 @@ MEM_API bool MemIsEqual(const void* ptr1, const void* ptr2, size_t size); // returns first offset of "value" byte, or "size" if not found MEM_API size_t MemFind(const void* ptr, size_t size, uint8_t value); - -MEM_API int MemCompare_sse2 (const void* ptr1, const void* ptr2, size_t size); -MEM_API int MemCompare_avx2 (const void* ptr1, const void* ptr2, size_t size); -MEM_API int MemCompare_avx512(const void* ptr1, const void* ptr2, size_t size); -MEM_API int MemCompare_arm64 (const void* ptr1, const void* ptr2, size_t size); -MEM_API int MemCompare_rvv (const void* ptr1, const void* ptr2, size_t size); +// returns first offset of byte not equal to "value", or "size" if not found +MEM_API size_t MemFindNot(const void* ptr, size_t size, uint8_t value); -MEM_API int MemCompareI_sse2 (const void* ptr1, const void* ptr2, size_t size); -MEM_API int MemCompareI_avx2 (const void* ptr1, const void* ptr2, size_t size); -MEM_API int MemCompareI_avx512(const void* ptr1, const void* ptr2, size_t size); -MEM_API int MemCompareI_arm64 (const void* ptr1, const void* ptr2, size_t size); -MEM_API int MemCompareI_rvv (const void* ptr1, const void* ptr2, size_t size); +// use functions below directly if you want to avoid dynamic dispatch (only relevant on x64) +MEM_API int MemCompare_sse2 (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompare_avx2 (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompare_avx512 (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompare_neon (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompare_rvv (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompare_generic(const void* ptr1, const void* ptr2, size_t size); -MEM_API bool MemIsEqual_sse2 (const void* ptr1, const void* ptr2, size_t size); -MEM_API bool MemIsEqual_avx2 (const void* ptr1, const void* ptr2, size_t size); -MEM_API bool MemIsEqual_avx512(const void* ptr1, const void* ptr2, size_t size); -MEM_API bool MemIsEqual_arm64 (const void* ptr1, const void* ptr2, size_t size); -MEM_API bool MemIsEqual_rvv (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompareI_sse2 (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompareI_avx2 (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompareI_avx512 (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompareI_neon (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompareI_rvv (const void* ptr1, const void* ptr2, size_t size); +MEM_API int MemCompareI_generic(const void* ptr1, const void* ptr2, size_t size); +MEM_API bool MemIsEqual_sse2 (const void* ptr1, const void* ptr2, size_t size); +MEM_API bool MemIsEqual_avx2 (const void* ptr1, const void* ptr2, size_t size); +MEM_API bool MemIsEqual_avx512 (const void* ptr1, const void* ptr2, size_t size); +MEM_API bool MemIsEqual_neon (const void* ptr1, const void* ptr2, size_t size); +MEM_API bool MemIsEqual_rvv (const void* ptr1, const void* ptr2, size_t size); +MEM_API bool MemIsEqual_generic(const void* ptr1, const void* ptr2, size_t size); -MEM_API size_t MemFind_sse2 (const void* ptr, size_t size, uint8_t value); -MEM_API size_t MemFind_avx2 (const void* ptr, size_t size, uint8_t value); -MEM_API size_t MemFind_avx512(const void* ptr, size_t size, uint8_t value); -MEM_API size_t MemFind_arm64 (const void* ptr, size_t size, uint8_t value); -MEM_API size_t MemFind_rvv (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFind_sse2 (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFind_avx2 (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFind_avx512 (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFind_neon (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFind_rvv (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFind_generic(const void* ptr, size_t size, uint8_t value); + +MEM_API size_t MemFindNot_sse2 (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFindNot_avx2 (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFindNot_avx512 (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFindNot_neon (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFindNot_rvv (const void* ptr, size_t size, uint8_t value); +MEM_API size_t MemFindNot_generic(const void* ptr, size_t size, uint8_t value); #ifdef __cplusplus @@ -109,8 +126,6 @@ MEM_API size_t MemFind_rvv (const void* ptr, size_t size, uint8_t value); #elif defined(__riscv) && __riscv_v >= 1000000 # define MEM_ARCH_RVV 1 # include -#else -# error unsupported arch #endif // cpuid, only for x64 @@ -121,13 +136,13 @@ MEM_API size_t MemFind_rvv (const void* ptr, size_t size, uint8_t value); # define MEM_CPUID2(x, y, info) __cpuid_count(x, y, info[0], info[1], info[2], info[3]) # define MEM_XGETBV(x) __builtin_ia32_xgetbv(x) # define MEM_GET32_RELAXED(ptr) __atomic_load_n(ptr, __ATOMIC_RELAXED) -# define MEM_SET32_RELAXED(ptr, value) __atomic_store_n(ptr, value, __ATOMIC_RELAXED); +# define MEM_SET32_RELAXED(ptr, value) __atomic_store_n(ptr, value, __ATOMIC_RELAXED) # elif MEM_COMPILER_MSVC # define MEM_CPUID(x, info) __cpuid(info, x) # define MEM_CPUID2(x, y, info) __cpuidex(info, x, y) # define MEM_XGETBV(x) _xgetbv(x) # define MEM_GET32_RELAXED(ptr) __iso_volatile_load32(ptr) -# define MEM_SET32_RELAXED(ptr, value) __iso_volatile_store32(ptr, value); +# define MEM_SET32_RELAXED(ptr, value) __iso_volatile_store32(ptr, value) # endif #endif @@ -156,8 +171,10 @@ typedef struct { uint64_t value; } MemUnalignedPtr64; // byteswap #if MEM_COMPILER_CLANG || MEM_COMPILER_GCC +# define MEM_BSWAP32(x) __builtin_bswap32(x) # define MEM_BSWAP64(x) __builtin_bswap64(x) #elif MEM_COMPILER_MSVC +# define MEM_BSWAP32(x) _byteswap_ulong(x) # define MEM_BSWAP64(x) _byteswap_uint64(x) #endif @@ -195,6 +212,7 @@ typedef struct { uint64_t value; } MemUnalignedPtr64; # define MEM_TARGET_AVX512 #endif + static inline uint8_t MemToLower1(uint8_t x) { return (uint8_t)(x - 'A') <= ('Z' - 'A') ? (x + 'a' - 'A') : x; @@ -257,6 +275,7 @@ static inline __m512i MemToLower64(__m512i x) #endif } + MEM_DISABLE_ASAN int MemCompare_sse2(const void* ptr1, const void* ptr2, size_t size) { @@ -272,28 +291,41 @@ int MemCompare_sse2(const void* ptr1, const void* ptr2, size_t size) { const uint32_t PAGE_SIZE = 4096; + // if 16 bytes from each pointer does not cross page boundary, can safely load them as 16-byte vector uint32_t address = (uint32_t)(uintptr_t)p1 | (uint32_t)(uintptr_t)p2; if ((address & (PAGE_SIZE - 1)) <= PAGE_SIZE - 16) { __m128i a0 = _mm_loadu_si128((const __m128i*)p1); __m128i b0 = _mm_loadu_si128((const __m128i*)p2); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); - uint32_t mask = (1 + (uint16_t)_mm_movemask_epi8(r0)) & ((1 << size) - 1); - if (mask) - { - size_t index = MEM_CTZ32(mask); - return p1[index] - p2[index]; - } - return 0; + // extract top bit of each lane to mask + // it will have bit value 0 in positions where bytes are not equal + // adding 1 will flip lowest bit with value 0 to value 1 + // and change all bits with value 1 below it to 0 + uint32_t m = 1U + (uint16_t)_mm_movemask_epi8(r0); + + // get index of byte that's different, m is guaranteed non-zero, because there are only max 16 bytes here + size_t index = MEM_CTZ32(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } + // cannot overread buffers, need to load exactly "size" bytes only + if (size < 2) // size == 1 { return p1[0] - p2[0]; } + // will load pair of 4, 8 or 16 overlapping bytes + // a/b0 from beginning of buffer + // a/b1 from end of buffers uint64_t a0, b0, a1, b1; + if (size < 4) // 2 <= size < 4 { a0 = MEM_PTR16U(p1); @@ -316,11 +348,15 @@ int MemCompare_sse2(const void* ptr1, const void* ptr2, size_t size) b1 = MEM_PTR64U(p2 + size - 8); } + // use a0/b0 if they are not equal, otherwise a1/b1 + // byte swap because in big-endian bytes can be compared as uint64 numbers uint64_t a = MEM_BSWAP64(a0 != b0 ? a0 : a1); uint64_t b = MEM_BSWAP64(a0 != b0 ? b0 : b1); + return (a > b) - (a < b); } + // process 64-byte blocks as much as possible while (size >= 64) { __m128i a0 = _mm_loadu_si128((const __m128i*)(p1 + 0x00)); @@ -331,38 +367,30 @@ int MemCompare_sse2(const void* ptr1, const void* ptr2, size_t size) __m128i b2 = _mm_loadu_si128((const __m128i*)(p2 + 0x20)); __m128i a3 = _mm_loadu_si128((const __m128i*)(p1 + 0x30)); __m128i b3 = _mm_loadu_si128((const __m128i*)(p2 + 0x30)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); __m128i r1 = _mm_cmpeq_epi8(a1, b1); __m128i r2 = _mm_cmpeq_epi8(a2, b2); __m128i r3 = _mm_cmpeq_epi8(a3, b3); + + // combine comparisons - leave 0x00 in all lanes that were not equal __m128i r = _mm_and_si128(_mm_and_si128(r0, r1), _mm_and_si128(r2, r3)); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r); if (mask) { - mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = 0x00 + MEM_CTZ32(mask); - return p1[index] - p2[index]; - } + // if mask is non-zero that means there is difference somewhere + // do the same thing as above, but for all r0/r1/r2/r3 comparisons + uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); + uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); + uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); + uint64_t m4 = 1ULL + (m0 | (m1 << 16) | (m2 << 32) | (m3 << 48)); - mask = 1 + (uint16_t)_mm_movemask_epi8(r1); - if (mask) - { - size_t index = 0x10 + MEM_CTZ32(mask); - return p1[index] - p2[index]; - } - - mask = 1 + (uint16_t)_mm_movemask_epi8(r2); - if (mask) - { - size_t index = 0x20 + MEM_CTZ32(mask); - return p1[index] - p2[index]; - } - - mask = 1 + (uint16_t)_mm_movemask_epi8(r3); - size_t index = 0x30 + MEM_CTZ32(mask); + // m4 is guaranteed to be non-zero, extract index and return result + size_t index = MEM_CTZ64(m4); return p1[index] - p2[index]; } @@ -373,65 +401,83 @@ int MemCompare_sse2(const void* ptr1, const void* ptr2, size_t size) if (size & 32) // 32 <= size < 64 { - __m128i a0 = _mm_loadu_si128((const __m128i*)p1); - __m128i b0 = _mm_loadu_si128((const __m128i*)p2); - __m128i r0 = _mm_cmpeq_epi8(a0, b0); - - uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask); - return p1[index] - p2[index]; - } - + // load 64 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m128i a0 = _mm_loadu_si128((const __m128i*)(p1 + 0x00)); + __m128i b0 = _mm_loadu_si128((const __m128i*)(p2 + 0x00)); __m128i a1 = _mm_loadu_si128((const __m128i*)(p1 + 0x10)); __m128i b1 = _mm_loadu_si128((const __m128i*)(p2 + 0x10)); + __m128i a2 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x20)); + __m128i b2 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x20)); + __m128i a3 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x10)); + __m128i b3 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(a0, b0); __m128i r1 = _mm_cmpeq_epi8(a1, b1); + __m128i r2 = _mm_cmpeq_epi8(a2, b2); + __m128i r3 = _mm_cmpeq_epi8(a3, b3); - mask = 1 + (uint16_t)_mm_movemask_epi8(r1); - if (mask) - { - size_t index = 0x10 + MEM_CTZ32(mask); - return p1[index] - p2[index]; - } + // extract top bit masks + uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); + uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); + uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); - size -= 32; - p1 += 32; - p2 += 32; + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m = 1ULL + (m0 | (m1 << 16) | (m2 << (size - 32)) | (m3 << (size - 16))); + + // get index of byte that's different, m is guaranteed non-zero, because there only max 63 bytes here + size_t index = MEM_CTZ64(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } - - if (size & 16) // 16 <= size < 32 + else if (size & 16) // 16 <= size < 32 { + // load 32 bytes, some will overlap __m128i a0 = _mm_loadu_si128((const __m128i*)p1); __m128i b0 = _mm_loadu_si128((const __m128i*)p2); + __m128i a1 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x10)); + __m128i b1 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); + __m128i r1 = _mm_cmpeq_epi8(a1, b1); - uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask); - return p1[index] - p2[index]; - } + // extract top bit masks + uint32_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint32_t m1 = (uint16_t)_mm_movemask_epi8(r1); - size -= 16; - p1 += 16; - p2 += 16; + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (m0 | (m1 << (size - 16))); + + // get index of byte that's different, m is guaranteed non-zero, because there only max 31 bytes here + size_t index = MEM_CTZ32(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } - - if (size) // size < 16 + else if (size) // 0 < size < 16, but initially size > 16 { - __m128i a0 = _mm_loadu_si128((const __m128i*)(p1 + size - 16)); - __m128i b0 = _mm_loadu_si128((const __m128i*)(p2 + size - 16)); + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they were equal) + __m128i a0 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x10)); + __m128i b0 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); - uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask) + size - 16; - return p1[index] - p2[index]; - } + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint16_t)_mm_movemask_epi8(r0); + + // get index of byte that's different, m is guaranteed non-zero, because there only max 15 bytes here + size_t index = MEM_CTZ32(m) + size - 16; + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } + // no differences found, inputs are equal return 0; } @@ -450,30 +496,39 @@ int MemCompareI_sse2(const void* ptr1, const void* ptr2, size_t size) { const uint32_t PAGE_SIZE = 4096; + // if 16 bytes from each pointer does not cross page boundary, can safely load them as 16-byte vector uint32_t address = (uint32_t)(uintptr_t)p1 | (uint32_t)(uintptr_t)p2; if ((address & (PAGE_SIZE - 1)) <= PAGE_SIZE - 16) { + // load bytes and convert them to lowercase __m128i a0 = MemToLower16(_mm_loadu_si128((const __m128i*)p1)); __m128i b0 = MemToLower16(_mm_loadu_si128((const __m128i*)p2)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); - uint32_t mask = (1 + (uint16_t)_mm_movemask_epi8(r0)) & ((1 << size) - 1); - if (mask) - { - size_t index = MEM_CTZ32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint16_t)_mm_movemask_epi8(r0); - return 0; + // get index of byte that's different, m is guaranteed non-zero, because there are only max 16 bytes here + size_t index = MEM_CTZ32(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } + // cannot overread buffers, need to load exactly "size" bytes only + if (size < 2) // size == 1 { return MemToLower1(p1[0]) - MemToLower1(p2[0]); } - size_t n; + // will load pair of 4, 8 or 16 overlapping bytes + // a/b0 from beginning of buffer + // a/b1 from end of buffers __m128i a0, b0, a1, b1; + size_t n; if (size < 4) // 2 <= size < 4 { @@ -500,25 +555,30 @@ int MemCompareI_sse2(const void* ptr1, const void* ptr2, size_t size) n = 8; } + // pack bytes into 16-byte simd register + // a/b0 goes into low 64-bit lane + // a/b1 goes into high 64-bit lane __m128i a = _mm_unpacklo_epi64(a0, a1); __m128i b = _mm_unpacklo_epi64(b0, b1); + + // lowercase, and set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r = _mm_cmpeq_epi8(MemToLower16(a), MemToLower16(b)); - uint16_t mask = (1 + (uint16_t)_mm_movemask_epi8(r)); - if (mask) - { - size_t index = MEM_CTZ32(mask); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint16_t)_mm_movemask_epi8(r); - // index = (index < 8) ? index : (index - 8) + (size - n); - size -= 8 + n; - index += index < 8 ? 0 : size; + // get index of byte that's different, m is guaranteed non-zero, because there are only max 16 bytes here + size_t index = MEM_CTZ32(m); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // adjust index to correct byte position (due to how they were packed with _mm_unpacklo_epi64) + // index = (index < 8) ? index : (index - 8) + (size - n); + index += (index >= 8) * (size - 8 - n); - return 0; + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } + // process 64-byte blocks as much as possible while (size >= 64) { __m128i a0 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + 0x00))); @@ -529,38 +589,31 @@ int MemCompareI_sse2(const void* ptr1, const void* ptr2, size_t size) __m128i b2 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + 0x20))); __m128i a3 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + 0x30))); __m128i b3 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + 0x30))); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); __m128i r1 = _mm_cmpeq_epi8(a1, b1); __m128i r2 = _mm_cmpeq_epi8(a2, b2); __m128i r3 = _mm_cmpeq_epi8(a3, b3); + + // combine comparisons - leave 0x00 in lanes that were not equal __m128i r = _mm_and_si128(_mm_and_si128(r0, r1), _mm_and_si128(r2, r3)); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r); if (mask) { - mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = 0x00 + MEM_CTZ32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit masks for each comparison + uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); + uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); + uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); - mask = 1 + (uint16_t)_mm_movemask_epi8(r1); - if (mask) - { - size_t index = 0x10 + MEM_CTZ32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // combine masks, and flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m4 = 1ULL + (m0 | (m1 << 16) | (m2 << 32) | (m3 << 48)); - mask = 1 + (uint16_t)_mm_movemask_epi8(r2); - if (mask) - { - size_t index = 0x20 + MEM_CTZ32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } - - mask = 1 + (uint16_t)_mm_movemask_epi8(r3); - size_t index = 0x30 + MEM_CTZ32(mask); + // m4 is guaranteed to be non-zero, extract index and return result + size_t index = MEM_CTZ64(m4); return MemToLower1(p1[index]) - MemToLower1(p2[index]); } @@ -571,65 +624,83 @@ int MemCompareI_sse2(const void* ptr1, const void* ptr2, size_t size) if (size & 32) // 32 <= size < 64 { - __m128i a0 = MemToLower16(_mm_loadu_si128((const __m128i*)p1)); - __m128i b0 = MemToLower16(_mm_loadu_si128((const __m128i*)p2)); - __m128i r0 = _mm_cmpeq_epi8(a0, b0); - - uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } - + // load 64 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m128i a0 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + 0x00))); + __m128i b0 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + 0x00))); __m128i a1 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + 0x10))); __m128i b1 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + 0x10))); + __m128i a2 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + size - 0x20))); + __m128i b2 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + size - 0x20))); + __m128i a3 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + size - 0x10))); + __m128i b3 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + size - 0x10))); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(a0, b0); __m128i r1 = _mm_cmpeq_epi8(a1, b1); + __m128i r2 = _mm_cmpeq_epi8(a2, b2); + __m128i r3 = _mm_cmpeq_epi8(a3, b3); - mask = 1 + (uint16_t)_mm_movemask_epi8(r1); - if (mask) - { - size_t index = 0x10 + MEM_CTZ32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit masks + uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); + uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); + uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); - size -= 32; - p1 += 32; - p2 += 32; + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m = 1ULL + (m0 | (m1 << 16) | (m2 << (size - 32)) | (m3 << (size - 16))); + + // get index of byte that's different, m is guaranteed non-zero, because there only max 63 bytes here + size_t index = MEM_CTZ64(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } - - if (size & 16) // 16 <= size < 32 + else if (size & 16) // 16 <= size < 32 { + // load 32 bytes, some will overlap __m128i a0 = MemToLower16(_mm_loadu_si128((const __m128i*)p1)); __m128i b0 = MemToLower16(_mm_loadu_si128((const __m128i*)p2)); + __m128i a1 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + size - 0x10))); + __m128i b1 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + size - 0x10))); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); + __m128i r1 = _mm_cmpeq_epi8(a1, b1); - uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit masks + uint32_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint32_t m1 = (uint16_t)_mm_movemask_epi8(r1); - size -= 16; - p1 += 16; - p2 += 16; + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (m0 | (m1 << (size - 16))); + + // get index of byte that's different, m is guaranteed non-zero, because there only max 31 bytes here + size_t index = MEM_CTZ32(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } - - if (size) // size < 16 + else if (size) // 0 < size < 16, but initially size > 16 { - __m128i a0 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + size - 16))); - __m128i b0 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + size - 16))); + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they were equal) + __m128i a0 = MemToLower16(_mm_loadu_si128((const __m128i*)(p1 + size - 0x10))); + __m128i b0 = MemToLower16(_mm_loadu_si128((const __m128i*)(p2 + size - 0x10))); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); - uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask) + size - 16; - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint16_t)_mm_movemask_epi8(r0); + + // get index of byte that's different, m is guaranteed non-zero, because there only max 15 bytes here + size_t index = MEM_CTZ32(m) + size - 16; + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } + // no differences found, inputs are equal return 0; } @@ -648,23 +719,36 @@ bool MemIsEqual_sse2(const void* ptr1, const void* ptr2, size_t size) { const uint32_t PAGE_SIZE = 4096; + // if 16 bytes from each pointer does not cross page boundary, can safely load them as 16-byte vector uint32_t address = (uint32_t)(uintptr_t)p1 | (uint32_t)(uintptr_t)p2; if ((address & (PAGE_SIZE - 1)) <= PAGE_SIZE - 16) { __m128i a0 = _mm_loadu_si128((const __m128i*)p1); __m128i b0 = _mm_loadu_si128((const __m128i*)p2); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); - uint32_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t mask = 1U + (uint16_t)_mm_movemask_epi8(r0); + + // need to ignore top "32 - size" bits, only low bits matter + // return "true" if they are zero, otherwise "false" return (mask << (32 - size)) == 0; } + // cannot overread buffers, need to load exactly "size" bytes only + if (size < 2) // size == 1 { return p1[0] == p2[0]; } + // will load pair of 4, 8 or 16 overlapping bytes + // a/b0 from beginning of buffer + // a/b1 from end of buffers uint64_t a0, b0, a1, b1; + if (size < 4) // 2 <= size < 4 { a0 = MEM_PTR16U(p1); @@ -687,9 +771,12 @@ bool MemIsEqual_sse2(const void* ptr1, const void* ptr2, size_t size) b1 = MEM_PTR64U(p2 + size - 8); } + // compare loaded bytes on equality, overlapped ones will be checked twice + // but result will still be correct return (a0 == b0) & (a1 == b1); } + // process 64-byte blocks as much as possible while (size >= 64) { __m128i a0 = _mm_loadu_si128((const __m128i*)(p1 + 0x00)); @@ -700,15 +787,21 @@ bool MemIsEqual_sse2(const void* ptr1, const void* ptr2, size_t size) __m128i b2 = _mm_loadu_si128((const __m128i*)(p2 + 0x20)); __m128i a3 = _mm_loadu_si128((const __m128i*)(p1 + 0x30)); __m128i b3 = _mm_loadu_si128((const __m128i*)(p2 + 0x30)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); __m128i r1 = _mm_cmpeq_epi8(a1, b1); __m128i r2 = _mm_cmpeq_epi8(a2, b2); __m128i r3 = _mm_cmpeq_epi8(a3, b3); + + // combine comparisons - leave 0x00 in lanes that were not equal __m128i r = _mm_and_si128(_mm_and_si128(r0, r1), _mm_and_si128(r2, r3)); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r); if (mask) { + // non-zero mask => there is difference in bytes return false; } @@ -719,55 +812,70 @@ bool MemIsEqual_sse2(const void* ptr1, const void* ptr2, size_t size) if (size & 32) // 32 <= size < 64 { - __m128i a0 = _mm_loadu_si128((const __m128i*)p1); - __m128i b0 = _mm_loadu_si128((const __m128i*)p2); + // load 64 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m128i a0 = _mm_loadu_si128((const __m128i*)(p1 + 0x00)); + __m128i b0 = _mm_loadu_si128((const __m128i*)(p2 + 0x00)); __m128i a1 = _mm_loadu_si128((const __m128i*)(p1 + 0x10)); __m128i b1 = _mm_loadu_si128((const __m128i*)(p2 + 0x10)); + __m128i a2 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x20)); + __m128i b2 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x20)); + __m128i a3 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x10)); + __m128i b3 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); __m128i r1 = _mm_cmpeq_epi8(a1, b1); - __m128i r = _mm_and_si128(r0, r1); + __m128i r2 = _mm_cmpeq_epi8(a2, b2); + __m128i r3 = _mm_cmpeq_epi8(a3, b3); + // combine comparisons - leave 0x00 in lanes that were not equal + __m128i r = _mm_and_si128(_mm_and_si128(r0, r1), _mm_and_si128(r2, r3)); + + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r); - if (mask) - { - return false; - } - size -= 32; - p1 += 32; - p2 += 32; + // zero mask => inputs are equal, no difference in bytes + return mask == 0; } - - if (size & 16) // 16 <= size < 32 + else if (size & 16) // 16 <= size < 32 { + // load 32 bytes, some will overlap __m128i a0 = _mm_loadu_si128((const __m128i*)p1); __m128i b0 = _mm_loadu_si128((const __m128i*)p2); + __m128i a1 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x10)); + __m128i b1 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); + __m128i r1 = _mm_cmpeq_epi8(a1, b1); - uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - return false; - } + // combine comparisons - leave 0x00 in lanes that were not equal + __m128i r = _mm_and_si128(r0, r1); - size -= 16; - p1 += 16; - p2 += 16; + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r); + + // zero mask => inputs are equal, no difference in bytes + return mask == 0; } - - if (size) // size < 16 + else if (size) // 0 < size < 16, but initially size > 16 { - __m128i a0 = _mm_loadu_si128((const __m128i*)(p1 + size - 16)); - __m128i b0 = _mm_loadu_si128((const __m128i*)(p2 + size - 16)); + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they were equal) + __m128i a0 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x10)); + __m128i b0 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m128i r0 = _mm_cmpeq_epi8(a0, b0); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - return false; - } + + // zero mask => inputs are equal, no difference in bytes + return mask == 0; } + // no differences found, inputs are equal return true; } @@ -775,6 +883,7 @@ MEM_DISABLE_ASAN size_t MemFind_sse2(const void* ptr, size_t size, uint8_t value) { const uint8_t* p = (const uint8_t*)ptr; + const __m128i value16 = _mm_set1_epi8((char)value); if (size == 0) @@ -787,37 +896,58 @@ size_t MemFind_sse2(const void* ptr, size_t size, uint8_t value) size_t address = (uint32_t)(uintptr_t)p % 16; size_t extra = (address + size) <= 16 ? address : 0; + // will load before the beginning buffer (16-byte aligned) if end is too close + // to 16-byte boundary, otherwise will load past the end of buffer __m128i a0 = _mm_loadu_si128((const __m128i*)(p - extra)); - __m128i r0 = _mm_cmpeq_epi8(a0, value16); - uint32_t mask = ((uint16_t)_mm_movemask_epi8(r0) >> extra) & ((1 << size) - 1); - return mask ? MEM_CTZ32(mask) : size; + // set lane to 0xff if lane matches input value, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(a0, value16); + + // drop any lowest "extra" bits (due to loading bytes before beginning buffer) + uint32_t m = (uint16_t)_mm_movemask_epi8(r0) >> extra; + + // mask out high bits (due to loading bytes after end of buffer) + // this will make mask non-zero, and will result in returning "size" value if inputs are equal + m |= 1U << size; + + // return index of first bit set, which will be index of first byte matching input value + return MEM_CTZ32(m); } size_t offset = 0; + + // process 64-byte blocks as much as possible while (size >= 64) { __m128i a0 = _mm_loadu_si128((const __m128i*)(p + 0x00)); __m128i a1 = _mm_loadu_si128((const __m128i*)(p + 0x10)); __m128i a2 = _mm_loadu_si128((const __m128i*)(p + 0x20)); __m128i a3 = _mm_loadu_si128((const __m128i*)(p + 0x30)); - __m128i r0 = _mm_cmpeq_epi8(a0, value16); - __m128i r1 = _mm_cmpeq_epi8(a1, value16); - __m128i r2 = _mm_cmpeq_epi8(a2, value16); - __m128i r3 = _mm_cmpeq_epi8(a3, value16); + + // set lane to 0xff if lane matches input value, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(value16, a0); + __m128i r1 = _mm_cmpeq_epi8(value16, a1); + __m128i r2 = _mm_cmpeq_epi8(value16, a2); + __m128i r3 = _mm_cmpeq_epi8(value16, a3); + + // combine comparisons - leave 0xff in lanes there equal to input value __m128i r = _mm_or_si128(_mm_or_si128(r0, r1), _mm_or_si128(r2, r3)); + // extract top bit mask, it will be non-zero if there is at least one matching lane to input value uint16_t mask = (uint16_t)_mm_movemask_epi8(r); if (mask) { + // extract top bit masks for each comparison uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); uint64_t m3 = mask; // if r0=r1=r2=0, then r3=r + // combine them into one mask, m4 is guaranteed to be non-zero uint64_t m4 = m0 | (m1 << 16) | (m2 << 32) | (m3 << 48); - size_t index = MEM_CTZ64(m4); - return offset + index; + + // find first bit set, and return index + return offset + MEM_CTZ64(m4); } offset += 64; @@ -825,66 +955,219 @@ size_t MemFind_sse2(const void* ptr, size_t size, uint8_t value) p += 64; } - if (size & 32) + if (size & 32) // 32 <= size < 64 { + // load 64 bytes, some will overlap __m128i a0 = _mm_loadu_si128((const __m128i*)p); - __m128i r0 = _mm_cmpeq_epi8(a0, value16); - - uint16_t mask = (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask); - return offset + index; - } - __m128i a1 = _mm_loadu_si128((const __m128i*)(p + 0x10)); - __m128i r1 = _mm_cmpeq_epi8(a1, value16); + __m128i a2 = _mm_loadu_si128((const __m128i*)(p + size - 0x20)); + __m128i a3 = _mm_loadu_si128((const __m128i*)(p + size - 0x10)); - mask = (uint16_t)_mm_movemask_epi8(r1); - if (mask) - { - size_t index = 0x10 + MEM_CTZ32(mask); - return offset + index; - } + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(value16, a0); + __m128i r1 = _mm_cmpeq_epi8(value16, a1); + __m128i r2 = _mm_cmpeq_epi8(value16, a2); + __m128i r3 = _mm_cmpeq_epi8(value16, a3); - offset += 32; - size -= 32; - p += 32; + // extract top bit masks for each comparison + uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); + uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); + uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); + + // combine masks, handling overlapped ones + uint64_t m = m0 | (m1 << 16) | (m2 << (size - 32)) | (m3 << (size - 16)); + + // make sure mask is non-zero, this will result in returning "size" value if inputs are equal + m |= 1ULL << size; + + // find first bit set, and return index + return offset + MEM_CTZ64(m); } - - if (size & 16) + else if (size & 16) // 16 <= size < 32 { + // load 32 bytes, some will overlap __m128i a0 = _mm_loadu_si128((const __m128i*)p); - __m128i r0 = _mm_cmpeq_epi8(a0, value16); + __m128i a1 = _mm_loadu_si128((const __m128i*)(p + size - 0x10)); - uint16_t mask = (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask); - return offset + index; - } + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(value16, a0); + __m128i r1 = _mm_cmpeq_epi8(value16, a1); - offset += 16; - size -= 16; - p += 16; + // extract top bit masks for each comparison + uint32_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint32_t m1 = (uint16_t)_mm_movemask_epi8(r1); + + // combine masks, handling overlapped ones + uint32_t m = m0 | (m1 << (size - 16)); + + // make sure mask is non-zero, this will result in returning "size" value if inputs are equal + m |= 1U << size; + + // find first bit set, and return index + return offset + MEM_CTZ32(m); } - - if (size) // size < 16 + else if (size) // 0 < size < 16, but initially size > 16 { - __m128i a0 = _mm_loadu_si128((const __m128i*)(p + size - 16)); - __m128i r0 = _mm_cmpeq_epi8(a0, value16); + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they did not match input value) + __m128i a0 = _mm_loadu_si128((const __m128i*)(p + size - 0x10)); - uint16_t mask = (uint16_t)_mm_movemask_epi8(r0); - if (mask) - { - size_t index = MEM_CTZ32(mask) + size - 16; - return offset + index; - } + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(value16, a0); - offset += size; + // extract top bit mask, make sure it is non-zero + uint32_t m = (uint16_t)_mm_movemask_epi8(r0) | (1U << 16); + + // find first bit set, adjust it due to reused bytes in load, and return index + return offset + MEM_CTZ32(m) + size - 16; } - return offset; + // no input value found, return original size (current offset plus pending tail size) + return offset + size; +} + +MEM_DISABLE_ASAN +size_t MemFindNot_sse2(const void* ptr, size_t size, uint8_t value) +{ + const uint8_t* p = (const uint8_t*)ptr; + + const __m128i value16 = _mm_set1_epi8((char)value); + + if (size == 0) + { + return 0; + } + + if (size <= 16) + { + size_t address = (uint32_t)(uintptr_t)p % 16; + size_t extra = (address + size) <= 16 ? address : 0; + + // will load before the beginning buffer (16-byte aligned) if end is too close + // to 16-byte boundary, otherwise will load past the end of buffer + __m128i a0 = _mm_loadu_si128((const __m128i*)(p - extra)); + + // set lane to 0xff if lane matches input value, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(a0, value16); + + // add 1 to flip lowest 0 bit (non-equal position) to 1, changing all bits below it to 0 + // drop any lowest "extra" bits (due to loading bytes before beginning buffer) + uint32_t m = 1U + ((uint16_t)_mm_movemask_epi8(r0) >> extra); + + // mask out high bits (due to loading bytes after end of buffer) + // this will make mask non-zero, and will result in returning "size" value if all bytes are same as input value + m |= 1U << size; + + // return index of first bit set, which will be index of first byte not matching input value + return MEM_CTZ32(m); + } + + size_t offset = 0; + + // process 64-byte blocks as much as possible + while (size >= 64) + { + __m128i a0 = _mm_loadu_si128((const __m128i*)(p + 0x00)); + __m128i a1 = _mm_loadu_si128((const __m128i*)(p + 0x10)); + __m128i a2 = _mm_loadu_si128((const __m128i*)(p + 0x20)); + __m128i a3 = _mm_loadu_si128((const __m128i*)(p + 0x30)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(value16, a0); + __m128i r1 = _mm_cmpeq_epi8(value16, a1); + __m128i r2 = _mm_cmpeq_epi8(value16, a2); + __m128i r3 = _mm_cmpeq_epi8(value16, a3); + + // combine comparisons - leave 0x00 in lanes that were not equal + __m128i r = _mm_and_si128(_mm_and_si128(r0, r1), _mm_and_si128(r2, r3)); + + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint16_t mask = 1 + (uint16_t)_mm_movemask_epi8(r); + if (mask) + { + // extract top bit masks for comparisons + uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); + uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); + uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); + + // combine masks, and flip lowest 0 bit(non - equal position) to 1, changing all bits below it to 0 + uint64_t m4 = 1ULL + (m0 | (m1 << 16) | (m2 << 32) | (m3 << 48)); + + // m4 is guaranteed to be non-zero, extract index and return result + return offset + MEM_CTZ64(m4); + } + + offset += 64; + size -= 64; + p += 64; + } + + if (size & 32) // 32 <= size < 64 + { + // load 64 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m128i a0 = _mm_loadu_si128((const __m128i*)p); + __m128i a1 = _mm_loadu_si128((const __m128i*)(p + 0x10)); + __m128i a2 = _mm_loadu_si128((const __m128i*)(p + size - 0x20)); + __m128i a3 = _mm_loadu_si128((const __m128i*)(p + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(value16, a0); + __m128i r1 = _mm_cmpeq_epi8(value16, a1); + __m128i r2 = _mm_cmpeq_epi8(value16, a2); + __m128i r3 = _mm_cmpeq_epi8(value16, a3); + + // extract top bit masks + uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); + uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); + uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); + + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m = 1ULL + (m0 | (m1 << 16) | (m2 << (size - 32)) | (m3 << (size - 16))); + + // return index of byte that's different, m is guaranteed non-zero, because there only max 63 bytes here + return offset + MEM_CTZ64(m); + } + else if (size & 16) // 16 <= size < 32 + { + // load 32 bytes, some will overlap + __m128i a0 = _mm_loadu_si128((const __m128i*)p); + __m128i a1 = _mm_loadu_si128((const __m128i*)(p + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(value16, a0); + __m128i r1 = _mm_cmpeq_epi8(value16, a1); + + // extract top bit masks + uint32_t m0 = (uint16_t)_mm_movemask_epi8(r0); + uint32_t m1 = (uint16_t)_mm_movemask_epi8(r1); + + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (m0 | (m1 << (size - 16))); + + // return index of byte that's different, m is guaranteed non-zero, because there only max 31 bytes here + return offset + MEM_CTZ32(m); + } + else if (size) // 0 < size < 16, but initially size > 16 + { + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they matched input value) + __m128i a0 = _mm_loadu_si128((const __m128i*)(p + size - 0x10)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m128i r0 = _mm_cmpeq_epi8(value16, a0); + + // extract top bit mask, flip lowest 0 bit (non-equal position) to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint16_t)_mm_movemask_epi8(r0); + + // get index of byte that's different, m is guaranteed non-zero, because there only max 15 bytes here + return offset + MEM_CTZ32(m) + size - 16; + } + + // all bytes are same as input value, return original size (current offset plus pending tail size) + return offset + size; } MEM_DISABLE_ASAN @@ -903,83 +1186,106 @@ int MemCompare_avx2(const void* ptr1, const void* ptr2, size_t size) { const uint32_t PAGE_SIZE = 4096; + // if 32 bytes from each pointer does not cross page boundary, can safely load them as 16-byte vector uint32_t address = (uint32_t)(uintptr_t)p1 | (uint32_t)(uintptr_t)p2; if ((address & (PAGE_SIZE - 1)) <= PAGE_SIZE - 32) { __m256i a0 = _mm256_loadu_si256((const __m256i*)p1); __m256i b0 = _mm256_loadu_si256((const __m256i*)p2); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); - uint32_t mask = _bzhi_u32(1 + (uint32_t)_mm256_movemask_epi8(r0), (uint32_t)size); - if (mask) - { - size_t index = _tzcnt_u32(mask); - return p1[index] - p2[index]; - } + // extract top bit of each lane to mask + // it will have bit value 0 in positions where bytes are not equal + // adding 1 will flip lowest bit with value 0 to value 1 + // and change all bits with value 1 below it to 0 + uint32_t m = 1U + (uint32_t)_mm256_movemask_epi8(r0); - return 0; + // get index of byte that's different, evaluates to 32 if all bytes are equal + size_t index = _tzcnt_u32(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } + // cannot overread buffers, need to load exactly "size" bytes only + if (size < 2) // size == 1 { return p1[0] - p2[0]; } else if (size < 4) // 2 <= size < 4 { + // load two pairs of 2 overlapping bytes, in big-endian uint32_t a0 = MEM_GET16BE(p1); uint32_t b0 = MEM_GET16BE(p2); uint32_t a1 = MEM_GET16BE(p1 + size - 2); uint32_t b1 = MEM_GET16BE(p2 + size - 2); + // use a0/b0 if they are not equal, otherwise a1/b1 uint32_t a = (a0 != b0 ? a0 : a1); uint32_t b = (a0 != b0 ? b0 : b1); + // big-endian numbers can be subtracted to get their order + // as long as numbers are small so subtraction does not overflow 32-bit int return (int)a - (int)b; } else if (size < 8) // 4 <= size < 8 { + // load two pairs of 4 overlapping bytes, in big-endian uint32_t a0 = MEM_GET32BE(p1); uint32_t b0 = MEM_GET32BE(p2); uint32_t a1 = MEM_GET32BE(p1 + size - 4); uint32_t b1 = MEM_GET32BE(p2 + size - 4); + // use a0/b0 if they are not equal, otherwise a1/b1 uint32_t a = (a0 != b0 ? a0 : a1); uint32_t b = (a0 != b0 ? b0 : b1); + // big-endian numbers can be compared directly return (a > b) - (a < b); } else if (size <= 16) // 8 <= size <= 16 { + // load two pairs of 8 overlapping bytes, in big-endian uint64_t a0 = MEM_GET64BE(p1); uint64_t b0 = MEM_GET64BE(p2); uint64_t a1 = MEM_GET64BE(p1 + size - 8); uint64_t b1 = MEM_GET64BE(p2 + size - 8); + // use a0/b0 if they are not equal, otherwise a1/b1 uint64_t a = (a0 != b0 ? a0 : a1); uint64_t b = (a0 != b0 ? b0 : b1); + // big-endian numbers can be compared directly return (a > b) - (a < b); } else // 16 < size <= 32 { + // load two pairs of 16 overlapping bytes __m128i a0 = _mm_loadu_si128((const __m128i*)p1); __m128i b0 = _mm_loadu_si128((const __m128i*)p2); - __m128i a1 = _mm_loadu_si128((const __m128i*)(p1 + size - 16)); - __m128i b1 = _mm_loadu_si128((const __m128i*)(p2 + size - 16)); + __m128i a1 = _mm_loadu_si128((const __m128i*)(p1 + size - 0x10)); + __m128i b1 = _mm_loadu_si128((const __m128i*)(p2 + size - 0x10)); + + // combine them into 32-byte registers __m256i a = _mm256_inserti128_si256(_mm256_castsi128_si256(a0), a1, 1); __m256i b = _mm256_inserti128_si256(_mm256_castsi128_si256(b0), b1, 1); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r = _mm256_cmpeq_epi8(a, b); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r); - if (mask) - { - size_t index = _tzcnt_u64(mask); + // extract top bit masks, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint32_t)_mm256_movemask_epi8(r); - // index = (index < 16) ? index : (index - 16) + (size - 16); - size -= 32; - index += index < 16 ? 0 : size; + // get index of byte that's different, evaluates to 32 if all bytes are equal + size_t index = _tzcnt_u32(m); - return p1[index] - p2[index]; - } + // adjust index to correct byte position (due to how they were packed with _mm256_inserti128_si256) + // index = (index < 16) ? index : (index - 16) + (size - 16); + index += (index >= 16) * (size - 32); - return 0; + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } } + // process 128-byte blocks as much as possible while (size >= 128) { __m256i a0 = _mm256_loadu_si256((const __m256i*)(p1 + 0x00)); @@ -990,38 +1296,39 @@ int MemCompare_avx2(const void* ptr1, const void* ptr2, size_t size) __m256i b2 = _mm256_loadu_si256((const __m256i*)(p2 + 0x40)); __m256i a3 = _mm256_loadu_si256((const __m256i*)(p1 + 0x60)); __m256i b3 = _mm256_loadu_si256((const __m256i*)(p2 + 0x60)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); __m256i r1 = _mm256_cmpeq_epi8(a1, b1); __m256i r2 = _mm256_cmpeq_epi8(a2, b2); __m256i r3 = _mm256_cmpeq_epi8(a3, b3); + + // combine comparisons - leave 0x00 in all lanes that were not equal __m256i r = _mm256_and_si256(_mm256_and_si256(r0, r1), _mm256_and_si256(r2, r3)); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t mask = 1U + (uint32_t)_mm256_movemask_epi8(r); if (mask) { - mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = 0x00 + _tzcnt_u32(mask); - return p1[index] - p2[index]; - } + // extract top bit masks from individual comparisons + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); + uint64_t m2 = (uint32_t)_mm256_movemask_epi8(r2); + uint64_t m3 = (uint32_t)_mm256_movemask_epi8(r3); - mask = 1 + (uint32_t)_mm256_movemask_epi8(r1); - if (mask) - { - size_t index = 0x20 + _tzcnt_u32(mask); - return p1[index] - p2[index]; - } + // combine masks, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m01 = 1ULL + (m0 | (m1 << 32)); + uint64_t m23 = 1ULL + (m2 | (m3 << 32)); - mask = 1 + (uint32_t)_mm256_movemask_epi8(r2); - if (mask) - { - size_t index = 0x40 + _tzcnt_u32(mask); - return p1[index] - p2[index]; - } + // find index of byte with difference + size_t idx0 = _tzcnt_u64(m01); + size_t idx1 = _tzcnt_u64(m23); + + // combine both indices to actual index across both comparisons + size_t index = 0; + index += idx0; + index += m01 ? 0 : idx1; - mask = 1 + (uint32_t)_mm256_movemask_epi8(r3); - size_t index = 0x60 + _tzcnt_u32(mask); return p1[index] - p2[index]; } @@ -1032,65 +1339,90 @@ int MemCompare_avx2(const void* ptr1, const void* ptr2, size_t size) if (size & 64) // 64 <= size < 128 { - __m256i a0 = _mm256_loadu_si256((const __m256i*)p1); - __m256i b0 = _mm256_loadu_si256((const __m256i*)p2); - __m256i r0 = _mm256_cmpeq_epi8(a0, b0); - - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask); - return p1[index] - p2[index]; - } - + // load 128 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p1 + 0x00)); + __m256i b0 = _mm256_loadu_si256((const __m256i*)(p2 + 0x00)); __m256i a1 = _mm256_loadu_si256((const __m256i*)(p1 + 0x20)); __m256i b1 = _mm256_loadu_si256((const __m256i*)(p2 + 0x20)); + __m256i a2 = _mm256_loadu_si256((const __m256i*)(p1 + size - 0x40)); + __m256i b2 = _mm256_loadu_si256((const __m256i*)(p2 + size - 0x40)); + __m256i a3 = _mm256_loadu_si256((const __m256i*)(p1 + size - 0x20)); + __m256i b3 = _mm256_loadu_si256((const __m256i*)(p2 + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m256i r0 = _mm256_cmpeq_epi8(a0, b0); __m256i r1 = _mm256_cmpeq_epi8(a1, b1); + __m256i r2 = _mm256_cmpeq_epi8(a2, b2); + __m256i r3 = _mm256_cmpeq_epi8(a3, b3); - mask = 1 + (uint32_t)_mm256_movemask_epi8(r1); - if (mask) - { - size_t index = 0x20 + _tzcnt_u32(mask); - return p1[index] - p2[index]; - } + // extract top bit masks + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); + uint64_t m2 = (uint32_t)_mm256_movemask_epi8(r2); + uint64_t m3 = (uint32_t)_mm256_movemask_epi8(r3); - size -= 64; - p1 += 64; - p2 += 64; + // combine masks, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m01 = 1ULL + (m0 | (m1 << 32)); + uint64_t m23 = 1ULL + (m2 | (m3 << 32)); + + // get index of byte with difference, plus adjust due to overlap + size_t idx0 = _tzcnt_u64(m01); + size_t idx1 = _tzcnt_u64(m23) + (size - 64) - 64; // 64 will be already in idx0 + + // combine both indices to actual index across both comparisons + size_t index = 0; + index += idx0; + index += m01 ? 0 : idx1; + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } - - if (size & 32) // 32 <= size < 64 + else if (size & 32) // 32 <= size < 64 { + // load 64 bytes, some will overlap __m256i a0 = _mm256_loadu_si256((const __m256i*)p1); __m256i b0 = _mm256_loadu_si256((const __m256i*)p2); + __m256i a1 = _mm256_loadu_si256((const __m256i*)(p1 + size - 0x20)); + __m256i b1 = _mm256_loadu_si256((const __m256i*)(p2 + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); + __m256i r1 = _mm256_cmpeq_epi8(a1, b1); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask); - return p1[index] - p2[index]; - } + // extract top bit masks + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); - size -= 32; - p1 += 32; - p2 += 32; + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m = 1ULL + (m0 | (m1 << (size - 32))); + + // get index of byte that's different + size_t index = _tzcnt_u64(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } - - if (size) // size < 32 + else if (size) // 0 < size < 32, but initially size > 32 { - __m256i a0 = _mm256_loadu_si256((const __m256i*)(p1 + size - 32)); - __m256i b0 = _mm256_loadu_si256((const __m256i*)(p2 + size - 32)); + // load 32 bytes from end of buffer + // this will load previously checked bytes in 128 byte loop (they were equal) + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p1 + size - 0x20)); + __m256i b0 = _mm256_loadu_si256((const __m256i*)(p2 + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask) + size - 32; - return p1[index] - p2[index]; - } + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint32_t)_mm256_movemask_epi8(r0); + + // get index of byte that's different + size_t index = _tzcnt_u32(m) + size - 32; + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } + // no differences found, inputs are equal return 0; } @@ -1110,30 +1442,39 @@ int MemCompareI_avx2(const void* ptr1, const void* ptr2, size_t size) { const uint32_t PAGE_SIZE = 4096; + // if 32 bytes from each pointer does not cross page boundary, can safely load them as 16-byte vector uint32_t address = (uint32_t)(uintptr_t)p1 | (uint32_t)(uintptr_t)p2; if ((address & (PAGE_SIZE - 1)) <= PAGE_SIZE - 32) { + // load bytes and convert them to lowercase __m256i a0 = MemToLower32(_mm256_loadu_si256((const __m256i*)p1)); __m256i b0 = MemToLower32(_mm256_loadu_si256((const __m256i*)p2)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); - uint32_t mask = _bzhi_u32(1 + (uint32_t)_mm256_movemask_epi8(r0), (uint32_t)size); - if (mask) - { - size_t index = _tzcnt_u32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint32_t)_mm256_movemask_epi8(r0); - return 0; + // get index of byte that's different, evaluates to 32 if all bytes are equal + size_t index = _tzcnt_u32(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } + // cannot overread buffers, need to load exactly "size" bytes only + if (size < 2) // size == 1 { return MemToLower1(p1[0]) - MemToLower1(p2[0]); } - size_t n; + // will load pair of 4, 8, 16 or 32 overlapping bytes + // a/b0 from beginning of buffer + // a/b1 from end of buffers __m128i a0, b0, a1, b1; + size_t n; if (size < 4) // 2 <= size < 4 { @@ -1168,25 +1509,30 @@ int MemCompareI_avx2(const void* ptr1, const void* ptr2, size_t size) n = 16; } + // pack bytes into 16-byte simd register + // a/b0 goes into low 128-bit part + // a/b1 goes into high 128-bit part __m256i a = _mm256_inserti128_si256(_mm256_castsi128_si256(a0), a1, 1); __m256i b = _mm256_inserti128_si256(_mm256_castsi128_si256(b0), b1, 1); + + // lowercase, and set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r = _mm256_cmpeq_epi8(MemToLower32(a), MemToLower32(b)); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r); - if (mask) - { - size_t index = _tzcnt_u64(mask); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint32_t)_mm256_movemask_epi8(r); - // index = (index < 16) ? index : (index - 16) + (size - n); - size -= 16 + n; - index += index < 16 ? 0 : size; + // get index of byte that's different, evaluates to 32 if all bytes are equal + size_t index = _tzcnt_u32(m); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // adjust index to correct byte position (due to how they were packed with _mm256_inserti128_si256) + // index = (index < 16) ? index : (index - 16) + (size - n); + index += (index >= 16) * (size - 16 - n); - return 0; + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } + // process 128-byte blocks as much as possible while (size >= 128) { __m256i a0 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + 0x00))); @@ -1197,38 +1543,39 @@ int MemCompareI_avx2(const void* ptr1, const void* ptr2, size_t size) __m256i b2 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + 0x40))); __m256i a3 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + 0x60))); __m256i b3 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + 0x60))); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); __m256i r1 = _mm256_cmpeq_epi8(a1, b1); __m256i r2 = _mm256_cmpeq_epi8(a2, b2); __m256i r3 = _mm256_cmpeq_epi8(a3, b3); + + // combine comparisons - leave 0x00 in lanes that were not equal __m256i r = _mm256_and_si256(_mm256_and_si256(r0, r1), _mm256_and_si256(r2, r3)); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t mask = 1U + (uint32_t)_mm256_movemask_epi8(r); if (mask) { - mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = 0x00 + _tzcnt_u32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit masks for each comparison + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); + uint64_t m2 = (uint32_t)_mm256_movemask_epi8(r2); + uint64_t m3 = (uint32_t)_mm256_movemask_epi8(r3); - mask = 1 + (uint32_t)_mm256_movemask_epi8(r1); - if (mask) - { - size_t index = 0x20 + _tzcnt_u32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // combine masks, and flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m01 = 1ULL + (m0 | (m1 << 32)); + uint64_t m23 = 1ULL + (m2 | (m3 << 32)); - mask = 1 + (uint32_t)_mm256_movemask_epi8(r2); - if (mask) - { - size_t index = 0x40 + _tzcnt_u32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // find index of byte with difference + size_t idx0 = _tzcnt_u64(m01); + size_t idx1 = _tzcnt_u64(m23); + + // combine both indices to actual index across both comparisons + size_t index = 0; + index += idx0; + index += m01 ? 0 : idx1; - mask = 1 + (uint32_t)_mm256_movemask_epi8(r3); - size_t index = 0x60 + _tzcnt_u32(mask); return MemToLower1(p1[index]) - MemToLower1(p2[index]); } @@ -1239,65 +1586,90 @@ int MemCompareI_avx2(const void* ptr1, const void* ptr2, size_t size) if (size & 64) // 64 <= size < 128 { - __m256i a0 = MemToLower32(_mm256_loadu_si256((const __m256i*)p1)); - __m256i b0 = MemToLower32(_mm256_loadu_si256((const __m256i*)p2)); - __m256i r0 = _mm256_cmpeq_epi8(a0, b0); - - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } - + // load 128 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m256i a0 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + 0x00))); + __m256i b0 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + 0x00))); __m256i a1 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + 0x20))); __m256i b1 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + 0x20))); + __m256i a2 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + size - 0x40))); + __m256i b2 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + size - 0x40))); + __m256i a3 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + size - 0x20))); + __m256i b3 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + size - 0x20))); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m256i r0 = _mm256_cmpeq_epi8(a0, b0); __m256i r1 = _mm256_cmpeq_epi8(a1, b1); + __m256i r2 = _mm256_cmpeq_epi8(a2, b2); + __m256i r3 = _mm256_cmpeq_epi8(a3, b3); - mask = 1 + (uint32_t)_mm256_movemask_epi8(r1); - if (mask) - { - size_t index = 0x20 + _tzcnt_u32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit masks + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); + uint64_t m2 = (uint32_t)_mm256_movemask_epi8(r2); + uint64_t m3 = (uint32_t)_mm256_movemask_epi8(r3); - size -= 64; - p1 += 64; - p2 += 64; + // combine masks, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m01 = 1ULL + (m0 | (m1 << 32)); + uint64_t m23 = 1ULL + (m2 | (m3 << 32)); + + // get index of byte with difference, plus adjust due to overlap + size_t idx0 = _tzcnt_u64(m01); + size_t idx1 = _tzcnt_u64(m23) + (size - 64) - 64; // 64 will be already in idx0 + + // combine both indices to actual index across both comparisons + size_t index = 0; + index += idx0; + index += m01 ? 0 : idx1; + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } - - if (size & 32) // 32 <= size < 64 + else if (size & 32) // 32 <= size < 64 { + // load 64 bytes, some will overlap __m256i a0 = MemToLower32(_mm256_loadu_si256((const __m256i*)p1)); __m256i b0 = MemToLower32(_mm256_loadu_si256((const __m256i*)p2)); + __m256i a1 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + size - 0x20))); + __m256i b1 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + size - 0x20))); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); + __m256i r1 = _mm256_cmpeq_epi8(a1, b1); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask); - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit masks + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); - size -= 32; - p1 += 32; - p2 += 32; + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m = 1ULL + (m0 | (m1 << (size - 32))); + + // get index of byte that's different + size_t index = _tzcnt_u64(m); + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } - - if (size) // size < 32 + else if (size) // 0 < size < 32, but initially size > 32 { - __m256i a0 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + size - 32))); - __m256i b0 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + size - 32))); + // load 32 bytes from end of buffer + // this will load previously checked bytes in 128 byte loop (they were equal) + __m256i a0 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p1 + size - 0x20))); + __m256i b0 = MemToLower32(_mm256_loadu_si256((const __m256i*)(p2 + size - 0x20))); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask) + size - 32; - return MemToLower1(p1[index]) - MemToLower1(p2[index]); - } + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t m = 1U + (uint32_t)_mm256_movemask_epi8(r0); + + // get index of byte that's different + size_t index = _tzcnt_u32(m) + size - 32; + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } + // no differences found, inputs are equal return 0; } @@ -1317,58 +1689,78 @@ bool MemIsEqual_avx2(const void* ptr1, const void* ptr2, size_t size) { const uint32_t PAGE_SIZE = 4096; + // if 32 bytes from each pointer does not cross page boundary, can safely load them as 32-byte vector uint32_t address = (uint32_t)(uintptr_t)p1 | (uint32_t)(uintptr_t)p2; if ((address & (PAGE_SIZE - 1)) <= PAGE_SIZE - 32) { __m256i a0 = _mm256_loadu_si256((const __m256i*)p1); __m256i b0 = _mm256_loadu_si256((const __m256i*)p2); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(a0, b0); - uint32_t mask = 1 + (uint32_t)_mm256_movemask_epi8(r0); + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t mask = 1U + (uint32_t)_mm256_movemask_epi8(r0); + + // need to ignore top "32 - size" bits, only low bits matter + // return "true" if they are zero, otherwise "false" return _bzhi_u32(mask, (uint32_t)size) == 0; } + // cannot overread buffers, need to load exactly "size" bytes only + if (size < 2) // size == 1 { return p1[0] == p2[0]; } else if (size < 4) // 2 <= size < 4 { + // load two pairs of 2 overlapping bytes uint32_t a0 = MEM_PTR16U(p1); uint32_t b0 = MEM_PTR16U(p2); uint32_t a1 = MEM_PTR16U(p1 + size - 2); uint32_t b1 = MEM_PTR16U(p2 + size - 2); + // return "true" if both are equal return (a0 == b0) & (a1 == b1); } else if (size < 8) // 4 <= size < 8 { + // load two pairs of 4 overlapping bytes uint32_t a0 = MEM_PTR32U(p1); uint32_t b0 = MEM_PTR32U(p2); uint32_t a1 = MEM_PTR32U(p1 + size - 4); uint32_t b1 = MEM_PTR32U(p2 + size - 4); + // return "true" if both are equal return (a0 == b0) & (a1 == b1); } else if (size <= 16) // 8 <= size <= 16 { + // load two pairs of 8 overlapping bytes uint64_t a0 = MEM_PTR64U(p1); uint64_t b0 = MEM_PTR64U(p2); uint64_t a1 = MEM_PTR64U(p1 + size - 8); uint64_t b1 = MEM_PTR64U(p2 + size - 8); + // return "true" if both are equal return (a0 == b0) & (a1 == b1); } else // 16 < size <= 32 { + // load two pairs of 16 overlapping bytes __m128i a0 = _mm_loadu_si128((const __m128i*)p1); __m128i b0 = _mm_loadu_si128((const __m128i*)p2); __m128i a1 = _mm_loadu_si128((const __m128i*)(p1 + size - 16)); __m128i b1 = _mm_loadu_si128((const __m128i*)(p2 + size - 16)); + // combine them into simd register (low lane & high lane) __m256i a = _mm256_inserti128_si256(_mm256_castsi128_si256(a1), a0, 1); __m256i b = _mm256_inserti128_si256(_mm256_castsi128_si256(b1), b0, 1); + // produce 0 if all of them are equal __m256i r = _mm256_xor_si256(a, b); + // return true, if whole simd register is 0 return !!_mm256_testz_si256(r, r); } } + // process 128-byte blocks as much as possible while (size >= 128) { __m256i a0 = _mm256_loadu_si256((const __m256i*)(p1 + 0x00)); @@ -1379,14 +1771,18 @@ bool MemIsEqual_avx2(const void* ptr1, const void* ptr2, size_t size) __m256i b2 = _mm256_loadu_si256((const __m256i*)(p2 + 0x40)); __m256i a3 = _mm256_loadu_si256((const __m256i*)(p1 + 0x60)); __m256i b3 = _mm256_loadu_si256((const __m256i*)(p2 + 0x60)); + + // produce 0 if all of them are equal __m256i r0 = _mm256_xor_si256(a0, b0); __m256i r1 = _mm256_xor_si256(a1, b1); __m256i r2 = _mm256_xor_si256(a2, b2); __m256i r3 = _mm256_xor_si256(a3, b3); - __m256i r = _mm256_or_si256(_mm256_or_si256(r0, r1), _mm256_or_si256(r2, r3)); + // combine results - leave non-zero value in any lane if they were not equal + __m256i r = _mm256_or_si256(_mm256_or_si256(r0, r1), _mm256_or_si256(r2, r3)); if (!_mm256_testz_si256(r, r)) { + // if simd register is not fully 0, then inputs are not equal return false; } @@ -1397,52 +1793,61 @@ bool MemIsEqual_avx2(const void* ptr1, const void* ptr2, size_t size) if (size & 64) // 64 <= size < 128 { - __m256i a0 = _mm256_loadu_si256((const __m256i*)p1); - __m256i b0 = _mm256_loadu_si256((const __m256i*)p2); + // load 128 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p1 + 0x00)); + __m256i b0 = _mm256_loadu_si256((const __m256i*)(p2 + 0x00)); __m256i a1 = _mm256_loadu_si256((const __m256i*)(p1 + 0x20)); __m256i b1 = _mm256_loadu_si256((const __m256i*)(p2 + 0x20)); + __m256i a2 = _mm256_loadu_si256((const __m256i*)(p1 + size - 0x40)); + __m256i b2 = _mm256_loadu_si256((const __m256i*)(p2 + size - 0x40)); + __m256i a3 = _mm256_loadu_si256((const __m256i*)(p1 + size - 0x20)); + __m256i b3 = _mm256_loadu_si256((const __m256i*)(p2 + size - 0x20)); + + // produce 0 if all of them are equal __m256i r0 = _mm256_xor_si256(a0, b0); __m256i r1 = _mm256_xor_si256(a1, b1); - __m256i r = _mm256_or_si256(r0, r1); + __m256i r2 = _mm256_xor_si256(a2, b2); + __m256i r3 = _mm256_xor_si256(a3, b3); - if (!_mm256_testz_si256(r, r)) - { - return false; - } + // combine results - leave non-zero value in any lane if they were not equal + __m256i r = _mm256_or_si256(_mm256_or_si256(r0, r1), _mm256_or_si256(r2, r3)); - size -= 64; - p1 += 64; - p2 += 64; + // return true, if simd register is fully 0, meaning inputs are equal + return !!_mm256_testz_si256(r, r); } - - if (size & 32) // 32 <= size < 64 + else if (size & 32) // 32 <= size < 64 { + // load 64 bytes, some will overlap __m256i a0 = _mm256_loadu_si256((const __m256i*)p1); __m256i b0 = _mm256_loadu_si256((const __m256i*)p2); + __m256i a1 = _mm256_loadu_si256((const __m256i*)(p1 + size - 0x20)); + __m256i b1 = _mm256_loadu_si256((const __m256i*)(p2 + size - 0x20)); + + // produce 0 if all of them are equal __m256i r0 = _mm256_xor_si256(a0, b0); + __m256i r1 = _mm256_xor_si256(a1, b1); - if (!_mm256_testz_si256(r0, r0)) - { - return false; - } + // combine results - leave non-zero value in any lane if they were not equal + __m256i r = _mm256_or_si256(r0, r1); - size -= 32; - p1 += 32; - p2 += 32; + // return true, if simd register is fully 0, meaning inputs are equal + return !!_mm256_testz_si256(r, r); } - - if (size) // size < 32 + else if (size) // 0 < size < 32, but initially size > 32 { - __m256i a0 = _mm256_loadu_si256((const __m256i*)(p1 + size - 32)); - __m256i b0 = _mm256_loadu_si256((const __m256i*)(p2 + size - 32)); + // load 64 bytes, some will overlap + // this will load previously checked bytes in 128 byte loop (they were equal) + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p1 + size - 0x20)); + __m256i b0 = _mm256_loadu_si256((const __m256i*)(p2 + size - 0x20)); + + // produce 0 if all of them are equal __m256i r0 = _mm256_xor_si256(a0, b0); - if (!_mm256_testz_si256(r0, r0)) - { - return false; - } + // return true, if simd register is fully 0, meaning inputs are equal + return !!_mm256_testz_si256(r0, r0); } + // no differences found, inputs are equal return true; } @@ -1451,6 +1856,7 @@ MEM_TARGET_AVX2 size_t MemFind_avx2(const void* ptr, size_t size, uint8_t value) { const uint8_t* p = (const uint8_t*)ptr; + __m256i value32 = _mm256_set1_epi8((char)value); if (size == 0) @@ -1463,42 +1869,62 @@ size_t MemFind_avx2(const void* ptr, size_t size, uint8_t value) size_t address = (uint32_t)(uintptr_t)p % 32; size_t extra = (address + size) <= 32 ? address : 0; + // will load before the beginning buffer (32-byte aligned) if end is too close + // to 32-byte boundary, otherwise will load past the end of buffer __m256i a0 = _mm256_loadu_si256((const __m256i*)(p - extra)); + + // set lane to 0xff if lane matches input value, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(value32, a0); - uint32_t mask = (uint32_t)_mm256_movemask_epi8(r0); - mask = _bzhi_u32(MEM_SHRX_32(mask, (uint32_t)extra), (uint32_t)size); + // drop any lowest "extra" bits (due to loading bytes before beginning buffer) + uint32_t m = MEM_SHRX_32((uint32_t)_mm256_movemask_epi8(r0), (uint32_t)extra); - return mask ? _tzcnt_u32(mask) : size; + // mask out high bits (due to loading bytes after end of buffer) + // this will result in returning "size" value if all bytes are same as input value + m |= (uint32_t)(1ULL << size); + + // return index of first bit set, which will be index of first byte different from input value + return _tzcnt_u32(m); } size_t offset = 0; + + // process 128-byte blocks as much as possible while (size >= 128) { __m256i a0 = _mm256_loadu_si256((const __m256i*)(p + 0x00)); __m256i a1 = _mm256_loadu_si256((const __m256i*)(p + 0x20)); __m256i a2 = _mm256_loadu_si256((const __m256i*)(p + 0x40)); __m256i a3 = _mm256_loadu_si256((const __m256i*)(p + 0x60)); + + // set lane to 0xff if lane matches input value, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(value32, a0); __m256i r1 = _mm256_cmpeq_epi8(value32, a1); __m256i r2 = _mm256_cmpeq_epi8(value32, a2); __m256i r3 = _mm256_cmpeq_epi8(value32, a3); + + // combine comparisons - leave 0xff in lanes there equal to input value __m256i r = _mm256_or_si256(_mm256_or_si256(r0, r1), _mm256_or_si256(r2, r3)); + // extract top bit mask, it will be non-zero if there is at least one matching lane to input value uint32_t mask = (uint32_t)_mm256_movemask_epi8(r); if (mask) { + // extract top bit masks for each comparison uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); uint64_t m2 = (uint32_t)_mm256_movemask_epi8(r2); uint64_t m3 = mask; // if r0=r1=r2=0, then r3=r + // combine masks uint64_t m01 = m0 | (m1 << 32); uint64_t m23 = m2 | (m3 << 32); + // find index of byte with difference size_t idx0 = _tzcnt_u64(m01); size_t idx1 = _tzcnt_u64(m23); + // combine both indices to actual index across both comparisons offset += idx0; offset += m01 ? 0 : idx1; return offset; @@ -1511,64 +1937,236 @@ size_t MemFind_avx2(const void* ptr, size_t size, uint8_t value) if (size & 64) // 64 <= size < 128 { - __m256i a0 = _mm256_loadu_si256((const __m256i*)p); - __m256i r0 = _mm256_cmpeq_epi8(value32, a0); - - uint32_t mask = (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask); - return offset + index; - } - + // load 128 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p + 0x00)); __m256i a1 = _mm256_loadu_si256((const __m256i*)(p + 0x20)); + __m256i a2 = _mm256_loadu_si256((const __m256i*)(p + size - 0x40)); + __m256i a3 = _mm256_loadu_si256((const __m256i*)(p + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m256i r0 = _mm256_cmpeq_epi8(value32, a0); + __m256i r1 = _mm256_cmpeq_epi8(value32, a1); + __m256i r2 = _mm256_cmpeq_epi8(value32, a2); + __m256i r3 = _mm256_cmpeq_epi8(value32, a3); + + // extract top bit masks for each comparison + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); + uint64_t m2 = (uint32_t)_mm256_movemask_epi8(r2); + uint64_t m3 = (uint32_t)_mm256_movemask_epi8(r3); + + // combine masks + uint64_t m01 = m0 | (m1 << 32); + uint64_t m23 = m2 | (m3 << 32); + + // get index of byte with difference, plus adjust due to overlap + size_t idx0 = _tzcnt_u64(m01); + size_t idx1 = _tzcnt_u64(m23) + (size - 64) - 64; // 64 will be already in idx0 + + // combine both indices to actual index across both comparisons + offset += idx0; + offset += m01 ? 0 : idx1; + return offset; + } + else if (size & 32) // 32 <= size < 64 + { + // load 64 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + __m256i a0 = _mm256_loadu_si256((const __m256i*)p); + __m256i a1 = _mm256_loadu_si256((const __m256i*)(p + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m256i r0 = _mm256_cmpeq_epi8(value32, a0); __m256i r1 = _mm256_cmpeq_epi8(value32, a1); - mask = (uint32_t)_mm256_movemask_epi8(r1); - if (mask) - { - size_t index = 0x20 + _tzcnt_u32(mask); - return offset + index; - } + // extract top bit masks for each comparison + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); - offset += 64; - size -= 64; - p += 64; + // combine masks, handling overlapped ones + uint64_t m = m0 | (m1 << (size - 32)); + + // make sure mask is non-zero, this will result in returning "size" value if inputs are equal + m |= 1ULL << size; + + // find first bit set, and return index + return offset + _tzcnt_u64(m); + } + else if (size) // 0 < size < 32, but initially size > 32 + { + // load 32 bytes from end of buffer + // this will load previously checked bytes in 128 byte loop (they did not match input value) + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m256i r0 = _mm256_cmpeq_epi8(value32, a0); + + // extract top bit mask + uint32_t m = (uint32_t)_mm256_movemask_epi8(r0); + + // find first bit set, adjust it due to reused bytes in load, and return index + return offset + _tzcnt_u32(m) + size - 32; } - if (size & 32) // 32 <= size < 64 + // no input value found, return original size (current offset plus pending tail size) + return offset + size; +} + +MEM_DISABLE_ASAN +MEM_TARGET_AVX2 +size_t MemFindNot_avx2(const void* ptr, size_t size, uint8_t value) +{ + const uint8_t* p = (const uint8_t*)ptr; + + __m256i value32 = _mm256_set1_epi8((char)value); + + if (size == 0) { + return 0; + } + + if (size <= 32) + { + size_t address = (uint32_t)(uintptr_t)p % 32; + size_t extra = (address + size) <= 32 ? address : 0; + + // will load before the beginning buffer (32-byte aligned) if end is too close + // to 32-byte boundary, otherwise will load past the end of buffer + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p - extra)); + // set lane to 0xff if lane matches input value, or 0x00 if not + + __m256i r0 = _mm256_cmpeq_epi8(value32, a0); + + // add 1 to flip lowest 0 bit (non-equal position) to 1, changing all bits below it to 0 + // drop any lowest "extra" bits (due to loading bytes before beginning buffer) + uint32_t m = 1U + MEM_SHRX_32((uint32_t)_mm256_movemask_epi8(r0), (uint32_t)extra); + + // mask out high bits (due to loading bytes after end of buffer) + // this will result in returning "size" value if inputs are equal + m |= (uint32_t)(1ULL << size); + + // return index of first bit set, which will be index of first byte matching input value + return _tzcnt_u32(m); + } + + size_t offset = 0; + + // process 128-byte blocks as much as possible + while (size >= 128) + { + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p + 0x00)); + __m256i a1 = _mm256_loadu_si256((const __m256i*)(p + 0x20)); + __m256i a2 = _mm256_loadu_si256((const __m256i*)(p + 0x40)); + __m256i a3 = _mm256_loadu_si256((const __m256i*)(p + 0x60)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m256i r0 = _mm256_cmpeq_epi8(value32, a0); + __m256i r1 = _mm256_cmpeq_epi8(value32, a1); + __m256i r2 = _mm256_cmpeq_epi8(value32, a2); + __m256i r3 = _mm256_cmpeq_epi8(value32, a3); + + // combine comparisons - leave 0x00 in lanes that were not equal + __m256i r = _mm256_and_si256(_mm256_and_si256(r0, r1), _mm256_and_si256(r2, r3)); + + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t mask = 1U + (uint32_t)_mm256_movemask_epi8(r); + if (mask) + { + // extract top bit masks for comparisons + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); + uint64_t m2 = (uint32_t)_mm256_movemask_epi8(r2); + uint64_t m3 = (uint32_t)_mm256_movemask_epi8(r3); + + // combine masks, and flip lowest 0 bit(non - equal position) to 1, changing all bits below it to 0 + uint64_t m01 = 1ULL + (m0 | (m1 << 32)); + uint64_t m23 = 1ULL + (m2 | (m3 << 32)); + + // find index of byte with difference + size_t idx0 = _tzcnt_u64(m01); + size_t idx1 = _tzcnt_u64(m23); + + // combine both indices to actual index across both comparisons + offset += idx0; + offset += m01 ? 0 : idx1; + return offset; + } + + offset += 128; + size -= 128; + p += 128; + } + + if (size & 64) // 64 <= size < 128 + { + // load 128 bytes, some will overlap + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p + 0x00)); + __m256i a1 = _mm256_loadu_si256((const __m256i*)(p + 0x20)); + __m256i a2 = _mm256_loadu_si256((const __m256i*)(p + size - 0x40)); + __m256i a3 = _mm256_loadu_si256((const __m256i*)(p + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not + __m256i r0 = _mm256_cmpeq_epi8(value32, a0); + __m256i r1 = _mm256_cmpeq_epi8(value32, a1); + __m256i r2 = _mm256_cmpeq_epi8(value32, a2); + __m256i r3 = _mm256_cmpeq_epi8(value32, a3); + + // extract top bit masks + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); + uint64_t m2 = (uint32_t)_mm256_movemask_epi8(r2); + uint64_t m3 = (uint32_t)_mm256_movemask_epi8(r3); + + // combine masks, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m01 = 1ULL + (m0 | (m1 << 32)); + uint64_t m23 = 1ULL + (m2 | (m3 << 32)); + + // get index of byte with difference, plus adjust due to overlap + size_t idx0 = _tzcnt_u64(m01); + size_t idx1 = _tzcnt_u64(m23) + (size - 64) - 64; // 64 will be already in idx0 + + // combine both indices to actual index across both comparisons + offset += idx0; + offset += m01 ? 0 : idx1; + return offset; + } + else if (size & 32) // 32 <= size < 64 + { + // load 64 bytes, some will overlap __m256i a0 = _mm256_loadu_si256((const __m256i*)p); + __m256i a1 = _mm256_loadu_si256((const __m256i*)(p + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(value32, a0); + __m256i r1 = _mm256_cmpeq_epi8(value32, a1); - uint32_t mask = (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask); - return offset + index; - } + // extract top bit masks + uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); + uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); - offset += 32; - size -= 32; - p += 32; + // combine masks, handling overlapped ones, flip lowest 0 bit to 1, changing all bits below it to 0 + uint64_t m = 1ULL + (m0 | (m1 << (size - 32))); + + // return index of byte that's different, m is guaranteed non-zero, because there only max 63 bytes here + return offset + _tzcnt_u64(m); } - - if (size) // size < 32 + else if (size) // 0 < size < 32, but initially size > 32 { - __m256i a0 = _mm256_loadu_si256((const __m256i*)(p + size - 32)); + // load 32 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they did match input value) + __m256i a0 = _mm256_loadu_si256((const __m256i*)(p + size - 0x20)); + + // set lanes to 0xff if bytes are equal, or 0x00 if not __m256i r0 = _mm256_cmpeq_epi8(value32, a0); - uint32_t mask = (uint32_t)_mm256_movemask_epi8(r0); - if (mask) - { - size_t index = _tzcnt_u32(mask) + size - 32; - return offset + index; - } + // extract top bit mask, flip lowest 0 bit to 1, changing all bits below it to 0 + uint32_t mask = 1U + (uint32_t)_mm256_movemask_epi8(r0); - offset += size; + // get index of byte that's different, m is guaranteed non-zero, because there only max 31 bytes here + return offset + _tzcnt_u32(mask) + size - 32; } - return offset; + // all bytes are same as input value, return original size (current offset plus pending tail size) + return offset + size; } MEM_TARGET_AVX512 @@ -1577,19 +2175,26 @@ int MemCompare_avx512(const void* ptr1, const void* ptr2, size_t size) const uint8_t* p1 = (const uint8_t*)ptr1; const uint8_t* p2 = (const uint8_t*)ptr2; + // first handle any non-multiple of 64 size, so code later can deal with 64-byte multiple sizes size_t extra = size & 63; if (extra) { + // mask to load "extra" amount of bytes __mmask64 mask = _cvtu64_mask64(_bzhi_u64(~0ULL, (uint32_t)extra)); + // do masked load __m512i a = _mm512_maskz_loadu_epi8(mask, p1); __m512i b = _mm512_maskz_loadu_epi8(mask, p2); + // check if any bytes are different __mmask64 m = _mm512_cmpneq_epu8_mask(a, b); if (!_kortestz_mask64_u8(m, m)) { + // if they are different, then find position of byte that is less than other value int r1 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(a, b))); int r2 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(b, a))); + + // return signed difference which is comparison result return r1 - r2; } @@ -1598,16 +2203,22 @@ int MemCompare_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += extra; } + // now size is multiple of 64 bytes, handle case when it is not 128-byte multiple if (size & 64) { + // 64 byte loads __m512i a = _mm512_loadu_epi8(p1); __m512i b = _mm512_loadu_epi8(p2); + // check if any bytes are different __mmask64 m = _mm512_cmpneq_epu8_mask(a, b); if (!_kortestz_mask64_u8(m, m)) { + // if they are different, then find position of byte that is less than other value int r1 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(a, b))); int r2 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(b, a))); + + // return signed difference which is comparison result return r1 - r2; } @@ -1616,6 +2227,7 @@ int MemCompare_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += 64; } + // now size is 128-byte multiple, process rest of them in 128-byte blocks while (size) { __m512i a0 = _mm512_loadu_epi8(p1 + 0x00); @@ -1623,17 +2235,22 @@ int MemCompare_avx512(const void* ptr1, const void* ptr2, size_t size) __m512i a1 = _mm512_loadu_epi8(p1 + 0x40); __m512i b1 = _mm512_loadu_epi8(p2 + 0x40); + // check if any bytes are different __mmask64 m0 = _mm512_cmpneq_epu8_mask(a0, b0); __mmask64 m1 = _mm512_cmpneq_epu8_mask(a1, b1); if (!_kortestz_mask64_u8(m0, m1)) { + // if they are different, then find position of byte that is less than other value int r10 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(a0, b0))); int r20 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(b0, a0))); int r11 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(a1, b1))); int r21 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(b1, a1))); + // if low 64 bytes are the same, use indices for high 64 bytes int r1 = (r10 == r20) ? r11 : r10; int r2 = (r10 == r20) ? r21 : r20; + + // return signed difference which is comparison result return r1 - r2; } @@ -1642,6 +2259,7 @@ int MemCompare_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += 128; } + // no differences found, inputs are equal return 0; } @@ -1651,19 +2269,26 @@ int MemCompareI_avx512(const void* ptr1, const void* ptr2, size_t size) const uint8_t* p1 = (const uint8_t*)ptr1; const uint8_t* p2 = (const uint8_t*)ptr2; + // first handle any non-multiple of 64 size, so code later can deal with 64-byte multiple sizes size_t extra = size & 63; if (extra) { + // mask to load "extra" amount of bytes __mmask64 mask = _cvtu64_mask64(_bzhi_u64(~0ULL, (uint32_t)extra)); + // do masked load & convert to lowercase __m512i a = MemToLower64(_mm512_maskz_loadu_epi8(mask, p1)); __m512i b = MemToLower64(_mm512_maskz_loadu_epi8(mask, p2)); + // check if any bytes are different __mmask64 m = _mm512_cmpneq_epu8_mask(a, b); if (!_kortestz_mask64_u8(m, m)) { + // if they are different, then find position of byte that is less than other value int r1 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(a, b))); int r2 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(b, a))); + + // return signed difference which is comparison result return r1 - r2; } @@ -1672,16 +2297,22 @@ int MemCompareI_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += extra; } + // now size is multiple of 64 bytes, handle case when it is not 128-byte multiple if (size & 64) { + // 64 byte loads & convert to lowercase __m512i a = MemToLower64(_mm512_loadu_epi8(p1)); __m512i b = MemToLower64(_mm512_loadu_epi8(p2)); + // check if any bytes are different __mmask64 m = _mm512_cmpneq_epu8_mask(a, b); if (!_kortestz_mask64_u8(m, m)) { + // if they are different, then find position of byte that is less than other value int r1 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(a, b))); int r2 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(b, a))); + + // return signed difference which is comparison result return r1 - r2; } @@ -1690,6 +2321,7 @@ int MemCompareI_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += 64; } + // now size is 128-byte multiple, process rest of them in 128-byte blocks while (size) { __m512i a0 = MemToLower64(_mm512_loadu_epi8(p1 + 0x00)); @@ -1697,17 +2329,22 @@ int MemCompareI_avx512(const void* ptr1, const void* ptr2, size_t size) __m512i a1 = MemToLower64(_mm512_loadu_epi8(p1 + 0x40)); __m512i b1 = MemToLower64(_mm512_loadu_epi8(p2 + 0x40)); + // check if any bytes are different __mmask64 m0 = _mm512_cmpneq_epu8_mask(a0, b0); __mmask64 m1 = _mm512_cmpneq_epu8_mask(a1, b1); if (!_kortestz_mask64_u8(m0, m1)) { + // if they are different, then find position of byte that is less than other value int r10 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(a0, b0))); int r20 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(b0, a0))); int r11 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(a1, b1))); int r21 = (int)_tzcnt_u64(_cvtmask64_u64(_mm512_cmplt_epu8_mask(b1, a1))); + // if low 64 bytes are the same, use indices for high 64 bytes int r1 = (r10 == r20) ? r11 : r10; int r2 = (r10 == r20) ? r21 : r20; + + // return signed difference which is comparison result return r1 - r2; } @@ -1716,6 +2353,7 @@ int MemCompareI_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += 128; } + // no differences found, inputs are equal return 0; } @@ -1725,17 +2363,22 @@ bool MemIsEqual_avx512(const void* ptr1, const void* ptr2, size_t size) const uint8_t* p1 = (const uint8_t*)ptr1; const uint8_t* p2 = (const uint8_t*)ptr2; + // first handle any non-multiple of 64 size, so code later can deal with 64-byte multiple sizes size_t extra = size & 63; if (extra) { + // mask to load "extra" amount of bytes __mmask64 mask = _cvtu64_mask64(_bzhi_u64(~0ULL, (uint32_t)extra)); + // do masked load & convert to lowercase __m512i a = _mm512_maskz_loadu_epi8(mask, p1); __m512i b = _mm512_maskz_loadu_epi8(mask, p2); + // check if any bytes are different __mmask64 m = _mm512_cmpneq_epu8_mask(a, b); if (!_kortestz_mask64_u8(m, m)) { + // they are different return false; } @@ -1744,14 +2387,18 @@ bool MemIsEqual_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += extra; } + // now size is multiple of 64 bytes, handle case when it is not 128-byte multiple if (size & 64) { + // 64 byte loads __m512i a = _mm512_loadu_epi8(p1); __m512i b = _mm512_loadu_epi8(p2); + // check if any bytes are different __mmask64 m = _mm512_cmpneq_epu8_mask(a, b); if (!_kortestz_mask64_u8(m, m)) { + // they are different return false; } @@ -1760,6 +2407,7 @@ bool MemIsEqual_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += 64; } + // now size is 128-byte multiple, process rest of them in 128-byte blocks while (size) { __m512i a0 = _mm512_loadu_epi8(p1 + 0x00); @@ -1767,11 +2415,12 @@ bool MemIsEqual_avx512(const void* ptr1, const void* ptr2, size_t size) __m512i a1 = _mm512_loadu_epi8(p1 + 0x40); __m512i b1 = _mm512_loadu_epi8(p2 + 0x40); + // check if any bytes are different __mmask64 m0 = _mm512_cmpneq_epu8_mask(a0, b0); __mmask64 m1 = _mm512_cmpneq_epu8_mask(a1, b1); - if (!_kortestz_mask64_u8(m0, m1)) { + // they are different return false; } @@ -1780,6 +2429,7 @@ bool MemIsEqual_avx512(const void* ptr1, const void* ptr2, size_t size) p2 += 128; } + // no differences found, inputs are equal return true; } @@ -1791,16 +2441,21 @@ size_t MemFind_avx512(const void* ptr, size_t size, uint8_t value) size_t offset = 0; + // first handle any non-multiple of 64 size, so code later can deal with 64-byte multiple sizes size_t extra = size & 63; if (extra) { + // mask to load "extra" amount of bytes __mmask64 mask = _cvtu64_mask64(_bzhi_u64(~0ULL, (uint32_t)extra)); - __m512i a = _mm512_mask_loadu_epi8(_mm512_set1_epi8((char)~value), mask, p); + // do masked load, zeroing out upper bytes + __m512i a = _mm512_maskz_loadu_epi8(mask, p); - __mmask64 m = _mm512_cmpeq_epu8_mask(value64, a); + // check if any bytes matches input value, only low "extra" bytes + __mmask64 m = _mm512_mask_cmpeq_epu8_mask(mask, value64, a); if (!_kortestz_mask64_u8(m, m)) { + // if it does, return index of lowest byte that matches return (size_t)_tzcnt_u64(_cvtmask64_u64(m)); } @@ -1809,13 +2464,17 @@ size_t MemFind_avx512(const void* ptr, size_t size, uint8_t value) p += extra; } + // now size is multiple of 64 bytes, handle case when it is not 128-byte multiple if (size & 64) { + // 64 byte load __m512i a = _mm512_loadu_epi8(p); + // check if any bytes matches input value __mmask64 m = _mm512_cmpeq_epu8_mask(value64, a); if (!_kortestz_mask64_u8(m, m)) { + // if it does, return index of lowest byte that matches return offset + (size_t)_tzcnt_u64(_cvtmask64_u64(m)); } @@ -1824,19 +2483,22 @@ size_t MemFind_avx512(const void* ptr, size_t size, uint8_t value) p += 64; } + // now size is 128-byte multiple, process rest of them in 128-byte blocks while (size) { __m512i a0 = _mm512_loadu_epi8(p + 0x00); __m512i a1 = _mm512_loadu_epi8(p + 0x40); + // check if any bytes matches input value __mmask64 m0 = _mm512_cmpeq_epu8_mask(value64, a0); __mmask64 m1 = _mm512_cmpeq_epu8_mask(value64, a1); - if (!_kortestz_mask64_u8(m0, m1)) { + // if it does, get index of lowest byte that matches size_t r0 = _tzcnt_u64(_cvtmask64_u64(m0)); size_t r1 = _tzcnt_u64(_cvtmask64_u64(m1)); + // combine both indices to actual index across both comparisons offset += r0; offset += r0 == 64 ? r1 : 0; return offset; @@ -1847,6 +2509,87 @@ size_t MemFind_avx512(const void* ptr, size_t size, uint8_t value) p += 128; } + // no input value found + return offset; +} + +MEM_TARGET_AVX512 +size_t MemFindNot_avx512(const void* ptr, size_t size, uint8_t value) +{ + const uint8_t* p = (const uint8_t*)ptr; + const __m512i value64 = _mm512_set1_epi8((char)value); + + size_t offset = 0; + + // first handle any non-multiple of 64 size, so code later can deal with 64-byte multiple sizes + size_t extra = size & 63; + if (extra) + { + // mask to load "extra" amount of bytes + __mmask64 mask = _cvtu64_mask64(_bzhi_u64(~0ULL, (uint32_t)extra)); + + // do masked load, zeroing out upper bytes + __m512i a = _mm512_maskz_loadu_epi8(mask, p); + + // check if any bytes are different, only low "extra" bytes + __mmask64 m = _mm512_mask_cmpneq_epu8_mask(mask, value64, a); + if (!_kortestz_mask64_u8(m, m)) + { + // if they are, return index of lowest byte that does not match + return (size_t)_tzcnt_u64(_cvtmask64_u64(m)); + } + + offset += extra; + size -= extra; + p += extra; + } + + // now size is multiple of 64 bytes, handle case when it is not 128-byte multiple + if (size & 64) + { + // 64 byte load + __m512i a = _mm512_loadu_epi8(p); + + // check if any bytes are different + __mmask64 m = _mm512_cmpneq_epu8_mask(value64, a); + if (!_kortestz_mask64_u8(m, m)) + { + // if they are, return index of lowest byte that does not match + return offset + (size_t)_tzcnt_u64(_cvtmask64_u64(m)); + } + + offset += 64; + size -= 64; + p += 64; + } + + // now size is 128-byte multiple, process rest of them in 128-byte blocks + while (size) + { + __m512i a0 = _mm512_loadu_epi8(p + 0x00); + __m512i a1 = _mm512_loadu_epi8(p + 0x40); + + // check if any bytes are different + __mmask64 m0 = _mm512_cmpneq_epu8_mask(value64, a0); + __mmask64 m1 = _mm512_cmpneq_epu8_mask(value64, a1); + if (!_kortestz_mask64_u8(m0, m1)) + { + // if they are, get index of lowest byte that does not match + size_t r0 = _tzcnt_u64(_cvtmask64_u64(m0)); + size_t r1 = _tzcnt_u64(_cvtmask64_u64(m1)); + + // combine both indices to actual index across both comparisons + offset += r0; + offset += r0 == 64 ? r1 : 0; + return offset; + } + + offset += 128; + size -= 128; + p += 128; + } + + // all bytes are same as input value return offset; } @@ -1864,7 +2607,7 @@ static inline uint8x16_t MemToLower16(uint8x16_t x) } MEM_DISABLE_ASAN -int MemCompare_arm64(const void* ptr1, const void* ptr2, size_t size) +int MemCompare_neon(const void* ptr1, const void* ptr2, size_t size) { const uint8_t* p1 = (const uint8_t*)ptr1; const uint8_t* p2 = (const uint8_t*)ptr2; @@ -1881,7 +2624,11 @@ int MemCompare_arm64(const void* ptr1, const void* ptr2, size_t size) return p1[0] - p2[0]; } + // will load pair of 4, 8 or 16 overlapping bytes + // a/b0 from beginning of buffer + // a/b1 from end of buffers uint64_t a0, b0, a1, b1; + if (size < 4) // 2 <= size < 4 { a0 = MEM_PTR16U(p1); @@ -1904,46 +2651,55 @@ int MemCompare_arm64(const void* ptr1, const void* ptr2, size_t size) b1 = MEM_PTR64U(p2 + size - 8); } + // use a0/b0 if they are not equal, otherwise a1/b1 + // byte swap because in big-endian bytes can be compared as uint64 numbers uint64_t a = MEM_BSWAP64(a0 != b0 ? a0 : a1); uint64_t b = MEM_BSWAP64(a0 != b0 ? b0 : b1); + return (a > b) - (a < b); } - uint8x16_t va = vdupq_n_u8(0); - uint8x16_t vb = vdupq_n_u8(0); + const uint8x16_t index4 = vreinterpretq_u8_u64(vdupq_n_u64(0x8040201008040201)); + // process 64-byte blocks as much as possible while (size >= 64) { uint8x16x4_t a = vld1q_u8_x4(p1); uint8x16x4_t b = vld1q_u8_x4(p2); + // set lanes to 0xff if bytes are equal, or 0x00 if not uint8x16_t c0 = vceqq_u8(a.val[0], b.val[0]); uint8x16_t c1 = vceqq_u8(a.val[1], b.val[1]); uint8x16_t c2 = vceqq_u8(a.val[2], b.val[2]); uint8x16_t c3 = vceqq_u8(a.val[3], b.val[3]); - uint8x16_t c = vandq_u8(vandq_u8(c0, c1), vandq_u8(c2, c3)); + // combine comparisons - leave 0xff in lanes that were not equal in at least one of inputs + uint8x16_t c = vmvnq_u8(vandq_u8(vandq_u8(c0, c1), vandq_u8(c2, c3))); + + // nibbles will contain 16 masks with 4-bit value 0xf for lanes that were not equal + // meaning if nibbles is non-zero, then there is mismatch in input bytes uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(c), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles + 1) + if (nibbles) { - uint8x16_t vm; - va = a.val[3]; - vb = b.val[3]; + // change all lanes with 0xff byte to 0 + // but for lanes with 0x00 byte keep its index (8 based) + uint8x16_t m0 = vbicq_u8(index4, c0); + uint8x16_t m1 = vbicq_u8(index4, c1); + uint8x16_t m2 = vbicq_u8(index4, c2); + uint8x16_t m3 = vbicq_u8(index4, c3); - vm = vdupq_n_u8(vminvq_u8(c2)); - va = vbslq_u8(vm, va, a.val[2]); - vb = vbslq_u8(vm, vb, b.val[2]); + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(vpaddq_u8(m0, m1), vpaddq_u8(m2, m3)); + uint8x16_t s2 = vpaddq_u8(s1, s1); - vm = vdupq_n_u8(vminvq_u8(c1)); - va = vbslq_u8(vm, va, a.val[1]); - vb = vbslq_u8(vm, vb, b.val[1]); + // now s3 will have bit set 1 in position we want to extract + uint64_t s3 = vgetq_lane_u64(vreinterpretq_u64_u8(s2), 0); - vm = vdupq_n_u8(vminvq_u8(c0)); - va = vbslq_u8(vm, va, a.val[0]); - vb = vbslq_u8(vm, vb, b.val[0]); + // get index for byte position that's different in inputs, s3 is non-zero here + size_t index = MEM_CTZ64(s3); - goto done; + return p1[index] - p2[index]; } size -= 64; @@ -1953,74 +2709,106 @@ int MemCompare_arm64(const void* ptr1, const void* ptr2, size_t size) if (size & 32) // 32 <= size < 64 { - uint8x16x2_t a = vld1q_u8_x2(p1); - uint8x16x2_t b = vld1q_u8_x2(p2); + // load 64 bytes, some will overlap, 0/1 from beginning of buffers, 2/3 from end of buffers + uint8x16x2_t a0 = vld1q_u8_x2(p1); + uint8x16x2_t b0 = vld1q_u8_x2(p2); + uint8x16x2_t a1 = vld1q_u8_x2(p1 + size - 0x20); + uint8x16x2_t b1 = vld1q_u8_x2(p2 + size - 0x20); - uint8x16_t c0 = vceqq_u8(a.val[0], b.val[0]); - uint8x16_t c1 = vceqq_u8(a.val[1], b.val[1]); + // set lanes to 0xff if bytes are equal, or 0x00 if not + uint8x16_t c0 = vceqq_u8(a0.val[0], b0.val[0]); + uint8x16_t c1 = vceqq_u8(a0.val[1], b0.val[1]); + uint8x16_t c2 = vceqq_u8(a1.val[0], b1.val[0]); + uint8x16_t c3 = vceqq_u8(a1.val[1], b1.val[1]); - uint8x8_t mask0 = vshrn_n_u16(vreinterpretq_u16_u8(c0), 4); - uint64_t nibbles0 = vget_lane_u64(vreinterpret_u64_u8(mask0), 0); - if (nibbles0 + 1) - { - va = a.val[0]; - vb = b.val[0]; - goto done; - } + // comparisons to bit index masks + uint8x16_t m0 = vbicq_u8(index4, c0); + uint8x16_t m1 = vbicq_u8(index4, c1); + uint8x16_t m2 = vbicq_u8(index4, c2); + uint8x16_t m3 = vbicq_u8(index4, c3); - uint8x8_t mask1 = vshrn_n_u16(vreinterpretq_u16_u8(c1), 4); - uint64_t nibbles1 = vget_lane_u64(vreinterpret_u64_u8(mask1), 0); - if (nibbles1 + 1) - { - va = a.val[1]; - vb = b.val[1]; - goto done; - } + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(vpaddq_u8(m0, m1), vpaddq_u8(m2, m3)); + uint8x16_t s2 = vpaddq_u8(s1, s1); - size -= 32; - p1 += 32; - p2 += 32; + // extract 64-bit index mask + uint64_t m = vgetq_lane_u64(vreinterpretq_u64_u8(s2), 0); + + // get index for byte position that's different in inputs, or 64 if inputs are equal + size_t index = m ? MEM_CTZ64(m) : 64; + + // adjust index to correct byte position, due to reused bytes in load + // index = (index < 32) ? index : (index - 32) + (size - 32); + index += (index >= 32) * (size - 64); + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } - - if (size & 16) // 16 <= size < 32 + else if (size & 16) // 16 <= size < 32 { - va = vld1q_u8(p1); - vb = vld1q_u8(p2); + // load 32 bytes, some will overlap + uint8x16_t a0 = vld1q_u8(p1); + uint8x16_t b0 = vld1q_u8(p2); + uint8x16_t a1 = vld1q_u8(p1 + size - 0x10); + uint8x16_t b1 = vld1q_u8(p2 + size - 0x10); - uint8x16_t c = vceqq_u8(va, vb); + // set lanes to 0xff if bytes are equal, or 0x00 if not + uint8x16_t c0 = vceqq_u8(a0, b0); + uint8x16_t c1 = vceqq_u8(a1, b1); + // comparisons to bit index masks + uint8x16_t m0 = vbicq_u8(index4, c0); + uint8x16_t m1 = vbicq_u8(index4, c1); + + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(m0, m1); + uint8x16_t s2 = vpaddq_u8(s1, s1); + uint8x16_t s3 = vpaddq_u8(s2, s2); + + // extract 64-bit index mask + uint32_t m = vgetq_lane_u32(vreinterpretq_u32_u8(s3), 0); + + // get index for byte position that's different in inputs, or 32 if inputs are equal + size_t index = m ? MEM_CTZ32(m) : 32; + + // adjust index to correct byte position, due to reused bytes in load + // index = (index < 16) ? index : (index - 16) + (size - 16); + index += (index >= 16) * (size - 32); + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; + } + else if (size) // 0 < size < 16, but initially size > 16 + { + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they were equal) + uint8x16_t a = vld1q_u8(p1 + size - 16); + uint8x16_t b = vld1q_u8(p2 + size - 16); + + // set lanes to 0x00 if bytes are equal, or 0xff if not + uint8x16_t c = vmvnq_u8(vceqq_u8(a, b)); + + // nibbles will contain 16 masks with 4-bit value 0x0 if lanes were equal + // if there is one lane that was not equal, it contains 0xf uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(c), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles + 1) - { - goto done; - } - size -= 16; - p1 += 16; - p2 += 16; + // get index for byte position that's different in inputs, or 16 + size_t index = (nibbles ? MEM_CTZ64(nibbles) : 64) / 4; + + // adjust index due to reused bytes in load + index += size - 16; + + // return comparison result, or 0 if inputs are equal + return index < size ? p1[index] - p2[index] : 0; } - if (size) // size < 16 - { - va = vld1q_u8(p1 + size - 16); - vb = vld1q_u8(p2 + size - 16); - } - -done:; - uint64x2_t a64 = vreinterpretq_u64_u8(vrev64q_u8(va)); - uint64x2_t b64 = vreinterpretq_u64_u8(vrev64q_u8(vb)); - uint64_t a0 = vgetq_lane_u64(a64, 0); - uint64_t b0 = vgetq_lane_u64(b64, 0); - uint64_t a1 = vgetq_lane_u64(a64, 1); - uint64_t b1 = vgetq_lane_u64(b64, 1); - uint64_t a = (a0 != b0 ? a0 : a1); - uint64_t b = (a0 != b0 ? b0 : b1); - return (a > b) - (a < b); + // no differences found, inputs are equal + return 0; } MEM_DISABLE_ASAN -int MemCompareI_arm64(const void* ptr1, const void* ptr2, size_t size) +int MemCompareI_neon(const void* ptr1, const void* ptr2, size_t size) { const uint8_t* p1 = (const uint8_t*)ptr1; const uint8_t* p2 = (const uint8_t*)ptr2; @@ -2039,85 +2827,96 @@ int MemCompareI_arm64(const void* ptr1, const void* ptr2, size_t size) if (size < 4) // 2 <= size < 4 { + // load pair of 4 overlapping bytes uint64_t a0 = MEM_PTR16U(p1); uint64_t b0 = MEM_PTR16U(p2); uint64_t a1 = MEM_PTR16U(p1 + size - 2); uint64_t b1 = MEM_PTR16U(p2 + size - 2); + + // combine, lower case them and byteswap to have numbers in big-endian uint64_t tmp = MEM_BSWAP64(MemToLower8(a0 | (a1 << 16) | (b0 << 32) | (b1 << 48))); uint32_t a = (uint32_t)(tmp >> 32); uint32_t b = (uint32_t)tmp; + + // can compare uint64 numbers if they are big-endian return (a > b) - (a < b); } else if (size < 8) // 4 <= size < 8 { + // load pair of 8 overlapping bytes uint64_t a0 = MEM_PTR32U(p1); uint64_t b0 = MEM_PTR32U(p2); uint64_t a1 = MEM_PTR32U(p1 + size - 4); uint64_t b1 = MEM_PTR32U(p2 + size - 4); + + // combine, lower case them and byteswap to have numbers in big-endian uint64_t a = MEM_BSWAP64(MemToLower8(a0 | (a1 << 32))); uint64_t b = MEM_BSWAP64(MemToLower8(b0 | (b1 << 32))); + + // can compare uint64 numbers if they are big-endian return (a > b) - (a < b); } else // 8 <= size <= 16 { + // load pair of 16 overlapping bytes uint8x16_t va = vcombine_u8(vld1_u8(p1), vld1_u8(p1 + size - 8)); uint8x16_t vb = vcombine_u8(vld1_u8(p2), vld1_u8(p2 + size - 8)); + + // lower case them and byteswap to have numbers in big-endian uint64x2_t a64 = vreinterpretq_u64_u8(vrev64q_u8(MemToLower16(va))); uint64x2_t b64 = vreinterpretq_u64_u8(vrev64q_u8(MemToLower16(vb))); + + // use a0/b0 if they are not equal, otherwise a1/b1 uint64_t a0 = vgetq_lane_u64(a64, 0); uint64_t b0 = vgetq_lane_u64(b64, 0); uint64_t a1 = vgetq_lane_u64(a64, 1); uint64_t b1 = vgetq_lane_u64(b64, 1); uint64_t a = (a0 != b0 ? a0 : a1); uint64_t b = (a0 != b0 ? b0 : b1); + return (a > b) - (a < b); } } - uint8x16_t va = vdupq_n_u8(0); - uint8x16_t vb = vdupq_n_u8(0); + const uint8x16_t index4 = vreinterpretq_u8_u64(vdupq_n_u64(0x8040201008040201)); + // process 64-byte blocks as much as possible while (size >= 64) { uint8x16x4_t a = vld1q_u8_x4(p1); uint8x16x4_t b = vld1q_u8_x4(p2); - a.val[0] = MemToLower16(a.val[0]); - b.val[0] = MemToLower16(b.val[0]); - a.val[1] = MemToLower16(a.val[1]); - b.val[1] = MemToLower16(b.val[1]); - a.val[2] = MemToLower16(a.val[2]); - b.val[2] = MemToLower16(b.val[2]); - a.val[3] = MemToLower16(a.val[3]); - b.val[3] = MemToLower16(b.val[3]); + // set lanes to 0xff if bytes are equal, or 0x00 if not + uint8x16_t c0 = vceqq_u8(MemToLower16(a.val[0]), MemToLower16(b.val[0])); + uint8x16_t c1 = vceqq_u8(MemToLower16(a.val[1]), MemToLower16(b.val[1])); + uint8x16_t c2 = vceqq_u8(MemToLower16(a.val[2]), MemToLower16(b.val[2])); + uint8x16_t c3 = vceqq_u8(MemToLower16(a.val[3]), MemToLower16(b.val[3])); - uint8x16_t c0 = vceqq_u8(a.val[0], b.val[0]); - uint8x16_t c1 = vceqq_u8(a.val[1], b.val[1]); - uint8x16_t c2 = vceqq_u8(a.val[2], b.val[2]); - uint8x16_t c3 = vceqq_u8(a.val[3], b.val[3]); - uint8x16_t c = vandq_u8(vandq_u8(c0, c1), vandq_u8(c2, c3)); + // combine comparisons - leave 0xff in lanes that were not equal in at least one of inputs + uint8x16_t c = vmvnq_u8(vandq_u8(vandq_u8(c0, c1), vandq_u8(c2, c3))); + // extract 4-bit nibble mask uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(c), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles + 1) + if (nibbles) { - uint8x16_t vm; - va = a.val[3]; - vb = b.val[3]; + // comparisons to bit index masks + uint8x16_t m0 = vbicq_u8(index4, c0); + uint8x16_t m1 = vbicq_u8(index4, c1); + uint8x16_t m2 = vbicq_u8(index4, c2); + uint8x16_t m3 = vbicq_u8(index4, c3); - vm = vdupq_n_u8(vminvq_u8(c2)); - va = vbslq_u8(vm, va, a.val[2]); - vb = vbslq_u8(vm, vb, b.val[2]); + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(vpaddq_u8(m0, m1), vpaddq_u8(m2, m3)); + uint8x16_t s2 = vpaddq_u8(s1, s1); - vm = vdupq_n_u8(vminvq_u8(c1)); - va = vbslq_u8(vm, va, a.val[1]); - vb = vbslq_u8(vm, vb, b.val[1]); + // extract 64-bit index mask + uint64_t s3 = vgetq_lane_u64(vreinterpretq_u64_u8(s2), 0); - vm = vdupq_n_u8(vminvq_u8(c0)); - va = vbslq_u8(vm, va, a.val[0]); - vb = vbslq_u8(vm, vb, b.val[0]); + // get index for byte position that's different in inputs, s3 is non-zero here + size_t index = MEM_CTZ64(s3); - goto done; + return MemToLower1(p1[index]) - MemToLower1(p2[index]); } size -= 64; @@ -2127,79 +2926,106 @@ int MemCompareI_arm64(const void* ptr1, const void* ptr2, size_t size) if (size & 32) // 32 <= size < 64 { - uint8x16x2_t a = vld1q_u8_x2(p1); - uint8x16x2_t b = vld1q_u8_x2(p2); + // load 64 bytes, some will overlap + uint8x16x2_t a0 = vld1q_u8_x2(p1); + uint8x16x2_t b0 = vld1q_u8_x2(p2); + uint8x16x2_t a1 = vld1q_u8_x2(p1 + size - 0x20); + uint8x16x2_t b1 = vld1q_u8_x2(p2 + size - 0x20); - a.val[0] = MemToLower16(a.val[0]); - b.val[0] = MemToLower16(b.val[0]); - a.val[1] = MemToLower16(a.val[1]); - b.val[1] = MemToLower16(b.val[1]); + // set lanes to 0xff if bytes are equal, or 0x00 if not + uint8x16_t c0 = vceqq_u8(MemToLower16(a0.val[0]), MemToLower16(b0.val[0])); + uint8x16_t c1 = vceqq_u8(MemToLower16(a0.val[1]), MemToLower16(b0.val[1])); + uint8x16_t c2 = vceqq_u8(MemToLower16(a1.val[0]), MemToLower16(b1.val[0])); + uint8x16_t c3 = vceqq_u8(MemToLower16(a1.val[1]), MemToLower16(b1.val[1])); - uint8x16_t c0 = vceqq_u8(a.val[0], b.val[0]); - uint8x16_t c1 = vceqq_u8(a.val[1], b.val[1]); + // comparisons to bit index masks + uint8x16_t m0 = vbicq_u8(index4, c0); + uint8x16_t m1 = vbicq_u8(index4, c1); + uint8x16_t m2 = vbicq_u8(index4, c2); + uint8x16_t m3 = vbicq_u8(index4, c3); - uint8x8_t mask0 = vshrn_n_u16(vreinterpretq_u16_u8(c0), 4); - uint64_t nibbles0 = vget_lane_u64(vreinterpret_u64_u8(mask0), 0); - if (nibbles0 + 1) - { - va = a.val[0]; - vb = b.val[0]; - goto done; - } + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(vpaddq_u8(m0, m1), vpaddq_u8(m2, m3)); + uint8x16_t s2 = vpaddq_u8(s1, s1); - uint8x8_t mask1 = vshrn_n_u16(vreinterpretq_u16_u8(c1), 4); - uint64_t nibbles1 = vget_lane_u64(vreinterpret_u64_u8(mask1), 0); - if (nibbles1 + 1) - { - va = a.val[1]; - vb = b.val[1]; - goto done; - } + // extract 64-bit index mask + uint64_t m = vgetq_lane_u64(vreinterpretq_u64_u8(s2), 0); - size -= 32; - p1 += 32; - p2 += 32; + // get index for byte position that's different in inputs, or 64 if inputs are equal + size_t index = m ? MEM_CTZ64(m) : 64; + + // adjust index to correct byte position, due to reused bytes in load + // index = (index < 32) ? index : (index - 32) + (size - 32); + index += (index >= 32) * (size - 64); + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } - - if (size & 16) // 16 <= size < 32 + else if (size & 16) // 16 <= size < 32 { - va = MemToLower16(vld1q_u8(p1)); - vb = MemToLower16(vld1q_u8(p2)); + // load 32 bytes, some will overlap + uint8x16_t a0 = vld1q_u8(p1); + uint8x16_t b0 = vld1q_u8(p2); + uint8x16_t a1 = vld1q_u8(p1 + size - 0x10); + uint8x16_t b1 = vld1q_u8(p2 + size - 0x10); - uint8x16_t c = vceqq_u8(va, vb); + // set lanes to 0xff if bytes are equal, or 0x00 if not + uint8x16_t c0 = vceqq_u8(MemToLower16(a0), MemToLower16(b0)); + uint8x16_t c1 = vceqq_u8(MemToLower16(a1), MemToLower16(b1)); + // comparisons to bit index masks + uint8x16_t m0 = vbicq_u8(index4, c0); + uint8x16_t m1 = vbicq_u8(index4, c1); + + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(m0, m1); + uint8x16_t s2 = vpaddq_u8(s1, s1); + uint8x16_t s3 = vpaddq_u8(s2, s2); + + // extract 64-bit index mask + uint32_t m = vgetq_lane_u32(vreinterpretq_u32_u8(s3), 0); + + // get index for byte position that's different in inputs, or 32 if inputs are equal + size_t index = m ? MEM_CTZ32(m) : 32; + + // adjust index to correct byte position, due to reused bytes in load + // index = (index < 16) ? index : (index - 16) + (size - 16); + index += (index >= 16) * (size - 32); + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; + } + else if (size) // 0 < size < 16, but initially size > 16 + { + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they were equal) + uint8x16_t a = vld1q_u8(p1 + size - 16); + uint8x16_t b = vld1q_u8(p2 + size - 16); + + // set lanes to 0x00 if bytes are equal, or 0xff if not + uint8x16_t c = vmvnq_u8(vceqq_u8(MemToLower16(a), MemToLower16(b))); + + // nibbles will contain 16 masks with 4-bit value 0x0 if lanes were equal + // if there is one lane that was not equal, it contains 0xf uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(c), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles + 1) - { - goto done; - } - size -= 16; - p1 += 16; - p2 += 16; + // get index for byte position that's different in inputs, or 16 + size_t index = (nibbles ? MEM_CTZ64(nibbles) : 64) / 4; + + // adjust index due to reused bytes in load + index += size - 16; + + // return comparison result, or 0 if inputs are equal + return index < size ? MemToLower1(p1[index]) - MemToLower1(p2[index]) : 0; } - if (size) // size < 16 - { - va = MemToLower16(vld1q_u8(p1 + size - 16)); - vb = MemToLower16(vld1q_u8(p2 + size - 16)); - } - -done:; - uint64x2_t a64 = vreinterpretq_u64_u8(vrev64q_u8(va)); - uint64x2_t b64 = vreinterpretq_u64_u8(vrev64q_u8(vb)); - uint64_t a0 = vgetq_lane_u64(a64, 0); - uint64_t b0 = vgetq_lane_u64(b64, 0); - uint64_t a1 = vgetq_lane_u64(a64, 1); - uint64_t b1 = vgetq_lane_u64(b64, 1); - uint64_t a = (a0 != b0 ? a0 : a1); - uint64_t b = (a0 != b0 ? b0 : b1); - return (a > b) - (a < b); + // no differences found, inputs are equal + return 0; } MEM_DISABLE_ASAN -bool MemIsEqual_arm64(const void* ptr1, const void* ptr2, size_t size) +bool MemIsEqual_neon(const void* ptr1, const void* ptr2, size_t size) { const uint8_t* p1 = (const uint8_t*)ptr1; const uint8_t* p2 = (const uint8_t*)ptr2; @@ -2216,7 +3042,11 @@ bool MemIsEqual_arm64(const void* ptr1, const void* ptr2, size_t size) return p1[0] == p2[0]; } + // will load pair of 4, 8 or 16 overlapping bytes + // a/b0 from beginning of buffer + // a/b1 from end of buffers uint64_t a0, b0, a1, b1; + if (size < 4) // 2 <= size < 4 { a0 = MEM_PTR16U(p1); @@ -2239,24 +3069,32 @@ bool MemIsEqual_arm64(const void* ptr1, const void* ptr2, size_t size) b1 = MEM_PTR64U(p2 + size - 8); } + // compare loaded bytes on equality, overlapped ones will be checked twice + // but result will still be correct return (a0 == b0) & (a1 == b1); } + // process 64-byte blocks as much as possible while (size >= 64) { uint8x16x4_t a = vld1q_u8_x4(p1); uint8x16x4_t b = vld1q_u8_x4(p2); + // set lanes to 0xff if bytes are equal, or 0x00 if not uint8x16_t c0 = vceqq_u8(a.val[0], b.val[0]); uint8x16_t c1 = vceqq_u8(a.val[1], b.val[1]); uint8x16_t c2 = vceqq_u8(a.val[2], b.val[2]); uint8x16_t c3 = vceqq_u8(a.val[3], b.val[3]); - uint8x16_t c = vandq_u8(vandq_u8(c0, c1), vandq_u8(c2, c3)); + // combine comparisons - leave 0xff in lanes that were not equal in at least one of inputs + uint8x16_t c = vmvnq_u8(vandq_u8(vandq_u8(c0, c1), vandq_u8(c2, c3))); + + // extract 4-bit nibble mask uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(c), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles + 1) + if (nibbles) { + // if nibbles is non-zero then there is at least one difference return false; } @@ -2267,66 +3105,77 @@ bool MemIsEqual_arm64(const void* ptr1, const void* ptr2, size_t size) if (size & 32) // 32 <= size < 64 { - uint8x16x2_t a = vld1q_u8_x2(p1); - uint8x16x2_t b = vld1q_u8_x2(p2); + // load 64 bytes, some will overlap + uint8x16x2_t a0 = vld1q_u8_x2(p1); + uint8x16x2_t b0 = vld1q_u8_x2(p2); + uint8x16x2_t a1 = vld1q_u8_x2(p1 + size - 0x20); + uint8x16x2_t b1 = vld1q_u8_x2(p2 + size - 0x20); - uint8x16_t c0 = vceqq_u8(a.val[0], b.val[0]); - uint8x16_t c1 = vceqq_u8(a.val[1], b.val[1]); - uint8x16_t c = vandq_u8(c0, c1); + // set lanes to 0xff if bytes are equal, or 0x00 if not + uint8x16_t c0 = vceqq_u8(a0.val[0], b0.val[0]); + uint8x16_t c1 = vceqq_u8(a0.val[1], b0.val[1]); + uint8x16_t c2 = vceqq_u8(a1.val[0], b1.val[0]); + uint8x16_t c3 = vceqq_u8(a1.val[1], b1.val[1]); + // combine comparisons - leave 0xff in lanes that were not equal in at least one of inputs + uint8x16_t c = vmvnq_u8(vandq_u8(vandq_u8(c0, c1), vandq_u8(c2, c3))); + + // extract 4-bit nibble mask uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(c), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles + 1) - { - return false; - } - size -= 32; - p1 += 32; - p2 += 32; + // if nibbles are zero, then there were no differences in inputs + return nibbles == 0; } - - if (size & 16) // 16 <= size < 32 + else if (size & 16) // 16 <= size < 32 { - uint8x16_t a = vld1q_u8(p1); - uint8x16_t b = vld1q_u8(p2); - uint8x16_t c = vceqq_u8(a, b); + // load 32 bytes, some will overlap + uint8x16_t a0 = vld1q_u8(p1); + uint8x16_t b0 = vld1q_u8(p2); + uint8x16_t a1 = vld1q_u8(p1 + size - 0x10); + uint8x16_t b1 = vld1q_u8(p2 + size - 0x10); + // set lanes to 0xff if bytes are equal, or 0x00 if not + uint8x16_t c0 = vceqq_u8(a0, b0); + uint8x16_t c1 = vceqq_u8(a1, b1); + + // combine comparisons - leave 0xff in lanes that were not equal in at least one of inputs + uint8x16_t c = vmvnq_u8(vandq_u8(c0, c1)); + + // extract 4-bit nibble mask uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(c), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles + 1) - { - return false; - } - size -= 16; - p1 += 16; - p2 += 16; + // if nibbles are zero, then there were no differences in inputs + return nibbles == 0; } - - if (size) // size < 16 + else if (size) // 0 < size < 16, but initially size > 16 { - uint8x16_t a = vld1q_u8(p1 + size - 16); - uint8x16_t b = vld1q_u8(p2 + size - 16); - uint8x16_t c = vceqq_u8(a, b); + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they were equal) + uint8x16_t a = vld1q_u8(p1 + size - 0x10); + uint8x16_t b = vld1q_u8(p2 + size - 0x10); + // combine comparisons - leave 0xff in lanes that were not equal in at least one of inputs + uint8x16_t c = vmvnq_u8(vceqq_u8(a, b)); + + // extract 4-bit nibble mask uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(c), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles + 1) - { - return false; - } + + // if nibbles are zero, then there were no differences in inputs + return nibbles == 0; } + // no differences found, inputs are equal return true; } MEM_DISABLE_ASAN -size_t MemFind_arm64(const void* ptr, size_t size, uint8_t value) +size_t MemFind_neon(const void* ptr, size_t size, uint8_t value) { const uint8_t* p = (const uint8_t*)ptr; - const uint8x16_t index4 = vreinterpretq_u8_u64(vdupq_n_u64(0x8040201008040201)); const uint8x16_t value16 = vdupq_n_u8(value); if (size == 0) @@ -2339,42 +3188,72 @@ size_t MemFind_arm64(const void* ptr, size_t size, uint8_t value) size_t address = (uint32_t)(uintptr_t)p % 16; size_t extra = (address + size) <= 16 ? address : 0; + // will load before the beginning buffer (16-byte aligned) if end is too close + // to 16-byte boundary, otherwise will load past the end of buffer uint8x16_t a = vld1q_u8(p - extra); + + // set lane to 0xff if lane matches input value, or 0x00 if not uint8x16_t b = vceqq_u8(a, value16); + // nibbles will contain 16 masks with 4-bit value 0xf if lane matches input value + // if there is one lane that was not equal, mask contains 0x0 uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(b), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles) - { - size_t index = MEM_CTZ64(nibbles >> (4*extra)) / 4; - return index < size ? index : size; - } - return size; + // drop any lowest "extra" bits (due to loading bytes before beginning buffer) + nibbles >>= (4 * extra); + + // for non-zero nibble find first bit set, which will be index of first byte matching input value + size_t index = (nibbles ? MEM_CTZ64(nibbles) : 64) / 4; + + // mask out any high bits (due to load past end of buffer) + return index < size ? index : size; } + const uint8x16_t index4 = vreinterpretq_u8_u64(vdupq_n_u64(0x8040201008040201)); + size_t offset = 0; + + // process 64-byte blocks as much as possible while (size >= 64) { uint8x16x4_t a = vld1q_u8_x4(p); + + // set lane to 0xff if lane matches input value, or 0x00 if not uint8x16_t b0 = vceqq_u8(a.val[0], value16); uint8x16_t b1 = vceqq_u8(a.val[1], value16); uint8x16_t b2 = vceqq_u8(a.val[2], value16); uint8x16_t b3 = vceqq_u8(a.val[3], value16); - uint8x16_t b = vorrq_u8(vorrq_u8(b0, b1), vorrq_u8(b2, b3)); + // combine comparisons - leave 0xff in lanes there equal to input value + uint8x16_t b01 = vorrq_u8(b0, b1); + uint8x16_t b23 = vorrq_u8(b2, b3); +#if defined(__clang__) + // without this clang 19+ generates worse code (runs slower) + __asm__ __volatile__("" : "+w"(b01), "+w"(b23)); +#endif + uint8x16_t b = vorrq_u8(b01, b23); + + // extract 4-bit nibble mask uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(b), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); if (nibbles) { - uint8x16_t mask0 = vandq_u8(b0, index4); - uint8x16_t mask1 = vandq_u8(b1, index4); - uint8x16_t mask2 = vandq_u8(b2, index4); - uint8x16_t mask3 = vandq_u8(b3, index4); + // comparisons to bit index masks + uint8x16_t m0 = vandq_u8(b0, index4); + uint8x16_t m1 = vandq_u8(b1, index4); + uint8x16_t m2 = vandq_u8(b2, index4); + uint8x16_t m3 = vandq_u8(b3, index4); - uint8x16_t sum = vpaddq_u8(vpaddq_u8(mask0, mask1), vpaddq_u8(mask2, mask3)); - uint64_t sum64 = vgetq_lane_u64(vreinterpretq_u64_u8(vpaddq_u8(sum, sum)), 0); - return offset + MEM_CTZ64(sum64); + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(vpaddq_u8(m0, m1), vpaddq_u8(m2, m3)); + uint8x16_t s2 = vpaddq_u8(s1, s1); + + // extract 64-bit index mask + uint64_t s3 = vgetq_lane_u64(vreinterpretq_u64_u8(s2), 0); + + // get index for byte position that matches input value + return offset + MEM_CTZ64(s3); } offset += 64; @@ -2384,58 +3263,264 @@ size_t MemFind_arm64(const void* ptr, size_t size, uint8_t value) if (size & 32) // 32 <= size < 64 { - uint8x16x2_t a = vld1q_u8_x2(p); + // load 64 bytes, some will overlap + uint8x16x2_t a0 = vld1q_u8_x2(p); + uint8x16x2_t a1 = vld1q_u8_x2(p + size - 0x20); + + // set lane to 0xff if lane matches input value, or 0x00 if not + uint8x16_t b0 = vceqq_u8(a0.val[0], value16); + uint8x16_t b1 = vceqq_u8(a0.val[1], value16); + uint8x16_t b2 = vceqq_u8(a1.val[0], value16); + uint8x16_t b3 = vceqq_u8(a1.val[1], value16); + + // comparisons to bit index masks + uint8x16_t m0 = vandq_u8(b0, index4); + uint8x16_t m1 = vandq_u8(b1, index4); + uint8x16_t m2 = vandq_u8(b2, index4); + uint8x16_t m3 = vandq_u8(b3, index4); + + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(vpaddq_u8(m0, m1), vpaddq_u8(m2, m3)); + uint8x16_t s2 = vpaddq_u8(s1, s1); + + // extract 64-bit index mask + uint64_t m = vgetq_lane_u64(vreinterpretq_u64_u8(s2), 0); + + // get index of byte that matches input value, or 64 + size_t index = m ? MEM_CTZ64(m) : 64; + + // adjust index to correct byte position, due to reused bytes in load + // index = (index < 32) ? index : (index - 32) + (size - 32); + index += (index >= 32) * (size - 64); + + return offset + index; + } + else if (size & 16) // 16 <= size < 32 + { + // load 32 bytes, some will overlap + uint8x16_t a0 = vld1q_u8(p); + uint8x16_t a1 = vld1q_u8(p + size - 0x10); + + // set lane to 0xff if lane matches input value, or 0x00 if not + uint8x16_t b0 = vceqq_u8(a0, value16); + uint8x16_t b1 = vceqq_u8(a1, value16); + + // comparisons to bit index masks + uint8x16_t m0 = vandq_u8(b0, index4); + uint8x16_t m1 = vandq_u8(b1, index4); + + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(m0, m1); + uint8x16_t s2 = vpaddq_u8(s1, s1); + uint8x16_t s3 = vpaddq_u8(s2, s2); + + // extract 64-bit index mask + uint32_t m = vgetq_lane_u32(vreinterpretq_u32_u8(s3), 0); + + // get index of byte that matches input value, or 32 + size_t index = m ? MEM_CTZ32(m) : 32; + + // adjust index to correct byte position, due to reused bytes in load + // index = (index < 16) ? index : (index - 16) + (size - 16); + index += (index >= 16) * (size - 32); + + return offset + index; + } + else if (size) // 0 < size < 16, but initially size > 16 + { + // load 32 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they did not match input value) + uint8x16_t a = vld1q_u8(p + size - 16); + + // set lane to 0xff if lane matches input value, or 0x00 if not + uint8x16_t b = vceqq_u8(a, value16); + + // extract 4-bit nibble mask + uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(b), 4); + uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); + + // get index of byte that matches input value, or 16 + size_t index = (nibbles ? MEM_CTZ64(nibbles) : 64) / 4; + + // adjust index due to reused bytes in load + return offset + index + size - 16; + } + + // no input value found, return original size (current offset plus pending tail size) + return offset + size; +} + +MEM_DISABLE_ASAN +size_t MemFindNot_neon(const void* ptr, size_t size, uint8_t value) +{ + const uint8_t* p = (const uint8_t*)ptr; + + const uint8x16_t value16 = vdupq_n_u8(value); + + if (size == 0) + { + return 0; + } + + if (size <= 16) + { + size_t address = (uint32_t)(uintptr_t)p % 16; + size_t extra = (address + size) <= 16 ? address : 0; + + // will load before the beginning buffer (16-byte aligned) if end is too close + // to 16-byte boundary, otherwise will load past the end of buffer + uint8x16_t a = vld1q_u8(p - extra); + + // set lane to 0x00 if lane matches input value, or 0xff if not + uint8x16_t b = vmvnq_u8(vceqq_u8(a, value16)); + + // nibbles will contain 16 masks with 4-bit value 0x0 if lane matches input value + // if there is one lane that was not equal, it contains 0xf + uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(b), 4); + uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); + + // drop any lowest "extra" bits (due to loading bytes before beginning buffer) + nibbles >>= (4 * extra); + + // for non-zero nibble find first bit set, which will be index of first byte different from input value + size_t index = (nibbles ? MEM_CTZ64(nibbles) : 64) / 4; + + // mask out any high bits (due to load past end of buffer) + return index < size ? index : size; + } + + const uint8x16_t index4 = vreinterpretq_u8_u64(vdupq_n_u64(0x8040201008040201)); + + size_t offset = 0; + + // process 64-byte blocks as much as possible + while (size >= 64) + { + uint8x16x4_t a = vld1q_u8_x4(p); + + // set lane to 0xff if lane matches input value, or 0x00 if not uint8x16_t b0 = vceqq_u8(a.val[0], value16); uint8x16_t b1 = vceqq_u8(a.val[1], value16); + uint8x16_t b2 = vceqq_u8(a.val[2], value16); + uint8x16_t b3 = vceqq_u8(a.val[3], value16); - uint8x16_t mask0 = vandq_u8(b0, index4); - uint8x16_t mask1 = vandq_u8(b1, index4); - - uint8x16_t sum1 = vpaddq_u8(mask0, mask1); - uint8x16_t sum2 = vpaddq_u8(sum1, sum1); - uint32_t sum32 = vgetq_lane_u32(vreinterpretq_u32_u8(vpaddq_u8(sum2, sum2)), 0); - if (sum32) - { - return offset + MEM_CTZ32(sum32); - } - - offset += 32; - size -= 32; - p += 32; - } - - if (size & 16) // 16 <= size < 32 - { - uint8x16_t a = vld1q_u8(p); - uint8x16_t b = vceqq_u8(a, value16); + // combine comparisons - leave 0xff in lanes that were not matching in at least one of inputs + uint8x16_t b = vmvnq_u8(vandq_u8(vandq_u8(b0, b1), vandq_u8(b2, b3))); + // extract 4-bit nibble mask uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(b), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); if (nibbles) { - return offset + MEM_CTZ64(nibbles) / 4; + // comparisons to bit index masks + uint8x16_t m0 = vbicq_u8(index4, b0); + uint8x16_t m1 = vbicq_u8(index4, b1); + uint8x16_t m2 = vbicq_u8(index4, b2); + uint8x16_t m3 = vbicq_u8(index4, b3); + + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(vpaddq_u8(m0, m1), vpaddq_u8(m2, m3)); + uint8x16_t s2 = vpaddq_u8(s1, s1); + + // extract 64-bit index mask + uint64_t s3 = vgetq_lane_u64(vreinterpretq_u64_u8(s2), 0); + + // get index for byte position that's different from input value + return offset + MEM_CTZ64(s3); } - offset += 16; - size -= 16; - p += 16; + offset += 64; + size -= 64; + p += 64; } - if (size) // size < 16 + if (size & 32) // 32 <= size < 64 { + // load 64 bytes, some will overlap + uint8x16x2_t a0 = vld1q_u8_x2(p); + uint8x16x2_t a1 = vld1q_u8_x2(p + size - 0x20); + + // set lane to 0xff if lane matches input value, or 0x00 if not + uint8x16_t b0 = vceqq_u8(a0.val[0], value16); + uint8x16_t b1 = vceqq_u8(a0.val[1], value16); + uint8x16_t b2 = vceqq_u8(a1.val[0], value16); + uint8x16_t b3 = vceqq_u8(a1.val[1], value16); + + // comparisons to bit index masks + uint8x16_t m0 = vbicq_u8(index4, b0); + uint8x16_t m1 = vbicq_u8(index4, b1); + uint8x16_t m2 = vbicq_u8(index4, b2); + uint8x16_t m3 = vbicq_u8(index4, b3); + + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(vpaddq_u8(m0, m1), vpaddq_u8(m2, m3)); + uint8x16_t s2 = vpaddq_u8(s1, s1); + + // extract 64-bit index mask + uint64_t m = vgetq_lane_u64(vreinterpretq_u64_u8(s2), 0); + + // get index of byte that does not match input value, or 64 + size_t index = m ? MEM_CTZ64(m) : 64; + + // adjust index to correct byte position, due to reused bytes in load + // index = (index < 32) ? index : (index - 32) + (size - 32); + index += (index >= 32) * (size - 64); + + return offset + index; + } + else if (size & 16) // 16 <= size < 32 + { + // load 32 bytes, some will overlap + uint8x16_t a0 = vld1q_u8(p); + uint8x16_t a1 = vld1q_u8(p + size - 0x10); + + // set lane to 0xff if lane matches input value, or 0x00 if not + uint8x16_t b0 = vceqq_u8(a0, value16); + uint8x16_t b1 = vceqq_u8(a1, value16); + + // comparisons to bit index masks + uint8x16_t m0 = vbicq_u8(index4, b0); + uint8x16_t m1 = vbicq_u8(index4, b1); + + // sum pairs of masks, so result fits into 64-bit low lane + uint8x16_t s1 = vpaddq_u8(m0, m1); + uint8x16_t s2 = vpaddq_u8(s1, s1); + uint8x16_t s3 = vpaddq_u8(s2, s2); + + // extract 64-bit index mask + uint32_t m = vgetq_lane_u32(vreinterpretq_u32_u8(s3), 0); + + // get index of byte that does not match input value, or 32 + size_t index = m ? MEM_CTZ32(m) : 32; + + // adjust index to correct byte position, due to reused bytes in load + // index = (index < 16) ? index : (index - 16) + (size - 16); + index += (index >= 16) * (size - 32); + + return offset + index; + } + else if (size) // 0 < size < 16, but initially size > 16 + { + // load 16 bytes from end of buffer + // this will load previously checked bytes in 64 byte loop (they did match input value) uint8x16_t a = vld1q_u8(p + size - 16); - uint8x16_t b = vceqq_u8(a, value16); + // set lane to 0x00 if lane matches input value, or 0xff if not + uint8x16_t b = vmvnq_u8(vceqq_u8(a, value16)); + + // extract 4-bit nibble mask uint8x8_t mask = vshrn_n_u16(vreinterpretq_u16_u8(b), 4); uint64_t nibbles = vget_lane_u64(vreinterpret_u64_u8(mask), 0); - if (nibbles) - { - return offset + MEM_CTZ64(nibbles) / 4 + size - 16; - } - offset += size; + + // get index of byte that does not match input value, or 16 + size_t index = (nibbles ? MEM_CTZ64(nibbles) : 64) / 4; + + // adjust index due to reused bytes in load + return offset + index + size - 16; } - return offset; + // all bytes are same as input value, return original size (current offset plus pending tail size) + return offset + size; } #endif // MEM_ARCH_ARM64 @@ -2564,6 +3649,33 @@ size_t MemFind_rvv(const void* ptr, size_t size, uint8_t value) return offset; } +size_t MemFindNot_rvv(const void* ptr, size_t size, uint8_t value) +{ + const uint8_t* p = (const uint8_t*)ptr; + + size_t offset = 0; + do + { + size_t vl = __riscv_vsetvl_e8m8(size); + + vuint8m8_t a = __riscv_vle8_v_u8m8(p, vl); + vbool1_t m = __riscv_vmsne_vx_u8m8_b1(a, value, vl); + + long index = __riscv_vfirst_m_b1(m, vl); + if (index >= 0) + { + return offset + (unsigned long)index; + } + + offset += vl; + size -= vl; + p += vl; + } + while (size); + + return offset; +} + #endif // MEM_ARCH_RVV @@ -2639,6 +3751,277 @@ static int MemCPUID(void) #endif // MEM_ARCH_X64 +int MemCompare_generic(const void* ptr1, const void* ptr2, size_t size) +{ + const uint8_t* p1 = (const uint8_t*)ptr1; + const uint8_t* p2 = (const uint8_t*)ptr2; + + while (size >= 8) + { + uint64_t a = MEM_PTR64U(p1); + uint64_t b = MEM_PTR64U(p2); + if (a != b) + { + a = MEM_BSWAP64(a); + b = MEM_BSWAP64(b); + return (a > b) - (a < b); + } + + size -= 8; + p1 += 8; + p2 += 8; + } + + if (size & 4) // 4 <= size < 8 + { + uint64_t a0 = MEM_PTR32U(p1); + uint64_t b0 = MEM_PTR32U(p2); + uint64_t a1 = MEM_PTR32U(p1 + size - 4); + uint64_t b1 = MEM_PTR32U(p2 + size - 4); + uint64_t a = MEM_BSWAP64(a0 | (a1 << 32)); + uint64_t b = MEM_BSWAP64(b0 | (b1 << 32)); + return (a > b) - (a < b); + } + + if (size & 2) // 2 <= size < 4 + { + uint32_t a0 = MEM_PTR16U(p1); + uint32_t b0 = MEM_PTR16U(p2); + uint32_t a1 = MEM_PTR16U(p1 + size - 2); + uint32_t b1 = MEM_PTR16U(p2 + size - 2); + uint32_t a = MEM_BSWAP32(a0 | (a1 << 16)); + uint32_t b = MEM_BSWAP32(b0 | (b1 << 16)); + return (a > b) - (a < b); + } + + if (size) // 1 <= size < 2 + { + return p1[0] - p2[0]; + } + + return 0; +} + +int MemCompareI_generic(const void* ptr1, const void* ptr2, size_t size) +{ + const uint8_t* p1 = (const uint8_t*)ptr1; + const uint8_t* p2 = (const uint8_t*)ptr2; + + while (size >= 8) + { + uint64_t a = MemToLower8(MEM_PTR64U(p1)); + uint64_t b = MemToLower8(MEM_PTR64U(p2)); + if (a != b) + { + a = MEM_BSWAP64(a); + b = MEM_BSWAP64(b); + return (a > b) - (a < b); + } + + size -= 8; + p1 += 8; + p2 += 8; + } + + if (size & 4) // 4 <= size < 8 + { + uint64_t a0 = MEM_PTR32U(p1); + uint64_t b0 = MEM_PTR32U(p2); + uint64_t a1 = MEM_PTR32U(p1 + size - 4); + uint64_t b1 = MEM_PTR32U(p2 + size - 4); + uint64_t a = MEM_BSWAP64(MemToLower8(a0 | (a1 << 32))); + uint64_t b = MEM_BSWAP64(MemToLower8(b0 | (b1 << 32))); + return (a > b) - (a < b); + } + + if (size & 2) // 2 <= size < 4 + { + uint64_t a0 = MEM_PTR16U(p1); + uint64_t b0 = MEM_PTR16U(p2); + uint64_t a1 = MEM_PTR16U(p1 + size - 2); + uint64_t b1 = MEM_PTR16U(p2 + size - 2); + uint64_t tmp = MEM_BSWAP64(MemToLower8(a0 | (a1 << 16) | (b0 << 32) | (b1 << 48))); + uint32_t a = (uint32_t)(tmp >> 32); + uint32_t b = (uint32_t)tmp; + return (a > b) - (a < b); + } + + if (size) // 1 <= size < 2 + { + return MemToLower1(p1[0]) - MemToLower1(p2[0]); + } + + return 0; +} + +bool MemIsEqual_generic(const void* ptr1, const void* ptr2, size_t size) +{ + const uint8_t* p1 = (const uint8_t*)ptr1; + const uint8_t* p2 = (const uint8_t*)ptr2; + + while (size >= 8) + { + uint64_t a = MEM_PTR64U(p1); + uint64_t b = MEM_PTR64U(p2); + if (a != b) + { + return false; + } + + size -= 8; + p1 += 8; + p2 += 8; + } + + if (size & 4) // 4 <= size < 8 + { + uint32_t a0 = MEM_PTR32U(p1); + uint32_t b0 = MEM_PTR32U(p2); + uint32_t a1 = MEM_PTR32U(p1 + size - 4); + uint32_t b1 = MEM_PTR32U(p2 + size - 4); + return (a0 == b0) & (a1 == b1); + } + + if (size & 2) // 2 <= size < 4 + { + uint16_t a0 = MEM_PTR16U(p1); + uint16_t b0 = MEM_PTR16U(p2); + uint16_t a1 = MEM_PTR16U(p1 + size - 2); + uint16_t b1 = MEM_PTR16U(p2 + size - 2); + return (a0 == b0) & (a1 == b1); + } + + if (size) // 1 <= size < 2 + { + return p1[0] == p2[0]; + } + + return true; +} + +static inline uint64_t MemByteMask8(uint64_t value, uint8_t byte) +{ + const uint64_t splat = ~0ULL / 255; + const uint64_t msb = 0x80 * splat; + const uint64_t lsb = 0x01 * splat; + + uint64_t x = value ^ (byte * splat); + return (x - lsb) & (~x) & msb; +} + +size_t MemFind_generic(const void* ptr, size_t size, uint8_t value) +{ + const uint8_t* p = (const uint8_t*)ptr; + + size_t offset = 0; + + while (size >= 8) + { + uint64_t a = MEM_PTR64U(p); + uint64_t m = MemByteMask8(a, value); + if (m) + { + return offset + (MEM_CTZ64(m) / 8); + } + + offset += 8; + size -= 8; + p += 8; + } + + if (size & 4) // 4 <= size < 8 + { + uint64_t a0 = MEM_PTR32U(p); + uint64_t a1 = MEM_PTR32U(p + size - 4); + uint64_t a = a0 | (a1 << 32); + uint64_t m = MemByteMask8(a, value); + + size_t index = (m ? MEM_CTZ64(m) : 64) / 8; + + // index = (index < 4) ? index : (index - 4) + (size - 4); + index += (index >= 4) * (size - 8); + + return offset + index; + } + else if (size & 2) // 2 <= size < 4 + { + uint32_t a0 = MEM_PTR16U(p); + uint32_t a1 = MEM_PTR16U(p + size - 2); + uint32_t a = a0 | (a1 << 16); + uint32_t m = (uint32_t)MemByteMask8(a, value); + + size_t index = (m ? MEM_CTZ32(m) : 32) / 8; + + // index = (index < 2) ? index : (index - 2) + (size - 2); + index += (index >= 2) * (size - 4); + + return offset + index; + } + else if (size) // size == 1 + { + return offset + (p[0] == value ? 0 : 1); + } + + return offset + size; +} + +size_t MemFindNot_generic(const void* ptr, size_t size, uint8_t value) +{ + const uint8_t* p = (const uint8_t*)ptr; + + size_t offset = 0; + + while (size >= 8) + { + uint64_t a = MEM_PTR64U(p); + uint64_t m = MemByteMask8(a, value) ^ 0x8080808080808080; + if (m) + { + return offset + (MEM_CTZ64(m) / 8); + } + + offset += 8; + size -= 8; + p += 8; + } + + if (size & 4) // 4 <= size < 8 + { + uint64_t a0 = MEM_PTR32U(p); + uint64_t a1 = MEM_PTR32U(p + size - 4); + uint64_t a = a0 | (a1 << 32); + uint64_t m = MemByteMask8(a, value) ^ 0x8080808080808080; + + size_t index = (m ? MEM_CTZ64(m) : 64) / 8; + + // index = (index < 4) ? index : (index - 4) + (size - 4); + index += (index >= 4) * (size - 8); + + return offset + index; + } + else if (size & 2) // 2 <= size < 4 + { + uint32_t a0 = MEM_PTR16U(p); + uint32_t a1 = MEM_PTR16U(p + size - 2); + uint32_t a = a0 | (a1 << 16); + uint32_t m = (uint32_t)MemByteMask8(a, value) ^ 0x80808080; + + size_t index = (m ? MEM_CTZ32(m) : 32) / 8; + + // index = (index < 2) ? index : (index - 2) + (size - 2); + index += (index >= 2) * (size - 4); + + return offset + index; + } + else if (size) // size == 1 + { + return offset + (p[0] != value ? 0 : 1); + } + + return offset + size; +} + + int MemCompare(const void* ptr1, const void* ptr2, size_t size) { #if MEM_ARCH_X64 @@ -2653,11 +4036,11 @@ int MemCompare(const void* ptr1, const void* ptr2, size_t size) } return MemCompare_sse2(ptr1, ptr2, size); #elif MEM_ARCH_ARM64 - return MemCompare_arm64(ptr1, ptr2, size); + return MemCompare_neon(ptr1, ptr2, size); #elif MEM_ARCH_RVV return MemCompare_rvv(ptr1, ptr2, size); #else -# error N/A + return MemCompare_generic(ptr1, ptr2, size); #endif } @@ -2675,11 +4058,11 @@ int MemCompareI(const void* ptr1, const void* ptr2, size_t size) } return MemCompareI_sse2(ptr1, ptr2, size); #elif MEM_ARCH_ARM64 - return MemCompareI_arm64(ptr1, ptr2, size); + return MemCompareI_neon(ptr1, ptr2, size); #elif MEM_ARCH_RVV return MemCompareI_rvv(ptr1, ptr2, size); #else -# error N/A + return MemCompareI_generic(ptr1, ptr2, size); #endif } @@ -2697,11 +4080,11 @@ bool MemIsEqual(const void* ptr1, const void* ptr2, size_t size) } return MemIsEqual_sse2(ptr1, ptr2, size); #elif MEM_ARCH_ARM64 - return MemIsEqual_arm64(ptr1, ptr2, size); + return MemIsEqual_neon(ptr1, ptr2, size); #elif MEM_ARCH_RVV return MemIsEqual_rvv(ptr1, ptr2, size); #else -# error N/A + return MemIsEqual_generic(ptr1, ptr2, size); #endif } @@ -2719,12 +4102,34 @@ size_t MemFind(const void* ptr, size_t size, uint8_t value) } return MemFind_sse2(ptr, size, value); #elif MEM_ARCH_ARM64 - return MemFind_arm64(ptr, size, value); + return MemFind_neon(ptr, size, value); #elif MEM_ARCH_RVV return MemFind_rvv(ptr, size, value); #else -# error N/A + return MemFind_generic(ptr, size, value); #endif } -#endif // defined(MEM_STATIC) || defined(MEM_IMPLEMENTATION) +size_t MemFindNot(const void* ptr, size_t size, uint8_t value) +{ +#if MEM_ARCH_X64 + int cpuid = MemCPUID(); + if (cpuid & MEM_CPUID_AVX512) + { + return MemFindNot_avx512(ptr, size, value); + } + else if (cpuid & MEM_CPUID_AVX2) + { + return MemFindNot_avx2(ptr, size, value); + } + return MemFindNot_sse2(ptr, size, value); +#elif MEM_ARCH_ARM64 + return MemFindNot_neon(ptr, size, value); +#elif MEM_ARCH_RVV + return MemFindNot_rvv(ptr, size, value); +#else + return MemFindNot_generic(ptr, size, value); +#endif +} + +#endif // defined(MEM_STATIC) || defined(MEM_IMPLEMENTATION) \ No newline at end of file From 84d88b394638a72b22854246f9008a639e362209 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 17 Aug 2026 13:11:24 -0700 Subject: [PATCH 050/251] discard environment variable RAD_TYPE_SERVER Linker already provides /RAD_TYPE_SERVER that switches linker into the type server mode, having a environment variable is redundant --- src/linker/lnk.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index e66833b9..4d8bbf7c 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -7049,21 +7049,6 @@ entry_point(CmdLine *cmdline) TP_Context *tp = tp_alloc(scratch.arena, config->worker_count, config->max_worker_count, config->shared_thread_pool_name); TP_Arena *tp_arena = tp_arena_alloc(tp); - // detect type server from the environment - { - HashMap env = lnk_env_vars_from_process_info(scratch.arena, get_process_info(), LNK_EnvVarRule_Current); - LNK_EnvVar *type_server_var = lnk_env_var_from_mapf(&env, "RAD_TYPE_SERVER"); - if (type_server_var) { - U64 do_type_server = 0; - if (lnk_env_var_to_u64(&env, type_server_var, &do_type_server)) { - if (do_type_server) { - lnk_config_pushf(config, "/RAD_TYPE_SERVER"); - lnk_log(LNK_Log_Debug, "type server mode was enabled from the environment\n"); - } - } - } - } - if (lnk_get_log_status(LNK_Log_Debug)) { String8 full_cmd_line = str8_list_join(scratch.arena, &config->raw_cmd_line, &(StringJoin){ .sep = str8_lit_comp(" ") }); lnk_fprintf(stderr, "--------------------------------------------------------------------------------\n"); From b8574e3a0bb2f5d2f6c0c2b772f7a087b7e6bf92 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 17 Aug 2026 13:13:01 -0700 Subject: [PATCH 051/251] left a todo in the linker IO code --- src/linker/lnk_io.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/linker/lnk_io.c b/src/linker/lnk_io.c index 8e522dfb..7e39c4cd 100644 --- a/src/linker/lnk_io.c +++ b/src/linker/lnk_io.c @@ -203,9 +203,9 @@ THREAD_POOL_TASK_FUNC(lnk_data_from_file_path_task) { LNK_DiskReader *task = raw_task; - File handle = task->handle_arr[task_id]; - U64 buffer_size = task->size_arr[task_id]; - U8 *buffer = task->buffer + task->off_arr[task_id]; + File handle = task->handle_arr[task_id]; + U64 buffer_size = task->size_arr[task_id]; + U8 *buffer = task->buffer + task->off_arr[task_id]; U64 read_size = lnk_read_file(&handle, buffer, buffer_size); Assert(read_size == buffer_size); @@ -218,21 +218,30 @@ THREAD_POOL_TASK_FUNC(lnk_memory_map_file_task) { LNK_DiskReader *task = raw_task; Temp scratch = scratch_begin(&arena, 1); + #if OS_WINDOWS - String16 path16 = str16_from_8(scratch.arena, task->path_arr.v[task_id]); + String16 path16 = str16_from_8(scratch.arena, task->path_arr.v[task_id]); + + // TODO: deprecate CoW file maps; they count against private byte space and * by design * do not allow over-commit; + // and if program's total file map size exceeds the cap, kernel terminates the whole process with an out-of-memory error; + // exception handler helpes us work-around this limitation but we stil cannot link on devices with small private byte space; if (task->io_flags & LNK_IO_Flags_MemoryMapFilesReadWrite) { HANDLE file_handle = CreateFileW(path16.str, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (file_handle != INVALID_HANDLE_VALUE) { HANDLE mapping_handle = CreateFileMappingA(file_handle, 0, PAGE_READWRITE, 0, 0, 0); if (mapping_handle != INVALID_HANDLE_VALUE) { LARGE_INTEGER file_size = {0}; GetFileSizeEx(file_handle, &file_size); + void *file_data = MapViewOfFile(mapping_handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, file_size.QuadPart); if (file_data) { task->data_arr.v[task_id] = str8(file_data, file_size.QuadPart); } + CloseHandle(mapping_handle); } + CloseHandle(file_handle); } } else { From 76e267482f6f73b0b48b0cbf59ad9319f8ad93f1 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 17 Aug 2026 18:21:05 -0700 Subject: [PATCH 052/251] improve handling of the response files * reject cyclic response files * prepend and append command line defined in LINK and _LINK_ * reduce config wrappers --- src/linker/lnk.c | 234 ++++--------------- src/linker/lnk.h | 4 - src/linker/lnk_cmd_line.c | 20 +- src/linker/lnk_config.c | 436 +++++++++++++++++++++++++++--------- src/linker/lnk_config.h | 6 +- src/linker/lnk_debug_info.c | 26 ++- src/linker/lnk_debug_info.h | 1 + src/linker/lnk_io.c | 43 ++-- src/linker/lnk_io.h | 5 +- 9 files changed, 433 insertions(+), 342 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 4d8bbf7c..a48614ad 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -130,171 +130,6 @@ // ----------------------------------------------------------------------------- -internal LNK_CmdLine -lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) -{ - Temp scratch = scratch_begin(&arena, 1); - LNK_CmdLine cmd_line = {0}; - - char *default_opts[] = { - "/ALIGN:4096", - "/DEBUG:none", - "/FILEALIGN:512", - "/HIGHENTROPYVA", - "/MANIFESTUAC:\"level='asInvoker' uiAccess='false'\"", - "/NXCOMPAT", - "/LARGEADDRESSAWARE", - "/PDBALTPATH:%_RAD_PDB_PATH%", - "/PDBPAGESIZE:4096", - (char*)str8f(scratch.arena, "/HEAP:%llu,%llu", MB(1), KB(4)).str, - (char*)str8f(scratch.arena, "/STACK:%llu,%llu", MB(1), KB(4)).str, - - "/RAD_BOOT_MODE:LINKER", - //"/RAD_BUILD_EXP", - "/RAD_BUILD_IMPLIB", - "/RAD_AGE:1", - "/RAD_CHECK_UNUSED_DELAY_LOAD_DLL", - "/RAD_DO_MERGE", - "/RAD_ENV_LIB", - "/RAD_EXE", - "/RAD_GUID:imageblake3", - "/RAD_LARGE_PAGES:no", - "/RAD_LINK_VER:14.0", - "/RAD_OS_VER:6.0", - "/RAD_PAGE_SIZE:4096", - "/RAD_PATH_STYLE:system", - "/RAD_PDB_HASH_TYPE_NAMES:NONE", - "/RAD_PDB_HASH_TYPE_NAME_LENGTH:8", - "/RAD_DEBUGALTPATH:%_RAD_RDI_PATH%", - "/RAD_MEMORY_MAP_FILES", - "/RAD_MAP_LINES_FOR_UNRESOLVED_SYMBOLS", - "/RAD_UNRESOLVED_SYMBOL_LIMIT:1000", - "/RAD_UNRESOLVED_SYMBOL_REF_LIMIT:10", - "/RAD_SORT_IMPORTS", - (char*)str8f(scratch.arena, "/RAD_MT_PATH:%s", LNK_MANIFEST_MERGE_TOOL_NAME).str, - (char*)str8f(scratch.arena, "/RAD_DATA_DIR_COUNT:%u", PE_DataDirectoryIndex_COUNT).str, - - // Set BLAKE3 as the default to match the LLVM default. - // - // When hash kinds conflict, radlink discards any .debug$H sections - // whose hash kind does not match the selected default. - "/RAD_DEBUG_TYPE_HASH:BLAKE3", - - // Use LLVM significant addresses hints for the /OPT:ICF. - "/LLVM_ADDRSIG", - - // By default keep full type names, override when TPI/IPI streams overflow. - "/RAD_PDB_HASH_TYPE_NAMES:NONE", - - // TODO: The ICF algorithm requires a cryptographic hash to establish - // equivalence. With xxHash and similar non-cryptographic hashes, - // the algorithm must compare each section property before - // deciding whether sections are truly identical. - "/RAD_ICF_HASH_KIND:BLAKE3", - }; - - char *push_opts[] = { - "/MERGE:.xdata=.rdata", - "/MERGE:.00cfg=.rdata", - // TODO: .tls must be always first contribution in .data section because compiler generates TLS relative movs - //"/MERGE:.tls=.data", - "/MERGE:.idata=.data", - "/MERGE:.didat=.data", - "/MERGE:.edata=.rdata", - "/MERGE:.RAD_LINK_PE_DEBUG_DIR=.rdata", - "/MERGE:.RAD_LINK_PE_DEBUG_DATA=.rdata", - - "/RAD_REMOVE_SECTION:.debug", - "/RAD_REMOVE_SECTION:.gehcont", - "/RAD_REMOVE_SECTION:.gfids", - "/RAD_REMOVE_SECTION:.gxfg", - - (char*)str8f(scratch.arena, "/RAD_WORKERS:%u", get_system_info()->logical_processor_count).str, - - // errors that are too verbose in release build - (char*)str8f(scratch.arena, "/RAD_IGNORE:%d", LNK_Warning_UnknownSwitch * (BUILD_DEBUG ? -1 : 1)).str, - (char*)str8f(scratch.arena, "/RAD_IGNORE:%d", LNK_Warning_UnknownDirective * (BUILD_DEBUG ? -1 : 1)).str, - (char*)str8f(scratch.arena, "/RAD_IGNORE:%d", LNK_Error_InvalidTypeIndex * (BUILD_DEBUG ? -1 : 1)).str, - - #if BUILD_DEBUG - "/RAD_LOG:debug", - "/RAD_LOG:io_write", - #else - (char*)str8f(scratch.arena, "/RAD_IGNORE:%u", LNK_Error_InvalidTypeIndex).str, - #endif - }; - -#define DefaultOpt(...) do { \ - LNK_CmdLine parsed_cmd_line = lnk_cmd_line_from_stringf_windows_rules(arena, __VA_ARGS__); \ - for EachNode(cmd, LNK_CmdOption, parsed_cmd_line.first_option) { \ - if (!lnk_cmd_line_has_switch(user_cmd_line, lnk_cmd_switch_type_from_string(cmd->string))) { \ - String8List value_strings = str8_list_copy(arena, &cmd->value_strings); \ - lnk_cmd_line_push_option_list(arena, &cmd_line, cmd->string, value_strings); \ - } \ - } \ -} while (0) - -#define PushOpt(...) do { \ - LNK_CmdLine parsed_cmd_line = lnk_cmd_line_from_stringf_windows_rules(arena, __VA_ARGS__); \ - lnk_cmd_line_concat_in_place(&cmd_line, &parsed_cmd_line); \ -} while (0) - - if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Dll)) { - DefaultOpt("/SUBSYSTEM:%S", pe_string_from_subsystem(PE_WindowsSubsystem_WINDOWS_GUI)); - } - if (!lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Brepro)) { - DefaultOpt("/RAD_TIME_STAMP:%u", get_process_start_time_unix()); - } - for EachIndex(i, ArrayCount(default_opts)) { - DefaultOpt("%s", default_opts[i]); - } - - for EachIndex(i, ArrayCount(push_opts)) { - PushOpt("%s", push_opts[i]); - } - - // when /FORCE is specified on the command line, do not stop on these errors - if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Force)) { - g_error_mode_arr[LNK_Error_UnresolvedSymbol] = LNK_ErrorMode_Continue; - g_error_mode_arr[LNK_Error_RelocationAgainstRemovedSection] = LNK_ErrorMode_Continue; - } - -#undef DefaultOpt -#undef PushOpt - scratch_end(scratch); - return cmd_line; -} - -internal LNK_Config * -lnk_config_from_argcv(CmdLine *cmdline) -{ - Temp scratch = scratch_begin(0,0); - - String8List raw_cmd_line = {0}; - for (U64 i = 1; i < cmdline->argc; i += 1) { str8_list_push(scratch.arena, &raw_cmd_line, str8_cstring(cmdline->argv[i])); } - -#if PROFILE_TELEMETRY - tmMessage(0, TMMF_ICON_NOTE, "Command Line: %.*s", str8_varg(str8_list_join(scratch.arena, &raw_cmd_line, &(StringJoin){ .sep = str8_lit_comp(" ") }))); -#endif - - // make command line - LNK_CmdLine cmd_line_msvc = {0}; - { - String8List unwrapped_cmd_line = lnk_unwrap_rsp(scratch.arena, raw_cmd_line); - LNK_CmdLine user_cmd_line = lnk_cmd_line_parse_windows_rules(scratch.arena, unwrapped_cmd_line); - user_cmd_line.raw_cmd_line = raw_cmd_line; - LNK_CmdLine default_cmd_line = lnk_make_default_cmd_line(scratch.arena, user_cmd_line); - lnk_cmd_line_concat_in_place(&cmd_line_msvc, &default_cmd_line); - lnk_cmd_line_concat_in_place(&cmd_line_msvc, &user_cmd_line); - } - - // init config - LNK_Config *config = lnk_config_init(cmd_line_msvc); - - scratch_end(scratch); - return config; -} - internal String8 lnk_make_full_path(Arena *arena, PathStyle system_path_style, String8 work_dir, String8 path) { @@ -500,8 +335,9 @@ lnk_manifest_from_inputs(Arena *arena, lnk_merge_manifest_files(mt_path, merged_manifest_path, unique_input_manifest_paths); // read mt.exe output from disk - manifest_data = lnk_read_data_from_file_path(arena, io_flags, merged_manifest_path); - if (manifest_data.size == 0) { + B8 was_manifest_read; + manifest_data = lnk_read_data_from_file_path(arena, io_flags, merged_manifest_path, &was_manifest_read); + if (was_manifest_read == 0) { lnk_error(LNK_Error_Mt, "unable to find mt.exe output manifest on disk, expected path \"%S\"", merged_manifest_path); } @@ -1228,13 +1064,17 @@ lnk_inputer_flush(Arena *arena, TP_Context *tp, LNK_Inputer *inputer, LNK_IO_Fla ProfEnd(); ProfBegin("Load Inputs From Disk"); - String8Array thin_input_datas = lnk_read_data_from_file_path_parallel(tp, inputer->arena, io_flags, thin_input_paths); - B32 is_mapped = !!(io_flags & (LNK_IO_Flags_MemoryMapFilesReadWrite|LNK_IO_Flags_MemoryMapFilesReadOnly)); + + B8 *thin_input_was_read = push_array(scratch.arena, B8, thin_input_paths.count); + String8Array thin_input_datas = lnk_read_data_from_file_path_parallel(tp, inputer->arena, io_flags, thin_input_paths, thin_input_was_read); + B32 is_mapped = !!(io_flags & (LNK_IO_Flags_MemoryMapFilesReadWrite|LNK_IO_Flags_MemoryMapFilesReadOnly)); + for EachIndex(thin_input_idx, thin_inputs_count) { - thin_inputs[thin_input_idx]->has_disk_read_failed = thin_input_datas.v[thin_input_idx].size == 0; - thin_inputs[thin_input_idx]->owns_file_map = is_mapped && !thin_inputs[thin_input_idx]->has_disk_read_failed; + thin_inputs[thin_input_idx]->has_disk_read_failed = !thin_input_was_read[thin_input_idx]; + thin_inputs[thin_input_idx]->owns_file_map = is_mapped && !thin_inputs[thin_input_idx]->has_disk_read_failed; thin_inputs[thin_input_idx]->data = thin_input_datas.v[thin_input_idx]; } + ProfEnd(); ProfBegin("Disk Read Check"); @@ -2328,18 +2168,21 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer } ProfBegin("Load .res files from disk"); - for (String8Node *node = config->input_list[LNK_Input_Res].first; node != 0; node = node->next) { - String8 res_data = lnk_read_data_from_file_path(scratch.arena, config->io_flags, node->string); - if (res_data.size > 0) { - if (pe_is_res(res_data)) { - str8_list_push(scratch.arena, &res_data_list, res_data); - String8 stable_res_path = lnk_make_full_path(scratch.arena, config->path_style, config->work_dir, node->string); - str8_list_push(scratch.arena, &res_path_list, stable_res_path); - } else { - lnk_error(LNK_Error_LoadRes, "file is not of RES format: %S", node->string); - } - } else { + for EachNode(node, String8Node, config->input_list[LNK_Input_Res].first) { + B8 was_read = 0; + String8 res_data = lnk_read_data_from_file_path(scratch.arena, config->io_flags, node->string, &was_read); + + if (was_read == 0) { lnk_error(LNK_Error_LoadRes, "unable to open res file: %S", node->string); + continue; + } + + if (pe_is_res(res_data)) { + str8_list_push(scratch.arena, &res_data_list, res_data); + String8 stable_res_path = lnk_make_full_path(scratch.arena, config->path_style, config->work_dir, node->string); + str8_list_push(scratch.arena, &res_path_list, stable_res_path); + } else { + lnk_error(LNK_Error_LoadRes, "file is not of RES format: %S", node->string); } } ProfEnd(); @@ -2357,8 +2200,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer { ProfBegin("Build * Linker * Obj"); String8 obj_name = str8_lit("* Linker *"); - String8 raw_cmd_line = str8_list_join(scratch.arena, &config->raw_cmd_line, &(StringJoin){ str8_lit_comp(""), str8_lit_comp(" "), str8_lit_comp("") }); - String8 obj_data = lnk_make_linker_coff_obj(arena->v[0], config->time_stamp, config->machine, config->work_dir, lnk_get_image_name(config), config->pdb_name, raw_cmd_line, obj_name); + String8 obj_data = lnk_make_linker_coff_obj(arena->v[0], config->time_stamp, config->machine, config->work_dir, lnk_get_image_name(config), config->pdb_name, config->raw_cmd_line, obj_name); lnk_inputer_push_obj_linkgen(inputer, 0, obj_name, obj_data); ProfEnd(); } @@ -6893,9 +6735,12 @@ lnk_run_type_server(TP_Context *tp, TP_Arena *arena, LNK_Config *config) Temp scratch = scratch_begin(arena->v, arena->count); // push default type-server switches - lnk_config_pushf(config, "/DEBUG:GHASH"); - lnk_config_pushf(config, "/NOD"); - lnk_config_pushf(config, "/RAD_WRITE_TEMP_FILES"); + { + LNK_CmdLine default_line = lnk_cmd_line_from_stringf_windows_rules(scratch.arena, "/DEBUG:GHASH /NOD /RAD_WRITE_TEMP_FILES"); + for EachNode(cmd, LNK_CmdOption, default_line.first_option) { + lnk_apply_cmd_option_to_config(config, cmd->string, cmd->value_strings, &(LNK_Obj){0}); + } + } // load objs U64 objs_count = 0; @@ -7045,18 +6890,21 @@ entry_point(CmdLine *cmdline) Temp scratch = scratch_begin(0,0); lnk_log_begin(); - LNK_Config *config = lnk_config_from_argcv(cmdline); - TP_Context *tp = tp_alloc(scratch.arena, config->worker_count, config->max_worker_count, config->shared_thread_pool_name); - TP_Arena *tp_arena = tp_arena_alloc(tp); + // init config from the command line + LNK_Config *config = lnk_config_init(cmdline->argc, cmdline->argv); if (lnk_get_log_status(LNK_Log_Debug)) { - String8 full_cmd_line = str8_list_join(scratch.arena, &config->raw_cmd_line, &(StringJoin){ .sep = str8_lit_comp(" ") }); lnk_fprintf(stderr, "--------------------------------------------------------------------------------\n"); - lnk_fprintf(stderr, "Command Line: %.*s\n", str8_varg(full_cmd_line)); - lnk_fprintf(stderr, "Work Dir : %.*s\n", str8_varg(config->work_dir)); + lnk_fprintf(stderr, "Command Line: %S\n", config->raw_cmd_line); + lnk_fprintf(stderr, "Work Dir : %S\n", config->work_dir); lnk_fprintf(stderr, "--------------------------------------------------------------------------------\n"); } + // init thread pool + TP_Context *tp = tp_alloc(scratch.arena, config->worker_count, config->max_worker_count, config->shared_thread_pool_name); + TP_Arena *tp_arena = tp_arena_alloc(tp); + + // pick entry point switch (config->boot_mode) { case LNK_BootMode_Linker: lnk_run_linker (tp, tp_arena, config); break; case LNK_BootMode_TypeServer: lnk_run_type_server(tp, tp_arena, config); break; diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 7d27c247..3ca9e150 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -332,10 +332,6 @@ typedef struct String8 image_data; } LNK_P2R; -// --- Config ----------------------------------------------------------------- - -internal LNK_Config * lnk_config_from_cmdline(CmdLine *cmdline); - // --- Entry Point ------------------------------------------------------------- internal void lnk_run(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config); diff --git a/src/linker/lnk_cmd_line.c b/src/linker/lnk_cmd_line.c index 89c86bd2..bbde4684 100644 --- a/src/linker/lnk_cmd_line.c +++ b/src/linker/lnk_cmd_line.c @@ -129,9 +129,11 @@ internal void lnk_cmd_line_concat_in_place(LNK_CmdLine *list, LNK_CmdLine *to_concat) { if (list->option_count > 0) { - list->option_count += to_concat->option_count; - list->last_option->next = to_concat->first_option; - list->last_option = to_concat->last_option; + if (to_concat->option_count > 0) { + list->option_count += to_concat->option_count; + list->last_option->next = to_concat->first_option; + list->last_option = to_concat->last_option; + } str8_list_concat_in_place(&list->input_list, &to_concat->input_list); str8_list_concat_in_place(&list->raw_cmd_line, &to_concat->raw_cmd_line); } else { @@ -147,11 +149,9 @@ lnk_cmd_line_parse_windows_rules(Arena *arena, String8List arg_list) LNK_CmdLine cmd_line = {0}; cmd_line.raw_cmd_line = str8_list_copy(arena, &arg_list); - for (String8Node *arg_node = arg_list.first; arg_node != 0; arg_node = arg_node->next) { + for EachNode(arg_node, String8Node, arg_list.first) { String8 arg = arg_node->string; - B32 is_option = str8_match_lit("/", arg, StringMatchFlag_RightSideSloppy) || - str8_match_lit("-", arg, StringMatchFlag_RightSideSloppy); - if (is_option) { + if (str8_starts_with(arg, str8_lit("/")) || str8_starts_with(arg, str8_lit("-"))) { U64 param_start_pos = str8_find_needle(arg, 0, str8_lit(":"), 0); String8 option_name = str8_chop(arg, arg.size - param_start_pos); @@ -162,12 +162,14 @@ lnk_cmd_line_parse_windows_rules(Arena *arena, String8List arg_list) String8 value_string = str8_skip(arg, param_start_pos + 1); // make value list - String8List value_list_unowned = str8_split_by_string_chars(scratch.arena, value_string, str8_lit(","), 0); - String8List value_list = str8_list_copy(arena, &value_list_unowned); + String8List value_list_temp = str8_split_by_string_chars(scratch.arena, value_string, str8_lit(","), 0); + String8List value_list = str8_list_copy(arena, &value_list_temp); // push command option_name = push_str8_copy(arena, option_name); lnk_cmd_line_push_option_list(arena, &cmd_line, option_name, value_list); + } else if (str8_starts_with(arg, str8_lit("@"))) { + lnk_cmd_line_push_option_list(arena, &cmd_line, push_str8_copy(arena, arg), (String8List){0}); } else { str8_list_push(arena, &cmd_line.input_list, push_str8_copy(arena, arg)); } diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 0aba8f5e..ad4f2f2f 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -66,49 +66,49 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Version, 0, "VERSION", "", "Image version." }, { LNK_CmdSwitch_WholeArchive, 0, "WHOLEARCHIVE", "[:LIBNAME]", "Force linker to pull in all objs from the specified lib." }, - { LNK_CmdSwitch_Rad_Age, 0, "RAD_AGE", ":#", "Age embeded in EXE and PDB, used to validate incremental build. Default is 1." }, + { LNK_CmdSwitch_Rad_Age, 0, "RAD_AGE", ":#", "Age embeded in EXE and PDB, used to validate incremental build. Default is 1." }, //{ LNK_CmdSwitch_Rad_BuildExp, 0, "RAD_BUILD_EXP", "[:NO]", "Build export data." }, - { LNK_CmdSwitch_Rad_BuildInfo, 0, "RAD_BUILD_INFO", "", "Print build info and exit." }, - { LNK_CmdSwitch_Rad_BuildImpLib, 0, "RAD_BUILD_IMPLIB", "[:NO]", "Build import library." }, - { LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll, 0, "RAD_CHECK_UNUSED_DELAY_LOAD_DLL", "[:NO]", "Check for unused delay load dlls." }, - { LNK_CmdSwitch_Rad_DataDirCount, 0, "RAD_DATA_DIR_COUNT", ":#", "Internal default for PE optional header data directory count." }, - { LNK_CmdSwitch_Rad_Map, 0, "RAD_MAP", ":FILENAME", "Emit file with the output image's layout description." }, - { LNK_CmdSwitch_Rad_MapLinesForUnresolvedSymbols, 0, "RAD_MAP_LINES_FOR_UNRESOLVED_SYMBOLS", "[:NO]", "Use debug info to print source file location for unresolved symbol" }, + { LNK_CmdSwitch_Rad_BuildInfo, 0, "RAD_BUILD_INFO", "", "Print build info and exit." }, + { LNK_CmdSwitch_Rad_BuildImpLib, 0, "RAD_BUILD_IMPLIB", "[:NO]", "Build import library." }, + { LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll, 0, "RAD_CHECK_UNUSED_DELAY_LOAD_DLL", "[:NO]", "Check for unused delay load dlls." }, + { LNK_CmdSwitch_Rad_DataDirCount, 0, "RAD_DATA_DIR_COUNT", ":#", "Internal default for PE optional header data directory count." }, + { LNK_CmdSwitch_Rad_Map, 0, "RAD_MAP", ":FILENAME", "Emit file with the output image's layout description." }, + { LNK_CmdSwitch_Rad_MapLinesForUnresolvedSymbols, 0, "RAD_MAP_LINES_FOR_UNRESOLVED_SYMBOLS", "[:NO]", "Use debug info to print source file location for unresolved symbol" }, { LNK_CmdSwitch_Rad_MemoryMapFiles, 0, "RAD_MEMORY_MAP_FILES", "[:{NO|READ_ONLY|READ_WRITE}]", "When enabled, files are memory-mapped instead of being read entirely on request." }, - { LNK_CmdSwitch_Rad_BootMode, 0, "RAD_BOOT_MODE", "[:LINKER|TYPE_SERVER]", "Overrides default boot program." }, - { LNK_CmdSwitch_Rad_Debug, 0, "RAD_DEBUG", "[:NO]", "Emit RAD debug info file." }, - { LNK_CmdSwitch_Rad_DebugAltPath, 0, "RAD_DEBUGALTPATH", ":PATH", "Alternative output path for the RDI." }, - { LNK_CmdSwitch_Rad_DebugName, 0, "RAD_DEBUG_NAME", ":FILENAME", "Set file name for RAD debug info file." }, - { LNK_CmdSwitch_Rad_DelayBind, 0, "RAD_DELAY_BIND", "[:NO]", "Emit bindable imports." }, - { LNK_CmdSwitch_Rad_DoMerge, 0, "RAD_DO_MERGE", "[:NO]", "Set whether the linker should execute /MERGE." }, - { LNK_CmdSwitch_Rad_EnvLib, 0, "RAD_ENV_LIB", "[:NO]", "Collect libraries from %%LIB%% and %%LIBPATH%% varibles." }, - { LNK_CmdSwitch_Rad_Exe, 0, "RAD_EXE", "[:NO]", "Set EXE bit in the image header." }, + { LNK_CmdSwitch_Rad_BootMode, 0, "RAD_BOOT_MODE", "[:LINKER|TYPE_SERVER]", "Overrides default boot program." }, + { LNK_CmdSwitch_Rad_Debug, 0, "RAD_DEBUG", "[:NO]", "Emit RAD debug info file." }, + { LNK_CmdSwitch_Rad_DebugAltPath, 0, "RAD_DEBUGALTPATH", ":PATH", "Alternative output path for the RDI." }, + { LNK_CmdSwitch_Rad_DebugName, 0, "RAD_DEBUG_NAME", ":FILENAME", "Set file name for RAD debug info file." }, + { LNK_CmdSwitch_Rad_DelayBind, 0, "RAD_DELAY_BIND", "[:NO]", "Emit bindable imports." }, + { LNK_CmdSwitch_Rad_DoMerge, 0, "RAD_DO_MERGE", "[:NO]", "Set whether the linker should execute /MERGE." }, + { LNK_CmdSwitch_Rad_EnvLib, 0, "RAD_ENV_LIB", "[:NO]", "Collect libraries from %%LIB%% and %%LIBPATH%% varibles." }, + { LNK_CmdSwitch_Rad_Exe, 0, "RAD_EXE", "[:NO]", "Set EXE bit in the image header." }, { LNK_CmdSwitch_Rad_Guid, 0, "RAD_GUID", ":{IMAGEBLAKE3|XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX}", "The image guid that is embeded in the debug info." }, - { LNK_CmdSwitch_Rad_LargePages, 0, "RAD_LARGE_PAGES", "[:NO]", "Disabled by default on Windows." }, - { LNK_CmdSwitch_Rad_LinkVer, 0, "RAD_LINK_VER", ":##,##", "Linker version." }, - { LNK_CmdSwitch_Rad_Log, 0, "RAD_LOG", ":{ALL,INPUT_OBJ,INPUT_LIB,IO,LINK_STATS,TIMERS}", "Loggers." }, - { LNK_CmdSwitch_Rad_MtPath, 0, "RAD_MT_PATH", ":EXEPATH", "Exe path to the manifest tool (default: " LNK_MANIFEST_MERGE_TOOL_NAME ")" }, - { LNK_CmdSwitch_Rad_OsVer, 0, "RAD_OS_VER", ":##,##", "OS version." }, - { LNK_CmdSwitch_Rad_PageSize, 0, "RAD_PAGE_SIZE", ":#", "Must be power of two." }, - { LNK_CmdSwitch_Rad_PathStyle, 0, "RAD_PATH_STYLE", ":{WindowsAbsolute|UnixAbsolute}", "Set path style in the PDB." }, - { LNK_CmdSwitch_Rad_PdbHashTypeNameLength, 0, "RAD_PDB_HASH_TYPE_NAME_LENGTH", ":#", "Number of hash bytes to use to replace type name. Default 8 bytes (Max 16)." }, - { LNK_CmdSwitch_Rad_PdbHashTypeNameMap, 0, "RAD_PDB_HASH_TYPE_NAME_MAP", ":FILENAME", "Produce map file with hash -> type name mappings." }, - { LNK_CmdSwitch_Rad_PdbHashTypeNames, 0, "RAD_PDB_HASH_TYPE_NAMES", ":{NONE|LENIENT|FULL}", "Replace type names in LF_STRUCTURE and LF_CLASS with hashes." }, - { LNK_CmdSwitch_Rad_RemoveSection, 0, "RAD_REMOVE_SECTION", ":NAME", "Removes a section from the image." }, - { LNK_CmdSwitch_Rad_SharedThreadPool, 0, "RAD_SHARED_THREAD_POOL", "[:STRING]", "Default value \"" LNK_DEFAULT_THREAD_POOL_NAME "\"" }, - { LNK_CmdSwitch_Rad_SharedThreadPoolMaxWorkers, 0, "RAD_SHARED_THREAD_POOL_MAX_WORKERS", ":#", "Set maximum number of workers in a thread pool." }, - { LNK_CmdSwitch_Rad_SortImports, 0, "RAD_SORT_IMPORTS", "[:NO]", "Sort static and delayed import tables by their order of appearance in libs, without assuming link order." }, - { LNK_CmdSwitch_Rad_IcfHashKind, 0, "RAD_ICF_HASH_KIND", "{BLAKE3|XXHASH}", "Sets hashing algorithm for /OPT:ICF." }, - { LNK_CmdSwitch_Rad_Ignore, 0, "RAD_IGNORE", ":#", "Ignore the specified RAD linker warning." }, - { LNK_CmdSwitch_Rad_ImageAltPath, 0, "RAD_IMAGEALTPATH", ":FILENAME", "Alternative name for the image" }, - { LNK_CmdSwitch_Rad_WriteTempFiles, 0, "RAD_WRITE_TEMP_FILES", "[:NO]", "When speicifed linker writes image and debug info to temporary files and renames after link is done." }, - { LNK_CmdSwitch_Rad_TimeStamp, 0, "RAD_TIME_STAMP", ":#", "Time stamp embeded in EXE and PDB." }, - { LNK_CmdSwitch_Rad_DebugTypeHash, 0, "RAD_DEBUG_TYPE_HASH", ":{BLAKE3|XXHASH}", "Sets hashing algorithm for debug type merging." }, - { LNK_CmdSwitch_Rad_UnresolvedSymbolLimit, 0, "RAD_UNRESOLVED_SYMBOL_LIMIT", ":#", "Limits number of unresolved symbol errors linker reports." }, - { LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit, 0, "RAD_UNRESOLVED_SYMBOL_REF_LIMIT", ":#", "Limit number of unresolved symbol references linker reports." }, - { LNK_CmdSwitch_Rad_Version, 0, "RAD_VERSION", "", "Print version and exit." }, - { LNK_CmdSwitch_Rad_Workers, 0, "RAD_WORKERS", ":#", "Set number of workers created in the pool. Number is capped at 1024. When /RAD_SHARED_THREAD_POOL is specified this number cant exceed /RAD_SHARED_THREAD_POOL_MAX_WORKERS." }, - { LNK_CmdSwitch_Rad_WorkDir, 0, "RAD_WORK_DIR", ":PATH", "Working directory used for stable debug paths." }, + { LNK_CmdSwitch_Rad_LargePages, 0, "RAD_LARGE_PAGES", "[:NO]", "Disabled by default on Windows." }, + { LNK_CmdSwitch_Rad_LinkVer, 0, "RAD_LINK_VER", ":##,##", "Linker version." }, + { LNK_CmdSwitch_Rad_Log, 0, "RAD_LOG", ":{ALL,INPUT_OBJ,INPUT_LIB,IO,LINK_STATS,TIMERS}", "Loggers." }, + { LNK_CmdSwitch_Rad_MtPath, 0, "RAD_MT_PATH", ":EXEPATH", "Exe path to the manifest tool (default: " LNK_MANIFEST_MERGE_TOOL_NAME ")" }, + { LNK_CmdSwitch_Rad_OsVer, 0, "RAD_OS_VER", ":##,##", "OS version." }, + { LNK_CmdSwitch_Rad_PageSize, 0, "RAD_PAGE_SIZE", ":#", "Must be power of two." }, + { LNK_CmdSwitch_Rad_PathStyle, 0, "RAD_PATH_STYLE", ":{WindowsAbsolute|UnixAbsolute}", "Set path style in the PDB." }, + { LNK_CmdSwitch_Rad_PdbHashTypeNameLength, 0, "RAD_PDB_HASH_TYPE_NAME_LENGTH", ":#", "Number of hash bytes to use to replace type name. Default 8 bytes (Max 16)." }, + { LNK_CmdSwitch_Rad_PdbHashTypeNameMap, 0, "RAD_PDB_HASH_TYPE_NAME_MAP", ":FILENAME", "Produce map file with hash -> type name mappings." }, + { LNK_CmdSwitch_Rad_PdbHashTypeNames, 0, "RAD_PDB_HASH_TYPE_NAMES", ":{NONE|LENIENT|FULL}", "Replace type names in LF_STRUCTURE and LF_CLASS with hashes." }, + { LNK_CmdSwitch_Rad_RemoveSection, 0, "RAD_REMOVE_SECTION", ":NAME", "Removes a section from the image." }, + { LNK_CmdSwitch_Rad_SharedThreadPool, 0, "RAD_SHARED_THREAD_POOL", "[:STRING]", "Default value \"" LNK_DEFAULT_THREAD_POOL_NAME "\"" }, + { LNK_CmdSwitch_Rad_SharedThreadPoolMaxWorkers, 0, "RAD_SHARED_THREAD_POOL_MAX_WORKERS", ":#", "Set maximum number of workers in a thread pool." }, + { LNK_CmdSwitch_Rad_SortImports, 0, "RAD_SORT_IMPORTS", "[:NO]", "Sort static and delayed import tables by their order of appearance in libs, without assuming link order." }, + { LNK_CmdSwitch_Rad_IcfHashKind, 0, "RAD_ICF_HASH_KIND", "{BLAKE3|XXHASH}", "Sets hashing algorithm for /OPT:ICF." }, + { LNK_CmdSwitch_Rad_Ignore, 0, "RAD_IGNORE", ":#", "Ignore the specified RAD linker warning." }, + { LNK_CmdSwitch_Rad_ImageAltPath, 0, "RAD_IMAGEALTPATH", ":FILENAME", "Alternative name for the image" }, + { LNK_CmdSwitch_Rad_WriteTempFiles, 0, "RAD_WRITE_TEMP_FILES", "[:NO]", "When speicifed linker writes image and debug info to temporary files and renames after link is done." }, + { LNK_CmdSwitch_Rad_TimeStamp, 0, "RAD_TIME_STAMP", ":#", "Time stamp embeded in EXE and PDB." }, + { LNK_CmdSwitch_Rad_DebugTypeHash, 0, "RAD_DEBUG_TYPE_HASH", ":{BLAKE3|XXHASH}", "Sets hashing algorithm for debug type merging." }, + { LNK_CmdSwitch_Rad_UnresolvedSymbolLimit, 0, "RAD_UNRESOLVED_SYMBOL_LIMIT", ":#", "Limits number of unresolved symbol errors linker reports." }, + { LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit, 0, "RAD_UNRESOLVED_SYMBOL_REF_LIMIT", ":#", "Limit number of unresolved symbol references linker reports." }, + { LNK_CmdSwitch_Rad_Version, 0, "RAD_VERSION", "", "Print version and exit." }, + { LNK_CmdSwitch_Rad_Workers, 0, "RAD_WORKERS", ":#", "Set number of workers created in the pool. Number is capped at 1024. When /RAD_SHARED_THREAD_POOL is specified this number cant exceed /RAD_SHARED_THREAD_POOL_MAX_WORKERS." }, + { LNK_CmdSwitch_Rad_WorkDir, 0, "RAD_WORK_DIR", ":PATH", "Working directory used for stable debug paths." }, { LNK_CmdSwitch_RadTypeServer, 0, "RAD_TYPE_SERVER", ":FILENAME", "Merge types and store them in the specified file. The filename must have the .rrt extension." }, @@ -157,7 +157,7 @@ global read_only struct internal LNK_CmdSwitchType lnk_cmd_switch_type_from_string(String8 name) { - for (U64 i = 0; i < ArrayCount(g_cmd_switch_map); i += 1) { + for EachElement(i, g_cmd_switch_map) { if (str8_match_cstr(g_cmd_switch_map[i].name, name, StringMatchFlag_CaseInsensitive)) { return g_cmd_switch_map[i].type; } @@ -1184,47 +1184,6 @@ lnk_print_help(void) scratch_end(scratch); } -internal String8List -lnk_unwrap_rsp(Arena *arena, String8List arg_list) -{ - Temp scratch = scratch_begin(&arena, 1); - - String8List result = {0}; - - for (String8Node *curr = arg_list.first; curr != 0; curr = curr->next) { - B32 is_rsp = str8_match_lit("@", curr->string, StringMatchFlag_RightSideSloppy); - if (is_rsp) { - // remove "@" - String8 name = str8_skip(curr->string, 1); - - if (file_path_exists(name)) { - // read rsp from disk - String8 file = lnk_read_data_from_file_path(scratch.arena, 0, name); - file = lnk_text_file_string_from_data(scratch.arena, file); - - // parse rsp - String8List rsp_args = lnk_arg_list_parse_windows_rules(scratch.arena, file); - - // handle case where rsp references another rsp - String8List list = lnk_unwrap_rsp(arena, rsp_args); - - // push arguments from rsp - list = str8_list_copy(arena, &list); - str8_list_concat_in_place(&result, &list); - } else { - lnk_error(LNK_Error_Cmdl, "unable to find rsp: %S", name); - } - } else { - // push regular argument - String8 str = push_str8_copy(arena, curr->string); - str8_list_push(arena, &result, str); - } - } - - scratch_end(scratch); - return result; -} - internal void lnk_apply_write_temp_files(Arena *arena, LNK_Config *config) { @@ -2486,8 +2445,14 @@ lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj) Temp scratch = scratch_begin(&config->arena, 1); // load DEF file - String8 raw_file = lnk_read_data_from_file_path(scratch.arena, config->io_flags, path); - String8 file = lnk_text_file_string_from_data(scratch.arena, raw_file); + B8 file_read = 0; + String8 raw_file = lnk_read_data_from_file_path(scratch.arena, config->io_flags, path, &file_read); + if ( ! file_read) { + lnk_error(LNK_Error_Cmdl, "failed to read DEF file: %S", path); + goto exit; + } + + String8 file = lnk_text_file_string_from_data(scratch.arena, raw_file); // parse & collect normalized DEF lines LNK_DefFileLineList def_lines = {0}; @@ -2723,32 +2688,285 @@ lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj) } } + exit:; scratch_end(scratch); } -internal void -lnk_config_pushf(LNK_Config *config, char *fmt, ...) +internal String8List +lnk_unwrap_cmd_line(Arena *arena, String8 arg_string) { - va_list args; - va_start(args, fmt); - LNK_CmdLine cmd_line = lnk_cmd_line_from_stringfv_windows_rules(config->arena, fmt, args); - va_end(args); + Temp scratch = scratch_begin(&arena, 1); - for EachNode(cmd, LNK_CmdOption, cmd_line.first_option) { - lnk_apply_cmd_option_to_config(config, cmd->string, cmd->value_strings, &(LNK_Obj){0}); + typedef struct LNK_RspFrame LNK_RspFrame; + struct LNK_RspFrame + { + LNK_RspFrame *next; + String8 path; + String8Node *arg_cursor; + }; + + String8List result = {0}; + String8List arg_list = lnk_arg_list_parse_windows_rules(scratch.arena, arg_string); + LNK_RspFrame *frame = &(LNK_RspFrame){ .arg_cursor = arg_list.first, .path = str8_lit("Command Line") }; + + while (frame) { + if (frame->arg_cursor == 0) { + frame = frame->next; + continue; + } + + String8Node *curr = frame->arg_cursor; + frame->arg_cursor = curr->next; + + if (str8_starts_with(curr->string, str8_lit("@"))) { + String8 file_name = str8_skip(curr->string, 1); + + // error check empty rsp argument + if (file_name.size == 0) { + lnk_error(LNK_Error_Cmdl, "RSP file name must follow '@'"); + continue; + } + + String8 full_path = full_path_from_path(scratch.arena, file_name); + + // error check for cyclic response files + { + B32 is_rsp_cyclic = 0; + for EachNode(parent, LNK_RspFrame, frame) { + if (path_match_normalized(parent->path, full_path)) { + is_rsp_cyclic = 1; + break; + } + } + if (is_rsp_cyclic) { + String8List cycle_list = {0}; + for EachNode(parent, LNK_RspFrame, frame) { + str8_list_push_front(scratch.arena, &cycle_list, parent->path); + } + str8_list_push(scratch.arena, &cycle_list, full_path); + String8 cycle_string = str8_list_join(scratch.arena, &cycle_list, &(StringJoin){ .sep = str8_lit(" -> ") }); + lnk_error(LNK_Error_Cmdl, "detected a cyclic RSP: %S", cycle_string); + continue; + } + } + + // read rsp from disk + B8 is_file_read = 0; + String8 file_data = lnk_read_data_from_file_path(scratch.arena, 0, file_name, &is_file_read); + if ( ! is_file_read) { + lnk_error(LNK_Error_Cmdl, "unable to find RSP: %S", file_name); + continue; + } + + // unapck rsp + String8 file_text = lnk_text_file_string_from_data(scratch.arena, file_data); + String8List file_args = lnk_arg_list_parse_windows_rules(scratch.arena, file_text); + + // push new frame with unapcked rsp + LNK_RspFrame *new_frame = push_array(scratch.arena, LNK_RspFrame, 1); + new_frame->path = full_path; + new_frame->arg_cursor = file_args.first; + SLLStackPush(frame, new_frame); + } else { + // append normal argument + str8_list_push(arena, &result, push_str8_copy(arena, curr->string)); + } } + + scratch_end(scratch); + return result; +} + +internal LNK_CmdLine +lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) +{ + Temp scratch = scratch_begin(&arena, 1); + LNK_CmdLine cmd_line = {0}; + + char *default_opts[] = { + "/ALIGN:4096", + "/DEBUG:none", + "/FILEALIGN:512", + "/HIGHENTROPYVA", + "/MANIFESTUAC:\"level='asInvoker' uiAccess='false'\"", + "/NXCOMPAT", + "/LARGEADDRESSAWARE", + "/PDBALTPATH:%_RAD_PDB_PATH%", + "/PDBPAGESIZE:4096", + (char*)str8f(scratch.arena, "/HEAP:%llu,%llu", MB(1), KB(4)).str, + (char*)str8f(scratch.arena, "/STACK:%llu,%llu", MB(1), KB(4)).str, + + "/RAD_BOOT_MODE:LINKER", + //"/RAD_BUILD_EXP", + "/RAD_BUILD_IMPLIB", + "/RAD_AGE:1", + "/RAD_CHECK_UNUSED_DELAY_LOAD_DLL", + "/RAD_DO_MERGE", + "/RAD_ENV_LIB", + "/RAD_EXE", + "/RAD_GUID:imageblake3", + "/RAD_LARGE_PAGES:no", + "/RAD_LINK_VER:14.0", + "/RAD_OS_VER:6.0", + "/RAD_PAGE_SIZE:4096", + "/RAD_PATH_STYLE:system", + "/RAD_PDB_HASH_TYPE_NAMES:NONE", + "/RAD_PDB_HASH_TYPE_NAME_LENGTH:8", + "/RAD_DEBUGALTPATH:%_RAD_RDI_PATH%", + "/RAD_MEMORY_MAP_FILES", + "/RAD_MAP_LINES_FOR_UNRESOLVED_SYMBOLS", + "/RAD_UNRESOLVED_SYMBOL_LIMIT:1000", + "/RAD_UNRESOLVED_SYMBOL_REF_LIMIT:10", + "/RAD_SORT_IMPORTS", + (char*)str8f(scratch.arena, "/RAD_MT_PATH:%s", LNK_MANIFEST_MERGE_TOOL_NAME).str, + (char*)str8f(scratch.arena, "/RAD_DATA_DIR_COUNT:%u", PE_DataDirectoryIndex_COUNT).str, + + // Set BLAKE3 as the default to match the LLVM default. + // + // When hash kinds conflict, radlink discards any .debug$H sections + // whose hash kind does not match the selected default. + "/RAD_DEBUG_TYPE_HASH:BLAKE3", + + // Use LLVM significant addresses hints for the /OPT:ICF. + "/LLVM_ADDRSIG", + + // By default keep full type names, override when TPI/IPI streams overflow. + "/RAD_PDB_HASH_TYPE_NAMES:NONE", + + // TODO: The ICF algorithm requires a cryptographic hash to establish + // equivalence. With xxHash and similar non-cryptographic hashes, + // the algorithm must compare each section property before + // deciding whether sections are truly identical. + "/RAD_ICF_HASH_KIND:BLAKE3", + }; + + char *push_opts[] = { + "/MERGE:.xdata=.rdata", + "/MERGE:.00cfg=.rdata", + // TODO: .tls must be always first contribution in .data section because compiler generates TLS relative movs + //"/MERGE:.tls=.data", + "/MERGE:.idata=.data", + "/MERGE:.didat=.data", + "/MERGE:.edata=.rdata", + "/MERGE:.RAD_LINK_PE_DEBUG_DIR=.rdata", + "/MERGE:.RAD_LINK_PE_DEBUG_DATA=.rdata", + + "/RAD_REMOVE_SECTION:.debug", + "/RAD_REMOVE_SECTION:.gehcont", + "/RAD_REMOVE_SECTION:.gfids", + "/RAD_REMOVE_SECTION:.gxfg", + + (char*)str8f(scratch.arena, "/RAD_WORKERS:%u", get_system_info()->logical_processor_count).str, + + // errors that are too verbose in release build + (char*)str8f(scratch.arena, "/RAD_IGNORE:%d", LNK_Warning_UnknownSwitch * (BUILD_DEBUG ? -1 : 1)).str, + (char*)str8f(scratch.arena, "/RAD_IGNORE:%d", LNK_Warning_UnknownDirective * (BUILD_DEBUG ? -1 : 1)).str, + (char*)str8f(scratch.arena, "/RAD_IGNORE:%d", LNK_Error_InvalidTypeIndex * (BUILD_DEBUG ? -1 : 1)).str, + + #if BUILD_DEBUG + "/RAD_LOG:debug", + "/RAD_LOG:io_write", + #else + (char*)str8f(scratch.arena, "/RAD_IGNORE:%u", LNK_Error_InvalidTypeIndex).str, + #endif + }; + +#define DefaultOpt(...) do { \ + LNK_CmdLine parsed_cmd_line = lnk_cmd_line_from_stringf_windows_rules(arena, __VA_ARGS__); \ + for EachNode(cmd, LNK_CmdOption, parsed_cmd_line.first_option) { \ + if (!lnk_cmd_line_has_switch(user_cmd_line, lnk_cmd_switch_type_from_string(cmd->string))) { \ + String8List value_strings = str8_list_copy(arena, &cmd->value_strings); \ + lnk_cmd_line_push_option_list(arena, &cmd_line, cmd->string, value_strings); \ + } \ + } \ +} while (0) + +#define PushOpt(...) do { \ + LNK_CmdLine parsed_cmd_line = lnk_cmd_line_from_stringf_windows_rules(arena, __VA_ARGS__); \ + lnk_cmd_line_concat_in_place(&cmd_line, &parsed_cmd_line); \ +} while (0) + + if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Dll)) { + DefaultOpt("/SUBSYSTEM:%S", pe_string_from_subsystem(PE_WindowsSubsystem_WINDOWS_GUI)); + } + if (!lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Brepro)) { + DefaultOpt("/RAD_TIME_STAMP:%u", get_process_start_time_unix()); + } + for EachIndex(i, ArrayCount(default_opts)) { + DefaultOpt("%s", default_opts[i]); + } + + for EachIndex(i, ArrayCount(push_opts)) { + PushOpt("%s", push_opts[i]); + } + + // when /FORCE is specified on the command line, do not stop on these errors + if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Force)) { + g_error_mode_arr[LNK_Error_UnresolvedSymbol] = LNK_ErrorMode_Continue; + g_error_mode_arr[LNK_Error_RelocationAgainstRemovedSection] = LNK_ErrorMode_Continue; + } + +#undef DefaultOpt +#undef PushOpt + scratch_end(scratch); + return cmd_line; } internal LNK_Config * -lnk_config_init(LNK_CmdLine cmd_line) +lnk_config_init(U64 argc, char **argv) { ProfBeginFunction(); Temp scratch = scratch_begin(0,0); + + // load env vars + HashMap env_vars = lnk_env_vars_from_process_info(scratch.arena, get_process_info(), LNK_EnvVarRule_Batch); + + // concat argv + String8 user_cmd_line; + { + String8List raw_cmd_line = {0}; + for (U64 i = 1; i < argc; i += 1) { + str8_list_push(scratch.arena, &raw_cmd_line, str8_cstring(argv[i])); + } + user_cmd_line = str8_list_join(scratch.arena, &raw_cmd_line, &(StringJoin){.sep = str8_lit(" ") }); + } + + // concat command line from env vars + String8 cmd_line_string = {0}; + { + LNK_EnvVar *link_var_pre = lnk_env_var_from_mapf(&env_vars, "LINK"); + LNK_EnvVar *link_var_post = lnk_env_var_from_mapf(&env_vars, "_LINK_"); + + String8List list = {0}; + if (link_var_pre) { + str8_list_push(scratch.arena, &list, link_var_pre->raw_value); + } + str8_list_push(scratch.arena, &list, user_cmd_line); + if (link_var_post) { + str8_list_push(scratch.arena, &list, link_var_post->raw_value); + } + + cmd_line_string = str8_list_join(scratch.arena, &list, &(StringJoin){.sep = str8_lit(" ")}); + } + +#if PROFILE_TELEMETRY + tmMessage(0, TMMF_ICON_NOTE, "Command Line: %.*s", str8_varg(cmd_line_string)); +#endif + + // concat default arguments + LNK_CmdLine cmd_line = {0}; + { + String8List cmd_unwrap = lnk_unwrap_cmd_line(scratch.arena, cmd_line_string); + LNK_CmdLine cmd_line_user = lnk_cmd_line_parse_windows_rules(scratch.arena, cmd_unwrap); + LNK_CmdLine cmd_line_default = lnk_make_default_cmd_line(scratch.arena, cmd_line_user); + lnk_cmd_line_concat_in_place(&cmd_line, &cmd_line_default); + lnk_cmd_line_concat_in_place(&cmd_line, &cmd_line_user); + } Arena *arena = arena_alloc(); LNK_Config *config = push_array(arena, LNK_Config, 1); config->arena = arena; - config->raw_cmd_line = str8_list_copy(arena, &cmd_line.raw_cmd_line); + config->raw_cmd_line = str8_copy(arena, cmd_line_string); config->work_dir = get_current_path(arena); config->force = lnk_cmd_line_has_switch(cmd_line, LNK_CmdSwitch_Force); @@ -2917,10 +3135,8 @@ lnk_config_init(LNK_CmdLine cmd_line) config->imp_lib_name = full_path_from_path(arena, config->imp_lib_name); config->manifest_name = full_path_from_path(arena, config->manifest_name); - // set up env vars - HashMap env_vars = lnk_env_vars_from_process_info(scratch.arena, get_process_info(), LNK_EnvVarRule_Batch); + // push linker env vars { - // define linker env vars struct { String8 key, value; } key_value_str8_table[] = { { str8_lit("_pdb"), str8_skip_last_slash(config->pdb_name) }, { str8_lit("_ext"), str8_skip_last_dot(config->out_path) }, @@ -2934,20 +3150,20 @@ lnk_config_init(LNK_CmdLine cmd_line) } lnk_env_var_batchf(scratch.arena, &env_vars, "%S=%S", key_value_str8_table[i].key, key_value_str8_table[i].value); } + } - if (config->flags & LNK_ConfigFlag_EnvLib) { - // collect LIB and LIBPATH - struct { String8List *config_list; char *key; } key_str8_list[] = { - { &config->lib_dir_list, "lib" }, - { &config->lib_dir_list, "lib_path" }, - }; - for EachElement(i, key_str8_list) { - LNK_EnvVar *var = lnk_env_var_from_mapf(&env_vars, key_str8_list[i].key); - if (var) { - String8List value = lnk_value_list_from_env_var(arena, var); - String8List value_copy = str8_list_copy(config->arena, &value); - str8_list_concat_in_place(key_str8_list[i].config_list, &value_copy); - } + // collect LIB and LIBPATH + if (config->flags & LNK_ConfigFlag_EnvLib) { + struct { String8List *config_list; char *key; } key_str8_list[] = { + { &config->lib_dir_list, "lib" }, + { &config->lib_dir_list, "lib_path" }, + }; + for EachElement(i, key_str8_list) { + LNK_EnvVar *var = lnk_env_var_from_mapf(&env_vars, key_str8_list[i].key); + if (var) { + String8List value = lnk_value_list_from_env_var(arena, var); + String8List value_copy = str8_list_copy(config->arena, &value); + str8_list_concat_in_place(key_str8_list[i].config_list, &value_copy); } } } diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index d6e8b1f1..97f29c79 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -360,7 +360,7 @@ typedef struct LNK_Config String8 out_path; String8 image_alt_path; String8 imp_lib_name; - String8List raw_cmd_line; + String8 raw_cmd_line; String8 pdb_name; String8 pdb_alt_path; String8 pdb_stripped_name; @@ -614,6 +614,6 @@ internal void lnk_whole_archive(LNK_Config *config, String8 lib_name); // --- Config ------------------------------------------------------------------ internal void lnk_apply_cmd_option_to_config(LNK_Config *config, String8 name, String8List value_list, struct LNK_Obj *obj); -internal void lnk_config_pushf(LNK_Config *config, char *fmt, ...); -internal LNK_Config * lnk_config_init(LNK_CmdLine cmd_line); +internal LNK_Config * lnk_config_init(U64 argc, char **argv); + diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 6e196cc4..00fe76d7 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -195,7 +195,17 @@ THREAD_POOL_TASK_FUNC(lnk_read_type_servers_task) } break; case LNK_TypeServerKind_PDB: { // read PDB from disk - String8 msf_data = lnk_read_data_from_file_path(scratch.arena, task->config->io_flags, ts->ts_path); + B8 msf_data_read = 0; + String8 msf_data = lnk_read_data_from_file_path(scratch.arena, task->config->io_flags, ts->ts_path, &msf_data_read); + + if ( ! msf_data_read) { + LNK_Obj *dep_obj = 0; + if (ts->obj_indices.count) { + dep_obj = task->obj_arr[ts->obj_indices.first->data]; + } + lnk_error_obj(LNK_Error_UnableToOpenTypeServer, dep_obj, "failed to read type server from path: %S", ts->ts_path); + goto exit; + } // check magic if (!msf_check_magic_70(msf_data) && msf_check_magic_20(msf_data)) { goto exit; } @@ -632,9 +642,10 @@ lnk_rrt_array_from_config(Arena *arena, LNK_Config *config) ProfBegin("Parse RRT"); LNK_RRT_Array rrt_arr = { .v = push_array(arena, LNK_RRT, config->input_list[LNK_Input_RRT].node_count) }; for EachNode(n, String8Node, config->input_list[LNK_Input_RRT].first) { - String8 rrt_path = n->string; - String8 raw_rrt = lnk_read_data_from_file_path(arena, config->io_flags, rrt_path); - if (raw_rrt.size == 0) { + B8 was_rrt_read = 0; + String8 rrt_path = n->string; + String8 raw_rrt = lnk_read_data_from_file_path(arena, config->io_flags, rrt_path, &was_rrt_read); + if (raw_rrt.size == 0 || was_rrt_read == 0) { lnk_error(LNK_Error_IllData, "ERROR: failed to open \"%S\"", rrt_path); continue; } @@ -3392,15 +3403,16 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config if (builder_flags & LNK_PDB_BuilderFlag_NATVIS) { ProfBegin("Build NatVis"); { - String8Array natvis_file_path_arr = str8_array_from_list(scratch.arena, &config->natvis_list); - String8Array natvis_file_data_arr = lnk_read_data_from_file_path_parallel(tp, scratch.arena, config->io_flags, natvis_file_path_arr); + String8Array natvis_file_path_arr = str8_array_from_list(scratch.arena, &config->natvis_list); + B8 *natvis_was_read = push_array(scratch.arena, B8, natvis_file_path_arr.count); + String8Array natvis_file_data_arr = lnk_read_data_from_file_path_parallel(tp, scratch.arena, config->io_flags, natvis_file_path_arr, natvis_was_read); for EachIndex(i, natvis_file_data_arr.count) { String8 natvis_file_path = natvis_file_path_arr.v[i]; String8 natvis_file_data = natvis_file_data_arr.v[i]; // did we read the file? - if (natvis_file_data.size == 0) { + if (natvis_was_read[i] == 0 || natvis_file_data.size == 0) { lnk_error(LNK_Warning_FileNotFound, "unable to open natvis file \"%S\"", natvis_file_path); continue; } diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index 28364855..3626b3f9 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -59,6 +59,7 @@ typedef struct LNK_RRT *rrt; CV_DebugH *debug_h; } LNK_TypeServer; + typedef struct LNK_TypeServerNode { LNK_TypeServer v; struct LNK_TypeServerNode *next; } LNK_TypeServerNode; typedef struct LNK_TypeServerList { U64 count; LNK_TypeServerNode *first, *last; } LNK_TypeServerList; typedef struct LNK_TypeServerArray { U64 count; LNK_TypeServer *v; } LNK_TypeServerArray; diff --git a/src/linker/lnk_io.c b/src/linker/lnk_io.c index 7e39c4cd..e5318d09 100644 --- a/src/linker/lnk_io.c +++ b/src/linker/lnk_io.c @@ -171,11 +171,11 @@ lnk_log_read(String8 path, U64 size) } internal String8 -lnk_read_data_from_file_path(Arena *arena, LNK_IO_Flags io_flags, String8 path) +lnk_read_data_from_file_path(Arena *arena, LNK_IO_Flags io_flags, String8 path, B8 *was_read_out) { Temp scratch = scratch_begin(&arena, 1); TP_Context *single_thread_ctx = tp_alloc(scratch.arena, 1, 1, str8_zero()); - String8Array data_arr = lnk_read_data_from_file_path_parallel(single_thread_ctx, arena, io_flags, (String8Array){ .count = 1, .v = &path }); + String8Array data_arr = lnk_read_data_from_file_path_parallel(single_thread_ctx, arena, io_flags, (String8Array){ .count = 1, .v = &path }, was_read_out); scratch_end(scratch); return data_arr.v[0]; } @@ -201,16 +201,20 @@ THREAD_POOL_TASK_FUNC(lnk_data_size_from_file_path_task) internal THREAD_POOL_TASK_FUNC(lnk_data_from_file_path_task) { - LNK_DiskReader *task = raw_task; + LNK_DiskReader *task = raw_task; + File handle = task->handle_arr[task_id]; + if ( ! MemoryIsZeroStruct(&handle)) { + U64 buffer_size = task->size_arr[task_id]; + U8 *buffer = task->buffer + task->off_arr[task_id]; + U64 read_size = lnk_read_file(&handle, buffer, buffer_size); + AssertAlways(read_size == buffer_size); // TODO: handle partial loads as invalid - File handle = task->handle_arr[task_id]; - U64 buffer_size = task->size_arr[task_id]; - U8 *buffer = task->buffer + task->off_arr[task_id]; + task->data_arr.v[task_id] = str8(buffer, read_size); - U64 read_size = lnk_read_file(&handle, buffer, buffer_size); - Assert(read_size == buffer_size); - - task->data_arr.v[task_id] = str8(buffer, read_size); + if (task->was_read) { + task->was_read[task_id] = 1; + } + } } internal @@ -237,6 +241,9 @@ THREAD_POOL_TASK_FUNC(lnk_memory_map_file_task) void *file_data = MapViewOfFile(mapping_handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, file_size.QuadPart); if (file_data) { task->data_arr.v[task_id] = str8(file_data, file_size.QuadPart); + if (task->was_read) { + task->was_read[task_id] = 1; + } } CloseHandle(mapping_handle); @@ -254,6 +261,9 @@ THREAD_POOL_TASK_FUNC(lnk_memory_map_file_task) void *file_data = MapViewOfFile(mapping_handle, FILE_MAP_COPY, 0, 0, file_size.QuadPart); if (file_data) { task->data_arr.v[task_id] = str8(file_data, file_size.QuadPart); + if (task->was_read) { + task->was_read[task_id] = 1; + } } CloseHandle(mapping_handle); } @@ -268,6 +278,9 @@ THREAD_POOL_TASK_FUNC(lnk_memory_map_file_task) void *file_data = mmap(0, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); if (file_data != MAP_FAILED) { task->data_arr.v[task_id] = str8(file_data, st.st_size); + if (task->was_read) { + task->was_read[task_id] = 1; + } } } close(fd); @@ -279,7 +292,7 @@ THREAD_POOL_TASK_FUNC(lnk_memory_map_file_task) } internal String8Array -lnk_read_data_from_file_path_parallel(TP_Context *tp, Arena *arena, LNK_IO_Flags io_flags, String8Array path_arr) +lnk_read_data_from_file_path_parallel(TP_Context *tp, Arena *arena, LNK_IO_Flags io_flags, String8Array path_arr, B8 *was_read) { ProfBeginFunction(); LNK_DiskReader reader = {0}; @@ -289,13 +302,15 @@ lnk_read_data_from_file_path_parallel(TP_Context *tp, Arena *arena, LNK_IO_Flags reader.path_arr = path_arr; reader.data_arr.count = path_arr.count; reader.data_arr.v = push_array(arena, String8, path_arr.count); + reader.was_read = was_read; tp_for_parallel(tp, 0, path_arr.count, lnk_memory_map_file_task, &reader); } else { Temp scratch = scratch_begin(&arena,1); - reader.path_arr = path_arr; - reader.handle_arr = push_array_no_zero(scratch.arena, File, path_arr.count); - reader.size_arr = push_array_no_zero(scratch.arena, U64, path_arr.count); + reader.path_arr = path_arr; + reader.handle_arr = push_array_no_zero(scratch.arena, File, path_arr.count); + reader.size_arr = push_array_no_zero(scratch.arena, U64, path_arr.count); + reader.was_read = was_read; // open handles and get sizes tp_for_parallel(tp, 0, path_arr.count, lnk_data_size_from_file_path_task, &reader); diff --git a/src/linker/lnk_io.h b/src/linker/lnk_io.h index dc9932b3..442456e4 100644 --- a/src/linker/lnk_io.h +++ b/src/linker/lnk_io.h @@ -16,6 +16,7 @@ typedef struct U64 *size_arr; U64 *off_arr; U8 *buffer; + B8 *was_read; } LNK_DiskReader; typedef struct @@ -50,8 +51,8 @@ internal File lnk_file_open_with_rename_permissions(String8 path); internal B32 lnk_file_set_delete_on_close(File handle, B32 delete_file); internal B32 lnk_file_rename(File handle, String8 new_name); -internal String8 lnk_read_data_from_file_path(Arena *arena, LNK_IO_Flags io_flags, String8 path); -internal String8Array lnk_read_data_from_file_path_parallel(TP_Context *tp, Arena *arena, LNK_IO_Flags io_flags, String8Array path_arr); +internal String8 lnk_read_data_from_file_path(Arena *arena, LNK_IO_Flags io_flags, String8 path, B8 *was_read_out); +internal String8Array lnk_read_data_from_file_path_parallel(TP_Context *tp, Arena *arena, LNK_IO_Flags io_flags, String8Array path_arr, B8 *was_read); internal void lnk_write_data_list_to_file_path(String8 path, String8 temp_path, String8List list); internal void lnk_write_data_to_file_path(String8 path, String8 temp_path, String8 data); From efe2a78019746cfb35c319570ac3f689d89b5d36 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 17 Aug 2026 22:49:05 -0700 Subject: [PATCH 053/251] type command-line option values * pass argv inputs directly to the config command line * define each option's value type in the switch table so comma splitting is applied only to list typed switches --- src/linker/lnk.c | 4 +- src/linker/lnk_cmd_line.c | 63 +--- src/linker/lnk_cmd_line.h | 4 +- src/linker/lnk_config.c | 733 ++++++++++++++++++-------------------- src/linker/lnk_config.h | 34 +- src/linker/lnk_obj.c | 2 +- src/linker/lnk_obj.h | 2 +- 7 files changed, 390 insertions(+), 452 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index a48614ad..049d5fc4 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1233,7 +1233,7 @@ lnk_load_objs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer * LNK_DirectiveInfo directive_info = lnk_directive_info_from_raw_directives(scratch.arena, obj, raw_directives); for EachIndex(i, ArrayCount(directive_info.v)) { for (LNK_Directive *dir = directive_info.v[i].first; dir != 0; dir = dir->next) { - lnk_apply_cmd_option_to_config(config, dir->id, dir->value_list, obj); + lnk_apply_cmd_option_to_config(config, dir->id, dir->value, obj); } } } @@ -6738,7 +6738,7 @@ lnk_run_type_server(TP_Context *tp, TP_Arena *arena, LNK_Config *config) { LNK_CmdLine default_line = lnk_cmd_line_from_stringf_windows_rules(scratch.arena, "/DEBUG:GHASH /NOD /RAD_WRITE_TEMP_FILES"); for EachNode(cmd, LNK_CmdOption, default_line.first_option) { - lnk_apply_cmd_option_to_config(config, cmd->string, cmd->value_strings, &(LNK_Obj){0}); + lnk_apply_cmd_option_to_config(config, cmd->string, cmd->value, &(LNK_Obj){0}); } } diff --git a/src/linker/lnk_cmd_line.c b/src/linker/lnk_cmd_line.c index bbde4684..5cc11fa4 100644 --- a/src/linker/lnk_cmd_line.c +++ b/src/linker/lnk_cmd_line.c @@ -15,7 +15,7 @@ lnk_arg_list_parse_windows_rules(Arena *arena, String8 string) while(ptr < opl) { U64 size = (U64)(opl - ptr); UnicodeDecode uni = utf8_decode(ptr, size); - if (uni.codepoint != ' ' && uni.codepoint != '\n' && uni.codepoint != '\r') { + if (uni.codepoint != ' ' && uni.codepoint != '\t' && uni.codepoint != '\n' && uni.codepoint != '\r') { break; } ptr += uni.inc; @@ -31,7 +31,7 @@ lnk_arg_list_parse_windows_rules(Arena *arena, String8 string) UnicodeDecode uni; uni = utf8_decode(ptr, (U64)(opl-ptr)); - if (uni.codepoint == '\0' || uni.codepoint == '\n' || uni.codepoint == '\r' || uni.codepoint == ' ') { + if (uni.codepoint == '\0' || uni.codepoint == '\t' || uni.codepoint == '\n' || uni.codepoint == '\r' || uni.codepoint == ' ') { break; } @@ -89,12 +89,12 @@ lnk_cmd_line_push_option_node(LNK_CmdLine *cmd_line, LNK_CmdOption *opt) } internal LNK_CmdOption * -lnk_cmd_line_push_option_list(Arena *arena, LNK_CmdLine *cmd_line, String8 string, String8List value_strings) +lnk_cmd_line_push_option_string(Arena *arena, LNK_CmdLine *cmd_line, String8 string, String8 value) { // fill out node LNK_CmdOption *opt = push_array(arena, LNK_CmdOption, 1); opt->string = string; - opt->value_strings = value_strings; + opt->value = value; // push node lnk_cmd_line_push_option_node(cmd_line, opt); @@ -102,14 +102,6 @@ lnk_cmd_line_push_option_list(Arena *arena, LNK_CmdLine *cmd_line, String8 strin return opt; } -internal LNK_CmdOption * -lnk_cmd_line_push_option_string(Arena *arena, LNK_CmdLine *cmd_line, String8 string, String8 value) -{ - String8List value_list = str8_split_by_string_chars(arena, value, str8_lit(","), StringSplitFlag_KeepEmpties); - LNK_CmdOption *opt = lnk_cmd_line_push_option_list(arena, cmd_line, string, value_list); - return opt; -} - internal LNK_CmdOption * lnk_cmd_line_push_option(Arena *arena, LNK_CmdLine *cmd_line, char *string, char *value) { @@ -161,15 +153,12 @@ lnk_cmd_line_parse_windows_rules(Arena *arena, String8List arg_list) // skip ':' String8 value_string = str8_skip(arg, param_start_pos + 1); - // make value list - String8List value_list_temp = str8_split_by_string_chars(scratch.arena, value_string, str8_lit(","), 0); - String8List value_list = str8_list_copy(arena, &value_list_temp); - // push command option_name = push_str8_copy(arena, option_name); - lnk_cmd_line_push_option_list(arena, &cmd_line, option_name, value_list); + value_string = push_str8_copy(arena, value_string); + lnk_cmd_line_push_option_string(arena, &cmd_line, option_name, value_string); } else if (str8_starts_with(arg, str8_lit("@"))) { - lnk_cmd_line_push_option_list(arena, &cmd_line, push_str8_copy(arena, arg), (String8List){0}); + lnk_cmd_line_push_option_string(arena, &cmd_line, push_str8_copy(arena, arg), str8_zero()); } else { str8_list_push(arena, &cmd_line.input_list, push_str8_copy(arena, arg)); } @@ -204,7 +193,7 @@ lnk_cmd_line_option_from_string(LNK_CmdLine cmd_line, String8 string) { LNK_CmdOption *opt; for (opt = cmd_line.first_option; opt != NULL; opt = opt->next) { - if (str8_match(string, opt->string, StringMatchFlag_CaseInsensitive)) { + if (str8_matchi(string, opt->string)) { break; } } @@ -230,42 +219,14 @@ lnk_data_from_cmd_line(Arena *arena, LNK_CmdLine cmd_line) { String8List result = {0}; - for (LNK_CmdOption *opt = cmd_line.first_option; opt != 0; opt = opt->next) { - // separate directives + for EachNode(opt, LNK_CmdOption, cmd_line.first_option) { + // separate commands if (opt != cmd_line.first_option) { str8_list_pushf(arena, &result, " "); } - // push new directive - str8_list_pushf(arena, &result, "/%.*s", str8_varg(opt->string)); - - // do we have arguments? - if (opt->value_strings.node_count > 0) { - str8_list_pushf(arena, &result, ":"); - - for (String8Node *value_node = opt->value_strings.first; value_node != 0; value_node = value_node->next) { - // separate arguments - if (value_node != opt->value_strings.first) { - str8_list_pushf(arena, &result, ","); - } - - // push argument - B32 has_spaces = str8_find_needle(value_node->string, 0, str8_lit(" "), StringMatchFlag_CaseInsensitive) < value_node->string.size; - if (has_spaces) { - str8_list_pushf(arena, &result, "\"%.*s\"", str8_varg(value_node->string)); - } else { - str8_list_pushf(arena, &result, "%.*s", str8_varg(value_node->string)); - } - } - } - } - - // append inputs - for (String8Node *input_node = cmd_line.input_list.first; input_node != 0; input_node = input_node->next) { - if (input_node != cmd_line.input_list.first) { - str8_list_pushf(arena, &result, " "); - } - str8_list_pushf(arena, &result, "\"%.*s\"", str8_varg(input_node->string)); + // push command line option + str8_list_pushf(arena, &result, "/%S%s%S", opt->string, opt->value.size ? ":" : "", opt->value); } return result; diff --git a/src/linker/lnk_cmd_line.h b/src/linker/lnk_cmd_line.h index 083a651f..247dd174 100644 --- a/src/linker/lnk_cmd_line.h +++ b/src/linker/lnk_cmd_line.h @@ -7,7 +7,7 @@ typedef struct LNK_CmdOption { struct LNK_CmdOption *next; String8 string; - String8List value_strings; + String8 value; } LNK_CmdOption; typedef struct LNK_CmdLine @@ -50,7 +50,7 @@ internal String8List lnk_arg_list_parse_windows_rules (Arena *arena, internal LNK_CmdLine lnk_cmd_line_parse_windows_rules (Arena *arena, String8List arg_list); internal LNK_CmdLine lnk_cmd_line_from_stringfv_windows_rules(Arena *arena, char *fmt, va_list args); internal LNK_CmdLine lnk_cmd_line_from_stringf_windows_rules (Arena *arena, char *fmt, ...); -internal LNK_CmdOption * lnk_cmd_line_push_option_list (Arena *arena, LNK_CmdLine *cmd_line, String8 string, String8List value_strings); +internal LNK_CmdOption * lnk_cmd_line_push_option_string (Arena *arena, LNK_CmdLine *cmd_line, String8 string, String8 value); internal LNK_CmdOption * lnk_cmd_line_push_option (Arena *arena, LNK_CmdLine *cmd_line, char *string, char *value); internal LNK_CmdOption * lnk_cmd_line_push_option_if_not_present (Arena *arena, LNK_CmdLine *cmd_line, char *string, char *value); internal String8List lnk_data_from_cmd_line (Arena *arena, LNK_CmdLine cmd_line); diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index ad4f2f2f..6c28deeb 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -3,119 +3,119 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { - { LNK_CmdSwitch_Null, 0, "", "", "" }, - { LNK_CmdSwitch_NotImplemented, 0, "NOT_IMPLEMENTED", "", "" }, - { LNK_CmdSwitch_Align, 0, "ALIGN", ":#", "Set section alignment in the virtual address space." }, - { LNK_CmdSwitch_AllowBind, 0, "ALLOWBIND", "[:NO]", "Toggles bind bit in the image header." }, - { LNK_CmdSwitch_AllowIsolation, 0, "ALLOWISOLATION", "[:NO]", "Toggles isolation bit in the image header." }, - { LNK_CmdSwitch_AlternateName, 1, "ALTERNATENAME", ":FROM=TO", "Creates a symbol alias \"FROM=TO\"." }, - { LNK_CmdSwitch_AppContainer, 0, "APPCONTAINER", "[:NO]", "Toggles app container bit in the image header." }, - { LNK_CmdSwitch_Base, 0, "BASE", "{ADDRESS[,SIZE]|@FILENAME,KEY}", "Set default image base address." }, - { LNK_CmdSwitch_Brepro, 0, "BREPRO", "", "No support." }, - { LNK_CmdSwitch_Debug, 0, "DEBUG", "[:{FULL|NONE}]", "Controls debug info level." }, - { LNK_CmdSwitch_DefaultLib, 1, "DEFAULTLIB", ":LIBNAME", "Set default library." }, - { LNK_CmdSwitch_Def, 1, "DEF", ":FILENAME", "Read exports from a module-definition file." }, - { LNK_CmdSwitch_Delay, 0, "DELAY", ":{NOBIND|UNLOAD}", "Controls emission of unload and bind tables." }, - { LNK_CmdSwitch_DelayLoad, 0, "DELAYLOAD", ":DLL", "Delay load DLL." }, - { LNK_CmdSwitch_Dll, 0, "DLL", "", "Link to a DLL." }, - { LNK_CmdSwitch_DisallowLib, 1, "DISALLOWLIB", ":LIBRARY", "Prevents LIBRARY from being linked.", }, - { LNK_CmdSwitch_DynamicBase, 0, "DYNAMICBASE", "[:NO]", "Enable random base address in the linked image." }, - { LNK_CmdSwitch_Entry, 1, "ENTRY", ":FUNCTION", "Name of the entry point symbol." }, - { LNK_CmdSwitch_Export, 1, "EXPORT", ":SYMBOL", "Create an export entry for SYMBOL." }, - { LNK_CmdSwitch_FailIfMismatch, 1, "FAILIFMISMATCH", "{id=value}", "Fails to link if same ids have conflicting values." }, - { LNK_CmdSwitch_FileAlign, 0, "FILEALIGN", ":#", "Set section alignment in the file." }, - { LNK_CmdSwitch_Fixed, 0, "FIXED", "[:NO]", "Load the image at the default base address." }, - { LNK_CmdSwitch_Force, 0, "FORCE", "", "Force image output despite errors." }, - { LNK_CmdSwitch_FunctionPadMin, 0, "FUNCTIONPADMIN", ":#", "Minimum function byte size." }, - { LNK_CmdSwitch_Guard, 0, "GUARD", ":{CF|NO|LONGJMP|EHCONT}", "Controls Control Flow Guard metadata." }, - { LNK_CmdSwitch_GuardSym, 1, "GUARDSYM", ":SYMBOL,S", "MSVC guard symbol directive." }, - { LNK_CmdSwitch_Heap, 0, "HEAP", "RESERVE[,COMMIT]", "Set reserve and commit size for the heap." }, - { LNK_CmdSwitch_HighEntropyVa, 0, "HIGHENTROPYVA", "[:NO]", "Indicate that image supports full 64-bit address space ASLR." }, - { LNK_CmdSwitch_Ignore, 0, "IGNORE", ":#", "Ignore a warning." }, - { LNK_CmdSwitch_ImpLib, 0, "IMPLIB", ":FILENAME", "Set file name for the import library." }, - { LNK_CmdSwitch_Include, 1, "INCLUDE", ":SYMBOL", "Force a link against SYMBOL." }, - { LNK_CmdSwitch_InferAsanLibs, 1, "INFERASANLIBS", "[:NO]", "No support." }, - { LNK_CmdSwitch_InferAsanLibsNo, 1, "INFERASANLIBSNO", "", "No support.", }, - { LNK_CmdSwitch_LargeAddressAware, 0, "LARGEADDRESSAWARE", "[:NO]", "For images that can handle addresses > 2GiB." }, - { LNK_CmdSwitch_Lib, 0, "LIB", "", "Turn linker into lib.exe." }, - { LNK_CmdSwitch_LibPath, 0, "LIBPATH", ":DIR", "Add DIR for the linker to search for libraries." }, - { LNK_CmdSwitch_Machine, 0, "MACHINE", ":{X64|X86}", "Image target platform." }, - { LNK_CmdSwitch_Manifest, 0, "MANIFEST", "[:{EMBED[,ID=#]|NO]", "Controls whether the linker should create a side manifest." }, - { LNK_CmdSwitch_ManifestDependency, 1, "MANIFESTDEPENDENCY", ":\"manifest dependency XML string\"", "Add a manifest dependency." }, - { LNK_CmdSwitch_ManifestFile, 0, "MANIFESTFILE", ":FILENAME", "Specifies a manifest file." }, - { LNK_CmdSwitch_ManifestInput, 0, "MANIFESTINPUT", ":FILENAME", "Manifest that is embedded in the image." }, - { LNK_CmdSwitch_ManifestUac, 0, "MANIFESTUAC", ":{NO|{'level'={'asInvoker'|'highestAvailable'|'requireAdministrator'} ['uiAccess'={'true'|'false'}]}}", "Controls UAC information in the manifest." }, - { LNK_CmdSwitch_Merge, 1, "MERGE", ":FROM=TO", "Merges sections." }, - { LNK_CmdSwitch_Natvis, 0, "NATVIS", ":FILENAME", "NATVIS to embed in the PDB." }, - { LNK_CmdSwitch_NoDefaultLib, 1, "NODEFAULTLIB", ":LIBNAME", "Ignore a /DEFAULTLIB." }, - { LNK_CmdSwitch_NoDefaultLib, 0, "NOD", ":LIBNAME", "Alias for /NODEFAULTLIB." }, - { LNK_CmdSwitch_NoExp, 0, "NOEXP", "", "No support." }, - { LNK_CmdSwitch_NoImpLib, 0, "NOIMPLIB", "", "Do not create the import library." }, - { LNK_CmdSwitch_NxCompat, 0, "NXCOMPAT", "[:NO]", "Image is compatible with data execution prevention." }, - { LNK_CmdSwitch_Opt, 0, "OPT", "{REF|ICF}", "Optimizations." }, - { LNK_CmdSwitch_Out, 0, "OUT", ":FILENAME", "File name of the output image." }, - { LNK_CmdSwitch_Pdb, 0, "PDB", ":FILENAME", "File name of the output PDB." }, - { LNK_CmdSwitch_PdbAltPath, 0, "PDBALTPATH", ":PATH", "Alternative output path for the PDB." }, - { LNK_CmdSwitch_PdbPageSize, 0, "PDBPAGESIZE", ":#", "Page size must be power of two." }, - { LNK_CmdSwitch_PdbStripped, 0, "PDBSTRIPPED", ":FILENAME", "Create a stripped PDB containing public symbols, a section map, and a list of object files." }, - { LNK_CmdSwitch_Release, 1, "RELEASE", "", "Write image checksum." }, - { LNK_CmdSwitch_Section, 1, "SECTION", ":NAME,ATTRS", "Set output section attributes." }, - { LNK_CmdSwitch_Stack, 1, "STACK", ":RESERVE[,COMMIT]", "Set reserve and commit size for the stack." }, - { LNK_CmdSwitch_SubSystem, 1, "SUBSYSTEM", ":{CONSOLE|NATIVE|WINDOWS}[,#[.##]]", "Set subsystem for the image." }, - { LNK_CmdSwitch_TsAware, 0, "TSAWARE", "[:NO]", "Image is terminal server aware." }, - { LNK_CmdSwitch_Version, 0, "VERSION", "", "Image version." }, - { LNK_CmdSwitch_WholeArchive, 0, "WHOLEARCHIVE", "[:LIBNAME]", "Force linker to pull in all objs from the specified lib." }, + { LNK_CmdSwitch_Null, 0, LNK_CmdValueKind_Null, "", "", "" }, + { LNK_CmdSwitch_NotImplemented, 0, LNK_CmdValueKind_Null, "NOT_IMPLEMENTED", "", "" }, + { LNK_CmdSwitch_Align, 0, LNK_CmdValueKind_Scalar, "ALIGN", ":#", "Set section alignment in the virtual address space." }, + { LNK_CmdSwitch_AllowBind, 0, LNK_CmdValueKind_Scalar, "ALLOWBIND", "[:NO]", "Toggles bind bit in the image header." }, + { LNK_CmdSwitch_AllowIsolation, 0, LNK_CmdValueKind_Scalar, "ALLOWISOLATION", "[:NO]", "Toggles isolation bit in the image header." }, + { LNK_CmdSwitch_AlternateName, 1, LNK_CmdValueKind_Scalar, "ALTERNATENAME", ":FROM=TO", "Creates a symbol alias \"FROM=TO\"." }, + { LNK_CmdSwitch_AppContainer, 0, LNK_CmdValueKind_Scalar, "APPCONTAINER", "[:NO]", "Toggles app container bit in the image header." }, + { LNK_CmdSwitch_Base, 0, LNK_CmdValueKind_List, "BASE", "{ADDRESS[,SIZE]|@FILENAME,KEY}", "Set default image base address." }, + { LNK_CmdSwitch_Brepro, 0, LNK_CmdValueKind_Null, "BREPRO", "", "No support." }, + { LNK_CmdSwitch_Debug, 0, LNK_CmdValueKind_Scalar, "DEBUG", "[:{FULL|NONE}]", "Controls debug info level." }, + { LNK_CmdSwitch_DefaultLib, 1, LNK_CmdValueKind_Scalar, "DEFAULTLIB", ":LIBNAME", "Set default library." }, + { LNK_CmdSwitch_Def, 1, LNK_CmdValueKind_Scalar, "DEF", ":FILENAME", "Read exports from a module-definition file." }, + { LNK_CmdSwitch_Delay, 0, LNK_CmdValueKind_Scalar, "DELAY", ":{NOBIND|UNLOAD}", "Controls emission of unload and bind tables." }, + { LNK_CmdSwitch_DelayLoad, 0, LNK_CmdValueKind_Scalar, "DELAYLOAD", ":DLL", "Delay load DLL." }, + { LNK_CmdSwitch_Dll, 0, LNK_CmdValueKind_Null, "DLL", "", "Link to a DLL." }, + { LNK_CmdSwitch_DisallowLib, 1, LNK_CmdValueKind_Scalar, "DISALLOWLIB", ":LIBRARY", "Prevents LIBRARY from being linked.", }, + { LNK_CmdSwitch_DynamicBase, 0, LNK_CmdValueKind_Scalar, "DYNAMICBASE", "[:NO]", "Enable random base address in the linked image." }, + { LNK_CmdSwitch_Entry, 1, LNK_CmdValueKind_Scalar, "ENTRY", ":FUNCTION", "Name of the entry point symbol." }, + { LNK_CmdSwitch_Export, 1, LNK_CmdValueKind_List, "EXPORT", ":SYMBOL", "Create an export entry for SYMBOL." }, + { LNK_CmdSwitch_FailIfMismatch, 1, LNK_CmdValueKind_Scalar, "FAILIFMISMATCH", "{id=value}", "Fails to link if same ids have conflicting values." }, + { LNK_CmdSwitch_FileAlign, 0, LNK_CmdValueKind_Scalar, "FILEALIGN", ":#", "Set section alignment in the file." }, + { LNK_CmdSwitch_Fixed, 0, LNK_CmdValueKind_Scalar, "FIXED", "[:NO]", "Load the image at the default base address." }, + { LNK_CmdSwitch_Force, 0, LNK_CmdValueKind_Null, "FORCE", "", "Force image output despite errors." }, + { LNK_CmdSwitch_FunctionPadMin, 0, LNK_CmdValueKind_Scalar, "FUNCTIONPADMIN", ":#", "Minimum function byte size." }, + { LNK_CmdSwitch_Guard, 0, LNK_CmdValueKind_List, "GUARD", ":{CF|NO|LONGJMP|EHCONT}", "Controls Control Flow Guard metadata." }, + { LNK_CmdSwitch_GuardSym, 1, LNK_CmdValueKind_List, "GUARDSYM", ":SYMBOL,S", "MSVC guard symbol directive." }, + { LNK_CmdSwitch_Heap, 0, LNK_CmdValueKind_List, "HEAP", "RESERVE[,COMMIT]", "Set reserve and commit size for the heap." }, + { LNK_CmdSwitch_HighEntropyVa, 0, LNK_CmdValueKind_Scalar, "HIGHENTROPYVA", "[:NO]", "Indicate that image supports full 64-bit address space ASLR." }, + { LNK_CmdSwitch_Ignore, 0, LNK_CmdValueKind_Scalar, "IGNORE", ":#", "Ignore a warning." }, + { LNK_CmdSwitch_ImpLib, 0, LNK_CmdValueKind_Scalar, "IMPLIB", ":FILENAME", "Set file name for the import library." }, + { LNK_CmdSwitch_Include, 1, LNK_CmdValueKind_Scalar, "INCLUDE", ":SYMBOL", "Force a link against SYMBOL." }, + { LNK_CmdSwitch_InferAsanLibs, 1, LNK_CmdValueKind_Scalar, "INFERASANLIBS", "[:NO]", "No support." }, + { LNK_CmdSwitch_InferAsanLibsNo, 1, LNK_CmdValueKind_Null, "INFERASANLIBSNO", "", "No support.", }, + { LNK_CmdSwitch_LargeAddressAware, 0, LNK_CmdValueKind_Scalar, "LARGEADDRESSAWARE", "[:NO]", "For images that can handle addresses > 2GiB." }, + { LNK_CmdSwitch_Lib, 0, LNK_CmdValueKind_Null, "LIB", "", "Turn linker into lib.exe." }, + { LNK_CmdSwitch_LibPath, 0, LNK_CmdValueKind_Scalar, "LIBPATH", ":DIR", "Add DIR for the linker to search for libraries." }, + { LNK_CmdSwitch_Machine, 0, LNK_CmdValueKind_Scalar, "MACHINE", ":{X64|X86}", "Image target platform." }, + { LNK_CmdSwitch_Manifest, 0, LNK_CmdValueKind_List, "MANIFEST", "[:{EMBED[,ID=#]|NO]", "Controls whether the linker should create a side manifest." }, + { LNK_CmdSwitch_ManifestDependency, 1, LNK_CmdValueKind_Scalar, "MANIFESTDEPENDENCY", ":\"manifest dependency XML string\"", "Add a manifest dependency." }, + { LNK_CmdSwitch_ManifestFile, 0, LNK_CmdValueKind_Scalar, "MANIFESTFILE", ":FILENAME", "Specifies a manifest file." }, + { LNK_CmdSwitch_ManifestInput, 0, LNK_CmdValueKind_Scalar, "MANIFESTINPUT", ":FILENAME", "Manifest that is embedded in the image." }, + { LNK_CmdSwitch_ManifestUac, 0, LNK_CmdValueKind_Scalar, "MANIFESTUAC", ":{NO|{'level'={'asInvoker'|'highestAvailable'|'requireAdministrator'} ['uiAccess'={'true'|'false'}]}}", "Controls UAC information in the manifest." }, + { LNK_CmdSwitch_Merge, 1, LNK_CmdValueKind_Scalar, "MERGE", ":FROM=TO", "Merges sections." }, + { LNK_CmdSwitch_Natvis, 0, LNK_CmdValueKind_Scalar, "NATVIS", ":FILENAME", "NATVIS to embed in the PDB." }, + { LNK_CmdSwitch_NoDefaultLib, 1, LNK_CmdValueKind_Scalar, "NODEFAULTLIB", ":LIBNAME", "Ignore a /DEFAULTLIB." }, + { LNK_CmdSwitch_NoDefaultLib, 0, LNK_CmdValueKind_Scalar, "NOD", ":LIBNAME", "Alias for /NODEFAULTLIB." }, + { LNK_CmdSwitch_NoExp, 0, LNK_CmdValueKind_Null, "NOEXP", "", "No support." }, + { LNK_CmdSwitch_NoImpLib, 0, LNK_CmdValueKind_Null, "NOIMPLIB", "", "Do not create the import library." }, + { LNK_CmdSwitch_NxCompat, 0, LNK_CmdValueKind_Scalar, "NXCOMPAT", "[:NO]", "Image is compatible with data execution prevention." }, + { LNK_CmdSwitch_Opt, 0, LNK_CmdValueKind_List, "OPT", "{REF|ICF}", "Optimizations." }, + { LNK_CmdSwitch_Out, 0, LNK_CmdValueKind_Scalar, "OUT", ":FILENAME", "File name of the output image." }, + { LNK_CmdSwitch_Pdb, 0, LNK_CmdValueKind_Scalar, "PDB", ":FILENAME", "File name of the output PDB." }, + { LNK_CmdSwitch_PdbAltPath, 0, LNK_CmdValueKind_Scalar, "PDBALTPATH", ":PATH", "Alternative output path for the PDB." }, + { LNK_CmdSwitch_PdbPageSize, 0, LNK_CmdValueKind_Scalar, "PDBPAGESIZE", ":#", "Page size must be power of two." }, + { LNK_CmdSwitch_PdbStripped, 0, LNK_CmdValueKind_Scalar, "PDBSTRIPPED", ":FILENAME", "Create a stripped PDB containing public symbols, a section map, and a list of object files." }, + { LNK_CmdSwitch_Release, 1, LNK_CmdValueKind_Null, "RELEASE", "", "Write image checksum." }, + { LNK_CmdSwitch_Section, 1, LNK_CmdValueKind_List, "SECTION", ":NAME,ATTRS", "Set output section attributes." }, + { LNK_CmdSwitch_Stack, 1, LNK_CmdValueKind_List, "STACK", ":RESERVE[,COMMIT]", "Set reserve and commit size for the stack." }, + { LNK_CmdSwitch_SubSystem, 1, LNK_CmdValueKind_List, "SUBSYSTEM", ":{CONSOLE|NATIVE|WINDOWS}[,#[.##]]", "Set subsystem for the image." }, + { LNK_CmdSwitch_TsAware, 0, LNK_CmdValueKind_Scalar, "TSAWARE", "[:NO]", "Image is terminal server aware." }, + { LNK_CmdSwitch_Version, 0, LNK_CmdValueKind_Scalar, "VERSION", "", "Image version." }, + { LNK_CmdSwitch_WholeArchive, 0, LNK_CmdValueKind_Scalar, "WHOLEARCHIVE", "[:LIBNAME]", "Force linker to pull in all objs from the specified lib." }, - { LNK_CmdSwitch_Rad_Age, 0, "RAD_AGE", ":#", "Age embeded in EXE and PDB, used to validate incremental build. Default is 1." }, - //{ LNK_CmdSwitch_Rad_BuildExp, 0, "RAD_BUILD_EXP", "[:NO]", "Build export data." }, - { LNK_CmdSwitch_Rad_BuildInfo, 0, "RAD_BUILD_INFO", "", "Print build info and exit." }, - { LNK_CmdSwitch_Rad_BuildImpLib, 0, "RAD_BUILD_IMPLIB", "[:NO]", "Build import library." }, - { LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll, 0, "RAD_CHECK_UNUSED_DELAY_LOAD_DLL", "[:NO]", "Check for unused delay load dlls." }, - { LNK_CmdSwitch_Rad_DataDirCount, 0, "RAD_DATA_DIR_COUNT", ":#", "Internal default for PE optional header data directory count." }, - { LNK_CmdSwitch_Rad_Map, 0, "RAD_MAP", ":FILENAME", "Emit file with the output image's layout description." }, - { LNK_CmdSwitch_Rad_MapLinesForUnresolvedSymbols, 0, "RAD_MAP_LINES_FOR_UNRESOLVED_SYMBOLS", "[:NO]", "Use debug info to print source file location for unresolved symbol" }, - { LNK_CmdSwitch_Rad_MemoryMapFiles, 0, "RAD_MEMORY_MAP_FILES", "[:{NO|READ_ONLY|READ_WRITE}]", "When enabled, files are memory-mapped instead of being read entirely on request." }, - { LNK_CmdSwitch_Rad_BootMode, 0, "RAD_BOOT_MODE", "[:LINKER|TYPE_SERVER]", "Overrides default boot program." }, - { LNK_CmdSwitch_Rad_Debug, 0, "RAD_DEBUG", "[:NO]", "Emit RAD debug info file." }, - { LNK_CmdSwitch_Rad_DebugAltPath, 0, "RAD_DEBUGALTPATH", ":PATH", "Alternative output path for the RDI." }, - { LNK_CmdSwitch_Rad_DebugName, 0, "RAD_DEBUG_NAME", ":FILENAME", "Set file name for RAD debug info file." }, - { LNK_CmdSwitch_Rad_DelayBind, 0, "RAD_DELAY_BIND", "[:NO]", "Emit bindable imports." }, - { LNK_CmdSwitch_Rad_DoMerge, 0, "RAD_DO_MERGE", "[:NO]", "Set whether the linker should execute /MERGE." }, - { LNK_CmdSwitch_Rad_EnvLib, 0, "RAD_ENV_LIB", "[:NO]", "Collect libraries from %%LIB%% and %%LIBPATH%% varibles." }, - { LNK_CmdSwitch_Rad_Exe, 0, "RAD_EXE", "[:NO]", "Set EXE bit in the image header." }, - { LNK_CmdSwitch_Rad_Guid, 0, "RAD_GUID", ":{IMAGEBLAKE3|XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX}", "The image guid that is embeded in the debug info." }, - { LNK_CmdSwitch_Rad_LargePages, 0, "RAD_LARGE_PAGES", "[:NO]", "Disabled by default on Windows." }, - { LNK_CmdSwitch_Rad_LinkVer, 0, "RAD_LINK_VER", ":##,##", "Linker version." }, - { LNK_CmdSwitch_Rad_Log, 0, "RAD_LOG", ":{ALL,INPUT_OBJ,INPUT_LIB,IO,LINK_STATS,TIMERS}", "Loggers." }, - { LNK_CmdSwitch_Rad_MtPath, 0, "RAD_MT_PATH", ":EXEPATH", "Exe path to the manifest tool (default: " LNK_MANIFEST_MERGE_TOOL_NAME ")" }, - { LNK_CmdSwitch_Rad_OsVer, 0, "RAD_OS_VER", ":##,##", "OS version." }, - { LNK_CmdSwitch_Rad_PageSize, 0, "RAD_PAGE_SIZE", ":#", "Must be power of two." }, - { LNK_CmdSwitch_Rad_PathStyle, 0, "RAD_PATH_STYLE", ":{WindowsAbsolute|UnixAbsolute}", "Set path style in the PDB." }, - { LNK_CmdSwitch_Rad_PdbHashTypeNameLength, 0, "RAD_PDB_HASH_TYPE_NAME_LENGTH", ":#", "Number of hash bytes to use to replace type name. Default 8 bytes (Max 16)." }, - { LNK_CmdSwitch_Rad_PdbHashTypeNameMap, 0, "RAD_PDB_HASH_TYPE_NAME_MAP", ":FILENAME", "Produce map file with hash -> type name mappings." }, - { LNK_CmdSwitch_Rad_PdbHashTypeNames, 0, "RAD_PDB_HASH_TYPE_NAMES", ":{NONE|LENIENT|FULL}", "Replace type names in LF_STRUCTURE and LF_CLASS with hashes." }, - { LNK_CmdSwitch_Rad_RemoveSection, 0, "RAD_REMOVE_SECTION", ":NAME", "Removes a section from the image." }, - { LNK_CmdSwitch_Rad_SharedThreadPool, 0, "RAD_SHARED_THREAD_POOL", "[:STRING]", "Default value \"" LNK_DEFAULT_THREAD_POOL_NAME "\"" }, - { LNK_CmdSwitch_Rad_SharedThreadPoolMaxWorkers, 0, "RAD_SHARED_THREAD_POOL_MAX_WORKERS", ":#", "Set maximum number of workers in a thread pool." }, - { LNK_CmdSwitch_Rad_SortImports, 0, "RAD_SORT_IMPORTS", "[:NO]", "Sort static and delayed import tables by their order of appearance in libs, without assuming link order." }, - { LNK_CmdSwitch_Rad_IcfHashKind, 0, "RAD_ICF_HASH_KIND", "{BLAKE3|XXHASH}", "Sets hashing algorithm for /OPT:ICF." }, - { LNK_CmdSwitch_Rad_Ignore, 0, "RAD_IGNORE", ":#", "Ignore the specified RAD linker warning." }, - { LNK_CmdSwitch_Rad_ImageAltPath, 0, "RAD_IMAGEALTPATH", ":FILENAME", "Alternative name for the image" }, - { LNK_CmdSwitch_Rad_WriteTempFiles, 0, "RAD_WRITE_TEMP_FILES", "[:NO]", "When speicifed linker writes image and debug info to temporary files and renames after link is done." }, - { LNK_CmdSwitch_Rad_TimeStamp, 0, "RAD_TIME_STAMP", ":#", "Time stamp embeded in EXE and PDB." }, - { LNK_CmdSwitch_Rad_DebugTypeHash, 0, "RAD_DEBUG_TYPE_HASH", ":{BLAKE3|XXHASH}", "Sets hashing algorithm for debug type merging." }, - { LNK_CmdSwitch_Rad_UnresolvedSymbolLimit, 0, "RAD_UNRESOLVED_SYMBOL_LIMIT", ":#", "Limits number of unresolved symbol errors linker reports." }, - { LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit, 0, "RAD_UNRESOLVED_SYMBOL_REF_LIMIT", ":#", "Limit number of unresolved symbol references linker reports." }, - { LNK_CmdSwitch_Rad_Version, 0, "RAD_VERSION", "", "Print version and exit." }, - { LNK_CmdSwitch_Rad_Workers, 0, "RAD_WORKERS", ":#", "Set number of workers created in the pool. Number is capped at 1024. When /RAD_SHARED_THREAD_POOL is specified this number cant exceed /RAD_SHARED_THREAD_POOL_MAX_WORKERS." }, - { LNK_CmdSwitch_Rad_WorkDir, 0, "RAD_WORK_DIR", ":PATH", "Working directory used for stable debug paths." }, + { LNK_CmdSwitch_Rad_Age, 0, LNK_CmdValueKind_Scalar, "RAD_AGE", ":#", "Age embeded in EXE and PDB, used to validate incremental build. Default is 1." }, + //{ LNK_CmdSwitch_Rad_BuildExp, 0, LNK_CmdValueKind_Scalar, "RAD_BUILD_EXP", "[:NO]", "Build export data." }, + { LNK_CmdSwitch_Rad_BuildInfo, 0, LNK_CmdValueKind_Null, "RAD_BUILD_INFO", "", "Print build info and exit." }, + { LNK_CmdSwitch_Rad_BuildImpLib, 0, LNK_CmdValueKind_Scalar, "RAD_BUILD_IMPLIB", "[:NO]", "Build import library." }, + { LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll, 0, LNK_CmdValueKind_Scalar, "RAD_CHECK_UNUSED_DELAY_LOAD_DLL", "[:NO]", "Check for unused delay load dlls." }, + { LNK_CmdSwitch_Rad_DataDirCount, 0, LNK_CmdValueKind_Scalar, "RAD_DATA_DIR_COUNT", ":#", "Internal default for PE optional header data directory count." }, + { LNK_CmdSwitch_Rad_Map, 0, LNK_CmdValueKind_Scalar, "RAD_MAP", ":FILENAME", "Emit file with the output image's layout description." }, + { LNK_CmdSwitch_Rad_MapLinesForUnresolvedSymbols, 0, LNK_CmdValueKind_Scalar, "RAD_MAP_LINES_FOR_UNRESOLVED_SYMBOLS", "[:NO]", "Use debug info to print source file location for unresolved symbol" }, + { LNK_CmdSwitch_Rad_MemoryMapFiles, 0, LNK_CmdValueKind_Scalar, "RAD_MEMORY_MAP_FILES", "[:{NO|READ_ONLY|READ_WRITE}]", "When enabled, files are memory-mapped instead of being read entirely on request." }, + { LNK_CmdSwitch_Rad_BootMode, 0, LNK_CmdValueKind_Scalar, "RAD_BOOT_MODE", "[:LINKER|TYPE_SERVER]", "Overrides default boot program." }, + { LNK_CmdSwitch_Rad_Debug, 0, LNK_CmdValueKind_Scalar, "RAD_DEBUG", "[:NO]", "Emit RAD debug info file." }, + { LNK_CmdSwitch_Rad_DebugAltPath, 0, LNK_CmdValueKind_Scalar, "RAD_DEBUGALTPATH", ":PATH", "Alternative output path for the RDI." }, + { LNK_CmdSwitch_Rad_DebugName, 0, LNK_CmdValueKind_Scalar, "RAD_DEBUG_NAME", ":FILENAME", "Set file name for RAD debug info file." }, + { LNK_CmdSwitch_Rad_DelayBind, 0, LNK_CmdValueKind_Scalar, "RAD_DELAY_BIND", "[:NO]", "Emit bindable imports." }, + { LNK_CmdSwitch_Rad_DoMerge, 0, LNK_CmdValueKind_Scalar, "RAD_DO_MERGE", "[:NO]", "Set whether the linker should execute /MERGE." }, + { LNK_CmdSwitch_Rad_EnvLib, 0, LNK_CmdValueKind_Scalar, "RAD_ENV_LIB", "[:NO]", "Collect libraries from %%LIB%% and %%LIBPATH%% varibles." }, + { LNK_CmdSwitch_Rad_Exe, 0, LNK_CmdValueKind_Scalar, "RAD_EXE", "[:NO]", "Set EXE bit in the image header." }, + { LNK_CmdSwitch_Rad_Guid, 0, LNK_CmdValueKind_Scalar, "RAD_GUID", ":{IMAGEBLAKE3|XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX}", "The image guid that is embeded in the debug info." }, + { LNK_CmdSwitch_Rad_LargePages, 0, LNK_CmdValueKind_Scalar, "RAD_LARGE_PAGES", "[:NO]", "Disabled by default on Windows." }, + { LNK_CmdSwitch_Rad_LinkVer, 0, LNK_CmdValueKind_Scalar, "RAD_LINK_VER", ":##,##", "Linker version." }, + { LNK_CmdSwitch_Rad_Log, 0, LNK_CmdValueKind_Scalar, "RAD_LOG", ":{ALL,INPUT_OBJ,INPUT_LIB,IO,LINK_STATS,TIMERS}", "Loggers." }, + { LNK_CmdSwitch_Rad_MtPath, 0, LNK_CmdValueKind_Scalar, "RAD_MT_PATH", ":EXEPATH", "Exe path to the manifest tool (default: " LNK_MANIFEST_MERGE_TOOL_NAME ")" }, + { LNK_CmdSwitch_Rad_OsVer, 0, LNK_CmdValueKind_Scalar, "RAD_OS_VER", ":##,##", "OS version." }, + { LNK_CmdSwitch_Rad_PageSize, 0, LNK_CmdValueKind_Scalar, "RAD_PAGE_SIZE", ":#", "Must be power of two." }, + { LNK_CmdSwitch_Rad_PathStyle, 0, LNK_CmdValueKind_Scalar, "RAD_PATH_STYLE", ":{WindowsAbsolute|UnixAbsolute}", "Set path style in the PDB." }, + { LNK_CmdSwitch_Rad_PdbHashTypeNameLength, 0, LNK_CmdValueKind_Scalar, "RAD_PDB_HASH_TYPE_NAME_LENGTH", ":#", "Number of hash bytes to use to replace type name. Default 8 bytes (Max 16)." }, + { LNK_CmdSwitch_Rad_PdbHashTypeNameMap, 0, LNK_CmdValueKind_Scalar, "RAD_PDB_HASH_TYPE_NAME_MAP", ":FILENAME", "Produce map file with hash -> type name mappings." }, + { LNK_CmdSwitch_Rad_PdbHashTypeNames, 0, LNK_CmdValueKind_Scalar, "RAD_PDB_HASH_TYPE_NAMES", ":{NONE|LENIENT|FULL}", "Replace type names in LF_STRUCTURE and LF_CLASS with hashes." }, + { LNK_CmdSwitch_Rad_RemoveSection, 0, LNK_CmdValueKind_Scalar, "RAD_REMOVE_SECTION", ":NAME", "Removes a section from the image." }, + { LNK_CmdSwitch_Rad_SharedThreadPool, 0, LNK_CmdValueKind_Scalar, "RAD_SHARED_THREAD_POOL", "[:STRING]", "Default value \"" LNK_DEFAULT_THREAD_POOL_NAME "\"" }, + { LNK_CmdSwitch_Rad_SharedThreadPoolMaxWorkers, 0, LNK_CmdValueKind_Scalar, "RAD_SHARED_THREAD_POOL_MAX_WORKERS", ":#", "Set maximum number of workers in a thread pool." }, + { LNK_CmdSwitch_Rad_SortImports, 0, LNK_CmdValueKind_Scalar, "RAD_SORT_IMPORTS", "[:NO]", "Sort static and delayed import tables by their order of appearance in libs, without assuming link order." }, + { LNK_CmdSwitch_Rad_IcfHashKind, 0, LNK_CmdValueKind_Scalar, "RAD_ICF_HASH_KIND", "{BLAKE3|XXHASH}", "Sets hashing algorithm for /OPT:ICF." }, + { LNK_CmdSwitch_Rad_Ignore, 0, LNK_CmdValueKind_List, "RAD_IGNORE", ":#", "Ignore the specified RAD linker warning." }, + { LNK_CmdSwitch_Rad_ImageAltPath, 0, LNK_CmdValueKind_Scalar, "RAD_IMAGEALTPATH", ":FILENAME", "Alternative name for the image" }, + { LNK_CmdSwitch_Rad_WriteTempFiles, 0, LNK_CmdValueKind_Scalar, "RAD_WRITE_TEMP_FILES", "[:NO]", "When speicifed linker writes image and debug info to temporary files and renames after link is done." }, + { LNK_CmdSwitch_Rad_TimeStamp, 0, LNK_CmdValueKind_Scalar, "RAD_TIME_STAMP", ":#", "Time stamp embeded in EXE and PDB." }, + { LNK_CmdSwitch_Rad_DebugTypeHash, 0, LNK_CmdValueKind_Scalar, "RAD_DEBUG_TYPE_HASH", ":{BLAKE3|XXHASH}", "Sets hashing algorithm for debug type merging." }, + { LNK_CmdSwitch_Rad_UnresolvedSymbolLimit, 0, LNK_CmdValueKind_Scalar, "RAD_UNRESOLVED_SYMBOL_LIMIT", ":#", "Limits number of unresolved symbol errors linker reports." }, + { LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit, 0, LNK_CmdValueKind_Scalar, "RAD_UNRESOLVED_SYMBOL_REF_LIMIT", ":#", "Limit number of unresolved symbol references linker reports." }, + { LNK_CmdSwitch_Rad_Version, 0, LNK_CmdValueKind_Null, "RAD_VERSION", "", "Print version and exit." }, + { LNK_CmdSwitch_Rad_Workers, 0, LNK_CmdValueKind_Scalar, "RAD_WORKERS", ":#", "Set number of workers created in the pool. Number is capped at 1024. When /RAD_SHARED_THREAD_POOL is specified this number cant exceed /RAD_SHARED_THREAD_POOL_MAX_WORKERS." }, + { LNK_CmdSwitch_Rad_WorkDir, 0, LNK_CmdValueKind_Scalar, "RAD_WORK_DIR", ":PATH", "Working directory used for stable debug paths." }, - { LNK_CmdSwitch_RadTypeServer, 0, "RAD_TYPE_SERVER", ":FILENAME", "Merge types and store them in the specified file. The filename must have the .rrt extension." }, + { LNK_CmdSwitch_RadTypeServer, 0, LNK_CmdValueKind_Scalar, "RAD_TYPE_SERVER", ":FILENAME", "Merge types and store them in the specified file. The filename must have the .rrt extension." }, - { LNK_CmdSwitch_LLVM_AddrSig, 0, "LLVM_ADDRSIG", "[:NO]", "Use .llvm_addrsig to guide ICF." }, + { LNK_CmdSwitch_LLVM_AddrSig, 0, LNK_CmdValueKind_Scalar, "LLVM_ADDRSIG", "[:NO]", "Use .llvm_addrsig to guide ICF." }, - { LNK_CmdSwitch_Help, 0, "HELP", "", "" }, - { LNK_CmdSwitch_Help, 0, "?", "", "" }, + { LNK_CmdSwitch_Help, 0, LNK_CmdValueKind_Null, "HELP", "", "" }, + { LNK_CmdSwitch_Help, 0, LNK_CmdValueKind_Null, "?", "", "" }, }; global read_only struct @@ -233,9 +233,8 @@ lnk_cmd_line_values_from_switch(Arena *arena, LNK_CmdLine cmd_line, LNK_CmdSwitc String8List values = {0}; String8 cmd_switch_name = lnk_string_from_cmd_switch_type(cmd_switch); for EachNode(cmd, LNK_CmdOption, cmd_line.first_option) { - if (str8_match(cmd->string, cmd_switch_name, StringMatchFlag_CaseInsensitive)) { - String8List value_strings = str8_list_copy(arena, &cmd->value_strings); - str8_list_concat_in_place(&values, &value_strings); + if (cmd->value.size > 0 && str8_matchi(cmd->string, cmd_switch_name)) { + str8_list_push(arena, &values, push_str8_copy(arena, cmd->value)); } } return values; @@ -307,13 +306,13 @@ lnk_error_invalid_uac_ui_access_param(LNK_ErrorCode error_code, LNK_Obj *obj, LN } internal B32 -lnk_cmd_switch_parse_version(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, Version *ver_out) +lnk_cmd_switch_parse_version(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, Version *ver_out) { Temp scratch = scratch_begin(0,0); B32 is_parsed = 0; - if (value_strings.node_count == 1) { - String8List split_list = str8_split_by_string_chars(scratch.arena, value_strings.first->string, str8_lit("."), StringSplitFlag_KeepEmpties); + if (value.size > 0) { + String8List split_list = str8_split_by_string_chars(scratch.arena, value, str8_lit("."), StringSplitFlag_KeepEmpties); String8 maj_str = str8_lit("0"); String8 min_str = str8_lit("0"); @@ -348,33 +347,34 @@ exit:; } internal B32 -lnk_cmd_switch_parse_tuple(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, Rng1U64 *tuple_out) +lnk_cmd_switch_parse_tuple(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List values, Rng1U64 *tuple_out) { - if (value_strings.node_count == 1) { - U64 value; - if (try_u64_from_str8_c_rules(value_strings.first->string, &value)) { - tuple_out->v[0] = value; - return 1; + B32 is_parsed = 0; + if (values.node_count == 1) { + U64 v; + if (try_u64_from_str8_c_rules(values.first->string, &v)) { + tuple_out->v[0] = v; + is_parsed = 1; } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse the parameter \"%S\"", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse the parameter \"%S\"", values.first->string); } - } else if (value_strings.node_count == 2) { + } else if (values.node_count == 2) { U64 a,b; - if (try_u64_from_str8_c_rules(value_strings.first->string, &a)) { - if (try_u64_from_str8_c_rules(value_strings.last->string, &b)) { + if (try_u64_from_str8_c_rules(values.first->string, &a)) { + if (try_u64_from_str8_c_rules(values.last->string, &b)) { tuple_out->v[0] = a; tuple_out->v[1] = b; - return 1; + is_parsed = 1; } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse second parameter \"%S\"", value_strings.last->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse second parameter \"%S\"", values.last->string); } } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse first parameter \"%S\"", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse first parameter \"%S\"", values.first->string); } } else { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); } - return 0; + return is_parsed; } internal B32 @@ -415,24 +415,24 @@ lnk_try_parse_s64(String8 string, LNK_ParseU64Flags flags, S64 *value_out) } internal B32 -lnk_cmd_switch_parse_u64(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U64 *value_out, LNK_ParseU64Flags flags) +lnk_cmd_switch_parse_u64(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U64 *value_out, LNK_ParseU64Flags flags) { - if (value_strings.node_count != 1) { + if (value.size == 0) { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid number of parameters, exepcted integer number as input"); return 0; } - if (!lnk_try_parse_u64(value_strings.first->string, flags, value_out)) { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse string \"%S\"", value_strings.first->string); + if (!lnk_try_parse_u64(value, flags, value_out)) { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse string \"%S\"", value); return 0; } return 1; } internal B32 -lnk_cmd_switch_parse_u32(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U32 *value_out, LNK_ParseU64Flags flags) +lnk_cmd_switch_parse_u32(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value_string, U32 *value_out, LNK_ParseU64Flags flags) { U64 value; - if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &value, flags | LNK_ParseU64Flag_CheckUnder32bit)) { + if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value_string, &value, flags | LNK_ParseU64Flag_CheckUnder32bit)) { *value_out = (U32)value; return 1; } @@ -440,9 +440,9 @@ lnk_cmd_switch_parse_u32(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List } internal B32 -lnk_cmd_switch_parse_u64_list(Arena *arena, LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U64List *list_out, LNK_ParseU64Flags flags) +lnk_cmd_switch_parse_u64_list(Arena *arena, LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List values, U64List *list_out, LNK_ParseU64Flags flags) { - for (String8Node *string_n = value_strings.first; string_n != 0; string_n = string_n->next) { + for (String8Node *string_n = values.first; string_n != 0; string_n = string_n->next) { U64 value; if (!lnk_try_parse_u64(string_n->string, flags, &value)) { return 0; @@ -453,9 +453,9 @@ lnk_cmd_switch_parse_u64_list(Arena *arena, LNK_Obj *obj, LNK_CmdSwitchType cmd_ } internal B32 -lnk_cmd_switch_parse_s64_list(Arena *arena, LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, S64List *list_out, LNK_ParseU64Flags flags) +lnk_cmd_switch_parse_s64_list(Arena *arena, LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List values, S64List *list_out, LNK_ParseU64Flags flags) { - for EachNode(string_n, String8Node, value_strings.first) { + for EachNode(string_n, String8Node, values.first) { S64 v; if (!lnk_try_parse_s64(string_n->string, flags, &v)) { return 0; @@ -466,23 +466,18 @@ lnk_cmd_switch_parse_s64_list(Arena *arena, LNK_Obj *obj, LNK_CmdSwitchType cmd_ } internal B32 -lnk_cmd_switch_parse_flag(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, LNK_SwitchState *value_out) +lnk_cmd_switch_parse_flag(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, LNK_SwitchState *value_out) { B32 is_parsed = 0; - if (value_strings.node_count > 1) { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "too many parameters"); - } else if (value_strings.node_count == 1) { - if (str8_match_lit("no", value_strings.first->string, StringMatchFlag_CaseInsensitive)) { + if (value.size > 0) { + if (str8_match_lit("no", value, StringMatchFlag_CaseInsensitive)) { *value_out = LNK_SwitchState_No; is_parsed = 1; - } else if (str8_match_lit("yes", value_strings.first->string, StringMatchFlag_CaseInsensitive)) { + } else if (str8_match_lit("yes", value, StringMatchFlag_CaseInsensitive)) { *value_out = LNK_SwitchState_Yes; is_parsed = 1; - } else if (value_strings.first->string.size == 0) { - *value_out = 1; - is_parsed = 1; } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid parameter \"%S\"", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid parameter \"%S\"", value); } } else { *value_out = LNK_SwitchState_Yes; @@ -492,10 +487,10 @@ lnk_cmd_switch_parse_flag(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8Lis } internal void -lnk_cmd_switch_set_flag_inv_16(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U16 *flags, U16 bits) +lnk_cmd_switch_set_flag_inv_16(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U16 *flags, U16 bits) { LNK_SwitchState state; - if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &state)) { + if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &state)) { switch (state) { case LNK_SwitchState_Null: break; case LNK_SwitchState_Yes : *flags |= bits; break; @@ -505,10 +500,10 @@ lnk_cmd_switch_set_flag_inv_16(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, Strin } internal void -lnk_cmd_switch_set_flag_inv_64(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U64 *flags, U64 bits) +lnk_cmd_switch_set_flag_inv_64(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U64 *flags, U64 bits) { LNK_SwitchState state; - if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &state)) { + if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &state)) { switch (state) { case LNK_SwitchState_Null: break; case LNK_SwitchState_Yes : *flags |= bits; break; @@ -518,10 +513,10 @@ lnk_cmd_switch_set_flag_inv_64(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, Strin } internal void -lnk_cmd_switch_set_flag_16(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U16 *flags, U16 bits) +lnk_cmd_switch_set_flag_16(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U16 *flags, U16 bits) { LNK_SwitchState state; - if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &state)) { + if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &state)) { switch (state) { case LNK_SwitchState_Null: break; case LNK_SwitchState_Yes : *flags |= bits; break; @@ -531,10 +526,10 @@ lnk_cmd_switch_set_flag_16(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8Li } internal void -lnk_cmd_switch_set_flag_32(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U32 *flags, U32 bits) +lnk_cmd_switch_set_flag_32(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U32 *flags, U32 bits) { LNK_SwitchState state; - if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &state)) { + if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &state)) { switch (state) { case LNK_SwitchState_Null: break; case LNK_SwitchState_Yes : *flags |= bits; break; @@ -544,10 +539,10 @@ lnk_cmd_switch_set_flag_32(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8Li } internal void -lnk_cmd_switch_set_flag_64(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U64 *flags, U64 bits) +lnk_cmd_switch_set_flag_64(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U64 *flags, U64 bits) { LNK_SwitchState state; - if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &state)) { + if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &state)) { switch (state) { case LNK_SwitchState_Null: break; case LNK_SwitchState_Yes : *flags |= bits; break; @@ -557,25 +552,21 @@ lnk_cmd_switch_set_flag_64(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8Li } internal B32 -lnk_cmd_switch_parse_string(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, String8 *string_out) +lnk_cmd_switch_parse_string(LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, String8 *string_out) { - if (value_strings.node_count == 1) { - if (value_strings.first->string.size > 0) { - *string_out = value_strings.first->string; - return 1; - } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "empty string is not permitted"); - } + if (value.size) { + *string_out = value; + return 1; } else { - lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "empty string is not allowed"); + return 0; } - return 0; } internal void -lnk_cmd_switch_parse_string_copy(Arena *arena, LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, String8 *string_out) +lnk_cmd_switch_parse_string_copy(Arena *arena, LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, String8 *string_out) { - if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, string_out)) { + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value, string_out)) { *string_out = push_str8_copy(arena, *string_out); } } @@ -1094,10 +1085,7 @@ lnk_include_symbol(LNK_Config *config, String8 name, LNK_Obj *obj) internal void lnk_whole_archive(LNK_Config *config, String8 lib_name) { - String8Node value = { .string = lib_name }; - String8List value_strings = {0}; - str8_list_push_node(&value_strings, &value); - lnk_apply_cmd_option_to_config(config, str8_lit("wholearchive"), value_strings, 0); + lnk_apply_cmd_option_to_config(config, str8_lit("wholearchive"), lib_name, 0); } internal void @@ -1207,41 +1195,44 @@ lnk_apply_write_temp_files(Arena *arena, LNK_Config *config) internal void lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj); internal void -lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List value_strings, LNK_Obj *obj) +lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8 value, LNK_Obj *obj) { Temp scratch = scratch_begin(&config->arena, 1); - LNK_CmdSwitchType cmd_switch = lnk_cmd_switch_type_from_string(cmd_name); + LNK_CmdSwitch *cmd_switch_info = lnk_cmd_switch_from_string(cmd_name); + LNK_CmdSwitchType cmd_switch = cmd_switch_info ? cmd_switch_info->type : LNK_CmdSwitch_Null; + String8List values = {0}; + if (cmd_switch_info && cmd_switch_info->value_kind == LNK_CmdValueKind_List) { + values = str8_split_by_string_chars(scratch.arena, value, str8_lit(","), 0); + } switch (cmd_switch) { case LNK_CmdSwitch_Null: { - String8 value = str8_list_join(scratch.arena, &value_strings, &(StringJoin){.sep=str8_lit_comp(",")}); lnk_error_obj(LNK_Warning_UnknownSwitch, obj, "unknown switch: \"/%S%s%S\"", cmd_name, value.size ? ":" : "", value); } break; default: break; case LNK_CmdSwitch_NotImplemented: { - String8 value = str8_list_join(scratch.arena, &value_strings, &(StringJoin){.sep=str8_lit_comp(",")}); lnk_not_implemented("switch \"%S\" is not implemented \"%S\"", cmd_name, value); } break; case LNK_CmdSwitch_Align: { - lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->sect_align, LNK_ParseU64Flag_CheckPow2); + lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &config->sect_align, LNK_ParseU64Flag_CheckPow2); } break; case LNK_CmdSwitch_AllowBind: { - lnk_cmd_switch_set_flag_inv_16(obj, cmd_switch, value_strings, &config->dll_characteristics, PE_DllCharacteristic_NO_BIND); + lnk_cmd_switch_set_flag_inv_16(obj, cmd_switch, value, &config->dll_characteristics, PE_DllCharacteristic_NO_BIND); } break; case LNK_CmdSwitch_AllowIsolation: { - lnk_cmd_switch_set_flag_inv_16(obj, cmd_switch, value_strings, &config->dll_characteristics, PE_DllCharacteristic_NO_ISOLATION); + lnk_cmd_switch_set_flag_inv_16(obj, cmd_switch, value, &config->dll_characteristics, PE_DllCharacteristic_NO_ISOLATION); } break; case LNK_CmdSwitch_AlternateName: { - if (value_strings.node_count == 1) { + if (value.size > 0) { LNK_AltName alt_name; - if (lnk_parse_alt_name_directive(value_strings.first->string, obj, &alt_name)) { + if (lnk_parse_alt_name_directive(value, obj, &alt_name)) { String8 *to_extant = hash_map_search_string_string(&config->alt_name_ht, alt_name.from); if (to_extant) { if (str8_match(*to_extant, alt_name.to, 0)) { @@ -1263,12 +1254,12 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_AppContainer: { - lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->dll_characteristics, PE_DllCharacteristic_APPCONTAINER); + lnk_cmd_switch_set_flag_16(obj, cmd_switch, value, &config->dll_characteristics, PE_DllCharacteristic_APPCONTAINER); } break; case LNK_CmdSwitch_Base: { - if (value_strings.node_count == 2) { - String8Node *first_node = value_strings.first; + if (values.node_count == 2) { + String8Node *first_node = values.first; //String8Node *second_node = first_node->next; B32 is_response_file = str8_match_lit("@", first_node->string, StringMatchFlag_RightSideSloppy); if (is_response_file) { @@ -1277,17 +1268,17 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List lnk_not_implemented("Response files are not implemented for /BASE"); } else { Rng1U64 addr_size = {0}; - if (lnk_cmd_switch_parse_tuple(obj, cmd_switch, value_strings, &addr_size)) { + if (lnk_cmd_switch_parse_tuple(obj, cmd_switch, values, &addr_size)) { config->user_base_addr = addr_size.v[0]; config->max_image_size = addr_size.v[1]; } } - } else if (value_strings.node_count == 1) { + } else if (values.node_count == 1) { U64 addr; - if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &addr, 0)) { + if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &addr, 0)) { config->user_base_addr = addr; } - } else if (value_strings.node_count == 0) { + } else if (values.node_count == 0) { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "expected at least 1 parameter"); } else { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "too many parameters"); @@ -1295,10 +1286,10 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Debug: { - if (value_strings.node_count == 0) { + if (value.size == 0) { config->debug_mode = LNK_DebugMode_Full; - } else if (value_strings.node_count == 1) { - LNK_DebugMode debug_mode = lnk_debug_mode_from_string(value_strings.first->string); + } else { + LNK_DebugMode debug_mode = lnk_debug_mode_from_string(value); if (debug_mode == LNK_DebugMode_GHash) { config->debug_mode = LNK_DebugMode_Full; config->ghash = 1; @@ -1308,34 +1299,33 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } else if (debug_mode != LNK_DebugMode_Null) { config->debug_mode = debug_mode; } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid parameter \"%S\"", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid parameter \"%S\"", value); } - } else { - lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); } } break; case LNK_CmdSwitch_DefaultLib: { - String8List default_lib_list = str8_list_copy(config->arena, &value_strings); - if (obj) { - str8_list_concat_in_place(&config->input_obj_lib_list, &default_lib_list); - } else { - str8_list_concat_in_place(&config->input_default_lib_list, &default_lib_list); + if (value.size > 0) { + String8 default_lib = push_str8_copy(config->arena, value); + if (obj) { + str8_list_push(config->arena, &config->input_obj_lib_list, default_lib); + } else { + str8_list_push(config->arena, &config->input_default_lib_list, default_lib); + } } } break; case LNK_CmdSwitch_Def: { String8 path = {0}; - if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &path)) { + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value, &path)) { lnk_apply_def_file_to_config(config, path, obj); } } break; case LNK_CmdSwitch_Delay: { - if (value_strings.node_count == 0 || value_strings.node_count > 1) { + if (value.size == 0) { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); } else { - String8 value = value_strings.first->string; if (str8_match_lit("unload", value, StringMatchFlag_CaseInsensitive)) { config->import_table_emit_uiat = LNK_SwitchState_Yes; } else if (str8_match_lit("nobind", value, StringMatchFlag_CaseInsensitive)) { @@ -1347,9 +1337,8 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_DelayLoad: { - for (String8Node *name_n = value_strings.first; name_n != 0; name_n = name_n->next) { - if (hash_map_search_path_u64(&config->delay_load_ht, name_n->string)) { continue; } - String8 name = push_str8_copy(config->arena, name_n->string); + if (value.size > 0 && !hash_map_search_path_u64(&config->delay_load_ht, value)) { + String8 name = push_str8_copy(config->arena, value); hash_map_push_path_u64(config->arena, &config->delay_load_ht, name, 1); str8_list_push(config->arena, &config->delay_load_dll_list, name); } @@ -1360,12 +1349,12 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_DynamicBase: { - lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->dll_characteristics, PE_DllCharacteristic_DYNAMIC_BASE); + lnk_cmd_switch_set_flag_16(obj, cmd_switch, value, &config->dll_characteristics, PE_DllCharacteristic_DYNAMIC_BASE); } break; case LNK_CmdSwitch_Entry: { String8 new_entry_point_name = {0}; - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &new_entry_point_name); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &new_entry_point_name); if (config->entry_point_name.size) { lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, cmd_switch, "unable to redefine entry point \"%S\" to \"%S\"", config->entry_point_name, new_entry_point_name); @@ -1377,19 +1366,19 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List case LNK_CmdSwitch_Export: { PE_ExportParse export_parse = {0}; - if (lnk_parse_export_directive_ex(config->arena, value_strings, obj, &export_parse)) { + if (lnk_parse_export_directive_ex(config->arena, values, obj, &export_parse)) { lnk_push_export_to_config(config, obj, export_parse); } } break; case LNK_CmdSwitch_FailIfMismatch: { - if (value_strings.node_count != 1) { + if (value.size == 0) { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); break; } LNK_AltName dir; - if ( ! lnk_parse_alt_name_directive(value_strings.first->string, obj, &dir)) { + if ( ! lnk_parse_alt_name_directive(value, obj, &dir)) { break; } @@ -1413,25 +1402,25 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_FileAlign: { - lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->file_align, LNK_ParseU64Flag_CheckPow2); + lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &config->file_align, LNK_ParseU64Flag_CheckPow2); } break; case LNK_CmdSwitch_Fixed: { - lnk_cmd_switch_set_flag_64(obj, cmd_switch, value_strings, &config->flags, LNK_ConfigFlag_Fixed); + lnk_cmd_switch_set_flag_64(obj, cmd_switch, value, &config->flags, LNK_ConfigFlag_Fixed); } break; case LNK_CmdSwitch_FunctionPadMin: { - if (value_strings.node_count == 0) { + if (value.size == 0) { config->function_pad_min = 0; config->infer_function_pad_min = 1; } else { - lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->function_pad_min, LNK_ParseU64Flag_CheckUnder32bit); + lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &config->function_pad_min, LNK_ParseU64Flag_CheckUnder32bit); } config->do_function_pad_min = LNK_SwitchState_Yes; } break; case LNK_CmdSwitch_Guard: { - for EachNode(n, String8Node, value_strings.first) { + for EachNode(n, String8Node, values.first) { LNK_GuardOption *option = 0; for EachElement(i, g_guard_option_table) { if (str8_matchi(g_guard_option_table[i].name, n->string)) { @@ -1453,7 +1442,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List Rng1U64 reserve_commit; reserve_commit.v[0] = config->heap_reserve; reserve_commit.v[1] = config->heap_commit; - if (lnk_cmd_switch_parse_tuple(obj, cmd_switch, value_strings, &reserve_commit)) { + if (lnk_cmd_switch_parse_tuple(obj, cmd_switch, values, &reserve_commit)) { if (reserve_commit.v[0] >= reserve_commit.v[1]) { U64 reserve_aligned = AlignPow2(reserve_commit.v[0], 4); U64 commit_aligned = AlignPow2(reserve_commit.v[1], 4); @@ -1474,12 +1463,12 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_HighEntropyVa: { - lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->dll_characteristics, PE_DllCharacteristic_HIGH_ENTROPY_VA); + lnk_cmd_switch_set_flag_16(obj, cmd_switch, value, &config->dll_characteristics, PE_DllCharacteristic_HIGH_ENTROPY_VA); } break; case LNK_CmdSwitch_Ignore: { U64 error_code; - if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &error_code, 0)) { + if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &error_code, 0)) { switch (error_code) { case LNK_MsWarningCode_UnsuedDelayLoadDll: { lnk_ignore_error(LNK_Warning_UnusedDelayLoadDll); @@ -1500,21 +1489,21 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_ImpLib: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->imp_lib_name); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->imp_lib_name); } break; case LNK_CmdSwitch_Include: { - for (String8Node *value_n = value_strings.first; value_n != 0; value_n = value_n->next) { - lnk_include_symbol(config, value_n->string, obj); + if (value.size > 0) { + lnk_include_symbol(config, value, obj); } } break; case LNK_CmdSwitch_InferAsanLibs: { - lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->infer_asan_libs); + lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &config->infer_asan_libs); } break; case LNK_CmdSwitch_LargeAddressAware: { - lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->file_characteristics, PE_ImageFileCharacteristic_LARGE_ADDRESS_AWARE); + lnk_cmd_switch_set_flag_16(obj, cmd_switch, value, &config->file_characteristics, PE_ImageFileCharacteristic_LARGE_ADDRESS_AWARE); } break; case LNK_CmdSwitch_Lib: { @@ -1522,23 +1511,23 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_LibPath: { - String8List lib_dir_list = str8_list_copy(config->arena, &value_strings); - for (String8Node *dir_n = lib_dir_list.first; dir_n != 0; dir_n = dir_n->next) { - if (!folder_path_exists(dir_n->string)) { - String8 full_path = full_path_from_path(scratch.arena, dir_n->string); + if (value.size > 0) { + String8 dir = push_str8_copy(config->arena, value); + if (!folder_path_exists(dir)) { + String8 full_path = full_path_from_path(scratch.arena, dir); lnk_error_cmd_switch(LNK_Warning_Cmdl, obj, cmd_switch, "path doesn't exist %S", full_path); } + str8_list_push(config->arena, &config->lib_dir_list, dir); } - str8_list_concat_in_place(&config->lib_dir_list, &lib_dir_list); } break; case LNK_CmdSwitch_Machine: { - if (value_strings.node_count == 1) { - COFF_MachineType machine = coff_machine_from_string(value_strings.first->string); + if (value.size > 0) { + COFF_MachineType machine = coff_machine_from_string(value); if (machine != COFF_MachineType_Unknown) { config->machine = machine; } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unknown parameter \"%S\"", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unknown parameter \"%S\"", value); } } else { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); @@ -1546,9 +1535,8 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Manifest: { - if (value_strings.node_count == 1) { - String8List param_list = str8_split_by_string_chars(scratch.arena, value_strings.first->string, str8_lit(","), 0); - String8Array param_arr = str8_array_from_list(scratch.arena, ¶m_list); + if (values.node_count > 0) { + String8Array param_arr = str8_array_from_list(scratch.arena, &values); if (param_arr.count > 0) { if (str8_match_lit("embed", param_arr.v[0], StringMatchFlag_CaseInsensitive)) { config->manifest_opt = LNK_ManifestOpt_Embed; @@ -1583,16 +1571,15 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } else { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); } - } else if (value_strings.node_count == 0) { - config->manifest_opt = LNK_ManifestOpt_WriteToFile; } else { - lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); + config->manifest_opt = LNK_ManifestOpt_WriteToFile; } } break; case LNK_CmdSwitch_ManifestDependency: { - String8List manifest_dependency_list = str8_list_copy(config->arena, &value_strings); - str8_list_concat_in_place(&config->manifest_dependency_list, &manifest_dependency_list); + if (value.size > 0) { + str8_list_push(config->arena, &config->manifest_dependency_list, push_str8_copy(config->arena, value)); + } if (config->manifest_opt == LNK_ManifestOpt_Null) { config->manifest_opt = LNK_ManifestOpt_WriteToFile; @@ -1600,7 +1587,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_ManifestFile: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->manifest_name); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->manifest_name); } break; case LNK_CmdSwitch_ManifestInput: { @@ -1608,8 +1595,8 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_ManifestUac: { - if (value_strings.node_count == 1) { - String8 uac = lnk_error_check_and_strip_quotes(LNK_Error_Cmdl, obj, cmd_switch, value_strings.first->string); + if (value.size > 0) { + String8 uac = lnk_error_check_and_strip_quotes(LNK_Error_Cmdl, obj, cmd_switch, value); String8List param_list = str8_split_by_string_chars(scratch.arena, uac, str8_lit(" "), 0); String8Array param_arr = str8_array_from_list(scratch.arena, ¶m_list); if (param_arr.count > 0) { @@ -1664,9 +1651,9 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Merge: { - if (value_strings.node_count == 1) { + if (value.size > 0) { LNK_MergeDirective merge = {0}; - if (lnk_parse_merge_directive(value_strings.first->string, obj, &merge)) { + if (lnk_parse_merge_directive(value, obj, &merge)) { merge.src = push_str8_copy(config->arena, merge.src); merge.dst = push_str8_copy(config->arena, merge.dst); lnk_merge_directive_list_push(config->arena, &config->merge_list, merge); @@ -1677,28 +1664,24 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Natvis: { - // warn about invalid natvis extension - for (String8Node *node = value_strings.first; node != 0; node = node->next) { - String8 ext = str8_skip_last_dot(node->string); + if (value.size > 0) { + // warn about invalid natvis extension + String8 ext = str8_skip_last_dot(value); if (!str8_match_lit("natvis", ext, StringMatchFlag_CaseInsensitive)) { - lnk_error_cmd_switch(LNK_Warning_InvalidNatvisFileExt, obj, cmd_switch, "Visual Studio expects .natvis extension: \"%S\"", node->string); + lnk_error_cmd_switch(LNK_Warning_InvalidNatvisFileExt, obj, cmd_switch, "Visual Studio expects .natvis extension: \"%S\"", value); } - } - String8List natvis_list = str8_list_copy(config->arena, &value_strings); - str8_list_concat_in_place(&config->natvis_list, &natvis_list); + str8_list_push(config->arena, &config->natvis_list, push_str8_copy(config->arena, value)); + } } break; case LNK_CmdSwitch_DisallowLib: case LNK_CmdSwitch_NoDefaultLib: { - if (value_strings.node_count == 0) { + if (value.size == 0) { config->no_default_libs = 1; } else { - for (String8Node *lib_n = value_strings.first; lib_n != 0; lib_n = lib_n->next) { - String8 lib_name = lnk_get_lib_name(lib_n->string); - if (hash_map_search_path_u64(&config->disallow_lib_ht, lib_name)) { - continue; - } + String8 lib_name = lnk_get_lib_name(value); + if (!hash_map_search_path_u64(&config->disallow_lib_ht, lib_name)) { hash_map_push_path_u64(config->arena, &config->disallow_lib_ht, lib_name, 1); } } @@ -1713,11 +1696,11 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_NxCompat: { - lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->dll_characteristics, PE_DllCharacteristic_NX_COMPAT); + lnk_cmd_switch_set_flag_16(obj, cmd_switch, value, &config->dll_characteristics, PE_DllCharacteristic_NX_COMPAT); } break; case LNK_CmdSwitch_Opt: { - for (String8Node *n = value_strings.first; n != 0; n = n->next) { + for (String8Node *n = values.first; n != 0; n = n->next) { String8 param = n->string; if (str8_match_lit("ref", param, StringMatchFlag_CaseInsensitive)) { config->opt_ref = LNK_SwitchState_Yes; @@ -1751,21 +1734,21 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Out: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->out_path); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->out_path); } break; case LNK_CmdSwitch_Pdb: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->pdb_name); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->pdb_name); } break; case LNK_CmdSwitch_PdbAltPath: { // see :PdbAltPath - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->pdb_alt_path); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->pdb_alt_path); } break; case LNK_CmdSwitch_PdbPageSize: { U64 page_size; - if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &page_size, LNK_ParseU64Flag_CheckPow2)) { + if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &page_size, LNK_ParseU64Flag_CheckPow2)) { if (page_size >= MSF_MIN_PAGE_SIZE) { if (page_size < MSF_MAX_PAGE_SIZE) { config->pdb_page_size = page_size; @@ -1780,13 +1763,13 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List case LNK_CmdSwitch_PdbStripped: { String8 file_name; - if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &file_name)) { + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value, &file_name)) { config->pdb_stripped_name = str8_copy(config->arena, file_name); } } break; case LNK_CmdSwitch_Release: { - if (value_strings.node_count == 0) { + if (value.size == 0) { config->flags |= LNK_ConfigFlag_WriteImageChecksum; } else { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); @@ -1797,15 +1780,15 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List LNK_SectionDirective section_dir = {0}; B32 is_parse_ok = 1; - if (value_strings.node_count < 2) { + if (values.node_count < 2) { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, LNK_CmdSwitch_Section, "expected section name and attributes"); is_parse_ok = 0; } else { - section_dir.name = value_strings.first->string; + section_dir.name = values.first->string; B32 has_attr = 0; B32 has_mem_attr = 0; - for (String8Node *param_n = value_strings.first->next; param_n != 0; param_n = param_n->next) { + for (String8Node *param_n = values.first->next; param_n != 0; param_n = param_n->next) { String8 param = param_n->string; if (str8_match_lit("ALIGN=", param, StringMatchFlag_CaseInsensitive|StringMatchFlag_RightSideSloppy)) { @@ -1892,7 +1875,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List Rng1U64 reserve_commit; reserve_commit.v[0] = config->stack_reserve; reserve_commit.v[1] = config->stack_commit; - if (lnk_cmd_switch_parse_tuple(obj, cmd_switch, value_strings, &reserve_commit)) { + if (lnk_cmd_switch_parse_tuple(obj, cmd_switch, values, &reserve_commit)) { if (reserve_commit.v[0] >= reserve_commit.v[1]) { U64 reserve_aligned = AlignPow2(reserve_commit.v[0], 4); U64 commit_aligned = AlignPow2(reserve_commit.v[1], 4); @@ -1913,9 +1896,9 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_SubSystem: { - if (value_strings.node_count <= 2 && value_strings.node_count > 0) { + if (values.node_count <= 2 && values.node_count > 0) { // set subsystem type - PE_WindowsSubsystem subsystem = pe_subsystem_from_string(value_strings.first->string); + PE_WindowsSubsystem subsystem = pe_subsystem_from_string(values.first->string); if (subsystem != PE_WindowsSubsystem_UNKNOWN) { if (config->subsystem != PE_WindowsSubsystem_UNKNOWN) { if (config->subsystem != subsystem) { @@ -1927,12 +1910,11 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List config->subsystem = subsystem; // parse version (optional) - if (value_strings.node_count == 2) { - str8_list_pop_front(&value_strings); // pop subsystem parameter - lnk_cmd_switch_parse_version(obj, cmd_switch, value_strings, &config->subsystem_ver); + if (values.node_count == 2) { + lnk_cmd_switch_parse_version(obj, cmd_switch, values.last->string, &config->subsystem_ver); } } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid subsystem \"%S\"", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid subsystem \"%S\"", values.first->string); } } else { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); @@ -1943,19 +1925,19 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_TsAware: { - lnk_cmd_switch_set_flag_inv_64(obj, cmd_switch, value_strings, &config->flags, LNK_ConfigFlag_NoTsAware); + lnk_cmd_switch_set_flag_inv_64(obj, cmd_switch, value, &config->flags, LNK_ConfigFlag_NoTsAware); } break; case LNK_CmdSwitch_Version: { - lnk_cmd_switch_parse_version(obj, cmd_switch, value_strings, &config->image_ver); + lnk_cmd_switch_parse_version(obj, cmd_switch, value, &config->image_ver); } break; case LNK_CmdSwitch_WholeArchive: { - if (value_strings.node_count == 0) { + if (value.size == 0) { config->whole_archive_all = 1; } else { String8 lib_name; - if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &lib_name)) { + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value, &lib_name)) { lib_name = str8_chop_last_dot(str8_skip_last_slash(lib_name)); hash_map_push_path_u64(config->arena, &config->whole_archive_ht, lib_name, 1); } @@ -1963,12 +1945,12 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_Age: { - lnk_cmd_switch_parse_u32(obj, cmd_switch, value_strings, &config->age, 0); + lnk_cmd_switch_parse_u32(obj, cmd_switch, value, &config->age, 0); } break; //case LNK_CmdSwitch_Rad_BuildExp: { // LNK_SwitchState state; - // if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &state)) { + // if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &state)) { // config->build_exp = (state == LNK_SwitchState_Yes); // } //} break; @@ -1980,18 +1962,18 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List case LNK_CmdSwitch_Rad_BuildImpLib: { LNK_SwitchState state; - if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &state)) { + if (lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &state)) { config->build_imp_lib = (state == LNK_SwitchState_Yes); } } break; case LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll: { - lnk_cmd_switch_set_flag_64(obj, cmd_switch, value_strings, &config->flags, LNK_ConfigFlag_CheckUnusedDelayLoadDll); + lnk_cmd_switch_set_flag_64(obj, cmd_switch, value, &config->flags, LNK_ConfigFlag_CheckUnusedDelayLoadDll); } break; case LNK_CmdSwitch_Rad_DataDirCount: { U64 data_dir_count = 0; - if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &data_dir_count, LNK_ParseU64Flag_CheckUnder32bit)) { + if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &data_dir_count, LNK_ParseU64Flag_CheckUnder32bit)) { if (1 <= data_dir_count && data_dir_count <= PE_DataDirectoryIndex_COUNT) { config->data_dir_count = data_dir_count; } else { @@ -2001,20 +1983,19 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_Map: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->rad_chunk_map_name); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->rad_chunk_map_name); config->rad_chunk_map = LNK_SwitchState_Yes; } break; case LNK_CmdSwitch_Rad_MapLinesForUnresolvedSymbols: { - lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->map_lines_for_unresolved_symbols); + lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &config->map_lines_for_unresolved_symbols); } break; case LNK_CmdSwitch_Rad_MemoryMapFiles: { - if (value_strings.node_count == 0) { + if (value.size == 0) { config->io_flags &= ~LNK_IO_Flags_MemoryMapFilesReadWrite; config->io_flags |= LNK_IO_Flags_MemoryMapFilesReadOnly; - } else if (value_strings.node_count == 1) { - String8 value = value_strings.first->string; + } else { if (str8_matchi(value, str8_lit("no"))) { config->io_flags &= ~(LNK_IO_Flags_MemoryMapFilesReadOnly|LNK_IO_Flags_MemoryMapFilesReadWrite); } else if (str8_matchi(value, str8_lit("yes")) || str8_matchi(value, str8_lit("read_only"))) { @@ -2026,19 +2007,17 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } else { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid parameter: \"%S\", expected NO, READ_ONLY, or READ_WRITE", value); } - } else { - lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); } } break; case LNK_CmdSwitch_Rad_BootMode: { - if (value_strings.node_count == 1) { - if (str8_matchi(value_strings.first->string, str8_lit("linker"))) { + if (value.size > 0) { + if (str8_matchi(value, str8_lit("linker"))) { config->boot_mode = LNK_BootMode_Linker; - } else if (str8_matchi(value_strings.first->string, str8_lit("type_server"))) { + } else if (str8_matchi(value, str8_lit("type_server"))) { config->boot_mode = LNK_BootMode_TypeServer; } else { - lnk_error_cmd_switch(LNK_Error_Boot, obj, cmd_switch, "unknown value: \"%S\".", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Boot, obj, cmd_switch, "unknown value: \"%S\".", value); } } else { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Boot, obj, cmd_switch); @@ -2046,46 +2025,46 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_Debug: { - lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->rad_debug); + lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &config->rad_debug); } break; case LNK_CmdSwitch_Rad_DebugName: { // :Rad_DebugAltPath - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->rad_debug_name); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->rad_debug_name); } break; case LNK_CmdSwitch_Rad_DebugAltPath: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->rad_debug_alt_path); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->rad_debug_alt_path); } break; case LNK_CmdSwitch_Rad_DelayBind: { - lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->import_table_emit_biat); + lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &config->import_table_emit_biat); } break; case LNK_CmdSwitch_Rad_DoMerge: { - lnk_cmd_switch_set_flag_64(obj, cmd_switch, value_strings, &config->flags, LNK_ConfigFlag_Merge); + lnk_cmd_switch_set_flag_64(obj, cmd_switch, value, &config->flags, LNK_ConfigFlag_Merge); } break; case LNK_CmdSwitch_Rad_EnvLib: { - lnk_cmd_switch_set_flag_64(obj, cmd_switch, value_strings, &config->flags, LNK_ConfigFlag_EnvLib); + lnk_cmd_switch_set_flag_64(obj, cmd_switch, value, &config->flags, LNK_ConfigFlag_EnvLib); } break; case LNK_CmdSwitch_Rad_Exe: { - lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->file_characteristics, PE_ImageFileCharacteristic_EXECUTABLE_IMAGE); + lnk_cmd_switch_set_flag_16(obj, cmd_switch, value, &config->file_characteristics, PE_ImageFileCharacteristic_EXECUTABLE_IMAGE); } break; case LNK_CmdSwitch_Rad_Guid: { - if (value_strings.node_count == 1) { - if (str8_match_lit("imageblake3", value_strings.first->string, StringMatchFlag_CaseInsensitive)) { + if (value.size > 0) { + if (str8_match_lit("imageblake3", value, StringMatchFlag_CaseInsensitive)) { config->guid_type = Lnk_DebugInfoGuid_ImageBlake3; - } else if (str8_match_lit("random", value_strings.first->string, StringMatchFlag_CaseInsensitive)) { + } else if (str8_match_lit("random", value, StringMatchFlag_CaseInsensitive)) { config->guid = make_guid(); } else { Guid guid; - if (try_guid_from_string(value_strings.first->string, &guid)) { + if (try_guid_from_string(value, &guid)) { config->guid = guid; } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse \"%S\"", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse \"%S\"", value); } } } else { @@ -2094,7 +2073,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_LargePages: { - if (value_strings.node_count == 0) { + if (value.size == 0) { ProcessInfo *process_info = get_process_info(); if (process_info->large_pages_allowed) { arena_default_flags |= ArenaFlag_LargePages; @@ -2110,30 +2089,26 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List lnk_supplement_error("\t- Click Oks and reboot the machine"); #endif } - } else if (value_strings.node_count == 1) { - if (str8_match_lit("quiet", value_strings.first->string, StringMatchFlag_CaseInsensitive)) { + } else { + if (str8_match_lit("quiet", value, StringMatchFlag_CaseInsensitive)) { ProcessInfo *process_info = get_process_info(); if (process_info->large_pages_allowed) { arena_default_flags |= ArenaFlag_LargePages; } - } else if (str8_match_lit("no", value_strings.first->string, StringMatchFlag_CaseInsensitive)) { + } else if (str8_match_lit("no", value, StringMatchFlag_CaseInsensitive)) { arena_default_flags &= ~ArenaFlag_LargePages; } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid parameter: \"%S\", expected NO or QUIET", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid parameter: \"%S\", expected NO or QUIET", value); } - } else { - lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); } } break; case LNK_CmdSwitch_Rad_LinkVer: { - lnk_cmd_switch_parse_version(obj, cmd_switch, value_strings, &config->link_ver); + lnk_cmd_switch_parse_version(obj, cmd_switch, value, &config->link_ver); } break; case LNK_CmdSwitch_Rad_Log: { - if (value_strings.node_count == 1) { - String8 value = value_strings.first->string; - + if (value.size > 0) { B32 status = 1; if (str8_starts_with(value, str8_lit("-"))) { value = str8_skip(value, 1); @@ -2161,24 +2136,24 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_MtPath: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->mt_path); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->mt_path); } break; case LNK_CmdSwitch_Rad_OsVer: { - lnk_cmd_switch_parse_version(obj, cmd_switch, value_strings, &config->os_ver); + lnk_cmd_switch_parse_version(obj, cmd_switch, value, &config->os_ver); } break; case LNK_CmdSwitch_Rad_PageSize: { - lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->machine_page_size, 0); + lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &config->machine_page_size, 0); } break; case LNK_CmdSwitch_Rad_PathStyle: { - if (value_strings.node_count == 1) { - PathStyle path_style = path_style_from_string(str8_list_first(&value_strings)); + if (value.size > 0) { + PathStyle path_style = path_style_from_string(value); if (path_style != PathStyle_Null) { config->path_style = path_style; } else { - lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse parameter \"%S\"", value_strings.first->string); + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unable to parse parameter \"%S\"", value); } } else { lnk_error_cmd_switch_invalid_param_count(LNK_Error_Cmdl, obj, cmd_switch); @@ -2186,7 +2161,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_PdbHashTypeNames: { - String8 mode_string = str8_list_first(&value_strings); + String8 mode_string = value; LNK_TypeNameHashMode mode; if (mode_string.size == 0) { @@ -2202,26 +2177,26 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_PdbHashTypeNameMap: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->pdb_hash_type_name_map); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->pdb_hash_type_name_map); } break; case LNK_CmdSwitch_Rad_PdbHashTypeNameLength: { - lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->pdb_hash_type_name_length, 0); + lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &config->pdb_hash_type_name_length, 0); } break; case LNK_CmdSwitch_Rad_RemoveSection: { String8 sect_name = {0}; - if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, §_name)) { + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value, §_name)) { sect_name = push_str8_copy(config->arena, sect_name); str8_list_push(config->arena, &config->remove_sections, sect_name); } } break; case LNK_CmdSwitch_Rad_SharedThreadPool: { - if (value_strings.node_count == 0) { + if (value.size == 0) { config->shared_thread_pool_name = str8_lit(LNK_DEFAULT_THREAD_POOL_NAME); } else { - lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &config->shared_thread_pool_name); + lnk_cmd_switch_parse_string(obj, cmd_switch, value, &config->shared_thread_pool_name); if (config->shared_thread_pool_name.size == 0) { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "invalid empty string for thread pool name"); } @@ -2230,10 +2205,10 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List case LNK_CmdSwitch_Rad_SharedThreadPoolMaxWorkers: { SystemInfo *sysinfo = get_system_info(); - if (value_strings.node_count == 0) { + if (value.size == 0) { config->max_worker_count = sysinfo->logical_processor_count; } else { - lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->max_worker_count, 0); + lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &config->max_worker_count, 0); if (config->max_worker_count == 0) { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "number of workers must be greater than zero"); } else if (config->max_worker_count > sysinfo->logical_processor_count) { @@ -2244,12 +2219,12 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_SortImports: { - lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->sort_imports); + lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &config->sort_imports); } break; case LNK_CmdSwitch_Rad_Ignore: { S64List error_code_list = {0}; - if ( ! lnk_cmd_switch_parse_s64_list(scratch.arena, obj, cmd_switch, value_strings, &error_code_list, 0)) { + if ( ! lnk_cmd_switch_parse_s64_list(scratch.arena, obj, cmd_switch, values, &error_code_list, 0)) { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "failed to parse input code"); break; } @@ -2271,24 +2246,24 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_ImageAltPath: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->image_alt_path); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->image_alt_path); } break; case LNK_CmdSwitch_Rad_WriteTempFiles: { - lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->write_temp_files); + lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &config->write_temp_files); if (config->write_temp_files == LNK_SwitchState_Yes) { lnk_apply_write_temp_files(config->arena, config); } } break; case LNK_CmdSwitch_Rad_TimeStamp: { - lnk_cmd_switch_parse_u32(obj, cmd_switch, value_strings, &config->time_stamp, 0); + lnk_cmd_switch_parse_u32(obj, cmd_switch, value, &config->time_stamp, 0); } break; case LNK_CmdSwitch_Rad_IcfHashKind: { // TODO: dedup String8 alg = {0}; - if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &alg)) { + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value, &alg)) { if (str8_matchi(alg, str8_lit("BLAKE3"))) { config->icf_hash_kind = LNK_HashKind_BLAKE3; } else if (str8_matchi(alg, str8_lit("XXHASH"))) { config->icf_hash_kind = LNK_HashKind_XXHash; } else { lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unknown hash alg: %S", alg); } @@ -2297,7 +2272,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List case LNK_CmdSwitch_Rad_DebugTypeHash: { String8 alg = {0}; - if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &alg)) { + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value, &alg)) { if (str8_match(alg, str8_lit("BLAKE3"), StringMatchFlag_CaseInsensitive)) { config->debug_types_hash = LNK_HashKind_BLAKE3; } else if (str8_match(alg, str8_lit("XXHASH"), StringMatchFlag_CaseInsensitive)) { @@ -2309,11 +2284,11 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_Rad_UnresolvedSymbolLimit: { - lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->unresolved_symbol_limit, 0); + lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &config->unresolved_symbol_limit, 0); } break; case LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit: { - lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->unresolved_symbol_ref_limit, 0); + lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &config->unresolved_symbol_ref_limit, 0); } break; case LNK_CmdSwitch_Rad_Version: { @@ -2323,13 +2298,13 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List case LNK_CmdSwitch_Rad_Workers: { U64 worker_count; - if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &worker_count, 0)) { + if (lnk_cmd_switch_parse_u64(obj, cmd_switch, value, &worker_count, 0)) { config->worker_count = worker_count; } } break; case LNK_CmdSwitch_Rad_WorkDir: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->work_dir); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->work_dir); } break; case LNK_CmdSwitch_Help: { @@ -2338,7 +2313,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_RadTypeServer: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value_strings, &config->type_server_name); + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->type_server_name); if (config->type_server_name.size) { String8 ext = str8_postfix(config->type_server_name, s("rrt").size); @@ -2353,7 +2328,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } break; case LNK_CmdSwitch_LLVM_AddrSig: { - lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->llvm_addrsig); + lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &config->llvm_addrsig); } break; } @@ -2593,8 +2568,7 @@ lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj) } if (has_base) { - String8List values = str8_split_by_string_chars(scratch.arena, base_value, str8_lit(","), 0); - lnk_apply_cmd_option_to_config(config, str8_lit("base"), values, obj); + lnk_apply_cmd_option_to_config(config, str8_lit("base"), base_value, obj); } else if (name.size == 0) { name = token; } else { @@ -2662,24 +2636,26 @@ lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj) } if (is_parse_ok) { - lnk_apply_cmd_option_to_config(config, str8_lit("section"), section_values, obj); + String8 value = str8_list_join(scratch.arena, §ion_values, &(StringJoin){ .sep = str8_lit(",") }); + lnk_apply_cmd_option_to_config(config, str8_lit("section"), value, obj); } } } break; case LNK_DefFileStmt_StackSize: { String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t,"), 0); - lnk_apply_cmd_option_to_config(config, str8_lit("stack"), values, obj); + String8 value = str8_list_join(scratch.arena, &values, &(StringJoin){ .sep = str8_lit(",") }); + lnk_apply_cmd_option_to_config(config, str8_lit("stack"), value, obj); } break; case LNK_DefFileStmt_Version: { - String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t"), 0); - lnk_apply_cmd_option_to_config(config, str8_lit("version"), values, obj); + lnk_apply_cmd_option_to_config(config, str8_lit("version"), str8_skip_chop_whitespace(line), obj); } break; case LNK_DefFileStmt_HeapSize: { String8List values = str8_split_by_string_chars(scratch.arena, line, str8_lit(" \t,"), 0); - lnk_apply_cmd_option_to_config(config, str8_lit("heap"), values, obj); + String8 value = str8_list_join(scratch.arena, &values, &(StringJoin){ .sep = str8_lit(",") }); + lnk_apply_cmd_option_to_config(config, str8_lit("heap"), value, obj); } break; default: { @@ -2693,7 +2669,7 @@ lnk_apply_def_file_to_config(LNK_Config *config, String8 path, LNK_Obj *obj) } internal String8List -lnk_unwrap_cmd_line(Arena *arena, String8 arg_string) +lnk_unwrap_cmd_line(Arena *arena, String8List arg_list) { Temp scratch = scratch_begin(&arena, 1); @@ -2706,7 +2682,6 @@ lnk_unwrap_cmd_line(Arena *arena, String8 arg_string) }; String8List result = {0}; - String8List arg_list = lnk_arg_list_parse_windows_rules(scratch.arena, arg_string); LNK_RspFrame *frame = &(LNK_RspFrame){ .arg_cursor = arg_list.first, .path = str8_lit("Command Line") }; while (frame) { @@ -2875,8 +2850,7 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) LNK_CmdLine parsed_cmd_line = lnk_cmd_line_from_stringf_windows_rules(arena, __VA_ARGS__); \ for EachNode(cmd, LNK_CmdOption, parsed_cmd_line.first_option) { \ if (!lnk_cmd_line_has_switch(user_cmd_line, lnk_cmd_switch_type_from_string(cmd->string))) { \ - String8List value_strings = str8_list_copy(arena, &cmd->value_strings); \ - lnk_cmd_line_push_option_list(arena, &cmd_line, cmd->string, value_strings); \ + lnk_cmd_line_push_option_string(arena, &cmd_line, cmd->string, cmd->value); \ } \ } \ } while (0) @@ -2921,34 +2895,29 @@ lnk_config_init(U64 argc, char **argv) // load env vars HashMap env_vars = lnk_env_vars_from_process_info(scratch.arena, get_process_info(), LNK_EnvVarRule_Batch); - // concat argv - String8 user_cmd_line; - { - String8List raw_cmd_line = {0}; - for (U64 i = 1; i < argc; i += 1) { - str8_list_push(scratch.arena, &raw_cmd_line, str8_cstring(argv[i])); - } - user_cmd_line = str8_list_join(scratch.arena, &raw_cmd_line, &(StringJoin){.sep = str8_lit(" ") }); - } - // concat command line from env vars - String8 cmd_line_string = {0}; + String8List user_args = {0}; { - LNK_EnvVar *link_var_pre = lnk_env_var_from_mapf(&env_vars, "LINK"); - LNK_EnvVar *link_var_post = lnk_env_var_from_mapf(&env_vars, "_LINK_"); + LNK_EnvVar *link; - String8List list = {0}; - if (link_var_pre) { - str8_list_push(scratch.arena, &list, link_var_pre->raw_value); - } - str8_list_push(scratch.arena, &list, user_cmd_line); - if (link_var_post) { - str8_list_push(scratch.arena, &list, link_var_post->raw_value); + if (link = lnk_env_var_from_mapf(&env_vars, "LINK")) { + String8List args = lnk_arg_list_parse_windows_rules(scratch.arena, link->raw_value); + str8_list_concat_in_place(&user_args, &args); } - cmd_line_string = str8_list_join(scratch.arena, &list, &(StringJoin){.sep = str8_lit(" ")}); + for (U64 i = 1; i < argc; i += 1) { + str8_list_push(scratch.arena, &user_args, str8_cstring(argv[i])); + } + + if (link = lnk_env_var_from_mapf(&env_vars, "_LINK_")) { + String8List args = lnk_arg_list_parse_windows_rules(scratch.arena, link->raw_value); + str8_list_concat_in_place(&user_args, &args); + } } + // concat command line string for later usages (e.g. embed in debug info) + String8 cmd_line_string = str8_list_join(scratch.arena, &user_args, &(StringJoin){ .sep = str8_lit(" ") }); + #if PROFILE_TELEMETRY tmMessage(0, TMMF_ICON_NOTE, "Command Line: %.*s", str8_varg(cmd_line_string)); #endif @@ -2956,7 +2925,7 @@ lnk_config_init(U64 argc, char **argv) // concat default arguments LNK_CmdLine cmd_line = {0}; { - String8List cmd_unwrap = lnk_unwrap_cmd_line(scratch.arena, cmd_line_string); + String8List cmd_unwrap = lnk_unwrap_cmd_line(scratch.arena, user_args); LNK_CmdLine cmd_line_user = lnk_cmd_line_parse_windows_rules(scratch.arena, cmd_unwrap); LNK_CmdLine cmd_line_default = lnk_make_default_cmd_line(scratch.arena, cmd_line_user); lnk_cmd_line_concat_in_place(&cmd_line, &cmd_line_default); @@ -2972,7 +2941,7 @@ lnk_config_init(U64 argc, char **argv) // apply command line switches for EachNode(cmd, LNK_CmdOption, cmd_line.first_option) { - lnk_apply_cmd_option_to_config(config, cmd->string, cmd->value_strings, 0); + lnk_apply_cmd_option_to_config(config, cmd->string, cmd->value, 0); } // in shared thread pool mode force fixed number of workers diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index 97f29c79..ab2da698 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -155,10 +155,18 @@ typedef enum LNK_CmdSwitch_Count } LNK_CmdSwitchType; +typedef enum LNK_CmdValueKind +{ + LNK_CmdValueKind_Null, + LNK_CmdValueKind_Scalar, + LNK_CmdValueKind_List, +} LNK_CmdValueKind; + typedef struct LNK_CmdSwitch { LNK_CmdSwitchType type; B32 is_legal_directive; + LNK_CmdValueKind value_kind; char *name; char *args; char *desc; @@ -566,18 +574,18 @@ internal void lnk_error_cmd_switch_invalid_param (LNK_ErrorCode code, struc // --- Specialized Parsers ------------------------------------------------------ -internal B32 lnk_cmd_switch_parse_version (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, Version *ver_out); -internal B32 lnk_cmd_switch_parse_tuple (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, Rng1U64 *tuple_out); -internal B32 lnk_cmd_switch_parse_u64 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U64 *value_out, LNK_ParseU64Flags flags); -internal B32 lnk_cmd_switch_parse_u32 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U32 *value_out, LNK_ParseU64Flags flags); -internal B32 lnk_cmd_switch_parse_flag (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, LNK_SwitchState *value_out); -internal void lnk_cmd_switch_set_flag_inv_16(struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U16 *flags, U16 bits); -internal void lnk_cmd_switch_set_flag_inv_64(struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U64 *flags, U64 bits); -internal void lnk_cmd_switch_set_flag_16 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U16 *flags, U16 bits); -internal void lnk_cmd_switch_set_flag_32 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U32 *flags, U32 bits); -internal void lnk_cmd_switch_set_flag_64 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, U64 *flags, U64 bits); -internal B32 lnk_cmd_switch_parse_string (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, String8 *string_out); -internal void lnk_cmd_switch_parse_string_copy(Arena *arena, struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List value_strings, String8 *string_out); +internal B32 lnk_cmd_switch_parse_version (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, Version *ver_out); +internal B32 lnk_cmd_switch_parse_tuple (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8List values, Rng1U64 *tuple_out); +internal B32 lnk_cmd_switch_parse_u64 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U64 *value_out, LNK_ParseU64Flags flags); +internal B32 lnk_cmd_switch_parse_u32 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U32 *value_out, LNK_ParseU64Flags flags); +internal B32 lnk_cmd_switch_parse_flag (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, LNK_SwitchState *value_out); +internal void lnk_cmd_switch_set_flag_inv_16(struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U16 *flags, U16 bits); +internal void lnk_cmd_switch_set_flag_inv_64(struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U64 *flags, U64 bits); +internal void lnk_cmd_switch_set_flag_16 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U16 *flags, U16 bits); +internal void lnk_cmd_switch_set_flag_32 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U32 *flags, U32 bits); +internal void lnk_cmd_switch_set_flag_64 (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, U64 *flags, U64 bits); +internal B32 lnk_cmd_switch_parse_string (struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, String8 *string_out); +internal void lnk_cmd_switch_parse_string_copy(Arena *arena, struct LNK_Obj *obj, LNK_CmdSwitchType cmd_switch, String8 value, String8 *string_out); internal B32 lnk_parse_alt_name_directive (String8 string, struct LNK_Obj *obj, LNK_AltName *alt_out); internal B32 lnk_parse_merge_directive (String8 string, struct LNK_Obj *obj, LNK_MergeDirective *parse_out); @@ -613,7 +621,7 @@ internal void lnk_whole_archive(LNK_Config *config, String8 lib_name); // --- Config ------------------------------------------------------------------ -internal void lnk_apply_cmd_option_to_config(LNK_Config *config, String8 name, String8List value_list, struct LNK_Obj *obj); +internal void lnk_apply_cmd_option_to_config(LNK_Config *config, String8 name, String8 value, struct LNK_Obj *obj); internal LNK_Config * lnk_config_init(U64 argc, char **argv); diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 73bd5366..d86f5e05 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -881,7 +881,7 @@ lnk_parse_msvc_linker_directive(Arena *arena, LNK_Obj *obj, LNK_DirectiveInfo *d LNK_Directive *directive = push_array_no_zero(arena, LNK_Directive, 1); directive->next = 0; directive->id = str8_cstring(cmd_switch->name); - directive->value_list = str8_list_copy(arena, &opt->value_strings); + directive->value = push_str8_copy(arena, opt->value); LNK_DirectiveList *directive_list = &directive_info->v[cmd_switch->type]; SLLQueuePush(directive_list->first, directive_list->last, directive); diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 2f771741..1a7d90b6 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -89,7 +89,7 @@ typedef struct LNK_Directive { struct LNK_Directive *next; String8 id; - String8List value_list; + String8 value; } LNK_Directive; typedef struct LNK_DirectiveList From 33f5a27fddccd5c7c2b39d4988f3d7690525b2cb Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 18 Aug 2026 10:49:13 -0700 Subject: [PATCH 054/251] work around for the function overrides --- src/linker/lnk.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 049d5fc4..1cf6a84d 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1354,6 +1354,40 @@ lnk_load_inputs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer lnk_push_obj_symbols(tp, arena, symtab, new_objs_count, new_objs); } + // TODO: This works around function override metadata pulled in from archives. + // We need a BDD merger to implement the overrides. For now, create a weak + // symbol for the function override to prevent all arch specific functions + // from being pulled in from archives. + { + B32 has_function_overrides = 0; + + for EachNode(alt_name_n, LNK_AltNameNode, config->alt_name_list.first) { + LNK_AltName alt_name = alt_name_n->v; + if ( ! str8_ends_with(alt_name.from, str8_lit("$fo$"), 0)) { + continue; + } + + LNK_Symbol *symbol = lnk_symbol_table_search(symtab, alt_name.from); + if (symbol && lnk_interp_from_symbol(symbol) == COFF_SymbolValueInterp_Undefined) { + COFF_ObjWriter *obj_writer = coff_obj_writer_alloc(0, COFF_MachineType_Unknown); + COFF_ObjSymbol *to_symbol = coff_obj_writer_push_symbol_undef(obj_writer, alt_name.to); + coff_obj_writer_push_symbol_weak(obj_writer, alt_name.from, COFF_WeakExt_SearchAlias, to_symbol); + String8 obj_data = coff_obj_writer_serialize(arena->v[0], obj_writer); + coff_obj_writer_release(&obj_writer); + + LNK_Obj *obj = alt_name.obj; + lnk_inputer_push_obj_linkgen(inputer, obj ? obj->link_member : 0, + obj ? obj->path : str8_lit("RADLINK"), obj_data); + + has_function_overrides = 1; + } + } + + if (has_function_overrides) { + lnk_load_inputs(tp, arena, config, inputer, symtab, link); + } + } + // input default libraries for (; *link->last_default_lib; link->last_default_lib = &(*link->last_default_lib)->next) { lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Default, (*link->last_default_lib)->string); From 73ba34f354e00f379eb76a887afec575505110e7 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 19 Aug 2026 10:21:50 -0600 Subject: [PATCH 055/251] adjust path absolutification - never assume selected module, since this changes all the time! instead, provide dbgi path (and maybe eventually we want module path too), so that any paths found in debug info can be treated as relative from the debug info path. --- src/base/base_strings.c | 8 ++--- src/dbg_engine/dbg_engine_ctrl.c | 9 +++++- src/dbg_engine/dbg_engine_ctrl.h | 1 + src/dbg_engine/dbg_engine_user.c | 34 +++++++++++++++------- src/dbg_engine/dbg_engine_user.h | 2 +- src/raddbg/raddbg_core.c | 50 ++++++++++++++++++-------------- src/raddbg/raddbg_views.c | 21 +++++++++----- 7 files changed, 80 insertions(+), 45 deletions(-) diff --git a/src/base/base_strings.c b/src/base/base_strings.c index aa65537d..209b0e37 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -1856,7 +1856,7 @@ path_absolute_dst_from_relative_dst_src(Arena *arena, String8 dst, String8 src) if(dst.size != 0 && dst_style == PathStyle_Relative) { Temp scratch = scratch_begin(&arena, 1); - String8 dst_from_src_absolute = push_str8f(scratch.arena, "%S/%S", src, dst); + String8 dst_from_src_absolute = str8f(scratch.arena, "%S/%S", src, dst); String8List dst_from_src_absolute_parts = str8_split_path(scratch.arena, dst_from_src_absolute); PathStyle dst_from_src_absolute_style = path_style_from_str8(src); str8_path_list_resolve_dots_in_place(&dst_from_src_absolute_parts, dst_from_src_absolute_style); @@ -3081,15 +3081,15 @@ str8_compar(String8 a, String8 b, B32 ignore_case) { U64 size = Min(a.size, b.size); int cmp = ignore_case ? MemCompareI(a.str, b.str, size) : MemCompare(a.str, b.str, size); - + // normalize compar result cmp = cmp > 0 ? 1 : cmp < 0 ? -1 : 0; - + // shorter prefix must precede longer prefixes if (cmp == 0) { cmp = a.size < b.size ? -1 : - a.size > b.size ? +1 : 0; + a.size > b.size ? +1 : 0; } return cmp; diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index a613378e..eb9f1fc1 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -823,7 +823,14 @@ 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 dbgi_key = di_key_from_path_timestamp(debug_info_path->string, debug_info_path->timestamp); + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(debug_info_path); + return dbgi_key; +} + +internal DI_Key +d_dbgi_key_from_debug_info_path(D_Entity *dbg_path) +{ + DI_Key dbgi_key = di_key_from_path_timestamp(dbg_path->string, dbg_path->timestamp); return dbgi_key; } diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index 4fe1ab95..dd83ff99 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -488,6 +488,7 @@ internal D_Entity *d_entity_ancestor_from_kind(D_Entity *entity, D_EntityKind ki internal D_Entity *d_process_from_entity(D_Entity *entity); internal D_Entity *d_module_from_process_vaddr(D_Entity *process, U64 vaddr); internal DI_Key d_dbgi_key_from_module(D_Entity *module); +internal DI_Key d_dbgi_key_from_debug_info_path(D_Entity *dbg_path); internal D_Entity *d_module_from_thread_candidates(D_Entity *thread, D_EntityList *candidates); internal U64 d_vaddr_from_voff(D_Entity *module, U64 voff); internal U64 d_voff_from_vaddr(D_Entity *module, U64 vaddr); diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index 9a923aa1..cc39701a 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -345,7 +345,9 @@ d_trap_net_from_thread__step_over_line(Arena *arena, D_Entity *thread) U64 ip_vaddr = d_cached_ip_from_thread(thread->handle); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); D_Entity *module = d_module_from_process_vaddr(process, ip_vaddr); - DI_Key dbgi_key = d_dbgi_key_from_module(module); + D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); + String8 dbgi_path = dbg_path->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); log_infof("ip_vaddr: 0x%I64x\n", ip_vaddr); log_infof("dbgi_key: {0x%I64x, 0x%I64x}\n", dbgi_key.u64[0], dbgi_key.u64[1]); @@ -355,7 +357,7 @@ d_trap_net_from_thread__step_over_line(Arena *arena, D_Entity *thread) 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); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, ip_voff); Rng1U64 line_voff_rng = {0}; if(lines.first != 0) { @@ -404,7 +406,7 @@ d_trap_net_from_thread__step_over_line(Arena *arena, D_Entity *thread) // 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); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, 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); @@ -583,7 +585,9 @@ d_trap_net_from_thread__step_into_line(Arena *arena, D_Entity *thread) U64 ip_vaddr = d_cached_ip_from_thread(thread->handle); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); D_Entity *module = d_module_from_process_vaddr(process, ip_vaddr); - DI_Key dbgi_key = d_dbgi_key_from_module(module); + D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); + String8 dbgi_path = dbg_path->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); // rjf: ip => line info String8 file_path = {0}; @@ -591,7 +595,7 @@ d_trap_net_from_thread__step_into_line(Arena *arena, D_Entity *thread) 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); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, ip_voff); Rng1U64 line_voff_rng = {0}; if(lines.first != 0) { @@ -623,7 +627,7 @@ d_trap_net_from_thread__step_into_line(Arena *arena, D_Entity *thread) // 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); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, 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); @@ -686,8 +690,10 @@ d_trap_net_from_thread__step_into_line(Arena *arena, D_Entity *thread) U64 jump_dest_vaddr = point->jump_dest_vaddr; D_Entity *jump_dest_module = d_module_from_process_vaddr(process, jump_dest_vaddr); U64 jump_dest_voff = d_voff_from_vaddr(jump_dest_module, jump_dest_vaddr); - DI_Key jump_dest_dbgi_key = d_dbgi_key_from_module(jump_dest_module); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, jump_dest_dbgi_key, jump_dest_voff); + D_Entity *jump_dest_dbg_path = d_entity_child_from_kind(jump_dest_module, D_EntityKind_DebugInfoPath); + String8 jump_dest_dbgi_path = jump_dest_dbg_path->string; + DI_Key jump_dest_dbgi_key = d_dbgi_key_from_debug_info_path(jump_dest_dbg_path); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, jump_dest_dbgi_key, jump_dest_dbgi_path, jump_dest_voff); if(lines.count == 0) { add = 0; @@ -996,7 +1002,7 @@ d_trap_net_from_thread__step_to_exit(Arena *arena, D_Entity *thread) //- rjf: voff -> line info internal D_LineList -d_lines_from_dbgi_key_voff(Arena *arena, DI_Key dbgi_key, U64 voff) +d_lines_from_dbgi_key_path_voff(Arena *arena, DI_Key dbgi_key, String8 dbgi_path, U64 voff) { Temp scratch = scratch_begin(&arena, 1); Access *access = access_open(); @@ -1064,12 +1070,18 @@ d_lines_from_dbgi_key_voff(Arena *arena, DI_Key dbgi_key, U64 voff) StringJoin join = {0}; join.sep = str8_lit("/"); String8 file_normalized_full_path = str8_list_join(arena, &path_parts, &join); + String8 file_normalized_full_path_absolute = file_normalized_full_path; + if(dbgi_path.size != 0) + { + String8 dbgi_path_folder = str8_chop_last_slash(dbgi_path); + file_normalized_full_path_absolute = path_absolute_dst_from_relative_dst_src(arena, file_normalized_full_path, dbgi_path_folder); + } 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) + if(line->file_idx != 0 && file_normalized_full_path_absolute.size != 0) { - n->v.file_path = file_normalized_full_path; + n->v.file_path = file_normalized_full_path_absolute; } 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]); diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index eb63a750..3eeb8598 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -433,7 +433,7 @@ internal D_TrapNet d_trap_net_from_thread__step_to_exit(Arena *arena, D_Entity * //~ rjf: Debug Info Lookups //- rjf: voff -> line info -internal D_LineList d_lines_from_dbgi_key_voff(Arena *arena, DI_Key dbgi_key, U64 voff); +internal D_LineList d_lines_from_dbgi_key_path_voff(Arena *arena, DI_Key dbgi_key, String8 dbgi_path, U64 voff); //- rjf: file:line -> line info // TODO(rjf): this depends on file path maps, needs to move diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 14b199ff..225fe2d9 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -4846,11 +4846,11 @@ rd_view_ui(Rng2F32 rect) // rjf: try to unpack debug info from type E_TypeKey type_key = e_type_key_unwrap(cell->eval.irtree.type_key, E_TypeUnwrapFlag_All); DI_Key dbgi_key = {0}; - String8 base_folder = {0}; + String8 dbgi_path = {0}; { E_DbgInfo *dbg_info = e_dbg_info_from_type_key(type_key); dbgi_key = dbg_info->dbgi_key; - base_folder = str8_chop_last_slash(dbg_info->name); + dbgi_path = dbg_info->name; } // rjf: if this is a vaddr evaluation, then unpack voff/vaddr & switch to module's debug info @@ -4864,11 +4864,10 @@ rd_view_ui(Rng2F32 rect) module = d_module_from_process_vaddr(process, vaddr); voff = d_voff_from_vaddr(module, vaddr); dbgi_key = d_dbgi_key_from_module(module); - base_folder = str8_chop_last_slash(module->string); } // rjf: voff * debug info -> lines - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, voff); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, voff); // rjf: snap to line if(lines.first != 0) @@ -10652,10 +10651,12 @@ rd_gather_auto_exprs(Arena *arena) D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); U64 thread_ip_vaddr = d_cached_ip_from_thread(thread_handle); D_Entity *module = d_module_from_process_vaddr(process, thread_ip_vaddr); - DI_Key dbgi_key = d_dbgi_key_from_module(module); + D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); + String8 dbgi_path = dbg_path->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); U64 thread_ip_voff = d_voff_from_vaddr(module, thread_ip_vaddr); - D_LineList thread_ip_lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, thread_ip_voff); + D_LineList thread_ip_lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, thread_ip_voff); //- rjf: gather all relevant code vaddr ranges Rng1U64List code_vaddr_ranges = {0}; @@ -13644,8 +13645,10 @@ rd_frame(void) D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); D_Entity *module = d_module_from_process_vaddr(process, vaddr); U64 voff = d_voff_from_vaddr(module, vaddr); - DI_Key dbgi_key = d_dbgi_key_from_module(module); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, voff); + D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); + String8 dbgi_path = dbg_path->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, voff); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " lines:\n {\n"); for EachNode(n, D_LineNode, lines.first) { @@ -13732,8 +13735,10 @@ rd_frame(void) D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); D_Entity *module = d_module_from_process_vaddr(process, vaddr); U64 voff = d_voff_from_vaddr(module, vaddr); - DI_Key dbgi_key = d_dbgi_key_from_module(module); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, voff); + D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); + String8 dbgi_path = dbg_path->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, voff); str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "lines:\n{\n"); for EachNode(n, D_LineNode, lines.first) { @@ -15399,10 +15404,12 @@ rd_frame(void) U64 rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, unwind_index); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); D_Entity *module = d_module_from_process_vaddr(process, rip_vaddr); - DI_Key dbgi_key = d_dbgi_key_from_module(module); + D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); + String8 dbgi_path = dbg_path->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); U64 rip_voff = d_voff_from_vaddr(module, rip_vaddr); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, rip_voff); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, rip_voff); D_Line line = {0}; { U64 idx = 0; @@ -15482,6 +15489,7 @@ rd_frame(void) // rjf: try to resolve name as a symbol U64 voff = 0; DI_Key voff_dbgi_key = {0}; + String8 voff_dbgi_path = {0}; if(!name_resolved) { DI_Match match = {0}; @@ -15502,6 +15510,14 @@ rd_frame(void) RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, match.idx); voff = rdi_first_voff_from_procedure(rdi, procedure); voff_dbgi_key = match.key; + for EachIndex(idx, e_base_ctx->dbg_infos_count) + { + if(di_key_match(e_base_ctx->dbg_infos[idx].dbgi_key, voff_dbgi_key)) + { + voff_dbgi_path = e_base_ctx->dbg_infos[idx].name; + break; + } + } } access_close(access); } @@ -15562,7 +15578,7 @@ rd_frame(void) // rjf: name resolved to voff * dbg info if(name_resolved && voff != 0) { - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, voff_dbgi_key, voff); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, voff_dbgi_key, voff_dbgi_path, voff); if(lines.first != 0) { D_Entity *process = &d_entity_nil; @@ -15660,14 +15676,6 @@ rd_frame(void) } } - //- rjf: absolutify file path - { - String8 base_folder = {0}; - if(base_folder.size == 0) { base_folder = str8_chop_last_slash(e_base_ctx->primary_dbg_info->name); } - if(base_folder.size == 0) { base_folder = str8_chop_last_slash(e_base_ctx->primary_module->name); } - file_path = path_absolute_dst_from_relative_dst_src(scratch.arena, file_path, base_folder); - } - //- rjf: if transient tabs are turned off, always prefer new tab if(!rd_setting_b32_from_name(str8_lit("transient_tabs"))) { diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 11c8a90c..5d788220 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -563,9 +563,11 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla U64 rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, unwind_count); U64 last_inst_on_unwound_rip_vaddr = rip_vaddr - !!unwind_count; D_Entity *module = d_module_from_process_vaddr(process, last_inst_on_unwound_rip_vaddr); + D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); U64 rip_voff = d_voff_from_vaddr(module, last_inst_on_unwound_rip_vaddr); - DI_Key dbgi_key = d_dbgi_key_from_module(module); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, rip_voff); + String8 dbgi_path = dbg_path->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, rip_voff); String8 file_checksums[RDI_ChecksumKind_COUNT] = {0}; for(D_LineNode *n = lines.first; n != 0; n = n->next) { @@ -715,14 +717,16 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla if(dasm_lines) { D_Entity *module = d_module_from_process_vaddr(process, dasm_vaddr_range.min); - DI_Key dbgi_key = d_dbgi_key_from_module(module); + D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); + String8 dbgi_path = dbg_path->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); for(S64 line_num = visible_line_num_range.min; line_num < visible_line_num_range.max; line_num += 1) { U64 vaddr = dasm_vaddr_range.min + dasm_line_array_code_off_from_idx(dasm_lines, line_num-1); U64 voff = d_voff_from_vaddr(module, vaddr); U64 slice_idx = line_num-visible_line_num_range.min; code_slice_params.line_vaddrs[slice_idx] = vaddr; - code_slice_params.line_infos[slice_idx] = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, voff); + code_slice_params.line_infos[slice_idx] = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, voff); } } @@ -2731,6 +2735,7 @@ RD_VIEW_UI_FUNCTION_DEF(disasm) D_Entity *space_entity = rd_ctrl_entity_from_eval_space(space); D_Entity *dasm_module = &d_entity_nil; DI_Key dbgi_key = {0}; + String8 dbgi_path = {0}; U64 base_vaddr = 0; switch(space_entity->kind) { @@ -2739,8 +2744,10 @@ RD_VIEW_UI_FUNCTION_DEF(disasm) { if(arch == Arch_Null) { arch = space_entity->arch; } dasm_module = d_module_from_process_vaddr(space_entity, range.min); - dbgi_key = d_dbgi_key_from_module(dasm_module); - base_vaddr = dasm_module->vaddr_range.min; + D_Entity *dbg_path = d_entity_child_from_kind(dasm_module, D_EntityKind_DebugInfoPath); + dbgi_path = dbg_path->string; + dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); + base_vaddr = dasm_module->vaddr_range.min; }break; } DASM_StyleFlags style_flags = 0; @@ -2835,7 +2842,7 @@ RD_VIEW_UI_FUNCTION_DEF(disasm) rd_regs()->vaddr = range.min+off; rd_regs()->vaddr_range = r1u64(range.min+off, range.min+off); rd_regs()->voff_range = d_voff_range_from_vaddr_range(dasm_module, rd_regs()->vaddr_range); - rd_regs()->lines = d_lines_from_dbgi_key_voff(rd_frame_arena(), dbgi_key, rd_regs()->voff_range.min); + rd_regs()->lines = d_lines_from_dbgi_key_path_voff(rd_frame_arena(), dbgi_key, dbgi_path, rd_regs()->voff_range.min); } ////////////////////////////// From 8a5979a1a6104ba3c23e2071effa48e0268694ef Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 19 Aug 2026 11:42:50 -0600 Subject: [PATCH 056/251] dbg_engine: instead of sending down relativized breakpoint paths, relativize when resolving breakpoints on ctrl thread per-module - this avoids preference for the selected module, which broke relative file breakpoints without a selected thread in the right module. --- src/dbg_engine/dbg_engine_ctrl.c | 35 ++++++++++++++++++++++++-------- src/dbg_engine/dbg_engine_user.c | 24 ++-------------------- src/dbg_engine/dbg_engine_user.h | 2 +- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index eb9f1fc1..96c168fc 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -2511,7 +2511,9 @@ d_ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, D_EvalScope *e D_Entity *module_entity = d_entity_from_handle(module); D_Entity *debug_info_path_entity = d_entity_child_from_kind(module_entity, D_EntityKind_DebugInfoPath); DMN_Handle process_dmn = d_dmn_from_handle(process); - DI_Key dbgi_key = d_dbgi_key_from_module(module_entity); + String8 dbgi_path = debug_info_path_entity->string; + String8 module_path = module_entity->string; + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(debug_info_path_entity); RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); U64 base_vaddr = module_entity->vaddr_range.min; for(D_BreakpointNode *n = user_bps->first; n != 0; n = n->next) @@ -2524,13 +2526,23 @@ d_ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, D_EvalScope *e // rjf: unpack & normalize TxtPt pt = bp->pt; String8 filename = bp->file_path; - String8 filename_normalized = push_str8_copy(scratch.arena, filename); + String8 filename_normalized = str8_copy(scratch.arena, filename); for(U64 idx = 0; idx < filename_normalized.size; idx += 1) { filename_normalized.str[idx] = lower_from_char(filename_normalized.str[idx]); filename_normalized.str[idx] = correct_slash_from_char(filename_normalized.str[idx]); } + // rjf: gather filename candidates - the path passed down by the frontend *may* + // exactly match what the debug info stores, but it may be absolute whereas our + // debug info stores relative paths. so we'll try both. + String8 filename_candidates[] = + { + filename_normalized, + path_relative_dst_from_absolute_dst_src(scratch.arena, filename_normalized, str8_chop_last_slash(dbgi_path)), + path_relative_dst_from_absolute_dst_src(scratch.arena, filename_normalized, str8_chop_last_slash(module_path)), + }; + // rjf: filename -> src_id U32 src_id = 0; { @@ -2539,14 +2551,21 @@ d_ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, D_EvalScope *e { RDI_ParsedNameMap map = {0}; rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, filename_normalized.str, filename_normalized.size); - if(node != 0) + for EachElement(idx, filename_candidates) { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, filename_candidates[idx].str, filename_candidates[idx].size); + if(node != 0) { - src_id = ids[0]; + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + src_id = ids[0]; + } + } + if(src_id != 0) + { + break; } } } diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index cc39701a..1d75177a 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -53,7 +53,7 @@ d_breakpoint_array_copy(Arena *arena, D_BreakpointArray *src) //~ rjf: Path Map Application internal String8List -d_possible_path_overrides_from_maps_path(Arena *arena, D_Entity *primary_module, D_PathMapArray *path_maps, String8 file_path) +d_possible_path_overrides_from_maps_path(Arena *arena, D_PathMapArray *path_maps, 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 @@ -73,20 +73,6 @@ d_possible_path_overrides_from_maps_path(Arena *arena, D_Entity *primary_module, PathStyle pth_style = PathStyle_Relative; String8List pth_parts = path_normalized_list_from_string(scratch.arena, file_path, &pth_style); - //- rjf: push possible overrides by relativizing according to module / debug info - { - String8 module_folder = str8_chop_last_slash(primary_module->string); - String8 path_relative_to_module = path_relative_dst_from_absolute_dst_src(arena, file_path, module_folder); - String8 debug_info_path = d_entity_child_from_kind(primary_module, D_EntityKind_DebugInfoPath)->string; - String8 debug_info_folder = str8_chop_last_slash(debug_info_path); - str8_list_push(arena, &result, path_relative_to_module); - if(!path_match_normalized(debug_info_folder, module_folder)) - { - String8 path_relative_to_debug_info = path_relative_dst_from_absolute_dst_src(arena, file_path, debug_info_folder); - str8_list_push(arena, &result, path_relative_to_debug_info); - } - } - //- rjf: push possible overrides based on path map rules { for(U64 idx = 0; idx < path_maps->count; idx += 1) @@ -2190,12 +2176,6 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P d_user_state->ctrl_last_run_param_state_hash = ctrl_param_state_hash; } - // rjf: unpack target thread/module - D_Entity *thread = d_entity_from_handle(params->thread); - D_Entity *process = d_process_from_entity(thread); - U64 thread_ip_vaddr = d_cached_ip_from_thread(thread->handle); - D_Entity *module = d_module_from_process_vaddr(process, thread_ip_vaddr); - // rjf: push & fill run message D_Msg *msg = d_msg_list_push(scratch.arena, &ctrl_msgs); { @@ -2223,7 +2203,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P // rjf: textual location -> add breakpoints for all possible override locations if(bp->file_path.size != 0 && bp->pt.line != 0) { - String8List overrides = d_possible_path_overrides_from_maps_path(scratch.arena, module, path_maps, bp->file_path); + String8List overrides = d_possible_path_overrides_from_maps_path(scratch.arena, path_maps, bp->file_path); for(String8Node *n = overrides.first; n != 0; n = n->next) { D_Breakpoint dst_bp = {0}; diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index 3eeb8598..ba467329 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -404,7 +404,7 @@ internal D_BreakpointArray d_breakpoint_array_copy(Arena *arena, D_BreakpointArr //////////////////////////////// //~ rjf: Path Map Application -internal String8List d_possible_path_overrides_from_maps_path(Arena *arena, D_Entity *primary_module, D_PathMapArray *path_maps, String8 file_path); +internal String8List d_possible_path_overrides_from_maps_path(Arena *arena, D_PathMapArray *path_maps, String8 file_path); //////////////////////////////// //~ rjf: Debug Info Extraction Type Pure Functions From 6619df49a7d62e4558ff6e9f07b983ba345ebb6b Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 19 Aug 2026 12:30:10 -0600 Subject: [PATCH 057/251] on implicit debug builds -> inform user of 'release' argument --- build.bat | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.bat b/build.bat index f636ea9c..c843111c 100644 --- a/build.bat +++ b/build.bat @@ -31,7 +31,7 @@ cd /D "%~dp0" :: --- Unpack Arguments ------------------------------------------------------- for %%a in (%*) do set "%%~a=1" if not "%msvc%"=="1" if not "%clang%"=="1" set msvc=1 -if not "%release%"=="1" set debug=1 +if not "%release%"=="1" set debug=1&& set debug_implicit=1 if "%debug%"=="1" set release=0 && echo [debug mode] if "%release%"=="1" set debug=0 && echo [release mode] if "%msvc%"=="1" set clang=0 && echo [msvc compile] @@ -187,3 +187,8 @@ if "%pgo_run%"=="1" ( ) goto restart ) + +:: --- Warn On Debug Builds (if debug not explicitly specified) --------------- +if "%debug_implicit%"=="1" ( + echo [INFO] Debug build complete. For a faster build, call this script with the `release` argument (this will take significantly longer than a debug build). +) From c36000895068056e61565a670e2ca2a2746136cf Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 19 Aug 2026 12:35:34 -0600 Subject: [PATCH 058/251] same for bash --- build.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.sh b/build.sh index 241e6f4a..3724a40d 100755 --- a/build.sh +++ b/build.sh @@ -77,3 +77,8 @@ then echo "[WARNING] no valid build target specified; must use build target names as arguments to this script, like \`./build.sh raddbg\` or \`./build.sh radlink\`." exit 1 fi + +# --- Warn On Debug Builds (if debug not explicitly specified) ---------------- +if [[ ! -v debug && ! -v release ]] then + echo "[INFO] Debug build complete. For a faster build, call this script with the \`release\` argument (this will take significantly longer than a debug build)." +fi From e8fb9725199049c7a9b6ac36e0f4592e8e4e5bc0 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 19 Aug 2026 15:20:44 -0700 Subject: [PATCH 059/251] fix bat --- build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.bat b/build.bat index c843111c..80b13109 100644 --- a/build.bat +++ b/build.bat @@ -190,5 +190,5 @@ if "%pgo_run%"=="1" ( :: --- Warn On Debug Builds (if debug not explicitly specified) --------------- if "%debug_implicit%"=="1" ( - echo [INFO] Debug build complete. For a faster build, call this script with the `release` argument (this will take significantly longer than a debug build). + echo [INFO] Debug build complete. For a faster build, call this script with the `release` argument; this will take significantly longer than a debug build. ) From 5e6fb63535a2b6ccf0e15da312664f00e8e0f3dc Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 21 Aug 2026 10:55:46 -0700 Subject: [PATCH 060/251] better stop snapping behavior - prefer unwinding to the first source location in a call stack, if the user is in source mode & has no disasm tabs --- build.bat | 3 +- project.4coder | 2 +- project.raddbg | 4 +- src/dbg_engine/dbg_engine_user.c | 2 + src/raddbg/generated/raddbg.meta.c | 6 +- src/raddbg/generated/raddbg.meta.h | 7 +- src/raddbg/raddbg.mdesk | 3 +- src/raddbg/raddbg_core.c | 132 +++++++++++++++++++++++------ 8 files changed, 123 insertions(+), 36 deletions(-) diff --git a/build.bat b/build.bat index 80b13109..e51a529e 100644 --- a/build.bat +++ b/build.bat @@ -31,7 +31,8 @@ cd /D "%~dp0" :: --- Unpack Arguments ------------------------------------------------------- for %%a in (%*) do set "%%~a=1" if not "%msvc%"=="1" if not "%clang%"=="1" set msvc=1 -if not "%release%"=="1" set debug=1&& set debug_implicit=1 +if not "%release%"=="1" if not "%debug%"=="1" set debug_implicit=1 +if not "%release%"=="1" set debug=1 if "%debug%"=="1" set release=0 && echo [debug mode] if "%release%"=="1" set debug=0 && echo [release mode] if "%msvc%"=="1" set clang=0 && echo [msvc compile] diff --git a/project.4coder b/project.4coder index c992f764..b653e386 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin && pushd build && pushd test && radbin --rdi a.out && radbin --dump a.rdi --out:a.dump && popd && popd", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/project.raddbg b/project.raddbg index 2b803237..eb7764a5 100755 --- a/project.raddbg +++ b/project.raddbg @@ -35,6 +35,6 @@ target: target: { executable: "build/radbin.exe" - working_directory: "build/" - arguments: "--rdi test/a.out" + working_directory: build + arguments: "--rdi raddbg" } diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index 1d75177a..1f77117f 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -1143,6 +1143,7 @@ d_lines_array_from_dbgi_key_file_path_line_range(Arena *arena, DI_Key dbgi_key, // rjf: good src-id -> look up line info for visible range if(good_src_id) ProfScope("good src-id -> look up line info for visible range") { + String8 match_file_path = str8_copy(arena, file_path); 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}; @@ -1169,6 +1170,7 @@ d_lines_array_from_dbgi_key_file_path_line_range(Arena *arena, DI_Key dbgi_key, 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.file_path = match_file_path; n->v.voff_range = range; n->v.pt.line = (S64)actual_line; n->v.pt.column = 1; diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index fa039843..63b19235 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -481,7 +481,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("thread"), 0, str8_lit_comp("x:{'label':code_string, 'id':u64, @no_expand 'active':bool, 'call_stack':set}")}, }; -String8 rd_reg_slot_code_name_table[54] = +String8 rd_reg_slot_code_name_table[55] = { {0}, str8_lit_comp("machine"), @@ -533,13 +533,14 @@ str8_lit_comp("prefer_new_tab"), str8_lit_comp("activate_with_single_click"), str8_lit_comp("disable_addresses"), str8_lit_comp("small_size"), +str8_lit_comp("auto_unwind"), str8_lit_comp("dir2"), str8_lit_comp("string"), str8_lit_comp("cmd_name"), str8_lit_comp("wm_event"), }; -Rng1U64 rd_reg_slot_range_table[54] = +Rng1U64 rd_reg_slot_range_table[55] = { {0}, {OffsetOf(RD_Regs, machine), OffsetOf(RD_Regs, machine) + sizeof(D_Handle)}, @@ -591,6 +592,7 @@ Rng1U64 rd_reg_slot_range_table[54] = {OffsetOf(RD_Regs, activate_with_single_click), OffsetOf(RD_Regs, activate_with_single_click) + sizeof(B32)}, {OffsetOf(RD_Regs, disable_addresses), OffsetOf(RD_Regs, disable_addresses) + sizeof(B32)}, {OffsetOf(RD_Regs, small_size), OffsetOf(RD_Regs, small_size) + sizeof(B32)}, +{OffsetOf(RD_Regs, auto_unwind), OffsetOf(RD_Regs, auto_unwind) + sizeof(B32)}, {OffsetOf(RD_Regs, dir2), OffsetOf(RD_Regs, dir2) + sizeof(Dir2)}, {OffsetOf(RD_Regs, string), OffsetOf(RD_Regs, string) + sizeof(String8)}, {OffsetOf(RD_Regs, cmd_name), OffsetOf(RD_Regs, cmd_name) + sizeof(String8)}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 1b22d152..5208386c 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -58,6 +58,7 @@ RD_RegSlot_PreferNewTab, RD_RegSlot_ActivateWithSingleClick, RD_RegSlot_DisableAddresses, RD_RegSlot_SmallSize, +RD_RegSlot_AutoUnwind, RD_RegSlot_Dir2, RD_RegSlot_String, RD_RegSlot_CmdName, @@ -515,6 +516,7 @@ B32 prefer_new_tab; B32 activate_with_single_click; B32 disable_addresses; B32 small_size; +B32 auto_unwind; Dir2 dir2; String8 string; String8 cmd_name; @@ -618,6 +620,7 @@ Z(getting_started)\ .activate_with_single_click = rd_regs()->activate_with_single_click,\ .disable_addresses = rd_regs()->disable_addresses,\ .small_size = rd_regs()->small_size,\ +.auto_unwind = rd_regs()->auto_unwind,\ .dir2 = rd_regs()->dir2,\ .string = rd_regs()->string,\ .cmd_name = rd_regs()->cmd_name,\ @@ -628,8 +631,8 @@ extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; extern RD_VocabInfo rd_vocab_info_table[372]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; -extern String8 rd_reg_slot_code_name_table[54]; -extern Rng1U64 rd_reg_slot_range_table[54]; +extern String8 rd_reg_slot_code_name_table[55]; +extern Rng1U64 rd_reg_slot_range_table[55]; extern String8 rd_binding_version_remap_old_name_table[9]; extern String8 rd_binding_version_remap_new_name_table[9]; extern String8 rd_icon_kind_text_table[75]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 4c3b98e9..c0c46440 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -325,7 +325,7 @@ RD_VocabTable: //- rjf: update check @default(1) @display_name('Check For Updates') @description("Enables a small network request to check if there is a newer release version available.") 'check_for_updates': bool, - + //- rjf: system installation @display_name('Install To System') @description("Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.") 'install_to_system': bool, } @@ -912,6 +912,7 @@ RD_RegTable: {B32 activate_with_single_click ActivateWithSingleClick } {B32 disable_addresses DisableAddresses } {B32 small_size SmallSize } + {B32 auto_unwind AutoUnwind } {Dir2 dir2 Dir2 } {String8 string String } {String8 cmd_name CmdName } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 225fe2d9..87a55bd1 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -15121,6 +15121,10 @@ rd_frame(void) break; } } + if(vaddr == 0) + { + vaddr = d_query_cached_rip_from_thread_unwind(thread, rd_regs()->unwind_count); + } rd_cmd(RD_CmdKind_FindCodeLocation, .process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process)->handle, .vaddr = vaddr, .prefer_disasm = 1); }break; case RD_CmdKind_GoToSource: @@ -15399,10 +15403,73 @@ rd_frame(void) //- rjf: unpack thread info D_Entity *thread = d_entity_from_handle(rd_regs()->thread); + D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); U64 unwind_index = rd_regs()->unwind_count; U64 inline_depth = rd_regs()->inline_depth; U64 rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, unwind_index); - D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); + + //- rjf: determine if any disassembly is open + B32 any_disasm_is_open = 0; + if(rd_regs()->auto_unwind) + { + for(RD_WindowState *ws = rd_state->first_window_state; ws != &rd_nil_window_state; ws = ws->order_next) + { + Temp scratch = scratch_begin(0, 0); + CFG_Node *window = cfg_node_from_id(ws->cfg_id); + CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, window); + for(CFG_PanelNode *p = panel_tree.root; p != &cfg_nil_panel_node; p = cfg_panel_node_rec__depth_first_pre(panel_tree.root, p).next) + { + for EachNode(n, CFG_NodePtrNode, p->tabs.first) + { + CFG_Node *tab = n->v; + if(str8_match(tab->string, s("disasm"), 0)) + { + any_disasm_is_open = 1; + goto break_find_disasm; + } + } + } + break_find_disasm:; + scratch_end(scratch); + } + } + + //- rjf: if we have no disassembly open -> try to unwind to the first point where we have source code. + B32 waiting_on_unwind = 0; + B32 need_unwind_selection = 0; + if(!any_disasm_is_open && rd_regs()->auto_unwind) + { + D_Unwind unwind = d_unwind_from_thread(scratch.arena, thread->handle, rd_state->frame_eval_memread_endt_us); + if(unwind.flags & D_UnwindFlag_Stale) + { + waiting_on_unwind = 1; + } + else + { + ARCH_Info *arch_info = arch_info_from_arch(thread->arch); + for(U64 frame_idx = 0; frame_idx < unwind.frames.count; frame_idx += 1) + { + void *regs = unwind.frames.v[frame_idx].regs; + U64 ip_vaddr = arch_ip_from_reg_block(arch_info, regs); + D_Entity *module = d_module_from_process_vaddr(process, ip_vaddr); + U64 ip_voff = d_voff_from_vaddr(module, ip_vaddr); + D_Entity *debug_info_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(debug_info_path); + String8 dbgi_path = debug_info_path->string; + D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, ip_voff); + if(lines.count != 0) + { + rip_vaddr = ip_vaddr; + unwind_index = frame_idx; + inline_depth = 0; + need_unwind_selection = 1; + break; + } + } + } + } + + //- rjf: unpack selected unwind address D_Entity *module = d_module_from_process_vaddr(process, rip_vaddr); D_Entity *dbg_path = d_entity_child_from_kind(module, D_EntityKind_DebugInfoPath); String8 dbgi_path = dbg_path->string; @@ -15429,35 +15496,45 @@ rd_frame(void) B32 has_module = (module != &d_entity_nil); B32 has_dbg_info = has_module && !dbgi_missing; - //- rjf: find-code-location on each affected window - if(!dbgi_pending && (has_line_info || has_module)) + //- rjf: auto unwind + if(need_unwind_selection) { - rd_cmd(RD_CmdKind_FindCodeLocation, - .file_path = line.file_path, - .line_num = (U64)line.pt.line, - .column_num = (U64)line.pt.column, - .process = process->handle, - .voff = rip_voff, - .vaddr = rip_vaddr, - .unwind_count = unwind_index, - .inline_depth = inline_depth, - .all_windows = 1); + rd_state->base_regs.v.unwind_count = unwind_index; + rd_state->base_regs.v.inline_depth = 0; } - if(!missing_rip && !dbgi_pending && !has_line_info && !has_module) + + //- rjf: find-code-location on each affected window + if(!waiting_on_unwind) { - rd_cmd(RD_CmdKind_FindCodeLocation, - .file_path = str8_zero(), - .process = process->handle, - .module = module->handle, - .voff = rip_voff, - .vaddr = rip_vaddr, - .unwind_count = unwind_index, - .inline_depth = inline_depth, - .all_windows = 1); + if(!dbgi_pending && (has_line_info || has_module)) + { + rd_cmd(RD_CmdKind_FindCodeLocation, + .file_path = line.file_path, + .line_num = (U64)line.pt.line, + .column_num = (U64)line.pt.column, + .process = process->handle, + .voff = rip_voff, + .vaddr = rip_vaddr, + .unwind_count = unwind_index, + .inline_depth = inline_depth, + .all_windows = 1); + } + if(!missing_rip && !dbgi_pending && !has_line_info && !has_module) + { + rd_cmd(RD_CmdKind_FindCodeLocation, + .file_path = str8_zero(), + .process = process->handle, + .module = module->handle, + .voff = rip_voff, + .vaddr = rip_vaddr, + .unwind_count = unwind_index, + .inline_depth = inline_depth, + .all_windows = 1); + } } // rjf: retry on stopped, pending debug info - if(!d_ctrl_targets_running() && (dbgi_pending || missing_rip)) + if(!d_ctrl_targets_running() && (dbgi_pending || missing_rip || waiting_on_unwind)) { find_thread_retry = thread->handle; } @@ -18054,18 +18131,19 @@ rd_frame(void) D_Entity *module = d_module_from_process_vaddr(process, vaddr); U64 voff = d_voff_from_vaddr(module, vaddr); U64 test_cached_vaddr = d_cached_ip_from_thread(thread->handle); + B32 auto_unwind = (d_ctrl_last_run_kind() == D_RunKind_Run); // rjf: valid stop thread? -> select & snap if(need_refocus && thread->kind == D_EntityKind_Thread && evt->cause != D_EventCause_InterruptedByHalt) { - rd_cmd(RD_CmdKind_SelectThread, .thread = thread->handle); + rd_cmd(RD_CmdKind_SelectThread, .thread = thread->handle, .auto_unwind = auto_unwind); } // rjf: no stop-causing thread, but have selected thread? -> snap to selected D_Entity *selected_thread = d_entity_from_handle(rd_base_regs()->thread); if(need_refocus && (evt->cause == D_EventCause_InterruptedByHalt || thread->kind != D_EntityKind_Thread) && selected_thread != &d_entity_nil) { - rd_cmd(RD_CmdKind_SelectThread, .thread = selected_thread->handle); + rd_cmd(RD_CmdKind_SelectThread, .thread = selected_thread->handle, .auto_unwind = auto_unwind); } // rjf: no stop-causing thread, but don't have selected thread? -> snap to first available thread @@ -18073,7 +18151,7 @@ rd_frame(void) { D_EntityArray threads = d_entity_array_from_kind(D_EntityKind_Thread); D_Entity *first_available_thread = d_entity_array_first(&threads); - rd_cmd(RD_CmdKind_SelectThread, .thread = first_available_thread->handle); + rd_cmd(RD_CmdKind_SelectThread, .thread = first_available_thread->handle, .auto_unwind = auto_unwind); } // rjf: increment breakpoint hit counts From 4eae2a2ecca3eead3c091808b5005ad640faa192 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 18 Aug 2026 15:29:42 -0700 Subject: [PATCH 061/251] clang fixes --- src/linker/lnk.c | 3 ++- src/linker/lnk_config.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 1cf6a84d..e29f7947 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2279,7 +2279,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer String8List list = {0}; for EachIndex(i, entry_points.count) { str8_list_push(scratch.arena, &list, entry_points.v[i]); } - String8 default_entries = str8_list_join(scratch.arena, &list, &(StringJoin){.sep = ", "}); + String8 default_entries = str8_list_join(scratch.arena, &list, &(StringJoin){.sep = str8_lit(", ")}); lnk_error(LNK_Error_EntryPoint, "failed to infer entry point symbol from the inputs\n" @@ -3615,6 +3615,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) #define X(ID) case LNK_ICF_Scope_##ID: scope_str = str8_lit(Stringify(ID)); break; LNK_ICF_Scope_XList #undef X + default: InvalidPath; } lnk_log(LNK_Log_Debug, " %-9S: removed %M, %.*s sections; live %M, %.*s sections", diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 6c28deeb..2e78384f 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -2900,7 +2900,7 @@ lnk_config_init(U64 argc, char **argv) { LNK_EnvVar *link; - if (link = lnk_env_var_from_mapf(&env_vars, "LINK")) { + if ((link = lnk_env_var_from_mapf(&env_vars, "LINK"))) { String8List args = lnk_arg_list_parse_windows_rules(scratch.arena, link->raw_value); str8_list_concat_in_place(&user_args, &args); } @@ -2909,7 +2909,7 @@ lnk_config_init(U64 argc, char **argv) str8_list_push(scratch.arena, &user_args, str8_cstring(argv[i])); } - if (link = lnk_env_var_from_mapf(&env_vars, "_LINK_")) { + if ((link = lnk_env_var_from_mapf(&env_vars, "_LINK_"))) { String8List args = lnk_arg_list_parse_windows_rules(scratch.arena, link->raw_value); str8_list_concat_in_place(&user_args, &args); } From 2abbae4d90ef27d2d8e4cd1f866fb8df7788abe7 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 18 Aug 2026 16:36:06 -0700 Subject: [PATCH 062/251] improve unresolved symbols diagnostic * show referenced by function * show inline site that references unresolved symbol instead of the top function --- src/codeview/codeview.c | 1 - src/codeview/codeview.h | 2 +- src/coff/coff.c | 2 +- src/linker/codeview_ext/codeview.c | 54 +-- src/linker/codeview_ext/codeview.h | 1 + src/linker/hash_table.c | 7 + src/linker/hash_table.h | 1 + src/linker/lnk.c | 158 +++++---- src/linker/lnk_obj.c | 539 ++++++++++++++++++++++++++++- src/linker/lnk_obj.h | 81 ++++- 10 files changed, 724 insertions(+), 122 deletions(-) diff --git a/src/codeview/codeview.c b/src/codeview/codeview.c index 2923f941..39ccd1a4 100644 --- a/src/codeview/codeview.c +++ b/src/codeview/codeview.c @@ -131,7 +131,6 @@ cv_map_encoded_base_pointer(CV_Arch arch, U32 encoded_frame_reg) return r; } - //////////////////////////////// //~ rjf: Enum -> String diff --git a/src/codeview/codeview.h b/src/codeview/codeview.h index 7e4672e4..196e1996 100644 --- a/src/codeview/codeview.h +++ b/src/codeview/codeview.h @@ -1161,7 +1161,7 @@ struct CV_SymPub32 //- (SymKind: LPROC32, GPROC32) -#define CV_IsProc32(x) (x == CV_SymKind_LPROC32_ID || x == CV_SymKind_GPROC32_ID || x == CV_SymKind_LPROC32_DPC) +#define CV_IsProc32(x) ((x) == CV_SymKind_GPROC32 || (x) == CV_SymKind_LPROC32 || (x) == CV_SymKind_GPROC32_ID || (x) == CV_SymKind_LPROC32_ID || (x) == CV_SymKind_LPROC32_DPC || (x) == CV_SymKind_LPROC32_DPC_ID) typedef struct CV_SymProc32 CV_SymProc32; struct CV_SymProc32 diff --git a/src/coff/coff.c b/src/coff/coff.c index 7541f1a2..437b39de 100644 --- a/src/coff/coff.c +++ b/src/coff/coff.c @@ -232,7 +232,7 @@ coff_pick_reloc_value_x64(COFF_Reloc_X64 type, reloc_value = safe_cast_s32(symbol_virtual_offset - reloc_virtual_offset - (4 + 5)); } break; case COFF_Reloc_X64_Section: { - reloc_value_size = 4; + reloc_value_size = 2; reloc_value = symbol_section_number; } break; case COFF_Reloc_X64_SecRel: { diff --git a/src/linker/codeview_ext/codeview.c b/src/linker/codeview_ext/codeview.c index 4c55c73d..11592f7a 100644 --- a/src/linker/codeview_ext/codeview.c +++ b/src/linker/codeview_ext/codeview.c @@ -1452,6 +1452,7 @@ cv_c13_lines_from_sub_sections(Arena *arena, String8 c13_data, Rng1U64 ss_range) String8 sub_sect_data = str8_substr(c13_data, ss_range); for (U64 cursor = 0; cursor + sizeof(CV_C13SubSecLinesHeader) <= sub_sect_data.size; ) { + U64 header_off = cursor; CV_C13SubSecLinesHeader *hdr = (CV_C13SubSecLinesHeader *)(sub_sect_data.str + cursor); cursor += sizeof(*hdr); @@ -1486,6 +1487,7 @@ cv_c13_lines_from_sub_sections(Arena *arena, String8 c13_data, Rng1U64 ss_range) lines_parsed_node->next = 0; CV_C13LinesHeader *lines_parsed = &lines_parsed_node->v; + lines_parsed->header_off = ss_range.min + header_off; lines_parsed->sec_idx = hdr->sec; lines_parsed->sec_off_lo = hdr->sec_off; lines_parsed->sec_off_hi = hdr->sec_off + hdr->len; @@ -1633,6 +1635,12 @@ cv_c13_voff_map_compar(const void *raw_a, const void *raw_b) return cmp; } +force_inline int +cv_c13_voff_map_is_before(void *raw_a, void *raw_b) +{ + return cv_c13_voff_map_compar(raw_a, raw_b) < 0; +} + internal CV_LinesAccel * cv_c13_make_lines_accel(Arena *arena, U64 lines_count, CV_LineArray *lines) { @@ -1676,56 +1684,22 @@ cv_c13_make_lines_accel(Arena *arena, U64 lines_count, CV_LineArray *lines) return accel; } -internal CV_LinesAccel * -cv_lines_accel_from_debug_s(Arena *arena, CV_DebugS debug_s) -{ - // parse $$LINES - U64 c13_lines_count = 0; - CV_LineArray *c13_lines = 0; - { - String8List raw_lines_list = cv_sub_section_from_debug_s(debug_s, CV_C13SubSectionKind_Lines); - - for (String8Node *raw_lines_node = raw_lines_list.first; raw_lines_node != 0; raw_lines_node = raw_lines_node->next) { - Temp temp = temp_begin(arena); - CV_C13LinesHeaderList parsed_list = cv_c13_lines_from_sub_sections(temp.arena, raw_lines_node->string, rng_1u64(0, raw_lines_node->string.size)); - c13_lines_count += parsed_list.count; - temp_end(temp); - } - - c13_lines = push_array_no_zero(arena, CV_LineArray, c13_lines_count); - - U64 c13_lines_idx = 0; - for (String8Node *raw_lines_node = raw_lines_list.first; raw_lines_node != 0; raw_lines_node = raw_lines_node->next) { - String8 raw_lines = raw_lines_node->string; - CV_C13LinesHeaderList parsed_list = cv_c13_lines_from_sub_sections(arena, raw_lines, rng_1u64(0, raw_lines.size)); - - for(CV_C13LinesHeaderNode *header_node = parsed_list.first; header_node != 0; header_node = header_node->next) { - c13_lines[c13_lines_idx++] = cv_c13_line_array_from_data(arena, raw_lines, 0, header_node->v); - } - } - } - - return cv_c13_make_lines_accel(arena, c13_lines_count, c13_lines); -} - internal U64 cv_nearest_line(CV_Line *arr, U64 count, U64 value) { if(count > 1 && arr[0].voff <= value && value < arr[count-1].voff) { U64 l = 0; - U64 r = count - 1; - for (; l <= r; ) { + U64 r = count; + for (; l < r; ) { U64 m = l + (r - l) / 2; - if (arr[m].voff == value) { - return m; - } else if (arr[m].voff < value) { + if (arr[m].voff <= value) { l = m + 1; } else { - r = m - 1; + r = m; } } - return l; + return l - 1; } else if (count == 1 && arr[0].voff == value) { @@ -1754,7 +1728,7 @@ cv_line_from_voff(CV_LinesAccel *accel, U64 voff, U64 *out_line_count) lines = accel->map + map_idx; - for(; map_idx < (accel->map_count-1); map_idx += 1) { + for(; map_idx < accel->map_count; map_idx += 1) { if(accel->map[map_idx].voff != near_voff) { break; } diff --git a/src/linker/codeview_ext/codeview.h b/src/linker/codeview_ext/codeview.h index 985f2dce..f3e5ae9f 100644 --- a/src/linker/codeview_ext/codeview.h +++ b/src/linker/codeview_ext/codeview.h @@ -97,6 +97,7 @@ typedef struct CV_File typedef struct CV_C13LinesHeader { + U64 header_off; U64 sec_idx; U64 sec_off_lo; U64 sec_off_hi; diff --git a/src/linker/hash_table.c b/src/linker/hash_table.c index 45591cbb..0fecf387 100644 --- a/src/linker/hash_table.c +++ b/src/linker/hash_table.c @@ -841,6 +841,13 @@ hash_map_search_u64_u64(HashMap *hm, U64 key) return n ? &n->v.value.value_u64 : 0; } +internal String8 * +hash_map_search_u64_string(HashMap *hm, U64 key) +{ + HashMapNode *n = hash_map_search(hm, hash_map_hasher(str8_struct(&key)), (HashMapKey){ .key_u64 = key }, hash_map_match_u64); + return n ? &n->v.value.value_string : 0; +} + internal void * hash_map_search_raw_raw(HashMap *hm, void *key) { diff --git a/src/linker/hash_table.h b/src/linker/hash_table.h index f9e89d67..a8645412 100644 --- a/src/linker/hash_table.h +++ b/src/linker/hash_table.h @@ -185,6 +185,7 @@ internal String8 * hash_map_search_path_string (HashMap *hm, String8 key); internal void * hash_map_search_path_raw (HashMap *hm, String8 key); internal void * hash_map_search_u64_raw (HashMap *hm, U64 key); internal U64 * hash_map_search_u64_u64 (HashMap *hm, U64 key); +internal String8 * hash_map_search_u64_string (HashMap *hm, U64 key); internal void * hash_map_search_raw_raw (HashMap *hm, void *key); internal U64 * hash_map_search_raw_u64 (HashMap *hm, void *key); diff --git a/src/linker/lnk.c b/src/linker/lnk.c index e29f7947..342edbb4 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2310,6 +2310,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer { ProfBegin("Report Unresolved Symbols"); + // collect unresolved symbols U64 unresolved_symbols_count = 0; LNK_Symbol **unresolved_symbols = 0; { @@ -2345,8 +2346,11 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer } if (unresolved_symbols_count) { - Temp debug_scratch = scratch_begin(&scratch.arena, 1); + Temp debug_scratch = temp_begin(scratch.arena); + LNK_ObjSymbolMap **func_symbol_maps = push_array(debug_scratch.arena, LNK_ObjSymbolMap *, link->objs.count); + LNK_ObjLineMap **line_maps = push_array(debug_scratch.arena, LNK_ObjLineMap *, link->objs.count); + HashMap message_hm = {0}; for EachIndex(i, unresolved_symbols_count) { LNK_Symbol *symbol = unresolved_symbols[i]; @@ -2355,82 +2359,112 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer break; } - String8List supp_info = {0}; - { - U64 refs_count = 0; - LNK_ObjSymbolRef **refs = lnk_ref_from_symbol_many(scratch.arena, symbol, &refs_count); - for EachIndex(ref_idx, refs_count) { - LNK_ObjSymbolRef *ref = refs[ref_idx]; - LNK_Obj *obj = ref->obj; - COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - String8 string_table = lnk_coff_string_table_from_obj(obj); + String8List ref_messages = {0}; + U64 refs_count = 0; + LNK_ObjSymbolRef **refs = lnk_ref_from_symbol_many(scratch.arena, symbol, &refs_count); + for EachIndex(ref_idx, refs_count) { + LNK_ObjSymbolRef *ref = refs[ref_idx]; + LNK_Obj *obj = ref->obj; + COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); + String8 string_table = lnk_coff_string_table_from_obj(obj); - Temp debug_temp = temp_begin(debug_scratch.arena); - CV_DebugS debug_s = {0}; - CV_LinesAccel *debug_lines = 0; - String8 debug_checksums = {0}; - String8 debug_strings = {0}; + if (func_symbol_maps[obj->input_idx] == 0) { + func_symbol_maps[obj->input_idx] = lnk_symbol_map_from_obj(debug_scratch.arena, obj); + } + if (config->map_lines_for_unresolved_symbols == LNK_SwitchState_Yes) { + if (line_maps[obj->input_idx] == 0) { + line_maps[obj->input_idx] = lnk_line_map_from_obj(debug_scratch.arena, obj); + } + } - for EachIndex(sect_idx, obj->header.section_count_no_null) { - if (obj->section_flags[sect_idx] & LNK_SECTION_FLAG_DEBUG) { continue; } + for EachIndex(sect_idx, obj->header.section_count_no_null) { + if (obj->section_flags[sect_idx] & LNK_SECTION_FLAG_DEBUG) { continue; } + if (obj->section_flags[sect_idx] & COFF_SectionFlag_LnkRemove) { continue; } - COFF_SectionHeader *section_header = §ion_table[sect_idx]; - String8 section_name = coff_name_from_section_header(string_table, section_header); - U64 section_number = sect_idx+1; - COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); - for EachIndex(reloc_idx, relocs.count) { - if (supp_info.node_count > config->unresolved_symbol_ref_limit) { - str8_list_pushf(scratch.arena, &supp_info, "too many unresolved symbol references reported, stopping now"); - temp_end(debug_temp); - goto next_undefined_symbol; - } - COFF_Reloc *reloc = &relocs.v[reloc_idx]; - if (reloc->isymbol == ref->symbol_idx) { - U64 line_matches_count = 0; - CV_Line *line_matches = 0; - if (config->map_lines_for_unresolved_symbols == LNK_SwitchState_Yes) { - if (debug_lines == 0) { - debug_s = lnk_debug_s_from_obj(debug_temp.arena, obj); - String8List raw_checksums = cv_sub_section_from_debug_s(debug_s, CV_C13SubSectionKind_FileChksms); - String8List raw_strings = cv_sub_section_from_debug_s(debug_s, CV_C13SubSectionKind_StringTable); - debug_lines = cv_lines_accel_from_debug_s(debug_temp.arena, debug_s); - debug_checksums = str8_list_first(&raw_checksums); - debug_strings = str8_list_first(&raw_strings); - } - line_matches_count = 0; - line_matches = cv_line_from_voff(debug_lines, reloc->apply_off, &line_matches_count); + COFF_SectionHeader *section_header = §ion_table[sect_idx]; + String8 section_name = coff_name_from_section_header(string_table, section_header); + U64 section_number = sect_idx+1; + COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); + + for EachIndex(reloc_idx, relocs.count) { + if (ref_messages.node_count > config->unresolved_symbol_ref_limit) { + str8_list_pushf(scratch.arena, &ref_messages, "too many unresolved symbol references reported, stopping now"); + goto next_undefined_symbol; + } + + COFF_Reloc *reloc = &relocs.v[reloc_idx]; + if (reloc->isymbol != ref->symbol_idx) { continue; } + + String8 ref_name = {0}; + String8 source_file = {0}; + CV_Line *source_line = 0; + if (config->map_lines_for_unresolved_symbols == LNK_SwitchState_Yes) { + LNK_ObjLineMap *line_map = line_maps[obj->input_idx]; + + // is reference inline site? + LNK_InlineSite *inline_site = lnk_inline_site_from_section_offset(line_map, safe_cast_u32(section_number), reloc->apply_off); + if (inline_site && inline_site->name.size) { + CV_Line *line_inline = lnk_line_from_inline_site(inline_site, reloc->apply_off); + if (line_inline) { + ref_name = inline_site->name; + source_line = line_inline; } + } - if (line_matches) { - for EachIndex(i, line_matches_count) { - CV_Line line = line_matches[i]; - CV_C13Checksum checksum = {0}; - String8 file_name = {0}; - str8_deserial_read_struct(debug_checksums, line.file_off, &checksum); - str8_deserial_read_cstr(debug_strings, checksum.name_off, &file_name); - str8_list_pushf(scratch.arena, &supp_info, "%S: %S:%u", lnk_loc_from_obj(debug_temp.arena, obj), file_name, line.line_num); - } - } else { - str8_list_pushf(scratch.arena, &supp_info, "%S: %S(%llx)+%x", lnk_loc_from_obj(debug_temp.arena, obj), section_name, section_number, reloc->apply_off); + // default to regular line table + if (source_line == 0) { + U64 line_matches_count = 0; + CV_Line *line_matches = lnk_lines_from_section_offset(line_map, safe_cast_u32(section_number), reloc->apply_off, &line_matches_count); + source_line = line_matches_count ? &line_matches[0] : 0; + } + + if (source_line) { + // read source file name + CV_C13Checksum checksum = {0}; + B32 is_invalid = source_line->line_num == 0 || + str8_deserial_read_struct(line_map->debug_checksums, source_line->file_off, &checksum) != sizeof(checksum) || + str8_deserial_read_cstr(line_map->debug_strings, checksum.name_off, &source_file) == 0; + + // no file name? -> invalidate source info + if (is_invalid) { + ref_name = str8_zero(); + source_file = str8_zero(); + source_line = 0; } } } + + // no inline site? -> lookup function in the COFF symbol table + if (ref_name.size == 0) { + U32 symbol_idx = lnk_symbol_from_section_offset(func_symbol_maps[obj->input_idx], safe_cast_u32(section_number), reloc->apply_off); + ref_name = symbol_idx != max_U32 ? lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx) : str8_lit("(N/A)"); + } + + // pick location format + String8 ref_location = source_line ? + push_str8f(scratch.arena, "%S(%u)", source_file, source_line->line_num) : + push_str8f(scratch.arena, "%S[%llx]+%x", section_name, section_number, reloc->apply_off); + + // push unique reference error + String8 message = push_str8f(scratch.arena, "%S: referenced by '%S' in %S", ref_location, ref_name, lnk_loc_from_obj(scratch.arena, obj)); + if (hash_map_search_string_u64(&message_hm, message) == 0) { + str8_list_push(scratch.arena, &ref_messages, message); + hash_map_push_string_u64(scratch.arena, &message_hm, message, 1); + } } - - temp_end(debug_temp); } - next_undefined_symbol:; } + next_undefined_symbol:; - lnk_error(LNK_Error_UnresolvedSymbol, "unresolved symbol %S", symbol->name); - lnk_supplement_error_list(supp_info); + lnk_error(LNK_Error_UnresolvedSymbol, "unresolved symbol '%S'", symbol->name); + lnk_supplement_error_list(ref_messages); } - scratch_end(debug_scratch); - } + if (!config->force) { + lnk_exit(LNK_Error_UnresolvedSymbol); + } - if (unresolved_symbols_count && !config->force) { - lnk_exit(LNK_Error_UnresolvedSymbol); + temp_end(debug_scratch); } ProfEnd(); diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index d86f5e05..b695e747 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -118,17 +118,16 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) str8_ends_with(sect_name, str8_lit("$fo_bdd$"), 0)) { section_flags[sect_idx] |= COFF_SectionFlag_LnkInfo; } - if (task->find_debug_t) { - if (str8_match(sect_name, str8_lit(".debug$T"), 0)) { - debug_t_sect_idx = sect_idx; - } else if (str8_match(sect_name, str8_lit(".debug$P"), 0)) { - debug_p_sect_idx = sect_idx; - } else if (str8_match(sect_name, str8_lit(".debug$H"), 0)) { - debug_h_sect_idx = sect_idx; - } + + if (str8_match(sect_name, str8_lit(".debug$T"), 0)) { + debug_t_sect_idx = sect_idx; + } else if (str8_match(sect_name, str8_lit(".debug$P"), 0)) { + debug_p_sect_idx = sect_idx; + } else if (str8_match(sect_name, str8_lit(".debug$H"), 0)) { + debug_h_sect_idx = sect_idx; } - if (task->find_llvm_addrsig && llvm_addrsig_sect_idx == max_U32 && - str8_match(sect_name, str8_lit(".llvm_addrsig"), 0)) { + + if (llvm_addrsig_sect_idx == max_U32 && str8_match(sect_name, str8_lit(".llvm_addrsig"), 0)) { llvm_addrsig_sect_idx = sect_idx; } @@ -394,8 +393,6 @@ lnk_obj_from_input_many(TP_Context *tp, TP_Arena *arena, LNK_Config *config, U64 .inputs = inputs, .objs = objs, .machine = config->machine, - .find_debug_t = lnk_do_debug_info(config), - .find_llvm_addrsig = config->opt_icf == LNK_SwitchState_Yes, }; tp_for_parallel(tp, arena, inputs_count, lnk_obj_initer, &task); } @@ -929,6 +926,74 @@ lnk_directive_info_from_raw_directives(Arena *arena, LNK_Obj *obj, String8List r return directive_info; } +force_inline int +lnk_section_offset_symbol_is_before(void *raw_a, void *raw_b) +{ + LNK_SectionOffsetSymbol *a = raw_a, *b = raw_b; + if (a->key == b->key) { + return a->symbol_idx < b->symbol_idx; + } + return a->key < b->key; +} + +internal LNK_ObjSymbolMap * +lnk_symbol_map_from_obj(Arena *arena, LNK_Obj *obj) +{ + // count functions + U64 count = 0; + COFF_ParsedSymbol symbol; + for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { + symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { + count += 1; + } + } + + LNK_ObjSymbolMap *map = push_array(arena, LNK_ObjSymbolMap, 1); + map->v = push_array_no_zero(arena, LNK_SectionOffsetSymbol, count); + + U64 map_idx = 0; + for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { + symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { + LNK_SectionOffsetSymbol *entry = &map->v[map->count++]; + entry->key = Compose64Bit(symbol.section_number, symbol.value); + entry->symbol_idx = safe_cast_u32(symbol_idx); + } + } + Assert(map->count == count); + + radsort(map->v, map->count, lnk_section_offset_symbol_is_before); + + return map; +} + +internal U32 +lnk_symbol_from_section_offset(LNK_ObjSymbolMap *map, U32 section_number, U32 offset) +{ + U64 key = Compose64Bit(section_number, offset); + + U64 min = 0; + U64 opl = map->count; + while (min < opl) { + U64 mid = min + (opl - min) / 2; + if (map->v[mid].key <= key) { + min = mid + 1; + } else { + opl = mid; + } + } + + U32 symbol_idx = max_U32; + if (min > 0) { + LNK_SectionOffsetSymbol *entry = &map->v[min - 1]; + if (entry->key >> 32 == section_number) { + symbol_idx = entry->symbol_idx; + } + } + return symbol_idx; +} + internal CV_DebugS lnk_debug_s_from_obj(Arena *arena, LNK_Obj *obj) { @@ -970,3 +1035,453 @@ lnk_debug_s_from_obj(Arena *arena, LNK_Obj *obj) scratch_end(scratch); return debug_s; } + +force_inline int +lnk_line_table_block_is_before(void *raw_a, void *raw_b) +{ + LNK_LineTableBlock *a = raw_a, *b = raw_b; + U64 key_a = Compose64Bit(a->header->sec_idx, a->header->sec_off_lo); + U64 key_b = Compose64Bit(b->header->sec_idx, b->header->sec_off_lo); + if (key_a == key_b) { return a->header->sec_off_hi < b->header->sec_off_hi; } + return key_a < key_b; +} + +force_inline int +lnk_inline_range_is_before(void *raw_a, void *raw_b) +{ + LNK_InlineRange *a = raw_a, *b = raw_b; + if (a->key_min != b->key_min) { return a->key_min < b->key_min; } + return a->site_idx < b->site_idx; +} + +internal LNK_ObjLineMap * +lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) +{ + Temp scratch = scratch_begin(&arena, 1); + + // + // build .debug$S section file offset -> relocation value + // +#define DebugRelocValueFromFileOffset(foff) hash_map_search_u64_u64(&debug_reloc_hm, foff) + HashMap debug_reloc_hm = {0}; + for EachIndex(sect_idx, obj->header.section_count_no_null) { + LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); + if (!str8_match(lnk_obj_section_name_from_sect_idx(obj, sect_idx), str8_lit(".debug$S"), 0)) { continue; } + + String8 section_data = str8_substr(obj->data, section.frange); + COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section.header); + for EachIndex(reloc_idx, relocs.count) { + COFF_Reloc *reloc = &relocs.v[reloc_idx]; + COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, reloc->isymbol); + if (coff_interp_from_parsed_symbol(symbol) != COFF_SymbolValueInterp_Regular) { continue; } + + U64 reloc_size = 0; + U64 reloc_value = 0; + if ((obj->header.machine == COFF_MachineType_X64 && reloc->type == COFF_Reloc_X64_Section) || + (obj->header.machine == COFF_MachineType_X86 && reloc->type == COFF_Reloc_X86_Section)) { + reloc_size = sizeof(U16); + reloc_value = symbol.section_number; + } else if ((obj->header.machine == COFF_MachineType_X64 && reloc->type == COFF_Reloc_X64_SecRel) || + (obj->header.machine == COFF_MachineType_X86 && reloc->type == COFF_Reloc_X86_SecRel)) { + reloc_size = sizeof(U32); + reloc_value = symbol.value; + } + + if (reloc_size > 0 && reloc->apply_off + reloc_size <= section_data.size) { + U64 addend = 0; + str8_deserial_read(section_data, reloc->apply_off, &addend, reloc_size, 1); + U64 foff = section.frange.min + reloc->apply_off; + U64 value = reloc_value + extend_sign64(addend, reloc_size); + U64 *existing = hash_map_search_u64_u64(&debug_reloc_hm, foff); + if (existing) { + *existing = value; + } else { + hash_map_push_u64_u64(scratch.arena, &debug_reloc_hm, foff, value); + } + } + } + } + + CV_DebugS debug_s = lnk_debug_s_from_obj(scratch.arena, obj); + String8List raw_lines_list = cv_sub_section_from_debug_s(debug_s, CV_C13SubSectionKind_Lines); + String8List raw_checksums = cv_sub_section_from_debug_s(debug_s, CV_C13SubSectionKind_FileChksms); + String8List raw_strings = cv_sub_section_from_debug_s(debug_s, CV_C13SubSectionKind_StringTable); + + // + // estimate requried line table block capacity + // + U64 line_table_blocks_cap = 0; + for EachNode(raw_lines_node, String8Node, raw_lines_list.first) { + Temp temp = temp_begin(scratch.arena); + CV_C13LinesHeaderList parsed_list = cv_c13_lines_from_sub_sections(temp.arena, raw_lines_node->string, rng_1u64(0, raw_lines_node->string.size)); + line_table_blocks_cap += parsed_list.count; + temp_end(temp); + } + + LNK_ObjLineMap *map = push_array(arena, LNK_ObjLineMap, 1); + map->arena = arena; + map->debug_checksums = str8_list_first(&raw_checksums); + map->debug_strings = str8_list_first(&raw_strings); + map->line_table_blocks = push_array(arena, LNK_LineTableBlock, line_table_blocks_cap); + map->tables = push_array(arena, LNK_LineTable, line_table_blocks_cap); + + for EachNode(raw_lines_node, String8Node, raw_lines_list.first) { + Temp temp = temp_begin(scratch.arena); + String8 raw_lines = raw_lines_node->string; + CV_C13LinesHeaderList parsed_list = cv_c13_lines_from_sub_sections(temp.arena, raw_lines, rng_1u64(0, raw_lines.size)); + for EachNode(header_node, CV_C13LinesHeaderNode, parsed_list.first) { + if (header_node->v.line_count == 0) { continue; } + LNK_LineTableBlock *block = &map->line_table_blocks[map->line_table_block_count++]; + block->raw_lines = raw_lines; + block->header = push_array(arena, CV_C13LinesHeader, 1); + *block->header = header_node->v; + + // resovle lines header + { + CV_C13LinesHeader *header = block->header; + U64 header_foff = (U64)(raw_lines.str - obj->data.str) + header->header_off; + + U64 *sec_off = DebugRelocValueFromFileOffset(header_foff + OffsetOf(CV_C13SubSecLinesHeader, sec_off)); + if (sec_off) { + U64 len = header->sec_off_hi - header->sec_off_lo; + header->sec_off_lo = *sec_off; + header->sec_off_hi = *sec_off + len; + } + + U64 *sec = DebugRelocValueFromFileOffset(header_foff + OffsetOf(CV_C13SubSecLinesHeader, sec)); + if (sec) { + header->sec_idx = *sec; + } + } + } + temp_end(temp); + } + Assert(map->line_table_block_count <= line_table_blocks_cap); + radsort(map->line_table_blocks, map->line_table_block_count, lnk_line_table_block_is_before); + + for (U64 block_idx = 0; block_idx < map->line_table_block_count;) { + LNK_LineTableBlock *block = &map->line_table_blocks[block_idx]; + U64 key_min = Compose64Bit(block->header->sec_idx, block->header->sec_off_lo); + U64 key_max = Compose64Bit(block->header->sec_idx, block->header->sec_off_hi); + + U64 block_opl = block_idx + 1; + for (; block_opl < map->line_table_block_count; block_opl += 1) { + LNK_LineTableBlock *next = &map->line_table_blocks[block_opl]; + if (next->header->sec_idx != block->header->sec_idx || + next->header->sec_off_lo != block->header->sec_off_lo || + next->header->sec_off_hi != block->header->sec_off_hi) { + break; + } + } + + LNK_LineTable *table = &map->tables[map->table_count++]; + table->key_min = key_min; + table->key_max = key_max; + table->prefix_max = map->table_count > 1 ? Max(map->tables[map->table_count - 2].prefix_max, key_max) : key_max; + table->block_first = block_idx; + table->block_count = block_opl - block_idx; + + block_idx = block_opl; + } + + // + // build inlinee id -> inlinee name + // + HashMap inlinee_hm = {0}; + if (obj->debug_t_sect_idx < obj->header.section_count_no_null) { + LNK_ObjSection debug_t_section = lnk_obj_section_from_sect_idx(obj, obj->debug_t_sect_idx); + String8 raw_debug_t = str8_substr(obj->data, debug_t_section.frange); + CV_Signature debug_t_sig = cv_signature_from_debug_s(raw_debug_t); + if (debug_t_sig == CV_Signature_C13) { + CV_DebugT debug_t = cv_debug_t_from_data(scratch.arena, str8_skip(raw_debug_t, sizeof(CV_Signature)), CV_LeafAlign); + + for EachIndex(leaf_idx, debug_t.count) { + CV_Leaf leaf = cv_debug_t_get_leaf(&debug_t, leaf_idx); + + String8 name = str8_zero(); + if (leaf.kind == CV_LeafKind_FUNC_ID) { + String8 data = str8_substr(leaf.data, r1u64(sizeof(CV_LeafFuncId), leaf.data.size)); + name = str8_cstring_capped(data.str, data.str + data.size); + } else if (leaf.kind == CV_LeafKind_MFUNC_ID) { + String8 data = str8_substr(leaf.data, r1u64(sizeof(CV_LeafMFuncId), leaf.data.size)); + name = str8_cstring_capped(data.str, data.str + data.size); + } + + if (name.size) { + U64 inlinee_itype = CV_MinComplexTypeIndex + leaf_idx; + hash_map_push_u64_string(scratch.arena, &inlinee_hm, inlinee_itype, name); + } + } + } + } + + // + // build inlinee lines accelerator + // + String8List raw_inlinee_lines = cv_sub_section_from_debug_s(debug_s, CV_C13SubSectionKind_InlineeLines); + CV_C13InlineeLinesParsedList inlinee_lines = cv_c13_inlinee_lines_from_sub_sections(scratch.arena, raw_inlinee_lines); + CV_InlineeLinesAccel *inlinee_lines_accel = cv_c13_make_inlinee_lines_accel(scratch.arena, inlinee_lines); + + // + // estimate required inline site capacity + // + String8List raw_symbols = cv_sub_section_from_debug_s(debug_s, CV_C13SubSectionKind_Symbols); + U64 inline_site_cap = 0; + for EachNode(raw_symbols_node, String8Node, raw_symbols.first) { + for (U64 cursor = 0; cursor < raw_symbols_node->string.size;) { + CV_Symbol symbol = {0}; + TryReadBreak(cv_read_symbol(raw_symbols_node->string, cursor, CV_SymbolAlign, &symbol), cursor); + inline_site_cap += symbol.kind == CV_SymKind_INLINESITE || symbol.kind == CV_SymKind_INLINESITE2; + } + } + + // + // extract inline site info from the .debug$S sections + // + typedef struct LNK_InlineRangeNode LNK_InlineRangeNode; + struct LNK_InlineRangeNode { + LNK_InlineRangeNode *next; + LNK_InlineRange v; + }; + LNK_InlineRangeNode *range_first = 0, *range_last = 0; + map->inline_sites = push_array(arena, LNK_InlineSite, inline_site_cap); + for EachNode(raw_symbols_node, String8Node, raw_symbols.first) { + U32 proc_section = 0; + U32 proc_offset = 0; + U64 inline_site_stack_count = 0; + U32 *inline_site_stack = push_array_no_zero(scratch.arena, U32, inline_site_cap); + for (U64 cursor = 0; cursor < raw_symbols_node->string.size;) { + CV_Symbol symbol = {0}; + TryReadBreak(cv_read_symbol(raw_symbols_node->string, cursor, CV_SymbolAlign, &symbol), cursor); + + if (CV_IsProc32(symbol.kind)) { + CV_SymProc32 *proc = str8_deserial_get_raw_ptr(symbol.data, 0, sizeof(*proc)); + if (proc) { + U64 proc_foff = IntFromPtr(symbol.data.str - obj->data.str); + U64 *off_reloc = DebugRelocValueFromFileOffset(proc_foff + OffsetOf(CV_SymProc32, off)); + U64 *sec_reloc = DebugRelocValueFromFileOffset(proc_foff + OffsetOf(CV_SymProc32, sec)); + proc_offset = off_reloc ? safe_cast_u32(*off_reloc) : proc->off; + proc_section = sec_reloc ? safe_cast_u32(*sec_reloc) : proc->sec; + inline_site_stack_count = 0; + } + } else if (symbol.kind == CV_SymKind_INLINESITE || symbol.kind == CV_SymKind_INLINESITE2) { + U64 header_size = symbol.kind == CV_SymKind_INLINESITE ? sizeof(CV_SymInlineSite) : sizeof(CV_SymInlineSite2); + if (proc_section && symbol.data.size >= header_size) { + CV_ItemId inlinee = symbol.kind == CV_SymKind_INLINESITE ? ((CV_SymInlineSite *)symbol.data.str)->inlinee : ((CV_SymInlineSite2 *)symbol.data.str)->inlinee; + String8 raw_annots = str8_skip(symbol.data, header_size); + String8 *inlinee_name = hash_map_search_u64_string(&inlinee_hm, inlinee); + U32 inline_site_idx = safe_cast_u32(map->inline_site_count++); + + LNK_InlineSite *inline_site = &map->inline_sites[inline_site_idx]; + inline_site->parent_idx_plus_one = inline_site_stack_count ? inline_site_stack[inline_site_stack_count - 1] + 1 : 0; + inline_site->depth = inline_site->parent_idx_plus_one ? map->inline_sites[inline_site->parent_idx_plus_one - 1].depth + 1 : 1; + inline_site->inlinee = inlinee; + inline_site->name = inlinee_name ? *inlinee_name : str8_zero(); + inline_site_stack[inline_site_stack_count++] = inline_site_idx; + + CV_C13InlineeLinesParsed *inlinee_info = cv_c13_inlinee_lines_accel_find(inlinee_lines_accel, inlinee); + if (inlinee_info) { + // + // decode inline site line table + // + typedef struct LNK_InlineLineNode LNK_InlineLineNode; + struct LNK_InlineLineNode { + LNK_InlineLineNode *next; + CV_Line v; + }; + LNK_InlineLineNode *line_first = 0; + LNK_InlineLineNode *line_last = 0; + LNK_InlineRangeNode *last_site_range = 0; + U32 current_file_off = inlinee_info->file_off; + CV_C13InlineSiteDecoder decoder = cv_c13_inline_site_decoder_init(inlinee_info->file_off, inlinee_info->first_source_ln, proc_offset); + for (;;) { + U64 old_cursor = decoder.cursor; + + CV_C13InlineSiteDecoderStep step = cv_c13_inline_site_decoder_step(&decoder, raw_annots); + if (step.flags == 0 || decoder.cursor <= old_cursor) { break; } + if (step.flags & CV_C13InlineSiteDecoderStepFlag_EmitFile) { + current_file_off = step.file_off; + } + + if (step.range.min < step.range.max) { + if ((step.flags & CV_C13InlineSiteDecoderStepFlag_EmitRange) || + ((step.flags & CV_C13InlineSiteDecoderStepFlag_ExtendLastRange) && last_site_range == 0)) { + LNK_InlineRangeNode *node = push_array(scratch.arena, LNK_InlineRangeNode, 1); + node->v.key_min = Compose64Bit(proc_section, step.range.min); + node->v.key_max = Compose64Bit(proc_section, step.range.max); + node->v.site_idx = inline_site_idx; + SLLQueuePush(range_first, range_last, node); + last_site_range = node; + map->inline_range_count += 1; + } + } + + if ((step.flags & CV_C13InlineSiteDecoderStepFlag_ExtendLastRange) && last_site_range) { + last_site_range->v.key_max = Compose64Bit(proc_section, step.range.max); + } + + if (step.flags & CV_C13InlineSiteDecoderStepFlag_EmitLine) { + LNK_InlineLineNode *node = push_array(scratch.arena, LNK_InlineLineNode, 1); + node->v.voff = step.line_voff; + node->v.file_off = current_file_off; + node->v.line_num = step.ln; + SLLQueuePush(line_first, line_last, node); + inline_site->line_count += 1; + } + } + + // line list -> sorted line array + inline_site->lines = push_array_no_zero(arena, CV_Line, inline_site->line_count); + U64 line_idx = 0; + for EachNode(line, LNK_InlineLineNode, line_first) { inline_site->lines[line_idx++] = line->v; } + radsort(inline_site->lines, inline_site->line_count, cv_c13_voff_map_is_before); + } + } + } else if (symbol.kind == CV_SymKind_INLINESITE_END) { + if (inline_site_stack_count > 0) { + inline_site_stack_count -= 1; + } + } else if (symbol.kind == CV_SymKind_END || symbol.kind == CV_SymKind_PROC_ID_END) { + proc_section = 0; + proc_offset = 0; + inline_site_stack_count = 0; + } + } + } + + // + // fill out & sort inline site ranges + // + { + map->inline_ranges = push_array_no_zero(arena, LNK_InlineRange, map->inline_range_count); + U64 range_idx = 0; + for EachNode(range, LNK_InlineRangeNode, range_first) { + map->inline_ranges[range_idx++] = range->v; + } + radsort(map->inline_ranges, map->inline_range_count, lnk_inline_range_is_before); + + for EachIndex(i, map->inline_range_count) { + map->inline_ranges[i].prefix_max = i ? + Max(map->inline_ranges[i - 1].prefix_max, map->inline_ranges[i].key_max) : + map->inline_ranges[i].key_max; + } + } + +#undef DebugRelocValueFromFileOff + scratch_end(scratch); + return map; +} + +internal CV_Line * +lnk_lines_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, U64 *line_count_out) +{ + U64 key = Compose64Bit(section_number, offset); + + // upper bound search + U64 min = 0; + U64 opl = map->table_count; + while (min < opl) { + U64 mid = min + (opl - min) / 2; + if (map->tables[mid].key_min <= key) { + min = mid + 1; + } else { + opl = mid; + } + } + + // backward search until containing interval is found + LNK_LineTable *table = 0; + while (min > 0) { + LNK_LineTable *candidate = &map->tables[--min]; + if (key < candidate->key_max) { + table = candidate; + break; + } + if (min == 0 || map->tables[min - 1].prefix_max <= key) { + break; + } + } + + CV_Line *lines = 0; + U64 line_count = 0; + if (table) { + if (table->accel == 0) { + Temp scratch = scratch_begin(&map->arena, 1); + + // unpack lines from line table blocks + CV_LineArray *line_arrays = push_array_no_zero(scratch.arena, CV_LineArray, table->block_count); + for EachIndex(i, table->block_count) { + LNK_LineTableBlock *block = &map->line_table_blocks[table->block_first + i]; + CV_C13LinesHeader header = *block->header; + header.sec_off_hi -= header.sec_off_lo; + header.sec_off_lo = 0; + line_arrays[i] = cv_c13_line_array_from_data(scratch.arena, block->raw_lines, 0, header); + } + + // cache line table accel + table->accel = cv_c13_make_lines_accel(map->arena, table->block_count, line_arrays); + + scratch_end(scratch); + } + + // match (section, offset) => lines + U32 table_offset = safe_cast_u32(key - table->key_min); + lines = cv_line_from_voff(table->accel, table_offset, &line_count); + } + + if (line_count_out) { + *line_count_out = line_count; + } + + return lines; +} + +internal LNK_InlineSite * +lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset) +{ + U64 key = Compose64Bit(section_number, offset); + + U64 min = 0; + U64 opl = map->inline_range_count; + while (min < opl) { + U64 mid = min + (opl - min) / 2; + if (map->inline_ranges[mid].key_min <= key) { + min = mid + 1; + } else { + opl = mid; + } + } + + LNK_InlineSite *site = 0; + while (min > 0) { + LNK_InlineRange *range = &map->inline_ranges[--min]; + if (key < range->key_max) { + LNK_InlineSite *candidate = &map->inline_sites[range->site_idx]; + if (site == 0 || site->depth < candidate->depth) { + site = candidate; + } + } + if (min == 0 || map->inline_ranges[min - 1].prefix_max <= key) { + break; + } + } + + return site; +} + +internal CV_Line * +lnk_line_from_inline_site(LNK_InlineSite *site, U32 offset) +{ + U64 min = 0; + U64 opl = site->line_count; + while (min < opl) { + U64 mid = min + (opl - min) / 2; + if (site->lines[mid].voff <= offset) { + min = mid + 1; + } else { + opl = mid; + } + } + return min > 0 ? &site->lines[min - 1] : 0; +} + diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 1a7d90b6..9b74e4a2 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -83,6 +83,69 @@ typedef struct LNK_ObjNodeArray LNK_ObjNode *v; } LNK_ObjNodeArray; +// --- Maps -------------------------------------------------------------- + +typedef struct LNK_SectionOffsetSymbol +{ + U64 key; + U32 symbol_idx; +} LNK_SectionOffsetSymbol; + +typedef struct LNK_ObjSymbolMap +{ + U64 count; + LNK_SectionOffsetSymbol *v; +} LNK_ObjSymbolMap; + +typedef struct LNK_LineTableBlock +{ + String8 raw_lines; + CV_C13LinesHeader *header; +} LNK_LineTableBlock; + +typedef struct LNK_LineTable +{ + U64 key_min; + U64 key_max; + U64 prefix_max; + U64 block_first; + U64 block_count; + CV_LinesAccel *accel; +} LNK_LineTable; + +typedef struct LNK_InlineSite +{ + U32 parent_idx_plus_one; + U32 depth; + CV_ItemId inlinee; + String8 name; + U64 line_count; + CV_Line *lines; +} LNK_InlineSite; + +typedef struct LNK_InlineRange +{ + U64 key_min; + U64 key_max; + U64 prefix_max; + U32 site_idx; +} LNK_InlineRange; + +typedef struct LNK_ObjLineMap +{ + Arena *arena; + String8 debug_checksums; + String8 debug_strings; + U64 line_table_block_count; + LNK_LineTableBlock *line_table_blocks; + U64 table_count; + LNK_LineTable *tables; + U64 inline_site_count; + LNK_InlineSite *inline_sites; + U64 inline_range_count; + LNK_InlineRange *inline_ranges; +} LNK_ObjLineMap; + // --- Directive Parser -------------------------------------------------------- typedef struct LNK_Directive @@ -112,8 +175,6 @@ typedef struct LNK_ObjNode *objs; U64 obj_id_base; U32 machine; - B32 find_debug_t; - B32 find_llvm_addrsig; } LNK_ObjIniter; typedef struct @@ -157,10 +218,11 @@ internal U32List lnk_obj_collect_associated_sections(Arena *arena, LNK_ // --- Symbol & Section Helpers ------------------------------------------------ -internal COFF_SectionHeader * lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number); internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx_no_name(LNK_Obj *obj, U64 symbol_idx); internal force_inline String8 lnk_symbol_name_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); + +internal COFF_SectionHeader * lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number); internal U64 lnk_obj_sect_idx_from_section_number(LNK_Obj *obj, U64 section_number); internal U64 lnk_obj_section_number_from_sect_idx(LNK_Obj *obj, U64 sect_idx); internal String8 lnk_obj_section_name_from_section_number(LNK_Obj *obj, U64 section_number); @@ -183,6 +245,15 @@ internal void lnk_parse_msvc_linker_directive(Arena *arena, LNK_Obj internal String8List lnk_raw_directives_from_obj(Arena *arena, LNK_Obj *obj); internal LNK_DirectiveInfo lnk_directive_info_from_raw_directives(Arena *arena, LNK_Obj *obj, String8List raw_directives); -// --- Debug Info -------------------------------------------------------------- +// --- Maps --------------------------------------------------------------------- + +// COFF map +internal LNK_ObjSymbolMap * lnk_symbol_map_from_obj(Arena *arena, LNK_Obj *obj); +internal U32 lnk_symbol_from_section_offset(LNK_ObjSymbolMap *map, U32 section_number, U32 offset); + +// CodeView map +internal LNK_ObjLineMap * lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj); +internal CV_Line * lnk_lines_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, U64 *line_count_out); +internal LNK_InlineSite * lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset); +internal CV_Line * lnk_line_from_inline_site(LNK_InlineSite *site, U32 offset); -internal CV_DebugS lnk_debug_s_from_obj(Arena *arena, LNK_Obj *obj); From 2767895b884b456157dcbc84b92b837d62b7e255 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 19 Aug 2026 20:41:47 -0700 Subject: [PATCH 063/251] turn off diagnostic for unresolved symbols in debug sections on /DEBUG:NONE --- src/linker/lnk.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 342edbb4..77d5c2e5 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2377,21 +2377,28 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer } } - for EachIndex(sect_idx, obj->header.section_count_no_null) { - if (obj->section_flags[sect_idx] & LNK_SECTION_FLAG_DEBUG) { continue; } - if (obj->section_flags[sect_idx] & COFF_SectionFlag_LnkRemove) { continue; } + COFF_SectionFlags sect_filter = 0; + sect_filter |= COFF_SectionFlag_LnkRemove; // skip sections that are discarded from the output + if ( ! lnk_do_debug_info(config)) { sect_filter |= LNK_SECTION_FLAG_DEBUG; } // on /DEBUG:NONE linker never invokes the debug info machinery + for EachIndex(sect_idx, obj->header.section_count_no_null) { + if (obj->section_flags[sect_idx] & sect_filter) { continue; } + + // unpack section and relocations COFF_SectionHeader *section_header = §ion_table[sect_idx]; String8 section_name = coff_name_from_section_header(string_table, section_header); U64 section_number = sect_idx+1; COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); + // scan for undefined target symbols in relocations for EachIndex(reloc_idx, relocs.count) { + // cap number of the diagnostic messages per symbol if (ref_messages.node_count > config->unresolved_symbol_ref_limit) { str8_list_pushf(scratch.arena, &ref_messages, "too many unresolved symbol references reported, stopping now"); goto next_undefined_symbol; } + // skip relocations that do not reference the unresolved symbol COFF_Reloc *reloc = &relocs.v[reloc_idx]; if (reloc->isymbol != ref->symbol_idx) { continue; } From 109180131a777c65f9142badad5d5ee585662ace Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 20 Aug 2026 13:30:52 -0700 Subject: [PATCH 064/251] bugfix forwarder export detection --- src/linker/lnk_config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 2e78384f..5729e2c7 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -687,7 +687,7 @@ lnk_parse_export_directive_ex(Arena *arena, String8List directive, LNK_Obj *obj, export_out->is_ordinal_assigned = ordinal.size > 0; export_out->is_noname_present = noname_flag.size > 0; export_out->is_private = private_flag.size > 0; - export_out->is_forwarder = str8_find_needle(name, 0, str8_lit("."), 0) < name.size; + export_out->is_forwarder = alias.size && str8_find_needle(name, 0, str8_lit("."), 0) < name.size; is_parsed = 1; From 24e321925af11c7fb21f20cef81243c44a225def Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 20 Aug 2026 13:38:33 -0700 Subject: [PATCH 065/251] update the change log --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69dd338..fe814893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ - Fixed DBI section-contribution image-section range mapping. - Improved debug logging for `/OPT:REF` liveness stats and unresolved symbols referenced by `.llvm_addrsig`. +- Improved diagnostics for unresolved symbols: radlink now identifies functions + that reference undefined symbols, even if the function was inlined (provided + the object file was compiled with debug info) +- Reduced memory usage for large links. +- Fixed forwarder export detection when the symbol name contains dots and has no alias. # v0.9.27-alpha From 8fa5ba5b6054c2c79777a41042a2e0a623074083 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 20 Aug 2026 15:40:41 -0700 Subject: [PATCH 066/251] shift type index base for PCH objs --- src/linker/lnk_obj.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index b695e747..e8c168bc 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -1195,6 +1195,18 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) if (debug_t_sig == CV_Signature_C13) { CV_DebugT debug_t = cv_debug_t_from_data(scratch.arena, str8_skip(raw_debug_t, sizeof(CV_Signature)), CV_LeafAlign); + CV_TypeIndex ti_base = CV_MinComplexTypeIndex; + if (cv_debug_t_is_pch(&debug_t)) { + CV_Leaf precomp_leaf = cv_debug_t_get_leaf(&debug_t, 0); + CV_PrecompInfo precomp = cv_precomp_info_from_leaf(precomp_leaf); + + // discard LF_PRECOMP + debug_t.offsets += 1; + debug_t.count -= 1; + + ti_base = precomp.start_index + precomp.leaf_count; + } + for EachIndex(leaf_idx, debug_t.count) { CV_Leaf leaf = cv_debug_t_get_leaf(&debug_t, leaf_idx); @@ -1208,7 +1220,7 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) } if (name.size) { - U64 inlinee_itype = CV_MinComplexTypeIndex + leaf_idx; + U64 inlinee_itype = ti_base + leaf_idx; hash_map_push_u64_string(scratch.arena, &inlinee_hm, inlinee_itype, name); } } From 8b52934c66a061e3195705fe346d4d558392d512 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 20 Aug 2026 16:32:52 -0700 Subject: [PATCH 067/251] set lower bound on CodeOffset --- src/codeview/codeview_parse.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/codeview/codeview_parse.c b/src/codeview/codeview_parse.c index 583666fb..3997d4a4 100644 --- a/src/codeview/codeview_parse.c +++ b/src/codeview/codeview_parse.c @@ -291,7 +291,9 @@ cv_c13_inline_site_decoder_step(CV_C13InlineSiteDecoder *decoder, String8 binary } break; case CV_InlineBinaryAnnotation_CodeOffset: { decoder->cursor += cv_decode_inline_annot_u32(binary_annots, decoder->cursor, &decoder->code_offset); - decoder->code_offset_changed = 1; + decoder->code_offset_lo = decoder->code_offset; + decoder->code_offset_lo_changed = 1; + decoder->code_offset_changed = 1; } break; case CV_InlineBinaryAnnotation_ChangeCodeOffsetBase: { AssertAlways(!"TODO: test case"); From 3c719de3f5cc1493ac9e008bdbdd68a2f756dda9 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 20 Aug 2026 17:44:29 -0700 Subject: [PATCH 068/251] use callee info to disambiguate overlapping sibling inline sites --- src/linker/lnk.c | 2 +- src/linker/lnk_obj.c | 52 +++++++++++++++++++++++++++++++++++++++++--- src/linker/lnk_obj.h | 16 ++++++++------ 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 77d5c2e5..dcec56e0 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2409,7 +2409,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer LNK_ObjLineMap *line_map = line_maps[obj->input_idx]; // is reference inline site? - LNK_InlineSite *inline_site = lnk_inline_site_from_section_offset(line_map, safe_cast_u32(section_number), reloc->apply_off); + LNK_InlineSite *inline_site = lnk_inline_site_from_section_offset(line_map, safe_cast_u32(section_number), reloc->apply_off, symbol); if (inline_site && inline_site->name.size) { CV_Line *line_inline = lnk_line_from_inline_site(inline_site, reloc->apply_off); if (line_inline) { diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index e8c168bc..20ee52f8 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -1349,6 +1349,20 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) radsort(inline_site->lines, inline_site->line_count, cv_c13_voff_map_is_before); } } + } else if (symbol.kind == CV_SymKind_CALLEES) { + if (inline_site_stack_count && symbol.data.size >= sizeof(CV_SymFunctionList)) { + CV_SymFunctionList *func_list = (CV_SymFunctionList *)symbol.data.str; + U64 callee_count = Min(func_list->count, (symbol.data.size - sizeof(*func_list)) / sizeof(CV_ItemId)); + CV_ItemId *callees = (CV_ItemId *)(func_list + 1); + + LNK_InlineSite *inline_site = &map->inline_sites[inline_site_stack[inline_site_stack_count - 1]]; + inline_site->callee_count = callee_count; + inline_site->callee_names = push_array(arena, String8, callee_count); + for EachIndex(callee_idx, callee_count) { + String8 *callee_name = hash_map_search_u64_string(&inlinee_hm, callees[callee_idx]); + inline_site->callee_names[callee_idx] = callee_name ? *callee_name : str8_zero(); + } + } } else if (symbol.kind == CV_SymKind_INLINESITE_END) { if (inline_site_stack_count > 0) { inline_site_stack_count -= 1; @@ -1449,10 +1463,11 @@ lnk_lines_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offse } internal LNK_InlineSite * -lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset) +lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, LNK_Symbol *callee_symbol) { U64 key = Compose64Bit(section_number, offset); + // find upper bound U64 min = 0; U64 opl = map->inline_range_count; while (min < opl) { @@ -1464,21 +1479,52 @@ lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 } } - LNK_InlineSite *site = 0; + // hacky demangle + String8 callee_name = callee_symbol ? callee_symbol->name : str8_zero(); + if (str8_starts_with(callee_name, str8_lit("__imp_"))) { + callee_name = str8_skip(callee_name, 6); + } + if (str8_starts_with(callee_name, str8_lit("?"))) { + U64 name_opl = str8_find_needle(callee_name, 1, str8_lit("@"), 0); + callee_name = str8_substr(callee_name, r1u64(1, name_opl)); + } + + // scan back to the lower bound and match inline sites + LNK_InlineSite *site = 0; + LNK_InlineSite *callee_site = 0; + B32 callee_site_is_ambiguous = 0; while (min > 0) { LNK_InlineRange *range = &map->inline_ranges[--min]; if (key < range->key_max) { LNK_InlineSite *candidate = &map->inline_sites[range->site_idx]; + if (site == 0 || site->depth < candidate->depth) { site = candidate; } + + for EachIndex(callee_idx, candidate->callee_count) { + String8 pdb_callee_name = candidate->callee_names[callee_idx]; + if (str8_match(callee_name, pdb_callee_name, 0)) { + + // candidate matches callee + if (callee_site == 0 || callee_site->depth < candidate->depth) { + callee_site = candidate; + callee_site_is_ambiguous = 0; + } else if (candidate->depth == callee_site->depth && candidate != callee_site) { + callee_site_is_ambiguous = 1; + } + + break; + } + } } + if (min == 0 || map->inline_ranges[min - 1].prefix_max <= key) { break; } } - return site; + return (callee_site && !callee_site_is_ambiguous) ? callee_site : site; } internal CV_Line * diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 9b74e4a2..d93ad522 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -115,12 +115,14 @@ typedef struct LNK_LineTable typedef struct LNK_InlineSite { - U32 parent_idx_plus_one; - U32 depth; - CV_ItemId inlinee; - String8 name; - U64 line_count; - CV_Line *lines; + U32 parent_idx_plus_one; + U32 depth; + CV_ItemId inlinee; + String8 name; + U64 callee_count; + String8 *callee_names; + U64 line_count; + CV_Line *lines; } LNK_InlineSite; typedef struct LNK_InlineRange @@ -254,6 +256,6 @@ internal U32 lnk_symbol_from_section_offset(LNK_ObjSymbolMap *map // CodeView map internal LNK_ObjLineMap * lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj); internal CV_Line * lnk_lines_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, U64 *line_count_out); -internal LNK_InlineSite * lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset); +internal LNK_InlineSite * lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, LNK_Symbol *callee_symbol); internal CV_Line * lnk_line_from_inline_site(LNK_InlineSite *site, U32 offset); From 03463abebc6dbda8e63a872230fa3bbbe0ea3d11 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 21 Aug 2026 11:05:53 -0700 Subject: [PATCH 069/251] fix incorrect bad-project-file checkin --- src/raddbg/raddbg_core.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 87a55bd1..2aeec9df 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -13920,14 +13920,7 @@ rd_frame(void) FileProperties file_props = properties_from_file_path(file_path); //- rjf: determine if the file is good - B32 file_is_okay = 0; - { - String8 stored_path = (kind == RD_CmdKind_OpenUser ? rd_state->user_path : rd_state->project_path); - file_is_okay = (((file_props.size == 0 && file_props.created == 0) || - str8_match(file_path, stored_path, 0) || - stored_path.size == 0) && - (file_data.size == 0 || str8_match(str8_prefix(file_data, 9), str8_lit("// raddbg"), 0))); - } + B32 file_is_okay = (file_data.size == 0 || str8_match(str8_prefix(file_data, 9), s("// raddbg"), 0)); //- rjf: determine file's version String8 file_version = {0}; From d75ea2d55ecd89f6e95030e996f292d655fe0e71 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 21 Aug 2026 11:35:41 -0700 Subject: [PATCH 070/251] fix incorrect module lookup on asan events; fix bad string size assumption with const string eval --- src/dbg_engine/dbg_engine_ctrl.c | 10 +--------- src/eval/eval_interpret.c | 6 ++++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 96c168fc..f7318e29 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -3031,15 +3031,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D Access *access = access_open(); D_Handle process_handle = d_handle_from_dmn(D_MachineID_Local, ev->process); D_Entity *process = d_entity_from_handle(process_handle); - D_Entity *module = &d_entity_nil; - for(D_Entity *child = process->first; child != &d_entity_nil; child = child->next) - { - if(child->kind == D_EntityKind_Module) - { - module = child; - break; - } - } + D_Entity *module = d_module_from_process_vaddr(process, ev->instruction_pointer); if(module != &d_entity_nil) { // rjf: determine base address of asan shadow space diff --git a/src/eval/eval_interpret.c b/src/eval/eval_interpret.c index e314ac10..5568eb5b 100644 --- a/src/eval/eval_interpret.c +++ b/src/eval/eval_interpret.c @@ -402,8 +402,10 @@ e_interpret(String8 bytecode) case RDI_EvalOp_ConstString: { - MemoryCopy(&nval, ptr, imm.u64); - ptr += imm.u64; + U64 src_size = imm.u64; + U64 dst_size = Min(sizeof(nval), src_size); + MemoryCopy(&nval, ptr, dst_size); + ptr += src_size; }break; case RDI_EvalOp_Abs: From 74624df82996d89945fbc65969f89492aac24608 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 21 Aug 2026 12:08:23 -0700 Subject: [PATCH 071/251] p2r: correctly fall back to bytecode when regoff locations have offs of outside of [min_S16, max_S16] - in these cases, the offset is too big to fit inside of a packed 64-bit location encoding, so we need to fall back. before, we were only falling back incorrectly when the offset was outside of [0, max_U16], but this is not right. also, win32_wm: don't interpret '\n' (ctrl+J) --- project.raddbg | 6 ------ src/rdi_from_codeview/rdi_from_codeview.c | 2 +- src/win32/window_manager/win32_window_manager.c | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/project.raddbg b/project.raddbg index eb7764a5..8e78d298 100755 --- a/project.raddbg +++ b/project.raddbg @@ -32,9 +32,3 @@ target: executable: "build/ryan_scratch.exe" working_directory: build } -target: -{ - executable: "build/radbin.exe" - working_directory: build - arguments: "--rdi raddbg" -} diff --git a/src/rdi_from_codeview/rdi_from_codeview.c b/src/rdi_from_codeview/rdi_from_codeview.c index 0fceeffc..9baa7eb5 100644 --- a/src/rdi_from_codeview/rdi_from_codeview.c +++ b/src/rdi_from_codeview/rdi_from_codeview.c @@ -591,7 +591,7 @@ internal RDIM_Location cv2r_location_from_addr_reg_off(Arena *arena, RDI_Arch arch, RDI_RegCode reg_code, U32 reg_byte_size, U32 reg_byte_pos, S64 offset, B32 extra_indirection) { RDIM_Location result = {0}; - if(0 <= offset && offset <= (S64)max_U16) + if(min_S16 <= offset && offset <= max_S16) { if(extra_indirection) { diff --git a/src/win32/window_manager/win32_window_manager.c b/src/win32/window_manager/win32_window_manager.c index 8719f5ec..4f06c111 100644 --- a/src/win32/window_manager/win32_window_manager.c +++ b/src/win32/window_manager/win32_window_manager.c @@ -506,7 +506,7 @@ w32_wm_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_CHAR: { U32 character = wParam; - if((character >= 32 && character != 127) || character == '\n' || character == '\r') + if((character >= 32 && character != 127) || character == '\r') { WM_Event *event = w32_wm_push_event(WM_EventKind_Text, window); if(lParam & bit29) From 5946bb87e6229e10e7d3840c89ac02a437d01a1e Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 21 Aug 2026 12:35:47 -0700 Subject: [PATCH 072/251] debugger changelog --- CHANGELOG.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe814893..2a0f45a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,36 @@ # v0.9.28-alpha +## Debugger Changes +- First pass (will only work for simple cases currently) of watch expressions + locks, allowing the same address (with the same type) to be evaluated after + the expression goes out of scope. +- Improved disassembly snapping behavior when not viewing disassembly. Now, if + you do not have disassembly open, the debugger will prefer snapping to a call + stack frame which has line info. +- Added some visual and discoverability improvements to the UI. +- Fixed a crash in the new (as of 0.9.27) unwinder. (#893, #882, #877, #869, + #867, #865, #857, #848, #855, #824, #823) +- Fixed a number of memory leaks, causing out-of-control memory usage in some + cases. (#850) +- Fixed certain cases of location information being incorrectly generated + (notably in optimized builds, and functions with large stack usage). (#864) +- Fixed incorrect session start logic (debug string clearing, breakpoint hit + count resets) in multi-process debugging scenarios. +- Fixed rare issues with call stack computation reliability. +- Fixed cursor trails not being toggled by the `Animations` setting. +- Fixed cursor trails being incorrectly rendered when a cursor is first + scrolled into view. +- Fixed line edits embedded in cells (e.g. in the Palette) not correctly + clipping and scrolling longer strings. (#873) +- Fixed behavior of setting the project path when that would overwrite a file + which is not a debugger project file. (#875) + ## Linker Changes - Implemented `/OPT:ICF` identical COMDAT folding. - Added DEF, and SECTION configuration support. - Improved COMDAT handling for weak symbols, ICF, and zero-sized COMDAT - anchors, and stopped emitting empty marker sections that could produce invalid - PE images. + anchors, and stopped emitting empty marker sections that could produce + invalid PE images. - Fixed import/export edge cases, including import data-directory bounds and archive member handling for `__imp_*` symbols. - Accepted duplicate identical weak search aliases without reporting multiply @@ -18,7 +43,8 @@ that reference undefined symbols, even if the function was inlined (provided the object file was compiled with debug info) - Reduced memory usage for large links. -- Fixed forwarder export detection when the symbol name contains dots and has no alias. +- Fixed forwarder export detection when the symbol name contains dots and has + no alias. # v0.9.27-alpha From a5f0f760bacbfc2621e1c7ff063cf21ebe2f5e39 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 21 Aug 2026 13:17:46 -0700 Subject: [PATCH 073/251] bump to 29 --- src/base/base_context_cracking.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/base_context_cracking.h b/src/base/base_context_cracking.h index 9fdf366c..e846c62f 100644 --- a/src/base/base_context_cracking.h +++ b/src/base/base_context_cracking.h @@ -163,7 +163,7 @@ #endif #if !defined(BUILD_VERSION_PATCH) -# define BUILD_VERSION_PATCH 28 +# define BUILD_VERSION_PATCH 29 #endif #define BUILD_VERSION_STRING_LITERAL Stringify(BUILD_VERSION_MAJOR) "." Stringify(BUILD_VERSION_MINOR) "." Stringify(BUILD_VERSION_PATCH) From bf7e2f7655e43bd09b4589924e5f5cf83a673aa3 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 21 Aug 2026 14:53:30 -0700 Subject: [PATCH 074/251] oops - revert to 28, disable deletes in code view ctrls --- src/base/base_context_cracking.h | 2 +- src/raddbg/raddbg_views.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/base_context_cracking.h b/src/base/base_context_cracking.h index e846c62f..9fdf366c 100644 --- a/src/base/base_context_cracking.h +++ b/src/base/base_context_cracking.h @@ -163,7 +163,7 @@ #endif #if !defined(BUILD_VERSION_PATCH) -# define BUILD_VERSION_PATCH 29 +# define BUILD_VERSION_PATCH 28 #endif #define BUILD_VERSION_STRING_LITERAL Stringify(BUILD_VERSION_MAJOR) "." Stringify(BUILD_VERSION_MINOR) "." Stringify(BUILD_VERSION_PATCH) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 5d788220..058111bf 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -158,7 +158,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla UI_TxtOp single_line_op = ui_single_line_txt_op_from_event(scratch.arena, evt, line, line_range, *cursor, *mark); //- TODO(rjf): skip replace-ranges for now - if(single_line_op.replace.size != 0) + if(single_line_op.replace.size != 0 || evt->flags & UI_EventFlag_Delete) { continue; } From e6e1f7a426ac7ad64413f766d604080efa8bc5ba Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 21 Aug 2026 15:31:25 -0700 Subject: [PATCH 075/251] okay bump back to 29 --- src/base/base_context_cracking.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/base_context_cracking.h b/src/base/base_context_cracking.h index 9fdf366c..e846c62f 100644 --- a/src/base/base_context_cracking.h +++ b/src/base/base_context_cracking.h @@ -163,7 +163,7 @@ #endif #if !defined(BUILD_VERSION_PATCH) -# define BUILD_VERSION_PATCH 28 +# define BUILD_VERSION_PATCH 29 #endif #define BUILD_VERSION_STRING_LITERAL Stringify(BUILD_VERSION_MAJOR) "." Stringify(BUILD_VERSION_MINOR) "." Stringify(BUILD_VERSION_PATCH) From 7633bd90abdc3c922705ec4e8da35e177f2402d5 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 20 Aug 2026 23:20:05 -0700 Subject: [PATCH 076/251] use packed linker symbol and section contribution arrays --- src/linker/lnk.c | 96 ++++++++----------------------- src/linker/lnk_debug_info.c | 54 +++++++++++------- src/linker/lnk_debug_info.h | 2 +- src/linker/lnk_obj.c | 98 +++++++++++++++++++++----------- src/linker/lnk_obj.h | 28 +++++++-- src/linker/lnk_symbol_table.c | 16 ++---- src/linker/pdb_ext/pdb_builder.c | 68 ++++++++-------------- src/linker/pdb_ext/pdb_builder.h | 20 +++---- 8 files changed, 182 insertions(+), 200 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index dcec56e0..e43ef673 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -4071,15 +4071,9 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) } if (should_patch) { - if (obj->header.is_big_obj) { - COFF_Symbol32 *symbol32 = symbol.raw_symbol; - symbol32->section_number = section_number; - symbol32->value = value; - } else { - COFF_Symbol16 *symbol16 = symbol.raw_symbol; - symbol16->section_number = safe_cast_u16(section_number); - symbol16->value = value; - } + symbol.section_number = section_number; + symbol.value = value; + lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value); } } ProfEnd(); @@ -4241,15 +4235,9 @@ THREAD_POOL_TASK_FUNC(lnk_patch_common_block_leaders_task) COFF_ParsedSymbol parsed_symbol = lnk_parsed_from_symbol(symbol); U64 section_number = task->u.patch_symtabs.common_block_sect->sect_idx + 1; - if (symbol_ref.obj->header.is_big_obj) { - COFF_Symbol32 *symbol32 = parsed_symbol.raw_symbol; - symbol32->value = contrib->u.offset; - symbol32->section_number = safe_cast_u32(section_number); - } else { - COFF_Symbol16 *symbol16 = parsed_symbol.raw_symbol; - symbol16->value = contrib->u.offset; - symbol16->section_number = safe_cast_u16(section_number); - } + parsed_symbol.value = contrib->u.offset; + parsed_symbol.section_number = safe_cast_u32(section_number); + lnk_obj_symbol_patch(symbol_ref.obj, symbol_ref.symbol_idx, parsed_symbol, LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_Section); task->u.patch_symtabs.was_symbol_patched[symbol_ref.obj->input_idx][symbol_ref.symbol_idx] = 1; } @@ -4274,17 +4262,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_common_block_symbols_task) COFF_ParsedSymbol defn_parsed = lnk_parsed_from_symbol(defn); Assert(lnk_interp_from_symbol(defn) == COFF_SymbolValueInterp_Regular); if (defn) { - if (obj->header.is_big_obj) { - COFF_Symbol32 *symbol32 = symbol.raw_symbol; - symbol32->section_number = defn_parsed.section_number; - symbol32->value = safe_cast_u32(defn_parsed.value); - symbol32->storage_class = COFF_SymStorageClass_Static; - } else { - COFF_Symbol16 *symbol16 = symbol.raw_symbol; - symbol16->section_number = safe_cast_u16(defn_parsed.section_number); - symbol16->value = safe_cast_u32(defn_parsed.value); - symbol16->storage_class = COFF_SymStorageClass_Static; - } + symbol.section_number = defn_parsed.section_number; + symbol.value = safe_cast_u32(defn_parsed.value); + symbol.storage_class = COFF_SymStorageClass_Static; + lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); } } } @@ -4318,15 +4299,9 @@ THREAD_POOL_TASK_FUNC(lnk_patch_regular_symbols_task) value = sc->u.off + symbol.value; } - if (obj->header.is_big_obj) { - COFF_Symbol32 *symbol32 = symbol.raw_symbol; - symbol32->section_number = section_number; - symbol32->value = value; - } else { - COFF_Symbol16 *symbol16 = symbol.raw_symbol; - symbol16->section_number = safe_cast_u16(section_number); - symbol16->value = value; - } + symbol.section_number = section_number; + symbol.value = value; + lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value); } } ProfEnd(); @@ -4363,17 +4338,10 @@ lnk_patch_obj_symtab(LNK_SymbolTable *symtab, LNK_Obj *obj, B8 *was_symbol_patch value = fixup_src.value; } - if (obj->header.is_big_obj) { - COFF_Symbol32 *symbol32 = fixup_dst.raw_symbol; - symbol32->section_number = section_number; - symbol32->value = value; - symbol32->storage_class = COFF_SymStorageClass_Static; - } else { - COFF_Symbol16 *symbol16 = fixup_dst.raw_symbol; - symbol16->section_number = (U16)section_number; - symbol16->value = value; - symbol16->storage_class = COFF_SymStorageClass_Static; - } + fixup_dst.section_number = section_number; + fixup_dst.value = value; + fixup_dst.storage_class = COFF_SymStorageClass_Static; + lnk_obj_symbol_patch(obj, symbol_idx, fixup_dst, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); was_symbol_patched[symbol_idx] = 1; } @@ -5047,17 +5015,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) if (sect && (~sect->flags & COFF_SectionFlag_LnkRemove)) { if (~sect->flags & COFF_SectionFlag_MemDiscardable) { LNK_SectionContrib *first_sc = lnk_get_first_section_contrib(sect); - if (obj->header.is_big_obj) { - COFF_Symbol32 *symbol32 = symbol.raw_symbol; - symbol32->section_number = safe_cast_u32(first_sc->u.sect_idx + 1); - symbol32->value = first_sc->u.off; - symbol32->storage_class = COFF_SymStorageClass_Static; - } else { - COFF_Symbol16 *symbol16 = symbol.raw_symbol; - symbol16->section_number = safe_cast_u16(first_sc->u.sect_idx + 1); - symbol16->value = first_sc->u.off; - symbol16->storage_class = COFF_SymStorageClass_Static; - } + symbol.section_number = safe_cast_u32(first_sc->u.sect_idx + 1); + symbol.value = first_sc->u.off; + symbol.storage_class = COFF_SymStorageClass_Static; + lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); } else { lnk_error_obj(LNK_Error_SectRefsDiscardedMemory, obj, "symbol %S (No. 0x%llx) references section with discard flag", symbol_name, symbol_idx); } @@ -5075,17 +5036,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) LNK_Section *fallback_sect = task->image_sects.v[task->image_sects.count-1]; U32 fallback_section_number = safe_cast_u32(fallback_sect->sect_idx + 1); U32 fallback_section_offset = safe_cast_u32(fallback_voff - fallback_sect->voff); - if (obj->header.is_big_obj) { - COFF_Symbol32 *symbol32 = symbol.raw_symbol; - symbol32->section_number = fallback_section_number; - symbol32->value = fallback_section_offset; - symbol32->storage_class = COFF_SymStorageClass_Static; - } else { - COFF_Symbol16 *symbol16 = symbol.raw_symbol; - symbol16->section_number = safe_cast_u16(fallback_section_number); - symbol16->value = fallback_section_offset; - symbol16->storage_class = COFF_SymStorageClass_Static; - } + symbol.section_number = fallback_section_number; + symbol.value = fallback_section_offset; + symbol.storage_class = COFF_SymStorageClass_Static; + lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); lnk_error_obj(LNK_Warning_UndefinedSectionSymbol, obj, "undefined section symbol %S (No. 0x%llx) refers to an image section that doesn't exist; patching to %#llx", symbol_name, symbol_idx, fallback_voff); } diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 00fe76d7..23e22e11 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -3082,8 +3082,6 @@ THREAD_POOL_TASK_FUNC(lnk_write_pdb_modules) internal THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) { - // TODO: use chunked lists for SC - // TODO: put back unused sc nodes // TODO: compute CRC for relocations U64 obj_idx = task_id; @@ -3091,8 +3089,9 @@ THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) PDB_DbiModule *mod = task->mod_arr [obj_idx]; LNK_Obj *obj = task->cv->obj_arr[obj_idx]; - PDB_DbiSCNode *sc_arr = push_array_no_zero(arena, PDB_DbiSCNode, obj->header.section_count_no_null); - U64 sc_count = 0; + PDB_DbiSCArray *sc_array = &task->sc_arrays[obj_idx]; + sc_array->v = push_array_no_zero(arena, PDB_DbiSC, obj->header.section_count_no_null); + sc_array->cap = obj->header.section_count_no_null; for EachIndex(sect_idx, obj->header.section_count_no_null) { @@ -3127,23 +3126,22 @@ THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) U64 range_idx = search_result - 1; // fill out & push section contribution - PDB_DbiSCNode *sc = sc_arr + sc_count++; - sc->data.base.sec = (U16)(is_virt ? range_idx : task->image_section_file_section_numbers[range_idx]); - sc->data.base.pad0 = 0; - sc->data.base.sec_off = section_range.min - image_ranges->v[range_idx].min; - sc->data.base.size = dim_1u64(section_range); - sc->data.base.flags = *section.flags; - sc->data.base.mod = mod->imod; - sc->data.base.pad1 = 0; - sc->data.data_crc = is_virt ? 0 : crc32_from_string(str8_substr(task->image_data, section_range)); - sc->data.reloc_crc = 0; - dbi_sec_contrib_list_push_node(&task->sc_list[obj_idx], sc); + PDB_DbiSC *sc = &sc_array->v[sc_array->count++]; + sc->base.sec = (U16)(is_virt ? range_idx : task->image_section_file_section_numbers[range_idx]); + sc->base.pad0 = 0; + sc->base.sec_off = section_range.min - image_ranges->v[range_idx].min; + sc->base.size = dim_1u64(section_range); + sc->base.flags = *section.flags; + sc->base.mod = mod->imod; + sc->base.pad1 = 0; + sc->data_crc = is_virt ? 0 : crc32_from_string(str8_substr(task->image_data, section_range)); + sc->reloc_crc = 0; } // find first code section contribution for the Mod1::fUpdateSecContrib - for EachIndex(i, sc_count) { - if (sc_arr[i].data.base.flags & COFF_SectionFlag_CntCode) { - mod->first_sc = sc_arr[i].data; + for EachIndex(i, sc_array->count) { + if (sc_array->v[i].base.flags & COFF_SectionFlag_CntCode) { + mod->first_sc = sc_array->v[i]; break; } } @@ -3393,9 +3391,25 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config task.image_section_virt_ranges.v[i] = rng_1u64(sect_header->voff, sect_header->voff + sect_header->vsize); } - task.sc_list = push_array(scratch.arena, PDB_DbiSCList, cv->obj_count); + task.sc_arrays = push_array(scratch.arena, PDB_DbiSCArray, cv->obj_count); tp_for_parallel(tp, tp_arena, cv->obj_count, lnk_push_dbi_sec_contrib_task, &task); - dbi_sec_list_concat_arr(&task.pdb->dbi->sec_contrib_list, cv->obj_count, task.sc_list); + + PDB_DbiSCArray *sec_contribs = &task.pdb->dbi->sec_contribs; + U64 new_count = sec_contribs->count; + for EachIndex(obj_idx, cv->obj_count) { + new_count += task.sc_arrays[obj_idx].count; + } + PDB_DbiSC *new_v = push_array_no_zero(task.pdb->dbi->arena, PDB_DbiSC, new_count); + MemoryCopyTyped(new_v, sec_contribs->v, sec_contribs->count); + U64 cursor = sec_contribs->count; + for EachIndex(obj_idx, cv->obj_count) { + PDB_DbiSCArray *src = &task.sc_arrays[obj_idx]; + MemoryCopyTyped(new_v + cursor, src->v, src->count); + cursor += src->count; + } + sec_contribs->v = new_v; + sec_contribs->count = new_count; + sec_contribs->cap = new_count; } ProfEnd(); } diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index 3626b3f9..29412a5b 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -237,7 +237,7 @@ typedef struct Rng1U64Array image_section_virt_ranges; Rng1U64Array image_section_file_ranges; U64 *image_section_file_section_numbers; - PDB_DbiSCList *sc_list; // [obj_count] + PDB_DbiSCArray *sc_arrays; // [obj_count] } LNK_BuildPdb; typedef struct diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 20ee52f8..89819113 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -165,27 +165,38 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } } - U64 name_block_count = CeilIntegerDiv(header.symbol_count, 64); - LNK_SymbolNameCache symbol_name_cache = { - .masks = push_array(arena, U64, name_block_count), - .block_bases = push_array_no_zero(arena, U32, name_block_count), - .name_sizes = push_array_no_zero(arena, U32, primary_symbol_count), + U64 symbol_block_count = CeilIntegerDiv(header.symbol_count, 64); + LNK_ObjSymbolArray symbols = { + .count = primary_symbol_count, + .primary_masks = push_array(arena, U64, symbol_block_count), + .block_bases = push_array_no_zero(arena, U32, symbol_block_count), + .values = push_array_no_zero(arena, U64, primary_symbol_count), + .section_numbers = push_array_no_zero(arena, U32, primary_symbol_count), + .types = push_array_no_zero(arena, COFF_SymbolType, primary_symbol_count), + .storage_classes = push_array_no_zero(arena, COFF_SymStorageClass, primary_symbol_count), + .aux_counts = push_array_no_zero(arena, U8, primary_symbol_count), + .name_offsets = push_array_no_zero(arena, U32, primary_symbol_count), + .name_sizes = push_array_no_zero(arena, U32, primary_symbol_count), }; { - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(input->data, header.section_table_range).str; U64 next_block = 0; - U32 name_count = 0; + U32 primary_idx = 0; COFF_ParsedSymbol symbol; for (U64 symbol_idx = 0; symbol_idx < header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = coff_parse_symbol(header, raw_coff_string_table, raw_coff_symbol_table, symbol_idx); U64 block_idx = symbol_idx >> 6; while (next_block <= block_idx) { - symbol_name_cache.block_bases[next_block++] = name_count; - } - if (symbol.name.size) { - symbol_name_cache.masks[block_idx] |= 1ull << (symbol_idx & 63); - symbol_name_cache.name_sizes[name_count++] = safe_cast_u32(symbol.name.size); + symbols.block_bases[next_block++] = primary_idx; } + symbols.primary_masks[block_idx] |= 1ull << (symbol_idx & 63); + symbols.values[primary_idx] = symbol.value; + symbols.section_numbers[primary_idx] = symbol.section_number; + symbols.types[primary_idx] = symbol.type; + symbols.storage_classes[primary_idx] = symbol.storage_class; + symbols.aux_counts[primary_idx] = symbol.aux_symbol_count; + symbols.name_offsets[primary_idx] = symbol.name.size ? safe_cast_u32(symbol.name.str - input->data.str) : 0; + symbols.name_sizes[primary_idx] = safe_cast_u32(symbol.name.size); + primary_idx += 1; COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { if (symbol.section_number == 0 || symbol.section_number > header.section_count_no_null) { @@ -203,9 +214,10 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } } } - while (next_block < name_block_count) { - symbol_name_cache.block_bases[next_block++] = name_count; + while (next_block < symbol_block_count) { + symbols.block_bases[next_block++] = primary_idx; } + Assert(primary_idx == primary_symbol_count); } // @@ -370,7 +382,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) obj->path = push_str8_copy(arena, input->path); obj->header = header; obj->section_flags = section_flags; - obj->symbol_name_cache = symbol_name_cache; + obj->symbols = symbols; obj->comdats = comdats; obj->exclude_from_debug_info = input->exclude_from_debug_info; obj->hotpatch = hotpatch; @@ -765,32 +777,37 @@ lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number) return §ion_table[sect_idx]; } +internal force_inline U64 +lnk_obj_primary_symbol_idx_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) +{ + U64 block_idx = symbol_idx >> 6; + U64 bit = 1ull << (symbol_idx & 63); + U64 block_mask = obj->symbols.primary_masks[block_idx]; + Assert(block_mask & bit); + return obj->symbols.block_bases[block_idx] + count_bits_set64(block_mask & (bit - 1)); +} + internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx_no_name(LNK_Obj *obj, U64 symbol_idx) { - return coff_parse_symbol_no_name(obj->header, lnk_coff_symbol_table_from_obj(obj), safe_cast_u32(symbol_idx)); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + U64 symbol_size = obj->header.is_big_obj ? sizeof(COFF_Symbol32) : sizeof(COFF_Symbol16); + COFF_ParsedSymbol result = { + .value = obj->symbols.values[primary_idx], + .section_number = obj->symbols.section_numbers[primary_idx], + .type = obj->symbols.types[primary_idx], + .storage_class = obj->symbols.storage_classes[primary_idx], + .aux_symbol_count = obj->symbols.aux_counts[primary_idx], + .raw_symbol = lnk_coff_symbol_table_from_obj(obj).str + symbol_idx * symbol_size, + }; + return result; } internal force_inline String8 lnk_symbol_name_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) { - String8 result = {0}; - U64 block_idx = symbol_idx >> 6; - U64 bit = 1ull << (symbol_idx & 63); - U64 mask = obj->symbol_name_cache.masks[block_idx]; - if (mask & bit) { - U64 name_idx = obj->symbol_name_cache.block_bases[block_idx] + count_bits_set64(mask & (bit - 1)); - U64 name_size = obj->symbol_name_cache.name_sizes[name_idx]; - - String8 symbol_table = lnk_coff_symbol_table_from_obj(obj); - COFF_SymbolName *name = obj->header.is_big_obj ? &((COFF_Symbol32 *)symbol_table.str)[symbol_idx].name - : &((COFF_Symbol16 *)symbol_table.str)[symbol_idx].name; - U8 *name_ptr = name->long_name.zeroes == 0 - ? obj->data.str + obj->header.string_table_range.min + name->long_name.string_table_offset - : name->short_name; - result = str8(name_ptr, name_size); - } - return result; + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + return str8(obj->data.str + obj->symbols.name_offsets[primary_idx], obj->symbols.name_sizes[primary_idx]); } internal force_inline COFF_ParsedSymbol @@ -801,6 +818,21 @@ lnk_parsed_symbol_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) return result; } +internal void +lnk_obj_symbol_patch(LNK_Obj *obj, U64 symbol_idx, COFF_ParsedSymbol patch, LNK_ObjSymbolPatchFlags flags) +{ + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + if (flags & LNK_ObjSymbolPatch_Value) { + obj->symbols.values[primary_idx] = patch.value; + } + if (flags & LNK_ObjSymbolPatch_Section) { + obj->symbols.section_numbers[primary_idx] = patch.section_number; + } + if (flags & LNK_ObjSymbolPatch_StorageClass) { + obj->symbols.storage_classes[primary_idx] = patch.storage_class; + } +} + internal THREAD_POOL_TASK_FUNC(lnk_collect_obj_chunks_task) { diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index d93ad522..807c21a1 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -5,12 +5,27 @@ // --- Input ------------------------------------------------------------------- -typedef struct LNK_SymbolNameCache +typedef struct LNK_ObjSymbolArray { - U64 *masks; - U32 *block_bases; - U32 *name_sizes; -} LNK_SymbolNameCache; + U64 count; + U64 *primary_masks; + U32 *block_bases; + U64 *values; + U32 *section_numbers; + COFF_SymbolType *types; + COFF_SymStorageClass *storage_classes; + U8 *aux_counts; + U32 *name_offsets; + U32 *name_sizes; +} LNK_ObjSymbolArray; + +typedef U32 LNK_ObjSymbolPatchFlags; +enum +{ + LNK_ObjSymbolPatch_Value = (1 << 0), + LNK_ObjSymbolPatch_Section = (1 << 1), + LNK_ObjSymbolPatch_StorageClass = (1 << 2), +}; typedef struct LNK_Obj { @@ -19,7 +34,7 @@ typedef struct LNK_Obj COFF_FileHeaderInfo header; COFF_SectionFlags *section_flags; - LNK_SymbolNameCache symbol_name_cache; + LNK_ObjSymbolArray symbols; // flags B8 hotpatch; @@ -223,6 +238,7 @@ internal U32List lnk_obj_collect_associated_sections(Arena *arena, LNK_ internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx_no_name(LNK_Obj *obj, U64 symbol_idx); internal force_inline String8 lnk_symbol_name_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); +internal void lnk_obj_symbol_patch(LNK_Obj *obj, U64 symbol_idx, COFF_ParsedSymbol patch, LNK_ObjSymbolPatchFlags flags); internal COFF_SectionHeader * lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number); internal U64 lnk_obj_sect_idx_from_section_number(LNK_Obj *obj, U64 section_number); diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 7226ed5e..1873a821 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -826,18 +826,10 @@ THREAD_POOL_TASK_FUNC(lnk_replace_weak_with_default_symbol_task) COFF_ParsedSymbol resolve_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(resolve.obj, resolve.symbol_idx); COFF_SymbolValueInterpType resolve_interp = coff_interp_from_parsed_symbol(resolve_parsed); if (resolve_interp == COFF_SymbolValueInterp_Weak) { - COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(resolve_parsed, symbol_ref.obj->header.is_big_obj); - if (symbol_ref.obj->header.is_big_obj) { - COFF_Symbol32 *symbol32 = symbol_parsed.raw_symbol; - symbol32->section_number = COFF_Symbol_UndefinedSection; - symbol32->value = 0; - symbol32->storage_class = COFF_SymStorageClass_External; - } else { - COFF_Symbol16 *symbol16 = symbol_parsed.raw_symbol; - symbol16->section_number = COFF_Symbol_UndefinedSection; - symbol16->value = 0; - symbol16->storage_class = COFF_SymStorageClass_External; - } + symbol_parsed.section_number = COFF_Symbol_UndefinedSection; + symbol_parsed.value = 0; + symbol_parsed.storage_class = COFF_SymStorageClass_External; + lnk_obj_symbol_patch(symbol_ref.obj, symbol_ref.symbol_idx, symbol_parsed, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Undefined); } else { symbol->first_ref->v = resolve; diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index 06c1ddb3..fbe2c9ae 100644 --- a/src/linker/pdb_ext/pdb_builder.c +++ b/src/linker/pdb_ext/pdb_builder.c @@ -2521,27 +2521,17 @@ psi_push(PDB_PsiContext *psi, CV_Pub32Flags flags, U32 offset, U16 isect, String //////////////////////////////// -internal void -dbi_sec_contrib_list_push_node(PDB_DbiSCList *list, PDB_DbiSCNode *node) +internal PDB_DbiSC * +dbi_sec_contrib_array_push(Arena *arena, PDB_DbiSCArray *array) { - node->next = 0; - SLLQueuePush(list->first, list->last, node); - list->count += 1; -} - -internal PDB_DbiSCNode * -dbi_sec_contrib_list_push(Arena *arena, PDB_DbiSCList *list) -{ - PDB_DbiSCNode *node = push_array_no_zero(arena, PDB_DbiSCNode, 1); - node->next = 0; - dbi_sec_contrib_list_push_node(list, node); - return node; -} - -internal void -dbi_sec_list_concat_arr(PDB_DbiSCList *list, U64 count, PDB_DbiSCList *to_concat) -{ - SLLConcatInPlaceArray(list, to_concat, count); + if (array->count >= array->cap) { + U64 new_cap = Max(16, array->cap * 2); + PDB_DbiSC *new_v = push_array_no_zero(arena, PDB_DbiSC, new_cap); + MemoryCopyTyped(new_v, array->v, array->count); + array->v = new_v; + array->cap = new_cap; + } + return &array->v[array->count++]; } internal PDB_DbiContext * @@ -2664,12 +2654,12 @@ dbi_open_module_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_Db return list; } -internal PDB_DbiSCList +internal PDB_DbiSCArray dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header) { ProfBeginFunction(); - PDB_DbiSCList sec_contrib = {0}; + PDB_DbiSCArray sec_contribs = {0}; if (dbi_header->sec_con_size > sizeof(PDB_DbiSC)) { Temp scratch = scratch_begin(&arena, 1); @@ -2686,16 +2676,11 @@ dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_Db switch (version) { case PDB_DbiSCVersion_1: { U64 contrib_count = dbi_header->sec_con_size / sizeof(PDB_DbiSC); - PDB_DbiSC *src_contrib_array = push_array(scratch.arena, PDB_DbiSC, contrib_count); - MSF_UInt sec_con_read = msf_stream_read_array(msf, sn, &src_contrib_array[0], contrib_count); - Assert(sec_con_read == sizeof(src_contrib_array[0]) * contrib_count); - - PDB_DbiSCNode *dst_contrib_array = push_array_no_zero(arena, PDB_DbiSCNode, contrib_count); - for (U64 icontrib = 0; icontrib < contrib_count; icontrib += 1) { - dst_contrib_array[icontrib].next = 0; - dst_contrib_array[icontrib].data = src_contrib_array[icontrib]; - dbi_sec_contrib_list_push_node(&sec_contrib, &dst_contrib_array[icontrib]); - } + sec_contribs.v = push_array_no_zero(arena, PDB_DbiSC, contrib_count); + sec_contribs.count = contrib_count; + sec_contribs.cap = contrib_count; + MSF_UInt sec_con_read = msf_stream_read_array(msf, sn, sec_contribs.v, contrib_count); + Assert(sec_con_read == sizeof(sec_contribs.v[0]) * contrib_count); } break; case PDB_DbiSCVersion_2: { NotImplemented; @@ -2709,7 +2694,7 @@ dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_Db } ProfEnd(); - return sec_contrib; + return sec_contribs; } internal void @@ -3138,21 +3123,18 @@ dbi_build_sec_con(Arena *arena, TP_Context *tp, PDB_DbiContext *dbi) *version = PDB_DbiSCVersion_1; // push section contribs V1 - ProfBegin("Push sect contribs [Count %llu]", dbi->sec_contrib_list.count); - PDB_DbiSC *sc_array = push_array_no_zero(arena, PDB_DbiSC, dbi->sec_contrib_list.count); - PDB_DbiSC *dst = &sc_array[0]; - for (PDB_DbiSCNode *src = dbi->sec_contrib_list.first; src != 0; src = src->next, dst += 1) { - *dst = src->data; - } + ProfBegin("Push sect contribs [Count %llu]", dbi->sec_contribs.count); + PDB_DbiSC *sc_array = push_array_no_zero(arena, PDB_DbiSC, dbi->sec_contribs.count); + MemoryCopyTyped(sc_array, dbi->sec_contribs.v, dbi->sec_contribs.count); ProfEnd(); // sort section contribs so they are binary searchable - lnk_radix_sort_dbi_sc_array(tp, sc_array, dbi->sec_contrib_list.count, dbi->section_list.count + 1); + lnk_radix_sort_dbi_sc_array(tp, sc_array, dbi->sec_contribs.count, dbi->section_list.count + 1); // DBI contribution maps an address range to its module, so adjacent same-module runs can share one record ProfBegin("Coalesce sect contribs"); U64 sc_count = 0; - for EachIndex(read_idx, dbi->sec_contrib_list.count) { + for EachIndex(read_idx, dbi->sec_contribs.count) { PDB_DbiSC *current = &sc_array[read_idx]; if (sc_count > 0) { PDB_DbiSC *previous = &sc_array[sc_count - 1]; @@ -3363,9 +3345,7 @@ dbi_module_push_section_contrib(PDB_DbiContext *dbi, sc.data_crc = data_crc; sc.reloc_crc = reloc_crc; - PDB_DbiSCNode *node = push_array_no_zero(dbi->arena, PDB_DbiSCNode, 1); - node->data = sc; - dbi_sec_contrib_list_push_node(&dbi->sec_contrib_list, node); + *dbi_sec_contrib_array_push(dbi->arena, &dbi->sec_contribs) = sc; // Mod1::fUpdateSecContrib if (mod->first_sc.base.mod == 0) { diff --git a/src/linker/pdb_ext/pdb_builder.h b/src/linker/pdb_ext/pdb_builder.h index ec0b749a..016e4aeb 100644 --- a/src/linker/pdb_ext/pdb_builder.h +++ b/src/linker/pdb_ext/pdb_builder.h @@ -255,18 +255,12 @@ typedef struct PDB_DbiModuleList U64 count; } PDB_DbiModuleList; -typedef struct PDB_DbiSCNode +typedef struct PDB_DbiSCArray { - struct PDB_DbiSCNode *next; - PDB_DbiSC data; -} PDB_DbiSCNode; - -typedef struct PDB_DbiSCList -{ - PDB_DbiSCNode *first; - PDB_DbiSCNode *last; - U64 count; -} PDB_DbiSCList; + PDB_DbiSC *v; + U64 count; + U64 cap; +} PDB_DbiSCArray; typedef struct PDB_DbiSectionNode { @@ -290,7 +284,7 @@ typedef struct PDB_DbiContext MSF_StreamNumber publics_sn; MSF_StreamNumber symbols_sn; PDB_DbiModuleList module_list; - PDB_DbiSCList sec_contrib_list; + PDB_DbiSCArray sec_contribs; PDB_DbiSectionList section_list; PDB_StringTable ec_names; MSF_StreamNumber dbg_streams[PDB_DbiStream_COUNT]; @@ -403,7 +397,7 @@ internal String8 dbi_module_read_c13_data(Arena *arena, MSF_Context * internal void dbi_module_push_section_contrib(PDB_DbiContext *dbi, PDB_DbiModule *mod, ISectOff isect_off, U32 size, U32 data_crc, U32 reloc_crc, COFF_SectionFlags flags); internal String8List * dbi_open_file_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); internal PDB_DbiModuleList dbi_open_module_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header, String8List *file_info); -internal PDB_DbiSCList dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); +internal PDB_DbiSCArray dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); internal PDB_StringTable dbi_open_ec_names(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); internal void dbi_open_dbg_streams(MSF_StreamNumber *dbg_streams, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); internal PDB_DbiSectionList dbi_open_section_headers(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn); From 993df2d9d855d3a76c441690d14ab36fea9e0689 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 24 Aug 2026 16:49:58 -0700 Subject: [PATCH 077/251] pack CodeView leaf refs and share radix sort centralize leaf ref index casts --- src/linker/base_ext/base_radix_sort.c | 93 ++++++ src/linker/base_ext/base_radix_sort.h | 6 + src/linker/lnk.c | 2 + src/linker/lnk_debug_info.c | 428 ++++++-------------------- src/linker/lnk_debug_info.h | 22 +- src/linker/tests/linker_tests.c | 42 +++ src/torture/torture_main.c | 2 + 7 files changed, 246 insertions(+), 349 deletions(-) create mode 100644 src/linker/base_ext/base_radix_sort.c create mode 100644 src/linker/base_ext/base_radix_sort.h diff --git a/src/linker/base_ext/base_radix_sort.c b/src/linker/base_ext/base_radix_sort.c new file mode 100644 index 00000000..f4c07fab --- /dev/null +++ b/src/linker/base_ext/base_radix_sort.c @@ -0,0 +1,93 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#define U64_RADIX_BITS 8 +#define U64_RADIX_SIZE (1 << U64_RADIX_BITS) +#define U64_RADIX_PARALLEL_MIN 140000 + +typedef struct U64RadixTask +{ + Rng1U64 *ranges; + U64 *src; + U64 *dst; + U64 *offsets; + U32 shift; +} U64RadixTask; + +internal +THREAD_POOL_TASK_FUNC(u64_radix_hist_task) +{ + U64RadixTask *task = raw_task; + U64 *counts = task->offsets + task_id * U64_RADIX_SIZE; + MemoryZeroTyped(counts, U64_RADIX_SIZE); + for EachInRange(i, task->ranges[task_id]) { + U64 digit = (task->src[i] >> task->shift) & (U64_RADIX_SIZE - 1); + counts[digit] += 1; + } +} + +internal +THREAD_POOL_TASK_FUNC(u64_radix_scatter_task) +{ + U64RadixTask *task = raw_task; + U64 *offsets = task->offsets + task_id * U64_RADIX_SIZE; + for EachInRange(i, task->ranges[task_id]) { + U64 value = task->src[i]; + U64 digit = (value >> task->shift) & (U64_RADIX_SIZE - 1); + task->dst[offsets[digit]++] = value; + } +} + +internal void +u64_array_sort_radix_parallel(TP_Context *tp, U64 count, U64 *v) +{ + if (count < U64_RADIX_PARALLEL_MIN) { + u64_array_sort(count, v); + return; + } + + U64 max_value = u64_array_max(count, v); + if (max_value == 0) { return; } + + Temp scratch = scratch_begin(0,0); + + U64 task_count = tp->worker_count; + U64 *buffer = push_array_no_zero(scratch.arena, U64, count); + U64 *offsets = push_array_no_zero(scratch.arena, U64, task_count * U64_RADIX_SIZE); + U64RadixTask task = { + .ranges = tp_divide_work(scratch.arena, count, task_count), + .src = v, + .dst = buffer, + .offsets = offsets, + }; + + U64 pass_count = (64 - clz64(max_value) + U64_RADIX_BITS - 1) / U64_RADIX_BITS; + for EachIndex(pass_idx, pass_count) { + task.shift = safe_cast_u32(pass_idx * U64_RADIX_BITS); + tp_for_parallel(tp, 0, task_count, u64_radix_hist_task, &task); + + U64 cursor = 0; + for EachIndex(digit, U64_RADIX_SIZE) { + for EachIndex(task_idx, task_count) { + U64 *slot = &offsets[task_idx * U64_RADIX_SIZE + digit]; + U64 slot_count = *slot; + *slot = cursor; + cursor += slot_count; + } + } + Assert(cursor == count); + + tp_for_parallel(tp, 0, task_count, u64_radix_scatter_task, &task); + Swap(U64 *, task.src, task.dst); + } + + if (task.src != v) { + MemoryCopyTyped(v, task.src, count); + } + + scratch_end(scratch); +} + +#undef U64_RADIX_BITS +#undef U64_RADIX_SIZE +#undef U64_RADIX_PARALLEL_MIN diff --git a/src/linker/base_ext/base_radix_sort.h b/src/linker/base_ext/base_radix_sort.h new file mode 100644 index 00000000..b89c4547 --- /dev/null +++ b/src/linker/base_ext/base_radix_sort.h @@ -0,0 +1,6 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#pragma once + +internal void u64_array_sort_radix_parallel(TP_Context *tp, U64 count, U64 *v); diff --git a/src/linker/lnk.c b/src/linker/lnk.c index e43ef673..0da81a8f 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -63,11 +63,13 @@ #include "base_ext/base_inc.h" #include "thread_pool/thread_pool.h" +#include "base_ext/base_radix_sort.h" #include "codeview_ext/codeview.h" #include "pdb_ext/msf_builder.h" #include "base_ext/base_inc.c" #include "thread_pool/thread_pool.c" +#include "base_ext/base_radix_sort.c" #include "codeview_ext/codeview.c" // --- PDB Extensions- --------------------------------------------------------- diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 23e22e11..213c8f76 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -1193,6 +1193,26 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, return input; } +internal force_inline LNK_LeafRef +lnk_leaf_ref_make(U64 obj_idx, U64 leaf_idx) +{ + LNK_LeafRef result = Compose64Bit(safe_cast_u32(obj_idx), safe_cast_u32(leaf_idx)); + Assert(result != LNK_LEAF_REF_NULL); + return result; +} + +internal force_inline U32 +lnk_leaf_ref_obj_idx(LNK_LeafRef ref) +{ + return (U32)(ref >> 32); +} + +internal force_inline U32 +lnk_leaf_ref_leaf_idx(LNK_LeafRef ref) +{ + return (U32)ref; +} + internal LNK_LeafRef lnk_leaf_ref_from_ti(LNK_CodeViewInput *input, U32 obj_idx, CV_TypeIndexSource source, CV_TypeIndex ti) { @@ -1221,45 +1241,34 @@ lnk_leaf_ref_from_ti(LNK_CodeViewInput *input, U32 obj_idx, CV_TypeIndexSource s // map original leaf index to RRT type index CV_TypeIndex final_ti = obj->ti_map[leaf_idx_og]; - return (LNK_LeafRef){ ts_debug_t_idx, cv_leaf_idx_from_ti(ts_debug_t, source, final_ti) }; + return lnk_leaf_ref_make(ts_debug_t_idx, cv_leaf_idx_from_ti(ts_debug_t, source, final_ti)); } - return (LNK_LeafRef){ ts_debug_t_idx, cv_leaf_idx_from_ti(ts_debug_t, source, ti) }; + return lnk_leaf_ref_make(ts_debug_t_idx, cv_leaf_idx_from_ti(ts_debug_t, source, ti)); } CV_DebugT *debug_t = input->debug_t_arr + obj_idx; // ti_range: PCH if (contains_1u64(debug_t->pch_ti_range[source], ti)) { - return (LNK_LeafRef){ debug_t->pch_obj_idx, - cv_leaf_idx_from_ti(&input->debug_t_arr[debug_t->pch_obj_idx], source, ti) }; + return lnk_leaf_ref_make(debug_t->pch_obj_idx, + cv_leaf_idx_from_ti(&input->debug_t_arr[debug_t->pch_obj_idx], source, ti)); } // ti range: internal type server - return (LNK_LeafRef){ obj_idx, cv_leaf_idx_from_ti(debug_t, source, ti) }; + return lnk_leaf_ref_make(obj_idx, cv_leaf_idx_from_ti(debug_t, source, ti)); } internal U64 lnk_hash_from_leaf_ref(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref) { - return input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx]; + return input->debug_h_arr[lnk_leaf_ref_obj_idx(leaf_ref)].v[lnk_leaf_ref_leaf_idx(leaf_ref)]; } internal int lnk_leaf_ref_compare(LNK_LeafRef a, LNK_LeafRef b) { - if (a.obj_idx == b.obj_idx) { - return a.leaf_idx < b.leaf_idx ? -1 : - a.leaf_idx > b.leaf_idx ? +1 : 0; - } - return a.obj_idx < b.obj_idx ? -1 : - a.obj_idx > b.obj_idx ? +1 : 0; -} - -internal int -lnk_leaf_ref_is_before(void *raw_a, void *raw_b) -{ - return lnk_leaf_ref_compare(**(LNK_LeafRef **)raw_a, **(LNK_LeafRef **)raw_b) < 0; + return a < b ? -1 : a > b ? +1 : 0; } internal B32 @@ -1273,10 +1282,12 @@ lnk_match_leaf_ref(LNK_CodeViewInput *input, LNK_LeafRef a, LNK_LeafRef b) internal U64 lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInfoList ti_info_list, B32 discard_cycles) { - CV_DebugT *debug_t = &input->debug_t_arr[leaf_ref.obj_idx]; - CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, leaf_ref.leaf_idx); + U32 obj_idx = lnk_leaf_ref_obj_idx(leaf_ref); + U32 leaf_idx = lnk_leaf_ref_leaf_idx(leaf_ref); + CV_DebugT *debug_t = &input->debug_t_arr[obj_idx]; + CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, leaf_idx); CV_TypeIndexSource curr_ti_source = cv_type_index_source_from_leaf_kind(leaf.kind); - CV_TypeIndex curr_ti = cv_ti_from_leaf_idx(debug_t, curr_ti_source, leaf_ref.leaf_idx); + CV_TypeIndex curr_ti = cv_ti_from_leaf_idx(debug_t, curr_ti_source, leaf_idx); // init hasher LNK_Hasher hasher; @@ -1323,7 +1334,7 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf Temp scratch = scratch_begin(0,0); String8 leaf_kind_str = cv_string_from_leaf_kind(leaf.kind); String8 error_msg = push_str8f(scratch.arena, "LF_%S(type_index: 0x%x) out of bounds type index 0x%x (leaf struct offset: 0x%llx)", leaf_kind_str, curr_ti, sub_ti, sub_ti_n->offset); - lnk_error_obj(LNK_Error_InvalidTypeIndex, input->obj_arr[leaf_ref.obj_idx], "%S", error_msg); + lnk_error_obj(LNK_Error_InvalidTypeIndex, input->obj_arr[obj_idx], "%S", error_msg); scratch_end(scratch); break; @@ -1345,31 +1356,31 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf Temp scratch = scratch_begin(0,0); String8 leaf_kind_str = cv_string_from_leaf_kind(leaf.kind); String8 error_msg = push_str8f(scratch.arena, "LF_%S(type_index: 0x%x) forward refs member type index 0x%x (leaf struct offset: 0x%llx)", leaf_kind_str, curr_ti, sub_ti, sub_ti_n->offset); - lnk_error_obj(LNK_Error_InvalidTypeIndex, input->obj_arr[leaf_ref.obj_idx], "%S", error_msg); + lnk_error_obj(LNK_Error_InvalidTypeIndex, input->obj_arr[obj_idx], "%S", error_msg); scratch_end(scratch); break; } // type index -> hash - LNK_LeafRef sub_ref = lnk_leaf_ref_from_ti(input, leaf_ref.obj_idx, sub_ti_n->source, sub_ti); - U64 sub_hash = input->debug_h_arr[sub_ref.obj_idx].v[sub_ref.leaf_idx]; + LNK_LeafRef sub_ref = lnk_leaf_ref_from_ti(input, obj_idx, sub_ti_n->source, sub_ti); + U64 sub_hash = input->debug_h_arr[lnk_leaf_ref_obj_idx(sub_ref)].v[lnk_leaf_ref_leaf_idx(sub_ref)]; // mix-in sub-type hash lnk_hasher_update_struct(&hasher, &sub_hash); } // hash leaf header - CV_LeafHeader *leaf_header_ptr = cv_debug_t_get_leaf_header(debug_t, leaf_ref.leaf_idx); + CV_LeafHeader *leaf_header_ptr = cv_debug_t_get_leaf_header(debug_t, leaf_idx); lnk_hasher_update_struct(&hasher, leaf_header_ptr); // finalize the type hash U64 hash = lnk_hasher_digest64(&hasher); Assert(hash != 0); - Assert(input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] == 0 || - input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] == 1); - input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] = hash; + Assert(input->debug_h_arr[obj_idx].v[leaf_idx] == 0 || + input->debug_h_arr[obj_idx].v[leaf_idx] == 1); + input->debug_h_arr[obj_idx].v[leaf_idx] = hash; return hash; } @@ -1393,14 +1404,15 @@ lnk_hash_cv_leaf_deep(Arena *arena, } HashStack; // set up root frame - CV_DebugT *root_debug_t = &input->debug_t_arr[root_leaf_ref.obj_idx]; + U32 root_obj_idx = lnk_leaf_ref_obj_idx(root_leaf_ref); + CV_DebugT *root_debug_t = &input->debug_t_arr[root_obj_idx]; HashStack *root_frame = push_array(temp.arena, HashStack, 1); root_frame->leaf_ref = root_leaf_ref; root_frame->ti_info_list = root_ti_info_list; root_frame->ti_info = root_ti_info_list.first; - root_frame->leaf = cv_debug_t_get_leaf(root_debug_t, root_leaf_ref.leaf_idx); + root_frame->leaf = cv_debug_t_get_leaf(root_debug_t, lnk_leaf_ref_leaf_idx(root_leaf_ref)); root_frame->ti_source = cv_type_index_source_from_leaf_kind(root_frame->leaf.kind); - root_frame->ti = cv_ti_from_leaf_idx(root_debug_t, root_frame->ti_source, root_leaf_ref.leaf_idx); + root_frame->ti = cv_ti_from_leaf_idx(root_debug_t, root_frame->ti_source, lnk_leaf_ref_leaf_idx(root_leaf_ref)); HashStack *stack = root_frame; while (stack) { @@ -1415,17 +1427,19 @@ lnk_hash_cv_leaf_deep(Arena *arena, CV_TypeIndex ti = memory_read32(ti_ptr); // skip out of bounds indices - if ( ! contains_1u64(input->debug_t_arr[root_leaf_ref.obj_idx].ti_ranges[ti_info->source], ti)) { continue; } + if ( ! contains_1u64(input->debug_t_arr[root_obj_idx].ti_ranges[ti_info->source], ti)) { continue; } // skip hashed types - LNK_LeafRef leaf_ref = lnk_leaf_ref_from_ti(input, root_leaf_ref.obj_idx, ti_info->source, ti); - if (input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] != 0) { continue; } - input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx] = 1; + LNK_LeafRef leaf_ref = lnk_leaf_ref_from_ti(input, root_obj_idx, ti_info->source, ti); + U32 obj_idx = lnk_leaf_ref_obj_idx(leaf_ref); + U32 leaf_idx = lnk_leaf_ref_leaf_idx(leaf_ref); + if (input->debug_h_arr[obj_idx].v[leaf_idx] != 0) { continue; } + input->debug_h_arr[obj_idx].v[leaf_idx] = 1; // recurse down to sub types HashStack *frame = push_array(temp.arena, HashStack, 1); frame->leaf_ref = leaf_ref; - frame->leaf = cv_debug_t_get_leaf(&input->debug_t_arr[leaf_ref.obj_idx], leaf_ref.leaf_idx); + frame->leaf = cv_debug_t_get_leaf(&input->debug_t_arr[obj_idx], leaf_idx); frame->ti_info_list = cv_get_leaf_type_index_offsets(temp.arena, frame->leaf.kind, frame->leaf.data); frame->ti_info = frame->ti_info_list.first; frame->ti = ti; @@ -1447,8 +1461,8 @@ lnk_hash_cv_leaf_deep(Arena *arena, internal CV_TypeIndex lnk_assigned_ti_hash_search(LNK_AssignedTiHash *ht, LNK_CodeViewInput *input, LNK_LeafRef leaf_ref) { - CV_DebugH *debug_h = &input->debug_h_arr[leaf_ref.obj_idx]; - U64 hash = debug_h->v[leaf_ref.leaf_idx]; + CV_DebugH *debug_h = &input->debug_h_arr[lnk_leaf_ref_obj_idx(leaf_ref)]; + U64 hash = debug_h->v[lnk_leaf_ref_leaf_idx(leaf_ref)]; U64 best_idx = hash % ht->cap; U64 idx = best_idx; do { @@ -1472,7 +1486,7 @@ THREAD_POOL_TASK_FUNC(lnk_hash_debug_t_task) Temp temp = temp_begin(task->fixed_arenas[worker_id]); CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, leaf_idx); CV_TypeIndexInfoList ti_list = cv_get_leaf_type_index_offsets(temp.arena, leaf.kind, leaf.data); - lnk_hash_cv_leaf(task->input, (LNK_LeafRef){ obj_idx, leaf_idx }, ti_list, 1); + lnk_hash_cv_leaf(task->input, lnk_leaf_ref_make(obj_idx, leaf_idx), ti_list, 1); temp_end(temp); } ProfEnd(); @@ -1490,7 +1504,7 @@ THREAD_POOL_TASK_FUNC(lnk_hash_debug_t_deep_task) Temp temp = temp_begin(task->fixed_arenas[worker_id]); CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, leaf_idx); CV_TypeIndexInfoList ti_list = cv_get_leaf_type_index_offsets(temp.arena, leaf.kind, leaf.data); - lnk_hash_cv_leaf_deep(temp.arena, task->input, (LNK_LeafRef){ obj_idx, leaf_idx }, ti_list); + lnk_hash_cv_leaf_deep(temp.arena, task->input, lnk_leaf_ref_make(obj_idx, leaf_idx), ti_list); temp_end(temp); } ProfEnd(); @@ -1506,49 +1520,29 @@ THREAD_POOL_TASK_FUNC(lnk_populate_leaf_ht) CV_DebugH *debug_h = &task->input->debug_h_arr[task->pop_obj_idx]; for EachInRange(leaf_idx, task->pop_range[task_id]) { - LNK_LeafRef *bucket = 0; - - // alloc new bucket and assign type ref - if (bucket == 0) { bucket = push_array_no_zero(arena, LNK_LeafRef, 1); } - - // fill bucket - *bucket = (LNK_LeafRef){ .obj_idx = obj_idx, .leaf_idx = leaf_idx }; - - B32 is_inserted_or_updated = 1; - + + LNK_LeafRef leaf_ref = lnk_leaf_ref_make(obj_idx, leaf_idx); CV_LeafHeader *header = cv_debug_t_get_leaf_header(debug_t, leaf_idx); // leaf index -> leaf header CV_LeafKind kind = memory_read16(MemberFromPtr(CV_LeafHeader, header, kind)); // leaf header -> leaf kind CV_TypeIndexSource leaf_source = cv_type_index_source_from_leaf_kind(kind); // leaf kind -> type stream LNK_LeafHashTable *leaf_ht = &task->leaf_ht_arr[leaf_source]; // type stream -> hash table U64 best_idx = debug_h->v[leaf_idx] % leaf_ht->cap; // leaf ref -> hash -> bucket index U64 idx = best_idx; + do { - // load leaf ref - LNK_LeafRef *curr = ins_atomic_ptr_eval(&leaf_ht->bucket_arr[idx]); - - while (curr == 0) { - // exit if leaf ref is not recent - if (curr != 0 && lnk_leaf_ref_compare(*bucket, *curr) >= 0) { - goto exit; - } - - // try to update the bucket - LNK_LeafRef *cmp = ins_atomic_ptr_eval_cond_assign(&leaf_ht->bucket_arr[idx], bucket, curr); + LNK_LeafRef curr = ins_atomic_u64_eval(&leaf_ht->bucket_arr[idx]); + if (curr == LNK_LEAF_REF_NULL) { + LNK_LeafRef cmp = ins_atomic_u64_eval_cond_assign(&leaf_ht->bucket_arr[idx], leaf_ref, curr); if (cmp == curr) { - bucket = 0; goto exit; } - - // another thread updated the bucket -- retry - curr = cmp; } // advance to next bucket idx = ((idx + 1) == leaf_ht->cap ? 0 : (idx + 1)); } while (idx != best_idx); - is_inserted_or_updated = 0; + InvalidPath; exit:; - Assert(is_inserted_or_updated); } } @@ -1559,38 +1553,34 @@ THREAD_POOL_TASK_FUNC(lnk_leaf_dedup_task) U64 obj_idx = task->indices.v[task_id]; CV_DebugT *debug_t = &task->input->debug_t_arr[obj_idx]; CV_DebugH *debug_h = &task->input->debug_h_arr[obj_idx]; + ProfBeginDynamic("dedup in obj 0x%llx (%.*s) leaf count %llu", obj_idx, str8_varg(task->input->obj_arr[obj_idx]->path), debug_t->count); - LNK_LeafRef *bucket = 0; for EachIndex(leaf_idx, debug_t->count) { - // alloc new bucket and assign type ref - if (bucket == 0) { bucket = push_array_no_zero(arena, LNK_LeafRef, 1); } - - // fill bucket - *bucket = (LNK_LeafRef){ .obj_idx = obj_idx, .leaf_idx = leaf_idx }; B32 is_inserted_or_updated = 1; + LNK_LeafRef leaf_ref = lnk_leaf_ref_make(obj_idx, leaf_idx); CV_LeafHeader *header = cv_debug_t_get_leaf_header(debug_t, leaf_idx); // leaf index -> leaf header CV_LeafKind kind = memory_read16(MemberFromPtr(CV_LeafHeader, header, kind)); // leaf header -> leaf kind CV_TypeIndexSource leaf_source = cv_type_index_source_from_leaf_kind(kind); // leaf kind -> type stream LNK_LeafHashTable *leaf_ht = &task->leaf_ht_arr[leaf_source]; // type stream -> hash table U64 best_idx = debug_h->v[leaf_idx] % leaf_ht->cap; // leaf ref -> hash -> bucket index U64 idx = best_idx; + do { // load leaf ref - LNK_LeafRef *curr = ins_atomic_ptr_eval(&leaf_ht->bucket_arr[idx]); + LNK_LeafRef curr = ins_atomic_u64_eval(&leaf_ht->bucket_arr[idx]); - while (curr == 0 || lnk_match_leaf_ref(task->input, *curr, *bucket)) { + while (curr == LNK_LEAF_REF_NULL || lnk_match_leaf_ref(task->input, curr, leaf_ref)) { // exit if leaf ref is not recent - if (curr != 0 && lnk_leaf_ref_compare(*bucket, *curr) >= 0) { + if (curr != LNK_LEAF_REF_NULL && lnk_leaf_ref_compare(leaf_ref, curr) >= 0) { goto exit; } // try to update the bucket - LNK_LeafRef *cmp = ins_atomic_ptr_eval_cond_assign(&leaf_ht->bucket_arr[idx], bucket, curr); + LNK_LeafRef cmp = ins_atomic_u64_eval_cond_assign(&leaf_ht->bucket_arr[idx], leaf_ref, curr); if (cmp == curr) { - bucket = 0; goto exit; } @@ -1601,6 +1591,7 @@ THREAD_POOL_TASK_FUNC(lnk_leaf_dedup_task) // advance to next bucket idx = ((idx + 1) == leaf_ht->cap ? 0 : (idx + 1)); } while (idx != best_idx); + is_inserted_or_updated = 0; exit:; Assert(is_inserted_or_updated); @@ -1618,7 +1609,7 @@ THREAD_POOL_TASK_FUNC(lnk_count_present_buckets_task) LNK_LeafHashTable *ht = &task->leaf_ht_arr[task->ti_source]; for EachInRange(bucket_idx, task->ranges[task_id]) { - if (ht->bucket_arr[bucket_idx] != 0) { + if (ht->bucket_arr[bucket_idx] != LNK_LEAF_REF_NULL) { task->counts[task->ti_source][task_id] += 1; } } @@ -1638,7 +1629,7 @@ THREAD_POOL_TASK_FUNC(lnk_get_present_buckets_task) LNK_LeafRefArray unique_leaf_refs = task->unique_leaf_refs_arr[task->ti_source]; for EachInRange(bucket_idx, task->ranges[task_id]) { - if (ht->bucket_arr[bucket_idx]) { + if (ht->bucket_arr[bucket_idx] != LNK_LEAF_REF_NULL) { unique_leaf_refs.v[cursor++] = ht->bucket_arr[bucket_idx]; } } @@ -1646,162 +1637,6 @@ THREAD_POOL_TASK_FUNC(lnk_get_present_buckets_task) ProfEnd(); } -internal -THREAD_POOL_TASK_FUNC(lnk_leaf_ref_histo_task) -{ - ProfBeginFunction(); - - LNK_MergeTypes *task = raw_task; - Rng1U64 range = task->ranges[task_id]; - U32 *counts_ptr = task->counts_arr[task_id]; - - U32 obj_idx_bit_count_0 = task->obj_idx_bit_count_0; - U32 obj_idx_bit_count_1 = task->obj_idx_bit_count_1; - U32 obj_idx_bit_count_2 = task->obj_idx_bit_count_2; - - MemoryZeroTyped(task->counts_arr[task_id], task->counts_max); - - switch (task->pass_idx) { - case 0: { - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 leaf_digit0 = BitExtract(bucket->leaf_idx, 10, 0); - counts_ptr[leaf_digit0] += 1; - } - } break; - case 1: { - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 leaf_digit1 = BitExtract(bucket->leaf_idx, 11, 10); - counts_ptr[leaf_digit1] += 1; - } - } break; - case 2: { - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 leaf_digit2 = BitExtract(bucket->leaf_idx, 11, 21); - counts_ptr[leaf_digit2] += 1; - } - } break; - - case 3: { - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 digit0 = BitExtract(bucket->obj_idx, obj_idx_bit_count_0, 0); - counts_ptr[digit0] += 1; - } - } break; - case 4: { - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 digit1 = BitExtract(bucket->obj_idx, obj_idx_bit_count_1, obj_idx_bit_count_0); - counts_ptr[digit1] += 1; - } - } break; - case 5: { - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 digit2 = BitExtract(bucket->obj_idx, obj_idx_bit_count_2, obj_idx_bit_count_0 + obj_idx_bit_count_1); - counts_ptr[digit2] += 1; - } - } break; - default: InvalidPath; - } - - ProfEnd(); -} - -internal -THREAD_POOL_TASK_FUNC(lnk_loc_idx_radix_sort_task) -{ - ProfBeginFunction(); - - LNK_MergeTypes *task = raw_task; - Rng1U64 range = task->ranges[task_id]; - U32 *counts_ptr = task->counts_arr[task_id]; - U32 obj_idx_bit_count_0 = task->obj_idx_bit_count_0; - U32 obj_idx_bit_count_1 = task->obj_idx_bit_count_1; - U32 obj_idx_bit_count_2 = task->obj_idx_bit_count_2; - - switch (task->pass_idx) { - // - // Sort items on leaf index - // - case 0: { - ProfBegin("Leaf Sort Low"); - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 leaf_digit0 = BitExtract(bucket->leaf_idx, 10, 0); - task->dst[counts_ptr[leaf_digit0]++] = bucket; - } - ProfEnd(); - } break; - case 1: { - ProfBegin("Leaf Sort Mid"); - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 leaf_digit1 = BitExtract(bucket->leaf_idx, 11, 10); - task->dst[counts_ptr[leaf_digit1]++] = bucket; - } - ProfEnd(); - } break; - case 2: { - ProfBegin("Leaf Sort High"); - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 leaf_digit2 = BitExtract(bucket->leaf_idx, 11, 21); - task->dst[counts_ptr[leaf_digit2]++] = bucket; - } - ProfEnd(); - } break; - - // - // Sort items on obj and type server index - // - case 3: { - ProfBegin("Obj Sort Low"); - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 digit0 = BitExtract(bucket->obj_idx, obj_idx_bit_count_0, 0); - task->dst[counts_ptr[digit0]++] = bucket; - } - ProfEnd(); - } break; - case 4: { - ProfBegin("Obj Sort Mid"); - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 digit1 = BitExtract(bucket->obj_idx, obj_idx_bit_count_1, obj_idx_bit_count_0); - task->dst[counts_ptr[digit1]++] = bucket; - } - ProfEnd(); - } break; - case 5: { - ProfBegin("Obj Sort High"); - for EachInRange(i, range) { - LNK_LeafRef *bucket = task->src[i]; - U64 digit2 = BitExtract(bucket->obj_idx, obj_idx_bit_count_2, obj_idx_bit_count_0 + obj_idx_bit_count_1); - Assert(counts_ptr[digit2] != max_U32); - task->dst[counts_ptr[digit2]++] = bucket; - } - ProfEnd(); - } break; - - default: InvalidPath; - } - - ProfEnd(); -} - -internal void -lnk_leaf_ref_array_sort(TP_Context *tp, LNK_CodeViewInput *input, LNK_LeafRefArray arr, U64 debug_t_count) -{ - Temp scratch = scratch_begin(0,0); - ProfBeginDynamic("Leaf Sort [Leaf Count: %.*s]", str8_varg(str8_from_count(scratch.arena, arr.count))); - scratch_end(scratch); - ProfEnd(); -} - internal THREAD_POOL_TASK_FUNC(lnk_assign_type_indices_task) { @@ -1814,10 +1649,10 @@ THREAD_POOL_TASK_FUNC(lnk_assign_type_indices_task) CV_DebugH *debug_h_arr = task->input->debug_h_arr; for EachInRange(i, task->ranges[task_id]) { - LNK_LeafRef *leaf_ref = unique_leaf_refs.v[i]; + LNK_LeafRef leaf_ref = unique_leaf_refs.v[i]; CV_TypeIndex type_index = min_type_index + i; - U64 hash = debug_h_arr[leaf_ref->obj_idx].v[leaf_ref->leaf_idx]; + U64 hash = debug_h_arr[lnk_leaf_ref_obj_idx(leaf_ref)].v[lnk_leaf_ref_leaf_idx(leaf_ref)]; U64 best_idx = hash % assigned->cap; U64 idx = best_idx; @@ -1910,11 +1745,12 @@ THREAD_POOL_TASK_FUNC(lnk_cv_patcher_leaves_task) Arena *fixed_arena = task->fixed_arenas[task_id]; for EachInRange(leaf_ref_idx, range) { Temp temp = temp_begin(fixed_arena); - LNK_LeafRef *patch = task->unique_leaf_refs_arr[task->ti_source].v[leaf_ref_idx]; - CV_DebugT *debug_t = &task->input->debug_t_arr[patch->obj_idx]; - CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, patch->leaf_idx); + LNK_LeafRef patch = task->unique_leaf_refs_arr[task->ti_source].v[leaf_ref_idx]; + U32 obj_idx = lnk_leaf_ref_obj_idx(patch); + CV_DebugT *debug_t = &task->input->debug_t_arr[obj_idx]; + CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, lnk_leaf_ref_leaf_idx(patch)); CV_TypeIndexInfoList ti_info_list = cv_get_leaf_type_index_offsets(temp.arena, leaf.kind, leaf.data); - lnk_fixup_cv_type_indices(task, patch->obj_idx, leaf.data, ti_info_list); + lnk_fixup_cv_type_indices(task, obj_idx, leaf.data, ti_info_list); temp_end(temp); } ProfEnd(); @@ -1926,9 +1762,9 @@ THREAD_POOL_TASK_FUNC(lnk_unbucket_raw_leaves_task) LNK_MergeTypes *task = raw_task; Rng1U64 range = task->ranges[task_id]; for EachInRange(i, range) { - LNK_LeafRef leaf_ref = *task->unique_leaf_refs_arr[task->ti_source].v[i]; - CV_DebugT *debug_t = &task->input->debug_t_arr[leaf_ref.obj_idx]; - String8 raw_leaf = cv_debug_t_get_raw_leaf(debug_t, leaf_ref.leaf_idx); + LNK_LeafRef leaf_ref = task->unique_leaf_refs_arr[task->ti_source].v[i]; + CV_DebugT *debug_t = &task->input->debug_t_arr[lnk_leaf_ref_obj_idx(leaf_ref)]; + String8 raw_leaf = cv_debug_t_get_raw_leaf(debug_t, lnk_leaf_ref_leaf_idx(leaf_ref)); task->result.v[task->ti_source][i] = raw_leaf.str; } } @@ -1939,10 +1775,12 @@ THREAD_POOL_TASK_FUNC(lnk_unbucket_hashes_task) LNK_MergeTypes *task = raw_task; Rng1U64 range = task->ranges[task_id]; for EachInRange(i, range) { - LNK_LeafRef leaf_ref = *task->unique_leaf_refs_arr[task->ti_source].v[i]; - CV_DebugT *debug_t = &task->input->debug_t_arr[leaf_ref.obj_idx]; - String8 raw_leaf = cv_debug_t_get_raw_leaf(debug_t, leaf_ref.leaf_idx); - task->result.hashes[task->ti_source][i] = task->input->debug_h_arr[leaf_ref.obj_idx].v[leaf_ref.leaf_idx]; + LNK_LeafRef leaf_ref = task->unique_leaf_refs_arr[task->ti_source].v[i]; + U32 obj_idx = lnk_leaf_ref_obj_idx(leaf_ref); + U32 leaf_idx = lnk_leaf_ref_leaf_idx(leaf_ref); + CV_DebugT *debug_t = &task->input->debug_t_arr[obj_idx]; + String8 raw_leaf = cv_debug_t_get_raw_leaf(debug_t, leaf_idx); + task->result.hashes[task->ti_source][i] = task->input->debug_h_arr[obj_idx].v[leaf_idx]; } } @@ -2021,7 +1859,7 @@ THREAD_POOL_TASK_FUNC(lnk_build_obj_ti_map) for EachIndex(leaf_idx, debug_t->count) { CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, leaf_idx); CV_TypeIndexSource source = cv_type_index_source_from_leaf_kind(leaf.kind); - LNK_LeafRef leaf_ref = { obj_idx, leaf_idx }; + LNK_LeafRef leaf_ref = lnk_leaf_ref_make(obj_idx, leaf_idx); LNK_AssignedTiHash *assigned = &task->assigned_ti_arr[source]; obj_ti_map[leaf_idx] = lnk_assigned_ti_hash_search(assigned, input, leaf_ref); } @@ -2110,7 +1948,8 @@ lnk_merge_types(TP_Context *tp, TP_Arena *tp_temp, LNK_CodeViewInput *input, LNK task.leaf_ht_arr[ti_source].cap = total_count; task.leaf_ht_arr[ti_source].cap = 1 + ((task.leaf_ht_arr[ti_source].cap * 13) / 10); // * 1.3 - task.leaf_ht_arr[ti_source].bucket_arr = push_array(scratch.arena, LNK_LeafRef *, task.leaf_ht_arr[ti_source].cap); + task.leaf_ht_arr[ti_source].bucket_arr = push_array_no_zero(scratch.arena, LNK_LeafRef, task.leaf_ht_arr[ti_source].cap); + MemorySet(task.leaf_ht_arr[ti_source].bucket_arr, 0xff, sizeof(LNK_LeafRef) * task.leaf_ht_arr[ti_source].cap); #if PROFILE_TELEMETRY tmMessage(0, TMMF_ICON_NOTE, "%.*s Bucket Count: %.*s", str8_varg(cv_string_from_type_index_source(ti_source)), str8_varg(str8_from_count(scratch.arena, task.leaf_ht_arr[ti_source].cap))); @@ -2166,90 +2005,13 @@ lnk_merge_types(TP_Context *tp, TP_Arena *tp_temp, LNK_CodeViewInput *input, LNK tp_for_parallel_prof(tp, 0, tp->worker_count, lnk_count_present_buckets_task, &task, "Count present buckets"); task.unique_leaf_refs_arr[ti_source].count = sum_array_u64(tp->worker_count, task.counts[ti_source]); - task.unique_leaf_refs_arr[ti_source].v = push_array_no_zero(scratch.arena, LNK_LeafRef *, task.unique_leaf_refs_arr[ti_source].count); + task.unique_leaf_refs_arr[ti_source].v = push_array_no_zero(scratch.arena, LNK_LeafRef, task.unique_leaf_refs_arr[ti_source].count); task.offsets[ti_source] = offsets_from_counts_array_u64(scratch.arena, task.counts[ti_source], tp->worker_count); tp_for_parallel_prof(tp, 0, tp->worker_count, lnk_get_present_buckets_task, &task, "Copy present buckets"); // sort output leaves based on { location index, leaf index } to guarantee determinism - { - LNK_LeafRefArray arr = task.unique_leaf_refs_arr[ti_source]; - if (arr.count > 140000) { - ProfBegin("Radix"); - - U32 obj_idx_max_bits = 32 - clz32(input->count); - - task.obj_idx_bit_count_0 = Clamp(0, (S32)obj_idx_max_bits - 21, 11); - task.obj_idx_bit_count_1 = Clamp(0, (S32)obj_idx_max_bits - 10, 11); - task.obj_idx_bit_count_2 = Clamp(0, (S32)obj_idx_max_bits, 10); - task.counts_max = (1 << 11); - task.ranges = tp_divide_work(scratch.arena, arr.count, tp->worker_count); - task.dst = push_array_no_zero(scratch.arena, LNK_LeafRef *, arr.count); - task.src = arr.v; - - ProfBegin("Push Counts"); - task.counts_arr = push_array_no_zero(scratch.arena, U32 *, tp->worker_count); - for (U64 i = 0; i < tp->worker_count; ++i) { - // zero-out happens in histogram step - task.counts_arr[i] = push_array_no_zero(scratch.arena, U32, task.counts_max); - } - ProfEnd(); - - for (task.pass_idx = 0; task.pass_idx < 6; ++task.pass_idx) { - ProfBeginDynamic("Pass: %u", task.pass_idx); - - ProfBegin("Histo"); - tp_for_parallel(tp, 0, tp->worker_count, lnk_leaf_ref_histo_task, &task); - ProfEnd(); - - B32 is_range_not_empty = 0; - for (U64 task_id = 0; task_id < tp->worker_count; ++task_id) { - is_range_not_empty = task.counts_arr[task_id][0] != dim_1u64(task.ranges[task_id]); - if (is_range_not_empty) { - break; - } - } - - ProfBegin("Counts -> Offsets"); - { - U64 digit_cursor = 0; - for EachIndex(digit_idx, task.counts_max) { - for EachIndex(task_id, tp->worker_count) { - U64 count = task.counts_arr[task_id][digit_idx]; - task.counts_arr[task_id][digit_idx] = digit_cursor; - digit_cursor += count; - } - } - Assert(digit_cursor == arr.count); - } - ProfEnd(); - - ProfBegin("Sort"); - tp_for_parallel(tp, 0, tp->worker_count, lnk_loc_idx_radix_sort_task, &task); - Swap(LNK_LeafRef **, task.src, task.dst); - ProfEnd(); - - ProfEnd(); - } - - if (task.src != arr.v) { - MemoryCopyTyped(arr.v, task.dst, arr.count); - } - - ProfEnd(); - } else { - ProfBegin("Radsort"); - radsort(arr.v, arr.count, lnk_leaf_ref_is_before); - ProfEnd(); - } - -#if 0 - for (U64 i = 1; i < arr.count; ++i) { - AssertAlways(arr.v[i-1]->obj_idx <= arr.v[i]->obj_idx); - if (arr.v[i-1]->obj_idx == arr.v[i]->obj_idx) { - AssertAlways(arr.v[i-1]->obj_idx <= arr.v[i]->obj_idx); - } - } -#endif + ProfScope("Radix Sort") { + u64_array_sort_radix_parallel(tp, task.unique_leaf_refs_arr[ti_source].count, task.unique_leaf_refs_arr[ti_source].v); } } @@ -2271,7 +2033,7 @@ lnk_merge_types(TP_Context *tp, TP_Arena *tp_temp, LNK_CodeViewInput *input, LNK // unique extraction is complete, so the dedup bucket slots can back the // direct hash table without increasing peak memory Assert(task.assigned_ti_arr[ti_source].cap <= task.leaf_ht_arr[ti_source].cap); - task.assigned_ti_arr[ti_source].hash_arr = (U64 *)task.leaf_ht_arr[ti_source].bucket_arr; + task.assigned_ti_arr[ti_source].hash_arr = task.leaf_ht_arr[ti_source].bucket_arr; task.min_type_indices[ti_source] = CV_MinComplexTypeIndex; task.ranges = tp_divide_work(scratch.arena, task.unique_leaf_refs_arr[ti_source].count, tp->worker_count); diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index 29412a5b..573fc3b5 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -120,13 +120,15 @@ typedef struct //////////////////////////////// // Type Merging -typedef struct { U32 obj_idx; U32 leaf_idx; } LNK_LeafRef; -typedef struct { U64 count; LNK_LeafRef **v; } LNK_LeafRefArray; +typedef U64 LNK_LeafRef; +typedef struct { U64 count; LNK_LeafRef *v; } LNK_LeafRefArray; + +#define LNK_LEAF_REF_NULL max_U64 typedef struct { - U64 cap; - LNK_LeafRef **bucket_arr; + U64 cap; + LNK_LeafRef *bucket_arr; } LNK_LeafHashTable; typedef struct @@ -181,16 +183,6 @@ typedef struct U64 *counts [CV_TypeIndexSource_COUNT]; U64 *offsets[CV_TypeIndexSource_COUNT]; - // leaf ref radix sort - U64 obj_idx_bit_count_0; - U64 obj_idx_bit_count_1; - U64 obj_idx_bit_count_2; - U64 counts_max; - U32 **counts_arr; - LNK_LeafRef **dst; - LNK_LeafRef **src; - U64 pass_idx; - CV_TypeIndex min_type_indices [CV_TypeIndexSource_COUNT]; LNK_LeafRefArray unique_leaf_refs_arr[CV_TypeIndexSource_COUNT]; @@ -270,11 +262,9 @@ internal B32 lnk_rrt_from_string (Arena *arena, String8 rrt_data, St internal LNK_CodeViewInput lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, U64 objs_count, LNK_Obj **objs, LNK_RRT_Array rrt_input); internal int lnk_leaf_ref_compare (LNK_LeafRef a, LNK_LeafRef b); -internal int lnk_leaf_ref_is_before (void *raw_a, void *raw_b); internal B32 lnk_match_leaf_ref (LNK_CodeViewInput *input, LNK_LeafRef a, LNK_LeafRef b); internal U64 lnk_hash_cv_leaf (LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInfoList ti_info_list, B32 discard_cycles); internal void lnk_hash_cv_leaf_deep (Arena *arena, LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInfoList ti_info_list); -internal LNK_LeafRef * lnk_leaf_hash_table_insert_or_update(LNK_LeafHashTable *leaf_ht, LNK_CodeViewInput *input, CV_DebugH *hashes, U64 hash, LNK_LeafRef *new_bucket); internal CV_TypeIndex lnk_assigned_ti_hash_search (LNK_AssignedTiHash *ht, LNK_CodeViewInput *input, LNK_LeafRef leaf_ref); internal LNK_MergedTypes lnk_merge_types (TP_Context *tp, TP_Arena *tp_temp, LNK_CodeViewInput *input, LNK_MergeTypeFlags merge_flags); internal void lnk_replace_type_names_with_hashes (TP_Context *tp, TP_Arena *arena, U64 leaf_count, U8 **leaf_arr, LNK_TypeNameHashMode mode, U64 hash_length, String8 map_name); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 99b0b0ad..1c6cad10 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -7146,6 +7146,48 @@ TEST(psi_addr_map_radix_sort) tp_release(tp); } +TEST(u64_array_radix_sort_parallel) +{ + TP_Context *tp = tp_alloc(arena, 1, 1, str8_zero()); + + U64 small_values[] = { max_U64, 0, 7, 1, 7, 0x100000000ull, 2 }; + u64_array_sort_radix_parallel(tp, ArrayCount(small_values), small_values); + for (U64 i = 1; i < ArrayCount(small_values); i += 1) { + T_Ok(small_values[i-1] <= small_values[i]); + } + + U64 count = 200003; + U64 *values = push_array_no_zero(arena, U64, count); + U64 state = 0x9e3779b97f4a7c15ull; + U64 sum_before = 0; + U64 xor_before = 0; + for EachIndex(i, count) { + state ^= state >> 12; + state ^= state << 25; + state ^= state >> 27; + values[i] = state * 0x2545f4914f6cdd1dull; + if ((i % 97) == 0) { values[i] = 0; } + if ((i % 193) == 0) { values[i] = max_U64; } + if ((i % 389) == 0) { values[i] = 0x100000001ull; } + sum_before += values[i]; + xor_before ^= values[i]; + } + + u64_array_sort_radix_parallel(tp, count, values); + U64 sum_after = 0; + U64 xor_after = 0; + B32 is_sorted = 1; + for EachIndex(i, count) { + if (i > 0 && values[i-1] > values[i]) { is_sorted = 0; } + sum_after += values[i]; + xor_after ^= values[i]; + } + T_Ok(is_sorted); + T_Ok(sum_before == sum_after); + T_Ok(xor_before == xor_after); + tp_release(tp); +} + TEST(pdbstripped) { String8 debug_obj; diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 9d9e58e8..1b3a6915 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -91,6 +91,7 @@ #include "linker/base_ext/base_arena.h" #include "linker/base_ext/base_arrays.h" #include "linker/thread_pool/thread_pool.h" +#include "linker/base_ext/base_radix_sort.h" #include "linker/codeview_ext/codeview.h" #include "linker/pdb_ext/msf_builder.h" #include "linker/pdb_ext/pdb.h" @@ -173,6 +174,7 @@ #include "linker/base_ext/base_arrays.c" #include "linker/base_ext/base_crc32.c" #include "linker/thread_pool/thread_pool.c" +#include "linker/base_ext/base_radix_sort.c" #include "linker/codeview_ext/codeview.c" #include "linker/pdb_ext/msf_builder.c" #include "linker/pdb_ext/pdb.c" From cf67aca7da5fc8c9d704131c86dbcf5a8e7dae65 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 00:42:50 -0700 Subject: [PATCH 078/251] keep COFF input metadata immutable --- src/linker/lnk.c | 4 ++-- src/linker/lnk_lib.c | 12 ++++-------- src/linker/lnk_obj.c | 9 ++++++--- src/linker/lnk_obj.h | 1 + src/linker/tests/linker_tests.c | 7 ++++++- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 0da81a8f..67f4626c 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -4940,7 +4940,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_virtual_offsets_and_sizes_in_obj_section_headers LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Patch Virtual Offset And Size In Section Headers [%S]", obj->path); - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; + COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { COFF_SectionHeader *sect_header = §ion_table[sect_idx]; if (~obj->section_flags[sect_idx] & COFF_SectionFlag_LnkRemove) { @@ -4961,7 +4961,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_file_offsets_and_sizes_in_obj_section_headers_ta LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Patch File Offsets And Sizes In Obj Section Headers [%S]", obj->path); - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; + COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { COFF_SectionHeader *sect_header = §ion_table[sect_idx]; COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; diff --git a/src/linker/lnk_lib.c b/src/linker/lnk_lib.c index 7f546e88..50bc4bd2 100644 --- a/src/linker/lnk_lib.c +++ b/src/linker/lnk_lib.c @@ -71,23 +71,19 @@ lnk_lib_from_data(Arena *arena, String8 data, String8 path, U64 input_idx, LNK_L symbol_count = first_member.symbol_count; - // convert big endian offsets - for (U32 offset_idx = 0; offset_idx < symbol_count; offset_idx += 1) { - first_member.member_offsets[offset_idx] = from_be_u32(first_member.member_offsets[offset_idx]); - } - // compress member offsets to match those from the second header { HashTable *member_off_ht = hash_table_init(scratch.arena, (U64)((F64)first_member.symbol_count * 1.3)); for EachIndex(symbol_idx, symbol_count) { - if (!hash_table_search_u32_u32(member_off_ht, first_member.member_offsets[symbol_idx], 0)) { - hash_table_push_u32_u32(scratch.arena, member_off_ht, first_member.member_offsets[symbol_idx], member_off_ht->count); + U32 member_off = from_be_u32(first_member.member_offsets[symbol_idx]); + if (!hash_table_search_u32_u32(member_off_ht, member_off, 0)) { + hash_table_push_u32_u32(scratch.arena, member_off_ht, member_off, member_off_ht->count); } } symbol_indices = push_array(arena, U16, first_member.symbol_count); for EachIndex(symbol_idx, first_member.symbol_count) { - U32 member_off = first_member.member_offsets[symbol_idx]; + U32 member_off = from_be_u32(first_member.member_offsets[symbol_idx]); U32 member_off_idx = 0; if (!hash_table_search_u32_u32(member_off_ht, member_off, &member_off_idx)) { InvalidPath; diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 89819113..1259157d 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -93,6 +93,9 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) lnk_error_input_obj(LNK_Error_IllData, input, "corrupted file, unable to read string table"); } + COFF_SectionHeader *section_headers = push_array_no_zero(arena, COFF_SectionHeader, header.section_count_no_null); + MemoryCopy(section_headers, raw_coff_section_table.str, raw_coff_section_table.size); + // // section table pass // - error check headers fields @@ -381,6 +384,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) obj->data = input->data; obj->path = push_str8_copy(arena, input->path); obj->header = header; + obj->section_headers = section_headers; obj->section_flags = section_flags; obj->symbols = symbols; obj->comdats = comdats; @@ -670,7 +674,7 @@ lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *s internal COFF_SectionHeader * lnk_coff_section_table_from_obj(LNK_Obj *obj) { - return (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; + return obj->section_headers; } internal U64 @@ -773,8 +777,7 @@ lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number) { Assert(section_number > 0); U64 sect_idx = section_number - 1; - COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->data, obj->header.section_table_range.min, dim_1u64(obj->header.section_table_range)); - return §ion_table[sect_idx]; + return &obj->section_headers[sect_idx]; } internal force_inline U64 diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 807c21a1..9eb6981b 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -33,6 +33,7 @@ typedef struct LNK_Obj String8 data; COFF_FileHeaderInfo header; + COFF_SectionHeader *section_headers; COFF_SectionFlags *section_flags; LNK_ObjSymbolArray symbols; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 1c6cad10..c4597d3d 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -5085,8 +5085,13 @@ TEST(first_member_header) })); T_Ok(t_write_entry_obj()); - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe test.lib entry.obj /include:1 /include:2 /include:3 /include:4 /include:5 /include:6 /include:7 /include:8 /include:9"); + String8 lib_before = t_read_file(arena, str8_lit("test.lib")); + String8 obj_before = t_read_file(arena, str8_lit("entry.obj")); + + t_invoke_linkerf("/RAD_MEMORY_MAP_FILES:READ_WRITE /subsystem:console /entry:entry /out:a.exe test.lib entry.obj /include:1 /include:2 /include:3 /include:4 /include:5 /include:6 /include:7 /include:8 /include:9"); T_Ok(g_last_exit_code == 0); + T_Ok(str8_match(lib_before, t_read_file(arena, str8_lit("test.lib")), 0)); + T_Ok(str8_match(obj_before, t_read_file(arena, str8_lit("entry.obj")), 0)); } TEST(second_member_header) From 646ff97b3c047d9b9843f64029e44fbae8cef2d7 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 01:19:50 -0700 Subject: [PATCH 079/251] centralize canonical COFF object data --- src/linker/lnk.c | 198 ++++++++++++++--------------- src/linker/lnk_debug_info.c | 43 +++---- src/linker/lnk_obj.c | 214 +++++++++++++++++++------------- src/linker/lnk_obj.h | 74 ++++++----- src/linker/lnk_symbol_table.c | 16 +-- src/linker/tests/linker_tests.c | 14 ++- 6 files changed, 306 insertions(+), 253 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 67f4626c..3e8d8131 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1221,8 +1221,8 @@ lnk_load_objs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer * // if machine type was unspecified on the command line, derive it from obj file if (config->machine == COFF_MachineType_Unknown) { for EachIndex(obj_idx, new_input_objs.count) { - if (new_objs[obj_idx].data.header.machine != COFF_MachineType_Unknown) { - config->machine = new_objs[obj_idx].data.header.machine; + if (new_objs[obj_idx].data.coff.header.machine != COFF_MachineType_Unknown) { + config->machine = new_objs[obj_idx].data.coff.header.machine; break; } } @@ -1335,7 +1335,6 @@ lnk_load_inputs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer lnk_obj_list_push_node(&link->objs, include_obj); } } - ProfEnd(); } @@ -2383,8 +2382,8 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer sect_filter |= COFF_SectionFlag_LnkRemove; // skip sections that are discarded from the output if ( ! lnk_do_debug_info(config)) { sect_filter |= LNK_SECTION_FLAG_DEBUG; } // on /DEBUG:NONE linker never invokes the debug info machinery - for EachIndex(sect_idx, obj->header.section_count_no_null) { - if (obj->section_flags[sect_idx] & sect_filter) { continue; } + for EachIndex(sect_idx, obj->coff.sections.count) { + if (obj->coff.sections.flags[sect_idx] & sect_filter) { continue; } // unpack section and relocations COFF_SectionHeader *section_header = §ion_table[sect_idx]; @@ -2524,7 +2523,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer // if (lnk_get_log_status(LNK_Log_InputObj)) { U64 total_input_size = 0; - for (LNK_ObjNode *obj_n = link->objs.first; obj_n != 0; obj_n = obj_n->next) { total_input_size += obj_n->data.data.size; } + for (LNK_ObjNode *obj_n = link->objs.first; obj_n != 0; obj_n = obj_n->next) { total_input_size += obj_n->data.coff.data.size; } lnk_log(LNK_Log_InputObj, "[Total Obj Input Size %M]", total_input_size); } if (lnk_get_log_status(LNK_Log_InputLib)) { @@ -2607,7 +2606,7 @@ lnk_obj_indices_from_section_counts(Arena *arena, U64 worker_count, LNK_Obj **ob } } - worker_section_counts[min_worker_idx] += objs[obj_idx]->header.section_count_no_null; + worker_section_counts[min_worker_idx] += objs[obj_idx]->coff.sections.count; worker_obj_counts[min_worker_idx] += 1; } @@ -2629,7 +2628,7 @@ lnk_obj_indices_from_section_counts(Arena *arena, U64 worker_count, LNK_Obj **ob U32Array *worker_obj_indices = &obj_indices[min_worker_idx]; worker_obj_indices->v[worker_obj_counts[min_worker_idx]++] = (U32)obj_idx; worker_obj_indices->count += 1; - worker_section_counts[min_worker_idx] += objs[obj_idx]->header.section_count_no_null; + worker_section_counts[min_worker_idx] += objs[obj_idx]->coff.sections.count; } scratch_end(scratch); @@ -2706,10 +2705,10 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; - is_live[obj_idx] = push_array(scratch.arena, U8, obj->header.section_count_no_null + 1); + is_live[obj_idx] = push_array(scratch.arena, U8, obj->coff.sections.count + 1); - for EachIndex(sect_idx, obj->header.section_count_no_null) { - is_live[obj_idx][sect_idx + 1] = !(obj->section_flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT); + for EachIndex(sect_idx, obj->coff.sections.count) { + is_live[obj_idx][sect_idx + 1] = !(obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT); } } } @@ -2740,9 +2739,9 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { U32 section_number = sect_idx+1; - COFF_SectionFlags section_flags = obj->section_flags[sect_idx]; + COFF_SectionFlags section_flags = obj->coff.sections.flags[sect_idx]; // is section eligible for walking? if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } @@ -2807,7 +2806,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachNode(section_n, U32Node, associated_sections.first) { U32 section_number = section_n->data; - COFF_SectionFlags section_flags = ref_symbol.obj->section_flags[section_number-1]; + COFF_SectionFlags section_flags = ref_symbol.obj->coff.sections.flags[section_number-1]; // on first section visit, set live flag and enqueue section U8 was_visited = ins_atomic_u8_eval_assign(&is_live[ref_symbol.obj->input_idx][section_number], 1); @@ -2874,11 +2873,11 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { U32 section_number = sect_idx+1; COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); if ( ! is_live[obj->input_idx][section_number]) { - obj->section_flags[sect_idx] |= COFF_SectionFlag_LnkRemove; + obj->coff.sections.flags[sect_idx] |= COFF_SectionFlag_LnkRemove; } } } @@ -2891,24 +2890,24 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { U32 section_number = sect_idx+1; COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); U64 stat_kind = Stat_Null; - if (obj->section_flags[sect_idx] & LNK_SECTION_FLAG_DEBUG) { stat_kind = Stat_Debug; } - else if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntCode) { stat_kind = Stat_Code; } + if (obj->coff.sections.flags[sect_idx] & LNK_SECTION_FLAG_DEBUG) { stat_kind = Stat_Debug; } + else if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntCode) { stat_kind = Stat_Code; } else { stat_kind = Stat_Data; } if (is_live[obj->input_idx][section_number]) { stats[stat_kind].live_count += 1; - if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { + if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { stats[stat_kind].live_vsize += section_header->vsize; } else { stats[stat_kind].live_fsize += section_header->fsize; } } else { - if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { + if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { stats[stat_kind].vsize += section_header->vsize; } else { stats[stat_kind].fsize += section_header->fsize; @@ -2979,16 +2978,16 @@ lnk_icf_scope_from_section(LNK_Obj *obj, U32 sect_idx) // * section flags filter * COFF_SectionFlags expected_flags = COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_MemRead; COFF_SectionFlags exclude_flags = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_MemWrite | LNK_SECTION_FLAG_NOICF; - if ((obj->section_flags[sect_idx] & expected_flags) != expected_flags || (obj->section_flags[sect_idx] & exclude_flags) != 0) { + if ((obj->coff.sections.flags[sect_idx] & expected_flags) != expected_flags || (obj->coff.sections.flags[sect_idx] & exclude_flags) != 0) { goto exit; } - if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntCode) { + if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntCode) { result = LNK_ICF_Scope_Code; goto exit; } - if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntInitializedData) { + if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntInitializedData) { U64 section_number = sect_idx + 1; COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); String8 section_name = str8_cstring_capped(section_header->name, section_header->name + sizeof(section_header->name)); @@ -3043,7 +3042,7 @@ lnk_icf_scope_from_section(LNK_Obj *obj, U32 sect_idx) } exit:; - Assert(result == LNK_ICF_Scope_Null || obj->comdats[sect_idx] != max_U32); // TODO: are symlinks on COMDATs optional? + Assert(result == LNK_ICF_Scope_Null || obj->coff.sections.comdats[sect_idx] != max_U32); // TODO: are symlinks on COMDATs optional? return result; } @@ -3118,39 +3117,36 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) if (task->config->llvm_addrsig == LNK_SwitchState_Yes) { ProfBegin("Flag significant sections"); + // .llvm_addrsig is an array of ULEB128 symbol indices, which mark sections // whose addresses are significant for EachIndex(i, task->obj_indices[task_id].count) { U64 obj_idx = task->obj_indices[task_id].v[i]; LNK_Obj *obj = task->objs[obj_idx]; - if (obj->llvm_addrsig_sect_idx >= obj->header.section_count_no_null) { continue; } - - String8 symbol_table = lnk_coff_symbol_table_from_obj(obj); - String8 string_table = lnk_coff_string_table_from_obj(obj); - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, obj->llvm_addrsig_sect_idx + 1); - String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize)); + if (obj->coff.llvm_addrsig_sect_idx >= obj->coff.sections.count) { continue; } // parse symbol indices and mark selected sections with NOICF flag, // the selected symbols maybe undefined/weak which need to be resolved + String8 section_data = lnk_obj_section_data_from_sect_idx(obj, obj->coff.llvm_addrsig_sect_idx); for (U64 off = 0; off < section_data.size;) { U64 symbol_off = off; U64 symbol_idx = 0; off += str8_deserial_read_uleb128(section_data, off, &symbol_idx); if (symbol_off == off) { break; } - if (symbol_idx < obj->header.symbol_count) { + if (symbol_idx < obj->coff.header.symbol_count) { LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = symbol_idx }; B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref); if (is_symbol_found) { COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(target_ref.obj, target_ref.symbol_idx); if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular) { - target_ref.obj->section_flags[symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; + target_ref.obj->coff.sections.flags[symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; } } else { COFF_ParsedSymbol original_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); if (coff_interp_from_parsed_symbol(original_symbol) == COFF_SymbolValueInterp_Regular) { - obj->section_flags[original_symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; + obj->coff.sections.flags[original_symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; } else { lnk_log(LNK_Log_Debug, "%S: .llvm_addrsig: contains an unresolved symbol index 0x%x at offset 0x%x", lnk_loc_from_obj(scratch.arena, obj), symbol_idx, symbol_off); } @@ -3179,7 +3175,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) for EachIndex(i, task->obj_indices[task_id].count) { U64 obj_idx = task->obj_indices[task_id].v[i]; LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { if (lnk_icf_scope_from_section(obj, sect_idx)) { contrib_counts[obj_idx] += 1; } @@ -3209,13 +3205,13 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 noncontrib_count = 0; for EachIndex(obj_idx, task->objs_count) { shared.noncontrib_offsets[obj_idx] = noncontrib_count; - noncontrib_count += objs[obj_idx]->header.section_count_no_null - contrib_counts[obj_idx]; + noncontrib_count += objs[obj_idx]->coff.sections.count - contrib_counts[obj_idx]; } shared.color_map = push_array(scratch.arena, U64 *, task->objs_count); for EachIndex(obj_idx, task->objs_count) { LNK_Obj *obj = objs[obj_idx]; - shared.color_map[obj_idx] = push_array(scratch.arena, U64, obj->header.section_count_no_null); + shared.color_map[obj_idx] = push_array(scratch.arena, U64, obj->coff.sections.count); } shared.contrib_offsets = offsets_from_counts_array_u64(scratch.arena, contrib_counts, task->objs_count); @@ -3240,7 +3236,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) LNK_Obj *obj = objs[obj_idx]; U64 cursor = 0; U64 noncontrib_cursor = 0; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { LNK_ICF_Scope scope = lnk_icf_scope_from_section(obj, sect_idx); if (scope == LNK_ICF_Scope_Null) { // assign colors in object order to avoid a contended atomic allocator @@ -3278,7 +3274,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 reloc_cursor = 0; for EachNode(associated_n, U32Node, associated_sections.first) { COFF_SectionHeader *associated_header = lnk_coff_section_header_from_section_number(obj, associated_n->data); - String8 associated_data = str8_substr(obj->data, r1u64s(associated_header->foff, associated_header->fsize)); + String8 associated_data = lnk_obj_section_data_from_sect_idx(obj, associated_n->data - 1); COFF_RelocArray associated_relocs = lnk_coff_relocs_from_section_header(obj, associated_header); lnk_hasher_update_string(&hasher, associated_data); @@ -3306,12 +3302,12 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U32 target_sect = target_symbol.section_number; // use the selected COMDAT leader so equivalent targets hash alike - if (target_sect != 0 && target_sect <= target_obj->header.section_count_no_null && - target_obj->section_flags[target_sect - 1] & COFF_SectionFlag_LnkCOMDAT) { + if (target_sect != 0 && target_sect <= target_obj->coff.sections.count && + target_obj->coff.sections.flags[target_sect - 1] & COFF_SectionFlag_LnkCOMDAT) { LNK_ObjSymbolRef leader_ref = {0}; if (lnk_obj_get_comdat_symlink(target_obj, target_sect, &leader_ref)) { COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(leader_ref.obj, leader_ref.symbol_idx); - if (leader_symbol.section_number != 0 && leader_symbol.section_number <= leader_ref.obj->header.section_count_no_null) { + if (leader_symbol.section_number != 0 && leader_symbol.section_number <= leader_ref.obj->coff.sections.count) { target_obj = leader_ref.obj; target_sect = leader_symbol.section_number; } @@ -3606,8 +3602,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // TODO: double-check if discarded section headers should be updated with leaders' alignment, // leaders at this point are resolved - U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->section_flags[contrib->sect_idx]); - COFF_SectionFlags *leader_flags = &leader_obj->section_flags[leader->sect_idx]; + U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->coff.sections.flags[contrib->sect_idx]); + COFF_SectionFlags *leader_flags = &leader_obj->coff.sections.flags[leader->sect_idx]; for (COFF_SectionFlags old_flags = ins_atomic_u32_eval((U32 *)leader_flags);;) { U64 leader_align = coff_align_size_from_section_flags(old_flags); if (leader_align >= contrib_align) { break; } @@ -3622,10 +3618,10 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) } // remove folded section from the image - contrib_obj->section_flags[contrib->sect_idx] |= COFF_SectionFlag_LnkRemove; + contrib_obj->coff.sections.flags[contrib->sect_idx] |= COFF_SectionFlag_LnkRemove; // update folded section COMDAT symlink to point to the leader section - contrib_obj->symlinks[contrib->sect_idx + 1] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->comdats[leader->sect_idx] }; + contrib_obj->symlinks[contrib->sect_idx + 1] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->coff.sections.comdats[leader->sect_idx] }; #if LNK_PARANOID String8 section_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib->sect_idx+1); @@ -3691,7 +3687,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) for EachIndex(i, task->obj_indices[task_id].count) { U64 obj_idx = task->obj_indices[task_id].v[i]; LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { U64 section_number = sect_idx + 1; LNK_ObjSymbolRef symlink_ref = {0}; @@ -3758,7 +3754,7 @@ lnk_section_definition_is_before(void *raw_a, void *raw_b) internal B32 lnk_should_gather_section(LNK_Obj *obj, U64 sect_idx, COFF_SectionHeader *sect_header) { - COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; + COFF_SectionFlags sect_flags = obj->coff.sections.flags[sect_idx]; // removed sections were eliminated before image layout if (sect_flags & COFF_SectionFlag_LnkRemove) { @@ -3801,10 +3797,10 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) ProfBegin("Gather Section Definitions"); for EachInRange(obj_idx, range) { LNK_Obj *obj = task->objs[obj_idx]; - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; - String8 string_table = str8_substr(obj->data, obj->header.string_table_range); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->coff.data, obj->coff.header.section_table_range).str; + String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { COFF_SectionHeader *sect_header = §ion_table[sect_idx]; if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } @@ -3812,7 +3808,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) Temp temp = temp_begin(scratch.arena); // was section defined? - COFF_SectionFlags image_sect_flags = obj->section_flags[sect_idx] & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); + COFF_SectionFlags image_sect_flags = obj->coff.sections.flags[sect_idx] & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); String8 sect_name = coff_name_from_section_header(string_table, sect_header); image_sect_flags = lnk_apply_section_directives_to_flags(task->config, sect_name, image_sect_flags); String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, image_sect_flags); @@ -3893,7 +3889,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) LNK_Obj *obj = sect_defn->obj; U32 sect_number = sect_defn->obj_sect_idx + 1; COFF_SectionHeader *sect_header = lnk_coff_section_header_from_section_number(obj, sect_number); - String8 sect_name = coff_name_from_section_header(str8_substr(obj->data, obj->header.string_table_range), sect_header); + String8 sect_name = coff_name_from_section_header(str8_substr(obj->coff.data, obj->coff.header.string_table_range), sect_header); String8 expected_flags_str = coff_string_from_section_flags(main_arena, sect_n->data.flags); String8 current_flags_str = coff_string_from_section_flags(main_arena, sect_defn->flags); lnk_error_obj(LNK_Warning_SectionFlagsConflict, sect_defn->obj, "detected section flags conflict in %S(No. %X); expected {%S} but got {%S}", sect_name, sect_number, expected_flags_str, current_flags_str); @@ -3922,7 +3918,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) ProfBegin("Alloc Section Map"); task->sect_map = push_array(main_arena, LNK_SectionContrib **, task->objs_count); - for EachIndex(obj_idx, task->objs_count) { task->sect_map[obj_idx] = push_array(main_arena, LNK_SectionContrib *, task->objs[obj_idx]->header.section_count_no_null); } + for EachIndex(obj_idx, task->objs_count) { task->sect_map[obj_idx] = push_array(main_arena, LNK_SectionContrib *, task->objs[obj_idx]->coff.sections.count); } ProfEnd(); } @@ -3931,14 +3927,14 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) ProfBegin("Gather Section Contribs"); for EachInRange(obj_idx, range) { LNK_Obj *obj = task->objs[obj_idx]; - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->data, obj->header.section_table_range).str; - String8 string_table = str8_substr(obj->data, obj->header.string_table_range); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->coff.data, obj->coff.header.section_table_range).str; + String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); ProfBeginV("Gather Section Contribs [%S]", obj->path); - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { LNK_SectionContrib *sc = task->null_sc; COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; + COFF_SectionFlags sect_flags = obj->coff.sections.flags[sect_idx]; task->sect_map[obj_idx][sect_idx] = sc; if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } @@ -3959,7 +3955,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) if (sect_flags & COFF_SectionFlag_CntUninitializedData) { data = str8(0, sect_header->fsize); } else { - data = str8_substr(obj->data, rng_1u64(sect_header->foff, sect_header->foff + sect_header->fsize)); + data = lnk_obj_section_data_from_sect_idx(obj, sect_idx); } U16 sc_align = coff_align_size_from_section_flags(sect_flags); @@ -3988,10 +3984,10 @@ THREAD_POOL_TASK_FUNC(lnk_set_comdat_leaders_contribs_task) LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Set COMDAT Section Contribs [%S]", obj->path); - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { U64 section_number = sect_idx+1; - if (~obj->section_flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT) { continue; } + if (~obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT) { continue; } LNK_ObjSymbolRef symlink_ref = {0}; if ( ! lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; } @@ -4010,11 +4006,11 @@ THREAD_POOL_TASK_FUNC(lnk_flag_debug_symbols_task) LNK_Obj *obj = task->objs[obj_idx]; COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { - if (obj->section_flags[symbol.section_number-1] & LNK_SECTION_FLAG_DEBUG) { + if (obj->coff.sections.flags[symbol.section_number-1] & LNK_SECTION_FLAG_DEBUG) { task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx] = 1; } } @@ -4030,7 +4026,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) ProfBeginV("Patch COMDAT Offsets in %S", obj->path); COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); @@ -4050,7 +4046,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) B32 should_patch = 0; if (is_same_obj) { - B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static && obj->comdats[symbol.section_number-1] == symbol_idx; + B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static && obj->coff.sections.comdats[symbol.section_number-1] == symbol_idx; if (is_external || is_static_comdat_leader) { section_number = leader_symbol.section_number; value = leader_symbol.value; @@ -4256,7 +4252,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_common_block_symbols_task) ProfBeginV("Patch Common Block Symbols [%S]", obj->path); COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Common) { @@ -4283,7 +4279,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_regular_symbols_task) ProfBeginV("Patch Regular Symbols [%S]", obj->path); COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); if (task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx]) { continue; } @@ -4315,7 +4311,7 @@ lnk_patch_obj_symtab(LNK_SymbolTable *symtab, LNK_Obj *obj, B8 *was_symbol_patch ProfBeginV("%S\n", obj->path); COFF_ParsedSymbol fixup_dst; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + fixup_dst.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + fixup_dst.aux_symbol_count)) { fixup_dst = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); if (was_symbol_patched[symbol_idx]) { continue; } @@ -4422,7 +4418,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) LNK_ObjRelocPatcher *task = raw_task; LNK_Obj *obj = task->objs[task_id]; - COFF_FileHeaderInfo obj_header = obj->header; + COFF_FileHeaderInfo obj_header = obj->coff.header; COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); String8 symbol_table = lnk_coff_symbol_table_from_obj(obj); String8 string_table = lnk_coff_string_table_from_obj(obj); @@ -4433,16 +4429,15 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) for EachIndex(sect_idx, obj_header.section_count_no_null) { COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->section_flags[sect_idx]; + COFF_SectionFlags section_flags = obj->coff.sections.flags[sect_idx]; if (section_flags & COFF_SectionFlag_LnkInfo) { continue; } if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } if (section_flags & COFF_SectionFlag_CntUninitializedData) { continue; } // get section bytes (special case debug info because it is not copied to the image) - String8 data = section_flags & LNK_SECTION_FLAG_DEBUG ? obj->data : task->image_data; Rng1U64 section_frange = rng_1u64(section_header->foff, section_header->foff + section_header->fsize); - String8 section_data = str8_substr(data, section_frange); + String8 section_data = section_flags & LNK_SECTION_FLAG_DEBUG ? lnk_obj_section_data_from_sect_idx(obj, sect_idx) : str8_substr(task->image_data, section_frange); // apply relocs COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); @@ -4450,12 +4445,12 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) COFF_Reloc *reloc = &relocs.v[reloc_idx]; // error check relocation - if (obj->header.machine == COFF_MachineType_X64) { + if (obj->coff.header.machine == COFF_MachineType_X64) { if (reloc->type > COFF_Reloc_X64_Last) { lnk_error_obj(LNK_Error_IllegalRelocation, obj, "unknown relocation type 0x%x", reloc->type); } - } else if (obj->header.machine != COFF_MachineType_Unknown) { - lnk_not_implemented("relocation patching is not implemented for %S", coff_string_from_machine_type(obj->header.machine)); + } else if (obj->coff.header.machine != COFF_MachineType_Unknown) { + lnk_not_implemented("relocation patching is not implemented for %S", coff_string_from_machine_type(obj->coff.header.machine)); continue; } @@ -4571,15 +4566,15 @@ THREAD_POOL_TASK_FUNC(lnk_flag_hotpatch_contribs_task) U64 obj_idx = task_id; LNK_Obj *obj = task->objs[obj_idx]; - if (obj->hotpatch) { + if (obj->coff.hotpatch) { COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { LNK_SectionContrib *sc = task->sect_map[obj_idx][symbol.section_number-1]; if (sc != task->null_sc) { - sc->hotpatch = !!(obj->section_flags[symbol.section_number-1] & COFF_SectionFlag_CntCode); + sc->hotpatch = !!(obj->coff.sections.flags[symbol.section_number-1] & COFF_SectionFlag_CntCode); } } } @@ -4941,9 +4936,9 @@ THREAD_POOL_TASK_FUNC(lnk_patch_virtual_offsets_and_sizes_in_obj_section_headers ProfBeginV("Patch Virtual Offset And Size In Section Headers [%S]", obj->path); COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { + for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - if (~obj->section_flags[sect_idx] & COFF_SectionFlag_LnkRemove) { + if (~obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkRemove) { LNK_SectionContrib *sc = task->sect_map[obj_idx][sect_idx]; LNK_Section *sect = task->image_sects.v[sc->u.sect_idx]; sect_header->vsize = lnk_size_from_section_contrib(sc); @@ -4962,9 +4957,9 @@ THREAD_POOL_TASK_FUNC(lnk_patch_file_offsets_and_sizes_in_obj_section_headers_ta ProfBeginV("Patch File Offsets And Sizes In Obj Section Headers [%S]", obj->path); COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { + for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; + COFF_SectionFlags sect_flags = obj->coff.sections.flags[sect_idx]; B32 patch_section_header = (~sect_flags & COFF_SectionFlag_LnkRemove) && (~sect_flags & LNK_SECTION_FLAG_DEBUG); if (patch_section_header) { @@ -4988,16 +4983,16 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) ProfBegin("Patch Section Symbols [%S]", obj->path); COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Undefined) { if (symbol.storage_class == COFF_SymStorageClass_Section) { B32 is_referenced = 0; COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->section_flags[sect_idx]; + COFF_SectionFlags section_flags = obj->coff.sections.flags[sect_idx]; if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } if (section_flags & COFF_SectionFlag_LnkInfo) { continue; } if (section_flags & LNK_SECTION_FLAG_DEBUG) { continue; } @@ -5061,9 +5056,9 @@ THREAD_POOL_TASK_FUNC(lnk_gather_base_reloc_pages_task) COFF_SectionHeader *sect_table = lnk_coff_section_table_from_obj(obj); ProfBeginV("%S", obj->path); - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { COFF_SectionHeader *sect_header = §_table[sect_idx]; - if (obj->section_flags[sect_idx] & COFF_SectionFlag_LnkRemove) { continue; } + if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkRemove) { continue; } COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, sect_header); for EachIndex(reloc_idx, relocs.count) { @@ -5073,7 +5068,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_base_reloc_pages_task) COFF_SymbolValueInterpType symbol_interp = coff_interp_from_parsed_symbol(symbol); if (symbol_interp == COFF_SymbolValueInterp_Abs) { continue; } - U64 is_addr = coff_is_addr_reloc(obj->header.machine, r->type); + U64 is_addr = coff_is_addr_reloc(obj->coff.header.machine, r->type); if (is_addr == 0) { continue; } U64 reloc_voff = sect_header->voff + r->apply_off; @@ -5759,7 +5754,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT task.u.patch_symtabs.common_block_ranges = tp_divide_work(temp.arena, common_block_contribs_count, tp->worker_count); task.u.patch_symtabs.common_block_contribs = common_block_contribs; task.u.patch_symtabs.was_symbol_patched = push_array(temp.arena, B8 *, objs_count); - for EachIndex(obj_idx, objs_count) { task.u.patch_symtabs.was_symbol_patched[obj_idx] = push_array(temp.arena, B8, objs[obj_idx]->header.symbol_count); } + for EachIndex(obj_idx, objs_count) { task.u.patch_symtabs.was_symbol_patched[obj_idx] = push_array(temp.arena, B8, objs[obj_idx]->coff.header.symbol_count); } // flag debug symbols to prevent them from being patched in subsequent passes tp_for_parallel_prof(tp, 0, objs_count, lnk_flag_debug_symbols_task, &task, "Flag Debug Symbols"); @@ -6139,15 +6134,15 @@ lnk_obj_sect_idx_from_section(Arena *arena, U64 objs_count, LNK_Obj **objs, LNK_ PairU32 *obj_sect_idxs = push_array(arena, PairU32, max_contribs); for (U64 obj_idx = 0; obj_idx < objs_count; obj_idx += 1) { LNK_Obj *obj = objs[obj_idx]; - COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->data, obj->header.section_table_range.min, 0); - String8 string_table = str8_substr(obj->data, obj->header.string_table_range); - for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { + COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->coff.data, obj->coff.header.section_table_range.min, 0); + String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); + for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { COFF_SectionHeader *section_header = §ion_table[sect_idx]; String8 full_section_name = coff_name_from_section_header(string_table, section_header); String8 section_name, section_postfix; coff_parse_section_name(full_section_name, §ion_name, §ion_postfix); - if (obj->section_flags[sect_idx] & COFF_SectionFlag_LnkRemove) { continue; } + if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkRemove) { continue; } if (section_header->fsize == 0) { continue; } if (lnk_is_section_removed(config, section_name)) { continue; } @@ -6172,7 +6167,7 @@ internal COFF_SectionHeader * lnk_coff_section_header_from_obj_sect_idx_pair(LNK_Obj **objs, PairU32 p) { LNK_Obj *obj = objs[p.v0]; - COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->data, obj->header.section_table_range.min, 0); + COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->coff.data, obj->coff.header.section_table_range.min, 0); return §ion_table[p.v1]; } @@ -6287,7 +6282,7 @@ lnk_build_rad_map(Arena *arena, String8 image_data, LNK_Config *config, U64 objs String8List source_list = {0}; if (obj) { COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, sect_idx+1); - String8 string_table = str8_substr(obj->data, obj->header.string_table_range); + String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); String8 section_name = coff_name_from_section_header(string_table, section_header); LNK_Lib *lib = lnk_obj_get_lib(obj); if (lib) { @@ -6315,10 +6310,10 @@ lnk_build_rad_map(Arena *arena, String8 image_data, LNK_Config *config, U64 objs str8_list_pushf(arena, &map, "# DEBUG\n"); for (U64 obj_idx = 0; obj_idx < objs_count; obj_idx += 1) { LNK_Obj *obj = objs[obj_idx]; - COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->data, obj->header.section_table_range.min, 0); - for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { + COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->coff.data, obj->coff.header.section_table_range.min, 0); + for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->section_flags[sect_idx]; + COFF_SectionFlags section_flags = obj->coff.sections.flags[sect_idx]; if (~section_flags & COFF_SectionFlag_LnkRemove && section_flags & LNK_SECTION_FLAG_DEBUG) { LNK_Lib *lib = lnk_obj_get_lib(obj); if (lib) { @@ -6812,12 +6807,11 @@ lnk_run_type_server(TP_Context *tp, TP_Arena *arena, LNK_Config *config) if (obj->link_member) { continue; } // does obj have debug types? - if (obj->debug_p_sect_idx < obj->header.section_count_no_null) { + if (obj->coff.debug_p_sect_idx < obj->coff.sections.count) { u64_list_push(scratch.arena, &include_obj_list, obj_idx); - } else if (obj->debug_t_sect_idx < obj->header.section_count_no_null) { + } else if (obj->coff.debug_t_sect_idx < obj->coff.sections.count) { // read first leaf - LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, obj->debug_t_sect_idx); - String8 raw_debug_t = str8_substr(obj->data, section.frange); + String8 raw_debug_t = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_t_sect_idx); CV_Leaf leaf = {0}; cv_read_leaf(raw_debug_t, 0, 1, &leaf); diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 213c8f76..c8efd6e0 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -76,10 +76,8 @@ THREAD_POOL_TASK_FUNC(lnk_parse_debug_h_task) LNK_CodeViewInput *task = raw_task; LNK_Obj *obj = task->obj_arr[obj_idx]; - if (obj->debug_h_sect_idx < obj->header.section_count_no_null) { - LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, obj->debug_h_sect_idx); - - String8 raw_debug_h = str8_substr(obj->data, section.frange); + if (obj->coff.debug_h_sect_idx < obj->coff.sections.count) { + String8 raw_debug_h = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_h_sect_idx); CV_DebugH *debug_h = &task->debug_h_arr[obj_idx]; LLVM_GHash ghash = {0}; U64 ghash_read_size = str8_deserial_read_struct(raw_debug_h, 0, &ghash); @@ -599,6 +597,7 @@ lnk_rrt_from_string(Arena *arena, String8 rrt_data, String8 path, LNK_RRT *rrt_o } // unpack type data + type_data_raw = push_str8_copy(arena, type_data_raw); String8 type_data[CV_TypeIndexSource_COUNT] = {0}; for EachIndex(ti_source, CV_TypeIndexSource_COUNT) { type_data[ti_source] = str8_substr(type_data_raw, type_data_ranges[ti_source]); @@ -700,9 +699,9 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, if (rrt->obj_time_stamps[rrt_obj_idx] != obj_file_props.modified) { continue; } // invalidate debug section pointers - obj->debug_t_sect_idx = ~0; - obj->debug_p_sect_idx = ~0; - obj->debug_h_sect_idx = ~0; + obj->coff.debug_t_sect_idx = ~0; + obj->coff.debug_p_sect_idx = ~0; + obj->coff.debug_h_sect_idx = ~0; // apply type index map obj->ti_range = rrt->obj_ti_ranges[rrt_obj_idx]; @@ -734,15 +733,15 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, for EachNode(n, String8Node, input.debug_s_list_arr[obj_idx].first) { total_debug_s_size += n->string.size; } - if (obj->debug_t_sect_idx < obj->header.section_count_no_null) { - total_debug_t_size += lnk_obj_section_from_sect_idx(obj, obj->debug_t_sect_idx).header->fsize; + if (obj->coff.debug_t_sect_idx < obj->coff.sections.count) { + total_debug_t_size += lnk_obj_section_from_sect_idx(obj, obj->coff.debug_t_sect_idx).header->fsize; } - if (obj->debug_p_sect_idx < obj->header.section_count_no_null) { - total_debug_p_size += lnk_obj_section_from_sect_idx(obj, obj->debug_p_sect_idx).header->fsize; + if (obj->coff.debug_p_sect_idx < obj->coff.sections.count) { + total_debug_p_size += lnk_obj_section_from_sect_idx(obj, obj->coff.debug_p_sect_idx).header->fsize; } if (config->ghash) { - if (obj->debug_h_sect_idx < obj->header.section_count_no_null) { - total_debug_h_size += lnk_obj_section_from_sect_idx(obj, obj->debug_h_sect_idx).header->fsize; + if (obj->coff.debug_h_sect_idx < obj->coff.sections.count) { + total_debug_h_size += lnk_obj_section_from_sect_idx(obj, obj->coff.debug_h_sect_idx).header->fsize; } } } @@ -784,18 +783,16 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, for EachIndex(obj_idx, obj_count) { LNK_Obj *obj = obj_arr[obj_idx]; - if (obj->debug_t_sect_idx < obj->header.section_count_no_null) { - LNK_ObjSection debug_t_sect = lnk_obj_section_from_sect_idx(obj, obj->debug_t_sect_idx); + if (obj->coff.debug_t_sect_idx < obj->coff.sections.count) { raw_debug_t_arr[obj_idx].count = 1; raw_debug_t_arr[obj_idx].v = push_array(scratch.arena, String8, 1); - raw_debug_t_arr[obj_idx].v[0] = str8_substr(obj->data, debug_t_sect.frange); + raw_debug_t_arr[obj_idx].v[0] = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_t_sect_idx); } - if (obj->debug_p_sect_idx < obj->header.section_count_no_null) { - LNK_ObjSection debug_p_sect = lnk_obj_section_from_sect_idx(obj, obj->debug_p_sect_idx); + if (obj->coff.debug_p_sect_idx < obj->coff.sections.count) { raw_debug_p_arr[obj_idx].count = 1; raw_debug_p_arr[obj_idx].v = push_array(scratch.arena, String8, 1); - raw_debug_p_arr[obj_idx].v[0] = str8_substr(obj->data, debug_p_sect.frange); + raw_debug_p_arr[obj_idx].v[0] = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_p_sect_idx); } } @@ -2852,13 +2849,13 @@ THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) LNK_Obj *obj = task->cv->obj_arr[obj_idx]; PDB_DbiSCArray *sc_array = &task->sc_arrays[obj_idx]; - sc_array->v = push_array_no_zero(arena, PDB_DbiSC, obj->header.section_count_no_null); - sc_array->cap = obj->header.section_count_no_null; + sc_array->v = push_array_no_zero(arena, PDB_DbiSC, obj->coff.sections.count); + sc_array->cap = obj->coff.sections.count; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { // filter by section flags - if (obj->section_flags[sect_idx] & (COFF_SectionFlag_LnkInfo | COFF_SectionFlag_LnkRemove | LNK_SECTION_FLAG_DEBUG)) { continue; } + if (obj->coff.sections.flags[sect_idx] & (COFF_SectionFlag_LnkInfo | COFF_SectionFlag_LnkRemove | LNK_SECTION_FLAG_DEBUG)) { continue; } // skip unwind info for the section contribution String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 1259157d..b2074265 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -156,6 +156,13 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } } + String8 *section_data = push_array(arena, String8, header.section_count_no_null); + for EachIndex(sect_idx, header.section_count_no_null) { + COFF_SectionHeader *sect_header = &coff_section_table[sect_idx]; + String8 data = str8_substr(input->data, r1u64s(sect_header->foff, sect_header->fsize)); + section_data[sect_idx] = section_flags[sect_idx] & LNK_SECTION_FLAG_DEBUG ? push_str8_copy(arena, data) : data; + } + // // error check symbol table and cache name lengths for primary symbols // @@ -168,38 +175,46 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } } - U64 symbol_block_count = CeilIntegerDiv(header.symbol_count, 64); + U64 symbol_block_count = CeilIntegerDiv(header.symbol_count, 64); LNK_ObjSymbolArray symbols = { .count = primary_symbol_count, .primary_masks = push_array(arena, U64, symbol_block_count), - .block_bases = push_array_no_zero(arena, U32, symbol_block_count), - .values = push_array_no_zero(arena, U64, primary_symbol_count), - .section_numbers = push_array_no_zero(arena, U32, primary_symbol_count), - .types = push_array_no_zero(arena, COFF_SymbolType, primary_symbol_count), + .block_bases = push_array_no_zero(arena, U32, symbol_block_count), + .values = push_array_no_zero(arena, U64, primary_symbol_count), + .section_numbers = push_array_no_zero(arena, U32, primary_symbol_count), + .types = push_array_no_zero(arena, COFF_SymbolType, primary_symbol_count), .storage_classes = push_array_no_zero(arena, COFF_SymStorageClass, primary_symbol_count), - .aux_counts = push_array_no_zero(arena, U8, primary_symbol_count), - .name_offsets = push_array_no_zero(arena, U32, primary_symbol_count), - .name_sizes = push_array_no_zero(arena, U32, primary_symbol_count), + .aux_counts = push_array_no_zero(arena, U8, primary_symbol_count), + .name_offsets = push_array_no_zero(arena, U32, primary_symbol_count), + .name_sizes = push_array_no_zero(arena, U32, primary_symbol_count), }; { - U64 next_block = 0; + U64 next_block = 0; U32 primary_idx = 0; COFF_ParsedSymbol symbol; for (U64 symbol_idx = 0; symbol_idx < header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + symbol = coff_parse_symbol(header, raw_coff_string_table, raw_coff_symbol_table, symbol_idx); - U64 block_idx = symbol_idx >> 6; - while (next_block <= block_idx) { - symbols.block_bases[next_block++] = primary_idx; + + // copy out primary symbols + { + U64 block_idx = symbol_idx >> 6; + while (next_block <= block_idx) { + symbols.block_bases[next_block++] = primary_idx; + } + + symbols.primary_masks[block_idx] |= 1ull << (symbol_idx & 63); + + symbols.values [primary_idx] = symbol.value; + symbols.section_numbers[primary_idx] = symbol.section_number; + symbols.types [primary_idx] = symbol.type; + symbols.storage_classes[primary_idx] = symbol.storage_class; + symbols.aux_counts [primary_idx] = symbol.aux_symbol_count; + symbols.name_offsets [primary_idx] = symbol.name.size ? safe_cast_u32(symbol.name.str - input->data.str) : 0; + symbols.name_sizes [primary_idx] = safe_cast_u32(symbol.name.size); + primary_idx += 1; } - symbols.primary_masks[block_idx] |= 1ull << (symbol_idx & 63); - symbols.values[primary_idx] = symbol.value; - symbols.section_numbers[primary_idx] = symbol.section_number; - symbols.types[primary_idx] = symbol.type; - symbols.storage_classes[primary_idx] = symbol.storage_class; - symbols.aux_counts[primary_idx] = symbol.aux_symbol_count; - symbols.name_offsets[primary_idx] = symbol.name.size ? safe_cast_u32(symbol.name.str - input->data.str) : 0; - symbols.name_sizes[primary_idx] = safe_cast_u32(symbol.name.size); - primary_idx += 1; + COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { if (symbol.section_number == 0 || symbol.section_number > header.section_count_no_null) { @@ -381,22 +396,28 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } // fill out obj - obj->data = input->data; + obj->coff = (LNK_ObjCoff){ + .data = input->data, + .header = header, + .sections = { + .count = header.section_count_no_null, + .headers = section_headers, + .flags = section_flags, + .data = section_data, + .comdats = comdats, + .associations = associated_sections, + }, + .symbols = symbols, + .debug_t_sect_idx = debug_t_sect_idx, + .debug_p_sect_idx = debug_p_sect_idx, + .debug_h_sect_idx = debug_h_sect_idx, + .llvm_addrsig_sect_idx = llvm_addrsig_sect_idx, + .hotpatch = hotpatch, + }; obj->path = push_str8_copy(arena, input->path); - obj->header = header; - obj->section_headers = section_headers; - obj->section_flags = section_flags; - obj->symbols = symbols; - obj->comdats = comdats; obj->exclude_from_debug_info = input->exclude_from_debug_info; - obj->hotpatch = hotpatch; - obj->associated_sections = associated_sections; obj->self = &task->objs[task_id]; obj->link_member = input->link_member; - obj->debug_t_sect_idx = debug_t_sect_idx; - obj->debug_p_sect_idx = debug_p_sect_idx; - obj->debug_h_sect_idx = debug_h_sect_idx; - obj->llvm_addrsig_sect_idx = llvm_addrsig_sect_idx; } internal LNK_ObjNode * @@ -447,7 +468,7 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) LNK_InputCoffSymbolTable *task = raw_task; LNK_Obj *obj = task->objs[task_id]; COFF_ParsedSymbol symbol = {0}; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); LNK_SymbolSearchType search_type = lnk_symbol_search_type_from_coff(obj, symbol, interp); @@ -493,14 +514,14 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) internal LNK_ObjSymbolRef * lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) { - LNK_ObjSymbolRef *symlinks = push_array(arena, LNK_ObjSymbolRef, obj->header.section_count_no_null + 1); + LNK_ObjSymbolRef *symlinks = push_array(arena, LNK_ObjSymbolRef, obj->coff.sections.count + 1); COFF_ParsedSymbol symbol = {0}; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); if (interp != COFF_SymbolValueInterp_Regular) { continue; } - COFF_SectionFlags section_flags = obj->section_flags[symbol.section_number - 1]; + COFF_SectionFlags section_flags = obj->coff.sections.flags[symbol.section_number - 1]; if (~section_flags & COFF_SectionFlag_LnkCOMDAT) { continue; } LNK_ObjSymbolRef *symlink = &symlinks[symbol.section_number]; @@ -556,12 +577,12 @@ lnk_obj_collect_associated_sections(Arena *arena, LNK_Obj *obj, U32 root_section // walk the complete descendant chain because associated COMDATs can nest for EachNode(parent_n, U32Node, queue.first) { - for EachNode(associated_n, U32Node, obj->associated_sections[parent_n->data]) { + for EachNode(associated_n, U32Node, obj->coff.sections.associations[parent_n->data]) { U32 child_section = associated_n->data; if (child_section == 0) { continue; } if (hash_map_search_u64_u64(&seen_hm, child_section)) { continue; } - if (obj->section_flags[child_section - 1] & skip_flags) { continue; } + if (obj->coff.sections.flags[child_section - 1] & skip_flags) { continue; } hash_map_push_u64_u64(scratch.arena, &seen_hm, child_section, 1); u32_list_push(arena, &queue, child_section); @@ -610,7 +631,7 @@ internal COFF_ParsedSymbol lnk_obj_match_symbol(LNK_Obj *obj, String8 match_name) { COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx); if (str8_match(symbol.name, match_name, 0)) { return symbol; @@ -657,7 +678,7 @@ lnk_obj_get_lib_path(LNK_Obj *obj) internal U32 lnk_obj_get_removed_section_number(LNK_Obj *obj) { - return obj->header.is_big_obj ? LNK_REMOVED_SECTION_NUMBER_32 : LNK_REMOVED_SECTION_NUMBER_16; + return obj->coff.header.is_big_obj ? LNK_REMOVED_SECTION_NUMBER_32 : LNK_REMOVED_SECTION_NUMBER_16; } internal B32 @@ -674,20 +695,42 @@ lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *s internal COFF_SectionHeader * lnk_coff_section_table_from_obj(LNK_Obj *obj) { - return obj->section_headers; + return obj->coff.sections.headers; +} + +internal String8 +lnk_obj_section_data_from_sect_idx(LNK_Obj *obj, U64 sect_idx) +{ + Assert(sect_idx < obj->coff.sections.count); + return obj->coff.sections.data[sect_idx]; +} + +internal U64 +lnk_obj_foff_from_section_data_ptr(LNK_Obj *obj, void *ptr) +{ + U64 ptr_v = IntFromPtr(ptr); + for EachIndex(sect_idx, obj->coff.sections.count) { + String8 data = obj->coff.sections.data[sect_idx]; + U64 data_min = IntFromPtr(data.str); + if (data_min <= ptr_v && ptr_v < data_min + data.size) { + return obj->coff.sections.headers[sect_idx].foff + ptr_v - data_min; + } + } + InvalidPath; + return 0; } internal U64 lnk_obj_sect_idx_from_section_number(LNK_Obj *obj, U64 section_number) { - Assert(1 <= section_number && section_number <= obj->header.section_count_no_null); + Assert(1 <= section_number && section_number <= obj->coff.sections.count); return section_number-1; } internal U64 lnk_obj_section_number_from_sect_idx(LNK_Obj *obj, U64 sect_idx) { - Assert(sect_idx < obj->header.section_count_no_null); + Assert(sect_idx < obj->coff.sections.count); return sect_idx+1; } @@ -708,13 +751,13 @@ lnk_obj_section_name_from_section_number(LNK_Obj *obj, U64 section_number) internal LNK_ObjSection lnk_obj_section_from_sect_idx(LNK_Obj *obj, U64 sect_idx) { - Assert(sect_idx < obj->header.section_count_no_null); + Assert(sect_idx < obj->coff.sections.count); LNK_ObjSection section = {0}; section.obj = obj; section.sect_idx = sect_idx; section.section_number = sect_idx+1; section.header = &lnk_coff_section_table_from_obj(obj)[sect_idx]; - section.flags = &obj->section_flags[sect_idx]; + section.flags = &obj->coff.sections.flags[sect_idx]; section.vrange = rng_1u64(section.header->voff, section.header->voff + section.header->vsize); section.frange = rng_1u64(section.header->foff, section.header->foff + section.header->fsize); section.reloc_count = section.header->reloc_count; @@ -731,8 +774,8 @@ lnk_obj_section_from_section_number(LNK_Obj *obj, U64 section_number) internal COFF_RelocArray lnk_coff_relocs_from_section_header(LNK_Obj *obj, COFF_SectionHeader *section_header) { - COFF_RelocInfo reloc_info = coff_reloc_info_from_section_header(obj->data, section_header); - COFF_Reloc *relocs = (COFF_Reloc *)(obj->data.str + reloc_info.array_off); + COFF_RelocInfo reloc_info = coff_reloc_info_from_section_header(obj->coff.data, section_header); + COFF_Reloc *relocs = (COFF_Reloc *)(obj->coff.data.str + reloc_info.array_off); COFF_RelocArray result = { .count = reloc_info.count, .v = relocs }; return result; } @@ -740,21 +783,21 @@ lnk_coff_relocs_from_section_header(LNK_Obj *obj, COFF_SectionHeader *section_he internal String8 lnk_coff_string_table_from_obj(LNK_Obj *obj) { - return str8_substr(obj->data, obj->header.string_table_range); + return str8_substr(obj->coff.data, obj->coff.header.string_table_range); } internal String8 lnk_coff_symbol_table_from_obj(LNK_Obj *obj) { - return str8_substr(obj->data, obj->header.symbol_table_range); + return str8_substr(obj->coff.data, obj->coff.header.symbol_table_range); } internal COFF_RelocArray lnk_coff_reloc_info_from_section_number(LNK_Obj *obj, U64 section_number) { LNK_ObjSection section = lnk_obj_section_from_section_number(obj, section_number); - COFF_RelocInfo reloc_info = coff_reloc_info_from_section_header(obj->data, section.header); - COFF_Reloc *relocs = str8_deserial_get_raw_ptr(obj->data, reloc_info.array_off, sizeof(*relocs)*reloc_info.count); + COFF_RelocInfo reloc_info = coff_reloc_info_from_section_header(obj->coff.data, section.header); + COFF_Reloc *relocs = str8_deserial_get_raw_ptr(obj->coff.data, reloc_info.array_off, sizeof(*relocs)*reloc_info.count); COFF_RelocArray result = { .count = reloc_info.count, .v = relocs }; return result; } @@ -763,10 +806,10 @@ internal B32 lnk_try_comdat_props_from_section_number(LNK_Obj *obj, U32 section_number, COFF_ComdatSelectType *select_out, U32 *section_number_out, U32 *section_length_out, U32 *check_sum_out) { Assert(section_number > 0); - U32 symbol_idx = obj->comdats[section_number-1]; + U32 symbol_idx = obj->coff.sections.comdats[section_number-1]; if (symbol_idx != max_U32) { COFF_ParsedSymbol secdef = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); - coff_parse_secdef(secdef, obj->header.is_big_obj, select_out, section_number_out, section_length_out, check_sum_out); + coff_parse_secdef(secdef, obj->coff.header.is_big_obj, select_out, section_number_out, section_length_out, check_sum_out); return 1; } return 0; @@ -777,7 +820,7 @@ lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number) { Assert(section_number > 0); U64 sect_idx = section_number - 1; - return &obj->section_headers[sect_idx]; + return &obj->coff.sections.headers[sect_idx]; } internal force_inline U64 @@ -785,22 +828,22 @@ lnk_obj_primary_symbol_idx_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) { U64 block_idx = symbol_idx >> 6; U64 bit = 1ull << (symbol_idx & 63); - U64 block_mask = obj->symbols.primary_masks[block_idx]; + U64 block_mask = obj->coff.symbols.primary_masks[block_idx]; Assert(block_mask & bit); - return obj->symbols.block_bases[block_idx] + count_bits_set64(block_mask & (bit - 1)); + return obj->coff.symbols.block_bases[block_idx] + count_bits_set64(block_mask & (bit - 1)); } internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx_no_name(LNK_Obj *obj, U64 symbol_idx) { U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - U64 symbol_size = obj->header.is_big_obj ? sizeof(COFF_Symbol32) : sizeof(COFF_Symbol16); + U64 symbol_size = obj->coff.header.is_big_obj ? sizeof(COFF_Symbol32) : sizeof(COFF_Symbol16); COFF_ParsedSymbol result = { - .value = obj->symbols.values[primary_idx], - .section_number = obj->symbols.section_numbers[primary_idx], - .type = obj->symbols.types[primary_idx], - .storage_class = obj->symbols.storage_classes[primary_idx], - .aux_symbol_count = obj->symbols.aux_counts[primary_idx], + .value = obj->coff.symbols.values[primary_idx], + .section_number = obj->coff.symbols.section_numbers[primary_idx], + .type = obj->coff.symbols.types[primary_idx], + .storage_class = obj->coff.symbols.storage_classes[primary_idx], + .aux_symbol_count = obj->coff.symbols.aux_counts[primary_idx], .raw_symbol = lnk_coff_symbol_table_from_obj(obj).str + symbol_idx * symbol_size, }; return result; @@ -810,7 +853,7 @@ internal force_inline String8 lnk_symbol_name_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) { U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - return str8(obj->data.str + obj->symbols.name_offsets[primary_idx], obj->symbols.name_sizes[primary_idx]); + return str8(obj->coff.data.str + obj->coff.symbols.name_offsets[primary_idx], obj->coff.symbols.name_sizes[primary_idx]); } internal force_inline COFF_ParsedSymbol @@ -826,13 +869,13 @@ lnk_obj_symbol_patch(LNK_Obj *obj, U64 symbol_idx, COFF_ParsedSymbol patch, LNK_ { U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); if (flags & LNK_ObjSymbolPatch_Value) { - obj->symbols.values[primary_idx] = patch.value; + obj->coff.symbols.values[primary_idx] = patch.value; } if (flags & LNK_ObjSymbolPatch_Section) { - obj->symbols.section_numbers[primary_idx] = patch.section_number; + obj->coff.symbols.section_numbers[primary_idx] = patch.section_number; } if (flags & LNK_ObjSymbolPatch_StorageClass) { - obj->symbols.storage_classes[primary_idx] = patch.storage_class; + obj->coff.symbols.storage_classes[primary_idx] = patch.storage_class; } } @@ -842,7 +885,7 @@ THREAD_POOL_TASK_FUNC(lnk_collect_obj_chunks_task) LNK_SectionCollector *task = raw_task; LNK_Obj *obj = task->objs[task_id]; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); if (*section.flags & COFF_SectionFlag_LnkRemove && !task->collect_discarded) { @@ -851,7 +894,7 @@ THREAD_POOL_TASK_FUNC(lnk_collect_obj_chunks_task) String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); if (str8_match(section_name, task->name, 0)) { - String8 section_data = str8_substr(obj->data, section.frange); + String8 section_data = lnk_obj_section_data_from_sect_idx(obj, sect_idx); str8_list_push(arena, &task->out_lists[task_id], section_data); } } @@ -927,7 +970,7 @@ internal String8List lnk_raw_directives_from_obj(Arena *arena, LNK_Obj *obj) { String8List drectve_data = {0}; - for (U64 sect_idx = 0; sect_idx < obj->header.section_count_no_null; sect_idx += 1) { + for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); if (*section.flags & COFF_SectionFlag_LnkInfo) { String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); @@ -944,7 +987,7 @@ lnk_raw_directives_from_obj(Arena *arena, LNK_Obj *obj) lnk_error_obj(LNK_Error_IllData, obj, ".drectve must not have relocations"); break; } - str8_list_push(arena, &drectve_data, str8_substr(obj->data, section.frange)); + str8_list_push(arena, &drectve_data, lnk_obj_section_data_from_sect_idx(obj, sect_idx)); } } } @@ -977,7 +1020,7 @@ lnk_symbol_map_from_obj(Arena *arena, LNK_Obj *obj) // count functions U64 count = 0; COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { count += 1; @@ -988,7 +1031,7 @@ lnk_symbol_map_from_obj(Arena *arena, LNK_Obj *obj) map->v = push_array_no_zero(arena, LNK_SectionOffsetSymbol, count); U64 map_idx = 0; - for (U64 symbol_idx = 0; symbol_idx < obj->header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { + for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { LNK_SectionOffsetSymbol *entry = &map->v[map->count++]; @@ -1036,11 +1079,11 @@ lnk_debug_s_from_obj(Arena *arena, LNK_Obj *obj) String8List raw_debug_s = {0}; { - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); if (str8_match(section_name, str8_lit(".debug$S"), 0)) { - String8 debug_s = str8_substr(obj->data, section.frange); + String8 debug_s = lnk_obj_section_data_from_sect_idx(obj, sect_idx); str8_list_push(scratch.arena, &raw_debug_s, debug_s); } } @@ -1099,11 +1142,11 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) // #define DebugRelocValueFromFileOffset(foff) hash_map_search_u64_u64(&debug_reloc_hm, foff) HashMap debug_reloc_hm = {0}; - for EachIndex(sect_idx, obj->header.section_count_no_null) { + for EachIndex(sect_idx, obj->coff.sections.count) { LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); if (!str8_match(lnk_obj_section_name_from_sect_idx(obj, sect_idx), str8_lit(".debug$S"), 0)) { continue; } - String8 section_data = str8_substr(obj->data, section.frange); + String8 section_data = lnk_obj_section_data_from_sect_idx(obj, sect_idx); COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section.header); for EachIndex(reloc_idx, relocs.count) { COFF_Reloc *reloc = &relocs.v[reloc_idx]; @@ -1112,12 +1155,12 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) U64 reloc_size = 0; U64 reloc_value = 0; - if ((obj->header.machine == COFF_MachineType_X64 && reloc->type == COFF_Reloc_X64_Section) || - (obj->header.machine == COFF_MachineType_X86 && reloc->type == COFF_Reloc_X86_Section)) { + if ((obj->coff.header.machine == COFF_MachineType_X64 && reloc->type == COFF_Reloc_X64_Section) || + (obj->coff.header.machine == COFF_MachineType_X86 && reloc->type == COFF_Reloc_X86_Section)) { reloc_size = sizeof(U16); reloc_value = symbol.section_number; - } else if ((obj->header.machine == COFF_MachineType_X64 && reloc->type == COFF_Reloc_X64_SecRel) || - (obj->header.machine == COFF_MachineType_X86 && reloc->type == COFF_Reloc_X86_SecRel)) { + } else if ((obj->coff.header.machine == COFF_MachineType_X64 && reloc->type == COFF_Reloc_X64_SecRel) || + (obj->coff.header.machine == COFF_MachineType_X86 && reloc->type == COFF_Reloc_X86_SecRel)) { reloc_size = sizeof(U32); reloc_value = symbol.value; } @@ -1174,7 +1217,7 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) // resovle lines header { CV_C13LinesHeader *header = block->header; - U64 header_foff = (U64)(raw_lines.str - obj->data.str) + header->header_off; + U64 header_foff = lnk_obj_foff_from_section_data_ptr(obj, raw_lines.str) + header->header_off; U64 *sec_off = DebugRelocValueFromFileOffset(header_foff + OffsetOf(CV_C13SubSecLinesHeader, sec_off)); if (sec_off) { @@ -1223,9 +1266,8 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) // build inlinee id -> inlinee name // HashMap inlinee_hm = {0}; - if (obj->debug_t_sect_idx < obj->header.section_count_no_null) { - LNK_ObjSection debug_t_section = lnk_obj_section_from_sect_idx(obj, obj->debug_t_sect_idx); - String8 raw_debug_t = str8_substr(obj->data, debug_t_section.frange); + if (obj->coff.debug_t_sect_idx < obj->coff.sections.count) { + String8 raw_debug_t = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_t_sect_idx); CV_Signature debug_t_sig = cv_signature_from_debug_s(raw_debug_t); if (debug_t_sig == CV_Signature_C13) { CV_DebugT debug_t = cv_debug_t_from_data(scratch.arena, str8_skip(raw_debug_t, sizeof(CV_Signature)), CV_LeafAlign); @@ -1304,7 +1346,7 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) if (CV_IsProc32(symbol.kind)) { CV_SymProc32 *proc = str8_deserial_get_raw_ptr(symbol.data, 0, sizeof(*proc)); if (proc) { - U64 proc_foff = IntFromPtr(symbol.data.str - obj->data.str); + U64 proc_foff = lnk_obj_foff_from_section_data_ptr(obj, symbol.data.str); U64 *off_reloc = DebugRelocValueFromFileOffset(proc_foff + OffsetOf(CV_SymProc32, off)); U64 *sec_reloc = DebugRelocValueFromFileOffset(proc_foff + OffsetOf(CV_SymProc32, sec)); proc_offset = off_reloc ? safe_cast_u32(*off_reloc) : proc->off; diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 9eb6981b..851b0c77 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -5,20 +5,6 @@ // --- Input ------------------------------------------------------------------- -typedef struct LNK_ObjSymbolArray -{ - U64 count; - U64 *primary_masks; - U32 *block_bases; - U64 *values; - U32 *section_numbers; - COFF_SymbolType *types; - COFF_SymStorageClass *storage_classes; - U8 *aux_counts; - U32 *name_offsets; - U32 *name_sizes; -} LNK_ObjSymbolArray; - typedef U32 LNK_ObjSymbolPatchFlags; enum { @@ -27,39 +13,61 @@ enum LNK_ObjSymbolPatch_StorageClass = (1 << 2), }; +typedef struct LNK_ObjSymbolArray +{ + U64 count; + U64 *primary_masks; + U32 *block_bases; + U64 *values; + U32 *section_numbers; + COFF_SymbolType *types; + COFF_SymStorageClass *storage_classes; + U8 *aux_counts; + U32 *name_offsets; + U32 *name_sizes; +} LNK_ObjSymbolArray; + +typedef struct LNK_ObjSectionArray +{ + U64 count; + COFF_SectionHeader *headers; + COFF_SectionFlags *flags; + String8 *data; + U32 *comdats; + U32Node **associations; +} LNK_ObjSectionArray; + +typedef struct LNK_ObjCoff +{ + String8 data; + COFF_FileHeaderInfo header; + LNK_ObjSectionArray sections; + LNK_ObjSymbolArray symbols; + U32 debug_t_sect_idx; + U32 debug_p_sect_idx; + U32 debug_h_sect_idx; + U32 llvm_addrsig_sect_idx; + B8 hotpatch; +} LNK_ObjCoff; + typedef struct LNK_Obj { String8 path; - String8 data; - COFF_FileHeaderInfo header; - COFF_SectionHeader *section_headers; - COFF_SectionFlags *section_flags; - LNK_ObjSymbolArray symbols; + LNK_ObjCoff coff; // flags - B8 hotpatch; B8 exclude_from_debug_info; U32 input_idx; - // COMDAT - U32 *comdats; - U32Node **associated_sections; - LNK_ObjSymbolRef *symlinks; + // link state + LNK_ObjSymbolRef *symlinks; // link struct LNK_LibMemberRef *link_member; struct LNK_ObjNode *self; - // type info - U32 debug_t_sect_idx; - U32 debug_p_sect_idx; - U32 debug_h_sect_idx; - - // ICF - U32 llvm_addrsig_sect_idx; - // @type_server Rng1U64 ti_range; CV_TypeIndex *ti_map; @@ -242,6 +250,8 @@ internal force_inline String8 lnk_symbol_name_from_coff_symbol_idx(LNK internal void lnk_obj_symbol_patch(LNK_Obj *obj, U64 symbol_idx, COFF_ParsedSymbol patch, LNK_ObjSymbolPatchFlags flags); internal COFF_SectionHeader * lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number); +internal String8 lnk_obj_section_data_from_sect_idx(LNK_Obj *obj, U64 sect_idx); +internal U64 lnk_obj_foff_from_section_data_ptr(LNK_Obj *obj, void *ptr); internal U64 lnk_obj_sect_idx_from_section_number(LNK_Obj *obj, U64 section_number); internal U64 lnk_obj_section_number_from_sect_idx(LNK_Obj *obj, U64 sect_idx); internal String8 lnk_obj_section_name_from_section_number(LNK_Obj *obj, U64 section_number); diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 1873a821..dc82c8ba 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -10,7 +10,7 @@ lnk_symbol_search_type_from_coff(LNK_Obj *obj, COFF_ParsedSymbol symbol, COFF_Sy if (interp == COFF_SymbolValueInterp_Undefined) { search_type = LNK_SymbolSearch_Undefined; } else if (interp == COFF_SymbolValueInterp_Weak) { - COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(symbol, obj->header.is_big_obj); + COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(symbol, obj->coff.header.is_big_obj); switch (weak_ext->characteristics) { case COFF_WeakExt_SearchLibrary: search_type = LNK_SymbolSearch_WeakLibrary; break; case COFF_WeakExt_AntiDependency: search_type = LNK_SymbolSearch_WeakAntiDependency; break; @@ -179,7 +179,7 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) } LNK_ObjSymbolRef weak_symbol_ref = lnk_ref_from_symbol(weak); - COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(weak_parsed, weak_symbol_ref.obj->header.is_big_obj); + COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(weak_parsed, weak_symbol_ref.obj->coff.header.is_big_obj); if (weak_ext->characteristics == COFF_WeakExt_SearchLibrary) { // NOTE: MSVC does not let a weak symbol to replace an undefined one, // but LLD links without errors or warnings, meaning undefined symbols @@ -242,8 +242,8 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) } // weak vs weak else if (dst_interp == COFF_SymbolValueInterp_Weak && src_interp == COFF_SymbolValueInterp_Weak) { - COFF_SymbolWeakExt *dst_ext = coff_parse_weak_tag(dst_parsed, dst_ref.obj->header.is_big_obj); - COFF_SymbolWeakExt *src_ext = coff_parse_weak_tag(src_parsed, src_ref.obj->header.is_big_obj); + COFF_SymbolWeakExt *dst_ext = coff_parse_weak_tag(dst_parsed, dst_ref.obj->coff.header.is_big_obj); + COFF_SymbolWeakExt *src_ext = coff_parse_weak_tag(src_parsed, src_ref.obj->coff.header.is_big_obj); if ((dst_ext->characteristics == COFF_WeakExt_SearchAlias && src_ext->characteristics != COFF_WeakExt_SearchAlias)) { if (lnk_symbol_is_before(dst, src) || src_ext->characteristics == COFF_WeakExt_AntiDependency) { can_replace = 0; @@ -342,8 +342,8 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) case COFF_ComdatSelect_ExactMatch: { LNK_ObjSection dst_section = lnk_obj_section_from_section_number(dst_obj, dst_parsed.section_number); LNK_ObjSection src_section = lnk_obj_section_from_section_number(src_obj, src_parsed.section_number); - String8 dst_data = str8_substr(dst_obj->data, dst_section.frange); - String8 src_data = str8_substr(src_obj->data, src_section.frange); + String8 dst_data = lnk_obj_section_data_from_sect_idx(dst_obj, dst_section.sect_idx); + String8 src_data = lnk_obj_section_data_from_sect_idx(src_obj, src_section.sect_idx); B32 is_exact_match = 0; if (dst_check_sum != 0 && src_check_sum != 0) { @@ -396,7 +396,7 @@ lnk_on_symbol_replace(LNK_Symbol *dst, LNK_Symbol *src) *dst_section.flags |= COFF_SectionFlag_LnkRemove; // remove associated sections from the output - for (U32Node *associated_section = dst_ref.obj->associated_sections[dst_parsed.section_number]; + for (U32Node *associated_section = dst_ref.obj->coff.sections.associations[dst_parsed.section_number]; associated_section != 0; associated_section = associated_section->next) { LNK_ObjSection section = lnk_obj_section_from_section_number(dst_ref.obj, associated_section->data); @@ -728,7 +728,7 @@ lnk_resolve_weak_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_Ob break; } - COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(current_parsed, current_symbol.obj->header.is_big_obj); + COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(current_parsed, current_symbol.obj->coff.header.is_big_obj); // no definition -- fallback to default symbol COFF_ParsedSymbol tag_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(current_symbol.obj, weak_ext->tag_index); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index c4597d3d..a5f8aafd 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -5881,8 +5881,12 @@ TEST(debug_p_sig_mismatch) T_Ok(t_write_file(str8_lit("b.obj"), b_obj)); T_Ok(t_write_entry_obj()); - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /debug:full a.obj b.obj entry.obj"); + String8 a_obj_before = t_read_file(arena, str8_lit("a.obj")); + String8 b_obj_before = t_read_file(arena, str8_lit("b.obj")); + t_invoke_linkerf("/RAD_MEMORY_MAP_FILES:READ_WRITE /subsystem:console /entry:entry /out:a.exe /debug:full a.obj b.obj entry.obj"); T_Ok(g_last_exit_code == 0); + T_Ok(str8_match(a_obj_before, t_read_file(arena, str8_lit("a.obj")), 0)); + T_Ok(str8_match(b_obj_before, t_read_file(arena, str8_lit("b.obj")), 0)); B32 found_error = 0; String8 a_obj_path = t_make_file_path(arena, str8_lit("a.obj")); @@ -6271,8 +6275,14 @@ TEST(gen_rrt) t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /debug:full /rad_type_server:foo.rrt a.obj b.obj entry.obj"); T_Ok(g_last_exit_code == 0); - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /debug:ghash a.obj foo.rrt b.obj entry.obj"); + String8 a_obj_before = t_read_file(arena, a_obj_file_path); + String8 b_obj_before = t_read_file(arena, b_obj_file_path); + String8 rrt_before = t_read_file(arena, str8_lit("foo.rrt")); + t_invoke_linkerf("/RAD_MEMORY_MAP_FILES:READ_WRITE /subsystem:console /entry:entry /out:a.exe /debug:ghash a.obj foo.rrt b.obj entry.obj"); T_Ok(g_last_exit_code == 0); + T_Ok(str8_match(a_obj_before, t_read_file(arena, a_obj_file_path), 0)); + T_Ok(str8_match(b_obj_before, t_read_file(arena, b_obj_file_path), 0)); + T_Ok(str8_match(rrt_before, t_read_file(arena, str8_lit("foo.rrt")), 0)); } TEST(debug_p_and_debug_t_in_obj) From 736a6c63a574517b1908ba7f5218bb797bfb7b15 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 01:27:46 -0700 Subject: [PATCH 080/251] remove per-section linker side arrays --- src/linker/lnk.c | 62 ++++++++++++++++----------------- src/linker/lnk_debug_info.c | 2 +- src/linker/lnk_obj.c | 47 +++++++++---------------- src/linker/lnk_obj.h | 2 -- src/linker/tests/linker_tests.c | 14 ++------ 5 files changed, 51 insertions(+), 76 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 3e8d8131..f6782ec9 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2383,7 +2383,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer if ( ! lnk_do_debug_info(config)) { sect_filter |= LNK_SECTION_FLAG_DEBUG; } // on /DEBUG:NONE linker never invokes the debug info machinery for EachIndex(sect_idx, obj->coff.sections.count) { - if (obj->coff.sections.flags[sect_idx] & sect_filter) { continue; } + if (obj->coff.sections.headers[sect_idx].flags & sect_filter) { continue; } // unpack section and relocations COFF_SectionHeader *section_header = §ion_table[sect_idx]; @@ -2708,7 +2708,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) is_live[obj_idx] = push_array(scratch.arena, U8, obj->coff.sections.count + 1); for EachIndex(sect_idx, obj->coff.sections.count) { - is_live[obj_idx][sect_idx + 1] = !(obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT); + is_live[obj_idx][sect_idx + 1] = !(obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkCOMDAT); } } } @@ -2741,7 +2741,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(sect_idx, obj->coff.sections.count) { U32 section_number = sect_idx+1; - COFF_SectionFlags section_flags = obj->coff.sections.flags[sect_idx]; + COFF_SectionFlags section_flags = obj->coff.sections.headers[sect_idx].flags; // is section eligible for walking? if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } @@ -2806,7 +2806,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachNode(section_n, U32Node, associated_sections.first) { U32 section_number = section_n->data; - COFF_SectionFlags section_flags = ref_symbol.obj->coff.sections.flags[section_number-1]; + COFF_SectionFlags section_flags = ref_symbol.obj->coff.sections.headers[section_number-1].flags; // on first section visit, set live flag and enqueue section U8 was_visited = ins_atomic_u8_eval_assign(&is_live[ref_symbol.obj->input_idx][section_number], 1); @@ -2877,7 +2877,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) U32 section_number = sect_idx+1; COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); if ( ! is_live[obj->input_idx][section_number]) { - obj->coff.sections.flags[sect_idx] |= COFF_SectionFlag_LnkRemove; + obj->coff.sections.headers[sect_idx].flags |= COFF_SectionFlag_LnkRemove; } } } @@ -2895,19 +2895,19 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); U64 stat_kind = Stat_Null; - if (obj->coff.sections.flags[sect_idx] & LNK_SECTION_FLAG_DEBUG) { stat_kind = Stat_Debug; } - else if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntCode) { stat_kind = Stat_Code; } + if (obj->coff.sections.headers[sect_idx].flags & LNK_SECTION_FLAG_DEBUG) { stat_kind = Stat_Debug; } + else if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntCode) { stat_kind = Stat_Code; } else { stat_kind = Stat_Data; } if (is_live[obj->input_idx][section_number]) { stats[stat_kind].live_count += 1; - if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { + if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntUninitializedData) { stats[stat_kind].live_vsize += section_header->vsize; } else { stats[stat_kind].live_fsize += section_header->fsize; } } else { - if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { + if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntUninitializedData) { stats[stat_kind].vsize += section_header->vsize; } else { stats[stat_kind].fsize += section_header->fsize; @@ -2978,16 +2978,16 @@ lnk_icf_scope_from_section(LNK_Obj *obj, U32 sect_idx) // * section flags filter * COFF_SectionFlags expected_flags = COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_MemRead; COFF_SectionFlags exclude_flags = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_MemWrite | LNK_SECTION_FLAG_NOICF; - if ((obj->coff.sections.flags[sect_idx] & expected_flags) != expected_flags || (obj->coff.sections.flags[sect_idx] & exclude_flags) != 0) { + if ((obj->coff.sections.headers[sect_idx].flags & expected_flags) != expected_flags || (obj->coff.sections.headers[sect_idx].flags & exclude_flags) != 0) { goto exit; } - if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntCode) { + if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntCode) { result = LNK_ICF_Scope_Code; goto exit; } - if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_CntInitializedData) { + if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntInitializedData) { U64 section_number = sect_idx + 1; COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); String8 section_name = str8_cstring_capped(section_header->name, section_header->name + sizeof(section_header->name)); @@ -3141,12 +3141,12 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) if (is_symbol_found) { COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(target_ref.obj, target_ref.symbol_idx); if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular) { - target_ref.obj->coff.sections.flags[symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; + target_ref.obj->coff.sections.headers[symbol.section_number - 1].flags |= LNK_SECTION_FLAG_NOICF; } } else { COFF_ParsedSymbol original_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); if (coff_interp_from_parsed_symbol(original_symbol) == COFF_SymbolValueInterp_Regular) { - obj->coff.sections.flags[original_symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; + obj->coff.sections.headers[original_symbol.section_number - 1].flags |= LNK_SECTION_FLAG_NOICF; } else { lnk_log(LNK_Log_Debug, "%S: .llvm_addrsig: contains an unresolved symbol index 0x%x at offset 0x%x", lnk_loc_from_obj(scratch.arena, obj), symbol_idx, symbol_off); } @@ -3303,7 +3303,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // use the selected COMDAT leader so equivalent targets hash alike if (target_sect != 0 && target_sect <= target_obj->coff.sections.count && - target_obj->coff.sections.flags[target_sect - 1] & COFF_SectionFlag_LnkCOMDAT) { + target_obj->coff.sections.headers[target_sect - 1].flags & COFF_SectionFlag_LnkCOMDAT) { LNK_ObjSymbolRef leader_ref = {0}; if (lnk_obj_get_comdat_symlink(target_obj, target_sect, &leader_ref)) { COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(leader_ref.obj, leader_ref.symbol_idx); @@ -3602,8 +3602,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // TODO: double-check if discarded section headers should be updated with leaders' alignment, // leaders at this point are resolved - U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->coff.sections.flags[contrib->sect_idx]); - COFF_SectionFlags *leader_flags = &leader_obj->coff.sections.flags[leader->sect_idx]; + U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->coff.sections.headers[contrib->sect_idx].flags); + COFF_SectionFlags *leader_flags = &leader_obj->coff.sections.headers[leader->sect_idx].flags; for (COFF_SectionFlags old_flags = ins_atomic_u32_eval((U32 *)leader_flags);;) { U64 leader_align = coff_align_size_from_section_flags(old_flags); if (leader_align >= contrib_align) { break; } @@ -3618,7 +3618,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) } // remove folded section from the image - contrib_obj->coff.sections.flags[contrib->sect_idx] |= COFF_SectionFlag_LnkRemove; + contrib_obj->coff.sections.headers[contrib->sect_idx].flags |= COFF_SectionFlag_LnkRemove; // update folded section COMDAT symlink to point to the leader section contrib_obj->symlinks[contrib->sect_idx + 1] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->coff.sections.comdats[leader->sect_idx] }; @@ -3754,7 +3754,7 @@ lnk_section_definition_is_before(void *raw_a, void *raw_b) internal B32 lnk_should_gather_section(LNK_Obj *obj, U64 sect_idx, COFF_SectionHeader *sect_header) { - COFF_SectionFlags sect_flags = obj->coff.sections.flags[sect_idx]; + COFF_SectionFlags sect_flags = obj->coff.sections.headers[sect_idx].flags; // removed sections were eliminated before image layout if (sect_flags & COFF_SectionFlag_LnkRemove) { @@ -3808,7 +3808,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) Temp temp = temp_begin(scratch.arena); // was section defined? - COFF_SectionFlags image_sect_flags = obj->coff.sections.flags[sect_idx] & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); + COFF_SectionFlags image_sect_flags = obj->coff.sections.headers[sect_idx].flags & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); String8 sect_name = coff_name_from_section_header(string_table, sect_header); image_sect_flags = lnk_apply_section_directives_to_flags(task->config, sect_name, image_sect_flags); String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, image_sect_flags); @@ -3934,7 +3934,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) for EachIndex(sect_idx, obj->coff.sections.count) { LNK_SectionContrib *sc = task->null_sc; COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - COFF_SectionFlags sect_flags = obj->coff.sections.flags[sect_idx]; + COFF_SectionFlags sect_flags = obj->coff.sections.headers[sect_idx].flags; task->sect_map[obj_idx][sect_idx] = sc; if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } @@ -3987,7 +3987,7 @@ THREAD_POOL_TASK_FUNC(lnk_set_comdat_leaders_contribs_task) for EachIndex(sect_idx, obj->coff.sections.count) { U64 section_number = sect_idx+1; - if (~obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkCOMDAT) { continue; } + if (~obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkCOMDAT) { continue; } LNK_ObjSymbolRef symlink_ref = {0}; if ( ! lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; } @@ -4010,7 +4010,7 @@ THREAD_POOL_TASK_FUNC(lnk_flag_debug_symbols_task) symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { - if (obj->coff.sections.flags[symbol.section_number-1] & LNK_SECTION_FLAG_DEBUG) { + if (obj->coff.sections.headers[symbol.section_number-1].flags & LNK_SECTION_FLAG_DEBUG) { task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx] = 1; } } @@ -4429,7 +4429,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) for EachIndex(sect_idx, obj_header.section_count_no_null) { COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->coff.sections.flags[sect_idx]; + COFF_SectionFlags section_flags = obj->coff.sections.headers[sect_idx].flags; if (section_flags & COFF_SectionFlag_LnkInfo) { continue; } if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } @@ -4574,7 +4574,7 @@ THREAD_POOL_TASK_FUNC(lnk_flag_hotpatch_contribs_task) if (interp == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { LNK_SectionContrib *sc = task->sect_map[obj_idx][symbol.section_number-1]; if (sc != task->null_sc) { - sc->hotpatch = !!(obj->coff.sections.flags[symbol.section_number-1] & COFF_SectionFlag_CntCode); + sc->hotpatch = !!(obj->coff.sections.headers[symbol.section_number-1].flags & COFF_SectionFlag_CntCode); } } } @@ -4938,7 +4938,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_virtual_offsets_and_sizes_in_obj_section_headers COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - if (~obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkRemove) { + if (~obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkRemove) { LNK_SectionContrib *sc = task->sect_map[obj_idx][sect_idx]; LNK_Section *sect = task->image_sects.v[sc->u.sect_idx]; sect_header->vsize = lnk_size_from_section_contrib(sc); @@ -4959,7 +4959,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_file_offsets_and_sizes_in_obj_section_headers_ta COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - COFF_SectionFlags sect_flags = obj->coff.sections.flags[sect_idx]; + COFF_SectionFlags sect_flags = obj->coff.sections.headers[sect_idx].flags; B32 patch_section_header = (~sect_flags & COFF_SectionFlag_LnkRemove) && (~sect_flags & LNK_SECTION_FLAG_DEBUG); if (patch_section_header) { @@ -4992,7 +4992,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); for EachIndex(sect_idx, obj->coff.sections.count) { COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->coff.sections.flags[sect_idx]; + COFF_SectionFlags section_flags = obj->coff.sections.headers[sect_idx].flags; if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } if (section_flags & COFF_SectionFlag_LnkInfo) { continue; } if (section_flags & LNK_SECTION_FLAG_DEBUG) { continue; } @@ -5058,7 +5058,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_base_reloc_pages_task) ProfBeginV("%S", obj->path); for EachIndex(sect_idx, obj->coff.sections.count) { COFF_SectionHeader *sect_header = §_table[sect_idx]; - if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkRemove) { continue; } + if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkRemove) { continue; } COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, sect_header); for EachIndex(reloc_idx, relocs.count) { @@ -6142,7 +6142,7 @@ lnk_obj_sect_idx_from_section(Arena *arena, U64 objs_count, LNK_Obj **objs, LNK_ String8 section_name, section_postfix; coff_parse_section_name(full_section_name, §ion_name, §ion_postfix); - if (obj->coff.sections.flags[sect_idx] & COFF_SectionFlag_LnkRemove) { continue; } + if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkRemove) { continue; } if (section_header->fsize == 0) { continue; } if (lnk_is_section_removed(config, section_name)) { continue; } @@ -6313,7 +6313,7 @@ lnk_build_rad_map(Arena *arena, String8 image_data, LNK_Config *config, U64 objs COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->coff.data, obj->coff.header.section_table_range.min, 0); for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->coff.sections.flags[sect_idx]; + COFF_SectionFlags section_flags = obj->coff.sections.headers[sect_idx].flags; if (~section_flags & COFF_SectionFlag_LnkRemove && section_flags & LNK_SECTION_FLAG_DEBUG) { LNK_Lib *lib = lnk_obj_get_lib(obj); if (lib) { diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index c8efd6e0..70ee1dab 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -2855,7 +2855,7 @@ THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) for EachIndex(sect_idx, obj->coff.sections.count) { // filter by section flags - if (obj->coff.sections.flags[sect_idx] & (COFF_SectionFlag_LnkInfo | COFF_SectionFlag_LnkRemove | LNK_SECTION_FLAG_DEBUG)) { continue; } + if (obj->coff.sections.headers[sect_idx].flags & (COFF_SectionFlag_LnkInfo | COFF_SectionFlag_LnkRemove | LNK_SECTION_FLAG_DEBUG)) { continue; } // skip unwind info for the section contribution String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index b2074265..2163d5ca 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -103,23 +103,23 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) // - find debug info and meta-data leaders // COFF_SectionHeader *coff_section_table = (COFF_SectionHeader *)raw_coff_section_table.str; - COFF_SectionFlags *section_flags = push_array_no_zero(arena, COFF_SectionFlags, header.section_count_no_null); U32 debug_t_sect_idx = ~0; U32 debug_p_sect_idx = ~0; U32 debug_h_sect_idx = ~0; U32 llvm_addrsig_sect_idx = ~0; for (U64 sect_idx = 0; sect_idx < header.section_count_no_null; sect_idx += 1) { COFF_SectionHeader *coff_sect_header = &coff_section_table[sect_idx]; - section_flags[sect_idx] = coff_sect_header->flags & ~3; // linker reserves low 2 bits for internal flags + COFF_SectionFlags *section_flags = §ion_headers[sect_idx].flags; String8 sect_name = coff_name_from_section_header(raw_coff_string_table, coff_sect_header); + *section_flags = coff_sect_header->flags & ~3; // linker reserves low 2 bits for internal flags if (str8_starts_with(sect_name, str8_lit(".debug$"))) { - section_flags[sect_idx] |= LNK_SECTION_FLAG_DEBUG; + *section_flags |= LNK_SECTION_FLAG_DEBUG; } if (str8_ends_with(sect_name, str8_lit("$fo$"), 0) || str8_ends_with(sect_name, str8_lit("$fo_rvas$"), 0) || str8_ends_with(sect_name, str8_lit("$fo_bdd$"), 0)) { - section_flags[sect_idx] |= COFF_SectionFlag_LnkInfo; + *section_flags |= COFF_SectionFlag_LnkInfo; } if (str8_match(sect_name, str8_lit(".debug$T"), 0)) { @@ -134,7 +134,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) llvm_addrsig_sect_idx = sect_idx; } - if (~section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { + if (~*section_flags & COFF_SectionFlag_CntUninitializedData) { if (coff_sect_header->fsize > 0) { Rng1U64 sect_range = rng_1u64(coff_sect_header->foff, coff_sect_header->foff + coff_sect_header->fsize); if (contains_1u64(header.header_range, coff_sect_header->foff) || @@ -156,13 +156,6 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } } - String8 *section_data = push_array(arena, String8, header.section_count_no_null); - for EachIndex(sect_idx, header.section_count_no_null) { - COFF_SectionHeader *sect_header = &coff_section_table[sect_idx]; - String8 data = str8_substr(input->data, r1u64s(sect_header->foff, sect_header->fsize)); - section_data[sect_idx] = section_flags[sect_idx] & LNK_SECTION_FLAG_DEBUG ? push_str8_copy(arena, data) : data; - } - // // error check symbol table and cache name lengths for primary symbols // @@ -255,7 +248,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) if (symbol.storage_class == COFF_SymStorageClass_Static) { if (symbol.section_number > 0 && symbol.section_number <= header.section_count_no_null) { COFF_SectionHeader *sect_header = &coff_section_table[symbol.section_number-1]; - if (section_flags[symbol.section_number-1] & COFF_SectionFlag_LnkCOMDAT) { + if (section_headers[symbol.section_number-1].flags & COFF_SectionFlag_LnkCOMDAT) { if (symbol.aux_symbol_count) { U32 section_length = 0; coff_parse_secdef(symbol, header.is_big_obj, 0, 0, §ion_length, 0); @@ -362,7 +355,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) CV_Symbol comp_symbol = {0}; for EachIndex(sect_idx, header.section_count_no_null) { COFF_SectionHeader *sect_header = &coff_section_table[sect_idx]; - if (section_flags[sect_idx] & LNK_SECTION_FLAG_DEBUG) { + if (section_headers[sect_idx].flags & LNK_SECTION_FLAG_DEBUG) { String8 name = str8_cstring_capped(sect_header->name, sect_header->name+sizeof(sect_header->name)); if (str8_match(name, str8_lit(".debug$S"), 0)) { Temp temp = temp_begin(scratch.arena); @@ -402,8 +395,6 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) .sections = { .count = header.section_count_no_null, .headers = section_headers, - .flags = section_flags, - .data = section_data, .comdats = comdats, .associations = associated_sections, }, @@ -521,7 +512,7 @@ lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); if (interp != COFF_SymbolValueInterp_Regular) { continue; } - COFF_SectionFlags section_flags = obj->coff.sections.flags[symbol.section_number - 1]; + COFF_SectionFlags section_flags = obj->coff.sections.headers[symbol.section_number - 1].flags; if (~section_flags & COFF_SectionFlag_LnkCOMDAT) { continue; } LNK_ObjSymbolRef *symlink = &symlinks[symbol.section_number]; @@ -582,7 +573,7 @@ lnk_obj_collect_associated_sections(Arena *arena, LNK_Obj *obj, U32 root_section if (child_section == 0) { continue; } if (hash_map_search_u64_u64(&seen_hm, child_section)) { continue; } - if (obj->coff.sections.flags[child_section - 1] & skip_flags) { continue; } + if (obj->coff.sections.headers[child_section - 1].flags & skip_flags) { continue; } hash_map_push_u64_u64(scratch.arena, &seen_hm, child_section, 1); u32_list_push(arena, &queue, child_section); @@ -702,22 +693,18 @@ internal String8 lnk_obj_section_data_from_sect_idx(LNK_Obj *obj, U64 sect_idx) { Assert(sect_idx < obj->coff.sections.count); - return obj->coff.sections.data[sect_idx]; + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->coff.data, obj->coff.header.section_table_range).str; + COFF_SectionHeader *section = §ion_table[sect_idx]; + return str8_substr(obj->coff.data, r1u64s(section->foff, section->fsize)); } internal U64 lnk_obj_foff_from_section_data_ptr(LNK_Obj *obj, void *ptr) { - U64 ptr_v = IntFromPtr(ptr); - for EachIndex(sect_idx, obj->coff.sections.count) { - String8 data = obj->coff.sections.data[sect_idx]; - U64 data_min = IntFromPtr(data.str); - if (data_min <= ptr_v && ptr_v < data_min + data.size) { - return obj->coff.sections.headers[sect_idx].foff + ptr_v - data_min; - } - } - InvalidPath; - return 0; + U64 data_min = IntFromPtr(obj->coff.data.str); + U64 ptr_v = IntFromPtr(ptr); + Assert(data_min <= ptr_v && ptr_v < data_min + obj->coff.data.size); + return ptr_v - data_min; } internal U64 @@ -757,7 +744,7 @@ lnk_obj_section_from_sect_idx(LNK_Obj *obj, U64 sect_idx) section.sect_idx = sect_idx; section.section_number = sect_idx+1; section.header = &lnk_coff_section_table_from_obj(obj)[sect_idx]; - section.flags = &obj->coff.sections.flags[sect_idx]; + section.flags = §ion.header->flags; section.vrange = rng_1u64(section.header->voff, section.header->voff + section.header->vsize); section.frange = rng_1u64(section.header->foff, section.header->foff + section.header->fsize); section.reloc_count = section.header->reloc_count; diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 851b0c77..364f3189 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -31,8 +31,6 @@ typedef struct LNK_ObjSectionArray { U64 count; COFF_SectionHeader *headers; - COFF_SectionFlags *flags; - String8 *data; U32 *comdats; U32Node **associations; } LNK_ObjSectionArray; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index a5f8aafd..c4597d3d 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -5881,12 +5881,8 @@ TEST(debug_p_sig_mismatch) T_Ok(t_write_file(str8_lit("b.obj"), b_obj)); T_Ok(t_write_entry_obj()); - String8 a_obj_before = t_read_file(arena, str8_lit("a.obj")); - String8 b_obj_before = t_read_file(arena, str8_lit("b.obj")); - t_invoke_linkerf("/RAD_MEMORY_MAP_FILES:READ_WRITE /subsystem:console /entry:entry /out:a.exe /debug:full a.obj b.obj entry.obj"); + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /debug:full a.obj b.obj entry.obj"); T_Ok(g_last_exit_code == 0); - T_Ok(str8_match(a_obj_before, t_read_file(arena, str8_lit("a.obj")), 0)); - T_Ok(str8_match(b_obj_before, t_read_file(arena, str8_lit("b.obj")), 0)); B32 found_error = 0; String8 a_obj_path = t_make_file_path(arena, str8_lit("a.obj")); @@ -6275,14 +6271,8 @@ TEST(gen_rrt) t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /debug:full /rad_type_server:foo.rrt a.obj b.obj entry.obj"); T_Ok(g_last_exit_code == 0); - String8 a_obj_before = t_read_file(arena, a_obj_file_path); - String8 b_obj_before = t_read_file(arena, b_obj_file_path); - String8 rrt_before = t_read_file(arena, str8_lit("foo.rrt")); - t_invoke_linkerf("/RAD_MEMORY_MAP_FILES:READ_WRITE /subsystem:console /entry:entry /out:a.exe /debug:ghash a.obj foo.rrt b.obj entry.obj"); + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /debug:ghash a.obj foo.rrt b.obj entry.obj"); T_Ok(g_last_exit_code == 0); - T_Ok(str8_match(a_obj_before, t_read_file(arena, a_obj_file_path), 0)); - T_Ok(str8_match(b_obj_before, t_read_file(arena, b_obj_file_path), 0)); - T_Ok(str8_match(rrt_before, t_read_file(arena, str8_lit("foo.rrt")), 0)); } TEST(debug_p_and_debug_t_in_obj) From a7e1b21e4ec2426e2fc23a6454d336f256b1a5c1 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 01:30:44 -0700 Subject: [PATCH 081/251] use base bit array for primary symbols --- src/linker/lnk_obj.c | 26 ++++++++++++++++---------- src/linker/lnk_obj.h | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 2163d5ca..25191e6a 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -171,7 +171,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) U64 symbol_block_count = CeilIntegerDiv(header.symbol_count, 64); LNK_ObjSymbolArray symbols = { .count = primary_symbol_count, - .primary_masks = push_array(arena, U64, symbol_block_count), + .primary_bits = bit_array_init32(arena, header.symbol_count), .block_bases = push_array_no_zero(arena, U32, symbol_block_count), .values = push_array_no_zero(arena, U64, primary_symbol_count), .section_numbers = push_array_no_zero(arena, U32, primary_symbol_count), @@ -192,11 +192,9 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) // copy out primary symbols { U64 block_idx = symbol_idx >> 6; - while (next_block <= block_idx) { - symbols.block_bases[next_block++] = primary_idx; - } + while (next_block <= block_idx) { symbols.block_bases[next_block++] = primary_idx; } - symbols.primary_masks[block_idx] |= 1ull << (symbol_idx & 63); + bit_array_set_bit32(symbols.primary_bits, symbol_idx, 1); symbols.values [primary_idx] = symbol.value; symbols.section_numbers[primary_idx] = symbol.section_number; @@ -205,6 +203,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) symbols.aux_counts [primary_idx] = symbol.aux_symbol_count; symbols.name_offsets [primary_idx] = symbol.name.size ? safe_cast_u32(symbol.name.str - input->data.str) : 0; symbols.name_sizes [primary_idx] = safe_cast_u32(symbol.name.size); + primary_idx += 1; } @@ -813,11 +812,18 @@ lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number) internal force_inline U64 lnk_obj_primary_symbol_idx_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) { - U64 block_idx = symbol_idx >> 6; - U64 bit = 1ull << (symbol_idx & 63); - U64 block_mask = obj->coff.symbols.primary_masks[block_idx]; - Assert(block_mask & bit); - return obj->coff.symbols.block_bases[block_idx] + count_bits_set64(block_mask & (bit - 1)); + U64 block_idx = symbol_idx >> 6; + U64 word_idx = symbol_idx >> 5; + U32 bit_idx = symbol_idx & 31; + U32Array primary_bits = obj->coff.symbols.primary_bits; + Assert(bit_array_get_bit32(primary_bits, symbol_idx)); + + U64 primary_idx = obj->coff.symbols.block_bases[block_idx]; + if (word_idx & 1) { + primary_idx += count_bits_set32(primary_bits.v[word_idx - 1]); + } + primary_idx += count_bits_set32(primary_bits.v[word_idx] & ((1u << bit_idx) - 1)); + return primary_idx; } internal force_inline COFF_ParsedSymbol diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 364f3189..162eb314 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -16,7 +16,7 @@ enum typedef struct LNK_ObjSymbolArray { U64 count; - U64 *primary_masks; + U32Array primary_bits; U32 *block_bases; U64 *values; U32 *section_numbers; From e3d49069bfdf22e81e42af9479752de69381270a Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 01:34:50 -0700 Subject: [PATCH 082/251] remove helper for patching symbols and update values directly --- src/linker/lnk.c | 51 +++++++++++++++++------------------ src/linker/lnk_obj.c | 15 ----------- src/linker/lnk_obj.h | 10 +------ src/linker/lnk_symbol_table.c | 8 +++--- 4 files changed, 30 insertions(+), 54 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index f6782ec9..e790b1d9 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -4069,9 +4069,9 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) } if (should_patch) { - symbol.section_number = section_number; - symbol.value = value; - lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + obj->coff.symbols.section_numbers[primary_idx] = section_number; + obj->coff.symbols.values [primary_idx] = value; } } ProfEnd(); @@ -4230,12 +4230,11 @@ THREAD_POOL_TASK_FUNC(lnk_patch_common_block_leaders_task) LNK_CommonBlockContrib *contrib = &task->u.patch_symtabs.common_block_contribs[contrib_idx]; LNK_Symbol *symbol = contrib->symbol; LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol); - COFF_ParsedSymbol parsed_symbol = lnk_parsed_from_symbol(symbol); U64 section_number = task->u.patch_symtabs.common_block_sect->sect_idx + 1; - parsed_symbol.value = contrib->u.offset; - parsed_symbol.section_number = safe_cast_u32(section_number); - lnk_obj_symbol_patch(symbol_ref.obj, symbol_ref.symbol_idx, parsed_symbol, LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_Section); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(symbol_ref.obj, symbol_ref.symbol_idx); + symbol_ref.obj->coff.symbols.values [primary_idx] = contrib->u.offset; + symbol_ref.obj->coff.symbols.section_numbers[primary_idx] = safe_cast_u32(section_number); task->u.patch_symtabs.was_symbol_patched[symbol_ref.obj->input_idx][symbol_ref.symbol_idx] = 1; } @@ -4260,10 +4259,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_common_block_symbols_task) COFF_ParsedSymbol defn_parsed = lnk_parsed_from_symbol(defn); Assert(lnk_interp_from_symbol(defn) == COFF_SymbolValueInterp_Regular); if (defn) { - symbol.section_number = defn_parsed.section_number; - symbol.value = safe_cast_u32(defn_parsed.value); - symbol.storage_class = COFF_SymStorageClass_Static; - lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + obj->coff.symbols.section_numbers[primary_idx] = defn_parsed.section_number; + obj->coff.symbols.values [primary_idx] = safe_cast_u32(defn_parsed.value); + obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_Static; } } } @@ -4297,9 +4296,9 @@ THREAD_POOL_TASK_FUNC(lnk_patch_regular_symbols_task) value = sc->u.off + symbol.value; } - symbol.section_number = section_number; - symbol.value = value; - lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + obj->coff.symbols.section_numbers[primary_idx] = section_number; + obj->coff.symbols.values [primary_idx] = value; } } ProfEnd(); @@ -4336,10 +4335,10 @@ lnk_patch_obj_symtab(LNK_SymbolTable *symtab, LNK_Obj *obj, B8 *was_symbol_patch value = fixup_src.value; } - fixup_dst.section_number = section_number; - fixup_dst.value = value; - fixup_dst.storage_class = COFF_SymStorageClass_Static; - lnk_obj_symbol_patch(obj, symbol_idx, fixup_dst, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + obj->coff.symbols.section_numbers[primary_idx] = section_number; + obj->coff.symbols.values [primary_idx] = value; + obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_Static; was_symbol_patched[symbol_idx] = 1; } @@ -5012,10 +5011,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) if (sect && (~sect->flags & COFF_SectionFlag_LnkRemove)) { if (~sect->flags & COFF_SectionFlag_MemDiscardable) { LNK_SectionContrib *first_sc = lnk_get_first_section_contrib(sect); - symbol.section_number = safe_cast_u32(first_sc->u.sect_idx + 1); - symbol.value = first_sc->u.off; - symbol.storage_class = COFF_SymStorageClass_Static; - lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + obj->coff.symbols.section_numbers[primary_idx] = safe_cast_u32(first_sc->u.sect_idx + 1); + obj->coff.symbols.values [primary_idx] = first_sc->u.off; + obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_Static; } else { lnk_error_obj(LNK_Error_SectRefsDiscardedMemory, obj, "symbol %S (No. 0x%llx) references section with discard flag", symbol_name, symbol_idx); } @@ -5033,10 +5032,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) LNK_Section *fallback_sect = task->image_sects.v[task->image_sects.count-1]; U32 fallback_section_number = safe_cast_u32(fallback_sect->sect_idx + 1); U32 fallback_section_offset = safe_cast_u32(fallback_voff - fallback_sect->voff); - symbol.section_number = fallback_section_number; - symbol.value = fallback_section_offset; - symbol.storage_class = COFF_SymStorageClass_Static; - lnk_obj_symbol_patch(obj, symbol_idx, symbol, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); + obj->coff.symbols.section_numbers[primary_idx] = fallback_section_number; + obj->coff.symbols.values [primary_idx] = fallback_section_offset; + obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_Static; lnk_error_obj(LNK_Warning_UndefinedSectionSymbol, obj, "undefined section symbol %S (No. 0x%llx) refers to an image section that doesn't exist; patching to %#llx", symbol_name, symbol_idx, fallback_voff); } diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 25191e6a..0fcab0b7 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -857,21 +857,6 @@ lnk_parsed_symbol_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) return result; } -internal void -lnk_obj_symbol_patch(LNK_Obj *obj, U64 symbol_idx, COFF_ParsedSymbol patch, LNK_ObjSymbolPatchFlags flags) -{ - U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - if (flags & LNK_ObjSymbolPatch_Value) { - obj->coff.symbols.values[primary_idx] = patch.value; - } - if (flags & LNK_ObjSymbolPatch_Section) { - obj->coff.symbols.section_numbers[primary_idx] = patch.section_number; - } - if (flags & LNK_ObjSymbolPatch_StorageClass) { - obj->coff.symbols.storage_classes[primary_idx] = patch.storage_class; - } -} - internal THREAD_POOL_TASK_FUNC(lnk_collect_obj_chunks_task) { diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 162eb314..321dcea4 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -5,14 +5,6 @@ // --- Input ------------------------------------------------------------------- -typedef U32 LNK_ObjSymbolPatchFlags; -enum -{ - LNK_ObjSymbolPatch_Value = (1 << 0), - LNK_ObjSymbolPatch_Section = (1 << 1), - LNK_ObjSymbolPatch_StorageClass = (1 << 2), -}; - typedef struct LNK_ObjSymbolArray { U64 count; @@ -245,7 +237,7 @@ internal U32List lnk_obj_collect_associated_sections(Arena *arena, LNK_ internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx_no_name(LNK_Obj *obj, U64 symbol_idx); internal force_inline String8 lnk_symbol_name_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); -internal void lnk_obj_symbol_patch(LNK_Obj *obj, U64 symbol_idx, COFF_ParsedSymbol patch, LNK_ObjSymbolPatchFlags flags); +internal force_inline U64 lnk_obj_primary_symbol_idx_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); internal COFF_SectionHeader * lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number); internal String8 lnk_obj_section_data_from_sect_idx(LNK_Obj *obj, U64 sect_idx); diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index dc82c8ba..174364d5 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -826,10 +826,10 @@ THREAD_POOL_TASK_FUNC(lnk_replace_weak_with_default_symbol_task) COFF_ParsedSymbol resolve_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(resolve.obj, resolve.symbol_idx); COFF_SymbolValueInterpType resolve_interp = coff_interp_from_parsed_symbol(resolve_parsed); if (resolve_interp == COFF_SymbolValueInterp_Weak) { - symbol_parsed.section_number = COFF_Symbol_UndefinedSection; - symbol_parsed.value = 0; - symbol_parsed.storage_class = COFF_SymStorageClass_External; - lnk_obj_symbol_patch(symbol_ref.obj, symbol_ref.symbol_idx, symbol_parsed, LNK_ObjSymbolPatch_Section | LNK_ObjSymbolPatch_Value | LNK_ObjSymbolPatch_StorageClass); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(symbol_ref.obj, symbol_ref.symbol_idx); + symbol_ref.obj->coff.symbols.section_numbers[primary_idx] = COFF_Symbol_UndefinedSection; + symbol_ref.obj->coff.symbols.values [primary_idx] = 0; + symbol_ref.obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_External; lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Undefined); } else { symbol->first_ref->v = resolve; From da00fa96270f22fd0c244fe1bca66cd238009fc0 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 03:05:58 -0700 Subject: [PATCH 083/251] use iterators to access COFF sections and symbols --- src/linker/lnk.c | 480 ++++++++++++++++----------------- src/linker/lnk.h | 2 +- src/linker/lnk_debug_info.c | 43 ++- src/linker/lnk_obj.c | 308 +++++++++++---------- src/linker/lnk_obj.h | 59 ++-- src/linker/lnk_section_table.h | 4 +- src/linker/lnk_symbol_table.c | 8 +- 7 files changed, 445 insertions(+), 459 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index e790b1d9..961da53c 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2366,7 +2366,6 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer for EachIndex(ref_idx, refs_count) { LNK_ObjSymbolRef *ref = refs[ref_idx]; LNK_Obj *obj = ref->obj; - COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); String8 string_table = lnk_coff_string_table_from_obj(obj); if (func_symbol_maps[obj->input_idx] == 0) { @@ -2382,13 +2381,13 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer sect_filter |= COFF_SectionFlag_LnkRemove; // skip sections that are discarded from the output if ( ! lnk_do_debug_info(config)) { sect_filter |= LNK_SECTION_FLAG_DEBUG; } // on /DEBUG:NONE linker never invokes the debug info machinery - for EachIndex(sect_idx, obj->coff.sections.count) { - if (obj->coff.sections.headers[sect_idx].flags & sect_filter) { continue; } + for LNK_EachCoffSection(it, obj) { + if (*it.v.flags & sect_filter) { continue; } // unpack section and relocations - COFF_SectionHeader *section_header = §ion_table[sect_idx]; + COFF_SectionHeader *section_header = it.v.header; String8 section_name = coff_name_from_section_header(string_table, section_header); - U64 section_number = sect_idx+1; + U64 section_number = it.v.section_number; COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); // scan for undefined target symbols in relocations @@ -2410,7 +2409,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer LNK_ObjLineMap *line_map = line_maps[obj->input_idx]; // is reference inline site? - LNK_InlineSite *inline_site = lnk_inline_site_from_section_offset(line_map, safe_cast_u32(section_number), reloc->apply_off, symbol); + LNK_InlineSite *inline_site = lnk_inline_site_from_section_offset(line_map, section_number, reloc->apply_off, symbol); if (inline_site && inline_site->name.size) { CV_Line *line_inline = lnk_line_from_inline_site(inline_site, reloc->apply_off); if (line_inline) { @@ -2422,7 +2421,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer // default to regular line table if (source_line == 0) { U64 line_matches_count = 0; - CV_Line *line_matches = lnk_lines_from_section_offset(line_map, safe_cast_u32(section_number), reloc->apply_off, &line_matches_count); + CV_Line *line_matches = lnk_lines_from_section_offset(line_map, section_number, reloc->apply_off, &line_matches_count); source_line = line_matches_count ? &line_matches[0] : 0; } @@ -2444,7 +2443,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer // no inline site? -> lookup function in the COFF symbol table if (ref_name.size == 0) { - U32 symbol_idx = lnk_symbol_from_section_offset(func_symbol_maps[obj->input_idx], safe_cast_u32(section_number), reloc->apply_off); + U32 symbol_idx = lnk_symbol_from_section_offset(func_symbol_maps[obj->input_idx], section_number, reloc->apply_off); ref_name = symbol_idx != max_U32 ? lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx) : str8_lit("(N/A)"); } @@ -2606,7 +2605,7 @@ lnk_obj_indices_from_section_counts(Arena *arena, U64 worker_count, LNK_Obj **ob } } - worker_section_counts[min_worker_idx] += objs[obj_idx]->coff.sections.count; + worker_section_counts[min_worker_idx] += objs[obj_idx]->coff.sections.count_no_null; worker_obj_counts[min_worker_idx] += 1; } @@ -2628,7 +2627,7 @@ lnk_obj_indices_from_section_counts(Arena *arena, U64 worker_count, LNK_Obj **ob U32Array *worker_obj_indices = &obj_indices[min_worker_idx]; worker_obj_indices->v[worker_obj_counts[min_worker_idx]++] = (U32)obj_idx; worker_obj_indices->count += 1; - worker_section_counts[min_worker_idx] += objs[obj_idx]->coff.sections.count; + worker_section_counts[min_worker_idx] += objs[obj_idx]->coff.sections.count_no_null; } scratch_end(scratch); @@ -2705,10 +2704,10 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; - is_live[obj_idx] = push_array(scratch.arena, U8, obj->coff.sections.count + 1); + is_live[obj_idx] = push_array(scratch.arena, U8, obj->coff.sections.count_no_null + 1); - for EachIndex(sect_idx, obj->coff.sections.count) { - is_live[obj_idx][sect_idx + 1] = !(obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkCOMDAT); + for LNK_EachCoffSection(it, obj) { + is_live[obj_idx][it.v.section_number] = !(*it.v.flags & COFF_SectionFlag_LnkCOMDAT); } } } @@ -2739,9 +2738,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->coff.sections.count) { - U32 section_number = sect_idx+1; - COFF_SectionFlags section_flags = obj->coff.sections.headers[sect_idx].flags; + for LNK_EachCoffSection(it, obj) { + COFF_SectionFlags section_flags = *it.v.flags; // is section eligible for walking? if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } @@ -2750,7 +2748,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) if (section_flags & LNK_SECTION_FLAG_DEBUG) { continue; } // divide relocs and push task for each reloc block - COFF_RelocArray relocs = lnk_coff_reloc_info_from_section_number(obj, section_number); + COFF_RelocArray relocs = lnk_coff_reloc_info_from_section_number(obj, it.v.section_number); U64 relocs_per_batch = 1000; U64 new_task_count = CeilIntegerDiv(relocs.count, relocs_per_batch); for EachIndex(new_task_idx, new_task_count) { @@ -2798,7 +2796,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) if (ref_interp == COFF_SymbolValueInterp_Regular) { Temp temp = temp_begin(scratch2.arena); - U32List associated_sections = lnk_obj_collect_associated_sections(temp.arena, ref_symbol.obj, ref_parsed.section_number, 0); + U32List associated_sections = lnk_obj_collect_associated_section_numbers(temp.arena, ref_symbol.obj, ref_parsed.section_number, 0); // visit root section u32_list_push(temp.arena, &associated_sections, ref_parsed.section_number); @@ -2806,7 +2804,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachNode(section_n, U32Node, associated_sections.first) { U32 section_number = section_n->data; - COFF_SectionFlags section_flags = ref_symbol.obj->coff.sections.headers[section_number-1].flags; + COFF_SectionFlags section_flags = ref_symbol.obj->coff.sections.headers[section_number].flags; // on first section visit, set live flag and enqueue section U8 was_visited = ins_atomic_u8_eval_assign(&is_live[ref_symbol.obj->input_idx][section_number], 1); @@ -2873,11 +2871,10 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->coff.sections.count) { - U32 section_number = sect_idx+1; - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); - if ( ! is_live[obj->input_idx][section_number]) { - obj->coff.sections.headers[sect_idx].flags |= COFF_SectionFlag_LnkRemove; + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, it.v.section_number); + if ( ! is_live[obj->input_idx][it.v.section_number]) { + *it.v.flags |= COFF_SectionFlag_LnkRemove; } } } @@ -2890,24 +2887,23 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->coff.sections.count) { - U32 section_number = sect_idx+1; - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, it.v.section_number); U64 stat_kind = Stat_Null; - if (obj->coff.sections.headers[sect_idx].flags & LNK_SECTION_FLAG_DEBUG) { stat_kind = Stat_Debug; } - else if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntCode) { stat_kind = Stat_Code; } - else { stat_kind = Stat_Data; } + if (*it.v.flags & LNK_SECTION_FLAG_DEBUG) { stat_kind = Stat_Debug; } + else if (*it.v.flags & COFF_SectionFlag_CntCode) { stat_kind = Stat_Code; } + else { stat_kind = Stat_Data; } - if (is_live[obj->input_idx][section_number]) { + if (is_live[obj->input_idx][it.v.section_number]) { stats[stat_kind].live_count += 1; - if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntUninitializedData) { + if (*it.v.flags & COFF_SectionFlag_CntUninitializedData) { stats[stat_kind].live_vsize += section_header->vsize; } else { stats[stat_kind].live_fsize += section_header->fsize; } } else { - if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntUninitializedData) { + if (*it.v.flags & COFF_SectionFlag_CntUninitializedData) { stats[stat_kind].vsize += section_header->vsize; } else { stats[stat_kind].fsize += section_header->fsize; @@ -2971,24 +2967,23 @@ typedef enum LNK_ICF_Scope } LNK_ICF_Scope; internal LNK_ICF_Scope -lnk_icf_scope_from_section(LNK_Obj *obj, U32 sect_idx) +lnk_icf_scope_from_section_number(LNK_Obj *obj, U32 section_number) { LNK_ICF_Scope result = LNK_ICF_Scope_Null; // * section flags filter * COFF_SectionFlags expected_flags = COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_MemRead; COFF_SectionFlags exclude_flags = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_MemWrite | LNK_SECTION_FLAG_NOICF; - if ((obj->coff.sections.headers[sect_idx].flags & expected_flags) != expected_flags || (obj->coff.sections.headers[sect_idx].flags & exclude_flags) != 0) { + if ((obj->coff.sections.headers[section_number].flags & expected_flags) != expected_flags || (obj->coff.sections.headers[section_number].flags & exclude_flags) != 0) { goto exit; } - if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntCode) { + if (obj->coff.sections.headers[section_number].flags & COFF_SectionFlag_CntCode) { result = LNK_ICF_Scope_Code; goto exit; } - if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_CntInitializedData) { - U64 section_number = sect_idx + 1; + if (obj->coff.sections.headers[section_number].flags & COFF_SectionFlag_CntInitializedData) { COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); String8 section_name = str8_cstring_capped(section_header->name, section_header->name + sizeof(section_header->name)); @@ -3003,7 +2998,7 @@ lnk_icf_scope_from_section(LNK_Obj *obj, U32 sect_idx) // query COMDAT symlink that is associated with the section number // because the properties are stored in the symbol table LNK_ObjSymbolRef comdat_ref = {0}; - if (lnk_obj_get_comdat_symlink(obj, section_number, &comdat_ref)) { + if (lnk_obj_get_comdat_symlink_from_section_number(obj, section_number, &comdat_ref)) { // load COMDAT symbol name String8 comdat_name = lnk_symbol_name_from_coff_symbol_idx(comdat_ref.obj, comdat_ref.symbol_idx); @@ -3042,7 +3037,7 @@ lnk_icf_scope_from_section(LNK_Obj *obj, U32 sect_idx) } exit:; - Assert(result == LNK_ICF_Scope_Null || obj->coff.sections.comdats[sect_idx] != max_U32); // TODO: are symlinks on COMDATs optional? + Assert(result == LNK_ICF_Scope_Null || obj->coff.sections.comdats[section_number] != max_U32); // TODO: are symlinks on COMDATs optional? return result; } @@ -3085,7 +3080,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 reloc_count; U64 color_slot_idx; U32 obj_idx; - U32 sect_idx; + U32 section_number; LNK_ICF_Scope scope; } Contrib; @@ -3124,11 +3119,11 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 obj_idx = task->obj_indices[task_id].v[i]; LNK_Obj *obj = task->objs[obj_idx]; - if (obj->coff.llvm_addrsig_sect_idx >= obj->coff.sections.count) { continue; } + if (obj->coff.llvm_addrsig_section_number == 0) { continue; } // parse symbol indices and mark selected sections with NOICF flag, // the selected symbols maybe undefined/weak which need to be resolved - String8 section_data = lnk_obj_section_data_from_sect_idx(obj, obj->coff.llvm_addrsig_sect_idx); + String8 section_data = lnk_obj_section_data_from_number(obj, obj->coff.llvm_addrsig_section_number); for (U64 off = 0; off < section_data.size;) { U64 symbol_off = off; U64 symbol_idx = 0; @@ -3141,12 +3136,12 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) if (is_symbol_found) { COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(target_ref.obj, target_ref.symbol_idx); if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular) { - target_ref.obj->coff.sections.headers[symbol.section_number - 1].flags |= LNK_SECTION_FLAG_NOICF; + target_ref.obj->coff.sections.headers[symbol.section_number].flags |= LNK_SECTION_FLAG_NOICF; } } else { COFF_ParsedSymbol original_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); if (coff_interp_from_parsed_symbol(original_symbol) == COFF_SymbolValueInterp_Regular) { - obj->coff.sections.headers[original_symbol.section_number - 1].flags |= LNK_SECTION_FLAG_NOICF; + obj->coff.sections.headers[original_symbol.section_number].flags |= LNK_SECTION_FLAG_NOICF; } else { lnk_log(LNK_Log_Debug, "%S: .llvm_addrsig: contains an unresolved symbol index 0x%x at offset 0x%x", lnk_loc_from_obj(scratch.arena, obj), symbol_idx, symbol_off); } @@ -3175,8 +3170,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) for EachIndex(i, task->obj_indices[task_id].count) { U64 obj_idx = task->obj_indices[task_id].v[i]; LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->coff.sections.count) { - if (lnk_icf_scope_from_section(obj, sect_idx)) { + for LNK_EachCoffSection(it, obj) { + if (lnk_icf_scope_from_section_number(obj, it.v.section_number)) { contrib_counts[obj_idx] += 1; } } @@ -3205,13 +3200,13 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 noncontrib_count = 0; for EachIndex(obj_idx, task->objs_count) { shared.noncontrib_offsets[obj_idx] = noncontrib_count; - noncontrib_count += objs[obj_idx]->coff.sections.count - contrib_counts[obj_idx]; + noncontrib_count += objs[obj_idx]->coff.sections.count_no_null - contrib_counts[obj_idx]; } shared.color_map = push_array(scratch.arena, U64 *, task->objs_count); for EachIndex(obj_idx, task->objs_count) { LNK_Obj *obj = objs[obj_idx]; - shared.color_map[obj_idx] = push_array(scratch.arena, U64, obj->coff.sections.count); + shared.color_map[obj_idx] = push_array(scratch.arena, U64, obj->coff.sections.count_no_null + 1); } shared.contrib_offsets = offsets_from_counts_array_u64(scratch.arena, contrib_counts, task->objs_count); @@ -3236,11 +3231,11 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) LNK_Obj *obj = objs[obj_idx]; U64 cursor = 0; U64 noncontrib_cursor = 0; - for EachIndex(sect_idx, obj->coff.sections.count) { - LNK_ICF_Scope scope = lnk_icf_scope_from_section(obj, sect_idx); + for LNK_EachCoffSection(it, obj) { + LNK_ICF_Scope scope = lnk_icf_scope_from_section_number(obj, it.v.section_number); if (scope == LNK_ICF_Scope_Null) { // assign colors in object order to avoid a contended atomic allocator - shared.color_map[obj_idx][sect_idx] = LNK_ICF_Scope_COUNT + shared.noncontrib_offsets[obj_idx] + noncontrib_cursor++; + shared.color_map[obj_idx][it.v.section_number] = LNK_ICF_Scope_COUNT + shared.noncontrib_offsets[obj_idx] + noncontrib_cursor++; continue; } @@ -3248,16 +3243,16 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 contrib_idx = shared.contrib_offsets[obj_idx] + cursor++; Contrib *contrib = &shared.contribs[contrib_idx]; *contrib = (Contrib){ - .obj_idx = safe_cast_u32(obj->input_idx), - .sect_idx = safe_cast_u32(sect_idx), - .scope = scope, + .obj_idx = safe_cast_u32(obj->input_idx), + .section_number = safe_cast_u32(it.v.section_number), + .scope = scope, }; Temp temp = temp_begin(scratch.arena); // include associative children in their parent COMDAT's identity COFF_SectionFlags associated_filter = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_LnkInfo | COFF_SectionFlag_MemDiscardable | LNK_SECTION_FLAG_DEBUG; - U32List associated_sections = lnk_obj_collect_associated_sections(temp.arena, obj, sect_idx + 1, associated_filter); - u32_list_push(temp.arena, &associated_sections, sect_idx + 1); + U32List associated_sections = lnk_obj_collect_associated_section_numbers(temp.arena, obj, it.v.section_number, associated_filter); + u32_list_push(temp.arena, &associated_sections, it.v.section_number); for EachNode(associated_n, U32Node, associated_sections.first) { COFF_SectionHeader *associated_header = lnk_coff_section_header_from_section_number(obj, associated_n->data); @@ -3274,7 +3269,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 reloc_cursor = 0; for EachNode(associated_n, U32Node, associated_sections.first) { COFF_SectionHeader *associated_header = lnk_coff_section_header_from_section_number(obj, associated_n->data); - String8 associated_data = lnk_obj_section_data_from_sect_idx(obj, associated_n->data - 1); + String8 associated_data = lnk_obj_section_data_from_number(obj, associated_n->data); COFF_RelocArray associated_relocs = lnk_coff_relocs_from_section_header(obj, associated_header); lnk_hasher_update_string(&hasher, associated_data); @@ -3302,19 +3297,19 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U32 target_sect = target_symbol.section_number; // use the selected COMDAT leader so equivalent targets hash alike - if (target_sect != 0 && target_sect <= target_obj->coff.sections.count && - target_obj->coff.sections.headers[target_sect - 1].flags & COFF_SectionFlag_LnkCOMDAT) { + if (target_sect != 0 && target_sect <= target_obj->coff.sections.count_no_null && + target_obj->coff.sections.headers[target_sect].flags & COFF_SectionFlag_LnkCOMDAT) { LNK_ObjSymbolRef leader_ref = {0}; - if (lnk_obj_get_comdat_symlink(target_obj, target_sect, &leader_ref)) { + if (lnk_obj_get_comdat_symlink_from_section_number(target_obj, target_sect, &leader_ref)) { COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(leader_ref.obj, leader_ref.symbol_idx); - if (leader_symbol.section_number != 0 && leader_symbol.section_number <= leader_ref.obj->coff.sections.count) { + if (leader_symbol.section_number != 0 && leader_symbol.section_number <= leader_ref.obj->coff.sections.count_no_null) { target_obj = leader_ref.obj; target_sect = leader_symbol.section_number; } } } - target->color = &shared.color_map[target_obj->input_idx][target_sect - 1]; + target->color = &shared.color_map[target_obj->input_idx][target_sect]; } break; default: { target->static_id = Compose64Bit(target_ref.obj->input_idx, target_ref.symbol_idx); @@ -3339,7 +3334,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) contrib->static_hash = lnk_hasher_digest128(&hasher); // seed foldable sections with their immutable content and relocation shape - shared.color_map[obj_idx][sect_idx] = hash_map_hasher(str8_struct(&contrib->static_hash)) | (1ull << 63); + shared.color_map[obj_idx][it.v.section_number] = hash_map_hasher(str8_struct(&contrib->static_hash)) | (1ull << 63); temp_end(temp); } } @@ -3391,7 +3386,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) ProfBegin("Compute colored hashes"); for EachInRange(contrib_idx, shared.contrib_ranges[task_id]) { Contrib *contrib = &shared.contribs[contrib_idx]; - contrib->key.old_color = shared.color_map[contrib->obj_idx][contrib->sect_idx]; + contrib->key.old_color = shared.color_map[contrib->obj_idx][contrib->section_number]; // apply colors from section references LNK_Hasher hasher; lnk_hasher_init(&hasher, icf_hash_kind); @@ -3551,7 +3546,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) Rng1U64 obj_contrib_range = r1u64(shared.contrib_offsets[obj_idx], shared.contrib_offsets[obj_idx] + contrib_counts[obj_idx]); for EachInRange(contrib_idx, obj_contrib_range) { Contrib *contrib = &shared.contribs[contrib_idx]; - shared.color_map[contrib->obj_idx][contrib->sect_idx] = color_table.slots[contrib->color_slot_idx].color; + shared.color_map[contrib->obj_idx][contrib->section_number] = color_table.slots[contrib->color_slot_idx].color; } } ProfEnd(); @@ -3586,7 +3581,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) if (fold_stats) { FoldStats *st = fold_stats + (task_id * LNK_ICF_Scope_COUNT); - U64 fsize = lnk_coff_section_header_from_section_number(contrib_obj, contrib->sect_idx + 1)->fsize; + U64 fsize = lnk_coff_section_header_from_section_number(contrib_obj, contrib->section_number)->fsize; if (leader == contrib) { st[leader->scope].live_count += 1; st[leader->scope].live_size += fsize; @@ -3602,8 +3597,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // TODO: double-check if discarded section headers should be updated with leaders' alignment, // leaders at this point are resolved - U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->coff.sections.headers[contrib->sect_idx].flags); - COFF_SectionFlags *leader_flags = &leader_obj->coff.sections.headers[leader->sect_idx].flags; + U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->coff.sections.headers[contrib->section_number].flags); + COFF_SectionFlags *leader_flags = &leader_obj->coff.sections.headers[leader->section_number].flags; for (COFF_SectionFlags old_flags = ins_atomic_u32_eval((U32 *)leader_flags);;) { U64 leader_align = coff_align_size_from_section_flags(old_flags); if (leader_align >= contrib_align) { break; } @@ -3618,15 +3613,15 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) } // remove folded section from the image - contrib_obj->coff.sections.headers[contrib->sect_idx].flags |= COFF_SectionFlag_LnkRemove; + contrib_obj->coff.sections.headers[contrib->section_number].flags |= COFF_SectionFlag_LnkRemove; // update folded section COMDAT symlink to point to the leader section - contrib_obj->symlinks[contrib->sect_idx + 1] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->coff.sections.comdats[leader->sect_idx] }; + contrib_obj->symlinks[contrib->section_number] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->coff.sections.comdats[leader->section_number] }; #if LNK_PARANOID - String8 section_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib->sect_idx+1); - String8 leader_name = lnk_obj_section_name_from_section_number(leader_obj, leader->sect_idx+1); - lnk_log(LNK_Log_Debug, "fold %.*s[SECT%X \"%.*s\"] ==> %.*s[SECT%X \"%.*s\"]", str8_varg(lnk_loc_from_obj(scratch.arena, contrib_obj)), contrib->sect_idx+1, str8_varg(section_name), str8_varg(lnk_loc_from_obj(scratch.arena, leader_obj)), leader->sect_idx+1, str8_varg(leader_name)); + String8 section_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib->section_number); + String8 leader_name = lnk_obj_section_name_from_section_number(leader_obj, leader->section_number); + lnk_log(LNK_Log_Debug, "fold %.*s[SECT%X \"%.*s\"] ==> %.*s[SECT%X \"%.*s\"]", str8_varg(lnk_loc_from_obj(scratch.arena, contrib_obj)), contrib->section_number, str8_varg(section_name), str8_varg(lnk_loc_from_obj(scratch.arena, leader_obj)), leader->section_number, str8_varg(leader_name)); #endif } ProfEnd(); @@ -3687,11 +3682,9 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) for EachIndex(i, task->obj_indices[task_id].count) { U64 obj_idx = task->obj_indices[task_id].v[i]; LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->coff.sections.count) { - U64 section_number = sect_idx + 1; - + for LNK_EachCoffSection(it, obj) { LNK_ObjSymbolRef symlink_ref = {0}; - if (!lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; } + if (!lnk_obj_get_comdat_symlink_from_section_number(obj, it.v.section_number, &symlink_ref)) { continue; } Temp temp = temp_begin(scratch.arena); HashMap seen_hm = {0}; @@ -3700,10 +3693,10 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) for(; hop_count < hop_cap; hop_count += 1) { COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symlink_ref.obj, symlink_ref.symbol_idx); LNK_ObjSymbolRef next_symlink_ref = {0}; - if (!lnk_obj_get_comdat_symlink(symlink_ref.obj, symlink_parsed.section_number, &next_symlink_ref)) { break; } + if (!lnk_obj_get_comdat_symlink_from_section_number(symlink_ref.obj, symlink_parsed.section_number, &next_symlink_ref)) { break; } if (MemoryMatchStruct(&next_symlink_ref, &symlink_ref)) { break; } if (hash_map_search_string_u64(&seen_hm, str8_struct(&next_symlink_ref)) != 0) { - lnk_error_obj(LNK_Error_IllData, obj, "recursive COMDAT symlink in SECT%X", section_number); + lnk_error_obj(LNK_Error_IllData, obj, "recursive COMDAT symlink in SECT%X", it.v.section_number); MemoryZeroStruct(&symlink_ref); break; } @@ -3711,12 +3704,12 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) hash_map_push_string_u64(temp.arena, &seen_hm, str8_copy(temp.arena, str8_struct(&symlink_ref)), 1); } if (hop_count >= hop_cap) { - lnk_error_obj(LNK_Error_IllData, obj, "failed to flatten symlink for SECT%X; max number of hops reached", section_number); + lnk_error_obj(LNK_Error_IllData, obj, "failed to flatten symlink for SECT%X; max number of hops reached", it.v.section_number); MemoryZeroStruct(&symlink_ref); } temp_end(temp); - obj->symlinks[section_number] = symlink_ref; + obj->symlinks[it.v.section_number] = symlink_ref; } } ProfEnd(); @@ -3746,15 +3739,15 @@ internal int lnk_section_definition_is_before(void *raw_a, void *raw_b) { LNK_SectionDefinition **a = raw_a, **b = raw_b; - U64 input_idx_a = Compose64Bit((*a)->obj->input_idx, (*a)->obj_sect_idx); - U64 input_idx_b = Compose64Bit((*b)->obj->input_idx, (*b)->obj_sect_idx); + U64 input_idx_a = Compose64Bit((*a)->obj->input_idx, (*a)->obj_section_number); + U64 input_idx_b = Compose64Bit((*b)->obj->input_idx, (*b)->obj_section_number); return u64_compar_is_before(&input_idx_a, &input_idx_b); } internal B32 -lnk_should_gather_section(LNK_Obj *obj, U64 sect_idx, COFF_SectionHeader *sect_header) +lnk_should_gather_section(LNK_Obj *obj, U64 section_number, COFF_SectionHeader *sect_header) { - COFF_SectionFlags sect_flags = obj->coff.sections.headers[sect_idx].flags; + COFF_SectionFlags sect_flags = obj->coff.sections.headers[section_number].flags; // removed sections were eliminated before image layout if (sect_flags & COFF_SectionFlag_LnkRemove) { @@ -3773,7 +3766,7 @@ lnk_should_gather_section(LNK_Obj *obj, U64 sect_idx, COFF_SectionHeader *sect_h } LNK_ObjSymbolRef symlink_ref = {0}; - if (!lnk_obj_get_comdat_symlink(obj, sect_idx + 1, &symlink_ref)) { + if (!lnk_obj_get_comdat_symlink_from_section_number(obj, section_number, &symlink_ref)) { return 0; } @@ -3796,19 +3789,18 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) ProfBegin("Gather Section Definitions"); for EachInRange(obj_idx, range) { - LNK_Obj *obj = task->objs[obj_idx]; - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->coff.data, obj->coff.header.section_table_range).str; - String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); + LNK_Obj *obj = task->objs[obj_idx]; + String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); - for EachIndex(sect_idx, obj->coff.sections.count) { - COFF_SectionHeader *sect_header = §ion_table[sect_idx]; + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *sect_header = it.v.header; - if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } + if ( ! lnk_should_gather_section(obj, it.v.section_number, sect_header)) { continue; } Temp temp = temp_begin(scratch.arena); // was section defined? - COFF_SectionFlags image_sect_flags = obj->coff.sections.headers[sect_idx].flags & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); + COFF_SectionFlags image_sect_flags = *it.v.flags & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); String8 sect_name = coff_name_from_section_header(string_table, sect_header); image_sect_flags = lnk_apply_section_directives_to_flags(task->config, sect_name, image_sect_flags); String8 sect_name_with_flags = lnk_make_name_with_flags(temp.arena, sect_name, image_sect_flags); @@ -3817,10 +3809,10 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) // push new section definition if (sect_defn == 0) { sect_defn = push_array(arena, LNK_SectionDefinition, 1); - sect_defn->name = sect_name; - sect_defn->obj = obj; - sect_defn->obj_sect_idx = sect_idx; - sect_defn->flags = image_sect_flags; + sect_defn->name = sect_name; + sect_defn->obj = obj; + sect_defn->obj_section_number = it.v.section_number; + sect_defn->flags = image_sect_flags; sect_name_with_flags = push_str8_copy(arena, sect_name_with_flags); hash_table_push_string_raw(arena, sect_defn_ht, sect_name_with_flags, sect_defn); @@ -3856,8 +3848,8 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) hash_table_push_string_raw(main_arena, task->u.gather_sects.defns[0], name_with_flags, main_defn); } else { if (lnk_section_definition_is_before(§_defns[defn_idx], &main_defn)) { - main_defn->obj = sect_defns[defn_idx]->obj; - main_defn->obj_sect_idx = sect_defns[defn_idx]->obj_sect_idx; + main_defn->obj = sect_defns[defn_idx]->obj; + main_defn->obj_section_number = sect_defns[defn_idx]->obj_section_number; } main_defn->contribs_count += sect_defns[defn_idx]->contribs_count; } @@ -3887,7 +3879,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { if (str8_match(sect_n->data.name, sect_name, 0) && sect_n->data.flags != sect_defn->flags) { LNK_Obj *obj = sect_defn->obj; - U32 sect_number = sect_defn->obj_sect_idx + 1; + U32 sect_number = sect_defn->obj_section_number; COFF_SectionHeader *sect_header = lnk_coff_section_header_from_section_number(obj, sect_number); String8 sect_name = coff_name_from_section_header(str8_substr(obj->coff.data, obj->coff.header.string_table_range), sect_header); String8 expected_flags_str = coff_string_from_section_flags(main_arena, sect_n->data.flags); @@ -3918,7 +3910,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) ProfBegin("Alloc Section Map"); task->sect_map = push_array(main_arena, LNK_SectionContrib **, task->objs_count); - for EachIndex(obj_idx, task->objs_count) { task->sect_map[obj_idx] = push_array(main_arena, LNK_SectionContrib *, task->objs[obj_idx]->coff.sections.count); } + for EachIndex(obj_idx, task->objs_count) { task->sect_map[obj_idx] = push_array(main_arena, LNK_SectionContrib *, task->objs[obj_idx]->coff.sections.count_no_null + 1); } ProfEnd(); } @@ -3926,18 +3918,17 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) ProfBegin("Gather Section Contribs"); for EachInRange(obj_idx, range) { - LNK_Obj *obj = task->objs[obj_idx]; - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->coff.data, obj->coff.header.section_table_range).str; - String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); + LNK_Obj *obj = task->objs[obj_idx]; + String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); ProfBeginV("Gather Section Contribs [%S]", obj->path); - for EachIndex(sect_idx, obj->coff.sections.count) { + for LNK_EachCoffSection(it, obj) { LNK_SectionContrib *sc = task->null_sc; - COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - COFF_SectionFlags sect_flags = obj->coff.sections.headers[sect_idx].flags; - task->sect_map[obj_idx][sect_idx] = sc; + COFF_SectionHeader *sect_header = it.v.header; + COFF_SectionFlags sect_flags = *it.v.flags; + task->sect_map[obj_idx][it.v.section_number] = sc; - if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } + if ( ! lnk_should_gather_section(obj, it.v.section_number, sect_header)) { continue; } LNK_SectionContribChunk *sc_chunk = 0; { @@ -3955,7 +3946,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) if (sect_flags & COFF_SectionFlag_CntUninitializedData) { data = str8(0, sect_header->fsize); } else { - data = lnk_obj_section_data_from_sect_idx(obj, sect_idx); + data = lnk_obj_section_data_from_number(obj, it.v.section_number); } U16 sc_align = coff_align_size_from_section_flags(sect_flags); @@ -3964,10 +3955,10 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) sc->first_data_node.string = data; sc->last_data_node = &sc->first_data_node; sc->align = sc_align == 0 ? task->default_align : sc_align; - sc->u.obj_idx = obj_idx; - sc->u.obj_sect_idx = sect_idx; + sc->u.obj_idx = obj_idx; + sc->u.obj_section_number = safe_cast_u32(it.v.section_number); } - task->sect_map[obj_idx][sect_idx] = sc; + task->sect_map[obj_idx][it.v.section_number] = sc; } ProfEnd(); } @@ -3984,16 +3975,16 @@ THREAD_POOL_TASK_FUNC(lnk_set_comdat_leaders_contribs_task) LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Set COMDAT Section Contribs [%S]", obj->path); - for EachIndex(sect_idx, obj->coff.sections.count) { - U64 section_number = sect_idx+1; + for LNK_EachCoffSection(it, obj) { + U64 section_number = it.v.section_number; - if (~obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkCOMDAT) { continue; } + if (~*it.v.flags & COFF_SectionFlag_LnkCOMDAT) { continue; } LNK_ObjSymbolRef symlink_ref = {0}; - if ( ! lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; } + if ( ! lnk_obj_get_comdat_symlink_from_section_number(obj, section_number, &symlink_ref)) { continue; } COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symlink_ref.obj, symlink_ref.symbol_idx); - task->sect_map[obj_idx][sect_idx] = task->sect_map[symlink_ref.obj->input_idx][symlink_parsed.section_number - 1]; + task->sect_map[obj_idx][section_number] = task->sect_map[symlink_ref.obj->input_idx][symlink_parsed.section_number]; } ProfEnd(); } @@ -4005,12 +3996,12 @@ THREAD_POOL_TASK_FUNC(lnk_flag_debug_symbols_task) U64 obj_idx = task_id; LNK_Obj *obj = task->objs[obj_idx]; - COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + U64 symbol_idx = it.symbol_idx; + COFF_ParsedSymbol symbol = it.v; COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { - if (obj->coff.sections.headers[symbol.section_number-1].flags & LNK_SECTION_FLAG_DEBUG) { + if (obj->coff.sections.headers[symbol.section_number].flags & LNK_SECTION_FLAG_DEBUG) { task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx] = 1; } } @@ -4025,15 +4016,15 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Patch COMDAT Offsets in %S", obj->path); - COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + U64 symbol_idx = it.symbol_idx; + COFF_ParsedSymbol symbol = it.v; COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); if (interp != COFF_SymbolValueInterp_Regular) { continue; } LNK_ObjSymbolRef symlink_ref = {0}; - if ( ! lnk_obj_get_comdat_symlink(obj, symbol.section_number, &symlink_ref)) { continue; } + if ( ! lnk_obj_get_comdat_symlink_from_section_number(obj, symbol.section_number, &symlink_ref)) { continue; } COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symlink_ref.obj, symlink_ref.symbol_idx); if (symlink_ref.obj == obj && leader_symbol.section_number == symbol.section_number) { continue; } @@ -4046,7 +4037,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) B32 should_patch = 0; if (is_same_obj) { - B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static && obj->coff.sections.comdats[symbol.section_number-1] == symbol_idx; + B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static && obj->coff.sections.comdats[symbol.section_number] == symbol_idx; if (is_external || is_static_comdat_leader) { section_number = leader_symbol.section_number; value = leader_symbol.value; @@ -4069,9 +4060,8 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) } if (should_patch) { - U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - obj->coff.symbols.section_numbers[primary_idx] = section_number; - obj->coff.symbols.values [primary_idx] = value; + obj->coff.symbols.section_numbers[it.primary_idx] = section_number; + obj->coff.symbols.values [it.primary_idx] = value; } } ProfEnd(); @@ -4081,8 +4071,8 @@ internal int lnk_section_contrib_ptr_is_before(void *raw_a, void *raw_b) { LNK_SectionContrib **a = raw_a, **b = raw_b; - U64 input_idx_a = Compose64Bit((*a)->u.obj_idx, (*a)->u.obj_sect_idx); - U64 input_idx_b = Compose64Bit((*b)->u.obj_idx, (*b)->u.obj_sect_idx); + U64 input_idx_a = Compose64Bit((*a)->u.obj_idx, (*a)->u.obj_section_number); + U64 input_idx_b = Compose64Bit((*b)->u.obj_idx, (*b)->u.obj_section_number); return u64_compar_is_before(&input_idx_a, &input_idx_b); } @@ -4138,7 +4128,7 @@ lnk_sort_contribs_chunk_radix(TP_Context *tp, Arena *arena, LNK_SectionContribCh U32 *indices = push_array_no_zero(scratch.arena, U32, count); U64 max_key = 0; for EachIndex(i, count) { - U64 key = Compose64Bit(chunk->v[i]->u.obj_idx, chunk->v[i]->u.obj_sect_idx); + U64 key = Compose64Bit(chunk->v[i]->u.obj_idx, chunk->v[i]->u.obj_section_number); keys[i] = key; indices[i] = (U32)i; max_key = Max(max_key, key); @@ -4250,19 +4240,18 @@ THREAD_POOL_TASK_FUNC(lnk_patch_common_block_symbols_task) LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Patch Common Block Symbols [%S]", obj->path); - COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + U64 symbol_idx = it.symbol_idx; + COFF_ParsedSymbol symbol = it.v; COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Common) { LNK_Symbol *defn = lnk_symbol_table_search(task->symtab, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx)); COFF_ParsedSymbol defn_parsed = lnk_parsed_from_symbol(defn); Assert(lnk_interp_from_symbol(defn) == COFF_SymbolValueInterp_Regular); if (defn) { - U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - obj->coff.symbols.section_numbers[primary_idx] = defn_parsed.section_number; - obj->coff.symbols.values [primary_idx] = safe_cast_u32(defn_parsed.value); - obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_Static; + obj->coff.symbols.section_numbers[it.primary_idx] = defn_parsed.section_number; + obj->coff.symbols.values [it.primary_idx] = safe_cast_u32(defn_parsed.value); + obj->coff.symbols.storage_classes[it.primary_idx] = COFF_SymStorageClass_Static; } } } @@ -4277,15 +4266,15 @@ THREAD_POOL_TASK_FUNC(lnk_patch_regular_symbols_task) LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Patch Regular Symbols [%S]", obj->path); - COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + U64 symbol_idx = it.symbol_idx; + COFF_ParsedSymbol symbol = it.v; if (task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx]) { continue; } COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular) { - LNK_SectionContrib *sc = task->sect_map[obj_idx][symbol.section_number-1]; + LNK_SectionContrib *sc = task->sect_map[obj_idx][symbol.section_number]; U32 section_number; U32 value; if (sc == task->null_sc) { @@ -4296,9 +4285,8 @@ THREAD_POOL_TASK_FUNC(lnk_patch_regular_symbols_task) value = sc->u.off + symbol.value; } - U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - obj->coff.symbols.section_numbers[primary_idx] = section_number; - obj->coff.symbols.values [primary_idx] = value; + obj->coff.symbols.section_numbers[it.primary_idx] = section_number; + obj->coff.symbols.values [it.primary_idx] = value; } } ProfEnd(); @@ -4309,9 +4297,9 @@ lnk_patch_obj_symtab(LNK_SymbolTable *symtab, LNK_Obj *obj, B8 *was_symbol_patch { ProfBeginV("%S\n", obj->path); - COFF_ParsedSymbol fixup_dst; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + fixup_dst.aux_symbol_count)) { - fixup_dst = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + U64 symbol_idx = it.symbol_idx; + COFF_ParsedSymbol fixup_dst = it.v; if (was_symbol_patched[symbol_idx]) { continue; } COFF_SymbolValueInterpType fixup_dst_type = coff_interp_symbol(fixup_dst.section_number, fixup_dst.value, fixup_dst.storage_class); @@ -4335,10 +4323,9 @@ lnk_patch_obj_symtab(LNK_SymbolTable *symtab, LNK_Obj *obj, B8 *was_symbol_patch value = fixup_src.value; } - U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - obj->coff.symbols.section_numbers[primary_idx] = section_number; - obj->coff.symbols.values [primary_idx] = value; - obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_Static; + obj->coff.symbols.section_numbers[it.primary_idx] = section_number; + obj->coff.symbols.values [it.primary_idx] = value; + obj->coff.symbols.storage_classes[it.primary_idx] = COFF_SymStorageClass_Static; was_symbol_patched[symbol_idx] = 1; } @@ -4418,17 +4405,15 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) LNK_Obj *obj = task->objs[task_id]; COFF_FileHeaderInfo obj_header = obj->coff.header; - COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - String8 symbol_table = lnk_coff_symbol_table_from_obj(obj); String8 string_table = lnk_coff_string_table_from_obj(obj); U32 closest_sect = 0; U32 closest_reloc = 0; U32 closest_foff = max_U32; - for EachIndex(sect_idx, obj_header.section_count_no_null) { - COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->coff.sections.headers[sect_idx].flags; + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *section_header = it.v.header; + COFF_SectionFlags section_flags = *it.v.flags; if (section_flags & COFF_SectionFlag_LnkInfo) { continue; } if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } @@ -4436,7 +4421,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) // get section bytes (special case debug info because it is not copied to the image) Rng1U64 section_frange = rng_1u64(section_header->foff, section_header->foff + section_header->fsize); - String8 section_data = section_flags & LNK_SECTION_FLAG_DEBUG ? lnk_obj_section_data_from_sect_idx(obj, sect_idx) : str8_substr(task->image_data, section_frange); + String8 section_data = section_flags & LNK_SECTION_FLAG_DEBUG ? lnk_obj_section_data_from_number(obj, it.v.section_number) : str8_substr(task->image_data, section_frange); // apply relocs COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); @@ -4466,9 +4451,9 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) if (interp == COFF_SymbolValueInterp_Regular) { if (symbol.section_number == lnk_obj_get_removed_section_number(obj)) { if (~section_flags & LNK_SECTION_FLAG_DEBUG) { - String8 sect_name = coff_name_from_section_header(string_table, §ion_table[sect_idx]); + String8 sect_name = coff_name_from_section_header(string_table, section_header); String8 symbol_name = lnk_symbol_name_from_coff_symbol_idx(obj, reloc->isymbol); - lnk_error_obj(LNK_Error_RelocationAgainstRemovedSection, obj, "relocating against symbol that is in a removed section (symbol: %S, reloc-section: %S 0x%llx, reloc-index: 0x%llx)", symbol_name, sect_name, sect_idx+1, reloc_idx); + lnk_error_obj(LNK_Error_RelocationAgainstRemovedSection, obj, "relocating against symbol that is in a removed section (symbol: %S, reloc-section: %S 0x%llx, reloc-index: 0x%llx)", symbol_name, sect_name, it.v.section_number, reloc_idx); } continue; } @@ -4566,14 +4551,13 @@ THREAD_POOL_TASK_FUNC(lnk_flag_hotpatch_contribs_task) LNK_Obj *obj = task->objs[obj_idx]; if (obj->coff.hotpatch) { - COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + COFF_ParsedSymbol symbol = it.v; COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); if (interp == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { - LNK_SectionContrib *sc = task->sect_map[obj_idx][symbol.section_number-1]; + LNK_SectionContrib *sc = task->sect_map[obj_idx][symbol.section_number]; if (sc != task->null_sc) { - sc->hotpatch = !!(obj->coff.sections.headers[symbol.section_number-1].flags & COFF_SectionFlag_CntCode); + sc->hotpatch = !!(obj->coff.sections.headers[symbol.section_number].flags & COFF_SectionFlag_CntCode); } } } @@ -4934,11 +4918,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_virtual_offsets_and_sizes_in_obj_section_headers LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Patch Virtual Offset And Size In Section Headers [%S]", obj->path); - COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { - COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - if (~obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkRemove) { - LNK_SectionContrib *sc = task->sect_map[obj_idx][sect_idx]; + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *sect_header = it.v.header; + if (~*it.v.flags & COFF_SectionFlag_LnkRemove) { + LNK_SectionContrib *sc = task->sect_map[obj_idx][it.v.section_number]; LNK_Section *sect = task->image_sects.v[sc->u.sect_idx]; sect_header->vsize = lnk_size_from_section_contrib(sc); sect_header->voff = sect->voff + sc->u.off; @@ -4955,14 +4938,13 @@ THREAD_POOL_TASK_FUNC(lnk_patch_file_offsets_and_sizes_in_obj_section_headers_ta LNK_Obj *obj = task->objs[obj_idx]; ProfBeginV("Patch File Offsets And Sizes In Obj Section Headers [%S]", obj->path); - COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { - COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - COFF_SectionFlags sect_flags = obj->coff.sections.headers[sect_idx].flags; + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *sect_header = it.v.header; + COFF_SectionFlags sect_flags = *it.v.flags; B32 patch_section_header = (~sect_flags & COFF_SectionFlag_LnkRemove) && (~sect_flags & LNK_SECTION_FLAG_DEBUG); if (patch_section_header) { - LNK_SectionContrib *sc = task->sect_map[obj_idx][sect_idx]; + LNK_SectionContrib *sc = task->sect_map[obj_idx][it.v.section_number]; LNK_Section *sect = task->image_sects.v[sc->u.sect_idx]; if (~sect->flags & COFF_SectionFlag_CntUninitializedData) { sect_header->fsize = lnk_size_from_section_contrib(sc); @@ -4981,20 +4963,23 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) LNK_Obj *obj = task->objs[obj_idx]; ProfBegin("Patch Section Symbols [%S]", obj->path); - COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); - COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); + for LNK_EachCoffSymbol(it, obj) { + U64 symbol_idx = it.symbol_idx; + COFF_ParsedSymbol symbol = it.v; + COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); + if (interp == COFF_SymbolValueInterp_Undefined) { if (symbol.storage_class == COFF_SymStorageClass_Section) { + B32 is_referenced = 0; - COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - for EachIndex(sect_idx, obj->coff.sections.count) { - COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->coff.sections.headers[sect_idx].flags; + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *section_header = it.v.header; + COFF_SectionFlags section_flags = *it.v.flags; + if (section_flags & COFF_SectionFlag_LnkRemove) { continue; } if (section_flags & COFF_SectionFlag_LnkInfo) { continue; } if (section_flags & LNK_SECTION_FLAG_DEBUG) { continue; } + COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); for EachIndex(reloc_idx, relocs.count) { if (relocs.v[reloc_idx].isymbol == symbol_idx) { @@ -5002,6 +4987,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) break; } } + if (is_referenced) { break; } } if (!is_referenced) { continue; } @@ -5010,32 +4996,32 @@ THREAD_POOL_TASK_FUNC(lnk_patch_section_symbols_task) LNK_Section *sect = lnk_section_table_search(task->sectab, symbol_name, symbol.value); if (sect && (~sect->flags & COFF_SectionFlag_LnkRemove)) { if (~sect->flags & COFF_SectionFlag_MemDiscardable) { + LNK_SectionContrib *first_sc = lnk_get_first_section_contrib(sect); - U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - obj->coff.symbols.section_numbers[primary_idx] = safe_cast_u32(first_sc->u.sect_idx + 1); - obj->coff.symbols.values [primary_idx] = first_sc->u.off; - obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_Static; + obj->coff.symbols.section_numbers[it.primary_idx] = safe_cast_u32(first_sc->u.sect_idx + 1); + obj->coff.symbols.values [it.primary_idx] = first_sc->u.off; + obj->coff.symbols.storage_classes[it.primary_idx] = COFF_SymStorageClass_Static; + } else { lnk_error_obj(LNK_Error_SectRefsDiscardedMemory, obj, "symbol %S (No. 0x%llx) references section with discard flag", symbol_name, symbol_idx); } } else { - U64 fallback_voff = 0; + U64 fallback_voff = 0; U64 fallback_align = Max(task->sect_align, KB(4)); for EachIndex(sect_idx, task->image_sects.count) { - LNK_Section *image_sect = task->image_sects.v[sect_idx]; - U64 image_sect_size = AlignPow2(Max(image_sect->vsize, image_sect->fsize), fallback_align); + LNK_Section *image_sect = task->image_sects.v[sect_idx]; + U64 image_sect_size = AlignPow2(Max(image_sect->vsize, image_sect->fsize), fallback_align); + if (image_sect_size == 0) { image_sect_size = fallback_align; } + fallback_voff = Max(fallback_voff, image_sect->voff + image_sect_size); } fallback_voff = AlignPow2(fallback_voff, fallback_align); LNK_Section *fallback_sect = task->image_sects.v[task->image_sects.count-1]; - U32 fallback_section_number = safe_cast_u32(fallback_sect->sect_idx + 1); - U32 fallback_section_offset = safe_cast_u32(fallback_voff - fallback_sect->voff); - U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(obj, symbol_idx); - obj->coff.symbols.section_numbers[primary_idx] = fallback_section_number; - obj->coff.symbols.values [primary_idx] = fallback_section_offset; - obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_Static; + obj->coff.symbols.section_numbers[it.primary_idx] = safe_cast_u32(fallback_sect->sect_idx + 1); + obj->coff.symbols.values [it.primary_idx] = safe_cast_u32(fallback_voff - fallback_sect->voff); + obj->coff.symbols.storage_classes[it.primary_idx] = COFF_SymStorageClass_Static; lnk_error_obj(LNK_Warning_UndefinedSectionSymbol, obj, "undefined section symbol %S (No. 0x%llx) refers to an image section that doesn't exist; patching to %#llx", symbol_name, symbol_idx, fallback_voff); } @@ -5052,12 +5038,11 @@ THREAD_POOL_TASK_FUNC(lnk_gather_base_reloc_pages_task) HashTable *page_ht = task->gather.page_ht[worker_id]; LNK_BaseRelocPageList *pages = &task->gather.pages[worker_id]; LNK_Obj *obj = task->gather.objs[task_id]; - COFF_SectionHeader *sect_table = lnk_coff_section_table_from_obj(obj); ProfBeginV("%S", obj->path); - for EachIndex(sect_idx, obj->coff.sections.count) { - COFF_SectionHeader *sect_header = §_table[sect_idx]; - if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkRemove) { continue; } + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *sect_header = it.v.header; + if (*it.v.flags & COFF_SectionFlag_LnkRemove) { continue; } COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, sect_header); for EachIndex(reloc_idx, relocs.count) { @@ -5672,7 +5657,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT LNK_SectionContribChunk *common_block_chunk = lnk_section_contrib_chunk_list_push_chunk(sectab->arena, &common_block_sect->contribs, 1, str8(0,0)); LNK_SectionContrib *common_block_sc = lnk_section_contrib_chunk_push(common_block_chunk, 1); common_block_sc->u.obj_idx = max_U32; - common_block_sc->u.obj_sect_idx = max_U32; + common_block_sc->u.obj_section_number = 0; common_block_sc->align = 1; common_block_sc->first_data_node.next = 0; common_block_sc->first_data_node.string = str8(0, common_block_cursor - common_block_sect->vsize); @@ -6122,62 +6107,59 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT } internal PairU32 * -lnk_obj_sect_idx_from_section(Arena *arena, U64 objs_count, LNK_Obj **objs, LNK_Section *sect, LNK_Config *config, U64 *obj_sect_idxs_count_out) +lnk_obj_section_numbers_from_section(Arena *arena, U64 objs_count, LNK_Obj **objs, LNK_Section *sect, LNK_Config *config, U64 *obj_section_numbers_count_out) { U64 max_contribs = 0; for (LNK_SectionContribChunk *chunk = sect->contribs.first; chunk != 0; chunk = chunk->next) { max_contribs += chunk->count; } - U64 obj_sect_idxs_count = 0; - PairU32 *obj_sect_idxs = push_array(arena, PairU32, max_contribs); + U64 obj_section_numbers_count = 0; + PairU32 *obj_section_numbers = push_array(arena, PairU32, max_contribs); for (U64 obj_idx = 0; obj_idx < objs_count; obj_idx += 1) { LNK_Obj *obj = objs[obj_idx]; - COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->coff.data, obj->coff.header.section_table_range.min, 0); String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); - for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { - COFF_SectionHeader *section_header = §ion_table[sect_idx]; + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *section_header = it.v.header; String8 full_section_name = coff_name_from_section_header(string_table, section_header); String8 section_name, section_postfix; coff_parse_section_name(full_section_name, §ion_name, §ion_postfix); - if (obj->coff.sections.headers[sect_idx].flags & COFF_SectionFlag_LnkRemove) { continue; } + if (*it.v.flags & COFF_SectionFlag_LnkRemove) { continue; } if (section_header->fsize == 0) { continue; } if (lnk_is_section_removed(config, section_name)) { continue; } if (sect->voff <= section_header->voff && section_header->voff < sect->voff + sect->vsize) { - Assert(obj_sect_idxs_count < max_contribs); - obj_sect_idxs[obj_sect_idxs_count].v0 = obj_idx; - obj_sect_idxs[obj_sect_idxs_count].v1 = sect_idx; - obj_sect_idxs_count += 1; + Assert(obj_section_numbers_count < max_contribs); + obj_section_numbers[obj_section_numbers_count].v0 = obj_idx; + obj_section_numbers[obj_section_numbers_count].v1 = it.v.section_number; + obj_section_numbers_count += 1; } } } - U64 pop_size = (max_contribs - obj_sect_idxs_count) * sizeof(obj_sect_idxs[0]); + U64 pop_size = (max_contribs - obj_section_numbers_count) * sizeof(obj_section_numbers[0]); arena_pop(arena, pop_size); - *obj_sect_idxs_count_out = obj_sect_idxs_count; + *obj_section_numbers_count_out = obj_section_numbers_count; - return obj_sect_idxs; + return obj_section_numbers; } internal COFF_SectionHeader * -lnk_coff_section_header_from_obj_sect_idx_pair(LNK_Obj **objs, PairU32 p) +lnk_coff_section_header_from_obj_section_number_pair(LNK_Obj **objs, PairU32 p) { - LNK_Obj *obj = objs[p.v0]; - COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->coff.data, obj->coff.header.section_table_range.min, 0); - return §ion_table[p.v1]; + return lnk_coff_section_header_from_section_number(objs[p.v0], p.v1); } global LNK_Obj **g_rad_map_objs; internal int -lnk_obj_sect_idx_is_before(void *raw_a, void *raw_b) +lnk_obj_section_number_is_before(void *raw_a, void *raw_b) { PairU32 *a = raw_a, *b = raw_b; - COFF_SectionHeader *section_header_a = lnk_coff_section_header_from_obj_sect_idx_pair(g_rad_map_objs, *a); - COFF_SectionHeader *section_header_b = lnk_coff_section_header_from_obj_sect_idx_pair(g_rad_map_objs, *b); + COFF_SectionHeader *section_header_a = lnk_coff_section_header_from_obj_section_number_pair(g_rad_map_objs, *a); + COFF_SectionHeader *section_header_b = lnk_coff_section_header_from_obj_section_number_pair(g_rad_map_objs, *b); return section_header_a->voff < section_header_b->voff; } @@ -6187,12 +6169,12 @@ lnk_pair_u32_nearest_section(PairU32 *arr, U64 count, LNK_Obj **objs, U32 voff) U64 result = max_U64; if (count > 0) { - COFF_SectionHeader *first = lnk_coff_section_header_from_obj_sect_idx_pair(objs, arr[0]); + COFF_SectionHeader *first = lnk_coff_section_header_from_obj_section_number_pair(objs, arr[0]); if (first->voff == voff) { return 0; } - COFF_SectionHeader *last = lnk_coff_section_header_from_obj_sect_idx_pair(objs, arr[count-1]); + COFF_SectionHeader *last = lnk_coff_section_header_from_obj_section_number_pair(objs, arr[count-1]); if (last->voff <= voff) { return count - 1; } @@ -6202,7 +6184,7 @@ lnk_pair_u32_nearest_section(PairU32 *arr, U64 count, LNK_Obj **objs, U32 voff) U64 r = count - 1; for (; l <= r; ) { U64 m = l + (r - l) / 2; - COFF_SectionHeader *s = lnk_coff_section_header_from_obj_sect_idx_pair(objs, arr[m]); + COFF_SectionHeader *s = lnk_coff_section_header_from_obj_section_number_pair(objs, arr[m]); if (s->voff == voff) { return m; } else if (s->voff < voff) { @@ -6211,7 +6193,7 @@ lnk_pair_u32_nearest_section(PairU32 *arr, U64 count, LNK_Obj **objs, U32 voff) r = m - 1; } } - result = l; + result = l - 1; } } @@ -6237,10 +6219,10 @@ lnk_build_rad_map(Arena *arena, String8 image_data, LNK_Config *config, U64 objs str8_list_pushf(arena, &map, "%S\n", sect->name); str8_list_pushf(arena, &map, "%-4s %-8s %-8s %-8s %-8s %-16s %-4s %s\n", "No.", "VirtOff", "VirtSize", "FileOff", "FileSize", "Blake3", "Algn", "SC"); - U64 obj_sect_idxs_count = 0; - PairU32 *obj_sect_idxs = lnk_obj_sect_idx_from_section(scratch.arena, objs_count, objs, sect, config, &obj_sect_idxs_count); + U64 obj_section_numbers_count = 0; + PairU32 *obj_section_numbers = lnk_obj_section_numbers_from_section(scratch.arena, objs_count, objs, sect, config, &obj_section_numbers_count); g_rad_map_objs = objs; - radsort(obj_sect_idxs, obj_sect_idxs_count, lnk_obj_sect_idx_is_before); + radsort(obj_section_numbers, obj_section_numbers_count, lnk_obj_section_number_is_before); U64 global_sc_idx = 0; for (LNK_SectionContribChunk *sc_chunk = sect->contribs.first; sc_chunk != 0; sc_chunk = sc_chunk->next) { @@ -6255,11 +6237,11 @@ lnk_build_rad_map(Arena *arena, String8 image_data, LNK_Config *config, U64 objs String8 sc_data = str8_substr(image_data, rng_1u64(file_off, file_off + virt_size)); LNK_Obj *obj = 0; - U32 sect_idx = 0; - U64 obj_sect_idx_idx = lnk_pair_u32_nearest_section(obj_sect_idxs, obj_sect_idxs_count, objs, virt_off); - if (obj_sect_idx_idx < obj_sect_idxs_count) { - obj = objs[obj_sect_idxs[obj_sect_idx_idx].v0]; - sect_idx = obj_sect_idxs[obj_sect_idx_idx].v1; + U32 section_number = 0; + U64 obj_section_number_idx = lnk_pair_u32_nearest_section(obj_section_numbers, obj_section_numbers_count, objs, virt_off); + if (obj_section_number_idx < obj_section_numbers_count) { + obj = objs[obj_section_numbers[obj_section_number_idx].v0]; + section_number = obj_section_numbers[obj_section_number_idx].v1; } U128 sc_hash = {0}; @@ -6280,16 +6262,16 @@ lnk_build_rad_map(Arena *arena, String8 image_data, LNK_Config *config, U64 objs { String8List source_list = {0}; if (obj) { - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, sect_idx+1); + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); String8 section_name = coff_name_from_section_header(string_table, section_header); LNK_Lib *lib = lnk_obj_get_lib(obj); if (lib) { String8 lib_name = str8_chop_last_dot(str8_skip_last_slash(lib->path)); String8 obj_name = str8_skip_last_slash(obj->path); - str8_list_pushf(temp.arena, &source_list, "%S(%S) SECT%X (%S)", lib_name, obj_name, sect_idx+1, section_name); + str8_list_pushf(temp.arena, &source_list, "%S(%S) SECT%X (%S)", lib_name, obj_name, section_number, section_name); } else { - str8_list_pushf(temp.arena, &source_list, "%S SECT%X (%S)", obj->path, sect_idx+1, section_name); + str8_list_pushf(temp.arena, &source_list, "%S SECT%X (%S)", obj->path, section_number, section_name); } } else { str8_list_pushf(temp.arena, &source_list, ""); @@ -6308,19 +6290,17 @@ lnk_build_rad_map(Arena *arena, String8 image_data, LNK_Config *config, U64 objs str8_list_pushf(arena, &map, "# DEBUG\n"); for (U64 obj_idx = 0; obj_idx < objs_count; obj_idx += 1) { - LNK_Obj *obj = objs[obj_idx]; - COFF_SectionHeader *section_table = str8_deserial_get_raw_ptr(obj->coff.data, obj->coff.header.section_table_range.min, 0); - for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { - COFF_SectionHeader *section_header = §ion_table[sect_idx]; - COFF_SectionFlags section_flags = obj->coff.sections.headers[sect_idx].flags; + LNK_Obj *obj = objs[obj_idx]; + for LNK_EachCoffSection(it, obj) { + COFF_SectionFlags section_flags = *it.v.flags; if (~section_flags & COFF_SectionFlag_LnkRemove && section_flags & LNK_SECTION_FLAG_DEBUG) { LNK_Lib *lib = lnk_obj_get_lib(obj); if (lib) { String8 lib_name = str8_chop_last_dot(str8_skip_last_slash(lib->path)); String8 obj_name = str8_skip_last_slash(obj->path); - str8_list_pushf(arena, &map, "%S(%S) SECT%X\n", lib_name, obj_name, sect_idx+1); + str8_list_pushf(arena, &map, "%S(%S) SECT%X\n", lib_name, obj_name, it.v.section_number); } else { - str8_list_pushf(arena, &map, "%S SECT%X\n", obj->path, sect_idx+1); + str8_list_pushf(arena, &map, "%S SECT%X\n", obj->path, it.v.section_number); } } } @@ -6806,11 +6786,11 @@ lnk_run_type_server(TP_Context *tp, TP_Arena *arena, LNK_Config *config) if (obj->link_member) { continue; } // does obj have debug types? - if (obj->coff.debug_p_sect_idx < obj->coff.sections.count) { + if (obj->coff.debug_p_section_number > 0) { u64_list_push(scratch.arena, &include_obj_list, obj_idx); - } else if (obj->coff.debug_t_sect_idx < obj->coff.sections.count) { + } else if (obj->coff.debug_t_section_number > 0) { // read first leaf - String8 raw_debug_t = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_t_sect_idx); + String8 raw_debug_t = lnk_obj_section_data_from_number(obj, obj->coff.debug_t_section_number); CV_Leaf leaf = {0}; cv_read_leaf(raw_debug_t, 0, 1, &leaf); diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 3ca9e150..86192015 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -130,7 +130,7 @@ typedef struct LNK_SectionDefinition COFF_SectionFlags flags; U64 contribs_count; struct LNK_Obj *obj; - U64 obj_sect_idx; + U64 obj_section_number; } LNK_SectionDefinition; typedef struct LNK_CommonBlockContrib diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 70ee1dab..d18791cb 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -76,8 +76,8 @@ THREAD_POOL_TASK_FUNC(lnk_parse_debug_h_task) LNK_CodeViewInput *task = raw_task; LNK_Obj *obj = task->obj_arr[obj_idx]; - if (obj->coff.debug_h_sect_idx < obj->coff.sections.count) { - String8 raw_debug_h = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_h_sect_idx); + if (obj->coff.debug_h_section_number > 0) { + String8 raw_debug_h = lnk_obj_section_data_from_number(obj, obj->coff.debug_h_section_number); CV_DebugH *debug_h = &task->debug_h_arr[obj_idx]; LLVM_GHash ghash = {0}; U64 ghash_read_size = str8_deserial_read_struct(raw_debug_h, 0, &ghash); @@ -699,9 +699,9 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, if (rrt->obj_time_stamps[rrt_obj_idx] != obj_file_props.modified) { continue; } // invalidate debug section pointers - obj->coff.debug_t_sect_idx = ~0; - obj->coff.debug_p_sect_idx = ~0; - obj->coff.debug_h_sect_idx = ~0; + obj->coff.debug_t_section_number = 0; + obj->coff.debug_p_section_number = 0; + obj->coff.debug_h_section_number = 0; // apply type index map obj->ti_range = rrt->obj_ti_ranges[rrt_obj_idx]; @@ -733,15 +733,15 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, for EachNode(n, String8Node, input.debug_s_list_arr[obj_idx].first) { total_debug_s_size += n->string.size; } - if (obj->coff.debug_t_sect_idx < obj->coff.sections.count) { - total_debug_t_size += lnk_obj_section_from_sect_idx(obj, obj->coff.debug_t_sect_idx).header->fsize; + if (obj->coff.debug_t_section_number > 0) { + total_debug_t_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_t_section_number)->fsize; } - if (obj->coff.debug_p_sect_idx < obj->coff.sections.count) { - total_debug_p_size += lnk_obj_section_from_sect_idx(obj, obj->coff.debug_p_sect_idx).header->fsize; + if (obj->coff.debug_p_section_number > 0) { + total_debug_p_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_p_section_number)->fsize; } if (config->ghash) { - if (obj->coff.debug_h_sect_idx < obj->coff.sections.count) { - total_debug_h_size += lnk_obj_section_from_sect_idx(obj, obj->coff.debug_h_sect_idx).header->fsize; + if (obj->coff.debug_h_section_number > 0) { + total_debug_h_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_h_section_number)->fsize; } } } @@ -783,16 +783,16 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, for EachIndex(obj_idx, obj_count) { LNK_Obj *obj = obj_arr[obj_idx]; - if (obj->coff.debug_t_sect_idx < obj->coff.sections.count) { + if (obj->coff.debug_t_section_number > 0) { raw_debug_t_arr[obj_idx].count = 1; raw_debug_t_arr[obj_idx].v = push_array(scratch.arena, String8, 1); - raw_debug_t_arr[obj_idx].v[0] = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_t_sect_idx); + raw_debug_t_arr[obj_idx].v[0] = lnk_obj_section_data_from_number(obj, obj->coff.debug_t_section_number); } - if (obj->coff.debug_p_sect_idx < obj->coff.sections.count) { + if (obj->coff.debug_p_section_number > 0) { raw_debug_p_arr[obj_idx].count = 1; raw_debug_p_arr[obj_idx].v = push_array(scratch.arena, String8, 1); - raw_debug_p_arr[obj_idx].v[0] = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_p_sect_idx); + raw_debug_p_arr[obj_idx].v[0] = lnk_obj_section_data_from_number(obj, obj->coff.debug_p_section_number); } } @@ -2849,20 +2849,19 @@ THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) LNK_Obj *obj = task->cv->obj_arr[obj_idx]; PDB_DbiSCArray *sc_array = &task->sc_arrays[obj_idx]; - sc_array->v = push_array_no_zero(arena, PDB_DbiSC, obj->coff.sections.count); - sc_array->cap = obj->coff.sections.count; - - for EachIndex(sect_idx, obj->coff.sections.count) { + sc_array->v = push_array_no_zero(arena, PDB_DbiSC, obj->coff.sections.count_no_null); + sc_array->cap = obj->coff.sections.count_no_null; + for LNK_EachCoffSection(it, obj) { // filter by section flags - if (obj->coff.sections.headers[sect_idx].flags & (COFF_SectionFlag_LnkInfo | COFF_SectionFlag_LnkRemove | LNK_SECTION_FLAG_DEBUG)) { continue; } + if (*it.v.flags & (COFF_SectionFlag_LnkInfo | COFF_SectionFlag_LnkRemove | LNK_SECTION_FLAG_DEBUG)) { continue; } // skip unwind info for the section contribution - String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); + String8 section_name = lnk_obj_section_name_from_section_number(obj, it.v.section_number); if (str8_match(section_name, str8_lit(".pdata"), 0)) { continue; } // load section and determine its type - LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); + LNK_ObjSection section = it.v; B32 is_virt = !!(*section.flags & COFF_SectionFlag_CntUninitializedData); // pick section range diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 0fcab0b7..b79edf31 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -93,8 +93,8 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) lnk_error_input_obj(LNK_Error_IllData, input, "corrupted file, unable to read string table"); } - COFF_SectionHeader *section_headers = push_array_no_zero(arena, COFF_SectionHeader, header.section_count_no_null); - MemoryCopy(section_headers, raw_coff_section_table.str, raw_coff_section_table.size); + COFF_SectionHeader *section_headers = push_array(arena, COFF_SectionHeader, header.section_count_no_null + 1); + MemoryCopy(section_headers + 1, raw_coff_section_table.str, raw_coff_section_table.size); // // section table pass @@ -103,13 +103,14 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) // - find debug info and meta-data leaders // COFF_SectionHeader *coff_section_table = (COFF_SectionHeader *)raw_coff_section_table.str; - U32 debug_t_sect_idx = ~0; - U32 debug_p_sect_idx = ~0; - U32 debug_h_sect_idx = ~0; - U32 llvm_addrsig_sect_idx = ~0; - for (U64 sect_idx = 0; sect_idx < header.section_count_no_null; sect_idx += 1) { + U32 debug_t_section_number = 0; + U32 debug_p_section_number = 0; + U32 debug_h_section_number = 0; + U32 llvm_addrsig_section_number = 0; + for EachIndex(sect_idx, header.section_count_no_null) { + U64 section_number = sect_idx + 1; COFF_SectionHeader *coff_sect_header = &coff_section_table[sect_idx]; - COFF_SectionFlags *section_flags = §ion_headers[sect_idx].flags; + COFF_SectionFlags *section_flags = §ion_headers[section_number].flags; String8 sect_name = coff_name_from_section_header(raw_coff_string_table, coff_sect_header); *section_flags = coff_sect_header->flags & ~3; // linker reserves low 2 bits for internal flags @@ -123,15 +124,15 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } if (str8_match(sect_name, str8_lit(".debug$T"), 0)) { - debug_t_sect_idx = sect_idx; + debug_t_section_number = section_number; } else if (str8_match(sect_name, str8_lit(".debug$P"), 0)) { - debug_p_sect_idx = sect_idx; + debug_p_section_number = section_number; } else if (str8_match(sect_name, str8_lit(".debug$H"), 0)) { - debug_h_sect_idx = sect_idx; + debug_h_section_number = section_number; } - if (llvm_addrsig_sect_idx == max_U32 && str8_match(sect_name, str8_lit(".llvm_addrsig"), 0)) { - llvm_addrsig_sect_idx = sect_idx; + if (llvm_addrsig_section_number == 0 && str8_match(sect_name, str8_lit(".llvm_addrsig"), 0)) { + llvm_addrsig_section_number = section_number; } if (~*section_flags & COFF_SectionFlag_CntUninitializedData) { @@ -218,7 +219,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) coff_parse_secdef(symbol, header.is_big_obj, &select, §ion_number, 0, 0); if (select == COFF_ComdatSelect_Associative) { if (section_number == 0 || section_number > header.section_count_no_null) { - lnk_error_input_obj(LNK_Error_IllData, input, "section definition symbol %S (No. 0x%x) associates with an out of bounds section 0x%x", symbol.name, symbol_idx, symbol.section_number); + lnk_error_input_obj(LNK_Error_IllData, input, "section definition symbol %S (No. 0x%x) associates with an out of bounds section 0x%x", symbol.name, symbol_idx, section_number); } } } @@ -235,8 +236,8 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) // U32 *comdats; { - comdats = push_array_no_zero(arena, U32, header.section_count_no_null); - MemorySet(comdats, 0xff, header.section_count_no_null * sizeof(comdats[0])); + comdats = push_array_no_zero(arena, U32, header.section_count_no_null + 1); + MemorySet(comdats, 0xff, (header.section_count_no_null + 1) * sizeof(comdats[0])); COFF_ParsedSymbol symbol; for (U64 symbol_idx = 0; symbol_idx < header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { @@ -247,13 +248,13 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) if (symbol.storage_class == COFF_SymStorageClass_Static) { if (symbol.section_number > 0 && symbol.section_number <= header.section_count_no_null) { COFF_SectionHeader *sect_header = &coff_section_table[symbol.section_number-1]; - if (section_headers[symbol.section_number-1].flags & COFF_SectionFlag_LnkCOMDAT) { + if (section_headers[symbol.section_number].flags & COFF_SectionFlag_LnkCOMDAT) { if (symbol.aux_symbol_count) { U32 section_length = 0; coff_parse_secdef(symbol, header.is_big_obj, 0, 0, §ion_length, 0); if (sect_header->fsize == section_length) { - if (comdats[symbol.section_number-1] == ~0) { - comdats[symbol.section_number-1] = symbol_idx; + if (comdats[symbol.section_number] == ~0) { + comdats[symbol.section_number] = symbol_idx; } else { lnk_error_input_obj(LNK_Error_IllData, input, "section definition symbo (No. 0x%llx) tries to ovewrite comdat", symbol_idx); } @@ -277,9 +278,9 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) Temp scratch = scratch_begin(&arena, 1); HashTable *visited_sections = hash_table_init(scratch.arena, 32); - for (U64 sect_idx = 0; sect_idx < header.section_count_no_null; sect_idx += 1) { - for (U32 curr_section = sect_idx;;) { - U32 symbol_idx = comdats[curr_section]; + for (U32 section_number = 1; section_number <= header.section_count_no_null; section_number += 1) { + for (U32 curr_section_number = section_number;;) { + U32 symbol_idx = comdats[curr_section_number]; // is section COMDAT? if (symbol_idx == max_U32) { @@ -289,8 +290,8 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) // extract COMDAT info for current section COFF_ParsedSymbol symbol = coff_parse_symbol_no_name(header, raw_coff_symbol_table, symbol_idx); COFF_ComdatSelectType select = COFF_ComdatSelect_Null; - U32 section_number = 0; - coff_parse_secdef(symbol, header.is_big_obj, &select, §ion_number, 0, 0); + U32 associated_section_number = 0; + coff_parse_secdef(symbol, header.is_big_obj, &select, &associated_section_number, 0, 0); if (select != COFF_ComdatSelect_Associative) { // section terminates at non-associative COMDAT -- no loop @@ -298,18 +299,18 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) } // was section visited? -- loop found - if (hash_table_search_u64(visited_sections, curr_section)) { - COFF_ParsedSymbol symbol = coff_parse_symbol(header, raw_coff_string_table, raw_coff_symbol_table, comdats[sect_idx]); - lnk_error_input_obj(LNK_Error_AssociativeLoop, input, "section symbol %S (No. 0x%x) does not terminate on a non-associate COMDAT symbol", symbol.name, comdats[sect_idx]); + if (hash_table_search_u64(visited_sections, curr_section_number)) { + COFF_ParsedSymbol symbol = coff_parse_symbol(header, raw_coff_string_table, raw_coff_symbol_table, comdats[section_number]); + lnk_error_input_obj(LNK_Error_AssociativeLoop, input, "section symbol %S (No. 0x%x) does not terminate on a non-associate COMDAT symbol", symbol.name, comdats[section_number]); break; } // track visited sections - hash_table_push_u64_u64(scratch.arena, visited_sections, curr_section, 0); + hash_table_push_u64_u64(scratch.arena, visited_sections, curr_section_number, 0); // follow association - Assert(section_number > 0); - curr_section = section_number-1; + Assert(associated_section_number > 0); + curr_section_number = associated_section_number; } // purge hash table for next run @@ -354,7 +355,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) CV_Symbol comp_symbol = {0}; for EachIndex(sect_idx, header.section_count_no_null) { COFF_SectionHeader *sect_header = &coff_section_table[sect_idx]; - if (section_headers[sect_idx].flags & LNK_SECTION_FLAG_DEBUG) { + if (section_headers[sect_idx + 1].flags & LNK_SECTION_FLAG_DEBUG) { String8 name = str8_cstring_capped(sect_header->name, sect_header->name+sizeof(sect_header->name)); if (str8_match(name, str8_lit(".debug$S"), 0)) { Temp temp = temp_begin(scratch.arena); @@ -392,17 +393,17 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) .data = input->data, .header = header, .sections = { - .count = header.section_count_no_null, + .count_no_null = header.section_count_no_null, .headers = section_headers, .comdats = comdats, - .associations = associated_sections, + .associated_section_numbers = associated_sections, }, - .symbols = symbols, - .debug_t_sect_idx = debug_t_sect_idx, - .debug_p_sect_idx = debug_p_sect_idx, - .debug_h_sect_idx = debug_h_sect_idx, - .llvm_addrsig_sect_idx = llvm_addrsig_sect_idx, - .hotpatch = hotpatch, + .symbols = symbols, + .debug_t_section_number = debug_t_section_number, + .debug_p_section_number = debug_p_section_number, + .debug_h_section_number = debug_h_section_number, + .llvm_addrsig_section_number = llvm_addrsig_section_number, + .hotpatch = hotpatch, }; obj->path = push_str8_copy(arena, input->path); obj->exclude_from_debug_info = input->exclude_from_debug_info; @@ -457,9 +458,9 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) { LNK_InputCoffSymbolTable *task = raw_task; LNK_Obj *obj = task->objs[task_id]; - COFF_ParsedSymbol symbol = {0}; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + U64 symbol_idx = it.symbol_idx; + COFF_ParsedSymbol symbol = it.v; COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); LNK_SymbolSearchType search_type = lnk_symbol_search_type_from_coff(obj, symbol, interp); switch (interp) { @@ -504,14 +505,14 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) internal LNK_ObjSymbolRef * lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) { - LNK_ObjSymbolRef *symlinks = push_array(arena, LNK_ObjSymbolRef, obj->coff.sections.count + 1); - COFF_ParsedSymbol symbol = {0}; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + LNK_ObjSymbolRef *symlinks = push_array(arena, LNK_ObjSymbolRef, obj->coff.sections.count_no_null + 1); + for LNK_EachCoffSymbol(it, obj) { + U64 symbol_idx = it.symbol_idx; + COFF_ParsedSymbol symbol = it.v; COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); if (interp != COFF_SymbolValueInterp_Regular) { continue; } - COFF_SectionFlags section_flags = obj->coff.sections.headers[symbol.section_number - 1].flags; + COFF_SectionFlags section_flags = obj->coff.sections.headers[symbol.section_number].flags; if (~section_flags & COFF_SectionFlag_LnkCOMDAT) { continue; } LNK_ObjSymbolRef *symlink = &symlinks[symbol.section_number]; @@ -553,29 +554,30 @@ lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) } internal U32List -lnk_obj_collect_associated_sections(Arena *arena, LNK_Obj *obj, U32 root_section, COFF_SectionFlags skip_flags) +lnk_obj_collect_associated_section_numbers(Arena *arena, LNK_Obj *obj, U32 root_section_number, COFF_SectionFlags skip_flags) { + Assert(1 <= root_section_number && root_section_number <= obj->coff.sections.count_no_null); Temp scratch = scratch_begin(&arena, 1); // track each child before enqueueing it because COFF associations can cycle HashMap seen_hm = {0}; U32List queue = {0}; - U32Node root_n = { root_section }; + U32Node root_n = { root_section_number }; u32_list_push_node(&queue, &root_n); - hash_map_push_u64_u64(scratch.arena, &seen_hm, root_section, 1); + hash_map_push_u64_u64(scratch.arena, &seen_hm, root_section_number, 1); // walk the complete descendant chain because associated COMDATs can nest for EachNode(parent_n, U32Node, queue.first) { - for EachNode(associated_n, U32Node, obj->coff.sections.associations[parent_n->data]) { - U32 child_section = associated_n->data; + for EachNode(associated_n, U32Node, obj->coff.sections.associated_section_numbers[parent_n->data]) { + U32 child_section_number = associated_n->data; - if (child_section == 0) { continue; } - if (hash_map_search_u64_u64(&seen_hm, child_section)) { continue; } - if (obj->coff.sections.headers[child_section - 1].flags & skip_flags) { continue; } + if (child_section_number == 0) { continue; } + if (hash_map_search_u64_u64(&seen_hm, child_section_number)) { continue; } + if (obj->coff.sections.headers[child_section_number].flags & skip_flags) { continue; } - hash_map_push_u64_u64(scratch.arena, &seen_hm, child_section, 1); - u32_list_push(arena, &queue, child_section); + hash_map_push_u64_u64(scratch.arena, &seen_hm, child_section_number, 1); + u32_list_push(arena, &queue, child_section_number); } } @@ -620,9 +622,9 @@ lnk_push_obj_symbols(TP_Context *tp, TP_Arena *arena, LNK_SymbolTable *symtab, U internal COFF_ParsedSymbol lnk_obj_match_symbol(LNK_Obj *obj, String8 match_name) { - COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + COFF_ParsedSymbol symbol = it.v; + symbol.name = lnk_symbol_name_from_coff_symbol_idx(obj, it.symbol_idx); if (str8_match(symbol.name, match_name, 0)) { return symbol; } @@ -636,18 +638,6 @@ lnk_obj_get_features(LNK_Obj *obj) return lnk_obj_match_symbol(obj, str8_lit("@feat.00")).value; } -internal U32 -lnk_obj_get_comp_id(LNK_Obj *obj) -{ - return lnk_obj_match_symbol(obj, str8_lit("@comp.id")).value; -} - -internal U32 -lnk_obj_get_vol_md(LNK_Obj *obj) -{ - return lnk_obj_match_symbol(obj, str8_lit("@vol.md")).value; -} - internal LNK_Lib * lnk_obj_get_lib(LNK_Obj *obj) { @@ -672,8 +662,9 @@ lnk_obj_get_removed_section_number(LNK_Obj *obj) } internal B32 -lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *symlink_out) +lnk_obj_get_comdat_symlink_from_section_number(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *symlink_out) { + Assert(1 <= section_number && section_number <= obj->coff.sections.count_no_null); LNK_ObjSymbolRef symlink = obj->symlinks[section_number]; B32 is_valid = symlink.obj != 0; if (is_valid && symlink_out) { @@ -682,18 +673,12 @@ lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *s return is_valid; } -internal COFF_SectionHeader * -lnk_coff_section_table_from_obj(LNK_Obj *obj) -{ - return obj->coff.sections.headers; -} - internal String8 -lnk_obj_section_data_from_sect_idx(LNK_Obj *obj, U64 sect_idx) +lnk_obj_section_data_from_number(LNK_Obj *obj, U64 section_number) { - Assert(sect_idx < obj->coff.sections.count); + Assert(1 <= section_number && section_number <= obj->coff.sections.count_no_null); COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(obj->coff.data, obj->coff.header.section_table_range).str; - COFF_SectionHeader *section = §ion_table[sect_idx]; + COFF_SectionHeader *section = §ion_table[section_number-1]; return str8_substr(obj->coff.data, r1u64s(section->foff, section->fsize)); } @@ -706,55 +691,40 @@ lnk_obj_foff_from_section_data_ptr(LNK_Obj *obj, void *ptr) return ptr_v - data_min; } -internal U64 -lnk_obj_sect_idx_from_section_number(LNK_Obj *obj, U64 section_number) -{ - Assert(1 <= section_number && section_number <= obj->coff.sections.count); - return section_number-1; -} - -internal U64 -lnk_obj_section_number_from_sect_idx(LNK_Obj *obj, U64 sect_idx) -{ - Assert(sect_idx < obj->coff.sections.count); - return sect_idx+1; -} - -internal String8 -lnk_obj_section_name_from_sect_idx(LNK_Obj *obj, U64 sect_idx) -{ - COFF_SectionHeader *section_header = &lnk_coff_section_table_from_obj(obj)[sect_idx]; - return coff_name_from_section_header(lnk_coff_string_table_from_obj(obj), section_header); -} - internal String8 lnk_obj_section_name_from_section_number(LNK_Obj *obj, U64 section_number) { - Assert(section_number > 0); - return lnk_obj_section_name_from_sect_idx(obj, section_number-1); -} - -internal LNK_ObjSection -lnk_obj_section_from_sect_idx(LNK_Obj *obj, U64 sect_idx) -{ - Assert(sect_idx < obj->coff.sections.count); - LNK_ObjSection section = {0}; - section.obj = obj; - section.sect_idx = sect_idx; - section.section_number = sect_idx+1; - section.header = &lnk_coff_section_table_from_obj(obj)[sect_idx]; - section.flags = §ion.header->flags; - section.vrange = rng_1u64(section.header->voff, section.header->voff + section.header->vsize); - section.frange = rng_1u64(section.header->foff, section.header->foff + section.header->fsize); - section.reloc_count = section.header->reloc_count; - return section; + Assert(1 <= section_number && section_number <= obj->coff.sections.count_no_null); + COFF_SectionHeader *section_header = &obj->coff.sections.headers[section_number]; + return coff_name_from_section_header(lnk_coff_string_table_from_obj(obj), section_header); } internal LNK_ObjSection lnk_obj_section_from_section_number(LNK_Obj *obj, U64 section_number) { - U64 sect_idx = lnk_obj_sect_idx_from_section_number(obj, section_number); - return lnk_obj_section_from_sect_idx(obj, sect_idx); + Assert(1 <= section_number && section_number <= obj->coff.sections.count_no_null); + COFF_SectionHeader *header = &obj->coff.sections.headers[section_number]; + LNK_ObjSection section = { + .obj = obj, + .section_number = section_number, + .header = header, + .flags = &header->flags, + .vrange = rng_1u64(header->voff, header->voff + header->vsize), + .frange = rng_1u64(header->foff, header->foff + header->fsize), + .reloc_count = header->reloc_count, + }; + return section; +} + +internal force_inline B32 +lnk_obj_section_iter_next(LNK_Obj *obj, LNK_ObjSectionIter *it) +{ + U64 section_number = it->v.section_number + 1; + B32 is_valid = section_number <= obj->coff.sections.count_no_null; + if (is_valid) { + it->v = lnk_obj_section_from_section_number(obj, section_number); + } + return is_valid; } internal COFF_RelocArray @@ -791,8 +761,8 @@ lnk_coff_reloc_info_from_section_number(LNK_Obj *obj, U64 section_number) internal B32 lnk_try_comdat_props_from_section_number(LNK_Obj *obj, U32 section_number, COFF_ComdatSelectType *select_out, U32 *section_number_out, U32 *section_length_out, U32 *check_sum_out) { - Assert(section_number > 0); - U32 symbol_idx = obj->coff.sections.comdats[section_number-1]; + Assert(1 <= section_number && section_number <= obj->coff.sections.count_no_null); + U32 symbol_idx = obj->coff.sections.comdats[section_number]; if (symbol_idx != max_U32) { COFF_ParsedSymbol secdef = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); coff_parse_secdef(secdef, obj->coff.header.is_big_obj, select_out, section_number_out, section_length_out, check_sum_out); @@ -804,9 +774,8 @@ lnk_try_comdat_props_from_section_number(LNK_Obj *obj, U32 section_number, COFF_ internal COFF_SectionHeader * lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number) { - Assert(section_number > 0); - U64 sect_idx = section_number - 1; - return &obj->coff.sections.headers[sect_idx]; + Assert(1 <= section_number && section_number <= obj->coff.sections.count_no_null); + return &obj->coff.sections.headers[section_number]; } internal force_inline U64 @@ -826,6 +795,31 @@ lnk_obj_primary_symbol_idx_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx) return primary_idx; } +internal force_inline B32 +lnk_obj_symbol_iter_next(LNK_Obj *obj, LNK_ObjSymbolIter *it) +{ + if (it->next_primary_idx >= obj->coff.symbols.count) { + Assert(it->next_symbol_idx >= obj->coff.header.symbol_count); + return 0; + } + + Assert(it->next_symbol_idx < obj->coff.header.symbol_count); + U64 symbol_size = obj->coff.header.is_big_obj ? sizeof(COFF_Symbol32) : sizeof(COFF_Symbol16); + it->symbol_idx = it->next_symbol_idx; + it->primary_idx = it->next_primary_idx; + it->v = (COFF_ParsedSymbol){ + .value = obj->coff.symbols.values[it->primary_idx], + .section_number = obj->coff.symbols.section_numbers[it->primary_idx], + .type = obj->coff.symbols.types[it->primary_idx], + .storage_class = obj->coff.symbols.storage_classes[it->primary_idx], + .aux_symbol_count = obj->coff.symbols.aux_counts[it->primary_idx], + .raw_symbol = lnk_coff_symbol_table_from_obj(obj).str + it->symbol_idx * symbol_size, + }; + it->next_symbol_idx += 1 + it->v.aux_symbol_count; + it->next_primary_idx += 1; + return 1; +} + internal force_inline COFF_ParsedSymbol lnk_parsed_symbol_from_coff_symbol_idx_no_name(LNK_Obj *obj, U64 symbol_idx) { @@ -863,16 +857,16 @@ THREAD_POOL_TASK_FUNC(lnk_collect_obj_chunks_task) LNK_SectionCollector *task = raw_task; LNK_Obj *obj = task->objs[task_id]; - for EachIndex(sect_idx, obj->coff.sections.count) { - LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); + for LNK_EachCoffSection(it, obj) { + LNK_ObjSection section = it.v; if (*section.flags & COFF_SectionFlag_LnkRemove && !task->collect_discarded) { continue; } - String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); + String8 section_name = lnk_obj_section_name_from_section_number(obj, section.section_number); if (str8_match(section_name, task->name, 0)) { - String8 section_data = lnk_obj_section_data_from_sect_idx(obj, sect_idx); + String8 section_data = lnk_obj_section_data_from_number(obj, section.section_number); str8_list_push(arena, &task->out_lists[task_id], section_data); } } @@ -948,10 +942,10 @@ internal String8List lnk_raw_directives_from_obj(Arena *arena, LNK_Obj *obj) { String8List drectve_data = {0}; - for (U64 sect_idx = 0; sect_idx < obj->coff.sections.count; sect_idx += 1) { - LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); + for LNK_EachCoffSection(it, obj) { + LNK_ObjSection section = it.v; if (*section.flags & COFF_SectionFlag_LnkInfo) { - String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); + String8 section_name = lnk_obj_section_name_from_section_number(obj, section.section_number); if (str8_match(section_name, str8_lit(".drectve"), 0)) { if (*section.flags & COFF_SectionFlag_CntUninitializedData) { lnk_error_obj(LNK_Error_IllData, obj, ".drectve section header has flag COFF_SectionFlag_CntUninitializedData"); @@ -965,7 +959,7 @@ lnk_raw_directives_from_obj(Arena *arena, LNK_Obj *obj) lnk_error_obj(LNK_Error_IllData, obj, ".drectve must not have relocations"); break; } - str8_list_push(arena, &drectve_data, lnk_obj_section_data_from_sect_idx(obj, sect_idx)); + str8_list_push(arena, &drectve_data, lnk_obj_section_data_from_number(obj, section.section_number)); } } } @@ -997,9 +991,8 @@ lnk_symbol_map_from_obj(Arena *arena, LNK_Obj *obj) { // count functions U64 count = 0; - COFF_ParsedSymbol symbol; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + COFF_ParsedSymbol symbol = it.v; if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { count += 1; } @@ -1008,13 +1001,12 @@ lnk_symbol_map_from_obj(Arena *arena, LNK_Obj *obj) LNK_ObjSymbolMap *map = push_array(arena, LNK_ObjSymbolMap, 1); map->v = push_array_no_zero(arena, LNK_SectionOffsetSymbol, count); - U64 map_idx = 0; - for (U64 symbol_idx = 0; symbol_idx < obj->coff.header.symbol_count; symbol_idx += 1 + symbol.aux_symbol_count) { - symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(obj, symbol_idx); + for LNK_EachCoffSymbol(it, obj) { + COFF_ParsedSymbol symbol = it.v; if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular && COFF_SymbolType_IsFunc(symbol.type)) { LNK_SectionOffsetSymbol *entry = &map->v[map->count++]; entry->key = Compose64Bit(symbol.section_number, symbol.value); - entry->symbol_idx = safe_cast_u32(symbol_idx); + entry->symbol_idx = safe_cast_u32(it.symbol_idx); } } Assert(map->count == count); @@ -1057,11 +1049,11 @@ lnk_debug_s_from_obj(Arena *arena, LNK_Obj *obj) String8List raw_debug_s = {0}; { - for EachIndex(sect_idx, obj->coff.sections.count) { - LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); - String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); + for LNK_EachCoffSection(it, obj) { + LNK_ObjSection section = it.v; + String8 section_name = lnk_obj_section_name_from_section_number(obj, section.section_number); if (str8_match(section_name, str8_lit(".debug$S"), 0)) { - String8 debug_s = lnk_obj_section_data_from_sect_idx(obj, sect_idx); + String8 debug_s = lnk_obj_section_data_from_number(obj, section.section_number); str8_list_push(scratch.arena, &raw_debug_s, debug_s); } } @@ -1120,11 +1112,11 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) // #define DebugRelocValueFromFileOffset(foff) hash_map_search_u64_u64(&debug_reloc_hm, foff) HashMap debug_reloc_hm = {0}; - for EachIndex(sect_idx, obj->coff.sections.count) { - LNK_ObjSection section = lnk_obj_section_from_sect_idx(obj, sect_idx); - if (!str8_match(lnk_obj_section_name_from_sect_idx(obj, sect_idx), str8_lit(".debug$S"), 0)) { continue; } + for LNK_EachCoffSection(it, obj) { + LNK_ObjSection section = it.v; + if (!str8_match(lnk_obj_section_name_from_section_number(obj, section.section_number), str8_lit(".debug$S"), 0)) { continue; } - String8 section_data = lnk_obj_section_data_from_sect_idx(obj, sect_idx); + String8 section_data = lnk_obj_section_data_from_number(obj, section.section_number); COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section.header); for EachIndex(reloc_idx, relocs.count) { COFF_Reloc *reloc = &relocs.v[reloc_idx]; @@ -1244,9 +1236,9 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) // build inlinee id -> inlinee name // HashMap inlinee_hm = {0}; - if (obj->coff.debug_t_sect_idx < obj->coff.sections.count) { - String8 raw_debug_t = lnk_obj_section_data_from_sect_idx(obj, obj->coff.debug_t_sect_idx); - CV_Signature debug_t_sig = cv_signature_from_debug_s(raw_debug_t); + if (obj->coff.debug_t_section_number) { + String8 raw_debug_t = lnk_obj_section_data_from_number(obj, obj->coff.debug_t_section_number); + CV_Signature debug_t_sig = cv_signature_from_debug_s(raw_debug_t); if (debug_t_sig == CV_Signature_C13) { CV_DebugT debug_t = cv_debug_t_from_data(scratch.arena, str8_skip(raw_debug_t, sizeof(CV_Signature)), CV_LeafAlign); @@ -1454,9 +1446,9 @@ lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj) } internal CV_Line * -lnk_lines_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, U64 *line_count_out) +lnk_lines_from_section_offset(LNK_ObjLineMap *map, U64 section_number, U64 offset, U64 *line_count_out) { - U64 key = Compose64Bit(section_number, offset); + U64 key = Compose64Bit(safe_cast_u32(section_number), safe_cast_u32(offset)); // upper bound search U64 min = 0; @@ -1518,9 +1510,9 @@ lnk_lines_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offse } internal LNK_InlineSite * -lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, LNK_Symbol *callee_symbol) +lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U64 section_number, U64 offset, LNK_Symbol *callee_symbol) { - U64 key = Compose64Bit(section_number, offset); + U64 key = Compose64Bit(safe_cast_u32(section_number), safe_cast_u32(offset)); // find upper bound U64 min = 0; diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 321dcea4..e3d50cdb 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -21,10 +21,11 @@ typedef struct LNK_ObjSymbolArray typedef struct LNK_ObjSectionArray { - U64 count; + // Parallel arrays are indexed by COFF section number; slot zero is null. + U64 count_no_null; COFF_SectionHeader *headers; U32 *comdats; - U32Node **associations; + U32Node **associated_section_numbers; } LNK_ObjSectionArray; typedef struct LNK_ObjCoff @@ -33,10 +34,10 @@ typedef struct LNK_ObjCoff COFF_FileHeaderInfo header; LNK_ObjSectionArray sections; LNK_ObjSymbolArray symbols; - U32 debug_t_sect_idx; - U32 debug_p_sect_idx; - U32 debug_h_sect_idx; - U32 llvm_addrsig_sect_idx; + U32 debug_t_section_number; + U32 debug_p_section_number; + U32 debug_h_section_number; + U32 llvm_addrsig_section_number; B8 hotpatch; } LNK_ObjCoff; @@ -52,7 +53,7 @@ typedef struct LNK_Obj U32 input_idx; // link state - LNK_ObjSymbolRef *symlinks; + LNK_ObjSymbolRef *symlinks; // indexed by COFF section number; slot zero is null // link struct LNK_LibMemberRef *link_member; @@ -68,7 +69,6 @@ typedef struct LNK_Obj typedef struct LNK_ObjSection { LNK_Obj *obj; - U64 sect_idx; U64 section_number; COFF_SectionHeader *header; COFF_SectionFlags *flags; @@ -77,6 +77,7 @@ typedef struct LNK_ObjSection U32 reloc_count; } LNK_ObjSection; + typedef struct LNK_ObjNode { struct LNK_ObjNode *next; @@ -97,7 +98,26 @@ typedef struct LNK_ObjNodeArray LNK_ObjNode *v; } LNK_ObjNodeArray; -// --- Maps -------------------------------------------------------------- +// --- Iterators --------------------------------------------------------------- + +typedef struct LNK_ObjSectionIter +{ + LNK_ObjSection v; +} LNK_ObjSectionIter; + +typedef struct LNK_ObjSymbolIter +{ + U64 next_symbol_idx; + U64 next_primary_idx; + U64 symbol_idx; + U64 primary_idx; + COFF_ParsedSymbol v; +} LNK_ObjSymbolIter; + +#define LNK_EachCoffSection(it, obj) (LNK_ObjSectionIter it = {0}; lnk_obj_section_iter_next((obj), &(it));) +#define LNK_EachCoffSymbol(it, obj) (LNK_ObjSymbolIter it = {0}; lnk_obj_symbol_iter_next((obj), &(it));) + +// --- Maps -------------------------------------------------------------------- typedef struct LNK_SectionOffsetSymbol { @@ -219,18 +239,14 @@ internal LNK_Obj ** lnk_array_from_obj_list(Arena *arena, LNK_ObjList list); internal void lnk_obj_list_push_node_many(LNK_ObjList *list, U64 count, LNK_ObjNode *nodes); internal void lnk_obj_list_push_node(LNK_ObjList *list, LNK_ObjNode *node); -internal void lnk_inputer_push_obj_symbols(TP_Context *tp, TP_Arena *arena, LNK_SymbolTable *symtab, U64 objs_count, LNK_ObjNode *objs); - // --- Metadata ---------------------------------------------------------------- internal U32 lnk_obj_get_features(LNK_Obj *obj); -internal U32 lnk_obj_get_comp_id(LNK_Obj *obj); -internal U32 lnk_obj_get_vol_md(LNK_Obj *obj); internal struct LNK_Lib * lnk_obj_get_lib(LNK_Obj *obj); internal String8 lnk_obj_get_lib_path(LNK_Obj *obj); internal U32 lnk_obj_get_removed_section_number(LNK_Obj *obj); -internal B32 lnk_obj_get_comdat_symlink(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *symlink_out); -internal U32List lnk_obj_collect_associated_sections(Arena *arena, LNK_Obj *obj, U32 root_section, COFF_SectionFlags skip_flags); +internal B32 lnk_obj_get_comdat_symlink_from_section_number(LNK_Obj *obj, U64 section_number, LNK_ObjSymbolRef *symlink_out); +internal U32List lnk_obj_collect_associated_section_numbers(Arena *arena, LNK_Obj *obj, U32 root_section_number, COFF_SectionFlags skip_flags); // --- Symbol & Section Helpers ------------------------------------------------ @@ -240,19 +256,18 @@ internal force_inline String8 lnk_symbol_name_from_coff_symbol_idx(LNK internal force_inline U64 lnk_obj_primary_symbol_idx_from_coff_symbol_idx(LNK_Obj *obj, U64 symbol_idx); internal COFF_SectionHeader * lnk_coff_section_header_from_section_number(LNK_Obj *obj, U64 section_number); -internal String8 lnk_obj_section_data_from_sect_idx(LNK_Obj *obj, U64 sect_idx); +internal String8 lnk_obj_section_data_from_number(LNK_Obj *obj, U64 section_number); internal U64 lnk_obj_foff_from_section_data_ptr(LNK_Obj *obj, void *ptr); -internal U64 lnk_obj_sect_idx_from_section_number(LNK_Obj *obj, U64 section_number); -internal U64 lnk_obj_section_number_from_sect_idx(LNK_Obj *obj, U64 sect_idx); internal String8 lnk_obj_section_name_from_section_number(LNK_Obj *obj, U64 section_number); -internal String8 lnk_obj_section_name_from_sect_idx(LNK_Obj *obj, U64 sect_idx); -internal LNK_ObjSection lnk_obj_section_from_sect_idx(LNK_Obj *obj, U64 sect_idx); internal LNK_ObjSection lnk_obj_section_from_section_number(LNK_Obj *obj, U64 section_number); internal COFF_RelocArray lnk_coff_relocs_from_section_header(LNK_Obj *obj, COFF_SectionHeader *section_header); internal String8 lnk_coff_string_table_from_obj(LNK_Obj *obj); internal String8 lnk_coff_symbol_table_from_obj(LNK_Obj *obj); internal B32 lnk_try_comdat_props_from_section_number(LNK_Obj *obj, U32 section_number, COFF_ComdatSelectType *select_out, U32 *section_number_out, U32 *section_length_out, U32 *check_sum_out); +internal force_inline B32 lnk_obj_section_iter_next(LNK_Obj *obj, LNK_ObjSectionIter *it); +internal force_inline B32 lnk_obj_symbol_iter_next(LNK_Obj *obj, LNK_ObjSymbolIter *it); + // --- Helpers ----------------------------------------------------------------- internal String8List * lnk_collect_obj_sections(TP_Context *tp, TP_Arena *arena, U64 objs_count, LNK_Obj **objs, String8 name, B32 collect_discarded); @@ -272,7 +287,7 @@ internal U32 lnk_symbol_from_section_offset(LNK_ObjSymbolMap *map // CodeView map internal LNK_ObjLineMap * lnk_line_map_from_obj(Arena *arena, LNK_Obj *obj); -internal CV_Line * lnk_lines_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, U64 *line_count_out); -internal LNK_InlineSite * lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U32 section_number, U32 offset, LNK_Symbol *callee_symbol); +internal CV_Line * lnk_lines_from_section_offset(LNK_ObjLineMap *map, U64 section_number, U64 offset, U64 *line_count_out); +internal LNK_InlineSite * lnk_inline_site_from_section_offset(LNK_ObjLineMap *map, U64 section_number, U64 offset, LNK_Symbol *callee_symbol); internal CV_Line * lnk_line_from_inline_site(LNK_InlineSite *site, U32 offset); diff --git a/src/linker/lnk_section_table.h b/src/linker/lnk_section_table.h index 071f7c9c..55e278c5 100644 --- a/src/linker/lnk_section_table.h +++ b/src/linker/lnk_section_table.h @@ -10,8 +10,8 @@ typedef struct LNK_SectionContrib union { // used to sort sections to get deterministic output struct { - U32 obj_idx; // index of the input obj that contributes to the image section - U32 obj_sect_idx; // index into contributing obj's section table + U32 obj_idx; // index of the input obj that contributes to the image section + U32 obj_section_number; // COFF section number in the contributing obj }; // used after section layout is finalized diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 174364d5..83ab6238 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -342,8 +342,8 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) case COFF_ComdatSelect_ExactMatch: { LNK_ObjSection dst_section = lnk_obj_section_from_section_number(dst_obj, dst_parsed.section_number); LNK_ObjSection src_section = lnk_obj_section_from_section_number(src_obj, src_parsed.section_number); - String8 dst_data = lnk_obj_section_data_from_sect_idx(dst_obj, dst_section.sect_idx); - String8 src_data = lnk_obj_section_data_from_sect_idx(src_obj, src_section.sect_idx); + String8 dst_data = lnk_obj_section_data_from_number(dst_obj, dst_section.section_number); + String8 src_data = lnk_obj_section_data_from_number(src_obj, src_section.section_number); B32 is_exact_match = 0; if (dst_check_sum != 0 && src_check_sum != 0) { @@ -396,7 +396,7 @@ lnk_on_symbol_replace(LNK_Symbol *dst, LNK_Symbol *src) *dst_section.flags |= COFF_SectionFlag_LnkRemove; // remove associated sections from the output - for (U32Node *associated_section = dst_ref.obj->coff.sections.associations[dst_parsed.section_number]; + for (U32Node *associated_section = dst_ref.obj->coff.sections.associated_section_numbers[dst_parsed.section_number]; associated_section != 0; associated_section = associated_section->next) { LNK_ObjSection section = lnk_obj_section_from_section_number(dst_ref.obj, associated_section->data); @@ -773,7 +773,7 @@ lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymb switch (symbol_interp) { case COFF_SymbolValueInterp_Regular: { LNK_ObjSymbolRef symlink = {0}; - *symbol_out = lnk_obj_get_comdat_symlink(symbol.obj, symbol_parsed.section_number, &symlink) ? symlink : symbol; + *symbol_out = lnk_obj_get_comdat_symlink_from_section_number(symbol.obj, symbol_parsed.section_number, &symlink) ? symlink : symbol; } break; case COFF_SymbolValueInterp_Weak: { LNK_Symbol *defn = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx)); From ad8fe1c61e01288446741348c799fd1f6cced52c Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 03:55:48 -0700 Subject: [PATCH 084/251] implement LINK-compatible map output --- src/linker/lnk.c | 378 +++++++++++++++++--------------- src/linker/lnk_config.c | 41 +++- src/linker/lnk_config.h | 8 +- src/linker/tests/linker_tests.c | 36 +++ 4 files changed, 272 insertions(+), 191 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 961da53c..543a4be5 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -6106,216 +6106,232 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT return image_ctx; } -internal PairU32 * -lnk_obj_section_numbers_from_section(Arena *arena, U64 objs_count, LNK_Obj **objs, LNK_Section *sect, LNK_Config *config, U64 *obj_section_numbers_count_out) +typedef struct LNK_MapSection { - U64 max_contribs = 0; - for (LNK_SectionContribChunk *chunk = sect->contribs.first; chunk != 0; chunk = chunk->next) { - max_contribs += chunk->count; - } + String8 name; + U32 section_number; + U32 off; + U32 end; + B32 is_code; +} LNK_MapSection; - U64 obj_section_numbers_count = 0; - PairU32 *obj_section_numbers = push_array(arena, PairU32, max_contribs); - for (U64 obj_idx = 0; obj_idx < objs_count; obj_idx += 1) { - LNK_Obj *obj = objs[obj_idx]; - String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); - for LNK_EachCoffSection(it, obj) { - COFF_SectionHeader *section_header = it.v.header; - String8 full_section_name = coff_name_from_section_header(string_table, section_header); - String8 section_name, section_postfix; - coff_parse_section_name(full_section_name, §ion_name, §ion_postfix); - - if (*it.v.flags & COFF_SectionFlag_LnkRemove) { continue; } - if (section_header->fsize == 0) { continue; } - if (lnk_is_section_removed(config, section_name)) { continue; } - - if (sect->voff <= section_header->voff && section_header->voff < sect->voff + sect->vsize) { - Assert(obj_section_numbers_count < max_contribs); - obj_section_numbers[obj_section_numbers_count].v0 = obj_idx; - obj_section_numbers[obj_section_numbers_count].v1 = it.v.section_number; - obj_section_numbers_count += 1; - } - } - } - - U64 pop_size = (max_contribs - obj_section_numbers_count) * sizeof(obj_section_numbers[0]); - arena_pop(arena, pop_size); - - *obj_section_numbers_count_out = obj_section_numbers_count; - - return obj_section_numbers; -} - -internal COFF_SectionHeader * -lnk_coff_section_header_from_obj_section_number_pair(LNK_Obj **objs, PairU32 p) +typedef struct LNK_MapSymbol { - return lnk_coff_section_header_from_section_number(objs[p.v0], p.v1); -} + String8 name; + String8 source; + U64 va; + U32 section_number; + U32 off; + B32 is_func; +} LNK_MapSymbol; -global LNK_Obj **g_rad_map_objs; +internal U32 +lnk_map_image_section_number_from_voff(COFF_SectionHeader **section_table, U64 section_count, U64 voff) +{ + for (U32 section_number = 1; section_number <= section_count; section_number += 1) { + COFF_SectionHeader *header = section_table[section_number]; + if (voff == header->voff) { return section_number; } + } + for (U32 section_number = 1; section_number <= section_count; section_number += 1) { + COFF_SectionHeader *header = section_table[section_number]; + U64 section_size = Max(header->vsize, header->fsize); + if (header->voff < voff && voff <= header->voff + section_size) { return section_number; } + } + return 0; +} internal int -lnk_obj_section_number_is_before(void *raw_a, void *raw_b) +lnk_map_section_is_before(void *raw_a, void *raw_b) { - PairU32 *a = raw_a, *b = raw_b; - COFF_SectionHeader *section_header_a = lnk_coff_section_header_from_obj_section_number_pair(g_rad_map_objs, *a); - COFF_SectionHeader *section_header_b = lnk_coff_section_header_from_obj_section_number_pair(g_rad_map_objs, *b); - return section_header_a->voff < section_header_b->voff; + LNK_MapSection *a = raw_a, *b = raw_b; + if (a->section_number != b->section_number) { return a->section_number < b->section_number; } + if (a->off != b->off) { return a->off < b->off; } + return str8_is_before_case_sensitive(&a->name, &b->name); } -internal U64 -lnk_pair_u32_nearest_section(PairU32 *arr, U64 count, LNK_Obj **objs, U32 voff) +internal int +lnk_map_symbol_is_before(void *raw_a, void *raw_b) { - U64 result = max_U64; + LNK_MapSymbol *a = *(LNK_MapSymbol **)raw_a, *b = *(LNK_MapSymbol **)raw_b; + if (a->va != b->va) { return a->va < b->va; } + if (a->section_number != b->section_number) { return a->section_number < b->section_number; } + if (a->off != b->off) { return a->off < b->off; } + return str8_is_before_case_sensitive(&a->name, &b->name); +} - if (count > 0) { - COFF_SectionHeader *first = lnk_coff_section_header_from_obj_section_number_pair(objs, arr[0]); - if (first->voff == voff) { - return 0; - } - - COFF_SectionHeader *last = lnk_coff_section_header_from_obj_section_number_pair(objs, arr[count-1]); - if (last->voff <= voff) { - return count - 1; - } - - if (first->voff <= voff && voff < last->voff + last->vsize) { - U64 l = 0; - U64 r = count - 1; - for (; l <= r; ) { - U64 m = l + (r - l) / 2; - COFF_SectionHeader *s = lnk_coff_section_header_from_obj_section_number_pair(objs, arr[m]); - if (s->voff == voff) { - return m; - } else if (s->voff < voff) { - l = m + 1; - } else { - r = m - 1; - } - } - result = l - 1; - } +internal String8 +lnk_map_source_from_obj(Arena *arena, LNK_Obj *obj) +{ + if (obj == 0 || str8_starts_with(obj->path, str8_lit("*"))) { + return str8_lit(""); } + String8 obj_name = str8_skip_last_slash(obj->path); + LNK_Lib *lib = lnk_obj_get_lib(obj); + if (lib) { + String8 lib_name = str8_chop_last_dot(str8_skip_last_slash(lib->path)); + return push_str8f(arena, "%S:%S", lib_name, obj_name); + } + return obj_name; +} - return result; +internal void +lnk_map_push_symbol_line(Arena *arena, String8List *map, LNK_MapSymbol *symbol) +{ + String8 function_marker = symbol->is_func ? str8_lit("f") : str8_lit(" "); + str8_list_pushf(arena, map, " %04x:%08x %-26S %016llx %S %S\r\n", + symbol->section_number, symbol->off, symbol->name, symbol->va, function_marker, symbol->source); } internal String8List -lnk_build_rad_map(Arena *arena, String8 image_data, LNK_Config *config, U64 objs_count, LNK_Obj **objs, U64 libs_count, LNK_Lib **libs, LNK_SectionTable *sectab) +lnk_build_map(Arena *arena, String8 image_data, LNK_Config *config, LNK_SymbolTable *symtab, U64 objs_count, LNK_Obj **objs) { ProfBeginFunction(); Temp scratch = scratch_begin(&arena, 1); PE_BinInfo pe = pe_bin_info_from_data(scratch.arena, image_data); COFF_SectionHeader **image_section_table = coff_section_table_from_data(scratch.arena, image_data, pe.section_table_range); + String8List map = {0}; - String8List map = {0}; + String8 image_name = str8_chop_last_dot(str8_skip_last_slash(config->out_path)); + DateTime universal_time = date_time_from_unix_time(config->time_stamp); + DateTime local_time = local_from_universal_time(&universal_time); + String8 week_day = string_from_week_day(local_time.week_day); + String8 month = string_from_month(local_time.month); - ProfBegin("SECTIONS"); - str8_list_pushf(arena, &map, "# SECTIONS\n"); - for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { - LNK_Section *sect = §_n->data; + str8_list_pushf(arena, &map, " %S\r\n\r\n", image_name); + str8_list_pushf(arena, &map, " Timestamp is %08x (%S %S %02u %02u:%02u:%02u %04u)\r\n\r\n", + config->time_stamp, week_day, month, local_time.day, local_time.hour, local_time.min, local_time.sec, local_time.year); + str8_list_pushf(arena, &map, " Preferred load address is %016llx\r\n\r\n", pe.image_base); + str8_list_pushf(arena, &map, " Start Length Name Class\r\n"); - str8_list_pushf(arena, &map, "%S\n", sect->name); - str8_list_pushf(arena, &map, "%-4s %-8s %-8s %-8s %-8s %-16s %-4s %s\n", "No.", "VirtOff", "VirtSize", "FileOff", "FileSize", "Blake3", "Algn", "SC"); - - U64 obj_section_numbers_count = 0; - PairU32 *obj_section_numbers = lnk_obj_section_numbers_from_section(scratch.arena, objs_count, objs, sect, config, &obj_section_numbers_count); - g_rad_map_objs = objs; - radsort(obj_section_numbers, obj_section_numbers_count, lnk_obj_section_number_is_before); - - U64 global_sc_idx = 0; - for (LNK_SectionContribChunk *sc_chunk = sect->contribs.first; sc_chunk != 0; sc_chunk = sc_chunk->next) { - for (U64 sc_idx = 0; sc_idx < sc_chunk->count; sc_idx += 1, global_sc_idx += 1) { - Temp temp = temp_begin(scratch.arena); - LNK_SectionContrib *sc = sc_chunk->v[sc_idx]; - - U64 file_off = image_section_table[sc->u.sect_idx+1]->foff + sc->u.off; - U64 virt_off = image_section_table[sc->u.sect_idx+1]->voff + sc->u.off; - U64 virt_size = lnk_size_from_section_contrib(sc); - U64 file_size = lnk_size_from_section_contrib(sc); - String8 sc_data = str8_substr(image_data, rng_1u64(file_off, file_off + virt_size)); - - LNK_Obj *obj = 0; - U32 section_number = 0; - U64 obj_section_number_idx = lnk_pair_u32_nearest_section(obj_section_numbers, obj_section_numbers_count, objs, virt_off); - if (obj_section_number_idx < obj_section_numbers_count) { - obj = objs[obj_section_numbers[obj_section_number_idx].v0]; - section_number = obj_section_numbers[obj_section_number_idx].v1; - } - - U128 sc_hash = {0}; - if (~sect->flags & COFF_SectionFlag_CntUninitializedData) { - blake3_hasher hasher; blake3_hasher_init(&hasher); - blake3_hasher_update(&hasher, sc_data.str, sc_data.size); - blake3_hasher_finalize(&hasher, (U8 *)&sc_hash, sizeof(sc_hash)); - } - - String8 sc_idx_str = push_str8f(temp.arena, "%4llx", global_sc_idx); - String8 virt_size_str = push_str8f(temp.arena, "%08x", virt_size); - String8 sc_hash_str = (~sect->flags & COFF_SectionFlag_CntUninitializedData) ? push_str8f(temp.arena, "%08x%08x", sc_hash.u64[0], sc_hash.u64[1]) : str8_lit("--------"); - String8 file_off_str = (~sect->flags & COFF_SectionFlag_CntUninitializedData) ? push_str8f(temp.arena, "%08x", file_off) : str8_lit("--------"); - String8 file_size_str = (~sect->flags & COFF_SectionFlag_CntUninitializedData) ? push_str8f(temp.arena, "%08x", file_size) : str8_lit("--------"); - String8 virt_off_str = push_str8f(temp.arena, "%08x", virt_off); - String8 align_str = push_str8f(temp.arena, "%4x", sc->align); - String8 contrib_str; - { - String8List source_list = {0}; - if (obj) { - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); - String8 string_table = str8_substr(obj->coff.data, obj->coff.header.string_table_range); - String8 section_name = coff_name_from_section_header(string_table, section_header); - LNK_Lib *lib = lnk_obj_get_lib(obj); - if (lib) { - String8 lib_name = str8_chop_last_dot(str8_skip_last_slash(lib->path)); - String8 obj_name = str8_skip_last_slash(obj->path); - str8_list_pushf(temp.arena, &source_list, "%S(%S) SECT%X (%S)", lib_name, obj_name, section_number, section_name); - } else { - str8_list_pushf(temp.arena, &source_list, "%S SECT%X (%S)", obj->path, section_number, section_name); - } - } else { - str8_list_pushf(temp.arena, &source_list, ""); - } - contrib_str = str8_list_join(temp.arena, &source_list, &(StringJoin){.sep=str8_lit(" ")}); - } - - str8_list_pushf(arena, &map, "%S %S %S %S %S %S %S %S\n", sc_idx_str, virt_off_str, virt_size_str, file_off_str, file_size_str, sc_hash_str, align_str, contrib_str); - - temp_end(temp); - } - } - str8_list_pushf(arena, &map, "\n"); - } - ProfEnd(); - - str8_list_pushf(arena, &map, "# DEBUG\n"); - for (U64 obj_idx = 0; obj_idx < objs_count; obj_idx += 1) { + U64 section_cap = 0; + for EachIndex(obj_idx, objs_count) { section_cap += objs[obj_idx]->coff.sections.count_no_null; } + LNK_MapSection *sections = push_array(scratch.arena, LNK_MapSection, section_cap); + U64 section_count = 0; + HashMap *section_maps = push_array(scratch.arena, HashMap, pe.section_count + 1); + for EachIndex(obj_idx, objs_count) { LNK_Obj *obj = objs[obj_idx]; for LNK_EachCoffSection(it, obj) { - COFF_SectionFlags section_flags = *it.v.flags; - if (~section_flags & COFF_SectionFlag_LnkRemove && section_flags & LNK_SECTION_FLAG_DEBUG) { - LNK_Lib *lib = lnk_obj_get_lib(obj); - if (lib) { - String8 lib_name = str8_chop_last_dot(str8_skip_last_slash(lib->path)); - String8 obj_name = str8_skip_last_slash(obj->path); - str8_list_pushf(arena, &map, "%S(%S) SECT%X\n", lib_name, obj_name, it.v.section_number); - } else { - str8_list_pushf(arena, &map, "%S SECT%X\n", obj->path, it.v.section_number); - } + COFF_SectionFlags flags = *it.v.flags; + if (flags & (COFF_SectionFlag_LnkRemove | COFF_SectionFlag_LnkInfo | LNK_SECTION_FLAG_DEBUG)) { continue; } + + U32 image_section_number = lnk_map_image_section_number_from_voff(image_section_table, pe.section_count, it.v.header->voff); + if (image_section_number == 0) { continue; } + + String8 name = lnk_obj_section_name_from_section_number(obj, it.v.section_number); + LNK_MapSection *section = hash_map_search_string_raw(§ion_maps[image_section_number], name); + U32 off = safe_cast_u32(it.v.header->voff - image_section_table[image_section_number]->voff); + U32 end = off + it.v.header->vsize; + if (section == 0) { + Assert(section_count < section_cap); + section = §ions[section_count++]; + *section = (LNK_MapSection){ name, image_section_number, off, end, !!(flags & COFF_SectionFlag_CntCode) }; + hash_map_push_string_raw(scratch.arena, §ion_maps[image_section_number], name, section); + } else { + section->off = Min(section->off, off); + section->end = Max(section->end, end); } } } - str8_list_pushf(arena, &map, "\n"); + if (section_count > 1) { radsort(sections, section_count, lnk_map_section_is_before); } + for EachIndex(i, section_count) { + LNK_MapSection *section = §ions[i]; + str8_list_pushf(arena, &map, " %04x:%08x %08xH %-22S %s\r\n", + section->section_number, section->off, section->end - section->off, + section->name, section->is_code ? "CODE" : "DATA"); + } - ProfBegin("LIBS"); - if (libs_count) { - str8_list_pushf(arena, &map, "# LIBS\n"); - for EachIndex(i, libs_count) { - str8_list_pushf(arena, &map, "%S\n", libs[i]->path); + str8_list_pushf(arena, &map, "\r\n Address Publics by Value Rva+Base Lib:Object\r\n\r\n"); + + U64 public_cap = 0; + for EachIndex(worker_idx, symtab->arena->count) { + for EachNode(chunk, LNK_SymbolHashTrieChunk, symtab->chunks[worker_idx].first) { public_cap += chunk->count; } + } + LNK_MapSymbol *publics = push_array(scratch.arena, LNK_MapSymbol, public_cap); + U64 public_count = 0; + for EachIndex(worker_idx, symtab->arena->count) { + for EachNode(chunk, LNK_SymbolHashTrieChunk, symtab->chunks[worker_idx].first) { + for EachIndex(i, chunk->count) { + LNK_Symbol *symbol = chunk->v[i].symbol; + if (symbol == 0 || str8_match(symbol->name, str8_lit(LNK_NULL_SYMBOL), 0) || str8_match(symbol->name, str8_lit(LNK_IMPORT_STUB), 0)) { continue; } + + LNK_ObjSymbolRef ref = lnk_ref_from_symbol(symbol); + COFF_ParsedSymbol parsed = lnk_parsed_from_symbol(symbol); + COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(parsed); + LNK_MapSymbol map_symbol = { .name = symbol->name, .is_func = COFF_SymbolType_IsFunc(parsed.type) }; + + if (interp == COFF_SymbolValueInterp_Regular) { + if (parsed.section_number == lnk_obj_get_removed_section_number(ref.obj) || parsed.section_number == 0 || parsed.section_number > pe.section_count) { continue; } + map_symbol.section_number = parsed.section_number; + map_symbol.off = safe_cast_u32(parsed.value); + map_symbol.va = pe.image_base + image_section_table[parsed.section_number]->voff + parsed.value; + + COFF_ParsedSymbol original = coff_parse_symbol_no_name(ref.obj->coff.header, lnk_coff_symbol_table_from_obj(ref.obj), ref.symbol_idx); + COFF_SymbolValueInterpType original_interp = coff_interp_from_parsed_symbol(original); + map_symbol.source = original_interp == COFF_SymbolValueInterp_Common ? str8_lit("") : lnk_map_source_from_obj(scratch.arena, ref.obj); + } else if (interp == COFF_SymbolValueInterp_Abs) { + map_symbol.off = safe_cast_u32(parsed.value); + if (str8_match(symbol->name, str8_lit("__ImageBase"), 0)) { + map_symbol.va = pe.image_base; + map_symbol.source = str8_lit(""); + } else { + map_symbol.va = parsed.value; + map_symbol.source = str8_lit(""); + } + } else { + continue; + } + + Assert(public_count < public_cap); + publics[public_count++] = map_symbol; + } } } - ProfEnd(); - + LNK_MapSymbol **public_ptrs = push_array_no_zero(scratch.arena, LNK_MapSymbol *, public_count); + for EachIndex(i, public_count) { public_ptrs[i] = &publics[i]; } + if (public_count > 1) { radsort(public_ptrs, public_count, lnk_map_symbol_is_before); } + for EachIndex(i, public_count) { lnk_map_push_symbol_line(arena, &map, public_ptrs[i]); } + + U32 entry_section_number = lnk_map_image_section_number_from_voff(image_section_table, pe.section_count, pe.entry_point); + U32 entry_section_off = entry_section_number ? pe.entry_point - image_section_table[entry_section_number]->voff : pe.entry_point; + str8_list_pushf(arena, &map, "\r\n entry point at %04x:%08x\r\n\r\n Static symbols\r\n\r\n", entry_section_number, entry_section_off); + + U64 static_cap = 0; + for EachIndex(obj_idx, objs_count) { static_cap += objs[obj_idx]->coff.symbols.count; } + LNK_MapSymbol *statics = push_array(scratch.arena, LNK_MapSymbol, static_cap); + U64 static_count = 0; + for EachIndex(obj_idx, objs_count) { + LNK_Obj *obj = objs[obj_idx]; + String8 string_table = lnk_coff_string_table_from_obj(obj); + String8 symbol_table = lnk_coff_symbol_table_from_obj(obj); + for LNK_EachCoffSymbol(it, obj) { + COFF_ParsedSymbol original = coff_parse_symbol(obj->coff.header, string_table, symbol_table, safe_cast_u32(it.symbol_idx)); + if (original.storage_class != COFF_SymStorageClass_Static || coff_interp_from_parsed_symbol(original) != COFF_SymbolValueInterp_Regular) { continue; } + if (str8_starts_with(original.name, str8_lit("$pdata$"))) { continue; } + if (original.section_number == 0 || original.section_number > obj->coff.sections.count_no_null) { continue; } + if (*lnk_obj_section_from_section_number(obj, original.section_number).flags & (COFF_SectionFlag_LnkInfo | LNK_SECTION_FLAG_DEBUG)) { continue; } + if (original.type.v == 0 && original.value == 0 && original.aux_symbol_count > 0 && + str8_match(original.name, lnk_obj_section_name_from_section_number(obj, original.section_number), 0)) { continue; } + + COFF_ParsedSymbol parsed = it.v; + if (coff_interp_from_parsed_symbol(parsed) != COFF_SymbolValueInterp_Regular || parsed.section_number == 0 || parsed.section_number > pe.section_count) { continue; } + + Assert(static_count < static_cap); + statics[static_count++] = (LNK_MapSymbol){ + .name = original.name, + .source = lnk_map_source_from_obj(scratch.arena, obj), + .va = pe.image_base + image_section_table[parsed.section_number]->voff + parsed.value, + .section_number = parsed.section_number, + .off = safe_cast_u32(parsed.value), + .is_func = COFF_SymbolType_IsFunc(parsed.type), + }; + } + } + LNK_MapSymbol **static_ptrs = push_array_no_zero(scratch.arena, LNK_MapSymbol *, static_count); + for EachIndex(i, static_count) { static_ptrs[i] = &statics[i]; } + if (static_count > 1) { radsort(static_ptrs, static_count, lnk_map_symbol_is_before); } + for EachIndex(i, static_count) { lnk_map_push_symbol_line(arena, &map, static_ptrs[i]); } + scratch_end(scratch); ProfEnd(); return map; @@ -6463,11 +6479,11 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) lnk_background_file_writer_begin(pdb_writer.file_writer); // - // RAD Map + // Map // - if (config->rad_chunk_map == LNK_SwitchState_Yes) { - String8List rad_map = lnk_build_rad_map(scratch.arena, image_ctx.image_data, config, objs_count, objs, libs_count, libs, image_ctx.sectab); - lnk_write_data_list_to_file_path(config->rad_chunk_map_name, config->temp_rad_chunk_map_name, rad_map); + if (config->map == LNK_SwitchState_Yes) { + String8List map = lnk_build_map(scratch.arena, image_ctx.image_data, config, symtab, objs_count, objs); + lnk_write_data_list_to_file_path(config->map_name, config->temp_map_name, map); } // diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 5729e2c7..4e099331 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -40,6 +40,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Lib, 0, LNK_CmdValueKind_Null, "LIB", "", "Turn linker into lib.exe." }, { LNK_CmdSwitch_LibPath, 0, LNK_CmdValueKind_Scalar, "LIBPATH", ":DIR", "Add DIR for the linker to search for libraries." }, { LNK_CmdSwitch_Machine, 0, LNK_CmdValueKind_Scalar, "MACHINE", ":{X64|X86}", "Image target platform." }, + { LNK_CmdSwitch_Map, 0, LNK_CmdValueKind_Scalar, "MAP", "[:FILENAME]", "Create a linker map file." }, { LNK_CmdSwitch_Manifest, 0, LNK_CmdValueKind_List, "MANIFEST", "[:{EMBED[,ID=#]|NO]", "Controls whether the linker should create a side manifest." }, { LNK_CmdSwitch_ManifestDependency, 1, LNK_CmdValueKind_Scalar, "MANIFESTDEPENDENCY", ":\"manifest dependency XML string\"", "Add a manifest dependency." }, { LNK_CmdSwitch_ManifestFile, 0, LNK_CmdValueKind_Scalar, "MANIFESTFILE", ":FILENAME", "Specifies a manifest file." }, @@ -72,7 +73,6 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Rad_BuildImpLib, 0, LNK_CmdValueKind_Scalar, "RAD_BUILD_IMPLIB", "[:NO]", "Build import library." }, { LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll, 0, LNK_CmdValueKind_Scalar, "RAD_CHECK_UNUSED_DELAY_LOAD_DLL", "[:NO]", "Check for unused delay load dlls." }, { LNK_CmdSwitch_Rad_DataDirCount, 0, LNK_CmdValueKind_Scalar, "RAD_DATA_DIR_COUNT", ":#", "Internal default for PE optional header data directory count." }, - { LNK_CmdSwitch_Rad_Map, 0, LNK_CmdValueKind_Scalar, "RAD_MAP", ":FILENAME", "Emit file with the output image's layout description." }, { LNK_CmdSwitch_Rad_MapLinesForUnresolvedSymbols, 0, LNK_CmdValueKind_Scalar, "RAD_MAP_LINES_FOR_UNRESOLVED_SYMBOLS", "[:NO]", "Use debug info to print source file location for unresolved symbol" }, { LNK_CmdSwitch_Rad_MemoryMapFiles, 0, LNK_CmdValueKind_Scalar, "RAD_MEMORY_MAP_FILES", "[:{NO|READ_ONLY|READ_WRITE}]", "When enabled, files are memory-mapped instead of being read entirely on request." }, { LNK_CmdSwitch_Rad_BootMode, 0, LNK_CmdValueKind_Scalar, "RAD_BOOT_MODE", "[:LINKER|TYPE_SERVER]", "Overrides default boot program." }, @@ -1175,8 +1175,8 @@ lnk_print_help(void) internal void lnk_apply_write_temp_files(Arena *arena, LNK_Config *config) { - if (config->rad_chunk_map_name.size) { - config->temp_rad_chunk_map_name = push_str8f(arena, "%S.tmp%x", config->rad_chunk_map_name, config->time_stamp); + if (config->map_name.size) { + config->temp_map_name = push_str8f(arena, "%S.tmp%x", config->map_name, config->time_stamp); } if (config->out_path.size) { config->temp_out_path = push_str8f(arena, "%S.tmp%x", config->out_path, config->time_stamp); @@ -1982,9 +1982,11 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8 val } } break; - case LNK_CmdSwitch_Rad_Map: { - lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->rad_chunk_map_name); - config->rad_chunk_map = LNK_SwitchState_Yes; + case LNK_CmdSwitch_Map: { + config->map = LNK_SwitchState_Yes; + if (value.size) { + lnk_cmd_switch_parse_string_copy(config->arena, obj, cmd_switch, value, &config->map_name); + } } break; case LNK_CmdSwitch_Rad_MapLinesForUnresolvedSymbols: { @@ -3082,6 +3084,11 @@ lnk_config_init(U64 argc, char **argv) config->pdb_name = path_replace_file_extension(scratch.arena, config->out_path, str8_lit("pdb")); } + // handle empty /MAP + if (config->map == LNK_SwitchState_Yes && !config->map_name.size) { + config->map_name = path_replace_file_extension(scratch.arena, config->out_path, str8_lit("map")); + } + // handle empty /RAD_DEBUG_NAME if (!lnk_cmd_line_has_switch(cmd_line, LNK_CmdSwitch_Rad_DebugName)) { config->rad_debug_name = path_replace_file_extension(scratch.arena, config->out_path, str8_lit("rdi")); @@ -3100,10 +3107,32 @@ lnk_config_init(U64 argc, char **argv) // convert to full paths config->out_path = full_path_from_path(arena, config->out_path); config->pdb_name = full_path_from_path(arena, config->pdb_name); + if (config->map_name.size) { + config->map_name = full_path_from_path(arena, config->map_name); + } config->rad_debug_name = full_path_from_path(arena, config->rad_debug_name); config->imp_lib_name = full_path_from_path(arena, config->imp_lib_name); config->manifest_name = full_path_from_path(arena, config->manifest_name); + // reject output path aliases before background writers are launched + if (config->map_name.size) { + String8 output_paths[] = { + config->out_path, + config->pdb_name, + config->rad_debug_name, + config->imp_lib_name, + config->manifest_name, + }; + for EachElement(i, output_paths) { + if (output_paths[i].size && path_match_normalized(config->map_name, output_paths[i])) { + lnk_error(LNK_Error_Cmdl, "/MAP output path %S conflicts with another linker output", config->map_name); + config->map = LNK_SwitchState_No; + config->map_name = str8_zero(); + break; + } + } + } + // push linker env vars { struct { String8 key, value; } key_value_str8_table[] = { diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index ab2da698..5ad6bb20 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -73,6 +73,7 @@ typedef enum LNK_CmdSwitch_Lib, LNK_CmdSwitch_LibPath, LNK_CmdSwitch_Machine, + LNK_CmdSwitch_Map, LNK_CmdSwitch_Manifest, LNK_CmdSwitch_ManifestDependency, LNK_CmdSwitch_ManifestFile, @@ -121,7 +122,6 @@ typedef enum LNK_CmdSwitch_Rad_LinkVer, LNK_CmdSwitch_Rad_Log, LNK_CmdSwitch_Rad_Logo, - LNK_CmdSwitch_Rad_Map, LNK_CmdSwitch_Rad_MapLinesForUnresolvedSymbols, LNK_CmdSwitch_Rad_MemoryMapFiles, LNK_CmdSwitch_Rad_Mode, @@ -388,8 +388,8 @@ typedef struct LNK_Config String8 manifest_ui_access; String8List manifest_dependency_list; LNK_SwitchState rad_debug; - LNK_SwitchState rad_chunk_map; - String8 rad_chunk_map_name; + LNK_SwitchState map; + String8 map_name; String8 rad_debug_name; String8 rad_debug_alt_path; LNK_IncludeSymbolList include_symbol_list; @@ -403,7 +403,7 @@ typedef struct LNK_Config String8 temp_out_path; String8 temp_pdb_name; String8 temp_rad_debug_name; - String8 temp_rad_chunk_map_name; + String8 temp_map_name; String8 temp_type_server_name; String8 delay_load_helper_name; String8List remove_sections; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index c4597d3d..6a4b6b35 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -571,6 +571,42 @@ TEST(simple_link_test) T_Ok(opt->loader_flags == 0); } +TEST(map) +{ + T_Ok(t_write_def_obj("map.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "text", ".text", str8_lit_comp("\xC3"), .flags = "rx:code" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("map_entry", "text", 0), + T_COFF_DefSymbol_Static("map_local", "text", 0), + {0} + } + })); + + t_invoke_linkerf("/entry:map_entry /subsystem:console /out:mapped.exe /map:explicit.map map.obj"); + T_Ok(g_last_exit_code == 0); + + String8 map = t_read_file(arena, str8_lit("explicit.map")); + T_Ok(str8_find_needle(map, 0, str8_lit(" Timestamp is "), 0) < map.size); + T_Ok(str8_find_needle(map, 0, str8_lit(" Preferred load address is "), 0) < map.size); + T_Ok(str8_find_needle(map, 0, str8_lit(" Start Length Name Class"), 0) < map.size); + T_Ok(str8_find_needle(map, 0, str8_lit(" Publics by Value"), 0) < map.size); + T_Ok(str8_find_needle(map, 0, str8_lit("map_entry"), 0) < map.size); + T_Ok(str8_find_needle(map, 0, str8_lit(" entry point at"), 0) < map.size); + T_Ok(str8_find_needle(map, 0, str8_lit(" Static symbols"), 0) < map.size); + T_Ok(str8_find_needle(map, 0, str8_lit("map_local"), 0) < map.size); + + t_invoke_linkerf("/entry:map_entry /subsystem:console /out:default.exe /map map.obj"); + T_Ok(g_last_exit_code == 0); + T_Ok(t_read_file(arena, str8_lit("default.map")).size > 0); + + t_invoke_linkerf("/entry:map_entry /subsystem:console /out:collision.exe /map:collision.exe map.obj"); + T_Ok(g_last_exit_code != 0); +} + TEST(out_of_bounds_section_number) { From 0a328898d000f4f75071e0886915a64b0c2f439a Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 04:07:56 -0700 Subject: [PATCH 085/251] sort contributions after section merges --- src/linker/lnk.c | 11 +++++++++-- src/linker/tests/linker_tests.c | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 543a4be5..e74af162 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -3899,7 +3899,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) String8 defn_name_with_flags = lnk_make_name_with_flags(sectab->arena, sect_defn->name, sect_defn->flags); LNK_SectionContribChunk *contrib_chunk = hash_table_search_string_raw(task->contribs_ht, defn_name_with_flags); if (!contrib_chunk) { - contrib_chunk = lnk_section_contrib_chunk_list_push_chunk(main_arena, §->contribs, sect_defn->contribs_count, sort_idx); + contrib_chunk = lnk_section_contrib_chunk_list_push_chunk(main_arena, §->contribs, sect_defn->contribs_count, sect_defn->name); hash_table_push_string_raw(sectab->arena, task->contribs_ht, defn_name_with_flags, contrib_chunk); } @@ -5654,7 +5654,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT } // append common block's contribution - LNK_SectionContribChunk *common_block_chunk = lnk_section_contrib_chunk_list_push_chunk(sectab->arena, &common_block_sect->contribs, 1, str8(0,0)); + LNK_SectionContribChunk *common_block_chunk = lnk_section_contrib_chunk_list_push_chunk(sectab->arena, &common_block_sect->contribs, 1, str8_lit(".bss")); LNK_SectionContrib *common_block_sc = lnk_section_contrib_chunk_push(common_block_chunk, 1); common_block_sc->u.obj_idx = max_U32; common_block_sc->u.obj_section_number = 0; @@ -5675,6 +5675,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // Grouped Sections (PE Format) // "All contributions with the same object-section name are allocated contiguously in the image, // and the blocks of contributions are sorted in lexical order by object-section name." + // Preserve each source section's contribution range across the merge. ProfBegin("Sort Sections"); for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { lnk_sort_section_contribs(§_n->data); @@ -5686,6 +5687,12 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT lnk_section_table_merge(sectab, config->merge_list); } + ProfBegin("Sort Merged Sections"); + for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { + lnk_sort_section_contribs(§_n->data); + } + ProfEnd(); + if (config->do_function_pad_min == LNK_SwitchState_Yes) { tp_for_parallel_prof(tp, arena, objs_count, lnk_flag_hotpatch_contribs_task, &task, "Flag Hotpatch Section Contribs"); } diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 6a4b6b35..98203a2e 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -728,6 +728,29 @@ TEST(merge) t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /merge:.qwe=.test entry.obj test.obj"); T_Ok(g_last_exit_code == 0); + // merged contribution groups retain lexical order + { + T_Ok(t_write_def_obj("order.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "a", ".a$m", str8_lit_comp("a"), .flags = "rw:data@1" }, + { "z", ".z$m", str8_lit_comp("z"), .flags = "rw:data@1" }, + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /merge:.a=.z entry.obj order.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + COFF_SectionHeader *sect = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".z")); + T_Ok(sect != 0); + String8 data = str8_substr(exe, rng_1u64(sect->foff, sect->foff + 2)); + T_Ok(str8_match(data, str8_lit("az"), 0)); + } + // merge .test -> .qwe -> .data { t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /merge:.test=.qwe /merge:.qwe=.data entry.obj test.obj"); From c9acc3f1ad2a32ee486089a2364abb75c6f7bb6f Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 04:25:19 -0700 Subject: [PATCH 086/251] match LINK import group placement --- src/linker/lnk.c | 48 +++++++++++++++++---------------- src/linker/lnk_section_table.c | 17 ++++++++++-- src/linker/tests/linker_tests.c | 9 +++++++ 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index e74af162..ef909c65 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -5678,7 +5678,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // Preserve each source section's contribution range across the merge. ProfBegin("Sort Sections"); for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { - lnk_sort_section_contribs(§_n->data); + lnk_sort_section_contribs(§_n->data, 0); } ProfEnd(); @@ -5689,7 +5689,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT ProfBegin("Sort Merged Sections"); for (LNK_SectionNode *sect_n = sectab->list.first; sect_n != 0; sect_n = sect_n->next) { - lnk_sort_section_contribs(§_n->data); + lnk_sort_section_contribs(§_n->data, 1); } ProfEnd(); @@ -6190,7 +6190,7 @@ lnk_map_push_symbol_line(Arena *arena, String8List *map, LNK_MapSymbol *symbol) } internal String8List -lnk_build_map(Arena *arena, String8 image_data, LNK_Config *config, LNK_SymbolTable *symtab, U64 objs_count, LNK_Obj **objs) +lnk_build_map(Arena *arena, String8 image_data, LNK_Config *config, LNK_SymbolTable *symtab, LNK_SectionTable *sectab, U64 objs_count, LNK_Obj **objs) { ProfBeginFunction(); Temp scratch = scratch_begin(&arena, 1); @@ -6212,31 +6212,33 @@ lnk_build_map(Arena *arena, String8 image_data, LNK_Config *config, LNK_SymbolTa str8_list_pushf(arena, &map, " Start Length Name Class\r\n"); U64 section_cap = 0; - for EachIndex(obj_idx, objs_count) { section_cap += objs[obj_idx]->coff.sections.count_no_null; } + for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { section_cap += sect_n->data.contribs.chunk_count; } LNK_MapSection *sections = push_array(scratch.arena, LNK_MapSection, section_cap); U64 section_count = 0; HashMap *section_maps = push_array(scratch.arena, HashMap, pe.section_count + 1); - for EachIndex(obj_idx, objs_count) { - LNK_Obj *obj = objs[obj_idx]; - for LNK_EachCoffSection(it, obj) { - COFF_SectionFlags flags = *it.v.flags; - if (flags & (COFF_SectionFlag_LnkRemove | COFF_SectionFlag_LnkInfo | LNK_SECTION_FLAG_DEBUG)) { continue; } + for EachNode(sect_n, LNK_SectionNode, sectab->list.first) { + LNK_Section *image_section = §_n->data; + U32 image_section_number = safe_cast_u32(image_section->sect_idx + 1); + if (image_section_number > pe.section_count) { continue; } - U32 image_section_number = lnk_map_image_section_number_from_voff(image_section_table, pe.section_count, it.v.header->voff); - if (image_section_number == 0) { continue; } + for EachNode(chunk, LNK_SectionContribChunk, image_section->contribs.first) { + String8 name = chunk->sort_idx; + if (name.size == 0) { continue; } - String8 name = lnk_obj_section_name_from_section_number(obj, it.v.section_number); LNK_MapSection *section = hash_map_search_string_raw(§ion_maps[image_section_number], name); - U32 off = safe_cast_u32(it.v.header->voff - image_section_table[image_section_number]->voff); - U32 end = off + it.v.header->vsize; - if (section == 0) { - Assert(section_count < section_cap); - section = §ions[section_count++]; - *section = (LNK_MapSection){ name, image_section_number, off, end, !!(flags & COFF_SectionFlag_CntCode) }; - hash_map_push_string_raw(scratch.arena, §ion_maps[image_section_number], name, section); - } else { - section->off = Min(section->off, off); - section->end = Max(section->end, end); + for EachIndex(sc_idx, chunk->count) { + LNK_SectionContrib *sc = chunk->v[sc_idx]; + U32 off = sc->u.off; + U32 end = off + safe_cast_u32(lnk_size_from_section_contrib(sc)); + if (section == 0) { + Assert(section_count < section_cap); + section = §ions[section_count++]; + *section = (LNK_MapSection){ name, image_section_number, off, end, !!(image_section->flags & COFF_SectionFlag_CntCode) }; + hash_map_push_string_raw(scratch.arena, §ion_maps[image_section_number], name, section); + } else { + section->off = Min(section->off, off); + section->end = Max(section->end, end); + } } } } @@ -6489,7 +6491,7 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) // Map // if (config->map == LNK_SwitchState_Yes) { - String8List map = lnk_build_map(scratch.arena, image_ctx.image_data, config, symtab, objs_count, objs); + String8List map = lnk_build_map(scratch.arena, image_ctx.image_data, config, symtab, image_ctx.sectab, objs_count, objs); lnk_write_data_list_to_file_path(config->map_name, config->temp_map_name, map); } diff --git a/src/linker/lnk_section_table.c b/src/linker/lnk_section_table.c index a095e64e..a275c9b1 100644 --- a/src/linker/lnk_section_table.c +++ b/src/linker/lnk_section_table.c @@ -331,14 +331,27 @@ lnk_section_contrib_chunk_is_before(void *raw_a, void *raw_b) return str8_is_before_case_sensitive(&(*a)->sort_idx, &(*b)->sort_idx); } +internal int +lnk_merged_section_contrib_chunk_is_before(void *raw_a, void *raw_b) +{ + LNK_SectionContribChunk **a = raw_a, **b = raw_b; + U64 a_rank = str8_starts_with((*a)->sort_idx, str8_lit(".idata$5")) ? 0 : str8_starts_with((*a)->sort_idx, str8_lit(".idata$")) ? 2 : 1; + U64 b_rank = str8_starts_with((*b)->sort_idx, str8_lit(".idata$5")) ? 0 : str8_starts_with((*b)->sort_idx, str8_lit(".idata$")) ? 2 : 1; + if (a_rank != b_rank) { + return a_rank < b_rank; + } + return str8_is_before_case_sensitive(&(*a)->sort_idx, &(*b)->sort_idx); +} + internal void -lnk_sort_section_contribs(LNK_Section *sect) +lnk_sort_section_contribs(LNK_Section *sect, B32 is_merged_output) { ProfBeginFunction(); Temp scratch = scratch_begin(0,0); LNK_SectionContribChunk **chunks = lnk_array_from_section_contrib_chunk_list(scratch.arena, sect->contribs); - radsort(chunks, sect->contribs.chunk_count, lnk_section_contrib_chunk_is_before); + radsort(chunks, sect->contribs.chunk_count, + is_merged_output ? lnk_merged_section_contrib_chunk_is_before : lnk_section_contrib_chunk_is_before); // repopulate chunk list in sorted order sect->contribs.first = 0; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 98203a2e..0f85918e 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -2312,6 +2312,9 @@ TEST(section_sort) T_Ok(t_write_def_obj("data.obj", (T_COFF_DefObj){ .machine = T_COFF_DefSetMachine(X64), .sections = (T_COFF_DefSection[]){ + { "idata_2", ".idata$2", str8_lit("last"), .flags = "rw:data@1" }, + { "idata_5", ".idata$5", str8_lit("first"), .flags = "rw:data@1" }, + { "rdata", ".rdata", str8_lit("middle"), .flags = "r:data@1" }, { "data_z", ".data$z", str8_lit("five"), .raw_flags = data_flags }, { "data_a", ".data$a", str8_lit("three"), .raw_flags = data_flags }, { "data_bbbbb", ".data$bbbbb", str8_lit("four"), .raw_flags = data_flags }, @@ -2347,6 +2350,12 @@ TEST(section_sort) String8 data = str8_substr(exe, rng_1u64(data_section->foff, data_section->foff + data_section->vsize)); String8 expected_data = str8_lit("onetwothreefourfive"); T_Ok(str8_match(data, expected_data, 0)); + + COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); + T_Ok(rdata_section); + + String8 rdata = str8_substr(exe, rng_1u64(rdata_section->foff, rdata_section->foff + 15)); + T_Ok(str8_match(rdata, str8_lit("firstmiddlelast"), 0)); } TEST(flag_conf) From e28395299461fd6e56da14770790f2ca0783a10c Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 05:21:04 -0700 Subject: [PATCH 087/251] match LINK import encoding and load config --- src/linker/lnk.c | 17 +++++++++++++---- src/linker/lnk.h | 2 +- src/pe/pe_make_import_table.c | 11 +++++++---- src/pe/pe_make_import_table.h | 2 +- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index ef909c65..44ec6e29 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -784,7 +784,7 @@ lnk_make_linker_coff_obj(Arena *arena, } internal String8 -lnk_make_linker_obj(Arena *arena, LNK_Config *config) +lnk_make_linker_obj(Arena *arena, LNK_Config *config, MSCRT_FeatFlags feat_flags) { ProfBeginFunction(); @@ -808,7 +808,7 @@ lnk_make_linker_obj(Arena *arena, LNK_Config *config) // TODO: investigate IMAGE_ENCLAVE_CONFIG 32/64 coff_obj_writer_push_symbol_abs(obj_writer, str8_lit(MSCRT_ENCLAVE_CONFIG_SYMBOL_NAME), 0, COFF_SymStorageClass_External); - coff_obj_writer_push_symbol_abs(obj_writer, str8_lit(MSCRT_GUARD_FLAGS_SYMBOL_NAME) , 0, COFF_SymStorageClass_External); + coff_obj_writer_push_symbol_abs(obj_writer, str8_lit(MSCRT_GUARD_FLAGS_SYMBOL_NAME) , feat_flags & MSCRT_FeatFlag_GUARD_STACK, COFF_SymStorageClass_External); coff_obj_writer_push_symbol_abs(obj_writer, str8_lit(MSCRT_GUARD_FIDS_TABLE_SYMBOL_NAME) , 0, COFF_SymStorageClass_External); coff_obj_writer_push_symbol_abs(obj_writer, str8_lit(MSCRT_GUARD_FIDS_COUNT_SYMBOL_NAME) , 0, COFF_SymStorageClass_External); coff_obj_writer_push_symbol_abs(obj_writer, str8_lit(MSCRT_GUARD_IAT_TABLE_SYMBOL_NAME) , 0, COFF_SymStorageClass_External); @@ -2003,9 +2003,17 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer // link inputer lnk_link_inputs(tp, arena, config, inputer, symtab, link); + MSCRT_FeatFlags image_feat_flags = 0; + for EachNode(obj_n, LNK_ObjNode, link->objs.first) { + image_feat_flags |= lnk_obj_get_features(&obj_n->data); + } + if (image_feat_flags & MSCRT_FeatFlag_GUARD_STACK) { + lnk_include_symbol(config, str8_lit(MSCRT_LOAD_CONFIG_SYMBOL_NAME), 0); + } + { ProfBegin("Push Linker Symbols"); - String8 linker_symbols_obj = lnk_make_linker_obj(arena->v[0], config); + String8 linker_symbols_obj = lnk_make_linker_obj(arena->v[0], config, image_feat_flags); lnk_inputer_push_obj_linkgen(inputer, 0, str8_lit("* Linker Symbols *"), linker_symbols_obj); ProfEnd(); } @@ -2105,7 +2113,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer for (String8Node *dll_name_n = static_dll_names.first; dll_name_n != 0; dll_name_n = dll_name_n->next) { PE_MakeImportList *imports = hash_map_search_path_raw(&static_imports_hm, dll_name_n->string); String8 import_debug_symbols = lnk_make_dll_import_debug_symbols(scratch.arena, config->machine, dll_name_n->string); - String8 import_obj = pe_make_import_dll_obj_static(arena->v[0], time_stamp, config->machine, dll_name_n->string, import_debug_symbols, *imports); + String8 import_obj = pe_make_import_dll_obj_static(arena->v[0], time_stamp, config->machine, dll_name_n->string, import_debug_symbols, *imports, dll_name_n->next != 0); lnk_inputer_push_obj(inputer, 0, str8f(inputer->arena, "Import:%S", dll_name_n->string), import_obj); } @@ -2723,6 +2731,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) // push tasks for each root symbol for EachNode(root_n, LNK_IncludeSymbolNode, config->include_symbol_list.first) { LNK_Symbol *root = lnk_symbol_table_search(symtab, root_n->v.name); + if (root == 0) { continue; } LNK_ObjSymbolRef root_ref = lnk_ref_from_symbol(root); LNK_RelocRefs r = {0}; diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 86192015..59b3d9a2 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -354,7 +354,7 @@ internal String8 lnk_manifest_from_inputs(Arena *arena, LNK_IO_Flags io_flags, S internal String8 lnk_make_null_obj(Arena *arena); internal String8 lnk_make_res_obj(Arena *arena, String8List res_file_list, String8List res_path_list, COFF_MachineType machine, U32 time_stamp, String8 work_dir, PathStyle system_path_style, String8 obj_name); -internal String8 lnk_make_linker_obj(Arena *arena, LNK_Config *config); +internal String8 lnk_make_linker_obj(Arena *arena, LNK_Config *config, MSCRT_FeatFlags feat_flags); // --- Inputer ----------------------------------------------------------------- diff --git a/src/pe/pe_make_import_table.c b/src/pe/pe_make_import_table.c index 8bc39203..77a062c6 100644 --- a/src/pe/pe_make_import_table.c +++ b/src/pe/pe_make_import_table.c @@ -253,7 +253,7 @@ pe_make_null_thunk_data_obj(Arena *arena, String8 dll_name, COFF_TimeStamp time_ } internal String8 -pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_MachineType machine, String8 dll_name, String8 debug_symbols, PE_MakeImportList import_headers) +pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_MachineType machine, String8 dll_name, String8 debug_symbols, PE_MakeImportList import_headers, B32 emit_null_thunk) { COFF_ObjWriter *obj_writer = coff_obj_writer_alloc(time_stamp, machine); @@ -267,7 +267,7 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach COFF_ObjSection *ilt_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$4"), PE_IDATA_SECTION_FLAGS|import_align, str8_zero()); COFF_ObjSection *iat_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$5"), PE_IDATA_SECTION_FLAGS|import_align, str8_zero()); COFF_ObjSection *int_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes, str8_zero()); - COFF_ObjSection *dll_name_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$7"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes, dll_name_cstr); + COFF_ObjSection *dll_name_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes, dll_name_cstr); COFF_ObjSection *code_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".text$zz"), PE_TEXT_SECTION_FLAGS |COFF_SectionFlag_Align1Bytes, str8_zero()); COFF_ObjSection *debug_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".debug$S"), PE_DEBUG_SECTION_FLAGS|COFF_SectionFlag_Align1Bytes, debug_symbols); @@ -297,6 +297,7 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach COFF_ObjSymbol *int_symbol = coff_obj_writer_push_symbol_static(obj_writer, int_sect->name, int_sect->data.total_size, int_sect); String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, import_header.func_name); str8_list_push(obj_writer->arena, &int_sect->data, int_data); + str8_list_push_aligner(obj_writer->arena, &int_sect->data, 0, 2); // in the file IAT mirrors ILT, dynamic linker later overwrites it with imported function addresses String8 iat_symbol_name = push_str8f(obj_writer->arena, "__imp_%S", import_header.func_name); @@ -330,8 +331,10 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach } } - str8_list_push(obj_writer->arena, &ilt_sect->data, str8(0, coff_word_size_from_machine(machine))); - str8_list_push(obj_writer->arena, &iat_sect->data, str8(0, coff_word_size_from_machine(machine))); + if (emit_null_thunk) { + str8_list_push(obj_writer->arena, &ilt_sect->data, str8(0, coff_word_size_from_machine(machine))); + str8_list_push(obj_writer->arena, &iat_sect->data, str8(0, coff_word_size_from_machine(machine))); + } String8 dll_obj = coff_obj_writer_serialize(arena, obj_writer); diff --git a/src/pe/pe_make_import_table.h b/src/pe/pe_make_import_table.h index c1a48c38..dba92caf 100644 --- a/src/pe/pe_make_import_table.h +++ b/src/pe/pe_make_import_table.h @@ -32,7 +32,7 @@ internal COFF_ObjSymbol * pe_make_indirect_jump_thunk_x64(COFF_ObjWriter *obj_wr internal COFF_ObjSymbol * pe_make_load_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *imp_addr_ptr, COFF_ObjSymbol *tail_merge, String8 func_name); internal COFF_ObjSymbol * pe_make_tail_merge_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, String8 dll_name, String8 delay_load_helper_name, COFF_ObjSymbol *dll_import_descriptor); -internal String8 pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stmap, COFF_MachineType machine, String8 dll_name, String8 debug_symbols, PE_MakeImportList import_headers); +internal String8 pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stmap, COFF_MachineType machine, String8 dll_name, String8 debug_symbols, PE_MakeImportList import_headers, B32 emit_null_thunk); internal String8 pe_make_import_dll_obj_delayed(Arena *arena, COFF_TimeStamp time_stamp, COFF_MachineType machine, String8 dll_name, String8 delay_load_helper_name, String8 debug_symbols, PE_MakeImportList import_headers, B32 emit_biat, B32 emit_uiat); internal String8 pe_make_import_entry_obj_delayed(Arena *arena, String8 dll_name, COFF_TimeStamp time_stamp, COFF_MachineType machine, String8 debug_symbols); From d1171dcabbe4a669b18c45f37af022ae4273b85c Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 05:24:23 -0700 Subject: [PATCH 088/251] fix PE initialized data size --- src/linker/lnk.c | 2 +- src/linker/tests/linker_tests.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 44ec6e29..26268d85 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -5380,7 +5380,7 @@ lnk_build_win32_header(Arena *arena, LNK_SymbolTable *symtab, LNK_Config *config if (sect->flags & COFF_SectionFlag_CntUninitializedData) { sizeof_uninited_data += sect->vsize; } - if ((sect->flags & COFF_SectionFlag_CntInitializedData) || (sect->flags & COFF_SectionFlag_CntCode)) { + if (sect->flags & COFF_SectionFlag_CntInitializedData) { sizeof_inited_data += sect->fsize; } if (sect->flags & COFF_SectionFlag_CntCode) { diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 0f85918e..74311a8c 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -554,7 +554,7 @@ TEST(simple_link_test) PE_OptionalHeader32Plus *opt = str8_deserial_get_raw_ptr(exe, pe.optional_header_off, sizeof(*opt)); T_Ok(opt->sizeof_code == text_section->fsize); - T_Ok(opt->sizeof_inited_data == (text_section->fsize + data_section->fsize)); + T_Ok(opt->sizeof_inited_data == data_section->fsize); T_Ok(opt->sizeof_uninited_data == 0x200); T_Ok(opt->code_base == 0x1000); T_Ok(opt->image_base == 0x140000000); From a11e2bf57b5ebbd8610385ff3e2eff80dfd3a3d8 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 05:31:33 -0700 Subject: [PATCH 089/251] manifest generation fix --- src/linker/lnk.c | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 26268d85..2b064591 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -212,45 +212,32 @@ lnk_make_linker_manifest(Arena *arena, String8List srl = {0}; str8_serial_begin(scratch.arena, &srl); str8_serial_push_string(scratch.arena, &srl, str8_lit( - "\n" - "\n")); + "\r\n" + "\r\n")); if (manifest_uac) { -#if 1 String8 uac = push_str8f(scratch.arena, - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n", + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n", manifest_level, manifest_ui_access); -#else - String8 uac = push_str8f(scratch.arena, - "\n" - "" - "" - "" - "" - "" - "" - "", manifest_level, manifest_ui_access); -#endif str8_serial_push_string(scratch.arena, &srl, uac); } for (String8Node *node = manifest_dependency_list.first; node != 0; node = node->next) { String8 dep = push_str8f(scratch.arena, - " \n" - " \n" - " \n" - " \n" - " \n", + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n", node->string); str8_serial_push_string(scratch.arena, &srl, dep); } - str8_serial_push_string(scratch.arena, &srl, str8_lit("\n")); + str8_serial_push_string(scratch.arena, &srl, str8_lit("\r\n")); String8 result = str8_list_join(arena, &srl, 0); From f6116bbede744a17904f3be7dc113fde1439f23d Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 05:37:16 -0700 Subject: [PATCH 090/251] match LINK resource section layout --- src/linker/lnk.c | 245 ++++++++++++++++---------------------- src/pe/pe_section_flags.h | 6 +- 2 files changed, 106 insertions(+), 145 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 2b064591..2e469fbc 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -212,14 +212,14 @@ lnk_make_linker_manifest(Arena *arena, String8List srl = {0}; str8_serial_begin(scratch.arena, &srl); str8_serial_push_string(scratch.arena, &srl, str8_lit( - "\r\n" - "\r\n")); + "\r\n" + "\r\n")); if (manifest_uac) { String8 uac = push_str8f(scratch.arena, " \r\n" " \r\n" " \r\n" - " \r\n" + " \r\n" " \r\n" " \r\n" " \r\n", @@ -231,7 +231,7 @@ lnk_make_linker_manifest(Arena *arena, String8 dep = push_str8f(scratch.arena, " \r\n" " \r\n" - " \r\n" + " \r\n" " \r\n" " \r\n", node->string); @@ -400,156 +400,117 @@ lnk_serialize_pe_resource_tree(COFF_ObjWriter *obj_writer, PE_ResourceDir *root_ ProfBeginFunction(); Temp scratch = scratch_begin(0, 0); - struct Stack { - struct Stack *next; - U64 arr_idx; - U64 res_idx[2]; - PE_ResourceArray res_arr[2]; - COFF_ResourceDirEntry *coff_entry_arr[2]; + struct QueueNode { + struct QueueNode *next; + PE_Resource *res; + COFF_ResourceDirEntry *coff_entry; }; - struct Stack *stack = push_array(scratch.arena, struct Stack, 1); - // init stack - { - PE_Resource *root_wrapper = push_array(scratch.arena, PE_Resource, 1); - root_wrapper->id.type = COFF_ResourceIDType_Number; - root_wrapper->id.u.number = 0; - root_wrapper->kind = PE_ResDataKind_DIR; - root_wrapper->u.dir = root_dir; + struct QueueNode *queue_first = 0; + struct QueueNode *queue_last = 0; - COFF_ResourceDirEntry *root_dir = push_array(scratch.arena, COFF_ResourceDirEntry, 1); + PE_Resource *root_wrapper = push_array(scratch.arena, PE_Resource, 1); + root_wrapper->id.type = COFF_ResourceIDType_Number; + root_wrapper->id.u.number = 0; + root_wrapper->kind = PE_ResDataKind_DIR; + root_wrapper->u.dir = root_dir; - stack->res_arr[0].count = 1; - stack->res_arr[0].v = root_wrapper; - - stack->coff_entry_arr[0] = root_dir; - stack->coff_entry_arr[1] = 0; - } + COFF_ResourceDirEntry *root_entry = push_array(scratch.arena, COFF_ResourceDirEntry, 1); + struct QueueNode *root_node = push_array(scratch.arena, struct QueueNode, 1); + root_node->res = root_wrapper; + root_node->coff_entry = root_entry; + SLLQueuePush(queue_first, queue_last, root_node); COFF_ObjSection *rsrc1 = coff_obj_writer_push_section(obj_writer, str8_lit(".rsrc$01"), PE_RSRC1_SECTION_FLAGS, str8_zero()); COFF_ObjSection *rsrc2 = coff_obj_writer_push_section(obj_writer, str8_lit(".rsrc$02"), PE_RSRC2_SECTION_FLAGS, str8_zero()); - for (; stack; ) { - for (; stack->arr_idx < ArrayCount(stack->res_arr); stack->arr_idx += 1) { - for (; stack->res_idx[stack->arr_idx] < stack->res_arr[stack->arr_idx].count; ) { - U64 res_idx = stack->res_idx[stack->arr_idx]++; - PE_Resource *res = &stack->res_arr[stack->arr_idx].v[res_idx]; + for (struct QueueNode *node = queue_first; node != 0; node = queue_first) { + SLLQueuePop(queue_first, queue_last); + PE_Resource *res = node->res; - { - COFF_ResourceDirEntry *coff_entry = &stack->coff_entry_arr[stack->arr_idx][res_idx]; + node->coff_entry->id.data_entry_offset = safe_cast_u32(rsrc1->data.total_size); + if (res->kind == PE_ResDataKind_DIR) { + node->coff_entry->id.data_entry_offset |= COFF_Resource_SubDirFlag; + } + + switch (res->kind) { + case PE_ResDataKind_DIR: { + COFF_ResourceDirTable *dir_header = push_array(obj_writer->arena, COFF_ResourceDirTable, 1); + dir_header->characteristics = res->u.dir->characteristics; + dir_header->time_stamp = res->u.dir->time_stamp; + dir_header->major_version = res->u.dir->major_version; + dir_header->minor_version = res->u.dir->minor_version; + dir_header->name_entry_count = res->u.dir->named_list.count; + dir_header->id_entry_count = res->u.dir->id_list.count; + + PE_ResourceArray named_array; + PE_ResourceArray id_array; + { + Temp scratch2 = scratch_begin(&scratch.arena, 1); + named_array = pe_resource_list_to_array(scratch2.arena, &res->u.dir->named_list); + id_array = pe_resource_list_to_array(scratch2.arena, &res->u.dir->id_list); + + PE_ResourcePtrArray named_ptr_array = pe_resource_ptr_from_array(scratch2.arena, named_array); + PE_ResourcePtrArray id_ptr_array = pe_resource_ptr_from_array(scratch2.arena, id_array); + radsort(named_ptr_array.v, named_ptr_array.count, lnk_res_string_id_is_before); + radsort(id_ptr_array.v, id_ptr_array.count, lnk_res_number_id_is_before); + + named_array = pe_resource_from_ptr_array(scratch.arena, named_ptr_array); + id_array = pe_resource_from_ptr_array(scratch.arena, id_ptr_array); + scratch_end(scratch2); + } - // assign entry data offset - coff_entry->id.data_entry_offset = safe_cast_u32(rsrc1->data.total_size); - - // set directory flag - if (res->kind == PE_ResDataKind_DIR) { - coff_entry->id.data_entry_offset |= COFF_Resource_SubDirFlag; - } - } - - switch (res->kind) { - case PE_ResDataKind_DIR: { - // fill out directory header - COFF_ResourceDirTable *dir_header = push_array(obj_writer->arena, COFF_ResourceDirTable, 1); - dir_header->characteristics = res->u.dir->characteristics; - dir_header->time_stamp = res->u.dir->time_stamp; - dir_header->major_version = res->u.dir->major_version; - dir_header->minor_version = res->u.dir->minor_version; - dir_header->name_entry_count = res->u.dir->named_list.count; - dir_header->id_entry_count = res->u.dir->id_list.count; - - // sort input resources - PE_ResourceArray named_array; - PE_ResourceArray id_array; - { - Temp scratch2 = scratch_begin(&scratch.arena, 1); - - named_array = pe_resource_list_to_array(scratch2.arena, &res->u.dir->named_list); - id_array = pe_resource_list_to_array(scratch2.arena, &res->u.dir->id_list); - - PE_ResourcePtrArray named_ptr_array = pe_resource_ptr_from_array(scratch2.arena, named_array); - PE_ResourcePtrArray id_ptr_array = pe_resource_ptr_from_array(scratch2.arena, id_array); - - radsort(named_ptr_array.v, named_ptr_array.count, lnk_res_string_id_is_before); - radsort(id_ptr_array.v, id_ptr_array.count, lnk_res_number_id_is_before); - - named_array = pe_resource_from_ptr_array(scratch.arena, named_ptr_array); - id_array = pe_resource_from_ptr_array(scratch.arena, id_ptr_array); - - scratch_end(scratch2); - } + COFF_ResourceDirEntry *named_entries = push_array(obj_writer->arena, COFF_ResourceDirEntry, named_array.count); + COFF_ResourceDirEntry *id_entries = push_array(obj_writer->arena, COFF_ResourceDirEntry, id_array.count); + str8_list_push(obj_writer->arena, &rsrc1->data, str8_struct(dir_header)); + str8_list_push(obj_writer->arena, &rsrc1->data, str8_array(named_entries, named_array.count)); + str8_list_push(obj_writer->arena, &rsrc1->data, str8_array(id_entries, id_array.count)); + + for (U64 i = 0; i < named_array.count; i += 1) { + PE_Resource *child = &named_array.v[i]; + COFF_ResourceDirEntry *entry = &named_entries[i]; + U32 name_off = safe_cast_u32(rsrc1->data.total_size); + String8 name = coff_resource_string_from_str8(obj_writer->arena, child->id.u.string); + str8_list_push(obj_writer->arena, &rsrc1->data, name); + entry->name.offset = COFF_Resource_SubDirFlag | name_off; + + struct QueueNode *child_node = push_array(scratch.arena, struct QueueNode, 1); + child_node->res = child; + child_node->coff_entry = entry; + SLLQueuePush(queue_first, queue_last, child_node); + } + for (U64 i = 0; i < id_array.count; i += 1) { + PE_Resource *child = &id_array.v[i]; + COFF_ResourceDirEntry *entry = &id_entries[i]; + entry->name.id = child->id.u.number; + + struct QueueNode *child_node = push_array(scratch.arena, struct QueueNode, 1); + child_node->res = child; + child_node->coff_entry = entry; + SLLQueuePush(queue_first, queue_last, child_node); + } + } break; + + case PE_ResDataKind_COFF_RESOURCE: { + COFF_ResourceDataEntry *coff_res = push_array(obj_writer->arena, COFF_ResourceDataEntry, 1); + coff_res->data_size = res->u.coff_res.data.size; + coff_res->data_voff = 0; + coff_res->code_page = 0; + + if (res->u.coff_res.data.size >= sizeof(U32)) { + U32 resdat_off = safe_cast_u32(rsrc2->data.total_size); + COFF_ObjSymbol *resdat = coff_obj_writer_push_symbol_static(obj_writer, str8_lit("resdat"), resdat_off, rsrc2); + U64 apply_off = rsrc1->data.total_size + OffsetOf(COFF_ResourceDataEntry, data_voff); + coff_obj_writer_section_push_reloc_voff(obj_writer, rsrc1, safe_cast_u32(apply_off), resdat); + } - // allocate COFF entries - COFF_ResourceDirEntry *named_entries = push_array(obj_writer->arena, COFF_ResourceDirEntry, named_array.count); - COFF_ResourceDirEntry *id_entries = push_array(obj_writer->arena, COFF_ResourceDirEntry, id_array.count); + str8_list_push(obj_writer->arena, &rsrc1->data, str8_struct(coff_res)); + str8_list_push(obj_writer->arena, &rsrc2->data, res->u.coff_res.data); + str8_list_push_aligner(obj_writer->arena, &rsrc2->data, 0, 8); + } break; - // push header and entries - str8_list_push(obj_writer->arena, &rsrc1->data, str8_struct(dir_header)); - str8_list_push(obj_writer->arena, &rsrc1->data, str8_array(named_entries, named_array.count)); - str8_list_push(obj_writer->arena, &rsrc1->data, str8_array(id_entries, id_array.count)); - - // fill out named ids - for (U64 i = 0; i < named_array.count; i += 1) { - PE_Resource src = named_array.v[i]; - COFF_ResourceDirEntry *dst = &named_entries[i]; - - // append resource name - U32 res_name_off = safe_cast_u32(rsrc1->data.total_size); - String8 res_name = coff_resource_string_from_str8(obj_writer->arena, res->id.u.string); - str8_list_push(obj_writer->arena, &rsrc1->data, res_name); - - // not sure why high bit has to be turned on here since number id and string id entries are - // in separate arrays but windows doesn't treat name offset like string without this bit. - dst->name.offset = (1 << 31) | res_name_off; - } - - // fill out number ids - for (U64 i = 0; i < id_array.count; i += 1) { - PE_Resource src = id_array.v[i]; - COFF_ResourceDirEntry *dst = &id_entries[i]; - dst->name.id = src.id.u.number; - } - - // fill out sub directory stack frame - struct Stack *frame = push_array(scratch.arena, struct Stack, 1); - frame->res_arr[0] = named_array; - frame->res_arr[1] = id_array; - frame->coff_entry_arr[0] = named_entries; - frame->coff_entry_arr[1] = id_entries; - SLLStackPush(stack, frame); - } goto yield; // recurse to sub directory - - case PE_ResDataKind_COFF_RESOURCE: { - // fill out resource header - COFF_ResourceDataEntry *coff_res = push_array(obj_writer->arena, COFF_ResourceDataEntry, 1); - coff_res->data_size = res->u.coff_res.data.size; - coff_res->data_voff = 0; // relocated - coff_res->code_page = 0; // TODO: whats this for? - - if (res->u.coff_res.data.size >= sizeof(U32)) { - // emit symbol for resource data - U32 resdat_off = safe_cast_u32(rsrc2->data.total_size); - COFF_ObjSymbol *resdat = coff_obj_writer_push_symbol_static(obj_writer, str8_lit("resdat"), resdat_off, rsrc2); - - // emit reloc for 'data_voff' - U64 apply_off = rsrc1->data.total_size + OffsetOf(COFF_ResourceDataEntry, data_voff); - U32 apply_off32 = safe_cast_u32(apply_off); - coff_obj_writer_section_push_reloc_voff(obj_writer, rsrc1, apply_off32, resdat); - } - - // push resource entry & data - str8_list_push(obj_writer->arena, &rsrc1->data, str8_struct(coff_res)); - str8_list_push(obj_writer->arena, &rsrc2->data, res->u.coff_res.data); - } break; - - case PE_ResDataKind_NULL: break; - - // we must not have this resource node here, it is used to represent on-disk version of entry - case PE_ResDataKind_COFF_LEAF: InvalidPath; - } - } + case PE_ResDataKind_NULL: break; + case PE_ResDataKind_COFF_LEAF: InvalidPath; } - SLLStackPop(stack); - yield:; } scratch_end(scratch); diff --git a/src/pe/pe_section_flags.h b/src/pe/pe_section_flags.h index cd835b38..14a482c4 100644 --- a/src/pe/pe_section_flags.h +++ b/src/pe/pe_section_flags.h @@ -10,9 +10,9 @@ #define PE_BSS_SECTION_FLAGS (COFF_SectionFlag_CntUninitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemWrite) #define PE_IDATA_SECTION_FLAGS PE_DATA_SECTION_FLAGS #define PE_DEBUG_DIR_SECTION_FLAGS PE_RDATA_SECTION_FLAGS -#define PE_RSRC_SECTION_FLAGS PE_DATA_SECTION_FLAGS -#define PE_RSRC1_SECTION_FLAGS (PE_DATA_SECTION_FLAGS | COFF_SectionFlag_Align4Bytes) -#define PE_RSRC2_SECTION_FLAGS (PE_DATA_SECTION_FLAGS | COFF_SectionFlag_Align4Bytes) +#define PE_RSRC_SECTION_FLAGS PE_RDATA_SECTION_FLAGS +#define PE_RSRC1_SECTION_FLAGS PE_RDATA_SECTION_FLAGS +#define PE_RSRC2_SECTION_FLAGS PE_RDATA_SECTION_FLAGS #define PE_XDATA_SECTION_FLAGS PE_RDATA_SECTION_FLAGS #define PE_PDATA_SECTION_FLAGS PE_RDATA_SECTION_FLAGS #define PE_EDATA_SECTION_FLAGS PE_RDATA_SECTION_FLAGS From 5894d3eaa215acac5bf84c3b7fc44a259327a7e5 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 10:49:30 -0700 Subject: [PATCH 091/251] count merged BSS in initialized data size --- src/linker/lnk.c | 2 +- src/linker/tests/linker_tests.c | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 2e469fbc..ecb55ec4 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -5329,7 +5329,7 @@ lnk_build_win32_header(Arena *arena, LNK_SymbolTable *symtab, LNK_Config *config sizeof_uninited_data += sect->vsize; } if (sect->flags & COFF_SectionFlag_CntInitializedData) { - sizeof_inited_data += sect->fsize; + sizeof_inited_data += Max(sect->fsize, AlignPow2(sect->vsize, config->file_align)); } if (sect->flags & COFF_SectionFlag_CntCode) { sizeof_code += sect->fsize; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 74311a8c..5310b747 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -728,6 +728,31 @@ TEST(merge) t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /merge:.qwe=.test entry.obj test.obj"); T_Ok(g_last_exit_code == 0); + // initialized data size includes the aligned virtual tail of merged BSS + { + T_Ok(t_write_def_obj("mixed.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".data", str8_lit_comp("d"), .flags = "rw:data@1" }, + { "bss", ".bss", str8(0, 0x201), .flags = "rw:bss@1" }, + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /merge:.bss=.data entry.obj mixed.obj"); + T_Ok(g_last_exit_code == 0); + + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + PE_OptionalHeader32Plus *opt = str8_deserial_get_raw_ptr(exe, pe.optional_header_off, sizeof(*opt)); + COFF_SectionHeader *data = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".data")); + T_Ok(data != 0); + T_Ok(data->fsize == 0x200); + T_Ok(data->vsize == 0x202); + T_Ok(opt->sizeof_inited_data == 0x400); + } + // merged contribution groups retain lexical order { T_Ok(t_write_def_obj("order.obj", (T_COFF_DefObj){ From e8aaf006290fcc131eeddc6a9f15aaf76fd94de0 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 11:03:15 -0700 Subject: [PATCH 092/251] prune dead static imports with /OPT:REF --- src/linker/tests/linker_tests.c | 36 ++++++++++++++++- src/pe/pe_make_import_table.c | 69 +++++++++++++++++---------------- 2 files changed, 71 insertions(+), 34 deletions(-) diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 5310b747..c0b46331 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -4783,21 +4783,55 @@ TEST(import_kernel32) {0}, } }, + { + "dead", ".text$dead", str8(0, 4), + .flags = "rx:code@1", + .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr32Nb, 0, "__imp_CompareStringW"), + {0}, + } + }, {0}, }, .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("dead", COFF_ComdatSelect_Any), T_COFF_DefSymbol_Extern("test", "data", 0), T_COFF_DefSymbol_Extern("entry", "text", 0), + T_COFF_DefSymbol_ExternFunc("dead", "dead", 0), T_COFF_DefSymbol_Undef("__imp_CreateFileA"), T_COFF_DefSymbol_Undef("__imp_CloseHandle"), + T_COFF_DefSymbol_Undef("__imp_CompareStringW"), {0}, } })); - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /fixed import.obj kernel32.lib"); + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /fixed /opt:ref import.obj kernel32.lib"); T_Ok(g_last_exit_code == 0); { + String8 exe = t_read_file(arena, str8_lit("a.exe")); + PE_BinInfo pe = pe_bin_info_from_data(arena, exe); + COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; + PE_ParsedStaticImportTable imports = pe_static_imports_from_data(arena, pe.is_pe32, pe.section_count, section_table, exe, pe.data_dir_franges[PE_DataDirectoryIndex_IMPORT]); + B32 found_create = 0; + B32 found_close = 0; + B32 found_dead = 0; + for EachIndex(dll_idx, imports.count) { + PE_ParsedStaticDLLImport *dll = &imports.v[dll_idx]; + for EachIndex(import_idx, dll->import_count) { + PE_ParsedImport *import = &dll->imports[import_idx]; + if (import->type == PE_ParsedImport_Name) { + found_create |= str8_match(import->u.name.string, str8_lit("CreateFileA"), 0); + found_close |= str8_match(import->u.name.string, str8_lit("CloseHandle"), 0); + found_dead |= str8_match(import->u.name.string, str8_lit("CompareStringW"), 0); + } + } + } + T_Ok(found_create); + T_Ok(found_close); + T_Ok(!found_dead); + String8 test_file_path = push_str8f(arena, "%S/test", g_wdir); delete_file_at_path(test_file_path); diff --git a/src/pe/pe_make_import_table.c b/src/pe/pe_make_import_table.c index 77a062c6..fd7e8505 100644 --- a/src/pe/pe_make_import_table.c +++ b/src/pe/pe_make_import_table.c @@ -261,51 +261,41 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach COFF_SectionFlags import_align = coff_section_flag_from_align_size(import_size); PE_ImportEntry *impdesc = push_array(obj_writer->arena, PE_ImportEntry, 1); - String8 dll_name_cstr = push_cstr(obj_writer->arena, dll_name); - - COFF_ObjSection *dll_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$2"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align4Bytes, str8_struct(impdesc)); - COFF_ObjSection *ilt_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$4"), PE_IDATA_SECTION_FLAGS|import_align, str8_zero()); - COFF_ObjSection *iat_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$5"), PE_IDATA_SECTION_FLAGS|import_align, str8_zero()); - COFF_ObjSection *int_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes, str8_zero()); - COFF_ObjSection *dll_name_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes, dll_name_cstr); - COFF_ObjSection *code_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".text$zz"), PE_TEXT_SECTION_FLAGS |COFF_SectionFlag_Align1Bytes, str8_zero()); - COFF_ObjSection *debug_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".debug$S"), PE_DEBUG_SECTION_FLAGS|COFF_SectionFlag_Align1Bytes, debug_symbols); - - COFF_ObjSymbol *ilt_symbol = coff_obj_writer_push_symbol_static(obj_writer, ilt_sect->name, 0, ilt_sect); - COFF_ObjSymbol *iat_symbol = coff_obj_writer_push_symbol_static(obj_writer, iat_sect->name, 0, iat_sect); - COFF_ObjSymbol *dll_name_symbol = coff_obj_writer_push_symbol_static(obj_writer, dll_name_sect->name, 0, dll_name_sect); - - // patch import DLL header - coff_obj_writer_section_push_reloc_voff(obj_writer, dll_sect, OffsetOf(PE_ImportEntry, lookup_table_voff), ilt_symbol); - coff_obj_writer_section_push_reloc_voff(obj_writer, dll_sect, OffsetOf(PE_ImportEntry, name_voff), dll_name_symbol); - coff_obj_writer_section_push_reloc_voff(obj_writer, dll_sect, OffsetOf(PE_ImportEntry, import_addr_table_voff), iat_symbol); + COFF_ObjSection *dll_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$2"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align4Bytes, str8_struct(impdesc)); + COFF_ObjSymbol *ilt_symbol = 0; + COFF_ObjSymbol *iat_symbol = 0; for (PE_MakeImportNode *import_header_n = import_headers.first; import_header_n != 0; import_header_n = import_header_n->next) { COFF_ParsedArchiveImportHeader import_header = coff_archive_import_from_data(import_header_n->v.header); - COFF_ObjSymbol *iat_symbol = 0; + COFF_SectionFlags comdat_flags = COFF_SectionFlag_LnkCOMDAT; + COFF_ObjSection *ilt_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$4"), PE_IDATA_SECTION_FLAGS|import_align|comdat_flags, str8_zero()); + COFF_ObjSection *iat_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$5"), PE_IDATA_SECTION_FLAGS|import_align|comdat_flags, str8_zero()); + coff_obj_writer_push_symbol_secdef(obj_writer, iat_sect, COFF_ComdatSelect_Any); + coff_obj_writer_push_symbol_associative(obj_writer, ilt_sect, iat_sect); + + String8 iat_symbol_name = push_str8f(obj_writer->arena, "__imp_%S", import_header.func_name); + COFF_ObjSymbol *func_iat_symbol = coff_obj_writer_push_symbol_extern(obj_writer, iat_symbol_name, 0, iat_sect); + if (ilt_symbol == 0) { + ilt_symbol = coff_obj_writer_push_symbol_static(obj_writer, ilt_sect->name, 0, ilt_sect); + iat_symbol = func_iat_symbol; + } switch (import_header.import_by) { case COFF_ImportBy_Ordinal: { String8 ordinal_data = coff_ordinal_data_from_hint(obj_writer->arena, import_header.machine, import_header.hint_or_ordinal); - String8 iat_symbol_name = push_str8f(obj_writer->arena, "__imp_%S", import_header.func_name); - iat_symbol = coff_obj_writer_push_symbol_extern(obj_writer, iat_symbol_name, iat_sect->data.total_size, iat_sect); str8_list_push(obj_writer->arena, &ilt_sect->data, ordinal_data); str8_list_push(obj_writer->arena, &iat_sect->data, ordinal_data); } break; case COFF_ImportBy_Name: { - // put together name look up entry - COFF_ObjSymbol *int_symbol = coff_obj_writer_push_symbol_static(obj_writer, int_sect->name, int_sect->data.total_size, int_sect); + COFF_ObjSection *int_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes|comdat_flags, str8_zero()); + coff_obj_writer_push_symbol_associative(obj_writer, int_sect, iat_sect); + COFF_ObjSymbol *int_symbol = coff_obj_writer_push_symbol_static(obj_writer, int_sect->name, 0, int_sect); String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, import_header.func_name); str8_list_push(obj_writer->arena, &int_sect->data, int_data); str8_list_push_aligner(obj_writer->arena, &int_sect->data, 0, 2); - // in the file IAT mirrors ILT, dynamic linker later overwrites it with imported function addresses - String8 iat_symbol_name = push_str8f(obj_writer->arena, "__imp_%S", import_header.func_name); - iat_symbol = coff_obj_writer_push_symbol_extern(obj_writer, iat_symbol_name, iat_sect->data.total_size, iat_sect); - - // patch IAT and ILT - coff_obj_writer_section_push_reloc_voff(obj_writer, ilt_sect, ilt_sect->data.total_size, int_symbol); - coff_obj_writer_section_push_reloc_voff(obj_writer, iat_sect, iat_sect->data.total_size, int_symbol); + coff_obj_writer_section_push_reloc_voff(obj_writer, ilt_sect, 0, int_symbol); + coff_obj_writer_section_push_reloc_voff(obj_writer, iat_sect, 0, int_symbol); U64 import_size = coff_word_size_from_machine(import_header.machine); U8 *import_entry = push_array(obj_writer->arena, U8, import_size); @@ -320,9 +310,11 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach // emit thunks if (import_header_n->v.make_jump_thunk) { if (import_header.type == COFF_ImportHeader_Code) { + COFF_ObjSection *code_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".text$zz"), PE_TEXT_SECTION_FLAGS|COFF_SectionFlag_Align1Bytes|comdat_flags, str8_zero()); + coff_obj_writer_push_symbol_secdef(obj_writer, code_sect, COFF_ComdatSelect_Any); switch (import_header.machine) { case COFF_MachineType_Unknown: {} break; - case COFF_MachineType_X64: { pe_make_indirect_jump_thunk_x64(obj_writer, code_sect, iat_symbol, import_header.func_name); } break; + case COFF_MachineType_X64: { pe_make_indirect_jump_thunk_x64(obj_writer, code_sect, func_iat_symbol, import_header.func_name); } break; default: { NotImplemented; } break; } } else { @@ -332,10 +324,21 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach } if (emit_null_thunk) { - str8_list_push(obj_writer->arena, &ilt_sect->data, str8(0, coff_word_size_from_machine(machine))); - str8_list_push(obj_writer->arena, &iat_sect->data, str8(0, coff_word_size_from_machine(machine))); + String8 null_entry = str8(0, coff_word_size_from_machine(machine)); + coff_obj_writer_push_section(obj_writer, str8_lit(".idata$4"), PE_IDATA_SECTION_FLAGS|import_align, null_entry); + coff_obj_writer_push_section(obj_writer, str8_lit(".idata$5"), PE_IDATA_SECTION_FLAGS|import_align, null_entry); } + String8 dll_name_cstr = push_cstr(obj_writer->arena, dll_name); + COFF_ObjSection *dll_name_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes, dll_name_cstr); + COFF_ObjSection *debug_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".debug$S"), PE_DEBUG_SECTION_FLAGS|COFF_SectionFlag_Align1Bytes, debug_symbols); + COFF_ObjSymbol *dll_name_symbol = coff_obj_writer_push_symbol_static(obj_writer, dll_name_sect->name, 0, dll_name_sect); + + Assert(ilt_symbol != 0 && iat_symbol != 0); + coff_obj_writer_section_push_reloc_voff(obj_writer, dll_sect, OffsetOf(PE_ImportEntry, lookup_table_voff), ilt_symbol); + coff_obj_writer_section_push_reloc_voff(obj_writer, dll_sect, OffsetOf(PE_ImportEntry, name_voff), dll_name_symbol); + coff_obj_writer_section_push_reloc_voff(obj_writer, dll_sect, OffsetOf(PE_ImportEntry, import_addr_table_voff), iat_symbol); + String8 dll_obj = coff_obj_writer_serialize(arena, obj_writer); coff_obj_writer_release(&obj_writer); From ae89d8ea88a305d43190fad3bf80a8cd12243a7d Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 21 Aug 2026 11:04:30 -0700 Subject: [PATCH 093/251] align static import DLL names --- src/pe/pe_make_import_table.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pe/pe_make_import_table.c b/src/pe/pe_make_import_table.c index fd7e8505..eb20b0e4 100644 --- a/src/pe/pe_make_import_table.c +++ b/src/pe/pe_make_import_table.c @@ -331,6 +331,7 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach String8 dll_name_cstr = push_cstr(obj_writer->arena, dll_name); COFF_ObjSection *dll_name_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes, dll_name_cstr); + str8_list_push_aligner(obj_writer->arena, &dll_name_sect->data, 0, 2); COFF_ObjSection *debug_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".debug$S"), PE_DEBUG_SECTION_FLAGS|COFF_SectionFlag_Align1Bytes, debug_symbols); COFF_ObjSymbol *dll_name_symbol = coff_obj_writer_push_symbol_static(obj_writer, dll_name_sect->name, 0, dll_name_sect); From ff1877dfa2fb18f83c20ac66dd259290805444c9 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 24 Aug 2026 13:26:25 -0700 Subject: [PATCH 094/251] push purged item to the free list --- src/linker/hash_table.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/linker/hash_table.c b/src/linker/hash_table.c index 0fecf387..66c9938c 100644 --- a/src/linker/hash_table.c +++ b/src/linker/hash_table.c @@ -334,6 +334,7 @@ hash_table_purge_item(HashTable *ht, U64 hash, BucketNode *node) { U64 bucket_idx = hash % ht->cap; DLLRemove(ht->buckets[bucket_idx].first, ht->buckets[bucket_idx].last, node); + SLLQueuePush(ht->free_buckets.first, ht->free_buckets.last, node); ht->count -= 1; } From 39fa231e7cad08867cf5d2c660a209e483a755cf Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 24 Aug 2026 17:11:46 -0700 Subject: [PATCH 095/251] ICF correctness fixes * added safe-guards for the const data ICF * exclude associated CFG metadata section guards * color primary section hash with associated sections colors * remove folded symbol associated chain --- src/linker/lnk.c | 108 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index ecb55ec4..75d96ed9 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2928,7 +2928,9 @@ lnk_icf_scope_from_section_number(LNK_Obj *obj, U32 section_number) { LNK_ICF_Scope result = LNK_ICF_Scope_Null; + // // * section flags filter * + // COFF_SectionFlags expected_flags = COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_MemRead; COFF_SectionFlags exclude_flags = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_MemWrite | LNK_SECTION_FLAG_NOICF; if ((obj->coff.sections.headers[section_number].flags & expected_flags) != expected_flags || (obj->coff.sections.headers[section_number].flags & exclude_flags) != 0) { @@ -2960,6 +2962,16 @@ lnk_icf_scope_from_section_number(LNK_Obj *obj, U32 section_number) // load COMDAT symbol name String8 comdat_name = lnk_symbol_name_from_coff_symbol_idx(comdat_ref.obj, comdat_ref.symbol_idx); + // + // * include MSVC C++ EH tables * + // + if (str8_starts_with(comdat_name, str8_lit("$cppxdata$")) || + str8_starts_with(comdat_name, str8_lit("$stateUnwindMap$")) || + str8_starts_with(comdat_name, str8_lit("$ip2state$"))) { + result = LNK_ICF_Scope_Unwind; + goto exit; + } + // // * include virtual function tables * // @@ -2968,6 +2980,18 @@ lnk_icf_scope_from_section_number(LNK_Obj *obj, U32 section_number) goto exit; } + // exclude static COMDAT data + COFF_ParsedSymbol comdat_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(comdat_ref.obj, comdat_ref.symbol_idx); + if (comdat_symbol.storage_class != COFF_SymStorageClass_External) { + goto exit; + } + + // exclude general read-only data records with fixups + COFF_SectionHeader *header = lnk_coff_section_header_from_section_number(obj, section_number); + if (lnk_coff_relocs_from_section_header(obj, header).count > 0) { + goto exit; + } + // // * include COMDATs * // @@ -3026,6 +3050,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) typedef struct { U64 *color; U64 static_id; + U64 association_id; U32 value; COFF_SymbolValueInterpType interp; } RelocTarget; @@ -3116,6 +3141,34 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // step 1: fill out color map and contributions // + COFF_SectionFlags associated_filter = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_LnkInfo | COFF_SectionFlag_MemDiscardable | LNK_SECTION_FLAG_DEBUG; + + ProfBegin("Flag Unsupported Associative Sections"); + for EachIndex(i, task->obj_indices[task_id].count) { + U64 obj_idx = task->obj_indices[task_id].v[i]; + LNK_Obj *obj = objs[obj_idx]; + + for LNK_EachCoffSection(it, obj) { + COFF_SectionHeader *header = lnk_coff_section_header_from_section_number(obj, it.v.section_number); + + if ((header->flags & (COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_CntCode)) != (COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_CntCode)) { + continue; + } + + Temp temp = temp_begin(scratch.arena); + U32List associated_sections = lnk_obj_collect_associated_section_numbers(temp.arena, obj, it.v.section_number, associated_filter); + for EachNode(section_n, U32Node, associated_sections.first) { + String8 section_name = lnk_obj_section_name_from_section_number(obj, section_n->data); + if (str8_starts_with(section_name, str8_lit(".gsspr$"))) { + header->flags |= LNK_SECTION_FLAG_NOICF; + break; + } + } + temp_end(temp); + } + } + ProfEnd(); + // alloc total section counter U64 *contrib_counts = 0; if (task_id == 0) { @@ -3207,7 +3260,6 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) Temp temp = temp_begin(scratch.arena); // include associative children in their parent COMDAT's identity - COFF_SectionFlags associated_filter = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_LnkInfo | COFF_SectionFlag_MemDiscardable | LNK_SECTION_FLAG_DEBUG; U32List associated_sections = lnk_obj_collect_associated_section_numbers(temp.arena, obj, it.v.section_number, associated_filter); u32_list_push(temp.arena, &associated_sections, it.v.section_number); @@ -3253,9 +3305,33 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) LNK_Obj *target_obj = target_ref.obj; U32 target_sect = target_symbol.section_number; + // references between corresponding associative children compare + // through the owning COMDAT rather than each child's unique identity + while (lnk_icf_scope_from_section_number(target_obj, target_sect) == LNK_ICF_Scope_Null) { + COFF_ComdatSelectType select = COFF_ComdatSelect_Null; + U32 parent_sect = 0; + if (!lnk_try_comdat_props_from_section_number(target_obj, target_sect, &select, &parent_sect, 0, 0)) { + break; + } + if (select != COFF_ComdatSelect_Associative) { + break; + } + + U64 child_pos = 0; + for EachNode(child_n, U32Node, target_obj->coff.sections.associated_section_numbers[parent_sect]) { + if (child_n->data == target_sect) { break; } + child_pos += 1; + } + + String8 child_name = lnk_obj_section_name_from_section_number(target_obj, target_sect); + U64 association_key[] = { target->association_id, hash_map_hasher(child_name), child_pos }; + + target->association_id = hash_map_hasher(str8_array_fixed(association_key)); + target_sect = parent_sect; + } + // use the selected COMDAT leader so equivalent targets hash alike - if (target_sect != 0 && target_sect <= target_obj->coff.sections.count_no_null && - target_obj->coff.sections.headers[target_sect].flags & COFF_SectionFlag_LnkCOMDAT) { + if (target_obj->coff.sections.headers[target_sect].flags & COFF_SectionFlag_LnkCOMDAT) { LNK_ObjSymbolRef leader_ref = {0}; if (lnk_obj_get_comdat_symlink_from_section_number(target_obj, target_sect, &leader_ref)) { COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(leader_ref.obj, leader_ref.symbol_idx); @@ -3352,6 +3428,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) RelocTarget *target = contrib->reloc_targets[reloc_idx]; U64 target_id = target->color ? *target->color : target->static_id; lnk_hasher_update_struct(&hasher, &target_id); + lnk_hasher_update_struct(&hasher, &target->association_id); } U128 hash = lnk_hasher_digest128(&hasher); @@ -3575,6 +3652,31 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // update folded section COMDAT symlink to point to the leader section contrib_obj->symlinks[contrib->section_number] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->coff.sections.comdats[leader->section_number] }; + // remove discarded associated sections + { + Temp assoc_temp = temp_begin(scratch.arena); + + U32List contrib_children = lnk_obj_collect_associated_section_numbers(assoc_temp.arena, contrib_obj, contrib->section_number, associated_filter); + U32List leader_children = lnk_obj_collect_associated_section_numbers(assoc_temp.arena, leader_obj, leader->section_number, associated_filter); + U32Node *contrib_child_n = contrib_children.first; + U32Node *leader_child_n = leader_children.first; + + for (; contrib_child_n && leader_child_n; contrib_child_n = contrib_child_n->next, leader_child_n = leader_child_n->next) { + U32 contrib_child = contrib_child_n->data; + U32 leader_child = leader_child_n->data; + String8 contrib_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib_child); + String8 leader_name = lnk_obj_section_name_from_section_number(leader_obj, leader_child); + + if ( ! str8_match(contrib_name, leader_name, 0)) { continue; } + if (lnk_icf_scope_from_section_number(contrib_obj, contrib_child) != LNK_ICF_Scope_Null) { continue; } + + contrib_obj->coff.sections.headers[contrib_child].flags |= COFF_SectionFlag_LnkRemove; + contrib_obj->symlinks[contrib_child] = (LNK_ObjSymbolRef){ leader_obj, leader_obj->coff.sections.comdats[leader_child] }; + } + + temp_end(assoc_temp); + } + #if LNK_PARANOID String8 section_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib->section_number); String8 leader_name = lnk_obj_section_name_from_section_number(leader_obj, leader->section_number); From 092a121484e0de7de10a89bd7a2e66b3290af3cb Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 25 Aug 2026 11:20:40 -0700 Subject: [PATCH 096/251] project-tagged watch exprs --- project.raddbg | 2 +- src/raddbg/raddbg_core.c | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/project.raddbg b/project.raddbg index 8e78d298..372df5de 100755 --- a/project.raddbg +++ b/project.raddbg @@ -1,4 +1,4 @@ -// raddbg 0.9.28 project file +// raddbg 0.9.29 project file name: "The RAD Debugger" target: diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 2aeec9df..64f9d664 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2054,6 +2054,7 @@ rd_view_ui(Rng2F32 rect) // for(CFG_Node *child = view->first; child != &cfg_nil_node; child = child->next) { + if(rd_cfg_is_project_filtered(child)) { continue; } if(str8_match(child->string, s("watch_expression"), 0)) { CFG_Node *locked = cfg_node_child_from_string(child, s("locked")); @@ -2897,6 +2898,8 @@ rd_view_ui(Rng2F32 rect) new_cfg_parent = cfg_node_child_from_string(cfg_node_root(), str8_lit("project")); } cfg = cfg_node_new(rd_state->cfg, new_cfg_parent, row_info.group_cfg_name); + CFG_Node *project = cfg_node_new(rd_state->cfg, cfg, s("project")); + cfg_node_new(rd_state->cfg, project, rd_state->project_path); state_dirty = 1; snap_to_cursor = 1; } @@ -3631,6 +3634,8 @@ rd_view_ui(Rng2F32 rect) cfg_node_equip_stringf(rd_state->cfg, cfg, "watch_expression"); CFG_Node *expr = cfg_node_new(rd_state->cfg, cfg, s("expression")); cfg_node_new(rd_state->cfg, expr, drag_regs->expr); + CFG_Node *project = cfg_node_new(rd_state->cfg, cfg, s("project")); + cfg_node_new(rd_state->cfg, project, rd_state->project_path); } cfg_node_insert_child(rd_state->cfg, drag_parent_cfg, drag_prev_cfg, cfg); }break; @@ -17069,6 +17074,8 @@ rd_frame(void) CFG_Node *watch = cfg_node_new(rd_state->cfg, watch_tab, s("watch_expression")); CFG_Node *expr = cfg_node_new(rd_state->cfg, watch, s("expression")); cfg_node_new(rd_state->cfg, expr, rd_regs()->string); + CFG_Node *project = cfg_node_new(rd_state->cfg, watch, s("project")); + cfg_node_new(rd_state->cfg, project, rd_state->project_path); } }break; From e28a543573277315ffd32a3e983c8171570bb946 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 25 Aug 2026 11:38:25 -0700 Subject: [PATCH 097/251] remove dead binary searching helper --- src/base/base_core.c | 33 --------------------------------- src/base/base_core.h | 4 ---- 2 files changed, 37 deletions(-) diff --git a/src/base/base_core.c b/src/base/base_core.c index 10c9c3cc..15a21998 100644 --- a/src/base/base_core.c +++ b/src/base/base_core.c @@ -659,39 +659,6 @@ wrapped_read(U8 *ring_base, U64 ring_size, U64 ring_pos, void *dst_data, U64 rea //////////////////////////////// -internal U64 -u64_array_bsearch(U64 *arr, U64 count, U64 value) -{ - if(count > 1 && arr[0] <= value && value < arr[count-1]) - { - U64 l = 0; - U64 r = count - 1; - for(; l <= r; ) - { - U64 m = l + (r - l) / 2; - if(arr[m] == value) - { - return m; - } - else if(arr[m] < value) - { - l = m + 1; - } - else - { - r = m - 1; - } - } - } - else if (count == 1 && arr[0] == value) - { - return 0; - } - return max_U64; -} - -//////////////////////////////// - internal U32 idx_of_zero_byte64(U8 *ptr, U64 size) { diff --git a/src/base/base_core.h b/src/base/base_core.h index 0cb16243..43831617 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -1123,10 +1123,6 @@ internal U64 wrapped_read(U8 *ring_base, U64 ring_size, U64 ring_pos, void *dst_ //////////////////////////////// -internal U64 u64_array_bsearch(U64 *arr, U64 count, U64 value); - -//////////////////////////////// - internal U32 idx_of_zero_byte64(U8 *ptr, U64 size); // size must be exactly 8 bytes internal U64 index_of_zero_u32(U32 *ptr, U64 count); internal U64 index_of_zero_u64(U64 *ptr, U64 count); From fb06485b871e14a828d727f8408a8f2a41230ef5 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 25 Aug 2026 17:31:33 -0700 Subject: [PATCH 098/251] browse button cell extension for cfg file path evaluations - route to 'pick file' command, fast-path for using query uis to inserting a path textually --- project.raddbg | 2 +- src/raddbg/generated/raddbg.meta.c | 14 ++++---- src/raddbg/generated/raddbg.meta.h | 7 ++-- src/raddbg/raddbg.mdesk | 5 ++- src/raddbg/raddbg_core.c | 53 ++++++++++++++++++++++++++++++ src/raddbg/raddbg_views.c | 4 +-- src/raddbg/raddbg_widgets.c | 37 +++++++++++++++++++++ src/raddbg/raddbg_widgets.h | 18 ++++++---- 8 files changed, 115 insertions(+), 25 deletions(-) diff --git a/project.raddbg b/project.raddbg index 372df5de..39492b35 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,7 +7,6 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { @@ -19,6 +18,7 @@ target: executable: "build/mule_main.exe" working_directory: build output_label: output_a + enabled: 1 } target: { diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 63b19235..b597ea71 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -62,7 +62,7 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[372] = +RD_VocabInfo rd_vocab_info_table[371] = { {str8_lit_comp("type_view"), str8_lit_comp("type_views"), str8_lit_comp("Type View"), str8_lit_comp("Type Views"), RD_IconKind_Binoculars}, {str8_lit_comp("file_path_map"), str8_lit_comp("file_path_maps"), str8_lit_comp("File Path Map"), str8_lit_comp("File Path Maps"), RD_IconKind_FileOutline}, @@ -400,9 +400,8 @@ RD_VocabInfo rd_vocab_info_table[372] = {str8_lit_comp("break_when_value_changes"), str8_lit_comp(""), str8_lit_comp("Break When Value Changes"), str8_lit_comp(""), RD_IconKind_CircleFilled}, {str8_lit_comp("search"), str8_lit_comp(""), str8_lit_comp("Search"), str8_lit_comp(""), RD_IconKind_Find}, {str8_lit_comp("search_backwards"), str8_lit_comp(""), str8_lit_comp("Search Backwards"), str8_lit_comp(""), RD_IconKind_Find}, -{str8_lit_comp("pick_file"), str8_lit_comp(""), str8_lit_comp("Pick File"), str8_lit_comp(""), RD_IconKind_FileOutline}, -{str8_lit_comp("pick_folder"), str8_lit_comp(""), str8_lit_comp("Pick Folder"), str8_lit_comp(""), RD_IconKind_FolderOpenFilled}, -{str8_lit_comp("pick_file_or_folder"), str8_lit_comp(""), str8_lit_comp("Pick File/Folder"), str8_lit_comp(""), RD_IconKind_FileOutline}, +{str8_lit_comp("pick_file_replacement"), str8_lit_comp(""), str8_lit_comp("Pick File Replacement"), str8_lit_comp(""), RD_IconKind_FileOutline}, +{str8_lit_comp("pick_file_path"), str8_lit_comp(""), str8_lit_comp("Pick File Path"), str8_lit_comp(""), RD_IconKind_FileOutline}, {str8_lit_comp("push_query"), str8_lit_comp(""), str8_lit_comp("Push Query"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("complete_query"), str8_lit_comp(""), str8_lit_comp("Complete Query"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("cancel_query"), str8_lit_comp(""), str8_lit_comp("Cancel Query"), str8_lit_comp(""), RD_IconKind_Null}, @@ -599,7 +598,7 @@ Rng1U64 rd_reg_slot_range_table[55] = {OffsetOf(RD_Regs, wm_event), OffsetOf(RD_Regs, wm_event) + sizeof(WM_Event *)}, }; -RD_CmdKindInfo rd_cmd_kind_info_table[259] = +RD_CmdKindInfo rd_cmd_kind_info_table[258] = { {0}, { str8_lit_comp("launch_and_run"), str8_lit_comp("Starts debugging a new instance of a target, then runs."), str8_lit_comp("launch,start,run,target"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:targets")}}, @@ -824,9 +823,8 @@ RD_CmdKindInfo rd_cmd_kind_info_table[259] = { str8_lit_comp("break_when_value_changes"), str8_lit_comp("Adds a new write data breakpoint for this variable's range."), str8_lit_comp(""), str8_lit_comp("{memory_eval_commands}"), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*1)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*0), RD_RegSlot_Expr, str8_lit_comp("")}}, { str8_lit_comp("search"), str8_lit_comp("Begins searching within the active interface."), str8_lit_comp("sort,search,filter,find"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*1)|(RD_QueryFlag_KeepOldInput*1)|(RD_QueryFlag_SelectOldInput*1)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*1), RD_RegSlot_String, str8_lit_comp("")}}, { str8_lit_comp("search_backwards"), str8_lit_comp("Begins searching backwards within the active interface."), str8_lit_comp("sort,search,filter,find"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*1)|(RD_QueryFlag_KeepOldInput*1)|(RD_QueryFlag_SelectOldInput*1)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*1), RD_RegSlot_String, str8_lit_comp("")}}, -{ str8_lit_comp("pick_file"), str8_lit_comp("Opens the file browser to pick a file."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*1)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, -{ str8_lit_comp("pick_folder"), str8_lit_comp("Opens the file browser to pick a folder."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*1)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, -{ str8_lit_comp("pick_file_or_folder"), str8_lit_comp("Opens the file browser to pick a file or folder."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*1)|(RD_QueryFlag_AllowFolders*1)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, +{ str8_lit_comp("pick_file_replacement"), str8_lit_comp("Opens the file browser to pick a file replacement for a missing source file."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*1)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, +{ str8_lit_comp("pick_file_path"), str8_lit_comp("Opens the file browser to pick a file, then inserts that file's path as text."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*1)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, { str8_lit_comp("push_query"), str8_lit_comp("Opens a new temporary query interface."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("complete_query"), str8_lit_comp("Completes and closes a query."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("cancel_query"), str8_lit_comp("Closes a query."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 5208386c..b24c64fb 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -291,9 +291,8 @@ RD_CmdKind_GoToMemory, RD_CmdKind_BreakWhenValueChanges, RD_CmdKind_Search, RD_CmdKind_SearchBackwards, -RD_CmdKind_PickFile, -RD_CmdKind_PickFolder, -RD_CmdKind_PickFileOrFolder, +RD_CmdKind_PickFileReplacement, +RD_CmdKind_PickFilePath, RD_CmdKind_PushQuery, RD_CmdKind_CompleteQuery, RD_CmdKind_CancelQuery, @@ -629,7 +628,7 @@ Z(getting_started)\ C_LINKAGE_BEGIN extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; -extern RD_VocabInfo rd_vocab_info_table[372]; +extern RD_VocabInfo rd_vocab_info_table[371]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; extern String8 rd_reg_slot_code_name_table[55]; extern Rng1U64 rd_reg_slot_range_table[55]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index c0c46440..3ce3ee7e 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1235,9 +1235,8 @@ RD_CmdTable: // | | | | {SearchBackwards 0 1 1 "" String 0 0 1 1 1 0 1 Find "search_backwards" "Search Backwards" "Begins searching backwards within the active interface." "sort,search,filter,find" "" } //- rjf: queries - {PickFile 0 0 0 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 FileOutline "pick_file" "Pick File" "Opens the file browser to pick a file." "" "" } - {PickFolder 0 0 0 `folder:\\"$input\\"` FilePath 0 1 0 0 0 1 1 FolderOpenFilled "pick_folder" "Pick Folder" "Opens the file browser to pick a folder." "" "" } - {PickFileOrFolder 0 0 0 `folder:\\"$input\\"` FilePath 1 1 0 0 0 1 1 FileOutline "pick_file_or_folder" "Pick File/Folder" "Opens the file browser to pick a file or folder." "" "" } + {PickFileReplacement 0 0 0 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 FileOutline "pick_file_replacement" "Pick File Replacement" "Opens the file browser to pick a file replacement for a missing source file." "" "" } + {PickFilePath 0 1 0 `folder:\\"$input\\"` FilePath 1 0 0 0 0 1 1 FileOutline "pick_file_path" "Pick File Path" "Opens the file browser to pick a file, then inserts that file's path as text." "" "" } //- rjf: query stack {PushQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "push_query" "Push Query" "Opens a new temporary query interface." "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 64f9d664..b2decdd7 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -4006,6 +4006,7 @@ rd_view_ui(Rng2F32 rect) // CFG_Node *cell_view = &cfg_nil_node; B32 revert_cell = 0; + B32 browse_cell = 0; UI_Signal sig = {0}; ProfScope("build cell contents") UI_Parent(cell_box) @@ -4187,6 +4188,27 @@ rd_view_ui(Rng2F32 rect) MemoryZeroStruct(&cell_params.description); } + // rjf: browse button for cfg file paths + if(cell->eval.space.kind == RD_EvalSpaceKind_MetaCfg && !(cell->flags & RD_WatchCellFlag_NoEval)) + { + CFG_Node *root_cfg = rd_cfg_from_eval_space(cell->eval.space); + String8 child_key = e_string_from_id(cell->eval.space.u64s[1]); + MD_NodePtrList schemas = cfg_schemas_from_name(scratch.arena, rd_state->cfg_schema_table, root_cfg->string); + for EachNode(n, MD_NodePtrNode, schemas.first) + { + MD_Node *schema_child = md_child_from_string(n->v, child_key, 0); + if(schema_child != &md_nil_node) + { + if(str8_match(schema_child->first->string, s("path"), 0)) + { + cell_params.flags |= RD_CellFlag_BrowseButton; + cell_params.browse_out = &browse_cell; + } + break; + } + } + } + // rjf: extra edit button for meta-cfg strings if(cell->eval.space.kind == RD_EvalSpaceKind_MetaCfg) { @@ -4572,6 +4594,18 @@ rd_view_ui(Rng2F32 rect) } } } + + // rjf: apply browse + if(browse_cell) + { + ewv->next_cursor = ewv->next_mark = cell_pt; + if(!rd_watch_pt_match(ewv->cursor, cell_pt) && ewv->text_editing) + { + rd_cmd(RD_CmdKind_Accept); + } + rd_cmd(RD_CmdKind_FocusPanel); + rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_PickFilePath].string); + } } } @@ -16417,6 +16451,25 @@ rd_frame(void) str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "$%I64x", bp->id); }break; + //- rjf: pick file paths + case RD_CmdKind_PickFilePath: + { + CFG_Node *window = cfg_node_from_id(rd_regs()->window); + RD_WindowState *ws = rd_window_state_from_cfg(window); + { + UI_Event evt = zero_struct; + evt.kind = UI_EventKind_Text; + evt.string = rd_regs()->file_path; + ui_event_list_push(scratch.arena, &ws->ui_events, &evt); + } + { + UI_Event evt = zero_struct; + evt.kind = UI_EventKind_Press; + evt.slot = UI_EventActionSlot_Accept; + ui_event_list_push(scratch.arena, &ws->ui_events, &evt); + } + }break; + //- rjf: queries case RD_CmdKind_PushQuery: { diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 058111bf..9531f992 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -2315,7 +2315,7 @@ RD_VIEW_UI_FUNCTION_DEF(text) default:{}break; // rjf: override file picking - case RD_CmdKind_PickFile: + case RD_CmdKind_PickFileReplacement: { String8 src = rd_regs()->file_path; String8 dst = cmd->regs->file_path; @@ -2445,7 +2445,7 @@ RD_VIEW_UI_FUNCTION_DEF(text) UI_TagF("pop") if(ui_clicked(ui_buttonf("Find alternative..."))) { - rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_PickFile].string); + rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_PickFileReplacement].string); } scratch_end(scratch); } diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index e9769997..d1d248fa 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -3656,6 +3656,43 @@ rd_cell(RD_CellParams *params, String8 string) } } + ////////////////////////////// + //- rjf: build browse button + // + if(params->flags & RD_CellFlag_BrowseButton && !is_focus_active && !is_focus_active_disabled) + { + UI_Parent(box) + UI_PrefWidth(ui_children_sum(1)) + RD_Font(RD_FontSlot_Main) + { + UI_TagF(".") + UI_TagF("weak") + UI_Column + UI_Padding(ui_pct(1, 0)) + UI_PrefHeight(ui_em(2.f, 1.f)) + UI_CornerRadius(ui_top_font_size()*0.5f) + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_text_dim(10, 1)) + { + UI_Box *revert_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DisableFocusOverlay| + UI_BoxFlag_DisableFocusBorder| + UI_BoxFlag_Clickable, + "Browse..."); + UI_Signal sig = ui_signal_from_box(revert_box); + if(ui_clicked(sig) && params->browse_out) + { + params->browse_out[0] = 1; + } + } + ui_spacer(ui_em(1, 1)); + } + } + ////////////////////////////// //- rjf: build toggle-switch // diff --git a/src/raddbg/raddbg_widgets.h b/src/raddbg/raddbg_widgets.h index 17dad246..a8a34a4c 100644 --- a/src/raddbg/raddbg_widgets.h +++ b/src/raddbg/raddbg_widgets.h @@ -28,19 +28,20 @@ enum RD_CellFlag_EmptyEditButton = (1<<6), RD_CellFlag_RevertButton = (1<<7), RD_CellFlag_Lock = (1<<8), + RD_CellFlag_BrowseButton = (1<<9), //- rjf: behavior - RD_CellFlag_DisableEdit = (1<<9), - RD_CellFlag_KeyboardClickable = (1<<10), - RD_CellFlag_SingleClickActivate = (1<<11), + RD_CellFlag_DisableEdit = (1<<10), + RD_CellFlag_KeyboardClickable = (1<<11), + RD_CellFlag_SingleClickActivate = (1<<12), //- rjf: contents description - RD_CellFlag_CodeContents = (1<<12), + RD_CellFlag_CodeContents = (1<<13), //- rjf: appearance - RD_CellFlag_Border = (1<<13), - RD_CellFlag_NoBackground = (1<<14), - RD_CellFlag_Button = (1<<15), + RD_CellFlag_Border = (1<<14), + RD_CellFlag_NoBackground = (1<<15), + RD_CellFlag_Button = (1<<16), }; typedef struct RD_CellParams RD_CellParams; @@ -75,6 +76,9 @@ struct RD_CellParams //- rjf: revert out B32 *revert_out; + //- rjf: browse out + B32 *browse_out; + //- rjf: text editing r/w info U64 *cursor; U64 *mark; From 944bff5290cbb4a02e09f8311e663f6ba2b80a39 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 25 Aug 2026 17:56:52 -0700 Subject: [PATCH 099/251] folder picking via same mechanism --- project.raddbg | 2 +- src/raddbg/generated/raddbg.meta.c | 8 +++++--- src/raddbg/generated/raddbg.meta.h | 7 +++++-- src/raddbg/raddbg.mdesk | 3 ++- src/raddbg/raddbg_core.c | 26 ++++++++++++++++++++++---- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/project.raddbg b/project.raddbg index 39492b35..e1eae2e2 100755 --- a/project.raddbg +++ b/project.raddbg @@ -16,7 +16,7 @@ target: target: { executable: "build/mule_main.exe" - working_directory: build + working_directory: "build/" output_label: output_a enabled: 1 } diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index b597ea71..516a7fb9 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -467,7 +467,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("color"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @display_name(\"Value\") @description(\"An expression to describe the value or location of the color.\")\n 'expression': expr_string,\n}\n")}, {str8_lit_comp("geo3d"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @display_name(\"Expression\") @description(\"An expression to describe the base address of the index buffer.\")\n 'expression': expr_string,\n 'count': expr_string,\n 'vtx': expr_string,\n 'vtx_size': expr_string,\n 'yaw': @range[0, 1] f32,\n 'pitch': @range[-0.5, 0] f32,\n 'zoom': @range[0, 100] f32,\n}\n")}, {str8_lit_comp("getting_started"), 0, str8_lit_comp("@inherit(tab) x:\n{\n}\n")}, -{str8_lit_comp("target"), 0, str8_lit_comp("@row_commands(@cmd_line save_cfg_to_project, enable_cfg, launch_and_run, launch_and_step_into, duplicate_cfg, remove_cfg)\n@collection_commands(add_target)\nx:\n{\n 'label': code_string,\n 'executable': path,\n 'arguments': string,\n 'working_directory': path,\n 'entry_point': expr_string,\n @default(\"output\") 'output_label': code_string,\n 'stdout_path': @no_relativize path,\n 'stderr_path': @no_relativize path,\n 'stdin_path': @no_relativize path,\n 'environment': set,\n 'debug_subprocesses': bool,\n @no_revert @no_expand @default(0) 'enabled': bool,\n}\n")}, +{str8_lit_comp("target"), 0, str8_lit_comp("@row_commands(@cmd_line save_cfg_to_project, enable_cfg, launch_and_run, launch_and_step_into, duplicate_cfg, remove_cfg)\n@collection_commands(add_target)\nx:\n{\n 'label': code_string,\n 'executable': path,\n 'arguments': string,\n 'working_directory': @folder path,\n 'entry_point': expr_string,\n @default(\"output\") 'output_label': code_string,\n 'stdout_path': @no_relativize path,\n 'stderr_path': @no_relativize path,\n 'stdin_path': @no_relativize path,\n 'environment': set,\n 'debug_subprocesses': bool,\n @no_revert @no_expand @default(0) 'enabled': bool,\n}\n")}, {str8_lit_comp("breakpoint"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(toggle_breakpoint, add_breakpoint, add_address_breakpoint, add_function_breakpoint, clear_breakpoints)\nx:\n{\n 'label': code_string,\n 'condition': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n 'hit_count': u64,\n 'address_range_size': @or(0, 1, 2, 4, 8) u64,\n 'break_on_write': bool,\n 'break_on_read': bool,\n 'break_on_execute': bool,\n @no_revert @no_expand @default(1) 'enabled': bool,\n}\n")}, {str8_lit_comp("watch_pin"), 0, str8_lit_comp("@row_commands(duplicate_cfg, remove_cfg)\n@collection_commands(add_watch_pin, toggle_watch_pin)\nx:\n{\n 'expression': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n}\n")}, {str8_lit_comp("debug_info"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(load_debug_info)\nx:\n{\n 'path': @no_relativize path,\n @no_revert @no_expand @default(1) 'enabled': bool,\n @query 'guid': string,\n}\n")}, @@ -480,7 +480,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("thread"), 0, str8_lit_comp("x:{'label':code_string, 'id':u64, @no_expand 'active':bool, 'call_stack':set}")}, }; -String8 rd_reg_slot_code_name_table[55] = +String8 rd_reg_slot_code_name_table[56] = { {0}, str8_lit_comp("machine"), @@ -533,13 +533,14 @@ str8_lit_comp("activate_with_single_click"), str8_lit_comp("disable_addresses"), str8_lit_comp("small_size"), str8_lit_comp("auto_unwind"), +str8_lit_comp("pick_folder"), str8_lit_comp("dir2"), str8_lit_comp("string"), str8_lit_comp("cmd_name"), str8_lit_comp("wm_event"), }; -Rng1U64 rd_reg_slot_range_table[55] = +Rng1U64 rd_reg_slot_range_table[56] = { {0}, {OffsetOf(RD_Regs, machine), OffsetOf(RD_Regs, machine) + sizeof(D_Handle)}, @@ -592,6 +593,7 @@ Rng1U64 rd_reg_slot_range_table[55] = {OffsetOf(RD_Regs, disable_addresses), OffsetOf(RD_Regs, disable_addresses) + sizeof(B32)}, {OffsetOf(RD_Regs, small_size), OffsetOf(RD_Regs, small_size) + sizeof(B32)}, {OffsetOf(RD_Regs, auto_unwind), OffsetOf(RD_Regs, auto_unwind) + sizeof(B32)}, +{OffsetOf(RD_Regs, pick_folder), OffsetOf(RD_Regs, pick_folder) + sizeof(B32)}, {OffsetOf(RD_Regs, dir2), OffsetOf(RD_Regs, dir2) + sizeof(Dir2)}, {OffsetOf(RD_Regs, string), OffsetOf(RD_Regs, string) + sizeof(String8)}, {OffsetOf(RD_Regs, cmd_name), OffsetOf(RD_Regs, cmd_name) + sizeof(String8)}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index b24c64fb..51969988 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -59,6 +59,7 @@ RD_RegSlot_ActivateWithSingleClick, RD_RegSlot_DisableAddresses, RD_RegSlot_SmallSize, RD_RegSlot_AutoUnwind, +RD_RegSlot_PickFolder, RD_RegSlot_Dir2, RD_RegSlot_String, RD_RegSlot_CmdName, @@ -516,6 +517,7 @@ B32 activate_with_single_click; B32 disable_addresses; B32 small_size; B32 auto_unwind; +B32 pick_folder; Dir2 dir2; String8 string; String8 cmd_name; @@ -620,6 +622,7 @@ Z(getting_started)\ .disable_addresses = rd_regs()->disable_addresses,\ .small_size = rd_regs()->small_size,\ .auto_unwind = rd_regs()->auto_unwind,\ +.pick_folder = rd_regs()->pick_folder,\ .dir2 = rd_regs()->dir2,\ .string = rd_regs()->string,\ .cmd_name = rd_regs()->cmd_name,\ @@ -630,8 +633,8 @@ extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; extern RD_VocabInfo rd_vocab_info_table[371]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; -extern String8 rd_reg_slot_code_name_table[55]; -extern Rng1U64 rd_reg_slot_range_table[55]; +extern String8 rd_reg_slot_code_name_table[56]; +extern Rng1U64 rd_reg_slot_range_table[56]; extern String8 rd_binding_version_remap_old_name_table[9]; extern String8 rd_binding_version_remap_new_name_table[9]; extern String8 rd_icon_kind_text_table[75]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 3ce3ee7e..94933829 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -722,7 +722,7 @@ RD_VocabTable: 'label': code_string, 'executable': path, 'arguments': string, - 'working_directory': path, + 'working_directory': @folder path, 'entry_point': expr_string, @default("output") 'output_label': code_string, 'stdout_path': @no_relativize path, @@ -913,6 +913,7 @@ RD_RegTable: {B32 disable_addresses DisableAddresses } {B32 small_size SmallSize } {B32 auto_unwind AutoUnwind } + {B32 pick_folder PickFolder } {Dir2 dir2 Dir2 } {String8 string String } {String8 cmd_name CmdName } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index b2decdd7..36b845a8 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2362,10 +2362,12 @@ rd_view_ui(Rng2F32 rect) String8 cmd_name = cmd->first->string; // rjf: if we have no selection, just pick the first row + B32 default_selection = 0; EV_Row *row = 0; if(selection_tbl.min.y == 0 && selection_tbl.max.y == 0) { row = ev_row_from_num(scratch.arena, eval_view, &block_ranges, 1); + default_selection = 1; } // rjf: if we do have a selection, compute that row @@ -2374,8 +2376,14 @@ rd_view_ui(Rng2F32 rect) row = ev_row_from_num(scratch.arena, eval_view, &block_ranges, selection_tbl.min.y); } + // rjf: default selection, and we're picking folder? -> pick currently browsed folder + if(cfg_node_child_from_string(view, s("pick_folder")) != &cfg_nil_node && default_selection) + { + rd_cmd(RD_CmdKind_CompleteQuery, .file_path = current_input); + } + // rjf: use row to complete query - if(row->eval.expr != &e_expr_nil) + else if(row->eval.expr != &e_expr_nil) { taken = 1; E_Eval eval = row->eval; @@ -2428,9 +2436,9 @@ rd_view_ui(Rng2F32 rect) { E_Type *type = e_type_from_key(eval.irtree.type_key); String8 file = rd_file_path_from_eval(scratch.arena, eval); - if(str8_match(type->name, str8_lit("folder"), 0)) + if(str8_match(type->name, s("folder"), 0)) { - String8 new_input_string = push_str8f(scratch.arena, "%S/", file); + String8 new_input_string = str8f(scratch.arena, "%S/", file); rd_cmd(RD_CmdKind_UpdateQuery, .string = new_input_string); } else @@ -4007,6 +4015,7 @@ rd_view_ui(Rng2F32 rect) CFG_Node *cell_view = &cfg_nil_node; B32 revert_cell = 0; B32 browse_cell = 0; + B32 browse_folder = 0; UI_Signal sig = {0}; ProfScope("build cell contents") UI_Parent(cell_box) @@ -4203,6 +4212,7 @@ rd_view_ui(Rng2F32 rect) { cell_params.flags |= RD_CellFlag_BrowseButton; cell_params.browse_out = &browse_cell; + browse_folder = md_node_has_tag(schema_child->first, s("folder"), 0); } break; } @@ -4604,7 +4614,7 @@ rd_view_ui(Rng2F32 rect) rd_cmd(RD_CmdKind_Accept); } rd_cmd(RD_CmdKind_FocusPanel); - rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_PickFilePath].string); + rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_PickFilePath].string, .pick_folder = browse_folder); } } } @@ -16564,6 +16574,14 @@ rd_frame(void) { cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("prefer_new_tab")); } + if(!rd_regs()->pick_folder) + { + cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, s("pick_folder"))); + } + else + { + cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("pick_folder")); + } } // rjf: choose initial input string From dc917cc82c6c138315117ae28428c71b9c1f0ead Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 25 Aug 2026 18:03:50 -0700 Subject: [PATCH 100/251] set initial path on browse press --- src/raddbg/raddbg_core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 36b845a8..53bfa415 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -4608,12 +4608,17 @@ rd_view_ui(Rng2F32 rect) // rjf: apply browse if(browse_cell) { + String8 current_path = rd_file_path_from_eval(scratch.arena, cell->eval); ewv->next_cursor = ewv->next_mark = cell_pt; if(!rd_watch_pt_match(ewv->cursor, cell_pt) && ewv->text_editing) { rd_cmd(RD_CmdKind_Accept); } rd_cmd(RD_CmdKind_FocusPanel); + if(current_path.size != 0) + { + rd_cmd(RD_CmdKind_SetCurrentPath, .file_path = str8_chop_last_slash(current_path)); + } rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_PickFilePath].string, .pick_folder = browse_folder); } } From 91e1b1fda233f6c6548517e11dc4d4f3d297ceb3 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 25 Aug 2026 18:09:51 -0700 Subject: [PATCH 101/251] adjust lister acceptance rule when picking paths for things that might not exist - do not always use the first item in such cases --- project.raddbg | 2 +- src/raddbg/generated/raddbg.meta.c | 6 +++--- src/raddbg/generated/raddbg.meta.h | 6 +++--- src/raddbg/raddbg.mdesk | 8 ++++---- src/raddbg/raddbg_core.c | 15 ++++++++------- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/project.raddbg b/project.raddbg index e1eae2e2..39492b35 100755 --- a/project.raddbg +++ b/project.raddbg @@ -16,7 +16,7 @@ target: target: { executable: "build/mule_main.exe" - working_directory: "build/" + working_directory: build output_label: output_a enabled: 1 } diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 516a7fb9..8889465f 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -467,7 +467,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("color"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @display_name(\"Value\") @description(\"An expression to describe the value or location of the color.\")\n 'expression': expr_string,\n}\n")}, {str8_lit_comp("geo3d"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @display_name(\"Expression\") @description(\"An expression to describe the base address of the index buffer.\")\n 'expression': expr_string,\n 'count': expr_string,\n 'vtx': expr_string,\n 'vtx_size': expr_string,\n 'yaw': @range[0, 1] f32,\n 'pitch': @range[-0.5, 0] f32,\n 'zoom': @range[0, 100] f32,\n}\n")}, {str8_lit_comp("getting_started"), 0, str8_lit_comp("@inherit(tab) x:\n{\n}\n")}, -{str8_lit_comp("target"), 0, str8_lit_comp("@row_commands(@cmd_line save_cfg_to_project, enable_cfg, launch_and_run, launch_and_step_into, duplicate_cfg, remove_cfg)\n@collection_commands(add_target)\nx:\n{\n 'label': code_string,\n 'executable': path,\n 'arguments': string,\n 'working_directory': @folder path,\n 'entry_point': expr_string,\n @default(\"output\") 'output_label': code_string,\n 'stdout_path': @no_relativize path,\n 'stderr_path': @no_relativize path,\n 'stdin_path': @no_relativize path,\n 'environment': set,\n 'debug_subprocesses': bool,\n @no_revert @no_expand @default(0) 'enabled': bool,\n}\n")}, +{str8_lit_comp("target"), 0, str8_lit_comp("@row_commands(@cmd_line save_cfg_to_project, enable_cfg, launch_and_run, launch_and_step_into, duplicate_cfg, remove_cfg)\n@collection_commands(add_target)\nx:\n{\n 'label': code_string,\n 'executable': path,\n 'arguments': string,\n 'working_directory': @folder path,\n 'entry_point': expr_string,\n @default(\"output\") 'output_label': code_string,\n 'stdout_path': @can_not_exist @no_relativize path,\n 'stderr_path': @can_not_exist @no_relativize path,\n 'stdin_path': @can_not_exist @no_relativize path,\n 'environment': set,\n 'debug_subprocesses': bool,\n @no_revert @no_expand @default(0) 'enabled': bool,\n}\n")}, {str8_lit_comp("breakpoint"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(toggle_breakpoint, add_breakpoint, add_address_breakpoint, add_function_breakpoint, clear_breakpoints)\nx:\n{\n 'label': code_string,\n 'condition': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n 'hit_count': u64,\n 'address_range_size': @or(0, 1, 2, 4, 8) u64,\n 'break_on_write': bool,\n 'break_on_read': bool,\n 'break_on_execute': bool,\n @no_revert @no_expand @default(1) 'enabled': bool,\n}\n")}, {str8_lit_comp("watch_pin"), 0, str8_lit_comp("@row_commands(duplicate_cfg, remove_cfg)\n@collection_commands(add_watch_pin, toggle_watch_pin)\nx:\n{\n 'expression': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n}\n")}, {str8_lit_comp("debug_info"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(load_debug_info)\nx:\n{\n 'path': @no_relativize path,\n @no_revert @no_expand @default(1) 'enabled': bool,\n @query 'guid': string,\n}\n")}, @@ -533,7 +533,7 @@ str8_lit_comp("activate_with_single_click"), str8_lit_comp("disable_addresses"), str8_lit_comp("small_size"), str8_lit_comp("auto_unwind"), -str8_lit_comp("pick_folder"), +str8_lit_comp("create_new"), str8_lit_comp("dir2"), str8_lit_comp("string"), str8_lit_comp("cmd_name"), @@ -593,7 +593,7 @@ Rng1U64 rd_reg_slot_range_table[56] = {OffsetOf(RD_Regs, disable_addresses), OffsetOf(RD_Regs, disable_addresses) + sizeof(B32)}, {OffsetOf(RD_Regs, small_size), OffsetOf(RD_Regs, small_size) + sizeof(B32)}, {OffsetOf(RD_Regs, auto_unwind), OffsetOf(RD_Regs, auto_unwind) + sizeof(B32)}, -{OffsetOf(RD_Regs, pick_folder), OffsetOf(RD_Regs, pick_folder) + sizeof(B32)}, +{OffsetOf(RD_Regs, create_new), OffsetOf(RD_Regs, create_new) + sizeof(B32)}, {OffsetOf(RD_Regs, dir2), OffsetOf(RD_Regs, dir2) + sizeof(Dir2)}, {OffsetOf(RD_Regs, string), OffsetOf(RD_Regs, string) + sizeof(String8)}, {OffsetOf(RD_Regs, cmd_name), OffsetOf(RD_Regs, cmd_name) + sizeof(String8)}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 51969988..d9c04881 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -59,7 +59,7 @@ RD_RegSlot_ActivateWithSingleClick, RD_RegSlot_DisableAddresses, RD_RegSlot_SmallSize, RD_RegSlot_AutoUnwind, -RD_RegSlot_PickFolder, +RD_RegSlot_CreateNew, RD_RegSlot_Dir2, RD_RegSlot_String, RD_RegSlot_CmdName, @@ -517,7 +517,7 @@ B32 activate_with_single_click; B32 disable_addresses; B32 small_size; B32 auto_unwind; -B32 pick_folder; +B32 create_new; Dir2 dir2; String8 string; String8 cmd_name; @@ -622,7 +622,7 @@ Z(getting_started)\ .disable_addresses = rd_regs()->disable_addresses,\ .small_size = rd_regs()->small_size,\ .auto_unwind = rd_regs()->auto_unwind,\ -.pick_folder = rd_regs()->pick_folder,\ +.create_new = rd_regs()->create_new,\ .dir2 = rd_regs()->dir2,\ .string = rd_regs()->string,\ .cmd_name = rd_regs()->cmd_name,\ diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 94933829..3edba731 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -725,9 +725,9 @@ RD_VocabTable: 'working_directory': @folder path, 'entry_point': expr_string, @default("output") 'output_label': code_string, - 'stdout_path': @no_relativize path, - 'stderr_path': @no_relativize path, - 'stdin_path': @no_relativize path, + 'stdout_path': @can_not_exist @no_relativize path, + 'stderr_path': @can_not_exist @no_relativize path, + 'stdin_path': @can_not_exist @no_relativize path, 'environment': set, 'debug_subprocesses': bool, @no_revert @no_expand @default(0) 'enabled': bool, @@ -913,7 +913,7 @@ RD_RegTable: {B32 disable_addresses DisableAddresses } {B32 small_size SmallSize } {B32 auto_unwind AutoUnwind } - {B32 pick_folder PickFolder } + {B32 create_new CreateNew } {Dir2 dir2 Dir2 } {String8 string String } {String8 cmd_name CmdName } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 53bfa415..e8bb75e3 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2376,8 +2376,8 @@ rd_view_ui(Rng2F32 rect) row = ev_row_from_num(scratch.arena, eval_view, &block_ranges, selection_tbl.min.y); } - // rjf: default selection, and we're picking folder? -> pick currently browsed folder - if(cfg_node_child_from_string(view, s("pick_folder")) != &cfg_nil_node && default_selection) + // rjf: default selection, and we're making new item? -> pick currently browsed string + if(cfg_node_child_from_string(view, s("create_new")) != &cfg_nil_node && default_selection) { rd_cmd(RD_CmdKind_CompleteQuery, .file_path = current_input); } @@ -4016,6 +4016,7 @@ rd_view_ui(Rng2F32 rect) B32 revert_cell = 0; B32 browse_cell = 0; B32 browse_folder = 0; + B32 path_can_not_exist = 0; UI_Signal sig = {0}; ProfScope("build cell contents") UI_Parent(cell_box) @@ -4212,7 +4213,7 @@ rd_view_ui(Rng2F32 rect) { cell_params.flags |= RD_CellFlag_BrowseButton; cell_params.browse_out = &browse_cell; - browse_folder = md_node_has_tag(schema_child->first, s("folder"), 0); + path_can_not_exist = (md_node_has_tag(schema_child->first, s("folder"), 0) || md_node_has_tag(schema_child->first, s("can_not_exist"), 0)); } break; } @@ -4619,7 +4620,7 @@ rd_view_ui(Rng2F32 rect) { rd_cmd(RD_CmdKind_SetCurrentPath, .file_path = str8_chop_last_slash(current_path)); } - rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_PickFilePath].string, .pick_folder = browse_folder); + rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_PickFilePath].string, .create_new = path_can_not_exist); } } } @@ -16579,13 +16580,13 @@ rd_frame(void) { cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("prefer_new_tab")); } - if(!rd_regs()->pick_folder) + if(!rd_regs()->create_new) { - cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, s("pick_folder"))); + cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, s("create_new"))); } else { - cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("pick_folder")); + cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("create_new")); } } From eb97f797efcfdcb298cde37e1a3978c6c8f20362 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 09:54:28 -0700 Subject: [PATCH 102/251] adjust window query state to be a stack rather than just a single selected query; simplify default panel layout, remove duplicative alternatives; fix bad scope idxs with unbalanced scope pts --- project.raddbg | 3 +- src/mule/mule_main.cpp | 2 + src/raddbg/generated/raddbg.meta.c | 10 +- src/raddbg/generated/raddbg.meta.h | 5 +- src/raddbg/raddbg.mdesk | 3 +- src/raddbg/raddbg_core.c | 501 ++++++++++++----------------- src/raddbg/raddbg_core.h | 18 +- src/raddbg/raddbg_views.c | 12 + src/raddbg/raddbg_views.h | 1 + src/raddbg/raddbg_widgets.c | 5 + src/text/text.c | 19 +- src/text/text.h | 2 +- 12 files changed, 252 insertions(+), 329 deletions(-) diff --git a/project.raddbg b/project.raddbg index 39492b35..e1df3c4e 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -17,8 +18,6 @@ target: { executable: "build/mule_main.exe" working_directory: build - output_label: output_a - enabled: 1 } target: { diff --git a/src/mule/mule_main.cpp b/src/mule/mule_main.cpp index 2d8e933c..f5b92f00 100644 --- a/src/mule/mule_main.cpp +++ b/src/mule/mule_main.cpp @@ -459,6 +459,8 @@ type_coverage_eval_tests(void) const char const_string_array[] = "Hello, World!"; const char *const const_ptr_const_string = "Hello, World!"; + char *test = ")"; + MyByte *non_string_byte_ptr = "Hello, World!"; void *pointer = &basics; diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 8889465f..e2c319d9 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -62,7 +62,7 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[371] = +RD_VocabInfo rd_vocab_info_table[370] = { {str8_lit_comp("type_view"), str8_lit_comp("type_views"), str8_lit_comp("Type View"), str8_lit_comp("Type Views"), RD_IconKind_Binoculars}, {str8_lit_comp("file_path_map"), str8_lit_comp("file_path_maps"), str8_lit_comp("File Path Map"), str8_lit_comp("File Path Maps"), RD_IconKind_FileOutline}, @@ -241,8 +241,6 @@ RD_VocabInfo rd_vocab_info_table[371] = {str8_lit_comp("popup_cancel"), str8_lit_comp(""), str8_lit_comp("Popup Cancel"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("reset_to_default_bindings"), str8_lit_comp(""), str8_lit_comp("Reset To Default Bindings"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("reset_to_default_panels"), str8_lit_comp(""), str8_lit_comp("Reset To Default Panel Layout"), str8_lit_comp(""), RD_IconKind_Window}, -{str8_lit_comp("reset_to_compact_panels"), str8_lit_comp(""), str8_lit_comp("Reset To Compact Panel Layout"), str8_lit_comp(""), RD_IconKind_Window}, -{str8_lit_comp("reset_to_simple_panels"), str8_lit_comp(""), str8_lit_comp("Reset To Simple Panel Layout"), str8_lit_comp(""), RD_IconKind_Window}, {str8_lit_comp("new_panel_left"), str8_lit_comp(""), str8_lit_comp("Split Panel Left"), str8_lit_comp(""), RD_IconKind_XSplit}, {str8_lit_comp("new_panel_up"), str8_lit_comp(""), str8_lit_comp("Split Panel Up"), str8_lit_comp(""), RD_IconKind_YSplit}, {str8_lit_comp("new_panel_right"), str8_lit_comp(""), str8_lit_comp("Split Panel Right"), str8_lit_comp(""), RD_IconKind_XSplit}, @@ -405,6 +403,7 @@ RD_VocabInfo rd_vocab_info_table[371] = {str8_lit_comp("push_query"), str8_lit_comp(""), str8_lit_comp("Push Query"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("complete_query"), str8_lit_comp(""), str8_lit_comp("Complete Query"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("cancel_query"), str8_lit_comp(""), str8_lit_comp("Cancel Query"), str8_lit_comp(""), RD_IconKind_Null}, +{str8_lit_comp("cancel_all_queries"), str8_lit_comp(""), str8_lit_comp("Cancel All Queries"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("update_query"), str8_lit_comp(""), str8_lit_comp("Update Query"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("open_event_buffer"), str8_lit_comp(""), str8_lit_comp("Open Event Buffer"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("close_event_buffer"), str8_lit_comp(""), str8_lit_comp("Close Event Buffer"), str8_lit_comp(""), RD_IconKind_Null}, @@ -600,7 +599,7 @@ Rng1U64 rd_reg_slot_range_table[56] = {OffsetOf(RD_Regs, wm_event), OffsetOf(RD_Regs, wm_event) + sizeof(WM_Event *)}, }; -RD_CmdKindInfo rd_cmd_kind_info_table[258] = +RD_CmdKindInfo rd_cmd_kind_info_table[257] = { {0}, { str8_lit_comp("launch_and_run"), str8_lit_comp("Starts debugging a new instance of a target, then runs."), str8_lit_comp("launch,start,run,target"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:targets")}}, @@ -666,8 +665,6 @@ RD_CmdKindInfo rd_cmd_kind_info_table[258] = { str8_lit_comp("popup_cancel"), str8_lit_comp("Cancels the active popup prompt."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("reset_to_default_bindings"), str8_lit_comp("Resets all keybindings to their defaults."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("reset_to_default_panels"), str8_lit_comp("Resets the window to the default panel layout."), str8_lit_comp("panel"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, -{ str8_lit_comp("reset_to_compact_panels"), str8_lit_comp("Resets the window to the compact panel layout."), str8_lit_comp("panel"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, -{ str8_lit_comp("reset_to_simple_panels"), str8_lit_comp("Resets the window to the simple panel layout."), str8_lit_comp("panel"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("new_panel_left"), str8_lit_comp("Creates a new panel to the left of the active panel."), str8_lit_comp("panel"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("new_panel_up"), str8_lit_comp("Creates a new panel at the top of the active panel."), str8_lit_comp("panel"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("new_panel_right"), str8_lit_comp("Creates a new panel to the right of the active panel."), str8_lit_comp("panel"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, @@ -830,6 +827,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[258] = { str8_lit_comp("push_query"), str8_lit_comp("Opens a new temporary query interface."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("complete_query"), str8_lit_comp("Completes and closes a query."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("cancel_query"), str8_lit_comp("Closes a query."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("cancel_all_queries"), str8_lit_comp("Closes all queries."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("update_query"), str8_lit_comp("Updates a query input."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("open_event_buffer"), str8_lit_comp("Opens a new event buffer, to which debugger events will be written, for external processing."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("close_event_buffer"), str8_lit_comp("Closes an existing event buffer."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Cfg, str8_lit_comp("")}}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index d9c04881..8ce32f7e 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -133,8 +133,6 @@ RD_CmdKind_PopupAccept, RD_CmdKind_PopupCancel, RD_CmdKind_ResetToDefaultBindings, RD_CmdKind_ResetToDefaultPanels, -RD_CmdKind_ResetToCompactPanels, -RD_CmdKind_ResetToSimplePanels, RD_CmdKind_NewPanelLeft, RD_CmdKind_NewPanelUp, RD_CmdKind_NewPanelRight, @@ -297,6 +295,7 @@ RD_CmdKind_PickFilePath, RD_CmdKind_PushQuery, RD_CmdKind_CompleteQuery, RD_CmdKind_CancelQuery, +RD_CmdKind_CancelAllQueries, RD_CmdKind_UpdateQuery, RD_CmdKind_OpenEventBuffer, RD_CmdKind_CloseEventBuffer, @@ -631,7 +630,7 @@ Z(getting_started)\ C_LINKAGE_BEGIN extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; -extern RD_VocabInfo rd_vocab_info_table[371]; +extern RD_VocabInfo rd_vocab_info_table[370]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; extern String8 rd_reg_slot_code_name_table[56]; extern Rng1U64 rd_reg_slot_range_table[56]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 3edba731..19d696b9 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1007,8 +1007,6 @@ RD_CmdTable: // | | | | //- rjf: panel splitting {ResetToDefaultPanels 0 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_default_panels" "Reset To Default Panel Layout" "Resets the window to the default panel layout." "panel" "" } - {ResetToCompactPanels 0 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_compact_panels" "Reset To Compact Panel Layout" "Resets the window to the compact panel layout." "panel" "" } - {ResetToSimplePanels 0 1 1 "" Null 0 0 0 0 0 0 0 Window "reset_to_simple_panels" "Reset To Simple Panel Layout" "Resets the window to the simple panel layout." "panel" "" } {NewPanelLeft 0 1 1 "" Null 0 0 0 0 0 0 0 XSplit "new_panel_left" "Split Panel Left" "Creates a new panel to the left of the active panel." "panel" "" } {NewPanelUp 0 1 1 "" Null 0 0 0 0 0 0 0 YSplit "new_panel_up" "Split Panel Up" "Creates a new panel at the top of the active panel." "panel" "" } {NewPanelRight 0 1 1 "" Null 0 0 0 0 0 0 0 XSplit "new_panel_right" "Split Panel Right" "Creates a new panel to the right of the active panel." "panel" "" } @@ -1243,6 +1241,7 @@ RD_CmdTable: // | | | | {PushQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "push_query" "Push Query" "Opens a new temporary query interface." "" "" } {CompleteQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "complete_query" "Complete Query" "Completes and closes a query." "" "" } {CancelQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "cancel_query" "Cancel Query" "Closes a query." "" "" } + {CancelAllQueries 0 0 0 "" Null 0 0 0 0 0 0 0 Null "cancel_all_queries" "Cancel All Queries" "Closes all queries." "" "" } {UpdateQuery 0 0 0 "" Null 0 0 0 0 0 0 0 Null "update_query" "Update Query" "Updates a query input." "" "" } //- rjf: event buffers diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index e8bb75e3..971abb59 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2379,6 +2379,7 @@ rd_view_ui(Rng2F32 rect) // rjf: default selection, and we're making new item? -> pick currently browsed string if(cfg_node_child_from_string(view, s("create_new")) != &cfg_nil_node && default_selection) { + taken = 1; rd_cmd(RD_CmdKind_CompleteQuery, .file_path = current_input); } @@ -2535,6 +2536,7 @@ rd_view_ui(Rng2F32 rect) } else { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = e_string_from_expr(scratch.arena, eval.expr, str8_zero())); } }break; @@ -2553,6 +2555,7 @@ rd_view_ui(Rng2F32 rect) } else if(e_type_kind_from_key(e_type_key_unwrap(eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative)) == E_TypeKind_Set) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = e_full_expr_string_from_key(scratch.arena, eval.key)); } else @@ -2562,6 +2565,7 @@ rd_view_ui(Rng2F32 rect) }break; case RD_EvalSpaceKind_MetaQuery: { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = e_full_expr_string_from_key(scratch.arena, eval.key)); }break; case RD_EvalSpaceKind_MetaUnattachedProcess: @@ -2588,6 +2592,7 @@ rd_view_ui(Rng2F32 rect) case RD_EvalSpaceKind_MetaCtrlEntity: { D_Entity *entity = rd_ctrl_entity_from_eval_space(eval.space); + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = push_str8f(scratch.arena, "query:control.%S", d_string_from_handle(scratch.arena, entity->handle))); }break; } @@ -4726,6 +4731,7 @@ rd_view_ui(Rng2F32 rect) D_Entity *entity = rd_ctrl_entity_from_eval_space(cell->eval.space); if(cell->eval.space.kind == D_EvalSpaceKind_Entity) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = s("query:memory_eval_commands"), .do_implicit_root = 1, @@ -4736,6 +4742,7 @@ rd_view_ui(Rng2F32 rect) } else if(cell->eval.space.kind == RD_EvalSpaceKind_MetaCtrlEntity && entity->kind == D_EntityKind_Module) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = s("query:module_commands"), .do_implicit_root = 1, @@ -4977,6 +4984,7 @@ rd_view_ui(Rng2F32 rect) // rjf: other cases, but this watch window is floating, and this has a cfg/entity? -> push query else if(view_is_floating && (cell_info.entity != &d_entity_nil || cell_info.cfg != &cfg_nil_node)) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = e_full_expr_string_from_key(scratch.arena, cell->eval.key)); } @@ -5644,7 +5652,7 @@ rd_window_frame(void) CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, window); B32 window_is_focused = (wm_window_is_focused(ws->os) || ws->window_temporarily_focused_ipc); B32 popup_is_open = (rd_state->popup_active); - B32 query_is_open = (ws->query_is_active); + B32 query_is_open = (ws->query_top != 0); U64 hover_eval_open_delay_us = 400000; B32 hover_eval_is_open = (!popup_is_open && !query_is_open && @@ -6671,6 +6679,7 @@ rd_window_frame(void) B32 force_inside_window_y; B32 only_secondary_navigation; B32 reset_open; + B32 disable_open_animation; UI_Signal signal; // NOTE(rjf): output, from build B32 pressed; B32 pressed_outside; @@ -6859,71 +6868,103 @@ rd_window_frame(void) } } - //- rjf: force-close query, if it's anchored, but box is gone - if(query_is_open) + //- rjf: force-close queries if: + // + // - query is anchored, but box is gone + // - query has expression, but that expression does not evaluate + // + RD_QueryView *top_query = ws->query_top; { - UI_Box *box = ui_box_from_key(ws->query_regs->ui_key); - if(!ui_key_match(ui_key_zero(), ws->query_regs->ui_key) && ui_box_is_nil(box)) + U64 remove_q_count = 0; + U64 q_count = 0; + for(RD_QueryView *q = top_query; q != 0; q = q->next, q_count += 1) + { + // rjf: close if expression is busted + { + String8 expr = q->regs->expr; + E_Eval eval = e_eval_from_string(expr); + if(eval.msgs.max_kind > E_MsgKind_Null) + { + top_query = q->next; + remove_q_count = q_count; + } + else if(eval.space.kind == RD_EvalSpaceKind_MetaCfg && + rd_cfg_from_eval_space(eval.space) == &cfg_nil_node) + { + remove_q_count = q_count; + } + else if((eval.space.kind == RD_EvalSpaceKind_MetaCtrlEntity || + eval.space.kind == D_EvalSpaceKind_Entity) && + rd_ctrl_entity_from_eval_space(eval.space) == &d_entity_nil) + { + remove_q_count = q_count; + } + } + + // rjf: close if anchor box is gone + { + UI_Box *box = ui_box_from_key(q->regs->ui_key); + if(!ui_key_match(ui_key_zero(), q->regs->ui_key) && ui_box_is_nil(box)) + { + top_query = q->next; + remove_q_count = q_count; + } + } + } + for EachIndex(idx, remove_q_count) { - query_is_open = 0; rd_cmd(RD_CmdKind_CancelQuery); } } + query_is_open = (top_query != 0); - //- rjf: force-close query, if it has an expression, but that expression does not evaluate - if(query_is_open) + //- rjf: touch immediate cfgs for all active queries + for(RD_QueryView *qv = top_query; qv != 0; qv = qv->next) { - String8 expr = ws->query_regs->expr; - E_Eval eval = e_eval_from_string(expr); - if(eval.msgs.max_kind > E_MsgKind_Null) - { - query_is_open = 0; - rd_cmd(RD_CmdKind_CancelQuery); - } - else if(eval.space.kind == RD_EvalSpaceKind_MetaCfg && - rd_cfg_from_eval_space(eval.space) == &cfg_nil_node) - { - query_is_open = 0; - rd_cmd(RD_CmdKind_CancelQuery); - } - else if((eval.space.kind == RD_EvalSpaceKind_MetaCtrlEntity || - eval.space.kind == D_EvalSpaceKind_Entity) && - rd_ctrl_entity_from_eval_space(eval.space) == &d_entity_nil) - { - query_is_open = 0; - rd_cmd(RD_CmdKind_CancelQuery); - } + rd_immediate_cfg_from_keyf("window_query_%p_%I64u", window, qv->q_arena_pos); } //- rjf: try to add opened query if(query_is_open) { // rjf: unpack view for query - CFG_Node *root = rd_immediate_cfg_from_keyf("window_query_%p", window); - CFG_Node *view = cfg_node_child_from_string_or_alloc(rd_state->cfg, root, str8_lit("watch")); - CFG_Node *query = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("query")); - B32 is_lister = (cfg_node_child_from_string(view, str8_lit("lister")) != &cfg_nil_node); - B32 is_small = (cfg_node_child_from_string(view, str8_lit("small")) != &cfg_nil_node); - B32 root_is_explicit = (cfg_node_child_from_string(view, str8_lit("explicit_root")) != &cfg_nil_node); + RD_Regs *query_regs = top_query->regs; + CFG_Node *root = rd_immediate_cfg_from_keyf("window_query_%p_%I64u", window, top_query->q_arena_pos); + CFG_Node *view = cfg_node_child_from_string_or_alloc(rd_state->cfg, root, s("watch")); + CFG_Node *query = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("query")); + B32 is_lister = (cfg_node_child_from_string(view, s("lister")) != &cfg_nil_node); + B32 is_small = (cfg_node_child_from_string(view, s("small")) != &cfg_nil_node); + B32 root_is_explicit = (cfg_node_child_from_string(view, s("explicit_root")) != &cfg_nil_node); RD_ViewState *vs = rd_view_state_from_cfg(view); - // rjf: did this view ID change? -> reset open animation - B32 reset_open = 0; - if(view->id != ws->query_last_view_id) + // rjf: if this is not the bottom of the query stack, never animate + B32 disable_open_animation = 0; + if(top_query->next != 0) { - ws->query_last_view_id = view->id; + disable_open_animation = 1; + } + + // rjf: did this view ID change? -> reset open animation (for bottom-of-stack queries) + B32 reset_open = 0; + if(top_query->next == 0 && view->id != ws->query_last_bottom_view_id) + { + ws->query_last_bottom_view_id = view->id; reset_open = 1; } + else if(top_query->next == 0 && view->id == ws->query_last_bottom_view_id) + { + disable_open_animation = 1; + } // rjf: unpack query info - String8 cmd_name = ws->query_regs->cmd_name; + String8 cmd_name = query_regs->cmd_name; RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name); - String8 query_expr = ws->query_regs->expr; + String8 query_expr = query_regs->expr; if(query_expr.size == 0 && cmd_name.size != 0) { query_expr = cmd_kind_info->query.expr; } - B32 query_is_anchored = (!ui_box_is_nil(ui_box_from_key(ws->query_regs->ui_key))); + B32 query_is_anchored = (!ui_box_is_nil(ui_box_from_key(query_regs->ui_key))); B32 size_query_by_expr_eval = (query_is_anchored || query_expr.size == 0); // rjf: compute query expression @@ -6955,7 +6996,7 @@ rd_window_frame(void) E_Eval query_eval = e_eval_from_string(query_expr); // rjf: determine & store row-height setting - if(ws->query_regs->do_big_rows) + if(query_regs->do_big_rows) { F32 row_height = 5.f; F32 row_height_px = row_height * ui_top_font_size(); @@ -6979,7 +7020,7 @@ rd_window_frame(void) query_width_px = floor_f32(content_rect_dim.x * 0.15f); max_query_height_px = content_rect_dim.y*0.3f; } - if(!ui_key_match(ui_key_zero(), ws->query_regs->ui_key)) + if(!ui_key_match(ui_key_zero(), query_regs->ui_key)) { query_width_px = is_small ? (ui_top_font_size()*40.f) : (ui_top_font_size()*60.f); max_query_height_px = is_small ? (ui_top_font_size()*40.f) : (ui_top_font_size()*80.f); @@ -6999,13 +7040,13 @@ rd_window_frame(void) content_rect_center.y - max_query_height_px/2.f, content_rect_center.x + query_width_px/2, content_rect_center.y - max_query_height_px/2.f + query_height_px); - if(!ui_key_match(ui_key_zero(), ws->query_regs->ui_key)) + if(!ui_key_match(ui_key_zero(), query_regs->ui_key)) { - UI_Box *anchor_box = ui_box_from_key(ws->query_regs->ui_key); + UI_Box *anchor_box = ui_box_from_key(query_regs->ui_key); if(anchor_box != &ui_nil_box) { - rect.x0 = anchor_box->rect.x0 + ws->query_regs->off_px.x; - rect.y0 = anchor_box->rect.y1 + ws->query_regs->off_px.y; + rect.x0 = anchor_box->rect.x0 + query_regs->off_px.x; + rect.y0 = anchor_box->rect.y1 + query_regs->off_px.y; rect.x1 = rect.x0 + query_width_px; rect.y1 = rect.y0 + query_height_px; } @@ -7018,11 +7059,12 @@ rd_window_frame(void) SLLQueuePush(first_floating_view_task, last_floating_view_task, t); query_floating_view_task = t; t->view = view; - t->regs = ws->query_regs; + t->regs = query_regs; t->rect = rect; t->is_focused = 1; t->is_anchored = query_is_anchored; t->reset_open = reset_open; + t->disable_open_animation = disable_open_animation; t->force_inside_window_x = 1; t->force_inside_window_y = 1; } @@ -7051,7 +7093,7 @@ rd_window_frame(void) F32 open_t = ui_anim(ui_key_from_stringf(ui_key_zero(), "floating_view_open_%p", view), 1.f, .rate = is_anchored ? fast_open_rate : slow_open_rate, .reset = t->reset_open, - .initial = 0.f); + .initial = t->disable_open_animation ? 1.f : 0.f); // rjf: force rect inside window if needed if(t->force_inside_window_x || t->force_inside_window_y) @@ -7266,7 +7308,7 @@ rd_window_frame(void) { CFG_Node *view = query_floating_view_task->view; RD_ViewState *vs = rd_view_state_from_cfg(query_floating_view_task->view); - String8 cmd_name = ws->query_regs->cmd_name; + String8 cmd_name = query_floating_view_task->regs->cmd_name; RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name); // rjf: close queries @@ -7381,7 +7423,7 @@ rd_window_frame(void) 0,//- 'x', }; - Assert(ArrayCount(codepoints) == ArrayCount(cmds)); + StaticAssert(ArrayCount(codepoints) == ArrayCount(cmds), menu_button_check); rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints); } @@ -7405,7 +7447,7 @@ rd_window_frame(void) 0,//- 's', }; - Assert(ArrayCount(codepoints) == ArrayCount(cmds)); + StaticAssert(ArrayCount(codepoints) == ArrayCount(cmds), menu_button_check); rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints); } @@ -7428,8 +7470,6 @@ rd_window_frame(void) rd_cmd_kind_info_table[RD_CmdKind_TabBarBottom].string, {0},//- rd_cmd_kind_info_table[RD_CmdKind_ResetToDefaultPanels].string, - rd_cmd_kind_info_table[RD_CmdKind_ResetToCompactPanels].string, - rd_cmd_kind_info_table[RD_CmdKind_ResetToSimplePanels].string, }; U32 codepoints[] = { @@ -7446,10 +7486,8 @@ rd_window_frame(void) 0, 0,//- 0, - 0, - 0, }; - Assert(ArrayCount(codepoints) == ArrayCount(cmds)); + StaticAssert(ArrayCount(codepoints) == ArrayCount(cmds), menu_button_check); rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints); } @@ -7485,7 +7523,7 @@ rd_window_frame(void) 0,//- 's', }; - Assert(ArrayCount(codepoints) == ArrayCount(cmds)); + StaticAssert(ArrayCount(codepoints) == ArrayCount(cmds), menu_button_check); rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints); } @@ -7506,7 +7544,7 @@ rd_window_frame(void) 'r', 's', }; - Assert(ArrayCount(codepoints) == ArrayCount(cmds)); + StaticAssert(ArrayCount(codepoints) == ArrayCount(cmds), menu_button_check); rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints); scratch_end(scratch); } @@ -7543,7 +7581,7 @@ rd_window_frame(void) 'a', 'd', }; - Assert(ArrayCount(codepoints) == ArrayCount(cmds)); + StaticAssert(ArrayCount(codepoints) == ArrayCount(cmds), menu_button_check); rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints); } @@ -7738,13 +7776,13 @@ rd_window_frame(void) }; CenterButtonTask center_button_tasks[] = { - {rd_cmd_kind_info_table[RD_CmdKind_Run].string, str8_lit("good"), (can_send_signal || d_ctrl_last_run_frame_idx()+4 > d_frame_index())}, - {rd_cmd_kind_info_table[RD_CmdKind_Restart].string, str8_lit("neutral"), processes.count != 0}, - {rd_cmd_kind_info_table[RD_CmdKind_Halt].string, str8_lit("weak"), !can_send_signal}, - {rd_cmd_kind_info_table[RD_CmdKind_KillAll].string, str8_lit("bad"), processes.count != 0}, - {rd_cmd_kind_info_table[RD_CmdKind_StepOver].string, str8_lit("weak"), can_send_signal}, - {rd_cmd_kind_info_table[RD_CmdKind_StepInto].string, str8_lit("weak"), can_send_signal}, - {rd_cmd_kind_info_table[RD_CmdKind_StepOut].string, str8_lit("weak"), processes.count != 0 && can_send_signal}, + {rd_cmd_kind_info_table[RD_CmdKind_Run].string, s("good"), (can_send_signal || d_ctrl_last_run_frame_idx()+4 > d_frame_index())}, + {rd_cmd_kind_info_table[RD_CmdKind_Restart].string, s("neutral"), processes.count != 0}, + {rd_cmd_kind_info_table[RD_CmdKind_Halt].string, s("weak"), !can_send_signal}, + {rd_cmd_kind_info_table[RD_CmdKind_KillAll].string, s("bad"), processes.count != 0}, + {rd_cmd_kind_info_table[RD_CmdKind_StepOver].string, s(""), can_send_signal}, + {rd_cmd_kind_info_table[RD_CmdKind_StepInto].string, s(""), can_send_signal}, + {rd_cmd_kind_info_table[RD_CmdKind_StepOut].string, s(""), processes.count != 0 && can_send_signal}, }; UI_TextAlignment(UI_TextAlign_Center) for EachElement(idx, center_button_tasks) @@ -9021,12 +9059,8 @@ rd_window_frame(void) UI_Signal sig = ui_signal_from_box(edit_box); if(ui_pressed(sig)) { - if(ws->query_is_active && - ui_key_match(sig.box->key, ws->query_regs->ui_key)) - { - rd_cmd(RD_CmdKind_CancelQuery); - } - else + rd_cmd(RD_CmdKind_CancelAllQueries); + if(ws->query_top == 0 || !ui_key_match(sig.box->key, ws->query_top->regs->ui_key)) { rd_cmd(RD_CmdKind_PushQuery, .ui_key = sig.box->key, @@ -9073,6 +9107,7 @@ rd_window_frame(void) } else if(ui_right_clicked(sig)) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .ui_key = sig.box->key, .expr = push_str8f(scratch.arena, "query:config.$%I64x", tab->id)); @@ -9171,10 +9206,9 @@ rd_window_frame(void) if(ui_pressed(sig)) { rd_cmd(RD_CmdKind_FocusPanel, .panel = panel->cfg->id); - if(ws->query_is_active && - ui_key_match(add_new_box->key, ws->query_regs->ui_key)) + if(ws->query_top && ui_key_match(add_new_box->key, ws->query_top->regs->ui_key)) { - rd_cmd(RD_CmdKind_CancelQuery); + rd_cmd(RD_CmdKind_CancelAllQueries); } else { @@ -13538,12 +13572,14 @@ rd_frame(void) str8_list_pushf(scratch.arena, &exprs, "query:source_files"); } String8 expr = str8_list_join(scratch.arena, &exprs, &(StringJoin){.sep = str8_lit(", ")}); + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = expr, .do_implicit_root = 1, .do_lister = 1, .do_big_rows = 1, .view = tab->id, .tab = tab->id); }break; //- rjf: tab opening case RD_CmdKind_OpenTab: { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = s("query:tab_commands, query:source_files"), .do_implicit_root = 1, .do_lister = 1, .prefer_new_tab = 1); }break; @@ -13872,6 +13908,7 @@ rd_frame(void) case RD_CmdKind_WindowSettings: { String8 expr = push_str8f(scratch.arena, "query:config.$%I64x", rd_regs()->window); + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = expr, .do_implicit_root = 1, .do_big_rows = 1, .do_lister = 1); }break; case RD_CmdKind_CloseWindow: @@ -14064,16 +14101,7 @@ rd_frame(void) CFG_Node *size = cfg_node_new(rd_state->cfg, new_window, str8_lit("size")); cfg_node_newf(rd_state->cfg, size, "%f", window_dim.x); cfg_node_newf(rd_state->cfg, size, "%f", window_dim.y); - F32 line_height_guess = 11.f * (monitor_dpi / 96.f); - F32 num_lines_in_monitor_height = monitor_dim.y / line_height_guess; - if(num_lines_in_monitor_height < 100) - { - rd_cmd(RD_CmdKind_ResetToCompactPanels, .window = new_window->id); - } - else - { - rd_cmd(RD_CmdKind_ResetToDefaultPanels, .window = new_window->id); - } + rd_cmd(RD_CmdKind_ResetToDefaultPanels, .window = new_window->id); } } @@ -14270,10 +14298,12 @@ rd_frame(void) //- rjf: opening user/project settings case RD_CmdKind_UserSettings: { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = str8_lit("query:user_settings"), .do_implicit_root = 1, .do_big_rows = 1, .do_lister = 1); }break; case RD_CmdKind_ProjectSettings: { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = str8_lit("query:project_settings"), .do_implicit_root = 1, .do_big_rows = 1, .do_lister = 1); }break; @@ -15093,7 +15123,8 @@ rd_frame(void) }break; case RD_CmdKind_TabSettings: { - String8 expr = push_str8f(scratch.arena, "query:config.$%I64x", rd_regs()->tab); + String8 expr = str8f(scratch.arena, "query:config.$%I64x", rd_regs()->tab); + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = expr, .do_implicit_root = 1, .do_big_rows = 1, .do_lister = 1); }break; @@ -15191,8 +15222,6 @@ rd_frame(void) //- rjf: panel built-in layout builds case RD_CmdKind_ResetToDefaultPanels: - case RD_CmdKind_ResetToCompactPanels: - case RD_CmdKind_ResetToSimplePanels: { CFG_Node *window = cfg_node_from_id(rd_regs()->window); CFG_Node *panels = cfg_node_child_from_string(window, str8_lit("panels")); @@ -15276,154 +15305,39 @@ rd_frame(void) case RD_CmdKind_ResetToDefaultPanels: { // rjf: root split - cfg_node_child_from_string_or_alloc(rd_state->cfg, window, str8_lit("split_x")); - CFG_Node *root_0 = cfg_node_new(rd_state->cfg, panels, str8_lit("0.85")); - CFG_Node *root_1 = cfg_node_new(rd_state->cfg, panels, str8_lit("0.15")); + cfg_node_child_from_string_or_alloc(rd_state->cfg, window, s("split_x")); + CFG_Node *root_0 = cfg_node_new(rd_state->cfg, panels, s("0.70")); + CFG_Node *root_1 = cfg_node_new(rd_state->cfg, panels, s("0.30")); - // rjf: root_0 split - CFG_Node *root_0_0 = cfg_node_new(rd_state->cfg, root_0, str8_lit("0.80")); - CFG_Node *root_0_1 = cfg_node_new(rd_state->cfg, root_0, str8_lit("0.20")); + // rjf: root 0 gets 'getting started' and source + if(getting_started != &cfg_nil_node) + { + cfg_node_insert_child(rd_state->cfg, root_0, root_0->last, getting_started); + cfg_node_new(rd_state->cfg, getting_started, s("selected")); + } + else if(texts.first) + { + cfg_node_new(rd_state->cfg, texts.first->v, s("selected")); + } + for(CFG_NodePtrNode *n = texts.first; n != 0; n = n->next) + { + cfg_node_insert_child(rd_state->cfg, root_0, root_0->last, n->v); + } + cfg_node_new(rd_state->cfg, root_0, s("selected")); - // rjf: root_1 split - CFG_Node *root_1_0 = cfg_node_new(rd_state->cfg, root_1, str8_lit("0.50")); - CFG_Node *root_1_1 = cfg_node_new(rd_state->cfg, root_1, str8_lit("0.50")); + // rjf: root 1 split + CFG_Node *root_1_0 = cfg_node_new(rd_state->cfg, root_1, s("0.25")); + CFG_Node *root_1_1 = cfg_node_new(rd_state->cfg, root_1, s("0.50")); + CFG_Node *root_1_2 = cfg_node_new(rd_state->cfg, root_1, s("0.25")); cfg_node_insert_child(rd_state->cfg, root_1_0, root_1_0->last, targets); - cfg_node_insert_child(rd_state->cfg, root_1_1, root_1_1->last, threads); - cfg_node_insert_child(rd_state->cfg, root_1_1, root_1_1->last, processes); - cfg_node_insert_child(rd_state->cfg, root_1_1, root_1_1->last, machines); - cfg_node_new(rd_state->cfg, targets, str8_lit("selected")); - cfg_node_new(rd_state->cfg, threads, str8_lit("selected")); - - // rjf: root 0_0 split - CFG_Node *root_0_0_0 = cfg_node_new(rd_state->cfg, root_0_0, str8_lit("0.25")); - CFG_Node *root_0_0_1 = cfg_node_new(rd_state->cfg, root_0_0, str8_lit("0.75")); - - // rjf: root_0_0_0 split - CFG_Node *root_0_0_0_0 = cfg_node_new(rd_state->cfg, root_0_0_0, str8_lit("0.50")); - CFG_Node *root_0_0_0_1 = cfg_node_new(rd_state->cfg, root_0_0_0, str8_lit("0.50")); - cfg_node_insert_child(rd_state->cfg, root_0_0_0_0, root_0_0_0_0->last, disasm); - cfg_node_new(rd_state->cfg, disasm, str8_lit("selected")); - cfg_node_insert_child(rd_state->cfg, root_0_0_0_1, root_0_0_0_1->last, breakpoints); - cfg_node_insert_child(rd_state->cfg, root_0_0_0_1, root_0_0_0_1->last, watch_pins); - cfg_node_insert_child(rd_state->cfg, root_0_0_0_1, root_0_0_0_1->last, output); - cfg_node_insert_child(rd_state->cfg, root_0_0_0_1, root_0_0_0_1->last, memory); - cfg_node_new(rd_state->cfg, output, str8_lit("selected")); - - // rjf: root_0_1 split - CFG_Node *root_0_1_0 = cfg_node_new(rd_state->cfg, root_0_1, str8_lit("0.60")); - CFG_Node *root_0_1_1 = cfg_node_new(rd_state->cfg, root_0_1, str8_lit("0.40")); - cfg_node_insert_child(rd_state->cfg, root_0_1_0, root_0_1_0->last, watches); - cfg_node_insert_child(rd_state->cfg, root_0_1_0, root_0_1_0->last, locals); - cfg_node_insert_child(rd_state->cfg, root_0_1_0, root_0_1_0->last, registers); - cfg_node_insert_child(rd_state->cfg, root_0_1_0, root_0_1_0->last, globals); - cfg_node_insert_child(rd_state->cfg, root_0_1_0, root_0_1_0->last, thread_locals); - cfg_node_insert_child(rd_state->cfg, root_0_1_0, root_0_1_0->last, types); - cfg_node_insert_child(rd_state->cfg, root_0_1_0, root_0_1_0->last, procedures); - cfg_node_new(rd_state->cfg, watches, str8_lit("selected")); - cfg_node_new(rd_state->cfg, root_0_1_0, str8_lit("tabs_on_bottom")); - cfg_node_insert_child(rd_state->cfg, root_0_1_1, root_0_1_1->last, call_stack); - cfg_node_insert_child(rd_state->cfg, root_0_1_1, root_0_1_1->last, modules); - cfg_node_new(rd_state->cfg, call_stack, str8_lit("selected")); - cfg_node_new(rd_state->cfg, root_0_1_1, str8_lit("tabs_on_bottom")); - - // rjf: fill main panel with getting started, OR all collected code views - CFG_Node *main_panel = root_0_0_1; - if(getting_started != &cfg_nil_node) - { - cfg_node_insert_child(rd_state->cfg, main_panel, main_panel->last, getting_started); - cfg_node_new(rd_state->cfg, getting_started, str8_lit("selected")); - } - else if(texts.first) - { - cfg_node_new(rd_state->cfg, texts.first->v, str8_lit("selected")); - } - for(CFG_NodePtrNode *n = texts.first; n != 0; n = n->next) - { - cfg_node_insert_child(rd_state->cfg, main_panel, main_panel->last, n->v); - } - - // rjf: set main panel as selected - cfg_node_new(rd_state->cfg, main_panel, str8_lit("selected")); - }break; - - //- rjf: (compact layout) - case RD_CmdKind_ResetToCompactPanels: - { - // rjf: root split - cfg_node_child_from_string_or_alloc(rd_state->cfg, window, str8_lit("split_x")); - CFG_Node *root_0 = cfg_node_new(rd_state->cfg, panels, str8_lit("0.25")); - CFG_Node *root_1 = cfg_node_new(rd_state->cfg, panels, str8_lit("0.75")); - - // rjf: root_0 split - CFG_Node *root_0_0 = cfg_node_new(rd_state->cfg, root_0, str8_lit("0.25")); - CFG_Node *root_0_1 = cfg_node_new(rd_state->cfg, root_0, str8_lit("0.25")); - CFG_Node *root_0_2 = cfg_node_new(rd_state->cfg, root_0, str8_lit("0.25")); - CFG_Node *root_0_3 = cfg_node_new(rd_state->cfg, root_0, str8_lit("0.25")); - cfg_node_insert_child(rd_state->cfg, root_0_0, root_0_0->last, watches); - cfg_node_insert_child(rd_state->cfg, root_0_0, root_0_0->last, types); - cfg_node_new(rd_state->cfg, watches, str8_lit("selected")); - cfg_node_insert_child(rd_state->cfg, root_0_1, root_0_1->last, threads); - cfg_node_insert_child(rd_state->cfg, root_0_1, root_0_1->last, targets); - cfg_node_insert_child(rd_state->cfg, root_0_1, root_0_1->last, breakpoints); - cfg_node_insert_child(rd_state->cfg, root_0_1, root_0_1->last, watch_pins); - cfg_node_new(rd_state->cfg, threads, str8_lit("selected")); - cfg_node_insert_child(rd_state->cfg, root_0_2, root_0_2->last, disasm); - cfg_node_insert_child(rd_state->cfg, root_0_2, root_0_2->last, output); - cfg_node_new(rd_state->cfg, disasm, str8_lit("selected")); - cfg_node_insert_child(rd_state->cfg, root_0_3, root_0_3->last, call_stack); - cfg_node_insert_child(rd_state->cfg, root_0_3, root_0_3->last, modules); - cfg_node_new(rd_state->cfg, call_stack, str8_lit("selected")); - - // rjf: fill main panel with getting started, OR all collected code views - CFG_Node *main_panel = root_1; - if(getting_started != &cfg_nil_node) - { - cfg_node_insert_child(rd_state->cfg, main_panel, main_panel->last, getting_started); - cfg_node_new(rd_state->cfg, getting_started, str8_lit("selected")); - } - else if(texts.first) - { - cfg_node_new(rd_state->cfg, texts.first->v, str8_lit("selected")); - } - for(CFG_NodePtrNode *n = texts.first; n != 0; n = n->next) - { - cfg_node_insert_child(rd_state->cfg, main_panel, main_panel->last, n->v); - } - - // rjf: set main panel as selected - cfg_node_new(rd_state->cfg, main_panel, str8_lit("selected")); - }break; - - //- rjf: simple layout - case RD_CmdKind_ResetToSimplePanels: - { - // rjf: root split - cfg_node_child_from_string_or_alloc(rd_state->cfg, window, str8_lit("split_x")); - CFG_Node *root_0 = cfg_node_new(rd_state->cfg, panels, str8_lit("0.25")); - CFG_Node *root_1 = cfg_node_new(rd_state->cfg, panels, str8_lit("0.75")); - - // rjf: fill smaller panel with watch - cfg_node_insert_child(rd_state->cfg, root_0, root_0->last, watches); - cfg_node_new(rd_state->cfg, watches, str8_lit("selected")); - - // rjf: fill main panel with getting started, OR all collected code views - CFG_Node *main_panel = root_1; - if(getting_started != &cfg_nil_node) - { - cfg_node_insert_child(rd_state->cfg, main_panel, main_panel->last, getting_started); - cfg_node_new(rd_state->cfg, getting_started, str8_lit("selected")); - } - else if(texts.first) - { - cfg_node_new(rd_state->cfg, texts.first->v, str8_lit("selected")); - } - for(CFG_NodePtrNode *n = texts.first; n != 0; n = n->next) - { - cfg_node_insert_child(rd_state->cfg, main_panel, main_panel->last, n->v); - } - - // rjf: set main panel as selected - cfg_node_new(rd_state->cfg, main_panel, str8_lit("selected")); + cfg_node_insert_child(rd_state->cfg, root_1_1, root_1_1->last, watches); + cfg_node_insert_child(rd_state->cfg, root_1_1, root_1_1->last, output); + cfg_node_insert_child(rd_state->cfg, root_1_1, root_1_1->last, breakpoints); + cfg_node_insert_child(rd_state->cfg, root_1_2, root_1_2->last, call_stack); + cfg_node_insert_child(rd_state->cfg, root_1_2, root_1_2->last, threads); + cfg_node_new(rd_state->cfg, targets, s("selected")); + cfg_node_new(rd_state->cfg, watches, s("selected")); + cfg_node_new(rd_state->cfg, call_stack, s("selected")); }break; } @@ -16509,17 +16423,22 @@ rd_frame(void) RD_WindowState *ws = rd_window_state_from_cfg(window); if(ws != &rd_nil_window_state) { - ws->query_is_active = 1; - arena_clear(ws->query_arena); - ws->query_regs = rd_regs_copy(ws->query_arena, rd_regs()); + // rjf: push onto window's query stack + U64 pre_query_arena_pos = arena_pos(ws->query_arena); + RD_QueryView *query_view = push_array(ws->query_arena, RD_QueryView, 1); + SLLStackPush(ws->query_top, query_view); + query_view->q_arena_pos = pre_query_arena_pos; + query_view->regs = rd_regs_copy(ws->query_arena, rd_regs()); + + // rjf: initialize cfg tree for query view + CFG_Node *window_query = rd_immediate_cfg_from_keyf("window_query_%p_%I64u", window, query_view->q_arena_pos); + cfg_node_release_all_children(rd_state->cfg, window_query); + view = cfg_node_child_from_string_or_alloc(rd_state->cfg, window_query, s("watch")); + CFG_Node *expr = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("expression")); + cfg_node_new_replace(rd_state->cfg, expr, rd_regs()->expr); + CFG_Node *ctx_expr = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("context_expression")); + cfg_node_new_replace(rd_state->cfg, ctx_expr, rd_regs()->ctx_expr); } - CFG_Node *window_query = rd_immediate_cfg_from_keyf("window_query_%p", window); - cfg_node_release_all_children(rd_state->cfg, window_query); - view = cfg_node_child_from_string_or_alloc(rd_state->cfg, window_query, str8_lit("watch")); - CFG_Node *expr = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("expression")); - cfg_node_new_replace(rd_state->cfg, expr, rd_regs()->expr); - CFG_Node *ctx_expr = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("context_expression")); - cfg_node_new_replace(rd_state->cfg, ctx_expr, rd_regs()->ctx_expr); } // rjf: non-floating -> embed in view @@ -16529,65 +16448,25 @@ rd_frame(void) } // rjf: determine if the target view is a lister (and thus already has a command) - B32 view_is_lister = (cfg_node_child_from_string(view, str8_lit("lister")) != &cfg_nil_node); + B32 view_is_lister = (cfg_node_child_from_string(view, s("lister")) != &cfg_nil_node); // rjf: target view is a lister -> do not do anything - cannot replace the command if(!view_is_lister) { // rjf: unpack view's query info - CFG_Node *query = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("query")); - CFG_Node *cmd = cfg_node_child_from_string_or_alloc(rd_state->cfg, query, str8_lit("cmd")); - CFG_Node *input = cfg_node_child_from_string_or_alloc(rd_state->cfg, query, str8_lit("input")); + CFG_Node *query = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("query")); + CFG_Node *cmd = cfg_node_child_from_string_or_alloc(rd_state->cfg, query, s("cmd")); + CFG_Node *input = cfg_node_child_from_string_or_alloc(rd_state->cfg, query, s("input")); if(is_floating) { - if(rd_regs()->do_implicit_root) - { - cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, str8_lit("explicit_root"))); - } - else - { - cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("explicit_root")); - } - if(!rd_regs()->do_lister) - { - cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, str8_lit("lister"))); - } - else - { - cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("lister")); - } - if(!rd_regs()->small_size) - { - cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, str8_lit("small"))); - } - else - { - cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("small")); - } - if(!rd_regs()->activate_with_single_click) - { - cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, str8_lit("activate_with_single_click"))); - } - else - { - cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("activate_with_single_click")); - } - if(!rd_regs()->prefer_new_tab) - { - cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, s("prefer_new_tab"))); - } - else - { - cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("prefer_new_tab")); - } - if(!rd_regs()->create_new) - { - cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, s("create_new"))); - } - else - { - cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s("create_new")); - } +#define Opt(cnd, key) if(!(cnd)) { cfg_node_release(rd_state->cfg, cfg_node_child_from_string(view, s(key))); } else { cfg_node_child_from_string_or_alloc(rd_state->cfg, view, s(key)); } + Opt(!rd_regs()->do_implicit_root, "explicit_root"); + Opt(rd_regs()->do_lister, "lister"); + Opt(rd_regs()->small_size, "small"); + Opt(rd_regs()->activate_with_single_click, "activate_with_single_click"); + Opt(rd_regs()->prefer_new_tab, "prefer_new_tab"); + Opt(rd_regs()->create_new, "create_new"); +#undef Opt } // rjf: choose initial input string @@ -16652,16 +16531,17 @@ rd_frame(void) RD_WindowState *ws = rd_window_state_from_cfg(window); CFG_Node *view = cfg_node_from_id(rd_regs()->view); String8 cmd_name = rd_view_query_cmd(); + RD_QueryView *qv = ws->query_top; // rjf: find out if this view is a lister - B32 is_lister = (cfg_node_child_from_string(view, str8_lit("lister")) != &cfg_nil_node); + B32 is_lister = (cfg_node_child_from_string(view, s("lister")) != &cfg_nil_node); // rjf: push command if(cmd_name.size != 0) RD_RegsScope() { - if(is_lister) + if(is_lister && qv) { - rd_regs()->view = ws->query_regs->view; + rd_regs()->view = qv->regs->view; } rd_push_cmd(cmd_name, rd_regs()); } @@ -16669,9 +16549,11 @@ rd_frame(void) // rjf: complete query, either by closing the query popup, or closing the // tab-embedded query edit RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name); - if(is_lister) + if(is_lister && qv && ws != &rd_nil_window_state) { - ws->query_is_active = 0; + U64 pop_pos = ws->query_top->q_arena_pos; + SLLStackPop(ws->query_top); + arena_pop_to(ws->query_arena, pop_pos); } else if(!(cmd_kind_info->query.flags & RD_QueryFlag_KeepOldInput)) { @@ -16681,14 +16563,24 @@ rd_frame(void) } }break; case RD_CmdKind_CancelQuery: + { + CFG_Node *window = cfg_node_from_id(rd_regs()->window); + RD_WindowState *ws = rd_window_state_from_cfg(window); + if(ws != &rd_nil_window_state && ws->query_top != 0) + { + U64 pop_pos = ws->query_top->q_arena_pos; + SLLStackPop(ws->query_top); + arena_pop_to(ws->query_arena, pop_pos); + } + }break; + case RD_CmdKind_CancelAllQueries: { CFG_Node *window = cfg_node_from_id(rd_regs()->window); RD_WindowState *ws = rd_window_state_from_cfg(window); if(ws != &rd_nil_window_state) { - ws->query_is_active = 0; arena_clear(ws->query_arena); - ws->query_regs = 0; + ws->query_top = 0; } }break; case RD_CmdKind_UpdateQuery: @@ -16989,11 +16881,13 @@ rd_frame(void) case RD_CmdKind_EditUserTheme: { CFG_Node *parent = cfg_node_child_from_string(cfg_node_root(), str8_lit("user")); + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = push_str8f(scratch.arena, "query:config.$%I64x.theme_colors", parent->id)); }break; case RD_CmdKind_EditProjectTheme: { CFG_Node *parent = cfg_node_child_from_string(cfg_node_root(), str8_lit("project")); + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = push_str8f(scratch.arena, "query:config.$%I64x.theme_colors", parent->id)); }break; case RD_CmdKind_AddThemeColor: @@ -17214,6 +17108,7 @@ rd_frame(void) rd_cmd(RD_CmdKind_SelectTarget, .cfg = target->id); if(!rd_regs()->non_graphical) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = push_str8f(scratch.arena, "query:config.$%I64x", target->id)); } str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "$%I64x", target->id); diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index 2294b6be..e9092ea8 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -267,6 +267,14 @@ struct RD_DropCompletionTask String8List paths; }; +typedef struct RD_QueryView RD_QueryView; +struct RD_QueryView +{ + RD_QueryView *next; + RD_Regs *regs; + U64 q_arena_pos; +}; + typedef struct RD_WindowState RD_WindowState; struct RD_WindowState { @@ -310,12 +318,14 @@ struct RD_WindowState CFG_ID drop_completion_panel; RD_DropCompletionTask *top_drop_completion_task; - // rjf: query state + // rjf: query stack state +#if 0 B32 query_is_active; - Arena *query_arena; RD_Regs *query_regs; - CFG_ID query_view_id; - CFG_ID query_last_view_id; +#endif + Arena *query_arena; + CFG_ID query_last_bottom_view_id; + RD_QueryView *query_top; // rjf: hover eval state B32 hover_eval_focused; diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 9531f992..adb3051b 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -14,6 +14,7 @@ rd_code_view_init(RD_CodeViewState *cv) cv->preferred_column = 1; cv->patch_arena = rd_push_view_arena(); cv->find_text_arena = rd_push_view_arena(); + cv->initial_center = 1; cv->center_cursor = 1; rd_store_view_loading_info(1, 0, 0); } @@ -971,6 +972,15 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla ui_scroll_pt_target_idx(&scroll_pos.y, new_idx); snap[Axis2_Y] = 0; } + + // rjf: just snap if this is the initial center + if(cv->initial_center) + { + scroll_pos.x.off = 0; + scroll_pos.y.off = 0; + cv->initial_center = 0; + + } } } @@ -4091,6 +4101,7 @@ RD_VIEW_UI_FUNCTION_DEF(memory) } if(ui_clicked(peek_sig)) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = casted_expr); } } @@ -4361,6 +4372,7 @@ RD_VIEW_UI_FUNCTION_DEF(memory) // rjf: right-click -> push query if(ui_right_clicked(sig)) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = str8f(scratch.arena, "query:config.$%I64x", rd_regs()->view), .do_implicit_root = 1, diff --git a/src/raddbg/raddbg_views.h b/src/raddbg/raddbg_views.h index 1bae6b7d..c7933e67 100644 --- a/src/raddbg/raddbg_views.h +++ b/src/raddbg/raddbg_views.h @@ -53,6 +53,7 @@ struct RD_CodeViewState // rjf: per-frame command info S64 goto_line_num; + B32 initial_center; B32 center_cursor; B32 contain_cursor; B32 force_contain_only; diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index d1d248fa..c36b193a 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -1561,6 +1561,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred } if(ui_right_clicked(thread_sig)) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .ui_key = thread_box->key, .expr = push_str8f(scratch.arena, "query:control.%S", d_string_from_handle(scratch.arena, thread->handle))); @@ -1715,6 +1716,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred } if(ui_right_clicked(thread_sig)) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .ui_key = thread_box->key, .expr = push_str8f(scratch.arena, "query:control.%S", d_string_from_handle(scratch.arena, thread->handle))); @@ -1796,6 +1798,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred // rjf: bp right-click => open query if(ui_right_clicked(bp_sig)) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .ui_key = bp_box->key, .expr = push_str8f(scratch.arena, "query:config.$%I64x", bp->id)); @@ -1853,6 +1856,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred // rjf: pin right-click => open query if(ui_right_clicked(pin_sig)) { + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .ui_key = pin_box->key, .expr = push_str8f(scratch.arena, "query:config.$%I64x", pin->id)); @@ -2096,6 +2100,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred commands_expr = (*cursor == *mark) ? s("query:text_pt_commands") : s("query:text_range_commands"); } rd_cmd(RD_CmdKind_FocusPanel); + rd_cmd(RD_CmdKind_CancelAllQueries); rd_cmd(RD_CmdKind_PushQuery, .expr = str8f(scratch.arena, "%S, query:config.$%I64x", commands_expr, rd_regs()->view), .do_implicit_root = 1, diff --git a/src/text/text.c b/src/text/text.c index dd5a0cb0..7ca53001 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -3488,15 +3488,18 @@ txt_scope_node_from_info_off(TXT_TextInfo *info, U64 off) break; } } - TXT_ScopeNode *closest_node = &info->scope_nodes.v[info->scope_pts.v[first].scope_idx]; - for(TXT_ScopeNode *scope_n = closest_node; - scope_n != &txt_scope_node_nil; - scope_n = txt_scope_node_from_info_num(info, scope_n->parent_num)) + if(1 <= info->scope_pts.v[first].scope_num && info->scope_pts.v[first].scope_num <= info->scope_nodes.count) { - if(info->tokens.v[scope_n->token_idx_range.min].range.min <= off && off < info->tokens.v[scope_n->token_idx_range.max].range.max) + TXT_ScopeNode *closest_node = &info->scope_nodes.v[info->scope_pts.v[first].scope_num-1]; + for(TXT_ScopeNode *scope_n = closest_node; + scope_n != &txt_scope_node_nil; + scope_n = txt_scope_node_from_info_num(info, scope_n->parent_num)) { - result = scope_n; - break; + if(info->tokens.v[scope_n->token_idx_range.min].range.min <= off && off < info->tokens.v[scope_n->token_idx_range.max].range.max) + { + result = scope_n; + break; + } } } } @@ -4183,7 +4186,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 if(top_scope_task && (is_opener || is_closer)) { shared->info.scope_pts.v[pt_idx].token_idx = token_idx; - shared->info.scope_pts.v[pt_idx].scope_idx = top_scope_task->scope_idx; + shared->info.scope_pts.v[pt_idx].scope_num = top_scope_task->scope_idx+1; pt_idx += 1; } diff --git a/src/text/text.h b/src/text/text.h index 521a46c8..298a1822 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -142,7 +142,7 @@ typedef struct TXT_ScopePt TXT_ScopePt; struct TXT_ScopePt { U64 token_idx; - U64 scope_idx; + U64 scope_num; }; typedef struct TXT_ScopePtArray TXT_ScopePtArray; From cacea99c223f89613a2c208740b3ba612cbd7e83 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 11:19:26 -0700 Subject: [PATCH 103/251] correctly restore view states when popping off query stack - fixes browse in query views - cleanup / tweaks --- src/raddbg/generated/raddbg.meta.c | 2 +- src/raddbg/raddbg.mdesk | 2 +- src/raddbg/raddbg_core.c | 193 +++++++++++++++++------------ 3 files changed, 118 insertions(+), 79 deletions(-) diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index e2c319d9..db9d96ec 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -1136,7 +1136,7 @@ str8_lit_comp("far_manager"), String8 rd_theme_preset_cfg_string_table[11] = { -str8_lit_comp("theme:\n{\n theme_color:{tags: background, value: 0x1f1f1fff}\n theme_color:{tags: \"alt background\", value: 0x222222ff}\n theme_color:{tags: \"pop background\", value: 0x675331ff}\n theme_color:{tags: \"fresh background\", value: 0x3d3631ff}\n theme_color:{tags: \"match background\", value: 0x31393dff}\n theme_color:{tags: border, value: 0x404040ff}\n theme_color:{tags: text, value: 0xe5e5e5ff}\n theme_color:{tags: \"weak text\", value: 0xa4a4a4ff}\n theme_color:{tags: \"good text\", value: 0x32a852ff}\n theme_color:{tags: \"neutral text\", value: 0x3a90bbff}\n theme_color:{tags: \"bad text\", value: 0xcf5242ff}\n theme_color:{tags: hover, value: 0xabdeffff}\n theme_color:{tags: \"focus overlay\", value: 0x2292eb14}\n theme_color:{tags: \"focus border\", value: 0x2392ebff}\n theme_color:{tags: cursor, value: 0x8aff00ff}\n theme_color:{tags: selection, value: 0x99ccff0f}\n theme_color:{tags: \"inactive background\", value: 0x16}\n theme_color:{tags: drop_shadow, value: 0x0000007f}\n theme_color:{tags: \"good_pop background\", value: 0x2c5b36ff}\n theme_color:{tags: \"good_pop border\", value: 0x568761ff}\n theme_color:{tags: \"good_pop hover\", value: 0xe3f5d3ff}\n theme_color:{tags: \"good_pop weak text\", value: 0xe3f5d3ff}\n theme_color:{tags: \"bad_pop background\", value: 0x803425ff}\n theme_color:{tags: \"bad_pop hover\", value: 0xff825cff}\n theme_color:{tags: code_default, value: 0xecececff}\n theme_color:{tags: code_symbol, value: 0xceaf64ff}\n theme_color:{tags: code_type, value: 0xceaf64ff}\n theme_color:{tags: code_local, value: 0xa7dae8ff}\n theme_color:{tags: code_register, value: 0xb7afd5ff}\n theme_color:{tags: code_keyword, value: 0x838b8fff}\n theme_color:{tags: code_delimiter_or_operator, value: 0x838b8fff}\n theme_color:{tags: code_numeric, value: 0x9cb198ff}\n theme_color:{tags: code_numeric_alt_digit_group, value: 0x6d8e67ff}\n theme_color:{tags: code_string, value: 0x9cb198ff}\n theme_color:{tags: code_meta, value: 0xb9819aff}\n theme_color:{tags: code_comment, value: 0x627b5eff}\n theme_color:{tags: line_info_0, value: 0x3c2d25ff}\n theme_color:{tags: line_info_1, value: 0x3c3925ff}\n theme_color:{tags: line_info_2, value: 0x2e4837ff}\n theme_color:{tags: line_info_3, value: 0x2e3d48ff}\n theme_color:{tags: line_info_4, value: 0x3c2d25ff}\n theme_color:{tags: line_info_5, value: 0x3c3925ff}\n theme_color:{tags: line_info_6, value: 0x2e4837ff}\n theme_color:{tags: line_info_7, value: 0x2e3d48ff}\n theme_color:{tags: thread_0, value: 0xebb624ff}\n theme_color:{tags: thread_1, value: 0x26d0d2ff}\n theme_color:{tags: thread_unwound, value: 0xb2ccd8ff}\n theme_color:{tags: thread_error, value: 0xb23219ff}\n theme_color:{tags: breakpoint, value: 0xa72911ff}\n theme_color:{tags: \"floating background\", value: 0x1b1b1baf}\n theme_color:{tags: \"floating background alt\", value: 0x0000005f}\n theme_color:{tags: \"floating background fresh\", value: 0x31393d5f}\n theme_color:{tags: \"floating border\", value: 0xbfbfbf1f}\n theme_color:{tags: \"floating scroll_bar background\", value: 0x3b3b3b5f}\n theme_color:{tags: \"floating scroll_bar border\", value: 0x5f5f5f5f}\n theme_color:{tags: \"scroll_bar background\", value: 0x2b2b2bff}\n theme_color:{tags: \"scroll_bar border\", value: 0x3f3f3fff}\n theme_color:{tags: \"implicit background\", value: 0x00000000}\n theme_color:{tags: \"implicit border\", value: 0x00000000}\n theme_color:{tags: \"hollow background\", value: 0x00000000}\n theme_color:{tags: \"hollow border\", value: 0xffffff1f}\n theme_color:{tags: \"tab background\", value: 0x333333ff}\n theme_color:{tags: \"tab border\", value: 0x2392ebff}\n theme_color:{tags: \"tab inactive background\", value: 0}\n theme_color:{tags: \"tab inactive border\", value: 0x42494eff}\n theme_color:{tags: \"tab auto background\", value: 0x3e4d6eff}\n theme_color:{tags: \"tab auto inactive background\", value: 0x3e4d6e39}\n theme_color:{tags: \"drop_site background\", value: 0xffffff05}\n theme_color:{tags: \"drop_site border\", value: 0xffffff0f}\n}\n"), +str8_lit_comp("theme:\n{\n theme_color:{tags: background, value: 0x1f1f1fff}\n theme_color:{tags: \"alt background\", value: 0x222222ff}\n theme_color:{tags: \"pop background\", value: 0x675331ff}\n theme_color:{tags: \"fresh background\", value: 0x3d3631ff}\n theme_color:{tags: \"match background\", value: 0x31393dff}\n theme_color:{tags: border, value: 0x404040ff}\n theme_color:{tags: text, value: 0xe5e5e5ff}\n theme_color:{tags: \"weak text\", value: 0xa4a4a4ff}\n theme_color:{tags: \"good text\", value: 0x32a852ff}\n theme_color:{tags: \"neutral text\", value: 0x3a90bbff}\n theme_color:{tags: \"bad text\", value: 0xcf5242ff}\n theme_color:{tags: hover, value: 0xffffffff}\n theme_color:{tags: \"focus overlay\", value: 0x2292eb14}\n theme_color:{tags: \"focus border\", value: 0x2392ebff}\n theme_color:{tags: cursor, value: 0x8aff00ff}\n theme_color:{tags: selection, value: 0x99ccff0f}\n theme_color:{tags: \"inactive background\", value: 0x16}\n theme_color:{tags: drop_shadow, value: 0x0000007f}\n theme_color:{tags: \"good_pop background\", value: 0x2c5b36ff}\n theme_color:{tags: \"good_pop border\", value: 0x568761ff}\n theme_color:{tags: \"good_pop hover\", value: 0xe3f5d3ff}\n theme_color:{tags: \"good_pop weak text\", value: 0xe3f5d3ff}\n theme_color:{tags: \"bad_pop background\", value: 0x803425ff}\n theme_color:{tags: \"bad_pop hover\", value: 0xff825cff}\n theme_color:{tags: code_default, value: 0xecececff}\n theme_color:{tags: code_symbol, value: 0xceaf64ff}\n theme_color:{tags: code_type, value: 0xceaf64ff}\n theme_color:{tags: code_local, value: 0xa7dae8ff}\n theme_color:{tags: code_register, value: 0xb7afd5ff}\n theme_color:{tags: code_keyword, value: 0x838b8fff}\n theme_color:{tags: code_delimiter_or_operator, value: 0x838b8fff}\n theme_color:{tags: code_numeric, value: 0x9cb198ff}\n theme_color:{tags: code_numeric_alt_digit_group, value: 0x6d8e67ff}\n theme_color:{tags: code_string, value: 0x9cb198ff}\n theme_color:{tags: code_meta, value: 0xb9819aff}\n theme_color:{tags: code_comment, value: 0x627b5eff}\n theme_color:{tags: line_info_0, value: 0x3c2d25ff}\n theme_color:{tags: line_info_1, value: 0x3c3925ff}\n theme_color:{tags: line_info_2, value: 0x2e4837ff}\n theme_color:{tags: line_info_3, value: 0x2e3d48ff}\n theme_color:{tags: line_info_4, value: 0x3c2d25ff}\n theme_color:{tags: line_info_5, value: 0x3c3925ff}\n theme_color:{tags: line_info_6, value: 0x2e4837ff}\n theme_color:{tags: line_info_7, value: 0x2e3d48ff}\n theme_color:{tags: thread_0, value: 0xebb624ff}\n theme_color:{tags: thread_1, value: 0x26d0d2ff}\n theme_color:{tags: thread_unwound, value: 0xb2ccd8ff}\n theme_color:{tags: thread_error, value: 0xb23219ff}\n theme_color:{tags: breakpoint, value: 0xa72911ff}\n theme_color:{tags: \"floating background\", value: 0x1b1b1baf}\n theme_color:{tags: \"floating background alt\", value: 0x0000005f}\n theme_color:{tags: \"floating background fresh\", value: 0x31393d5f}\n theme_color:{tags: \"floating border\", value: 0xbfbfbf1f}\n theme_color:{tags: \"floating scroll_bar background\", value: 0x3b3b3b5f}\n theme_color:{tags: \"floating scroll_bar border\", value: 0x5f5f5f5f}\n theme_color:{tags: \"scroll_bar background\", value: 0x2b2b2bff}\n theme_color:{tags: \"scroll_bar border\", value: 0x3f3f3fff}\n theme_color:{tags: \"implicit background\", value: 0x00000000}\n theme_color:{tags: \"implicit border\", value: 0x00000000}\n theme_color:{tags: \"hollow background\", value: 0x00000000}\n theme_color:{tags: \"hollow border\", value: 0xffffff1f}\n theme_color:{tags: \"tab background\", value: 0x333333ff}\n theme_color:{tags: \"tab border\", value: 0x2392ebff}\n theme_color:{tags: \"tab inactive background\", value: 0}\n theme_color:{tags: \"tab inactive border\", value: 0x42494eff}\n theme_color:{tags: \"tab auto background\", value: 0x3e4d6eff}\n theme_color:{tags: \"tab auto inactive background\", value: 0x3e4d6e39}\n theme_color:{tags: \"drop_site background\", value: 0xffffff05}\n theme_color:{tags: \"drop_site border\", value: 0xffffff0f}\n}\n"), str8_lit_comp("theme:\n{\n theme_color:{tags: background , value: 0xf4f4f4ff}\n theme_color:{tags: \"alt background\" , value: 0xe7e7e7ff}\n theme_color:{tags: \"pop background\" , value: 0xe7b96bff}\n theme_color:{tags: \"fresh background\" , value: 0xe7d7d0ff}\n theme_color:{tags: \"match background\" , value: 0xcbd8deff}\n theme_color:{tags: border , value: 0xb9b9b9ff}\n theme_color:{tags: text , value: 0xff}\n theme_color:{tags: \"weak text\" , value: 0x4d4d4dff}\n theme_color:{tags: \"good text\" , value: 0x32a852ff}\n theme_color:{tags: \"neutral text\" , value: 0x3a90bbff}\n theme_color:{tags: \"bad text\" , value: 0xcf5242ff}\n theme_color:{tags: hover , value: 0xff}\n theme_color:{tags: \"focus overlay\" , value: 0x2192eb49}\n theme_color:{tags: \"focus border\" , value: 0x2392ebff}\n theme_color:{tags: cursor , value: 0xff}\n theme_color:{tags: selection , value: 0x2170be51}\n theme_color:{tags: \"inactive background\" , value: 0xb}\n theme_color:{tags: drop_shadow , value: 0x183d8a53}\n theme_color:{tags: \"good_pop background\" , value: 0x5c9368ff}\n theme_color:{tags: \"good_pop border\" , value: 0x6616ff}\n theme_color:{tags: \"good_pop hover\" , value: 0xe3f5d3ff}\n theme_color:{tags: \"good_pop weak text\" , value: 0xe3f5d3ff}\n theme_color:{tags: \"bad_pop background\" , value: 0xbd5946ff}\n theme_color:{tags: \"bad_pop hover\" , value: 0xff825cff}\n theme_color:{tags: code_default , value: 0xff}\n theme_color:{tags: code_symbol , value: 0x976b00ff}\n theme_color:{tags: code_type , value: 0x976b00ff}\n theme_color:{tags: code_local , value: 0x7fa2ff}\n theme_color:{tags: code_register , value: 0x6a50caff}\n theme_color:{tags: code_keyword , value: 0x1f4253ff}\n theme_color:{tags: code_delimiter_or_operator , value: 0x424b4fff}\n theme_color:{tags: code_numeric , value: 0xb4500ff}\n theme_color:{tags: code_numeric_alt_digit_group , value: 0x3c852fff}\n theme_color:{tags: code_string , value: 0xb4500ff}\n theme_color:{tags: code_meta , value: 0xe3609aff}\n theme_color:{tags: code_comment , value: 0x648e5dff}\n theme_color:{tags: line_info_0 , value: 0xcdb3a5ff}\n theme_color:{tags: line_info_1 , value: 0xcac5a4ff}\n theme_color:{tags: line_info_2 , value: 0xafcebaff}\n theme_color:{tags: line_info_3 , value: 0xacc5d7ff}\n theme_color:{tags: line_info_4 , value: 0x3c2d25ff}\n theme_color:{tags: line_info_5 , value: 0x3c3925ff}\n theme_color:{tags: line_info_6 , value: 0x2e4837ff}\n theme_color:{tags: line_info_7 , value: 0x2e3d48ff}\n theme_color:{tags: thread_0 , value: 0xde8e2aff}\n theme_color:{tags: thread_1 , value: 0x7affff}\n theme_color:{tags: thread_unwound , value: 0xb2ccd8ff}\n theme_color:{tags: thread_error , value: 0xb23219ff}\n theme_color:{tags: breakpoint , value: 0xa72911ff}\n theme_color:{tags: \"floating background\" , value: 0xffffffff}\n theme_color:{tags: \"floating background alt\" , value: 0xefefefff}\n theme_color:{tags: \"floating background fresh\" , value: 0x31393d5f}\n theme_color:{tags: \"floating border\" , value: 0xaeaeaeff}\n theme_color:{tags: \"floating scroll_bar background\" , value: 0x3b3b3b5f}\n theme_color:{tags: \"floating scroll_bar border\" , value: 0x5f5f5f5f}\n theme_color:{tags: \"scroll_bar background\" , value: 0xefefefff}\n theme_color:{tags: \"scroll_bar border\" , value: 0xb9b9b9ff}\n theme_color:{tags: \"implicit background\" , value: 0x00000000}\n theme_color:{tags: \"implicit border\" , value: 0x00000000}\n theme_color:{tags: \"hollow background\" , value: 0x00000000}\n theme_color:{tags: \"hollow border\" , value: 0x1f}\n theme_color:{tags: \"tab background\" , value: 0xffffffff}\n theme_color:{tags: \"tab border\" , value: 0x2392ebff}\n theme_color:{tags: \"tab inactive background\" , value: 0}\n theme_color:{tags: \"tab inactive border\" , value: 0x42494eff}\n theme_color:{tags: \"tab auto background\" , value: 0xb9d0ffff}\n theme_color:{tags: \"tab auto inactive background\" , value: 0xc4cde0ff}\n theme_color:{tags: \"drop_site background\" , value: 0xffffff05}\n theme_color:{tags: \"drop_site border\" , value: 0xffffff0f}\n theme_color:{tags: \"bad_pop weak text\" , value: 0xffffffff}\n}\n"), str8_lit_comp("theme:\n{\n theme_color:{ tags: background , value: 0x1f1f1fff }\n theme_color:{ tags: \"alt background\" , value: 0x222222ff }\n theme_color:{ tags: \"pop background\" , value: 0x383167ff }\n theme_color:{ tags: \"fresh background\" , value: 0x31393dff }\n theme_color:{ tags: \"match background\" , value: 0x31393dff }\n theme_color:{ tags: border , value: 0x404040ff }\n theme_color:{ tags: text , value: 0xe5e5e5ff }\n theme_color:{ tags: \"weak text\" , value: 0xa4a4a4ff }\n theme_color:{ tags: \"good text\" , value: 0x32a852ff }\n theme_color:{ tags: \"neutral text\" , value: 0x3a90bbff }\n theme_color:{ tags: \"bad text\" , value: 0xcf5242ff }\n theme_color:{ tags: hover , value: 0xffffffff }\n theme_color:{ tags: \"focus overlay\" , value: 0x7160e81e }\n theme_color:{ tags: \"focus border\" , value: 0x7160e8ff }\n theme_color:{ tags: cursor , value: 0x8aff00ff }\n theme_color:{ tags: selection , value: 0x99ccff0f }\n theme_color:{ tags: \"inactive background\" , value: 0x0000002f }\n theme_color:{ tags: drop_shadow , value: 0x0000007f }\n theme_color:{ tags: \"good_pop background\" , value: 0x2c5b36ff }\n theme_color:{ tags: \"good_pop border\" , value: 0x568761ff }\n theme_color:{ tags: \"good_pop hover\" , value: 0xe3f5d3ff }\n theme_color:{ tags: \"good_pop weak text\" , value: 0xe3f5d3ff }\n theme_color:{ tags: \"bad_pop background\" , value: 0x803425ff }\n theme_color:{ tags: \"bad_pop hover\" , value: 0xff825cff }\n theme_color:{ tags: code_default , value: 0xe0e0e0ff }\n theme_color:{ tags: code_symbol , value: 0xdcdcaaff }\n theme_color:{ tags: code_type , value: 0x4ec9b0ff }\n theme_color:{ tags: code_local , value: 0x9cdcfeff }\n theme_color:{ tags: code_register , value: 0xb7afd5ff }\n theme_color:{ tags: code_keyword , value: 0x569cd6ff }\n theme_color:{ tags: code_delimiter_or_operator , value: 0x767676ff }\n theme_color:{ tags: code_numeric , value: 0xb5cea8ff }\n theme_color:{ tags: code_numeric_alt_digit_group , value: 0x7c986dff }\n theme_color:{ tags: code_string , value: 0xd69d85ff }\n theme_color:{ tags: code_meta , value: 0x9b9b9bff }\n theme_color:{ tags: code_comment , value: 0x51a644ff }\n theme_color:{ tags: line_info_0 , value: 0x4f3022ff }\n theme_color:{ tags: line_info_1 , value: 0x4f3e15ff }\n theme_color:{ tags: line_info_2 , value: 0x434e2aff }\n theme_color:{ tags: line_info_3 , value: 0x36241fff }\n theme_color:{ tags: line_info_4 , value: 0x4f3022ff }\n theme_color:{ tags: line_info_5 , value: 0x4f3e15ff }\n theme_color:{ tags: line_info_6 , value: 0x434e2aff }\n theme_color:{ tags: line_info_7 , value: 0x36241fff }\n theme_color:{ tags: thread_0 , value: 0xffdc48ff }\n theme_color:{ tags: thread_1 , value: 0xb2ff65ff }\n theme_color:{ tags: thread_2 , value: 0xff99e5ff }\n theme_color:{ tags: thread_3 , value: 0x6598ffff }\n theme_color:{ tags: thread_4 , value: 0x65ffcbff }\n theme_color:{ tags: thread_5 , value: 0xff9819ff }\n theme_color:{ tags: thread_6 , value: 0x9932ffff }\n theme_color:{ tags: thread_7 , value: 0x65ff4cff }\n theme_color:{ tags: thread_unwound , value: 0xb2ccd8ff }\n theme_color:{ tags: thread_error , value: 0xb23219ff }\n theme_color:{ tags: breakpoint , value: 0xa72911ff }\n theme_color:{ tags: \"floating background\" , value: 0x1b1b1baf }\n theme_color:{ tags: \"floating background alt\" , value: 0x0000005f }\n theme_color:{ tags: \"floating background fresh\" , value: 0x31393d5f }\n theme_color:{ tags: \"floating border\" , value: 0xbfbfbf1f }\n theme_color:{ tags: \"floating scroll_bar background\" , value: 0x3b3b3b5f }\n theme_color:{ tags: \"floating scroll_bar border\" , value: 0x5f5f5f5f }\n theme_color:{ tags: \"scroll_bar background\" , value: 0x2b2b2bff }\n theme_color:{ tags: \"scroll_bar border\" , value: 0x3f3f3fff }\n theme_color:{ tags: \"implicit background\" , value: 0x00000000 }\n theme_color:{ tags: \"implicit border\" , value: 0x00000000 }\n theme_color:{ tags: \"hollow background\" , value: 0x00000000 }\n theme_color:{ tags: \"hollow border\" , value: 0xffffff1f }\n theme_color:{ tags: \"tab background\" , value: 0x333333ff }\n theme_color:{ tags: \"tab border\" , value: 0x7160e8ff }\n theme_color:{ tags: \"tab inactive background\" , value: 0x171717ff }\n theme_color:{ tags: \"tab inactive border\" , value: 0x3e4c57ff }\n theme_color:{ tags: \"tab auto background\" , value: 0x3f386dff }\n theme_color:{ tags: \"tab auto border\" , value: 0x7160e8ff }\n theme_color:{ tags: \"tab auto inactive background\" , value: 0x2f2633ff }\n theme_color:{ tags: \"tab auto inactive border\" , value: 0x685073ff }\n theme_color:{ tags: \"drop_site background\" , value: 0xffffff05 }\n theme_color:{ tags: \"drop_site border\" , value: 0xffffff0f }\n}\n"), str8_lit_comp("theme:\n{\n theme_color:{tags: background, value: 0xffffffff}\n theme_color:{tags: \"alt background\", value: 0xefefefff}\n theme_color:{tags: \"pop background\", value: 0xe3eaf2ff}\n theme_color:{tags: \"fresh background\", value: 0xeccbbeff}\n theme_color:{tags: \"match background\", value: 0xedcbf9ff}\n theme_color:{tags: border, value: 0xe7e7e7ff}\n theme_color:{tags: text, value: 0xff}\n theme_color:{tags: \"weak text\", value: 0x353535ff}\n theme_color:{tags: \"good text\", value: 0x32a852ff}\n theme_color:{tags: \"neutral text\", value: 0x3a90bbff}\n theme_color:{tags: \"bad text\", value: 0xcf5242ff}\n theme_color:{tags: hover, value: 0xa7ffff}\n theme_color:{tags: \"focus overlay\", value: 0x8eff3f}\n theme_color:{tags: \"focus border\", value: 0x8effff}\n theme_color:{tags: cursor, value: 0xff}\n theme_color:{tags: selection, value: 0x56aaff77}\n theme_color:{tags: \"inactive background\", value: 0x17}\n theme_color:{tags: drop_shadow, value: 0xe7b27}\n theme_color:{tags: \"good_pop background\", value: 0x21a43dff}\n theme_color:{tags: \"good_pop border\", value: 0x21a43dff}\n theme_color:{tags: \"good_pop hover\", value: 0xe3f5d3ff}\n theme_color:{tags: \"good_pop weak text\", value: 0xe3f5d3ff}\n theme_color:{tags: \"good_pop text\", value: 0xe3f5d3ff}\n theme_color:{tags: \"bad_pop background\", value: 0xcb3f23ff}\n theme_color:{tags: \"bad_pop text\", value: 0xffcdc4ff}\n theme_color:{tags: \"bad_pop text weak\", value: 0xffcdc4ff}\n theme_color:{tags: \"bad_pop hover\", value: 0xff825cff}\n theme_color:{tags: code_default, value: 0x000000ff}\n theme_color:{tags: code_symbol, value: 0x74531fff}\n theme_color:{tags: code_type, value: 0x2b91afff}\n theme_color:{tags: code_local, value: 0x1f377fff}\n theme_color:{tags: code_register, value: 0x8a1bffff}\n theme_color:{tags: code_keyword, value: 0x0000ffff}\n theme_color:{tags: code_delimiter_or_operator, value: 0x767676ff}\n theme_color:{tags: code_numeric, value: 0xff}\n theme_color:{tags: code_numeric_alt_digit_group, value: 0x1d1d1dff}\n theme_color:{tags: code_string, value: 0xa61515ff}\n theme_color:{tags: code_meta, value: 0x808080ff}\n theme_color:{tags: code_comment, value: 0x008000ff}\n theme_color:{tags: line_info_0, value: 0xb5d9c8ff}\n theme_color:{tags: line_info_1, value: 0xa9c1d0ff}\n theme_color:{tags: line_info_2, value: 0x99abc5ff}\n theme_color:{tags: line_info_3, value: 0xc6bcd5ff}\n theme_color:{tags: thread_0, value: 0xffb141ff}\n theme_color:{tags: thread_1, value: 0x66c407ff}\n theme_color:{tags: thread_unwound, value: 0x67b3d7ff}\n theme_color:{tags: thread_error, value: 0xff2900ff}\n theme_color:{tags: breakpoint, value: 0xff2800ff}\n theme_color:{tags: \"floating background\", value: 0xffffffff}\n theme_color:{tags: \"floating background alt\", value: 0x11}\n theme_color:{tags: \"floating background fresh\", value: 0xa0c2d318}\n theme_color:{tags: \"floating border\", value: 0x50}\n theme_color:{tags: \"floating scroll_bar background\", value: 0x3b3b3b5f}\n theme_color:{tags: \"floating scroll_bar border\", value: 0x5f5f5f5f}\n theme_color:{tags: \"menu_bar background\", value: 0xccd5f0ff}\n theme_color:{tags: \"menu_bar border\", value: 0xbabdc3ff}\n theme_color:{tags: \"scroll_bar background\", value: 0xe4e4e4ff}\n theme_color:{tags: \"implicit background\", value: 0x00000000}\n theme_color:{tags: \"implicit border\", value: 0x00000000}\n theme_color:{tags: \"hollow background\", value: 0x00000000}\n theme_color:{tags: \"hollow border\", value: 0xffffff1f}\n theme_color:{tags: \"tab background\", value: 0xf5cc84ff}\n theme_color:{tags: \"tab border\", value: 0xae7718ff}\n theme_color:{tags: \"tab inactive background\", value: 0x3b4f81ff}\n theme_color:{tags: \"tab inactive text\", value: 0xffffffff}\n theme_color:{tags: \"tab inactive border\", value: 0x3b4f81ff}\n theme_color:{tags: \"tab auto background\", value: 0xe99595ff}\n theme_color:{tags: \"tab auto border\", value: 0xff6262ff}\n theme_color:{tags: \"tab auto inactive background\", value: 0xac6060ff}\n theme_color:{tags: \"tab auto inactive border\", value: 0xff6262ff}\n theme_color:{tags: \"drop_site background\", value: 0xffffff05}\n theme_color:{tags: \"drop_site border\", value: 0xa7ffff}\n}\n"), diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 19d696b9..2c21d406 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1615,7 +1615,7 @@ RD_ThemePresetTable: theme_color:{tags: "good text", value: 0x32a852ff} theme_color:{tags: "neutral text", value: 0x3a90bbff} theme_color:{tags: "bad text", value: 0xcf5242ff} - theme_color:{tags: hover, value: 0xabdeffff} + theme_color:{tags: hover, value: 0xffffffff} theme_color:{tags: "focus overlay", value: 0x2292eb14} theme_color:{tags: "focus border", value: 0x2392ebff} theme_color:{tags: cursor, value: 0x8aff00ff} diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 971abb59..8d62551c 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -1788,9 +1788,10 @@ rd_view_ui(Rng2F32 rect) { CFG_NodePtrList targets = cfg_node_top_level_list_from_string(scratch.arena, str8_lit("target")); D_EntityArray processes = d_entity_array_from_kind(D_EntityKind_Process); + Rng2F32 view_rect = ui_top_parent()->rect; //- rjf: icon & info - UI_Padding(ui_em(2.f, 1.f)) UI_TagF("weak") + UI_Padding(ui_em(2.f, 1.f)) { //- rjf: icon { @@ -1806,16 +1807,42 @@ rd_view_ui(Rng2F32 rect) } } - //- rjf: info - UI_Padding(ui_em(2.f, 1.f)) - UI_WidthFill UI_PrefHeight(ui_em(2.f, 1.f)) + //- rjf: title + ui_spacer(ui_em(2.f, 1.f)); + UI_WidthFill UI_PrefHeight(ui_em(2.f, 1.f)) UI_Row UI_Padding(ui_pct(1, 0)) UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_text_dim(10, 1)) + UI_PrefWidth(ui_text_dim(1, 1)) + UI_FontSize(ui_top_font_size()*2.f) { - ui_label(str8_lit(BUILD_TITLE_STRING_LITERAL)); + ui_label(s(BUILD_TITLE)); } + + //- rjf: info + ui_spacer(ui_em(1.f, 1.f)); + UI_WidthFill UI_PrefHeight(ui_em(2.f, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_text_dim(1, 1)) + UI_TagF("weak") + { + ui_label(s(BUILD_VERSION_STRING_LITERAL " " BUILD_RELEASE_PHASE_STRING_LITERAL)); + ui_label(s(BUILD_GIT_HASH)); + } + } + + //- rjf: helper for command lister activation + UI_PrefHeight(ui_em(3.75f, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_em(28.f, 1.f)) + UI_CornerRadius(ui_top_font_size()/2.f) + if(ui_clicked(rd_icon_buttonf(RD_IconKind_List, 0, "Open Commands & Settings Palette"))) + { + rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_OpenPalette].string); } //- rjf: targets state dependent helper @@ -1828,22 +1855,51 @@ rd_view_ui(Rng2F32 rect) //- rjf: user has no targets. build helper for adding them case 0: { - UI_PrefHeight(ui_em(3.75f, 1.f)) - UI_Row - UI_Padding(ui_pct(1, 0)) + // rjf: drop target visualization + ui_spacer(ui_em(2.f, 1.f)); + UI_PrefHeight(ui_em(30.f, 1.f)) UI_Row UI_Padding(ui_pct(1, 0)) UI_PrefWidth(ui_em(40.f, 1.f)) UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_em(22.f, 1.f)) UI_CornerRadius(ui_top_font_size()/2.f) - UI_TagF("pop") - if(ui_clicked(rd_icon_buttonf(RD_IconKind_Add, 0, "Add Target"))) { - rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_AddTarget].string); + ui_set_next_child_layout_axis(Axis2_Y); + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawBorder, "###add_target_drop_site"); + UI_Parent(box) UI_Padding(ui_pct(1, 0)) UI_PrefHeight(ui_em(2.5f, 1.f)) + { + UI_PrefHeight(ui_em(3.75f, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_em(12.f, 1.f)) + UI_CornerRadius(ui_top_font_size()/2.f) + UI_TagF("pop") + if(ui_clicked(rd_icon_buttonf(RD_IconKind_Add, 0, "Add Target"))) + { + rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_AddTarget].string); + } + ui_spacer(ui_em(1.f, 1.f)); + UI_TagF("weak") + { + ui_label(s("or drop target executable file")); + } + } + for(UI_Event *evt = 0; ui_next_event(&evt);) + { + if(evt->kind == UI_EventKind_FileDrop && + contains_2f32(view_rect, evt->pos) && + evt->paths.node_count != 0) + { + rd_cmd(RD_CmdKind_AddTarget, .file_path = evt->paths.first->string); + ui_eat_event(evt); + } + } } + ui_spacer(ui_em(4.f, 1.f)); }break; //- rjf: user has 1 target. build helper for launching it case 1: { + ui_spacer(ui_em(1.f, 1.f)); CFG_Node *target_cfg = cfg_node_ptr_list_first(&targets); D_Target target = rd_target_from_cfg(scratch.arena, target_cfg); String8 target_full_path = target.exe; @@ -1860,7 +1916,7 @@ rd_view_ui(Rng2F32 rect) { rd_cmd(RD_CmdKind_LaunchAndRun, .cfg = target_cfg->id); } - ui_spacer(ui_em(1.5f, 1)); + ui_spacer(ui_em(1.f, 1)); if(ui_clicked(rd_icon_buttonf(RD_IconKind_StepInto, 0, "Step Into %S", target_name))) { rd_cmd(RD_CmdKind_LaunchAndStepInto, .cfg = target_cfg->id); @@ -1875,30 +1931,6 @@ rd_view_ui(Rng2F32 rect) }break; } } - - //- rjf: or text - if(helper_built) - { - UI_TagF("weak") - UI_PrefHeight(ui_em(2.25f, 1.f)) - UI_Row - UI_Padding(ui_pct(1, 0)) - UI_TextAlignment(UI_TextAlign_Center) - UI_WidthFill - ui_labelf("- or -"); - } - - //- rjf: helper text for command lister activation - UI_TagF("weak") - UI_PrefHeight(ui_em(2.25f, 1.f)) UI_Row - UI_PrefWidth(ui_text_dim(10, 1)) - UI_TextAlignment(UI_TextAlign_Center) - UI_Padding(ui_pct(1, 0)) - { - ui_labelf("use"); - UI_TextAlignment(UI_TextAlign_Center) rd_cmd_binding_buttons(rd_cmd_kind_info_table[RD_CmdKind_OpenPalette].string, s(""), 1, 0); - ui_labelf("to search for commands and options"); - } } scratch_end(scratch); } @@ -6880,24 +6912,27 @@ rd_window_frame(void) for(RD_QueryView *q = top_query; q != 0; q = q->next, q_count += 1) { // rjf: close if expression is busted + if(q->regs->cmd_name.size == 0) { String8 expr = q->regs->expr; E_Eval eval = e_eval_from_string(expr); if(eval.msgs.max_kind > E_MsgKind_Null) { top_query = q->next; - remove_q_count = q_count; + remove_q_count = q_count+1; } else if(eval.space.kind == RD_EvalSpaceKind_MetaCfg && rd_cfg_from_eval_space(eval.space) == &cfg_nil_node) { - remove_q_count = q_count; + top_query = q->next; + remove_q_count = q_count+1; } else if((eval.space.kind == RD_EvalSpaceKind_MetaCtrlEntity || eval.space.kind == D_EvalSpaceKind_Entity) && rd_ctrl_entity_from_eval_space(eval.space) == &d_entity_nil) { - remove_q_count = q_count; + top_query = q->next; + remove_q_count = q_count+1; } } @@ -6918,10 +6953,12 @@ rd_window_frame(void) } query_is_open = (top_query != 0); - //- rjf: touch immediate cfgs for all active queries + //- rjf: touch immediate cfgs / view states for all active queries for(RD_QueryView *qv = top_query; qv != 0; qv = qv->next) { - rd_immediate_cfg_from_keyf("window_query_%p_%I64u", window, qv->q_arena_pos); + CFG_Node *root = rd_immediate_cfg_from_keyf("window_query_%p_%I64u", window, qv->q_arena_pos); + CFG_Node *view = cfg_node_child_from_string(root, s("watch")); + RD_ViewState *vs = rd_view_state_from_cfg(view); } //- rjf: try to add opened query @@ -7709,7 +7746,8 @@ rd_window_frame(void) // rjf: make ui UI_TagF("implicit") - UI_VisualMargin(ui_top_font_size()*0.45f) + UI_VisualMarginX(ui_top_font_size()*0.45f) + UI_VisualMarginY(ui_top_font_size()*0.5f) UI_CornerRadius(ui_top_font_size()*0.5f) for(U64 idx = 0; idx < ArrayCount(items); idx += 1) { @@ -7849,41 +7887,40 @@ rd_window_frame(void) // rjf: loaded project viz if(do_user_prof) - UI_VisualMargin(ui_top_font_size()*0.5f) - UI_CornerRadius(ui_top_font_size()*0.5f) { - ui_set_next_pref_width(ui_children_sum(1)); - ui_set_next_child_layout_axis(Axis2_X); - UI_Box *prof_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| - UI_BoxFlag_DrawBackground| - UI_BoxFlag_DrawHotEffects| - UI_BoxFlag_DrawActiveEffects, - "###loaded_project_button"); - wm_window_push_custom_title_bar_client_area(ws->os, prof_box->rect); - UI_Parent(prof_box) UI_PrefWidth(ui_text_dim(10, 0)) UI_TextAlignment(UI_TextAlign_Center) UI_Padding(ui_em(0.5f, 1.f)) + CFG_Node *root = cfg_node_root(); + CFG_Node *project = cfg_node_child_from_string(root, str8_lit("project")); + CFG_Node *name = cfg_node_child_from_string(project, str8_lit("name")); + String8 project_name = name->first->string; + if(project_name.size == 0) { - CFG_Node *root = cfg_node_root(); - CFG_Node *project = cfg_node_child_from_string(root, str8_lit("project")); - CFG_Node *name = cfg_node_child_from_string(project, str8_lit("name")); - String8 project_name = name->first->string; - if(project_name.size == 0) - { - String8 prof_path = rd_state->project_path; - prof_path = str8_chop_last_dot(prof_path); - project_name = str8_skip_last_slash(prof_path); - } - if(project_name.size == 0) - { - project_name = str8_lit("Untitled Project"); - } - RD_Font(RD_FontSlot_Icons) - ui_label(rd_icon_kind_text_table[RD_IconKind_Briefcase]); - ui_label(project_name); + String8 prof_path = rd_state->project_path; + prof_path = str8_chop_last_dot(prof_path); + project_name = str8_skip_last_slash(prof_path); } - UI_Signal prof_sig = ui_signal_from_box(prof_box); - if(ui_clicked(prof_sig)) + if(project_name.size != 0) { - rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_OpenProject].string); + UI_VisualMargin(ui_top_font_size()*0.5f) + UI_CornerRadius(ui_top_font_size()*0.5f) + { + ui_set_next_pref_width(ui_children_sum(1)); + ui_set_next_child_layout_axis(Axis2_X); + UI_Box *prof_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects, + "###loaded_project_button"); + wm_window_push_custom_title_bar_client_area(ws->os, prof_box->rect); + UI_Parent(prof_box) UI_PrefWidth(ui_text_dim(10, 0)) UI_TextAlignment(UI_TextAlign_Center) UI_Padding(ui_em(0.5f, 1.f)) + { + ui_label(project_name); + } + UI_Signal prof_sig = ui_signal_from_box(prof_box); + if(ui_clicked(prof_sig)) + { + rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_ProjectSettings].string); + } + } } } @@ -9187,7 +9224,7 @@ rd_window_frame(void) UI_Box *add_new_box = &ui_nil_box; RD_Font(RD_FontSlot_Icons) UI_CornerRadius((tab_bar_vheight - tab_bar_vheight/4.f) / 3.f) - UI_VisualMargin(tab_bar_vheight/4.f) + UI_VisualMargin(tab_bar_vheight/5.f) UI_TagF("implicit") UI_TagF("weak") { @@ -9539,6 +9576,7 @@ rd_window_frame(void) } // rjf: soft circle around mouse +#if 0 if(box->hot_t > 0.01f && dim_2f32(box->rect).x > box->font_size*8.f) DR_ClipScope(intersect_2f32(box_bg_rect, dr_top_clip())) { Vec4F32 color = hover_color; @@ -9552,8 +9590,9 @@ rd_window_frame(void) F32 max_dim = Max(box_dim.x, box_dim.y); F32 radius = box->font_size*24.f; radius = Min(max_dim, radius); - dr_rect(pad_2f32(r2f32(center, center), radius), color, radius, 0, radius/3.f); + dr_rect(pad_2f32(r2f32(center, center), radius*2.f), color, radius, 0, radius/2.f); } +#endif } // rjf: active effect extension From af2932f0ff256fdd0a60b72cff21ee2be5ee01a3 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 11:41:44 -0700 Subject: [PATCH 104/251] run-to-line interaction in priority margin in code views --- project.raddbg | 1 + src/raddbg/raddbg_widgets.c | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/project.raddbg b/project.raddbg index e1df3c4e..4a9d97ad 100755 --- a/project.raddbg +++ b/project.raddbg @@ -18,6 +18,7 @@ target: { executable: "build/mule_main.exe" working_directory: build + enabled: 0 } target: { diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index c36b193a..d87a6153 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -1442,6 +1442,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred ui_set_next_child_layout_axis(Axis2_Y); priority_margin_container_box = ui_build_box_from_string(UI_BoxFlag_Clickable*!!(params->flags & RD_CodeSliceFlag_Clickable), s("priority_margin_container")); UI_Parent(priority_margin_container_box) UI_PrefHeight(ui_px(params->line_height_px, 1.f)) + UI_TextAlignment(UI_TextAlign_Center) { U64 line_idx = 0; for(S64 line_num = params->line_num_range.min; @@ -1449,7 +1450,8 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred line_num += 1, line_idx += 1) { D_EntityList line_ips = params->line_ips[line_idx]; - UI_Box *line_margin_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable*!!(params->flags & RD_CodeSliceFlag_Clickable)|UI_BoxFlag_DrawActiveEffects, "line_margin_%I64x", line_num); + ui_set_next_background_color(v4f32(0, 0, 0, 0)); + UI_Box *line_margin_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable*!!(params->flags & RD_CodeSliceFlag_Clickable)|UI_BoxFlag_DrawActiveEffects|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawText, "###line_margin_%I64x", line_num); UI_Parent(line_margin_box) { //- rjf: build margin thread ip ui @@ -1572,6 +1574,26 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred } } } + + // rjf: empty priority margin interaction + UI_Signal line_margin_sig = ui_signal_from_box(line_margin_box); + if(ui_hovering(line_margin_sig)) + { + DR_FStrList fstrs = {0}; + Vec4F32 color = ui_color_from_name(s("thread_0")); + color.w *= 0.2f; + DR_FStrParams p = {rd_font_from_slot(RD_FontSlot_Icons), rd_raster_flags_from_slot(RD_FontSlot_Icons), color, ui_top_font_size()}; + dr_fstrs_push_new(scratch.arena, &fstrs, &p, rd_icon_kind_text_table[RD_IconKind_RightArrow]); + ui_box_equip_display_fstrs(line_margin_box, &fstrs); + } + if(ui_clicked(line_margin_sig)) + { + rd_cmd(RD_CmdKind_RunToLine, + .file_path = params->line_vaddrs[line_idx] ? str8_zero() : rd_regs()->file_path, + .line_num = params->line_vaddrs[line_idx] ? 0 : line_num, + .vaddr = params->line_vaddrs[line_idx], + .expr = s("")); + } } } } From 58948fab3d60695cce7ed1d3963697030c69d1c2 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 12:05:38 -0700 Subject: [PATCH 105/251] draw run-to-line info --- project.raddbg | 1 - src/raddbg/raddbg_core.c | 26 ++++++++++++++++++++++++++ src/raddbg/raddbg_core.h | 4 ---- src/raddbg/raddbg_views.c | 28 ++++++++++++++++++++++++++++ src/raddbg/raddbg_widgets.c | 2 +- src/raddbg/raddbg_widgets.h | 1 + 6 files changed, 56 insertions(+), 6 deletions(-) diff --git a/project.raddbg b/project.raddbg index 4a9d97ad..e1df3c4e 100755 --- a/project.raddbg +++ b/project.raddbg @@ -18,7 +18,6 @@ target: { executable: "build/mule_main.exe" working_directory: build - enabled: 0 } target: { diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 8d62551c..1ecc3baa 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -13568,6 +13568,23 @@ rd_frame(void) d_push_cmd((D_CmdKind)kind, ¶ms); } + // rjf: run-to-line? -> remember where we're running to, so we can render + if((D_CmdKind)kind == D_CmdKind_RunToLine) + { + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); + CFG_Node *run_to_line = cfg_node_child_from_string_or_alloc(rd_state->cfg, transient, s("run_to_line")); + if(rd_regs()->file_path.size != 0) + { + CFG_Node *src_loc = cfg_node_child_from_string_or_alloc(rd_state->cfg, run_to_line, s("source_location")); + cfg_node_new_replacef(rd_state->cfg, src_loc, "%S:%I64u:%I64u", rd_regs()->file_path, rd_regs()->line_num, rd_regs()->column_num); + } + else + { + CFG_Node *addr_loc = cfg_node_child_from_string_or_alloc(rd_state->cfg, run_to_line, s("address_location")); + cfg_node_new_replacef(rd_state->cfg, addr_loc, "0x%I64x", rd_regs()->vaddr); + } + } + // rjf: try to open tabs, if this is a tab-fastpath-opener if(kind >= RD_CmdKind_FirstTabFastPathCmd) { @@ -18263,6 +18280,15 @@ rd_frame(void) } } + //////////////////////////// + //- rjf: remove run-to-line info + // + if(!d_ctrl_targets_running()) + { + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); + cfg_node_release(rd_state->cfg, cfg_node_child_from_string(transient, s("run_to_line"))); + } + ////////////////////////////// //- rjf: retry find-thread // diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index e9092ea8..353491c0 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -319,10 +319,6 @@ struct RD_WindowState RD_DropCompletionTask *top_drop_completion_task; // rjf: query stack state -#if 0 - B32 query_is_active; - RD_Regs *query_regs; -#endif Arena *query_arena; CFG_ID query_last_bottom_view_id; RD_QueryView *query_top; diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index adb3051b..a9aac20a 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -644,6 +644,18 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla code_slice_params.relevant_dbgi_keys = lines_array.dbgi_keys; } + // rjf: find visible run-to-line info for source code + if(!dasm_lines) + { + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); + CFG_Node *run_to_line = cfg_node_child_from_string(transient, s("run_to_line")); + RD_Location loc = rd_location_from_cfg(run_to_line); + if(path_match_normalized(loc.file_path, rd_regs()->file_path)) + { + code_slice_params.run_to_line_num = loc.pt.line; + } + } + // rjf: find live threads mapping to disasm if(dasm_lines) ProfScope("find live threads mapping to this disassembly") { @@ -731,6 +743,22 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } } + // rjf: find visible run-to-line info for disassembly + if(dasm_lines) + { + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); + CFG_Node *run_to_line = cfg_node_child_from_string(transient, s("run_to_line")); + RD_Location loc = rd_location_from_cfg(run_to_line); + E_Value loc_value = e_value_from_string(loc.expr); + if(contains_1u64(dasm_vaddr_range, loc_value.u64)) + { + U64 off = loc_value.u64 - dasm_vaddr_range.min; + U64 idx = dasm_line_array_idx_from_code_off__linear_scan(dasm_lines, off); + S64 line_num = (S64)idx+1; + code_slice_params.run_to_line_num = line_num; + } + } + // rjf: add dasm dbgi key to relevant dbgis if(dasm_lines != 0) { diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index d87a6153..1b1548bb 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -1577,7 +1577,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred // rjf: empty priority margin interaction UI_Signal line_margin_sig = ui_signal_from_box(line_margin_box); - if(ui_hovering(line_margin_sig)) + if(ui_hovering(line_margin_sig) || line_num == params->run_to_line_num) { DR_FStrList fstrs = {0}; Vec4F32 color = ui_color_from_name(s("thread_0")); diff --git a/src/raddbg/raddbg_widgets.h b/src/raddbg/raddbg_widgets.h index a8a34a4c..bb78649d 100644 --- a/src/raddbg/raddbg_widgets.h +++ b/src/raddbg/raddbg_widgets.h @@ -113,6 +113,7 @@ struct RD_CodeSliceParams D_EntityList *line_ips; CFG_NodePtrList *line_pins; U64 *line_vaddrs; + S64 run_to_line_num; D_LineList *line_infos; DI_KeyList relevant_dbgi_keys; TXT_TextInfo *text_info; From c1309e29484a84e2692e66849c4fe108222962b0 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 14:56:24 -0700 Subject: [PATCH 106/251] rdi_from_dwarf: readjust struct hashing, avoid following member ptrs for now --- CHANGELOG.md | 13 +++++++++++++ project.4coder | 2 +- src/mule/mule_main.cpp | 2 -- src/raddbg/raddbg_main.c | 2 +- src/rdi_from_dwarf/rdi_from_dwarf.c | 15 +++++++++------ 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a0f45a7..6c69c219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# v0.9.29-alpha + +## Debugger Changes +- Reintroduced `Browse` buttons for cells that edit file paths, which spawn a + file picker, to more conveniently choose paths. +- The debugger now tags watch expressions with the selected project, which + means they'll be loaded and unloaded along with projects, similar to targets + and breakpoints. +- Empty spaces in the leftmost margin (where the selected thread position is + visualized) in source and disassembly views is now clickable, as a fastpath + for `Run To Line`. +- The position of the destination of a `Run To Line` command is now visualized. + # v0.9.28-alpha ## Debugger Changes diff --git a/project.4coder b/project.4coder index b653e386..b286935f 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin telemetry debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/mule/mule_main.cpp b/src/mule/mule_main.cpp index f5b92f00..2d8e933c 100644 --- a/src/mule/mule_main.cpp +++ b/src/mule/mule_main.cpp @@ -459,8 +459,6 @@ type_coverage_eval_tests(void) const char const_string_array[] = "Hello, World!"; const char *const const_ptr_const_string = "Hello, World!"; - char *test = ")"; - MyByte *non_string_byte_ptr = "Hello, World!"; void *pointer = &basics; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index f7b47811..3e419070 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -87,7 +87,7 @@ // [ ] memory_size(...) view for quickly evaluating memory sizes // [ ] value coloring view in watch window, so you can quickly scroll & see values outside of a threshold // -// [ ] more things should move to user data, but project-tagged - like +// [x] more things should move to user data, but project-tagged - like // recent files, watches?, etc. // // [ ] linux/dwarf fixes diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index a17a535a..7fc368f0 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -1624,18 +1624,18 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) top_task->dependency_count = already_computed_dependency_count; } + //- rjf: unpack task's unit + Rng1U64 unit_info_tag_range = unit_info_tag_ranges[top_task->unit_idx]; + DW2_ParseCtx *unit_parse_ctx = &unit_parse_ctxs[top_task->unit_idx]; + //- rjf: determine if we're done with this hash - B32 done = (found_already_computed_hash || (top_task->depth == 0 && top_task->start_off != top_task->off)); + B32 done = (found_already_computed_hash || (top_task->depth == 0 && top_task->start_off != top_task->off) || !contains_1u64(unit_info_tag_range, top_task->off)); //- rjf: if we're not done -> continue contributing to the task's hash if(!done) { Temp tag_scratch = scratch_begin(&dedup_root_scratch.arena, 1); - // rjf: unpack unit - Rng1U64 unit_info_tag_range = unit_info_tag_ranges[top_task->unit_idx]; - DW2_ParseCtx *unit_parse_ctx = &unit_parse_ctxs[top_task->unit_idx]; - // rjf: read next tag DW2_Tag tag = {0}; U64 start_off = top_task->off; @@ -1707,7 +1707,10 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) // rjf: unpack type reference in this tag DW2_Attrib *type_attrib = &dw2_attrib_nil; U64 type_info_off = 0; - if(include_tag_in_hash) + if(include_tag_in_hash && (!top_task->is_member || (tag.kind != DW_TagKind_PointerType && + tag.kind != DW_TagKind_PtrToMemberType && + tag.kind != DW_TagKind_ReferenceType && + tag.kind != DW_TagKind_RValueReferenceType))) { type_attrib = dw2_attrib_from_kind(&tag, DW_AttribKind_Type); type_info_off = dw2_reference_info_off_from_form_val(unit_parse_ctx, &type_attrib->val); From 653cc98662f4ff6115f8234469886d1563450973 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 15:21:36 -0700 Subject: [PATCH 107/251] do not assume presence of _MOTIF_WM_HINTS --- src/linux/window_manager/linux_window_manager.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 9caca2e9..83a92f9d 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -638,7 +638,10 @@ wm_window_open(Rng2F32 rect, WM_WindowFlags flags, String8 title) }; Hints hints = {.flags = 2, .decorations = 0}; Atom property = XInternAtom(lnx_wm_state->display, "_MOTIF_WM_HINTS", 1); - XChangeProperty(lnx_wm_state->display, w->window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + if(property != None) + { + XChangeProperty(lnx_wm_state->display, w->window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); + } } //- rjf: convert to handle & return From 64daed703082cf8f5673bad1e2ce14503afed1ad Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 15:28:35 -0700 Subject: [PATCH 108/251] keep o.g. debug info extension in rdi filenames --- project.4coder | 2 +- project.raddbg | 2 +- src/dbg_info/dbg_info.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/project.4coder b/project.4coder index b286935f..5de5073c 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin telemetry debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/project.raddbg b/project.raddbg index e1df3c4e..fb4d5d7a 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,7 +7,6 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { @@ -18,6 +17,7 @@ target: { executable: "build/mule_main.exe" working_directory: build + enabled: 1 } target: { diff --git a/src/dbg_info/dbg_info.c b/src/dbg_info/dbg_info.c index df265907..ba0f0f3a 100644 --- a/src/dbg_info/dbg_info.c +++ b/src/dbg_info/dbg_info.c @@ -748,7 +748,7 @@ di_async_tick(void) } else { - rdi_path = str8f(scratch.arena, "%S.rdi", str8_chop_last_dot(og_path)); + rdi_path = str8f(scratch.arena, "%S.rdi", og_path); } } @@ -785,7 +785,7 @@ di_async_tick(void) make_directory(cache_folder); make_directory(raddbg_folder); make_directory(rdis_folder); - rdi_path = str8f(scratch.arena, "%S/%S.rdi", rdis_folder, str8_chop_last_dot(str8_skip_last_slash(og_path))); + rdi_path = str8f(scratch.arena, "%S/%S.rdi", rdis_folder, str8_skip_last_slash(og_path)); } //- rjf: determine if RDI is stale From 8a7be007c13eb52724d72451b644bef85da4438a Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 17:24:43 -0700 Subject: [PATCH 109/251] checkpoint on minidump support --- src/dbg_engine/dbg_engine.mdesk | 2 +- src/dbg_engine/dbg_engine_ctrl.c | 717 +++++++++++++++++------------ src/dbg_engine/dbg_engine_ctrl.h | 9 +- src/linker/lnk.c | 20 +- src/minidump/minidump.h | 21 +- src/pe/pe.c | 13 +- src/pe/pe.h | 96 ++-- src/raddbg/generated/raddbg.meta.c | 2 +- 8 files changed, 530 insertions(+), 350 deletions(-) diff --git a/src/dbg_engine/dbg_engine.mdesk b/src/dbg_engine/dbg_engine.mdesk index 338c6db4..c7c9b904 100644 --- a/src/dbg_engine/dbg_engine.mdesk +++ b/src/dbg_engine/dbg_engine.mdesk @@ -57,7 +57,7 @@ D_CmdTable: // | | | {Attach 1 1 "query:unattached_processes" PID 0 0 0 0 0 1 1 Null "attach" "Attach" "Attaches to a process that is already running on the local machine." "" "" } //- rjf: crash dump opening - {OpenCrashDump 0 0 `folder:\\"$input\\"` FilePath 0 0 0 0 0 1 1 FileOutline "open_crash_dump" "Open Crash Dump" "Opens a crash dump file and recreates debuggee state at the time of the crash." "" "" } + {OpenCrashDump 1 1 `folder:\\"$input\\"` FilePath 0 0 0 0 0 1 1 FileOutline "open_crash_dump" "Open Crash Dump" "Opens a crash dump file and recreates debuggee state at the time of the crash." "" "" } } @enum D_CmdKind: diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index f7318e29..b83871c1 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1751,6 +1751,75 @@ d_cached_sp_from_thread(D_Handle handle) //////////////////////////////// //~ rjf: Module Image Info Functions +//- rjf: module info stores / evictions + +internal void +d_module_info_store(D_Handle module, Arena **stored_arena, D_ModuleInfo *info) +{ + D_ModuleInfoCache *cache = &d_ctrl_state->module_info_cache; + U64 hash = d_hash_from_handle(module); + U64 slot_idx = hash%cache->slots_count; + D_ModuleInfoCacheSlot *slot = &cache->slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&cache->stripes, slot_idx); + MutexScopeW(stripe->rw_mutex) + { + D_ModuleInfoCacheNode *node = 0; + for EachNode(n, D_ModuleInfoCacheNode, slot->first) + { + if(d_handle_match(n->module, module)) + { + node = n; + break; + } + } + if(!node) + { + node = push_array(*stored_arena, D_ModuleInfoCacheNode, 1); + DLLPushBack(slot->first, slot->last, node); + node->module = module; + node->arena = *stored_arena; + node->v = *info; + } + } + *stored_arena = 0; +} + +internal void +d_module_info_evict(D_Handle module) +{ + D_ModuleInfoCache *cache = &d_ctrl_state->module_info_cache; + U64 hash = d_hash_from_handle(module); + U64 slot_idx = hash%cache->slots_count; + Stripe *stripe = stripe_from_slot_idx(&cache->stripes, slot_idx); + D_ModuleInfoCacheSlot *slot = &cache->slots[slot_idx]; + MutexScopeW(stripe->rw_mutex) for(;;) + { + D_ModuleInfoCacheNode *node = 0; + for(D_ModuleInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(d_handle_match(n->module, module)) + { + node = n; + break; + } + } + if(node && access_pt_is_expired(&node->access_pt, .time = 0, .update_idxs = 0)) + { + DLLRemove(slot->first, slot->last, node); + arena_release(node->arena); + break; + } + if(node) + { + cond_var_wait_rw_w(stripe->cv, stripe->rw_mutex, max_U64); + } + else + { + break; + } + } +} + //- rjf: cache lookups internal D_ModuleInfo * @@ -1785,6 +1854,15 @@ d_entry_point_voff_from_module(D_Handle module_handle) return result; } +internal U64 +d_raddbg_attached_marker_voff_from_module(D_Handle module_handle) +{ + Access *access = access_open(); + U64 result = d_info_from_module(access, module_handle)->raddbg_attached_marker_voff; + access_close(access); + return result; +} + internal String8 d_initial_debug_info_path_from_module(Arena *arena, D_Handle module_handle) { @@ -2650,238 +2728,79 @@ d_ctrl_thread__append_program_defined_bp_traps(Arena *arena, D_Entity *bp, DMN_T dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); } -//- rjf: module lifetime open/close work - -internal void -d_ctrl_thread__module_open(D_Handle process, D_Handle module, U64 base_vaddr, DMN_ModuleInfo *module_info) -{ - Temp scratch = scratch_begin(0,0); - - ////////////////////////////// - //- rjf: allocate / set up basic per-module info - // - Arena *arena = arena_alloc(); - String8 raddbg_data = d_data_from_process_vaddr_range(arena, process, shift_1u64(module_info->raddbg_info_voff_range, base_vaddr), 0); - - ////////////////////////////// - //- rjf: prepare unwind info - // - UWND_Unwinder unwinder = UWND_Unwinder_Null; - void *unwind_info_opaque = 0; - { - //- rjf: PE/x64 unwinder - if(dim_1u64(module_info->pe_intel_pdatas_vaddr_range) != 0) - { - unwinder = UWND_Unwinder_PEx64; - PE_X64_UWND_ModuleUnwindInfo *unwind_info = push_array(arena, PE_X64_UWND_ModuleUnwindInfo, 1); - { - U64 pdatas_count = dim_1u64(module_info->pe_intel_pdatas_vaddr_range) / sizeof(PE_IntelPdata); - String8 pdatas_data = d_data_from_process_vaddr_range(arena, process, module_info->pe_intel_pdatas_vaddr_range, 0); - pdatas_count = Min(pdatas_count, pdatas_data.size / sizeof(PE_IntelPdata)); - PE_IntelPdata *pdatas = (PE_IntelPdata *)pdatas_data.str; - unwind_info->pdatas = pdatas; - unwind_info->pdatas_count = pdatas_count; - } - unwind_info_opaque = unwind_info; - } - - //- rjf: .eh_frame unwinder - else if(dim_1u64(module_info->eh_frame_header_vaddr_range) != 0) - { - unwinder = UWND_Unwinder_EHFrame; - EH_UWND_ModuleUnwindInfo *unwind_info = push_array(arena, EH_UWND_ModuleUnwindInfo, 1); - { - String8 eh_frame_hdr_data = d_data_from_process_vaddr_range(arena, process, module_info->eh_frame_header_vaddr_range, 0); - unwind_info->ptr_ctx.pc_vaddr = module_info->eh_frame_header_vaddr_range.min; - unwind_info->ptr_ctx.data_vaddr = module_info->eh_frame_header_vaddr_range.min; - unwind_info->header = eh_parse_frame_hdr(eh_frame_hdr_data, byte_size_from_arch(module_info->arch), &unwind_info->ptr_ctx); - } - unwind_info_opaque = unwind_info; - } - } - - ////////////////////////////// - //- rjf: compute local symbol server cache path for this debug info - // - String8 local_symbol_server_cache_path = smsv_local_path_from_key(scratch.arena, str8_skip_last_slash(module_info->debug_info_path), module_info->debug_info_guid, module_info->debug_info_age); - - ////////////////////////////// - //- rjf: pick default initial debug info path - // - String8 initial_debug_info_path = {0}; - { - String8List candidates = {0}; - - // rjf: push module-embedded debug info paths, try both relative-to-exe & absolute - if(module_info->debug_info_path.size != 0) - { - PathStyle path_style = path_style_from_str8(module_info->debug_info_path); - if(path_style == PathStyle_Relative) - { - String8 module_image_folder = str8_chop_last_slash(module_info->module_path); - str8_list_pushf(scratch.arena, &candidates, "%S/%S", module_image_folder, module_info->debug_info_path); - } - str8_list_push(scratch.arena, &candidates, module_info->debug_info_path); - } - - // rjf: push heuristic-based debug info paths - { - String8 exe_path = module_info->module_path; - String8 exe_path_no_ext = str8_chop_last_dot(exe_path); - str8_list_pushf(scratch.arena, &candidates, "%S.pdb", exe_path_no_ext); - str8_list_pushf(scratch.arena, &candidates, "%S.pdb", exe_path); - str8_list_pushf(scratch.arena, &candidates, "%S.rdi", exe_path_no_ext); - str8_list_pushf(scratch.arena, &candidates, "%S.rdi", exe_path); - } - - // rjf: push local symbol server cache's path - if(local_symbol_server_cache_path.size != 0) - { - str8_list_push(scratch.arena, &candidates, local_symbol_server_cache_path); - } - - // rjf: pick first candidate that works - for EachNode(n, String8Node, candidates.first) - { - String8 candidate_path = n->string; - FileProperties props = properties_from_file_path(candidate_path); - if(props.modified != 0 && props.size != 0) - { - initial_debug_info_path = str8_copy(arena, path_normalized_from_string(scratch.arena, candidate_path)); - break; - } - } - } - - ////////////////////////////// - //- rjf: no found debug info path -> try to fall back on symbol server cache path. - // - // if it exists, we can just use it. if it doesn't, then we only want to pick it *if* - // automatic downloads are enabled. - // - if(initial_debug_info_path.size == 0 && d_ctrl_state->auto_download_debug_info && local_symbol_server_cache_path.size != 0) - { - initial_debug_info_path = str8_copy(arena, local_symbol_server_cache_path); - } - - ////////////////////////////// - //- rjf: write 1 at attachment marker, to signify attachment - // - if(module_info->raddbg_is_attached_marker_voff != 0) - { - U8 new_value = 1; - d_process_write_struct(process, base_vaddr + module_info->raddbg_is_attached_marker_voff, &new_value); - } - - ////////////////////////////// - //- rjf: fill info - // - D_ModuleInfo info = {0}; - { - info.entry_point_voff = module_info->entry_point_voff; - info.unwinder = unwinder; - info.unwind_info = unwind_info_opaque; - info.local_debug_info_path = initial_debug_info_path; - info.dbg_name = str8_copy(arena, str8_skip_last_slash(module_info->debug_info_path)); - info.dbg_guid = module_info->debug_info_guid; - info.dbg_age = module_info->debug_info_age; - info.raddbg_attached_marker_voff = module_info->raddbg_is_attached_marker_voff; - info.raddbg_data = raddbg_data; - } - - ////////////////////////////// - //- rjf: insert info into cache - // - { - D_ModuleInfoCache *cache = &d_ctrl_state->module_info_cache; - U64 hash = d_hash_from_handle(module); - U64 slot_idx = hash%cache->slots_count; - D_ModuleInfoCacheSlot *slot = &cache->slots[slot_idx]; - Stripe *stripe = stripe_from_slot_idx(&cache->stripes, slot_idx); - MutexScopeW(stripe->rw_mutex) - { - D_ModuleInfoCacheNode *node = 0; - for EachNode(n, D_ModuleInfoCacheNode, slot->first) - { - if(d_handle_match(n->module, module)) - { - node = n; - break; - } - } - if(!node) - { - node = push_array(arena, D_ModuleInfoCacheNode, 1); - DLLPushBack(slot->first, slot->last, node); - node->module = module; - node->arena = arena; - node->v = info; - } - } - } - - scratch_end(scratch); -} - -internal void -d_ctrl_thread__module_close(D_Handle process, D_Handle module, U64 base_vaddr) -{ - DMN_Handle process_dmn = d_dmn_from_handle(process); - - ////////////////////////////// - //- rjf: evict module info from cache - // - U64 raddbg_attached_marker_voff = 0; - { - D_ModuleInfoCache *cache = &d_ctrl_state->module_info_cache; - U64 hash = d_hash_from_handle(module); - U64 slot_idx = hash%cache->slots_count; - Stripe *stripe = stripe_from_slot_idx(&cache->stripes, slot_idx); - D_ModuleInfoCacheSlot *slot = &cache->slots[slot_idx]; - MutexScopeW(stripe->rw_mutex) for(;;) - { - D_ModuleInfoCacheNode *node = 0; - for(D_ModuleInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(d_handle_match(n->module, module)) - { - node = n; - break; - } - } - if(node && access_pt_is_expired(&node->access_pt, .time = 0, .update_idxs = 0)) - { - raddbg_attached_marker_voff = node->v.raddbg_attached_marker_voff; - DLLRemove(slot->first, slot->last, node); - arena_release(node->arena); - break; - } - if(node) - { - cond_var_wait_rw_w(stripe->cv, stripe->rw_mutex, max_U64); - } - else - { - break; - } - } - } - - ////////////////////////////// - //- rjf: write 0 at attachment marker, to signify detachment - // - if(raddbg_attached_marker_voff != 0) - { - U8 new_value = 0; - d_process_write_struct(process, base_vaddr + raddbg_attached_marker_voff, &new_value); - } -} - //- rjf: dump process closing work internal void d_ctrl_thread__close_dump_process(D_MsgID msg_id, D_Handle process) { + // rjf: unpack dump cache + D_DumpCache *cache = &d_ctrl_state->dump_cache; + U64 hash = d_hash_from_handle(process); + U64 slot_idx = hash%cache->slots_count; + Stripe *stripe = stripe_from_slot_idx(&cache->stripes, slot_idx); + + // rjf: remove node from cache + D_DumpNode *node = 0; + { + RWMutexScope(stripe->rw_mutex, 1) + { + for(D_DumpNode *n = cache->slots[slot_idx].first; n != 0; n = n->next) + { + if(d_handle_match(n->process, process)) + { + node = n; + break; + } + } + if(node != 0) + { + DLLRemove(cache->slots[slot_idx].first, cache->slots[slot_idx].last, node); + } + } + } + + // rjf: evict module infos + for EachIndex(idx, node->modules_count) + { + d_module_info_evict(node->modules[idx].module_handle); + } + + // rjf: close debug infos + for EachIndex(idx, node->modules_count) + { + D_Entity *module_ent = d_entity_from_handle(node->modules[idx].module_handle); + DI_Key dbgi_key = d_dbgi_key_from_module(module_ent); + di_close(dbgi_key, 0); + } + + // rjf: send events for modules being unloaded + for EachIndex(idx, node->modules_count) + { + Temp scratch = scratch_begin(0, 0); + D_EventList evts = {0}; + D_Event *evt = d_event_list_push(scratch.arena, &evts); + evt->kind = D_EventKind_EndModule; + evt->msg_id = msg_id; + evt->entity = node->modules[idx].module_handle; + evt->string = node->modules[idx].path; + d_c2u_push_events(&evts); + scratch_end(scratch); + } + + // rjf: send events for threads being ended + for EachIndex(idx, node->threads_count) + { + Temp scratch = scratch_begin(0, 0); + D_EventList evts = {0}; + D_Event *evt = d_event_list_push(scratch.arena, &evts); + evt->kind = D_EventKind_EndThread; + evt->msg_id = msg_id; + evt->entity = node->threads[idx].thread_handle; + evt->entity_id = node->threads[idx].id; + d_c2u_push_events(&evts); + scratch_end(scratch); + } + // rjf: send debug event for process closing { Temp scratch = scratch_begin(0, 0); @@ -2899,52 +2818,26 @@ d_ctrl_thread__close_dump_process(D_MsgID msg_id, D_Handle process) ins_atomic_u64_inc_eval(&d_ctrl_state->reg_gen); ins_atomic_u64_inc_eval(&d_ctrl_state->run_gen); - // rjf: eliminate dump node from cache + // rjf: release node contents + if(node != 0) { - D_DumpCache *cache = &d_ctrl_state->dump_cache; - U64 hash = d_hash_from_handle(process); - U64 slot_idx = hash%cache->slots_count; - Stripe *stripe = stripe_from_slot_idx(&cache->stripes, slot_idx); - - // rjf: remove node - D_DumpNode *node = 0; - RWMutexScope(stripe->rw_mutex, 1) + for EachIndex(idx, node->modules_count) { - for(D_DumpNode *n = cache->slots[slot_idx].first; n != 0; n = n->next) - { - if(d_handle_match(n->process, process)) - { - node = n; - break; - } - } - if(node != 0) - { - DLLRemove(cache->slots[slot_idx].first, cache->slots[slot_idx].last, node); - } - } - - // rjf: release node contents - if(node != 0) - { - for EachIndex(idx, node->modules_count) - { - file_map_view_close(node->modules[idx].map, node->modules[idx].base, r1u64(0, node->modules[idx].props.size)); - file_map_close(node->modules[idx].map); - file_close(node->modules[idx].file); - } - file_map_view_close(node->map, node->base, r1u64(0, node->props.size)); - file_map_close(node->map); - file_close(node->file); - arena_release(node->arena); - } - - // rjf: attach node to free list - if(node != 0) RWMutexScope(stripe->rw_mutex, 1) - { - node->next = (D_DumpNode *)stripe->free; - stripe->free = node; + file_map_view_close(node->modules[idx].map, node->modules[idx].base, r1u64(0, node->modules[idx].props.size)); + file_map_close(node->modules[idx].map); + file_close(node->modules[idx].file); } + file_map_view_close(node->map, node->base, r1u64(0, node->props.size)); + file_map_close(node->map); + file_close(node->file); + arena_release(node->arena); + } + + // rjf: attach node to free list + if(node != 0) RWMutexScope(stripe->rw_mutex, 1) + { + node->next = (D_DumpNode *)stripe->free; + stripe->free = node; } } @@ -3208,8 +3101,11 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D ProfScope("push ctrl events associated with this demon event") switch(event->kind) { default:{}break; + + //- rjf: process creation case DMN_EventKind_CreateProcess: { + // rjf: process ID -> message ID D_MsgID msg_id = 0; for(D_Entity *entry = d_ctrl_state->ctrl_thread_entity_store->ctx.root->first; entry != &d_entity_nil; @@ -3222,6 +3118,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D break; } } + + // rjf: push D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_NewProc; out_evt->msg_id = msg_id; @@ -3231,6 +3129,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->os = OperatingSystem_CURRENT; // TODO: operating system of the remote target machine d_ctrl_state->process_counter += 1; }break; + + //- rjf: create thread case DMN_EventKind_CreateThread: { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); @@ -3245,14 +3145,144 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->rip_vaddr = event->instruction_pointer; out_evt->string = event->string; }break; + + //- rjf: load module case DMN_EventKind_LoadModule: { + Temp scratch2 = scratch_begin(&scratch.arena, 1); + + //- rjf: unpack module D_Handle process_handle = d_handle_from_dmn(D_MachineID_Local, event->process); D_Handle module_handle = d_handle_from_dmn(D_MachineID_Local, event->module); - D_Event *out_evt1 = d_event_list_push(scratch.arena, &evts); - String8 module_path = path_normalized_from_string(scratch.arena, event->string); + String8 module_path = path_normalized_from_string(scratch2.arena, event->string); U64 exe_timestamp = properties_from_file_path(module_path).modified; - d_ctrl_thread__module_open(process_handle, module_handle, event->address, event->module_info); + DMN_ModuleInfo *module_info = event->module_info; + U64 base_vaddr = event->address; + + //- rjf: allocate / set up basic per-module info + Arena *arena = arena_alloc(); + String8 raddbg_data = d_data_from_process_vaddr_range(arena, process_handle, shift_1u64(module_info->raddbg_info_voff_range, base_vaddr), 0); + + //- rjf: prepare unwind info + UWND_Unwinder unwinder = UWND_Unwinder_Null; + void *unwind_info_opaque = 0; + { + //- rjf: PE/x64 unwinder + if(dim_1u64(module_info->pe_intel_pdatas_vaddr_range) != 0) + { + unwinder = UWND_Unwinder_PEx64; + PE_X64_UWND_ModuleUnwindInfo *unwind_info = push_array(arena, PE_X64_UWND_ModuleUnwindInfo, 1); + { + U64 pdatas_count = dim_1u64(module_info->pe_intel_pdatas_vaddr_range) / sizeof(PE_IntelPdata); + String8 pdatas_data = d_data_from_process_vaddr_range(arena, process_handle, module_info->pe_intel_pdatas_vaddr_range, 0); + pdatas_count = Min(pdatas_count, pdatas_data.size / sizeof(PE_IntelPdata)); + PE_IntelPdata *pdatas = (PE_IntelPdata *)pdatas_data.str; + unwind_info->pdatas = pdatas; + unwind_info->pdatas_count = pdatas_count; + } + unwind_info_opaque = unwind_info; + } + + //- rjf: .eh_frame unwinder + else if(dim_1u64(module_info->eh_frame_header_vaddr_range) != 0) + { + unwinder = UWND_Unwinder_EHFrame; + EH_UWND_ModuleUnwindInfo *unwind_info = push_array(arena, EH_UWND_ModuleUnwindInfo, 1); + { + String8 eh_frame_hdr_data = d_data_from_process_vaddr_range(arena, process_handle, module_info->eh_frame_header_vaddr_range, 0); + unwind_info->ptr_ctx.pc_vaddr = module_info->eh_frame_header_vaddr_range.min; + unwind_info->ptr_ctx.data_vaddr = module_info->eh_frame_header_vaddr_range.min; + unwind_info->header = eh_parse_frame_hdr(eh_frame_hdr_data, byte_size_from_arch(module_info->arch), &unwind_info->ptr_ctx); + } + unwind_info_opaque = unwind_info; + } + } + + //- rjf: compute local symbol server cache path for this debug info + String8 local_symbol_server_cache_path = smsv_local_path_from_key(scratch2.arena, str8_skip_last_slash(module_info->debug_info_path), module_info->debug_info_guid, module_info->debug_info_age); + + //- rjf: pick default initial debug info path + String8 initial_debug_info_path = {0}; + { + String8List candidates = {0}; + + // rjf: push module-embedded debug info paths, try both relative-to-exe & absolute + if(module_info->debug_info_path.size != 0) + { + PathStyle path_style = path_style_from_str8(module_info->debug_info_path); + if(path_style == PathStyle_Relative) + { + String8 module_image_folder = str8_chop_last_slash(module_info->module_path); + str8_list_pushf(scratch2.arena, &candidates, "%S/%S", module_image_folder, module_info->debug_info_path); + } + str8_list_push(scratch2.arena, &candidates, module_info->debug_info_path); + } + + // rjf: push heuristic-based debug info paths + { + String8 exe_path = module_info->module_path; + String8 exe_path_no_ext = str8_chop_last_dot(exe_path); + str8_list_pushf(scratch2.arena, &candidates, "%S.pdb", exe_path_no_ext); + str8_list_pushf(scratch2.arena, &candidates, "%S.pdb", exe_path); + str8_list_pushf(scratch2.arena, &candidates, "%S.rdi", exe_path_no_ext); + str8_list_pushf(scratch2.arena, &candidates, "%S.rdi", exe_path); + } + + // rjf: push local symbol server cache's path + if(local_symbol_server_cache_path.size != 0) + { + str8_list_push(scratch2.arena, &candidates, local_symbol_server_cache_path); + } + + // rjf: pick first candidate that works + for EachNode(n, String8Node, candidates.first) + { + String8 candidate_path = n->string; + FileProperties props = properties_from_file_path(candidate_path); + if(props.modified != 0 && props.size != 0) + { + initial_debug_info_path = str8_copy(arena, path_normalized_from_string(scratch2.arena, candidate_path)); + break; + } + } + } + + //- rjf: no found debug info path -> try to fall back on symbol server cache path. + // + // if it exists, we can just use it. if it doesn't, then we only want to pick it *if* + // automatic downloads are enabled. + // + if(initial_debug_info_path.size == 0 && d_ctrl_state->auto_download_debug_info && local_symbol_server_cache_path.size != 0) + { + initial_debug_info_path = str8_copy(arena, local_symbol_server_cache_path); + } + + //- rjf: write 1 at attachment marker, to signify attachment + if(module_info->raddbg_is_attached_marker_voff != 0) + { + U8 new_value = 1; + d_process_write_struct(process_handle, base_vaddr + module_info->raddbg_is_attached_marker_voff, &new_value); + } + + //- rjf: fill info + D_ModuleInfo info = {0}; + { + info.entry_point_voff = module_info->entry_point_voff; + info.unwinder = unwinder; + info.unwind_info = unwind_info_opaque; + info.local_debug_info_path = initial_debug_info_path; + info.dbg_name = str8_copy(arena, str8_skip_last_slash(module_info->debug_info_path)); + info.dbg_guid = module_info->debug_info_guid; + info.dbg_age = module_info->debug_info_age; + info.raddbg_attached_marker_voff = module_info->raddbg_is_attached_marker_voff; + info.raddbg_data = raddbg_data; + } + + //- rjf: insert info into cache + d_module_info_store(module_handle, &arena, &info); + + //- rjf: push module load + D_Event *out_evt1 = d_event_list_push(scratch.arena, &evts); out_evt1->kind = D_EventKind_NewModule; out_evt1->msg_id = msg->msg_id; out_evt1->entity = module_handle; @@ -3265,8 +3295,9 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt1->string = module_path; out_evt1->tls_index = event->module_info->tls_index; out_evt1->tls_offset = event->module_info->tls_offset; + + //- rjf: push initial debug info path set D_Event *out_evt2 = d_event_list_push(scratch.arena, &evts); - String8 initial_debug_info_path = d_initial_debug_info_path_from_module(scratch.arena, module_handle); U64 debug_info_timestamp = properties_from_file_path(initial_debug_info_path).modified; out_evt2->kind = D_EventKind_ModuleDebugInfoPathChange; out_evt2->msg_id = msg->msg_id; @@ -3274,9 +3305,14 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt2->parent = process_handle; out_evt2->timestamp = debug_info_timestamp; out_evt2->string = initial_debug_info_path; + + // rjf: open debug info DI_Key initial_dbgi_key = di_key_from_path_timestamp(initial_debug_info_path, debug_info_timestamp); di_open(initial_dbgi_key); + scratch_end(scratch2); }break; + + //- rjf: exit process case DMN_EventKind_ExitProcess: { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); @@ -3286,6 +3322,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->u64_code = event->code; d_ctrl_state->process_counter -= 1; }break; + + //- rjf: exit thread case DMN_EventKind_ExitThread: { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); @@ -3294,22 +3332,42 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->thread); out_evt->entity_id = event->code; }break; + + //- rjf: unload module case DMN_EventKind_UnloadModule: ProfScope("unload module %.*s", str8_varg(event->string)) { - D_Event *out_evt = d_event_list_push(scratch.arena, &evts); + //- rjf: unpack module D_Handle module_handle = d_handle_from_dmn(D_MachineID_Local, event->module); D_Entity *module_ent = d_entity_from_handle(module_handle); D_Entity *process_ent = d_process_from_entity(module_ent); String8 module_path = event->string; - d_ctrl_thread__module_close(process_ent->handle, module_handle, module_ent->vaddr_range.min); + U64 base_vaddr = module_ent->vaddr_range.min; + U64 raddbg_attached_marker_voff = d_raddbg_attached_marker_voff_from_module(module_handle); + + //- rjf: evict module info from cache + d_module_info_evict(module_handle); + + //- rjf: write 0 at attachment marker, to signify detachment + if(raddbg_attached_marker_voff != 0) + { + U8 new_value = 0; + d_process_write_struct(process_ent->handle, base_vaddr + raddbg_attached_marker_voff, &new_value); + } + + //- rjf: push event + D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_EndModule; out_evt->msg_id = msg->msg_id; out_evt->entity = module_handle; out_evt->string = module_path; + + //- rjf: close debug info DI_Key dbgi_key = d_dbgi_key_from_module(module_ent); di_close(dbgi_key, 0); }break; + + //- rjf: debug string case DMN_EventKind_DebugString: { U64 num_strings = (event->string.size + d_ctrl_state->c2u_ring_max_string_size-1) / d_ctrl_state->c2u_ring_max_string_size; @@ -3323,6 +3381,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->string = str8_substr(event->string, r1u64(string_idx*d_ctrl_state->c2u_ring_max_string_size, (string_idx+1)*d_ctrl_state->c2u_ring_max_string_size)); } }break; + + //- rjf: thread names case DMN_EventKind_SetThreadName: { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); @@ -3333,6 +3393,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->string = event->string; out_evt->entity_id = event->code; }break; + + //- rjf: thread color case DMN_EventKind_SetThreadColor: { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); @@ -3343,6 +3405,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->entity_id = event->code; out_evt->rgba = event->user_data; }break; + + //- rjf: vaddr range notes case DMN_EventKind_SetVAddrRangeNote: { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); @@ -3352,6 +3416,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->vaddr_rng = r1u64(event->address, event->address + event->size); out_evt->string = event->string; }break; + + //- rjf: programmatic breakpoint sets case DMN_EventKind_SetBreakpoint: { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); @@ -3361,6 +3427,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); out_evt->bp_flags = d_breakpoint_flags_from_dmn_trap_flags(event->flags); }break; + + //- rjf: programmatic breakpoint unsets case DMN_EventKind_UnsetBreakpoint: { // TODO(rjf): this needs to be reflected in the resolved trap list too!!!!!!!! @@ -3858,7 +3926,9 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { Temp scratch = scratch_begin(0, 0); + ////////////////////////////// //- rjf: unpack path + // String8 path = msg->path; File file = file_open(AccessFlag_Read, path); FileProperties props = properties_from_file(file); @@ -3866,7 +3936,9 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) void *base = file_map_view_open(map, AccessFlag_Read, r1u64(0, props.size)); String8 data = str8(base, base ? props.size : 0); + ////////////////////////////// //- rjf: try to parse file data as a dump file, extract abstracted (over format) dump info + // B32 stored = 0; D_EventList evts = {0}; Arena *arena = 0; @@ -3880,7 +3952,9 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) Arch process_arch = Arch_Null; OperatingSystem process_os = OperatingSystem_Null; { + //////////////////////////// //- rjf: try minidump parse -> construct events for entity creation + // { MDMP_Header header = {0}; str8_deserial_read_struct(data, 0, &header); @@ -3954,7 +4028,6 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) }break; case MDMP_StreamKind_Memory64List: { - U64 memories64_count = 0; U64 off = dir->location.foff; off += str8_deserial_read_struct(data, off, &memories64_count); off += str8_deserial_read_struct(data, off, &memories64_base_foff); @@ -3992,10 +4065,21 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) case MDMP_Arch_x64:{process_arch = Arch_x64;}break; } + // rjf: system info -> os + if(system_info != 0) switch(system_info->platform_id) + { + default:{}break; + case MDMP_PlatformID_Win32s: + case MDMP_PlatformID_Win32Windows: + case MDMP_PlatformID_Win32NT: + { + process_os = OperatingSystem_Windows; + }break; + } + // rjf: create process handle d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; process = d_dump_handle_make(D_MachineID_Local, d_ctrl_state->ctrl_thread_dump_handle_id_gen); - process_os = OperatingSystem_Windows; // NOTE(rjf): minidumps are always windows // rjf: gather threads dump_threads_count = threads_count; @@ -4035,6 +4119,7 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) FileProperties module_props = properties_from_file(module_file); FileMap module_map = file_map_open(AccessFlag_Read, module_file); void *module_base = file_map_view_open(module_map, AccessFlag_Read, r1u64(0, module_props.size)); + String8 module_data = str8(module_base, module_props.size); // rjf: store d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; @@ -4050,7 +4135,9 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) } } + ////////////////////////////// //- rjf: record process creation + // { D_Event *evt = d_event_list_push(scratch.arena, &evts); evt->kind = D_EventKind_NewProc; @@ -4061,7 +4148,9 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) evt->string = path; } + ////////////////////////////// //- rjf: store the dump parse artifacts in the dump cache, otherwise release + // if(stored) { U64 hash = d_hash_from_handle(process); @@ -4105,7 +4194,9 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) file_close(file); } + ////////////////////////////// //- rjf: record thread creation + // for EachIndex(idx, dump_threads_count) { D_Handle thread_handle = dump_threads[idx].thread_handle; @@ -4120,24 +4211,84 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) // TODO(rjf): evt->tls_root = ; (use thread->thread_context) } + ////////////////////////////// //- rjf: record module loading + // for EachIndex(idx, dump_modules_count) { - // rjf: open module + //- rjf: open module D_Handle module_handle = dump_modules[idx].module_handle; Rng1U64 vaddr_range = dump_modules[idx].vaddr_range; + #if 0 - // TODO(rjf): I don't think we can use this demon module info path here + //- TODO(rjf): I don't think we can use this demon module info path here DMN_ModuleInfo module_info = {0}; d_ctrl_thread__module_open(process, module_handle, vaddr_range.min, dump_modules[idx].path, r1u64(0, 0), 0); #endif - // rjf: open debug info + + //- rjf: analyze module file, produce module info + Arena *module_info_arena = arena_alloc(); + D_ModuleInfo module_info = {0}; + { + String8 module_data = str8((U8 *)dump_modules[idx].base, dump_modules[idx].props.size); + + //- rjf: PEs + PE_BinInfo pe = pe_bin_info_from_data(scratch.arena, module_data); + if(pe.arch != Arch_Null) + { + //- rjf: fill basics + module_info.entry_point_voff = pe.entry_point; + + //- rjf: fill unwinder info + switch(pe.arch) + { + default:{}break; + case Arch_x64: + if(PE_DataDirectoryIndex_EXCEPTIONS < pe.data_dir_count) + { + Rng1U64 pdatas_foff_range = pe.data_dir_franges[PE_DataDirectoryIndex_EXCEPTIONS]; + U64 pdatas_count_supposedly = dim_1u64(pdatas_foff_range) / sizeof(PE_IntelPdata); + U64 pdatas_count_possible = pdatas_foff_range.min < module_data.size ? (module_data.size - pdatas_foff_range.min) / sizeof(PE_IntelPdata) : 0; + U64 pdatas_count = Min(pdatas_count_possible, pdatas_count_supposedly); + PE_X64_UWND_ModuleUnwindInfo *module_unwind_info = push_array(module_info_arena, PE_X64_UWND_ModuleUnwindInfo, 1); + module_unwind_info->pdatas_count = pdatas_count; + module_unwind_info->pdatas = (PE_IntelPdata *)(module_data.str + pdatas_foff_range.min); + module_info.unwinder = UWND_Unwinder_PEx64; + module_info.unwind_info = module_unwind_info; + }break; + } + + //- rjf: fill debug path info + { + String8 raw_debug_dir = str8_substr(module_data, pe.data_dir_franges[PE_DataDirectoryIndex_DEBUG]); + PE_DebugInfoList debug_dir = pe_debug_info_list_from_raw_debug_dir(scratch.arena, module_data, raw_debug_dir); + for(PE_DebugInfoNode *n = debug_dir.first; n != 0; n = n->next) + { + if(n->v.path.size != 0) + { + module_info.local_debug_info_path = str8_copy(module_info_arena, n->v.path); + module_info.dbg_name = str8_skip_last_slash(module_info.local_debug_info_path); + module_info.dbg_guid = n->v.guid; + module_info.dbg_age = n->v.age; + break; + } + } + } + + //- TODO(rjf): still missing: raddbg data (rare but in theory possible) + } + } + + //- rjf: store module info + d_module_info_store(module_handle, &module_info_arena, &module_info); + + //- rjf: open debug info String8 initial_debug_info_path = d_initial_debug_info_path_from_module(scratch.arena, module_handle); U64 debug_info_timestamp = properties_from_file_path(initial_debug_info_path).modified; DI_Key initial_dbgi_key = di_key_from_path_timestamp(initial_debug_info_path, debug_info_timestamp); di_open(initial_dbgi_key); - // rjf: push module load event + //- rjf: push module load event { D_Event *evt = d_event_list_push(scratch.arena, &evts); evt->kind = D_EventKind_NewModule; @@ -4151,7 +4302,7 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) // TODO(rjf): evt->tls_root = ; (use thread->thread_context) } - // rjf: push debug info initial path set event + //- rjf: push debug info initial path set event { D_Event *evt = d_event_list_push(scratch.arena, &evts); evt->kind = D_EventKind_ModuleDebugInfoPathChange; diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index dd83ff99..5f61ae3c 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -549,9 +549,14 @@ internal U64 d_cached_sp_from_thread(D_Handle handle); //////////////////////////////// //~ rjf: Module Image Info Functions +//- rjf: module info stores / evictions +internal void d_module_info_store(D_Handle module, Arena **stored_arena, D_ModuleInfo *info); +internal void d_module_info_evict(D_Handle module); + //- rjf: cache lookups internal D_ModuleInfo *d_info_from_module(Access *access, D_Handle module); internal U64 d_entry_point_voff_from_module(D_Handle module_handle); +internal U64 d_raddbg_attached_marker_voff_from_module(D_Handle module_handle); internal String8 d_initial_debug_info_path_from_module(Arena *arena, D_Handle module_handle); //////////////////////////////// @@ -600,10 +605,6 @@ internal void d_ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, internal void d_ctrl_thread__append_resolved_process_user_bp_traps(Arena *arena, D_EvalScope *eval_scope, D_Handle process, D_BreakpointList *user_bps, DMN_TrapChunkList *traps_out); internal void d_ctrl_thread__append_program_defined_bp_traps(Arena *arena, D_Entity *bp, DMN_TrapChunkList *traps_out); -//- rjf: module lifetime open/close work -internal void d_ctrl_thread__module_open(D_Handle process, D_Handle module, U64 base_vaddr, DMN_ModuleInfo *module_info); -internal void d_ctrl_thread__module_close(D_Handle process, D_Handle module, U64 base_vaddr); - //- rjf: dump process closing work internal void d_ctrl_thread__close_dump_process(D_MsgID msg_id, D_Handle process); diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 75d96ed9..dbf4f577 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -5969,7 +5969,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT U32 load_config_size = 0; if (sizeof(load_config_size) <= load_config_data.size) { MemoryCopyStruct(&load_config_size, load_config_data.str); // TODO: load config - PE_DataDirectory *load_config_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_LOAD_CONFIG); + PE_DataDirectory *load_config_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_LOAD_CONFIG); load_config_dir->virt_off = lnk_voff_from_symbol(image_section_table, load_config_symbol); load_config_dir->virt_size = load_config_size; } else { @@ -5985,7 +5985,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT String8 raw_pdata = str8_substr(image_data, rng_1u64(pdata_sect->foff, pdata_sect->foff + pdata_sect->vsize)); pe_pdata_sort(config->machine, raw_pdata); - PE_DataDirectory *pdata_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_EXCEPTIONS); + PE_DataDirectory *pdata_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_EXCEPTIONS); pdata_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, pdata_sect); pdata_dir->virt_size = lnk_get_section_contrib_size(pdata_sect); } @@ -5995,7 +5995,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT { LNK_Section *edata_sect = lnk_image_section_table_search(config, sectab, str8_lit(".edata"), PE_EDATA_SECTION_FLAGS); if (edata_sect) { - PE_DataDirectory *export_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_EXPORT); + PE_DataDirectory *export_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_EXPORT); LNK_SectionContrib *edata_first_contrib = lnk_get_first_section_contrib(edata_sect); LNK_SectionContrib *edata_last_contrib = lnk_get_last_section_contrib(edata_sect); export_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, edata_sect); @@ -6007,7 +6007,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT { LNK_Section *reloc_sect = lnk_image_section_table_search(config, sectab, str8_lit(".reloc"), PE_RELOC_SECTION_FLAGS); if (reloc_sect) { - PE_DataDirectory *reloc_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_BASE_RELOC); + PE_DataDirectory *reloc_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_BASE_RELOC); reloc_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, reloc_sect); reloc_dir->virt_size = lnk_get_section_contrib_size(reloc_sect); } @@ -6021,7 +6021,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT if (idata_sect && null_import_desc && null_thunk_data) { COFF_ParsedSymbol null_import_desc_parsed = lnk_parsed_from_symbol(null_import_desc); LNK_SectionContrib *idata_first_contrib = lnk_get_first_section_contrib(idata_sect); - PE_DataDirectory *import_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_IMPORT); + PE_DataDirectory *import_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_IMPORT); import_dir->virt_off = image_section_table[idata_first_contrib->u.sect_idx + 1]->voff + idata_first_contrib->u.off; import_dir->virt_size = null_import_desc_parsed.value - idata_first_contrib->u.off + sizeof(PE_ImportEntry); @@ -6029,7 +6029,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT U64 null_thunk_data_voff = image_section_table[null_thunk_data_parsed.section_number]->voff + null_thunk_data_parsed.value; U64 first_import_foff = image_section_table[idata_first_contrib->u.sect_idx+1]->foff + idata_first_contrib->u.off; PE_ImportEntry *first_import = str8_deserial_get_raw_ptr(image_data, first_import_foff, sizeof(*first_import)); - PE_DataDirectory *import_addr_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_IMPORT_ADDR); + PE_DataDirectory *import_addr_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_IMPORT_ADDR); import_addr_dir->virt_off = first_import->import_addr_table_voff; import_addr_dir->virt_size = null_thunk_data_voff - first_import->import_addr_table_voff /* null */ + coff_word_size_from_machine(config->machine); } @@ -6043,7 +6043,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT if (didat_sect && null_import_desc && last_null_thunk) { COFF_ParsedSymbol null_import_desc_parsed = lnk_parsed_from_symbol(null_import_desc); LNK_SectionContrib *didat_first_contrib = lnk_get_first_section_contrib(didat_sect); - PE_DataDirectory *import_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_DELAY_IMPORT); + PE_DataDirectory *import_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_DELAY_IMPORT); import_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, didat_sect); import_dir->virt_size = lnk_get_section_contrib_size(didat_sect); } @@ -6077,7 +6077,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT } // patch directory - PE_DataDirectory *tls_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_TLS); + PE_DataDirectory *tls_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_TLS); tls_dir->virt_off = lnk_voff_from_symbol(image_section_table, tls_used_symbol); tls_dir->virt_size = is_tls_header64 ? sizeof(PE_TLSHeader64) : sizeof(PE_TLSHeader32); @@ -6090,7 +6090,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT LNK_Section *debug_dir_sect = lnk_image_section_table_search(config, sectab, str8_lit(".RAD_LINK_PE_DEBUG_DIR"), PE_RDATA_SECTION_FLAGS); if (debug_dir_sect) { // patch directory - PE_DataDirectory *debug_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_DEBUG); + PE_DataDirectory *debug_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_DEBUG); debug_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, debug_dir_sect); debug_dir->virt_size = lnk_get_section_contrib_size(debug_dir_sect); @@ -6116,7 +6116,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT { LNK_Section *rsrc_sect = lnk_image_section_table_search(config, sectab, str8_lit(".rsrc"), PE_RSRC_SECTION_FLAGS); if (rsrc_sect) { - PE_DataDirectory *rsrc_dir = pe_data_directory_from_idx(image_data, pe, PE_DataDirectoryIndex_RESOURCES); + PE_DataDirectory *rsrc_dir = pe_data_directory_from_idx(image_data, &pe, PE_DataDirectoryIndex_RESOURCES); rsrc_dir->virt_off = lnk_get_first_section_contrib_voff(image_section_table, rsrc_sect); rsrc_dir->virt_size = lnk_get_section_contrib_size(rsrc_sect); } diff --git a/src/minidump/minidump.h b/src/minidump/minidump.h index aab59e03..7e68a8db 100644 --- a/src/minidump/minidump.h +++ b/src/minidump/minidump.h @@ -210,6 +210,25 @@ typedef enum MDMP_ArchEnum } MDMP_ArchEnum; +typedef U32 MDMP_PlatformID; +typedef enum MDMP_PlatformIDEnum +{ + MDMP_PlatformID_Win32s = 0x0, // Windows 3.1 + MDMP_PlatformID_Win32Windows = 0x1, // Windows 95, 98, Me + MDMP_PlatformID_Win32NT = 0x2, // Windows NT, 2000, XP, & later + //- rjf: extensions + MDMP_PlatformID_Unix = 0x8000, + MDMP_PlatformID_MacOSX = 0x8101, + MDMP_PlatformID_IOS = 0x8102, + MDMP_PlatformID_Linux = 0x8201, + MDMP_PlatformID_Solaris = 0x8202, + MDMP_PlatformID_Android = 0x8203, + MDMP_PlatformID_PS3 = 0x8204, + MDMP_PlatformID_NaCl = 0x8205, + MDMP_PlatformID_Fuchsia = 0x8206, +} +MDMP_PlatformIDEnum; + typedef struct MDMP_SystemInfo MDMP_SystemInfo; struct MDMP_SystemInfo { @@ -221,7 +240,7 @@ struct MDMP_SystemInfo U32 major_version; U32 minor_version; U32 build_number; - U32 platform_id; + MDMP_PlatformID platform_id; U32 csd_version_rva; U16 suite_mask; U16 padding; diff --git a/src/pe/pe.c b/src/pe/pe.c index 85449aeb..90a06fbd 100644 --- a/src/pe/pe.c +++ b/src/pe/pe.c @@ -361,7 +361,7 @@ pe_string_from_file_characteristics(Arena *arena, PE_ImageFileCharacteristics fi #define X(ID, VAL, DISPLAY_STRING) case (1 << VAL): str8_list_push(scratch.arena, &result, str8_lit(DISPLAY_STRING)); break; PE_ImageFileCharacteristic_XList #undef X - default: str8_list_pushf(scratch.arena, &result, "%llu", i); + default: str8_list_pushf(scratch.arena, &result, "%llu", i); } } String8 string = str8_list_join(arena, &result, &(StringJoin){.sep=str8_lit(", ")}); @@ -649,11 +649,12 @@ pe_bin_info_from_data(Arena *arena, String8 data) } internal PE_DataDirectory * -pe_data_directory_from_idx(String8 file_data, PE_BinInfo pe, PE_DataDirectoryIndex dir_idx) +pe_data_directory_from_idx(String8 file_data, PE_BinInfo *pe, PE_DataDirectoryIndex dir_idx) { PE_DataDirectory *result = 0; - if (dir_idx < pe.data_dir_count) { - result = str8_deserial_get_raw_ptr(file_data, pe.data_dir_range.min + sizeof(*result)*dir_idx, sizeof(*result)); + if(dir_idx < pe->data_dir_count) + { + result = str8_deserial_get_raw_ptr(file_data, pe->data_dir_range.min + sizeof(*result)*dir_idx, sizeof(*result)); } return result; } @@ -688,6 +689,7 @@ pe_debug_info_list_from_raw_debug_dir(Arena *arena, String8 raw_image, String8 r str8_deserial_read_cstr(raw_image, entry->foff+sizeof(cv), &path); n->v.cv_pdb20_header = cv; n->v.path = path; + n->v.age = cv.age; } }break; case PE_CODEVIEW_PDB70_MAGIC: @@ -700,6 +702,8 @@ pe_debug_info_list_from_raw_debug_dir(Arena *arena, String8 raw_image, String8 r str8_deserial_read_cstr(raw_image, entry->foff+sizeof(cv), &path); n->v.cv_pdb70_header = cv; n->v.path = path; + n->v.guid = cv.guid; + n->v.age = cv.age; } }break; case PE_CODEVIEW_RDI_MAGIC: @@ -712,6 +716,7 @@ pe_debug_info_list_from_raw_debug_dir(Arena *arena, String8 raw_image, String8 r str8_deserial_read_cstr(raw_image, entry->foff+sizeof(cv), &path); n->v.cv_rdi_header = cv; n->v.path = path; + n->v.guid = cv.guid; } }break; default:{}break; diff --git a/src/pe/pe.h b/src/pe/pe.h index be8a34df..84caf572 100644 --- a/src/pe/pe.h +++ b/src/pe/pe.h @@ -54,22 +54,22 @@ enum }; #define PE_ImageFileCharacteristic_XList \ - X(RELOCS_STRIPPED , 0 , "Relocations Stripped" ) \ - X(EXECUTABLE_IMAGE , 1 , "Executable" ) \ - X(LINE_NUMS_STRIPPED , 2 , "Line Numbers Stripped" ) \ - X(LOCAL_SYMS_STRIPPED , 3 , "Local Symbols Stripped" ) \ - X(AGGRESSIVE_WS_TRIM , 4 , "Aggressive Working Set Trim") \ - X(LARGE_ADDRESS_AWARE , 5 , "Large Address Aware" ) \ - X(RESERVED , 6 , "Reserved" ) \ - X(BYTES_REVERSED_LO , 7 , "Bytes Reversed Low" ) \ - X(32BIT_MACHINE , 8 , "32-Bit Machine" ) \ - X(DEBUG_STRIPPED , 9 , "Debug Information Stripped" ) \ - X(REMOVABLE_RUN_FROM_SWAP, 10, "Removable: Run from Swap" ) \ - X(NET_RUN_FROM_SWAP , 11, "Network: Run from Swap" ) \ - X(SYSTEM , 12, "System File" ) \ - X(DLL , 13, "DLL" ) \ - X(UP_SYSTEM_ONLY , 14, "Uniprocessor System Only" ) \ - X(BYTES_REVERSED_HI , 15, "Bytes Reversed High" ) +X(RELOCS_STRIPPED , 0 , "Relocations Stripped" ) \ +X(EXECUTABLE_IMAGE , 1 , "Executable" ) \ +X(LINE_NUMS_STRIPPED , 2 , "Line Numbers Stripped" ) \ +X(LOCAL_SYMS_STRIPPED , 3 , "Local Symbols Stripped" ) \ +X(AGGRESSIVE_WS_TRIM , 4 , "Aggressive Working Set Trim") \ +X(LARGE_ADDRESS_AWARE , 5 , "Large Address Aware" ) \ +X(RESERVED , 6 , "Reserved" ) \ +X(BYTES_REVERSED_LO , 7 , "Bytes Reversed Low" ) \ +X(32BIT_MACHINE , 8 , "32-Bit Machine" ) \ +X(DEBUG_STRIPPED , 9 , "Debug Information Stripped" ) \ +X(REMOVABLE_RUN_FROM_SWAP, 10, "Removable: Run from Swap" ) \ +X(NET_RUN_FROM_SWAP , 11, "Network: Run from Swap" ) \ +X(SYSTEM , 12, "System File" ) \ +X(DLL , 13, "DLL" ) \ +X(UP_SYSTEM_ONLY , 14, "Uniprocessor System Only" ) \ +X(BYTES_REVERSED_HI , 15, "Bytes Reversed High" ) typedef U16 PE_ImageFileCharacteristics; enum @@ -1006,28 +1006,29 @@ struct PE_HandlerScope typedef struct PE_BinInfo PE_BinInfo; struct PE_BinInfo { - Arch arch; - U64 image_base; - U64 entry_point; - B32 is_pe32; - PE_WindowsSubsystem subsystem; - U32 *check_sum; - U64 virt_section_align; - U64 file_section_align; - U64 section_count; - U64 symbol_count; - U64 optional_header_off; - Rng1U64 section_table_range; - Rng1U64 symbol_table_range; - Rng1U64 string_table_range; - Rng1U64 data_dir_range; - Rng1U64 *data_dir_franges; - Rng1U64 *data_dir_vranges; - U32 data_dir_count; - PE_TLSHeader64 tls_header; + Arch arch; + U64 image_base; + U64 entry_point; + B32 is_pe32; + PE_WindowsSubsystem subsystem; + U32 *check_sum; + U64 virt_section_align; + U64 file_section_align; + U64 section_count; + U64 symbol_count; + U64 optional_header_off; + Rng1U64 section_table_range; + Rng1U64 symbol_table_range; + Rng1U64 string_table_range; + Rng1U64 data_dir_range; + Rng1U64 *data_dir_franges; + Rng1U64 *data_dir_vranges; + U32 data_dir_count; + PE_TLSHeader64 tls_header; }; -typedef struct PE_DebugInfo +typedef struct PE_DebugInfo PE_DebugInfo; +struct PE_DebugInfo { PE_DebugDirectory header; U32 cv_magic; @@ -1035,21 +1036,24 @@ typedef struct PE_DebugInfo PE_CvHeaderPDB70 cv_pdb70_header; PE_CvHeaderRDI cv_rdi_header; String8 path; -} -PE_DebugInfo; + Guid guid; + U32 age; +}; -typedef struct PE_DebugInfoNode +typedef struct PE_DebugInfoNode PE_DebugInfoNode; +struct PE_DebugInfoNode { - struct PE_DebugInfoNode *next; - PE_DebugInfo v; -} PE_DebugInfoNode; + PE_DebugInfoNode *next; + PE_DebugInfo v; +}; -typedef struct PE_DebugInfoList +typedef struct PE_DebugInfoList PE_DebugInfoList; +struct PE_DebugInfoList { PE_DebugInfoNode *first; PE_DebugInfoNode *last; - U64 count; -} PE_DebugInfoList; + U64 count; +}; //////////////////////////////// //~ rjf: Basic Enum Functions @@ -1076,7 +1080,7 @@ internal String8 pe_string_from_dll_characteristics(Arena *arena, PE_DllCharacte internal B32 pe_check_magic(String8 data); internal PE_BinInfo pe_bin_info_from_data(Arena *arena, String8 data); -internal PE_DataDirectory * pe_data_directory_from_idx(String8 file_data, PE_BinInfo pe, PE_DataDirectoryIndex dir_idx); +internal PE_DataDirectory * pe_data_directory_from_idx(String8 file_data, PE_BinInfo *pe, PE_DataDirectoryIndex dir_idx); internal PE_DebugInfoList pe_parse_debug_directory(Arena *arena, String8 raw_image, String8 raw_debug_dir); internal PE_DebugInfoList pe_debug_info_list_from_raw_debug_dir(Arena *arena, String8 raw_image, String8 raw_debug_dir); internal PE_ParsedStaticImportTable pe_static_imports_from_data(Arena *arena, B32 is_pe32, U64 section_count, COFF_SectionHeader *sections, String8 raw_data, Rng1U64 dir_file_range); diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index db9d96ec..7e5759da 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -637,7 +637,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[257] = { str8_lit_comp("set_entity_name"), str8_lit_comp("Sets the passed entity's name."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("download_module_debug_info"), str8_lit_comp("Tries to download a module's debug info from symbol server(s)."), str8_lit_comp(""), str8_lit_comp("{module_commands}"), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CtrlEntity, str8_lit_comp("query:modules")}}, { str8_lit_comp("attach"), str8_lit_comp("Attaches to a process that is already running on the local machine."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_PID, str8_lit_comp("query:unattached_processes")}}, -{ str8_lit_comp("open_crash_dump"), str8_lit_comp("Opens a crash dump file and recreates debuggee state at the time of the crash."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, +{ str8_lit_comp("open_crash_dump"), str8_lit_comp("Opens a crash dump file and recreates debuggee state at the time of the crash."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, { str8_lit_comp("exit"), str8_lit_comp("Exits the debugger."), str8_lit_comp("quit,close,abort"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("open_palette"), str8_lit_comp("Opens the palette."), str8_lit_comp("help,cmd,lister"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("run_command"), str8_lit_comp("Runs a command from the command palette."), str8_lit_comp("help,cmd"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CmdName, str8_lit_comp("query:commands")}}, From 9efcc6b22c581eb744aa1ba137b2f1c3ff7fa34c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 17:31:58 -0700 Subject: [PATCH 110/251] drop file support for dmps --- src/raddbg/raddbg_core.c | 25 +++++++++++++++++++++++++ src/raddbg/raddbg_core.h | 1 + 2 files changed, 26 insertions(+) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 1ecc3baa..e7cf7ef6 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -6487,6 +6487,19 @@ rd_window_frame(void) } } + // rjf: option to load dump files + if(task->dmp) + { + if(ui_clicked(rd_icon_buttonf(RD_IconKind_Module, 0, "Load as debuggee dump"))) + { + for(String8Node *n = task->paths.first; n != 0; n = n->next) + { + rd_cmd(RD_CmdKind_OpenCrashDump, .file_path = n->string); + } + done = 1; + } + } + // rjf: option to just open & view the file contents if(ui_clicked(rd_icon_buttonf(RD_IconKind_FileOutline, 0, "View file%s contents", (task->paths.node_count > 1) ? "s'" : ""))) { @@ -9296,6 +9309,7 @@ rd_window_frame(void) ws->drop_completion_panel = panel->cfg->id; String8List exe_paths = {0}; String8List dbg_paths = {0}; + String8List dmp_paths = {0}; for(String8Node *n = evt->paths.first; n != 0; n = n->next) { Temp scratch = scratch_begin(0, 0); @@ -9310,6 +9324,10 @@ rd_window_frame(void) { str8_list_push(ws->drop_completion_arena, &dbg_paths, str8_copy(ws->drop_completion_arena, path)); } + else if(str8_match(ext, str8_lit("dmp"), StringMatchFlag_CaseInsensitive)) + { + str8_list_push(ws->drop_completion_arena, &dmp_paths, str8_copy(ws->drop_completion_arena, path)); + } else { rd_cmd(RD_CmdKind_Open, .file_path = path, .panel = panel->cfg->id); @@ -9330,6 +9348,13 @@ rd_window_frame(void) t->exe = 1; t->paths = exe_paths; } + if(dmp_paths.node_count != 0) + { + RD_DropCompletionTask *t = push_array(ws->drop_completion_arena, RD_DropCompletionTask, 1); + SLLStackPush(ws->top_drop_completion_task, t); + t->dmp = 1; + t->paths = dmp_paths; + } if(ws->top_drop_completion_task != 0) { ui_ctx_menu_open(rd_state->drop_completion_key, ui_key_zero(), evt->pos); diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index 353491c0..5ae1b37a 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -264,6 +264,7 @@ struct RD_DropCompletionTask B32 exe; B32 dbg; B32 cfg; + B32 dmp; String8List paths; }; From 99ea44c4ccfec014c65129c8164c4e0025ce1929 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 26 Aug 2026 17:52:33 -0700 Subject: [PATCH 111/251] try relative paths correctly with crash dump modules --- src/dbg_engine/dbg_engine_ctrl.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index b83871c1..980f18e4 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -4114,7 +4114,16 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) module_name = path_normalized_from_string(scratch.arena, module_name); } - // rjf: open module file + // rjf: relativize module path to the dmp file, if needed + { + String8 relative_module_path = str8f(scratch.arena, "%S/%S", str8_chop_last_slash(path), str8_skip_last_slash(module_name)); + if(properties_from_file_path(relative_module_path).created != 0) + { + module_name = relative_module_path; + } + } + + // rjf: unpack module file File module_file = file_open(AccessFlag_Read|AccessFlag_ShareRead, module_name); FileProperties module_props = properties_from_file(module_file); FileMap module_map = file_map_open(AccessFlag_Read, module_file); From 3e704eabb0d7a061cd49590511a839573889810f Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 27 Aug 2026 09:59:34 -0700 Subject: [PATCH 112/251] minidump thread names --- src/dbg_engine/dbg_engine_ctrl.c | 94 ++++++++++++++++++++++++++++++-- src/dbg_engine/dbg_engine_ctrl.h | 1 + src/dbg_engine/dbg_engine_user.c | 50 ++++++++++++----- src/minidump/minidump.h | 7 +++ src/raddbg/raddbg_core.c | 40 ++++++++++++-- 5 files changed, 165 insertions(+), 27 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 980f18e4..091ffc61 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -3951,6 +3951,9 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_Handle process = {0}; Arch process_arch = Arch_Null; OperatingSystem process_os = OperatingSystem_Null; + U64 exception_vaddr = 0; + U32 exception_thread_id = 0; + U32 exception_code = 0; { //////////////////////////// //- rjf: try minidump parse -> construct events for entity creation @@ -3987,6 +3990,8 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) MDMP_MemoryDescriptor64 *memories64 = 0; U64 memories64_count = 0; U64 memories64_base_foff = 0; + MDMP_ThreadName *thread_names = 0; + U64 thread_names_count = 0; { for EachIndex(idx, directories_count) { @@ -4008,6 +4013,15 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) threads = (MDMP_Thread *)(data.str + off); threads_count = Min(threads_count_32, threads_count_max); }break; + case MDMP_StreamKind_ThreadNames: + { + U32 thread_names_count_32 = 0; + U64 off = dir->location.foff; + off += str8_deserial_read_struct(data, off, &thread_names_count_32); + U64 thread_names_count_max = (data.size - off) / sizeof(MDMP_ThreadName); + thread_names = (MDMP_ThreadName *)(data.str + off); + thread_names_count = Min(thread_names_count_32, thread_names_count_max); + }break; case MDMP_StreamKind_ModuleList: { U32 modules_count_32 = 0; @@ -4035,6 +4049,14 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) memories64 = (MDMP_MemoryDescriptor64 *)(data.str + off); memories64_count = Min(memories64_count, memories64_count_max); }break; + case MDMP_StreamKind_Exception: + { + MDMP_ExceptionStream stream = {0}; + str8_deserial_read_struct(data, dir->location.foff, &stream); + exception_vaddr = stream.exception.exception_addr; + exception_thread_id = stream.thread_id; + exception_code = stream.exception.exception_code; + }break; } } } @@ -4084,6 +4106,14 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) // rjf: gather threads dump_threads_count = threads_count; dump_threads = push_array(arena, D_DumpThread, dump_threads_count); + typedef struct DumpThreadNode DumpThreadNode; + struct DumpThreadNode + { + DumpThreadNode *next; + D_DumpThread *v; + }; + U64 dump_thread_from_id_slots_count = threads_count; + DumpThreadNode **dump_thread_from_id_slots = push_array(scratch.arena, DumpThreadNode *, dump_thread_from_id_slots_count); for EachIndex(idx, threads_count) { d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; @@ -4092,6 +4122,38 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) dump_threads[idx].thread_handle = thread_handle; dump_threads[idx].id = thread->id; dump_threads[idx].context_foff = thread->thread_context.foff; + U64 slot_idx = u64_hash_from_str8(str8_struct(&thread->id))%dump_thread_from_id_slots_count; + DumpThreadNode *n = push_array(scratch.arena, DumpThreadNode, 1); + n->v = &dump_threads[idx]; + SLLStackPush(dump_thread_from_id_slots[slot_idx], n); + } + + // rjf: equip thread names + for EachIndex(idx, thread_names_count) + { + MDMP_ThreadName *thread_name = &thread_names[idx]; + U64 slot_idx = u64_hash_from_str8(str8_struct(&thread_name->thread_id))%dump_thread_from_id_slots_count; + D_DumpThread *thread = 0; + for(DumpThreadNode *n = dump_thread_from_id_slots[slot_idx]; n != 0; n = n->next) + { + if(n->v->id == thread_name->thread_id) + { + thread = n->v; + break; + } + } + if(thread != 0) + { + U64 name_foff = thread_name->thread_name_foff; + U32 name_size_supposedly = 0; + U64 off = name_foff; + off += str8_deserial_read_struct(data, off, &name_size_supposedly); + U64 name_size_max = (data.size - off); + U64 name_size = Min(name_size_max, name_size_supposedly); + String16 name16 = str16((U16 *)(data.str + off), name_size/sizeof(U16)); + String8 name8 = str8_from_16(arena, name16); + thread->name = name8; + } } // rjf: gather modules @@ -4218,6 +4280,16 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) evt->entity_id = dump_threads[idx].id; // TODO(rjf): evt->stack_base = ; (use thread->stack) // TODO(rjf): evt->tls_root = ; (use thread->thread_context) + if(dump_threads[idx].name.size != 0) + { + D_Event *evt2 = d_event_list_push(scratch.arena, &evts); + evt2->kind = D_EventKind_ThreadName; + evt2->msg_id = msg->msg_id; + evt2->entity = thread_handle; + evt2->parent = process; + evt2->string = dump_threads[idx].name; + evt2->entity_id = dump_threads[idx].id; + } } ////////////////////////////// @@ -4229,12 +4301,6 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_Handle module_handle = dump_modules[idx].module_handle; Rng1U64 vaddr_range = dump_modules[idx].vaddr_range; -#if 0 - //- TODO(rjf): I don't think we can use this demon module info path here - DMN_ModuleInfo module_info = {0}; - d_ctrl_thread__module_open(process, module_handle, vaddr_range.min, dump_modules[idx].path, r1u64(0, 0), 0); -#endif - //- rjf: analyze module file, produce module info Arena *module_info_arena = arena_alloc(); D_ModuleInfo module_info = {0}; @@ -4329,6 +4395,22 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) event->kind = D_EventKind_Stopped; event->cause = D_EventCause_Finished; event->msg_id = msg->msg_id; + if(exception_thread_id != 0) + { + D_Handle thread = {0}; + for EachIndex(idx, dump_threads_count) + { + if(dump_threads[idx].id == exception_thread_id) + { + thread = dump_threads[idx].thread_handle; + break; + } + } + event->cause = D_EventCause_InterruptedByException; + event->exception_code = exception_code; + event->entity = thread; + event->parent = process; + } } //- rjf: push all events from the parse diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index 5f61ae3c..a003bd0f 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -218,6 +218,7 @@ struct D_DumpThread D_Handle thread_handle; U32 id; U64 context_foff; + String8 name; }; typedef struct D_DumpModule D_DumpModule; diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index 1f77117f..0702e9b5 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -1835,7 +1835,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P // rjf: no targets -> error if(targets_to_launch->count == 0) { - log_user_error(str8_lit("No active targets exist; cannot launch. You must select a target first.")); + log_user_error(s("No active targets exist; cannot launch. You must select a target first.")); } }break; case D_CmdKind_Kill: @@ -1843,7 +1843,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P D_Entity *process = d_entity_from_handle(params->process); if(process == &d_entity_nil) { - log_user_error(str8_lit("Cannot kill; no process was specified.")); + log_user_error(s("Cannot kill; no process was specified.")); } else { @@ -1868,7 +1868,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P D_Entity *process = d_entity_from_handle(params->process); if(process == &d_entity_nil) { - log_user_error(str8_lit("Cannot detach; no process specified.")); + log_user_error(s("Cannot detach; no process specified.")); } else { @@ -1882,19 +1882,28 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P case D_CmdKind_Continue: { B32 good_to_run = 0; + B32 any_non_dump_threads = 0; D_EntityArray threads = d_entity_array_from_kind(D_EntityKind_Thread); if(threads.count > 0) { for EachIndex(idx, threads.count) { D_Entity *thread = threads.v[idx]; + if(threads.v[idx]->handle.controller_kind != D_ControllerKind_Dump) + { + any_non_dump_threads = 1; + } if(!thread->is_frozen) { good_to_run = 1; break; } } - if(good_to_run) + if(!any_non_dump_threads) + { + log_user_error(s("Cannot run with only dump file threads.")); + } + else if(good_to_run) { need_run = 1; run_kind = D_RunKind_Run; @@ -1902,7 +1911,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P } else { - log_user_error(str8_lit("Cannot run with all threads frozen.")); + log_user_error(s("Cannot run with all threads frozen.")); } } }break; @@ -1916,18 +1925,22 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P D_Entity *thread = d_entity_from_handle(params->thread); if(thread == &d_entity_nil) { - log_user_error(str8_lit("Must have a selected thread to step.")); + log_user_error(s("Must have a selected thread to step.")); + } + else if(thread->handle.controller_kind == D_ControllerKind_Dump) + { + log_user_error(s("Cannot step a thread from a dump file.")); } else if(d_ctrl_targets_running()) { if(d_ctrl_last_run_kind() == D_RunKind_Run) { - log_user_error(str8_lit("Must halt before stepping.")); + log_user_error(s("Must halt before stepping.")); } } else if(thread->is_frozen) { - log_user_error(str8_lit("Must thaw selected thread before stepping.")); + log_user_error(s("Must thaw selected thread before stepping.")); } else { @@ -1967,7 +1980,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P } else if(!good_trap_net) { - log_user_error(str8_lit("Could not successfully step.")); + log_user_error(s("Could not successfully step.")); } } }break; @@ -1988,12 +2001,19 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P case D_CmdKind_SetThreadIP: { D_Entity *thread = d_entity_from_handle(params->thread); - ARCH_Info *arch_info = arch_info_from_arch(thread->arch); - U64 vaddr = params->vaddr; - void *block = d_cached_reg_block_from_thread(scratch.arena, thread->handle); - arch_reg_block_write_ip(arch_info, block, vaddr); - B32 result = d_thread_write_reg_block(thread->handle, block); - (void)result; + if(thread->handle.controller_kind == D_ControllerKind_Dump) + { + log_user_error(s("Cannot write register values to dump file threads.")); + } + else + { + ARCH_Info *arch_info = arch_info_from_arch(thread->arch); + U64 vaddr = params->vaddr; + void *block = d_cached_reg_block_from_thread(scratch.arena, thread->handle); + arch_reg_block_write_ip(arch_info, block, vaddr); + B32 result = d_thread_write_reg_block(thread->handle, block); + (void)result; + } }break; //- rjf: high-level composite target control operations diff --git a/src/minidump/minidump.h b/src/minidump/minidump.h index 7e68a8db..314c648d 100644 --- a/src/minidump/minidump.h +++ b/src/minidump/minidump.h @@ -199,6 +199,13 @@ struct MDMP_ExceptionStream MDMP_LocationDescriptor32 thread_context; }; +typedef struct MDMP_ThreadName MDMP_ThreadName; +struct MDMP_ThreadName +{ + U32 thread_id; + U64 thread_name_foff; +}; + typedef U16 MDMP_Arch; typedef enum MDMP_ArchEnum { diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index e7cf7ef6..9f529b1b 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -1783,10 +1783,10 @@ rd_view_ui(Rng2F32 rect) { Temp scratch = scratch_begin(0, 0); ui_set_next_flags(UI_BoxFlag_DefaultFocusNav); - UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(str8_lit("empty_view")) + UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(s("empty_view")) UI_Padding(ui_pct(1, 0)) UI_Focus(UI_FocusKind_Null) { - CFG_NodePtrList targets = cfg_node_top_level_list_from_string(scratch.arena, str8_lit("target")); + CFG_NodePtrList targets = cfg_node_top_level_list_from_string(scratch.arena, s("target")); D_EntityArray processes = d_entity_array_from_kind(D_EntityKind_Process); Rng2F32 view_rect = ui_top_parent()->rect; @@ -1796,6 +1796,7 @@ rd_view_ui(Rng2F32 rect) //- rjf: icon { F32 icon_dim = ui_top_font_size()*10.f; + ui_set_next_flags(UI_BoxFlag_AnimatePosY); UI_PrefHeight(ui_px(icon_dim, 1.f)) UI_Row UI_Padding(ui_pct(1, 0)) @@ -11386,6 +11387,7 @@ rd_init(CmdLine *cmdln) //- rjf: setup initial target from command line args String8 implicit_user_arg = {0}; String8 implicit_project_arg = {0}; + String8 implicit_dmp_arg = {0}; { Temp scratch2 = scratch_begin(&scratch.arena, 1); String8List target_args = {0}; @@ -11398,12 +11400,15 @@ rd_init(CmdLine *cmdln) str8_match(str8_prefix(arg, 1), str8_lit("--"), 0) || str8_match(str8_prefix(arg, 1), str8_lit("/"), 0)); B32 is_cfg = 0; + B32 is_dmp = 0; if(!is_flag && !after_first_non_flag) { File file = file_open(AccessFlag_Read|AccessFlag_ShareRead, arg); U8 raddbg_cfg_magic[] = "// raddbg "; U8 file_magic_maybe[ArrayCount(raddbg_cfg_magic)] = {0}; file_read(file, r1u64(0, 10), file_magic_maybe); + MDMP_Header mdmp_header_maybe = {0}; + file_read_struct(file, 0, &mdmp_header_maybe); if(MemoryMatchArray(raddbg_cfg_magic, file_magic_maybe)) { is_cfg = 1; @@ -11421,13 +11426,18 @@ rd_init(CmdLine *cmdln) implicit_project_arg = path_absolute_dst_from_relative_dst_src(scratch.arena, arg, get_process_info()->initial_path); } } + else if(mdmp_header_maybe.magic == MDMP_MAGIC) + { + is_dmp = 1; + implicit_dmp_arg = arg; + } file_close(file); } if(!is_flag) { after_first_non_flag = 1; } - if(after_first_non_flag && !is_cfg) + if(after_first_non_flag && !is_cfg && !is_dmp) { str8_list_push(scratch2.arena, &target_args, arg); } @@ -11490,6 +11500,12 @@ rd_init(CmdLine *cmdln) scratch_end(scratch2); } + //- rjf: load dump arguments + if(implicit_dmp_arg.size != 0) + { + rd_cmd(RD_CmdKind_OpenCrashDump, .file_path = implicit_dmp_arg); + } + // rjf: set up user / project paths { Temp scratch2 = scratch_begin(&scratch.arena, 1); @@ -13929,11 +13945,23 @@ rd_frame(void) //- rjf: exiting case RD_CmdKind_Exit: { + D_EntityArray processes = d_entity_array_from_kind(D_EntityKind_Process); + + // rjf: determine if we're attached to live processes + B32 attached_to_live_processes = 0; + for EachIndex(idx, processes.count) + { + if(processes.v[idx]->handle.controller_kind != D_ControllerKind_Dump) + { + attached_to_live_processes = 1; + break; + } + } + // rjf: if control processes are live, but this is not force-confirmed, then // get confirmation from user - D_EntityArray processes = d_entity_array_from_kind(D_EntityKind_Process); - UI_Key key = ui_key_from_string(ui_key_zero(), str8_lit("lossy_exit_confirmation")); - if(processes.count != 0 && !rd_regs()->force_confirm && !ui_key_match(rd_state->popup_key, key)) + UI_Key key = ui_key_from_string(ui_key_zero(), s("lossy_exit_confirmation")); + if(attached_to_live_processes && !rd_regs()->force_confirm && !ui_key_match(rd_state->popup_key, key)) { rd_state->popup_key = key; rd_state->popup_active = 1; From 3bab86e6af4c7157d9d845e1b2024a305420e3f0 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 27 Aug 2026 13:52:34 -0700 Subject: [PATCH 113/251] checkpoint on sockets layer --- project.raddbg | 2 +- src/base/base_entry_point.c | 6 ++ src/base/base_ring.c | 131 +++++++++++++++++++++--------- src/base/base_ring.h | 6 ++ src/raddbg/raddbg_core.c | 2 + src/raddbg/raddbg_main.c | 2 + src/socket/socket.c | 96 ++++++++++++++++++++++ src/socket/socket.h | 67 ++++++++++++++++ src/socket/socket_inc.c | 9 +++ src/socket/socket_inc.h | 14 ++++ src/socket/socket_stub.c | 7 ++ src/socket/socket_stub.h | 7 ++ src/win32/socket/win32_socket.c | 136 ++++++++++++++++++++++++++++++++ src/win32/socket/win32_socket.h | 39 +++++++++ 14 files changed, 486 insertions(+), 38 deletions(-) create mode 100644 src/socket/socket.c create mode 100644 src/socket/socket.h create mode 100644 src/socket/socket_inc.c create mode 100644 src/socket/socket_inc.h create mode 100644 src/socket/socket_stub.c create mode 100644 src/socket/socket_stub.h create mode 100644 src/win32/socket/win32_socket.c create mode 100644 src/win32/socket/win32_socket.h diff --git a/project.raddbg b/project.raddbg index fb4d5d7a..e1df3c4e 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -17,7 +18,6 @@ target: { executable: "build/mule_main.exe" working_directory: build - enabled: 1 } target: { diff --git a/src/base/base_entry_point.c b/src/base/base_entry_point.c index ddf34406..169f2573 100644 --- a/src/base/base_entry_point.c +++ b/src/base/base_entry_point.c @@ -58,6 +58,9 @@ main_thread_base_entry_point(int arguments_count, char **arguments) } //- rjf: initialize all included layers +#if defined(SOCKET_H) && !defined(SOCKET_INIT_MANUAL) + sock_init(); +#endif #if defined(HTTP_H) && !defined(HTTP_INIT_MANUAL) http_init(); #endif @@ -222,6 +225,9 @@ async_thread_entry_point(void *params) #if defined(FILE_STREAM_H) fs_async_tick(); #endif +#if defined(SOCKET_H) + sock_async_tick(); +#endif #if defined(HTTP_H) http_async_tick(); #endif diff --git a/src/base/base_ring.c b/src/base/base_ring.c index 41f26095..a9145e82 100644 --- a/src/base/base_ring.c +++ b/src/base/base_ring.c @@ -13,16 +13,42 @@ make_ring(Arena *arena, U64 size) return ring; } -internal B32 -ring_try_write(Ring *ring, U64 size, void *ptr) +internal void * +ring_try_push(Ring *ring, U64 size) { U64 bytes_unconsumed = (ring->write_pos - ring->read_pos); U64 bytes_available = ring->size - bytes_unconsumed; - B32 result = 0; + void *base = 0; if(bytes_available >= size) + { + base = ring->base + ring->write_pos%ring->size; + ring->write_pos += size; + } + return base; +} + +internal void * +ring_try_pop(Ring *ring, U64 size) +{ + U64 bytes_unconsumed = (ring->write_pos - ring->read_pos); + void *base = 0; + if(bytes_unconsumed >= size) + { + base = ring->base + ring->read_pos%ring->size; + ring->read_pos += size; + } + return base; +} + +internal B32 +ring_try_write(Ring *ring, U64 size, void *ptr) +{ + B32 result = 0; + void *dst = ring_try_push(ring, size); + if(dst) { result = 1; - ring->write_pos += wrapped_write(ring->base, ring->size, ring->write_pos, ptr, size); + MemoryCopy(dst, ptr, size); } return result; } @@ -30,12 +56,12 @@ ring_try_write(Ring *ring, U64 size, void *ptr) internal B32 ring_try_read(Ring *ring, U64 size, void *ptr) { - U64 bytes_unconsumed = (ring->write_pos - ring->read_pos); B32 result = 0; - if(bytes_unconsumed >= size) + void *src = ring_try_pop(ring, size); + if(src) { result = 1; - ring->read_pos += wrapped_read(ring->base, ring->size, ring->read_pos, ptr, size); + MemoryCopy(ptr, src, size); } return result; } @@ -76,16 +102,27 @@ internal void guarded_ring_close(RingGuard *guard) { mutex_drop(guard->r->mutex); + cond_var_broadcast(guard->r->cv); +} + +internal void * +guarded_ring_try_push(RingGuard *guard, U64 size) +{ + void *result = ring_try_push(guard->r->ring, size); + return result; +} + +internal void * +guarded_ring_try_pop(RingGuard *guard, U64 size) +{ + void *result = ring_try_pop(guard->r->ring, size); + return result; } internal B32 guarded_ring_try_write(RingGuard *guard, U64 size, void *ptr) { B32 result = ring_try_write(guard->r->ring, size, ptr); - if(result) - { - cond_var_broadcast(guard->r->cv); - } return result; } @@ -93,9 +130,43 @@ internal B32 guarded_ring_try_read(RingGuard *guard, U64 size, void *ptr) { B32 result = ring_try_read(guard->r->ring, size, ptr); - if(result) + return result; +} + +internal void * +guarded_ring_push_or_wait(RingGuard *guard, U64 size, U64 endt_us) +{ + void *result = 0; + for(;!result;) { - cond_var_broadcast(guard->r->cv); + result = guarded_ring_try_push(guard, size); + if(now_time_us() >= endt_us) + { + break; + } + if(!result) + { + cond_var_wait(guard->r->cv, guard->r->mutex, endt_us); + } + } + return result; +} + +internal void * +guarded_ring_pop_or_wait(RingGuard *guard, U64 size, U64 endt_us) +{ + void *result = 0; + for(;!result;) + { + result = guarded_ring_try_pop(guard, size); + if(now_time_us() >= endt_us) + { + break; + } + if(!result) + { + cond_var_wait(guard->r->cv, guard->r->mutex, endt_us); + } } return result; } @@ -103,37 +174,23 @@ guarded_ring_try_read(RingGuard *guard, U64 size, void *ptr) internal B32 guarded_ring_write_or_wait(RingGuard *guard, U64 size, void *ptr, U64 endt_us) { - B32 write_good = 0; - for(;!write_good;) + void *dst = guarded_ring_push_or_wait(guard, size, endt_us); + B32 result = !!dst; + if(dst) { - write_good = guarded_ring_try_write(guard, size, ptr); - if(now_time_us() >= endt_us) - { - break; - } - if(!write_good) - { - cond_var_wait(guard->r->cv, guard->r->mutex, endt_us); - } + MemoryCopy(dst, ptr, size); } - return write_good; + return result; } internal B32 guarded_ring_read_or_wait(RingGuard *guard, U64 size, void *ptr, U64 endt_us) { - B32 read_good = 0; - for(;!read_good;) + void *src = guarded_ring_pop_or_wait(guard, size, endt_us); + B32 result = !!src; + if(src) { - read_good = guarded_ring_try_read(guard, size, ptr); - if(now_time_us() >= endt_us) - { - break; - } - if(!read_good) - { - cond_var_wait(guard->r->cv, guard->r->mutex, endt_us); - } + MemoryCopy(ptr, src, size); } - return read_good; + return result; } diff --git a/src/base/base_ring.h b/src/base/base_ring.h index 59b07b86..f7e8e188 100644 --- a/src/base/base_ring.h +++ b/src/base/base_ring.h @@ -31,6 +31,8 @@ struct RingGuard //~ rjf: Ring Functions internal Ring *make_ring(Arena *arena, U64 size); +internal void *ring_try_push(Ring *ring, U64 size); +internal void *ring_try_pop(Ring *ring, U64 size); internal B32 ring_try_write(Ring *ring, U64 size, void *ptr); internal B32 ring_try_read(Ring *ring, U64 size, void *ptr); #define ring_try_write_struct(ring, ptr) ring_try_write((ring), sizeof(*(ptr)), (ptr)) @@ -43,10 +45,14 @@ internal GuardedRing *guarded_ring_alloc(Arena *arena, U64 size); internal void guarded_ring_release(GuardedRing *ring); internal RingGuard guarded_ring_open(GuardedRing *ring); internal void guarded_ring_close(RingGuard *guard); +internal void *guarded_ring_try_push(RingGuard *guard, U64 size); +internal void *guarded_ring_try_pop(RingGuard *guard, U64 size); internal B32 guarded_ring_try_write(RingGuard *guard, U64 size, void *ptr); internal B32 guarded_ring_try_read(RingGuard *guard, U64 size, void *ptr); #define guarded_ring_try_write_struct(ring, ptr) guarded_ring_try_write((ring), sizeof(*(ptr)), (ptr)) #define guarded_ring_try_read_struct(ring, ptr) guarded_ring_try_read((ring), sizeof(*(ptr)), (ptr)) +internal void *guarded_ring_push_or_wait(RingGuard *guard, U64 size, U64 endt_us); +internal void *guarded_ring_pop_or_wait(RingGuard *guard, U64 size, U64 endt_us); internal B32 guarded_ring_write_or_wait(RingGuard *guard, U64 size, void *ptr, U64 endt_us); internal B32 guarded_ring_read_or_wait(RingGuard *guard, U64 size, void *ptr, U64 endt_us); #define guarded_ring_write_struct_or_wait(ring, ptr, endt_us) guarded_ring_write_or_wait((ring), sizeof(*(ptr)), (ptr), (endt_us)) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 9f529b1b..88c4427e 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -7442,6 +7442,7 @@ rd_window_frame(void) { rd_cmd_kind_info_table[RD_CmdKind_Open].string, rd_cmd_kind_info_table[RD_CmdKind_OpenSourceFileFromDebugInfo].string, + rd_cmd_kind_info_table[RD_CmdKind_OpenCrashDump].string, {0},//- rd_cmd_kind_info_table[RD_CmdKind_NewProject].string, rd_cmd_kind_info_table[RD_CmdKind_OpenProject].string, @@ -7460,6 +7461,7 @@ rd_window_frame(void) { 'o', 'i', + 'd', 0,//- 'j', 'p', diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 3e419070..eb7459c9 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -388,6 +388,7 @@ #include "mdesk/mdesk.h" #include "window_manager/window_manager_inc.h" #include "shell/shell_inc.h" +// #include "socket/socket_inc.h" #include "http/http_inc.h" #include "symbol_server/symbol_server_inc.h" #include "config/config_inc.h" @@ -450,6 +451,7 @@ #include "mdesk/mdesk.c" #include "window_manager/window_manager_inc.c" #include "shell/shell_inc.c" +// #include "socket/socket_inc.c" #include "http/http_inc.c" #include "symbol_server/symbol_server_inc.c" #include "config/config_inc.c" diff --git a/src/socket/socket.c b/src/socket/socket.c new file mode 100644 index 00000000..8accf484 --- /dev/null +++ b/src/socket/socket.c @@ -0,0 +1,96 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Helpers + +internal SOCK_Endpoint +sock_endpoint_from_string_port(String8 address, U16 port) +{ + //- rjf: detect kind + SOCK_EndpointKind kind = SOCK_EndpointKind_IPv4; + { + if(str8_find_needle(address, 0, s(":"), 0) < address.size) + { + kind = SOCK_EndpointKind_IPv6; + } + } + + //- rjf: fill based on format + SOCK_Endpoint ep = {0}; + ep.port = port; + ep.kind = kind; + switch(kind) + { + default: + case SOCK_EndpointKind_IPv4: + { + U64 off = 0; + U64 part_start_off = 0; + for(;off < 4;) + { + U64 part_end_off = str8_find_needle(address, part_start_off+1, s("."), 0); + String8 part = str8_substr(address, r1u64(part_start_off, part_end_off)); + U64 part_val = u64_from_str8(part, 10); + ep.address_u8[off] = (U8)part_val; + part_start_off = part_end_off+1; + off += 1; + if(part_end_off >= address.size) + { + break; + } + } + }break; + case SOCK_EndpointKind_IPv6: + { + U64 off = 0; + U64 part_start_off = 0; + U64 double_colon_off = 0; + for(;off < 8;) + { + U64 part_end_off = str8_find_needle(address, part_start_off+1, s(":"), 0); + String8 part = str8_substr(address, r1u64(part_start_off, part_end_off)); + U64 part_val = u64_from_str8(part, 16); + ep.address_u16[off] = (U16)part_val; + part_start_off = part_end_off+1; + if(part_end_off+1 < address.size && address.str[part_end_off+1] == ':') + { + double_colon_off = off; + } + off += 1; + if(part_end_off >= address.size) + { + break; + } + } + if(off < 8 && double_colon_off < 8) + { + U64 shift_amt = 8 - off; + MemoryCopy(&ep.address_u16[0] + double_colon_off + shift_amt, &ep.address_u16[0] + double_colon_off, sizeof(U16) * (off - double_colon_off)); + } + }break; + } +} + +internal SOCK_Endpoint +sock_endpoint_from_string(String8 address_and_port) +{ + String8 addr = {0}; + U64 port_off = 0; + if(address_and_port.size > 0 && address_and_port.str[0] == '[') + { + U64 addr_end_off = str8_find_needle(address_and_port, 1, s("]"), 0); + addr = str8_substr(address_and_port, r1u64(1, addr_end_off)); + port_off = str8_find_needle(address_and_port, addr_end_off+1, s(":"), 0)+1; + } + else + { + U64 colon_off = str8_find_needle(address_and_port, 0, s(":"), 0); + port_off = colon_off + 1; + addr = str8_prefix(address_and_port, colon_off); + } + String8 port_string = str8_skip(address_and_port, port_off); + U16 port = u64_from_str8(port_string, 10); + SOCK_Endpoint ep = sock_endpoint_from_string_port(addr, port); + return ep; +} diff --git a/src/socket/socket.h b/src/socket/socket.h new file mode 100644 index 00000000..cc07e715 --- /dev/null +++ b/src/socket/socket.h @@ -0,0 +1,67 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef SOCKET_H +#define SOCKET_H + +#if !defined(SOCKET_PORT) +# define SOCKET_PORT 7423 +#endif + +typedef enum SOCK_Protocol +{ + SOCK_Protocol_TCP, + SOCK_Protocol_UDP, +} +SOCK_Protocol; + +typedef U8 SOCK_EndpointKind; +typedef enum SOCK_EndpointKindEnum +{ + SOCK_EndpointKind_IPv4, + SOCK_EndpointKind_IPv6, +} +SOCK_EndpointKindEnum; + +typedef struct SOCK_Endpoint SOCK_Endpoint; +struct SOCK_Endpoint +{ + U16 port; + SOCK_EndpointKind kind; + U8 _pad_0; + U32 _pad_1; + union + { + U8 address_u8[16]; + U16 address_u16[8]; + }; +}; + +//////////////////////////////// +//~ rjf: Helpers + +internal SOCK_Endpoint sock_endpoint_from_string_port(String8 address, U16 port); +internal SOCK_Endpoint sock_endpoint_from_string(String8 address_and_port); + +//////////////////////////////// +//~ rjf: @per_os_impl Top-Level Layer Calls + +#if !defined(NEED_ASYNC) +# define NEED_ASYNC 1 +#endif +internal void sock_init(void); +internal void sock_async_tick(void); + +//////////////////////////////// +//~ rjf: @per_os_impl Sends + +internal U64 sock_send(U8 *ptr, U64 size, SOCK_Protocol *protocol_in, SOCK_Endpoint *endpoint_in, U64 endt_us); +#define sock_send_struct(ptr, protocol_in, endpoint_in, endt_us) sock_send((ptr), sizeof(*(ptr)), (endpoint_in), (endt_us)) + +//////////////////////////////// +//~ rjf: @per_os_impl Receives + +internal U64 sock_recv(U8 *ptr, U64 size, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, U64 endt_us); +#define sock_recv_struct(ptr, protocol_out, endpoint_out, endt_us) sock_recv((ptr), sizeof(*(ptr)), (endpoint_out), (endt_us)) + +#endif // SOCKET_H diff --git a/src/socket/socket_inc.c b/src/socket/socket_inc.c new file mode 100644 index 00000000..28e10284 --- /dev/null +++ b/src/socket/socket_inc.c @@ -0,0 +1,9 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "socket.c" +#if OS_WINDOWS +# include "win32/socket/win32_socket.c" +#else +# include "socket_stub.c" +#endif diff --git a/src/socket/socket_inc.h b/src/socket/socket_inc.h new file mode 100644 index 00000000..56928a79 --- /dev/null +++ b/src/socket/socket_inc.h @@ -0,0 +1,14 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef SOCKET_INC_H +#define SOCKET_INC_H + +#include "socket.h" +#if OS_WINDOWS +# include "win32/socket/win32_socket.h" +#else +# include "socket_stub.h" +#endif + +#endif // SOCKET_INC_H diff --git a/src/socket/socket_stub.c b/src/socket/socket_stub.c new file mode 100644 index 00000000..639c9a8a --- /dev/null +++ b/src/socket/socket_stub.c @@ -0,0 +1,7 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal void sock_init(void){} +internal void sock_async_tick(void){} +internal U64 sock_send(U8 *ptr, U64 size, SOCK_Protocol *protocol_in, SOCK_Endpoint *endpoint_in, U64 endt_us){return 0;} +internal U64 sock_recv(U8 *ptr, U64 size, SOCK_Protocol *protocol_in, SOCK_Endpoint *endpoint_in, U64 endt_us){return 0;} diff --git a/src/socket/socket_stub.h b/src/socket/socket_stub.h new file mode 100644 index 00000000..6ffde4b0 --- /dev/null +++ b/src/socket/socket_stub.h @@ -0,0 +1,7 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef SOCKET_STUB_H +#define SOCKET_STUB_H + +#endif // SOCKET_STUB_H diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c new file mode 100644 index 00000000..00adcc40 --- /dev/null +++ b/src/win32/socket/win32_socket.c @@ -0,0 +1,136 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Listener Threads + +internal void +w32_sock_tcp_listener_thread_entry_point(void *p) +{ + ThreadNameF("w32_sock_tcp_listener_thread"); + for(;;) + { + SOCKET new_socket = accept(w32_sock_state->tcp_listen_socket, 0, 0); + if(new_socket != INVALID_SOCKET) + { + // TODO(rjf) + } + } +} + +internal void +w32_sock_udp_listener_thread_entry_point(void *p) +{ + ThreadNameF("w32_sock_udp_listener_thread"); + for(;;) + { + SOCKET new_socket = accept(w32_sock_state->udp_listen_socket, 0, 0); + if(new_socket != INVALID_SOCKET) + { + // TODO(rjf) + } + } +} + +//////////////////////////////// +//~ rjf: @per_os_impl Top-Level Layer Calls + +internal void +sock_init(void) +{ + // NOTE(rjf): winsock2 is already initialized by the base layer for RIO function grabbing. + + //- rjf: set up state + Arena *arena = arena_alloc(); + w32_sock_state = push_array(arena, W32_SOCK_State, 1); + w32_sock_state->arena = arena; + w32_sock_state->u2s_ring = guarded_ring_alloc(arena, KB(256)); + w32_sock_state->s2u_ring = guarded_ring_alloc(arena, KB(256)); + + //- rjf: create listener sockets + w32_sock_state->tcp_listen_socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + w32_sock_state->udp_listen_socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + { + DWORD ipv6only = 0; + setsockopt(w32_sock_state->tcp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); + setsockopt(w32_sock_state->udp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); + } + + //- rjf: bind listener sockets + { + struct sockaddr_in server_addr; + server_addr.sin_family = AF_INET6; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(SOCKET_PORT); + bind(w32_sock_state->tcp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); + bind(w32_sock_state->udp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); + } + + //- rjf: start listening + { + listen(w32_sock_state->tcp_listen_socket, SOMAXCONN); + listen(w32_sock_state->udp_listen_socket, SOMAXCONN); + } + + //- rjf: launch one-off listener threads to block & accept connections + w32_sock_state->tcp_listener_thread = thread_launch(w32_sock_tcp_listener_thread_entry_point, 0); + w32_sock_state->udp_listener_thread = thread_launch(w32_sock_udp_listener_thread_entry_point, 0); +} + +internal void +sock_async_tick(void) +{ + +} + +//////////////////////////////// +//~ rjf: @per_os_impl Sends + +internal U64 +sock_send(U8 *ptr, U64 size, SOCK_Protocol *protocol_in, SOCK_Endpoint *endpoint_in, U64 endt_us) +{ + U64 result = 0; + U64 needed_size = sizeof(*protocol_in) + sizeof(*endpoint_in) + sizeof(U64) + size; + RingGuard guard = guarded_ring_open(w32_sock_state->u2s_ring); + { + void *dst = guarded_ring_push_or_wait(&guard, needed_size, endt_us); + if(dst != 0) + { + MemoryCopy((U8 *)dst + 0, protocol_in, sizeof(*protocol_in)); + MemoryCopy((U8 *)dst + sizeof(*protocol_in), endpoint_in, sizeof(*endpoint_in)); + MemoryCopy((U8 *)dst + sizeof(*protocol_in) + sizeof(endpoint_in), &size, sizeof(size)); + MemoryCopy((U8 *)dst + sizeof(*protocol_in) + sizeof(endpoint_in) + sizeof(U64), ptr, size); + result = needed_size; + } + } + guarded_ring_close(&guard); + return result; +} + +//////////////////////////////// +//~ rjf: @per_os_impl Receives + +internal U64 +sock_recv(U8 *ptr, U64 size, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, U64 endt_us) +{ + U64 received_size = 0; + U64 header_size = sizeof(*protocol_out) + sizeof(*endpoint_out) + sizeof(U64); + { + RingGuard guard = guarded_ring_open(w32_sock_state->u2s_ring); + { + void *src = guarded_ring_pop_or_wait(&guard, header_size, endt_us); + if(src != 0) + { + U64 payload_size = 0; + MemoryCopy(protocol_out, (U8 *)src + 0, sizeof(*protocol_out)); + MemoryCopy(endpoint_out, (U8 *)src + sizeof(*protocol_out), sizeof(*endpoint_out)); + MemoryCopy(&payload_size, (U8 *)src + sizeof(*protocol_out) + sizeof(*endpoint_out), sizeof(U64)); + void *payload = guarded_ring_pop_or_wait(&guard, payload_size, max_U64); + MemoryCopy(ptr, payload, payload_size); + received_size = payload_size; + } + } + guarded_ring_close(&guard); + } + return received_size; +} diff --git a/src/win32/socket/win32_socket.h b/src/win32/socket/win32_socket.h new file mode 100644 index 00000000..d411836e --- /dev/null +++ b/src/win32/socket/win32_socket.h @@ -0,0 +1,39 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef WIN32_SOCKET_H +#define WIN32_SOCKET_H + +#include +#pragma comment(lib, "ws2_32") + +typedef struct W32_SOCK_Connection W32_SOCK_Connection; +struct W32_SOCK_Connection +{ + W32_SOCK_Connection *next; + SOCK_Endpoint endpoint; + SOCK_Protocol protocol; + SOCKET socket; +}; + +typedef struct W32_SOCK_State W32_SOCK_State; +struct W32_SOCK_State +{ + Arena *arena; + GuardedRing *u2s_ring; + GuardedRing *s2u_ring; + SOCKET tcp_listen_socket; + SOCKET udp_listen_socket; + Thread tcp_listener_thread; + Thread udp_listener_thread; +}; + +global W32_SOCK_State *w32_sock_state = 0; + +//////////////////////////////// +//~ rjf: Listener Threads + +internal void w32_sock_tcp_listener_thread_entry_point(void *p); +internal void w32_sock_udp_listener_thread_entry_point(void *p); + +#endif // WIN32_SOCKET_H From 0579e0f658c7674eee0bc30205b3e5a4bfd77a52 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 27 Aug 2026 14:14:37 -0700 Subject: [PATCH 114/251] first pass at using symbol server to download correct module versions for dump debugging --- src/dbg_engine/dbg_engine_ctrl.c | 31 ++++++++++++++++++- src/dbg_engine/dbg_engine_user.c | 2 +- src/pe/pe.c | 1 + src/pe/pe.h | 1 + src/symbol_server/symbol_server.h | 3 +- src/symbol_server/symbol_server_stub.c | 8 ++++- src/win32/symbol_server/win32_symbol_server.c | 10 +++++- 7 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 091ffc61..a386e67a 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -3199,7 +3199,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D } //- rjf: compute local symbol server cache path for this debug info - String8 local_symbol_server_cache_path = smsv_local_path_from_key(scratch2.arena, str8_skip_last_slash(module_info->debug_info_path), module_info->debug_info_guid, module_info->debug_info_age); + String8 local_symbol_server_cache_path = smsv_local_debug_info_path_from_key(scratch2.arena, str8_skip_last_slash(module_info->debug_info_path), module_info->debug_info_guid, module_info->debug_info_age); //- rjf: pick default initial debug info path String8 initial_debug_info_path = {0}; @@ -4185,6 +4185,35 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) } } + // rjf: check if the local module matches the dump's version + B32 local_module_matches = 1; + { + File file = file_open(AccessFlag_Read|AccessFlag_ShareRead, module_name); + if(file_match(file, file_zero())) + { + local_module_matches = 0; + } + else + { + FileProperties props = properties_from_file(file); + FileMap map = file_map_open(AccessFlag_Read, file); + void *base = file_map_view_open(map, AccessFlag_Read, r1u64(0, props.size)); + String8 data = str8((U8 *)base, props.size); + PE_BinInfo pe = pe_bin_info_from_data(scratch.arena, data); + local_module_matches = (pe.timestamp == module->time_date_stamp); + } + file_close(file); + } + + // rjf: download module if needed +#if defined(SYMBOL_SERVER_H) + if(!local_module_matches) + { + module_name = smsv_local_module_path_from_key(scratch.arena, str8_skip_last_slash(module_name), module->time_date_stamp, module->image_size); + smsv_fill_local_path(module_name); + } +#endif + // rjf: unpack module file File module_file = file_open(AccessFlag_Read|AccessFlag_ShareRead, module_name); FileProperties module_props = properties_from_file(module_file); diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index 0702e9b5..4ea507a3 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -2153,7 +2153,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P { Access *access = access_open(); D_ModuleInfo *info = d_info_from_module(access, params->entity); - String8 symbol_server_local_cache_path = smsv_local_path_from_key(scratch.arena, info->dbg_name, info->dbg_guid, info->dbg_age); + String8 symbol_server_local_cache_path = smsv_local_debug_info_path_from_key(scratch.arena, info->dbg_name, info->dbg_guid, info->dbg_age); smsv_fill_local_path(symbol_server_local_cache_path); D_Msg *msg = d_msg_list_push(scratch.arena, &ctrl_msgs); msg->kind = D_MsgKind_SetModuleDebugInfoPath; diff --git a/src/pe/pe.c b/src/pe/pe.c index 90a06fbd..e4eebb7a 100644 --- a/src/pe/pe.c +++ b/src/pe/pe.c @@ -627,6 +627,7 @@ pe_bin_info_from_data(Arena *arena, String8 data) info.arch = arch_from_coff_machine(file_header.machine); info.image_base = image_base; info.entry_point = entry_point; + info.timestamp = file_header.time_stamp; info.is_pe32 = (optional_magic == PE_PE32_MAGIC); info.subsystem = subsystem; info.check_sum = check_sum; diff --git a/src/pe/pe.h b/src/pe/pe.h index 84caf572..169b47c1 100644 --- a/src/pe/pe.h +++ b/src/pe/pe.h @@ -1009,6 +1009,7 @@ struct PE_BinInfo Arch arch; U64 image_base; U64 entry_point; + U32 timestamp; B32 is_pe32; PE_WindowsSubsystem subsystem; U32 *check_sum; diff --git a/src/symbol_server/symbol_server.h b/src/symbol_server/symbol_server.h index 2d9951bb..747005f4 100644 --- a/src/symbol_server/symbol_server.h +++ b/src/symbol_server/symbol_server.h @@ -18,7 +18,8 @@ SMSV_Status; internal void smsv_init(void); internal void smsv_async_tick(void); internal String8 smsv_cache_path(void); -internal String8 smsv_local_path_from_key(Arena *arena, String8 dbg_name, Guid guid, U64 age); +internal String8 smsv_local_module_path_from_key(Arena *arena, String8 module_name, U32 timestamp, U32 size_of_image); +internal String8 smsv_local_debug_info_path_from_key(Arena *arena, String8 dbg_name, Guid guid, U64 age); internal void smsv_fill_local_path(String8 path); internal SMSV_Status smsv_status_from_local_path(String8 path); diff --git a/src/symbol_server/symbol_server_stub.c b/src/symbol_server/symbol_server_stub.c index bbd71e35..ce691ce2 100644 --- a/src/symbol_server/symbol_server_stub.c +++ b/src/symbol_server/symbol_server_stub.c @@ -18,7 +18,13 @@ smsv_cache_path(void) } internal String8 -smsv_local_path_from_key(Arena *arena, String8 dbg_name, Guid guid, U64 age) +smsv_local_module_path_from_key(Arena *arena, String8 module_name, U32 timestamp, U32 size_of_image) +{ + return s(""); +} + +internal String8 +smsv_local_debug_info_path_from_key(Arena *arena, String8 dbg_name, Guid guid, U64 age) { return s(""); } diff --git a/src/win32/symbol_server/win32_symbol_server.c b/src/win32/symbol_server/win32_symbol_server.c index 74f5149c..52e19b88 100644 --- a/src/win32/symbol_server/win32_symbol_server.c +++ b/src/win32/symbol_server/win32_symbol_server.c @@ -359,7 +359,15 @@ smsv_cache_path(void) } internal String8 -smsv_local_path_from_key(Arena *arena, String8 dbg_name, Guid guid, U64 age) +smsv_local_module_path_from_key(Arena *arena, String8 module_name, U32 timestamp, U32 size_of_image) +{ + String8 symbol_cache_path = w32_smsv_state->symbol_cache_path; + String8 result = str8f(arena, "%S/%S/%08X%x/%S", symbol_cache_path, module_name, timestamp, size_of_image, module_name); + return result; +} + +internal String8 +smsv_local_debug_info_path_from_key(Arena *arena, String8 dbg_name, Guid guid, U64 age) { String8 result = {0}; Temp scratch = scratch_begin(&arena, 1); From 802de06f316c1bdc707627f7a12ccccd34ea9ae0 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 27 Aug 2026 15:20:53 -0700 Subject: [PATCH 115/251] debug info loading status viz; fix bad event string in module load path --- src/dbg_engine/dbg_engine_ctrl.c | 2 +- src/dbg_info/dbg_info.c | 4 ++++ src/dbg_info/dbg_info.h | 1 + src/raddbg/raddbg_core.c | 2 +- src/raddbg/raddbg_widgets.c | 15 ++++++++++++--- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index a386e67a..9af18b47 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -3292,7 +3292,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt1->vaddr_rng = r1u64(event->address, event->address+event->size); out_evt1->rip_vaddr = event->address; out_evt1->timestamp = exe_timestamp; - out_evt1->string = module_path; + out_evt1->string = str8_copy(scratch.arena, module_path); out_evt1->tls_index = event->module_info->tls_index; out_evt1->tls_offset = event->module_info->tls_offset; diff --git a/src/dbg_info/dbg_info.c b/src/dbg_info/dbg_info.c index ba0f0f3a..350e54d4 100644 --- a/src/dbg_info/dbg_info.c +++ b/src/dbg_info/dbg_info.c @@ -483,6 +483,10 @@ di_rdi_from_key(Access *access, DI_Key key, B32 high_priority, U64 endt_us) rdi = &n->rdi; access_touch(access, &n->access_pt, stripe->cv); } + if(ins_atomic_u64_eval(&n->working_count) != 0) + { + rdi = &di_rdi_parsed_nil_waiting; + } break; } } diff --git a/src/dbg_info/dbg_info.h b/src/dbg_info/dbg_info.h index 56c88acc..051ae7a5 100644 --- a/src/dbg_info/dbg_info.h +++ b/src/dbg_info/dbg_info.h @@ -305,6 +305,7 @@ struct DI_Shared //////////////////////////////// //~ rjf: Globals +global read_only RDI_Parsed di_rdi_parsed_nil_waiting = {0}; global DI_Shared *di_shared = 0; //////////////////////////////// diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 88c4427e..d151ffec 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -6180,7 +6180,7 @@ rd_window_frame(void) RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); if(rdi->raw_data_size != 0) { - ui_labelf("Debug information successfully loaded from %S", dbg_info_entity->string); + ui_labelf("Debug info successfully loaded from %S", dbg_info_entity->string); } else if(dbg_info_entity->string.size != 0) { diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 1b1548bb..c7551fe4 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -620,10 +620,19 @@ rd_title_fstrs_from_ctrl_entity(Arena *arena, D_Entity *entity, B32 include_extr Access *access = access_open(); DI_Key dbgi_key = d_dbgi_key_from_module(entity); RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); - if(rdi->raw_data_size == 0) + if(rdi == &di_rdi_parsed_nil_waiting) { - dr_fstrs_push_new(arena, &result, ¶ms, str8_lit(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, str8_lit("(Debug info not loaded)"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, s("(Loading debug info)"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + } + else if(rdi == &rdi_parsed_nil) + { + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, s("(Debug info not loaded)"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + } + else + { + } access_close(access); } From 04bd46dad6b9c0196f9b67c49707ecbcd8d2fa22 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 27 Aug 2026 15:40:27 -0700 Subject: [PATCH 116/251] revert ring push changes, can't be contiguous, duh... --- src/base/base_ring.c | 135 ++++++++++-------------------------- src/base/base_ring.h | 6 -- src/raddbg/raddbg_widgets.c | 34 +++++++-- 3 files changed, 64 insertions(+), 111 deletions(-) diff --git a/src/base/base_ring.c b/src/base/base_ring.c index a9145e82..d43f0955 100644 --- a/src/base/base_ring.c +++ b/src/base/base_ring.c @@ -13,42 +13,16 @@ make_ring(Arena *arena, U64 size) return ring; } -internal void * -ring_try_push(Ring *ring, U64 size) -{ - U64 bytes_unconsumed = (ring->write_pos - ring->read_pos); - U64 bytes_available = ring->size - bytes_unconsumed; - void *base = 0; - if(bytes_available >= size) - { - base = ring->base + ring->write_pos%ring->size; - ring->write_pos += size; - } - return base; -} - -internal void * -ring_try_pop(Ring *ring, U64 size) -{ - U64 bytes_unconsumed = (ring->write_pos - ring->read_pos); - void *base = 0; - if(bytes_unconsumed >= size) - { - base = ring->base + ring->read_pos%ring->size; - ring->read_pos += size; - } - return base; -} - internal B32 ring_try_write(Ring *ring, U64 size, void *ptr) { + U64 bytes_unconsumed = (ring->write_pos - ring->read_pos); + U64 bytes_available = ring->size - bytes_unconsumed; B32 result = 0; - void *dst = ring_try_push(ring, size); - if(dst) + if(bytes_available >= size) { result = 1; - MemoryCopy(dst, ptr, size); + ring->write_pos += wrapped_write(ring->base, ring->size, ring->write_pos, ptr, size); } return result; } @@ -56,12 +30,12 @@ ring_try_write(Ring *ring, U64 size, void *ptr) internal B32 ring_try_read(Ring *ring, U64 size, void *ptr) { + U64 bytes_unconsumed = (ring->write_pos - ring->read_pos); B32 result = 0; - void *src = ring_try_pop(ring, size); - if(src) + if(bytes_unconsumed >= size) { result = 1; - MemoryCopy(ptr, src, size); + ring->read_pos += wrapped_read(ring->base, ring->size, ring->read_pos, ptr, size); } return result; } @@ -72,22 +46,18 @@ ring_try_read(Ring *ring, U64 size, void *ptr) internal GuardedRing * guarded_ring_alloc(Arena *arena, U64 size) { - ProfBeginFunction(); GuardedRing *gr = push_array(arena, GuardedRing, 1); gr->ring = make_ring(arena, size); gr->mutex = mutex_alloc(); gr->cv = cond_var_alloc(); - ProfEnd(); return gr; } internal void guarded_ring_release(GuardedRing *ring) { - ProfBeginFunction(); mutex_release(ring->mutex); cond_var_release(ring->cv); - ProfEnd(); } internal RingGuard @@ -102,27 +72,16 @@ internal void guarded_ring_close(RingGuard *guard) { mutex_drop(guard->r->mutex); - cond_var_broadcast(guard->r->cv); -} - -internal void * -guarded_ring_try_push(RingGuard *guard, U64 size) -{ - void *result = ring_try_push(guard->r->ring, size); - return result; -} - -internal void * -guarded_ring_try_pop(RingGuard *guard, U64 size) -{ - void *result = ring_try_pop(guard->r->ring, size); - return result; } internal B32 guarded_ring_try_write(RingGuard *guard, U64 size, void *ptr) { B32 result = ring_try_write(guard->r->ring, size, ptr); + if(result) + { + cond_var_broadcast(guard->r->cv); + } return result; } @@ -130,43 +89,9 @@ internal B32 guarded_ring_try_read(RingGuard *guard, U64 size, void *ptr) { B32 result = ring_try_read(guard->r->ring, size, ptr); - return result; -} - -internal void * -guarded_ring_push_or_wait(RingGuard *guard, U64 size, U64 endt_us) -{ - void *result = 0; - for(;!result;) + if(result) { - result = guarded_ring_try_push(guard, size); - if(now_time_us() >= endt_us) - { - break; - } - if(!result) - { - cond_var_wait(guard->r->cv, guard->r->mutex, endt_us); - } - } - return result; -} - -internal void * -guarded_ring_pop_or_wait(RingGuard *guard, U64 size, U64 endt_us) -{ - void *result = 0; - for(;!result;) - { - result = guarded_ring_try_pop(guard, size); - if(now_time_us() >= endt_us) - { - break; - } - if(!result) - { - cond_var_wait(guard->r->cv, guard->r->mutex, endt_us); - } + cond_var_broadcast(guard->r->cv); } return result; } @@ -174,23 +99,37 @@ guarded_ring_pop_or_wait(RingGuard *guard, U64 size, U64 endt_us) internal B32 guarded_ring_write_or_wait(RingGuard *guard, U64 size, void *ptr, U64 endt_us) { - void *dst = guarded_ring_push_or_wait(guard, size, endt_us); - B32 result = !!dst; - if(dst) + B32 write_good = 0; + for(;!write_good;) { - MemoryCopy(dst, ptr, size); + write_good = guarded_ring_try_write(guard, size, ptr); + if(now_time_us() >= endt_us) + { + break; + } + if(!write_good) + { + cond_var_wait(guard->r->cv, guard->r->mutex, endt_us); + } } - return result; + return write_good; } internal B32 guarded_ring_read_or_wait(RingGuard *guard, U64 size, void *ptr, U64 endt_us) { - void *src = guarded_ring_pop_or_wait(guard, size, endt_us); - B32 result = !!src; - if(src) + B32 read_good = 0; + for(;!read_good;) { - MemoryCopy(ptr, src, size); + read_good = guarded_ring_try_read(guard, size, ptr); + if(now_time_us() >= endt_us) + { + break; + } + if(!read_good) + { + cond_var_wait(guard->r->cv, guard->r->mutex, endt_us); + } } - return result; + return read_good; } diff --git a/src/base/base_ring.h b/src/base/base_ring.h index f7e8e188..59b07b86 100644 --- a/src/base/base_ring.h +++ b/src/base/base_ring.h @@ -31,8 +31,6 @@ struct RingGuard //~ rjf: Ring Functions internal Ring *make_ring(Arena *arena, U64 size); -internal void *ring_try_push(Ring *ring, U64 size); -internal void *ring_try_pop(Ring *ring, U64 size); internal B32 ring_try_write(Ring *ring, U64 size, void *ptr); internal B32 ring_try_read(Ring *ring, U64 size, void *ptr); #define ring_try_write_struct(ring, ptr) ring_try_write((ring), sizeof(*(ptr)), (ptr)) @@ -45,14 +43,10 @@ internal GuardedRing *guarded_ring_alloc(Arena *arena, U64 size); internal void guarded_ring_release(GuardedRing *ring); internal RingGuard guarded_ring_open(GuardedRing *ring); internal void guarded_ring_close(RingGuard *guard); -internal void *guarded_ring_try_push(RingGuard *guard, U64 size); -internal void *guarded_ring_try_pop(RingGuard *guard, U64 size); internal B32 guarded_ring_try_write(RingGuard *guard, U64 size, void *ptr); internal B32 guarded_ring_try_read(RingGuard *guard, U64 size, void *ptr); #define guarded_ring_try_write_struct(ring, ptr) guarded_ring_try_write((ring), sizeof(*(ptr)), (ptr)) #define guarded_ring_try_read_struct(ring, ptr) guarded_ring_try_read((ring), sizeof(*(ptr)), (ptr)) -internal void *guarded_ring_push_or_wait(RingGuard *guard, U64 size, U64 endt_us); -internal void *guarded_ring_pop_or_wait(RingGuard *guard, U64 size, U64 endt_us); internal B32 guarded_ring_write_or_wait(RingGuard *guard, U64 size, void *ptr, U64 endt_us); internal B32 guarded_ring_read_or_wait(RingGuard *guard, U64 size, void *ptr, U64 endt_us); #define guarded_ring_write_struct_or_wait(ring, ptr, endt_us) guarded_ring_write_or_wait((ring), sizeof(*(ptr)), (ptr), (endt_us)) diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index c7551fe4..d4d7ac73 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -618,21 +618,41 @@ rd_title_fstrs_from_ctrl_entity(Arena *arena, D_Entity *entity, B32 include_extr if(entity->kind == D_EntityKind_Module && include_extras) { Access *access = access_open(); - DI_Key dbgi_key = d_dbgi_key_from_module(entity); + D_Entity *debug_info_path = d_entity_child_from_kind(entity, D_EntityKind_DebugInfoPath); + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(debug_info_path); RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); if(rdi == &di_rdi_parsed_nil_waiting) { - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, s("(Loading debug info)"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + UI_TagF("pop") + { + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, s("Loading debug info"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_CircleFilled], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + } } - else if(rdi == &rdi_parsed_nil) + else if(rdi->raw_data_size == 0) { - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, s("(Debug info not loaded)"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + UI_TagF("bad_pop") + { + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, s("Debug info not found"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_X], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + } } else { - + UI_TagF("good_pop") + { + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, str8_skip_last_slash(debug_info_path->string), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_Check], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + } } access_close(access); } From 313a51ba21218435068c6d48a7bae252bb1f9382 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 27 Aug 2026 15:55:39 -0700 Subject: [PATCH 117/251] setting for use-alternate-ui-font (on by default) --- src/raddbg/generated/raddbg.meta.c | 2 +- src/raddbg/raddbg.mdesk | 8 +++-- src/raddbg/raddbg_core.c | 54 +++++++++++++++++++----------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 7e5759da..52ab0f05 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -438,7 +438,7 @@ RD_VocabInfo rd_vocab_info_table[370] = RD_NameSchemaInfo rd_name_schema_info_table[39] = { -{str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n\n //- rjf: system installation\n @display_name('Install To System') @description(\"Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.\") 'install_to_system': bool,\n}\n")}, +{str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('Use Alternative Font For UI') @description(\"Uses an alternative font for non-code UI elements.\")\n @default(1) 'use_alternative_font_for_ui': bool,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n @expand_if(\"!$.use_alternative_font_for_ui\") @display_name('Font') @description(\"The name of, or path to, the font used in all UI.\")\n @default('') 'font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n\n //- rjf: system installation\n @display_name('Install To System') @description(\"Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.\") 'install_to_system': bool,\n}\n")}, {str8_lit_comp("project"), 0, str8_lit_comp("@expand_commands(edit_project_theme) x:\n{\n @display_name('Project Name') 'name': string,\n @default(2) @display_name('Project Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: visualizers\n @display_name('Display Pointer Addresses Before Contents') @description(\"When visualizing pointers, always shows the address first, before showing contents at the pointer's address.\")\n @default(0) display_pointer_addresses_before_contents: bool,\n @display_name('Use Default C++ STL Type Visualizers') @description(\"Enables the built-in type views for C++ STL types.\")\n @default(1) use_default_stl_type_views: bool,\n @display_name('Use Default Unreal Engine Type Visualizers') @description(\"Enables the built-in type views for Unreal Engine types.\")\n @default(1) use_default_ue_type_views: bool,\n\n //- rjf: theme\n @default(\"None\") @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme': string,\n @no_expand @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme_colors': set,\n\n //- rjf: exception settings\n @default(1) @display_name(\"Break On Win32 Control-C Exceptions\") @description(\"Code: 0x40010005\")\n win32_ctrl_c: bool;\n @default(1) @display_name(\"Break On Win32 Control-Break Exceptions\") @description(\"Code: 0x40010008\")\n win32_ctrl_break: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Originate Error Exceptions\") @description(\"Code: 0x40080201\")\n win32_win_rt_originate_error: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Transform Error Exceptions\") @description(\"Code: 0x40080202\")\n win32_win_rt_transform_error: bool;\n @default(0) @display_name(\"Break On Win32 RPC Call Cancelled Exceptions\") @description(\"Code: 0x0000071a\")\n win32_rpc_call_cancelled: bool;\n @default(0) @display_name(\"Break On Win32 Data Type Misalignment Exceptions\") @description(\"Code: 0x80000002\")\n win32_datatype_misalignment: bool;\n @default(1) @display_name(\"Break On Win32 Access Violation Exceptions\") @description(\"Code: 0xc0000005\")\n win32_access_violation: bool;\n @default(0) @display_name(\"Break On Win32 In Page Error Exceptions\") @description(\"Code: 0xc0000006\")\n win32_in_page_error: bool;\n @default(1) @display_name(\"Break On Win32 Invalid Handle Specified Exceptions\") @description(\"Code: 0xc0000008\")\n win32_invalid_handle: bool;\n @default(0) @display_name(\"Break On Win32 Not Enough Quota Exceptions\") @description(\"Code: 0xc0000017\")\n win32_not_enough_quota: bool;\n @default(0) @display_name(\"Break On Win32 Illegal Instruction Exceptions\") @description(\"Code: 0xc000001d\")\n win32_illegal_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Cannot Continue From Exception Exceptions\") @description(\"Code: 0xc0000025\")\n win32_cannot_continue_exception: bool;\n @default(0) @display_name(\"Break On Win32 Invalid Exception Disposition Returned By Handler Exceptions\") @description(\"Code: 0xc0000026\")\n win32_invalid_exception_disposition: bool;\n @default(0) @display_name(\"Break On Win32 Array Bounds Exceeded Exceptions\") @description(\"Code: 0xc000008c\")\n win32_array_bounds_exceeded: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Denormal Operand Exceptions\") @description(\"Code: 0xc000008d\")\n win32_floating_point_denormal_operand: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Division By Zero Exceptions\") @description(\"Code: 0xc000008e\")\n win32_floating_point_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Inexact Result Exceptions\") @description(\"Code: 0xc000008f\")\n win32_floating_point_inexact_result: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Invalid Operation Exceptions\") @description(\"Code: 0xc0000090\")\n win32_floating_point_invalid_operation: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Overflow Exceptions\") @description(\"Code: 0xc0000091\")\n win32_floating_point_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Stack Check Exceptions\") @description(\"Code: 0xc0000092\")\n win32_floating_point_stack_check: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Underflow Exceptions\") @description(\"Code: 0xc0000093\")\n win32_floating_point_underflow: bool;\n @default(0) @display_name(\"Break On Win32 Integer Division By Zero Exceptions\") @description(\"Code: 0xc0000094\")\n win32_integer_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Integer Overflow Exceptions\") @description(\"Code: 0xc0000095\")\n win32_integer_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Privileged Instruction Exceptions\") @description(\"Code: 0xc0000096\")\n win32_privileged_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Stack Overflow Exceptions\") @description(\"Code: 0xc00000fd\")\n win32_stack_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Unable To Locate DLL Exceptions\") @description(\"Code: 0xc0000135\")\n win32_unable_to_locate_dll: bool;\n @default(0) @display_name(\"Break On Win32 Ordinal Not Found Exceptions\") @description(\"Code: 0xc0000138\")\n win32_ordinal_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Entry Point Not Found Exceptions\") @description(\"Code: 0xc0000139\")\n win32_entry_point_not_found: bool;\n @default(0) @display_name(\"Break On Win32 DLL Initialization Failed Exceptions\") @description(\"Code: 0xc0000142\")\n win32_dll_initialization_failed: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Faults Exceptions\") @description(\"Code: 0xc00002b4\")\n win32_floating_point_sse_multiple_faults: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Traps Exceptions\") @description(\"Code: 0xc00002b5\")\n win32_floating_point_sse_multiple_traps: bool;\n @default(1) @display_name(\"Break On Win32 Assertion Failed Exceptions\") @description(\"Code: 0xc0000420\")\n win32_assertion_failed: bool;\n @default(0) @display_name(\"Break On Win32 Module Not Found Exceptions\") @description(\"Code: 0xc06d007e\")\n win32_module_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Procedure Not Found Exceptions\") @description(\"Code: 0xc06d007f\")\n win32_procedure_not_found: bool;\n @default(1) @display_name(\"Break On Win32 Sanitizer Error Detected Exceptions\") @description(\"Code: 0xe073616e\")\n win32_sanitizer_error_detected: bool;\n @default(0) @display_name(\"Break On Win32 Sanitizer Raw Access Violation Exceptions\") @description(\"Code: 0xe0736171\")\n win32_sanitizer_raw_access_violation: bool;\n @default(1) @display_name(\"Break On Win32 DirectX Debug Layer Exceptions\") @description(\"Code: 0x0000087a\")\n win32_directx_debug_layer: bool;\n}\n")}, {str8_lit_comp("theme_color"), 0, str8_lit_comp("@collection_commands(add_theme_color, fork_theme, save_theme, save_and_set_theme)\n@row_commands(duplicate_cfg, remove_cfg)\nx:\n{\n @display_name('Tags') tags: string,\n @display_name('Value') value: @color @hex u32,\n}\n")}, {str8_lit_comp("window"), 0, str8_lit_comp("x:\n{\n //- rjf: text rasterization settings\n @default(1) @display_name('Smooth UI Text') @description(\"Controls whether or not UI text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_ui_text': bool,\n @default(1) @display_name('Hint UI Text') @description(\"Controls whether or not UI text is hinted, for better text readability at small sizes.\")\n 'hint_ui_text': bool,\n @default(0) @display_name('Smooth Code Text') @description(\"Controls whether or not code text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_code_text': bool,\n @default(1) @display_name('Hint Code Text') @description(\"Controls whether or not code text is hinted, for better text readability at small sizes.\")\n 'hint_code_text': bool,\n @default(11) @display_name('Window Font Size') @description(\"Controls the window's default font size. Does not apply to tabs with their own font size set.\")\n 'font_size': @range[6, 72] u64,\n\n //- rjf: size settings\n @default(3.f) @display_name('Window Row Height') @description(\"Controls the window's default row height, in multiples of the font size. Does not apply to tabs with their own row height set.\")\n 'row_height': @range[1.75f, 5.f] f32,\n @default(3.f) @description(\"Controls the height of tabs, in multiples of the font size.\")\n 'tab_height': @range[1.75f, 5.f] f32,\n\n //- rjf: theme settings\n @default(1) @display_name('Use Project Theme') @description(\"Prefer using the project theme for this window, if any. If off, only the user's theme settings will be used.\")\n 'use_project_theme': bool,\n}\n")}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 2c21d406..a1ec9285 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -245,10 +245,14 @@ RD_VocabTable: @expand_if("$.animations") @default(1) 'menu_animations': bool, //- rjf: fonts - @display_name('UI Font') @description("The name of, or path to, the font used when displaying non-code UI elements.") + @display_name('Use Alternative Font For UI') @description("Uses an alternative font for non-code UI elements.") + @default(1) 'use_alternative_font_for_ui': bool, + @expand_if("$.use_alternative_font_for_ui") @display_name('UI Font') @description("The name of, or path to, the font used when displaying non-code UI elements.") @default('') 'main_font': string, - @display_name('Code Font') @description("The name of, or path to, the font used when displaying code.") + @expand_if("$.use_alternative_font_for_ui") @display_name('Code Font') @description("The name of, or path to, the font used when displaying code.") @default('') 'code_font': string, + @expand_if("!$.use_alternative_font_for_ui") @display_name('Font') @description("The name of, or path to, the font used in all UI.") + @default('') 'font': string, //- rjf: theme @default("Default (Dark)") @display_name('User Theme') diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index d151ffec..02feb823 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -12144,26 +12144,6 @@ rd_frame(void) rd_state->key_map = cfg_key_map_from_cfg(rd_frame_arena()); } - ////////////////////////////// - //- rjf: get fonts from config - // - ProfScope("get fonts from config") - { - String8 main_font_name = rd_setting_from_name(str8_lit("main_font")); - String8 code_font_name = rd_setting_from_name(str8_lit("code_font")); - rd_state->font_slot_table[RD_FontSlot_Main] = fnt_tag_from_path(main_font_name); - rd_state->font_slot_table[RD_FontSlot_Code] = fnt_tag_from_path(code_font_name); - if(fnt_tag_match(rd_state->font_slot_table[RD_FontSlot_Main], fnt_tag_zero())) - { - rd_state->font_slot_table[RD_FontSlot_Main] = fnt_tag_from_static_data_string(&rd_default_main_font_bytes); - } - if(fnt_tag_match(rd_state->font_slot_table[RD_FontSlot_Code], fnt_tag_zero())) - { - rd_state->font_slot_table[RD_FontSlot_Code] = fnt_tag_from_static_data_string(&rd_default_code_font_bytes); - } - rd_state->font_slot_table[RD_FontSlot_Icons] = fnt_tag_from_static_data_string(&rd_icon_font_bytes); - } - ////////////////////////////// //- rjf: consume events // @@ -18335,6 +18315,40 @@ rd_frame(void) } } + ////////////////////////////// + //- rjf: get fonts from config + // + ProfScope("get fonts from config") + { + B32 use_alternative_font_for_ui = rd_setting_b32_from_name(s("use_alternative_font_for_ui")); + if(use_alternative_font_for_ui) + { + String8 main_font_name = rd_setting_from_name(s("main_font")); + String8 code_font_name = rd_setting_from_name(s("code_font")); + rd_state->font_slot_table[RD_FontSlot_Main] = fnt_tag_from_path(main_font_name); + rd_state->font_slot_table[RD_FontSlot_Code] = fnt_tag_from_path(code_font_name); + if(fnt_tag_match(rd_state->font_slot_table[RD_FontSlot_Main], fnt_tag_zero())) + { + rd_state->font_slot_table[RD_FontSlot_Main] = fnt_tag_from_static_data_string(&rd_default_main_font_bytes); + } + } + else + { + String8 font_name = rd_setting_from_name(s("font")); + rd_state->font_slot_table[RD_FontSlot_Code] = fnt_tag_from_path(font_name); + rd_state->font_slot_table[RD_FontSlot_Main] = rd_state->font_slot_table[RD_FontSlot_Code]; + if(fnt_tag_match(rd_state->font_slot_table[RD_FontSlot_Main], fnt_tag_zero())) + { + rd_state->font_slot_table[RD_FontSlot_Main] = fnt_tag_from_static_data_string(&rd_default_code_font_bytes); + } + } + if(fnt_tag_match(rd_state->font_slot_table[RD_FontSlot_Code], fnt_tag_zero())) + { + rd_state->font_slot_table[RD_FontSlot_Code] = fnt_tag_from_static_data_string(&rd_default_code_font_bytes); + } + rd_state->font_slot_table[RD_FontSlot_Icons] = fnt_tag_from_static_data_string(&rd_icon_font_bytes); + } + //////////////////////////// //- rjf: remove run-to-line info // From c97907a7b970eb58c4b9c3b257868c9f97c7ca1d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 27 Aug 2026 19:24:16 -0600 Subject: [PATCH 118/251] fix overly aggressive tls module base vaddr lookup cache retry --- project.raddbg | 8 +++++++- src/dbg_engine/dbg_engine_ctrl.c | 10 ++++++++-- src/linux/demon/linux_demon.c | 19 +++++++++++-------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/project.raddbg b/project.raddbg index e1df3c4e..83038133 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,7 +7,6 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { @@ -31,3 +30,10 @@ target: executable: "build/ryan_scratch.exe" working_directory: build } +target: +{ + executable: "build/raddbg" + working_directory: build + arguments: "--user:raddbg_test.user" + enabled: 1 +} diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 9af18b47..ba69ae65 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -6317,6 +6317,7 @@ d_tls_vaddr_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_o DMN_Handle module_dmn = d_dmn_from_handle(module); // rjf: compute vaddr + U64 pre_run_gen = d_run_gen(); U64 tls_vaddr = 0; B32 success = 1; if(!dmn_handle_match(thread_dmn, dmn_handle_zero()) && @@ -6324,12 +6325,17 @@ d_tls_vaddr_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_o { success = dmn_thread_get_module_tls_vaddr(thread_dmn, module_dmn, &tls_vaddr); } + U64 post_run_gen = d_run_gen(); - // rjf: if not successful -> retry - if(!success) + // rjf: if not successful due to run -> retry + if(!success && (pre_run_gen != post_run_gen || d_ctrl_state->ctrl_thread_run_state)) { status_out[0] = AC_Status_NeedRetry; } + else if(!success) + { + status_out[0] = AC_Status_Failed; + } // rjf: package as artifact AC_Artifact artifact = {0}; diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 3633f869..b78c528d 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -3350,16 +3350,19 @@ dmn_thread_get_module_tls_vaddr(DMN_Handle thread_handle, DMN_Handle module_hand Arch arch = thread->process->ctx->arch; U64 addr_size = byte_size_from_arch(arch); U64 dtv_base_vaddr = thread->dtv_base_vaddr; - U64 dtv_entry_size = 2 * addr_size; - U64 module_dtv_ptr_vaddr = dtv_base_vaddr + module->tls_index * dtv_entry_size; - U64 module_dtv_vaddr = 0; - if(lnx_dmn_read(process->fd, r1u64(module_dtv_ptr_vaddr, module_dtv_ptr_vaddr+addr_size), &module_dtv_vaddr) == addr_size) + if(dtv_base_vaddr != 0) { - U64 unallocated_dtv_vaddr_marker = (addr_size == 4 ? max_U32 : max_U64); - if(module_dtv_vaddr != unallocated_dtv_vaddr_marker) + U64 dtv_entry_size = 2 * addr_size; + U64 module_dtv_ptr_vaddr = dtv_base_vaddr + module->tls_index * dtv_entry_size; + U64 module_dtv_vaddr = 0; + if(lnx_dmn_read(process->fd, r1u64(module_dtv_ptr_vaddr, module_dtv_ptr_vaddr+addr_size), &module_dtv_vaddr) == addr_size) { - vaddr_out[0] = module_dtv_vaddr + module->tls_offset; - result = 1; + U64 unallocated_dtv_vaddr_marker = (addr_size == 4 ? max_U32 : max_U64); + if(module_dtv_vaddr != unallocated_dtv_vaddr_marker) + { + vaddr_out[0] = module_dtv_vaddr + module->tls_offset; + result = 1; + } } } } From 7a5a649b89f6a06abdbaee61540468fca9db58f9 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 27 Aug 2026 20:00:01 -0600 Subject: [PATCH 119/251] fix incorrect dwarf file deduplication --- src/rdi_from_dwarf/rdi_from_dwarf.c | 58 +++++++++++++++-------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index 7fc368f0..3e05376b 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -436,37 +436,41 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) for EachIndex(file_idx, hdr->files.count) { DW2_LineTableFile *f = &hdr->files.v[file_idx]; - DW2_LineTableFile *dir = &hdr->dirs.v[f->dir_idx]; - String8 full_file_path = str8f(scratch2.arena, "%S%s%S", dir->file_name, dir->file_name.size != 0 ? "/" : "", f->file_name); - U64 hash = u64_hash_from_str8(full_file_path); - U64 slot_idx = hash%slots_count; - SrcFileNode *node = 0; - for(SrcFileNode *n = slots[slot_idx]; n != 0; n = n->next) + if(f->file_name.size != 0) { - if(str8_match(n->full_path, full_file_path, 0)) + DW2_LineTableFile *dir = &hdr->dirs.v[f->dir_idx]; + String8 full_file_path = str8f(scratch2.arena, "%S%s%S", dir->file_name, dir->file_name.size != 0 ? "/" : "", f->file_name); + U64 hash = u64_hash_from_str8(full_file_path); + U64 slot_idx = hash%slots_count; + SrcFileNode *node = 0; + for(SrcFileNode *n = slots[slot_idx]; n != 0; n = n->next) { - node = n; - break; + if(str8_match(n->full_path, full_file_path, 0)) + { + node = n; + break; + } } + if(!node) + { + node = push_array(scratch2.arena, SrcFileNode, 1); + SLLStackPush(slots[slot_idx], node); + node->full_path = full_file_path; + node->src_file = rdim_src_file_chunk_list_push(arena, all_src_files, slots_count); + node->src_file->path = str8_copy(arena, full_file_path); + if(f->flags & DW2_LineTableFileFlag_HasMD5) + { + node->src_file->checksum_kind = RDI_ChecksumKind_MD5; + node->src_file->checksum = str8_copy(arena, str8_struct(&f->md5)); + } + else if(f->flags & DW2_LineTableFileFlag_HasModifyTime) + { + node->src_file->checksum_kind = RDI_ChecksumKind_Timestamp; + node->src_file->checksum = str8_copy(arena, str8_struct(&f->modify_time)); + } + } + unit_src_file_maps[unit_idx].v[file_idx] = node->src_file; } - if(!node) - { - node = push_array(scratch2.arena, SrcFileNode, 1); - node->full_path = full_file_path; - node->src_file = rdim_src_file_chunk_list_push(arena, all_src_files, slots_count); - node->src_file->path = str8_copy(arena, full_file_path); - if(f->flags & DW2_LineTableFileFlag_HasMD5) - { - node->src_file->checksum_kind = RDI_ChecksumKind_MD5; - node->src_file->checksum = str8_copy(arena, str8_struct(&f->md5)); - } - else if(f->flags & DW2_LineTableFileFlag_HasModifyTime) - { - node->src_file->checksum_kind = RDI_ChecksumKind_Timestamp; - node->src_file->checksum = str8_copy(arena, str8_struct(&f->modify_time)); - } - } - unit_src_file_maps[unit_idx].v[file_idx] = node->src_file; } } scratch_end(scratch2); From 8d869affca2529e1bc58bcbf54b38bff896958c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Mo=C5=BEeiko?= Date: Wed, 2 Sep 2026 18:26:14 -0700 Subject: [PATCH 120/251] blake3: update to 1.8.7 version and use asm implementations Assembly code is >25% faster than intrinsic implementations on msvc. --- .github/workflows/builds.yml | 2 + build.bat | 16 + build.sh | 17 +- src/linker/base_ext/base_blake3.c | 99 +- src/linker/base_ext/base_blake3.h | 2 +- src/third_party/blake3/LICENSE_CC0 | 121 + src/third_party/blake3/asm/LICENSE | 330 --- src/third_party/blake3/asm/blake3.h | 82 - src/third_party/blake3/asm/blake3_dispatch.c | 278 -- src/third_party/blake3/{asm => }/blake3.c | 131 +- src/third_party/blake3/{c => }/blake3.h | 8 +- .../{asm => }/blake3_avx2_x86-64_unix.S | 0 .../blake3_avx2_x86-64_windows_msvc.asm | 0 .../{asm => }/blake3_avx512_x86-64_unix.S | 2239 +++++++++++++++++ .../blake3_avx512_x86-64_windows_msvc.asm | 0 .../blake3/{c => }/blake3_dispatch.c | 74 +- .../blake3/{asm => }/blake3_impl.h | 52 +- .../blake3/{asm => }/blake3_neon.c | 17 +- .../blake3/{asm => }/blake3_portable.c | 0 .../{asm => }/blake3_sse2_x86-64_unix.S | 0 .../blake3_sse2_x86-64_windows_msvc.asm | 0 .../{asm => }/blake3_sse41_x86-64_unix.S | 0 .../blake3_sse41_x86-64_windows_msvc.asm | 0 src/third_party/blake3/c/LICENSE | 330 --- src/third_party/blake3/c/blake3.c | 616 ----- src/third_party/blake3/c/blake3_avx2.c | 312 --- src/third_party/blake3/c/blake3_avx512.c | 1220 --------- src/third_party/blake3/c/blake3_impl.h | 285 --- src/third_party/blake3/c/blake3_neon.c | 368 --- src/third_party/blake3/c/blake3_portable.c | 160 -- src/third_party/blake3/c/blake3_sse2.c | 566 ----- src/third_party/blake3/c/blake3_sse41.c | 560 ----- 32 files changed, 2585 insertions(+), 5300 deletions(-) create mode 100644 src/third_party/blake3/LICENSE_CC0 delete mode 100644 src/third_party/blake3/asm/LICENSE delete mode 100644 src/third_party/blake3/asm/blake3.h delete mode 100644 src/third_party/blake3/asm/blake3_dispatch.c rename src/third_party/blake3/{asm => }/blake3.c (87%) rename src/third_party/blake3/{c => }/blake3.h (90%) rename src/third_party/blake3/{asm => }/blake3_avx2_x86-64_unix.S (100%) rename src/third_party/blake3/{asm => }/blake3_avx2_x86-64_windows_msvc.asm (100%) rename src/third_party/blake3/{asm => }/blake3_avx512_x86-64_unix.S (55%) rename src/third_party/blake3/{asm => }/blake3_avx512_x86-64_windows_msvc.asm (100%) rename src/third_party/blake3/{c => }/blake3_dispatch.c (85%) rename src/third_party/blake3/{asm => }/blake3_impl.h (84%) rename src/third_party/blake3/{asm => }/blake3_neon.c (96%) rename src/third_party/blake3/{asm => }/blake3_portable.c (100%) rename src/third_party/blake3/{asm => }/blake3_sse2_x86-64_unix.S (100%) rename src/third_party/blake3/{asm => }/blake3_sse2_x86-64_windows_msvc.asm (100%) rename src/third_party/blake3/{asm => }/blake3_sse41_x86-64_unix.S (100%) rename src/third_party/blake3/{asm => }/blake3_sse41_x86-64_windows_msvc.asm (100%) delete mode 100644 src/third_party/blake3/c/LICENSE delete mode 100644 src/third_party/blake3/c/blake3.c delete mode 100644 src/third_party/blake3/c/blake3_avx2.c delete mode 100644 src/third_party/blake3/c/blake3_avx512.c delete mode 100644 src/third_party/blake3/c/blake3_impl.h delete mode 100644 src/third_party/blake3/c/blake3_neon.c delete mode 100644 src/third_party/blake3/c/blake3_portable.c delete mode 100644 src/third_party/blake3/c/blake3_sse2.c delete mode 100644 src/third_party/blake3/c/blake3_sse41.c diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 58a20867..f666f2fc 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -45,6 +45,7 @@ jobs: - name: build shell: bash run: | + [[ "${{ matrix.compiler }}" == "clang-22" ]] && export AR=llvm-ar-22 CC="${{ matrix.compiler }}" ./build.sh "${{ matrix.target }}" "${{ matrix.mode }}" build-windows-2022: @@ -103,6 +104,7 @@ jobs: - name: Run shell: bash run: | + [[ "${{ matrix.compiler }}" == "clang-22" ]] && export AR=llvm-ar-22 CC="${{ matrix.compiler }}" ./run_tests.sh clang "${{ matrix.mode }}" -- \* $TORTURE_FLAGS - name: Upload Artifacts if: failure() diff --git a/build.bat b/build.bat index e51a529e..6a692d18 100644 --- a/build.bat +++ b/build.bat @@ -98,6 +98,10 @@ if "%msvc%"=="1" set rc=call rc if "%clang%"=="1" set rc=call llvm-rc if "%msvc%"=="1" set link_dll=/link /DLL if "%clang%"=="1" set link_dll=-Xlinker -DLL +if "%msvc%"=="1" set asm=call ml64 -nologo -c -Zi -Fo +if "%clang%"=="1" set asm=call ml64 -nologo -c -Zi -Fo +if "%msvc%"=="1" set mklib=call lib -nologo +if "%clang%"=="1" set mklib=call llvm-lib :: --- Choose Compile/Link Lines ---------------------------------------------- if "%msvc%"=="1" set compile_debug=%cl_debug% @@ -138,6 +142,18 @@ if "%no_meta%"=="" if exist metagen.exe ( ) popd +:: --- Assemble BLAKE3 -------------------------------------------------------- +if not exist build\blake3.lib if "%VSCMD_ARG_TGT_ARCH%" equ "x64" ( + echo [assembling blake3] + pushd build + %asm% blake3_sse2_x86-64_windows_msvc.obj ..\src\third_party\blake3\blake3_sse2_x86-64_windows_msvc.asm || exit /b 1 + %asm% blake3_sse41_x86-64_windows_msvc.obj ..\src\third_party\blake3\blake3_sse41_x86-64_windows_msvc.asm || exit /b 1 + %asm% blake3_avx2_x86-64_windows_msvc.obj ..\src\third_party\blake3\blake3_avx2_x86-64_windows_msvc.asm || exit /b 1 + %asm% blake3_avx512_x86-64_windows_msvc.obj ..\src\third_party\blake3\blake3_avx512_x86-64_windows_msvc.asm || exit /b 1 + %mklib% -out:blake3.lib blake3_*_msvc.obj || exit /b 1 + popd +) + :: --- Build Everything (@build_targets) -------------------------------------- pushd build if "%raddbg%"=="1" set didbuild=1 && %compile% ..\src\raddbg\raddbg_main.c %compile_link% %link_icon% %out%raddbg.exe || exit /b 1 diff --git a/build.sh b/build.sh index 3724a40d..a3d11950 100755 --- a/build.sh +++ b/build.sh @@ -42,8 +42,8 @@ fi cc_icon="-DLNX_WM_ICON=1" # --- Choose Compile/Link Lines ----------------------------------------------- -if [[ "${gcc:-0}" == "1" ]]; then compiler="${CC:-gcc} $cc_cflags_gcc"; echo "[gcc compile]"; -elif [[ "${clang:-1}" == "1" ]]; then compiler="${CC:-clang} $cc_cflags_clang"; echo "[clang compile]"; +if [[ "${gcc:-0}" == "1" ]]; then compiler="${CC:-gcc} $cc_cflags_gcc"; ar="${AR:-ar}"; echo "[gcc compile]"; +elif [[ "${clang:-1}" == "1" ]]; then compiler="${CC:-clang} $cc_cflags_clang"; ar="${AR:-llvm-ar}"; echo "[clang compile]"; fi if [[ "${release:-0}" == "1" ]]; then echo "[release mode]"; compile="$compiler $cc_release"; elif [[ "${debug:-1}" == "1" ]]; then echo "[debug mode]"; compile="$compiler $cc_debug"; @@ -62,12 +62,23 @@ then cd .. fi +# --- Assemble BLAKE3 --------------------------------------------------------- +if [[ ! -f "build/blake3.a" ]] +then + echo "[assembling blake3]" + $compiler -c -g -o build/blake3_sse2_x86-64_unix.o src/third_party/blake3/blake3_sse2_x86-64_unix.S + $compiler -c -g -o build/blake3_sse41_x86-64_unix.o src/third_party/blake3/blake3_sse41_x86-64_unix.S + $compiler -c -g -o build/blake3_avx2_x86-64_unix.o src/third_party/blake3/blake3_avx2_x86-64_unix.S + $compiler -c -g -o build/blake3_avx512_x86-64_unix.o src/third_party/blake3/blake3_avx512_x86-64_unix.S + $ar rs build/blake3.a build/blake3_*_unix.o +fi + # --- Build Everything (@build_targets) --------------------------------------- cd build if [[ "${raddbg:-0}" == "1" ]]; then didbuild=1 && $compile ../src/raddbg/raddbg_main.c $cc_icon $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg; fi if [[ "${raddbg_non_graphical:-0}" == "1" ]]; then didbuild=1 && $compile ../src/raddbg/raddbg_main.c -DWM_STUB=1 -DR_BACKEND=R_BACKEND_STUB $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg_non_graphical; fi if [[ "${radbin:-0}" == "1" ]]; then didbuild=1 && $compile ../src/radbin/radbin_main.c $cc_link -o radbin; fi -if [[ "${radlink:-0}" == "1" ]]; then didbuild=1 && $compile ../src/linker/lnk.c $cc_link -o radlink; fi +if [[ "${radlink:-0}" == "1" ]]; then didbuild=1 && $compile ../src/linker/lnk.c $cc_link -o radlink blake3.a; fi if [[ "${torture:-0}" == "1" ]]; then didbuild=1 && $compile ../src/torture/torture_main.c $cc_link $cc_os_gfx $cc_render $cc_font_provider -o torture; fi cd .. diff --git a/src/linker/base_ext/base_blake3.c b/src/linker/base_ext/base_blake3.c index 59f063c4..df932455 100644 --- a/src/linker/base_ext/base_blake3.c +++ b/src/linker/base_ext/base_blake3.c @@ -1,103 +1,16 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmacro-redefined" -#pragma clang diagnostic ignored "-Wsign-conversion" -#elif defined(_MSC_VER) -#pragma warning (push, 0) -#endif -#include "third_party/blake3/c/blake3_portable.c" - -#if defined(_M_AMD64) || defined(__x86_64__) - -#define round_fn sse2_round_fn -#define compress_pre sse2_compress_pre - -#include "third_party/blake3/c/blake3_sse2.c" - -#define loadu sse41_loadu -#define storeu sse41_storeu -#define addv sse41_addv -#define xorv sse41_xorv -#define set1 sse41_set1 -#define set4 sse41_set4 -#define rot16 sse41_rot16 -#define rot12 sse41_rot12 -#define rot8 sse41_rot8 -#define rot7 sse41_rot7 -#define g1 sse41_g1 -#define g2 sse41_g2 -#define diagonalize sse41_diagonalize -#define undiagonalize sse41_undiagonalize -#define compress_pre sse41_compress_pre -#define round_fn sse41_round_fn -#define transpose_vecs sse41_transpose_vecs -#define transpose_msg_vecs sse41_transpose_msg_vecs -#define load_counters sse41_load_counters - -#if defined(__clang__) -#pragma clang attribute push(__attribute__((target("sse4.1"))), apply_to=function) -#endif -#include "third_party/blake3/c/blake3_sse41.c" -#if defined(__clang__) -#pragma clang attribute pop -#endif - -#define loadu avx2_loadu -#define storeu avx2_storeu -#define addv avx2_addv -#define xorv avx2_xorv -#define set1 avx2_set1 -#define rot7 avx2_rot7 -#define rot8 avx2_rot8 -#define rot12 avx2_rot12 -#define rot16 avx2_rot16 -#define round_fn avx2_round_fn -#define transpose_vecs avx2_transpose_vecs -#define transpose_msg_vecs avx2_transpose_msg_vecs -#define load_counters avx2_load_counters - -#if defined(__clang__) -#pragma clang attribute push(__attribute__((target("avx2"))), apply_to=function) -#endif -#include "third_party/blake3/c/blake3_avx2.c" -#if defined(__clang__) -#pragma clang attribute pop -#endif - -#define set4 avx512_set4 -#define g1 avx512_g1 -#define g2 avx512_g2 -#define diagonalize avx512_diagonalize -#define undiagonalize avx512_undiagonalize -#define compress_pre avx512_compress_pre -#define transpose_vecs avx512_transpose_vecs -#define transpose_msg_vecs avx512_transpose_msg_vecs -#define load_counters avx512_load_counters - -#if defined(__clang__) -#pragma clang attribute push(__attribute__((target("avx512f,avx512vl"))), apply_to=function) -#endif -#include "third_party/blake3/c/blake3_avx512.c" -#if defined(__clang__) -#pragma clang attribute pop -#endif - -#endif +#include "third_party/blake3/blake3_portable.c" #if defined(__aarch64__) || defined(_M_ARM64) -#include "third_party/blake3/c/blake3_neon.c" +#include "third_party/blake3/blake3_neon.c" #endif -#include "third_party/blake3/c/blake3_dispatch.c" -#include "third_party/blake3/c/blake3.c" +#include "third_party/blake3/blake3_dispatch.c" +#include "third_party/blake3/blake3.c" -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(_MSC_VER) -#pragma warning (pop, 0) +#if defined(_MSC_VER) && defined(_M_AMD64) +#pragma comment (lib, "blake3") #endif - diff --git a/src/linker/base_ext/base_blake3.h b/src/linker/base_ext/base_blake3.h index e80c1e35..90e7cdfd 100644 --- a/src/linker/base_ext/base_blake3.h +++ b/src/linker/base_ext/base_blake3.h @@ -6,7 +6,7 @@ #define BLAKE3_API static #define BLAKE3_PRIVATE static -#include "third_party/blake3/c/blake3.h" +#include "third_party/blake3/blake3.h" static void blake3(void* out, size_t outlen, void* in, size_t inlen) diff --git a/src/third_party/blake3/LICENSE_CC0 b/src/third_party/blake3/LICENSE_CC0 new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/src/third_party/blake3/LICENSE_CC0 @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/src/third_party/blake3/asm/LICENSE b/src/third_party/blake3/asm/LICENSE deleted file mode 100644 index f5892efc..00000000 --- a/src/third_party/blake3/asm/LICENSE +++ /dev/null @@ -1,330 +0,0 @@ -This work is released into the public domain with CC0 1.0. Alternatively, it is -licensed under the Apache License 2.0. - -------------------------------------------------------------------------------- - -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. - -------------------------------------------------------------------------------- - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2019 Jack O'Connor and Samuel Neves - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/third_party/blake3/asm/blake3.h b/src/third_party/blake3/asm/blake3.h deleted file mode 100644 index f694dcf2..00000000 --- a/src/third_party/blake3/asm/blake3.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef BLAKE3_H -#define BLAKE3_H - -#include -#include - -#if !defined(BLAKE3_API) -# if defined(_WIN32) || defined(__CYGWIN__) -# if defined(BLAKE3_DLL) -# if defined(BLAKE3_DLL_EXPORTS) -# define BLAKE3_API __declspec(dllexport) -# else -# define BLAKE3_API __declspec(dllimport) -# endif -# define BLAKE3_PRIVATE -# else -# define BLAKE3_API -# define BLAKE3_PRIVATE -# endif -# elif __GNUC__ >= 4 -# define BLAKE3_API __attribute__((visibility("default"))) -# define BLAKE3_PRIVATE __attribute__((visibility("hidden"))) -# else -# define BLAKE3_API -# define BLAKE3_PRIVATE -# endif -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define BLAKE3_VERSION_STRING "1.5.0" -#define BLAKE3_KEY_LEN 32 -#define BLAKE3_OUT_LEN 32 -#define BLAKE3_BLOCK_LEN 64 -#define BLAKE3_CHUNK_LEN 1024 -#define BLAKE3_MAX_DEPTH 54 - -// This struct is a private implementation detail. It has to be here because -// it's part of blake3_hasher below. -typedef struct { - uint32_t cv[8]; - uint64_t chunk_counter; - uint8_t buf[BLAKE3_BLOCK_LEN]; - uint8_t buf_len; - uint8_t blocks_compressed; - uint8_t flags; -} blake3_chunk_state; - -typedef struct { - uint32_t key[8]; - blake3_chunk_state chunk; - uint8_t cv_stack_len; - // The stack size is MAX_DEPTH + 1 because we do lazy merging. For example, - // with 7 chunks, we have 3 entries in the stack. Adding an 8th chunk - // requires a 4th entry, rather than merging everything down to 1, because we - // don't know whether more input is coming. This is different from how the - // reference implementation does things. - uint8_t cv_stack[(BLAKE3_MAX_DEPTH + 1) * BLAKE3_OUT_LEN]; -} blake3_hasher; - -BLAKE3_API const char *blake3_version(void); -BLAKE3_API void blake3_hasher_init(blake3_hasher *self); -BLAKE3_API void blake3_hasher_init_keyed(blake3_hasher *self, - const uint8_t key[BLAKE3_KEY_LEN]); -BLAKE3_API void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context); -BLAKE3_API void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context, - size_t context_len); -BLAKE3_API void blake3_hasher_update(blake3_hasher *self, const void *input, - size_t input_len); -BLAKE3_API void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out, - size_t out_len); -BLAKE3_API void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek, - uint8_t *out, size_t out_len); -BLAKE3_API void blake3_hasher_reset(blake3_hasher *self); - -#ifdef __cplusplus -} -#endif - -#endif /* BLAKE3_H */ diff --git a/src/third_party/blake3/asm/blake3_dispatch.c b/src/third_party/blake3/asm/blake3_dispatch.c deleted file mode 100644 index 6f814569..00000000 --- a/src/third_party/blake3/asm/blake3_dispatch.c +++ /dev/null @@ -1,278 +0,0 @@ -#include -#include -#include - -#include "blake3_impl.h" - -#if defined(IS_X86) -#if defined(_MSC_VER) -#include -#elif defined(__GNUC__) -#include -#else -#undef IS_X86 /* Unimplemented! */ -#endif -#endif - -#define MAYBE_UNUSED(x) (void)((x)) - -#if defined(IS_X86) -static uint64_t xgetbv(void) { -#if defined(_MSC_VER) - return _xgetbv(0); -#else - uint32_t eax = 0, edx = 0; - __asm__ __volatile__("xgetbv\n" : "=a"(eax), "=d"(edx) : "c"(0)); - return ((uint64_t)edx << 32) | eax; -#endif -} - -static void cpuid(uint32_t out[4], uint32_t id) { -#if defined(_MSC_VER) - __cpuid((int *)out, id); -#elif defined(__i386__) || defined(_M_IX86) - __asm__ __volatile__("movl %%ebx, %1\n" - "cpuid\n" - "xchgl %1, %%ebx\n" - : "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3]) - : "a"(id)); -#else - __asm__ __volatile__("cpuid\n" - : "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3]) - : "a"(id)); -#endif -} - -static void cpuidex(uint32_t out[4], uint32_t id, uint32_t sid) { -#if defined(_MSC_VER) - __cpuidex((int *)out, id, sid); -#elif defined(__i386__) || defined(_M_IX86) - __asm__ __volatile__("movl %%ebx, %1\n" - "cpuid\n" - "xchgl %1, %%ebx\n" - : "=a"(out[0]), "=r"(out[1]), "=c"(out[2]), "=d"(out[3]) - : "a"(id), "c"(sid)); -#else - __asm__ __volatile__("cpuid\n" - : "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3]) - : "a"(id), "c"(sid)); -#endif -} - -#endif - -enum cpu_feature { - SSE2 = 1 << 0, - SSSE3 = 1 << 1, - SSE41 = 1 << 2, - AVX = 1 << 3, - AVX2 = 1 << 4, - AVX512F = 1 << 5, - AVX512VL = 1 << 6, - /* ... */ - UNDEFINED = 1 << 30 -}; - -#if !defined(BLAKE3_TESTING) -static /* Allow the variable to be controlled manually for testing */ -#endif - volatile int g_cpu_features = UNDEFINED; - -#if !defined(BLAKE3_TESTING) -static -#endif - enum cpu_feature - get_cpu_features(void) { - - /* If TSAN detects a data race here, try compiling with -DBLAKE3_ATOMICS=1 */ - long features = g_cpu_features; - if (features != UNDEFINED) { - return (enum cpu_feature)features; - } else { -#if defined(IS_X86) - uint32_t regs[4] = {0}; - uint32_t *eax = ®s[0], *ebx = ®s[1], *ecx = ®s[2], *edx = ®s[3]; - (void)edx; - features = 0; - cpuid(regs, 0); - const int max_id = *eax; - cpuid(regs, 1); -#if defined(__amd64__) || defined(_M_X64) - features |= SSE2; -#else - if (*edx & (1UL << 26)) - features |= SSE2; -#endif - if (*ecx & (1UL << 9)) - features |= SSSE3; - if (*ecx & (1UL << 19)) - features |= SSE41; - - if (*ecx & (1UL << 27)) { // OSXSAVE - const uint64_t mask = xgetbv(); - if ((mask & 6) == 6) { // SSE and AVX states - if (*ecx & (1UL << 28)) - features |= AVX; - if (max_id >= 7) { - cpuidex(regs, 7, 0); - if (*ebx & (1UL << 5)) - features |= AVX2; - if ((mask & 224) == 224) { // Opmask, ZMM_Hi256, Hi16_Zmm - if (*ebx & (1UL << 31)) - features |= AVX512VL; - if (*ebx & (1UL << 16)) - features |= AVX512F; - } - } - } - } - g_cpu_features = features; - return (enum cpu_feature)features; -#else - /* How to detect NEON? */ - return 0; -#endif - } -} - -void blake3_compress_in_place(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags) { -#if defined(IS_X86) - const enum cpu_feature features = get_cpu_features(); - MAYBE_UNUSED(features); -#if !defined(BLAKE3_NO_AVX512) - if (features & AVX512VL) { - blake3_compress_in_place_avx512(cv, block, block_len, counter, flags); - return; - } -#endif -#if !defined(BLAKE3_NO_SSE41) - if (features & SSE41) { - blake3_compress_in_place_sse41(cv, block, block_len, counter, flags); - return; - } -#endif -#if !defined(BLAKE3_NO_SSE2) - if (features & SSE2) { - blake3_compress_in_place_sse2(cv, block, block_len, counter, flags); - return; - } -#endif -#endif - blake3_compress_in_place_portable(cv, block, block_len, counter, flags); -} - -void blake3_compress_xof(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, uint8_t flags, - uint8_t out[64]) { -#if defined(IS_X86) - const enum cpu_feature features = get_cpu_features(); - MAYBE_UNUSED(features); -#if !defined(BLAKE3_NO_AVX512) - if (features & AVX512VL) { - blake3_compress_xof_avx512(cv, block, block_len, counter, flags, out); - return; - } -#endif -#if !defined(BLAKE3_NO_SSE41) - if (features & SSE41) { - blake3_compress_xof_sse41(cv, block, block_len, counter, flags, out); - return; - } -#endif -#if !defined(BLAKE3_NO_SSE2) - if (features & SSE2) { - blake3_compress_xof_sse2(cv, block, block_len, counter, flags, out); - return; - } -#endif -#endif - blake3_compress_xof_portable(cv, block, block_len, counter, flags, out); -} - -void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out) { -#if defined(IS_X86) - const enum cpu_feature features = get_cpu_features(); - MAYBE_UNUSED(features); -#if !defined(BLAKE3_NO_AVX512) - if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) { - blake3_hash_many_avx512(inputs, num_inputs, blocks, key, counter, - increment_counter, flags, flags_start, flags_end, - out); - return; - } -#endif -#if !defined(BLAKE3_NO_AVX2) - if (features & AVX2) { - blake3_hash_many_avx2(inputs, num_inputs, blocks, key, counter, - increment_counter, flags, flags_start, flags_end, - out); - return; - } -#endif -#if !defined(BLAKE3_NO_SSE41) - if (features & SSE41) { - blake3_hash_many_sse41(inputs, num_inputs, blocks, key, counter, - increment_counter, flags, flags_start, flags_end, - out); - return; - } -#endif -#if !defined(BLAKE3_NO_SSE2) - if (features & SSE2) { - blake3_hash_many_sse2(inputs, num_inputs, blocks, key, counter, - increment_counter, flags, flags_start, flags_end, - out); - return; - } -#endif -#endif - -#if BLAKE3_USE_NEON == 1 - blake3_hash_many_neon(inputs, num_inputs, blocks, key, counter, - increment_counter, flags, flags_start, flags_end, out); - return; -#endif - - blake3_hash_many_portable(inputs, num_inputs, blocks, key, counter, - increment_counter, flags, flags_start, flags_end, - out); -} - -// The dynamically detected SIMD degree of the current platform. -size_t blake3_simd_degree(void) { -#if defined(IS_X86) - const enum cpu_feature features = get_cpu_features(); - MAYBE_UNUSED(features); -#if !defined(BLAKE3_NO_AVX512) - if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) { - return 16; - } -#endif -#if !defined(BLAKE3_NO_AVX2) - if (features & AVX2) { - return 8; - } -#endif -#if !defined(BLAKE3_NO_SSE41) - if (features & SSE41) { - return 4; - } -#endif -#if !defined(BLAKE3_NO_SSE2) - if (features & SSE2) { - return 4; - } -#endif -#endif -#if BLAKE3_USE_NEON == 1 - return 4; -#endif - return 1; -} diff --git a/src/third_party/blake3/asm/blake3.c b/src/third_party/blake3/blake3.c similarity index 87% rename from src/third_party/blake3/asm/blake3.c rename to src/third_party/blake3/blake3.c index 692f4b02..00f91f44 100644 --- a/src/third_party/blake3/asm/blake3.c +++ b/src/third_party/blake3/blake3.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "blake3.h" #include "blake3_impl.h" @@ -88,24 +89,30 @@ INLINE void output_chaining_value(const output_t *self, uint8_t cv[32]) { INLINE void output_root_bytes(const output_t *self, uint64_t seek, uint8_t *out, size_t out_len) { + if (out_len == 0) { + return; + } uint64_t output_block_counter = seek / 64; size_t offset_within_block = seek % 64; uint8_t wide_buf[64]; - while (out_len > 0) { - blake3_compress_xof(self->input_cv, self->block, self->block_len, - output_block_counter, self->flags | ROOT, wide_buf); - size_t available_bytes = 64 - offset_within_block; - size_t memcpy_len; - if (out_len > available_bytes) { - memcpy_len = available_bytes; - } else { - memcpy_len = out_len; - } - memcpy(out, wide_buf + offset_within_block, memcpy_len); - out += memcpy_len; - out_len -= memcpy_len; + if(offset_within_block) { + blake3_compress_xof(self->input_cv, self->block, self->block_len, output_block_counter, self->flags | ROOT, wide_buf); + const size_t available_bytes = 64 - offset_within_block; + const size_t bytes = out_len > available_bytes ? available_bytes : out_len; + memcpy(out, wide_buf + offset_within_block, bytes); + out += bytes; + out_len -= bytes; output_block_counter += 1; - offset_within_block = 0; + } + if(out_len / 64) { + blake3_xof_many(self->input_cv, self->block, self->block_len, output_block_counter, self->flags | ROOT, out, out_len / 64); + } + output_block_counter += out_len / 64; + out += out_len & -64; + out_len -= out_len & -64; + if(out_len) { + blake3_compress_xof(self->input_cv, self->block, self->block_len, output_block_counter, self->flags | ROOT, wide_buf); + memcpy(out, wide_buf, out_len); } } @@ -134,9 +141,7 @@ INLINE void chunk_state_update(blake3_chunk_state *self, const uint8_t *input, input_len -= BLAKE3_BLOCK_LEN; } - size_t take = chunk_state_fill_buf(self, input, input_len); - input += take; - input_len -= take; + chunk_state_fill_buf(self, input, input_len); } INLINE output_t chunk_state_output(const blake3_chunk_state *self) { @@ -154,10 +159,10 @@ INLINE output_t parent_output(const uint8_t block[BLAKE3_BLOCK_LEN], // Given some input larger than one chunk, return the number of bytes that // should go in the left subtree. This is the largest power-of-2 number of // chunks that leaves at least 1 byte for the right subtree. -INLINE size_t left_len(size_t content_len) { - // Subtract 1 to reserve at least one byte for the right side. content_len +INLINE size_t left_subtree_len(size_t input_len) { + // Subtract 1 to reserve at least one byte for the right side. input_len // should always be greater than BLAKE3_CHUNK_LEN. - size_t full_chunks = (content_len - 1) / BLAKE3_CHUNK_LEN; + size_t full_chunks = (input_len - 1) / BLAKE3_CHUNK_LEN; return round_down_to_power_of_2(full_chunks) * BLAKE3_CHUNK_LEN; } @@ -261,11 +266,10 @@ INLINE size_t compress_parents_parallel(const uint8_t *child_chaining_values, // Why not just have the caller split the input on the first update(), instead // of implementing this special rule? Because we don't want to limit SIMD or // multi-threading parallelism for that update(). -static size_t blake3_compress_subtree_wide(const uint8_t *input, - size_t input_len, - const uint32_t key[8], - uint64_t chunk_counter, - uint8_t flags, uint8_t *out) { +size_t blake3_compress_subtree_wide(const uint8_t *input, size_t input_len, + const uint32_t key[8], + uint64_t chunk_counter, uint8_t flags, + uint8_t *out, bool use_tbb) { // Note that the single chunk case does *not* bump the SIMD degree up to 2 // when it is 1. If this implementation adds multi-threading in the future, // this gives us the option of multi-threading even the 2-chunk case, which @@ -279,7 +283,7 @@ static size_t blake3_compress_subtree_wide(const uint8_t *input, // the input into left and right subtrees. (Note that this is only optimal // as long as the SIMD degree is a power of 2. If we ever get a SIMD degree // of 3 or something, we'll need a more complicated strategy.) - size_t left_input_len = left_len(input_len); + size_t left_input_len = left_subtree_len(input_len); size_t right_input_len = input_len - left_input_len; const uint8_t *right_input = &input[left_input_len]; uint64_t right_chunk_counter = @@ -299,12 +303,24 @@ static size_t blake3_compress_subtree_wide(const uint8_t *input, } uint8_t *right_cvs = &cv_array[degree * BLAKE3_OUT_LEN]; - // Recurse! If this implementation adds multi-threading support in the - // future, this is where it will go. - size_t left_n = blake3_compress_subtree_wide(input, left_input_len, key, - chunk_counter, flags, cv_array); - size_t right_n = blake3_compress_subtree_wide( - right_input, right_input_len, key, right_chunk_counter, flags, right_cvs); + // Recurse! + size_t left_n = SIZE_MAX; + size_t right_n = SIZE_MAX; + +#if defined(BLAKE3_USE_TBB) + blake3_compress_subtree_wide_join_tbb( + key, flags, use_tbb, + // left-hand side + input, left_input_len, chunk_counter, cv_array, &left_n, + // right-hand side + right_input, right_input_len, right_chunk_counter, right_cvs, &right_n); +#else + left_n = blake3_compress_subtree_wide( + input, left_input_len, key, chunk_counter, flags, cv_array, use_tbb); + right_n = blake3_compress_subtree_wide(right_input, right_input_len, key, + right_chunk_counter, flags, right_cvs, + use_tbb); +#endif // BLAKE3_USE_TBB // The special case again. If simd_degree=1, then we'll have left_n=1 and // right_n=1. Rather than compressing them into a single output, return @@ -330,32 +346,37 @@ static size_t blake3_compress_subtree_wide(const uint8_t *input, // // As with compress_subtree_wide(), this function is not used on inputs of 1 // chunk or less. That's a different codepath. -INLINE void compress_subtree_to_parent_node( - const uint8_t *input, size_t input_len, const uint32_t key[8], - uint64_t chunk_counter, uint8_t flags, uint8_t out[2 * BLAKE3_OUT_LEN]) { +INLINE void +compress_subtree_to_parent_node(const uint8_t *input, size_t input_len, + const uint32_t key[8], uint64_t chunk_counter, + uint8_t flags, uint8_t out[2 * BLAKE3_OUT_LEN], + bool use_tbb) { #if defined(BLAKE3_TESTING) assert(input_len > BLAKE3_CHUNK_LEN); #endif uint8_t cv_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN]; size_t num_cvs = blake3_compress_subtree_wide(input, input_len, key, - chunk_counter, flags, cv_array); + chunk_counter, flags, cv_array, use_tbb); assert(num_cvs <= MAX_SIMD_DEGREE_OR_2); - - // If MAX_SIMD_DEGREE is greater than 2 and there's enough input, + // The following loop never executes when MAX_SIMD_DEGREE_OR_2 is 2, because + // as we just asserted, num_cvs will always be <=2 in that case. But GCC + // (particularly GCC 8.5) can't tell that it never executes, and if NDEBUG is + // set then it emits incorrect warnings here. We tried a few different + // hacks to silence these, but in the end our hacks just produced different + // warnings (see https://github.com/BLAKE3-team/BLAKE3/pull/380). Out of + // desperation, we ifdef out this entire loop when we know it's not needed. +#if MAX_SIMD_DEGREE_OR_2 > 2 + // If MAX_SIMD_DEGREE_OR_2 is greater than 2 and there's enough input, // compress_subtree_wide() returns more than 2 chaining values. Condense // them into 2 by forming parent nodes repeatedly. uint8_t out_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN / 2]; - // The second half of this loop condition is always true, and we just - // asserted it above. But GCC can't tell that it's always true, and if NDEBUG - // is set on platforms where MAX_SIMD_DEGREE_OR_2 == 2, GCC emits spurious - // warnings here. GCC 8.5 is particularly sensitive, so if you're changing - // this code, test it against that version. - while (num_cvs > 2 && num_cvs <= MAX_SIMD_DEGREE_OR_2) { + while (num_cvs > 2) { num_cvs = compress_parents_parallel(cv_array, num_cvs, key, flags, out_array); memcpy(cv_array, out_array, num_cvs * BLAKE3_OUT_LEN); } +#endif memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN); } @@ -427,7 +448,7 @@ INLINE void hasher_merge_cv_stack(blake3_hasher *self, uint64_t total_len) { // of the whole tree, and it would need to be ROOT finalized. We can't // compress it until we know. // 2) This 64 KiB input might complete a larger tree, whose root node is -// similarly going to be the the root of the whole tree. For example, maybe +// similarly going to be the root of the whole tree. For example, maybe // we have 196 KiB (that is, 128 + 64) hashed so far. We can't compress the // node at the root of the 256 KiB subtree until we know how to finalize it. // @@ -452,8 +473,8 @@ INLINE void hasher_push_cv(blake3_hasher *self, uint8_t new_cv[BLAKE3_OUT_LEN], self->cv_stack_len += 1; } -void blake3_hasher_update(blake3_hasher *self, const void *input, - size_t input_len) { +INLINE void blake3_hasher_update_base(blake3_hasher *self, const void *input, + size_t input_len, bool use_tbb) { // Explicitly checking for zero avoids causing UB by passing a null pointer // to memcpy. This comes up in practice with things like: // std::vector v; @@ -539,7 +560,7 @@ void blake3_hasher_update(blake3_hasher *self, const void *input, uint8_t cv_pair[2 * BLAKE3_OUT_LEN]; compress_subtree_to_parent_node(input_bytes, subtree_len, self->key, self->chunk.chunk_counter, - self->chunk.flags, cv_pair); + self->chunk.flags, cv_pair, use_tbb); hasher_push_cv(self, cv_pair, self->chunk.chunk_counter); hasher_push_cv(self, &cv_pair[BLAKE3_OUT_LEN], self->chunk.chunk_counter + (subtree_chunks / 2)); @@ -561,6 +582,20 @@ void blake3_hasher_update(blake3_hasher *self, const void *input, } } +void blake3_hasher_update(blake3_hasher *self, const void *input, + size_t input_len) { + bool use_tbb = false; + blake3_hasher_update_base(self, input, input_len, use_tbb); +} + +#if defined(BLAKE3_USE_TBB) +void blake3_hasher_update_tbb(blake3_hasher *self, const void *input, + size_t input_len) { + bool use_tbb = true; + blake3_hasher_update_base(self, input, input_len, use_tbb); +} +#endif // BLAKE3_USE_TBB + void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out, size_t out_len) { blake3_hasher_finalize_seek(self, 0, out, out_len); diff --git a/src/third_party/blake3/c/blake3.h b/src/third_party/blake3/blake3.h similarity index 90% rename from src/third_party/blake3/c/blake3.h rename to src/third_party/blake3/blake3.h index f694dcf2..c5b79987 100644 --- a/src/third_party/blake3/c/blake3.h +++ b/src/third_party/blake3/blake3.h @@ -30,7 +30,7 @@ extern "C" { #endif -#define BLAKE3_VERSION_STRING "1.5.0" +#define BLAKE3_VERSION_STRING "1.8.7" #define BLAKE3_KEY_LEN 32 #define BLAKE3_OUT_LEN 32 #define BLAKE3_BLOCK_LEN 64 @@ -38,7 +38,7 @@ extern "C" { #define BLAKE3_MAX_DEPTH 54 // This struct is a private implementation detail. It has to be here because -// it's part of blake3_hasher below. +// it's part of the blake3_hasher structure defined below. typedef struct { uint32_t cv[8]; uint64_t chunk_counter; @@ -69,6 +69,10 @@ BLAKE3_API void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const voi size_t context_len); BLAKE3_API void blake3_hasher_update(blake3_hasher *self, const void *input, size_t input_len); +#if defined(BLAKE3_USE_TBB) +BLAKE3_API void blake3_hasher_update_tbb(blake3_hasher *self, const void *input, + size_t input_len); +#endif // BLAKE3_USE_TBB BLAKE3_API void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out, size_t out_len); BLAKE3_API void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek, diff --git a/src/third_party/blake3/asm/blake3_avx2_x86-64_unix.S b/src/third_party/blake3/blake3_avx2_x86-64_unix.S similarity index 100% rename from src/third_party/blake3/asm/blake3_avx2_x86-64_unix.S rename to src/third_party/blake3/blake3_avx2_x86-64_unix.S diff --git a/src/third_party/blake3/asm/blake3_avx2_x86-64_windows_msvc.asm b/src/third_party/blake3/blake3_avx2_x86-64_windows_msvc.asm similarity index 100% rename from src/third_party/blake3/asm/blake3_avx2_x86-64_windows_msvc.asm rename to src/third_party/blake3/blake3_avx2_x86-64_windows_msvc.asm diff --git a/src/third_party/blake3/asm/blake3_avx512_x86-64_unix.S b/src/third_party/blake3/blake3_avx512_x86-64_unix.S similarity index 55% rename from src/third_party/blake3/asm/blake3_avx512_x86-64_unix.S rename to src/third_party/blake3/blake3_avx512_x86-64_unix.S index a06aede0..9642e413 100644 --- a/src/third_party/blake3/asm/blake3_avx512_x86-64_unix.S +++ b/src/third_party/blake3/blake3_avx512_x86-64_unix.S @@ -19,6 +19,8 @@ .global _blake3_compress_in_place_avx512 .global blake3_compress_xof_avx512 .global _blake3_compress_xof_avx512 +.global blake3_xof_many_avx512 +.global _blake3_xof_many_avx512 #ifdef __APPLE__ .text @@ -2553,6 +2555,2243 @@ blake3_compress_xof_avx512: vmovdqu xmmword ptr [r9+0x30], xmm3 ret +.p2align 6 +blake3_xof_many_avx512: +_blake3_xof_many_avx512: + _CET_ENDBR + mov r10,QWORD PTR [rsp+0x8] + cmp r10,0x1 + ja 2f + vmovdqu xmm0,XMMWORD PTR [rdi] + vmovdqu xmm1,XMMWORD PTR [rdi+0x10] + movzx eax,r8b + movzx edx,dl + shl rax,0x20 + add rdx,rax + vmovq xmm3,rcx + vmovq xmm4,rdx + vpunpcklqdq xmm3,xmm3,xmm4 + vmovaps xmm2,XMMWORD PTR [BLAKE3_IV+rip] + vmovups xmm8,XMMWORD PTR [rsi] + vmovups xmm9,XMMWORD PTR [rsi+0x10] + vshufps xmm4,xmm8,xmm9,0x88 + vshufps xmm5,xmm8,xmm9,0xdd + vmovups xmm8,XMMWORD PTR [rsi+0x20] + vmovups xmm9,XMMWORD PTR [rsi+0x30] + vshufps xmm6,xmm8,xmm9,0x88 + vshufps xmm7,xmm8,xmm9,0xdd + vpshufd xmm6,xmm6,0x93 + vpshufd xmm7,xmm7,0x93 + mov al,0x7 +3: + vpaddd xmm0,xmm0,xmm4 + vpaddd xmm0,xmm0,xmm1 + vpxord xmm3,xmm3,xmm0 + vprord xmm3,xmm3,0x10 + vpaddd xmm2,xmm2,xmm3 + vpxord xmm1,xmm1,xmm2 + vprord xmm1,xmm1,0xc + vpaddd xmm0,xmm0,xmm5 + vpaddd xmm0,xmm0,xmm1 + vpxord xmm3,xmm3,xmm0 + vprord xmm3,xmm3,0x8 + vpaddd xmm2,xmm2,xmm3 + vpxord xmm1,xmm1,xmm2 + vprord xmm1,xmm1,0x7 + vpshufd xmm0,xmm0,0x93 + vpshufd xmm3,xmm3,0x4e + vpshufd xmm2,xmm2,0x39 + vpaddd xmm0,xmm0,xmm6 + vpaddd xmm0,xmm0,xmm1 + vpxord xmm3,xmm3,xmm0 + vprord xmm3,xmm3,0x10 + vpaddd xmm2,xmm2,xmm3 + vpxord xmm1,xmm1,xmm2 + vprord xmm1,xmm1,0xc + vpaddd xmm0,xmm0,xmm7 + vpaddd xmm0,xmm0,xmm1 + vpxord xmm3,xmm3,xmm0 + vprord xmm3,xmm3,0x8 + vpaddd xmm2,xmm2,xmm3 + vpxord xmm1,xmm1,xmm2 + vprord xmm1,xmm1,0x7 + vpshufd xmm0,xmm0,0x39 + vpshufd xmm3,xmm3,0x4e + vpshufd xmm2,xmm2,0x93 + dec al + je 3f + vshufps xmm8,xmm4,xmm5,0xd6 + vpshufd xmm9,xmm4,0xf + vpshufd xmm4,xmm8,0x39 + vshufps xmm8,xmm6,xmm7,0xfa + vpblendd xmm9,xmm9,xmm8,0xaa + vpunpcklqdq xmm8,xmm7,xmm5 + vpblendd xmm8,xmm8,xmm6,0x88 + vpshufd xmm8,xmm8,0x78 + vpunpckhdq xmm5,xmm5,xmm7 + vpunpckldq xmm6,xmm6,xmm5 + vpshufd xmm7,xmm6,0x1e + vmovdqa xmm5,xmm9 + vmovdqa xmm6,xmm8 + jmp 3b +3: + vpxor xmm0,xmm0,xmm2 + vpxor xmm1,xmm1,xmm3 + vpxor xmm2,xmm2,XMMWORD PTR [rdi] + vpxor xmm3,xmm3,XMMWORD PTR [rdi+0x10] + vmovdqu XMMWORD PTR [r9],xmm0 + vmovdqu XMMWORD PTR [r9+0x10],xmm1 + vmovdqu XMMWORD PTR [r9+0x20],xmm2 + vmovdqu XMMWORD PTR [r9+0x30],xmm3 + ret +.p2align 6 +2: + push rbp + mov rbp,rsp + sub rsp,0x90 + and rsp,0xffffffffffffffc0 + vpbroadcastd zmm0,ecx + shr rcx,0x20 + vpbroadcastd zmm1,ecx + vpaddd zmm2,zmm0,ZMMWORD PTR [ADD0+rip] + vpcmpltud k1,zmm2,zmm0 + vpaddd zmm1{k1},zmm1,DWORD PTR [ADD1+rip]{1to16} + vmovdqa32 ZMMWORD PTR [rsp],zmm2 + vmovdqa32 ZMMWORD PTR [rsp+0x40],zmm1 + cmp r10,0x10 + jb 2f +3: + vpbroadcastd zmm16,DWORD PTR [rsi] + vpbroadcastd zmm17,DWORD PTR [rsi+0x4] + vpbroadcastd zmm18,DWORD PTR [rsi+0x8] + vpbroadcastd zmm19,DWORD PTR [rsi+0xc] + vpbroadcastd zmm20,DWORD PTR [rsi+0x10] + vpbroadcastd zmm21,DWORD PTR [rsi+0x14] + vpbroadcastd zmm22,DWORD PTR [rsi+0x18] + vpbroadcastd zmm23,DWORD PTR [rsi+0x1c] + vpbroadcastd zmm24,DWORD PTR [rsi+0x20] + vpbroadcastd zmm25,DWORD PTR [rsi+0x24] + vpbroadcastd zmm26,DWORD PTR [rsi+0x28] + vpbroadcastd zmm27,DWORD PTR [rsi+0x2c] + vpbroadcastd zmm28,DWORD PTR [rsi+0x30] + vpbroadcastd zmm29,DWORD PTR [rsi+0x34] + vpbroadcastd zmm30,DWORD PTR [rsi+0x38] + vpbroadcastd zmm31,DWORD PTR [rsi+0x3c] + vpbroadcastd zmm0,DWORD PTR [rdi] + vpbroadcastd zmm1,DWORD PTR [rdi+0x4] + vpbroadcastd zmm2,DWORD PTR [rdi+0x8] + vpbroadcastd zmm3,DWORD PTR [rdi+0xc] + vpbroadcastd zmm4,DWORD PTR [rdi+0x10] + vpbroadcastd zmm5,DWORD PTR [rdi+0x14] + vpbroadcastd zmm6,DWORD PTR [rdi+0x18] + vpbroadcastd zmm7,DWORD PTR [rdi+0x1c] + vpbroadcastd zmm8,DWORD PTR [BLAKE3_IV_0+rip] + vpbroadcastd zmm9,DWORD PTR [BLAKE3_IV_1+rip] + vpbroadcastd zmm10,DWORD PTR [BLAKE3_IV_2+rip] + vpbroadcastd zmm11,DWORD PTR [BLAKE3_IV_3+rip] + vmovdqa32 zmm12,ZMMWORD PTR [rsp] + vmovdqa32 zmm13,ZMMWORD PTR [rsp+0x40] + vpbroadcastd zmm14,edx + vpbroadcastd zmm15,r8d + vpaddd zmm0,zmm0,zmm16 + vpaddd zmm1,zmm1,zmm18 + vpaddd zmm2,zmm2,zmm20 + vpaddd zmm3,zmm3,zmm22 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vprord zmm15,zmm15,0x10 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0xc + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vpaddd zmm0,zmm0,zmm17 + vpaddd zmm1,zmm1,zmm19 + vpaddd zmm2,zmm2,zmm21 + vpaddd zmm3,zmm3,zmm23 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vprord zmm15,zmm15,0x8 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0x7 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vpaddd zmm0,zmm0,zmm24 + vpaddd zmm1,zmm1,zmm26 + vpaddd zmm2,zmm2,zmm28 + vpaddd zmm3,zmm3,zmm30 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x10 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vprord zmm4,zmm4,0xc + vpaddd zmm0,zmm0,zmm25 + vpaddd zmm1,zmm1,zmm27 + vpaddd zmm2,zmm2,zmm29 + vpaddd zmm3,zmm3,zmm31 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x8 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vprord zmm4,zmm4,0x7 + vpaddd zmm0,zmm0,zmm18 + vpaddd zmm1,zmm1,zmm19 + vpaddd zmm2,zmm2,zmm23 + vpaddd zmm3,zmm3,zmm20 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vprord zmm15,zmm15,0x10 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0xc + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vpaddd zmm0,zmm0,zmm22 + vpaddd zmm1,zmm1,zmm26 + vpaddd zmm2,zmm2,zmm16 + vpaddd zmm3,zmm3,zmm29 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vprord zmm15,zmm15,0x8 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0x7 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vpaddd zmm0,zmm0,zmm17 + vpaddd zmm1,zmm1,zmm28 + vpaddd zmm2,zmm2,zmm25 + vpaddd zmm3,zmm3,zmm31 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x10 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vprord zmm4,zmm4,0xc + vpaddd zmm0,zmm0,zmm27 + vpaddd zmm1,zmm1,zmm21 + vpaddd zmm2,zmm2,zmm30 + vpaddd zmm3,zmm3,zmm24 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x8 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vprord zmm4,zmm4,0x7 + vpaddd zmm0,zmm0,zmm19 + vpaddd zmm1,zmm1,zmm26 + vpaddd zmm2,zmm2,zmm29 + vpaddd zmm3,zmm3,zmm23 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vprord zmm15,zmm15,0x10 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0xc + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vpaddd zmm0,zmm0,zmm20 + vpaddd zmm1,zmm1,zmm28 + vpaddd zmm2,zmm2,zmm18 + vpaddd zmm3,zmm3,zmm30 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vprord zmm15,zmm15,0x8 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0x7 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vpaddd zmm0,zmm0,zmm22 + vpaddd zmm1,zmm1,zmm25 + vpaddd zmm2,zmm2,zmm27 + vpaddd zmm3,zmm3,zmm24 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x10 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vprord zmm4,zmm4,0xc + vpaddd zmm0,zmm0,zmm21 + vpaddd zmm1,zmm1,zmm16 + vpaddd zmm2,zmm2,zmm31 + vpaddd zmm3,zmm3,zmm17 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x8 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vprord zmm4,zmm4,0x7 + vpaddd zmm0,zmm0,zmm26 + vpaddd zmm1,zmm1,zmm28 + vpaddd zmm2,zmm2,zmm30 + vpaddd zmm3,zmm3,zmm29 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vprord zmm15,zmm15,0x10 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0xc + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vpaddd zmm0,zmm0,zmm23 + vpaddd zmm1,zmm1,zmm25 + vpaddd zmm2,zmm2,zmm19 + vpaddd zmm3,zmm3,zmm31 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vprord zmm15,zmm15,0x8 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0x7 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vpaddd zmm0,zmm0,zmm20 + vpaddd zmm1,zmm1,zmm27 + vpaddd zmm2,zmm2,zmm21 + vpaddd zmm3,zmm3,zmm17 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x10 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vprord zmm4,zmm4,0xc + vpaddd zmm0,zmm0,zmm16 + vpaddd zmm1,zmm1,zmm18 + vpaddd zmm2,zmm2,zmm24 + vpaddd zmm3,zmm3,zmm22 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x8 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vprord zmm4,zmm4,0x7 + vpaddd zmm0,zmm0,zmm28 + vpaddd zmm1,zmm1,zmm25 + vpaddd zmm2,zmm2,zmm31 + vpaddd zmm3,zmm3,zmm30 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vprord zmm15,zmm15,0x10 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0xc + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vpaddd zmm0,zmm0,zmm29 + vpaddd zmm1,zmm1,zmm27 + vpaddd zmm2,zmm2,zmm26 + vpaddd zmm3,zmm3,zmm24 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vprord zmm15,zmm15,0x8 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0x7 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vpaddd zmm0,zmm0,zmm23 + vpaddd zmm1,zmm1,zmm21 + vpaddd zmm2,zmm2,zmm16 + vpaddd zmm3,zmm3,zmm22 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x10 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vprord zmm4,zmm4,0xc + vpaddd zmm0,zmm0,zmm18 + vpaddd zmm1,zmm1,zmm19 + vpaddd zmm2,zmm2,zmm17 + vpaddd zmm3,zmm3,zmm20 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x8 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vprord zmm4,zmm4,0x7 + vpaddd zmm0,zmm0,zmm25 + vpaddd zmm1,zmm1,zmm27 + vpaddd zmm2,zmm2,zmm24 + vpaddd zmm3,zmm3,zmm31 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vprord zmm15,zmm15,0x10 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0xc + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vpaddd zmm0,zmm0,zmm30 + vpaddd zmm1,zmm1,zmm21 + vpaddd zmm2,zmm2,zmm28 + vpaddd zmm3,zmm3,zmm17 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vprord zmm15,zmm15,0x8 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0x7 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vpaddd zmm0,zmm0,zmm29 + vpaddd zmm1,zmm1,zmm16 + vpaddd zmm2,zmm2,zmm18 + vpaddd zmm3,zmm3,zmm20 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x10 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vprord zmm4,zmm4,0xc + vpaddd zmm0,zmm0,zmm19 + vpaddd zmm1,zmm1,zmm26 + vpaddd zmm2,zmm2,zmm22 + vpaddd zmm3,zmm3,zmm23 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x8 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vprord zmm4,zmm4,0x7 + vpaddd zmm0,zmm0,zmm27 + vpaddd zmm1,zmm1,zmm21 + vpaddd zmm2,zmm2,zmm17 + vpaddd zmm3,zmm3,zmm24 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vprord zmm15,zmm15,0x10 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0xc + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vpaddd zmm0,zmm0,zmm31 + vpaddd zmm1,zmm1,zmm16 + vpaddd zmm2,zmm2,zmm25 + vpaddd zmm3,zmm3,zmm22 + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm1,zmm1,zmm5 + vpaddd zmm2,zmm2,zmm6 + vpaddd zmm3,zmm3,zmm7 + vpxord zmm12,zmm12,zmm0 + vpxord zmm13,zmm13,zmm1 + vpxord zmm14,zmm14,zmm2 + vpxord zmm15,zmm15,zmm3 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vprord zmm15,zmm15,0x8 + vpaddd zmm8,zmm8,zmm12 + vpaddd zmm9,zmm9,zmm13 + vpaddd zmm10,zmm10,zmm14 + vpaddd zmm11,zmm11,zmm15 + vpxord zmm4,zmm4,zmm8 + vpxord zmm5,zmm5,zmm9 + vpxord zmm6,zmm6,zmm10 + vpxord zmm7,zmm7,zmm11 + vprord zmm4,zmm4,0x7 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vpaddd zmm0,zmm0,zmm30 + vpaddd zmm1,zmm1,zmm18 + vpaddd zmm2,zmm2,zmm19 + vpaddd zmm3,zmm3,zmm23 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x10 + vprord zmm12,zmm12,0x10 + vprord zmm13,zmm13,0x10 + vprord zmm14,zmm14,0x10 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0xc + vprord zmm6,zmm6,0xc + vprord zmm7,zmm7,0xc + vprord zmm4,zmm4,0xc + vpaddd zmm0,zmm0,zmm26 + vpaddd zmm1,zmm1,zmm28 + vpaddd zmm2,zmm2,zmm20 + vpaddd zmm3,zmm3,zmm29 + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm1,zmm1,zmm6 + vpaddd zmm2,zmm2,zmm7 + vpaddd zmm3,zmm3,zmm4 + vpxord zmm15,zmm15,zmm0 + vpxord zmm12,zmm12,zmm1 + vpxord zmm13,zmm13,zmm2 + vpxord zmm14,zmm14,zmm3 + vprord zmm15,zmm15,0x8 + vprord zmm12,zmm12,0x8 + vprord zmm13,zmm13,0x8 + vprord zmm14,zmm14,0x8 + vpaddd zmm10,zmm10,zmm15 + vpaddd zmm11,zmm11,zmm12 + vpaddd zmm8,zmm8,zmm13 + vpaddd zmm9,zmm9,zmm14 + vpxord zmm5,zmm5,zmm10 + vpxord zmm6,zmm6,zmm11 + vpxord zmm7,zmm7,zmm8 + vpxord zmm4,zmm4,zmm9 + vprord zmm5,zmm5,0x7 + vprord zmm6,zmm6,0x7 + vprord zmm7,zmm7,0x7 + vprord zmm4,zmm4,0x7 + vpxord zmm0,zmm0,zmm8 + vpxord zmm1,zmm1,zmm9 + vpxord zmm2,zmm2,zmm10 + vpxord zmm3,zmm3,zmm11 + vpxord zmm4,zmm4,zmm12 + vpxord zmm5,zmm5,zmm13 + vpxord zmm6,zmm6,zmm14 + vpxord zmm7,zmm7,zmm15 + vpxord zmm8,zmm8,DWORD PTR [rdi]{1to16} + vpxord zmm9,zmm9,DWORD PTR [rdi+0x4]{1to16} + vpxord zmm10,zmm10,DWORD PTR [rdi+0x8]{1to16} + vpxord zmm11,zmm11,DWORD PTR [rdi+0xc]{1to16} + vpxord zmm12,zmm12,DWORD PTR [rdi+0x10]{1to16} + vpxord zmm13,zmm13,DWORD PTR [rdi+0x14]{1to16} + vpxord zmm14,zmm14,DWORD PTR [rdi+0x18]{1to16} + vpxord zmm15,zmm15,DWORD PTR [rdi+0x1c]{1to16} + vpunpckldq zmm16,zmm0,zmm1 + vpunpckhdq zmm17,zmm0,zmm1 + vpunpckldq zmm18,zmm2,zmm3 + vpunpckhdq zmm19,zmm2,zmm3 + vpunpckldq zmm20,zmm4,zmm5 + vpunpckhdq zmm21,zmm4,zmm5 + vpunpckldq zmm22,zmm6,zmm7 + vpunpckhdq zmm23,zmm6,zmm7 + vpunpckldq zmm24,zmm8,zmm9 + vpunpckhdq zmm25,zmm8,zmm9 + vpunpckldq zmm26,zmm10,zmm11 + vpunpckhdq zmm27,zmm10,zmm11 + vpunpckldq zmm28,zmm12,zmm13 + vpunpckhdq zmm29,zmm12,zmm13 + vpunpckldq zmm30,zmm14,zmm15 + vpunpckhdq zmm31,zmm14,zmm15 + vpunpcklqdq zmm0,zmm16,zmm18 + vpunpckhqdq zmm1,zmm16,zmm18 + vpunpcklqdq zmm2,zmm17,zmm19 + vpunpckhqdq zmm3,zmm17,zmm19 + vpunpcklqdq zmm4,zmm20,zmm22 + vpunpckhqdq zmm5,zmm20,zmm22 + vpunpcklqdq zmm6,zmm21,zmm23 + vpunpckhqdq zmm7,zmm21,zmm23 + vpunpcklqdq zmm8,zmm24,zmm26 + vpunpckhqdq zmm9,zmm24,zmm26 + vpunpcklqdq zmm10,zmm25,zmm27 + vpunpckhqdq zmm11,zmm25,zmm27 + vpunpcklqdq zmm12,zmm28,zmm30 + vpunpckhqdq zmm13,zmm28,zmm30 + vpunpcklqdq zmm14,zmm29,zmm31 + vpunpckhqdq zmm15,zmm29,zmm31 + vshufi32x4 zmm16,zmm0,zmm4,0x88 + vshufi32x4 zmm17,zmm1,zmm5,0x88 + vshufi32x4 zmm18,zmm2,zmm6,0x88 + vshufi32x4 zmm19,zmm3,zmm7,0x88 + vshufi32x4 zmm20,zmm0,zmm4,0xdd + vshufi32x4 zmm21,zmm1,zmm5,0xdd + vshufi32x4 zmm22,zmm2,zmm6,0xdd + vshufi32x4 zmm23,zmm3,zmm7,0xdd + vshufi32x4 zmm24,zmm8,zmm12,0x88 + vshufi32x4 zmm25,zmm9,zmm13,0x88 + vshufi32x4 zmm26,zmm10,zmm14,0x88 + vshufi32x4 zmm27,zmm11,zmm15,0x88 + vshufi32x4 zmm28,zmm8,zmm12,0xdd + vshufi32x4 zmm29,zmm9,zmm13,0xdd + vshufi32x4 zmm30,zmm10,zmm14,0xdd + vshufi32x4 zmm31,zmm11,zmm15,0xdd + vshufi32x4 zmm0,zmm16,zmm24,0x88 + vshufi32x4 zmm1,zmm17,zmm25,0x88 + vshufi32x4 zmm2,zmm18,zmm26,0x88 + vshufi32x4 zmm3,zmm19,zmm27,0x88 + vshufi32x4 zmm4,zmm20,zmm28,0x88 + vshufi32x4 zmm5,zmm21,zmm29,0x88 + vshufi32x4 zmm6,zmm22,zmm30,0x88 + vshufi32x4 zmm7,zmm23,zmm31,0x88 + vshufi32x4 zmm8,zmm16,zmm24,0xdd + vshufi32x4 zmm9,zmm17,zmm25,0xdd + vshufi32x4 zmm10,zmm18,zmm26,0xdd + vshufi32x4 zmm11,zmm19,zmm27,0xdd + vshufi32x4 zmm12,zmm20,zmm28,0xdd + vshufi32x4 zmm13,zmm21,zmm29,0xdd + vshufi32x4 zmm14,zmm22,zmm30,0xdd + vshufi32x4 zmm15,zmm23,zmm31,0xdd + vmovdqu32 ZMMWORD PTR [r9],zmm0 + vmovdqu32 ZMMWORD PTR [r9+0x40],zmm1 + vmovdqu32 ZMMWORD PTR [r9+0x80],zmm2 + vmovdqu32 ZMMWORD PTR [r9+0xc0],zmm3 + vmovdqu32 ZMMWORD PTR [r9+0x100],zmm4 + vmovdqu32 ZMMWORD PTR [r9+0x140],zmm5 + vmovdqu32 ZMMWORD PTR [r9+0x180],zmm6 + vmovdqu32 ZMMWORD PTR [r9+0x1c0],zmm7 + vmovdqu32 ZMMWORD PTR [r9+0x200],zmm8 + vmovdqu32 ZMMWORD PTR [r9+0x240],zmm9 + vmovdqu32 ZMMWORD PTR [r9+0x280],zmm10 + vmovdqu32 ZMMWORD PTR [r9+0x2c0],zmm11 + vmovdqu32 ZMMWORD PTR [r9+0x300],zmm12 + vmovdqu32 ZMMWORD PTR [r9+0x340],zmm13 + vmovdqu32 ZMMWORD PTR [r9+0x380],zmm14 + vmovdqu32 ZMMWORD PTR [r9+0x3c0],zmm15 + vmovdqa32 zmm0,ZMMWORD PTR [rsp] + vmovdqa32 zmm1,ZMMWORD PTR [rsp+0x40] + vpaddd zmm2,zmm0,DWORD PTR [ADD16+rip]{1to16} + vpcmpltud k1,zmm2,zmm0 + vpaddd zmm1{k1},zmm1,DWORD PTR [ADD1+rip]{1to16} + vmovdqa32 ZMMWORD PTR [rsp],zmm2 + vmovdqa32 ZMMWORD PTR [rsp+0x40],zmm1 + add r9,0x400 + sub r10,0x10 + cmp r10,0x10 + jae 3b + test r10,r10 + jne 2f +9: + vzeroupper + mov rsp,rbp + pop rbp + ret +2: + test r10,0x8 + je 2f + vpbroadcastd ymm16,DWORD PTR [rsi] + vpbroadcastd ymm17,DWORD PTR [rsi+0x4] + vpbroadcastd ymm18,DWORD PTR [rsi+0x8] + vpbroadcastd ymm19,DWORD PTR [rsi+0xc] + vpbroadcastd ymm20,DWORD PTR [rsi+0x10] + vpbroadcastd ymm21,DWORD PTR [rsi+0x14] + vpbroadcastd ymm22,DWORD PTR [rsi+0x18] + vpbroadcastd ymm23,DWORD PTR [rsi+0x1c] + vpbroadcastd ymm24,DWORD PTR [rsi+0x20] + vpbroadcastd ymm25,DWORD PTR [rsi+0x24] + vpbroadcastd ymm26,DWORD PTR [rsi+0x28] + vpbroadcastd ymm27,DWORD PTR [rsi+0x2c] + vpbroadcastd ymm28,DWORD PTR [rsi+0x30] + vpbroadcastd ymm29,DWORD PTR [rsi+0x34] + vpbroadcastd ymm30,DWORD PTR [rsi+0x38] + vpbroadcastd ymm31,DWORD PTR [rsi+0x3c] + vpbroadcastd ymm0,DWORD PTR [rdi] + vpbroadcastd ymm1,DWORD PTR [rdi+0x4] + vpbroadcastd ymm2,DWORD PTR [rdi+0x8] + vpbroadcastd ymm3,DWORD PTR [rdi+0xc] + vpbroadcastd ymm4,DWORD PTR [rdi+0x10] + vpbroadcastd ymm5,DWORD PTR [rdi+0x14] + vpbroadcastd ymm6,DWORD PTR [rdi+0x18] + vpbroadcastd ymm7,DWORD PTR [rdi+0x1c] + vpbroadcastd ymm8,DWORD PTR [BLAKE3_IV_0+rip] + vpbroadcastd ymm9,DWORD PTR [BLAKE3_IV_1+rip] + vpbroadcastd ymm10,DWORD PTR [BLAKE3_IV_2+rip] + vpbroadcastd ymm11,DWORD PTR [BLAKE3_IV_3+rip] + vmovdqa ymm12,YMMWORD PTR [rsp] + vmovdqa ymm13,YMMWORD PTR [rsp+0x40] + vpbroadcastd ymm14,edx + vpbroadcastd ymm15,r8d + vpaddd ymm0,ymm0,ymm16 + vpaddd ymm1,ymm1,ymm18 + vpaddd ymm2,ymm2,ymm20 + vpaddd ymm3,ymm3,ymm22 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vprord ymm15,ymm15,0x10 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0xc + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vpaddd ymm0,ymm0,ymm17 + vpaddd ymm1,ymm1,ymm19 + vpaddd ymm2,ymm2,ymm21 + vpaddd ymm3,ymm3,ymm23 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vprord ymm15,ymm15,0x8 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0x7 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vpaddd ymm0,ymm0,ymm24 + vpaddd ymm1,ymm1,ymm26 + vpaddd ymm2,ymm2,ymm28 + vpaddd ymm3,ymm3,ymm30 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x10 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vprord ymm4,ymm4,0xc + vpaddd ymm0,ymm0,ymm25 + vpaddd ymm1,ymm1,ymm27 + vpaddd ymm2,ymm2,ymm29 + vpaddd ymm3,ymm3,ymm31 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x8 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vprord ymm4,ymm4,0x7 + vpaddd ymm0,ymm0,ymm18 + vpaddd ymm1,ymm1,ymm19 + vpaddd ymm2,ymm2,ymm23 + vpaddd ymm3,ymm3,ymm20 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vprord ymm15,ymm15,0x10 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0xc + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vpaddd ymm0,ymm0,ymm22 + vpaddd ymm1,ymm1,ymm26 + vpaddd ymm2,ymm2,ymm16 + vpaddd ymm3,ymm3,ymm29 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vprord ymm15,ymm15,0x8 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0x7 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vpaddd ymm0,ymm0,ymm17 + vpaddd ymm1,ymm1,ymm28 + vpaddd ymm2,ymm2,ymm25 + vpaddd ymm3,ymm3,ymm31 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x10 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vprord ymm4,ymm4,0xc + vpaddd ymm0,ymm0,ymm27 + vpaddd ymm1,ymm1,ymm21 + vpaddd ymm2,ymm2,ymm30 + vpaddd ymm3,ymm3,ymm24 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x8 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vprord ymm4,ymm4,0x7 + vpaddd ymm0,ymm0,ymm19 + vpaddd ymm1,ymm1,ymm26 + vpaddd ymm2,ymm2,ymm29 + vpaddd ymm3,ymm3,ymm23 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vprord ymm15,ymm15,0x10 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0xc + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vpaddd ymm0,ymm0,ymm20 + vpaddd ymm1,ymm1,ymm28 + vpaddd ymm2,ymm2,ymm18 + vpaddd ymm3,ymm3,ymm30 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vprord ymm15,ymm15,0x8 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0x7 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vpaddd ymm0,ymm0,ymm22 + vpaddd ymm1,ymm1,ymm25 + vpaddd ymm2,ymm2,ymm27 + vpaddd ymm3,ymm3,ymm24 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x10 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vprord ymm4,ymm4,0xc + vpaddd ymm0,ymm0,ymm21 + vpaddd ymm1,ymm1,ymm16 + vpaddd ymm2,ymm2,ymm31 + vpaddd ymm3,ymm3,ymm17 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x8 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vprord ymm4,ymm4,0x7 + vpaddd ymm0,ymm0,ymm26 + vpaddd ymm1,ymm1,ymm28 + vpaddd ymm2,ymm2,ymm30 + vpaddd ymm3,ymm3,ymm29 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vprord ymm15,ymm15,0x10 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0xc + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vpaddd ymm0,ymm0,ymm23 + vpaddd ymm1,ymm1,ymm25 + vpaddd ymm2,ymm2,ymm19 + vpaddd ymm3,ymm3,ymm31 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vprord ymm15,ymm15,0x8 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0x7 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vpaddd ymm0,ymm0,ymm20 + vpaddd ymm1,ymm1,ymm27 + vpaddd ymm2,ymm2,ymm21 + vpaddd ymm3,ymm3,ymm17 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x10 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vprord ymm4,ymm4,0xc + vpaddd ymm0,ymm0,ymm16 + vpaddd ymm1,ymm1,ymm18 + vpaddd ymm2,ymm2,ymm24 + vpaddd ymm3,ymm3,ymm22 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x8 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vprord ymm4,ymm4,0x7 + vpaddd ymm0,ymm0,ymm28 + vpaddd ymm1,ymm1,ymm25 + vpaddd ymm2,ymm2,ymm31 + vpaddd ymm3,ymm3,ymm30 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vprord ymm15,ymm15,0x10 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0xc + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vpaddd ymm0,ymm0,ymm29 + vpaddd ymm1,ymm1,ymm27 + vpaddd ymm2,ymm2,ymm26 + vpaddd ymm3,ymm3,ymm24 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vprord ymm15,ymm15,0x8 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0x7 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vpaddd ymm0,ymm0,ymm23 + vpaddd ymm1,ymm1,ymm21 + vpaddd ymm2,ymm2,ymm16 + vpaddd ymm3,ymm3,ymm22 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x10 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vprord ymm4,ymm4,0xc + vpaddd ymm0,ymm0,ymm18 + vpaddd ymm1,ymm1,ymm19 + vpaddd ymm2,ymm2,ymm17 + vpaddd ymm3,ymm3,ymm20 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x8 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vprord ymm4,ymm4,0x7 + vpaddd ymm0,ymm0,ymm25 + vpaddd ymm1,ymm1,ymm27 + vpaddd ymm2,ymm2,ymm24 + vpaddd ymm3,ymm3,ymm31 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vprord ymm15,ymm15,0x10 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0xc + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vpaddd ymm0,ymm0,ymm30 + vpaddd ymm1,ymm1,ymm21 + vpaddd ymm2,ymm2,ymm28 + vpaddd ymm3,ymm3,ymm17 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vprord ymm15,ymm15,0x8 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0x7 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vpaddd ymm0,ymm0,ymm29 + vpaddd ymm1,ymm1,ymm16 + vpaddd ymm2,ymm2,ymm18 + vpaddd ymm3,ymm3,ymm20 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x10 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vprord ymm4,ymm4,0xc + vpaddd ymm0,ymm0,ymm19 + vpaddd ymm1,ymm1,ymm26 + vpaddd ymm2,ymm2,ymm22 + vpaddd ymm3,ymm3,ymm23 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x8 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vprord ymm4,ymm4,0x7 + vpaddd ymm0,ymm0,ymm27 + vpaddd ymm1,ymm1,ymm21 + vpaddd ymm2,ymm2,ymm17 + vpaddd ymm3,ymm3,ymm24 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vprord ymm15,ymm15,0x10 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0xc + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vpaddd ymm0,ymm0,ymm31 + vpaddd ymm1,ymm1,ymm16 + vpaddd ymm2,ymm2,ymm25 + vpaddd ymm3,ymm3,ymm22 + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm1,ymm1,ymm5 + vpaddd ymm2,ymm2,ymm6 + vpaddd ymm3,ymm3,ymm7 + vpxord ymm12,ymm12,ymm0 + vpxord ymm13,ymm13,ymm1 + vpxord ymm14,ymm14,ymm2 + vpxord ymm15,ymm15,ymm3 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vprord ymm15,ymm15,0x8 + vpaddd ymm8,ymm8,ymm12 + vpaddd ymm9,ymm9,ymm13 + vpaddd ymm10,ymm10,ymm14 + vpaddd ymm11,ymm11,ymm15 + vpxord ymm4,ymm4,ymm8 + vpxord ymm5,ymm5,ymm9 + vpxord ymm6,ymm6,ymm10 + vpxord ymm7,ymm7,ymm11 + vprord ymm4,ymm4,0x7 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vpaddd ymm0,ymm0,ymm30 + vpaddd ymm1,ymm1,ymm18 + vpaddd ymm2,ymm2,ymm19 + vpaddd ymm3,ymm3,ymm23 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x10 + vprord ymm12,ymm12,0x10 + vprord ymm13,ymm13,0x10 + vprord ymm14,ymm14,0x10 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0xc + vprord ymm6,ymm6,0xc + vprord ymm7,ymm7,0xc + vprord ymm4,ymm4,0xc + vpaddd ymm0,ymm0,ymm26 + vpaddd ymm1,ymm1,ymm28 + vpaddd ymm2,ymm2,ymm20 + vpaddd ymm3,ymm3,ymm29 + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm1,ymm1,ymm6 + vpaddd ymm2,ymm2,ymm7 + vpaddd ymm3,ymm3,ymm4 + vpxord ymm15,ymm15,ymm0 + vpxord ymm12,ymm12,ymm1 + vpxord ymm13,ymm13,ymm2 + vpxord ymm14,ymm14,ymm3 + vprord ymm15,ymm15,0x8 + vprord ymm12,ymm12,0x8 + vprord ymm13,ymm13,0x8 + vprord ymm14,ymm14,0x8 + vpaddd ymm10,ymm10,ymm15 + vpaddd ymm11,ymm11,ymm12 + vpaddd ymm8,ymm8,ymm13 + vpaddd ymm9,ymm9,ymm14 + vpxord ymm5,ymm5,ymm10 + vpxord ymm6,ymm6,ymm11 + vpxord ymm7,ymm7,ymm8 + vpxord ymm4,ymm4,ymm9 + vprord ymm5,ymm5,0x7 + vprord ymm6,ymm6,0x7 + vprord ymm7,ymm7,0x7 + vprord ymm4,ymm4,0x7 + vpxor ymm0,ymm0,ymm8 + vpxor ymm1,ymm1,ymm9 + vpxor ymm2,ymm2,ymm10 + vpxor ymm3,ymm3,ymm11 + vpxor ymm4,ymm4,ymm12 + vpxor ymm5,ymm5,ymm13 + vpxor ymm6,ymm6,ymm14 + vpxor ymm7,ymm7,ymm15 + vpxord ymm8,ymm8,DWORD PTR [rdi]{1to8} + vpxord ymm9,ymm9,DWORD PTR [rdi+0x4]{1to8} + vpxord ymm10,ymm10,DWORD PTR [rdi+0x8]{1to8} + vpxord ymm11,ymm11,DWORD PTR [rdi+0xc]{1to8} + vpxord ymm12,ymm12,DWORD PTR [rdi+0x10]{1to8} + vpxord ymm13,ymm13,DWORD PTR [rdi+0x14]{1to8} + vpxord ymm14,ymm14,DWORD PTR [rdi+0x18]{1to8} + vpxord ymm15,ymm15,DWORD PTR [rdi+0x1c]{1to8} + vpunpckldq ymm16,ymm0,ymm1 + vpunpckhdq ymm17,ymm0,ymm1 + vpunpckldq ymm18,ymm2,ymm3 + vpunpckhdq ymm19,ymm2,ymm3 + vpunpckldq ymm20,ymm4,ymm5 + vpunpckhdq ymm21,ymm4,ymm5 + vpunpckldq ymm22,ymm6,ymm7 + vpunpckhdq ymm23,ymm6,ymm7 + vpunpckldq ymm24,ymm8,ymm9 + vpunpckhdq ymm25,ymm8,ymm9 + vpunpckldq ymm26,ymm10,ymm11 + vpunpckhdq ymm27,ymm10,ymm11 + vpunpckldq ymm28,ymm12,ymm13 + vpunpckhdq ymm29,ymm12,ymm13 + vpunpckldq ymm30,ymm14,ymm15 + vpunpckhdq ymm31,ymm14,ymm15 + vpunpcklqdq ymm0,ymm16,ymm18 + vpunpckhqdq ymm1,ymm16,ymm18 + vpunpcklqdq ymm2,ymm17,ymm19 + vpunpckhqdq ymm3,ymm17,ymm19 + vpunpcklqdq ymm4,ymm20,ymm22 + vpunpckhqdq ymm5,ymm20,ymm22 + vpunpcklqdq ymm6,ymm21,ymm23 + vpunpckhqdq ymm7,ymm21,ymm23 + vpunpcklqdq ymm8,ymm24,ymm26 + vpunpckhqdq ymm9,ymm24,ymm26 + vpunpcklqdq ymm10,ymm25,ymm27 + vpunpckhqdq ymm11,ymm25,ymm27 + vpunpcklqdq ymm12,ymm28,ymm30 + vpunpckhqdq ymm13,ymm28,ymm30 + vpunpcklqdq ymm14,ymm29,ymm31 + vpunpckhqdq ymm15,ymm29,ymm31 + vshufi32x4 ymm16,ymm0,ymm4,0x0 + vshufi32x4 ymm17,ymm8,ymm12,0x0 + vshufi32x4 ymm18,ymm1,ymm5,0x0 + vshufi32x4 ymm19,ymm9,ymm13,0x0 + vshufi32x4 ymm20,ymm2,ymm6,0x0 + vshufi32x4 ymm21,ymm10,ymm14,0x0 + vshufi32x4 ymm22,ymm3,ymm7,0x0 + vshufi32x4 ymm23,ymm11,ymm15,0x0 + vshufi32x4 ymm24,ymm0,ymm4,0x3 + vshufi32x4 ymm25,ymm8,ymm12,0x3 + vshufi32x4 ymm26,ymm1,ymm5,0x3 + vshufi32x4 ymm27,ymm9,ymm13,0x3 + vshufi32x4 ymm28,ymm2,ymm6,0x3 + vshufi32x4 ymm29,ymm10,ymm14,0x3 + vshufi32x4 ymm30,ymm3,ymm7,0x3 + vshufi32x4 ymm31,ymm11,ymm15,0x3 + vmovdqu32 YMMWORD PTR [r9],ymm16 + vmovdqu32 YMMWORD PTR [r9+0x20],ymm17 + vmovdqu32 YMMWORD PTR [r9+0x40],ymm18 + vmovdqu32 YMMWORD PTR [r9+0x60],ymm19 + vmovdqu32 YMMWORD PTR [r9+0x80],ymm20 + vmovdqu32 YMMWORD PTR [r9+0xa0],ymm21 + vmovdqu32 YMMWORD PTR [r9+0xc0],ymm22 + vmovdqu32 YMMWORD PTR [r9+0xe0],ymm23 + vmovdqu32 YMMWORD PTR [r9+0x100],ymm24 + vmovdqu32 YMMWORD PTR [r9+0x120],ymm25 + vmovdqu32 YMMWORD PTR [r9+0x140],ymm26 + vmovdqu32 YMMWORD PTR [r9+0x160],ymm27 + vmovdqu32 YMMWORD PTR [r9+0x180],ymm28 + vmovdqu32 YMMWORD PTR [r9+0x1a0],ymm29 + vmovdqu32 YMMWORD PTR [r9+0x1c0],ymm30 + vmovdqu32 YMMWORD PTR [r9+0x1e0],ymm31 + vmovdqa ymm0,YMMWORD PTR [rsp+0x20] + vmovdqa ymm1,YMMWORD PTR [rsp+0x60] + vmovdqa YMMWORD PTR [rsp],ymm0 + vmovdqa YMMWORD PTR [rsp+0x40],ymm1 + add r9,0x200 + sub r10,0x8 +2: + test r10,0x4 + je 2f + vbroadcasti32x4 zmm0,XMMWORD PTR [rdi] + vbroadcasti32x4 zmm1,XMMWORD PTR [rdi+0x10] + vbroadcasti32x4 zmm2,XMMWORD PTR [BLAKE3_IV+rip] + vmovdqa xmm12,XMMWORD PTR [rsp] + vmovdqa xmm13,XMMWORD PTR [rsp+0x40] + vpunpckldq xmm14,xmm12,xmm13 + vpunpckhdq xmm15,xmm12,xmm13 + vpermq ymm14,ymm14,0xdc + vpermq ymm15,ymm15,0xdc + vpbroadcastd zmm12,edx + vinserti64x4 zmm13,zmm14,ymm15,0x1 + mov eax,0x4444 + kmovw k2,eax + vpblendmd zmm13{k2},zmm13,zmm12 + vpbroadcastd zmm15,r8d + mov eax,0x8888 + kmovw k4,eax + vpblendmd zmm3{k4},zmm13,zmm15 + mov eax,0xaaaa + kmovw k3,eax + vbroadcasti32x4 zmm8,XMMWORD PTR [rsi] + vbroadcasti32x4 zmm9,XMMWORD PTR [rsi+0x10] + vshufps zmm4,zmm8,zmm9,0x88 + vshufps zmm5,zmm8,zmm9,0xdd + vbroadcasti32x4 zmm8,XMMWORD PTR [rsi+0x20] + vbroadcasti32x4 zmm9,XMMWORD PTR [rsi+0x30] + vshufps zmm6,zmm8,zmm9,0x88 + vshufps zmm7,zmm8,zmm9,0xdd + vpshufd zmm6,zmm6,0x93 + vpshufd zmm7,zmm7,0x93 + mov al,0x7 +3: + vpaddd zmm0,zmm0,zmm4 + vpaddd zmm0,zmm0,zmm1 + vpxord zmm3,zmm3,zmm0 + vprord zmm3,zmm3,0x10 + vpaddd zmm2,zmm2,zmm3 + vpxord zmm1,zmm1,zmm2 + vprord zmm1,zmm1,0xc + vpaddd zmm0,zmm0,zmm5 + vpaddd zmm0,zmm0,zmm1 + vpxord zmm3,zmm3,zmm0 + vprord zmm3,zmm3,0x8 + vpaddd zmm2,zmm2,zmm3 + vpxord zmm1,zmm1,zmm2 + vprord zmm1,zmm1,0x7 + vpshufd zmm0,zmm0,0x93 + vpshufd zmm3,zmm3,0x4e + vpshufd zmm2,zmm2,0x39 + vpaddd zmm0,zmm0,zmm6 + vpaddd zmm0,zmm0,zmm1 + vpxord zmm3,zmm3,zmm0 + vprord zmm3,zmm3,0x10 + vpaddd zmm2,zmm2,zmm3 + vpxord zmm1,zmm1,zmm2 + vprord zmm1,zmm1,0xc + vpaddd zmm0,zmm0,zmm7 + vpaddd zmm0,zmm0,zmm1 + vpxord zmm3,zmm3,zmm0 + vprord zmm3,zmm3,0x8 + vpaddd zmm2,zmm2,zmm3 + vpxord zmm1,zmm1,zmm2 + vprord zmm1,zmm1,0x7 + vpshufd zmm0,zmm0,0x39 + vpshufd zmm3,zmm3,0x4e + vpshufd zmm2,zmm2,0x93 + dec al + je 3f + vshufps zmm8,zmm4,zmm5,0xd6 + vpshufd zmm9,zmm4,0xf + vpshufd zmm4,zmm8,0x39 + vshufps zmm8,zmm6,zmm7,0xfa + vpblendmd zmm9{k3},zmm9,zmm8 + vpunpcklqdq zmm8,zmm7,zmm5 + vpblendmd zmm8{k4},zmm8,zmm6 + vpshufd zmm8,zmm8,0x78 + vpunpckhdq zmm5,zmm5,zmm7 + vpunpckldq zmm6,zmm6,zmm5 + vpshufd zmm7,zmm6,0x1e + vmovdqa32 zmm5,zmm9 + vmovdqa32 zmm6,zmm8 + jmp 3b +3: + vpxord zmm0,zmm0,zmm2 + vpxord zmm1,zmm1,zmm3 + vbroadcasti32x4 zmm8,XMMWORD PTR [rdi] + vbroadcasti32x4 zmm9,XMMWORD PTR [rdi+0x10] + vpxord zmm2,zmm2,zmm8 + vpxord zmm3,zmm3,zmm9 + vmovdqu XMMWORD PTR [r9],xmm0 + vmovdqu XMMWORD PTR [r9+0x10],xmm1 + vmovdqu XMMWORD PTR [r9+0x20],xmm2 + vmovdqu XMMWORD PTR [r9+0x30],xmm3 + vextracti128 XMMWORD PTR [r9+0x40],ymm0,0x1 + vextracti128 XMMWORD PTR [r9+0x50],ymm1,0x1 + vextracti128 XMMWORD PTR [r9+0x60],ymm2,0x1 + vextracti128 XMMWORD PTR [r9+0x70],ymm3,0x1 + vextracti32x4 XMMWORD PTR [r9+0x80],zmm0,0x2 + vextracti32x4 XMMWORD PTR [r9+0x90],zmm1,0x2 + vextracti32x4 XMMWORD PTR [r9+0xa0],zmm2,0x2 + vextracti32x4 XMMWORD PTR [r9+0xb0],zmm3,0x2 + vextracti32x4 XMMWORD PTR [r9+0xc0],zmm0,0x3 + vextracti32x4 XMMWORD PTR [r9+0xd0],zmm1,0x3 + vextracti32x4 XMMWORD PTR [r9+0xe0],zmm2,0x3 + vextracti32x4 XMMWORD PTR [r9+0xf0],zmm3,0x3 + vmovdqa xmm0,XMMWORD PTR [rsp+0x10] + vmovdqa xmm1,XMMWORD PTR [rsp+0x50] + vmovdqa XMMWORD PTR [rsp],xmm0 + vmovdqa XMMWORD PTR [rsp+0x40],xmm1 + add r9,0x100 + sub r10,0x4 +2: + test r10,0x2 + je 2f + vbroadcasti128 ymm0,XMMWORD PTR [rdi] + vbroadcasti128 ymm1,XMMWORD PTR [rdi+0x10] + vmovd xmm13,DWORD PTR [rsp] + vpinsrd xmm13,xmm13,DWORD PTR [rsp+0x40],0x1 + vpinsrd xmm13,xmm13,edx,0x2 + vmovd xmm14,DWORD PTR [rsp+0x4] + vpinsrd xmm14,xmm14,DWORD PTR [rsp+0x44],0x1 + vpinsrd xmm14,xmm14,edx,0x2 + vinserti128 ymm13,ymm13,xmm14,0x1 + vbroadcasti128 ymm2,XMMWORD PTR [BLAKE3_IV+rip] + vpbroadcastd ymm8,r8d + vpblendd ymm3,ymm13,ymm8,0x88 + vbroadcasti128 ymm8,XMMWORD PTR [rsi] + vbroadcasti128 ymm9,XMMWORD PTR [rsi+0x10] + vshufps ymm4,ymm8,ymm9,0x88 + vshufps ymm5,ymm8,ymm9,0xdd + vbroadcasti128 ymm8,XMMWORD PTR [rsi+0x20] + vbroadcasti128 ymm9,XMMWORD PTR [rsi+0x30] + vshufps ymm6,ymm8,ymm9,0x88 + vshufps ymm7,ymm8,ymm9,0xdd + vpshufd ymm6,ymm6,0x93 + vpshufd ymm7,ymm7,0x93 + mov al,0x7 +3: + vpaddd ymm0,ymm0,ymm4 + vpaddd ymm0,ymm0,ymm1 + vpxord ymm3,ymm3,ymm0 + vprord ymm3,ymm3,0x10 + vpaddd ymm2,ymm2,ymm3 + vpxord ymm1,ymm1,ymm2 + vprord ymm1,ymm1,0xc + vpaddd ymm0,ymm0,ymm5 + vpaddd ymm0,ymm0,ymm1 + vpxord ymm3,ymm3,ymm0 + vprord ymm3,ymm3,0x8 + vpaddd ymm2,ymm2,ymm3 + vpxord ymm1,ymm1,ymm2 + vprord ymm1,ymm1,0x7 + vpshufd ymm0,ymm0,0x93 + vpshufd ymm3,ymm3,0x4e + vpshufd ymm2,ymm2,0x39 + vpaddd ymm0,ymm0,ymm6 + vpaddd ymm0,ymm0,ymm1 + vpxord ymm3,ymm3,ymm0 + vprord ymm3,ymm3,0x10 + vpaddd ymm2,ymm2,ymm3 + vpxord ymm1,ymm1,ymm2 + vprord ymm1,ymm1,0xc + vpaddd ymm0,ymm0,ymm7 + vpaddd ymm0,ymm0,ymm1 + vpxord ymm3,ymm3,ymm0 + vprord ymm3,ymm3,0x8 + vpaddd ymm2,ymm2,ymm3 + vpxord ymm1,ymm1,ymm2 + vprord ymm1,ymm1,0x7 + vpshufd ymm0,ymm0,0x39 + vpshufd ymm3,ymm3,0x4e + vpshufd ymm2,ymm2,0x93 + dec al + je 3f + vshufps ymm8,ymm4,ymm5,0xd6 + vpshufd ymm9,ymm4,0xf + vpshufd ymm4,ymm8,0x39 + vshufps ymm8,ymm6,ymm7,0xfa + vpblendd ymm9,ymm9,ymm8,0xaa + vpunpcklqdq ymm8,ymm7,ymm5 + vpblendd ymm8,ymm8,ymm6,0x88 + vpshufd ymm8,ymm8,0x78 + vpunpckhdq ymm5,ymm5,ymm7 + vpunpckldq ymm6,ymm6,ymm5 + vpshufd ymm7,ymm6,0x1e + vmovdqa ymm5,ymm9 + vmovdqa ymm6,ymm8 + jmp 3b +3: + vpxor ymm0,ymm0,ymm2 + vpxor ymm1,ymm1,ymm3 + vbroadcasti128 ymm8,XMMWORD PTR [rdi] + vbroadcasti128 ymm9,XMMWORD PTR [rdi+0x10] + vpxor ymm2,ymm2,ymm8 + vpxor ymm3,ymm3,ymm9 + vmovdqu XMMWORD PTR [r9],xmm0 + vmovdqu XMMWORD PTR [r9+0x10],xmm1 + vmovdqu XMMWORD PTR [r9+0x20],xmm2 + vmovdqu XMMWORD PTR [r9+0x30],xmm3 + vextracti128 XMMWORD PTR [r9+0x40],ymm0,0x1 + vextracti128 XMMWORD PTR [r9+0x50],ymm1,0x1 + vextracti128 XMMWORD PTR [r9+0x60],ymm2,0x1 + vextracti128 XMMWORD PTR [r9+0x70],ymm3,0x1 + vmovdqu xmm0,XMMWORD PTR [rsp+0x8] + vmovdqu xmm1,XMMWORD PTR [rsp+0x48] + vmovdqa XMMWORD PTR [rsp],xmm0 + vmovdqa XMMWORD PTR [rsp+0x40],xmm1 + add r9,0x80 + sub r10,0x2 +2: + test r10,0x1 + je 9b + vmovdqu xmm0,XMMWORD PTR [rdi] + vmovdqu xmm1,XMMWORD PTR [rdi+0x10] + vmovd xmm14,DWORD PTR [rsp] + vpinsrd xmm14,xmm14,DWORD PTR [rsp+0x40],0x1 + vpinsrd xmm14,xmm14,edx,0x2 + vmovdqa xmm2,XMMWORD PTR [BLAKE3_IV+rip] + vpinsrd xmm3,xmm14,r8d,0x3 + vmovups xmm8,XMMWORD PTR [rsi] + vmovups xmm9,XMMWORD PTR [rsi+0x10] + vshufps xmm4,xmm8,xmm9,0x88 + vshufps xmm5,xmm8,xmm9,0xdd + vmovups xmm8,XMMWORD PTR [rsi+0x20] + vmovups xmm9,XMMWORD PTR [rsi+0x30] + vshufps xmm6,xmm8,xmm9,0x88 + vshufps xmm7,xmm8,xmm9,0xdd + vpshufd xmm6,xmm6,0x93 + vpshufd xmm7,xmm7,0x93 + mov al,0x7 +3: + vpaddd xmm0,xmm0,xmm4 + vpaddd xmm0,xmm0,xmm1 + vpxord xmm3,xmm3,xmm0 + vprord xmm3,xmm3,0x10 + vpaddd xmm2,xmm2,xmm3 + vpxord xmm1,xmm1,xmm2 + vprord xmm1,xmm1,0xc + vpaddd xmm0,xmm0,xmm5 + vpaddd xmm0,xmm0,xmm1 + vpxord xmm3,xmm3,xmm0 + vprord xmm3,xmm3,0x8 + vpaddd xmm2,xmm2,xmm3 + vpxord xmm1,xmm1,xmm2 + vprord xmm1,xmm1,0x7 + vpshufd xmm0,xmm0,0x93 + vpshufd xmm3,xmm3,0x4e + vpshufd xmm2,xmm2,0x39 + vpaddd xmm0,xmm0,xmm6 + vpaddd xmm0,xmm0,xmm1 + vpxord xmm3,xmm3,xmm0 + vprord xmm3,xmm3,0x10 + vpaddd xmm2,xmm2,xmm3 + vpxord xmm1,xmm1,xmm2 + vprord xmm1,xmm1,0xc + vpaddd xmm0,xmm0,xmm7 + vpaddd xmm0,xmm0,xmm1 + vpxord xmm3,xmm3,xmm0 + vprord xmm3,xmm3,0x8 + vpaddd xmm2,xmm2,xmm3 + vpxord xmm1,xmm1,xmm2 + vprord xmm1,xmm1,0x7 + vpshufd xmm0,xmm0,0x39 + vpshufd xmm3,xmm3,0x4e + vpshufd xmm2,xmm2,0x93 + dec al + je 3f + vshufps xmm8,xmm4,xmm5,0xd6 + vpshufd xmm9,xmm4,0xf + vpshufd xmm4,xmm8,0x39 + vshufps xmm8,xmm6,xmm7,0xfa + vpblendd xmm9,xmm9,xmm8,0xaa + vpunpcklqdq xmm8,xmm7,xmm5 + vpblendd xmm8,xmm8,xmm6,0x88 + vpshufd xmm8,xmm8,0x78 + vpunpckhdq xmm5,xmm5,xmm7 + vpunpckldq xmm6,xmm6,xmm5 + vpshufd xmm7,xmm6,0x1e + vmovdqa xmm5,xmm9 + vmovdqa xmm6,xmm8 + jmp 3b +3: + vpxor xmm0,xmm0,xmm2 + vpxor xmm1,xmm1,xmm3 + vpxor xmm2,xmm2,XMMWORD PTR [rdi] + vpxor xmm3,xmm3,XMMWORD PTR [rdi+0x10] + vmovdqu XMMWORD PTR [r9],xmm0 + vmovdqu XMMWORD PTR [r9+0x10],xmm1 + vmovdqu XMMWORD PTR [r9+0x20],xmm2 + vmovdqu XMMWORD PTR [r9+0x30],xmm3 + jmp 9b + + #ifdef __APPLE__ .static_data #else diff --git a/src/third_party/blake3/asm/blake3_avx512_x86-64_windows_msvc.asm b/src/third_party/blake3/blake3_avx512_x86-64_windows_msvc.asm similarity index 100% rename from src/third_party/blake3/asm/blake3_avx512_x86-64_windows_msvc.asm rename to src/third_party/blake3/blake3_avx512_x86-64_windows_msvc.asm diff --git a/src/third_party/blake3/c/blake3_dispatch.c b/src/third_party/blake3/blake3_dispatch.c similarity index 85% rename from src/third_party/blake3/c/blake3_dispatch.c rename to src/third_party/blake3/blake3_dispatch.c index 88f6bc88..0092a36c 100644 --- a/src/third_party/blake3/c/blake3_dispatch.c +++ b/src/third_party/blake3/blake3_dispatch.c @@ -4,43 +4,23 @@ #include "blake3_impl.h" + #if defined(IS_X86) -#if defined(_MSC_VER) + +#if defined(__GNUC__) || defined(__clang__) +#define ATOMIC_INT int +#define ATOMIC_LOAD(x) __atomic_load_n(&x, __ATOMIC_RELAXED) +#define ATOMIC_STORE(x, y) __atomic_store_n(&x, y, __ATOMIC_RELAXED) +#else #include -#elif defined(__GNUC__) -#include -#else -#undef IS_X86 /* Unimplemented! */ -#endif +#define ATOMIC_INT int +#define ATOMIC_LOAD(x) __iso_volatile_load32(&x) +#define ATOMIC_STORE(x, y) __iso_volatile_store32(&x, y) #endif - -#if !defined(BLAKE3_ATOMICS) -#if defined(__has_include) -#if __has_include() && !defined(_MSC_VER) -#define BLAKE3_ATOMICS 1 -#else -#define BLAKE3_ATOMICS 0 -#endif /* __has_include() && !defined(_MSC_VER) */ -#else -#define BLAKE3_ATOMICS 0 -#endif /* defined(__has_include) */ -#endif /* BLAKE3_ATOMICS */ - -#if BLAKE3_ATOMICS -#define ATOMIC_INT _Atomic int -#define ATOMIC_LOAD(x) x -#define ATOMIC_STORE(x, y) x = y -#elif defined(_MSC_VER) -#define ATOMIC_INT long -#define ATOMIC_LOAD(x) _InterlockedOr(&x, 0) -#define ATOMIC_STORE(x, y) _InterlockedExchange(&x, y) -#else -#define ATOMIC_INT int -#define ATOMIC_LOAD(x) x -#define ATOMIC_STORE(x, y) x = y #endif + #define MAYBE_UNUSED(x) (void)((x)) #if defined(IS_X86) @@ -86,7 +66,6 @@ static void cpuidex(uint32_t out[4], uint32_t id, uint32_t sid) { #endif } -#endif enum cpu_feature { SSE2 = 1 << 0, @@ -112,9 +91,9 @@ static get_cpu_features(void) { /* If TSAN detects a data race here, try compiling with -DBLAKE3_ATOMICS=1 */ - long features = ATOMIC_LOAD(g_cpu_features); + enum cpu_feature features = ATOMIC_LOAD(g_cpu_features); if (features != UNDEFINED) { - return (enum cpu_feature)features; + return features; } else { #if defined(IS_X86) uint32_t regs[4] = {0}; @@ -154,13 +133,14 @@ static } } ATOMIC_STORE(g_cpu_features, features); - return (enum cpu_feature)features; + return features; #else /* How to detect NEON? */ return 0; #endif } } +#endif void blake3_compress_in_place(uint32_t cv[8], const uint8_t block[BLAKE3_BLOCK_LEN], @@ -220,6 +200,30 @@ void blake3_compress_xof(const uint32_t cv[8], blake3_compress_xof_portable(cv, block, block_len, counter, flags, out); } + +void blake3_xof_many(const uint32_t cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, uint64_t counter, uint8_t flags, + uint8_t out[64], size_t outblocks) { + if (outblocks == 0) { + // The current assembly implementation always outputs at least 1 block. + return; + } +#if defined(IS_X86) + const enum cpu_feature features = get_cpu_features(); + MAYBE_UNUSED(features); +#if !defined(_WIN32) && !defined(__CYGWIN__) && !defined(BLAKE3_NO_AVX512) + if (features & AVX512VL) { + blake3_xof_many_avx512(cv, block, block_len, counter, flags, out, outblocks); + return; + } +#endif +#endif + for(size_t i = 0; i < outblocks; ++i) { + blake3_compress_xof(cv, block, block_len, counter + i, flags, out + 64*i); + } +} + void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs, size_t blocks, const uint32_t key[8], uint64_t counter, bool increment_counter, uint8_t flags, diff --git a/src/third_party/blake3/asm/blake3_impl.h b/src/third_party/blake3/blake3_impl.h similarity index 84% rename from src/third_party/blake3/asm/blake3_impl.h rename to src/third_party/blake3/blake3_impl.h index beab5cf5..88e71e41 100644 --- a/src/third_party/blake3/asm/blake3_impl.h +++ b/src/third_party/blake3/blake3_impl.h @@ -9,6 +9,10 @@ #include "blake3.h" +#ifdef __cplusplus +extern "C" { +#endif + // internal flags enum blake3_flags { CHUNK_START = 1 << 0, @@ -28,7 +32,13 @@ enum blake3_flags { #define INLINE static inline __attribute__((always_inline)) #endif -#if defined(__x86_64__) || defined(_M_X64) +#ifdef __cplusplus +#define NOEXCEPT noexcept +#else +#define NOEXCEPT +#endif + +#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_M_ARM64EC) #define IS_X86 #define IS_X86_64 #endif @@ -38,7 +48,7 @@ enum blake3_flags { #define IS_X86_32 #endif -#if defined(__aarch64__) || defined(_M_ARM64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #define IS_AARCH64 #endif @@ -162,6 +172,13 @@ INLINE void load_key_words(const uint8_t key[BLAKE3_KEY_LEN], key_words[7] = load32(&key[7 * 4]); } +INLINE void load_block_words(const uint8_t block[BLAKE3_BLOCK_LEN], + uint32_t block_words[16]) { + for (size_t i = 0; i < 16; i++) { + block_words[i] = load32(&block[i * 4]); + } +} + INLINE void store32(void *dst, uint32_t w) { uint8_t *p = (uint8_t *)dst; p[0] = (uint8_t)(w >> 0); @@ -191,6 +208,11 @@ void blake3_compress_xof(const uint32_t cv[8], uint8_t block_len, uint64_t counter, uint8_t flags, uint8_t out[64]); +void blake3_xof_many(const uint32_t cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, uint64_t counter, uint8_t flags, + uint8_t out[64], size_t outblocks); + void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs, size_t blocks, const uint32_t key[8], uint64_t counter, bool increment_counter, uint8_t flags, @@ -198,6 +220,22 @@ void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs, size_t blake3_simd_degree(void); +BLAKE3_PRIVATE size_t blake3_compress_subtree_wide(const uint8_t *input, size_t input_len, + const uint32_t key[8], + uint64_t chunk_counter, uint8_t flags, + uint8_t *out, bool use_tbb); + +#if defined(BLAKE3_USE_TBB) +BLAKE3_PRIVATE void blake3_compress_subtree_wide_join_tbb( + // shared params + const uint32_t key[8], uint8_t flags, bool use_tbb, + // left-hand side params + const uint8_t *l_input, size_t l_input_len, uint64_t l_chunk_counter, + uint8_t *l_cvs, size_t *l_n, + // right-hand side params + const uint8_t *r_input, size_t r_input_len, uint64_t r_chunk_counter, + uint8_t *r_cvs, size_t *r_n) NOEXCEPT; +#endif // Declarations for implementation-specific functions. void blake3_compress_in_place_portable(uint32_t cv[8], @@ -270,6 +308,13 @@ void blake3_hash_many_avx512(const uint8_t *const *inputs, size_t num_inputs, uint64_t counter, bool increment_counter, uint8_t flags, uint8_t flags_start, uint8_t flags_end, uint8_t *out); + +#if !defined(_WIN32) && !defined(__CYGWIN__) +void blake3_xof_many_avx512(const uint32_t cv[8], + const uint8_t block[BLAKE3_BLOCK_LEN], + uint8_t block_len, uint64_t counter, uint8_t flags, + uint8_t* out, size_t outblocks); +#endif #endif #endif @@ -281,5 +326,8 @@ void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs, uint8_t flags_end, uint8_t *out); #endif +#ifdef __cplusplus +} +#endif #endif /* BLAKE3_IMPL_H */ diff --git a/src/third_party/blake3/asm/blake3_neon.c b/src/third_party/blake3/blake3_neon.c similarity index 96% rename from src/third_party/blake3/asm/blake3_neon.c rename to src/third_party/blake3/blake3_neon.c index 8a818fc7..794ea80f 100644 --- a/src/third_party/blake3/asm/blake3_neon.c +++ b/src/third_party/blake3/blake3_neon.c @@ -10,14 +10,12 @@ INLINE uint32x4_t loadu_128(const uint8_t src[16]) { // vld1q_u32 has alignment requirements. Don't use it. - uint32x4_t x; - memcpy(&x, src, 16); - return x; + return vreinterpretq_u32_u8(vld1q_u8(src)); } INLINE void storeu_128(uint32x4_t src, uint8_t dest[16]) { // vst1q_u32 has alignment requirements. Don't use it. - memcpy(dest, &src, 16); + vst1q_u8(dest, vreinterpretq_u8_u32(src)); } INLINE uint32x4_t add_128(uint32x4_t a, uint32x4_t b) { @@ -36,7 +34,7 @@ INLINE uint32x4_t set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { } INLINE uint32x4_t rot16_128(uint32x4_t x) { - // The straightfoward implementation would be two shifts and an or, but that's + // The straightforward implementation would be two shifts and an or, but that's // slower on microarchitectures we've tested. See // https://github.com/BLAKE3-team/BLAKE3/pull/319. // return vorrq_u32(vshrq_n_u32(x, 16), vshlq_n_u32(x, 32 - 16)); @@ -245,10 +243,11 @@ INLINE void load_counters4(uint64_t counter, bool increment_counter, counter_high(counter + (mask & 2)), counter_high(counter + (mask & 3))); } -void blake3_hash4_neon(const uint8_t *const *inputs, size_t blocks, - const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out) { +static void blake3_hash4_neon(const uint8_t *const *inputs, size_t blocks, + const uint32_t key[8], uint64_t counter, + bool increment_counter, uint8_t flags, + uint8_t flags_start, uint8_t flags_end, + uint8_t *out) { uint32x4_t h_vecs[8] = { set1_128(key[0]), set1_128(key[1]), set1_128(key[2]), set1_128(key[3]), set1_128(key[4]), set1_128(key[5]), set1_128(key[6]), set1_128(key[7]), diff --git a/src/third_party/blake3/asm/blake3_portable.c b/src/third_party/blake3/blake3_portable.c similarity index 100% rename from src/third_party/blake3/asm/blake3_portable.c rename to src/third_party/blake3/blake3_portable.c diff --git a/src/third_party/blake3/asm/blake3_sse2_x86-64_unix.S b/src/third_party/blake3/blake3_sse2_x86-64_unix.S similarity index 100% rename from src/third_party/blake3/asm/blake3_sse2_x86-64_unix.S rename to src/third_party/blake3/blake3_sse2_x86-64_unix.S diff --git a/src/third_party/blake3/asm/blake3_sse2_x86-64_windows_msvc.asm b/src/third_party/blake3/blake3_sse2_x86-64_windows_msvc.asm similarity index 100% rename from src/third_party/blake3/asm/blake3_sse2_x86-64_windows_msvc.asm rename to src/third_party/blake3/blake3_sse2_x86-64_windows_msvc.asm diff --git a/src/third_party/blake3/asm/blake3_sse41_x86-64_unix.S b/src/third_party/blake3/blake3_sse41_x86-64_unix.S similarity index 100% rename from src/third_party/blake3/asm/blake3_sse41_x86-64_unix.S rename to src/third_party/blake3/blake3_sse41_x86-64_unix.S diff --git a/src/third_party/blake3/asm/blake3_sse41_x86-64_windows_msvc.asm b/src/third_party/blake3/blake3_sse41_x86-64_windows_msvc.asm similarity index 100% rename from src/third_party/blake3/asm/blake3_sse41_x86-64_windows_msvc.asm rename to src/third_party/blake3/blake3_sse41_x86-64_windows_msvc.asm diff --git a/src/third_party/blake3/c/LICENSE b/src/third_party/blake3/c/LICENSE deleted file mode 100644 index f5892efc..00000000 --- a/src/third_party/blake3/c/LICENSE +++ /dev/null @@ -1,330 +0,0 @@ -This work is released into the public domain with CC0 1.0. Alternatively, it is -licensed under the Apache License 2.0. - -------------------------------------------------------------------------------- - -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. - -------------------------------------------------------------------------------- - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2019 Jack O'Connor and Samuel Neves - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/third_party/blake3/c/blake3.c b/src/third_party/blake3/c/blake3.c deleted file mode 100644 index 692f4b02..00000000 --- a/src/third_party/blake3/c/blake3.c +++ /dev/null @@ -1,616 +0,0 @@ -#include -#include -#include - -#include "blake3.h" -#include "blake3_impl.h" - -const char *blake3_version(void) { return BLAKE3_VERSION_STRING; } - -INLINE void chunk_state_init(blake3_chunk_state *self, const uint32_t key[8], - uint8_t flags) { - memcpy(self->cv, key, BLAKE3_KEY_LEN); - self->chunk_counter = 0; - memset(self->buf, 0, BLAKE3_BLOCK_LEN); - self->buf_len = 0; - self->blocks_compressed = 0; - self->flags = flags; -} - -INLINE void chunk_state_reset(blake3_chunk_state *self, const uint32_t key[8], - uint64_t chunk_counter) { - memcpy(self->cv, key, BLAKE3_KEY_LEN); - self->chunk_counter = chunk_counter; - self->blocks_compressed = 0; - memset(self->buf, 0, BLAKE3_BLOCK_LEN); - self->buf_len = 0; -} - -INLINE size_t chunk_state_len(const blake3_chunk_state *self) { - return (BLAKE3_BLOCK_LEN * (size_t)self->blocks_compressed) + - ((size_t)self->buf_len); -} - -INLINE size_t chunk_state_fill_buf(blake3_chunk_state *self, - const uint8_t *input, size_t input_len) { - size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len); - if (take > input_len) { - take = input_len; - } - uint8_t *dest = self->buf + ((size_t)self->buf_len); - memcpy(dest, input, take); - self->buf_len += (uint8_t)take; - return take; -} - -INLINE uint8_t chunk_state_maybe_start_flag(const blake3_chunk_state *self) { - if (self->blocks_compressed == 0) { - return CHUNK_START; - } else { - return 0; - } -} - -typedef struct { - uint32_t input_cv[8]; - uint64_t counter; - uint8_t block[BLAKE3_BLOCK_LEN]; - uint8_t block_len; - uint8_t flags; -} output_t; - -INLINE output_t make_output(const uint32_t input_cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags) { - output_t ret; - memcpy(ret.input_cv, input_cv, 32); - memcpy(ret.block, block, BLAKE3_BLOCK_LEN); - ret.block_len = block_len; - ret.counter = counter; - ret.flags = flags; - return ret; -} - -// Chaining values within a given chunk (specifically the compress_in_place -// interface) are represented as words. This avoids unnecessary bytes<->words -// conversion overhead in the portable implementation. However, the hash_many -// interface handles both user input and parent node blocks, so it accepts -// bytes. For that reason, chaining values in the CV stack are represented as -// bytes. -INLINE void output_chaining_value(const output_t *self, uint8_t cv[32]) { - uint32_t cv_words[8]; - memcpy(cv_words, self->input_cv, 32); - blake3_compress_in_place(cv_words, self->block, self->block_len, - self->counter, self->flags); - store_cv_words(cv, cv_words); -} - -INLINE void output_root_bytes(const output_t *self, uint64_t seek, uint8_t *out, - size_t out_len) { - uint64_t output_block_counter = seek / 64; - size_t offset_within_block = seek % 64; - uint8_t wide_buf[64]; - while (out_len > 0) { - blake3_compress_xof(self->input_cv, self->block, self->block_len, - output_block_counter, self->flags | ROOT, wide_buf); - size_t available_bytes = 64 - offset_within_block; - size_t memcpy_len; - if (out_len > available_bytes) { - memcpy_len = available_bytes; - } else { - memcpy_len = out_len; - } - memcpy(out, wide_buf + offset_within_block, memcpy_len); - out += memcpy_len; - out_len -= memcpy_len; - output_block_counter += 1; - offset_within_block = 0; - } -} - -INLINE void chunk_state_update(blake3_chunk_state *self, const uint8_t *input, - size_t input_len) { - if (self->buf_len > 0) { - size_t take = chunk_state_fill_buf(self, input, input_len); - input += take; - input_len -= take; - if (input_len > 0) { - blake3_compress_in_place( - self->cv, self->buf, BLAKE3_BLOCK_LEN, self->chunk_counter, - self->flags | chunk_state_maybe_start_flag(self)); - self->blocks_compressed += 1; - self->buf_len = 0; - memset(self->buf, 0, BLAKE3_BLOCK_LEN); - } - } - - while (input_len > BLAKE3_BLOCK_LEN) { - blake3_compress_in_place(self->cv, input, BLAKE3_BLOCK_LEN, - self->chunk_counter, - self->flags | chunk_state_maybe_start_flag(self)); - self->blocks_compressed += 1; - input += BLAKE3_BLOCK_LEN; - input_len -= BLAKE3_BLOCK_LEN; - } - - size_t take = chunk_state_fill_buf(self, input, input_len); - input += take; - input_len -= take; -} - -INLINE output_t chunk_state_output(const blake3_chunk_state *self) { - uint8_t block_flags = - self->flags | chunk_state_maybe_start_flag(self) | CHUNK_END; - return make_output(self->cv, self->buf, self->buf_len, self->chunk_counter, - block_flags); -} - -INLINE output_t parent_output(const uint8_t block[BLAKE3_BLOCK_LEN], - const uint32_t key[8], uint8_t flags) { - return make_output(key, block, BLAKE3_BLOCK_LEN, 0, flags | PARENT); -} - -// Given some input larger than one chunk, return the number of bytes that -// should go in the left subtree. This is the largest power-of-2 number of -// chunks that leaves at least 1 byte for the right subtree. -INLINE size_t left_len(size_t content_len) { - // Subtract 1 to reserve at least one byte for the right side. content_len - // should always be greater than BLAKE3_CHUNK_LEN. - size_t full_chunks = (content_len - 1) / BLAKE3_CHUNK_LEN; - return round_down_to_power_of_2(full_chunks) * BLAKE3_CHUNK_LEN; -} - -// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE chunks at the same time -// on a single thread. Write out the chunk chaining values and return the -// number of chunks hashed. These chunks are never the root and never empty; -// those cases use a different codepath. -INLINE size_t compress_chunks_parallel(const uint8_t *input, size_t input_len, - const uint32_t key[8], - uint64_t chunk_counter, uint8_t flags, - uint8_t *out) { -#if defined(BLAKE3_TESTING) - assert(0 < input_len); - assert(input_len <= MAX_SIMD_DEGREE * BLAKE3_CHUNK_LEN); -#endif - - const uint8_t *chunks_array[MAX_SIMD_DEGREE]; - size_t input_position = 0; - size_t chunks_array_len = 0; - while (input_len - input_position >= BLAKE3_CHUNK_LEN) { - chunks_array[chunks_array_len] = &input[input_position]; - input_position += BLAKE3_CHUNK_LEN; - chunks_array_len += 1; - } - - blake3_hash_many(chunks_array, chunks_array_len, - BLAKE3_CHUNK_LEN / BLAKE3_BLOCK_LEN, key, chunk_counter, - true, flags, CHUNK_START, CHUNK_END, out); - - // Hash the remaining partial chunk, if there is one. Note that the empty - // chunk (meaning the empty message) is a different codepath. - if (input_len > input_position) { - uint64_t counter = chunk_counter + (uint64_t)chunks_array_len; - blake3_chunk_state chunk_state; - chunk_state_init(&chunk_state, key, flags); - chunk_state.chunk_counter = counter; - chunk_state_update(&chunk_state, &input[input_position], - input_len - input_position); - output_t output = chunk_state_output(&chunk_state); - output_chaining_value(&output, &out[chunks_array_len * BLAKE3_OUT_LEN]); - return chunks_array_len + 1; - } else { - return chunks_array_len; - } -} - -// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE parents at the same time -// on a single thread. Write out the parent chaining values and return the -// number of parents hashed. (If there's an odd input chaining value left over, -// return it as an additional output.) These parents are never the root and -// never empty; those cases use a different codepath. -INLINE size_t compress_parents_parallel(const uint8_t *child_chaining_values, - size_t num_chaining_values, - const uint32_t key[8], uint8_t flags, - uint8_t *out) { -#if defined(BLAKE3_TESTING) - assert(2 <= num_chaining_values); - assert(num_chaining_values <= 2 * MAX_SIMD_DEGREE_OR_2); -#endif - - const uint8_t *parents_array[MAX_SIMD_DEGREE_OR_2]; - size_t parents_array_len = 0; - while (num_chaining_values - (2 * parents_array_len) >= 2) { - parents_array[parents_array_len] = - &child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN]; - parents_array_len += 1; - } - - blake3_hash_many(parents_array, parents_array_len, 1, key, - 0, // Parents always use counter 0. - false, flags | PARENT, - 0, // Parents have no start flags. - 0, // Parents have no end flags. - out); - - // If there's an odd child left over, it becomes an output. - if (num_chaining_values > 2 * parents_array_len) { - memcpy(&out[parents_array_len * BLAKE3_OUT_LEN], - &child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN], - BLAKE3_OUT_LEN); - return parents_array_len + 1; - } else { - return parents_array_len; - } -} - -// The wide helper function returns (writes out) an array of chaining values -// and returns the length of that array. The number of chaining values returned -// is the dynamically detected SIMD degree, at most MAX_SIMD_DEGREE. Or fewer, -// if the input is shorter than that many chunks. The reason for maintaining a -// wide array of chaining values going back up the tree, is to allow the -// implementation to hash as many parents in parallel as possible. -// -// As a special case when the SIMD degree is 1, this function will still return -// at least 2 outputs. This guarantees that this function doesn't perform the -// root compression. (If it did, it would use the wrong flags, and also we -// wouldn't be able to implement extendable output.) Note that this function is -// not used when the whole input is only 1 chunk long; that's a different -// codepath. -// -// Why not just have the caller split the input on the first update(), instead -// of implementing this special rule? Because we don't want to limit SIMD or -// multi-threading parallelism for that update(). -static size_t blake3_compress_subtree_wide(const uint8_t *input, - size_t input_len, - const uint32_t key[8], - uint64_t chunk_counter, - uint8_t flags, uint8_t *out) { - // Note that the single chunk case does *not* bump the SIMD degree up to 2 - // when it is 1. If this implementation adds multi-threading in the future, - // this gives us the option of multi-threading even the 2-chunk case, which - // can help performance on smaller platforms. - if (input_len <= blake3_simd_degree() * BLAKE3_CHUNK_LEN) { - return compress_chunks_parallel(input, input_len, key, chunk_counter, flags, - out); - } - - // With more than simd_degree chunks, we need to recurse. Start by dividing - // the input into left and right subtrees. (Note that this is only optimal - // as long as the SIMD degree is a power of 2. If we ever get a SIMD degree - // of 3 or something, we'll need a more complicated strategy.) - size_t left_input_len = left_len(input_len); - size_t right_input_len = input_len - left_input_len; - const uint8_t *right_input = &input[left_input_len]; - uint64_t right_chunk_counter = - chunk_counter + (uint64_t)(left_input_len / BLAKE3_CHUNK_LEN); - - // Make space for the child outputs. Here we use MAX_SIMD_DEGREE_OR_2 to - // account for the special case of returning 2 outputs when the SIMD degree - // is 1. - uint8_t cv_array[2 * MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN]; - size_t degree = blake3_simd_degree(); - if (left_input_len > BLAKE3_CHUNK_LEN && degree == 1) { - // The special case: We always use a degree of at least two, to make - // sure there are two outputs. Except, as noted above, at the chunk - // level, where we allow degree=1. (Note that the 1-chunk-input case is - // a different codepath.) - degree = 2; - } - uint8_t *right_cvs = &cv_array[degree * BLAKE3_OUT_LEN]; - - // Recurse! If this implementation adds multi-threading support in the - // future, this is where it will go. - size_t left_n = blake3_compress_subtree_wide(input, left_input_len, key, - chunk_counter, flags, cv_array); - size_t right_n = blake3_compress_subtree_wide( - right_input, right_input_len, key, right_chunk_counter, flags, right_cvs); - - // The special case again. If simd_degree=1, then we'll have left_n=1 and - // right_n=1. Rather than compressing them into a single output, return - // them directly, to make sure we always have at least two outputs. - if (left_n == 1) { - memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN); - return 2; - } - - // Otherwise, do one layer of parent node compression. - size_t num_chaining_values = left_n + right_n; - return compress_parents_parallel(cv_array, num_chaining_values, key, flags, - out); -} - -// Hash a subtree with compress_subtree_wide(), and then condense the resulting -// list of chaining values down to a single parent node. Don't compress that -// last parent node, however. Instead, return its message bytes (the -// concatenated chaining values of its children). This is necessary when the -// first call to update() supplies a complete subtree, because the topmost -// parent node of that subtree could end up being the root. It's also necessary -// for extended output in the general case. -// -// As with compress_subtree_wide(), this function is not used on inputs of 1 -// chunk or less. That's a different codepath. -INLINE void compress_subtree_to_parent_node( - const uint8_t *input, size_t input_len, const uint32_t key[8], - uint64_t chunk_counter, uint8_t flags, uint8_t out[2 * BLAKE3_OUT_LEN]) { -#if defined(BLAKE3_TESTING) - assert(input_len > BLAKE3_CHUNK_LEN); -#endif - - uint8_t cv_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN]; - size_t num_cvs = blake3_compress_subtree_wide(input, input_len, key, - chunk_counter, flags, cv_array); - assert(num_cvs <= MAX_SIMD_DEGREE_OR_2); - - // If MAX_SIMD_DEGREE is greater than 2 and there's enough input, - // compress_subtree_wide() returns more than 2 chaining values. Condense - // them into 2 by forming parent nodes repeatedly. - uint8_t out_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN / 2]; - // The second half of this loop condition is always true, and we just - // asserted it above. But GCC can't tell that it's always true, and if NDEBUG - // is set on platforms where MAX_SIMD_DEGREE_OR_2 == 2, GCC emits spurious - // warnings here. GCC 8.5 is particularly sensitive, so if you're changing - // this code, test it against that version. - while (num_cvs > 2 && num_cvs <= MAX_SIMD_DEGREE_OR_2) { - num_cvs = - compress_parents_parallel(cv_array, num_cvs, key, flags, out_array); - memcpy(cv_array, out_array, num_cvs * BLAKE3_OUT_LEN); - } - memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN); -} - -INLINE void hasher_init_base(blake3_hasher *self, const uint32_t key[8], - uint8_t flags) { - memcpy(self->key, key, BLAKE3_KEY_LEN); - chunk_state_init(&self->chunk, key, flags); - self->cv_stack_len = 0; -} - -void blake3_hasher_init(blake3_hasher *self) { hasher_init_base(self, IV, 0); } - -void blake3_hasher_init_keyed(blake3_hasher *self, - const uint8_t key[BLAKE3_KEY_LEN]) { - uint32_t key_words[8]; - load_key_words(key, key_words); - hasher_init_base(self, key_words, KEYED_HASH); -} - -void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context, - size_t context_len) { - blake3_hasher context_hasher; - hasher_init_base(&context_hasher, IV, DERIVE_KEY_CONTEXT); - blake3_hasher_update(&context_hasher, context, context_len); - uint8_t context_key[BLAKE3_KEY_LEN]; - blake3_hasher_finalize(&context_hasher, context_key, BLAKE3_KEY_LEN); - uint32_t context_key_words[8]; - load_key_words(context_key, context_key_words); - hasher_init_base(self, context_key_words, DERIVE_KEY_MATERIAL); -} - -void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context) { - blake3_hasher_init_derive_key_raw(self, context, strlen(context)); -} - -// As described in hasher_push_cv() below, we do "lazy merging", delaying -// merges until right before the next CV is about to be added. This is -// different from the reference implementation. Another difference is that we -// aren't always merging 1 chunk at a time. Instead, each CV might represent -// any power-of-two number of chunks, as long as the smaller-above-larger stack -// order is maintained. Instead of the "count the trailing 0-bits" algorithm -// described in the spec, we use a "count the total number of 1-bits" variant -// that doesn't require us to retain the subtree size of the CV on top of the -// stack. The principle is the same: each CV that should remain in the stack is -// represented by a 1-bit in the total number of chunks (or bytes) so far. -INLINE void hasher_merge_cv_stack(blake3_hasher *self, uint64_t total_len) { - size_t post_merge_stack_len = (size_t)popcnt(total_len); - while (self->cv_stack_len > post_merge_stack_len) { - uint8_t *parent_node = - &self->cv_stack[(self->cv_stack_len - 2) * BLAKE3_OUT_LEN]; - output_t output = parent_output(parent_node, self->key, self->chunk.flags); - output_chaining_value(&output, parent_node); - self->cv_stack_len -= 1; - } -} - -// In reference_impl.rs, we merge the new CV with existing CVs from the stack -// before pushing it. We can do that because we know more input is coming, so -// we know none of the merges are root. -// -// This setting is different. We want to feed as much input as possible to -// compress_subtree_wide(), without setting aside anything for the chunk_state. -// If the user gives us 64 KiB, we want to parallelize over all 64 KiB at once -// as a single subtree, if at all possible. -// -// This leads to two problems: -// 1) This 64 KiB input might be the only call that ever gets made to update. -// In this case, the root node of the 64 KiB subtree would be the root node -// of the whole tree, and it would need to be ROOT finalized. We can't -// compress it until we know. -// 2) This 64 KiB input might complete a larger tree, whose root node is -// similarly going to be the the root of the whole tree. For example, maybe -// we have 196 KiB (that is, 128 + 64) hashed so far. We can't compress the -// node at the root of the 256 KiB subtree until we know how to finalize it. -// -// The second problem is solved with "lazy merging". That is, when we're about -// to add a CV to the stack, we don't merge it with anything first, as the -// reference impl does. Instead we do merges using the *previous* CV that was -// added, which is sitting on top of the stack, and we put the new CV -// (unmerged) on top of the stack afterwards. This guarantees that we never -// merge the root node until finalize(). -// -// Solving the first problem requires an additional tool, -// compress_subtree_to_parent_node(). That function always returns the top -// *two* chaining values of the subtree it's compressing. We then do lazy -// merging with each of them separately, so that the second CV will always -// remain unmerged. (That also helps us support extendable output when we're -// hashing an input all-at-once.) -INLINE void hasher_push_cv(blake3_hasher *self, uint8_t new_cv[BLAKE3_OUT_LEN], - uint64_t chunk_counter) { - hasher_merge_cv_stack(self, chunk_counter); - memcpy(&self->cv_stack[self->cv_stack_len * BLAKE3_OUT_LEN], new_cv, - BLAKE3_OUT_LEN); - self->cv_stack_len += 1; -} - -void blake3_hasher_update(blake3_hasher *self, const void *input, - size_t input_len) { - // Explicitly checking for zero avoids causing UB by passing a null pointer - // to memcpy. This comes up in practice with things like: - // std::vector v; - // blake3_hasher_update(&hasher, v.data(), v.size()); - if (input_len == 0) { - return; - } - - const uint8_t *input_bytes = (const uint8_t *)input; - - // If we have some partial chunk bytes in the internal chunk_state, we need - // to finish that chunk first. - if (chunk_state_len(&self->chunk) > 0) { - size_t take = BLAKE3_CHUNK_LEN - chunk_state_len(&self->chunk); - if (take > input_len) { - take = input_len; - } - chunk_state_update(&self->chunk, input_bytes, take); - input_bytes += take; - input_len -= take; - // If we've filled the current chunk and there's more coming, finalize this - // chunk and proceed. In this case we know it's not the root. - if (input_len > 0) { - output_t output = chunk_state_output(&self->chunk); - uint8_t chunk_cv[32]; - output_chaining_value(&output, chunk_cv); - hasher_push_cv(self, chunk_cv, self->chunk.chunk_counter); - chunk_state_reset(&self->chunk, self->key, self->chunk.chunk_counter + 1); - } else { - return; - } - } - - // Now the chunk_state is clear, and we have more input. If there's more than - // a single chunk (so, definitely not the root chunk), hash the largest whole - // subtree we can, with the full benefits of SIMD (and maybe in the future, - // multi-threading) parallelism. Two restrictions: - // - The subtree has to be a power-of-2 number of chunks. Only subtrees along - // the right edge can be incomplete, and we don't know where the right edge - // is going to be until we get to finalize(). - // - The subtree must evenly divide the total number of chunks up until this - // point (if total is not 0). If the current incomplete subtree is only - // waiting for 1 more chunk, we can't hash a subtree of 4 chunks. We have - // to complete the current subtree first. - // Because we might need to break up the input to form powers of 2, or to - // evenly divide what we already have, this part runs in a loop. - while (input_len > BLAKE3_CHUNK_LEN) { - size_t subtree_len = round_down_to_power_of_2(input_len); - uint64_t count_so_far = self->chunk.chunk_counter * BLAKE3_CHUNK_LEN; - // Shrink the subtree_len until it evenly divides the count so far. We know - // that subtree_len itself is a power of 2, so we can use a bitmasking - // trick instead of an actual remainder operation. (Note that if the caller - // consistently passes power-of-2 inputs of the same size, as is hopefully - // typical, this loop condition will always fail, and subtree_len will - // always be the full length of the input.) - // - // An aside: We don't have to shrink subtree_len quite this much. For - // example, if count_so_far is 1, we could pass 2 chunks to - // compress_subtree_to_parent_node. Since we'll get 2 CVs back, we'll still - // get the right answer in the end, and we might get to use 2-way SIMD - // parallelism. The problem with this optimization, is that it gets us - // stuck always hashing 2 chunks. The total number of chunks will remain - // odd, and we'll never graduate to higher degrees of parallelism. See - // https://github.com/BLAKE3-team/BLAKE3/issues/69. - while ((((uint64_t)(subtree_len - 1)) & count_so_far) != 0) { - subtree_len /= 2; - } - // The shrunken subtree_len might now be 1 chunk long. If so, hash that one - // chunk by itself. Otherwise, compress the subtree into a pair of CVs. - uint64_t subtree_chunks = subtree_len / BLAKE3_CHUNK_LEN; - if (subtree_len <= BLAKE3_CHUNK_LEN) { - blake3_chunk_state chunk_state; - chunk_state_init(&chunk_state, self->key, self->chunk.flags); - chunk_state.chunk_counter = self->chunk.chunk_counter; - chunk_state_update(&chunk_state, input_bytes, subtree_len); - output_t output = chunk_state_output(&chunk_state); - uint8_t cv[BLAKE3_OUT_LEN]; - output_chaining_value(&output, cv); - hasher_push_cv(self, cv, chunk_state.chunk_counter); - } else { - // This is the high-performance happy path, though getting here depends - // on the caller giving us a long enough input. - uint8_t cv_pair[2 * BLAKE3_OUT_LEN]; - compress_subtree_to_parent_node(input_bytes, subtree_len, self->key, - self->chunk.chunk_counter, - self->chunk.flags, cv_pair); - hasher_push_cv(self, cv_pair, self->chunk.chunk_counter); - hasher_push_cv(self, &cv_pair[BLAKE3_OUT_LEN], - self->chunk.chunk_counter + (subtree_chunks / 2)); - } - self->chunk.chunk_counter += subtree_chunks; - input_bytes += subtree_len; - input_len -= subtree_len; - } - - // If there's any remaining input less than a full chunk, add it to the chunk - // state. In that case, also do a final merge loop to make sure the subtree - // stack doesn't contain any unmerged pairs. The remaining input means we - // know these merges are non-root. This merge loop isn't strictly necessary - // here, because hasher_push_chunk_cv already does its own merge loop, but it - // simplifies blake3_hasher_finalize below. - if (input_len > 0) { - chunk_state_update(&self->chunk, input_bytes, input_len); - hasher_merge_cv_stack(self, self->chunk.chunk_counter); - } -} - -void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out, - size_t out_len) { - blake3_hasher_finalize_seek(self, 0, out, out_len); -} - -void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek, - uint8_t *out, size_t out_len) { - // Explicitly checking for zero avoids causing UB by passing a null pointer - // to memcpy. This comes up in practice with things like: - // std::vector v; - // blake3_hasher_finalize(&hasher, v.data(), v.size()); - if (out_len == 0) { - return; - } - - // If the subtree stack is empty, then the current chunk is the root. - if (self->cv_stack_len == 0) { - output_t output = chunk_state_output(&self->chunk); - output_root_bytes(&output, seek, out, out_len); - return; - } - // If there are any bytes in the chunk state, finalize that chunk and do a - // roll-up merge between that chunk hash and every subtree in the stack. In - // this case, the extra merge loop at the end of blake3_hasher_update - // guarantees that none of the subtrees in the stack need to be merged with - // each other first. Otherwise, if there are no bytes in the chunk state, - // then the top of the stack is a chunk hash, and we start the merge from - // that. - output_t output; - size_t cvs_remaining; - if (chunk_state_len(&self->chunk) > 0) { - cvs_remaining = self->cv_stack_len; - output = chunk_state_output(&self->chunk); - } else { - // There are always at least 2 CVs in the stack in this case. - cvs_remaining = self->cv_stack_len - 2; - output = parent_output(&self->cv_stack[cvs_remaining * 32], self->key, - self->chunk.flags); - } - while (cvs_remaining > 0) { - cvs_remaining -= 1; - uint8_t parent_block[BLAKE3_BLOCK_LEN]; - memcpy(parent_block, &self->cv_stack[cvs_remaining * 32], 32); - output_chaining_value(&output, &parent_block[32]); - output = parent_output(parent_block, self->key, self->chunk.flags); - } - output_root_bytes(&output, seek, out, out_len); -} - -void blake3_hasher_reset(blake3_hasher *self) { - chunk_state_reset(&self->chunk, self->key, 0); - self->cv_stack_len = 0; -} diff --git a/src/third_party/blake3/c/blake3_avx2.c b/src/third_party/blake3/c/blake3_avx2.c deleted file mode 100644 index 46b5cecf..00000000 --- a/src/third_party/blake3/c/blake3_avx2.c +++ /dev/null @@ -1,312 +0,0 @@ -#include "blake3_impl.h" - -#include - -#define DEGREE 8 - -INLINE __m256i loadu(const uint8_t src[32]) { - return _mm256_loadu_si256((const __m256i *)src); -} - -INLINE void storeu(__m256i src, uint8_t dest[16]) { - _mm256_storeu_si256((__m256i *)dest, src); -} - -INLINE __m256i addv(__m256i a, __m256i b) { return _mm256_add_epi32(a, b); } - -// Note that clang-format doesn't like the name "xor" for some reason. -INLINE __m256i xorv(__m256i a, __m256i b) { return _mm256_xor_si256(a, b); } - -INLINE __m256i set1(uint32_t x) { return _mm256_set1_epi32((int32_t)x); } - -INLINE __m256i rot16(__m256i x) { - return _mm256_shuffle_epi8( - x, _mm256_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2, - 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2)); -} - -INLINE __m256i rot12(__m256i x) { - return _mm256_or_si256(_mm256_srli_epi32(x, 12), _mm256_slli_epi32(x, 32 - 12)); -} - -INLINE __m256i rot8(__m256i x) { - return _mm256_shuffle_epi8( - x, _mm256_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1, - 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1)); -} - -INLINE __m256i rot7(__m256i x) { - return _mm256_or_si256(_mm256_srli_epi32(x, 7), _mm256_slli_epi32(x, 32 - 7)); -} - -INLINE void round_fn(__m256i v[16], __m256i m[16], size_t r) { - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]); - v[0] = addv(v[0], v[4]); - v[1] = addv(v[1], v[5]); - v[2] = addv(v[2], v[6]); - v[3] = addv(v[3], v[7]); - v[12] = xorv(v[12], v[0]); - v[13] = xorv(v[13], v[1]); - v[14] = xorv(v[14], v[2]); - v[15] = xorv(v[15], v[3]); - v[12] = rot16(v[12]); - v[13] = rot16(v[13]); - v[14] = rot16(v[14]); - v[15] = rot16(v[15]); - v[8] = addv(v[8], v[12]); - v[9] = addv(v[9], v[13]); - v[10] = addv(v[10], v[14]); - v[11] = addv(v[11], v[15]); - v[4] = xorv(v[4], v[8]); - v[5] = xorv(v[5], v[9]); - v[6] = xorv(v[6], v[10]); - v[7] = xorv(v[7], v[11]); - v[4] = rot12(v[4]); - v[5] = rot12(v[5]); - v[6] = rot12(v[6]); - v[7] = rot12(v[7]); - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]); - v[0] = addv(v[0], v[4]); - v[1] = addv(v[1], v[5]); - v[2] = addv(v[2], v[6]); - v[3] = addv(v[3], v[7]); - v[12] = xorv(v[12], v[0]); - v[13] = xorv(v[13], v[1]); - v[14] = xorv(v[14], v[2]); - v[15] = xorv(v[15], v[3]); - v[12] = rot8(v[12]); - v[13] = rot8(v[13]); - v[14] = rot8(v[14]); - v[15] = rot8(v[15]); - v[8] = addv(v[8], v[12]); - v[9] = addv(v[9], v[13]); - v[10] = addv(v[10], v[14]); - v[11] = addv(v[11], v[15]); - v[4] = xorv(v[4], v[8]); - v[5] = xorv(v[5], v[9]); - v[6] = xorv(v[6], v[10]); - v[7] = xorv(v[7], v[11]); - v[4] = rot7(v[4]); - v[5] = rot7(v[5]); - v[6] = rot7(v[6]); - v[7] = rot7(v[7]); - - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]); - v[0] = addv(v[0], v[5]); - v[1] = addv(v[1], v[6]); - v[2] = addv(v[2], v[7]); - v[3] = addv(v[3], v[4]); - v[15] = xorv(v[15], v[0]); - v[12] = xorv(v[12], v[1]); - v[13] = xorv(v[13], v[2]); - v[14] = xorv(v[14], v[3]); - v[15] = rot16(v[15]); - v[12] = rot16(v[12]); - v[13] = rot16(v[13]); - v[14] = rot16(v[14]); - v[10] = addv(v[10], v[15]); - v[11] = addv(v[11], v[12]); - v[8] = addv(v[8], v[13]); - v[9] = addv(v[9], v[14]); - v[5] = xorv(v[5], v[10]); - v[6] = xorv(v[6], v[11]); - v[7] = xorv(v[7], v[8]); - v[4] = xorv(v[4], v[9]); - v[5] = rot12(v[5]); - v[6] = rot12(v[6]); - v[7] = rot12(v[7]); - v[4] = rot12(v[4]); - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]); - v[0] = addv(v[0], v[5]); - v[1] = addv(v[1], v[6]); - v[2] = addv(v[2], v[7]); - v[3] = addv(v[3], v[4]); - v[15] = xorv(v[15], v[0]); - v[12] = xorv(v[12], v[1]); - v[13] = xorv(v[13], v[2]); - v[14] = xorv(v[14], v[3]); - v[15] = rot8(v[15]); - v[12] = rot8(v[12]); - v[13] = rot8(v[13]); - v[14] = rot8(v[14]); - v[10] = addv(v[10], v[15]); - v[11] = addv(v[11], v[12]); - v[8] = addv(v[8], v[13]); - v[9] = addv(v[9], v[14]); - v[5] = xorv(v[5], v[10]); - v[6] = xorv(v[6], v[11]); - v[7] = xorv(v[7], v[8]); - v[4] = xorv(v[4], v[9]); - v[5] = rot7(v[5]); - v[6] = rot7(v[6]); - v[7] = rot7(v[7]); - v[4] = rot7(v[4]); -} - -INLINE void transpose_vecs(__m256i vecs[DEGREE]) { - // Interleave 32-bit lanes. The low unpack is lanes 00/11/44/55, and the high - // is 22/33/66/77. - __m256i ab_0145 = _mm256_unpacklo_epi32(vecs[0], vecs[1]); - __m256i ab_2367 = _mm256_unpackhi_epi32(vecs[0], vecs[1]); - __m256i cd_0145 = _mm256_unpacklo_epi32(vecs[2], vecs[3]); - __m256i cd_2367 = _mm256_unpackhi_epi32(vecs[2], vecs[3]); - __m256i ef_0145 = _mm256_unpacklo_epi32(vecs[4], vecs[5]); - __m256i ef_2367 = _mm256_unpackhi_epi32(vecs[4], vecs[5]); - __m256i gh_0145 = _mm256_unpacklo_epi32(vecs[6], vecs[7]); - __m256i gh_2367 = _mm256_unpackhi_epi32(vecs[6], vecs[7]); - - // Interleave 64-bit lanes. The low unpack is lanes 00/22 and the high is - // 11/33. - __m256i abcd_04 = _mm256_unpacklo_epi64(ab_0145, cd_0145); - __m256i abcd_15 = _mm256_unpackhi_epi64(ab_0145, cd_0145); - __m256i abcd_26 = _mm256_unpacklo_epi64(ab_2367, cd_2367); - __m256i abcd_37 = _mm256_unpackhi_epi64(ab_2367, cd_2367); - __m256i efgh_04 = _mm256_unpacklo_epi64(ef_0145, gh_0145); - __m256i efgh_15 = _mm256_unpackhi_epi64(ef_0145, gh_0145); - __m256i efgh_26 = _mm256_unpacklo_epi64(ef_2367, gh_2367); - __m256i efgh_37 = _mm256_unpackhi_epi64(ef_2367, gh_2367); - - // Interleave 128-bit lanes. - vecs[0] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x20); - vecs[1] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x20); - vecs[2] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x20); - vecs[3] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x20); - vecs[4] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x31); - vecs[5] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x31); - vecs[6] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x31); - vecs[7] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x31); -} - -INLINE void transpose_msg_vecs(const uint8_t *const *inputs, - size_t block_offset, __m256i out[16]) { - out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m256i)]); - out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m256i)]); - out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m256i)]); - out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m256i)]); - out[4] = loadu(&inputs[4][block_offset + 0 * sizeof(__m256i)]); - out[5] = loadu(&inputs[5][block_offset + 0 * sizeof(__m256i)]); - out[6] = loadu(&inputs[6][block_offset + 0 * sizeof(__m256i)]); - out[7] = loadu(&inputs[7][block_offset + 0 * sizeof(__m256i)]); - out[8] = loadu(&inputs[0][block_offset + 1 * sizeof(__m256i)]); - out[9] = loadu(&inputs[1][block_offset + 1 * sizeof(__m256i)]); - out[10] = loadu(&inputs[2][block_offset + 1 * sizeof(__m256i)]); - out[11] = loadu(&inputs[3][block_offset + 1 * sizeof(__m256i)]); - out[12] = loadu(&inputs[4][block_offset + 1 * sizeof(__m256i)]); - out[13] = loadu(&inputs[5][block_offset + 1 * sizeof(__m256i)]); - out[14] = loadu(&inputs[6][block_offset + 1 * sizeof(__m256i)]); - out[15] = loadu(&inputs[7][block_offset + 1 * sizeof(__m256i)]); - for (size_t i = 0; i < 8; ++i) { - _mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0); - } - transpose_vecs(&out[0]); - transpose_vecs(&out[8]); -} - -INLINE void load_counters(uint64_t counter, bool increment_counter, - __m256i *out_lo, __m256i *out_hi) { - const __m256i mask = _mm256_set1_epi32(-(int32_t)increment_counter); - const __m256i add0 = _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0); - const __m256i add1 = _mm256_and_si256(mask, add0); - __m256i l = _mm256_add_epi32(_mm256_set1_epi32((int32_t)counter), add1); - __m256i carry = _mm256_cmpgt_epi32(_mm256_xor_si256(add1, _mm256_set1_epi32(0x80000000)), - _mm256_xor_si256( l, _mm256_set1_epi32(0x80000000))); - __m256i h = _mm256_sub_epi32(_mm256_set1_epi32((int32_t)(counter >> 32)), carry); - *out_lo = l; - *out_hi = h; -} - -static -void blake3_hash8_avx2(const uint8_t *const *inputs, size_t blocks, - const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out) { - __m256i h_vecs[8] = { - set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]), - set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]), - }; - __m256i counter_low_vec, counter_high_vec; - load_counters(counter, increment_counter, &counter_low_vec, - &counter_high_vec); - uint8_t block_flags = flags | flags_start; - - for (size_t block = 0; block < blocks; block++) { - if (block + 1 == blocks) { - block_flags |= flags_end; - } - __m256i block_len_vec = set1(BLAKE3_BLOCK_LEN); - __m256i block_flags_vec = set1(block_flags); - __m256i msg_vecs[16]; - transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs); - - __m256i v[16] = { - h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3], - h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7], - set1(IV[0]), set1(IV[1]), set1(IV[2]), set1(IV[3]), - counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec, - }; - round_fn(v, msg_vecs, 0); - round_fn(v, msg_vecs, 1); - round_fn(v, msg_vecs, 2); - round_fn(v, msg_vecs, 3); - round_fn(v, msg_vecs, 4); - round_fn(v, msg_vecs, 5); - round_fn(v, msg_vecs, 6); - h_vecs[0] = xorv(v[0], v[8]); - h_vecs[1] = xorv(v[1], v[9]); - h_vecs[2] = xorv(v[2], v[10]); - h_vecs[3] = xorv(v[3], v[11]); - h_vecs[4] = xorv(v[4], v[12]); - h_vecs[5] = xorv(v[5], v[13]); - h_vecs[6] = xorv(v[6], v[14]); - h_vecs[7] = xorv(v[7], v[15]); - - block_flags = flags; - } - - transpose_vecs(h_vecs); - storeu(h_vecs[0], &out[0 * sizeof(__m256i)]); - storeu(h_vecs[1], &out[1 * sizeof(__m256i)]); - storeu(h_vecs[2], &out[2 * sizeof(__m256i)]); - storeu(h_vecs[3], &out[3 * sizeof(__m256i)]); - storeu(h_vecs[4], &out[4 * sizeof(__m256i)]); - storeu(h_vecs[5], &out[5 * sizeof(__m256i)]); - storeu(h_vecs[6], &out[6 * sizeof(__m256i)]); - storeu(h_vecs[7], &out[7 * sizeof(__m256i)]); -} - -void blake3_hash_many_avx2(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out) { - while (num_inputs >= DEGREE) { - blake3_hash8_avx2(inputs, blocks, key, counter, increment_counter, flags, - flags_start, flags_end, out); - if (increment_counter) { - counter += DEGREE; - } - inputs += DEGREE; - num_inputs -= DEGREE; - out = &out[DEGREE * BLAKE3_OUT_LEN]; - } -#if !defined(BLAKE3_NO_SSE41) - blake3_hash_many_sse41(inputs, num_inputs, blocks, key, counter, - increment_counter, flags, flags_start, flags_end, out); -#else - blake3_hash_many_portable(inputs, num_inputs, blocks, key, counter, - increment_counter, flags, flags_start, flags_end, - out); -#endif -} diff --git a/src/third_party/blake3/c/blake3_avx512.c b/src/third_party/blake3/c/blake3_avx512.c deleted file mode 100644 index f969bf37..00000000 --- a/src/third_party/blake3/c/blake3_avx512.c +++ /dev/null @@ -1,1220 +0,0 @@ -#include "blake3_impl.h" - -#include - -#define _mm_shuffle_ps2(a, b, c) \ - (_mm_castps_si128( \ - _mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), (c)))) - -INLINE __m128i loadu_128(const uint8_t src[16]) { - return _mm_loadu_si128((const __m128i *)src); -} - -INLINE __m256i loadu_256(const uint8_t src[32]) { - return _mm256_loadu_si256((const __m256i *)src); -} - -INLINE __m512i loadu_512(const uint8_t src[64]) { - return _mm512_loadu_si512((const __m512i *)src); -} - -INLINE void storeu_128(__m128i src, uint8_t dest[16]) { - _mm_storeu_si128((__m128i *)dest, src); -} - -INLINE void storeu_256(__m256i src, uint8_t dest[16]) { - _mm256_storeu_si256((__m256i *)dest, src); -} - -INLINE __m128i add_128(__m128i a, __m128i b) { return _mm_add_epi32(a, b); } - -INLINE __m256i add_256(__m256i a, __m256i b) { return _mm256_add_epi32(a, b); } - -INLINE __m512i add_512(__m512i a, __m512i b) { return _mm512_add_epi32(a, b); } - -INLINE __m128i xor_128(__m128i a, __m128i b) { return _mm_xor_si128(a, b); } - -INLINE __m256i xor_256(__m256i a, __m256i b) { return _mm256_xor_si256(a, b); } - -INLINE __m512i xor_512(__m512i a, __m512i b) { return _mm512_xor_si512(a, b); } - -INLINE __m128i set1_128(uint32_t x) { return _mm_set1_epi32((int32_t)x); } - -INLINE __m256i set1_256(uint32_t x) { return _mm256_set1_epi32((int32_t)x); } - -INLINE __m512i set1_512(uint32_t x) { return _mm512_set1_epi32((int32_t)x); } - -INLINE __m128i set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { - return _mm_setr_epi32((int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d); -} - -INLINE __m128i rot16_128(__m128i x) { return _mm_ror_epi32(x, 16); } - -INLINE __m256i rot16_256(__m256i x) { return _mm256_ror_epi32(x, 16); } - -INLINE __m512i rot16_512(__m512i x) { return _mm512_ror_epi32(x, 16); } - -INLINE __m128i rot12_128(__m128i x) { return _mm_ror_epi32(x, 12); } - -INLINE __m256i rot12_256(__m256i x) { return _mm256_ror_epi32(x, 12); } - -INLINE __m512i rot12_512(__m512i x) { return _mm512_ror_epi32(x, 12); } - -INLINE __m128i rot8_128(__m128i x) { return _mm_ror_epi32(x, 8); } - -INLINE __m256i rot8_256(__m256i x) { return _mm256_ror_epi32(x, 8); } - -INLINE __m512i rot8_512(__m512i x) { return _mm512_ror_epi32(x, 8); } - -INLINE __m128i rot7_128(__m128i x) { return _mm_ror_epi32(x, 7); } - -INLINE __m256i rot7_256(__m256i x) { return _mm256_ror_epi32(x, 7); } - -INLINE __m512i rot7_512(__m512i x) { return _mm512_ror_epi32(x, 7); } - -/* - * ---------------------------------------------------------------------------- - * compress_avx512 - * ---------------------------------------------------------------------------- - */ - -INLINE void g1(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3, - __m128i m) { - *row0 = add_128(add_128(*row0, m), *row1); - *row3 = xor_128(*row3, *row0); - *row3 = rot16_128(*row3); - *row2 = add_128(*row2, *row3); - *row1 = xor_128(*row1, *row2); - *row1 = rot12_128(*row1); -} - -INLINE void g2(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3, - __m128i m) { - *row0 = add_128(add_128(*row0, m), *row1); - *row3 = xor_128(*row3, *row0); - *row3 = rot8_128(*row3); - *row2 = add_128(*row2, *row3); - *row1 = xor_128(*row1, *row2); - *row1 = rot7_128(*row1); -} - -// Note the optimization here of leaving row1 as the unrotated row, rather than -// row0. All the message loads below are adjusted to compensate for this. See -// discussion at https://github.com/sneves/blake2-avx2/pull/4 -INLINE void diagonalize(__m128i *row0, __m128i *row2, __m128i *row3) { - *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(2, 1, 0, 3)); - *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2)); - *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(0, 3, 2, 1)); -} - -INLINE void undiagonalize(__m128i *row0, __m128i *row2, __m128i *row3) { - *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(0, 3, 2, 1)); - *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2)); - *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3)); -} - -INLINE void compress_pre(__m128i rows[4], const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, uint8_t flags) { - rows[0] = loadu_128((uint8_t *)&cv[0]); - rows[1] = loadu_128((uint8_t *)&cv[4]); - rows[2] = set4(IV[0], IV[1], IV[2], IV[3]); - rows[3] = set4(counter_low(counter), counter_high(counter), - (uint32_t)block_len, (uint32_t)flags); - - __m128i m0 = loadu_128(&block[sizeof(__m128i) * 0]); - __m128i m1 = loadu_128(&block[sizeof(__m128i) * 1]); - __m128i m2 = loadu_128(&block[sizeof(__m128i) * 2]); - __m128i m3 = loadu_128(&block[sizeof(__m128i) * 3]); - - __m128i t0, t1, t2, t3, tt; - - // Round 1. The first round permutes the message words from the original - // input order, into the groups that get mixed in parallel. - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(2, 0, 2, 0)); // 6 4 2 0 - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 3, 1)); // 7 5 3 1 - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(2, 0, 2, 0)); // 14 12 10 8 - t2 = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2, 1, 0, 3)); // 12 10 8 14 - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 1, 3, 1)); // 15 13 11 9 - t3 = _mm_shuffle_epi32(t3, _MM_SHUFFLE(2, 1, 0, 3)); // 13 11 9 15 - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 2. This round and all following rounds apply a fixed permutation - // to the message words from the round before. - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 3 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 4 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 5 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 6 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 7 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); -} - -void blake3_compress_xof_avx512(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags, uint8_t out[64]) { - __m128i rows[4]; - compress_pre(rows, cv, block, block_len, counter, flags); - storeu_128(xor_128(rows[0], rows[2]), &out[0]); - storeu_128(xor_128(rows[1], rows[3]), &out[16]); - storeu_128(xor_128(rows[2], loadu_128((uint8_t *)&cv[0])), &out[32]); - storeu_128(xor_128(rows[3], loadu_128((uint8_t *)&cv[4])), &out[48]); -} - -void blake3_compress_in_place_avx512(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags) { - __m128i rows[4]; - compress_pre(rows, cv, block, block_len, counter, flags); - storeu_128(xor_128(rows[0], rows[2]), (uint8_t *)&cv[0]); - storeu_128(xor_128(rows[1], rows[3]), (uint8_t *)&cv[4]); -} - -/* - * ---------------------------------------------------------------------------- - * hash4_avx512 - * ---------------------------------------------------------------------------- - */ - -INLINE void round_fn4(__m128i v[16], __m128i m[16], size_t r) { - v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][0]]); - v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][2]]); - v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][4]]); - v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][6]]); - v[0] = add_128(v[0], v[4]); - v[1] = add_128(v[1], v[5]); - v[2] = add_128(v[2], v[6]); - v[3] = add_128(v[3], v[7]); - v[12] = xor_128(v[12], v[0]); - v[13] = xor_128(v[13], v[1]); - v[14] = xor_128(v[14], v[2]); - v[15] = xor_128(v[15], v[3]); - v[12] = rot16_128(v[12]); - v[13] = rot16_128(v[13]); - v[14] = rot16_128(v[14]); - v[15] = rot16_128(v[15]); - v[8] = add_128(v[8], v[12]); - v[9] = add_128(v[9], v[13]); - v[10] = add_128(v[10], v[14]); - v[11] = add_128(v[11], v[15]); - v[4] = xor_128(v[4], v[8]); - v[5] = xor_128(v[5], v[9]); - v[6] = xor_128(v[6], v[10]); - v[7] = xor_128(v[7], v[11]); - v[4] = rot12_128(v[4]); - v[5] = rot12_128(v[5]); - v[6] = rot12_128(v[6]); - v[7] = rot12_128(v[7]); - v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][1]]); - v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][3]]); - v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][5]]); - v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][7]]); - v[0] = add_128(v[0], v[4]); - v[1] = add_128(v[1], v[5]); - v[2] = add_128(v[2], v[6]); - v[3] = add_128(v[3], v[7]); - v[12] = xor_128(v[12], v[0]); - v[13] = xor_128(v[13], v[1]); - v[14] = xor_128(v[14], v[2]); - v[15] = xor_128(v[15], v[3]); - v[12] = rot8_128(v[12]); - v[13] = rot8_128(v[13]); - v[14] = rot8_128(v[14]); - v[15] = rot8_128(v[15]); - v[8] = add_128(v[8], v[12]); - v[9] = add_128(v[9], v[13]); - v[10] = add_128(v[10], v[14]); - v[11] = add_128(v[11], v[15]); - v[4] = xor_128(v[4], v[8]); - v[5] = xor_128(v[5], v[9]); - v[6] = xor_128(v[6], v[10]); - v[7] = xor_128(v[7], v[11]); - v[4] = rot7_128(v[4]); - v[5] = rot7_128(v[5]); - v[6] = rot7_128(v[6]); - v[7] = rot7_128(v[7]); - - v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][8]]); - v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][10]]); - v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][12]]); - v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][14]]); - v[0] = add_128(v[0], v[5]); - v[1] = add_128(v[1], v[6]); - v[2] = add_128(v[2], v[7]); - v[3] = add_128(v[3], v[4]); - v[15] = xor_128(v[15], v[0]); - v[12] = xor_128(v[12], v[1]); - v[13] = xor_128(v[13], v[2]); - v[14] = xor_128(v[14], v[3]); - v[15] = rot16_128(v[15]); - v[12] = rot16_128(v[12]); - v[13] = rot16_128(v[13]); - v[14] = rot16_128(v[14]); - v[10] = add_128(v[10], v[15]); - v[11] = add_128(v[11], v[12]); - v[8] = add_128(v[8], v[13]); - v[9] = add_128(v[9], v[14]); - v[5] = xor_128(v[5], v[10]); - v[6] = xor_128(v[6], v[11]); - v[7] = xor_128(v[7], v[8]); - v[4] = xor_128(v[4], v[9]); - v[5] = rot12_128(v[5]); - v[6] = rot12_128(v[6]); - v[7] = rot12_128(v[7]); - v[4] = rot12_128(v[4]); - v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][9]]); - v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][11]]); - v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][13]]); - v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][15]]); - v[0] = add_128(v[0], v[5]); - v[1] = add_128(v[1], v[6]); - v[2] = add_128(v[2], v[7]); - v[3] = add_128(v[3], v[4]); - v[15] = xor_128(v[15], v[0]); - v[12] = xor_128(v[12], v[1]); - v[13] = xor_128(v[13], v[2]); - v[14] = xor_128(v[14], v[3]); - v[15] = rot8_128(v[15]); - v[12] = rot8_128(v[12]); - v[13] = rot8_128(v[13]); - v[14] = rot8_128(v[14]); - v[10] = add_128(v[10], v[15]); - v[11] = add_128(v[11], v[12]); - v[8] = add_128(v[8], v[13]); - v[9] = add_128(v[9], v[14]); - v[5] = xor_128(v[5], v[10]); - v[6] = xor_128(v[6], v[11]); - v[7] = xor_128(v[7], v[8]); - v[4] = xor_128(v[4], v[9]); - v[5] = rot7_128(v[5]); - v[6] = rot7_128(v[6]); - v[7] = rot7_128(v[7]); - v[4] = rot7_128(v[4]); -} - -INLINE void transpose_vecs_128(__m128i vecs[4]) { - // Interleave 32-bit lanes. The low unpack is lanes 00/11 and the high is - // 22/33. Note that this doesn't split the vector into two lanes, as the - // AVX2 counterparts do. - __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]); - __m128i ab_23 = _mm_unpackhi_epi32(vecs[0], vecs[1]); - __m128i cd_01 = _mm_unpacklo_epi32(vecs[2], vecs[3]); - __m128i cd_23 = _mm_unpackhi_epi32(vecs[2], vecs[3]); - - // Interleave 64-bit lanes. - __m128i abcd_0 = _mm_unpacklo_epi64(ab_01, cd_01); - __m128i abcd_1 = _mm_unpackhi_epi64(ab_01, cd_01); - __m128i abcd_2 = _mm_unpacklo_epi64(ab_23, cd_23); - __m128i abcd_3 = _mm_unpackhi_epi64(ab_23, cd_23); - - vecs[0] = abcd_0; - vecs[1] = abcd_1; - vecs[2] = abcd_2; - vecs[3] = abcd_3; -} - -INLINE void transpose_msg_vecs4(const uint8_t *const *inputs, - size_t block_offset, __m128i out[16]) { - out[0] = loadu_128(&inputs[0][block_offset + 0 * sizeof(__m128i)]); - out[1] = loadu_128(&inputs[1][block_offset + 0 * sizeof(__m128i)]); - out[2] = loadu_128(&inputs[2][block_offset + 0 * sizeof(__m128i)]); - out[3] = loadu_128(&inputs[3][block_offset + 0 * sizeof(__m128i)]); - out[4] = loadu_128(&inputs[0][block_offset + 1 * sizeof(__m128i)]); - out[5] = loadu_128(&inputs[1][block_offset + 1 * sizeof(__m128i)]); - out[6] = loadu_128(&inputs[2][block_offset + 1 * sizeof(__m128i)]); - out[7] = loadu_128(&inputs[3][block_offset + 1 * sizeof(__m128i)]); - out[8] = loadu_128(&inputs[0][block_offset + 2 * sizeof(__m128i)]); - out[9] = loadu_128(&inputs[1][block_offset + 2 * sizeof(__m128i)]); - out[10] = loadu_128(&inputs[2][block_offset + 2 * sizeof(__m128i)]); - out[11] = loadu_128(&inputs[3][block_offset + 2 * sizeof(__m128i)]); - out[12] = loadu_128(&inputs[0][block_offset + 3 * sizeof(__m128i)]); - out[13] = loadu_128(&inputs[1][block_offset + 3 * sizeof(__m128i)]); - out[14] = loadu_128(&inputs[2][block_offset + 3 * sizeof(__m128i)]); - out[15] = loadu_128(&inputs[3][block_offset + 3 * sizeof(__m128i)]); - for (size_t i = 0; i < 4; ++i) { - _mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0); - } - transpose_vecs_128(&out[0]); - transpose_vecs_128(&out[4]); - transpose_vecs_128(&out[8]); - transpose_vecs_128(&out[12]); -} - -INLINE void load_counters4(uint64_t counter, bool increment_counter, - __m128i *out_lo, __m128i *out_hi) { - uint64_t mask = (increment_counter ? ~0 : 0); - __m256i mask_vec = _mm256_set1_epi64x(mask); - __m256i deltas = _mm256_setr_epi64x(0, 1, 2, 3); - deltas = _mm256_and_si256(mask_vec, deltas); - __m256i counters = - _mm256_add_epi64(_mm256_set1_epi64x((int64_t)counter), deltas); - *out_lo = _mm256_cvtepi64_epi32(counters); - *out_hi = _mm256_cvtepi64_epi32(_mm256_srli_epi64(counters, 32)); -} - -static -void blake3_hash4_avx512(const uint8_t *const *inputs, size_t blocks, - const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out) { - __m128i h_vecs[8] = { - set1_128(key[0]), set1_128(key[1]), set1_128(key[2]), set1_128(key[3]), - set1_128(key[4]), set1_128(key[5]), set1_128(key[6]), set1_128(key[7]), - }; - __m128i counter_low_vec, counter_high_vec; - load_counters4(counter, increment_counter, &counter_low_vec, - &counter_high_vec); - uint8_t block_flags = flags | flags_start; - - for (size_t block = 0; block < blocks; block++) { - if (block + 1 == blocks) { - block_flags |= flags_end; - } - __m128i block_len_vec = set1_128(BLAKE3_BLOCK_LEN); - __m128i block_flags_vec = set1_128(block_flags); - __m128i msg_vecs[16]; - transpose_msg_vecs4(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs); - - __m128i v[16] = { - h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3], - h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7], - set1_128(IV[0]), set1_128(IV[1]), set1_128(IV[2]), set1_128(IV[3]), - counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec, - }; - round_fn4(v, msg_vecs, 0); - round_fn4(v, msg_vecs, 1); - round_fn4(v, msg_vecs, 2); - round_fn4(v, msg_vecs, 3); - round_fn4(v, msg_vecs, 4); - round_fn4(v, msg_vecs, 5); - round_fn4(v, msg_vecs, 6); - h_vecs[0] = xor_128(v[0], v[8]); - h_vecs[1] = xor_128(v[1], v[9]); - h_vecs[2] = xor_128(v[2], v[10]); - h_vecs[3] = xor_128(v[3], v[11]); - h_vecs[4] = xor_128(v[4], v[12]); - h_vecs[5] = xor_128(v[5], v[13]); - h_vecs[6] = xor_128(v[6], v[14]); - h_vecs[7] = xor_128(v[7], v[15]); - - block_flags = flags; - } - - transpose_vecs_128(&h_vecs[0]); - transpose_vecs_128(&h_vecs[4]); - // The first four vecs now contain the first half of each output, and the - // second four vecs contain the second half of each output. - storeu_128(h_vecs[0], &out[0 * sizeof(__m128i)]); - storeu_128(h_vecs[4], &out[1 * sizeof(__m128i)]); - storeu_128(h_vecs[1], &out[2 * sizeof(__m128i)]); - storeu_128(h_vecs[5], &out[3 * sizeof(__m128i)]); - storeu_128(h_vecs[2], &out[4 * sizeof(__m128i)]); - storeu_128(h_vecs[6], &out[5 * sizeof(__m128i)]); - storeu_128(h_vecs[3], &out[6 * sizeof(__m128i)]); - storeu_128(h_vecs[7], &out[7 * sizeof(__m128i)]); -} - -/* - * ---------------------------------------------------------------------------- - * hash8_avx512 - * ---------------------------------------------------------------------------- - */ - -INLINE void round_fn8(__m256i v[16], __m256i m[16], size_t r) { - v[0] = add_256(v[0], m[(size_t)MSG_SCHEDULE[r][0]]); - v[1] = add_256(v[1], m[(size_t)MSG_SCHEDULE[r][2]]); - v[2] = add_256(v[2], m[(size_t)MSG_SCHEDULE[r][4]]); - v[3] = add_256(v[3], m[(size_t)MSG_SCHEDULE[r][6]]); - v[0] = add_256(v[0], v[4]); - v[1] = add_256(v[1], v[5]); - v[2] = add_256(v[2], v[6]); - v[3] = add_256(v[3], v[7]); - v[12] = xor_256(v[12], v[0]); - v[13] = xor_256(v[13], v[1]); - v[14] = xor_256(v[14], v[2]); - v[15] = xor_256(v[15], v[3]); - v[12] = rot16_256(v[12]); - v[13] = rot16_256(v[13]); - v[14] = rot16_256(v[14]); - v[15] = rot16_256(v[15]); - v[8] = add_256(v[8], v[12]); - v[9] = add_256(v[9], v[13]); - v[10] = add_256(v[10], v[14]); - v[11] = add_256(v[11], v[15]); - v[4] = xor_256(v[4], v[8]); - v[5] = xor_256(v[5], v[9]); - v[6] = xor_256(v[6], v[10]); - v[7] = xor_256(v[7], v[11]); - v[4] = rot12_256(v[4]); - v[5] = rot12_256(v[5]); - v[6] = rot12_256(v[6]); - v[7] = rot12_256(v[7]); - v[0] = add_256(v[0], m[(size_t)MSG_SCHEDULE[r][1]]); - v[1] = add_256(v[1], m[(size_t)MSG_SCHEDULE[r][3]]); - v[2] = add_256(v[2], m[(size_t)MSG_SCHEDULE[r][5]]); - v[3] = add_256(v[3], m[(size_t)MSG_SCHEDULE[r][7]]); - v[0] = add_256(v[0], v[4]); - v[1] = add_256(v[1], v[5]); - v[2] = add_256(v[2], v[6]); - v[3] = add_256(v[3], v[7]); - v[12] = xor_256(v[12], v[0]); - v[13] = xor_256(v[13], v[1]); - v[14] = xor_256(v[14], v[2]); - v[15] = xor_256(v[15], v[3]); - v[12] = rot8_256(v[12]); - v[13] = rot8_256(v[13]); - v[14] = rot8_256(v[14]); - v[15] = rot8_256(v[15]); - v[8] = add_256(v[8], v[12]); - v[9] = add_256(v[9], v[13]); - v[10] = add_256(v[10], v[14]); - v[11] = add_256(v[11], v[15]); - v[4] = xor_256(v[4], v[8]); - v[5] = xor_256(v[5], v[9]); - v[6] = xor_256(v[6], v[10]); - v[7] = xor_256(v[7], v[11]); - v[4] = rot7_256(v[4]); - v[5] = rot7_256(v[5]); - v[6] = rot7_256(v[6]); - v[7] = rot7_256(v[7]); - - v[0] = add_256(v[0], m[(size_t)MSG_SCHEDULE[r][8]]); - v[1] = add_256(v[1], m[(size_t)MSG_SCHEDULE[r][10]]); - v[2] = add_256(v[2], m[(size_t)MSG_SCHEDULE[r][12]]); - v[3] = add_256(v[3], m[(size_t)MSG_SCHEDULE[r][14]]); - v[0] = add_256(v[0], v[5]); - v[1] = add_256(v[1], v[6]); - v[2] = add_256(v[2], v[7]); - v[3] = add_256(v[3], v[4]); - v[15] = xor_256(v[15], v[0]); - v[12] = xor_256(v[12], v[1]); - v[13] = xor_256(v[13], v[2]); - v[14] = xor_256(v[14], v[3]); - v[15] = rot16_256(v[15]); - v[12] = rot16_256(v[12]); - v[13] = rot16_256(v[13]); - v[14] = rot16_256(v[14]); - v[10] = add_256(v[10], v[15]); - v[11] = add_256(v[11], v[12]); - v[8] = add_256(v[8], v[13]); - v[9] = add_256(v[9], v[14]); - v[5] = xor_256(v[5], v[10]); - v[6] = xor_256(v[6], v[11]); - v[7] = xor_256(v[7], v[8]); - v[4] = xor_256(v[4], v[9]); - v[5] = rot12_256(v[5]); - v[6] = rot12_256(v[6]); - v[7] = rot12_256(v[7]); - v[4] = rot12_256(v[4]); - v[0] = add_256(v[0], m[(size_t)MSG_SCHEDULE[r][9]]); - v[1] = add_256(v[1], m[(size_t)MSG_SCHEDULE[r][11]]); - v[2] = add_256(v[2], m[(size_t)MSG_SCHEDULE[r][13]]); - v[3] = add_256(v[3], m[(size_t)MSG_SCHEDULE[r][15]]); - v[0] = add_256(v[0], v[5]); - v[1] = add_256(v[1], v[6]); - v[2] = add_256(v[2], v[7]); - v[3] = add_256(v[3], v[4]); - v[15] = xor_256(v[15], v[0]); - v[12] = xor_256(v[12], v[1]); - v[13] = xor_256(v[13], v[2]); - v[14] = xor_256(v[14], v[3]); - v[15] = rot8_256(v[15]); - v[12] = rot8_256(v[12]); - v[13] = rot8_256(v[13]); - v[14] = rot8_256(v[14]); - v[10] = add_256(v[10], v[15]); - v[11] = add_256(v[11], v[12]); - v[8] = add_256(v[8], v[13]); - v[9] = add_256(v[9], v[14]); - v[5] = xor_256(v[5], v[10]); - v[6] = xor_256(v[6], v[11]); - v[7] = xor_256(v[7], v[8]); - v[4] = xor_256(v[4], v[9]); - v[5] = rot7_256(v[5]); - v[6] = rot7_256(v[6]); - v[7] = rot7_256(v[7]); - v[4] = rot7_256(v[4]); -} - -INLINE void transpose_vecs_256(__m256i vecs[8]) { - // Interleave 32-bit lanes. The low unpack is lanes 00/11/44/55, and the high - // is 22/33/66/77. - __m256i ab_0145 = _mm256_unpacklo_epi32(vecs[0], vecs[1]); - __m256i ab_2367 = _mm256_unpackhi_epi32(vecs[0], vecs[1]); - __m256i cd_0145 = _mm256_unpacklo_epi32(vecs[2], vecs[3]); - __m256i cd_2367 = _mm256_unpackhi_epi32(vecs[2], vecs[3]); - __m256i ef_0145 = _mm256_unpacklo_epi32(vecs[4], vecs[5]); - __m256i ef_2367 = _mm256_unpackhi_epi32(vecs[4], vecs[5]); - __m256i gh_0145 = _mm256_unpacklo_epi32(vecs[6], vecs[7]); - __m256i gh_2367 = _mm256_unpackhi_epi32(vecs[6], vecs[7]); - - // Interleave 64-bit lanes. The low unpack is lanes 00/22 and the high is - // 11/33. - __m256i abcd_04 = _mm256_unpacklo_epi64(ab_0145, cd_0145); - __m256i abcd_15 = _mm256_unpackhi_epi64(ab_0145, cd_0145); - __m256i abcd_26 = _mm256_unpacklo_epi64(ab_2367, cd_2367); - __m256i abcd_37 = _mm256_unpackhi_epi64(ab_2367, cd_2367); - __m256i efgh_04 = _mm256_unpacklo_epi64(ef_0145, gh_0145); - __m256i efgh_15 = _mm256_unpackhi_epi64(ef_0145, gh_0145); - __m256i efgh_26 = _mm256_unpacklo_epi64(ef_2367, gh_2367); - __m256i efgh_37 = _mm256_unpackhi_epi64(ef_2367, gh_2367); - - // Interleave 128-bit lanes. - vecs[0] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x20); - vecs[1] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x20); - vecs[2] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x20); - vecs[3] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x20); - vecs[4] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x31); - vecs[5] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x31); - vecs[6] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x31); - vecs[7] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x31); -} - -INLINE void transpose_msg_vecs8(const uint8_t *const *inputs, - size_t block_offset, __m256i out[16]) { - out[0] = loadu_256(&inputs[0][block_offset + 0 * sizeof(__m256i)]); - out[1] = loadu_256(&inputs[1][block_offset + 0 * sizeof(__m256i)]); - out[2] = loadu_256(&inputs[2][block_offset + 0 * sizeof(__m256i)]); - out[3] = loadu_256(&inputs[3][block_offset + 0 * sizeof(__m256i)]); - out[4] = loadu_256(&inputs[4][block_offset + 0 * sizeof(__m256i)]); - out[5] = loadu_256(&inputs[5][block_offset + 0 * sizeof(__m256i)]); - out[6] = loadu_256(&inputs[6][block_offset + 0 * sizeof(__m256i)]); - out[7] = loadu_256(&inputs[7][block_offset + 0 * sizeof(__m256i)]); - out[8] = loadu_256(&inputs[0][block_offset + 1 * sizeof(__m256i)]); - out[9] = loadu_256(&inputs[1][block_offset + 1 * sizeof(__m256i)]); - out[10] = loadu_256(&inputs[2][block_offset + 1 * sizeof(__m256i)]); - out[11] = loadu_256(&inputs[3][block_offset + 1 * sizeof(__m256i)]); - out[12] = loadu_256(&inputs[4][block_offset + 1 * sizeof(__m256i)]); - out[13] = loadu_256(&inputs[5][block_offset + 1 * sizeof(__m256i)]); - out[14] = loadu_256(&inputs[6][block_offset + 1 * sizeof(__m256i)]); - out[15] = loadu_256(&inputs[7][block_offset + 1 * sizeof(__m256i)]); - for (size_t i = 0; i < 8; ++i) { - _mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0); - } - transpose_vecs_256(&out[0]); - transpose_vecs_256(&out[8]); -} - -INLINE void load_counters8(uint64_t counter, bool increment_counter, - __m256i *out_lo, __m256i *out_hi) { - uint64_t mask = (increment_counter ? ~0 : 0); - __m512i mask_vec = _mm512_set1_epi64(mask); - __m512i deltas = _mm512_setr_epi64(0, 1, 2, 3, 4, 5, 6, 7); - deltas = _mm512_and_si512(mask_vec, deltas); - __m512i counters = - _mm512_add_epi64(_mm512_set1_epi64((int64_t)counter), deltas); - *out_lo = _mm512_cvtepi64_epi32(counters); - *out_hi = _mm512_cvtepi64_epi32(_mm512_srli_epi64(counters, 32)); -} - -static -void blake3_hash8_avx512(const uint8_t *const *inputs, size_t blocks, - const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out) { - __m256i h_vecs[8] = { - set1_256(key[0]), set1_256(key[1]), set1_256(key[2]), set1_256(key[3]), - set1_256(key[4]), set1_256(key[5]), set1_256(key[6]), set1_256(key[7]), - }; - __m256i counter_low_vec, counter_high_vec; - load_counters8(counter, increment_counter, &counter_low_vec, - &counter_high_vec); - uint8_t block_flags = flags | flags_start; - - for (size_t block = 0; block < blocks; block++) { - if (block + 1 == blocks) { - block_flags |= flags_end; - } - __m256i block_len_vec = set1_256(BLAKE3_BLOCK_LEN); - __m256i block_flags_vec = set1_256(block_flags); - __m256i msg_vecs[16]; - transpose_msg_vecs8(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs); - - __m256i v[16] = { - h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3], - h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7], - set1_256(IV[0]), set1_256(IV[1]), set1_256(IV[2]), set1_256(IV[3]), - counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec, - }; - round_fn8(v, msg_vecs, 0); - round_fn8(v, msg_vecs, 1); - round_fn8(v, msg_vecs, 2); - round_fn8(v, msg_vecs, 3); - round_fn8(v, msg_vecs, 4); - round_fn8(v, msg_vecs, 5); - round_fn8(v, msg_vecs, 6); - h_vecs[0] = xor_256(v[0], v[8]); - h_vecs[1] = xor_256(v[1], v[9]); - h_vecs[2] = xor_256(v[2], v[10]); - h_vecs[3] = xor_256(v[3], v[11]); - h_vecs[4] = xor_256(v[4], v[12]); - h_vecs[5] = xor_256(v[5], v[13]); - h_vecs[6] = xor_256(v[6], v[14]); - h_vecs[7] = xor_256(v[7], v[15]); - - block_flags = flags; - } - - transpose_vecs_256(h_vecs); - storeu_256(h_vecs[0], &out[0 * sizeof(__m256i)]); - storeu_256(h_vecs[1], &out[1 * sizeof(__m256i)]); - storeu_256(h_vecs[2], &out[2 * sizeof(__m256i)]); - storeu_256(h_vecs[3], &out[3 * sizeof(__m256i)]); - storeu_256(h_vecs[4], &out[4 * sizeof(__m256i)]); - storeu_256(h_vecs[5], &out[5 * sizeof(__m256i)]); - storeu_256(h_vecs[6], &out[6 * sizeof(__m256i)]); - storeu_256(h_vecs[7], &out[7 * sizeof(__m256i)]); -} - -/* - * ---------------------------------------------------------------------------- - * hash16_avx512 - * ---------------------------------------------------------------------------- - */ - -INLINE void round_fn16(__m512i v[16], __m512i m[16], size_t r) { - v[0] = add_512(v[0], m[(size_t)MSG_SCHEDULE[r][0]]); - v[1] = add_512(v[1], m[(size_t)MSG_SCHEDULE[r][2]]); - v[2] = add_512(v[2], m[(size_t)MSG_SCHEDULE[r][4]]); - v[3] = add_512(v[3], m[(size_t)MSG_SCHEDULE[r][6]]); - v[0] = add_512(v[0], v[4]); - v[1] = add_512(v[1], v[5]); - v[2] = add_512(v[2], v[6]); - v[3] = add_512(v[3], v[7]); - v[12] = xor_512(v[12], v[0]); - v[13] = xor_512(v[13], v[1]); - v[14] = xor_512(v[14], v[2]); - v[15] = xor_512(v[15], v[3]); - v[12] = rot16_512(v[12]); - v[13] = rot16_512(v[13]); - v[14] = rot16_512(v[14]); - v[15] = rot16_512(v[15]); - v[8] = add_512(v[8], v[12]); - v[9] = add_512(v[9], v[13]); - v[10] = add_512(v[10], v[14]); - v[11] = add_512(v[11], v[15]); - v[4] = xor_512(v[4], v[8]); - v[5] = xor_512(v[5], v[9]); - v[6] = xor_512(v[6], v[10]); - v[7] = xor_512(v[7], v[11]); - v[4] = rot12_512(v[4]); - v[5] = rot12_512(v[5]); - v[6] = rot12_512(v[6]); - v[7] = rot12_512(v[7]); - v[0] = add_512(v[0], m[(size_t)MSG_SCHEDULE[r][1]]); - v[1] = add_512(v[1], m[(size_t)MSG_SCHEDULE[r][3]]); - v[2] = add_512(v[2], m[(size_t)MSG_SCHEDULE[r][5]]); - v[3] = add_512(v[3], m[(size_t)MSG_SCHEDULE[r][7]]); - v[0] = add_512(v[0], v[4]); - v[1] = add_512(v[1], v[5]); - v[2] = add_512(v[2], v[6]); - v[3] = add_512(v[3], v[7]); - v[12] = xor_512(v[12], v[0]); - v[13] = xor_512(v[13], v[1]); - v[14] = xor_512(v[14], v[2]); - v[15] = xor_512(v[15], v[3]); - v[12] = rot8_512(v[12]); - v[13] = rot8_512(v[13]); - v[14] = rot8_512(v[14]); - v[15] = rot8_512(v[15]); - v[8] = add_512(v[8], v[12]); - v[9] = add_512(v[9], v[13]); - v[10] = add_512(v[10], v[14]); - v[11] = add_512(v[11], v[15]); - v[4] = xor_512(v[4], v[8]); - v[5] = xor_512(v[5], v[9]); - v[6] = xor_512(v[6], v[10]); - v[7] = xor_512(v[7], v[11]); - v[4] = rot7_512(v[4]); - v[5] = rot7_512(v[5]); - v[6] = rot7_512(v[6]); - v[7] = rot7_512(v[7]); - - v[0] = add_512(v[0], m[(size_t)MSG_SCHEDULE[r][8]]); - v[1] = add_512(v[1], m[(size_t)MSG_SCHEDULE[r][10]]); - v[2] = add_512(v[2], m[(size_t)MSG_SCHEDULE[r][12]]); - v[3] = add_512(v[3], m[(size_t)MSG_SCHEDULE[r][14]]); - v[0] = add_512(v[0], v[5]); - v[1] = add_512(v[1], v[6]); - v[2] = add_512(v[2], v[7]); - v[3] = add_512(v[3], v[4]); - v[15] = xor_512(v[15], v[0]); - v[12] = xor_512(v[12], v[1]); - v[13] = xor_512(v[13], v[2]); - v[14] = xor_512(v[14], v[3]); - v[15] = rot16_512(v[15]); - v[12] = rot16_512(v[12]); - v[13] = rot16_512(v[13]); - v[14] = rot16_512(v[14]); - v[10] = add_512(v[10], v[15]); - v[11] = add_512(v[11], v[12]); - v[8] = add_512(v[8], v[13]); - v[9] = add_512(v[9], v[14]); - v[5] = xor_512(v[5], v[10]); - v[6] = xor_512(v[6], v[11]); - v[7] = xor_512(v[7], v[8]); - v[4] = xor_512(v[4], v[9]); - v[5] = rot12_512(v[5]); - v[6] = rot12_512(v[6]); - v[7] = rot12_512(v[7]); - v[4] = rot12_512(v[4]); - v[0] = add_512(v[0], m[(size_t)MSG_SCHEDULE[r][9]]); - v[1] = add_512(v[1], m[(size_t)MSG_SCHEDULE[r][11]]); - v[2] = add_512(v[2], m[(size_t)MSG_SCHEDULE[r][13]]); - v[3] = add_512(v[3], m[(size_t)MSG_SCHEDULE[r][15]]); - v[0] = add_512(v[0], v[5]); - v[1] = add_512(v[1], v[6]); - v[2] = add_512(v[2], v[7]); - v[3] = add_512(v[3], v[4]); - v[15] = xor_512(v[15], v[0]); - v[12] = xor_512(v[12], v[1]); - v[13] = xor_512(v[13], v[2]); - v[14] = xor_512(v[14], v[3]); - v[15] = rot8_512(v[15]); - v[12] = rot8_512(v[12]); - v[13] = rot8_512(v[13]); - v[14] = rot8_512(v[14]); - v[10] = add_512(v[10], v[15]); - v[11] = add_512(v[11], v[12]); - v[8] = add_512(v[8], v[13]); - v[9] = add_512(v[9], v[14]); - v[5] = xor_512(v[5], v[10]); - v[6] = xor_512(v[6], v[11]); - v[7] = xor_512(v[7], v[8]); - v[4] = xor_512(v[4], v[9]); - v[5] = rot7_512(v[5]); - v[6] = rot7_512(v[6]); - v[7] = rot7_512(v[7]); - v[4] = rot7_512(v[4]); -} - -// 0b10001000, or lanes a0/a2/b0/b2 in little-endian order -#define LO_IMM8 0x88 - -INLINE __m512i unpack_lo_128(__m512i a, __m512i b) { - return _mm512_shuffle_i32x4(a, b, LO_IMM8); -} - -// 0b11011101, or lanes a1/a3/b1/b3 in little-endian order -#define HI_IMM8 0xdd - -INLINE __m512i unpack_hi_128(__m512i a, __m512i b) { - return _mm512_shuffle_i32x4(a, b, HI_IMM8); -} - -INLINE void transpose_vecs_512(__m512i vecs[16]) { - // Interleave 32-bit lanes. The _0 unpack is lanes - // 0/0/1/1/4/4/5/5/8/8/9/9/12/12/13/13, and the _2 unpack is lanes - // 2/2/3/3/6/6/7/7/10/10/11/11/14/14/15/15. - __m512i ab_0 = _mm512_unpacklo_epi32(vecs[0], vecs[1]); - __m512i ab_2 = _mm512_unpackhi_epi32(vecs[0], vecs[1]); - __m512i cd_0 = _mm512_unpacklo_epi32(vecs[2], vecs[3]); - __m512i cd_2 = _mm512_unpackhi_epi32(vecs[2], vecs[3]); - __m512i ef_0 = _mm512_unpacklo_epi32(vecs[4], vecs[5]); - __m512i ef_2 = _mm512_unpackhi_epi32(vecs[4], vecs[5]); - __m512i gh_0 = _mm512_unpacklo_epi32(vecs[6], vecs[7]); - __m512i gh_2 = _mm512_unpackhi_epi32(vecs[6], vecs[7]); - __m512i ij_0 = _mm512_unpacklo_epi32(vecs[8], vecs[9]); - __m512i ij_2 = _mm512_unpackhi_epi32(vecs[8], vecs[9]); - __m512i kl_0 = _mm512_unpacklo_epi32(vecs[10], vecs[11]); - __m512i kl_2 = _mm512_unpackhi_epi32(vecs[10], vecs[11]); - __m512i mn_0 = _mm512_unpacklo_epi32(vecs[12], vecs[13]); - __m512i mn_2 = _mm512_unpackhi_epi32(vecs[12], vecs[13]); - __m512i op_0 = _mm512_unpacklo_epi32(vecs[14], vecs[15]); - __m512i op_2 = _mm512_unpackhi_epi32(vecs[14], vecs[15]); - - // Interleave 64-bit lanes. The _0 unpack is lanes - // 0/0/0/0/4/4/4/4/8/8/8/8/12/12/12/12, the _1 unpack is lanes - // 1/1/1/1/5/5/5/5/9/9/9/9/13/13/13/13, the _2 unpack is lanes - // 2/2/2/2/6/6/6/6/10/10/10/10/14/14/14/14, and the _3 unpack is lanes - // 3/3/3/3/7/7/7/7/11/11/11/11/15/15/15/15. - __m512i abcd_0 = _mm512_unpacklo_epi64(ab_0, cd_0); - __m512i abcd_1 = _mm512_unpackhi_epi64(ab_0, cd_0); - __m512i abcd_2 = _mm512_unpacklo_epi64(ab_2, cd_2); - __m512i abcd_3 = _mm512_unpackhi_epi64(ab_2, cd_2); - __m512i efgh_0 = _mm512_unpacklo_epi64(ef_0, gh_0); - __m512i efgh_1 = _mm512_unpackhi_epi64(ef_0, gh_0); - __m512i efgh_2 = _mm512_unpacklo_epi64(ef_2, gh_2); - __m512i efgh_3 = _mm512_unpackhi_epi64(ef_2, gh_2); - __m512i ijkl_0 = _mm512_unpacklo_epi64(ij_0, kl_0); - __m512i ijkl_1 = _mm512_unpackhi_epi64(ij_0, kl_0); - __m512i ijkl_2 = _mm512_unpacklo_epi64(ij_2, kl_2); - __m512i ijkl_3 = _mm512_unpackhi_epi64(ij_2, kl_2); - __m512i mnop_0 = _mm512_unpacklo_epi64(mn_0, op_0); - __m512i mnop_1 = _mm512_unpackhi_epi64(mn_0, op_0); - __m512i mnop_2 = _mm512_unpacklo_epi64(mn_2, op_2); - __m512i mnop_3 = _mm512_unpackhi_epi64(mn_2, op_2); - - // Interleave 128-bit lanes. The _0 unpack is - // 0/0/0/0/8/8/8/8/0/0/0/0/8/8/8/8, the _1 unpack is - // 1/1/1/1/9/9/9/9/1/1/1/1/9/9/9/9, and so on. - __m512i abcdefgh_0 = unpack_lo_128(abcd_0, efgh_0); - __m512i abcdefgh_1 = unpack_lo_128(abcd_1, efgh_1); - __m512i abcdefgh_2 = unpack_lo_128(abcd_2, efgh_2); - __m512i abcdefgh_3 = unpack_lo_128(abcd_3, efgh_3); - __m512i abcdefgh_4 = unpack_hi_128(abcd_0, efgh_0); - __m512i abcdefgh_5 = unpack_hi_128(abcd_1, efgh_1); - __m512i abcdefgh_6 = unpack_hi_128(abcd_2, efgh_2); - __m512i abcdefgh_7 = unpack_hi_128(abcd_3, efgh_3); - __m512i ijklmnop_0 = unpack_lo_128(ijkl_0, mnop_0); - __m512i ijklmnop_1 = unpack_lo_128(ijkl_1, mnop_1); - __m512i ijklmnop_2 = unpack_lo_128(ijkl_2, mnop_2); - __m512i ijklmnop_3 = unpack_lo_128(ijkl_3, mnop_3); - __m512i ijklmnop_4 = unpack_hi_128(ijkl_0, mnop_0); - __m512i ijklmnop_5 = unpack_hi_128(ijkl_1, mnop_1); - __m512i ijklmnop_6 = unpack_hi_128(ijkl_2, mnop_2); - __m512i ijklmnop_7 = unpack_hi_128(ijkl_3, mnop_3); - - // Interleave 128-bit lanes again for the final outputs. - vecs[0] = unpack_lo_128(abcdefgh_0, ijklmnop_0); - vecs[1] = unpack_lo_128(abcdefgh_1, ijklmnop_1); - vecs[2] = unpack_lo_128(abcdefgh_2, ijklmnop_2); - vecs[3] = unpack_lo_128(abcdefgh_3, ijklmnop_3); - vecs[4] = unpack_lo_128(abcdefgh_4, ijklmnop_4); - vecs[5] = unpack_lo_128(abcdefgh_5, ijklmnop_5); - vecs[6] = unpack_lo_128(abcdefgh_6, ijklmnop_6); - vecs[7] = unpack_lo_128(abcdefgh_7, ijklmnop_7); - vecs[8] = unpack_hi_128(abcdefgh_0, ijklmnop_0); - vecs[9] = unpack_hi_128(abcdefgh_1, ijklmnop_1); - vecs[10] = unpack_hi_128(abcdefgh_2, ijklmnop_2); - vecs[11] = unpack_hi_128(abcdefgh_3, ijklmnop_3); - vecs[12] = unpack_hi_128(abcdefgh_4, ijklmnop_4); - vecs[13] = unpack_hi_128(abcdefgh_5, ijklmnop_5); - vecs[14] = unpack_hi_128(abcdefgh_6, ijklmnop_6); - vecs[15] = unpack_hi_128(abcdefgh_7, ijklmnop_7); -} - -INLINE void transpose_msg_vecs16(const uint8_t *const *inputs, - size_t block_offset, __m512i out[16]) { - out[0] = loadu_512(&inputs[0][block_offset]); - out[1] = loadu_512(&inputs[1][block_offset]); - out[2] = loadu_512(&inputs[2][block_offset]); - out[3] = loadu_512(&inputs[3][block_offset]); - out[4] = loadu_512(&inputs[4][block_offset]); - out[5] = loadu_512(&inputs[5][block_offset]); - out[6] = loadu_512(&inputs[6][block_offset]); - out[7] = loadu_512(&inputs[7][block_offset]); - out[8] = loadu_512(&inputs[8][block_offset]); - out[9] = loadu_512(&inputs[9][block_offset]); - out[10] = loadu_512(&inputs[10][block_offset]); - out[11] = loadu_512(&inputs[11][block_offset]); - out[12] = loadu_512(&inputs[12][block_offset]); - out[13] = loadu_512(&inputs[13][block_offset]); - out[14] = loadu_512(&inputs[14][block_offset]); - out[15] = loadu_512(&inputs[15][block_offset]); - for (size_t i = 0; i < 16; ++i) { - _mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0); - } - transpose_vecs_512(out); -} - -INLINE void load_counters16(uint64_t counter, bool increment_counter, - __m512i *out_lo, __m512i *out_hi) { - const __m512i mask = _mm512_set1_epi32(-(int32_t)increment_counter); - const __m512i deltas = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); - const __m512i masked_deltas = _mm512_and_si512(deltas, mask); - const __m512i low_words = _mm512_add_epi32( - _mm512_set1_epi32((int32_t)counter), - masked_deltas); - // The carry bit is 1 if the high bit of the word was 1 before addition and is - // 0 after. - // NOTE: It would be a bit more natural to use _mm512_cmp_epu32_mask to - // compute the carry bits here, and originally we did, but that intrinsic is - // broken under GCC 5.4. See https://github.com/BLAKE3-team/BLAKE3/issues/271. - const __m512i carries = _mm512_srli_epi32( - _mm512_andnot_si512( - low_words, // 0 after (gets inverted by andnot) - _mm512_set1_epi32((int32_t)counter)), // and 1 before - 31); - const __m512i high_words = _mm512_add_epi32( - _mm512_set1_epi32((int32_t)(counter >> 32)), - carries); - *out_lo = low_words; - *out_hi = high_words; -} - -static -void blake3_hash16_avx512(const uint8_t *const *inputs, size_t blocks, - const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, - uint8_t *out) { - __m512i h_vecs[8] = { - set1_512(key[0]), set1_512(key[1]), set1_512(key[2]), set1_512(key[3]), - set1_512(key[4]), set1_512(key[5]), set1_512(key[6]), set1_512(key[7]), - }; - __m512i counter_low_vec, counter_high_vec; - load_counters16(counter, increment_counter, &counter_low_vec, - &counter_high_vec); - uint8_t block_flags = flags | flags_start; - - for (size_t block = 0; block < blocks; block++) { - if (block + 1 == blocks) { - block_flags |= flags_end; - } - __m512i block_len_vec = set1_512(BLAKE3_BLOCK_LEN); - __m512i block_flags_vec = set1_512(block_flags); - __m512i msg_vecs[16]; - transpose_msg_vecs16(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs); - - __m512i v[16] = { - h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3], - h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7], - set1_512(IV[0]), set1_512(IV[1]), set1_512(IV[2]), set1_512(IV[3]), - counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec, - }; - round_fn16(v, msg_vecs, 0); - round_fn16(v, msg_vecs, 1); - round_fn16(v, msg_vecs, 2); - round_fn16(v, msg_vecs, 3); - round_fn16(v, msg_vecs, 4); - round_fn16(v, msg_vecs, 5); - round_fn16(v, msg_vecs, 6); - h_vecs[0] = xor_512(v[0], v[8]); - h_vecs[1] = xor_512(v[1], v[9]); - h_vecs[2] = xor_512(v[2], v[10]); - h_vecs[3] = xor_512(v[3], v[11]); - h_vecs[4] = xor_512(v[4], v[12]); - h_vecs[5] = xor_512(v[5], v[13]); - h_vecs[6] = xor_512(v[6], v[14]); - h_vecs[7] = xor_512(v[7], v[15]); - - block_flags = flags; - } - - // transpose_vecs_512 operates on a 16x16 matrix of words, but we only have 8 - // state vectors. Pad the matrix with zeros. After transposition, store the - // lower half of each vector. - __m512i padded[16] = { - h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3], - h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7], - set1_512(0), set1_512(0), set1_512(0), set1_512(0), - set1_512(0), set1_512(0), set1_512(0), set1_512(0), - }; - transpose_vecs_512(padded); - _mm256_mask_storeu_epi32(&out[0 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[0])); - _mm256_mask_storeu_epi32(&out[1 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[1])); - _mm256_mask_storeu_epi32(&out[2 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[2])); - _mm256_mask_storeu_epi32(&out[3 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[3])); - _mm256_mask_storeu_epi32(&out[4 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[4])); - _mm256_mask_storeu_epi32(&out[5 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[5])); - _mm256_mask_storeu_epi32(&out[6 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[6])); - _mm256_mask_storeu_epi32(&out[7 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[7])); - _mm256_mask_storeu_epi32(&out[8 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[8])); - _mm256_mask_storeu_epi32(&out[9 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[9])); - _mm256_mask_storeu_epi32(&out[10 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[10])); - _mm256_mask_storeu_epi32(&out[11 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[11])); - _mm256_mask_storeu_epi32(&out[12 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[12])); - _mm256_mask_storeu_epi32(&out[13 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[13])); - _mm256_mask_storeu_epi32(&out[14 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[14])); - _mm256_mask_storeu_epi32(&out[15 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[15])); -} - -/* - * ---------------------------------------------------------------------------- - * hash_many_avx512 - * ---------------------------------------------------------------------------- - */ - -INLINE void hash_one_avx512(const uint8_t *input, size_t blocks, - const uint32_t key[8], uint64_t counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) { - uint32_t cv[8]; - memcpy(cv, key, BLAKE3_KEY_LEN); - uint8_t block_flags = flags | flags_start; - while (blocks > 0) { - if (blocks == 1) { - block_flags |= flags_end; - } - blake3_compress_in_place_avx512(cv, input, BLAKE3_BLOCK_LEN, counter, - block_flags); - input = &input[BLAKE3_BLOCK_LEN]; - blocks -= 1; - block_flags = flags; - } - memcpy(out, cv, BLAKE3_OUT_LEN); -} - -void blake3_hash_many_avx512(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out) { - while (num_inputs >= 16) { - blake3_hash16_avx512(inputs, blocks, key, counter, increment_counter, flags, - flags_start, flags_end, out); - if (increment_counter) { - counter += 16; - } - inputs += 16; - num_inputs -= 16; - out = &out[16 * BLAKE3_OUT_LEN]; - } - while (num_inputs >= 8) { - blake3_hash8_avx512(inputs, blocks, key, counter, increment_counter, flags, - flags_start, flags_end, out); - if (increment_counter) { - counter += 8; - } - inputs += 8; - num_inputs -= 8; - out = &out[8 * BLAKE3_OUT_LEN]; - } - while (num_inputs >= 4) { - blake3_hash4_avx512(inputs, blocks, key, counter, increment_counter, flags, - flags_start, flags_end, out); - if (increment_counter) { - counter += 4; - } - inputs += 4; - num_inputs -= 4; - out = &out[4 * BLAKE3_OUT_LEN]; - } - while (num_inputs > 0) { - hash_one_avx512(inputs[0], blocks, key, counter, flags, flags_start, - flags_end, out); - if (increment_counter) { - counter += 1; - } - inputs += 1; - num_inputs -= 1; - out = &out[BLAKE3_OUT_LEN]; - } -} diff --git a/src/third_party/blake3/c/blake3_impl.h b/src/third_party/blake3/c/blake3_impl.h deleted file mode 100644 index beab5cf5..00000000 --- a/src/third_party/blake3/c/blake3_impl.h +++ /dev/null @@ -1,285 +0,0 @@ -#ifndef BLAKE3_IMPL_H -#define BLAKE3_IMPL_H - -#include -#include -#include -#include -#include - -#include "blake3.h" - -// internal flags -enum blake3_flags { - CHUNK_START = 1 << 0, - CHUNK_END = 1 << 1, - PARENT = 1 << 2, - ROOT = 1 << 3, - KEYED_HASH = 1 << 4, - DERIVE_KEY_CONTEXT = 1 << 5, - DERIVE_KEY_MATERIAL = 1 << 6, -}; - -// This C implementation tries to support recent versions of GCC, Clang, and -// MSVC. -#if defined(_MSC_VER) -#define INLINE static __forceinline -#else -#define INLINE static inline __attribute__((always_inline)) -#endif - -#if defined(__x86_64__) || defined(_M_X64) -#define IS_X86 -#define IS_X86_64 -#endif - -#if defined(__i386__) || defined(_M_IX86) -#define IS_X86 -#define IS_X86_32 -#endif - -#if defined(__aarch64__) || defined(_M_ARM64) -#define IS_AARCH64 -#endif - -#if defined(IS_X86) -#if defined(_MSC_VER) -#include -#endif -#endif - -#if !defined(BLAKE3_USE_NEON) - // If BLAKE3_USE_NEON not manually set, autodetect based on AArch64ness - #if defined(IS_AARCH64) - #if defined(__ARM_BIG_ENDIAN) - #define BLAKE3_USE_NEON 0 - #else - #define BLAKE3_USE_NEON 1 - #endif - #else - #define BLAKE3_USE_NEON 0 - #endif -#endif - -#if defined(IS_X86) -#define MAX_SIMD_DEGREE 16 -#elif BLAKE3_USE_NEON == 1 -#define MAX_SIMD_DEGREE 4 -#else -#define MAX_SIMD_DEGREE 1 -#endif - -// There are some places where we want a static size that's equal to the -// MAX_SIMD_DEGREE, but also at least 2. -#define MAX_SIMD_DEGREE_OR_2 (MAX_SIMD_DEGREE > 2 ? MAX_SIMD_DEGREE : 2) - -static const uint32_t IV[8] = {0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, - 0xA54FF53AUL, 0x510E527FUL, 0x9B05688CUL, - 0x1F83D9ABUL, 0x5BE0CD19UL}; - -static const uint8_t MSG_SCHEDULE[7][16] = { - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - {2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8}, - {3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1}, - {10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6}, - {12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4}, - {9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7}, - {11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13}, -}; - -/* Find index of the highest set bit */ -/* x is assumed to be nonzero. */ -static unsigned int highest_one(uint64_t x) { -#if defined(__GNUC__) || defined(__clang__) - return 63 ^ (unsigned int)__builtin_clzll(x); -#elif defined(_MSC_VER) && defined(IS_X86_64) - unsigned long index; - _BitScanReverse64(&index, x); - return index; -#elif defined(_MSC_VER) && defined(IS_X86_32) - if(x >> 32) { - unsigned long index; - _BitScanReverse(&index, (unsigned long)(x >> 32)); - return 32 + index; - } else { - unsigned long index; - _BitScanReverse(&index, (unsigned long)x); - return index; - } -#else - unsigned int c = 0; - if(x & 0xffffffff00000000ULL) { x >>= 32; c += 32; } - if(x & 0x00000000ffff0000ULL) { x >>= 16; c += 16; } - if(x & 0x000000000000ff00ULL) { x >>= 8; c += 8; } - if(x & 0x00000000000000f0ULL) { x >>= 4; c += 4; } - if(x & 0x000000000000000cULL) { x >>= 2; c += 2; } - if(x & 0x0000000000000002ULL) { c += 1; } - return c; -#endif -} - -// Count the number of 1 bits. -INLINE unsigned int popcnt(uint64_t x) { -#if defined(__GNUC__) || defined(__clang__) - return (unsigned int)__builtin_popcountll(x); -#else - unsigned int count = 0; - while (x != 0) { - count += 1; - x &= x - 1; - } - return count; -#endif -} - -// Largest power of two less than or equal to x. As a special case, returns 1 -// when x is 0. -INLINE uint64_t round_down_to_power_of_2(uint64_t x) { - return 1ULL << highest_one(x | 1); -} - -INLINE uint32_t counter_low(uint64_t counter) { return (uint32_t)counter; } - -INLINE uint32_t counter_high(uint64_t counter) { - return (uint32_t)(counter >> 32); -} - -INLINE uint32_t load32(const void *src) { - const uint8_t *p = (const uint8_t *)src; - return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) | - ((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24); -} - -INLINE void load_key_words(const uint8_t key[BLAKE3_KEY_LEN], - uint32_t key_words[8]) { - key_words[0] = load32(&key[0 * 4]); - key_words[1] = load32(&key[1 * 4]); - key_words[2] = load32(&key[2 * 4]); - key_words[3] = load32(&key[3 * 4]); - key_words[4] = load32(&key[4 * 4]); - key_words[5] = load32(&key[5 * 4]); - key_words[6] = load32(&key[6 * 4]); - key_words[7] = load32(&key[7 * 4]); -} - -INLINE void store32(void *dst, uint32_t w) { - uint8_t *p = (uint8_t *)dst; - p[0] = (uint8_t)(w >> 0); - p[1] = (uint8_t)(w >> 8); - p[2] = (uint8_t)(w >> 16); - p[3] = (uint8_t)(w >> 24); -} - -INLINE void store_cv_words(uint8_t bytes_out[32], uint32_t cv_words[8]) { - store32(&bytes_out[0 * 4], cv_words[0]); - store32(&bytes_out[1 * 4], cv_words[1]); - store32(&bytes_out[2 * 4], cv_words[2]); - store32(&bytes_out[3 * 4], cv_words[3]); - store32(&bytes_out[4 * 4], cv_words[4]); - store32(&bytes_out[5 * 4], cv_words[5]); - store32(&bytes_out[6 * 4], cv_words[6]); - store32(&bytes_out[7 * 4], cv_words[7]); -} - -void blake3_compress_in_place(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags); - -void blake3_compress_xof(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, uint8_t flags, - uint8_t out[64]); - -void blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out); - -size_t blake3_simd_degree(void); - - -// Declarations for implementation-specific functions. -void blake3_compress_in_place_portable(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags); - -void blake3_compress_xof_portable(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags, uint8_t out[64]); - -void blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out); - -#if defined(IS_X86) -#if !defined(BLAKE3_NO_SSE2) -void blake3_compress_in_place_sse2(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags); -void blake3_compress_xof_sse2(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags, uint8_t out[64]); -void blake3_hash_many_sse2(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out); -#endif -#if !defined(BLAKE3_NO_SSE41) -void blake3_compress_in_place_sse41(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags); -void blake3_compress_xof_sse41(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags, uint8_t out[64]); -void blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out); -#endif -#if !defined(BLAKE3_NO_AVX2) -void blake3_hash_many_avx2(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out); -#endif -#if !defined(BLAKE3_NO_AVX512) -void blake3_compress_in_place_avx512(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags); - -void blake3_compress_xof_avx512(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags, uint8_t out[64]); - -void blake3_hash_many_avx512(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out); -#endif -#endif - -#if BLAKE3_USE_NEON == 1 -void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out); -#endif - - -#endif /* BLAKE3_IMPL_H */ diff --git a/src/third_party/blake3/c/blake3_neon.c b/src/third_party/blake3/c/blake3_neon.c deleted file mode 100644 index 8a818fc7..00000000 --- a/src/third_party/blake3/c/blake3_neon.c +++ /dev/null @@ -1,368 +0,0 @@ -#include "blake3_impl.h" - -#include - -#ifdef __ARM_BIG_ENDIAN -#error "This implementation only supports little-endian ARM." -// It might be that all we need for big-endian support here is to get the loads -// and stores right, but step zero would be finding a way to test it in CI. -#endif - -INLINE uint32x4_t loadu_128(const uint8_t src[16]) { - // vld1q_u32 has alignment requirements. Don't use it. - uint32x4_t x; - memcpy(&x, src, 16); - return x; -} - -INLINE void storeu_128(uint32x4_t src, uint8_t dest[16]) { - // vst1q_u32 has alignment requirements. Don't use it. - memcpy(dest, &src, 16); -} - -INLINE uint32x4_t add_128(uint32x4_t a, uint32x4_t b) { - return vaddq_u32(a, b); -} - -INLINE uint32x4_t xor_128(uint32x4_t a, uint32x4_t b) { - return veorq_u32(a, b); -} - -INLINE uint32x4_t set1_128(uint32_t x) { return vld1q_dup_u32(&x); } - -INLINE uint32x4_t set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { - uint32_t array[4] = {a, b, c, d}; - return vld1q_u32(array); -} - -INLINE uint32x4_t rot16_128(uint32x4_t x) { - // The straightfoward implementation would be two shifts and an or, but that's - // slower on microarchitectures we've tested. See - // https://github.com/BLAKE3-team/BLAKE3/pull/319. - // return vorrq_u32(vshrq_n_u32(x, 16), vshlq_n_u32(x, 32 - 16)); - return vreinterpretq_u32_u16(vrev32q_u16(vreinterpretq_u16_u32(x))); -} - -INLINE uint32x4_t rot12_128(uint32x4_t x) { - // See comment in rot16_128. - // return vorrq_u32(vshrq_n_u32(x, 12), vshlq_n_u32(x, 32 - 12)); - return vsriq_n_u32(vshlq_n_u32(x, 32-12), x, 12); -} - -INLINE uint32x4_t rot8_128(uint32x4_t x) { - // See comment in rot16_128. - // return vorrq_u32(vshrq_n_u32(x, 8), vshlq_n_u32(x, 32 - 8)); -#if defined(__clang__) - return vreinterpretq_u32_u8(__builtin_shufflevector(vreinterpretq_u8_u32(x), vreinterpretq_u8_u32(x), 1,2,3,0,5,6,7,4,9,10,11,8,13,14,15,12)); -#elif __GNUC__ * 10000 + __GNUC_MINOR__ * 100 >=40700 - static const uint8x16_t r8 = {1,2,3,0,5,6,7,4,9,10,11,8,13,14,15,12}; - return vreinterpretq_u32_u8(__builtin_shuffle(vreinterpretq_u8_u32(x), vreinterpretq_u8_u32(x), r8)); -#else - return vsriq_n_u32(vshlq_n_u32(x, 32-8), x, 8); -#endif -} - -INLINE uint32x4_t rot7_128(uint32x4_t x) { - // See comment in rot16_128. - // return vorrq_u32(vshrq_n_u32(x, 7), vshlq_n_u32(x, 32 - 7)); - return vsriq_n_u32(vshlq_n_u32(x, 32-7), x, 7); -} - -// TODO: compress_neon - -// TODO: hash2_neon - -/* - * ---------------------------------------------------------------------------- - * hash4_neon - * ---------------------------------------------------------------------------- - */ - -INLINE void round_fn4(uint32x4_t v[16], uint32x4_t m[16], size_t r) { - v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][0]]); - v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][2]]); - v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][4]]); - v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][6]]); - v[0] = add_128(v[0], v[4]); - v[1] = add_128(v[1], v[5]); - v[2] = add_128(v[2], v[6]); - v[3] = add_128(v[3], v[7]); - v[12] = xor_128(v[12], v[0]); - v[13] = xor_128(v[13], v[1]); - v[14] = xor_128(v[14], v[2]); - v[15] = xor_128(v[15], v[3]); - v[12] = rot16_128(v[12]); - v[13] = rot16_128(v[13]); - v[14] = rot16_128(v[14]); - v[15] = rot16_128(v[15]); - v[8] = add_128(v[8], v[12]); - v[9] = add_128(v[9], v[13]); - v[10] = add_128(v[10], v[14]); - v[11] = add_128(v[11], v[15]); - v[4] = xor_128(v[4], v[8]); - v[5] = xor_128(v[5], v[9]); - v[6] = xor_128(v[6], v[10]); - v[7] = xor_128(v[7], v[11]); - v[4] = rot12_128(v[4]); - v[5] = rot12_128(v[5]); - v[6] = rot12_128(v[6]); - v[7] = rot12_128(v[7]); - v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][1]]); - v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][3]]); - v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][5]]); - v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][7]]); - v[0] = add_128(v[0], v[4]); - v[1] = add_128(v[1], v[5]); - v[2] = add_128(v[2], v[6]); - v[3] = add_128(v[3], v[7]); - v[12] = xor_128(v[12], v[0]); - v[13] = xor_128(v[13], v[1]); - v[14] = xor_128(v[14], v[2]); - v[15] = xor_128(v[15], v[3]); - v[12] = rot8_128(v[12]); - v[13] = rot8_128(v[13]); - v[14] = rot8_128(v[14]); - v[15] = rot8_128(v[15]); - v[8] = add_128(v[8], v[12]); - v[9] = add_128(v[9], v[13]); - v[10] = add_128(v[10], v[14]); - v[11] = add_128(v[11], v[15]); - v[4] = xor_128(v[4], v[8]); - v[5] = xor_128(v[5], v[9]); - v[6] = xor_128(v[6], v[10]); - v[7] = xor_128(v[7], v[11]); - v[4] = rot7_128(v[4]); - v[5] = rot7_128(v[5]); - v[6] = rot7_128(v[6]); - v[7] = rot7_128(v[7]); - - v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][8]]); - v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][10]]); - v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][12]]); - v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][14]]); - v[0] = add_128(v[0], v[5]); - v[1] = add_128(v[1], v[6]); - v[2] = add_128(v[2], v[7]); - v[3] = add_128(v[3], v[4]); - v[15] = xor_128(v[15], v[0]); - v[12] = xor_128(v[12], v[1]); - v[13] = xor_128(v[13], v[2]); - v[14] = xor_128(v[14], v[3]); - v[15] = rot16_128(v[15]); - v[12] = rot16_128(v[12]); - v[13] = rot16_128(v[13]); - v[14] = rot16_128(v[14]); - v[10] = add_128(v[10], v[15]); - v[11] = add_128(v[11], v[12]); - v[8] = add_128(v[8], v[13]); - v[9] = add_128(v[9], v[14]); - v[5] = xor_128(v[5], v[10]); - v[6] = xor_128(v[6], v[11]); - v[7] = xor_128(v[7], v[8]); - v[4] = xor_128(v[4], v[9]); - v[5] = rot12_128(v[5]); - v[6] = rot12_128(v[6]); - v[7] = rot12_128(v[7]); - v[4] = rot12_128(v[4]); - v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][9]]); - v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][11]]); - v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][13]]); - v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][15]]); - v[0] = add_128(v[0], v[5]); - v[1] = add_128(v[1], v[6]); - v[2] = add_128(v[2], v[7]); - v[3] = add_128(v[3], v[4]); - v[15] = xor_128(v[15], v[0]); - v[12] = xor_128(v[12], v[1]); - v[13] = xor_128(v[13], v[2]); - v[14] = xor_128(v[14], v[3]); - v[15] = rot8_128(v[15]); - v[12] = rot8_128(v[12]); - v[13] = rot8_128(v[13]); - v[14] = rot8_128(v[14]); - v[10] = add_128(v[10], v[15]); - v[11] = add_128(v[11], v[12]); - v[8] = add_128(v[8], v[13]); - v[9] = add_128(v[9], v[14]); - v[5] = xor_128(v[5], v[10]); - v[6] = xor_128(v[6], v[11]); - v[7] = xor_128(v[7], v[8]); - v[4] = xor_128(v[4], v[9]); - v[5] = rot7_128(v[5]); - v[6] = rot7_128(v[6]); - v[7] = rot7_128(v[7]); - v[4] = rot7_128(v[4]); -} - -INLINE void transpose_vecs_128(uint32x4_t vecs[4]) { - // Individually transpose the four 2x2 sub-matrices in each corner. - uint32x4x2_t rows01 = vtrnq_u32(vecs[0], vecs[1]); - uint32x4x2_t rows23 = vtrnq_u32(vecs[2], vecs[3]); - - // Swap the top-right and bottom-left 2x2s (which just got transposed). - vecs[0] = - vcombine_u32(vget_low_u32(rows01.val[0]), vget_low_u32(rows23.val[0])); - vecs[1] = - vcombine_u32(vget_low_u32(rows01.val[1]), vget_low_u32(rows23.val[1])); - vecs[2] = - vcombine_u32(vget_high_u32(rows01.val[0]), vget_high_u32(rows23.val[0])); - vecs[3] = - vcombine_u32(vget_high_u32(rows01.val[1]), vget_high_u32(rows23.val[1])); -} - -INLINE void transpose_msg_vecs4(const uint8_t *const *inputs, - size_t block_offset, uint32x4_t out[16]) { - out[0] = loadu_128(&inputs[0][block_offset + 0 * sizeof(uint32x4_t)]); - out[1] = loadu_128(&inputs[1][block_offset + 0 * sizeof(uint32x4_t)]); - out[2] = loadu_128(&inputs[2][block_offset + 0 * sizeof(uint32x4_t)]); - out[3] = loadu_128(&inputs[3][block_offset + 0 * sizeof(uint32x4_t)]); - out[4] = loadu_128(&inputs[0][block_offset + 1 * sizeof(uint32x4_t)]); - out[5] = loadu_128(&inputs[1][block_offset + 1 * sizeof(uint32x4_t)]); - out[6] = loadu_128(&inputs[2][block_offset + 1 * sizeof(uint32x4_t)]); - out[7] = loadu_128(&inputs[3][block_offset + 1 * sizeof(uint32x4_t)]); - out[8] = loadu_128(&inputs[0][block_offset + 2 * sizeof(uint32x4_t)]); - out[9] = loadu_128(&inputs[1][block_offset + 2 * sizeof(uint32x4_t)]); - out[10] = loadu_128(&inputs[2][block_offset + 2 * sizeof(uint32x4_t)]); - out[11] = loadu_128(&inputs[3][block_offset + 2 * sizeof(uint32x4_t)]); - out[12] = loadu_128(&inputs[0][block_offset + 3 * sizeof(uint32x4_t)]); - out[13] = loadu_128(&inputs[1][block_offset + 3 * sizeof(uint32x4_t)]); - out[14] = loadu_128(&inputs[2][block_offset + 3 * sizeof(uint32x4_t)]); - out[15] = loadu_128(&inputs[3][block_offset + 3 * sizeof(uint32x4_t)]); - transpose_vecs_128(&out[0]); - transpose_vecs_128(&out[4]); - transpose_vecs_128(&out[8]); - transpose_vecs_128(&out[12]); -} - -INLINE void load_counters4(uint64_t counter, bool increment_counter, - uint32x4_t *out_low, uint32x4_t *out_high) { - uint64_t mask = (increment_counter ? ~0 : 0); - *out_low = set4( - counter_low(counter + (mask & 0)), counter_low(counter + (mask & 1)), - counter_low(counter + (mask & 2)), counter_low(counter + (mask & 3))); - *out_high = set4( - counter_high(counter + (mask & 0)), counter_high(counter + (mask & 1)), - counter_high(counter + (mask & 2)), counter_high(counter + (mask & 3))); -} - -void blake3_hash4_neon(const uint8_t *const *inputs, size_t blocks, - const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out) { - uint32x4_t h_vecs[8] = { - set1_128(key[0]), set1_128(key[1]), set1_128(key[2]), set1_128(key[3]), - set1_128(key[4]), set1_128(key[5]), set1_128(key[6]), set1_128(key[7]), - }; - uint32x4_t counter_low_vec, counter_high_vec; - load_counters4(counter, increment_counter, &counter_low_vec, - &counter_high_vec); - uint8_t block_flags = flags | flags_start; - - for (size_t block = 0; block < blocks; block++) { - if (block + 1 == blocks) { - block_flags |= flags_end; - } - uint32x4_t block_len_vec = set1_128(BLAKE3_BLOCK_LEN); - uint32x4_t block_flags_vec = set1_128(block_flags); - uint32x4_t msg_vecs[16]; - transpose_msg_vecs4(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs); - - uint32x4_t v[16] = { - h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3], - h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7], - set1_128(IV[0]), set1_128(IV[1]), set1_128(IV[2]), set1_128(IV[3]), - counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec, - }; - round_fn4(v, msg_vecs, 0); - round_fn4(v, msg_vecs, 1); - round_fn4(v, msg_vecs, 2); - round_fn4(v, msg_vecs, 3); - round_fn4(v, msg_vecs, 4); - round_fn4(v, msg_vecs, 5); - round_fn4(v, msg_vecs, 6); - h_vecs[0] = xor_128(v[0], v[8]); - h_vecs[1] = xor_128(v[1], v[9]); - h_vecs[2] = xor_128(v[2], v[10]); - h_vecs[3] = xor_128(v[3], v[11]); - h_vecs[4] = xor_128(v[4], v[12]); - h_vecs[5] = xor_128(v[5], v[13]); - h_vecs[6] = xor_128(v[6], v[14]); - h_vecs[7] = xor_128(v[7], v[15]); - - block_flags = flags; - } - - transpose_vecs_128(&h_vecs[0]); - transpose_vecs_128(&h_vecs[4]); - // The first four vecs now contain the first half of each output, and the - // second four vecs contain the second half of each output. - storeu_128(h_vecs[0], &out[0 * sizeof(uint32x4_t)]); - storeu_128(h_vecs[4], &out[1 * sizeof(uint32x4_t)]); - storeu_128(h_vecs[1], &out[2 * sizeof(uint32x4_t)]); - storeu_128(h_vecs[5], &out[3 * sizeof(uint32x4_t)]); - storeu_128(h_vecs[2], &out[4 * sizeof(uint32x4_t)]); - storeu_128(h_vecs[6], &out[5 * sizeof(uint32x4_t)]); - storeu_128(h_vecs[3], &out[6 * sizeof(uint32x4_t)]); - storeu_128(h_vecs[7], &out[7 * sizeof(uint32x4_t)]); -} - -/* - * ---------------------------------------------------------------------------- - * hash_many_neon - * ---------------------------------------------------------------------------- - */ - -void blake3_compress_in_place_portable(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags); - -INLINE void hash_one_neon(const uint8_t *input, size_t blocks, - const uint32_t key[8], uint64_t counter, - uint8_t flags, uint8_t flags_start, uint8_t flags_end, - uint8_t out[BLAKE3_OUT_LEN]) { - uint32_t cv[8]; - memcpy(cv, key, BLAKE3_KEY_LEN); - uint8_t block_flags = flags | flags_start; - while (blocks > 0) { - if (blocks == 1) { - block_flags |= flags_end; - } - // TODO: Implement compress_neon. However note that according to - // https://github.com/BLAKE2/BLAKE2/commit/7965d3e6e1b4193438b8d3a656787587d2579227, - // compress_neon might not be any faster than compress_portable. - blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter, - block_flags); - input = &input[BLAKE3_BLOCK_LEN]; - blocks -= 1; - block_flags = flags; - } - memcpy(out, cv, BLAKE3_OUT_LEN); -} - -void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out) { - while (num_inputs >= 4) { - blake3_hash4_neon(inputs, blocks, key, counter, increment_counter, flags, - flags_start, flags_end, out); - if (increment_counter) { - counter += 4; - } - inputs += 4; - num_inputs -= 4; - out = &out[4 * BLAKE3_OUT_LEN]; - } - while (num_inputs > 0) { - hash_one_neon(inputs[0], blocks, key, counter, flags, flags_start, - flags_end, out); - if (increment_counter) { - counter += 1; - } - inputs += 1; - num_inputs -= 1; - out = &out[BLAKE3_OUT_LEN]; - } -} diff --git a/src/third_party/blake3/c/blake3_portable.c b/src/third_party/blake3/c/blake3_portable.c deleted file mode 100644 index 062dd1b4..00000000 --- a/src/third_party/blake3/c/blake3_portable.c +++ /dev/null @@ -1,160 +0,0 @@ -#include "blake3_impl.h" -#include - -INLINE uint32_t rotr32(uint32_t w, uint32_t c) { - return (w >> c) | (w << (32 - c)); -} - -INLINE void g(uint32_t *state, size_t a, size_t b, size_t c, size_t d, - uint32_t x, uint32_t y) { - state[a] = state[a] + state[b] + x; - state[d] = rotr32(state[d] ^ state[a], 16); - state[c] = state[c] + state[d]; - state[b] = rotr32(state[b] ^ state[c], 12); - state[a] = state[a] + state[b] + y; - state[d] = rotr32(state[d] ^ state[a], 8); - state[c] = state[c] + state[d]; - state[b] = rotr32(state[b] ^ state[c], 7); -} - -INLINE void round_fn(uint32_t state[16], const uint32_t *msg, size_t round) { - // Select the message schedule based on the round. - const uint8_t *schedule = MSG_SCHEDULE[round]; - - // Mix the columns. - g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]); - g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]); - g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]); - g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]); - - // Mix the rows. - g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]); - g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]); - g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]); - g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]); -} - -INLINE void compress_pre(uint32_t state[16], const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, uint8_t flags) { - uint32_t block_words[16]; - block_words[0] = load32(block + 4 * 0); - block_words[1] = load32(block + 4 * 1); - block_words[2] = load32(block + 4 * 2); - block_words[3] = load32(block + 4 * 3); - block_words[4] = load32(block + 4 * 4); - block_words[5] = load32(block + 4 * 5); - block_words[6] = load32(block + 4 * 6); - block_words[7] = load32(block + 4 * 7); - block_words[8] = load32(block + 4 * 8); - block_words[9] = load32(block + 4 * 9); - block_words[10] = load32(block + 4 * 10); - block_words[11] = load32(block + 4 * 11); - block_words[12] = load32(block + 4 * 12); - block_words[13] = load32(block + 4 * 13); - block_words[14] = load32(block + 4 * 14); - block_words[15] = load32(block + 4 * 15); - - state[0] = cv[0]; - state[1] = cv[1]; - state[2] = cv[2]; - state[3] = cv[3]; - state[4] = cv[4]; - state[5] = cv[5]; - state[6] = cv[6]; - state[7] = cv[7]; - state[8] = IV[0]; - state[9] = IV[1]; - state[10] = IV[2]; - state[11] = IV[3]; - state[12] = counter_low(counter); - state[13] = counter_high(counter); - state[14] = (uint32_t)block_len; - state[15] = (uint32_t)flags; - - round_fn(state, &block_words[0], 0); - round_fn(state, &block_words[0], 1); - round_fn(state, &block_words[0], 2); - round_fn(state, &block_words[0], 3); - round_fn(state, &block_words[0], 4); - round_fn(state, &block_words[0], 5); - round_fn(state, &block_words[0], 6); -} - -void blake3_compress_in_place_portable(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags) { - uint32_t state[16]; - compress_pre(state, cv, block, block_len, counter, flags); - cv[0] = state[0] ^ state[8]; - cv[1] = state[1] ^ state[9]; - cv[2] = state[2] ^ state[10]; - cv[3] = state[3] ^ state[11]; - cv[4] = state[4] ^ state[12]; - cv[5] = state[5] ^ state[13]; - cv[6] = state[6] ^ state[14]; - cv[7] = state[7] ^ state[15]; -} - -void blake3_compress_xof_portable(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags, uint8_t out[64]) { - uint32_t state[16]; - compress_pre(state, cv, block, block_len, counter, flags); - - store32(&out[0 * 4], state[0] ^ state[8]); - store32(&out[1 * 4], state[1] ^ state[9]); - store32(&out[2 * 4], state[2] ^ state[10]); - store32(&out[3 * 4], state[3] ^ state[11]); - store32(&out[4 * 4], state[4] ^ state[12]); - store32(&out[5 * 4], state[5] ^ state[13]); - store32(&out[6 * 4], state[6] ^ state[14]); - store32(&out[7 * 4], state[7] ^ state[15]); - store32(&out[8 * 4], state[8] ^ cv[0]); - store32(&out[9 * 4], state[9] ^ cv[1]); - store32(&out[10 * 4], state[10] ^ cv[2]); - store32(&out[11 * 4], state[11] ^ cv[3]); - store32(&out[12 * 4], state[12] ^ cv[4]); - store32(&out[13 * 4], state[13] ^ cv[5]); - store32(&out[14 * 4], state[14] ^ cv[6]); - store32(&out[15 * 4], state[15] ^ cv[7]); -} - -INLINE void hash_one_portable(const uint8_t *input, size_t blocks, - const uint32_t key[8], uint64_t counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) { - uint32_t cv[8]; - memcpy(cv, key, BLAKE3_KEY_LEN); - uint8_t block_flags = flags | flags_start; - while (blocks > 0) { - if (blocks == 1) { - block_flags |= flags_end; - } - blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter, - block_flags); - input = &input[BLAKE3_BLOCK_LEN]; - blocks -= 1; - block_flags = flags; - } - store_cv_words(out, cv); -} - -void blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out) { - while (num_inputs > 0) { - hash_one_portable(inputs[0], blocks, key, counter, flags, flags_start, - flags_end, out); - if (increment_counter) { - counter += 1; - } - inputs += 1; - num_inputs -= 1; - out = &out[BLAKE3_OUT_LEN]; - } -} diff --git a/src/third_party/blake3/c/blake3_sse2.c b/src/third_party/blake3/c/blake3_sse2.c deleted file mode 100644 index 86bb17ab..00000000 --- a/src/third_party/blake3/c/blake3_sse2.c +++ /dev/null @@ -1,566 +0,0 @@ -#include "blake3_impl.h" - -#include - -#define DEGREE 4 - -#define _mm_shuffle_ps2(a, b, c) \ - (_mm_castps_si128( \ - _mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), (c)))) - -INLINE __m128i loadu(const uint8_t src[16]) { - return _mm_loadu_si128((const __m128i *)src); -} - -INLINE void storeu(__m128i src, uint8_t dest[16]) { - _mm_storeu_si128((__m128i *)dest, src); -} - -INLINE __m128i addv(__m128i a, __m128i b) { return _mm_add_epi32(a, b); } - -// Note that clang-format doesn't like the name "xor" for some reason. -INLINE __m128i xorv(__m128i a, __m128i b) { return _mm_xor_si128(a, b); } - -INLINE __m128i set1(uint32_t x) { return _mm_set1_epi32((int32_t)x); } - -INLINE __m128i set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { - return _mm_setr_epi32((int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d); -} - -INLINE __m128i rot16(__m128i x) { - return _mm_shufflehi_epi16(_mm_shufflelo_epi16(x, 0xB1), 0xB1); -} - -INLINE __m128i rot12(__m128i x) { - return xorv(_mm_srli_epi32(x, 12), _mm_slli_epi32(x, 32 - 12)); -} - -INLINE __m128i rot8(__m128i x) { - return xorv(_mm_srli_epi32(x, 8), _mm_slli_epi32(x, 32 - 8)); -} - -INLINE __m128i rot7(__m128i x) { - return xorv(_mm_srli_epi32(x, 7), _mm_slli_epi32(x, 32 - 7)); -} - -INLINE void g1(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3, - __m128i m) { - *row0 = addv(addv(*row0, m), *row1); - *row3 = xorv(*row3, *row0); - *row3 = rot16(*row3); - *row2 = addv(*row2, *row3); - *row1 = xorv(*row1, *row2); - *row1 = rot12(*row1); -} - -INLINE void g2(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3, - __m128i m) { - *row0 = addv(addv(*row0, m), *row1); - *row3 = xorv(*row3, *row0); - *row3 = rot8(*row3); - *row2 = addv(*row2, *row3); - *row1 = xorv(*row1, *row2); - *row1 = rot7(*row1); -} - -// Note the optimization here of leaving row1 as the unrotated row, rather than -// row0. All the message loads below are adjusted to compensate for this. See -// discussion at https://github.com/sneves/blake2-avx2/pull/4 -INLINE void diagonalize(__m128i *row0, __m128i *row2, __m128i *row3) { - *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(2, 1, 0, 3)); - *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2)); - *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(0, 3, 2, 1)); -} - -INLINE void undiagonalize(__m128i *row0, __m128i *row2, __m128i *row3) { - *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(0, 3, 2, 1)); - *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2)); - *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3)); -} - -INLINE __m128i blend_epi16(__m128i a, __m128i b, const int16_t imm8) { - const __m128i bits = _mm_set_epi16(0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01); - __m128i mask = _mm_set1_epi16(imm8); - mask = _mm_and_si128(mask, bits); - mask = _mm_cmpeq_epi16(mask, bits); - return _mm_or_si128(_mm_and_si128(mask, b), _mm_andnot_si128(mask, a)); -} - -INLINE void compress_pre(__m128i rows[4], const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, uint8_t flags) { - rows[0] = loadu((uint8_t *)&cv[0]); - rows[1] = loadu((uint8_t *)&cv[4]); - rows[2] = set4(IV[0], IV[1], IV[2], IV[3]); - rows[3] = set4(counter_low(counter), counter_high(counter), - (uint32_t)block_len, (uint32_t)flags); - - __m128i m0 = loadu(&block[sizeof(__m128i) * 0]); - __m128i m1 = loadu(&block[sizeof(__m128i) * 1]); - __m128i m2 = loadu(&block[sizeof(__m128i) * 2]); - __m128i m3 = loadu(&block[sizeof(__m128i) * 3]); - - __m128i t0, t1, t2, t3, tt; - - // Round 1. The first round permutes the message words from the original - // input order, into the groups that get mixed in parallel. - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(2, 0, 2, 0)); // 6 4 2 0 - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 3, 1)); // 7 5 3 1 - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(2, 0, 2, 0)); // 14 12 10 8 - t2 = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2, 1, 0, 3)); // 12 10 8 14 - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 1, 3, 1)); // 15 13 11 9 - t3 = _mm_shuffle_epi32(t3, _MM_SHUFFLE(2, 1, 0, 3)); // 13 11 9 15 - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 2. This round and all following rounds apply a fixed permutation - // to the message words from the round before. - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 3 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 4 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 5 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 6 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 7 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); -} - -void blake3_compress_in_place_sse2(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags) { - __m128i rows[4]; - compress_pre(rows, cv, block, block_len, counter, flags); - storeu(xorv(rows[0], rows[2]), (uint8_t *)&cv[0]); - storeu(xorv(rows[1], rows[3]), (uint8_t *)&cv[4]); -} - -void blake3_compress_xof_sse2(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags, uint8_t out[64]) { - __m128i rows[4]; - compress_pre(rows, cv, block, block_len, counter, flags); - storeu(xorv(rows[0], rows[2]), &out[0]); - storeu(xorv(rows[1], rows[3]), &out[16]); - storeu(xorv(rows[2], loadu((uint8_t *)&cv[0])), &out[32]); - storeu(xorv(rows[3], loadu((uint8_t *)&cv[4])), &out[48]); -} - -INLINE void round_fn(__m128i v[16], __m128i m[16], size_t r) { - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]); - v[0] = addv(v[0], v[4]); - v[1] = addv(v[1], v[5]); - v[2] = addv(v[2], v[6]); - v[3] = addv(v[3], v[7]); - v[12] = xorv(v[12], v[0]); - v[13] = xorv(v[13], v[1]); - v[14] = xorv(v[14], v[2]); - v[15] = xorv(v[15], v[3]); - v[12] = rot16(v[12]); - v[13] = rot16(v[13]); - v[14] = rot16(v[14]); - v[15] = rot16(v[15]); - v[8] = addv(v[8], v[12]); - v[9] = addv(v[9], v[13]); - v[10] = addv(v[10], v[14]); - v[11] = addv(v[11], v[15]); - v[4] = xorv(v[4], v[8]); - v[5] = xorv(v[5], v[9]); - v[6] = xorv(v[6], v[10]); - v[7] = xorv(v[7], v[11]); - v[4] = rot12(v[4]); - v[5] = rot12(v[5]); - v[6] = rot12(v[6]); - v[7] = rot12(v[7]); - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]); - v[0] = addv(v[0], v[4]); - v[1] = addv(v[1], v[5]); - v[2] = addv(v[2], v[6]); - v[3] = addv(v[3], v[7]); - v[12] = xorv(v[12], v[0]); - v[13] = xorv(v[13], v[1]); - v[14] = xorv(v[14], v[2]); - v[15] = xorv(v[15], v[3]); - v[12] = rot8(v[12]); - v[13] = rot8(v[13]); - v[14] = rot8(v[14]); - v[15] = rot8(v[15]); - v[8] = addv(v[8], v[12]); - v[9] = addv(v[9], v[13]); - v[10] = addv(v[10], v[14]); - v[11] = addv(v[11], v[15]); - v[4] = xorv(v[4], v[8]); - v[5] = xorv(v[5], v[9]); - v[6] = xorv(v[6], v[10]); - v[7] = xorv(v[7], v[11]); - v[4] = rot7(v[4]); - v[5] = rot7(v[5]); - v[6] = rot7(v[6]); - v[7] = rot7(v[7]); - - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]); - v[0] = addv(v[0], v[5]); - v[1] = addv(v[1], v[6]); - v[2] = addv(v[2], v[7]); - v[3] = addv(v[3], v[4]); - v[15] = xorv(v[15], v[0]); - v[12] = xorv(v[12], v[1]); - v[13] = xorv(v[13], v[2]); - v[14] = xorv(v[14], v[3]); - v[15] = rot16(v[15]); - v[12] = rot16(v[12]); - v[13] = rot16(v[13]); - v[14] = rot16(v[14]); - v[10] = addv(v[10], v[15]); - v[11] = addv(v[11], v[12]); - v[8] = addv(v[8], v[13]); - v[9] = addv(v[9], v[14]); - v[5] = xorv(v[5], v[10]); - v[6] = xorv(v[6], v[11]); - v[7] = xorv(v[7], v[8]); - v[4] = xorv(v[4], v[9]); - v[5] = rot12(v[5]); - v[6] = rot12(v[6]); - v[7] = rot12(v[7]); - v[4] = rot12(v[4]); - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]); - v[0] = addv(v[0], v[5]); - v[1] = addv(v[1], v[6]); - v[2] = addv(v[2], v[7]); - v[3] = addv(v[3], v[4]); - v[15] = xorv(v[15], v[0]); - v[12] = xorv(v[12], v[1]); - v[13] = xorv(v[13], v[2]); - v[14] = xorv(v[14], v[3]); - v[15] = rot8(v[15]); - v[12] = rot8(v[12]); - v[13] = rot8(v[13]); - v[14] = rot8(v[14]); - v[10] = addv(v[10], v[15]); - v[11] = addv(v[11], v[12]); - v[8] = addv(v[8], v[13]); - v[9] = addv(v[9], v[14]); - v[5] = xorv(v[5], v[10]); - v[6] = xorv(v[6], v[11]); - v[7] = xorv(v[7], v[8]); - v[4] = xorv(v[4], v[9]); - v[5] = rot7(v[5]); - v[6] = rot7(v[6]); - v[7] = rot7(v[7]); - v[4] = rot7(v[4]); -} - -INLINE void transpose_vecs(__m128i vecs[DEGREE]) { - // Interleave 32-bit lanes. The low unpack is lanes 00/11 and the high is - // 22/33. Note that this doesn't split the vector into two lanes, as the - // AVX2 counterparts do. - __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]); - __m128i ab_23 = _mm_unpackhi_epi32(vecs[0], vecs[1]); - __m128i cd_01 = _mm_unpacklo_epi32(vecs[2], vecs[3]); - __m128i cd_23 = _mm_unpackhi_epi32(vecs[2], vecs[3]); - - // Interleave 64-bit lanes. - __m128i abcd_0 = _mm_unpacklo_epi64(ab_01, cd_01); - __m128i abcd_1 = _mm_unpackhi_epi64(ab_01, cd_01); - __m128i abcd_2 = _mm_unpacklo_epi64(ab_23, cd_23); - __m128i abcd_3 = _mm_unpackhi_epi64(ab_23, cd_23); - - vecs[0] = abcd_0; - vecs[1] = abcd_1; - vecs[2] = abcd_2; - vecs[3] = abcd_3; -} - -INLINE void transpose_msg_vecs(const uint8_t *const *inputs, - size_t block_offset, __m128i out[16]) { - out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m128i)]); - out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m128i)]); - out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m128i)]); - out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m128i)]); - out[4] = loadu(&inputs[0][block_offset + 1 * sizeof(__m128i)]); - out[5] = loadu(&inputs[1][block_offset + 1 * sizeof(__m128i)]); - out[6] = loadu(&inputs[2][block_offset + 1 * sizeof(__m128i)]); - out[7] = loadu(&inputs[3][block_offset + 1 * sizeof(__m128i)]); - out[8] = loadu(&inputs[0][block_offset + 2 * sizeof(__m128i)]); - out[9] = loadu(&inputs[1][block_offset + 2 * sizeof(__m128i)]); - out[10] = loadu(&inputs[2][block_offset + 2 * sizeof(__m128i)]); - out[11] = loadu(&inputs[3][block_offset + 2 * sizeof(__m128i)]); - out[12] = loadu(&inputs[0][block_offset + 3 * sizeof(__m128i)]); - out[13] = loadu(&inputs[1][block_offset + 3 * sizeof(__m128i)]); - out[14] = loadu(&inputs[2][block_offset + 3 * sizeof(__m128i)]); - out[15] = loadu(&inputs[3][block_offset + 3 * sizeof(__m128i)]); - for (size_t i = 0; i < 4; ++i) { - _mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0); - } - transpose_vecs(&out[0]); - transpose_vecs(&out[4]); - transpose_vecs(&out[8]); - transpose_vecs(&out[12]); -} - -INLINE void load_counters(uint64_t counter, bool increment_counter, - __m128i *out_lo, __m128i *out_hi) { - const __m128i mask = _mm_set1_epi32(-(int32_t)increment_counter); - const __m128i add0 = _mm_set_epi32(3, 2, 1, 0); - const __m128i add1 = _mm_and_si128(mask, add0); - __m128i l = _mm_add_epi32(_mm_set1_epi32((int32_t)counter), add1); - __m128i carry = _mm_cmpgt_epi32(_mm_xor_si128(add1, _mm_set1_epi32(0x80000000)), - _mm_xor_si128( l, _mm_set1_epi32(0x80000000))); - __m128i h = _mm_sub_epi32(_mm_set1_epi32((int32_t)(counter >> 32)), carry); - *out_lo = l; - *out_hi = h; -} - -static -void blake3_hash4_sse2(const uint8_t *const *inputs, size_t blocks, - const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out) { - __m128i h_vecs[8] = { - set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]), - set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]), - }; - __m128i counter_low_vec, counter_high_vec; - load_counters(counter, increment_counter, &counter_low_vec, - &counter_high_vec); - uint8_t block_flags = flags | flags_start; - - for (size_t block = 0; block < blocks; block++) { - if (block + 1 == blocks) { - block_flags |= flags_end; - } - __m128i block_len_vec = set1(BLAKE3_BLOCK_LEN); - __m128i block_flags_vec = set1(block_flags); - __m128i msg_vecs[16]; - transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs); - - __m128i v[16] = { - h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3], - h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7], - set1(IV[0]), set1(IV[1]), set1(IV[2]), set1(IV[3]), - counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec, - }; - round_fn(v, msg_vecs, 0); - round_fn(v, msg_vecs, 1); - round_fn(v, msg_vecs, 2); - round_fn(v, msg_vecs, 3); - round_fn(v, msg_vecs, 4); - round_fn(v, msg_vecs, 5); - round_fn(v, msg_vecs, 6); - h_vecs[0] = xorv(v[0], v[8]); - h_vecs[1] = xorv(v[1], v[9]); - h_vecs[2] = xorv(v[2], v[10]); - h_vecs[3] = xorv(v[3], v[11]); - h_vecs[4] = xorv(v[4], v[12]); - h_vecs[5] = xorv(v[5], v[13]); - h_vecs[6] = xorv(v[6], v[14]); - h_vecs[7] = xorv(v[7], v[15]); - - block_flags = flags; - } - - transpose_vecs(&h_vecs[0]); - transpose_vecs(&h_vecs[4]); - // The first four vecs now contain the first half of each output, and the - // second four vecs contain the second half of each output. - storeu(h_vecs[0], &out[0 * sizeof(__m128i)]); - storeu(h_vecs[4], &out[1 * sizeof(__m128i)]); - storeu(h_vecs[1], &out[2 * sizeof(__m128i)]); - storeu(h_vecs[5], &out[3 * sizeof(__m128i)]); - storeu(h_vecs[2], &out[4 * sizeof(__m128i)]); - storeu(h_vecs[6], &out[5 * sizeof(__m128i)]); - storeu(h_vecs[3], &out[6 * sizeof(__m128i)]); - storeu(h_vecs[7], &out[7 * sizeof(__m128i)]); -} - -INLINE void hash_one_sse2(const uint8_t *input, size_t blocks, - const uint32_t key[8], uint64_t counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) { - uint32_t cv[8]; - memcpy(cv, key, BLAKE3_KEY_LEN); - uint8_t block_flags = flags | flags_start; - while (blocks > 0) { - if (blocks == 1) { - block_flags |= flags_end; - } - blake3_compress_in_place_sse2(cv, input, BLAKE3_BLOCK_LEN, counter, - block_flags); - input = &input[BLAKE3_BLOCK_LEN]; - blocks -= 1; - block_flags = flags; - } - memcpy(out, cv, BLAKE3_OUT_LEN); -} - -void blake3_hash_many_sse2(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out) { - while (num_inputs >= DEGREE) { - blake3_hash4_sse2(inputs, blocks, key, counter, increment_counter, flags, - flags_start, flags_end, out); - if (increment_counter) { - counter += DEGREE; - } - inputs += DEGREE; - num_inputs -= DEGREE; - out = &out[DEGREE * BLAKE3_OUT_LEN]; - } - while (num_inputs > 0) { - hash_one_sse2(inputs[0], blocks, key, counter, flags, flags_start, - flags_end, out); - if (increment_counter) { - counter += 1; - } - inputs += 1; - num_inputs -= 1; - out = &out[BLAKE3_OUT_LEN]; - } -} diff --git a/src/third_party/blake3/c/blake3_sse41.c b/src/third_party/blake3/c/blake3_sse41.c deleted file mode 100644 index b28ae13b..00000000 --- a/src/third_party/blake3/c/blake3_sse41.c +++ /dev/null @@ -1,560 +0,0 @@ -#include "blake3_impl.h" - -#include - -#define DEGREE 4 - -#define _mm_shuffle_ps2(a, b, c) \ - (_mm_castps_si128( \ - _mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), (c)))) - -INLINE __m128i loadu(const uint8_t src[16]) { - return _mm_loadu_si128((const __m128i *)src); -} - -INLINE void storeu(__m128i src, uint8_t dest[16]) { - _mm_storeu_si128((__m128i *)dest, src); -} - -INLINE __m128i addv(__m128i a, __m128i b) { return _mm_add_epi32(a, b); } - -// Note that clang-format doesn't like the name "xor" for some reason. -INLINE __m128i xorv(__m128i a, __m128i b) { return _mm_xor_si128(a, b); } - -INLINE __m128i set1(uint32_t x) { return _mm_set1_epi32((int32_t)x); } - -INLINE __m128i set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { - return _mm_setr_epi32((int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d); -} - -INLINE __m128i rot16(__m128i x) { - return _mm_shuffle_epi8( - x, _mm_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2)); -} - -INLINE __m128i rot12(__m128i x) { - return xorv(_mm_srli_epi32(x, 12), _mm_slli_epi32(x, 32 - 12)); -} - -INLINE __m128i rot8(__m128i x) { - return _mm_shuffle_epi8( - x, _mm_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1)); -} - -INLINE __m128i rot7(__m128i x) { - return xorv(_mm_srli_epi32(x, 7), _mm_slli_epi32(x, 32 - 7)); -} - -INLINE void g1(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3, - __m128i m) { - *row0 = addv(addv(*row0, m), *row1); - *row3 = xorv(*row3, *row0); - *row3 = rot16(*row3); - *row2 = addv(*row2, *row3); - *row1 = xorv(*row1, *row2); - *row1 = rot12(*row1); -} - -INLINE void g2(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3, - __m128i m) { - *row0 = addv(addv(*row0, m), *row1); - *row3 = xorv(*row3, *row0); - *row3 = rot8(*row3); - *row2 = addv(*row2, *row3); - *row1 = xorv(*row1, *row2); - *row1 = rot7(*row1); -} - -// Note the optimization here of leaving row1 as the unrotated row, rather than -// row0. All the message loads below are adjusted to compensate for this. See -// discussion at https://github.com/sneves/blake2-avx2/pull/4 -INLINE void diagonalize(__m128i *row0, __m128i *row2, __m128i *row3) { - *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(2, 1, 0, 3)); - *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2)); - *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(0, 3, 2, 1)); -} - -INLINE void undiagonalize(__m128i *row0, __m128i *row2, __m128i *row3) { - *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(0, 3, 2, 1)); - *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2)); - *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3)); -} - -INLINE void compress_pre(__m128i rows[4], const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, uint8_t flags) { - rows[0] = loadu((uint8_t *)&cv[0]); - rows[1] = loadu((uint8_t *)&cv[4]); - rows[2] = set4(IV[0], IV[1], IV[2], IV[3]); - rows[3] = set4(counter_low(counter), counter_high(counter), - (uint32_t)block_len, (uint32_t)flags); - - __m128i m0 = loadu(&block[sizeof(__m128i) * 0]); - __m128i m1 = loadu(&block[sizeof(__m128i) * 1]); - __m128i m2 = loadu(&block[sizeof(__m128i) * 2]); - __m128i m3 = loadu(&block[sizeof(__m128i) * 3]); - - __m128i t0, t1, t2, t3, tt; - - // Round 1. The first round permutes the message words from the original - // input order, into the groups that get mixed in parallel. - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(2, 0, 2, 0)); // 6 4 2 0 - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 3, 1)); // 7 5 3 1 - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(2, 0, 2, 0)); // 14 12 10 8 - t2 = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2, 1, 0, 3)); // 12 10 8 14 - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 1, 3, 1)); // 15 13 11 9 - t3 = _mm_shuffle_epi32(t3, _MM_SHUFFLE(2, 1, 0, 3)); // 13 11 9 15 - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 2. This round and all following rounds apply a fixed permutation - // to the message words from the round before. - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 3 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 4 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 5 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 6 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); - m0 = t0; - m1 = t1; - m2 = t2; - m3 = t3; - - // Round 7 - t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2)); - t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t0); - t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2)); - tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3)); - t1 = _mm_blend_epi16(tt, t1, 0xCC); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t1); - diagonalize(&rows[0], &rows[2], &rows[3]); - t2 = _mm_unpacklo_epi64(m3, m1); - tt = _mm_blend_epi16(t2, m2, 0xC0); - t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0)); - g1(&rows[0], &rows[1], &rows[2], &rows[3], t2); - t3 = _mm_unpackhi_epi32(m1, m3); - tt = _mm_unpacklo_epi32(m2, t3); - t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2)); - g2(&rows[0], &rows[1], &rows[2], &rows[3], t3); - undiagonalize(&rows[0], &rows[2], &rows[3]); -} - -void blake3_compress_in_place_sse41(uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags) { - __m128i rows[4]; - compress_pre(rows, cv, block, block_len, counter, flags); - storeu(xorv(rows[0], rows[2]), (uint8_t *)&cv[0]); - storeu(xorv(rows[1], rows[3]), (uint8_t *)&cv[4]); -} - -void blake3_compress_xof_sse41(const uint32_t cv[8], - const uint8_t block[BLAKE3_BLOCK_LEN], - uint8_t block_len, uint64_t counter, - uint8_t flags, uint8_t out[64]) { - __m128i rows[4]; - compress_pre(rows, cv, block, block_len, counter, flags); - storeu(xorv(rows[0], rows[2]), &out[0]); - storeu(xorv(rows[1], rows[3]), &out[16]); - storeu(xorv(rows[2], loadu((uint8_t *)&cv[0])), &out[32]); - storeu(xorv(rows[3], loadu((uint8_t *)&cv[4])), &out[48]); -} - -INLINE void round_fn(__m128i v[16], __m128i m[16], size_t r) { - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]); - v[0] = addv(v[0], v[4]); - v[1] = addv(v[1], v[5]); - v[2] = addv(v[2], v[6]); - v[3] = addv(v[3], v[7]); - v[12] = xorv(v[12], v[0]); - v[13] = xorv(v[13], v[1]); - v[14] = xorv(v[14], v[2]); - v[15] = xorv(v[15], v[3]); - v[12] = rot16(v[12]); - v[13] = rot16(v[13]); - v[14] = rot16(v[14]); - v[15] = rot16(v[15]); - v[8] = addv(v[8], v[12]); - v[9] = addv(v[9], v[13]); - v[10] = addv(v[10], v[14]); - v[11] = addv(v[11], v[15]); - v[4] = xorv(v[4], v[8]); - v[5] = xorv(v[5], v[9]); - v[6] = xorv(v[6], v[10]); - v[7] = xorv(v[7], v[11]); - v[4] = rot12(v[4]); - v[5] = rot12(v[5]); - v[6] = rot12(v[6]); - v[7] = rot12(v[7]); - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]); - v[0] = addv(v[0], v[4]); - v[1] = addv(v[1], v[5]); - v[2] = addv(v[2], v[6]); - v[3] = addv(v[3], v[7]); - v[12] = xorv(v[12], v[0]); - v[13] = xorv(v[13], v[1]); - v[14] = xorv(v[14], v[2]); - v[15] = xorv(v[15], v[3]); - v[12] = rot8(v[12]); - v[13] = rot8(v[13]); - v[14] = rot8(v[14]); - v[15] = rot8(v[15]); - v[8] = addv(v[8], v[12]); - v[9] = addv(v[9], v[13]); - v[10] = addv(v[10], v[14]); - v[11] = addv(v[11], v[15]); - v[4] = xorv(v[4], v[8]); - v[5] = xorv(v[5], v[9]); - v[6] = xorv(v[6], v[10]); - v[7] = xorv(v[7], v[11]); - v[4] = rot7(v[4]); - v[5] = rot7(v[5]); - v[6] = rot7(v[6]); - v[7] = rot7(v[7]); - - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]); - v[0] = addv(v[0], v[5]); - v[1] = addv(v[1], v[6]); - v[2] = addv(v[2], v[7]); - v[3] = addv(v[3], v[4]); - v[15] = xorv(v[15], v[0]); - v[12] = xorv(v[12], v[1]); - v[13] = xorv(v[13], v[2]); - v[14] = xorv(v[14], v[3]); - v[15] = rot16(v[15]); - v[12] = rot16(v[12]); - v[13] = rot16(v[13]); - v[14] = rot16(v[14]); - v[10] = addv(v[10], v[15]); - v[11] = addv(v[11], v[12]); - v[8] = addv(v[8], v[13]); - v[9] = addv(v[9], v[14]); - v[5] = xorv(v[5], v[10]); - v[6] = xorv(v[6], v[11]); - v[7] = xorv(v[7], v[8]); - v[4] = xorv(v[4], v[9]); - v[5] = rot12(v[5]); - v[6] = rot12(v[6]); - v[7] = rot12(v[7]); - v[4] = rot12(v[4]); - v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]); - v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]); - v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]); - v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]); - v[0] = addv(v[0], v[5]); - v[1] = addv(v[1], v[6]); - v[2] = addv(v[2], v[7]); - v[3] = addv(v[3], v[4]); - v[15] = xorv(v[15], v[0]); - v[12] = xorv(v[12], v[1]); - v[13] = xorv(v[13], v[2]); - v[14] = xorv(v[14], v[3]); - v[15] = rot8(v[15]); - v[12] = rot8(v[12]); - v[13] = rot8(v[13]); - v[14] = rot8(v[14]); - v[10] = addv(v[10], v[15]); - v[11] = addv(v[11], v[12]); - v[8] = addv(v[8], v[13]); - v[9] = addv(v[9], v[14]); - v[5] = xorv(v[5], v[10]); - v[6] = xorv(v[6], v[11]); - v[7] = xorv(v[7], v[8]); - v[4] = xorv(v[4], v[9]); - v[5] = rot7(v[5]); - v[6] = rot7(v[6]); - v[7] = rot7(v[7]); - v[4] = rot7(v[4]); -} - -INLINE void transpose_vecs(__m128i vecs[DEGREE]) { - // Interleave 32-bit lanes. The low unpack is lanes 00/11 and the high is - // 22/33. Note that this doesn't split the vector into two lanes, as the - // AVX2 counterparts do. - __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]); - __m128i ab_23 = _mm_unpackhi_epi32(vecs[0], vecs[1]); - __m128i cd_01 = _mm_unpacklo_epi32(vecs[2], vecs[3]); - __m128i cd_23 = _mm_unpackhi_epi32(vecs[2], vecs[3]); - - // Interleave 64-bit lanes. - __m128i abcd_0 = _mm_unpacklo_epi64(ab_01, cd_01); - __m128i abcd_1 = _mm_unpackhi_epi64(ab_01, cd_01); - __m128i abcd_2 = _mm_unpacklo_epi64(ab_23, cd_23); - __m128i abcd_3 = _mm_unpackhi_epi64(ab_23, cd_23); - - vecs[0] = abcd_0; - vecs[1] = abcd_1; - vecs[2] = abcd_2; - vecs[3] = abcd_3; -} - -INLINE void transpose_msg_vecs(const uint8_t *const *inputs, - size_t block_offset, __m128i out[16]) { - out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m128i)]); - out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m128i)]); - out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m128i)]); - out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m128i)]); - out[4] = loadu(&inputs[0][block_offset + 1 * sizeof(__m128i)]); - out[5] = loadu(&inputs[1][block_offset + 1 * sizeof(__m128i)]); - out[6] = loadu(&inputs[2][block_offset + 1 * sizeof(__m128i)]); - out[7] = loadu(&inputs[3][block_offset + 1 * sizeof(__m128i)]); - out[8] = loadu(&inputs[0][block_offset + 2 * sizeof(__m128i)]); - out[9] = loadu(&inputs[1][block_offset + 2 * sizeof(__m128i)]); - out[10] = loadu(&inputs[2][block_offset + 2 * sizeof(__m128i)]); - out[11] = loadu(&inputs[3][block_offset + 2 * sizeof(__m128i)]); - out[12] = loadu(&inputs[0][block_offset + 3 * sizeof(__m128i)]); - out[13] = loadu(&inputs[1][block_offset + 3 * sizeof(__m128i)]); - out[14] = loadu(&inputs[2][block_offset + 3 * sizeof(__m128i)]); - out[15] = loadu(&inputs[3][block_offset + 3 * sizeof(__m128i)]); - for (size_t i = 0; i < 4; ++i) { - _mm_prefetch((const char *)&inputs[i][block_offset + 256], _MM_HINT_T0); - } - transpose_vecs(&out[0]); - transpose_vecs(&out[4]); - transpose_vecs(&out[8]); - transpose_vecs(&out[12]); -} - -INLINE void load_counters(uint64_t counter, bool increment_counter, - __m128i *out_lo, __m128i *out_hi) { - const __m128i mask = _mm_set1_epi32(-(int32_t)increment_counter); - const __m128i add0 = _mm_set_epi32(3, 2, 1, 0); - const __m128i add1 = _mm_and_si128(mask, add0); - __m128i l = _mm_add_epi32(_mm_set1_epi32((int32_t)counter), add1); - __m128i carry = _mm_cmpgt_epi32(_mm_xor_si128(add1, _mm_set1_epi32(0x80000000)), - _mm_xor_si128( l, _mm_set1_epi32(0x80000000))); - __m128i h = _mm_sub_epi32(_mm_set1_epi32((int32_t)(counter >> 32)), carry); - *out_lo = l; - *out_hi = h; -} - -static -void blake3_hash4_sse41(const uint8_t *const *inputs, size_t blocks, - const uint32_t key[8], uint64_t counter, - bool increment_counter, uint8_t flags, - uint8_t flags_start, uint8_t flags_end, uint8_t *out) { - __m128i h_vecs[8] = { - set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]), - set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]), - }; - __m128i counter_low_vec, counter_high_vec; - load_counters(counter, increment_counter, &counter_low_vec, - &counter_high_vec); - uint8_t block_flags = flags | flags_start; - - for (size_t block = 0; block < blocks; block++) { - if (block + 1 == blocks) { - block_flags |= flags_end; - } - __m128i block_len_vec = set1(BLAKE3_BLOCK_LEN); - __m128i block_flags_vec = set1(block_flags); - __m128i msg_vecs[16]; - transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs); - - __m128i v[16] = { - h_vecs[0], h_vecs[1], h_vecs[2], h_vecs[3], - h_vecs[4], h_vecs[5], h_vecs[6], h_vecs[7], - set1(IV[0]), set1(IV[1]), set1(IV[2]), set1(IV[3]), - counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec, - }; - round_fn(v, msg_vecs, 0); - round_fn(v, msg_vecs, 1); - round_fn(v, msg_vecs, 2); - round_fn(v, msg_vecs, 3); - round_fn(v, msg_vecs, 4); - round_fn(v, msg_vecs, 5); - round_fn(v, msg_vecs, 6); - h_vecs[0] = xorv(v[0], v[8]); - h_vecs[1] = xorv(v[1], v[9]); - h_vecs[2] = xorv(v[2], v[10]); - h_vecs[3] = xorv(v[3], v[11]); - h_vecs[4] = xorv(v[4], v[12]); - h_vecs[5] = xorv(v[5], v[13]); - h_vecs[6] = xorv(v[6], v[14]); - h_vecs[7] = xorv(v[7], v[15]); - - block_flags = flags; - } - - transpose_vecs(&h_vecs[0]); - transpose_vecs(&h_vecs[4]); - // The first four vecs now contain the first half of each output, and the - // second four vecs contain the second half of each output. - storeu(h_vecs[0], &out[0 * sizeof(__m128i)]); - storeu(h_vecs[4], &out[1 * sizeof(__m128i)]); - storeu(h_vecs[1], &out[2 * sizeof(__m128i)]); - storeu(h_vecs[5], &out[3 * sizeof(__m128i)]); - storeu(h_vecs[2], &out[4 * sizeof(__m128i)]); - storeu(h_vecs[6], &out[5 * sizeof(__m128i)]); - storeu(h_vecs[3], &out[6 * sizeof(__m128i)]); - storeu(h_vecs[7], &out[7 * sizeof(__m128i)]); -} - -INLINE void hash_one_sse41(const uint8_t *input, size_t blocks, - const uint32_t key[8], uint64_t counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) { - uint32_t cv[8]; - memcpy(cv, key, BLAKE3_KEY_LEN); - uint8_t block_flags = flags | flags_start; - while (blocks > 0) { - if (blocks == 1) { - block_flags |= flags_end; - } - blake3_compress_in_place_sse41(cv, input, BLAKE3_BLOCK_LEN, counter, - block_flags); - input = &input[BLAKE3_BLOCK_LEN]; - blocks -= 1; - block_flags = flags; - } - memcpy(out, cv, BLAKE3_OUT_LEN); -} - -void blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs, - size_t blocks, const uint32_t key[8], - uint64_t counter, bool increment_counter, - uint8_t flags, uint8_t flags_start, - uint8_t flags_end, uint8_t *out) { - while (num_inputs >= DEGREE) { - blake3_hash4_sse41(inputs, blocks, key, counter, increment_counter, flags, - flags_start, flags_end, out); - if (increment_counter) { - counter += DEGREE; - } - inputs += DEGREE; - num_inputs -= DEGREE; - out = &out[DEGREE * BLAKE3_OUT_LEN]; - } - while (num_inputs > 0) { - hash_one_sse41(inputs[0], blocks, key, counter, flags, flags_start, - flags_end, out); - if (increment_counter) { - counter += 1; - } - inputs += 1; - num_inputs -= 1; - out = &out[BLAKE3_OUT_LEN]; - } -} From 99d00a5a428019a060c1543e616aa555e1952b5a Mon Sep 17 00:00:00 2001 From: Martins Mozeiko Date: Thu, 3 Sep 2026 11:18:02 -0700 Subject: [PATCH 121/251] use llvm-ml for assembler when doing clang toolchain in build.bat Actually it should llvm-ml64, but older llvm versions don't include llvm-ml64 binary. Only disadvantage over ml64.exe is that there will be no coff debug info, but it's asm code anyway, so meh... --- build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.bat b/build.bat index 6a692d18..9a8c81cd 100644 --- a/build.bat +++ b/build.bat @@ -99,7 +99,7 @@ if "%clang%"=="1" set rc=call llvm-rc if "%msvc%"=="1" set link_dll=/link /DLL if "%clang%"=="1" set link_dll=-Xlinker -DLL if "%msvc%"=="1" set asm=call ml64 -nologo -c -Zi -Fo -if "%clang%"=="1" set asm=call ml64 -nologo -c -Zi -Fo +if "%clang%"=="1" set asm=call llvm-ml -m64 -nologo -c -Fo if "%msvc%"=="1" set mklib=call lib -nologo if "%clang%"=="1" set mklib=call llvm-lib From dd125cc04ec50a204f45fa6b12a04c91aaa19e0f Mon Sep 17 00:00:00 2001 From: Martins Mozeiko Date: Thu, 3 Sep 2026 11:35:54 -0700 Subject: [PATCH 122/251] Casting between incompatible function pointers is UB --- src/torture/torture.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/torture/torture.c b/src/torture/torture.c index b85f2660..bd33a8f7 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -949,8 +949,10 @@ t_match_linef(String8 *output, char *fmt, ...) } force_inline int -t_test_info_is_before(TestInfo **a, TestInfo **b) +t_test_info_is_before(void *a_, void *b_) { + TestInfo **a = a_; + TestInfo **b = b_; String8 layer_a = a[0]->layer; String8 layer_b = b[0]->layer; int cmp = str8_compar(layer_a, layer_b, 0); @@ -1135,7 +1137,7 @@ t_entry_point(CmdLine *cmdline) // Gather tests { for EachIndex(i, test_infos_count) { g_sorted_test_infos[i] = &test_infos[i]; } - radsort(g_sorted_test_infos, test_infos_count, (int (*)(void *, void *))t_test_info_is_before); + radsort(g_sorted_test_infos, test_infos_count, t_test_info_is_before); } // From 959a54a45a889bd0e644692d315b0e5954212f0c Mon Sep 17 00:00:00 2001 From: Martins Mozeiko Date: Thu, 3 Sep 2026 11:39:16 -0700 Subject: [PATCH 123/251] github actions improvements * add clang-17 builds on ubuntu * add vs2026 builds on windows * temporarily disable "run" jobs * better naming for matrix jobs * use explicit debug/release build steps, not part of job matrix --- .github/workflows/builds.yml | 101 ++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 20 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index f666f2fc..0ad9c629 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -12,7 +12,7 @@ env: TORTURE_FLAGS: "--gen_crash_dump" jobs: - build-ubuntu-24-04: + build-ubuntu-24: runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -24,31 +24,54 @@ jobs: - radbin - torture compiler: + - clang-17 - clang-22 # TODO: - gcc - mode: - - debug - - release + + name: ${{ matrix.target }}-${{ matrix.compiler }}-ubuntu24 + steps: - name: Checkout uses: actions/checkout@v6 with: lfs: true - - name: Install Dependencies + + - name: Install Common Dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends -y pkg-config libfreetype6-dev libx11-dev libxext-dev libgl1-mesa-dev libegl1-mesa-dev + + - name: Install clang-17 Dependencies + if: ${{ matrix.compiler == 'clang-17' }} + shell: bash + run: | + sudo apt-get install --no-install-recommends -y clang-17 lld-17 llvm-17 + sudo ln -sf /usr/bin/llvm-symbolizer-17 /usr/bin/llvm-symbolizer + echo CC="${{ matrix.compiler }}" >> "${GITHUB_ENV}" + echo AR=llvm-ar-17 >> "${GITHUB_ENV}" + + - name: Install ${{ matrix.compiler }} Dependencies + if: ${{ matrix.compiler == 'clang-22' }} shell: bash run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-add-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-22 main" sudo apt-get update - sudo apt-get install -y clang-22 lld-22 llvm-22 gcc-14 pkg-config libfreetype6-dev libx11-dev libxext-dev libgl1-mesa-dev libegl1-mesa-dev - sudo ln -s /usr/bin/llvm-symbolizer-22 /usr/bin/llvm-symbolizer - - name: build - shell: bash - run: | - [[ "${{ matrix.compiler }}" == "clang-22" ]] && export AR=llvm-ar-22 - CC="${{ matrix.compiler }}" ./build.sh "${{ matrix.target }}" "${{ matrix.mode }}" + sudo apt-get install -y clang-22 lld-22 llvm-22 + sudo ln -sf /usr/bin/llvm-symbolizer-22 /usr/bin/llvm-symbolizer + echo CC="${{ matrix.compiler }}" >> "${GITHUB_ENV}" + echo AR=llvm-ar-22 >> "${GITHUB_ENV}" - build-windows-2022: + - name: Build (debug) + shell: bash + run: ./build.sh "${{ matrix.target }}" debug + + - name: Build (release) + shell: bash + run: ./build.sh "${{ matrix.target }}" release + + build-windows-vs2022: runs-on: windows-2022 strategy: fail-fast: false @@ -61,23 +84,60 @@ jobs: compiler: - msvc - clang - mode: - - debug - - release + + name: ${{ matrix.target }}-${{ matrix.compiler }}-vs2022 + steps: - name: Checkout uses: actions/checkout@v6 with: lfs: true - - name: build (vs 2022) + - name: Build (debug) shell: cmd run: | - for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VS_INSTALL=%%i" - call "%VS_INSTALL%\VC\Auxiliary\Build\vcvarsall.bat" x64 - call build meta "${{ matrix.target }}" "${{ matrix.compiler }}" "${{ matrix.mode }}" || exit /b 1 + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 || exit /b 1 + call build meta "${{ matrix.target }}" "${{ matrix.compiler }}" debug || exit /b 1 + - name: Build (debug) + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 || exit /b 1 + call build meta "${{ matrix.target }}" "${{ matrix.compiler }}" release || exit /b 1 + + build-windows-vs2026: + runs-on: windows-2025 + strategy: + fail-fast: false + matrix: + target: + - raddbg + - radlink + - radbin + - mule_main + compiler: + - msvc + - clang + + name: ${{ matrix.target }}-${{ matrix.compiler }}-vs2026 + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + lfs: true + - name: Build (debug) + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 || exit /b 1 + call build meta "${{ matrix.target }}" "${{ matrix.compiler }}" debug || exit /b 1 + - name: Build (release) + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 || exit /b 1 + call build meta "${{ matrix.target }}" "${{ matrix.compiler }}" release || exit /b 1 run-ubuntu-24-04: runs-on: ubuntu-24.04 + if: false strategy: fail-fast: false matrix: @@ -120,6 +180,7 @@ jobs: run-windows-2022: runs-on: windows-2022 + if: false strategy: fail-fast: false matrix: From b2c4dce4b01e5add38e2d5b2e979fde26c99922c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 10 Sep 2026 09:39:13 -0700 Subject: [PATCH 124/251] fix duplicative RDI serialization in breakpad output --- src/radbin/radbin.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/radbin/radbin.c b/src/radbin/radbin.c index 43d735f2..09beb13a 100644 --- a/src/radbin/radbin.c +++ b/src/radbin/radbin.c @@ -1035,11 +1035,17 @@ rb_thread_entry_point(void *p) case OutputKind_Breakpad: { //- rjf: flatten to RDI data - String8List rdi_blobs = rdim_file_blobs_from_section_bundle(arena, serialized_section_bundle); - String8 rdi_data = str8_list_join(arena, &rdi_blobs, 0); - RDI_Parsed rdi_ = {0}; - RDI_Parsed *rdi = &rdi_; - RDI_ParseStatus rdi_status = rdi_parse(rdi_data.str, rdi_data.size, rdi); + ProfBegin("flatten to RDI data"); + RDI_Parsed *rdi = 0; + if(lane_idx() == 0) + { + String8List rdi_blobs = rdim_file_blobs_from_section_bundle(arena, serialized_section_bundle); + String8 rdi_data = str8_list_join(arena, &rdi_blobs, 0); + rdi = push_array(arena, RDI_Parsed, 1); + rdi_parse(rdi_data.str, rdi_data.size, rdi); + } + lane_sync_u64(&rdi, 0); + ProfEnd(); //- rjf: set up shared state typedef struct P2B_Shared P2B_Shared; From c44e4a388e2559bfdcbce2c09ee67ce81e45e775 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 10 Sep 2026 14:27:53 -0700 Subject: [PATCH 125/251] rdi_make: ensure terminator line records are sorted *before* records with actual line info, since these are always ending sequences - we always want a line record with an actual file/line info to come *after* terminators. fixes missing line info --- src/lib_rdi_make/rdi_make.c | 3 ++- src/radbin/radbin.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib_rdi_make/rdi_make.c b/src/lib_rdi_make/rdi_make.c index fe187d22..8b634ea3 100644 --- a/src/lib_rdi_make/rdi_make.c +++ b/src/lib_rdi_make/rdi_make.c @@ -442,7 +442,8 @@ rdim_sort_key_array(RDIM_Arena *arena, RDIM_SortKey *keys, RDI_U64 count) RDI_U64 j2_opl = range2->opl; for(;;) { - if(src[j1].key <= src[j2].key) + if(src[j1].key < src[j2].key || + (src[j1].key == src[j2].key && src[j1].val == 0 && src[j2].val != 0)) { rdim_memcpy(dst + jd, src + j1, sizeof(*src)); j1 += 1; diff --git a/src/radbin/radbin.c b/src/radbin/radbin.c index 09beb13a..bd941178 100644 --- a/src/radbin/radbin.c +++ b/src/radbin/radbin.c @@ -1143,7 +1143,6 @@ rb_thread_entry_point(void *p) RDI_Scope *root_scope = rdi_element_from_name_idx(rdi, Scopes, proc->root_scope_idx); if(root_scope->voff_range_opl > root_scope->voff_range_first) { - // rjf: dump function record RDIM_Rng1U64 voff_range = { @@ -1152,7 +1151,8 @@ rb_thread_entry_point(void *p) }; { Temp scratch = scratch_begin(0, 0); - str8_list_pushf(arena, out, "FUNC %I64x %I64x %I64x %S\n", voff_range.min, voff_range.max-voff_range.min, 0ull, fully_qualified_str8_from_rdi_symbol(scratch.arena, rdi, proc)); + String8 name = fully_qualified_str8_from_rdi_symbol(scratch.arena, rdi, proc); + str8_list_pushf(arena, out, "FUNC %I64x %I64x %I64x %S\n", voff_range.min, voff_range.max-voff_range.min, 0ull, name); scratch_end(scratch); } From 77bacd640af62933a3cab61f0e8ae2902aefd9f0 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 26 Aug 2026 01:40:50 -0700 Subject: [PATCH 126/251] process groups + pipe pair --- src/base/base_files.h | 12 +++ src/base/base_processes.h | 15 ++++ src/linux/base/linux_base.c | 153 +++++++++++++++++++++++++++++++++++- src/linux/base/linux_base.h | 2 + src/win32/base/win32_base.c | 150 ++++++++++++++++++++++++++++++++++- 5 files changed, 328 insertions(+), 4 deletions(-) diff --git a/src/base/base_files.h b/src/base/base_files.h index 4ead7246..b06216c1 100644 --- a/src/base/base_files.h +++ b/src/base/base_files.h @@ -40,6 +40,13 @@ struct File U64 u64[1]; }; +typedef struct FilePair FilePair; +struct FilePair +{ + File read; + File write; +}; + typedef struct FileMap FileMap; struct FileMap { @@ -70,6 +77,11 @@ internal String8 file_read_cstring(Arena *arena, File file, U64 off); //- rjf: files internal File file_open(AccessFlags flags, String8 path); internal void file_close(File file); +internal FilePair file_pipe_make(B32 read_inherited, B32 write_inherited); +internal U64 file_pipe_read(File file, void *out_data, U64 size); +internal U64 file_pipe_write(File file, void *data, U64 size); +internal U64 file_pipe_bytes_available(File file); +internal B32 file_pipe_is_end(File file); internal U64 file_read(File file, Rng1U64 rng, void *out_data); #define file_read_struct(f, off, ptr) file_read((f), r1u64((off), (off)+sizeof(*(ptr))), (ptr)) internal U64 file_write(File file, Rng1U64 rng, void *data); diff --git a/src/base/base_processes.h b/src/base/base_processes.h index ce50a222..38b0a6b8 100644 --- a/src/base/base_processes.h +++ b/src/base/base_processes.h @@ -25,6 +25,12 @@ struct Process U64 u64[1]; }; +typedef struct ProcessGroup ProcessGroup; +struct ProcessGroup +{ + U64 u64[1]; +}; + typedef struct ProcessNode ProcessNode; struct ProcessNode { @@ -49,6 +55,8 @@ struct ProcessLaunchParams B32 inherit_env; B32 debug_subprocesses; B32 consoleless; + B32 new_console; + ProcessGroup process_group; File stdout_file; File stderr_file; File stdin_file; @@ -84,8 +92,15 @@ internal U32 get_process_start_time_unix(void); internal Process process_launch(ProcessLaunchParams *params); internal U64 pid_from_process(Process process); +internal B32 process_poll(Process process, U64 *exit_code_out); +internal B32 process_is_active(Process process); internal B32 process_join(Process process, U64 endt_us, U64 *exit_code_out); internal void process_detach(Process process); internal B32 process_kill(Process process); +internal B32 process_send_ctrl_c(Process process); + +internal ProcessGroup process_group_make(B32 kill_on_close); +internal B32 process_group_add(ProcessGroup group, Process process); +internal void process_group_close(ProcessGroup group); #endif // BASE_PROCESSES_H diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index 88999e5e..3daa94f7 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -833,6 +833,51 @@ file_close(File file) close(fd); } +internal FilePair +file_pipe_make(B32 read_inherited, B32 write_inherited) +{ + FilePair result = {0}; + int fds[2] = {0}; + if(pipe(fds) == 0) + { + if(!read_inherited) { fcntl(fds[0], F_SETFD, FD_CLOEXEC); } + if(!write_inherited) { fcntl(fds[1], F_SETFD, FD_CLOEXEC); } + result.read.u64[0] = fds[0]; + result.write.u64[0] = fds[1]; + } + return result; +} + +internal U64 +file_pipe_read(File file, void *out_data, U64 size) +{ + ssize_t read_size = LNX_RETRY_ON_EINTR(read((int)file.u64[0], out_data, size)); + return read_size > 0 ? (U64)read_size : 0; +} + +internal U64 +file_pipe_write(File file, void *data, U64 size) +{ + ssize_t write_size = LNX_RETRY_ON_EINTR(write((int)file.u64[0], data, size)); + return write_size > 0 ? (U64)write_size : 0; +} + +internal U64 +file_pipe_bytes_available(File file) +{ + int size = 0; + B32 is_ok = !file_match(file, file_zero()) && ioctl((int)file.u64[0], FIONREAD, &size) == 0; + return is_ok && size > 0 ? (U64)size : 0; +} + +internal B32 +file_pipe_is_end(File file) +{ + struct pollfd poll_fd = { .fd = (int)file.u64[0], .events = POLLIN|POLLHUP }; + poll(&poll_fd, 1, 0); + return !!(poll_fd.revents & (POLLHUP|POLLERR|POLLNVAL)); +} + internal U64 file_read(File file, Rng1U64 rng, void *out_data) { @@ -1248,13 +1293,43 @@ process_launch(ProcessLaunchParams *params) int chdir_code = posix_spawn_file_actions_addchdir_np(&file_actions, (char *)push_cstr(scratch.arena, params->path).str); Assert(chdir_code == 0); } - int stdout_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stdout_file.u64[0], STDOUT_FILENO); - int stderr_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stderr_file.u64[0], STDERR_FILENO); - int stdin_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stdin_file.u64[0], STDIN_FILENO); + if(!file_match(params->stdout_file, file_zero())) + { + int stdout_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stdout_file.u64[0], STDOUT_FILENO); + Assert(stdout_code == 0); + } + if(!file_match(params->stderr_file, file_zero())) + { + int stderr_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stderr_file.u64[0], STDERR_FILENO); + Assert(stderr_code == 0); + } + if(!file_match(params->stdin_file, file_zero())) + { + int stdin_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stdin_file.u64[0], STDIN_FILENO); + Assert(stdin_code == 0); + } + File std_files[] = { params->stdout_file, params->stderr_file, params->stdin_file }; + for EachIndex(i, ArrayCount(std_files)) + { + int fd = (int)std_files[i].u64[0]; + B32 is_unique = !file_match(std_files[i], file_zero()); + for EachIndex(j, i) { is_unique &= !file_match(std_files[i], std_files[j]); } + if(is_unique && fd > STDERR_FILENO) + { + int close_code = posix_spawn_file_actions_addclose(&file_actions, fd); + Assert(close_code == 0); + } + } posix_spawnattr_t attr = {0}; int attr_init_code = posix_spawnattr_init(&attr); if(attr_init_code == 0) { + if(params->new_console || params->process_group.u64[0] != 0) + { + posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP); + posix_spawnattr_setpgroup(&attr, 0); + } + // package argv char **argv = push_array(scratch.arena, char *, params->cmd_line.node_count + 1); { @@ -1290,6 +1365,12 @@ process_launch(ProcessLaunchParams *params) if(spawn_code == 0) { handle.u64[0] = (U64)pid; + if(params->process_group.u64[0] != 0 && !process_group_add(params->process_group, handle)) + { + kill(pid, SIGKILL); + waitpid(pid, 0, 0); + MemoryZeroStruct(&handle); + } } // clean up attributes @@ -1310,6 +1391,27 @@ pid_from_process(Process process) return result; } +internal B32 +process_poll(Process process, U64 *exit_code_out) +{ + if(process_match(process, process_zero())) { return 0; } + siginfo_t info = {0}; + B32 result = waitid(P_PID, (pid_t)process.u64[0], &info, WEXITED|WNOHANG|WNOWAIT) == 0 && info.si_pid != 0; + if(result && exit_code_out != 0) + { + *exit_code_out = info.si_code == CLD_EXITED ? (U64)info.si_status : (U64)info.si_status + 128; + } + return result; +} + +internal B32 +process_is_active(Process process) +{ + B32 result = !process_match(process, process_zero()) && + !process_poll(process, 0) && kill((pid_t)process.u64[0], 0) == 0; + return result; +} + internal B32 process_join(Process process, U64 endt_us, U64 *exit_code_out) { @@ -1359,6 +1461,51 @@ process_kill(Process process) return is_killed; } +internal B32 +process_send_ctrl_c(Process process) +{ + B32 result = !process_match(process, process_zero()) && kill((pid_t)process.u64[0], SIGINT) == 0; + return result; +} + +typedef struct LNX_ProcessGroup LNX_ProcessGroup; +struct LNX_ProcessGroup +{ + pid_t pid; + B32 kill_on_close; +}; + +internal ProcessGroup +process_group_make(B32 kill_on_close) +{ + LNX_ProcessGroup *group = malloc(sizeof(*group)); + if(group != 0) { *group = (LNX_ProcessGroup){ .kill_on_close = kill_on_close }; } + return (ProcessGroup){ .u64[0] = (U64)group }; +} + +internal B32 +process_group_add(ProcessGroup group, Process process) +{ + LNX_ProcessGroup *lnx_group = (LNX_ProcessGroup *)group.u64[0]; + if(lnx_group == 0 || lnx_group->pid != 0 || process_match(process, process_zero())) { return 0; } + lnx_group->pid = (pid_t)process.u64[0]; + return 1; +} + +internal void +process_group_close(ProcessGroup group) +{ + LNX_ProcessGroup *lnx_group = (LNX_ProcessGroup *)group.u64[0]; + if(lnx_group != 0) + { + if(lnx_group->kill_on_close && lnx_group->pid != 0) + { + kill(-lnx_group->pid, SIGKILL); + } + free(lnx_group); + } +} + //////////////////////////////// //~ rjf: @per_os_impl Dynamically-Loaded Libraries diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index 9b3be274..209c6080 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -15,11 +15,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include diff --git a/src/win32/base/win32_base.c b/src/win32/base/win32_base.c index 296373d0..a67c204c 100644 --- a/src/win32/base/win32_base.c +++ b/src/win32/base/win32_base.c @@ -805,6 +805,62 @@ file_close(File file) (void)result; } +internal FilePair +file_pipe_make(B32 read_inherited, B32 write_inherited) +{ + FilePair result = {0}; + SECURITY_ATTRIBUTES attributes = { .nLength = sizeof(attributes), .bInheritHandle = TRUE }; + HANDLE read = 0; + HANDLE write = 0; + if(CreatePipe(&read, &write, &attributes, 0) && + (read_inherited || SetHandleInformation(read, HANDLE_FLAG_INHERIT, 0)) && + (write_inherited || SetHandleInformation(write, HANDLE_FLAG_INHERIT, 0))) + { + result.read.u64[0] = (U64)read; + result.write.u64[0] = (U64)write; + } + else + { + if(read != 0) { CloseHandle(read); } + if(write != 0) { CloseHandle(write); } + } + return result; +} + +internal U64 +file_pipe_read(File file, void *out_data, U64 size) +{ + DWORD read = 0; + B32 is_ok = !file_match(file, file_zero()) && + ReadFile((HANDLE)file.u64[0], out_data, safe_cast_u32(size), &read, 0); + return is_ok ? read : 0; +} + +internal U64 +file_pipe_write(File file, void *data, U64 size) +{ + DWORD written = 0; + B32 is_ok = !file_match(file, file_zero()) && + WriteFile((HANDLE)file.u64[0], data, safe_cast_u32(size), &written, 0); + return is_ok ? written : 0; +} + +internal U64 +file_pipe_bytes_available(File file) +{ + DWORD available = 0; + B32 is_ok = !file_match(file, file_zero()) && + PeekNamedPipe((HANDLE)file.u64[0], 0, 0, 0, &available, 0); + return is_ok ? available : 0; +} + +internal B32 +file_pipe_is_end(File file) +{ + DWORD available = 0; + return !PeekNamedPipe((HANDLE)file.u64[0], 0, 0, 0, &available, 0); +} + internal U64 file_read(File file, Rng1U64 rng, void *out_data) { @@ -1391,6 +1447,14 @@ process_launch(ProcessLaunchParams *params) { creation_flags |= CREATE_NO_WINDOW; } + if(params->new_console) + { + creation_flags |= CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP; + } + if(params->process_group.u64[0] != 0) + { + creation_flags |= CREATE_SUSPENDED; + } //- rjf: launch BOOL inherit_handles = 0; @@ -1419,7 +1483,19 @@ process_launch(ProcessLaunchParams *params) PROCESS_INFORMATION process_info = {0}; if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, inherit_handles, creation_flags, use_null_env_arg ? 0 : (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) { - result.u64[0] = (U64)process_info.hProcess; + B32 group_is_ok = params->process_group.u64[0] == 0 || + AssignProcessToJobObject((HANDLE)params->process_group.u64[0], process_info.hProcess); + B32 resume_is_ok = params->process_group.u64[0] == 0 || ResumeThread(process_info.hThread) != (DWORD)-1; + if(group_is_ok && resume_is_ok) + { + result.u64[0] = (U64)process_info.hProcess; + } + else + { + TerminateProcess(process_info.hProcess, 999); + WaitForSingleObject(process_info.hProcess, INFINITE); + CloseHandle(process_info.hProcess); + } CloseHandle(process_info.hThread); } @@ -1435,6 +1511,25 @@ pid_from_process(Process process) return result; } +internal B32 +process_poll(Process process, U64 *exit_code_out) +{ + if(process_match(process, process_zero()) || WaitForSingleObject((HANDLE)process.u64[0], 0) != WAIT_OBJECT_0) + { + return 0; + } + DWORD exit_code = 0; + B32 result = GetExitCodeProcess((HANDLE)process.u64[0], &exit_code); + if(result && exit_code_out != 0) { *exit_code_out = exit_code; } + return result; +} + +internal B32 +process_is_active(Process process) +{ + return !process_match(process, process_zero()) && !process_poll(process, 0); +} + internal B32 process_join(Process process, U64 endt_us, U64 *exit_code_out) { @@ -1472,6 +1567,59 @@ process_kill(Process process) return was_terminated; } +internal B32 +process_send_ctrl_c(Process process) +{ + B32 result = 0; + if(process_match(process, process_zero())) { return 0; } + B32 had_console = GetConsoleCP() != 0; + if(had_console && !FreeConsole()) { return 0; } + if(AttachConsole(safe_cast_u32(pid_from_process(process)))) { + SetConsoleCtrlHandler(0, TRUE); + result = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) != 0; + if(result) { Sleep(10); } + } + FreeConsole(); + if(had_console) { AttachConsole(ATTACH_PARENT_PROCESS); } + SetConsoleCtrlHandler(0, FALSE); + return result; +} + +internal ProcessGroup +process_group_make(B32 kill_on_close) +{ + ProcessGroup result = {0}; + HANDLE handle = CreateJobObjectW(0, 0); + if(handle != 0) + { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {0}; + if(kill_on_close) { limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; } + if(SetInformationJobObject(handle, JobObjectExtendedLimitInformation, &limits, sizeof(limits))) + { + result.u64[0] = (U64)handle; + } + else + { + CloseHandle(handle); + } + } + return result; +} + +internal B32 +process_group_add(ProcessGroup group, Process process) +{ + B32 result = group.u64[0] != 0 && !process_match(process, process_zero()) && + AssignProcessToJobObject((HANDLE)group.u64[0], (HANDLE)process.u64[0]); + return result; +} + +internal void +process_group_close(ProcessGroup group) +{ + if(group.u64[0] != 0) { CloseHandle((HANDLE)group.u64[0]); } +} + //////////////////////////////// //~ rjf: @per_os_impl Dynamically-Loaded Libraries From b8bbd520a169b6f83b194afeda44ac62568cb7ae Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 26 Aug 2026 11:40:18 -0700 Subject: [PATCH 127/251] add test user data field for smuggling script state --- src/base/base_test.c | 19 +++++++++++++------ src/base/base_test.h | 3 +++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/base/base_test.c b/src/base/base_test.c index 149352fc..f55c9d73 100644 --- a/src/base/base_test.c +++ b/src/base/base_test.c @@ -32,16 +32,23 @@ test_exemplar_path(Arena *arena, TestCtx *ctx, String8 name) return path; } +internal String8 +test_layer_from_file_path(String8 file_path) +{ + U64 src_min = str8_find_needle(file_path, 0, s("src/"), StringMatchFlag_SlashInsensitive); + if (src_min == file_path.size) { return str8_zero(); } + U64 src_max = src_min + str8_lit("src/").size; + U64 layer_slash_max = str8_find_needle(file_path, src_max, s("/"), StringMatchFlag_SlashInsensitive); + if (layer_slash_max == file_path.size) { return str8_zero(); } + return str8_substr(file_path, r1u64(src_max, layer_slash_max)); +} + internal void base_register_test(char *func_name_cstr, TestFunctionType *fn, char *file_path_cstr, int line, int skip) { - String8 file_path = str8_cstring(file_path_cstr); - U64 src_min = str8_find_needle(file_path, 0, s("src/"), StringMatchFlag_SlashInsensitive); - U64 src_max = src_min + str8_lit("src/").size; - U64 layer_slash_max = str8_find_needle(file_path, src_max, s("/"), StringMatchFlag_SlashInsensitive); - + String8 file_path = str8_cstring(file_path_cstr); TestInfo *t = &test_infos[test_infos_count++]; - t->layer = str8_substr(file_path, r1u64(src_max, layer_slash_max)); + t->layer = test_layer_from_file_path(file_path); t->label = str8_cstring(func_name_cstr); t->decl_line = line; t->skip = skip; diff --git a/src/base/base_test.h b/src/base/base_test.h index c7a225ee..0bfc9d60 100644 --- a/src/base/base_test.h +++ b/src/base/base_test.h @@ -30,6 +30,7 @@ struct TestCtx String8 exemplars_path; String8 artifacts_path; String8 input_data_path; + String8 user_data; TestResult *result_out; String8List *test_out; }; @@ -46,6 +47,7 @@ struct TestInfo S64 decl_line; B32 skip; TestFunctionType *test_fn; + String8 user_data; }; #if BUILD_TESTS @@ -58,6 +60,7 @@ internal String8 test_build_exe_path(Arena *arena, String8 name); internal String8 test_input_path(Arena *arena, TestCtx *ctx, String8 name); internal String8 test_input_exe_path(Arena *arena, TestCtx *ctx, String8 name); internal String8 test_exemplar_path(Arena *arena, TestCtx *ctx, String8 name); +internal String8 test_layer_from_file_path(String8 file_path); internal void base_register_test(char *func_name, TestFunctionType *fn, char *file_path, int line, int skip); #define AddTest(name, file_path, line, skip_, ...) \ From 28044743271b3356e183c722dd425ab5f25e9ebc Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 26 Aug 2026 11:40:55 -0700 Subject: [PATCH 128/251] const import header is data header --- src/coff/coff_lib_writer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coff/coff_lib_writer.c b/src/coff/coff_lib_writer.c index 152e558a..83ceb4c1 100644 --- a/src/coff/coff_lib_writer.c +++ b/src/coff/coff_lib_writer.c @@ -149,13 +149,13 @@ coff_lib_writer_push_import(COFF_LibWriter *lib_writer, COFF_MachineType machine imp_symbol.member_idx = member_idx; coff_lib_writer_symbol_list_push(lib_writer->arena, &lib_writer->symbol_list, imp_symbol); } break; - case COFF_ImportHeader_Data: { + case COFF_ImportHeader_Data: + case COFF_ImportHeader_Const: { COFF_LibWriterSymbol imp_symbol = {0}; imp_symbol.name = push_str8f(lib_writer->arena, "__imp_%S", name); imp_symbol.member_idx = member_idx; coff_lib_writer_symbol_list_push(lib_writer->arena, &lib_writer->symbol_list, imp_symbol); } break; - case COFF_ImportHeader_Const: { NotImplemented; } break; default: { InvalidPath; } break; } } From 8923472c238f169ef9e6164ffb199ab981ecdcd1 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 2 Sep 2026 21:22:10 -0700 Subject: [PATCH 129/251] radlink: fast path for single task runs --- src/linker/thread_pool/thread_pool.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/linker/thread_pool/thread_pool.c b/src/linker/thread_pool/thread_pool.c index 3dc2c146..d91efc44 100644 --- a/src/linker/thread_pool/thread_pool.c +++ b/src/linker/thread_pool/thread_pool.c @@ -182,6 +182,13 @@ tp_temp_end(TP_Temp temp) internal void tp_for_parallel(TP_Context *pool, TP_Arena *task_arena, U64 task_count, TP_TaskFunc *task_func, void *task_data) { + // fast path for single tasks to bypass syncs + if (task_count == 1) { + Arena *arena = task_arena ? task_arena->v[0] : 0; + task_func(arena, 0, 0, task_data, pool); + return; + } + if (task_count) { // init run pool->task_arena = task_arena; From 50a489e816dbdb9867314ff208e70da8e0338755 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 11:11:11 -0700 Subject: [PATCH 130/251] radlink: track anti-dependency symbols search position with a parallel cursor array * this helps prevent case where an anti-dependency search resets the cursors for the undefined symbol scan --- src/linker/lnk.c | 82 ++++++++++++++++++++++++++++---------------- src/linker/lnk.h | 1 - src/linker/lnk_lib.h | 3 +- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index dbf4f577..13b265df 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1532,8 +1532,8 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) LNK_LibMemberInfo *lib_member_infos = task->lib_member_infos; LNK_LibMemberRefList *member_ref_list = &task->member_ref_lists[task_id]; - LNK_SymbolHashTrieChunk *start_chunk = task->reset_search_cursor ? 0 : lib->search_cursor_chunks[task_id]; - U64 start_idx = task->reset_search_cursor ? 0 : lib->search_cursor_indices[task_id]; + LNK_SymbolHashTrieChunk *start_chunk = lib->search_cursor_chunks[task_id]; + U64 start_idx = lib->search_cursor_indices[task_id]; LNK_SymbolHashTrieChunk *end_chunk = symtab->search_chunks[task_id].last; U64 end_count = end_chunk ? end_chunk->count : 0; @@ -1549,16 +1549,30 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) if (lnk_search_lib(lib, symbol->name, &member_idx)) { lnk_queue_lib_member(arena, task->imports_hm, task->link->lib_member_infos_hm, member_ref_list, symbol, lib, lib_member_infos, member_idx); } - } else if (search_type == LNK_SymbolSearch_WeakAntiDependency && search_anti_deps) { - LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol); - LNK_ObjSymbolRef dep_symbol = {0}; - if (lnk_resolve_weak_symbol(symtab, symbol_ref, &dep_symbol)) { - COFF_ParsedSymbol dep_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(dep_symbol.obj, dep_symbol.symbol_idx); - COFF_SymbolValueInterpType dep_interp = coff_interp_from_parsed_symbol(dep_parsed); - if (dep_interp == COFF_SymbolValueInterp_Weak) { - U32 member_idx; - if (lnk_search_lib(lib, symbol->name, &member_idx)) { - lnk_queue_lib_member(arena, task->imports_hm, task->link->lib_member_infos_hm, member_ref_list, symbol, lib, lib_member_infos, member_idx); + } + } + } + + if (search_anti_deps) { + start_chunk = lib->anti_dep_search_cursor_chunks[task_id]; + start_idx = lib->anti_dep_search_cursor_indices[task_id]; + for EachNode(c, LNK_SymbolHashTrieChunk, start_chunk ? start_chunk : symtab->search_chunks[task_id].first) { + U64 i_begin = (c == start_chunk) ? start_idx : 0; + U64 i_end = (c == end_chunk) ? end_count : c->count; + for (U64 i = i_begin; i < i_end; i += 1) { + LNK_Symbol *symbol = c->v[i].symbol; + LNK_SymbolSearchType search_type = lnk_search_type_from_symbol(symbol); + if (search_type == LNK_SymbolSearch_WeakAntiDependency) { + LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol); + LNK_ObjSymbolRef dep_symbol = {0}; + if (lnk_resolve_weak_symbol(symtab, symbol_ref, &dep_symbol)) { + COFF_ParsedSymbol dep_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(dep_symbol.obj, dep_symbol.symbol_idx); + COFF_SymbolValueInterpType dep_interp = coff_interp_from_parsed_symbol(dep_parsed); + if (dep_interp == COFF_SymbolValueInterp_Weak) { + U32 member_idx; + if (lnk_search_lib(lib, symbol->name, &member_idx)) { + lnk_queue_lib_member(arena, task->imports_hm, task->link->lib_member_infos_hm, member_ref_list, symbol, lib, lib_member_infos, member_idx); + } } } } @@ -1569,6 +1583,10 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) // cache search cursors lib->search_cursor_chunks[task_id] = end_chunk; lib->search_cursor_indices[task_id] = end_count; + if (search_anti_deps) { + lib->anti_dep_search_cursor_chunks[task_id] = end_chunk; + lib->anti_dep_search_cursor_indices[task_id] = end_count; + } } internal U64 @@ -1576,8 +1594,8 @@ lnk_search_lib_task_work_count(LNK_SearchLibTask *task, U64 task_id) { LNK_Lib *lib = task->lib; LNK_SymbolTable *symtab = task->symtab; - LNK_SymbolHashTrieChunk *start_chunk = task->reset_search_cursor ? 0 : lib->search_cursor_chunks[task_id]; - U64 start_idx = task->reset_search_cursor ? 0 : lib->search_cursor_indices[task_id]; + LNK_SymbolHashTrieChunk *start_chunk = lib->search_cursor_chunks[task_id]; + U64 start_idx = lib->search_cursor_indices[task_id]; LNK_SymbolHashTrieChunk *end_chunk = symtab->search_chunks[task_id].last; U64 end_count = end_chunk ? end_chunk->count : 0; @@ -1587,6 +1605,15 @@ lnk_search_lib_task_work_count(LNK_SearchLibTask *task, U64 task_id) U64 i_end = (c == end_chunk) ? end_count : c->count; work_count += i_end - i_begin; } + if (task->search_anti_deps) { + start_chunk = lib->anti_dep_search_cursor_chunks[task_id]; + start_idx = lib->anti_dep_search_cursor_indices[task_id]; + for EachNode(c, LNK_SymbolHashTrieChunk, start_chunk ? start_chunk : symtab->search_chunks[task_id].first) { + U64 i_begin = (c == start_chunk) ? start_idx : 0; + U64 i_end = (c == end_chunk) ? end_count : c->count; + work_count += i_end - i_begin; + } + } return work_count; } @@ -1728,24 +1755,22 @@ lnk_link_inputs(TP_Context *tp, } else { // search symbols in lib MemoryZeroTyped(member_ref_lists, tp->worker_count); - // anti-dep mode changes which weak symbols can resolve from this lib - B32 reset_search_cursor = lib->search_cursor_chunks != 0 && lib->searched_anti_deps != search_anti_deps; - // lazy alloc cursors for tracking searched symbols if (lib->search_cursor_chunks == 0) { - lib->search_cursor_chunks = push_array(link->arena, LNK_SymbolHashTrieChunk *, tp->worker_count); - lib->search_cursor_indices = push_array(link->arena, U64, tp->worker_count); + lib->search_cursor_chunks = push_array(link->arena, LNK_SymbolHashTrieChunk *, tp->worker_count); + lib->search_cursor_indices = push_array(link->arena, U64, tp->worker_count); + lib->anti_dep_search_cursor_chunks = push_array(link->arena, LNK_SymbolHashTrieChunk *, tp->worker_count); + lib->anti_dep_search_cursor_indices = push_array(link->arena, U64, tp->worker_count); } LNK_SearchLibTask search_task = { - .search_anti_deps = search_anti_deps, - .reset_search_cursor = reset_search_cursor, - .link = link, - .imports_hm = &imports_hm, - .lib = lib, - .symtab = symtab, - .lib_member_infos = lib_member_infos, - .member_ref_lists = member_ref_lists + .search_anti_deps = search_anti_deps, + .link = link, + .imports_hm = &imports_hm, + .lib = lib, + .symtab = symtab, + .lib_member_infos = lib_member_infos, + .member_ref_lists = member_ref_lists }; enum { serial_work_limit = 16384 }; U64 search_work_count = 0; @@ -1764,9 +1789,6 @@ lnk_link_inputs(TP_Context *tp, } else { tp_for_parallel(tp, arena, tp->worker_count, lnk_search_lib_task, &search_task); } - - // cache last search mode, if the mode changes then skipped weak anti-dependency must be searched again - lib->searched_anti_deps = search_anti_deps; } LNK_LibMemberRefList queued_members = {0}; diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 59b3d9a2..cff79a53 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -206,7 +206,6 @@ typedef struct LNK_BaseRelocPageArray typedef struct { B32 search_anti_deps; - B32 reset_search_cursor; LNK_Link *link; HashMap *imports_hm; LNK_SymbolTable *symtab; diff --git a/src/linker/lnk_lib.h b/src/linker/lnk_lib.h index 6db16e29..03378cb8 100644 --- a/src/linker/lnk_lib.h +++ b/src/linker/lnk_lib.h @@ -18,7 +18,8 @@ typedef struct LNK_Lib struct LNK_SymbolHashTrieChunk **search_cursor_chunks; U64 *search_cursor_indices; - B32 searched_anti_deps; + struct LNK_SymbolHashTrieChunk **anti_dep_search_cursor_chunks; + U64 *anti_dep_search_cursor_indices; } LNK_Lib; typedef struct LNK_LibNode From 86ec8137d4e8687e19547bfd9477b257b975a4fc Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 11:24:45 -0700 Subject: [PATCH 131/251] radlink: track function override alternate name directives with a cursor to prevent unnecessary rescans --- src/linker/lnk.c | 5 +++-- src/linker/lnk.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 13b265df..fdda4d28 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1142,6 +1142,7 @@ lnk_link_init(TP_Arena *arena, LNK_Config *config) link->arena = arena_alloc(.name = "LINK"); link->last_symbol_input = &link->objs.first; link->last_include = &config->include_symbol_list.first; + link->last_func_override_alt_name = &config->alt_name_list.first; link->last_default_lib = &config->input_default_lib_list.first; link->last_obj_lib = &config->input_obj_lib_list.first; link->last_cmd_lib = &config->input_list[LNK_Input_Lib].first; @@ -1310,8 +1311,8 @@ lnk_load_inputs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer { B32 has_function_overrides = 0; - for EachNode(alt_name_n, LNK_AltNameNode, config->alt_name_list.first) { - LNK_AltName alt_name = alt_name_n->v; + for (; *link->last_func_override_alt_name; link->last_func_override_alt_name = &(*link->last_func_override_alt_name)->next) { + LNK_AltName alt_name = (*link->last_func_override_alt_name)->v; if ( ! str8_ends_with(alt_name.from, str8_lit("$fo$"), 0)) { continue; } diff --git a/src/linker/lnk.h b/src/linker/lnk.h index cff79a53..598f13e9 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -98,6 +98,7 @@ typedef struct LNK_Link LNK_LibList libs; LNK_ObjNode **last_symbol_input; LNK_IncludeSymbolNode **last_include; + LNK_AltNameNode **last_func_override_alt_name; String8Node **last_cmd_lib; String8Node **last_default_lib; String8Node **last_obj_lib; From 7e995e8194bc1dba922297d6070a36aea689f7ae Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 11:33:51 -0700 Subject: [PATCH 132/251] radlink: guard against empty input load requests --- src/linker/lnk.c | 20 ++++++++++++-------- src/linker/lnk.h | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index fdda4d28..f24030a7 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -969,19 +969,19 @@ lnk_inputer_push_lib_thin(LNK_Inputer *inputer, LNK_Config *config, LNK_InputSou } internal B32 -lnk_inputer_has_items(LNK_Inputer *inputer) +lnk_has_pending_input_work(LNK_Inputer *inputer, LNK_Link *link) { if (inputer->new_objs.count > 0) { return 1; } - for EachIndex(i, ArrayCount(inputer->new_libs)) { if (inputer->new_libs[i].count > 0) { return 1; } } - - return 0; + return *link->last_include != 0 || + *link->last_default_lib != 0 || + *link->last_obj_lib != 0; } internal LNK_InputPtrArray @@ -1670,7 +1670,9 @@ lnk_link_inputs(TP_Context *tp, for (U64 resolved_members_count = 0; ; resolved_members_count = 0) { ProfBegin("Search Pass"); - lnk_load_inputs(tp, arena, config, inputer, symtab, link); + if (lnk_has_pending_input_work(inputer, link)) { + lnk_load_inputs(tp, arena, config, inputer, symtab, link); + } for EachNode(lib_n, LNK_LibNode, link->libs.first) { LNK_Lib *lib = &lib_n->data; @@ -1740,7 +1742,9 @@ lnk_link_inputs(TP_Context *tp, ProfBeginV("Search %S", str8_skip_last_slash(lib->path)); do { - lnk_load_inputs(tp, arena, config, inputer, symtab, link); + if (lnk_has_pending_input_work(inputer, link)) { + lnk_load_inputs(tp, arena, config, inputer, symtab, link); + } if (link_whole_archive) { local_persist LNK_Symbol *null_symbol = 0; @@ -1895,7 +1899,7 @@ lnk_link_inputs(TP_Context *tp, } resolved_members_count += queued_members.count; - } while (lnk_inputer_has_items(inputer)); + } while (lnk_has_pending_input_work(inputer, link)); ProfEnd(); } @@ -1933,7 +1937,7 @@ lnk_link_inputs(TP_Context *tp, } } - resolved_members_count = lnk_inputer_has_items(inputer); + resolved_members_count = lnk_has_pending_input_work(inputer, link); } ProfEnd(); diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 598f13e9..2fd14a9c 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -376,9 +376,9 @@ internal LNK_Input * lnk_inputer_push_lib(LNK_Inputer *inputer, LNK_InputSourceT internal LNK_Input * lnk_inputer_push_lib_linkgen(LNK_Inputer *inputer, LNK_InputSourceType input_source, String8 path, String8 data); internal LNK_Input * lnk_inputer_push_lib_thin(LNK_Inputer *inputer, LNK_Config *config, LNK_InputSourceType input_source, String8 lib_path); -internal B32 lnk_inputer_has_items(LNK_Inputer *inputer); +internal B32 lnk_has_pending_input_work(LNK_Inputer *inputer, LNK_Link *link); internal LNK_InputPtrArray lnk_inputer_flush(Arena *arena, TP_Context *tp, LNK_Inputer *inputer, LNK_IO_Flags io_flags, LNK_InputList *all_inputs, LNK_InputList *new_inputs); -internal void lnk_inputer_release_file_maps(TP_Context *tp, LNK_Inputer *inputer); +internal void lnk_inputer_release_file_maps(TP_Context *tp, LNK_Inputer *inputer); // --- Link Context ------------------------------------------------------------ From dcea1b49a147ec67d5ba201bdb87f199ca8803d0 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 11:39:45 -0700 Subject: [PATCH 133/251] radlink: update /BRepro and /RAD_LOG help lines --- src/linker/lnk_config.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 4e099331..904e5abb 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -11,10 +11,10 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_AlternateName, 1, LNK_CmdValueKind_Scalar, "ALTERNATENAME", ":FROM=TO", "Creates a symbol alias \"FROM=TO\"." }, { LNK_CmdSwitch_AppContainer, 0, LNK_CmdValueKind_Scalar, "APPCONTAINER", "[:NO]", "Toggles app container bit in the image header." }, { LNK_CmdSwitch_Base, 0, LNK_CmdValueKind_List, "BASE", "{ADDRESS[,SIZE]|@FILENAME,KEY}", "Set default image base address." }, - { LNK_CmdSwitch_Brepro, 0, LNK_CmdValueKind_Null, "BREPRO", "", "No support." }, + { LNK_CmdSwitch_Brepro, 0, LNK_CmdValueKind_Null, "BREPRO", "", "Equivalent to '/RAD_TIME_STAMP:0'." }, { LNK_CmdSwitch_Debug, 0, LNK_CmdValueKind_Scalar, "DEBUG", "[:{FULL|NONE}]", "Controls debug info level." }, { LNK_CmdSwitch_DefaultLib, 1, LNK_CmdValueKind_Scalar, "DEFAULTLIB", ":LIBNAME", "Set default library." }, - { LNK_CmdSwitch_Def, 1, LNK_CmdValueKind_Scalar, "DEF", ":FILENAME", "Read exports from a module-definition file." }, + { LNK_CmdSwitch_Def, 1, LNK_CmdValueKind_Scalar, "DEF", ":FILENAME", "Read exports from a module-definition file." }, { LNK_CmdSwitch_Delay, 0, LNK_CmdValueKind_Scalar, "DELAY", ":{NOBIND|UNLOAD}", "Controls emission of unload and bind tables." }, { LNK_CmdSwitch_DelayLoad, 0, LNK_CmdValueKind_Scalar, "DELAYLOAD", ":DLL", "Delay load DLL." }, { LNK_CmdSwitch_Dll, 0, LNK_CmdValueKind_Null, "DLL", "", "Link to a DLL." }, @@ -86,7 +86,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Rad_Guid, 0, LNK_CmdValueKind_Scalar, "RAD_GUID", ":{IMAGEBLAKE3|XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX}", "The image guid that is embeded in the debug info." }, { LNK_CmdSwitch_Rad_LargePages, 0, LNK_CmdValueKind_Scalar, "RAD_LARGE_PAGES", "[:NO]", "Disabled by default on Windows." }, { LNK_CmdSwitch_Rad_LinkVer, 0, LNK_CmdValueKind_Scalar, "RAD_LINK_VER", ":##,##", "Linker version." }, - { LNK_CmdSwitch_Rad_Log, 0, LNK_CmdValueKind_Scalar, "RAD_LOG", ":{ALL,INPUT_OBJ,INPUT_LIB,IO,LINK_STATS,TIMERS}", "Loggers." }, + { LNK_CmdSwitch_Rad_Log, 0, LNK_CmdValueKind_Scalar, "RAD_LOG", ":{ALL,INPUT_OBJ,INPUT_LIB,IO,LINKS,TIMERS}", "Loggers." }, { LNK_CmdSwitch_Rad_MtPath, 0, LNK_CmdValueKind_Scalar, "RAD_MT_PATH", ":EXEPATH", "Exe path to the manifest tool (default: " LNK_MANIFEST_MERGE_TOOL_NAME ")" }, { LNK_CmdSwitch_Rad_OsVer, 0, LNK_CmdValueKind_Scalar, "RAD_OS_VER", ":##,##", "OS version." }, { LNK_CmdSwitch_Rad_PageSize, 0, LNK_CmdValueKind_Scalar, "RAD_PAGE_SIZE", ":#", "Must be power of two." }, @@ -2865,16 +2865,14 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Dll)) { DefaultOpt("/SUBSYSTEM:%S", pe_string_from_subsystem(PE_WindowsSubsystem_WINDOWS_GUI)); } - if (!lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Brepro)) { + if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Brepro)) { + DefaultOpt("/RAD_TIME_STAMP:0"); + } else { DefaultOpt("/RAD_TIME_STAMP:%u", get_process_start_time_unix()); } - for EachIndex(i, ArrayCount(default_opts)) { - DefaultOpt("%s", default_opts[i]); - } - for EachIndex(i, ArrayCount(push_opts)) { - PushOpt("%s", push_opts[i]); - } + for EachIndex(i, ArrayCount(default_opts)) { DefaultOpt("%s", default_opts[i]); } + for EachIndex(i, ArrayCount(push_opts)) { PushOpt("%s", push_opts[i]); } // when /FORCE is specified on the command line, do not stop on these errors if (lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Force)) { From 6e45983b193f5e25ed85ec2fd56816123a442c30 Mon Sep 17 00:00:00 2001 From: Henrik Karlsson Date: Wed, 2 Sep 2026 14:17:13 -0700 Subject: [PATCH 134/251] coff: emit BigObj for oversized generated import objects --- src/coff/coff_obj_writer.c | 95 +++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/src/coff/coff_obj_writer.c b/src/coff/coff_obj_writer.c index 01b9cbfb..bceaf964 100644 --- a/src/coff/coff_obj_writer.c +++ b/src/coff/coff_obj_writer.c @@ -32,12 +32,13 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) str8_list_push(scratch.arena, &string_table, str8_struct(string_table_size)); // - // assing section numbers + // assign section numbers // U64 obj_sections_count; COFF_ObjSection **obj_sections; { obj_sections_count = obj_writer->sect_count; + AssertAlways(obj_sections_count < COFF_Symbol_DebugSection32); obj_sections = push_array(scratch.arena, COFF_ObjSection *, obj_writer->sect_count); U64 sect_idx = 0; for (COFF_ObjSectionNode *sect_n = obj_writer->sect_first; sect_n != 0; sect_n = sect_n->next, sect_idx += 1) { @@ -47,7 +48,10 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) } } - AssertAlways(obj_sections_count <= max_U16); + // Standard COFF reserves section numbers 0xff00 and above. BigObj also + // widens every symbol/auxiliary record from 18 to 20 bytes. + B32 is_big_obj = obj_sections_count > 0xfeff; + U64 symbol_size = is_big_obj ? sizeof(COFF_Symbol32) : sizeof(COFF_Symbol16); // // serialize symbol table @@ -69,9 +73,6 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) for (COFF_ObjSymbolNode *symbol_n = obj_writer->symbol_first; symbol_n != 0; symbol_n = symbol_n->next) { COFF_ObjSymbol *s = &symbol_n->v; - COFF_Symbol16 *d = push_array(scratch.arena, COFF_Symbol16, 1); - str8_list_push(scratch.arena, &symbol_table, str8_struct(d)); - COFF_SymbolName name = {0}; // long name if (s->name.size > sizeof(name.short_name)) { @@ -90,28 +91,43 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) // symbol header AssertAlways(s->aux_symbols.node_count <= max_U8); - d->name = name; - d->value = s->value; + U32 section_number = 0; switch (s->loc.type) { case COFF_SymbolLocation_Null: break; - case COFF_SymbolLocation_Section: d->section_number = safe_cast_u16(s->loc.u.section->section_number); break; - case COFF_SymbolLocation_Abs: d->section_number = COFF_Symbol_AbsSection16; break; - case COFF_SymbolLocation_Undef: d->section_number = COFF_Symbol_UndefinedSection; break; - case COFF_SymbolLocation_Common: d->section_number = COFF_Symbol_UndefinedSection; break; + case COFF_SymbolLocation_Section: section_number = s->loc.u.section->section_number; break; + case COFF_SymbolLocation_Abs: section_number = is_big_obj ? COFF_Symbol_AbsSection32 : COFF_Symbol_AbsSection16; break; + case COFF_SymbolLocation_Undef: section_number = COFF_Symbol_UndefinedSection; break; + case COFF_SymbolLocation_Common: section_number = COFF_Symbol_UndefinedSection; break; + } + if (is_big_obj) { + COFF_Symbol32 *d = push_array(scratch.arena, COFF_Symbol32, 1); + d->name = name; + d->value = s->value; + d->section_number = section_number; + d->type = s->type; + d->storage_class = s->storage_class; + d->aux_symbol_count = (U8)s->aux_symbols.node_count; + str8_list_push(scratch.arena, &symbol_table, str8_struct(d)); + } else { + COFF_Symbol16 *d = push_array(scratch.arena, COFF_Symbol16, 1); + d->name = name; + d->value = s->value; + d->section_number = safe_cast_u16(section_number); + d->type = s->type; + d->storage_class = s->storage_class; + d->aux_symbol_count = (U8)s->aux_symbols.node_count; + str8_list_push(scratch.arena, &symbol_table, str8_struct(d)); } - d->type = s->type; - d->storage_class = s->storage_class; - d->aux_symbol_count = 0; U64 start_symbol_idx = symbol_idx; if (s->storage_class == COFF_SymStorageClass_WeakExternal) { if (s->aux_symbols.node_count > 0) { COFF_ObjSymbolWeak *s_weak = (COFF_ObjSymbolWeak *)s->aux_symbols.first->string.str; - COFF_SymbolWeakExt *d_weak = push_array(scratch.arena, COFF_SymbolWeakExt, 1); + COFF_SymbolWeakExt *d_weak = (COFF_SymbolWeakExt *)push_array(scratch.arena, U8, symbol_size); d_weak->tag_index = s_weak->tag ? s_weak->tag->idx : max_U32; d_weak->characteristics = s_weak->characteristics; - str8_list_push(scratch.arena, &symbol_table, str8_struct(d_weak)); + str8_list_push(scratch.arena, &symbol_table, str8((U8 *)d_weak, symbol_size)); symbol_idx += 1; } } else if (s->storage_class == COFF_SymStorageClass_Static) { @@ -120,15 +136,17 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) COFF_ObjSection *sect = s->loc.u.section; COFF_ObjSymbolSecDef *s_sd = (COFF_ObjSymbolSecDef *)s->aux_symbols.first->string.str; - COFF_SymbolSecDef *d_sd = push_array(scratch.arena, COFF_SymbolSecDef, 1); + COFF_SymbolSecDef *d_sd = (COFF_SymbolSecDef *)push_array(scratch.arena, U8, symbol_size); d_sd->length = safe_cast_u32(sect->data.total_size); d_sd->number_of_relocations = (U16)sect->reloc_count; d_sd->check_sum = 0; - d_sd->number_lo = s_sd->selection == COFF_ComdatSelect_Associative ? safe_cast_u16(s_sd->associate->section_number) : 0; + U32 associate_number = s_sd->selection == COFF_ComdatSelect_Associative ? s_sd->associate->section_number : 0; + d_sd->number_lo = (U16)associate_number; + d_sd->number_hi = (U16)(associate_number >> 16); d_sd->selection = s_sd->selection; - str8_list_push(scratch.arena, &symbol_table, str8_struct(d_sd)); + str8_list_push(scratch.arena, &symbol_table, str8((U8 *)d_sd, symbol_size)); symbol_idx += 1; } } @@ -136,26 +154,37 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) U8 processed_aux_symbol_count = (U8)(symbol_idx - start_symbol_idx); for (U64 aux_idx = processed_aux_symbol_count; aux_idx < s->aux_symbols.node_count; aux_idx += 1) { - COFF_Symbol16 *a = push_array(scratch.arena, COFF_Symbol16, 1); - str8_list_push(scratch.arena, &symbol_table, str8_struct(a)); + U8 *a = push_array(scratch.arena, U8, symbol_size); + str8_list_push(scratch.arena, &symbol_table, str8(a, symbol_size)); } - - d->aux_symbol_count = (U8)s->aux_symbols.node_count; } } // // file header // - COFF_FileHeader *file_header = push_array(scratch.arena, COFF_FileHeader, 1); - file_header->machine = obj_writer->machine; - file_header->section_count = obj_sections_count; - file_header->time_stamp = obj_writer->time_stamp; - file_header->symbol_table_foff = 0; - file_header->symbol_count = safe_cast_u32(symbol_table.node_count); - file_header->optional_header_size = 0; - file_header->flags = 0; - str8_list_push(scratch.arena, &srl, str8_struct(file_header)); + U32 *symbol_table_foff; + if (is_big_obj) { + COFF_BigObjHeader *header = push_array(scratch.arena, COFF_BigObjHeader, 1); + header->sig1 = COFF_MachineType_Unknown; + header->sig2 = max_U16; + header->version = 2; + header->machine = obj_writer->machine; + header->time_stamp = obj_writer->time_stamp; + MemoryCopy(header->magic, g_coff_big_header_magic, sizeof(header->magic)); + header->section_count = safe_cast_u32(obj_sections_count); + header->symbol_count = safe_cast_u32(symbol_table.node_count); + symbol_table_foff = &header->symbol_table_foff; + str8_list_push(scratch.arena, &srl, str8_struct(header)); + } else { + COFF_FileHeader *header = push_array(scratch.arena, COFF_FileHeader, 1); + header->machine = obj_writer->machine; + header->section_count = safe_cast_u16(obj_sections_count); + header->time_stamp = obj_writer->time_stamp; + header->symbol_count = safe_cast_u32(symbol_table.node_count); + symbol_table_foff = &header->symbol_table_foff; + str8_list_push(scratch.arena, &srl, str8_struct(header)); + } // // section table @@ -230,7 +259,7 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) // symbol table // if (symbol_table.total_size || string_table.total_size > sizeof(*string_table_size)) { - file_header->symbol_table_foff = srl.total_size; + *symbol_table_foff = safe_cast_u32(srl.total_size); str8_list_concat_in_place(&srl, &symbol_table); } From 5fe1bac74ebf0015c267898c18cdcfa157e68269 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 12:41:24 -0700 Subject: [PATCH 135/251] test big obj generation --- src/coff/coff_obj_writer.c | 24 ++--- src/linker/tests/linker_tests.c | 149 +++++++++++++++++++++++++------- 2 files changed, 131 insertions(+), 42 deletions(-) diff --git a/src/coff/coff_obj_writer.c b/src/coff/coff_obj_writer.c index bceaf964..451fe548 100644 --- a/src/coff/coff_obj_writer.c +++ b/src/coff/coff_obj_writer.c @@ -26,8 +26,8 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) String8List srl = {0}; - String8List string_table = {0}; - U32 *string_table_size = push_array(scratch.arena, U32, 1); + String8List string_table = {0}; + U32 *string_table_size = push_array(scratch.arena, U32, 1); *string_table_size = sizeof(*string_table_size); str8_list_push(scratch.arena, &string_table, str8_struct(string_table_size)); @@ -41,16 +41,16 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) AssertAlways(obj_sections_count < COFF_Symbol_DebugSection32); obj_sections = push_array(scratch.arena, COFF_ObjSection *, obj_writer->sect_count); U64 sect_idx = 0; - for (COFF_ObjSectionNode *sect_n = obj_writer->sect_first; sect_n != 0; sect_n = sect_n->next, sect_idx += 1) { + for EachNode(sect_n, COFF_ObjSectionNode, obj_writer->sect_first) { COFF_ObjSection *sect = §_n->v; sect->section_number = sect_idx+1; obj_sections[sect_idx] = sect; + sect_idx += 1; } } - // Standard COFF reserves section numbers 0xff00 and above. BigObj also - // widens every symbol/auxiliary record from 18 to 20 bytes. - B32 is_big_obj = obj_sections_count > 0xfeff; + + B32 is_big_obj = obj_sections_count > 0xfeff; U64 symbol_size = is_big_obj ? sizeof(COFF_Symbol32) : sizeof(COFF_Symbol16); // @@ -60,7 +60,7 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) { { U64 symbol_idx = 0; - for (COFF_ObjSymbolNode *symbol_n = obj_writer->symbol_first; symbol_n != 0; symbol_n = symbol_n->next) { + for EachNode(symbol_n, COFF_ObjSymbolNode, obj_writer->symbol_first) { COFF_ObjSymbol *s = &symbol_n->v; // assign symbol index @@ -70,7 +70,7 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) } U64 symbol_idx = 0; - for (COFF_ObjSymbolNode *symbol_n = obj_writer->symbol_first; symbol_n != 0; symbol_n = symbol_n->next) { + for EachNode(symbol_n, COFF_ObjSymbolNode, obj_writer->symbol_first) { COFF_ObjSymbol *s = &symbol_n->v; COFF_SymbolName name = {0}; @@ -193,7 +193,7 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) COFF_SectionHeader *sectab = push_array(scratch.arena, COFF_SectionHeader, obj_sections_count); str8_list_push(scratch.arena, &srl, str8_array(sectab, obj_sections_count)); { - for (U64 sect_idx = 0; sect_idx < obj_sections_count; sect_idx += 1) { + for EachIndex(sect_idx, obj_sections_count) { COFF_ObjSection *s = obj_sections[sect_idx]; COFF_SectionHeader *d = §ab[sect_idx]; @@ -208,7 +208,7 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) } // alloc zero nodes - for (String8Node *data_n = s->data.first; data_n != 0; data_n = data_n->next) { + for EachNode(data_n, String8Node, s->data.first) { if (data_n->string.str == 0 && data_n->string.size > 0) { data_n->string = str8(push_array(scratch.arena, U8, data_n->string.size), data_n->string.size); } @@ -229,9 +229,9 @@ coff_obj_writer_serialize(Arena *arena, COFF_ObjWriter *obj_writer) AssertAlways(s->reloc_count <= max_U16); COFF_Reloc *relocs = push_array(scratch.arena, COFF_Reloc, s->reloc_count); U64 reloc_idx = 0; - for (COFF_ObjRelocNode *reloc_n = s->reloc_first; reloc_n != 0; reloc_n = reloc_n->next, reloc_idx += 1) { + for EachNode(reloc_n, COFF_ObjRelocNode, s->reloc_first) { COFF_ObjReloc *rs = &reloc_n->v; - COFF_Reloc *rd = &relocs[reloc_idx]; + COFF_Reloc *rd = &relocs[reloc_idx++]; rd->apply_off = rs->apply_off; rd->isymbol = rs->symbol->idx; rd->type = rs->type; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index c0b46331..8e07d43d 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -433,6 +433,72 @@ t_write_entry_obj(void) //////////////////////////////// +TEST(coff_writer_bigobj) +{ + // Exercise the standard-COFF boundary and an associative parent above 16 bits. + U32 section_counts[] = {3, 0xfeff, 0xff00, 0x10002}; + for EachElement(case_idx, section_counts) { + COFF_ObjWriter *writer = coff_obj_writer_alloc(0x12345678, COFF_MachineType_X64); + + U32 section_count = section_counts[case_idx]; + for (U32 i = 0; i < section_count - 2; i += 1) { + coff_obj_writer_push_section(writer, str8_lit(".empty"), COFF_SectionFlag_LnkRemove, str8_zero()); + } + + COFF_SectionFlags flags = COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_LnkCOMDAT; + COFF_ObjSection *head = coff_obj_writer_push_section(writer, str8_lit(".long_head_section"), flags, str8_lit("head")); + COFF_ObjSection *assoc = coff_obj_writer_push_section(writer, str8_lit(".assoc"), flags, str8(push_array(arena, U8, 8), 8)); + coff_obj_writer_push_symbol_secdef(writer, head, COFF_ComdatSelect_Any); + + COFF_ObjSymbol *target = coff_obj_writer_push_symbol_extern(writer, str8_lit("long_target_symbol"), 0, head); + COFF_ObjSymbol *weak = coff_obj_writer_push_symbol_weak(writer, str8_lit("weak"), COFF_WeakExt_SearchAlias, target); + COFF_ObjSymbol *absolute = coff_obj_writer_push_symbol_abs(writer, str8_lit("absolute"), 17, COFF_SymStorageClass_External); + COFF_ObjSymbol *assoc_def = coff_obj_writer_push_symbol_associative(writer, assoc, head); + COFF_ObjSymbol *undef = coff_obj_writer_push_symbol_undef(writer, str8_lit("undef")); + COFF_ObjSymbol *common = coff_obj_writer_push_symbol_common(writer, str8_lit("common"), 32); + + coff_obj_writer_section_push_reloc_addr(writer, assoc, 0, weak); + + String8 data = coff_obj_writer_serialize(arena, writer); + COFF_FileHeaderInfo info = coff_file_header_info_from_data(data); + T_Ok(info.is_big_obj == (section_count > 0xfeff)); + T_Ok(info.section_count_no_null == section_count); + T_Ok(info.symbol_size == (info.is_big_obj ? sizeof(COFF_Symbol32) : sizeof(COFF_Symbol16))); + T_Ok(info.symbol_count == 10); + + String8 symbols = str8_substr(data, info.symbol_table_range); + String8 strings = str8_substr(data, info.string_table_range); + COFF_ParsedSymbol parsed_target = coff_parse_symbol(info, strings, symbols, target->idx); + T_Ok(parsed_target.section_number == section_count - 1); + T_Ok(str8_match(parsed_target.name, target->name, 0)); + + COFF_ParsedSymbol parsed_assoc = coff_parse_symbol(info, strings, symbols, assoc_def->idx); + U32 parent = 0; + COFF_ComdatSelectType selection = 0; + coff_parse_secdef(parsed_assoc, info.is_big_obj, &selection, &parent, 0, 0); + T_Ok(parsed_assoc.section_number == section_count); + T_Ok(selection == COFF_ComdatSelect_Associative && parent == section_count - 1); + + COFF_ParsedSymbol parsed_weak = coff_parse_symbol(info, strings, symbols, weak->idx); + COFF_SymbolWeakExt *weak_aux = coff_parse_weak_tag(parsed_weak, info.is_big_obj); + T_Ok(weak_aux->tag_index == target->idx && weak_aux->characteristics == COFF_WeakExt_SearchAlias); + + COFF_ParsedSymbol parsed_absolute = coff_parse_symbol(info, strings, symbols, absolute->idx); + T_Ok(parsed_absolute.section_number == COFF_Symbol_AbsSection32 && parsed_absolute.value == 17); + T_Ok(coff_parse_symbol(info, strings, symbols, undef->idx).section_number == COFF_Symbol_UndefinedSection); + + COFF_ParsedSymbol parsed_common = coff_parse_symbol(info, strings, symbols, common->idx); + T_Ok(parsed_common.section_number == COFF_Symbol_UndefinedSection && parsed_common.value == 32); + + COFF_SectionHeader *sections = (COFF_SectionHeader *)(data.str + info.section_table_range.min); + COFF_Reloc *reloc = (COFF_Reloc *)(data.str + sections[section_count - 1].relocs_foff); + T_Ok(sections[section_count - 1].reloc_count == 1 && reloc->isymbol == weak->idx); + T_Ok(t_write_file(str8f(arena, "writer_%u.obj", section_count), data)); + + coff_obj_writer_release(&writer); + } +} + TEST(machine_compat_check) { // unknown.obj @@ -728,31 +794,6 @@ TEST(merge) t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /merge:.qwe=.test entry.obj test.obj"); T_Ok(g_last_exit_code == 0); - // initialized data size includes the aligned virtual tail of merged BSS - { - T_Ok(t_write_def_obj("mixed.obj", (T_COFF_DefObj){ - .machine = T_COFF_DefSetMachine(X64), - .sections = (T_COFF_DefSection[]){ - { "data", ".data", str8_lit_comp("d"), .flags = "rw:data@1" }, - { "bss", ".bss", str8(0, 0x201), .flags = "rw:bss@1" }, - {0} - } - })); - - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /merge:.bss=.data entry.obj mixed.obj"); - T_Ok(g_last_exit_code == 0); - - String8 exe = t_read_file(arena, str8_lit("a.exe")); - PE_BinInfo pe = pe_bin_info_from_data(arena, exe); - COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(exe, pe.section_table_range).str; - PE_OptionalHeader32Plus *opt = str8_deserial_get_raw_ptr(exe, pe.optional_header_off, sizeof(*opt)); - COFF_SectionHeader *data = coff_section_header_from_name(exe, section_table, pe.section_count, str8_lit(".data")); - T_Ok(data != 0); - T_Ok(data->fsize == 0x200); - T_Ok(data->vsize == 0x202); - T_Ok(opt->sizeof_inited_data == 0x400); - } - // merged contribution groups retain lexical order { T_Ok(t_write_def_obj("order.obj", (T_COFF_DefObj){ @@ -2333,7 +2374,7 @@ TEST(guard_cf_pulls_load_config) TEST(section_sort) { - COFF_SectionFlags data_flags = COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemRead|COFF_SectionFlag_Align1Bytes; + COFF_SectionFlags data_flags = COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemWrite|COFF_SectionFlag_Align1Bytes; T_Ok(t_write_def_obj("data.obj", (T_COFF_DefObj){ .machine = T_COFF_DefSetMachine(X64), .sections = (T_COFF_DefSection[]){ @@ -2373,14 +2414,14 @@ TEST(section_sort) T_Ok(data_section); String8 data = str8_substr(exe, rng_1u64(data_section->foff, data_section->foff + data_section->vsize)); - String8 expected_data = str8_lit("onetwothreefourfive"); + String8 expected_data = str8_lit("firstonetwothreefourfivelast"); T_Ok(str8_match(data, expected_data, 0)); COFF_SectionHeader *rdata_section = coff_section_header_from_name(string_table, section_table, pe.section_count, str8_lit(".rdata")); T_Ok(rdata_section); - String8 rdata = str8_substr(exe, rng_1u64(rdata_section->foff, rdata_section->foff + 15)); - T_Ok(str8_match(rdata, str8_lit("firstmiddlelast"), 0)); + String8 rdata = str8_substr(exe, rng_1u64(rdata_section->foff, rdata_section->foff + rdata_section->vsize)); + T_Ok(str8_match(rdata, str8_lit("middle"), 0)); } TEST(flag_conf) @@ -4747,6 +4788,54 @@ TEST(communal_var_vs_regular_comdat) } } +TEST(link_large_import_object) +{ + // Each named import creates three sections in the synthesized DLL object. + // Add a direct function reference as well to exercise its jump thunk. + + U32 import_count = 22000; + COFF_LibWriter *lib = coff_lib_writer_alloc(); + COFF_ObjWriter *obj = coff_obj_writer_alloc(0, COFF_MachineType_X64); + COFF_ObjSection *refs = coff_obj_writer_push_section(obj, str8_lit(".data"), + COFF_SectionFlag_CntInitializedData|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemWrite|COFF_SectionFlag_Align8Bytes, + str8(push_array(arena, U8, (import_count + 1)*8), (import_count + 1)*8)); + + for EachIndex(i, import_count) { + String8 name = str8f(arena, "import_%05u", i); + coff_lib_writer_push_import(lib, COFF_MachineType_X64, 0, str8_lit("large.dll"), COFF_ImportBy_Name, name, 0, COFF_ImportHeader_Code); + COFF_ObjSymbol *symbol = coff_obj_writer_push_symbol_undef(obj, str8f(arena, "__imp_%S", name)); + coff_obj_writer_section_push_reloc_addr(obj, refs, i*8, symbol); + } + + COFF_ObjSymbol *func = coff_obj_writer_push_symbol_undef_func(obj, str8f(arena, "import_%05u", import_count - 1)); + coff_obj_writer_section_push_reloc_addr(obj, refs, import_count*8, func); + T_Ok(t_write_file(str8_lit("refs.obj"), coff_obj_writer_serialize(arena, obj))); + T_Ok(t_write_file(str8_lit("large.lib"), coff_lib_writer_serialize(arena, lib, 0, 0, 1))); + + coff_obj_writer_release(&obj); + coff_lib_writer_release(&lib); + + T_Ok(t_write_entry_obj()); + t_invoke_linkerf("/subsystem:console /entry:entry /debug:full /opt:ref /out:large.exe entry.obj refs.obj large.lib"); + T_Ok(g_last_exit_code == 0); + + String8 image = t_read_file(arena, str8_lit("large.exe")); + PE_BinInfo bin = pe_bin_info_from_data(arena, image); + T_Ok(bin.data_dir_count > PE_DataDirectoryIndex_IMPORT); + + COFF_SectionHeader *sections = (COFF_SectionHeader *)(image.str + bin.section_table_range.min); + PE_ParsedStaticImportTable imports = pe_static_imports_from_data(arena, bin.is_pe32, bin.section_count, sections, image, bin.data_dir_franges[PE_DataDirectoryIndex_IMPORT]); + T_Ok(imports.count == 1); + T_Ok(str8_match(imports.v[0].name, str8_lit("large.dll"), 0)); + T_Ok(imports.v[0].import_count == import_count); + + for EachIndex(i, import_count) { + PE_ParsedImport *import = &imports.v[0].imports[i]; + T_Ok(import->type == PE_ParsedImport_Name); + T_Ok(str8_match(import->u.name.string, str8f(arena, "import_%05u", i), 0)); + } +} + #if OS_WINDOWS TEST(import_kernel32) { @@ -7947,7 +8036,7 @@ TEST(infer_asan) TEST(determ_test) { // compile the test target (torture) - t_invoke_cl("/fsanitize=address /c /Z7 /Fo:test.obj -I%S /Zc:preprocessor %S/torture/torture_main.c", t_src_path(), t_src_path()); + t_invoke_cl("/fsanitize=address /c /Z7 /DBUILD_GIT_HASH=Stringify() /Fo:test.obj -I%S /Zc:preprocessor %S/torture/torture_main.c", t_src_path(), t_src_path()); T_Ok(g_last_exit_code == 0); U64 run_count = 25; From 55fbbb09d7a214011ae1d7664e8e02797e6263db Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 14:50:14 -0700 Subject: [PATCH 136/251] fixes for the GCC build --- build.sh | 14 ++++--- src/base/base_core.h | 2 +- src/base/base_log.c | 6 +-- src/base/base_log.h | 1 + src/base/base_strings.c | 10 ++--- src/base/base_strings.h | 2 +- src/base/base_thread_context.c | 5 +-- src/base/base_thread_context.h | 5 +++ src/lib_rdi/rdi_parse.c | 2 +- src/linker/base_ext/base_core.c | 38 +++++++++---------- src/linker/codeview_ext/codeview.c | 2 +- src/linker/hash_table.c | 28 +++++++------- src/linker/lnk.c | 2 +- src/linker/lnk_obj.c | 6 +-- src/linker/pdb_ext/pdb_builder.c | 10 ++--- src/pe/pe.h | 2 +- src/rdi_from_dwarf/rdi_from_dwarf.c | 8 ++-- .../rad_lzb_simple/rad_lzb_simple.h | 2 +- src/third_party/radsort/radsort.h | 6 +-- src/torture/torture.c | 2 +- 20 files changed, 78 insertions(+), 75 deletions(-) diff --git a/build.sh b/build.sh index a3d11950..c481af6e 100755 --- a/build.sh +++ b/build.sh @@ -49,6 +49,10 @@ if [[ "${release:-0}" == "1" ]]; then echo "[release mode]"; compile="$compile elif [[ "${debug:-1}" == "1" ]]; then echo "[debug mode]"; compile="$compiler $cc_debug"; fi +compile() { + $compile -Wl,--start-group "$@" blake3.a -latomic -Wl,--end-group +} + # --- Prep Directories -------------------------------------------------------- mkdir -p build local @@ -75,11 +79,11 @@ fi # --- Build Everything (@build_targets) --------------------------------------- cd build -if [[ "${raddbg:-0}" == "1" ]]; then didbuild=1 && $compile ../src/raddbg/raddbg_main.c $cc_icon $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg; fi -if [[ "${raddbg_non_graphical:-0}" == "1" ]]; then didbuild=1 && $compile ../src/raddbg/raddbg_main.c -DWM_STUB=1 -DR_BACKEND=R_BACKEND_STUB $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg_non_graphical; fi -if [[ "${radbin:-0}" == "1" ]]; then didbuild=1 && $compile ../src/radbin/radbin_main.c $cc_link -o radbin; fi -if [[ "${radlink:-0}" == "1" ]]; then didbuild=1 && $compile ../src/linker/lnk.c $cc_link -o radlink blake3.a; fi -if [[ "${torture:-0}" == "1" ]]; then didbuild=1 && $compile ../src/torture/torture_main.c $cc_link $cc_os_gfx $cc_render $cc_font_provider -o torture; fi +if [[ "${raddbg:-0}" == "1" ]]; then didbuild=1 && compile ../src/raddbg/raddbg_main.c $cc_icon $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg; fi +if [[ "${raddbg_non_graphical:-0}" == "1" ]]; then didbuild=1 && compile ../src/raddbg/raddbg_main.c -DWM_STUB=1 -DR_BACKEND=R_BACKEND_STUB $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg_non_graphical; fi +if [[ "${radbin:-0}" == "1" ]]; then didbuild=1 && compile ../src/radbin/radbin_main.c $cc_link -o radbin; fi +if [[ "${radlink:-0}" == "1" ]]; then didbuild=1 && compile ../src/linker/lnk.c $cc_link -o radlink; fi +if [[ "${torture:-0}" == "1" ]]; then didbuild=1 && compile ../src/torture/torture_main.c $cc_link $cc_os_gfx $cc_render $cc_font_provider -o torture; fi cd .. # --- Warn On No Builds ------------------------------------------------------- diff --git a/src/base/base_core.h b/src/base/base_core.h index 43831617..05b9213c 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -54,7 +54,7 @@ #if COMPILER_MSVC # define force_inline __forceinline #elif COMPILER_CLANG || COMPILER_GCC -# define force_inline __attribute__((always_inline)) +# define force_inline inline __attribute__((always_inline)) #else # error force_inline not defined for this compiler. #endif diff --git a/src/base/base_log.c b/src/base/base_log.c index 5adf5a8a..4f471a05 100644 --- a/src/base/base_log.c +++ b/src/base/base_log.c @@ -4,11 +4,7 @@ //////////////////////////////// //~ rjf: Globals/Thread-Locals -C_LINKAGE thread_static Log *log_active; -#if !BUILD_SUPPLEMENTARY_UNIT -C_LINKAGE thread_static Log *log_active = 0; -#endif - +thread_static Log *log_active; String8 g_logs_folder; //////////////////////////////// diff --git a/src/base/base_log.h b/src/base/base_log.h index 8ef71a37..ef30a18d 100644 --- a/src/base/base_log.h +++ b/src/base/base_log.h @@ -40,6 +40,7 @@ struct Log // Globals extern String8 g_logs_folder; +extern thread_static Log *log_active; //////////////////////////////// //~ rjf: Log Creation/Selection diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 209b0e37..60c69eed 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -196,7 +196,7 @@ str32_zero(void) } internal String8 -str8_cstring(char *c) +str8_cstring(const char *c) { String8 result = {(U8*)c, cstring8_length((U8*)c)}; return result; @@ -3076,7 +3076,7 @@ str8_deserial_read_sleb128(String8 string, U64 off, S64 *value_out) //////////////////////////////// -force_inline int +internal force_inline int str8_compar(String8 a, String8 b, B32 ignore_case) { U64 size = Min(a.size, b.size); @@ -3095,19 +3095,19 @@ str8_compar(String8 a, String8 b, B32 ignore_case) return cmp; } -force_inline int +internal force_inline int str8_compar_ignore_case(const void *a, const void *b) { return str8_compar(*(String8*)a, *(String8*)b, 1); } -force_inline int +internal force_inline int str8_compar_case_sensitive(const void *a, const void *b) { return str8_compar(*(String8*)a, *(String8*)b, 0); } -force_inline int +internal force_inline int str8_is_before_case_sensitive(const void *a, const void *b) { int cmp = str8_compar_case_sensitive(a, b); diff --git a/src/base/base_strings.h b/src/base/base_strings.h index 84cd6aa0..27cdc9d1 100644 --- a/src/base/base_strings.h +++ b/src/base/base_strings.h @@ -180,7 +180,7 @@ internal String16 str16_zero(void); internal String32 str32(U32 *str, U64 size); internal String32 str32_range(U32 *first, U32 *one_past_last); internal String32 str32_zero(void); -internal String8 str8_cstring(char *c); +internal String8 str8_cstring(const char *c); internal String16 str16_cstring(U16 *c); internal String32 str32_cstring(U32 *c); internal String8 str8_cstring_capped(void *cstr, void *cap); diff --git a/src/base/base_thread_context.c b/src/base/base_thread_context.c index 42163610..617fcc3d 100644 --- a/src/base/base_thread_context.c +++ b/src/base/base_thread_context.c @@ -4,10 +4,7 @@ //////////////////////////////// //~ rjf: Globals -C_LINKAGE thread_static TCTX *tctx_thread_local; -#if !BUILD_SUPPLEMENTARY_UNIT -C_LINKAGE thread_static TCTX *tctx_thread_local = 0; -#endif +thread_static TCTX *tctx_thread_local; //////////////////////////////// //~ rjf: Thread Context Functions diff --git a/src/base/base_thread_context.h b/src/base/base_thread_context.h index 494d1804..584d83c7 100644 --- a/src/base/base_thread_context.h +++ b/src/base/base_thread_context.h @@ -79,6 +79,11 @@ struct TCTX U64 *progress_target_ptr; }; +//////////////////////////////// +// Globals + +extern thread_static TCTX *tctx_thread_local; + //////////////////////////////// //~ rjf: Thread Context Functions diff --git a/src/lib_rdi/rdi_parse.c b/src/lib_rdi/rdi_parse.c index 375a07fb..425e48f3 100644 --- a/src/lib_rdi/rdi_parse.c +++ b/src/lib_rdi/rdi_parse.c @@ -67,7 +67,7 @@ typedef S32 RAD_S32; #if defined(_MSC_VER) # define RADFORCEINLINE __forceinline #elif defined(__clang__) || defined(__GNUC__) -# define RADFORCEINLINE __attribute__((always_inline)) +# define RADFORCEINLINE inline __attribute__((always_inline)) #else # error need force inline for this compiler #endif diff --git a/src/linker/base_ext/base_core.c b/src/linker/base_ext/base_core.c index 890b0831..0d6b1f26 100644 --- a/src/linker/base_ext/base_core.c +++ b/src/linker/base_ext/base_core.c @@ -23,7 +23,7 @@ make_version(U64 major, U64 minor) return version; } -internal int +internal force_inline int version_compar(Version a, Version b) { int cmp = 0; @@ -48,7 +48,7 @@ isect_off(U32 isect, U32 off) return result; } -internal int +internal force_inline int u16_compar(const void *raw_a, const void *raw_b) { U16 a = *(U16*)raw_a; @@ -59,7 +59,7 @@ u16_compar(const void *raw_a, const void *raw_b) return result; } -internal int +internal force_inline int u32_compar(const void *raw_a, const void *raw_b) { U32 a = *(U32*)raw_a; @@ -70,7 +70,7 @@ u32_compar(const void *raw_a, const void *raw_b) return result; } -internal int +internal force_inline int u64_compar(const void *raw_a, const void *raw_b) { U64 a = *(const U64*)raw_a; @@ -79,7 +79,7 @@ u64_compar(const void *raw_a, const void *raw_b) return result; } -internal int +internal force_inline int u64_compar_inv(const void *raw_a, const void *raw_b) { U64 a = *(const U64*)raw_a; @@ -88,7 +88,7 @@ u64_compar_inv(const void *raw_a, const void *raw_b) return result; } -internal int +internal force_inline int u16_compar_is_before(void *raw_a, void *raw_b) { U16 *a = (U16 *)raw_a; @@ -97,7 +97,7 @@ u16_compar_is_before(void *raw_a, void *raw_b) return is_before; } -internal int +internal force_inline int u32_compar_is_before(void *raw_a, void *raw_b) { U32 *a = (U32 *)raw_a; @@ -106,7 +106,7 @@ u32_compar_is_before(void *raw_a, void *raw_b) return is_before; } -internal int +internal force_inline int u64_compar_is_before(void *raw_a, void *raw_b) { U64 *a = (U64 *)raw_a; @@ -115,7 +115,7 @@ u64_compar_is_before(void *raw_a, void *raw_b) return is_before; } -internal int +internal force_inline int u8_is_before(void *raw_a, void *raw_b) { U8 *a = (U8 *) raw_a; @@ -123,7 +123,7 @@ u8_is_before(void *raw_a, void *raw_b) return *a < *b; } -internal int +internal force_inline int u16_is_before(void *raw_a, void *raw_b) { U16 *a = (U16 *) raw_a; @@ -131,7 +131,7 @@ u16_is_before(void *raw_a, void *raw_b) return *a < *b; } -internal int +internal force_inline int u32_is_before(void *raw_a, void *raw_b) { U32 *a = (U32 *) raw_a; @@ -139,7 +139,7 @@ u32_is_before(void *raw_a, void *raw_b) return *a < *b; } -internal int +internal force_inline int u64_is_before(void *raw_a, void *raw_b) { U64 *a = (U64 *) raw_a; @@ -147,7 +147,7 @@ u64_is_before(void *raw_a, void *raw_b) return *a < *b; } -internal int +internal force_inline int pair_u32_is_before_v0(void *raw_a, void *raw_b) { PairU32 *a = raw_a; @@ -155,7 +155,7 @@ pair_u32_is_before_v0(void *raw_a, void *raw_b) return a->v0 < b->v0; } -internal int +internal force_inline int pair_u32_is_before(void *raw_a, void *raw_b) { PairU32 *a = raw_a; @@ -163,7 +163,7 @@ pair_u32_is_before(void *raw_a, void *raw_b) return a->v1 < b->v1; } -internal int +internal force_inline int pair_u64_is_before_v0(void *raw_a, void *raw_b) { PairU64 *a = raw_a; @@ -171,7 +171,7 @@ pair_u64_is_before_v0(void *raw_a, void *raw_b) return a->v0 < b->v0; } -internal int +internal force_inline int pair_u64_is_before_v1(void *raw_a, void *raw_b) { PairU64 *a = raw_a; @@ -179,7 +179,7 @@ pair_u64_is_before_v1(void *raw_a, void *raw_b) return a->v1 < b->v1; } -internal int +internal force_inline int pair_u32_compar_v0(const void *raw_a, const void *raw_b) { const PairU32 *a = raw_a; @@ -187,7 +187,7 @@ pair_u32_compar_v0(const void *raw_a, const void *raw_b) return u32_compar(&a->v0, &b->v0); } -internal int +internal force_inline int pair_u64_compar_v0(const void *raw_a, const void *raw_b) { const PairU64 *a = raw_a; @@ -195,7 +195,7 @@ pair_u64_compar_v0(const void *raw_a, const void *raw_b) return u64_compar(&a->v0, &b->v0); } -internal int +internal force_inline int pair_u64_compar_v1(const void *raw_a, const void *raw_b) { const PairU64 *a = raw_a; diff --git a/src/linker/codeview_ext/codeview.c b/src/linker/codeview_ext/codeview.c index 11592f7a..c76f78a5 100644 --- a/src/linker/codeview_ext/codeview.c +++ b/src/linker/codeview_ext/codeview.c @@ -1635,7 +1635,7 @@ cv_c13_voff_map_compar(const void *raw_a, const void *raw_b) return cmp; } -force_inline int +internal force_inline int cv_c13_voff_map_is_before(void *raw_a, void *raw_b) { return cv_c13_voff_map_compar(raw_a, raw_b) < 0; diff --git a/src/linker/hash_table.c b/src/linker/hash_table.c index 66c9938c..23eddc0e 100644 --- a/src/linker/hash_table.c +++ b/src/linker/hash_table.c @@ -655,11 +655,11 @@ hash_map_purge(HashMap *hm) //////////////////////////////// -force_inline HASH_MAP_KEY_MATCH(hash_map_match_u32) { return a->key_u32 == b->key_u32; } -force_inline HASH_MAP_KEY_MATCH(hash_map_match_u64) { return a->key_u64 == b->key_u64; } -force_inline HASH_MAP_KEY_MATCH(hash_map_match_raw) { return a->key_raw == b->key_raw; } -force_inline HASH_MAP_KEY_MATCH(hash_map_match_string) { return str8_match(a->key_string, b->key_string, 0); } -force_inline HASH_MAP_KEY_MATCH(hash_map_match_path) { return str8_match(a->key_string, b->key_string, StringMatchFlag_CaseInsensitive|StringMatchFlag_SlashInsensitive); } +internal force_inline HASH_MAP_KEY_MATCH(hash_map_match_u32) { return a->key_u32 == b->key_u32; } +internal force_inline HASH_MAP_KEY_MATCH(hash_map_match_u64) { return a->key_u64 == b->key_u64; } +internal force_inline HASH_MAP_KEY_MATCH(hash_map_match_raw) { return a->key_raw == b->key_raw; } +internal force_inline HASH_MAP_KEY_MATCH(hash_map_match_string) { return str8_match(a->key_string, b->key_string, 0); } +internal force_inline HASH_MAP_KEY_MATCH(hash_map_match_path) { return str8_match(a->key_string, b->key_string, StringMatchFlag_CaseInsensitive|StringMatchFlag_SlashInsensitive); } internal U64 hash_map_hash_from_path(String8 path) @@ -949,29 +949,29 @@ hash_map_extract(Arena *arena, } // { keys, value } -force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_key_value) { MemoryCopy(buffer, kv, sizeof(*kv)); } +internal force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_key_value) { MemoryCopy(buffer, kv, sizeof(*kv)); } internal HashMapKeyValue * key_value_from_hash_map ( Arena *arena, HashMap *hm) { return hash_map_extract(arena, hm, hash_map_extract_key_value, sizeof(HashMapKeyValue), 0, 0).keys; } // keys -force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_key_u32) { MemoryCopy(buffer, &kv->key.key_u32, sizeof(kv->key.key_u32)); } -force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_key_u64) { MemoryCopy(buffer, &kv->key.key_u64, sizeof(kv->key.key_u64)); } -force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_key_string) { MemoryCopy(buffer, &kv->key.key_string, sizeof(kv->key.key_string)); } +internal force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_key_u32) { MemoryCopy(buffer, &kv->key.key_u32, sizeof(kv->key.key_u32)); } +internal force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_key_u64) { MemoryCopy(buffer, &kv->key.key_u64, sizeof(kv->key.key_u64)); } +internal force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_key_string) { MemoryCopy(buffer, &kv->key.key_string, sizeof(kv->key.key_string)); } internal U32 * keys_from_hash_map_u32 ( Arena *arena, HashMap *hm) { return hash_map_extract(arena, hm, hash_map_extract_key_u32, sizeof(U32), 0, 0).keys; } internal U64 * keys_from_hash_map_u64 ( Arena *arena, HashMap *hm) { return hash_map_extract(arena, hm, hash_map_extract_key_u64, sizeof(U64), 0, 0).keys; } internal String8 * keys_from_hash_map_string( Arena *arena, HashMap *hm) { return hash_map_extract(arena, hm, hash_map_extract_key_string, sizeof(String8), 0, 0).keys; } internal void * keys_from_hash_map_raw ( Arena *arena, HashMap *hm) { return hash_map_extract(arena, hm, hash_map_extract_key_string, sizeof(void *), 0, 0).keys; } // values -force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_value_u64) { MemoryCopy(buffer, &kv->value.value_u64, sizeof(kv->value.value_u64)); } -force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_value_raw) { MemoryCopy(buffer, &kv->value.value_raw, sizeof(kv->value.value_raw)); } +internal force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_value_u64) { MemoryCopy(buffer, &kv->value.value_u64, sizeof(kv->value.value_u64)); } +internal force_inline HASH_MAP_EXTRACT_FUNC(hash_map_extract_value_raw) { MemoryCopy(buffer, &kv->value.value_raw, sizeof(kv->value.value_raw)); } internal U64 * values_from_hash_map_u64(Arena *arena, HashMap *hm) { return hash_map_extract(arena, hm, 0, 0, hash_map_extract_value_u64, sizeof(U64)).values; } internal void * values_from_hash_map_raw(Arena *arena, HashMap *hm) {return hash_map_extract(arena, hm, 0, 0, hash_map_extract_value_raw, sizeof(void *)).values; } //////////////////////////////// -force_inline int hash_map_key_is_before_u32(void *a, void *b) { return ((HashMapKeyValue*)a)->key.key_u32 < ((HashMapKeyValue*)b)->key.key_u32; } -force_inline int hash_map_key_is_before_u64(void *a, void *b) { return ((HashMapKeyValue*)a)->key.key_u64 < ((HashMapKeyValue*)b)->key.key_u64; } -force_inline int hash_map_key_is_before_string(void *a, void *b) { return str8_is_before_case_sensitive(a, b); } +internal force_inline int hash_map_key_is_before_u32(void *a, void *b) { return ((HashMapKeyValue*)a)->key.key_u32 < ((HashMapKeyValue*)b)->key.key_u32; } +internal force_inline int hash_map_key_is_before_u64(void *a, void *b) { return ((HashMapKeyValue*)a)->key.key_u64 < ((HashMapKeyValue*)b)->key.key_u64; } +internal force_inline int hash_map_key_is_before_string(void *a, void *b) { return str8_is_before_case_sensitive(a, b); } internal void sort_hash_map_key_value_u32(HashMapKeyValue *pairs, U64 count) { radsort(pairs, count, hash_map_key_is_before_u32); } internal void sort_hash_map_key_value_u64(HashMapKeyValue *pairs, U64 count) { radsort(pairs, count, hash_map_key_is_before_u64); } diff --git a/src/linker/lnk.c b/src/linker/lnk.c index f24030a7..2a9bf39f 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1109,7 +1109,7 @@ lnk_lib_member_ref_is_before(void *raw_a, void *raw_b) g_sort_lib_member_context[(*b)->member_idx].link); } -force_inline int +internal force_inline int lnk_import_ref_is_before(void *raw_a, void *raw_b) { LNK_LibMemberRef **a_ptr = raw_a, **b_ptr = raw_b; diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index b79edf31..0c7a0a02 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -976,7 +976,7 @@ lnk_directive_info_from_raw_directives(Arena *arena, LNK_Obj *obj, String8List r return directive_info; } -force_inline int +internal force_inline int lnk_section_offset_symbol_is_before(void *raw_a, void *raw_b) { LNK_SectionOffsetSymbol *a = raw_a, *b = raw_b; @@ -1084,7 +1084,7 @@ lnk_debug_s_from_obj(Arena *arena, LNK_Obj *obj) return debug_s; } -force_inline int +internal force_inline int lnk_line_table_block_is_before(void *raw_a, void *raw_b) { LNK_LineTableBlock *a = raw_a, *b = raw_b; @@ -1094,7 +1094,7 @@ lnk_line_table_block_is_before(void *raw_a, void *raw_b) return key_a < key_b; } -force_inline int +internal force_inline int lnk_inline_range_is_before(void *raw_a, void *raw_b) { LNK_InlineRange *a = raw_a, *b = raw_b; diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index fbe2c9ae..f1084429 100644 --- a/src/linker/pdb_ext/pdb_builder.c +++ b/src/linker/pdb_ext/pdb_builder.c @@ -1833,14 +1833,14 @@ typedef struct PDB_PsiAddrMapNameSortTask Rng1U64 *batch_arr; } PDB_PsiAddrMapNameSortTask; -force_inline U64 +internal force_inline U64 psi_addr_map_sort_key(PDB_GsiSortRecord *record) { U64 key = ((U64)record->isect_off.isect << 32) | record->isect_off.off; return key; } -force_inline int +internal force_inline int psi_addr_map_name_compar_is_before(void *raw_a, void *raw_b) { PDB_PsiAddrMapSortRecord *a = raw_a; @@ -1903,7 +1903,7 @@ THREAD_POOL_TASK_FUNC(psi_addr_map_radix_sort_task) ProfEnd(); } -force_inline U64 +internal force_inline U64 psi_addr_map_name_sort_work(Rng1U64 run) { U64 count = dim_1u64(run); @@ -2084,7 +2084,7 @@ THREAD_POOL_TASK_FUNC(gsi_size_buckets_task) } } -force_inline int +internal force_inline int gsi_symbol_is_before(void *raw_a, void *raw_b) { CV_Symbol *a = *(CV_Symbol **)raw_a; @@ -2113,7 +2113,7 @@ gsi_symbol_is_before(void *raw_a, void *raw_b) return is_before; } -force_inline int +internal force_inline int gsi_pub_symbol_is_before(void *raw_a, void *raw_b) { CV_Symbol *a = *(CV_Symbol **)raw_a; diff --git a/src/pe/pe.h b/src/pe/pe.h index 169b47c1..a1d9d9a1 100644 --- a/src/pe/pe.h +++ b/src/pe/pe.h @@ -526,7 +526,7 @@ struct PE_IntelPdata #define PE_CODEVIEW_PDB20_MAGIC 0x3031424e #define PE_CODEVIEW_PDB70_MAGIC 0x53445352 -#define PE_CODEVIEW_RDI_MAGIC '0IDR' +#define PE_CODEVIEW_RDI_MAGIC 0x30494452 // 0IDR typedef struct PE_CvHeaderPDB20 PE_CvHeaderPDB20; struct PE_CvHeaderPDB20 diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index 3e05376b..ce77f122 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -2040,10 +2040,10 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) // This compiles to in DWARF: // // foo -> DW_TAG_ArrayType -> (A0) DW_TAG_Subrange [2] - // \ - // -> (B0) DW_TAG_PointerType -> (A1) DW_TAG_ArrayType -> DW_TAG_Subrange [3] - // \ - // -> (B1) DW_TAG_BaseType (int) + // | + // +-> (B0) DW_TAG_PointerType -> (A1) DW_TAG_ArrayType -> DW_TAG_Subrange [3] + // | + // +-> (B1) DW_TAG_BaseType (int) // // RDI expects: // diff --git a/src/third_party/rad_lzb_simple/rad_lzb_simple.h b/src/third_party/rad_lzb_simple/rad_lzb_simple.h index 719639d4..062ddd8c 100644 --- a/src/third_party/rad_lzb_simple/rad_lzb_simple.h +++ b/src/third_party/rad_lzb_simple/rad_lzb_simple.h @@ -61,7 +61,7 @@ typedef S32 RAD_S32; #if defined(_MSC_VER) # define RADFORCEINLINE __forceinline #elif defined(__clang__) || defined(__GNUC__) -# define RADFORCEINLINE __attribute__((always_inline)) +# define RADFORCEINLINE inline __attribute__((always_inline)) #else # error need force inline for this compiler #endif diff --git a/src/third_party/radsort/radsort.h b/src/third_party/radsort/radsort.h index ee84f863..dbfbd11c 100644 --- a/src/third_party/radsort/radsort.h +++ b/src/third_party/radsort/radsort.h @@ -24,7 +24,7 @@ #define RSFORCEINLINE __forceinline __declspec(safebuffers) #define CompilerReset(ptr) __assume(ptr) #else -#define RSFORCEINLINE __attribute__((always_inline)) +#define RSFORCEINLINE inline __attribute__((always_inline)) #define CompilerReset(ptr) #endif @@ -137,7 +137,7 @@ radsortmovesize( sizeof( (start)[0] ) ), \ //=================================================================================================== // small heap sort - this sort is around 200 bytes compiled - can use directly when size is important -RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper ) +internal RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper ) { void * left; void * right; @@ -430,7 +430,7 @@ rs_es = element_size; #define RSS_MAX_RECURSE 128 -RSFORCEINLINE void radsortinternal( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper, move_func * mover, rs_small_sort_func * small_sort, size_t small_sort_thres, void * tmp ) +static RSFORCEINLINE void radsortinternal( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper, move_func * mover, rs_small_sort_func * small_sort, size_t small_sort_thres, void * tmp ) { void * left; size_t length; diff --git a/src/torture/torture.c b/src/torture/torture.c index bd33a8f7..c703c589 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -948,7 +948,7 @@ t_match_linef(String8 *output, char *fmt, ...) return is_match; } -force_inline int +internal force_inline int t_test_info_is_before(void *a_, void *b_) { TestInfo **a = a_; From 027730f9f612d3f7bf7e15095ff15d17172e9fe4 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 14:58:21 -0700 Subject: [PATCH 137/251] enable GCC build --- .github/workflows/builds.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 0ad9c629..c881c968 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -26,7 +26,7 @@ jobs: compiler: - clang-17 - clang-22 - # TODO: - gcc + - gcc name: ${{ matrix.target }}-${{ matrix.compiler }}-ubuntu24 @@ -65,11 +65,15 @@ jobs: - name: Build (debug) shell: bash - run: ./build.sh "${{ matrix.target }}" debug + run: | + export CC_BUILD="${{ matrix.compiler }}" + ./build.sh "${{ matrix.target }}" debug ${CC_BUILD%%-*} - name: Build (release) shell: bash - run: ./build.sh "${{ matrix.target }}" release + run: | + export CC_BUILD="${{ matrix.compiler }}" + ./build.sh "${{ matrix.target }}" release ${CC_BUILD%%-*} build-windows-vs2022: runs-on: windows-2022 From 8d23d310976bdd9f999319d5cc949eca3c880744 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 15:09:08 -0700 Subject: [PATCH 138/251] internal on force inline function --- src/linker/lnk_hasher.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linker/lnk_hasher.h b/src/linker/lnk_hasher.h index b4a6512f..7276adc8 100644 --- a/src/linker/lnk_hasher.h +++ b/src/linker/lnk_hasher.h @@ -27,7 +27,7 @@ typedef struct LNK_Hasher #if BUILD_DEBUG #define LNK_HASHER_FN internal #else -#define LNK_HASHER_FN force_inline +#define LNK_HASHER_FN internal force_inline #endif LNK_HASHER_FN void lnk_hasher_init (LNK_Hasher *hasher, LNK_HashKind kind); From 3093f23de88fcbde1884c9ecbeb5b64dd86fd61b Mon Sep 17 00:00:00 2001 From: Henrik Karlsson Date: Thu, 3 Sep 2026 15:12:09 -0700 Subject: [PATCH 139/251] radlink: satisfy default-library names with explicitly supplied paths --- src/linker/lnk.c | 16 ++++++++ src/linker/lnk.h | 1 + src/linker/tests/linker_tests.c | 71 +++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 2a9bf39f..aedaa99e 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -818,6 +818,7 @@ lnk_inputer_init(void) inputer->arena = arena; inputer->objs_ht = hash_table_init(arena, 0x20000); inputer->libs_ht = hash_table_init(arena, 0x1000); + inputer->cmd_lib_names_ht = hash_table_init(arena, 0x100); inputer->missing_lib_ht = hash_table_init(arena, 0x100); return inputer; } @@ -928,6 +929,13 @@ lnk_inputer_push_lib_thin(LNK_Inputer *inputer, LNK_Config *config, LNK_InputSou if (lnk_is_lib_disallowed(config, path)) { goto exit; } + // An explicitly supplied library also satisfies a bare /DEFAULTLIB name, + // even when its directory is not on LIBPATH. Do not strip directory parts + // from the request: path-qualified defaults still name distinct libraries. + input = hash_table_search_path_raw(inputer->cmd_lib_names_ht, path); + if (input) { + goto exit; + } } // was library already loaded? @@ -964,6 +972,14 @@ lnk_inputer_push_lib_thin(LNK_Inputer *inputer, LNK_Config *config, LNK_InputSou } exit:; + if (input && input_source == LNK_InputSource_CmdLine) { + // Keep basename aliases separate from path identity so two explicitly + // supplied libraries with the same basename can both be linked. + String8 name = str8_skip_last_slash(input->path); + if (!hash_table_search_path(inputer->cmd_lib_names_ht, name)) { + hash_table_push_path_raw(inputer->arena, inputer->cmd_lib_names_ht, name, input); + } + } scratch_end(scratch); return input; } diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 2fd14a9c..9291e5db 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -64,6 +64,7 @@ typedef struct LNK_Inputer LNK_InputList new_objs; HashTable *libs_ht; + HashTable *cmd_lib_names_ht; HashTable *missing_lib_ht; LNK_InputList libs; LNK_InputList new_libs[LNK_InputSource_Count]; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 8e07d43d..2a60c78b 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -2663,6 +2663,77 @@ TEST(base_relocs) } } +TEST(default_lib_matches_explicit_path) +{ + COFF_ObjWriter *entry = coff_obj_writer_alloc(0, COFF_MachineType_X64); + COFF_ObjSection *text = coff_obj_writer_push_section(entry, str8_lit(".text"), + COFF_SectionFlag_CntCode|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemExecute|COFF_SectionFlag_Align1Bytes, + str8_lit("\x31\xc0\xc3")); + coff_obj_writer_push_symbol_extern_func(entry, str8_lit("entry"), 0, text); + T_Ok(t_write_file(str8_lit("entry.obj"), coff_obj_writer_serialize(arena, entry))); + coff_obj_writer_release(&entry); + char *dirs[] = {"sdk libs", "other libs"}; + char *symbols[] = {"first_provider", "second_provider"}; + for EachIndex(i, ArrayCount(dirs)) { + T_Ok(make_directory(t_make_file_path(arena, str8_cstring(dirs[i])))); + COFF_ObjWriter *obj = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_symbol_abs(obj, str8_cstring(symbols[i]), 1, COFF_SymStorageClass_External); + COFF_LibWriter *lib = coff_lib_writer_alloc(); + coff_lib_writer_push_obj(lib, str8_lit("provider.obj"), coff_obj_writer_serialize(arena, obj)); + T_Ok(t_write_file(str8f(arena, "%s/provider.lib", dirs[i]), coff_lib_writer_serialize(arena, lib, 0, 0, 1))); + coff_lib_writer_release(&lib); + coff_obj_writer_release(&obj); + } + + char *args = "/subsystem:console /entry:entry /out:default.exe entry.obj /include:first_provider"; + String8 paths[] = {str8_lit("sdk libs/provider.lib"), t_make_file_path(arena, str8_lit("sdk libs/provider.lib"))}; + // Debug builds also write normal progress to stderr; reject only the missing-library diagnostic. + char *names[] = {"provider.lib", "PROVIDER.LIB", "provider"}; + // RAD accepts an omitted extension; LLD checks visited names before adding it. + U64 name_count = t_id_linker() == Linker_radlink ? ArrayCount(names) : 2; + for EachIndex(path_idx, ArrayCount(paths)) { + for EachIndex(name_idx, name_count) { + t_invoke_linkerf("%s \"%S\" /defaultlib:%s", args, paths[path_idx], names[name_idx]); + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library"), 0) == g_errors.size); + } + } + + // Embedded directives use a separate input-source queue from /DEFAULTLIB. + COFF_ObjWriter *directive = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_directive(directive, str8_lit("/DEFAULTLIB:provider.lib")); + T_Ok(t_write_file(str8_lit("directive.obj"), coff_obj_writer_serialize(arena, directive))); + coff_obj_writer_release(&directive); + t_invoke_linkerf("%s \"%S\" directive.obj", args, paths[1]); + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library"), 0) == g_errors.size); + + // A basename match must not swallow another explicitly named library, or a + // path-qualified default, with the same basename but a different provider. + t_invoke_linkerf("%s \"%S\" \"other libs/provider.lib\" /include:second_provider", args, paths[1]); + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library"), 0) == g_errors.size); + t_invoke_linkerf("%s \"%S\" /defaultlib:\"other libs/provider.lib\" /include:second_provider", args, paths[1]); + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library"), 0) == g_errors.size); + + // Missing defaults must still warn; explicit bare paths are not defaults. + t_invoke_linkerf("%s \"%S\" /defaultlib:missing_provider.lib", args, paths[1]); + if (t_id_linker() == Linker_radlink) { + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library `missing_provider.lib`"), 0) < g_errors.size); + } else { + T_Ok(g_last_exit_code != 0); + } + t_invoke_linkerf("%s \"%S\" provider.lib", args, paths[1]); + if (t_id_linker() == Linker_radlink) { + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library `provider.lib`"), 0) < g_errors.size); + } else { + T_Ok(g_last_exit_code != 0); + } +} + TEST(simple_lib_test) { String8 test_payload = str8_lit("The quick brown fox jumps over the lazy dog"); From 6f824e4b09dcf4ecb4f311d6cce6e38d28aee663 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 17:18:51 -0700 Subject: [PATCH 140/251] minor cleanups * updated the comments * simplified the test --- src/linker/lnk.c | 11 ++++----- src/linker/tests/linker_tests.c | 41 +++++++++++++-------------------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index aedaa99e..d5d0c38a 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -921,17 +921,16 @@ lnk_inputer_push_lib_thin(LNK_Inputer *inputer, LNK_Config *config, LNK_InputSou LNK_Input *input = 0; - // default libraries may omit extension if (input_source == LNK_InputSource_Default || input_source == LNK_InputSource_Obj) { + // default libraries may omit extension if (!str8_ends_with(path, str8_lit(".lib"), StringMatchFlag_CaseInsensitive)) { path = push_str8f(scratch.arena, "%S.lib", path); } if (lnk_is_lib_disallowed(config, path)) { goto exit; } - // An explicitly supplied library also satisfies a bare /DEFAULTLIB name, - // even when its directory is not on LIBPATH. Do not strip directory parts - // from the request: path-qualified defaults still name distinct libraries. + + // default libraries may duplicate libs that are specified using full path input = hash_table_search_path_raw(inputer->cmd_lib_names_ht, path); if (input) { goto exit; @@ -973,8 +972,8 @@ lnk_inputer_push_lib_thin(LNK_Inputer *inputer, LNK_Config *config, LNK_InputSou exit:; if (input && input_source == LNK_InputSource_CmdLine) { - // Keep basename aliases separate from path identity so two explicitly - // supplied libraries with the same basename can both be linked. + // store command-line libraries by basename; keep only the first mapping because + // /DEFAULTLIB only needs to know whether a matching library was explicitly supplied String8 name = str8_skip_last_slash(input->path); if (!hash_table_search_path(inputer->cmd_lib_names_ht, name)) { hash_table_push_path_raw(inputer->arena, inputer->cmd_lib_names_ht, name, input); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 2a60c78b..edaa55b9 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -2665,16 +2665,15 @@ TEST(base_relocs) TEST(default_lib_matches_explicit_path) { - COFF_ObjWriter *entry = coff_obj_writer_alloc(0, COFF_MachineType_X64); - COFF_ObjSection *text = coff_obj_writer_push_section(entry, str8_lit(".text"), - COFF_SectionFlag_CntCode|COFF_SectionFlag_MemRead|COFF_SectionFlag_MemExecute|COFF_SectionFlag_Align1Bytes, - str8_lit("\x31\xc0\xc3")); + COFF_ObjWriter *entry = coff_obj_writer_alloc(0, COFF_MachineType_X64); + COFF_ObjSection *text = coff_obj_writer_push_section(entry, str8_lit(".text"), PE_TEXT_SECTION_FLAGS, str8_lit("\x31\xc0\xc3")); coff_obj_writer_push_symbol_extern_func(entry, str8_lit("entry"), 0, text); T_Ok(t_write_file(str8_lit("entry.obj"), coff_obj_writer_serialize(arena, entry))); coff_obj_writer_release(&entry); - char *dirs[] = {"sdk libs", "other libs"}; + + char *dirs[] = {"sdk libs", "other libs"}; char *symbols[] = {"first_provider", "second_provider"}; - for EachIndex(i, ArrayCount(dirs)) { + for EachElement(i, dirs) { T_Ok(make_directory(t_make_file_path(arena, str8_cstring(dirs[i])))); COFF_ObjWriter *obj = coff_obj_writer_alloc(0, COFF_MachineType_X64); coff_obj_writer_push_symbol_abs(obj, str8_cstring(symbols[i]), 1, COFF_SymStorageClass_External); @@ -2685,14 +2684,13 @@ TEST(default_lib_matches_explicit_path) coff_obj_writer_release(&obj); } - char *args = "/subsystem:console /entry:entry /out:default.exe entry.obj /include:first_provider"; - String8 paths[] = {str8_lit("sdk libs/provider.lib"), t_make_file_path(arena, str8_lit("sdk libs/provider.lib"))}; // Debug builds also write normal progress to stderr; reject only the missing-library diagnostic. - char *names[] = {"provider.lib", "PROVIDER.LIB", "provider"}; - // RAD accepts an omitted extension; LLD checks visited names before adding it. - U64 name_count = t_id_linker() == Linker_radlink ? ArrayCount(names) : 2; - for EachIndex(path_idx, ArrayCount(paths)) { - for EachIndex(name_idx, name_count) { + char *names[] = {"provider.lib", "PROVIDER.LIB", "provider"}; + char *args = "/subsystem:console /entry:entry /out:default.exe entry.obj /include:first_provider"; + String8 paths[] = {str8_lit("sdk libs/provider.lib"), t_make_file_path(arena, str8_lit("sdk libs/provider.lib"))}; + + for EachElement(path_idx, paths) { + for EachElement(name_idx, names) { t_invoke_linkerf("%s \"%S\" /defaultlib:%s", args, paths[path_idx], names[name_idx]); T_Ok(g_last_exit_code == 0); T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library"), 0) == g_errors.size); @@ -2719,19 +2717,12 @@ TEST(default_lib_matches_explicit_path) // Missing defaults must still warn; explicit bare paths are not defaults. t_invoke_linkerf("%s \"%S\" /defaultlib:missing_provider.lib", args, paths[1]); - if (t_id_linker() == Linker_radlink) { - T_Ok(g_last_exit_code == 0); - T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library `missing_provider.lib`"), 0) < g_errors.size); - } else { - T_Ok(g_last_exit_code != 0); - } + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library `missing_provider.lib`"), 0) < g_errors.size); + t_invoke_linkerf("%s \"%S\" provider.lib", args, paths[1]); - if (t_id_linker() == Linker_radlink) { - T_Ok(g_last_exit_code == 0); - T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library `provider.lib`"), 0) < g_errors.size); - } else { - T_Ok(g_last_exit_code != 0); - } + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, str8_lit("unable to find library `provider.lib`"), 0) < g_errors.size); } TEST(simple_lib_test) From 8649b2886538f6eb4ab5aec59c724906bb7d9ad9 Mon Sep 17 00:00:00 2001 From: Henrik Karlsson Date: Thu, 3 Sep 2026 16:08:15 -0700 Subject: [PATCH 141/251] radlink: derive MSF header page count from serialized extent --- src/linker/pdb_ext/msf_builder.c | 11 +++-- src/linker/tests/linker_tests.c | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/linker/pdb_ext/msf_builder.c b/src/linker/pdb_ext/msf_builder.c index b19560af..1d07a27a 100644 --- a/src/linker/pdb_ext/msf_builder.c +++ b/src/linker/pdb_ext/msf_builder.c @@ -635,9 +635,10 @@ msf_find_max_pn_(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNu // FPM is empty if (msb_idx >= hi) { break; } - // hit FPM page -- keep going + // Skip reserved FPM pages, but not slot 0: after the file header's + // interval, that slot can contain stream data. U64 k = msb_idx % fpm_interval_wrong; - if (k < 3) { + if (k == MSF_FPM0 || k == MSF_FPM1) { hi = msb_idx; continue; } @@ -648,7 +649,7 @@ msf_find_max_pn_(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNu // stop if there is a page if (bit_idx != max_U64) { - max_pn = Max(bit_idx, 2) + fpm_pn_idx * fpm_interval_correct; + max_pn = Max(bit_idx + fpm_pn_idx * fpm_interval_correct, MSF_FPM1); break; } } @@ -1607,7 +1608,9 @@ msf_build_header(MSF_Context *msf, MSF_UInt stream_table_size) MemoryCopy(&header.magic[0], &msf_msf70_magic[0], sizeof(msf_msf70_magic)); header.page_size = msf->page_size; header.active_fpm = msf->active_fpm; - header.page_count = msf->page_count; + // The allocator also counts reserved FPM pages beyond the serialized extent. + // The stream table and root are finalized; use the same extent as the writer. + header.page_count = (MSF_UInt)(msf_get_save_size(msf) / msf->page_size); header.stream_table_size = stream_table_size; header.unknown = 0; header.root_pn = msf->root_page_list.first->pn; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index edaa55b9..8f413956 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -7121,6 +7121,83 @@ TEST(get_msf_stream_pages) msf_release(msf); } +TEST(msf_header_matches_saved_extent) +{ + MSF_UInt page_sizes[] = {512, 4096}; + MSF_UInt fpms[] = {MSF_FPM0, MSF_FPM1}; + for EachIndex(size_idx, ArrayCount(page_sizes)) { + MSF_UInt page_size = page_sizes[size_idx]; + MSF_PageNumber last_pages[] = {68, page_size - 1, page_size, page_size + 3, + page_size * 8 - 1, page_size * 8, page_size * 8 + 3}; + // Cover an entire FPM's bitmap with small pages, and the default page size's + // first interval without making this a large-memory test. + U64 case_count = page_size == 512 ? ArrayCount(last_pages) : 4; + for EachIndex(fpm_idx, ArrayCount(fpms)) { + for EachIndex(case_idx, case_count) { + Temp temp = temp_begin(arena); + MSF_Context *msf = msf_alloc(page_size, fpms[fpm_idx]); + + // Leave low pages for the stream table and root so that the final data + // page, not metadata, determines EOF. This also exercises a sparse file. + MSF_PageList gap = msf_alloc_pages(msf, 64); + T_Ok(gap.first->pn == 4 && gap.last->pn == 67); + MSF_PageNumber last_pn = last_pages[case_idx]; + MSF_UInt data_page_count = 0; + for (MSF_PageNumber pn = 68; pn <= last_pn; ++pn) { + MSF_UInt slot = pn % page_size; + data_page_count += slot != MSF_FPM0 && slot != MSF_FPM1; + } + String8 payload = str8(push_array_no_zero(arena, U8, (U64)data_page_count * page_size), + (U64)data_page_count * page_size); + MemorySet(payload.str, 0xA5, payload.size); + MSF_StreamNumber sn = msf_stream_alloc(msf); + T_Ok(msf_stream_write(msf, sn, payload.str, (MSF_UInt)payload.size)); + T_Ok(msf_find_stream(msf, sn)->page_list.last->pn == last_pn); + msf_free_pages(msf, &gap); + T_Ok(msf_build(msf) == MSF_Error_OK); + T_Ok(msf->root_page_list.last->pn < last_pn); + T_Ok(msf->st_page_list.last->pn < last_pn); + + U64 save_size = msf_get_save_size(msf); + T_Ok(save_size == ((U64)last_pn + 1) * page_size); + String8 saved = str8(push_array_no_zero(arena, U8, save_size), save_size); + T_Ok(msf_save(msf, saved.str, saved.size)); + MSF_Header70 *header = (MSF_Header70 *)saved.str; + T_Ok((U64)header->page_count * header->page_size == saved.size); + String8List pages = msf_get_page_data_nodes(arena, msf); + T_Ok(str8_match(saved, str8_list_join(arena, &pages, 0), 0)); + + // Size agreement alone must not conceal a truncated final stream page. + MSF_Parsed *parsed = msf_parsed_from_data(arena, saved); + T_Ok(parsed != 0); + if (parsed != 0 && sn < parsed->stream_count) { + T_Ok(str8_match(parsed->streams[sn], payload, 0)); + } else { + T_Ok(0); + } + msf_release(msf); + temp_end(temp); + } + } + } +} + +TEST(pdb_header_matches_file_size) +{ + T_Ok(t_write_entry_obj()); + t_invoke_linkerf("/subsystem:console /entry:entry /debug:full /out:pages.exe /pdb:pages.pdb /pdbstripped:pages.stripped.pdb entry.obj"); + T_Ok(g_last_exit_code == 0); + char *pdb_paths[] = {"pages.pdb", "pages.stripped.pdb"}; + for EachIndex(i, ArrayCount(pdb_paths)) { + String8 pdb = t_read_file(arena, str8_cstring(pdb_paths[i])); + T_Ok(pdb.size >= sizeof(MSF_Header70)); + if (pdb.size >= sizeof(MSF_Header70)) { + MSF_Header70 *header = (MSF_Header70 *)pdb.str; + T_Ok((U64)header->page_count * header->page_size == pdb.size); + } + } +} + internal String8 data_from_pdb(Arena *arena, PDB_Context *pdb) { From 1603bcf11eb7bb76be0ecc5c93c7e2e0b7cbecdb Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 21:32:59 -0700 Subject: [PATCH 142/251] tests: make determ_test artifacts reproducible --- src/linker/tests/linker_tests.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 8f413956..95e3711e 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -8175,15 +8175,16 @@ TEST(infer_asan) TEST(determ_test) { // compile the test target (torture) - t_invoke_cl("/fsanitize=address /c /Z7 /DBUILD_GIT_HASH=Stringify() /Fo:test.obj -I%S /Zc:preprocessor %S/torture/torture_main.c", t_src_path(), t_src_path()); + t_invoke_cl("/Brepro /fsanitize=address /c /Z7 /DBUILD_GIT_HASH=Stringify() /Fo:test.obj -I%S /Zc:preprocessor %S/torture/torture_main.c", t_src_path(), t_src_path()); T_Ok(g_last_exit_code == 0); U64 run_count = 25; T_Ok(run_count > 1); String8 test_path = t_make_file_path(arena, str8_lit("test.obj")); + String8 lib_path = str8_chop_last_slash(t_radlink_path()); // single-threaded link - t_invoke_linkerf("%S /debug:full /rad_time_stamp:0 /rad_workers:1 /pdbaltpath:main.pdb /rad_log:-all /rad_ignore:74 /out:main.exe", test_path); + t_invoke_linkerf("%S /libpath:\"%S\" /debug:full /rad_time_stamp:0 /rad_workers:1 /pdbaltpath:main.pdb /rad_log:-all /rad_ignore:74 /out:main.exe", test_path, lib_path); T_Ok(g_last_exit_code == 0); // read b @@ -8194,7 +8195,7 @@ TEST(determ_test) ProcessList linkers = {0}; for EachIndex(i, run_count) { String8 out_path = t_make_file_path(arena, str8f(arena, "%llu.exe", i)); - String8 cmdl = str8f(arena, "%S %S /debug:full /rad_time_stamp:0 /rad_imagealtpath:main.exe /pdbaltpath:main.pdb /rad_log:-all /rad_ignore:74 /out:%S", t_radlink_path(), test_path, out_path); + String8 cmdl = str8f(arena, "%S %S /libpath:\"%S\" /debug:full /rad_time_stamp:0 /rad_imagealtpath:main.exe /pdbaltpath:main.pdb /rad_log:-all /rad_ignore:74 /out:%S", t_radlink_path(), test_path, lib_path, out_path); Process process_handle = launch_cmd_line(cmdl); T_Ok(!process_match(process_zero(), process_handle)); process_list_push(arena, &linkers, process_handle); From da23fa1607b065e1007e968a0c938e7edc4c7f16 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 3 Sep 2026 21:05:46 -0700 Subject: [PATCH 143/251] msf: update msf_header_matches_saved_extent * changed test to apply full page allocation pattern in a stream * added new test cases for the page allocation pattern * bugfix stream growth logic --- src/linker/pdb_ext/msf_builder.c | 13 +++--- src/linker/tests/linker_tests.c | 70 ++++++++++++++++++++++++-------- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/linker/pdb_ext/msf_builder.c b/src/linker/pdb_ext/msf_builder.c index 1d07a27a..b996bdf2 100644 --- a/src/linker/pdb_ext/msf_builder.c +++ b/src/linker/pdb_ext/msf_builder.c @@ -490,14 +490,17 @@ msf_alloc_pn_arr(Arena *arena, MSF_Context *msf, MSF_UInt alloc_count) { // make sure FPM has enough space for new page numbers // - // we grow FPM at correct intervals here because we pre-alloc unused FPM pages ahead of time + // grow FPM at correct intervals here because we pre-alloc unused FPM pages ahead of time MSF_UInt curr_page_cap = msf_get_page_count_cap(msf->page_data_list, msf->page_size); - MSF_UInt new_page_count = msf->page_count + alloc_count; - if (new_page_count > curr_page_cap) { - B32 is_fpm_alloced = msf_grow(msf, new_page_count); - if (!is_fpm_alloced) { + for (;;) { + U64 new_page_count = (U64)msf->page_count + alloc_count; + if (new_page_count <= curr_page_cap) { + break; + } + if (new_page_count > MSF_PN_MAX || !msf_grow(msf, (MSF_PageNumber)new_page_count)) { return 0; } + curr_page_cap = msf_get_page_count_cap(msf->page_data_list, msf->page_size); } Temp scratch = scratch_begin(&arena, 1); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 95e3711e..44449283 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -7123,58 +7123,96 @@ TEST(get_msf_stream_pages) TEST(msf_header_matches_saved_extent) { - MSF_UInt page_sizes[] = {512, 4096}; + MSF_UInt page_sizes[] = {512, KB(4)}; MSF_UInt fpms[] = {MSF_FPM0, MSF_FPM1}; + for EachIndex(size_idx, ArrayCount(page_sizes)) { - MSF_UInt page_size = page_sizes[size_idx]; - MSF_PageNumber last_pages[] = {68, page_size - 1, page_size, page_size + 3, - page_size * 8 - 1, page_size * 8, page_size * 8 + 3}; - // Cover an entire FPM's bitmap with small pages, and the default page size's - // first interval without making this a large-memory test. - U64 case_count = page_size == 512 ? ArrayCount(last_pages) : 4; + MSF_UInt P = page_sizes[size_idx]; + + // Exercise physical FPM-slot boundaries and transitions between bitmap intervals. + // + // P * 2 and P * 7: intermediate valid slot-zero pages. + // P * 16 - 1: end of the second bitmap interval. + // P * 16: first page in the third interval. + // P * 16 + 3: first ordinary page after reserved FPM slots. + // + // Both active FPM choices. + // + MSF_PageNumber last_pages[] = { + 68, + P - 1, + P, + P + 3, + P * 2, + P * 7, + P * 8 - 1, + P * 8, + P * 8 + 3, + P * 16 - 1, + P * 16, + P * 16 + 3, + }; + // Full bitmap coverage is inexpensive with 512-byte pages. With 4 KiB + // pages, stop after the first physical FPM-slot boundary. + U64 case_count = P == 512 ? ArrayCount(last_pages) : 4; for EachIndex(fpm_idx, ArrayCount(fpms)) { for EachIndex(case_idx, case_count) { Temp temp = temp_begin(arena); - MSF_Context *msf = msf_alloc(page_size, fpms[fpm_idx]); + MSF_Context *msf = msf_alloc(P, fpms[fpm_idx]); - // Leave low pages for the stream table and root so that the final data - // page, not metadata, determines EOF. This also exercises a sparse file. MSF_PageList gap = msf_alloc_pages(msf, 64); T_Ok(gap.first->pn == 4 && gap.last->pn == 67); - MSF_PageNumber last_pn = last_pages[case_idx]; - MSF_UInt data_page_count = 0; + + // Cover an entire FPM's bitmap with small pages, and the default page size's + // first interval without making this a large-memory test. + MSF_PageNumber last_pn = last_pages[case_idx]; + MSF_UInt data_page_count = 0; for (MSF_PageNumber pn = 68; pn <= last_pn; ++pn) { - MSF_UInt slot = pn % page_size; + MSF_UInt slot = pn % P; data_page_count += slot != MSF_FPM0 && slot != MSF_FPM1; } - String8 payload = str8(push_array_no_zero(arena, U8, (U64)data_page_count * page_size), - (U64)data_page_count * page_size); + String8 payload = str8(push_array_no_zero(arena, U8, (U64)data_page_count * P), + (U64)data_page_count * P); MemorySet(payload.str, 0xA5, payload.size); + MSF_StreamNumber sn = msf_stream_alloc(msf); T_Ok(msf_stream_write(msf, sn, payload.str, (MSF_UInt)payload.size)); T_Ok(msf_find_stream(msf, sn)->page_list.last->pn == last_pn); + + // Free the low pages so metadata can reuse them while the payload remains at + // high page numbers. Unused pages below EOF exercise sparse MSF page layouts. msf_free_pages(msf, &gap); T_Ok(msf_build(msf) == MSF_Error_OK); T_Ok(msf->root_page_list.last->pn < last_pn); T_Ok(msf->st_page_list.last->pn < last_pn); + // validate that last page was assigned to the payload U64 save_size = msf_get_save_size(msf); - T_Ok(save_size == ((U64)last_pn + 1) * page_size); + T_Ok(save_size == ((U64)last_pn + 1) * P); + + // allocate buffer for the serialized MSF file String8 saved = str8(push_array_no_zero(arena, U8, save_size), save_size); T_Ok(msf_save(msf, saved.str, saved.size)); + + // validate 'page_count' field matches the serialized file size MSF_Header70 *header = (MSF_Header70 *)saved.str; T_Ok((U64)header->page_count * header->page_size == saved.size); + + // match internal page data list to the serialized file bytes String8List pages = msf_get_page_data_nodes(arena, msf); T_Ok(str8_match(saved, str8_list_join(arena, &pages, 0), 0)); // Size agreement alone must not conceal a truncated final stream page. MSF_Parsed *parsed = msf_parsed_from_data(arena, saved); T_Ok(parsed != 0); + + // validate parsed stream against the expected payload if (parsed != 0 && sn < parsed->stream_count) { T_Ok(str8_match(parsed->streams[sn], payload, 0)); } else { T_Ok(0); } + msf_release(msf); temp_end(temp); } From 40c1ac39ba76fc6636832f09169f9f4ad53896c4 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 5 Sep 2026 16:51:58 -0700 Subject: [PATCH 144/251] radlink: set target section offset so COMDATs data offsets contribute to the color too --- src/linker/lnk.c | 31 +++++++++++++------ src/linker/tests/linker_tests.c | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index d5d0c38a..b64a3219 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2633,20 +2633,26 @@ lnk_obj_indices_from_section_counts(Arena *arena, U64 worker_count, LNK_Obj **ob } internal B32 -lnk_resolve_reloc_target_symbol(Arena *arena, LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, String8 pass_name, LNK_ObjSymbolRef *resolved_symbol_out) +lnk_resolve_reloc_target_symbol(Arena *arena, LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, String8 pass_name, LNK_ObjSymbolRef *resolved_symbol_out, U32 *resolved_value_out) { - Temp temp = temp_begin(arena); - B32 is_resolved = 1; - HashMap seen_hm = {0}; - LNK_ObjSymbolRef result = symbol; + Temp temp = temp_begin(arena); + + B32 is_resolved = 1; + HashMap seen_hm = {0}; + LNK_ObjSymbolRef result = symbol; + U32 result_value = 0; + for (;;) { // unpack symbol COFF_ParsedSymbol result_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(result.obj, result.symbol_idx); COFF_SymbolValueInterpType result_interp = coff_interp_from_parsed_symbol(result_parsed); + if (result_interp == COFF_SymbolValueInterp_Regular) { + result_value = result_parsed.value; + } // resolve symbol LNK_ObjSymbolRef next_ref = {0}; - if (!lnk_resolve_symbol(symtab, result, &next_ref)) { + if (lnk_resolve_symbol(symtab, result, &next_ref) == 0) { break; } if (result_interp != COFF_SymbolValueInterp_Weak && result_interp != COFF_SymbolValueInterp_Undefined) { @@ -2655,7 +2661,7 @@ lnk_resolve_reloc_target_symbol(Arena *arena, LNK_SymbolTable *symtab, LNK_ObjSy } // most relocations resolve in one step; only allocate cycle tracking for chains - U64 symbol_key = ((U64)result.obj->input_idx << 32ull) | (U64)result.symbol_idx; + U64 symbol_key = Compose64Bit(result.obj->input_idx, result.symbol_idx); if (hash_map_search_u64_u64(&seen_hm, symbol_key) != 0) { COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx); lnk_error_obj(LNK_Warning_CyclicSymbol, symbol.obj, "symbol %S forms a cyclic chain (%S)", symbol_parsed.name, pass_name); @@ -2670,6 +2676,9 @@ lnk_resolve_reloc_target_symbol(Arena *arena, LNK_SymbolTable *symtab, LNK_ObjSy if (resolved_symbol_out) { *resolved_symbol_out = result; } + if (resolved_value_out) { + *resolved_value_out = result_value; + } temp_end(temp); return is_resolved; @@ -2783,7 +2792,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) // reloc -> symbol LNK_ObjSymbolRef ref_symbol = (LNK_ObjSymbolRef){ .obj = batch->v[i].obj, .symbol_idx = reloc->isymbol }; - lnk_resolve_reloc_target_symbol(scratch2.arena, symtab, ref_symbol, str8_lit("/OPT:REF"), &ref_symbol); + lnk_resolve_reloc_target_symbol(scratch2.arena, symtab, ref_symbol, str8_lit("/OPT:REF"), &ref_symbol, 0); // skip unresolved symbol if (ref_symbol.obj == 0) { continue; } @@ -3156,7 +3165,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) if (symbol_idx < obj->coff.header.symbol_count) { LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = symbol_idx }; - B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref); + B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref, 0); if (is_symbol_found) { COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(target_ref.obj, target_ref.symbol_idx); if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular) { @@ -3336,7 +3345,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) *target = (RelocTarget){0}; LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = r->isymbol }; - B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch2.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref); + U32 target_value = 0; + B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch2.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref, &target_value); if (is_symbol_found) { COFF_ParsedSymbol target_symbol = lnk_parsed_symbol_from_coff_symbol_idx_no_name(target_ref.obj, target_ref.symbol_idx); target->interp = coff_interp_from_parsed_symbol(target_symbol); @@ -3384,6 +3394,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) } } + target->value = target_value; target->color = &shared.color_map[target_obj->input_idx][target_sect]; } break; default: { diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 44449283..55fae91d 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -9377,6 +9377,61 @@ TEST(icf_multihop_reloc_target_colors_do_not_fold) T_Ok(vaddrs[4] != vaddrs[5]); } +TEST(icf_nonzero_local_reloc_targets_do_not_fold) +{ + U8 address_of_local[] = { + 0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00, // lea rax, [rip + local] + 0xc3, // ret + 0x90, // nop + }; + U8 entry_text[] = { 0xc3 }; + U8 addresses[2 * sizeof(U64)] = {0}; + + T_Ok(t_write_def_obj("icf_nonzero_local.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "entry", ".text", str8_array_fixed(entry_text), .flags = "rx:code@1" }, + { + "func_a", ".text$mn", str8_array_fixed(address_of_local), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 3, "local_a"), {0} } + }, + { + "func_b", ".text$mn", str8_array_fixed(address_of_local), .flags = "rx:code@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT, + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Rel32, 3, "local_b"), {0} } + }, + { + "addresses", ".data", str8_array_fixed(addresses), .flags = "rw:data@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "func_a"), + T_COFF_DefReloc(X64_Addr64, 8, "func_b"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("func_a", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_Secdef("func_b", COFF_ComdatSelect_NoDuplicates), + T_COFF_DefSymbol_ExternFunc("entry", "entry", 0), + T_COFF_DefSymbol_ExternFunc("func_a", "func_a", 0), + T_COFF_DefSymbol_ExternFunc("func_b", "func_b", 0), + T_COFF_DefSymbol_Static("local_a", "func_a", 7), + T_COFF_DefSymbol_Static("local_b", "func_b", 8), + T_COFF_DefSymbol_Extern("addresses", "addresses", 0), + {0} + } + })); + + t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref,icf /include:addresses icf_nonzero_local.obj"); + T_Ok(g_last_exit_code == 0); + + U64 vaddrs[ArrayCount(addresses) / sizeof(U64)] = {0}; + T_Ok(t_read_exe_data_vaddrs(arena, str8_lit("a.exe"), vaddrs, ArrayCount(vaddrs))); + T_Ok(vaddrs[0] != 0); + T_Ok(vaddrs[1] != 0); + T_Ok(vaddrs[0] != vaddrs[1]); +} + TEST(icf_comdat_symlink_chain) { U8 ret_small[] = { 0xc3 }; From fd6e2bb6b299fac363279fe57f0e2607b7c10d32 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 9 Sep 2026 13:01:49 -0700 Subject: [PATCH 145/251] radlink: relax rules for relocations pointing into removed associative sections --- src/linker/lnk.c | 42 +++++++++++--- src/linker/lnk.h | 5 +- src/linker/lnk_config.c | 1 + src/linker/lnk_log.h | 1 + src/linker/tests/linker_tests.c | 99 +++++++++++++++++++++++++++++++++ 5 files changed, 137 insertions(+), 11 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index b64a3219..0d908965 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -4392,6 +4392,11 @@ THREAD_POOL_TASK_FUNC(lnk_patch_regular_symbols_task) if (sc == task->null_sc) { section_number = lnk_obj_get_removed_section_number(obj); value = max_U32; + + COFF_ComdatSelectType selection = COFF_ComdatSelect_Null; + if (lnk_try_comdat_props_from_section_number(obj, symbol.section_number, &selection, 0, 0, 0) && selection == COFF_ComdatSelect_Associative) { + value = LNK_REMOVED_ASSOCIATIVE_SYMBOL_VALUE; + } } else { section_number = safe_cast_u32(sc->u.sect_idx + 1); value = sc->u.off + symbol.value; @@ -4429,7 +4434,7 @@ lnk_patch_obj_symtab(LNK_SymbolTable *symtab, LNK_Obj *obj, B8 *was_symbol_patch U32 value; if (was_fixup_removed || fixup_type == COFF_SymbolValueInterp_Undefined || fixup_type == COFF_SymbolValueInterp_Weak) { section_number = lnk_obj_get_removed_section_number(obj); - value = 0; + value = was_fixup_removed && fixup_src.value == LNK_REMOVED_ASSOCIATIVE_SYMBOL_VALUE ? LNK_REMOVED_ASSOCIATIVE_SYMBOL_VALUE : 0; } else { section_number = fixup_src.section_number; value = fixup_src.value; @@ -4562,16 +4567,35 @@ THREAD_POOL_TASK_FUNC(lnk_obj_reloc_patcher) COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); if (interp == COFF_SymbolValueInterp_Regular) { if (symbol.section_number == lnk_obj_get_removed_section_number(obj)) { - if (~section_flags & LNK_SECTION_FLAG_DEBUG) { - String8 sect_name = coff_name_from_section_header(string_table, section_header); - String8 symbol_name = lnk_symbol_name_from_coff_symbol_idx(obj, reloc->isymbol); - lnk_error_obj(LNK_Error_RelocationAgainstRemovedSection, obj, "relocating against symbol that is in a removed section (symbol: %S, reloc-section: %S 0x%llx, reloc-index: 0x%llx)", symbol_name, sect_name, it.v.section_number, reloc_idx); + + // With /OPT:REF, linkers may partially discard functions. In this case, relocations referencing those symbols must be left unchanged. + // the rule is to leave the relocations to those symbols as they are. + if (section_flags & LNK_SECTION_FLAG_DEBUG) { continue; } + + { + Temp scratch = scratch_begin(0,0); + + String8 sect_name = coff_name_from_section_header(string_table, section_header); + String8 symbol_name = lnk_symbol_name_from_coff_symbol_idx(obj, reloc->isymbol); + String8 error_message = str8f(scratch.arena, "relocating against symbol that is in a removed section (symbol: %S, reloc-section: %S 0x%llx, reloc-index: 0x%llx)", symbol_name, sect_name, it.v.section_number, reloc_idx); + + // MSVC allows relocations to reference discarded associative sections + if (symbol.value == LNK_REMOVED_ASSOCIATIVE_SYMBOL_VALUE) { + lnk_error_obj(LNK_Warning_RelocationAgainstRemovedAssociativeSection, obj, "%S", error_message); + goto next_reloc; + } + + lnk_error_obj(LNK_Error_RelocationAgainstRemovedSection, obj, "%S", error_message); + goto next_reloc; + + next_reloc:; + scratch_end(scratch); } - continue; + } else { + symbol_secnum = symbol.section_number; + symbol_secoff = symbol.value; + symbol_voff = safe_cast_u32((U64)task->image_section_table[symbol.section_number]->voff + (U64)symbol_secoff); } - symbol_secnum = symbol.section_number; - symbol_secoff = symbol.value; - symbol_voff = safe_cast_u32((U64)task->image_section_table[symbol.section_number]->voff + (U64)symbol_secoff); } else if (interp == COFF_SymbolValueInterp_Abs) { // There aren't enough bits in COFF symbol to store full image base address, // so we special case __ImageBase. A better solution would be to add diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 9291e5db..a426fbac 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -117,8 +117,9 @@ typedef struct LNK_LinkResult // -- Image Layout ------------------------------------------------------------ -#define LNK_REMOVED_SECTION_NUMBER_32 (U32)-3 -#define LNK_REMOVED_SECTION_NUMBER_16 (U16)-3 +#define LNK_REMOVED_ASSOCIATIVE_SYMBOL_VALUE ((U32)-4) +#define LNK_REMOVED_SECTION_NUMBER_32 ((U32)-3) +#define LNK_REMOVED_SECTION_NUMBER_16 ((U16)-3) typedef struct LNK_ImageContext { diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 904e5abb..1db2eba4 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -2845,6 +2845,7 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) "/RAD_LOG:io_write", #else (char*)str8f(scratch.arena, "/RAD_IGNORE:%u", LNK_Error_InvalidTypeIndex).str, + (char*)str8f(scratch.arena, "/RAD_IGNORE:%u", LNK_Warning_RelocationAgainstRemovedAssociativeSection).str, #endif }; diff --git a/src/linker/lnk_log.h b/src/linker/lnk_log.h index b10c836b..a7fa489b 100644 --- a/src/linker/lnk_log.h +++ b/src/linker/lnk_log.h @@ -119,6 +119,7 @@ typedef enum LNK_Warning_GHash, LNK_Warning_UndefinedSectionSymbol, LNK_Warning_CyclicSymbol, + LNK_Warning_RelocationAgainstRemovedAssociativeSection, LNK_Warning_Last, LNK_Error_Count diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 55fae91d..0772dd37 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -9841,6 +9841,105 @@ TEST(icf_cpp_multihop_functions_do_not_fold) #endif +TEST(asan_discarded_associative_metadata) +{ + // ASan can share a filename across metadata belonging to different COMDATs. + // link.exe tombstones references to a discarded associative child at RVA 0; + // it does not redirect them to the winning owner's child or retain the loser. + + U8 pointers[24] = {5}; + pointers[8] = 7; + pointers[12] = 9; + pointers[16] = 2; + pointers[20] = 11; + + for EachIndex(obj_idx, 2) { + T_Ok(t_write_def_obj(obj_idx ? "second.obj" : "first.obj", + ((T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + + .sections = (T_COFF_DefSection[]){ + {"owner", ".owner", obj_idx ? str8_lit("OWNER_B") : str8_lit("OWNER_A"), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT}, + {"meta", ".names", obj_idx ? str8_lit("SECOND.c\0") : str8_lit("FIRST.c\0"), .flags = "r:data@1", .raw_flags = COFF_SectionFlag_LnkCOMDAT}, + {"entry", ".text", str8_lit("\xC3"), .flags = "rx:code@1"}, + {obj_idx ? "pointers" : 0, ".data", str8_array_fixed(pointers), .flags = "rw:data@8", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Addr64, 0, "filename"), + T_COFF_DefReloc(X64_Addr32Nb, 8, "filename"), + T_COFF_DefReloc(X64_SecRel, 12, "filename"), + T_COFF_DefReloc(X64_Section, 16, "filename"), + T_COFF_DefReloc(X64_Rel32, 20, "filename"), + {0} + } + }, + {0} + }, + + .symbols = (T_COFF_DefSymbol[]){ + {.type = obj_idx ? T_COFF_DefSymbol_Static : T_COFF_DefSymbol_ExternFunc, .name = "entry", .section = "entry"}, + T_COFF_DefSymbol_Secdef("owner", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("shared", "owner", 0), + T_COFF_DefSymbol_Associative("meta", "owner"), + T_COFF_DefSymbol_Static("filename", "meta", 3), + {.type = obj_idx ? T_COFF_DefSymbol_Extern : 0, .name = "pointers", .section = "pointers"}, + {0} + } + + }))); + } + + for EachIndex(reverse, 2) { + for EachIndex(ref, 2) { + t_invoke_linkerf("/subsystem:console /entry:entry /nodefaultlib /fixed:no /include:shared /include:pointers /out:assoc.exe /opt:%s,noicf %s", + ref ? "ref" : "noref", + reverse ? "second.obj first.obj" : "first.obj second.obj"); + T_Ok(g_last_exit_code == 0); + + String8 image = t_read_file(arena, str8_lit("assoc.exe")); + PE_BinInfo bin = pe_bin_info_from_data(arena, image); + COFF_SectionHeader *sections = (COFF_SectionHeader *)(image.str + bin.section_table_range.min); + COFF_SectionHeader *data = 0; + COFF_SectionHeader *names = 0; + COFF_SectionHeader *owner = 0; + COFF_SectionHeader *base_relocs = 0; + U32 names_number = 0; + + for EachIndex(i, bin.section_count) { + if (MemoryMatch(sections[i].name, ".data", 6)) { data = §ions[i]; } + if (MemoryMatch(sections[i].name, ".names", 7)) { names = §ions[i]; names_number = i+1; } + if (MemoryMatch(sections[i].name, ".owner", 7)) { owner = §ions[i]; } + if (MemoryMatch(sections[i].name, ".reloc", 7)) { base_relocs = §ions[i]; } + } + + T_Ok(data && names && owner && base_relocs); + T_Ok(MemoryMatch(image.str + owner->foff, reverse ? "OWNER_B" : "OWNER_A", 7)); + T_Ok(MemoryMatch(image.str + names->foff, reverse ? "SECOND.c" : "FIRST.c", reverse ? 8 : 7)); + + U64 addr64; + U32 addr32nb, secrel, rel32; U16 section; + MemoryCopy(&addr64, image.str + data->foff, 8); + MemoryCopy(&addr32nb, image.str + data->foff + 8, 4); + MemoryCopy(&secrel, image.str + data->foff + 12, 4); + MemoryCopy(§ion, image.str + data->foff + 16, 2); + MemoryCopy(&rel32, image.str + data->foff + 20, 4); + + U32 target_voff = reverse ? names->voff + 3 : 0; + T_Ok(addr64 == bin.image_base + target_voff + 5); + T_Ok(addr32nb == target_voff + 7); + T_Ok(secrel == (reverse ? 3 : 0) + 9); + T_Ok(section == (reverse ? names_number : 0) + 2); + T_Ok(rel32 == (target_voff - data->voff - 24 + 11)); + + // Even the tombstoned ADDR64 still needs loader rebasing. + U32 page; + U16 fixup; + MemoryCopy(&page, image.str + base_relocs->foff, 4); + MemoryCopy(&fixup, image.str + base_relocs->foff + 8, 2); + T_Ok((fixup >> 12) == 10 && page + (fixup & 0xfff) == data->voff); + } + } +} + #if 0 TEST(defer_imp_link) { From 1673303d562c6e59ee2fbec09639d9e1e5479104 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 9 Sep 2026 18:47:40 -0700 Subject: [PATCH 146/251] coff: parse long section names encoded with LLVM/GNU format --- src/coff/coff.c | 38 +++++++++++++++++++++++--- src/linker/tests/linker_tests.c | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/coff/coff.c b/src/coff/coff.c index 437b39de..51eb05cc 100644 --- a/src/coff/coff.c +++ b/src/coff/coff.c @@ -55,11 +55,41 @@ internal String8 coff_name_from_section_header(String8 string_table, COFF_SectionHeader *header) { String8 name = str8_cstring_capped(header->name, header->name + sizeof(header->name)); - if (name.str[0] == '/') { - String8 name_off_str = str8_skip(name, 1); - U64 name_off = u64_from_str8(name_off_str, 10); - name = str8_cstring_capped(string_table.str + name_off, string_table.str+string_table.size); + + if (name.size > 0 && name.str[0] == '/') { + U64 name_off = max_U64; + + // LLVM/GNU Base64 offset encoding, most significant digit first + if (name.size > 1 && name.str[1] == '/') { + if (name.size > 2) { + name_off = 0; + for (U64 i = 2; i < name.size; ++i) { + U8 c = name.str[i]; + U64 digit = 0; + if (char_is_upper(c)) { digit = c - 'A'; } + else if (char_is_lower(c)) { digit = c - 'a' + 26; } + else if (char_is_digit(c, 10)) { digit = c - '0' + 52; } + else if (c == '+') { digit = 62; } + else if (c == '/') { digit = 63; } + else { + name_off = max_U64; + break; + } + name_off = (name_off << 6) | digit; + } + } + } else { + // parse standard COFF long name encoding + name_off = u64_from_str8(str8_skip(name, 1), 10); + } + + if (name_off < string_table.size) { + name = str8_cstring_capped(string_table.str + name_off, string_table.str+string_table.size); + } else { + name = str8_zero(); + } } + return name; } diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 0772dd37..86b66708 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -9940,6 +9940,53 @@ TEST(asan_discarded_associative_metadata) } } +TEST(coff_section_name_offsets) +{ + String8 expected = str8_lit(".lcovfun$M"); + String8 encoded[] = { + str8_lit("//AAAAAE"), str8_lit("//AAAAA+"), str8_lit("//AAAAA/"), + str8_lit("//AAAABA"), str8_lit("//AAmJZ/"), str8_lit("//AAmJaA"), + str8_lit("//AAyPM2"), + }; + U64 offsets[] = {4, 62, 63, 64, 9999999, 10000000, 13169462}; + String8 table = str8(push_array(arena, U8, 13169462 + 32), 13169462 + 32); + for EachElement(i, offsets) { + MemoryCopy(table.str + offsets[i], expected.str, expected.size + 1); + + COFF_SectionHeader header = {0}; + MemoryCopy(header.name, encoded[i].str, encoded[i].size); + T_Ok(str8_match(coff_name_from_section_header(table, &header), expected, 0)); + + if (offsets[i] <= 9999999) { + String8 decimal = str8f(arena, "/%llu", offsets[i]); + MemoryZeroStruct(&header); + MemoryCopy(header.name, decimal.str, decimal.size); + T_Ok(str8_match(coff_name_from_section_header(table, &header), expected, 0)); + } + } + + String8 invalid[] = { + str8_lit("//AAAAA!"), + str8_lit("//"), + str8_lit("////////"), + str8_lit("//AAAAAE"), // offset exactly at the end of the short table below + str8_lit("/9999999"), + }; + String8 short_table = str8_prefix(table, 4); + for EachElement(i, invalid) { + COFF_SectionHeader header = {0}; + MemoryCopy(header.name, invalid[i].str, invalid[i].size); + T_Ok(coff_name_from_section_header(short_table, &header).size == 0); + } + + COFF_SectionHeader inline_header = {0}; + MemoryCopy(inline_header.name, ".text$mn", 8); + T_Ok(str8_match(coff_name_from_section_header(str8_zero(), &inline_header), str8_lit(".text$mn"), 0)); + + MemoryZeroStruct(&inline_header); + T_Ok(coff_name_from_section_header(str8_zero(), &inline_header).size == 0); +} + #if 0 TEST(defer_imp_link) { From c762d1cd7106db2b97f7edfa5e0a2bef39c3a12b Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 10 Sep 2026 14:54:33 -0700 Subject: [PATCH 147/251] do not output nil file in breakpad output --- src/radbin/radbin.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/radbin/radbin.c b/src/radbin/radbin.c index bd941178..bec46503 100644 --- a/src/radbin/radbin.c +++ b/src/radbin/radbin.c @@ -1118,14 +1118,17 @@ rb_thread_entry_point(void *p) { U64 count = 0; RDI_SourceFile *v = rdi_table_from_name(rdi, SourceFiles, &count); - Rng1U64 range = lane_range(count); - for EachInRange(idx, range) + if(count > 0) { - String8List *out = &p2b_shared->lane_file_dumps[lane_idx()]; - Temp scratch = scratch_begin(&arena, 1); - String8 src_path = str8_from_rdi_path_node_idx(scratch.arena, rdi, PathStyle_Relative, v[idx].file_path_node_idx); - str8_list_pushf(arena, out, "FILE %I64u %S\n", idx, src_path); - scratch_end(scratch); + Rng1U64 range = lane_range(count-1); + for EachInRange(idx, range) + { + String8List *out = &p2b_shared->lane_file_dumps[lane_idx()]; + Temp scratch = scratch_begin(&arena, 1); + String8 src_path = str8_from_rdi_path_node_idx(scratch.arena, rdi, PathStyle_Relative, v[idx+1].file_path_node_idx); + str8_list_pushf(arena, out, "FILE %I64u %S\n", idx+1, src_path); + scratch_end(scratch); + } } } From e162b800d9ad0538c44c78e76b78b51827783cd4 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 10 Sep 2026 15:55:44 -0700 Subject: [PATCH 148/251] radlink: make command-line /INFERASANLIBS:NO disable ASAN library inference without allowing directive to re-enable it --- src/linker/lnk_config.c | 22 +++++++++++--- src/linker/lnk_obj.c | 4 +-- src/linker/tests/linker_tests.c | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 1db2eba4..f44b4f1e 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -1,7 +1,7 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -global read_only LNK_CmdSwitch g_cmd_switch_map[] = +global LNK_CmdSwitch g_cmd_switch_map[] = { { LNK_CmdSwitch_Null, 0, LNK_CmdValueKind_Null, "", "", "" }, { LNK_CmdSwitch_NotImplemented, 0, LNK_CmdValueKind_Null, "NOT_IMPLEMENTED", "", "" }, @@ -34,8 +34,8 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Ignore, 0, LNK_CmdValueKind_Scalar, "IGNORE", ":#", "Ignore a warning." }, { LNK_CmdSwitch_ImpLib, 0, LNK_CmdValueKind_Scalar, "IMPLIB", ":FILENAME", "Set file name for the import library." }, { LNK_CmdSwitch_Include, 1, LNK_CmdValueKind_Scalar, "INCLUDE", ":SYMBOL", "Force a link against SYMBOL." }, - { LNK_CmdSwitch_InferAsanLibs, 1, LNK_CmdValueKind_Scalar, "INFERASANLIBS", "[:NO]", "No support." }, - { LNK_CmdSwitch_InferAsanLibsNo, 1, LNK_CmdValueKind_Null, "INFERASANLIBSNO", "", "No support.", }, + { LNK_CmdSwitch_InferAsanLibs, 1, LNK_CmdValueKind_Scalar, "INFERASANLIBS", "[:NO]", "Infer Asan libraries from the link." }, + { LNK_CmdSwitch_InferAsanLibsNo, 1, LNK_CmdValueKind_Null, "INFERASANLIBSNO", "", "Disable Asan lib inference.", }, { LNK_CmdSwitch_LargeAddressAware, 0, LNK_CmdValueKind_Scalar, "LARGEADDRESSAWARE", "[:NO]", "For images that can handle addresses > 2GiB." }, { LNK_CmdSwitch_Lib, 0, LNK_CmdValueKind_Null, "LIB", "", "Turn linker into lib.exe." }, { LNK_CmdSwitch_LibPath, 0, LNK_CmdValueKind_Scalar, "LIBPATH", ":DIR", "Add DIR for the linker to search for libraries." }, @@ -60,7 +60,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_PdbPageSize, 0, LNK_CmdValueKind_Scalar, "PDBPAGESIZE", ":#", "Page size must be power of two." }, { LNK_CmdSwitch_PdbStripped, 0, LNK_CmdValueKind_Scalar, "PDBSTRIPPED", ":FILENAME", "Create a stripped PDB containing public symbols, a section map, and a list of object files." }, { LNK_CmdSwitch_Release, 1, LNK_CmdValueKind_Null, "RELEASE", "", "Write image checksum." }, - { LNK_CmdSwitch_Section, 1, LNK_CmdValueKind_List, "SECTION", ":NAME,ATTRS", "Set output section attributes." }, + { LNK_CmdSwitch_Section, 1, LNK_CmdValueKind_List, "SECTION", ":NAME,ATTRS", "Set output section attributes." }, { LNK_CmdSwitch_Stack, 1, LNK_CmdValueKind_List, "STACK", ":RESERVE[,COMMIT]", "Set reserve and commit size for the stack." }, { LNK_CmdSwitch_SubSystem, 1, LNK_CmdValueKind_List, "SUBSYSTEM", ":{CONSOLE|NATIVE|WINDOWS}[,#[.##]]", "Set subsystem for the image." }, { LNK_CmdSwitch_TsAware, 0, LNK_CmdValueKind_Scalar, "TSAWARE", "[:NO]", "Image is terminal server aware." }, @@ -1500,6 +1500,20 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8 val case LNK_CmdSwitch_InferAsanLibs: { lnk_cmd_switch_parse_flag(obj, cmd_switch, value, &config->infer_asan_libs); + + // Make the command-line option /INFERASANLIBS:NO authorative for ASAN library resolution. + // The LLVM toolchain passes ASAN libraries explicitly to lld-link, so we need to disable + // library inference and reject directives that re-enable it. Otherwise, a stray MSVC + // object file could force radlink to pull in duplicate ASAN libraries. + if (obj == 0) { + LNK_CmdSwitch *cmd = lnk_cmd_switch_from_type(LNK_CmdSwitch_InferAsanLibs); + cmd->is_legal_directive = (config->infer_asan_libs == LNK_SwitchState_Yes); + } + } break; + + case LNK_CmdSwitch_InferAsanLibsNo: { + String8 infer_asan_libs_name = lnk_string_from_cmd_switch_type(LNK_CmdSwitch_InferAsanLibsNo); + lnk_apply_cmd_option_to_config(config, infer_asan_libs_name, str8_lit("NO"), obj); } break; case LNK_CmdSwitch_LargeAddressAware: { diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 0c7a0a02..228291e7 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -913,14 +913,14 @@ lnk_parse_msvc_linker_directive(Arena *arena, LNK_Obj *obj, LNK_DirectiveInfo *d String8List arg_list = lnk_arg_list_parse_windows_rules(scratch.arena, to_parse); LNK_CmdLine cmd_line = lnk_cmd_line_parse_windows_rules(scratch.arena, arg_list); - for (LNK_CmdOption *opt = cmd_line.first_option; opt != 0; opt = opt->next) { + for EachNode(opt, LNK_CmdOption, cmd_line.first_option) { LNK_CmdSwitch *cmd_switch = lnk_cmd_switch_from_string(opt->string); if (cmd_switch == 0) { lnk_error_obj(LNK_Warning_UnknownDirective, obj, "unknown directive \"%S\"", opt->string); continue; } - if (!cmd_switch->is_legal_directive) { + if (cmd_switch->is_legal_directive == 0) { lnk_error_obj(LNK_Warning_IllegalDirective, obj, "illegal directive \"%S\"", opt->string); continue; } diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 86b66708..a1dbb2cf 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -8127,6 +8127,60 @@ TEST(whole_archive) T_Ok(b_sect != 0); } } + +#if 0 +TEST(infer_asan_command_line_no) +{ + Linker linker = t_id_linker(); + if (linker != Linker_Null && linker != Linker_radlink) { return; } + + T_Ok(t_write_entry_obj()); + + // Model LLVM's explicitly supplied runtime and an inferred MSVC runtime thunk + // defining the same symbol. Use local stub libraries so no ASAN installation is needed. + T_COFF_DefLib runtime = { + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ + { + .type = T_COFF_DefLibMember_Obj, + .obj = { + .sections = (T_COFF_DefSection[]){ + { "text", ".text", str8_lit_comp("\xC3"), .flags = "rx:code" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("asan_runtime", "text", 0), + {0} + } + } + }, + {0} + } + }; + T_Ok(t_write_def_lib("explicit_asan.lib", runtime)); + T_Ok(t_write_def_lib("clang_rt.asan_dynamic_runtime_thunk-x86_64.lib", runtime)); + T_Ok(t_write_def_lib("clang_rt.asan_dynamic-x86_64.lib", (T_COFF_DefLib){ .emit_second_member = 1 })); + T_Ok(t_write_def_lib("msvcrt.lib", (T_COFF_DefLib){ .emit_second_member = 1 })); + + char *args = "/entry:entry /subsystem:console /out:asan.exe /include:asan_runtime entry.obj explicit_asan.lib msvcrt.lib"; + String8 illegal_directive = str8_lit("illegal directive \"INFERASANLIBS\""); + String8 directives[] = { str8_lit("/INFERASANLIBS"), str8_lit("/INFERASANLIBS:YES") }; + for EachElement(i, directives) { + T_Ok(t_write_file(str8_lit("msvc.obj"), t_make_obj_with_directive(arena, directives[i]))); + + // Without a command-line override, the directive pulls in the duplicate thunk. + t_invoke_linkerf("%s msvc.obj", args); + T_Ok(g_last_exit_code == LNK_Error_MultiplyDefinedSymbol); + T_Ok(str8_find_needle(g_errors, 0, illegal_directive, 0) == g_errors.size); + + // /NO must reject the directive and leave the explicit runtime as the sole provider. + t_invoke_linkerf("%s /INFERASANLIBS:NO msvc.obj", args); + T_Ok(g_last_exit_code == 0); + T_Ok(str8_find_needle(g_errors, 0, illegal_directive, 0) < g_errors.size); + } +} +#endif + #if OS_WINDOWS internal B32 From 3e7b11fd1b8993a854edf85e99840e70dabeb7cc Mon Sep 17 00:00:00 2001 From: Henrik Karlsson Date: Tue, 8 Sep 2026 12:42:46 -0700 Subject: [PATCH 149/251] radlink: prefer non-zero function prefixes for COMDAT Any --- src/linker/lnk_symbol_table.c | 37 ++++++++++++++++++--- src/linker/tests/linker_tests.c | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 83ab6238..98178cd1 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -148,6 +148,28 @@ lnk_error_multiply_defined_symbol(LNK_Symbol *dst, LNK_Symbol *src) lnk_error_obj(LNK_Error_MultiplyDefinedSymbol, dst_ref.obj, "symbol \"%S\" (No. %#x) is multiply defined in %S (No. %#x)", dst->name, dst_ref.symbol_idx, src_ref.obj->path, src_ref.symbol_idx); } +internal B32 +lnk_symbol_has_nonzero_prefix(LNK_Obj *obj, COFF_ParsedSymbol symbol) +{ + if (symbol.value == 0) { + return 0; + } + COFF_SectionHeader *section = lnk_coff_section_header_from_section_number(obj, symbol.section_number); + if (section->flags & COFF_SectionFlag_CntUninitializedData) { + return 0; + } + String8 data = lnk_obj_section_data_from_number(obj, symbol.section_number); + if (symbol.value > data.size) { + return 0; + } + for (U32 i = 0; i < symbol.value; i += 1) { + if (data.str[i] != 0) { + return 1; + } + } + return 0; +} + internal B32 lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) { @@ -325,10 +347,17 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) if (src_select == dst_select) { switch (src_select) { - case COFF_ComdatSelect_Null: - case COFF_ComdatSelect_Any: { - can_replace = lnk_obj_is_before(src_obj, dst_obj); - } break; + case COFF_ComdatSelect_Null: { + can_replace = lnk_obj_is_before(src_obj, dst_obj); + } break; + case COFF_ComdatSelect_Any: { + // LLVM emits prefix data before the function entry point (the symbol + // value). AutoRTFM uses a non-zero prefix to locate the closed function. + // Prefer it over an uninstrumented copy, regardless of input/insert order. + B32 src_has_prefix = lnk_symbol_has_nonzero_prefix(src_obj, src_parsed); + B32 dst_has_prefix = lnk_symbol_has_nonzero_prefix(dst_obj, dst_parsed); + can_replace = src_has_prefix != dst_has_prefix ? src_has_prefix : lnk_obj_is_before(src_obj, dst_obj); + } break; case COFF_ComdatSelect_NoDuplicates: { lnk_error_multiply_defined_symbol(dst, src); } break; diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index a1dbb2cf..1fa20b5e 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -3253,6 +3253,65 @@ TEST(image_base) T_Ok(str8_match(text_data, str8_array_fixed(expected_text), 0)); } +TEST(comdat_any_nonzero_prefix) +{ + // The symbol value is the prefix length, not the length of the function. + String8 plain = str8_lit("\xB8\x01\x00\x00\x00\xC3"); + String8 zero = str8_lit("\x00\x00\x00\x00\xB8\x02\x00\x00\x00\xC3"); + String8 nonzero = str8_lit("\x00\x00\x00\xA5\xB8\x03\x00\x00\x00\xC3"); + String8 nonzero2 = str8_lit("\x5A\x00\x00\x00\xB8\x04\x00\x00\x00\xC3"); + struct { + String8 data[2]; + U32 value[2]; + COFF_ComdatSelectType selection[2]; + S32 winner; // -1 means the first input must win. + } cases[] = { + {{plain, nonzero}, {0, 4}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Any}, 1}, + {{zero, nonzero}, {4, 4}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Any}, 1}, + {{nonzero, nonzero2}, {4, 4}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Any}, -1}, + {{plain, zero}, {0, 4}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Any}, -1}, + {{plain, plain}, {0, 0}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Any}, -1}, + {{zero, zero}, {4, 4}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Any}, -1}, + // Out-of-range values must not turn function bytes into a valid prefix. + {{plain, nonzero}, {7, 4}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Any}, 1}, + {{plain, nonzero}, {max_U32, 4}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Any}, 1}, + // Equal-sized Largest/SameSize and Any promoted to Largest retain input order. + {{zero, nonzero}, {4, 4}, {COFF_ComdatSelect_Largest, COFF_ComdatSelect_Largest}, -1}, + {{zero, nonzero}, {4, 4}, {COFF_ComdatSelect_SameSize, COFF_ComdatSelect_SameSize}, -1}, + {{zero, nonzero}, {4, 4}, {COFF_ComdatSelect_Any, COFF_ComdatSelect_Largest}, -1}, + }; + for (U64 case_idx = 0; case_idx < ArrayCount(cases); case_idx += 1) { + for (U32 obj_idx = 0; obj_idx < 2; obj_idx += 1) { + T_Ok(t_write_def_obj(obj_idx ? "second.obj" : "first.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + {"text", ".text", cases[case_idx].data[obj_idx], .flags = "rx:code", + .raw_flags = COFF_SectionFlag_LnkCOMDAT}, {0}}, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("text", cases[case_idx].selection[obj_idx]), + T_COFF_DefSymbol_ExternFunc("entry", "text", cases[case_idx].value[obj_idx]), {0}}, + })); + } + for (U32 reverse = 0; reverse < 2; reverse += 1) { + for (U32 workers = 1; workers <= 4; workers += 3) { + t_invoke_linkerf("/subsystem:console /entry:entry /nodefaultlib /opt:ref,noicf /rad_workers:%u /out:prefix.exe %s", + workers, reverse ? "second.obj first.obj" : "first.obj second.obj"); + T_Ok(g_last_exit_code == 0); + String8 image = t_read_file(arena, str8_lit("prefix.exe")); + PE_BinInfo bin = pe_bin_info_from_data(arena, image); + U32 winner = cases[case_idx].winner < 0 ? reverse : (U32)cases[case_idx].winner; + U64 entry_off = pe_foff_from_voff(image, &bin, bin.entry_point); + U32 prefix_size = cases[case_idx].value[winner]; + T_Ok(entry_off >= prefix_size); + if (entry_off >= prefix_size) { + String8 actual = str8_substr(image, r1u64s(entry_off - prefix_size, cases[case_idx].data[winner].size)); + T_Ok(str8_match(actual, cases[case_idx].data[winner], 0)); + } + } + } + } +} + TEST(comdat_any) { T_Ok(t_write_def_obj("1.obj", (T_COFF_DefObj){ From 000997d3afca8cac6cafb0e22108e8701516c5b9 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 11 Sep 2026 22:57:06 -0700 Subject: [PATCH 150/251] radlink: implement COFF_ImportBy_Undecorate --- src/linker/tests/linker_tests.c | 140 ++++++++++++++++++++++++++++++++ src/pe/pe_make_import_table.c | 20 ++++- src/pe/pe_make_import_table.h | 1 + 3 files changed, 157 insertions(+), 4 deletions(-) diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 1fa20b5e..b9cc804b 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -4909,6 +4909,146 @@ TEST(communal_var_vs_regular_comdat) } } +TEST(import_undecorate) +{ + // Keep the decorated COFF symbols, but use the undecorated DLL export name + // in the hint/name table. An empty ILT/IAT still links, but calls through its + // jump thunk reach address zero at runtime. + struct { + char *symbol_name; + char *export_name; + } cases[] = { + { "?CppImport@@YAHXZ", "CppImport" }, + { "_StdcallImport@8", "StdcallImport" }, + { "@FastcallImport@8", "FastcallImport" }, + { "PlainImport", "PlainImport" }, + }; + struct { + char *args; + B32 delayed; + } opts[] = { + { "/opt:ref", 0 }, + { "/opt:noref", 0 }, + { "/opt:ref /delayload:undecorate.dll", 1 }, + { "/opt:noref /delayload:undecorate.dll", 1 }, + }; + + for EachElement(case_idx, cases) { + String8 symbol_name = str8_cstring(cases[case_idx].symbol_name); + String8 iat_name = str8f(arena, "__imp_%S", symbol_name); + U16 hint = 17; + + T_Ok(t_write_def_lib("undecorate.lib", (T_COFF_DefLib){ + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ + { + .type = T_COFF_DefLibMember_Import, + .import = { "undecorate.dll", cases[case_idx].symbol_name, + COFF_ImportBy_Undecorate, COFF_ImportHeader_Code, + .hit_or_ordinal = hint }, + }, + {0}, + }, + })); + T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){ + .sections = (T_COFF_DefSection[]){ + { + "text", ".text", + str8_lit_comp( + "\x48\x83\xEC\x28" // sub rsp,28h + "\xE8\0\0\0\0" // call decorated function's jump thunk + "\xFF\x15\0\0\0\0" // call [__imp_decorated_function] + "\x48\x83\xC4\x28\xC3" // add rsp,28h; ret + ), + .flags = "rx:code", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 5, cases[case_idx].symbol_name), + T_COFF_DefReloc(X64_Rel32, 11, (char *)iat_name.str), + {0}, + }, + }, + {0}, + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("entry", "text", 0), + // Link-only test: the delay helper only needs to resolve to a ret. + T_COFF_DefSymbol_ExternFunc("__delayLoadHelper2", "text", 19), + T_COFF_DefSymbol_UndefFunc(cases[case_idx].symbol_name), + T_COFF_DefSymbol_Undef((char *)iat_name.str), + {0}, + }, + })); + + for EachElement(opt_idx, opts) { + t_infof("Undecorate import %S -> %s (%s)\n", symbol_name, cases[case_idx].export_name, opts[opt_idx].args); + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:undecorate.exe %s entry.obj undecorate.lib", opts[opt_idx].args); + T_Ok(g_last_exit_code == 0); + + String8 image = t_read_file(arena, str8_lit("undecorate.exe")); + PE_BinInfo bin = pe_bin_info_from_data(arena, image); + COFF_SectionHeader *sections = (COFF_SectionHeader *)(image.str + bin.section_table_range.min); + PE_ParsedImport *import = 0; + U64 iat_voff = 0, ilt_voff = 0; + + if (opts[opt_idx].delayed) { + T_Ok(bin.data_dir_count > PE_DataDirectoryIndex_DELAY_IMPORT); + PE_ParsedDelayImportTable imports = pe_delay_imports_from_data(arena, bin.is_pe32, bin.section_count, sections, image, bin.data_dir_franges[PE_DataDirectoryIndex_DELAY_IMPORT]); + T_Ok(imports.count == 1); + T_Ok(str8_match(imports.v[0].name, str8_lit("undecorate.dll"), 0)); + T_Ok(imports.v[0].import_count == 1); + import = &imports.v[0].imports[0]; + iat_voff = imports.v[0].iat_voff; + ilt_voff = imports.v[0].name_table_voff; + } else { + T_Ok(bin.data_dir_count > PE_DataDirectoryIndex_IMPORT); + PE_ParsedStaticImportTable imports = pe_static_imports_from_data(arena, bin.is_pe32, bin.section_count, sections, image, bin.data_dir_franges[PE_DataDirectoryIndex_IMPORT]); + T_Ok(imports.count == 1); + T_Ok(str8_match(imports.v[0].name, str8_lit("undecorate.dll"), 0)); + T_Ok(imports.v[0].import_count == 1); + import = &imports.v[0].imports[0]; + iat_voff = imports.v[0].import_address_table_voff; + ilt_voff = imports.v[0].import_name_table_voff; + } + T_Ok(import->type == PE_ParsedImport_Name); + + // Delay-load linkers may replace the advisory import hint with zero. + if (!opts[opt_idx].delayed) { + T_Ok(import->u.name.hint == hint); + } + T_Ok(str8_match(import->u.name.string, str8_cstring(cases[case_idx].export_name), 0)); + + // Both call forms must resolve to the same populated IAT slot. + U64 entry_off = pe_foff_from_voff(image, &bin, bin.entry_point); + S32 call_disp = 0, iat_disp = 0, thunk_disp = 0; + T_Ok(str8_deserial_read_struct(image, entry_off + 5, &call_disp) == sizeof(call_disp)); + T_Ok(str8_deserial_read_struct(image, entry_off + 11, &iat_disp) == sizeof(iat_disp)); + + U64 thunk_voff = (U64)((S64)bin.entry_point + 9 + call_disp); + U64 thunk_off = pe_foff_from_voff(image, &bin, thunk_voff); + T_Ok(str8_match(str8_substr(image, rng_1u64(thunk_off, thunk_off + 2)), str8_lit("\xFF\x25"), 0)); + T_Ok(str8_deserial_read_struct(image, thunk_off + 2, &thunk_disp) == sizeof(thunk_disp)); + T_Ok((U64)((S64)bin.entry_point + 15 + iat_disp) == iat_voff); + T_Ok((U64)((S64)thunk_voff + 6 + thunk_disp) == iat_voff); + + U64 iat_off = pe_foff_from_voff(image, &bin, iat_voff); + U64 ilt_off = pe_foff_from_voff(image, &bin, ilt_voff); + U64 iat_entry = 0, iat_end = 1, ilt_entry = 0; + T_Ok(str8_deserial_read_struct(image, iat_off, &iat_entry) == sizeof(iat_entry)); + T_Ok(str8_deserial_read_struct(image, iat_off + 8, &iat_end) == sizeof(iat_end)); + T_Ok(str8_deserial_read_struct(image, ilt_off, &ilt_entry) == sizeof(ilt_entry)); + T_Ok(iat_entry != 0 && iat_end == 0); + + if (opts[opt_idx].delayed) { + // Delay-load IAT entries start at the load thunk, rather than the name. + T_Ok(iat_entry >= bin.image_base); + T_Ok(pe_foff_from_voff(image, &bin, iat_entry - bin.image_base) != 0); + } else { + T_Ok(iat_entry == ilt_entry); + } + } + } +} + TEST(link_large_import_object) { // Each named import creates three sections in the synthesized DLL object. diff --git a/src/pe/pe_make_import_table.c b/src/pe/pe_make_import_table.c index eb20b0e4..ee60fcfe 100644 --- a/src/pe/pe_make_import_table.c +++ b/src/pe/pe_make_import_table.c @@ -17,6 +17,16 @@ pe_make_import_header_list_push(Arena *arena, PE_MakeImportList *list, PE_MakeIm return node; } +internal String8 +pe_undecorate_import_name(String8 name) +{ + // COFF IMPORT_NAME_UNDECORATE strips one prefix character and the suffix starting at the first '@' + if (name.size && (name.str[0] == '?' || name.str[0] == '@' || name.str[0] == '_')) { + name = str8_skip(name, 1); + } + return str8_prefix(name, str8_find_needle(name, 0, str8_lit("@"), 0)); +} + internal COFF_ObjSymbol * pe_make_indirect_jump_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *iat_symbol, String8 thunk_name) { @@ -286,11 +296,13 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach str8_list_push(obj_writer->arena, &ilt_sect->data, ordinal_data); str8_list_push(obj_writer->arena, &iat_sect->data, ordinal_data); } break; + case COFF_ImportBy_Undecorate: case COFF_ImportBy_Name: { COFF_ObjSection *int_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes|comdat_flags, str8_zero()); coff_obj_writer_push_symbol_associative(obj_writer, int_sect, iat_sect); COFF_ObjSymbol *int_symbol = coff_obj_writer_push_symbol_static(obj_writer, int_sect->name, 0, int_sect); - String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, import_header.func_name); + String8 lookup_name = import_header.import_by == COFF_ImportBy_Undecorate ? pe_undecorate_import_name(import_header.func_name) : import_header.func_name; + String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, lookup_name); str8_list_push(obj_writer->arena, &int_sect->data, int_data); str8_list_push_aligner(obj_writer->arena, &int_sect->data, 0, 2); @@ -302,7 +314,6 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach str8_list_push(obj_writer->arena, &ilt_sect->data, str8_array(import_entry, import_size)); str8_list_push(obj_writer->arena, &iat_sect->data, str8_array(import_entry, import_size)); } break; - case COFF_ImportBy_Undecorate: { NotImplemented; } break; case COFF_ImportBy_NameNoPrefix: { NotImplemented; } break; default: { InvalidPath; } break; } @@ -461,9 +472,11 @@ pe_make_import_dll_obj_delayed(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mac coff_obj_writer_section_push_reloc_addr(obj_writer, uiat_sect, uiat_offset, load_thunk_symbol); } } break; + case COFF_ImportBy_Undecorate: case COFF_ImportBy_Name: { // put together name look up entry - String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, import_header.func_name); + String8 lookup_name = import_header.import_by == COFF_ImportBy_Undecorate ? pe_undecorate_import_name(import_header.func_name) : import_header.func_name; + String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, lookup_name); U64 int_data_offset = int_sect->data.total_size; str8_list_push(obj_writer->arena, &int_sect->data, int_data); @@ -503,7 +516,6 @@ pe_make_import_dll_obj_delayed(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mac coff_obj_writer_section_push_reloc_addr(obj_writer, uiat_sect, uiat_data_offset, load_thunk_symbol); } } break; - case COFF_ImportBy_Undecorate: { NotImplemented; } break; case COFF_ImportBy_NameNoPrefix: { NotImplemented; } break; } } diff --git a/src/pe/pe_make_import_table.h b/src/pe/pe_make_import_table.h index dba92caf..82cd598a 100644 --- a/src/pe/pe_make_import_table.h +++ b/src/pe/pe_make_import_table.h @@ -27,6 +27,7 @@ typedef struct PE_MakeImportList internal void pe_make_import_header_list_push_node(PE_MakeImportList *list, PE_MakeImportNode *node); internal PE_MakeImportNode * pe_make_import_header_list_push(Arena *arena, PE_MakeImportList *list, PE_MakeImport header); +internal String8 pe_undecorate_import_name(String8 name); internal COFF_ObjSymbol * pe_make_indirect_jump_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *iat_symbol, String8 thunk_name); internal COFF_ObjSymbol * pe_make_load_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *imp_addr_ptr, COFF_ObjSymbol *tail_merge, String8 func_name); From 23507a826ffbd0b652799d1c18a0c50701a98011 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 15 Sep 2026 08:35:05 -0700 Subject: [PATCH 151/251] content: fix inconsistent root -> node path --- src/content/content.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/content.c b/src/content/content.c index 6ff6c6f0..320c6c0b 100644 --- a/src/content/content.c +++ b/src/content/content.c @@ -272,7 +272,7 @@ c_submit_data(C_Key key, Arena **data_arena, String8 data) // rjf: key is new -> add this key to the associated root if(key_is_new) { - U64 root_hash = u64_hash_from_str8(str8_struct(&key.root)); + U64 root_hash = key.root.u64[0]; U64 root_slot_idx = root_hash%c_shared->root_slots_count; U64 root_stripe_idx = root_slot_idx%c_shared->root_stripes_count; C_RootSlot *root_slot = &c_shared->root_slots[root_slot_idx]; From dc2a2064872f83fffbb8eb888a8a7f3d68916855 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 15 Sep 2026 14:08:11 -0700 Subject: [PATCH 152/251] win32 symbol server :sync request task count --- src/win32/symbol_server/win32_symbol_server.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/win32/symbol_server/win32_symbol_server.c b/src/win32/symbol_server/win32_symbol_server.c index 52e19b88..fb6c8e5a 100644 --- a/src/win32/symbol_server/win32_symbol_server.c +++ b/src/win32/symbol_server/win32_symbol_server.c @@ -229,6 +229,7 @@ smsv_async_tick(void) } } lane_sync_u64(&request_tasks, 0); + lane_sync_u64(&request_task_count, 0); //- rjf: do all request tasks { From f2ea61ab83a8867ba893441b5ec0223c512e3ddc Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 21 Sep 2026 09:04:32 -0700 Subject: [PATCH 153/251] fix dumb passthrough of raddbg.com in args in com shim --- src/com_shim/com_shim_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com_shim/com_shim_main.c b/src/com_shim/com_shim_main.c index 5003eb3a..ba698e13 100644 --- a/src/com_shim/com_shim_main.c +++ b/src/com_shim/com_shim_main.c @@ -41,7 +41,7 @@ entry_point(CmdLine *cmd_line) } str8_list_push(scratch.arena, &command_parts, exe_name); str8_list_push(scratch.arena, &command_parts, s("--cli")); - for EachIndex(idx, cmd_line->argc) + for(U64 idx = 1; idx < cmd_line->argc; idx += 1) { String8 arg = str8_cstring(cmd_line->argv[idx]); str8_list_push(scratch.arena, &command_parts, arg); From 707a3e11ca70c2e28e256250b27022261280d7d4 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 12 Sep 2026 19:05:10 -0700 Subject: [PATCH 154/251] release asserts --- src/base/base_core.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/base_core.h b/src/base/base_core.h index 05b9213c..1a21cd7a 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -241,8 +241,8 @@ #else # define Assert(x) (void)(x) #endif -#define InvalidPath Assert(!"Invalid Path!") -#define NotImplemented Assert(!"Not Implemented!") +#define InvalidPath AssertAlways(!"Invalid Path!") +#define NotImplemented AssertAlways(!"Not Implemented!") #define NoOp ((void)0) #define StaticAssert(C, ID) global U8 Glue(ID, __LINE__)[(C)?1:-1] From 986a6c526ffd04216cb1c9daa801d97076c90401 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 12 Sep 2026 19:06:54 -0700 Subject: [PATCH 155/251] radlink: implement COFF_ImportBy_NoPrefix --- src/linker/tests/linker_tests.c | 126 ++++++++++++++++++++++++++++++++ src/pe/pe_make_import_table.c | 22 +++++- src/pe/pe_make_import_table.h | 1 + 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index b9cc804b..1fd3f752 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -4909,6 +4909,132 @@ TEST(communal_var_vs_regular_comdat) } } +TEST(import_name_no_prefix) +{ + // IMPORT_NAME_NOPREFIX removes exactly one leading '?', '@', or '_'. + // Unlike IMPORT_NAME_UNDECORATE, it must preserve the rest of the name. + struct { + char *symbol_name; + char *export_name; + } cases[] = { + { "_add", "add" }, + { "_StdcallImport@8", "StdcallImport@8" }, + { "@FastcallImport@8", "FastcallImport@8" }, + { "?Method@Class@@QEAAXXZ", "Method@Class@@QEAAXXZ" }, + { "__DoublePrefix", "_DoublePrefix" }, + { "PlainImport@8", "PlainImport@8" }, + }; + struct { + char *args; + B32 delayed; + } opts[] = { + { "/opt:ref", 0 }, + { "/opt:noref", 0 }, + { "/opt:ref /delayload:noprefix.dll", 1 }, + { "/opt:noref /delayload:noprefix.dll", 1 }, + }; + + for EachElement(case_idx, cases) { + String8 symbol_name = str8_cstring(cases[case_idx].symbol_name); + String8 iat_name = str8f(arena, "__imp_%S", symbol_name); + + // Synthetic x64 inputs isolate the import-name encoding from compiler + // name decoration and do not require a DLL or an x86 toolchain to run. + T_Ok(t_write_def_lib("noprefix.lib", (T_COFF_DefLib){ + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ + { + .type = T_COFF_DefLibMember_Import, + .import = { "noprefix.dll", cases[case_idx].symbol_name, + COFF_ImportBy_NameNoPrefix, COFF_ImportHeader_Code, + .hit_or_ordinal = 17 }, + }, + {0}, + }, + })); + T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){ + .sections = (T_COFF_DefSection[]){ + { + "text", ".text", + str8_lit_comp( + "\x48\x83\xEC\x28" // sub rsp,28h + "\xE8\0\0\0\0" // call decorated function's jump thunk + "\xFF\x15\0\0\0\0" // call [__imp_decorated_function] + "\x48\x83\xC4\x28\xC3" // add rsp,28h; ret + ), + .flags = "rx:code", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 5, cases[case_idx].symbol_name), + T_COFF_DefReloc(X64_Rel32, 11, (char *)iat_name.str), + {0}, + }, + }, + {0}, + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("entry", "text", 0), + // Link-only test: the delay helper only needs to resolve to a ret. + T_COFF_DefSymbol_ExternFunc("__delayLoadHelper2", "text", 19), + T_COFF_DefSymbol_UndefFunc(cases[case_idx].symbol_name), + T_COFF_DefSymbol_Undef((char *)iat_name.str), + {0}, + }, + })); + + for EachElement(opt_idx, opts) { + t_infof("NameNoPrefix import %S -> %s (%s)\n", symbol_name, cases[case_idx].export_name, opts[opt_idx].args); + t_invoke_linkerf("/nodefaultlib /subsystem:console /entry:entry /out:noprefix.exe %s entry.obj noprefix.lib", opts[opt_idx].args); + T_Ok(g_last_exit_code == 0); + + String8 image = t_read_file(arena, str8_lit("noprefix.exe")); + PE_BinInfo bin = pe_bin_info_from_data(arena, image); + COFF_SectionHeader *sections = (COFF_SectionHeader *)(image.str + bin.section_table_range.min); + PE_ParsedImport *import = 0; + U64 iat_voff = 0; + if (opts[opt_idx].delayed) { + T_Ok(bin.data_dir_count > PE_DataDirectoryIndex_DELAY_IMPORT); + PE_ParsedDelayImportTable imports = pe_delay_imports_from_data(arena, bin.is_pe32, bin.section_count, sections, image, bin.data_dir_franges[PE_DataDirectoryIndex_DELAY_IMPORT]); + T_Ok(imports.count == 1); + T_Ok(str8_match(imports.v[0].name, str8_lit("noprefix.dll"), 0)); + T_Ok(imports.v[0].import_count == 1); + import = &imports.v[0].imports[0]; + iat_voff = imports.v[0].iat_voff; + } else { + T_Ok(bin.data_dir_count > PE_DataDirectoryIndex_IMPORT); + PE_ParsedStaticImportTable imports = pe_static_imports_from_data(arena, bin.is_pe32, bin.section_count, sections, image, bin.data_dir_franges[PE_DataDirectoryIndex_IMPORT]); + T_Ok(imports.count == 1); + T_Ok(str8_match(imports.v[0].name, str8_lit("noprefix.dll"), 0)); + T_Ok(imports.v[0].import_count == 1); + import = &imports.v[0].imports[0]; + iat_voff = imports.v[0].import_address_table_voff; + } + T_Ok(import->type == PE_ParsedImport_Name); + if (!opts[opt_idx].delayed) { + T_Ok(import->u.name.hint == 17); + } + T_Ok(str8_match(import->u.name.string, str8_cstring(cases[case_idx].export_name), 0)); + + // Prefix stripping must not rename the COFF symbols or disconnect the + // direct-call thunk from the slot referenced by __imp_. + U64 entry_off = pe_foff_from_voff(image, &bin, bin.entry_point); + S32 call_disp = 0, iat_disp = 0, thunk_disp = 0; + T_Ok(str8_deserial_read_struct(image, entry_off + 5, &call_disp) == sizeof(call_disp)); + T_Ok(str8_deserial_read_struct(image, entry_off + 11, &iat_disp) == sizeof(iat_disp)); + U64 thunk_voff = (U64)((S64)bin.entry_point + 9 + call_disp); + U64 thunk_off = pe_foff_from_voff(image, &bin, thunk_voff); + T_Ok(str8_match(str8_substr(image, rng_1u64(thunk_off, thunk_off + 2)), str8_lit("\xFF\x25"), 0)); + T_Ok(str8_deserial_read_struct(image, thunk_off + 2, &thunk_disp) == sizeof(thunk_disp)); + T_Ok((U64)((S64)bin.entry_point + 15 + iat_disp) == iat_voff); + T_Ok((U64)((S64)thunk_voff + 6 + thunk_disp) == iat_voff); + U64 iat_off = pe_foff_from_voff(image, &bin, iat_voff); + U64 iat_entry = 0, iat_end = 1; + T_Ok(str8_deserial_read_struct(image, iat_off, &iat_entry) == sizeof(iat_entry)); + T_Ok(str8_deserial_read_struct(image, iat_off + 8, &iat_end) == sizeof(iat_end)); + T_Ok(iat_entry != 0 && iat_end == 0); + } + } +} + TEST(import_undecorate) { // Keep the decorated COFF symbols, but use the undecorated DLL export name diff --git a/src/pe/pe_make_import_table.c b/src/pe/pe_make_import_table.c index ee60fcfe..58aa33c4 100644 --- a/src/pe/pe_make_import_table.c +++ b/src/pe/pe_make_import_table.c @@ -18,12 +18,20 @@ pe_make_import_header_list_push(Arena *arena, PE_MakeImportList *list, PE_MakeIm } internal String8 -pe_undecorate_import_name(String8 name) +pe_import_name_no_prefix(String8 name) { - // COFF IMPORT_NAME_UNDECORATE strips one prefix character and the suffix starting at the first '@' + // COFF IMPORT_NAME_NOPREFIX strips exactly one prefix character if (name.size && (name.str[0] == '?' || name.str[0] == '@' || name.str[0] == '_')) { name = str8_skip(name, 1); } + return name; +} + +internal String8 +pe_undecorate_import_name(String8 name) +{ + // IMPORT_NAME_UNDECORATE additionally removes the suffix at the first '@'. + name = pe_import_name_no_prefix(name); return str8_prefix(name, str8_find_needle(name, 0, str8_lit("@"), 0)); } @@ -297,11 +305,15 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach str8_list_push(obj_writer->arena, &iat_sect->data, ordinal_data); } break; case COFF_ImportBy_Undecorate: + case COFF_ImportBy_NameNoPrefix: case COFF_ImportBy_Name: { COFF_ObjSection *int_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".idata$6"), PE_IDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes|comdat_flags, str8_zero()); coff_obj_writer_push_symbol_associative(obj_writer, int_sect, iat_sect); COFF_ObjSymbol *int_symbol = coff_obj_writer_push_symbol_static(obj_writer, int_sect->name, 0, int_sect); String8 lookup_name = import_header.import_by == COFF_ImportBy_Undecorate ? pe_undecorate_import_name(import_header.func_name) : import_header.func_name; + if (import_header.import_by == COFF_ImportBy_NameNoPrefix) { + lookup_name = pe_import_name_no_prefix(import_header.func_name); + } String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, lookup_name); str8_list_push(obj_writer->arena, &int_sect->data, int_data); str8_list_push_aligner(obj_writer->arena, &int_sect->data, 0, 2); @@ -314,7 +326,6 @@ pe_make_import_dll_obj_static(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mach str8_list_push(obj_writer->arena, &ilt_sect->data, str8_array(import_entry, import_size)); str8_list_push(obj_writer->arena, &iat_sect->data, str8_array(import_entry, import_size)); } break; - case COFF_ImportBy_NameNoPrefix: { NotImplemented; } break; default: { InvalidPath; } break; } @@ -473,9 +484,13 @@ pe_make_import_dll_obj_delayed(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mac } } break; case COFF_ImportBy_Undecorate: + case COFF_ImportBy_NameNoPrefix: case COFF_ImportBy_Name: { // put together name look up entry String8 lookup_name = import_header.import_by == COFF_ImportBy_Undecorate ? pe_undecorate_import_name(import_header.func_name) : import_header.func_name; + if (import_header.import_by == COFF_ImportBy_NameNoPrefix) { + lookup_name = pe_import_name_no_prefix(import_header.func_name); + } String8 int_data = coff_make_import_lookup(obj_writer->arena, import_header.hint_or_ordinal, lookup_name); U64 int_data_offset = int_sect->data.total_size; str8_list_push(obj_writer->arena, &int_sect->data, int_data); @@ -516,7 +531,6 @@ pe_make_import_dll_obj_delayed(Arena *arena, COFF_TimeStamp time_stamp, COFF_Mac coff_obj_writer_section_push_reloc_addr(obj_writer, uiat_sect, uiat_data_offset, load_thunk_symbol); } } break; - case COFF_ImportBy_NameNoPrefix: { NotImplemented; } break; } } diff --git a/src/pe/pe_make_import_table.h b/src/pe/pe_make_import_table.h index 82cd598a..5d9a2161 100644 --- a/src/pe/pe_make_import_table.h +++ b/src/pe/pe_make_import_table.h @@ -28,6 +28,7 @@ typedef struct PE_MakeImportList internal void pe_make_import_header_list_push_node(PE_MakeImportList *list, PE_MakeImportNode *node); internal PE_MakeImportNode * pe_make_import_header_list_push(Arena *arena, PE_MakeImportList *list, PE_MakeImport header); internal String8 pe_undecorate_import_name(String8 name); +internal String8 pe_import_name_no_prefix(String8 name); internal COFF_ObjSymbol * pe_make_indirect_jump_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *iat_symbol, String8 thunk_name); internal COFF_ObjSymbol * pe_make_load_thunk_x64(COFF_ObjWriter *obj_writer, COFF_ObjSection *code_sect, COFF_ObjSymbol *imp_addr_ptr, COFF_ObjSymbol *tail_merge, String8 func_name); From 44a37e2a8030aa2dc085a26bf26d7cea0a7b656f Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 12 Sep 2026 19:16:57 -0700 Subject: [PATCH 156/251] fixes for export test --- src/linker/tests/linker_tests.c | 46 ++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 1fd3f752..6dffdbb8 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -2903,7 +2903,9 @@ TEST(import_export) T_Ok(t_write_def_obj("export.obj", (T_COFF_DefObj){ .machine = T_COFF_DefSetMachine(X64), .sections = (T_COFF_DefSection[]){ - { "data", ".data", str8_lit("test"), .flags = "rw:data" }, + // keep the separate from CRT/import data, and back every export + // offset (including ord3 and ord4) with bytes in this section + { "data", ".exports", str8_lit_comp("test\0\0\0\0\0\x09\x0A"), .flags = "rw:data" }, {0} }, .symbols = (T_COFF_DefSymbol[]){ @@ -2964,7 +2966,8 @@ TEST(import_export) PE_BinInfo pe = pe_bin_info_from_data(arena, dll); COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(dll, pe.section_table_range).str; PE_ParsedExportTable export_table = pe_exports_from_data(arena, pe.section_count, section_table, dll, pe.data_dir_franges[PE_DataDirectoryIndex_EXPORT], pe.data_dir_vranges[PE_DataDirectoryIndex_EXPORT]); - COFF_SectionHeader *data_sect = coff_section_header_from_name(str8_zero(), section_table, pe.section_count, str8_lit(".data")); + COFF_SectionHeader *data_sect = coff_section_header_from_name(str8_zero(), section_table, pe.section_count, str8_lit(".exports")); + T_Ok(data_sect != 0); // validate header T_Ok(export_table.flags == 0); @@ -3019,6 +3022,9 @@ TEST(import_export) PE_BinInfo pe = pe_bin_info_from_data(arena, dll); COFF_SectionHeader *section_table = (COFF_SectionHeader *)str8_substr(dll, pe.section_table_range).str; PE_ParsedExportTable export_table = pe_exports_from_data(arena, pe.section_count, section_table, dll, pe.data_dir_franges[PE_DataDirectoryIndex_EXPORT], pe.data_dir_vranges[PE_DataDirectoryIndex_EXPORT]); + COFF_SectionHeader *s1_sect = coff_section_header_from_name(str8_zero(), section_table, pe.section_count, str8_lit(".s1")); + COFF_SectionHeader *s2_sect = coff_section_header_from_name(str8_zero(), section_table, pe.section_count, str8_lit(".s2")); + T_Ok(s1_sect != 0 && s2_sect != 0); // validate header T_Ok(export_table.flags == 0); @@ -3041,10 +3047,10 @@ TEST(import_export) T_Ok(str8_match(export_table.exports[3].forwarder, str8_zero(), 0)); // validate voffs - T_Ok(export_table.exports[0].voff == 0x3000); - T_Ok(export_table.exports[1].voff == 0x4000); - T_Ok(export_table.exports[2].voff == 0x4000); - T_Ok(export_table.exports[3].voff == 0x3000); + T_Ok(export_table.exports[0].voff == s1_sect->voff); + T_Ok(export_table.exports[1].voff == s2_sect->voff); + T_Ok(export_table.exports[2].voff == s2_sect->voff); + T_Ok(export_table.exports[3].voff == s1_sect->voff); // validate ordinals T_Ok(export_table.exports[0].ordinal == 2); @@ -8592,16 +8598,16 @@ TEST(infer_asan) TEST(determ_test) { // compile the test target (torture) - t_invoke_cl("/Brepro /fsanitize=address /c /Z7 /DBUILD_GIT_HASH=Stringify() /Fo:test.obj -I%S /Zc:preprocessor %S/torture/torture_main.c", t_src_path(), t_src_path()); + t_invoke_cl("/Brepro /fsanitize=address /c /Z7 /DBUILD_GIT_HASH=Stringify() /Fo:test.obj -I\"%S\" /Zc:preprocessor \"%S/torture/torture_main.c\"", t_src_path(), t_src_path()); T_Ok(g_last_exit_code == 0); U64 run_count = 25; T_Ok(run_count > 1); String8 test_path = t_make_file_path(arena, str8_lit("test.obj")); - String8 lib_path = str8_chop_last_slash(t_radlink_path()); + String8 lib_path = str8_chop_last_slash(t_linker_path()); // single-threaded link - t_invoke_linkerf("%S /libpath:\"%S\" /debug:full /rad_time_stamp:0 /rad_workers:1 /pdbaltpath:main.pdb /rad_log:-all /rad_ignore:74 /out:main.exe", test_path, lib_path); + t_invoke_linkerf("\"%S\" /libpath:\"%S\" /debug:full /rad_time_stamp:0 /rad_workers:1 /pdbaltpath:main.pdb /rad_log:-all /rad_ignore:74 /out:main.exe", test_path, lib_path); T_Ok(g_last_exit_code == 0); // read b @@ -8612,14 +8618,30 @@ TEST(determ_test) ProcessList linkers = {0}; for EachIndex(i, run_count) { String8 out_path = t_make_file_path(arena, str8f(arena, "%llu.exe", i)); - String8 cmdl = str8f(arena, "%S %S /libpath:\"%S\" /debug:full /rad_time_stamp:0 /rad_imagealtpath:main.exe /pdbaltpath:main.pdb /rad_log:-all /rad_ignore:74 /out:%S", t_radlink_path(), test_path, lib_path, out_path); - Process process_handle = launch_cmd_line(cmdl); + // TODO: use an argument list: launch_cmd_line splits on spaces without parsing + // quotes. Match the baseline link's working directory as well + ProcessLaunchParams params = {0}; + params.path = g_wdir; + params.inherit_env = 1; + str8_list_push(arena, ¶ms.cmd_line, t_linker_path()); + str8_list_push(arena, ¶ms.cmd_line, test_path); + str8_list_pushf(arena, ¶ms.cmd_line, "/libpath:%S", lib_path); + char *args[] = { "/debug:full", "/rad_time_stamp:0", "/rad_imagealtpath:main.exe", "/pdbaltpath:main.pdb", "/rad_log:-all", "/rad_ignore:74" }; + for EachElement(arg_idx, args) { + str8_list_push(arena, ¶ms.cmd_line, str8_cstring(args[arg_idx])); + } + str8_list_pushf(arena, ¶ms.cmd_line, "/out:%S", out_path); + Process process_handle = process_launch(¶ms); T_Ok(!process_match(process_zero(), process_handle)); process_list_push(arena, &linkers, process_handle); } // wait for linkers - for EachNode(n, ProcessNode, linkers.first) { process_join(n->v, max_U64, 0); } + for EachNode(n, ProcessNode, linkers.first) { + U64 exit_code = 1; + T_Ok(process_join(n->v, max_U64, &exit_code)); + T_Ok(exit_code == 0); + } for EachIndex(i, run_count) { Temp temp = temp_begin(arena); From 51bd5e088271f303633bb843f7393a4d33ad9455 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 12 Sep 2026 19:17:49 -0700 Subject: [PATCH 157/251] more accurate name for the function that queries test linker path --- src/dwarf/tests/dwarf_tests.c | 2 +- src/raddbg/tests/raddbg_tests.c | 2 +- src/torture/torture.c | 19 ++++++++++--------- src/torture/torture.h | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/dwarf/tests/dwarf_tests.c b/src/dwarf/tests/dwarf_tests.c index 0ccca30d..cc41abc6 100644 --- a/src/dwarf/tests/dwarf_tests.c +++ b/src/dwarf/tests/dwarf_tests.c @@ -199,7 +199,7 @@ dw_input_from_writer(Arena *arena, DW_Writer *writer) String8 raw_coff = coff_from_obj(scratch.arena, obj); t_write_file(str8_lit("dwarf.obj"), raw_coff); - t_invoke(t_radlink_path(), str8_lit("/subsystem:console /entry:entry /out:a.exe /debug:full dwarf.obj"), max_U64); + t_invoke(t_linker_path(), str8_lit("/subsystem:console /entry:entry /out:a.exe /debug:full dwarf.obj"), max_U64); delete_file_at_path(t_make_file_path(scratch.arena, str8_lit("a.pdb"))); String8 exe = t_read_file(arena, str8_lit("a.exe")); diff --git a/src/raddbg/tests/raddbg_tests.c b/src/raddbg/tests/raddbg_tests.c index 2a6b7de7..b737346c 100644 --- a/src/raddbg/tests/raddbg_tests.c +++ b/src/raddbg/tests/raddbg_tests.c @@ -1503,7 +1503,7 @@ internal TEST_FUNCTION_DEF(dbg_script_runner) } // run linkers - String8 linker_path = t_radlink_path(); + String8 linker_path = t_linker_path(); for EachNode(directive, T_DbgScriptDirective, script.directives[OperatingSystem_CURRENT][T_DbgScriptDirectiveKind_Link].first) { String8 expanded_args = lnk_expand_env_vars_windows(arena, script_vars, directive->args); if (t_invoke(linker_path, expanded_args, max_U64) == 0) { diff --git a/src/torture/torture.c b/src/torture/torture.c index c703c589..b87a83bf 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -59,10 +59,10 @@ t_test_layer_from_name(Arena *arena, String8 pattern) internal Linker t_id_linker(void) { - String8 name = str8_chop_last_dot(str8_skip_last_slash(g_linker_path)); - if (str8_match(name, str8_lit("radlink"), StringMatchFlag_CaseInsensitive)) { return Linker_radlink; } - if (str8_match(name, str8_lit("link"), StringMatchFlag_CaseInsensitive)) { return Linker_msvc; } - if (str8_match(name, str8_lit("lld-link"), StringMatchFlag_CaseInsensitive)) { return Linker_lld; } + String8 name = str8_chop_last_dot(str8_skip_last_slash(t_linker_path())); + if (str8_matchi(name, str8_lit("radlink"))) { return Linker_radlink; } + if (str8_matchi(name, str8_lit("link"))) { return Linker_msvc; } + if (str8_matchi(name, str8_lit("lld-link"))) { return Linker_lld; } return Linker_Null; } @@ -372,7 +372,7 @@ t_cl_version(void) } internal String8 -t_radlink_path(void) +t_linker_path(void) { local_persist String8 path = {0}; if(g_linker_path.size != 0) @@ -384,9 +384,9 @@ t_radlink_path(void) ArenaParams params = { .reserve_size = sizeof(buffer), .commit_size = sizeof(buffer), .optional_backing_buffer = buffer }; Arena *arena = arena_alloc_(¶ms); #if OS_WINDOWS - path = full_path_from_path(arena, str8_lit("radlink.exe")); + path = str8f(arena, "%S/radlink.exe", get_process_info()->binary_path); #else - path = full_path_from_path(arena, str8_lit("radlink")); + path = str8f(arena, "%S/radlink", get_process_info()->binary_path); #endif AssertAlways(path.size); } @@ -444,7 +444,8 @@ t_src_path(void) local_persist U8 path[4096] = {0}; if (path[0] == 0) { Temp scratch = scratch_begin(0, 0); - String8 src = str8f(scratch.arena, "%S/src", t_cwd_path()); + String8 root = str8_chop_last_slash(get_process_info()->binary_path); + String8 src = str8f(scratch.arena, "%S/src", root); MemoryCopyStr8(path, src); path[src.size] = 0; scratch_end(scratch); @@ -880,7 +881,7 @@ t_invoke_linkerf(char *fmt, ...) va_start(args, fmt); String8 cmdl = push_str8fv(scratch.arena, fmt, args); va_end(args); - B32 is_ok = t_invoke(t_radlink_path(), cmdl, max_U64); + B32 is_ok = t_invoke(t_linker_path(), cmdl, max_U64); scratch_end(scratch); return is_ok; } diff --git a/src/torture/torture.h b/src/torture/torture.h index 56578809..3b27d341 100644 --- a/src/torture/torture.h +++ b/src/torture/torture.h @@ -50,7 +50,7 @@ internal String8 t_radbin_path(void); internal String8 t_cl_path(void); internal String8 t_clang_path(void); internal String8 t_gcc_path(void); -internal String8 t_radlink_path(void); +internal String8 t_linker_path(void); internal String8 t_cwd_path(void); internal String8 t_src_path(void); @@ -60,7 +60,7 @@ internal B32 t_invoke_cl(char *fmt, ...); internal B32 t_invoke_linkerf(char *fmt, ...); internal B32 t_invoke_radbin(char *fmt, ...); internal void t_kill_all(String8 pattern); -#define t_invoke_linker_timeout(c, t) T_Ok(t_invoke(t_radlink_path(), c, t)) +#define t_invoke_linker_timeout(c, t) T_Ok(t_invoke(t_linker_path(), c, t)) #define t_invoke_linker_timeoutf(t, f, ...) t_invoke_linker_timeout(push_str8f(arena, f, ##__VA_ARGS__), t) #define t_invoke_linker(c) t_invoke_linker_timeout(c, max_U64) From 458e5029dbf0b23c3ea3b35f4ba42da1bf2926f6 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sun, 13 Sep 2026 20:55:44 -0700 Subject: [PATCH 158/251] radlink: absolute symbols with identical value are legal --- src/linker/lnk_symbol_table.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 98178cd1..9fd5e51e 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -260,7 +260,11 @@ lnk_can_replace_symbol(LNK_Symbol *dst, LNK_Symbol *src) } // abs vs abs else if (dst_interp == COFF_SymbolValueInterp_Abs && src_interp == COFF_SymbolValueInterp_Abs) { - lnk_error_multiply_defined_symbol(dst, src); + if (dst_parsed.value == src_parsed.value) { + can_replace = lnk_symbol_is_before(src, dst); + } else { + lnk_error_multiply_defined_symbol(dst, src); + } } // weak vs weak else if (dst_interp == COFF_SymbolValueInterp_Weak && src_interp == COFF_SymbolValueInterp_Weak) { From 4663d1a89a249e764404e2789e664687f96664fa Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 15 Sep 2026 18:19:13 -0700 Subject: [PATCH 159/251] radlink: handle thread pool release correctly --- src/linker/thread_pool/thread_pool.c | 31 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/linker/thread_pool/thread_pool.c b/src/linker/thread_pool/thread_pool.c index d91efc44..513a7cdf 100644 --- a/src/linker/thread_pool/thread_pool.c +++ b/src/linker/thread_pool/thread_pool.c @@ -1,11 +1,16 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -internal void +internal B32 tp_run_tasks(TP_Context *pool, TP_Worker *worker) { barrier_wait(pool->run_barrier); + // @pool_shutdown + if (!pool->is_live) { + return 0; + } + for (;;) { S64 task_left = ins_atomic_u64_dec_eval(&pool->task_left); @@ -24,6 +29,7 @@ tp_run_tasks(TP_Context *pool, TP_Worker *worker) } barrier_wait(pool->run_barrier); + return 1; } internal void @@ -31,9 +37,7 @@ tp_worker_main(void *raw_worker) { TP_Worker *worker = raw_worker; TP_Context *pool = worker->pool; - for (; pool->is_live; ) { - tp_run_tasks(pool, worker); - } + while (tp_run_tasks(pool, worker)) {} } internal void @@ -41,9 +45,11 @@ tp_worker_main_shared(void *raw_worker) { TP_Worker *worker = raw_worker; TP_Context *pool = worker->pool; - for (; pool->is_live; ) { + for (;;) { if (semaphore_take(pool->exec_semaphore, max_U64)) { - tp_run_tasks(pool, worker); + if (!tp_run_tasks(pool, worker)) { + break; + } } } } @@ -101,16 +107,21 @@ tp_release(TP_Context *pool) B32 is_shared = pool->exec_semaphore.u64[0] != 0; if (is_shared) { - for EachIndex(i, pool->worker_count) { - semaphore_drop(pool->exec_semaphore); - } + semaphore_drop_count(pool->exec_semaphore, pool->worker_count - 1); } + + // rendezvous with all workers at the task dispatch barrier so + // they observe @pool_shutdown and exit without running another batch + barrier_wait(pool->run_barrier); + for (U64 i = 1; i < pool->worker_count; i += 1) { - thread_detach(pool->worker_arr[i].handle); + thread_join(pool->worker_arr[i].handle, max_U64); } + if (is_shared) { semaphore_release(pool->exec_semaphore); } + barrier_release(pool->run_barrier); barrier_release(pool->barrier); From 5b646344eb1cdd9d69a35f6b60de7135dffa660d Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 15 Sep 2026 18:35:22 -0700 Subject: [PATCH 160/251] radlink: implement /NOENTRY --- src/coff/coff_parse.c | 86 ++++++++-------- src/linker/lnk.c | 16 +-- src/linker/lnk_config.c | 10 ++ src/linker/lnk_config.h | 2 + src/linker/tests/linker_tests.c | 169 ++++++++++++++++++++++++++++++++ 5 files changed, 234 insertions(+), 49 deletions(-) diff --git a/src/coff/coff_parse.c b/src/coff/coff_parse.c index c2d129f7..b5848cc8 100644 --- a/src/coff/coff_parse.c +++ b/src/coff/coff_parse.c @@ -166,20 +166,20 @@ coff_section_header_array_from_name(Arena *arena, String8 string_table, COFF_Sec } -internal COFF_ParsedSymbol -coff_parse_symbol32(String8 string_table, COFF_Symbol32 *sym32) -{ - COFF_ParsedSymbol result = coff_parse_symbol32_no_name(sym32); - result.name = coff_read_symbol_name(string_table, &sym32->name); - return result; -} - -internal force_inline COFF_ParsedSymbol -coff_parse_symbol32_no_name(COFF_Symbol32 *sym32) -{ - COFF_ParsedSymbol result = {0}; - result.value = sym32->value; - result.section_number = sym32->section_number; +internal COFF_ParsedSymbol +coff_parse_symbol32(String8 string_table, COFF_Symbol32 *sym32) +{ + COFF_ParsedSymbol result = coff_parse_symbol32_no_name(sym32); + result.name = coff_read_symbol_name(string_table, &sym32->name); + return result; +} + +internal force_inline COFF_ParsedSymbol +coff_parse_symbol32_no_name(COFF_Symbol32 *sym32) +{ + COFF_ParsedSymbol result = {0}; + result.value = sym32->value; + result.section_number = sym32->section_number; result.type = sym32->type; result.storage_class = sym32->storage_class; result.aux_symbol_count = sym32->aux_symbol_count; @@ -187,19 +187,19 @@ coff_parse_symbol32_no_name(COFF_Symbol32 *sym32) return result; } -internal COFF_ParsedSymbol -coff_parse_symbol16(String8 string_table, COFF_Symbol16 *sym16) -{ - COFF_ParsedSymbol result = coff_parse_symbol16_no_name(sym16); - result.name = coff_read_symbol_name(string_table, &sym16->name); - return result; -} - -internal force_inline COFF_ParsedSymbol -coff_parse_symbol16_no_name(COFF_Symbol16 *sym16) -{ - COFF_ParsedSymbol result = {0}; - result.value = sym16->value; +internal COFF_ParsedSymbol +coff_parse_symbol16(String8 string_table, COFF_Symbol16 *sym16) +{ + COFF_ParsedSymbol result = coff_parse_symbol16_no_name(sym16); + result.name = coff_read_symbol_name(string_table, &sym16->name); + return result; +} + +internal force_inline COFF_ParsedSymbol +coff_parse_symbol16_no_name(COFF_Symbol16 *sym16) +{ + COFF_ParsedSymbol result = {0}; + result.value = sym16->value; if (sym16->section_number == COFF_Symbol_DebugSection16) { result.section_number = COFF_Symbol_DebugSection32; } else if (sym16->section_number == COFF_Symbol_AbsSection16) { @@ -214,8 +214,8 @@ coff_parse_symbol16_no_name(COFF_Symbol16 *sym16) return result; } -internal COFF_ParsedSymbol -coff_parse_symbol(COFF_FileHeaderInfo header, String8 string_table, String8 symbol_table, U32 symbol_idx) +internal COFF_ParsedSymbol +coff_parse_symbol(COFF_FileHeaderInfo header, String8 string_table, String8 symbol_table, U32 symbol_idx) { COFF_ParsedSymbol symbol; if (header.is_big_obj) { @@ -223,20 +223,20 @@ coff_parse_symbol(COFF_FileHeaderInfo header, String8 string_table, String8 symb } else { symbol = coff_parse_symbol16(string_table, (COFF_Symbol16 *)symbol_table.str + symbol_idx); } - return symbol; -} - -internal force_inline COFF_ParsedSymbol -coff_parse_symbol_no_name(COFF_FileHeaderInfo header, String8 symbol_table, U32 symbol_idx) -{ - COFF_ParsedSymbol symbol; - if (header.is_big_obj) { - symbol = coff_parse_symbol32_no_name((COFF_Symbol32 *)symbol_table.str + symbol_idx); - } else { - symbol = coff_parse_symbol16_no_name((COFF_Symbol16 *)symbol_table.str + symbol_idx); - } - return symbol; -} + return symbol; +} + +internal force_inline COFF_ParsedSymbol +coff_parse_symbol_no_name(COFF_FileHeaderInfo header, String8 symbol_table, U32 symbol_idx) +{ + COFF_ParsedSymbol symbol; + if (header.is_big_obj) { + symbol = coff_parse_symbol32_no_name((COFF_Symbol32 *)symbol_table.str + symbol_idx); + } else { + symbol = coff_parse_symbol16_no_name((COFF_Symbol16 *)symbol_table.str + symbol_idx); + } + return symbol; +} internal COFF_Symbol32Array coff_symbol_array_from_data_16(Arena *arena, String8 raw_coff, U64 symbol_array_off, U64 symbol_count) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 0d908965..6afd95d5 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1366,13 +1366,13 @@ lnk_load_inputs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer // load new libs lnk_load_libs(tp, arena, config, inputer, link); - // resolve entry point + // resolve entry point and subsystem if (link->try_to_resolve_entry_point) { B32 is_entry_point_name_inferred = config->entry_point_name.size == 0; // loop over all possible subsystems and entry point names and pick // subsystem that has a defined entry point symbol - if (config->entry_point_name.size == 0) { + if (config->no_entry == 0 && config->entry_point_name.size == 0) { PE_WindowsSubsystem subsys_first = config->subsystem; PE_WindowsSubsystem subsys_last = config->subsystem == PE_WindowsSubsystem_UNKNOWN ? PE_WindowsSubsystem_COUNT : config->subsystem+1; LNK_Symbol *entry_point_symbol = 0; @@ -1391,7 +1391,7 @@ lnk_load_inputs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer } // search for entry point in libs - if (config->entry_point_name.size == 0 && config->subsystem != PE_WindowsSubsystem_UNKNOWN) { + if (config->no_entry == 0 && config->entry_point_name.size == 0 && config->subsystem != PE_WindowsSubsystem_UNKNOWN) { String8Array entry_points = pe_get_entry_point_names(config->machine, config->subsystem, config->file_characteristics); for EachIndex(entry_idx, entry_points.count) { for (LNK_LibNode *lib_n = link->libs.first; lib_n != 0; lib_n = lib_n->next) { @@ -1427,7 +1427,9 @@ lnk_load_inputs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer } // generate undefined symbol for entry point - lnk_include_symbol(config, config->entry_point_name, 0); + if (config->entry_point_name.size) { + lnk_include_symbol(config, config->entry_point_name, 0); + } // do we have a subsystem? if (config->subsystem != PE_WindowsSubsystem_UNKNOWN) { @@ -2271,7 +2273,7 @@ lnk_link_image(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer // // was entry point resolved? // - if (config->entry_point_name.size == 0 || link->try_to_resolve_entry_point) { + if (config->no_entry == 0 && (config->entry_point_name.size == 0 || link->try_to_resolve_entry_point)) { String8 machine_str = coff_string_from_machine_type(config->machine); String8 subsystem_str = pe_string_from_subsystem(config->subsystem); String8Array entry_points = pe_get_entry_point_names(config->machine, config->subsystem, config->file_characteristics); @@ -5635,7 +5637,7 @@ lnk_build_win32_header(Arena *arena, LNK_SymbolTable *symtab, LNK_Config *config // // entry point // - { + if (config->no_entry == 0) { Temp scratch = scratch_begin(&arena, 1); COFF_SectionHeader **section_table = push_array(arena, COFF_SectionHeader *, coff_section_table_count + 1); @@ -7076,6 +7078,8 @@ entry_point(CmdLine *cmdline) case LNK_BootMode_TypeServer: lnk_run_type_server(tp, tp_arena, config); break; } + // Stop workers before tearing down linker state. + tp_release(tp); lnk_log_end(); scratch_end(scratch); } diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index f44b4f1e..d5bd26ec 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -50,6 +50,7 @@ global LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Natvis, 0, LNK_CmdValueKind_Scalar, "NATVIS", ":FILENAME", "NATVIS to embed in the PDB." }, { LNK_CmdSwitch_NoDefaultLib, 1, LNK_CmdValueKind_Scalar, "NODEFAULTLIB", ":LIBNAME", "Ignore a /DEFAULTLIB." }, { LNK_CmdSwitch_NoDefaultLib, 0, LNK_CmdValueKind_Scalar, "NOD", ":LIBNAME", "Alias for /NODEFAULTLIB." }, + { LNK_CmdSwitch_NoEntry, 0, LNK_CmdValueKind_Null, "NOENTRY", "", "Link to a DLL without an entry point." }, { LNK_CmdSwitch_NoExp, 0, LNK_CmdValueKind_Null, "NOEXP", "", "No support." }, { LNK_CmdSwitch_NoImpLib, 0, LNK_CmdValueKind_Null, "NOIMPLIB", "", "Do not create the import library." }, { LNK_CmdSwitch_NxCompat, 0, LNK_CmdValueKind_Scalar, "NXCOMPAT", "[:NO]", "Image is compatible with data execution prevention." }, @@ -1362,6 +1363,7 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8 val } config->entry_point_name = new_entry_point_name; + config->no_entry = 0; } break; case LNK_CmdSwitch_Export: { @@ -1701,6 +1703,10 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8 val } } break; + case LNK_CmdSwitch_NoEntry: { + config->no_entry = 1; + } break; + case LNK_CmdSwitch_NoExp: { config->build_exp = 0; } break; @@ -3190,6 +3196,10 @@ lnk_config_init(U64 argc, char **argv) lnk_apply_write_temp_files(arena, config); } + if (config->no_entry && (config->file_characteristics & PE_ImageFileCharacteristic_DLL) == 0) { + lnk_error_cmd_switch(LNK_Error_IncomatibleCmdOptions, 0, LNK_CmdSwitch_NoEntry, "cannot link current image without an entry point; please use /DLL"); + } + scratch_end(scratch); ProfEnd(); return config; diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index 5ad6bb20..d4f025a5 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -82,6 +82,7 @@ typedef enum LNK_CmdSwitch_Merge, LNK_CmdSwitch_Natvis, LNK_CmdSwitch_NoDefaultLib, + LNK_CmdSwitch_NoEntry, LNK_CmdSwitch_NoExp, LNK_CmdSwitch_NoImpLib, LNK_CmdSwitch_NxCompat, @@ -425,6 +426,7 @@ typedef struct LNK_Config LNK_SwitchState type_server; LNK_SwitchState sort_imports; LNK_SwitchState llvm_addrsig; + B32 no_entry; } LNK_Config; // --- MSVC Error Codes -------------------------------------------------------- diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 6dffdbb8..42cd74fd 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -637,6 +637,175 @@ TEST(simple_link_test) T_Ok(opt->loader_flags == 0); } +TEST(entry_point) +{ + // Give the user entry, CRT startup, and custom entry distinct offsets so that + // the PE header tells us which symbol was selected, including CRT redirection. + struct { + char *user_entry; + char *crt_entry; + } entries[] = { + { "main", "mainCRTStartup" }, + { "wmain", "wmainCRTStartup" }, + { "WinMain", "WinMainCRTStartup" }, + { "wWinMain", "wWinMainCRTStartup" }, + { "DllMain", "_DllMainCRTStartup" }, + }; + for EachElement(i, entries) { + T_COFF_DefObj obj = { + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "text", ".text", str8_lit_comp("\xc3\xc3\xc3"), .flags = "rx:code@1" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc(entries[i].user_entry, "text", 0), + T_COFF_DefSymbol_ExternFunc(entries[i].crt_entry, "text", 1), + T_COFF_DefSymbol_ExternFunc("custom_entry", "text", 2), + {0} + } + }; + T_Ok(t_write_def_obj((char *)str8f(arena, "%s.obj", entries[i].user_entry).str, obj)); + T_Ok(t_write_def_lib((char *)str8f(arena, "%s.lib", entries[i].user_entry).str, (T_COFF_DefLib){ + .members = (T_COFF_DefLibMember[]){ + { .type = T_COFF_DefLibMember_Obj, .obj = obj }, + {0} + } + })); + } + + // Keep a real input section even when no startup object is needed. + T_Ok(t_write_def_obj("data.obj", (T_COFF_DefObj){ + .sections = (T_COFF_DefSection[]){ + { "data", ".data", str8_lit("data"), .flags = "rw:data" }, + {0} + } + })); + T_Ok(t_write_def_obj("crt.obj", (T_COFF_DefObj){ + .sections = (T_COFF_DefSection[]){ + { "text", ".text", str8_lit_comp("\xc3"), .flags = "rx:code@1" }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("mainCRTStartup", "text", 0), + {0} + } + })); + + // /NOENTRY must not extract this member just to resolve DLL startup. Without /NOENTRY, + // the missing dependency proves that the member was extracted. + T_Ok(t_write_def_lib("bad_startup.lib", (T_COFF_DefLib){ + .members = (T_COFF_DefLibMember[]){ + { .type = T_COFF_DefLibMember_Obj, .obj = { + .sections = (T_COFF_DefSection[]){ + { "text", ".text", str8_lit_comp("\xe8\0\0\0\0\xc3"), .flags = "rx:code@1", + .relocs = (T_COFF_DefReloc[]){ + T_COFF_DefReloc(X64_Rel32, 1, "missing_dependency"), + {0} + } + }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_ExternFunc("_DllMainCRTStartup", "text", 0), + T_COFF_DefSymbol_UndefFunc("missing_dependency"), + {0} + } + } }, + {0} + } + })); + + struct { + char *options; + char *inputs; + PE_WindowsSubsystem subsystem; + S32 entry_off; // -1 means no entry point. + LNK_ErrorCode error; + } cases[] = { + // Explicit /ENTRY bypasses CRT redirection and overrides default startup. + { "/subsystem:console /entry:custom_entry", "main.obj", PE_WindowsSubsystem_WINDOWS_CUI, 2 }, + { "/subsystem:console /entry:main", "main.obj", PE_WindowsSubsystem_WINDOWS_CUI, 0 }, + { "/entry:main", "main.obj", PE_WindowsSubsystem_WINDOWS_CUI, 0 }, + { "/dll /entry:custom_entry", "DllMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, 2 }, + { "/subsystem:console /entry:custom_entry", "main.lib", PE_WindowsSubsystem_WINDOWS_CUI, 2 }, + + // Infer the subsystem and redirect each user entry to its CRT startup. + { "", "main.obj", PE_WindowsSubsystem_WINDOWS_CUI, 1 }, + { "", "wmain.obj", PE_WindowsSubsystem_WINDOWS_CUI, 1 }, + { "", "WinMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, 1 }, + { "", "wWinMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, 1 }, + { "/subsystem:console", "main.obj", PE_WindowsSubsystem_WINDOWS_CUI, 1 }, + { "/subsystem:console", "wmain.obj", PE_WindowsSubsystem_WINDOWS_CUI, 1 }, + { "/subsystem:windows", "WinMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, 1 }, + { "/subsystem:windows", "wWinMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, 1 }, + { "/subsystem:console", "crt.obj", PE_WindowsSubsystem_WINDOWS_CUI, 0 }, + + // Resolve startup from a library, or use the default DLL entry/subsystem. + { "/subsystem:console", "main.lib", PE_WindowsSubsystem_WINDOWS_CUI, 1 }, + { "/subsystem:console /entry:wmainCRTStartup", "wmain.lib", PE_WindowsSubsystem_WINDOWS_CUI, 1 }, + { "/subsystem:windows", "WinMain.lib", PE_WindowsSubsystem_WINDOWS_GUI, 1 }, + { "/subsystem:windows /entry:wWinMainCRTStartup", "wWinMain.lib", PE_WindowsSubsystem_WINDOWS_GUI, 1 }, + { "/dll", "DllMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, 1 }, + { "/dll", "DllMain.lib", PE_WindowsSubsystem_WINDOWS_GUI, 1 }, + { "/dll /subsystem:console", "DllMain.obj", PE_WindowsSubsystem_WINDOWS_CUI, 1 }, + + // /NOENTRY leaves a zero RVA even when default startup symbols are available. + { "/dll /noentry", "", PE_WindowsSubsystem_WINDOWS_GUI, -1 }, + { "/noentry /dll", "", PE_WindowsSubsystem_WINDOWS_GUI, -1 }, + { "/dll /noentry", "DllMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, -1 }, + { "/dll /noentry", "DllMain.lib", PE_WindowsSubsystem_WINDOWS_GUI, -1 }, + { "/dll /noentry", "bad_startup.lib", PE_WindowsSubsystem_WINDOWS_GUI, -1 }, + { "/dll /noentry /subsystem:console", "", PE_WindowsSubsystem_WINDOWS_CUI, -1 }, + + // Match LINK: the last /ENTRY or /NOENTRY wins, including undefined entries. + { "/dll /entry:custom_entry /noentry", "DllMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, -1 }, + { "/dll /noentry /entry:custom_entry", "DllMain.obj", PE_WindowsSubsystem_WINDOWS_GUI, 2 }, + { "/dll /entry:missing_entry /noentry", "", PE_WindowsSubsystem_WINDOWS_GUI, -1, LNK_Error_UnresolvedSymbol }, + { "/dll /noentry /entry:missing_entry", "", 0, -1, LNK_Error_UnresolvedSymbol }, + { "/noentry /subsystem:console /entry:main", "main.obj", PE_WindowsSubsystem_WINDOWS_CUI, 0 }, + + // Missing startup, unresolved explicit entry, and invalid /NOENTRY usage. + { "/subsystem:console", "", 0, 0, LNK_Error_EntryPoint }, + { "/dll", "", 0, 0, LNK_Error_EntryPoint }, + { "/subsystem:console /entry:missing_entry", "main.obj", 0, 0, LNK_Error_UnresolvedSymbol }, + { "/dll", "bad_startup.lib", 0, 0, LNK_Error_UnresolvedSymbol }, + { "/noentry /subsystem:console", "", 0, 0, LNK_Error_IncomatibleCmdOptions }, + { "/subsystem:console /entry:main /noentry", "main.obj", 0, 0, LNK_Error_IncomatibleCmdOptions }, + }; + + for EachElement(i, cases) { + String8 out_name = str8f(arena, "entry_%u.dll", i); + test_outf("entry configuration %u: %s data.obj %s\n", i, cases[i].options, cases[i].inputs); + T_Ok(t_invoke_linkerf("/machine:x64 /nod /manifest:no /opt:ref /opt:noicf /out:%S %s data.obj %s", out_name, cases[i].options, cases[i].inputs)); + + if (cases[i].error) { + T_Ok(g_last_exit_code != 0); + + if (t_id_linker() == Linker_radlink) { + T_Ok(g_last_exit_code == cases[i].error); + } + } else { + T_Ok(g_last_exit_code == 0); + + String8 image = t_read_file(arena, out_name); + PE_BinInfo pe = pe_bin_info_from_data(arena, image); + T_Ok(pe.arch == Arch_x64); + T_Ok(pe.subsystem == cases[i].subsystem); + + if (cases[i].entry_off < 0) { + T_Ok(pe.entry_point == 0); + } else { + COFF_SectionHeader *sections = (COFF_SectionHeader *)str8_substr(image, pe.section_table_range).str; + String8 strings = str8_substr(image, pe.string_table_range); + COFF_SectionHeader *text = coff_section_header_from_name(strings, sections, pe.section_count, str8_lit(".text")); + T_Ok(text); + T_Ok(pe.entry_point == text->voff + cases[i].entry_off); + } + } + } +} + TEST(map) { T_Ok(t_write_def_obj("map.obj", (T_COFF_DefObj){ From 64c2216ae694d97276c9e3cec8de016664bcd569 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sun, 13 Sep 2026 00:36:02 -0700 Subject: [PATCH 161/251] radlink: encode the DBI major build number in its 16-bit field --- src/pdb/pdb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pdb/pdb.h b/src/pdb/pdb.h index 771ddad4..685a843b 100644 --- a/src/pdb/pdb.h +++ b/src/pdb/pdb.h @@ -181,7 +181,7 @@ typedef U16 PDB_DbiBuildNumber; #define PDB_DbiBuildNumberMinor(bn) ((bn)&0xFF) #define PDB_DbiBuildNumberMajor(bn) (((bn) >> 8)&0x7F) #define PDB_DbiBuildNumberNewFormat(bn) (!!((bn)&PDB_DbiBuildNumberNewFormatFlag)) -#define PDB_DbiMakeBuildNumber(maj, min) (PDB_DbiBuildNumber)(PDB_DbiBuildNumberNewFormatFlag | ((min)&0xFF) | (((maj)&0x7F) << 16)) +#define PDB_DbiMakeBuildNumber(maj, min) (PDB_DbiBuildNumber)(PDB_DbiBuildNumberNewFormatFlag | ((min)&0xFF) | (((maj)&0x7F) << 8)) typedef U16 PDB_DbiHeaderFlags; enum From 2afb78e49877621993ebeb4441a3e3ba1d73c8cd Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 16 Sep 2026 15:12:15 -0700 Subject: [PATCH 162/251] radlink: resolve alternate-name fallbacks through prevailing weak symbols Follow prevailing weak definitions instead of synthetic anti-dependency placeholders. Resolve all weak symbols before publishing results so anti-dependency chaining rules are independent of worker order. --- src/linker/lnk.c | 23 +-- src/linker/lnk_symbol_table.c | 147 +++++++++----- src/linker/tests/linker_tests.c | 339 ++++++++++++++++++++++++++------ 3 files changed, 387 insertions(+), 122 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 6afd95d5..ad666b0d 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1154,14 +1154,14 @@ internal LNK_Link * lnk_link_init(TP_Arena *arena, LNK_Config *config) { LNK_Link *link = push_array(arena->v[0], LNK_Link, 1); - link->arena = arena_alloc(.name = "LINK"); - link->last_symbol_input = &link->objs.first; - link->last_include = &config->include_symbol_list.first; + link->arena = arena_alloc(.name = "LINK"); + link->last_symbol_input = &link->objs.first; + link->last_include = &config->include_symbol_list.first; link->last_func_override_alt_name = &config->alt_name_list.first; - link->last_default_lib = &config->input_default_lib_list.first; - link->last_obj_lib = &config->input_obj_lib_list.first; - link->last_cmd_lib = &config->input_list[LNK_Input_Lib].first; - link->try_to_resolve_entry_point = 1; + link->last_default_lib = &config->input_default_lib_list.first; + link->last_obj_lib = &config->input_obj_lib_list.first; + link->last_cmd_lib = &config->input_list[LNK_Input_Lib].first; + link->try_to_resolve_entry_point = 1; return link; } @@ -1924,14 +1924,13 @@ lnk_link_inputs(TP_Context *tp, search_anti_deps = 0; // replace undefined symbols that have an alternate name with a weak symbol - for (LNK_AltNameNode *alt_name_n = config->alt_name_list.first; alt_name_n != 0; alt_name_n = alt_name_n->next) { + for EachNode(alt_name_n, LNK_AltNameNode, config->alt_name_list.first) { LNK_SymbolHashTrie *symbol_ht = lnk_symbol_table_search_(symtab, alt_name_n->v.from); if (symbol_ht) { COFF_SymbolValueInterpType interp = lnk_interp_from_symbol(symbol_ht->symbol); if (interp == COFF_SymbolValueInterp_Undefined) { - // clear out slot so weak symbol can replace undefined symbol (general rule is - // weak symbol is not allowed to replace undefined) - LNK_Symbol *undef_symbol = symbol_ht->symbol; + // /ALTERNATENAME explicitly installs a fallback for an unresolved name. + // Normally, an undefined external prevails over a SERACH_LIBRARY weak symbol. symbol_ht->symbol = 0; // make obj with alternamte name symbol @@ -1945,10 +1944,12 @@ lnk_link_inputs(TP_Context *tp, coff_obj_writer_release(&obj_writer); } + // input synthetic object LNK_Obj *obj_with_alt_name = alt_name_n->v.obj; String8 obj_with_alt_name_path = obj_with_alt_name ? obj_with_alt_name->path : str8_lit("RADLINK"); lnk_inputer_push_obj_linkgen(inputer, obj_with_alt_name ? obj_with_alt_name->link_member : 0, obj_with_alt_name_path, alt_name_obj_data); + // request another round of anti-dependency symbols search search_anti_deps = 1; } } diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 9fd5e51e..d0578332 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -37,6 +37,12 @@ lnk_make_symbol(Arena *arena, String8 name, LNK_Obj *obj, U32 symbol_idx, LNK_Sy return symbol; } +internal B32 +lnk_obj_symbol_ref_match(LNK_ObjSymbolRef a, LNK_ObjSymbolRef b) +{ + return MemoryMatchStruct(&a, &b); +} + internal LNK_ObjSymbolRefNode * lnk_last_ref_from_symbol(LNK_Symbol *symbol) { @@ -717,69 +723,92 @@ lnk_resolve_weak_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_Ob { Temp scratch = scratch_begin(0,0); - B32 is_resolved = 0; - - struct S { struct S *next; LNK_ObjSymbolRef symbol; B32 is_anti_dep; }; + struct S { struct S *next; LNK_ObjSymbolRef symbol; }; struct S *sf = 0, *sl = 0; - LNK_ObjSymbolRef current_symbol = symbol; + LNK_ObjSymbolRef current_symbol = symbol; + B32 followed_fallback = 0; + B32 is_resolved = 0; for (;;) { - // guard against self-referencing weak symbols - struct S *was_visited = 0; - for (struct S *s = sf; s != 0; s = s->next) { - if (MemoryCompare(&s->symbol, ¤t_symbol, sizeof(LNK_ObjSymbolRef)) == 0) { was_visited = s; break; } - } - if (was_visited) { - String8List chain = {0}; - for (struct S *s = sf; s != 0; s = s->next) { - COFF_ParsedSymbol s_parsed = lnk_parsed_symbol_from_coff_symbol_idx(s->symbol.obj, s->symbol.symbol_idx); - str8_list_pushf(scratch.arena, &chain, "\t%S Symbol %S (No. %#x) =>", s->symbol.obj->path, s_parsed.name, s->symbol.symbol_idx); + // guard against cyclic weak symbols + { + struct S *was_visited = 0; + for EachNode(s, struct S, sf) { + if (s->symbol.obj == current_symbol.obj && s->symbol.symbol_idx == current_symbol.symbol_idx) { was_visited = s; break; } } - COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx); - str8_list_pushf(scratch.arena, &chain, "\t%S Symbol %S (No. %#x)", sf->symbol.obj->path, symbol_parsed.name, sf->symbol.symbol_idx); + if (was_visited) { + String8List chain = {0}; + for EachNode(s, struct S, sf) { + COFF_ParsedSymbol s_parsed = lnk_parsed_symbol_from_coff_symbol_idx(s->symbol.obj, s->symbol.symbol_idx); + str8_list_pushf(scratch.arena, &chain, "\t%S Symbol %S (No. %#x) =>", s->symbol.obj->path, s_parsed.name, s->symbol.symbol_idx); + } + COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx); + str8_list_pushf(scratch.arena, &chain, "\t%S Symbol %S (No. %#x)", sf->symbol.obj->path, symbol_parsed.name, sf->symbol.symbol_idx); - String8 chain_string = str8_list_join(scratch.arena, &chain, &(StringJoin){ .sep = str8_lit("\n") }); - lnk_error_obj(LNK_Error_WeakCycle, symbol.obj, "unable to resolve cyclic symbol %S; ref chain:\n%S", symbol_parsed.name, chain_string); + String8 chain_string = str8_list_join(scratch.arena, &chain, &(StringJoin){ .sep = str8_lit("\n") }); + lnk_error_obj(LNK_Error_WeakCycle, symbol.obj, "unable to resolve cyclic symbol %S; ref chain:\n%S", symbol_parsed.name, chain_string); - goto exit; + goto exit; + } } COFF_ParsedSymbol current_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(current_symbol.obj, current_symbol.symbol_idx); COFF_SymbolValueInterpType current_interp = coff_interp_symbol(current_parsed.section_number, current_parsed.value, current_parsed.storage_class); + if (current_interp == COFF_SymbolValueInterp_Weak) { // record visited symbol - struct S *s = push_array(scratch.arena, struct S, 1); - s->symbol = current_symbol; - SLLQueuePush(sf, sl, s); + { + struct S *s = push_array(scratch.arena, struct S, 1); + s->symbol = current_symbol; + SLLQueuePush(sf, sl, s); + } - // does weak symbol have a definition? - LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx)); - COFF_ParsedSymbol defn_parsed = lnk_parsed_from_symbol(defn_symbol); - COFF_SymbolValueInterpType defn_interp = coff_interp_symbol(defn_parsed.section_number, defn_parsed.value, defn_parsed.storage_class); - if (defn_interp != COFF_SymbolValueInterp_Weak) { - current_symbol = lnk_ref_from_symbol(defn_symbol); - break; + // follow the leader symbol even when it is another weak symbol + { + LNK_Symbol *leader_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx)); + if (leader_symbol) { + LNK_ObjSymbolRef leader_ref = lnk_ref_from_symbol(leader_symbol); + if ( ! lnk_obj_symbol_ref_match(leader_ref, current_symbol)) { + current_symbol = leader_ref; + continue; + } + } } COFF_SymbolWeakExt *weak_ext = coff_parse_weak_tag(current_parsed, current_symbol.obj->coff.header.is_big_obj); + // + // An anti-dependency can resolve its own direct fallback, but cannot be + // used as a link in another alias's chain. Test the prevailing record's + // kind, before any weak symbols have been flattened to their defaults. + // + // Given this chain: + // foo [WeakAlias] -> bar [AntiDependency] -> qwe [Defined] + // + // * A relocation referencing 'foo' cannot follow through 'bar'; 'foo' remains unresolved. + // * A relocation referencing 'bar' directly resolves to 'qwe'. The relocation reference itself is not an alias-chain step. + // + if (followed_fallback && weak_ext->characteristics == COFF_WeakExt_AntiDependency) { break; } + // no definition -- fallback to default symbol COFF_ParsedSymbol tag_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(current_symbol.obj, weak_ext->tag_index); COFF_SymbolValueInterpType tag_interp = coff_interp_symbol(tag_parsed.section_number, tag_parsed.value, tag_parsed.storage_class); current_symbol = (LNK_ObjSymbolRef){ .obj = current_symbol.obj, .symbol_idx = weak_ext->tag_index }; + followed_fallback = 1; if (weak_ext->characteristics == COFF_WeakExt_AntiDependency) { if (tag_interp == COFF_SymbolValueInterp_Undefined || tag_interp == COFF_SymbolValueInterp_Weak) { LNK_Symbol *dep_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, weak_ext->tag_index)); - tag_interp = lnk_interp_from_symbol(dep_symbol); + if (dep_symbol) { tag_interp = lnk_interp_from_symbol(dep_symbol); } } if (tag_interp == COFF_SymbolValueInterp_Weak) { break; } } } else if (current_interp == COFF_SymbolValueInterp_Undefined) { - LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx)); - COFF_SymbolValueInterpType defn_interp = lnk_interp_from_symbol(defn_symbol); + LNK_Symbol *defn_symbol = lnk_symbol_table_search(symtab, lnk_symbol_name_from_coff_symbol_idx(current_symbol.obj, current_symbol.symbol_idx)); + if (defn_symbol == 0) { break; } - // unresolved undefined symbol + // unresolved symbol + COFF_SymbolValueInterpType defn_interp = lnk_interp_from_symbol(defn_symbol); if (defn_interp == COFF_SymbolValueInterp_Undefined) { break; } // follow symbol definition @@ -846,6 +875,19 @@ lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymb internal THREAD_POOL_TASK_FUNC(lnk_replace_weak_with_default_symbol_task) { + Temp scratch = scratch_begin(0,0); + + struct Resolution { + struct Resolution *next; + LNK_Symbol *symbol; + LNK_ObjSymbolRef target; + B32 resolved; + }; + struct Resolution *first = 0, *last = 0; + + // Resolve against a stable symbol table. Updating the symbol table in this loop + // would make alias/anti-dependency semantics depend on traversal order and + // allow other workers to read symbol records while they are being rewritten. LNK_SymbolTable *symtab = raw_task; for EachNode(c, LNK_SymbolHashTrieChunk, symtab->search_chunks[task_id].first) { for EachIndex(i, c->count) { @@ -853,25 +895,38 @@ THREAD_POOL_TASK_FUNC(lnk_replace_weak_with_default_symbol_task) LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol); COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(symbol_ref.obj, symbol_ref.symbol_idx); COFF_SymbolValueInterpType symbol_interp = coff_interp_from_parsed_symbol(symbol_parsed); + if (symbol_interp == COFF_SymbolValueInterp_Weak) { - LNK_ObjSymbolRef resolve = {0}; - if (lnk_resolve_weak_symbol(symtab, symbol_ref, &resolve)) { - COFF_ParsedSymbol resolve_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(resolve.obj, resolve.symbol_idx); + struct Resolution *r = push_array(scratch.arena, struct Resolution, 1); + r->symbol = symbol; + SLLQueuePush(first, last, r); + + if (lnk_resolve_weak_symbol(symtab, symbol_ref, &r->target)) { + COFF_ParsedSymbol resolve_parsed = lnk_parsed_symbol_from_coff_symbol_idx_no_name(r->target.obj, r->target.symbol_idx); COFF_SymbolValueInterpType resolve_interp = coff_interp_from_parsed_symbol(resolve_parsed); - if (resolve_interp == COFF_SymbolValueInterp_Weak) { - U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(symbol_ref.obj, symbol_ref.symbol_idx); - symbol_ref.obj->coff.symbols.section_numbers[primary_idx] = COFF_Symbol_UndefinedSection; - symbol_ref.obj->coff.symbols.values [primary_idx] = 0; - symbol_ref.obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_External; - lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Undefined); - } else { - symbol->first_ref->v = resolve; - lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Null); - } + r->resolved = resolve_interp != COFF_SymbolValueInterp_Weak && resolve_interp != COFF_SymbolValueInterp_Undefined; } } } } + barrier_wait(tp->barrier); + + for EachNode(r, struct Resolution, first) { + LNK_Symbol *symbol = r->symbol; + if (r->resolved) { + symbol->first_ref->v = r->target; + lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Null); + } else { + LNK_ObjSymbolRef symbol_ref = lnk_ref_from_symbol(symbol); + U64 primary_idx = lnk_obj_primary_symbol_idx_from_coff_symbol_idx(symbol_ref.obj, symbol_ref.symbol_idx); + symbol_ref.obj->coff.symbols.section_numbers[primary_idx] = COFF_Symbol_UndefinedSection; + symbol_ref.obj->coff.symbols.values [primary_idx] = 0; + symbol_ref.obj->coff.symbols.storage_classes[primary_idx] = COFF_SymStorageClass_External; + lnk_symbol_set_search_type(symbol, LNK_SymbolSearch_Undefined); + } + } + + scratch_end(scratch); } internal void diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 42cd74fd..8622aec9 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -4786,99 +4786,308 @@ TEST(sect_align) TEST(alt_name) { - T_Ok(t_write_def_obj("test.obj", (T_COFF_DefObj){ - .machine = T_COFF_DefSetMachine(X64), + // Inspect an RVA relocation instead of merely accepting a successful link. + // Separate sections and nonzero symbol offsets distinguish the selected target + // without depending on a particular linker's section ordering or image layout. + T_COFF_DefObj target = { + .machine = T_COFF_DefSetMachine(X64), + .path = str8_lit("target.obj"), .sections = (T_COFF_DefSection[]){ - { "data", ".data", str8_lit("test"), .flags = "rw:data" }, + { "data", ".target", str8_lit("pad!TEST"), .flags = "rw:data@4" }, {0} }, .symbols = (T_COFF_DefSymbol[]){ - T_COFF_DefSymbol_Extern("test", "data", 0), + T_COFF_DefSymbol_Extern("test", "data", 4), {0} } - })); + }; - T_Ok(t_write_def_obj("foo.obj", (T_COFF_DefObj){ - .machine = T_COFF_DefSetMachine(X64), + T_COFF_DefObj primary = { + .machine = T_COFF_DefSetMachine(X64), + .path = str8_lit("foo.obj"), .sections = (T_COFF_DefSection[]){ - { "data", ".data", str8_lit("foo"), .flags = "rw:data" }, + { "data", ".primary", str8_lit("pad!REAL"), .flags = "rw:data@4" }, {0} }, .symbols = (T_COFF_DefSymbol[]){ - T_COFF_DefSymbol_Extern("foo", "data", 0), + T_COFF_DefSymbol_Extern("foo", "data", 4), + {0} + } + }; + + T_COFF_DefObj weak = { + .machine = T_COFF_DefSetMachine(X64), + .path = str8_lit("weak.obj"), + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Undef("test"), + T_COFF_DefSymbol_Weak("altsym", COFF_WeakExt_SearchAlias, "test"), + T_COFF_DefSymbol_AbsExtern("alias_anchor", 1), + {0} + } + }; + + T_Ok(t_write_def_obj("target.obj", target)); + T_Ok(t_write_def_obj("foo.obj", primary)); + T_Ok(t_write_def_obj("weak.obj", weak)); + T_Ok(t_write_entry_obj()); + T_Ok(t_write_def_obj("ref.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "probe", ".probe", str8_lit_comp("\0\0\0\0"), .flags = "rw:data@4", + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr32Nb, 0, "foo"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_Undef("foo"), {0} } + })); + + // Use ADDR32 for the absolute target: LINK rejects absolute ADDR32NB fixups. + T_Ok(t_write_def_obj("ref_absolute.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "probe", ".probe", str8_lit_comp("\0\0\0\0"), .flags = "rw:data@4", + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr32, 0, "foo"), {0} } }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_Undef("foo"), {0} } + })); + + // Same-object weak target, as in LLVM's alternatename-alias.s. + T_Ok(t_write_def_obj("weak_local.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = target.sections, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("test", "data", 4), + T_COFF_DefSymbol_Weak("altsym", COFF_WeakExt_SearchAlias, "test"), {0} } })); - - T_Ok(t_write_def_obj("entry.obj", (T_COFF_DefObj){ - .machine = T_COFF_DefSetMachine(X64), + // A local default is identified by its object-local tag, not its spelling. + T_Ok(t_write_def_obj("weak_static.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = target.sections, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Static("local_target", "data", 4), + T_COFF_DefSymbol_Weak("altsym", COFF_WeakExt_SearchAlias, "local_target"), + {0} + } + })); + T_Ok(t_write_def_obj("global_target.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .sections = primary.sections, + .symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_Extern("local_target", "data", 4), {0} } + })); + T_Ok(t_write_def_obj("override.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), .sections = (T_COFF_DefSection[]){ - { - "text", ".text", - str8_lit_comp( - "\x48\xC7\xC0\x00\x00\x00\x00" // mov rax, $imm - "\xC3" - ), // ret - .flags = "rx:code", - .relocs = (T_COFF_DefReloc[]){ - T_COFF_DefReloc(X64_Addr32Nb, 0, "foo"), - {0} - } + { "data", ".chosen", str8_lit("pad!OVER"), .flags = "rw:data@4" }, {0} + }, + .symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_Extern("altsym", "data", 4), {0} } + })); + T_Ok(t_write_def_obj("absolute.obj", (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .symbols = (T_COFF_DefSymbol[]){ T_COFF_DefSymbol_AbsExtern("absolute", 0x1234), {0} } + })); + + struct { + char *path; + char *name; + char *tag; + COFF_WeakExtType type; + } aliases[] = { + { "weak_chain.obj", "altsym", "middle", COFF_WeakExt_SearchAlias }, + { "weak_middle.obj", "middle", "test", COFF_WeakExt_SearchAlias }, + { "anti.obj", "altsym", "test", COFF_WeakExt_AntiDependency }, + { "anti_middle.obj", "middle", "test", COFF_WeakExt_AntiDependency }, + { "missing.obj", "altsym", "missing", COFF_WeakExt_SearchAlias }, + { "cycle_tail.obj", "middle", "altsym", COFF_WeakExt_SearchAlias }, + { "source_weak.obj", "foo", "test", COFF_WeakExt_SearchAlias }, + { "weak_nolib.obj", "altsym", "test", COFF_WeakExt_NoLibrary }, + { "weak_lib.obj", "altsym", "test", COFF_WeakExt_SearchLibrary }, + }; + for EachElement(i, aliases) { + T_Ok(t_write_def_obj(aliases[i].path, (T_COFF_DefObj){ + .machine = T_COFF_DefSetMachine(X64), + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Undef(aliases[i].tag), + T_COFF_DefSymbol_Weak(aliases[i].name, aliases[i].type, aliases[i].tag), + {0} + } + })); + } + + T_Ok(t_write_def_obj("directive.obj", (T_COFF_DefObj){ .directives = (char *[]){ "/alternatename:foo=test", 0 } })); + T_Ok(t_write_def_obj("weak_directive.obj", (T_COFF_DefObj){ .directives = (char *[]){ "/alternatename:foo=altsym", 0 } })); + T_Ok(t_write_def_obj("conflict.obj", (T_COFF_DefObj){ .directives = (char *[]){ "/alternatename:foo=other", 0 } })); + T_Ok(t_write_file(str8_lit("alternate.rsp"), str8_lit("/alternatename:foo=test"))); + + T_COFF_DefObj lib_objs[] = { target, primary, weak }; + char *lib_paths[] = { "target.lib", "foo.lib", "weak.lib" }; + for EachElement(i, lib_objs) { + T_Ok(t_write_def_lib(lib_paths[i], (T_COFF_DefLib){ + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ { .type = T_COFF_DefLibMember_Obj, .obj = lib_objs[i] }, {0} } + })); + } + + // Extracting the fallback unnecessarily must fail, even if its data is unused. + T_Ok(t_write_def_lib("poison.lib", (T_COFF_DefLib){ + .emit_second_member = 1, + .members = (T_COFF_DefLibMember[]){ { .type = T_COFF_DefLibMember_Obj, .obj = { + .path = str8_lit("poison.obj"), + .machine = T_COFF_DefSetMachine(X64), + .sections = (T_COFF_DefSection[]){ + { "data", ".poison", str8_lit_comp("\0\0\0\0"), .flags = "rw:data@4", + .relocs = (T_COFF_DefReloc[]){ T_COFF_DefReloc(X64_Addr32Nb, 0, "poison_missing"), {0} } }, + {0} }, - {0} - }, - .symbols = (T_COFF_DefSymbol[]){ - T_COFF_DefSymbol_Extern("entry", "text", 0), - T_COFF_DefSymbol_Undef("foo"), - {0} - } + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Extern("test", "data", 0), + T_COFF_DefSymbol_Undef("poison_missing"), + {0} + } + } }, {0} } })); - // basic alternate name test - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename:foo=test test.obj entry.obj"); - T_Ok(g_last_exit_code == 0); + struct { + char *name; + char *options; + char *inputs; + B32 success; + char *section; // NULL on success means an absolute symbol value. + U32 offset; + LNK_ErrorCode error; // Defaults to UnresolvedSymbol for negative cases. + } cases[] = { + { "direct", "/alternatename:foo=test", "target.obj", 1, ".target", 4 }, + { "identical_duplicate", "/alternatename:foo=test /alternatename:foo=test", "target.obj", 1, ".target", 4 }, + { "absolute", "/alternatename:foo=absolute", "absolute.obj", 1, 0, 0x1234 }, + { "primary_before", "/alternatename:foo=test", "foo.obj target.obj", 1, ".primary", 4 }, + { "primary_after", "/alternatename:foo=test", "target.obj foo.obj", 1, ".primary", 4 }, + { "primary_missing_alt", "/alternatename:foo=missing", "foo.obj", 1, ".primary", 4 }, + { "primary_self", "/alternatename:foo=foo", "foo.obj", 1, ".primary", 4 }, + { "unused_mapping", "/alternatename:unused=missing", "foo.obj", 1, ".primary", 4 }, + { "unused_cycle", "/alternatename:unused=other /alternatename:other=unused", "foo.obj", 1, ".primary", 4 }, - // linker should not chase alt name links - t_invoke_linkerf("/subsystem:console /entry:entry /out:b.exe /alternatename:foo=bar /alternatename:bar=test test.obj entry.obj"); - T_Ok(g_last_exit_code != 0); + // Object-file and response-file directives have the same fallback semantics. + { "directive_before", "", "directive.obj target.obj", 1, ".target", 4 }, + { "directive_after", "", "target.obj directive.obj", 1, ".target", 4 }, + { "directive_duplicate", "/alternatename:foo=test", "directive.obj target.obj", 1, ".target", 4 }, + { "response_file", "@alternate.rsp", "target.obj", 1, ".target", 4 }, - // alt name conflict - t_invoke_linkerf("/subsystem:console /entry:entry /out:c.exe /alternatename:foo=test /alternatename:foo=qwe test.obj entry.obj"); - T_Ok(g_last_exit_code != 0); + // Archive search must prefer the primary name and avoid unused fallbacks. + { "archive_target", "/alternatename:foo=test", "target.lib", 1, ".target", 4 }, + { "archive_primary", "/alternatename:foo=test", "target.obj foo.lib", 1, ".primary", 4 }, + { "archives_before", "/alternatename:foo=test", "foo.lib target.lib", 1, ".primary", 4 }, + { "archives_after", "/alternatename:foo=test", "target.lib foo.lib", 1, ".primary", 4 }, + { "unused_fallback_lib", "/alternatename:foo=test", "foo.obj poison.lib", 1, ".primary", 4 }, + { "unused_mapping_lib", "/alternatename:unused=test", "foo.obj poison.lib", 1, ".primary", 4 }, + { "required_poison_lib", "/alternatename:foo=test", "poison.lib", 0 }, - // syntax error - t_invoke_linkerf("/subsystem:console /entry:entry /out:d.exe /alternatename:foo foo.obj entry.obj"); - T_Ok(g_last_exit_code != 0); - - // syntax error - t_invoke_linkerf("/subsystem:console /entry:entry /out:e.exe /alternatename:foo-oof foo.obj entry.obj"); - T_Ok(g_last_exit_code != 0); + // An ordinary weak alias is a valid target; its prevailing definition wins. + { "weak_local", "/alternatename:foo=altsym", "weak_local.obj", 1, ".target", 4 }, + { "weak_before", "/alternatename:foo=altsym", "weak.obj target.obj", 1, ".target", 4 }, + { "weak_after", "/alternatename:foo=altsym", "target.obj weak.obj", 1, ".target", 4 }, + { "weak_chain_before", "/alternatename:foo=altsym", "weak_chain.obj weak_middle.obj target.obj", 1, ".target", 4 }, + { "weak_chain_after", "/alternatename:foo=altsym", "target.obj weak_middle.obj weak_chain.obj", 1, ".target", 4 }, + { "weak_override_first", "/alternatename:foo=altsym", "override.obj weak.obj target.obj", 1, ".chosen", 4 }, + { "weak_override_last", "/alternatename:foo=altsym", "weak.obj target.obj override.obj", 1, ".chosen", 4 }, + { "weak_source", "/alternatename:foo=missing", "source_weak.obj target.obj", 1, ".target", 4 }, + { "weak_directive", "", "weak_directive.obj weak.obj target.obj", 1, ".target", 4 }, + { "weak_archive", "/alternatename:foo=altsym /include:alias_anchor", "weak.lib target.obj", 1, ".target", 4 }, + { "weak_target_archive", "/alternatename:foo=altsym", "weak.obj target.lib", 1, ".target", 4 }, + { "weak_nolib_target", "/alternatename:foo=altsym", "weak_nolib.obj target.obj", 1, ".target", 4 }, + { "weak_lib_target", "/alternatename:foo=altsym", "weak_lib.obj target.obj", 1, ".target", 4 }, + // LINK rejects a section-defined static weak default; RAD and LLD support it. + { "weak_static_target", "/alternatename:foo=altsym", "weak_static.obj global_target.obj", t_id_linker() != Linker_msvc, ".target", 4 }, - // syntax error - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename:foo=test=bar foo.obj entry.obj"); - T_Ok(g_last_exit_code != 0); + // altsym is discovered only after both synthetic fallback pairs exist. + { "late_weak_archive", "/alternatename:foo=altsym /alternatename:trigger=alias_anchor /include:trigger", "weak.lib target.obj", 1, ".target", 4 }, - // syntax error - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename:foo= foo.obj entry.obj"); - T_Ok(g_last_exit_code != 0); + // Unlike an ordinary alias, an anti-dependency cannot be chained through. + { "anti_before", "/alternatename:foo=altsym", "anti.obj target.obj", 0 }, + { "anti_after", "/alternatename:foo=altsym", "target.obj anti.obj", 0 }, + { "nested_anti", "/alternatename:foo=altsym", "weak_chain.obj anti_middle.obj target.obj", 0 }, + { "anti_override_first", "/alternatename:foo=altsym", "override.obj anti.obj target.obj", 1, ".chosen", 4 }, + { "anti_override_last", "/alternatename:foo=altsym", "anti.obj target.obj override.obj", 1, ".chosen", 4 }, - // syntax error - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename:= foo.obj entry.obj"); - T_Ok(g_last_exit_code != 0); + // Unreferenced intermediate command-line alternate names are not alias chains. + { "command_chain", "/alternatename:foo=bar /alternatename:bar=test", "target.obj", 0 }, + { "command_chain_order", "/alternatename:bar=test /alternatename:foo=bar", "target.obj", 0 }, + { "missing_target", "/alternatename:foo=missing", "target.obj", 0 }, + { "case_sensitive_from", "/alternatename:Foo=test", "target.obj", 0 }, + { "case_sensitive_to", "/alternatename:foo=TEST", "target.obj", 0 }, + { "missing_weak_target", "/alternatename:foo=altsym", "missing.obj", 0 }, + { "undefined_self", "/alternatename:foo=foo", "target.obj", 0 }, + { "command_cycle", "/alternatename:foo=bar /alternatename:bar=foo", "target.obj", 0 }, + { "weak_cycle", "/alternatename:foo=altsym", "weak_chain.obj cycle_tail.obj", 0 }, - // syntax error - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /alternatename: foo.obj entry.obj"); - T_Ok(g_last_exit_code != 0); + // RAD and LLD reject conflicts even for defined sources; LINK keeps the first mapping. + { "conflict", "/alternatename:foo=test /alternatename:foo=other", "target.obj", t_id_linker() == Linker_msvc, ".target", 4, LNK_Error_AlternateNameConflict }, + { "defined_conflict", "/alternatename:foo=test /alternatename:foo=other", "foo.obj", t_id_linker() == Linker_msvc, ".primary", 4, LNK_Error_AlternateNameConflict }, + { "directive_conflict", "/alternatename:foo=test", "conflict.obj target.obj", t_id_linker() == Linker_msvc, ".target", 4, LNK_Error_AlternateNameConflict }, + { "two_directives", "", "directive.obj conflict.obj target.obj", t_id_linker() == Linker_msvc, ".target", 4, LNK_Error_AlternateNameConflict }, + { "missing_equals", "/alternatename:foo", "foo.obj", 0, 0, 0, LNK_Error_Cmdl }, + { "wrong_separator", "/alternatename:foo-oof", "foo.obj", 0, 0, 0, LNK_Error_Cmdl }, + { "empty_target", "/alternatename:foo=", "foo.obj", 0, 0, 0, LNK_Error_Cmdl }, - // TODO: check that RAD Linker prints these warnings + // LINK accepts an empty (unused) source; RAD and LLD diagnose it. + { "empty_source", "/alternatename:=test", "foo.obj", t_id_linker() == Linker_msvc, ".primary", 4, LNK_Error_Cmdl }, + { "empty_both", "/alternatename:=", "foo.obj", 0, 0, 0, LNK_Error_Cmdl }, + { "empty_option", "/alternatename:", "foo.obj", 0, 0, 0, LNK_Error_Cmdl }, + + // LLD and LINK accept the extra '=' with a defined source; RAD rejects it. + { "extra_equals", "/alternatename:foo=test=bar", "foo.obj", t_id_linker() != Linker_radlink, ".primary", 4, LNK_Error_Cmdl }, + }; - // warn about alt name to self alt name? - t_invoke_linkerf("/subsystem:console /entry:entry /out:f.exe /alternatename:foo=foo foo.obj entry.obj"); - T_Ok(g_last_exit_code == 0); + B32 all_ok = 1; + U32 worker_counts[] = { 1, 4 }; + U64 pass_count = t_id_linker() == Linker_radlink ? ArrayCount(worker_counts) : 1; + for EachIndex(pass_idx, pass_count) { + for EachElement(case_idx, cases) { + String8 out_name = str8f(arena, "alt_%s_%u.exe", cases[case_idx].name, worker_counts[pass_idx]); + String8 workers = t_id_linker() == Linker_radlink ? str8f(arena, "/rad_workers:%u", worker_counts[pass_idx]) : str8_zero(); + char *ref_obj = cases[case_idx].success && !cases[case_idx].section ? "ref_absolute.obj" : "ref.obj"; + String8 cmdline = str8f(arena, "/subsystem:console /entry:entry /nodefaultlib /opt:noref,noicf /out:%S %S %s entry.obj %s %s", + out_name, workers, cases[case_idx].options, ref_obj, cases[case_idx].inputs); + test_outf("alt_name %s (workers %u): %S\n", cases[case_idx].name, worker_counts[pass_idx], cmdline); - // warn about alt name to unknown symbol? - t_invoke_linkerf("/subsystem:console /entry:entry /out:g.exe /alternatename:qwe=ewq foo.obj entry.obj"); - T_Ok(g_last_exit_code == 0); + // Bound cycles require a normal exit: a crash/timeout is not an expected failure. + B32 invoked = t_invoke(t_linker_path(), cmdline, 10 * 1000 * 1000); + B32 normal_exit = invoked && g_last_exit_code < 0x80000000 && g_last_exit_code != 999; + B32 case_ok = normal_exit && ((g_last_exit_code == 0) == cases[case_idx].success); + if (case_ok && !cases[case_idx].success && t_id_linker() == Linker_radlink) { + LNK_ErrorCode expected_error = cases[case_idx].error ? cases[case_idx].error : LNK_Error_UnresolvedSymbol; + case_ok = g_last_exit_code == expected_error; + if (!case_ok) { test_outf(" expected error code %u\n", expected_error); } + } + + if (normal_exit && g_last_exit_code == 0 && cases[case_idx].success) { + String8 image = t_read_file(arena, out_name); + PE_BinInfo pe = pe_bin_info_from_data(arena, image); + COFF_SectionHeader *sections = (COFF_SectionHeader *)str8_substr(image, pe.section_table_range).str; + String8 strings = str8_substr(image, pe.string_table_range); + COFF_SectionHeader *probe = coff_section_header_from_name(strings, sections, pe.section_count, str8_lit(".probe")); + U64 expected_rva = cases[case_idx].offset; + + case_ok = pe.arch == Arch_x64 && probe && probe->vsize == sizeof(U32); + if (cases[case_idx].section) { + COFF_SectionHeader *dest = coff_section_header_from_name(strings, sections, pe.section_count, str8_cstring(cases[case_idx].section)); + case_ok &= dest != 0 && dest->vsize >= cases[case_idx].offset + sizeof(U32); + if (dest) { expected_rva += dest->voff; } + } + + if (case_ok) { + String8 data = str8_substr(image, rng_1u64(probe->foff, (U64)probe->foff + sizeof(U32))); + case_ok = data.size == sizeof(U32) && memory_read32(data.str) == expected_rva; + if (!case_ok) { test_outf(" incorrect relocated RVA; expected %#llx\n", expected_rva); } + } + } + + test_outf(" %s: normal exit %u, exit code %llu, expected %s\n", case_ok ? "PASS" : "FAIL", normal_exit, g_last_exit_code, cases[case_idx].success ? "success with correct RVA" : "failure"); + T_Ok(case_ok); + } + } + + T_Ok(all_ok); } TEST(include) From e50cef2bff2b8b0b04c87e2e4a35f01dbb7786d2 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 16 Sep 2026 18:25:04 -0700 Subject: [PATCH 163/251] minor fixes --- src/linker/base_ext/base_bit_array.c | 2 +- src/linker/base_ext/base_bit_array.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linker/base_ext/base_bit_array.c b/src/linker/base_ext/base_bit_array.c index 3cb4f7c4..ddfe197f 100644 --- a/src/linker/base_ext/base_bit_array.c +++ b/src/linker/base_ext/base_bit_array.c @@ -41,7 +41,7 @@ bit_array_get_bit32(U32Array bit_array, U64 idx) Assert(idx < bit_array.count*32); U64 word_idx = idx / 32; U64 bit_idx = idx % 32; - U32 bit = (bit_array.v[word_idx] & (1 << bit_idx)) >> bit_idx; + U32 bit = (bit_array.v[word_idx] & (1u << bit_idx)) >> bit_idx; return bit; } diff --git a/src/linker/base_ext/base_bit_array.h b/src/linker/base_ext/base_bit_array.h index 5c76ef32..b54483ee 100644 --- a/src/linker/base_ext/base_bit_array.h +++ b/src/linker/base_ext/base_bit_array.h @@ -3,7 +3,7 @@ #pragma once -internal U32Array bit_array_init32 (Arena *arena, U64 word_count); +internal U32Array bit_array_init32 (Arena *arena, U64 bit_count); internal U64 bit_array_scan_left_to_right32(U32Array bit_array, U64 lo, U64 hi, B32 state); internal U64 bit_array_scan_right_to_left32(U32Array bit_array, U64 lo, U64 hi, B32 state); internal void bit_array_set_bit32 (U32Array bit_array, U64 idx, B32 state); From 4e12148910f64cfe5ef263d8235ed8412e33ba8d Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 16 Sep 2026 18:38:59 -0700 Subject: [PATCH 164/251] set up compressor set up compressor on linux --- build.bat | 33 +++++++++++++++++++++++++++++++++ build.sh | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/build.bat b/build.bat index 9a8c81cd..684a30d3 100644 --- a/build.bat +++ b/build.bat @@ -27,6 +27,7 @@ cd /D "%~dp0" :: - `ubsan`: enable undefined-behavior sanitizer :: - `telemetry`: enable RAD telemetry profiling support :: - `spall`: enable spall profiling support +:: - `oodle`: enable Oodle from local\oodle2*\win or an explicit OODLE_SDK_DIR :: --- Unpack Arguments ------------------------------------------------------- for %%a in (%*) do set "%%~a=1" @@ -154,6 +155,38 @@ if not exist build\blake3.lib if "%VSCMD_ARG_TGT_ARCH%" equ "x64" ( popd ) +:: --- Set up Oodle SDK ------------------------------------------------------- +if "%oodle%"=="1" ( + set "oodle_sdk_path=%OODLE_SDK_DIR%" + if not defined oodle_sdk_path ( + for /d %%d in ("%~dp0local\oodle2*") do ( + if exist "%%~fd\win\include\oodle2.h" ( + if defined oodle_sdk_path ( + echo Multiple Oodle SDKs found: "!oodle_sdk_path!" and "%%~fd\win" + echo Set OODLE_SDK_DIR to select the SDK root. + exit /b 1 + ) + set "oodle_sdk_path=%%~fd\win" + ) + ) + ) + if not defined oodle_sdk_path ( + echo Oodle SDK not found. Put an oodle2* SDK with a win subdirectory in "%~dp0local" or set OODLE_SDK_DIR. + exit /b 1 + ) + if exist "!oodle_sdk_path!\win\include\oodle2.h" set "oodle_sdk_path=!oodle_sdk_path!\win" + for %%d in ("!oodle_sdk_path!") do set "oodle_sdk_path=%%~fd" + if not exist "!oodle_sdk_path!\include\oodle2.h" ( + echo Oodle SDK directory "!oodle_sdk_path!" must contain include\oodle2.h + exit /b 1 + ) + echo [Oodle SDK: !oodle_sdk_path!] + set "compile=!compile! -DOODLE_SDK=1 -I"!oodle_sdk_path!\include"" + if exist "!oodle_sdk_path!\redist\oo2core_*_win64.dll" ( + copy /y "!oodle_sdk_path!\redist\oo2core_*_win64.dll" "%~dp0build\" >nul || exit /b 1 + ) +) + :: --- Build Everything (@build_targets) -------------------------------------- pushd build if "%raddbg%"=="1" set didbuild=1 && %compile% ..\src\raddbg\raddbg_main.c %compile_link% %link_icon% %out%raddbg.exe || exit /b 1 diff --git a/build.sh b/build.sh index c481af6e..4f015187 100755 --- a/build.sh +++ b/build.sh @@ -49,8 +49,9 @@ if [[ "${release:-0}" == "1" ]]; then echo "[release mode]"; compile="$compile elif [[ "${debug:-1}" == "1" ]]; then echo "[debug mode]"; compile="$compiler $cc_debug"; fi +oodle_flags=() compile() { - $compile -Wl,--start-group "$@" blake3.a -latomic -Wl,--end-group + $compile "${oodle_flags[@]}" -Wl,--start-group "$@" blake3.a -latomic -Wl,--end-group } # --- Prep Directories -------------------------------------------------------- @@ -77,6 +78,43 @@ then $ar rs build/blake3.a build/blake3_*_unix.o fi +# --- Set up Oodle SDK ------------------------------------------------------- +if [[ "${oodle:-0}" == "1" ]]; then + oodle_sdk_path="${OODLE_SDK_DIR:-}" + if [[ -z "$oodle_sdk_path" ]]; then + for sdk in "$PWD"/local/oodle2*; do + if [[ -f "$sdk/linux/include/oodle2.h" ]]; then + if [[ -n "$oodle_sdk_path" ]]; then + echo "Multiple Oodle SDKs found: $oodle_sdk_path and $sdk/linux" >&2 + echo "Set OODLE_SDK_DIR to select the SDK root." >&2 + exit 1 + fi + oodle_sdk_path="$sdk/linux" + fi + done + fi + if [[ -z "$oodle_sdk_path" ]]; then + echo "Oodle SDK not found. Put an oodle2* SDK with a linux subdirectory in $PWD/local or set OODLE_SDK_DIR." >&2 + exit 1 + fi + if [[ -f "$oodle_sdk_path/linux/include/oodle2.h" ]]; then + oodle_sdk_path="$oodle_sdk_path/linux" + fi + if [[ ! -f "$oodle_sdk_path/include/oodle2.h" ]]; then + echo "Oodle SDK directory '$oodle_sdk_path' must contain include/oodle2.h" >&2 + exit 1 + fi + oodle_sdk_path="$(cd "$oodle_sdk_path" && pwd)" + echo "[Oodle SDK: $oodle_sdk_path]" + # Keep the include path as one argument, including when it contains spaces. + oodle_flags=(-DOODLE_SDK=1 "-I$oodle_sdk_path/include") + for library in "$oodle_sdk_path"/lib/liboo2corelinux64.so "$oodle_sdk_path"/lib/liboo2corelinux64.so.*; do + if [[ -f "$library" ]]; then + cp -- "$library" "$PWD/build/" + fi + done +fi + # --- Build Everything (@build_targets) --------------------------------------- cd build if [[ "${raddbg:-0}" == "1" ]]; then didbuild=1 && compile ../src/raddbg/raddbg_main.c $cc_icon $cc_link $cc_os_gfx $cc_render $cc_font_provider -o raddbg; fi From 0ac6b35a98a86a2f5de30e057d4b5103d0c39f03 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 17 Sep 2026 17:26:25 -0700 Subject: [PATCH 165/251] base: add memory placeholders and generic page fault handler * Add windows and Linux demand-mapping backends * Add exclusive file creation, sizing, flushing and replacement APIs --- src/base/base_core.h | 1 + src/base/base_files.h | 4 + src/base/base_inc.c | 3 + src/base/base_inc.h | 1 + src/base/base_memory.h | 14 + src/base/base_shared_memory.h | 1 + src/base/base_system.h | 1 + src/linker/lnk.c | 2 - src/linux/base/linux_base.c | 415 +++++++++++++++++++++++++----- src/linux/base/linux_base.h | 47 +++- src/radbin/radbin_main.c | 2 - src/raddbg/raddbg_main.c | 2 - src/scratch/ryan_scratch.c | 2 - src/scratch/unit_sizes_from_pdb.c | 2 - src/torture/torture_main.c | 2 - src/win32/base/win32_base.c | 234 +++++++++++++++-- src/win32/base/win32_base.h | 12 + src/x64/x64.h | 46 ++++ 18 files changed, 690 insertions(+), 101 deletions(-) diff --git a/src/base/base_core.h b/src/base/base_core.h index 1a21cd7a..580089af 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -695,6 +695,7 @@ enum AccessFlag_ShareRead = (1<<4), AccessFlag_ShareWrite = (1<<5), AccessFlag_Inherited = (1<<6), + AccessFlag_CreateNew = (1<<7), // fail if file already exists; never truncates }; //////////////////////////////// diff --git a/src/base/base_files.h b/src/base/base_files.h index b06216c1..c9a98e1d 100644 --- a/src/base/base_files.h +++ b/src/base/base_files.h @@ -89,9 +89,12 @@ internal B32 file_set_times(File file, DateTime time); internal FileProperties properties_from_file(File file); internal FileID id_from_file(File file); internal B32 file_reserve_size(File file, U64 size); +internal B32 file_set_size(File file, U64 size); +internal B32 file_flush(File file); internal B32 delete_file_at_path(String8 path); internal B32 copy_file_path(String8 dst, String8 src); internal B32 move_file_path(String8 dst, String8 src); +internal B32 replace_file_path(String8 dst, String8 src); internal String8 full_path_from_path(Arena *arena, String8 path); internal B32 file_path_exists(String8 path); internal B32 folder_path_exists(String8 path); @@ -101,6 +104,7 @@ internal FileProperties properties_from_file_path(String8 path); internal FileMap file_map_open(AccessFlags flags, File file); internal void file_map_close(FileMap map); internal void * file_map_view_open(FileMap map, AccessFlags flags, Rng1U64 range); +internal void * file_map_view_replace_placeholder(FileMap map, void *ptr, Rng1U64 range); internal void file_map_view_close(FileMap map, void *ptr, Rng1U64 range); //- rjf: directory iteration diff --git a/src/base/base_inc.c b/src/base/base_inc.c index 5a102437..1cff7bf2 100644 --- a/src/base/base_inc.c +++ b/src/base/base_inc.c @@ -30,6 +30,9 @@ #include "base_test.c" #include "base_entry_point.c" +// TODO: move x64 to base layer +#include "x64/x64.c" + #if OS_WINDOWS # include "win32/base/win32_base.c" #elif OS_LINUX diff --git a/src/base/base_inc.h b/src/base/base_inc.h index f83f8858..3aa43a74 100644 --- a/src/base/base_inc.h +++ b/src/base/base_inc.h @@ -32,6 +32,7 @@ #include "base_test.h" #include "base_entry_point.h" +#include "x64/x64.h" #if OS_WINDOWS # include "win32/base/win32_base.h" #elif OS_LINUX diff --git a/src/base/base_memory.h b/src/base/base_memory.h index e73c7afd..6b84fdff 100644 --- a/src/base/base_memory.h +++ b/src/base/base_memory.h @@ -13,6 +13,20 @@ internal B32 commit_memory(void *ptr, U64 size); internal void decommit_memory(void *ptr, U64 size); internal void release_memory(void *ptr, U64 size); +// memory placeholders +internal B32 memory_placeholders_supported(void); +internal void *reserve_memory_placeholders(U64 size, U64 block_size); +internal void release_memory_placeholders(void *ptr, U64 size, U64 block_size); +internal B32 unmap_memory_preserve_placeholder(void *ptr, U64 size); +internal B32 split_memory_placeholder(void *ptr, U64 size, U64 first_size); +internal B32 coalesce_memory_placeholders(void *ptr, U64 size); +union Rng1U64; +internal void prefetch_memory_ranges(U64 count, union Rng1U64 *ranges); + +// generic memory page fault handler (calls may run concurrently) +typedef B32 MemoryReadFaultFunction(void *address, void *user_data); +internal B32 memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data); + //- rjf: large pages internal void *reserve_memory_large(U64 size); internal B32 commit_memory_large(void *ptr, U64 size); diff --git a/src/base/base_shared_memory.h b/src/base/base_shared_memory.h index ec288d86..bd59f0d6 100644 --- a/src/base/base_shared_memory.h +++ b/src/base/base_shared_memory.h @@ -18,5 +18,6 @@ internal SharedMemory shared_memory_open(String8 name); internal void shared_memory_close(SharedMemory handle); internal void * shared_memory_view_open(SharedMemory handle, Rng1U64 range); internal void shared_memory_view_close(SharedMemory handle, void *ptr, Rng1U64 range); +internal void * shared_memory_view_replace_placeholder(SharedMemory handle, void *ptr, Rng1U64 range, AccessFlags flags); #endif // BASE_SHARED_MEMORY_H diff --git a/src/base/base_system.h b/src/base/base_system.h index 2907c881..f28fde04 100644 --- a/src/base/base_system.h +++ b/src/base/base_system.h @@ -11,6 +11,7 @@ struct SystemInfo U64 page_size; U64 large_page_size; U64 allocation_granularity; + U64 physical_memory_size; String8 machine_name; }; diff --git a/src/linker/lnk.c b/src/linker/lnk.c index ad666b0d..423b13bd 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -13,7 +13,6 @@ // --- Code Base --------------------------------------------------------------- #include "base/base_inc.h" -#include "x64/x64.h" #include "hash_table.h" #include "coff/coff.h" #include "coff/coff_parse.h" @@ -35,7 +34,6 @@ #include "dwarf/x64/dwarf_x64.h" #include "base/base_inc.c" -#include "x64/x64.c" #include "hash_table.c" #include "coff/coff.c" #include "coff/coff_parse.c" diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index 3daa94f7..fec7a0da 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -69,8 +69,10 @@ lnx_file_properties_from_stat(struct stat *s) } internal void -lnx_safe_call_sig_handler(int x) +lnx_safe_call_sig_handler(int sig, siginfo_t *info, void *context) { + if(lnx_dispatch_memory_read_fault(sig, info, context)) { return; } + LNX_SafeCallChain *chain = lnx_safe_call_chain; if(chain != 0 && chain->fail_handler != 0) { @@ -238,8 +240,7 @@ reserve_memory(U64 size) internal B32 commit_memory(void *ptr, U64 size) { - mprotect(ptr, size, PROT_READ|PROT_WRITE); - return 1; + return mprotect(ptr, size, PROT_READ|PROT_WRITE) == 0; } internal void @@ -281,12 +282,29 @@ commit_memory_large(void *ptr, U64 size) internal SharedMemory shared_memory_alloc(U64 size, String8 name) { - Temp scratch = scratch_begin(0, 0); - String8 name_copy = push_str8_copy(scratch.arena, name); - int id = shm_open((char *)name_copy.str, O_RDWR|O_CREAT, 0666); - ftruncate(id, size); - SharedMemory result = {(U64)id}; - scratch_end(scratch); + SharedMemory result = { max_U64 }; + + if(size == 0 || size > max_S64) { return result; } + + int id; + if(name.size == 0) + { + id = memfd_create("rad-shared-memory", MFD_CLOEXEC); + } + else + { + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + id = shm_open((char *)name_copy.str, O_RDWR|O_CREAT|O_CLOEXEC, 0666); + scratch_end(scratch); + } + + if(id >= 0) + { + if(ftruncate(id, size) == 0) { result.u64[0] = (U64)id; } + else { close(id); } + } + return result; } @@ -294,9 +312,9 @@ internal SharedMemory shared_memory_open(String8 name) { Temp scratch = scratch_begin(0, 0); - String8 name_copy = push_str8_copy(scratch.arena, name); - int id = shm_open((char *)name_copy.str, O_RDWR, 0); - SharedMemory result = {(U64)id}; + char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; + int id = shm_open(name_cstr, O_RDWR|O_CLOEXEC, 0); + SharedMemory result = {id >= 0 ? (U64)id : max_U64}; scratch_end(scratch); return result; } @@ -304,15 +322,12 @@ shared_memory_open(String8 name) internal void shared_memory_close(SharedMemory handle) { - if(MemoryIsZeroStruct(&handle)){return;} - int id = (int)handle.u64[0]; - close(id); + close((int)handle.u64[0]); } internal void * shared_memory_view_open(SharedMemory handle, Rng1U64 range) { - if(MemoryIsZeroStruct(&handle)){return 0;} int id = (int)handle.u64[0]; void *base = mmap(0, dim_1u64(range), PROT_READ|PROT_WRITE, MAP_SHARED, id, range.min); if(base == MAP_FAILED) @@ -325,7 +340,6 @@ shared_memory_view_open(SharedMemory handle, Rng1U64 range) internal void shared_memory_view_close(SharedMemory handle, void *ptr, Rng1U64 range) { - if(MemoryIsZeroStruct(&handle)){return;} munmap(ptr, dim_1u64(range)); } @@ -370,7 +384,7 @@ thread_launch(ThreadEntryPointFunctionType *f, void *p) entity->thread.ptr = p; { int pthread_result = pthread_create(&entity->thread.handle, 0, lnx_thread_entry_point, entity); - if(pthread_result == -1) + if(pthread_result != 0) { lnx_entity_release(entity); entity = 0; @@ -760,7 +774,8 @@ safe_call(ThreadEntryPointFunctionType *func, ThreadEntryPointFunctionType *fail // rjf: set up sig handler info struct sigaction new_act = {0}; - new_act.sa_handler = lnx_safe_call_sig_handler; + new_act.sa_sigaction = lnx_safe_call_sig_handler; + new_act.sa_flags = SA_SIGINFO; int signals_to_handle[] = { SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, @@ -781,6 +796,7 @@ safe_call(ThreadEntryPointFunctionType *func, ThreadEntryPointFunctionType *fail { sigaction(signals_to_handle[i], &og_act[i], 0); } + lnx_safe_call_chain = chain.next; } //////////////////////////////// @@ -815,6 +831,7 @@ file_open(AccessFlags flags, String8 path) lnx_flags |= O_CREAT; } lnx_flags |= O_CLOEXEC; + if(flags & AccessFlag_CreateNew) { lnx_flags |= O_CREAT|O_EXCL; } int fd = open((char *)path_copy.str, lnx_flags, 0755); File handle = {0}; if(fd != -1) @@ -969,6 +986,265 @@ id_from_file(File file) return id; } +internal B32 +file_set_size(File file, U64 size) +{ + return size <= max_S64 && ftruncate((int)file.u64[0], (off_t)size) == 0; +} + +internal B32 +file_flush(File file) +{ + return fsync((int)file.u64[0]) == 0; +} + +internal B32 +replace_file_path(String8 dst, String8 src) +{ + Temp scratch = scratch_begin(0,0); + char *dst_cstr = (char *)push_str8_copy(scratch.arena, dst).str; + char *src_cstr = (char *)push_str8_copy(scratch.arena, src).str; + B32 result = rename(src_cstr, dst_cstr) == 0; + scratch_end(scratch); + return result; +} + +internal B32 +memory_placeholders_supported(void) +{ +#if ARCH_X64 || ARCH_X86 + return 1; +#else + NotImplemented; + return 0; +#endif +} + +internal void * +reserve_memory_placeholders(U64 size, U64 block_size) +{ + Assert(size > 0); + Assert(size == (size_t)size); + Assert(block_size > 0); + Assert((size % block_size) == 0); + Assert((block_size % get_system_info()->page_size) == 0); + return reserve_memory(size); +} + +internal void +release_memory_placeholders(void *ptr, U64 size, U64 block_size) +{ + if(size && block_size) { release_memory(ptr, size); } +} + +internal B32 +unmap_memory_preserve_placeholder(void *ptr, U64 size) +{ + Assert(ptr != 0); + Assert(size > 0); + Assert(size == (size_t)size); + Assert((IntFromPtr(ptr) % get_system_info()->page_size) == 0); + Assert((size % get_system_info()->page_size) == 0); + return mmap(ptr, size, PROT_NONE, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) == ptr; +} + +internal void * +shared_memory_view_replace_placeholder(SharedMemory handle, void *ptr, Rng1U64 range, AccessFlags flags) +{ + Assert(range.max > range.min); + Assert(range.max < max_S64); + Assert(dim_1u64(range) == (size_t)dim_1u64(range)); + Assert((range.min % get_system_info()->page_size) == 0); + Assert((range.max % get_system_info()->page_size) == 0); + Assert((IntFromPtr(ptr) % get_system_info()->page_size) == 0); + Assert((flags == AccessFlag_Read) || (flags == (AccessFlag_Read|AccessFlag_Write))); + int protection = flags == AccessFlag_Read ? PROT_READ : PROT_READ|PROT_WRITE; + void *result = mmap(ptr, dim_1u64(range), protection, MAP_FIXED|MAP_SHARED, (int)handle.u64[0], range.min); + return result == MAP_FAILED ? 0 : result; +} + +internal U64 +get_available_commit_memory(void) +{ + long pages = sysconf(_SC_AVPHYS_PAGES); + return pages > 0 ? (U64)pages*get_system_info()->page_size : get_system_info()->physical_memory_size; +} + +internal B32 +split_memory_placeholder(void *ptr, U64 size, U64 first_size) +{ + return first_size > 0 && first_size <= size; +} + +internal B32 +coalesce_memory_placeholders(void *ptr, U64 size) +{ + return 1; +} + +internal void * +file_map_view_replace_placeholder(FileMap map, void *ptr, Rng1U64 range) +{ + void *result = mmap(ptr, dim_1u64(range), PROT_READ, MAP_FIXED|MAP_PRIVATE, (int)map.u64[0], range.min); + return result == MAP_FAILED ? 0 : result; +} + +internal void +prefetch_memory_ranges(U64 count, Rng1U64 *ranges) +{ + U64 page = get_system_info()->page_size; + for EachIndex(i, count) { + U64 min = AlignDownPow2(ranges[i].min, page), max = AlignPow2(ranges[i].max, page); + if (max > min) { madvise(PtrFromInt(min), max-min, MADV_WILLNEED); } + } +} + +internal void +lnx_memory_fault_thread(void *unused) +{ + ThreadNameF("Memory Pager %u", (U32)IntFromPtr(unused)); + for(;;) + { + LNX_MemoryFaultRequest *request = 0; + ssize_t count = LNX_RETRY_ON_EINTR(read(lnx_state.demand_memory.requests[0], &request, sizeof(request))); + + if(count == 0) { break; } + if(count != sizeof(request)) { _exit(1); } + if(request == 0) { break; } + + lnx_in_memory_fault_callback = 1; + B32 handled = lnx_state.demand_memory.fault(request->address, lnx_state.demand_memory.user_data); + lnx_in_memory_fault_callback = 0; + + U32 *completion = &request->result; + ins_atomic_u32_eval_assign(completion, handled ? 2 : 1); + syscall(SYS_futex, completion, FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); + } +} + +internal B32 +lnx_memory_fault_workers_stop(LNX_DemandMemory *memory) +{ + // Explicit stop records work even if a forked child still owns an inherited + // pipe descriptor. EOF-based shutdown could otherwise wait for that child. + LNX_MemoryFaultRequest *stop = 0; + while(memory->stops_sent < memory->worker_count) + { + if(LNX_RETRY_ON_EINTR(write(memory->requests[1], &stop, sizeof(stop))) != sizeof(stop)) { return 0; } + memory->stops_sent += 1; + } + while(memory->workers_joined < memory->worker_count) + { + if(!thread_join(memory->workers[memory->workers_joined], max_U64)) { return 0; } + memory->workers_joined += 1; + } + close(memory->requests[0]); + close(memory->requests[1]); + MemoryZeroStruct(memory); + return 1; +} + +internal B32 +lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) +{ + LNX_DemandMemory *memory = &lnx_state.demand_memory; + +#if ARCH_X64 || ARCH_X86 + if(sig != SIGSEGV || info == 0 || context == 0 || + (info->si_code != SEGV_ACCERR && info->si_code != SEGV_MAPERR) || + lnx_in_memory_fault_callback || + ins_atomic_u32_eval(&memory->active) == 0) + { + return 0; + } + + ucontext_t *state = context; + if((state->uc_mcontext.gregs[REG_TRAPNO] != X64_Exception_PF) || + (state->uc_mcontext.gregs[REG_ERR] & X64_PageFaultError_Write) || + (state->uc_mcontext.gregs[REG_ERR] & X64_PageFaultError_InstructionFetch) || + memory->owner_pid != getpid()) + { + return 0; + } + + int saved_errno = errno; + LNX_MemoryFaultRequest request = { .address = info->si_addr }; + LNX_MemoryFaultRequest *ptr = &request; + ssize_t count = LNX_RETRY_ON_EINTR(write(memory->requests[1], &ptr, sizeof(ptr))); + if(count != sizeof(ptr)) + { + errno = saved_errno; + return 0; + } + + // wait for the page fault handler to complete + while(!ins_atomic_u32_eval(&request.result)) + { + if(LNX_RETRY_ON_EINTR(syscall(SYS_futex, &request.result, FUTEX_WAIT_PRIVATE, 0, 0, 0, 0)) < 0 && errno != EAGAIN) + { + _exit(1); + } + } + + B32 handled = request.result == 2; + errno = saved_errno; + + return handled; +#else + NotImplemented; + return 0; +#endif +} + +internal B32 +memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) +{ + LNX_DemandMemory *memory = &lnx_state.demand_memory; + if(func) + { + if(memory_placeholders_supported() == 0 || memory->worker_count || ins_atomic_u32_eval(&memory->active)) + { + return 0; + } + if(pipe2(memory->requests, O_CLOEXEC) != 0) + { + return 0; + } + + memory->fault = func; + memory->user_data = user_data; + memory->owner_pid = getpid(); + // Independent pager workers must remain runnable when all application + // workers are asleep in their SIGSEGV handlers. Fixed-size pointer writes + // fit PIPE_BUF; workers each consume one complete request from the pipe. + U32 worker_count = (U32)Clamp(2, get_system_info()->logical_processor_count, LNX_MEMORY_FAULT_WORKER_LIMIT); + for(U32 i = 0; i < worker_count; i += 1) + { + Thread worker = thread_launch(lnx_memory_fault_thread, PtrFromInt((U64)i)); + if(MemoryIsZeroStruct(&worker)) + { + // Drain and join already-launched workers on partial startup failure. + lnx_memory_fault_workers_stop(memory); + return 0; + } + memory->workers[memory->worker_count++] = worker; + } + + ins_atomic_u32_eval_assign(&memory->active, 1); + } + else if(memory->worker_count) + { + if(memory->owner_pid != getpid() || lnx_in_memory_fault_callback) + { + return 0; + } + + ins_atomic_u32_eval_assign(&memory->active, 0); + return lnx_memory_fault_workers_stop(memory); + } + return 1; +} + internal B32 file_reserve_size(File file, U64 size) { @@ -1523,7 +1799,7 @@ library_open(String8 path) internal void library_close(Library lib) { - void *so = (void *)lib.u64; + void *so = (void *)lib.u64[0]; dlclose(so); } @@ -1531,7 +1807,7 @@ internal VoidProc * library_load_proc(Library lib, String8 name) { Temp scratch = scratch_begin(0, 0); - void *so = (void *)lib.u64; + void *so = (void *)lib.u64[0]; char *name_cstr = (char *)str8_copy(scratch.arena, name).str; VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); scratch_end(scratch); @@ -1544,57 +1820,63 @@ library_load_proc(Library lib, String8 name) internal void lnx_signal_handler(int sig, siginfo_t *info, void *arg) { - local_persist volatile U32 first = 0; - if (ins_atomic_u32_eval_cond_assign(&first, 1, 0) != 0) + // page fault handler + if(lnx_dispatch_memory_read_fault(sig, info, arg)) { return; } + + // crash handler { - for(;;) + local_persist volatile U32 first = 0; + if (ins_atomic_u32_eval_cond_assign(&first, 1, 0) != 0) { - sleep(UINT32_MAX); - } - } - - local_persist void *ips[4096]; - int ips_count = backtrace(ips, ArrayCount(ips)); - - fprintf(stderr, "A fatal signal was received: %s (%d). The process is terminating.\n", strsignal(sig), sig); - fprintf(stderr, "Create a new issue with this report at %s.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); - fprintf(stderr, "Callstack:\n"); - for EachIndex(i, ips_count) - { - Dl_info info = {0}; - dladdr(ips[i], &info); - - char cmd[2048]; - snprintf(cmd, sizeof(cmd), "llvm-symbolizer --relative-address -f -e %s %lu", info.dli_fname, (unsigned long)ips[i] - (unsigned long)info.dli_fbase); - FILE *f = popen(cmd, "r"); - if(f) - { - char func_name[256], file_name[256]; - if(fgets(func_name, sizeof(func_name), f) && fgets(file_name, sizeof(file_name), f)) + for(;;) { - String8 func = str8_cstring(func_name); - if(func.size > 0) func.size -= 1; - String8 module = str8_skip_last_slash(str8_cstring(info.dli_fname)); - String8 file = str8_skip_last_slash(str8_cstring_capped(file_name, file_name + sizeof(file_name))); - if(file.size > 0) file.size -= 1; - - B32 no_func = str8_match(func, str8_lit("??"), StringMatchFlag_RightSideSloppy); - B32 no_file = str8_match(file, str8_lit("??"), StringMatchFlag_RightSideSloppy); - if(no_func) { func = str8_zero(); } - if(no_file) { file = str8_zero(); } - - fprintf(stderr, "%ld. [0x%016lx] %.*s%s%.*s %.*s\n", i+1, (unsigned long)ips[i], (int)module.size, module.str, (!no_func || !no_file) ? ", " : "", (int)func.size, func.str, (int)file.size, file.str); + sleep(UINT32_MAX); } - pclose(f); } - else + + local_persist void *ips[4096]; + int ips_count = backtrace(ips, ArrayCount(ips)); + + fprintf(stderr, "A fatal signal was received: %s (%d). The process is terminating.\n", strsignal(sig), sig); + fprintf(stderr, "Create a new issue with this report at %s.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); + fprintf(stderr, "Callstack:\n"); + for EachIndex(i, ips_count) { - fprintf(stderr, "%ld. [0x%016lx] %s\n", i+1, (unsigned long)ips[i], info.dli_fname); + Dl_info info = {0}; + dladdr(ips[i], &info); + + char cmd[2048]; + snprintf(cmd, sizeof(cmd), "llvm-symbolizer --relative-address -f -e %s %lu", info.dli_fname, (unsigned long)ips[i] - (unsigned long)info.dli_fbase); + FILE *f = popen(cmd, "r"); + if(f) + { + char func_name[256], file_name[256]; + if(fgets(func_name, sizeof(func_name), f) && fgets(file_name, sizeof(file_name), f)) + { + String8 func = str8_cstring(func_name); + if(func.size > 0) func.size -= 1; + String8 module = str8_skip_last_slash(str8_cstring(info.dli_fname)); + String8 file = str8_skip_last_slash(str8_cstring_capped(file_name, file_name + sizeof(file_name))); + if(file.size > 0) file.size -= 1; + + B32 no_func = str8_match(func, str8_lit("??"), StringMatchFlag_RightSideSloppy); + B32 no_file = str8_match(file, str8_lit("??"), StringMatchFlag_RightSideSloppy); + if(no_func) { func = str8_zero(); } + if(no_file) { file = str8_zero(); } + + fprintf(stderr, "%ld. [0x%016lx] %.*s%s%.*s %.*s\n", i+1, (unsigned long)ips[i], (int)module.size, module.str, (!no_func || !no_file) ? ", " : "", (int)func.size, func.str, (int)file.size, file.str); + } + pclose(f); + } + else + { + fprintf(stderr, "%ld. [0x%016lx] %s\n", i+1, (unsigned long)ips[i], info.dli_fname); + } } + fprintf(stderr, "\nVersion: %s%s\n\n", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); + + _exit(1); } - fprintf(stderr, "\nVersion: %s%s\n\n", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); - - _exit(1); } int @@ -1617,11 +1899,16 @@ main(int argc, char **argv) { //- rjf: get statically-allocated system/process info { + U64 pages = (U64)sysconf(_SC_PHYS_PAGES); + U64 page_size = (U64)sysconf(_SC_PAGESIZE); + U64 commit_size = (U64)sysconf(_SC_AVPHYS_PAGES); + SystemInfo *info = &lnx_state.system_info; info->logical_processor_count = (U32)get_nprocs(); info->page_size = (U64)getpagesize(); info->large_page_size = MB(2); info->allocation_granularity = info->page_size; + info->physical_memory_size = pages * page_size; } { ProcessInfo *info = &lnx_state.process_info; diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index 209c6080..87998e59 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include pid_t gettid(void); int pthread_setname_np(pthread_t thread, const char *name); @@ -43,14 +45,14 @@ typedef struct timespec timespec; //////////////////////////////// //~ rjf: Linux Call Interruption Retry Helper -#define LNX_RETRY_ON_EINTR(expr) \ -(__extension__({ \ -__typeof__(expr) __ret; \ -do { \ -__ret = (expr); \ -} while ((__ret == -1) && errno == EINTR); \ -__ret; \ -})) +#define LNX_RETRY_ON_EINTR(expr) \ + (__extension__({ \ + __typeof__(expr) __ret; \ + do { \ + __ret = (expr); \ + } while ((__ret == -1) && errno == EINTR); \ + __ret; \ + })) //////////////////////////////// @@ -113,6 +115,30 @@ struct LNX_Entity }; }; +//////////////////////////////// +//~ On Demand Memory + +#define LNX_MEMORY_FAULT_WORKER_LIMIT 32 + +typedef struct +{ + void *address; + U32 result; +} LNX_MemoryFaultRequest; + +typedef struct +{ + MemoryReadFaultFunction *fault; + void *user_data; + Thread workers[LNX_MEMORY_FAULT_WORKER_LIMIT]; + U32 worker_count; + U32 stops_sent; + U32 workers_joined; + int requests[2]; + U32 active; + pid_t owner_pid; +} LNX_DemandMemory; + //////////////////////////////// //~ rjf: State @@ -127,6 +153,7 @@ struct LNX_State LNX_Entity *entity_free; U64 default_env_count; char **default_env; + LNX_DemandMemory demand_memory; }; //////////////////////////////// @@ -134,6 +161,7 @@ struct LNX_State global LNX_State lnx_state = {0}; thread_static LNX_SafeCallChain *lnx_safe_call_chain = 0; +thread_static B32 lnx_in_memory_fault_callback; //////////////////////////////// //~ rjf: Helpers @@ -143,7 +171,8 @@ internal tm lnx_tm_from_date_time(DateTime dt); internal timespec lnx_timespec_from_date_time(DateTime dt); internal DenseTime lnx_dense_time_from_timespec(timespec in); internal FileProperties lnx_file_properties_from_stat(struct stat *s); -internal void lnx_safe_call_sig_handler(int x); +internal B32 lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context); +internal void lnx_safe_call_sig_handler(int sig, siginfo_t *info, void *context); //////////////////////////////// //~ rjf: Entities diff --git a/src/radbin/radbin_main.c b/src/radbin/radbin_main.c index 3c1fb526..47e351dd 100644 --- a/src/radbin/radbin_main.c +++ b/src/radbin/radbin_main.c @@ -12,7 +12,6 @@ //- rjf: [h] #include "base/base_inc.h" -#include "x64/x64.h" #include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" #include "rdi/rdi_local.h" @@ -47,7 +46,6 @@ //- rjf: [c] #include "base/base_inc.c" -#include "x64/x64.c" #include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" #include "rdi/rdi_local.c" diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index eb7459c9..b2307cb8 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -376,7 +376,6 @@ //- rjf: [h] #include "base/base_inc.h" -#include "x64/x64.h" #include "win32/win32_inc.h" #include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" @@ -439,7 +438,6 @@ //- rjf: [c] #include "base/base_inc.c" -#include "x64/x64.c" #include "win32/win32_inc.c" #include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 41a5572d..5509bdac 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -12,7 +12,6 @@ //- rjf: [h] #include "base/base_inc.h" -#include "x64/x64.h" #include "artifact_cache/artifact_cache.h" #include "rdi/rdi_local.h" #include "dbg_info/dbg_info.h" @@ -21,7 +20,6 @@ //- rjf: [c] #include "base/base_inc.c" -#include "x64/x64.c" #include "artifact_cache/artifact_cache.c" #include "rdi/rdi_local.c" #include "dbg_info/dbg_info.c" diff --git a/src/scratch/unit_sizes_from_pdb.c b/src/scratch/unit_sizes_from_pdb.c index a2fd7766..4155d33b 100644 --- a/src/scratch/unit_sizes_from_pdb.c +++ b/src/scratch/unit_sizes_from_pdb.c @@ -2,7 +2,6 @@ #define NO_ASYNC 1 #include "base/base_inc.h" -#include "x64/x64.h" #include "linker/hash_table.h" #include "coff/coff.h" #include "coff/coff_parse.h" @@ -25,7 +24,6 @@ #include "dwarf/x64/dwarf_x64.h" #include "base/base_inc.c" -#include "x64/x64.c" #include "linker/hash_table.c" #include "coff/coff.c" #include "coff/coff_parse.c" diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 1b3a6915..e7fc5fb1 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -20,7 +20,6 @@ //////////////////////////////// #include "base/base_inc.h" -#include "x64/x64.h" #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" @@ -104,7 +103,6 @@ #include "torture.h" #include "base/base_inc.c" -#include "x64/x64.c" #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" diff --git a/src/win32/base/win32_base.c b/src/win32/base/win32_base.c index a67c204c..c2c087a2 100644 --- a/src/win32/base/win32_base.c +++ b/src/win32/base/win32_base.c @@ -6,16 +6,37 @@ // // (We must dynamically link to them, since they can be missing in older SDKs) -typedef HRESULT W32_SetThreadDescription_Type(HANDLE hThread, PCWSTR lpThreadDescription); -global W32_SetThreadDescription_Type *w32_SetThreadDescription_func = 0; -typedef BOOL W32_InitializeSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier, LONG lTotalThreads, LONG lSpinCount); -global W32_InitializeSynchronizationBarrier_Type *w32_InitializeSynchronizationBarrier_func = 0; -typedef BOOL W32_DeleteSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier); -global W32_DeleteSynchronizationBarrier_Type *w32_DeleteSynchronizationBarrier_func = 0; -typedef BOOL W32_EnterSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier, DWORD dwFlags); -global W32_EnterSynchronizationBarrier_Type *w32_EnterSynchronizationBarrier_func = 0; global RIO_EXTENSION_FUNCTION_TABLE w32_rio_functions = {0}; +typedef HRESULT W32_SetThreadDescription_Type(HANDLE hThread, PCWSTR lpThreadDescription); +typedef BOOL W32_InitializeSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier, LONG lTotalThreads, LONG lSpinCount); +typedef BOOL W32_DeleteSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier); +typedef BOOL W32_EnterSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier, DWORD dwFlags); + +typedef struct { void *address; SIZE_T size; } W32_PrefetchRange; +typedef BOOL WINAPI W32_PrefetchVirtualMemory_Type(HANDLE, ULONG_PTR, W32_PrefetchRange *, ULONG); + +#define W32_KERNEL32_EXT_XLIST \ + X(SetThreadDescription) \ + X(InitializeSynchronizationBarrier) \ + X(DeleteSynchronizationBarrier) \ + X(EnterSynchronizationBarrier) \ + X(PrefetchVirtualMemory) + +typedef PVOID W32_VirtualAlloc2_Type(HANDLE, PVOID, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER *, ULONG); +typedef PVOID W32_MapViewOfFile3_Type(HANDLE, HANDLE, PVOID, ULONG64, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER *, ULONG); +typedef BOOL W32_UnmapViewOfFile2_Type(HANDLE, PVOID, ULONG); + +#define W32_KERNELBASE_EXT_XLIST \ + X(VirtualAlloc2) \ + X(MapViewOfFile3) \ + X(UnmapViewOfFile2) + +#define X(n) global W32_##n##_Type *w32_##n##_func; +W32_KERNEL32_EXT_XLIST +W32_KERNELBASE_EXT_XLIST +#undef X + //////////////////////////////// //~ rjf: File Info Conversion Helpers @@ -304,6 +325,148 @@ release_memory(void *ptr, U64 size) VirtualFree(ptr, 0, MEM_RELEASE); } + +internal B32 +memory_placeholders_supported(void) +{ + return w32_VirtualAlloc2_func && w32_MapViewOfFile3_func && w32_UnmapViewOfFile2_func; +} + +internal void * +reserve_memory_placeholders(U64 size, U64 block_size) +{ + Assert(block_size > 0); + Assert(size > 0); + Assert(size % block_size == 0); + Assert((block_size % get_system_info()->allocation_granularity) == 0); + + // reserve requested placeholder block + U8 *base = w32_VirtualAlloc2_func(GetCurrentProcess(), 0, size, MEM_RESERVE|MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, 0, 0); + if (base == 0) { return 0; } + + // split placeholder block into specified block sizes + for (U64 offset = 0; offset + block_size < size; offset += block_size) + { + if (VirtualFree(base + offset, block_size, MEM_RELEASE|MEM_PRESERVE_PLACEHOLDER) == 0) + { + if (offset == 0 || VirtualFree(base, size, MEM_RELEASE|MEM_COALESCE_PLACEHOLDERS)) + { + VirtualFree(base, 0, MEM_RELEASE); + } + return 0; + } + } + + return base; +} + +internal void +release_memory_placeholders(void *ptr, U64 size, U64 block_size) +{ + Assert(block_size > 0 && size > 0); + if (size > block_size && VirtualFree(ptr, size, MEM_RELEASE|MEM_COALESCE_PLACEHOLDERS) == 0) { return; } + VirtualFree(ptr, 0, MEM_RELEASE); +} + +internal void * +shared_memory_view_replace_placeholder(SharedMemory handle, void *ptr, Rng1U64 range, AccessFlags flags) +{ + Assert(flags == AccessFlag_Read || flags == (AccessFlag_Read|AccessFlag_Write)); + DWORD protection = flags == AccessFlag_Read ? PAGE_READONLY : PAGE_READWRITE; + return w32_MapViewOfFile3_func((HANDLE)handle.u64[0], GetCurrentProcess(), ptr, range.min, dim_1u64(range), MEM_REPLACE_PLACEHOLDER, protection, 0, 0); +} + +internal B32 +unmap_memory_preserve_placeholder(void *ptr, U64 size) +{ + return w32_UnmapViewOfFile2_func(GetCurrentProcess(), ptr, MEM_PRESERVE_PLACEHOLDER); +} + +internal B32 +split_memory_placeholder(void *ptr, U64 size, U64 first_size) +{ + return first_size == size || (first_size && first_size < size && VirtualFree(ptr, first_size, MEM_RELEASE|MEM_PRESERVE_PLACEHOLDER)); +} + +internal B32 +coalesce_memory_placeholders(void *ptr, U64 size) +{ + return VirtualFree(ptr, size, MEM_RELEASE|MEM_COALESCE_PLACEHOLDERS) != 0; +} + +internal void * +file_map_view_replace_placeholder(FileMap map, void *ptr, Rng1U64 range) +{ + return w32_MapViewOfFile3_func((HANDLE)map.u64[0], GetCurrentProcess(), ptr, range.min, dim_1u64(range), MEM_REPLACE_PLACEHOLDER, PAGE_READONLY, 0, 0); +} + +internal void +prefetch_memory_ranges(U64 count, Rng1U64 *ranges) +{ + W32_PrefetchRange batch[256]; + for (U64 first = 0; first < count; first += ArrayCount(batch)) { + U64 n = Min(ArrayCount(batch), count - first); + for EachIndex(i, n) { + batch[i].address = PtrFromInt(ranges[first+i].min); + batch[i].size = dim_1u64(ranges[first+i]); + } + w32_PrefetchVirtualMemory_func(GetCurrentProcess(), n, batch, 0); + } +} + +internal LONG CALLBACK +w32_memory_read_fault(EXCEPTION_POINTERS *exception) +{ + EXCEPTION_RECORD *record = exception->ExceptionRecord; + if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && + record->NumberParameters >= 2 && + record->ExceptionInformation[0] == 0 && + w32_state.demand_memory.fault((void *)record->ExceptionInformation[1], w32_state.demand_memory.user_data)) + { + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +internal B32 +memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) +{ + W32_DemandMemory *memory = &w32_state.demand_memory; + + if(func) + { + if (memory->handler == 0) + { + memory->fault = func; + memory->user_data = user_data; + memory->handler = AddVectoredExceptionHandler(1, w32_memory_read_fault); + if(memory->handler == 0) + { + memory->fault = 0; + return 0; + } + } + else { Assert(0 && "handler is already registered"); return 0; } + } + else + { + if (memory->handler) + { + if(RemoveVectoredExceptionHandler(memory->handler) == 0) + { + return 0; + } + memory->handler = 0; + memory->fault = 0; + memory->user_data = 0; + } + else + { Assert(0 && "handler was not registered"); return 0; } + } + + return 1; +} + //- rjf: large pages internal void * @@ -333,7 +496,7 @@ shared_memory_alloc(U64 size, String8 name) PAGE_READWRITE, (U32)((size & 0xffffffff00000000) >> 32), (U32)((size & 0x00000000ffffffff)), - (WCHAR *)name16.str); + name.size ? (WCHAR *)name16.str : 0); SharedMemory result = {(U64)file}; scratch_end(scratch); return result; @@ -778,6 +941,7 @@ file_open(AccessFlags flags, String8 path) if(flags & AccessFlag_ShareWrite) {share_mode |= FILE_SHARE_WRITE|FILE_SHARE_DELETE;} if(flags & AccessFlag_Write) {creation_disposition = CREATE_ALWAYS;} if(flags & AccessFlag_Append) {creation_disposition = OPEN_ALWAYS; access_flags |= FILE_APPEND_DATA; } + if(flags & AccessFlag_CreateNew) {creation_disposition = CREATE_NEW;} if(flags & AccessFlag_Inherited) { security_attributes.bInheritHandle = 1; @@ -983,6 +1147,32 @@ file_reserve_size(File file, U64 size) return is_reserved; } +internal B32 +file_set_size(File file, U64 size) +{ + if (size > max_S64) { return 0; } + FILE_END_OF_FILE_INFO info = {0}; + info.EndOfFile.QuadPart = size; + return SetFileInformationByHandle((HANDLE)file.u64[0], FileEndOfFileInfo, &info, sizeof(info)); +} + +internal B32 +file_flush(File file) +{ + return FlushFileBuffers((HANDLE)file.u64[0]); +} + +internal B32 +replace_file_path(String8 dst, String8 src) +{ + Temp scratch = scratch_begin(0,0); + String16 dst16 = str16_from_8(scratch.arena, dst); + String16 src16 = str16_from_8(scratch.arena, src); + B32 result = MoveFileExW((WCHAR *)src16.str, (WCHAR *)dst16.str, MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH); + scratch_end(scratch); + return result; +} + internal B32 delete_file_at_path(String8 path) { @@ -1939,17 +2129,24 @@ w32_entry_point_caller(int argc, WCHAR **wargv) //- rjf: dynamically load windows functions which are not guaranteed // in all SDKs { - HMODULE module = LoadLibraryA("kernel32.dll"); - w32_SetThreadDescription_func = (W32_SetThreadDescription_Type *)GetProcAddress(module, "SetThreadDescription"); - w32_InitializeSynchronizationBarrier_func = (W32_InitializeSynchronizationBarrier_Type *)GetProcAddress(module, "InitializeSynchronizationBarrier"); - w32_DeleteSynchronizationBarrier_func = (W32_DeleteSynchronizationBarrier_Type *)GetProcAddress(module, "DeleteSynchronizationBarrier"); - w32_EnterSynchronizationBarrier_func = (W32_EnterSynchronizationBarrier_Type *)GetProcAddress(module, "EnterSynchronizationBarrier"); + #define X(n) w32_##n##_func = (void*)GetProcAddress(module, Stringify(n)); + HMODULE module; + + module = LoadLibraryA("kernel32.dll"); + W32_KERNEL32_EXT_XLIST + FreeLibrary(module); + + module = LoadLibraryA("kernelbase.dll"); + W32_KERNELBASE_EXT_XLIST + FreeLibrary(module); + if(w32_InitializeSynchronizationBarrier_func == 0) { w32_DeleteSynchronizationBarrier_func = 0; - w32_EnterSynchronizationBarrier_func = 0; + w32_EnterSynchronizationBarrier_func = 0; } - FreeLibrary(module); + + #undef X } //- rjf: try to allow large pages if we can @@ -2000,11 +2197,16 @@ w32_entry_point_caller(int argc, WCHAR **wargv) } } { + + MEMORYSTATUSEX memory = { .dwLength = sizeof(memory) }; + GlobalMemoryStatusEx(&memory); + SystemInfo *info = &w32_state.system_info; info->logical_processor_count = (U64)sysinfo.dwNumberOfProcessors; info->page_size = sysinfo.dwPageSize; info->large_page_size = GetLargePageMinimum(); info->allocation_granularity = sysinfo.dwAllocationGranularity; + info->physical_memory_size = memory.ullTotalPhys; } { ProcessInfo *info = &w32_state.process_info; diff --git a/src/win32/base/win32_base.h b/src/win32/base/win32_base.h index a70812e1..553d7264 100644 --- a/src/win32/base/win32_base.h +++ b/src/win32/base/win32_base.h @@ -88,6 +88,16 @@ struct W32_Entity }; }; +//////////////////////////////// +//~ On Demand Memory + +typedef struct +{ + MemoryReadFaultFunction *fault; + void *user_data; + PVOID handler; +} W32_DemandMemory; + //////////////////////////////// //~ rjf: State @@ -105,6 +115,8 @@ struct W32_State CRITICAL_SECTION entity_mutex; Arena *entity_arena; W32_Entity *entity_free; + + W32_DemandMemory demand_memory; }; //////////////////////////////// diff --git a/src/x64/x64.h b/src/x64/x64.h index db6ec7d2..7d9c1fd1 100644 --- a/src/x64/x64.h +++ b/src/x64/x64.h @@ -209,6 +209,52 @@ typedef enum X64_DebugControlFlags } X64_DebugControlFlags; +//////////////////////////////// +//~ Exception Codes + +typedef enum +{ + X64_Exception_DE = 0, // Divide error: division by zero or quotient overflow + X64_Exception_DB = 1, // Debug exception (single stepping, hardware breakpoint) + X64_Exception_NMI = 2, // Non-maskable interrupt + X64_Exception_BP = 3, // Breakpoint (int3) + X64_Exception_OF = 4, // Overflow + X64_Exception_BR = 5, // Bound range exceeded + X64_Exception_UD = 6, // Invalid opcode + X64_Exception_NM = 7, // Device not available + X64_Exception_DF = 8, // Double fault + X64_Exception_9 = 9, // Coprocessor segment overrun (obsolete) + X64_Exception_TS = 10, // Invalid task-state segment + X64_Exception_NP = 11, // Segment not present + X64_Exception_SS = 12, // Stack-segment fault + X64_Exception_GP = 13, // General-protection fault + X64_Exception_PF = 14, // Page fault + X64_Exception_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS + X64_Exception_MF = 16, // x87 floating-point exception + X64_Exception_AC = 17, // Alignment check + X64_Exception_MC = 18, // Machine check + X64_Exception_XF = 19, // SIMD floating-point exception + X64_Exception_VE = 20, // Virtualization exception + X64_Exception_CP = 21, // Control-protection exception + X64_Exception_Amd_HV = 28, // Hypervisor injection exception + X64_Exception_Amd_VC = 29, // VMM communication exception + X64_Exception_Amd_SX = 30, // Security exception +} X64_ExceptionCode; + +//////////////////////////////// +//~ Page Fault Flags + +typedef enum +{ + X64_PageFaultError_Protection = (1u << 0), + X64_PageFaultError_Write = (1u << 1), + X64_PageFaultError_User = (1u << 2), + X64_PageFaultError_ReservedBit = (1u << 3), + X64_PageFaultError_InstructionFetch = (1u << 4), + X64_PageFaultError_ProtectionKey = (1u << 5), + X64_PageFaultError_ShadowStack = (1u << 6), +} X64_PageFaultError; + //////////////////////////////// //~ rjf: Generated From 48ba845a913a3b47c264a5e2d9bbd44b1e1c7742 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 19 Sep 2026 14:18:02 -0700 Subject: [PATCH 166/251] radlink: strip LF_PRECOMP earlier before parsing .debug$T to exclude it from leaf arrays --- src/linker/base_ext/base_bit_array.c | 2 +- src/linker/codeview_ext/codeview.c | 28 ++-- src/linker/codeview_ext/codeview.h | 6 +- src/linker/lnk_debug_info.c | 193 +++++++++++++-------------- src/linker/lnk_debug_info.h | 2 +- src/linker/pdb_ext/pdb_builder.c | 14 +- 6 files changed, 122 insertions(+), 123 deletions(-) diff --git a/src/linker/base_ext/base_bit_array.c b/src/linker/base_ext/base_bit_array.c index ddfe197f..e21bd668 100644 --- a/src/linker/base_ext/base_bit_array.c +++ b/src/linker/base_ext/base_bit_array.c @@ -46,7 +46,7 @@ bit_array_get_bit32(U32Array bit_array, U64 idx) } internal B32 -bit_array_is_bit_set(U32Array bit_arr, U64 bit_pos) +bit_array_is_set32(U32Array bit_arr, U64 bit_pos) { U64 word_idx = bit_pos / 32; Assert(word_idx < bit_arr.count); diff --git a/src/linker/codeview_ext/codeview.c b/src/linker/codeview_ext/codeview.c index c76f78a5..02b7e08f 100644 --- a/src/linker/codeview_ext/codeview.c +++ b/src/linker/codeview_ext/codeview.c @@ -1147,8 +1147,23 @@ cv_debug_t_from_data(Arena *arena, String8 data, U64 align) { CV_DebugT debug_t = { .data = data }; + U64 parse_start_offset = 0; + + // strip LF_PRECOMP from .debug$T to exclude it from leaf arrays + B8 has_pch = 0; + CV_PrecompInfo precomp = {0}; + { + CV_Leaf leaf = {0}; + U64 leaf_size = cv_read_leaf(data, 0, CV_LeafAlign, &leaf); + if (leaf.kind == CV_LeafKind_PRECOMP) { + debug_t.has_pch = 1; + debug_t.precomp = cv_precomp_info_from_leaf(leaf); + parse_start_offset = leaf_size; + } + } + ProfBegin("Upfront parse for counts"); - for (U64 cursor = 0, prev_cursor = 0, ti = CV_MinComplexTypeIndex; cursor < data.size; ti += 1) { + for (U64 cursor = parse_start_offset, prev_cursor = parse_start_offset, ti = CV_MinComplexTypeIndex; cursor < data.size; ti += 1) { CV_Leaf leaf = {0}; TryRead(cv_read_leaf(data, cursor, align, &leaf), cursor, count_stop); debug_t.source_counts[cv_type_index_source_from_leaf_kind(leaf.kind)] += 1; @@ -1160,20 +1175,15 @@ count_stop: ProfBegin("store leaf offsets"); debug_t.offsets = push_array_no_zero(arena, U32, debug_t.count); - for (U64 cursor = 0, idx = 0; cursor < data.size && idx < debug_t.count; idx += 1) { + for (U64 cursor = parse_start_offset, idx = 0; cursor < data.size && idx < debug_t.count; idx += 1) { debug_t.offsets[idx] = cursor; TryRead(cv_read_leaf(data, cursor, align, &(CV_Leaf){0}), cursor, store_stop); } store_stop: - for EachElement(i, debug_t.ti_ranges) { debug_t.ti_ranges[i] = r1u64(CV_MinComplexTypeIndex, CV_MinComplexTypeIndex + debug_t.count); } + for EachElement(i, debug_t.ti_ranges) { debug_t.ti_ranges[i] = r1u64(CV_MinComplexTypeIndex, CV_MinComplexTypeIndex + debug_t.count + debug_t.precomp.leaf_count); } - // shift upper type index bound to include precompiled types - CV_Leaf leaf = cv_debug_t_get_leaf(&debug_t, 0); - if (leaf.kind == CV_LeafKind_PRECOMP) { - CV_PrecompInfo precomp_info = cv_precomp_info_from_leaf(leaf); - for EachElement(i, debug_t.ti_ranges) { debug_t.ti_ranges[i].max += precomp_info.leaf_count; } - } + for (U64 i = 1; i < CV_TypeIndexSource_COUNT; i += 1) { debug_t.pch_ti_range[i] = r1u64s(debug_t.precomp.start_index, debug_t.precomp.leaf_count); } ProfEnd(); return debug_t; diff --git a/src/linker/codeview_ext/codeview.h b/src/linker/codeview_ext/codeview.h index f3e5ae9f..72817ad5 100644 --- a/src/linker/codeview_ext/codeview.h +++ b/src/linker/codeview_ext/codeview.h @@ -240,8 +240,10 @@ typedef struct CV_DebugT Rng1U64 ti_ranges [CV_TypeIndexSource_COUNT]; // PCH - Rng1U64 pch_ti_range[CV_TypeIndexSource_COUNT]; - U32 pch_obj_idx; + B8 has_pch; + CV_PrecompInfo precomp; + Rng1U64 pch_ti_range[CV_TypeIndexSource_COUNT]; + U32 pch_obj_idx; } CV_DebugT; //////////////////////////////// diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index d18791cb..7a53e8c1 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -138,22 +138,22 @@ THREAD_POOL_TASK_FUNC(lnk_strip_debug_t_sig_task) U64 obj_idx = task_id; LNK_ParseCvTypes *task = raw_task; - for EachIndex(i, task->raw_types[obj_idx].count) { - String8 *d = task->raw_types[obj_idx].v + i; - if (d->size == 0) { continue; } - if (d->size < sizeof(CV_Signature)) { lnk_error_obj(LNK_Error_IllData, task->input->obj_arr[obj_idx], ".debug$T must have at least 4 bytes for CodeView signature"); continue; } + String8 *d = &task->raw_types[obj_idx]; + if (d->size == 0) { goto exit; } + if (d->size < sizeof(CV_Signature)) { lnk_error_obj(LNK_Error_IllData, task->input->obj_arr[obj_idx], ".debug$T must have at least 4 bytes for CodeView signature"); goto exit; } - CV_Signature sig = cv_signature_from_debug_s(*d); - switch (sig) { - default: { - lnk_error_obj(LNK_Warning_IllData, task->input->obj_arr[obj_idx], "unknown CodeView type signature in section (TODO: print section index)"); - *d = str8(0,0); - } break; - case CV_Signature_C13: { - *d = str8_skip(*d, sizeof(CV_Signature)); - } break; - } + CV_Signature sig = cv_signature_from_debug_s(*d); + switch (sig) { + default: { + lnk_error_obj(LNK_Warning_IllData, task->input->obj_arr[obj_idx], "unknown CodeView type signature in section (TODO: print section index)"); + *d = str8(0,0); + } break; + case CV_Signature_C13: { + *d = str8_skip(*d, sizeof(CV_Signature)); + } break; } + + exit:; } internal @@ -161,11 +161,7 @@ THREAD_POOL_TASK_FUNC(lnk_parse_debug_t_task) { ProfBeginFunction(); LNK_ParseCvTypes *task = raw_task; - if (task->raw_types[task_id].count > 0) { - task->out_types[task_id] = cv_debug_t_from_data(arena, task->raw_types[task_id].v[0], CV_LeafAlign); - } else { - MemoryZeroStruct(&task->out_types[task_id]); - } + task->out_types[task_id] = cv_debug_t_from_data(arena, task->raw_types[task_id], CV_LeafAlign); ProfEnd(); } @@ -662,7 +658,14 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, ProfBegin("Extract CodeView"); Temp scratch = scratch_begin(0,0); - LNK_CodeViewInput input = { .config = config, .obj_count = obj_count, .count = obj_count, .obj_arr = obj_arr, .rrt_input = rrt_input, .ts_obj_range = r1u64(0,0) }; + LNK_CodeViewInput input = { + .config = config, + .obj_count = obj_count, + .count = obj_count, + .obj_arr = obj_arr, + .rrt_input = rrt_input, + .ts_obj_range = r1u64(0,0) + }; HashMap rrt_hm = {0}; ProfScope("Make obj path -> RRT hash map") @@ -674,52 +677,51 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } } - ProfBegin("Apply RRT to Objs"); + ProfScope("Apply RRT to Objs") { + // hash map (obj path, obj idx) + HashMap obj_path_hm = {0}; + for EachIndex(obj_idx, obj_count) { + hash_map_push_path_u64(scratch.arena, &obj_path_hm, obj_arr[obj_idx]->path, obj_idx); + } - // hash map (obj path, obj idx) - HashMap obj_path_hm = {0}; - for EachIndex(obj_idx, obj_count) { - hash_map_push_path_u64(scratch.arena, &obj_path_hm, obj_arr[obj_idx]->path, obj_idx); - } + for EachIndex(obj_idx, obj_count) { + LNK_Obj *obj = obj_arr[obj_idx]; + U64 *packed_rrt_idx = hash_map_search_path_u64(&rrt_hm, obj->path); - for EachIndex(obj_idx, obj_count) { - LNK_Obj *obj = obj_arr[obj_idx]; - U64 *packed_rrt_idx = hash_map_search_path_u64(&rrt_hm, obj->path); + // obj is not part of any input RRT + if (packed_rrt_idx == 0) { continue; } - // obj is not part of any input RRT - if (packed_rrt_idx == 0) { continue; } + // unpack index + U32 rrt_idx = *packed_rrt_idx >> 32; + U32 rrt_obj_idx = *packed_rrt_idx & max_U32; + LNK_RRT *rrt = &rrt_input.v[rrt_idx]; - // unpack index - U32 rrt_idx = *packed_rrt_idx >> 32; - U32 rrt_obj_idx = *packed_rrt_idx & max_U32; - LNK_RRT *rrt = &rrt_input.v[rrt_idx]; + // obj was recompiled, do not apply RRT indirection + FileProperties obj_file_props = properties_from_file_path(obj->path); + if (rrt->obj_time_stamps[rrt_obj_idx] != obj_file_props.modified) { continue; } - // obj was recompiled, do not apply RRT indirection - FileProperties obj_file_props = properties_from_file_path(obj->path); - if (rrt->obj_time_stamps[rrt_obj_idx] != obj_file_props.modified) { continue; } + // invalidate debug section pointers + obj->coff.debug_t_section_number = 0; + obj->coff.debug_p_section_number = 0; + obj->coff.debug_h_section_number = 0; - // invalidate debug section pointers - obj->coff.debug_t_section_number = 0; - obj->coff.debug_p_section_number = 0; - obj->coff.debug_h_section_number = 0; + // apply type index map + obj->ti_range = rrt->obj_ti_ranges[rrt_obj_idx]; + obj->ti_map = rrt->obj_ti_maps [rrt_obj_idx]; - // apply type index map - obj->ti_range = rrt->obj_ti_ranges[rrt_obj_idx]; - obj->ti_map = rrt->obj_ti_maps [rrt_obj_idx]; - - // apply PCH info - U32 rrt_pch_obj_idx = rrt->obj_pch_indices[rrt_obj_idx]; - if (rrt_pch_obj_idx < rrt->obj_count) { - String8 rrt_pch_obj_path = rrt->obj_paths.v[rrt_pch_obj_idx]; - U64 pch_obj_idx = *hash_map_search_path_u64(&obj_path_hm, rrt_pch_obj_path); - obj->pch_ti_range = rrt->obj_pch_ti_ranges[rrt_obj_idx]; - obj->pch_obj_idx = pch_obj_idx; - } else { - obj->pch_ti_range = r1u64(0,0); - obj->pch_obj_idx = ~0; + // apply PCH info + U32 rrt_pch_obj_idx = rrt->obj_pch_indices[rrt_obj_idx]; + if (rrt_pch_obj_idx < rrt->obj_count) { + String8 rrt_pch_obj_path = rrt->obj_paths.v[rrt_pch_obj_idx]; + U64 pch_obj_idx = *hash_map_search_path_u64(&obj_path_hm, rrt_pch_obj_path); + obj->pch_ti_range = rrt->obj_pch_ti_ranges[rrt_obj_idx]; + obj->pch_obj_idx = pch_obj_idx; + } else { + obj->pch_ti_range = r1u64(0,0); + obj->pch_obj_idx = ~0; + } } } - ProfEnd(); ProfBegin("Collect CodeView"); input.debug_s_list_arr = lnk_collect_obj_sections(tp, tp_arena, obj_count, obj_arr, str8_lit(".debug$S"), 0); @@ -770,55 +772,44 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } } - ProfBegin("Parse CodeView"); CV_DebugT *debug_p_arr; + ProfScope("Parse CodeView") { // parse .debug$S input.debug_s_arr = push_array(tp_arena->v[0], CV_DebugS, input.obj_count); tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_s_task, &input, "Parse .debug$S"); // collect .debug$P and .debug$T - String8Array *raw_debug_p_arr = push_array(scratch.arena, String8Array, obj_count); - String8Array *raw_debug_t_arr = push_array(scratch.arena, String8Array, obj_count); + String8 *raw_debug_p_arr = push_array(scratch.arena, String8, obj_count); + String8 *raw_debug_t_arr = push_array(scratch.arena, String8, obj_count); for EachIndex(obj_idx, obj_count) { LNK_Obj *obj = obj_arr[obj_idx]; - - if (obj->coff.debug_t_section_number > 0) { - raw_debug_t_arr[obj_idx].count = 1; - raw_debug_t_arr[obj_idx].v = push_array(scratch.arena, String8, 1); - raw_debug_t_arr[obj_idx].v[0] = lnk_obj_section_data_from_number(obj, obj->coff.debug_t_section_number); - } - - if (obj->coff.debug_p_section_number > 0) { - raw_debug_p_arr[obj_idx].count = 1; - raw_debug_p_arr[obj_idx].v = push_array(scratch.arena, String8, 1); - raw_debug_p_arr[obj_idx].v[0] = lnk_obj_section_data_from_number(obj, obj->coff.debug_p_section_number); - } + if (obj->coff.debug_t_section_number > 0) { raw_debug_t_arr[obj_idx] = lnk_obj_section_data_from_number(obj, obj->coff.debug_t_section_number); } + if (obj->coff.debug_p_section_number > 0) { raw_debug_p_arr[obj_idx] = lnk_obj_section_data_from_number(obj, obj->coff.debug_p_section_number); } } LNK_ParseCvTypes parse_types = { .input = &input }; - // parse .debug$P + // .debug$P debug_p_arr = push_array(tp_arena->v[0], CV_DebugT, obj_count); parse_types.raw_types = raw_debug_p_arr; parse_types.out_types = debug_p_arr; - tp_for_parallel_prof(tp, 0, obj_count, lnk_strip_debug_t_sig_task, &parse_types, "Strip .debug$P"); + tp_for_parallel_prof(tp, 0, obj_count, lnk_strip_debug_t_sig_task, &parse_types, "Strip Signature .debug$P"); tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_t_task, &parse_types, "Parse .debug$P"); - // parse .debug$T + // .debug$T input.debug_t_arr = push_array(tp_arena->v[0], CV_DebugT, obj_count); parse_types.raw_types = raw_debug_t_arr; parse_types.out_types = input.debug_t_arr; - tp_for_parallel_prof(tp, 0, obj_count, lnk_strip_debug_t_sig_task, &parse_types, "Strip .debug$T"); - tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_t_task, &parse_types, "Parse .debug$T"); + tp_for_parallel_prof(tp, 0, obj_count, lnk_strip_debug_t_sig_task, &parse_types, "Strip Signature .debug$T"); + tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_t_task, &parse_types, "Parse .debug$T"); - // parse .debug$H + // .debug$H input.debug_h_arr = push_array(tp_arena->v[0], CV_DebugH, input.obj_count); if (config->ghash) { tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_h_task, &input, "Parse .debug$H"); } } - ProfEnd(); // sort objs based on type: PCH, /Zi (external), /Z7 (internal) input.debug_p_indices.v = push_array(tp_arena->v[0], U32, obj_count); @@ -1020,31 +1011,31 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } for EachIndex(i, input.int_obj_indices.count) { - U64 obj_idx = input.int_obj_indices.v[i]; - CV_DebugT *debug_t = &input.debug_t_arr[obj_idx]; + U64 obj_idx = input.int_obj_indices.v[i]; + CV_DebugT *debug_t = &input.debug_t_arr[obj_idx]; + CV_PrecompInfo *precomp = &debug_t->precomp; // skip objs that do not depend on PCH - if ( ! cv_debug_t_is_pch(debug_t)) { continue; } + if ( ! debug_t->has_pch) { continue; } // find PCH obj by file path - CV_PrecompInfo precomp = cv_precomp_info_from_leaf(cv_debug_t_get_leaf(debug_t, 0)); - String8 obj_path = path_absolute_dst_from_relative_dst_src(scratch.arena, precomp.obj_name, config->work_dir); - U64 *debug_p_obj_idx_ptr = hash_map_search_path_u64(&debug_p_hm_path, obj_path); - U64 debug_p_obj_idx = debug_p_obj_idx_ptr ? *debug_p_obj_idx_ptr : max_U64; + String8 obj_path = path_absolute_dst_from_relative_dst_src(scratch.arena, precomp->obj_name, config->work_dir); + U64 *debug_p_obj_idx_ptr = hash_map_search_path_u64(&debug_p_hm_path, obj_path); + U64 debug_p_obj_idx = debug_p_obj_idx_ptr ? *debug_p_obj_idx_ptr : max_U64; - // find PCH obj by signature + // fallback: find PCH obj by signature if (debug_p_obj_idx_ptr == 0) { for EachIndex(pch_i, input.debug_p_indices.count) { U64 pch_obj_idx = input.debug_p_indices.v[pch_i]; CV_DebugT *pch_debug_t = &input.debug_t_arr[pch_obj_idx]; - if (precomp.leaf_count >= pch_debug_t->count) { continue; } + if (precomp->leaf_count >= pch_debug_t->count) { continue; } - CV_Leaf end_leaf = cv_debug_t_get_leaf(pch_debug_t, precomp.leaf_count); + CV_Leaf end_leaf = cv_debug_t_get_leaf(pch_debug_t, precomp->leaf_count); if (end_leaf.kind != CV_LeafKind_ENDPRECOMP) { continue; } if (end_leaf.data.size < sizeof(CV_LeafEndPreComp)) { continue; } CV_LeafEndPreComp *end_precomp = str8_deserial_get_raw_ptr(end_leaf.data, 0, sizeof(*end_precomp)); - if (end_precomp->sig != precomp.sig) { continue; } + if (end_precomp->sig != precomp->sig) { continue; } debug_p_obj_idx = pch_obj_idx; break; @@ -1061,25 +1052,21 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, CV_DebugT *debug_p = &input.debug_t_arr[debug_p_obj_idx]; // error check LF_PRECOMP - if (precomp.start_index > CV_MinComplexTypeIndex) { lnk_error_obj(LNK_Warning_AtypicalStartIndex, obj_arr[obj_idx], "atypical start index 0x%x in LF_PRECOMP", precomp.start_index); } - if (precomp.start_index < CV_MinComplexTypeIndex) { lnk_error_obj(LNK_Error_InvalidStartIndex, obj_arr[obj_idx], "invalid start index 0x%x in LF_PRECOMP; must be >= 0x%x", precomp.start_index, CV_MinComplexTypeIndex); continue; } - if (precomp.leaf_count >= debug_p->count) { lnk_error_obj(LNK_Error_InvalidPrecompLeafCount, obj_arr[obj_idx], "leaf count %u LF_PRECOMP exceeds leaf count %u in .debug$P in %S", precomp.leaf_count, debug_p->count, obj_arr[debug_p_obj_idx]->path); continue; } + if (precomp->start_index > CV_MinComplexTypeIndex) { lnk_error_obj(LNK_Warning_AtypicalStartIndex, obj_arr[obj_idx], "atypical start index 0x%x in LF_PRECOMP", precomp->start_index); } + if (precomp->start_index < CV_MinComplexTypeIndex) { lnk_error_obj(LNK_Error_InvalidStartIndex, obj_arr[obj_idx], "invalid start index 0x%x in LF_PRECOMP; must be >= 0x%x", precomp->start_index, CV_MinComplexTypeIndex); continue; } + if (precomp->leaf_count >= debug_p->count) { lnk_error_obj(LNK_Error_InvalidPrecompLeafCount, obj_arr[obj_idx], "leaf count %u LF_PRECOMP exceeds leaf count %u in .debug$P in %S", precomp->leaf_count, debug_p->count, obj_arr[debug_p_obj_idx]->path); continue; } - // get LF_PRECOMP - CV_Leaf endprecomp_leaf = cv_debug_t_get_leaf(debug_p, precomp.leaf_count); + // get LF_ENDPRECOMP + CV_Leaf endprecomp_leaf = cv_debug_t_get_leaf(debug_p, precomp->leaf_count); CV_LeafEndPreComp *endprecomp = str8_deserial_get_raw_ptr(endprecomp_leaf.data, 0, sizeof(*endprecomp)); // error check LF_ENDPRECOMP - if (endprecomp_leaf.kind != CV_LeafKind_ENDPRECOMP) { lnk_error_obj(LNK_Error_EndprecompNotFound, obj_arr[obj_idx], "missing LF_ENDPRECOMP [0x%x] in %S", precomp.leaf_count, obj_arr[debug_p_obj_idx]->path); continue; } + if (endprecomp_leaf.kind != CV_LeafKind_ENDPRECOMP) { lnk_error_obj(LNK_Error_EndprecompNotFound, obj_arr[obj_idx], "missing LF_ENDPRECOMP [0x%x] in %S", precomp->leaf_count, obj_arr[debug_p_obj_idx]->path); continue; } if (endprecomp_leaf.data.size != sizeof(CV_LeafEndPreComp)) { lnk_error_obj(LNK_Error_IllData, obj_arr[obj_idx], "invalid size 0x%x for LF_ENDPRECOMP", endprecomp_leaf.data.size); continue; } - if (endprecomp->sig != precomp.sig) { lnk_error_obj(LNK_Error_PrecompSigMismatch, obj_arr[obj_idx], "PCH signature mismatch, expected 0x%x got 0x%x; PCH obj %S", precomp.sig, endprecomp->sig, obj_arr[debug_p_obj_idx]->path); continue; } + if (endprecomp->sig != precomp->sig) { lnk_error_obj(LNK_Error_PrecompSigMismatch, obj_arr[obj_idx], "PCH signature mismatch, expected 0x%x got 0x%x; PCH obj %S", precomp->sig, endprecomp->sig, obj_arr[debug_p_obj_idx]->path); continue; } - for (U64 i = 1; i < CV_TypeIndexSource_COUNT; i += 1) { debug_t->pch_ti_range[i] = r1u64(precomp.start_index, precomp.start_index + precomp.leaf_count); } - debug_t->pch_obj_idx = debug_p_obj_idx; - - // remove CV_LeafKind_PRECOMP - debug_t->count -= 1; - debug_t->offsets += 1; + // associate .debug$P and .debug$T + debug_t->pch_obj_idx = debug_p_obj_idx; } // remove LF_ENDPRECOMP from .debug$P diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index 573fc3b5..d74f23fd 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -113,7 +113,7 @@ typedef struct typedef struct { LNK_CodeViewInput *input; - String8Array *raw_types; // [obj_count] + String8 *raw_types; // [obj_count] CV_DebugT *out_types; // [obj_count] } LNK_ParseCvTypes; diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index f1084429..1c6a523e 100644 --- a/src/linker/pdb_ext/pdb_builder.c +++ b/src/linker/pdb_ext/pdb_builder.c @@ -98,8 +98,8 @@ pdb_hash_table_from_data(PDB_HashTable *ht, // unpack buckets U64 read_count = 0; for (U64 bucket_idx = 0; bucket_idx < max; bucket_idx += 1) { - if (bit_array_is_bit_set(present_bits_new, bucket_idx)) { - if (bit_array_is_bit_set(deleted_bits_new, bucket_idx)) { + if (bit_array_is_set32(present_bits_new, bucket_idx)) { + if (bit_array_is_set32(deleted_bits_new, bucket_idx)) { error = PDB_HashTableParseError_CORRUPTED; break; } @@ -198,7 +198,7 @@ pdb_hash_table_grow(PDB_HashTable *ht, U64 new_capacity) PDB_HashTable new_ht; pdb_hash_table_alloc(&new_ht, new_capacity); for (U32 i = 0; i < ht->max; ++i) { - if (bit_array_is_bit_set(ht->present_bits, i)) { + if (bit_array_is_set32(ht->present_bits, i)) { PDB_HashTableBucket *bucket = &ht->bucket_arr[i]; PDB_HashTableBucket *is_set = pdb_hash_table_try_set(&new_ht, bucket->key, bucket->value); is_set->insert_idx = bucket->insert_idx; @@ -316,14 +316,14 @@ internal B32 pdb_hash_table_is_present(PDB_HashTable *ht, U32 k) { Assert(k < ht->max); - return bit_array_is_bit_set(ht->present_bits, k); + return bit_array_is_set32(ht->present_bits, k); } internal B32 pdb_hash_table_is_deleted(PDB_HashTable *ht, U32 k) { Assert(k < ht->max); - return bit_array_is_bit_set(ht->deleted_bits, k); + return bit_array_is_set32(ht->deleted_bits, k); } internal PDB_HashTableBucket ** @@ -332,7 +332,7 @@ pdb_hash_table_get_present_buckets(Arena *arena, PDB_HashTable *ht) U64 result_count = 0; PDB_HashTableBucket **result = push_array(arena, PDB_HashTableBucket *, ht->count); for EachIndex(bucket_idx, ht->max) { - if (bit_array_is_bit_set(ht->present_bits, bucket_idx)) { + if (bit_array_is_set32(ht->present_bits, bucket_idx)) { PDB_HashTableBucket *bucket = &ht->bucket_arr[bucket_idx]; Assert(result_count < ht->count); result[result_count++] = bucket; @@ -347,7 +347,7 @@ pdb_hash_table_get_present_keys_and_values(Arena *arena, PDB_HashTable *ht, Stri *keys_out = str8_array_reserve(arena, ht->count); *values_out = str8_array_reserve(arena, ht->count); for (U64 bucket_idx = 0; bucket_idx < ht->max; bucket_idx += 1) { - if (bit_array_is_bit_set(ht->present_bits, bucket_idx)) { + if (bit_array_is_set32(ht->present_bits, bucket_idx)) { PDB_HashTableBucket *bucket = &ht->bucket_arr[bucket_idx]; Assert(keys_out->count < ht->count); keys_out->v[keys_out->count++] = bucket->key; From 4e4639c2a04348a981037cf6e83756f49f22c506 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 21 Sep 2026 09:14:11 -0700 Subject: [PATCH 167/251] x64 does not belong in base - base is intended to be things we can assume everywhere, which explicitly disqualifies x64 by definition. --- src/base/base_core.h | 1 + src/base/base_inc.c | 3 --- src/base/base_inc.h | 1 - src/raddbg/raddbg_main.c | 4 ++-- src/third_party/radsort/radsort.h | 2 +- src/torture/torture_main.c | 2 ++ 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/base/base_core.h b/src/base/base_core.h index 580089af..2f2c4c48 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -21,6 +21,7 @@ #define STB_SPRINTF_DECORATE(name) raddbg_##name #define STB_SPRINTF_STATIC #include "third_party/stb/stb_sprintf.h" +#include "third_party/radsort/radsort.h" //////////////////////////////// //~ rjf: Codebase Keywords diff --git a/src/base/base_inc.c b/src/base/base_inc.c index 1cff7bf2..5a102437 100644 --- a/src/base/base_inc.c +++ b/src/base/base_inc.c @@ -30,9 +30,6 @@ #include "base_test.c" #include "base_entry_point.c" -// TODO: move x64 to base layer -#include "x64/x64.c" - #if OS_WINDOWS # include "win32/base/win32_base.c" #elif OS_LINUX diff --git a/src/base/base_inc.h b/src/base/base_inc.h index 3aa43a74..f83f8858 100644 --- a/src/base/base_inc.h +++ b/src/base/base_inc.h @@ -32,7 +32,6 @@ #include "base_test.h" #include "base_entry_point.h" -#include "x64/x64.h" #if OS_WINDOWS # include "win32/base/win32_base.h" #elif OS_LINUX diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index b2307cb8..abe98430 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -376,8 +376,8 @@ //- rjf: [h] #include "base/base_inc.h" +#include "x64/x64.h" #include "win32/win32_inc.h" -#include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" #include "artifact_cache/artifact_cache.h" #include "rdi/rdi_local.h" @@ -438,8 +438,8 @@ //- rjf: [c] #include "base/base_inc.c" +#include "x64/x64.c" #include "win32/win32_inc.c" -#include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" #include "artifact_cache/artifact_cache.c" #include "rdi/rdi_local.c" diff --git a/src/third_party/radsort/radsort.h b/src/third_party/radsort/radsort.h index dbfbd11c..7cf3146e 100644 --- a/src/third_party/radsort/radsort.h +++ b/src/third_party/radsort/radsort.h @@ -137,7 +137,7 @@ radsortmovesize( sizeof( (start)[0] ) ), \ //=================================================================================================== // small heap sort - this sort is around 200 bytes compiled - can use directly when size is important -internal RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper ) +static RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper ) { void * left; void * right; diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index e7fc5fb1..f9607b21 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -20,6 +20,7 @@ //////////////////////////////// #include "base/base_inc.h" +#include "x64/x64_inc.h" #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" @@ -103,6 +104,7 @@ #include "torture.h" #include "base/base_inc.c" +#include "x64/x64_inc.c" #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" From 9796328ffa6589bb903f04a9df6403718dd17d79 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 21 Sep 2026 09:43:28 -0700 Subject: [PATCH 168/251] only inc x64 into base by default on linux, where we actually apparently need it - ideally we do not just pull in all of x64 just for a few enum codes' --- src/linux/base/linux_base.c | 54 ++++++++++++++++---------------- src/linux/base/linux_base.h | 21 ++++++++----- src/x64/x64.h | 62 +++++++++++++++++++------------------ 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index fec7a0da..e95e2be8 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -285,7 +285,7 @@ shared_memory_alloc(U64 size, String8 name) SharedMemory result = { max_U64 }; if(size == 0 || size > max_S64) { return result; } - + int id; if(name.size == 0) { @@ -304,7 +304,7 @@ shared_memory_alloc(U64 size, String8 name) if(ftruncate(id, size) == 0) { result.u64[0] = (U64)id; } else { close(id); } } - + return result; } @@ -1107,15 +1107,15 @@ lnx_memory_fault_thread(void *unused) { LNX_MemoryFaultRequest *request = 0; ssize_t count = LNX_RETRY_ON_EINTR(read(lnx_state.demand_memory.requests[0], &request, sizeof(request))); - + if(count == 0) { break; } if(count != sizeof(request)) { _exit(1); } if(request == 0) { break; } - + lnx_in_memory_fault_callback = 1; B32 handled = lnx_state.demand_memory.fault(request->address, lnx_state.demand_memory.user_data); lnx_in_memory_fault_callback = 0; - + U32 *completion = &request->result; ins_atomic_u32_eval_assign(completion, handled ? 2 : 1); syscall(SYS_futex, completion, FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); @@ -1148,25 +1148,25 @@ internal B32 lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) { LNX_DemandMemory *memory = &lnx_state.demand_memory; - + #if ARCH_X64 || ARCH_X86 if(sig != SIGSEGV || info == 0 || context == 0 || - (info->si_code != SEGV_ACCERR && info->si_code != SEGV_MAPERR) || - lnx_in_memory_fault_callback || - ins_atomic_u32_eval(&memory->active) == 0) + (info->si_code != SEGV_ACCERR && info->si_code != SEGV_MAPERR) || + lnx_in_memory_fault_callback || + ins_atomic_u32_eval(&memory->active) == 0) { return 0; } - + ucontext_t *state = context; - if((state->uc_mcontext.gregs[REG_TRAPNO] != X64_Exception_PF) || + if((state->uc_mcontext.gregs[REG_TRAPNO] != X64_ExceptionCode_PF) || (state->uc_mcontext.gregs[REG_ERR] & X64_PageFaultError_Write) || (state->uc_mcontext.gregs[REG_ERR] & X64_PageFaultError_InstructionFetch) || memory->owner_pid != getpid()) { return 0; } - + int saved_errno = errno; LNX_MemoryFaultRequest request = { .address = info->si_addr }; LNX_MemoryFaultRequest *ptr = &request; @@ -1176,7 +1176,7 @@ lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) errno = saved_errno; return 0; } - + // wait for the page fault handler to complete while(!ins_atomic_u32_eval(&request.result)) { @@ -1185,10 +1185,10 @@ lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) _exit(1); } } - + B32 handled = request.result == 2; errno = saved_errno; - + return handled; #else NotImplemented; @@ -1210,7 +1210,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) { return 0; } - + memory->fault = func; memory->user_data = user_data; memory->owner_pid = getpid(); @@ -1229,7 +1229,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) } memory->workers[memory->worker_count++] = worker; } - + ins_atomic_u32_eval_assign(&memory->active, 1); } else if(memory->worker_count) @@ -1238,7 +1238,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) { return 0; } - + ins_atomic_u32_eval_assign(&memory->active, 0); return lnx_memory_fault_workers_stop(memory); } @@ -1605,7 +1605,7 @@ process_launch(ProcessLaunchParams *params) posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP); posix_spawnattr_setpgroup(&attr, 0); } - + // package argv char **argv = push_array(scratch.arena, char *, params->cmd_line.node_count + 1); { @@ -1822,7 +1822,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) { // page fault handler if(lnx_dispatch_memory_read_fault(sig, info, arg)) { return; } - + // crash handler { local_persist volatile U32 first = 0; @@ -1833,10 +1833,10 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) sleep(UINT32_MAX); } } - + local_persist void *ips[4096]; int ips_count = backtrace(ips, ArrayCount(ips)); - + fprintf(stderr, "A fatal signal was received: %s (%d). The process is terminating.\n", strsignal(sig), sig); fprintf(stderr, "Create a new issue with this report at %s.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); fprintf(stderr, "Callstack:\n"); @@ -1844,7 +1844,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) { Dl_info info = {0}; dladdr(ips[i], &info); - + char cmd[2048]; snprintf(cmd, sizeof(cmd), "llvm-symbolizer --relative-address -f -e %s %lu", info.dli_fname, (unsigned long)ips[i] - (unsigned long)info.dli_fbase); FILE *f = popen(cmd, "r"); @@ -1858,12 +1858,12 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) String8 module = str8_skip_last_slash(str8_cstring(info.dli_fname)); String8 file = str8_skip_last_slash(str8_cstring_capped(file_name, file_name + sizeof(file_name))); if(file.size > 0) file.size -= 1; - + B32 no_func = str8_match(func, str8_lit("??"), StringMatchFlag_RightSideSloppy); B32 no_file = str8_match(file, str8_lit("??"), StringMatchFlag_RightSideSloppy); if(no_func) { func = str8_zero(); } if(no_file) { file = str8_zero(); } - + fprintf(stderr, "%ld. [0x%016lx] %.*s%s%.*s %.*s\n", i+1, (unsigned long)ips[i], (int)module.size, module.str, (!no_func || !no_file) ? ", " : "", (int)func.size, func.str, (int)file.size, file.str); } pclose(f); @@ -1874,7 +1874,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) } } fprintf(stderr, "\nVersion: %s%s\n\n", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); - + _exit(1); } } @@ -1902,7 +1902,7 @@ main(int argc, char **argv) U64 pages = (U64)sysconf(_SC_PHYS_PAGES); U64 page_size = (U64)sysconf(_SC_PAGESIZE); U64 commit_size = (U64)sysconf(_SC_AVPHYS_PAGES); - + SystemInfo *info = &lnx_state.system_info; info->logical_processor_count = (U32)get_nprocs(); info->page_size = (U64)getpagesize(); diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index 87998e59..b5cce089 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -35,6 +35,13 @@ #include #include +// NOTE(rjf): this is required because we need to use architecture-specific code for "base" +// OS functionality - we may want to reevaluate if such things should be in base - but for +// now just pulling in the whole x64 layer, just for linux. +#if ARCH_X64 || ARCH_X86 +# include "x64/x64.h" +#endif + pid_t gettid(void); int pthread_setname_np(pthread_t thread, const char *name); int pthread_getname_np(pthread_t thread, char *name, size_t size); @@ -46,13 +53,13 @@ typedef struct timespec timespec; //~ rjf: Linux Call Interruption Retry Helper #define LNX_RETRY_ON_EINTR(expr) \ - (__extension__({ \ - __typeof__(expr) __ret; \ - do { \ - __ret = (expr); \ - } while ((__ret == -1) && errno == EINTR); \ - __ret; \ - })) +(__extension__({ \ +__typeof__(expr) __ret; \ +do { \ +__ret = (expr); \ +} while ((__ret == -1) && errno == EINTR); \ +__ret; \ +})) //////////////////////////////// diff --git a/src/x64/x64.h b/src/x64/x64.h index 7d9c1fd1..2ee37845 100644 --- a/src/x64/x64.h +++ b/src/x64/x64.h @@ -212,39 +212,40 @@ X64_DebugControlFlags; //////////////////////////////// //~ Exception Codes -typedef enum +typedef enum X64_ExceptionCode { - X64_Exception_DE = 0, // Divide error: division by zero or quotient overflow - X64_Exception_DB = 1, // Debug exception (single stepping, hardware breakpoint) - X64_Exception_NMI = 2, // Non-maskable interrupt - X64_Exception_BP = 3, // Breakpoint (int3) - X64_Exception_OF = 4, // Overflow - X64_Exception_BR = 5, // Bound range exceeded - X64_Exception_UD = 6, // Invalid opcode - X64_Exception_NM = 7, // Device not available - X64_Exception_DF = 8, // Double fault - X64_Exception_9 = 9, // Coprocessor segment overrun (obsolete) - X64_Exception_TS = 10, // Invalid task-state segment - X64_Exception_NP = 11, // Segment not present - X64_Exception_SS = 12, // Stack-segment fault - X64_Exception_GP = 13, // General-protection fault - X64_Exception_PF = 14, // Page fault - X64_Exception_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS - X64_Exception_MF = 16, // x87 floating-point exception - X64_Exception_AC = 17, // Alignment check - X64_Exception_MC = 18, // Machine check - X64_Exception_XF = 19, // SIMD floating-point exception - X64_Exception_VE = 20, // Virtualization exception - X64_Exception_CP = 21, // Control-protection exception - X64_Exception_Amd_HV = 28, // Hypervisor injection exception - X64_Exception_Amd_VC = 29, // VMM communication exception - X64_Exception_Amd_SX = 30, // Security exception -} X64_ExceptionCode; + X64_ExceptionCode_DE = 0, // Divide error: division by zero or quotient overflow + X64_ExceptionCode_DB = 1, // Debug exception (single stepping, hardware breakpoint) + X64_ExceptionCode_NMI = 2, // Non-maskable interrupt + X64_ExceptionCode_BP = 3, // Breakpoint (int3) + X64_ExceptionCode_OF = 4, // Overflow + X64_ExceptionCode_BR = 5, // Bound range exceeded + X64_ExceptionCode_UD = 6, // Invalid opcode + X64_ExceptionCode_NM = 7, // Device not available + X64_ExceptionCode_DF = 8, // Double fault + X64_ExceptionCode_9 = 9, // Coprocessor segment overrun (obsolete) + X64_ExceptionCode_TS = 10, // Invalid task-state segment + X64_ExceptionCode_NP = 11, // Segment not present + X64_ExceptionCode_SS = 12, // Stack-segment fault + X64_ExceptionCode_GP = 13, // General-protection fault + X64_ExceptionCode_PF = 14, // Page fault + X64_ExceptionCode_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS + X64_ExceptionCode_MF = 16, // x87 floating-point exception + X64_ExceptionCode_AC = 17, // Alignment check + X64_ExceptionCode_MC = 18, // Machine check + X64_ExceptionCode_XF = 19, // SIMD floating-point exception + X64_ExceptionCode_VE = 20, // Virtualization exception + X64_ExceptionCode_CP = 21, // Control-protection exception + X64_ExceptionCode_Amd_HV = 28, // Hypervisor injection exception + X64_ExceptionCode_Amd_VC = 29, // VMM communication exception + X64_ExceptionCode_Amd_SX = 30, // Security exception +} +X64_ExceptionCode; //////////////////////////////// //~ Page Fault Flags -typedef enum +typedef enum X64_PageFaultError { X64_PageFaultError_Protection = (1u << 0), X64_PageFaultError_Write = (1u << 1), @@ -253,7 +254,8 @@ typedef enum X64_PageFaultError_InstructionFetch = (1u << 4), X64_PageFaultError_ProtectionKey = (1u << 5), X64_PageFaultError_ShadowStack = (1u << 6), -} X64_PageFaultError; +} +X64_PageFaultError; //////////////////////////////// //~ rjf: Generated @@ -280,4 +282,4 @@ internal X64_XSaveLayout x64_get_xsave_layout(U64 xcr0); internal void x64_set_debug_break(U64 *drs, U64 trap_idx, U64 addr, U64 size, X64_BreakpointType bp_type, X64_DebugBreakType break_type); -#endif // BASE_X64_H +#endif // X64_H From 7bfa4578d14763ac829a9aacf4cda23ca78e8d4a Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 21 Sep 2026 09:54:19 -0700 Subject: [PATCH 169/251] fix radbin/torture builds --- src/radbin/radbin_main.c | 2 ++ src/torture/torture_main.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/radbin/radbin_main.c b/src/radbin/radbin_main.c index 47e351dd..3c1fb526 100644 --- a/src/radbin/radbin_main.c +++ b/src/radbin/radbin_main.c @@ -12,6 +12,7 @@ //- rjf: [h] #include "base/base_inc.h" +#include "x64/x64.h" #include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" #include "rdi/rdi_local.h" @@ -46,6 +47,7 @@ //- rjf: [c] #include "base/base_inc.c" +#include "x64/x64.c" #include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" #include "rdi/rdi_local.c" diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index f9607b21..1b3a6915 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -20,7 +20,7 @@ //////////////////////////////// #include "base/base_inc.h" -#include "x64/x64_inc.h" +#include "x64/x64.h" #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" @@ -104,7 +104,7 @@ #include "torture.h" #include "base/base_inc.c" -#include "x64/x64_inc.c" +#include "x64/x64.c" #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" From 98c1191506b939e3b755b11572e012440ac48878 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 19 Sep 2026 19:54:45 -0700 Subject: [PATCH 170/251] radlink: use bit arrays for marking discarded CV types --- src/codeview/codeview_parse.c | 1 + src/linker/lnk_debug_info.c | 256 ++++++++++++++++------------------ src/linker/lnk_debug_info.h | 1 + src/linker/lnk_obj.c | 2 + 4 files changed, 126 insertions(+), 134 deletions(-) diff --git a/src/codeview/codeview_parse.c b/src/codeview/codeview_parse.c index 3997d4a4..39fd4e1c 100644 --- a/src/codeview/codeview_parse.c +++ b/src/codeview/codeview_parse.c @@ -950,6 +950,7 @@ cv_get_leaf_type_index_offsets(Arena *arena, CV_LeafKind leaf_kind, String8 data case CV_LeafKind_NESTTYPEEX: { cv_symbol_type_index_info_push(arena, &list, CV_TypeIndexSource_TPI, OffsetOf(CV_LeafNestTypeEx, itype)); } break; + case CV_LeafKind_ENDPRECOMP: {} break; default: { NotImplemented; } break; diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 7a53e8c1..154d902d 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -664,12 +664,11 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, .count = obj_count, .obj_arr = obj_arr, .rrt_input = rrt_input, - .ts_obj_range = r1u64(0,0) + .ts_obj_range = r1u64(0,0), }; HashMap rrt_hm = {0}; - ProfScope("Make obj path -> RRT hash map") - { + ProfScope("Make obj path -> RRT hash map") { for EachIndex(rrt_idx, rrt_input.count) { for EachIndex(obj_idx, rrt_input.v[rrt_idx].obj_paths.count) { hash_map_push_path_u64(scratch.arena, &rrt_hm, rrt_input.v[rrt_idx].obj_paths.v[obj_idx], Compose64Bit(rrt_idx, obj_idx)); @@ -723,28 +722,19 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } } - ProfBegin("Collect CodeView"); + // collect debug info sections input.debug_s_list_arr = lnk_collect_obj_sections(tp, tp_arena, obj_count, obj_arr, str8_lit(".debug$S"), 0); - ProfEnd(); // profiler info if (lnk_get_log_status(LNK_Log_Debug) || PROFILE_TELEMETRY) { U64 total_debug_s_size = 0, total_debug_t_size = 0, total_debug_p_size = 0, total_debug_h_size = 0; for EachIndex(obj_idx, obj_count) { LNK_Obj *obj = obj_arr[obj_idx]; - for EachNode(n, String8Node, input.debug_s_list_arr[obj_idx].first) { total_debug_s_size += n->string.size; } - - if (obj->coff.debug_t_section_number > 0) { - total_debug_t_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_t_section_number)->fsize; - } - if (obj->coff.debug_p_section_number > 0) { - total_debug_p_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_p_section_number)->fsize; - } + if (obj->coff.debug_t_section_number > 0) { total_debug_t_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_t_section_number)->fsize; } + if (obj->coff.debug_p_section_number > 0) { total_debug_p_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_p_section_number)->fsize; } if (config->ghash) { - if (obj->coff.debug_h_section_number > 0) { - total_debug_h_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_h_section_number)->fsize; - } + if (obj->coff.debug_h_section_number > 0) { total_debug_h_size += lnk_coff_section_header_from_section_number(obj, obj->coff.debug_h_section_number)->fsize; } } } @@ -773,8 +763,7 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } CV_DebugT *debug_p_arr; - ProfScope("Parse CodeView") - { + ProfScope("Parse CodeView") { // parse .debug$S input.debug_s_arr = push_array(tp_arena->v[0], CV_DebugS, input.obj_count); tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_s_task, &input, "Parse .debug$S"); @@ -834,8 +823,7 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } } - ProfScope("Set up PDB and RRT") - { + ProfScope("Set up PDB and RRT") { input.obj_to_ts = push_array(tp_arena->v[0], U64, input.count); MemorySet(input.obj_to_ts, 0xff, input.count * sizeof(input.obj_to_ts[0])); @@ -851,8 +839,7 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, for EachIndex(i, input.ext_obj_indices.count) { // first leaf is always type server - U64 obj_idx = input.ext_obj_indices.v[i]; - + U64 obj_idx = input.ext_obj_indices.v[i]; U64 *packed_rrt_info = hash_map_search_path_u64(&rrt_hm, obj_arr[obj_idx]->path); LNK_TypeServerKind ts_kind = LNK_TypeServerKind_Null; @@ -982,9 +969,100 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, lnk_error(LNK_Error_UnableToOpenTypeServer, "%S", str8_list_join(scratch.arena, &error_msg_list, 0)); } } + + // set default min type index + for EachIndex(ti_source, CV_TypeIndexSource_COUNT) { input.min_type_indices[ti_source] = CV_MinComplexTypeIndex; } + + // PCH and /Z7 objs have default min type index set to CV_MinComplexTypeIndex + // but type servers can bump up the lower bound. In practice nobody does this. + // But to cover all our bases loop through type servers and compute max + // lower bound. + for EachInRange(ts_idx, input.ts_obj_range) { + CV_DebugT *debug_t = &input.debug_t_arr[ts_idx]; + for EachIndex(ti_source, CV_TypeIndexSource_COUNT) { + input.min_type_indices[ti_source] = Max(input.min_type_indices[ti_source], debug_t->ti_ranges[ti_source].min); + } + } + + ProfScope("Make Symbol Inputs") { + // count symbol blocks + for EachIndex(obj_idx, input.count) { + String8List s = cv_sub_section_from_debug_s(input.debug_s_arr[obj_idx], CV_C13SubSectionKind_Symbols); + input.symbol_input_count += s.node_count; + } + + // alloc block pointers + input.symbol_inputs = push_array_no_zero(tp_arena->v[0], LNK_SymbolInput, input.symbol_input_count); + + U64 symbol_input_count = 0; + for EachIndex(obj_idx, input.count) { + String8List s = cv_sub_section_from_debug_s(input.debug_s_arr[obj_idx], CV_C13SubSectionKind_Symbols); + for EachNode(n, String8Node, s.first) { + Assert(symbol_input_count < input.symbol_input_count); + LNK_SymbolInput *in = &input.symbol_inputs[symbol_input_count++]; + in->obj_idx = obj_idx; + in->raw_symbols = n->string; + } + } + + ProfBegin("Make Ranges"); + + U64 total_input_size = 0; + for EachIndex(i, input.symbol_input_count) { total_input_size += input.symbol_inputs[i].raw_symbols.size; } + + U64 max_weight = CeilIntegerDiv(total_input_size, tp->worker_count); + U64 cursor = 0; + input.symbol_input_ranges = push_array(tp_arena->v[0], Rng1U64, tp->worker_count); + for EachIndex(i, tp->worker_count) { + if (cursor >= input.symbol_input_count) { break; } + U64 begin = cursor; + U64 weight = 0; + for (; cursor < input.symbol_input_count; cursor += 1) { + if (weight >= max_weight) { break; } + weight += input.symbol_inputs[cursor].raw_symbols.size; + } + input.symbol_input_ranges[i] = r1u64(begin, cursor); + } + + ProfEnd(); + + if (input.symbol_input_count) { + ProfBegin("Balance Symbol Inputs"); + + U64 task_cap = Min(input.symbol_input_count, tp->worker_count * 16); + U64 task_weight = Max(1, CeilIntegerDiv(total_input_size, task_cap)); + + input.symbol_patch_task = push_array_no_zero(tp_arena->v[0], LNK_SymbolInputTask, task_cap); + + cursor = 0; + while (cursor < input.symbol_input_count) { + U64 begin = cursor; + U64 weight = 0; + do { + weight += input.symbol_inputs[cursor++].raw_symbols.size; + } while (cursor < input.symbol_input_count && weight < task_weight); + + Assert(input.symbol_patch_task_count < task_cap); + LNK_SymbolInputTask *task = &input.symbol_patch_task[input.symbol_patch_task_count++]; + task->input_range = r1u64(begin, cursor); + task->weight = weight; + } + + radsort(input.symbol_patch_task, input.symbol_patch_task_count, lnk_symbol_input_task_is_before); + + ProfEnd(); + } + } + + ProfScope("Set up Leaf Discard Bit Arrays") { + input.is_leaf_discarded = push_array(tp_arena->v[0], U32Array, input.count); + for EachIndex(obj_idx, input.count) { + CV_DebugT *debug_t = &input.debug_t_arr[obj_idx]; + input.is_leaf_discarded[obj_idx] = bit_array_init32(tp_arena->v[0], debug_t->count); + } + } - ProfBegin("Set up PCH"); - { + ProfScope("Set up PCH") { // register PCH file paths HashMap debug_p_hm_path = {0}; HashMap debug_p_hm_name = {0}; // (obj name, U64List of PCH obj indices) @@ -1069,7 +1147,7 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, debug_t->pch_obj_idx = debug_p_obj_idx; } - // remove LF_ENDPRECOMP from .debug$P + // discard LF_ENDPRECOMP from .debug$P for EachIndex(i, input.debug_p_indices.count) { U64 debug_p_idx = input.debug_p_indices.v[i]; CV_DebugT *debug_p = &input.debug_t_arr[debug_p_idx]; @@ -1077,100 +1155,12 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, U64 lf_idx = debug_p->count - (i + 1); CV_LeafHeader *lf = cv_debug_t_get_leaf_header(debug_p, lf_idx); if (lf->kind == CV_LeafKind_ENDPRECOMP) { - memory_write16(&lf->size, sizeof(lf->kind)); - memory_write16(&lf->kind, CV_LeafKind_NOTYPE); + bit_array_set_bit32(input.is_leaf_discarded[debug_p_idx], lf_idx, 1); break; } } } } - ProfEnd(); - - // set default min type index - for EachIndex(ti_source, CV_TypeIndexSource_COUNT) { input.min_type_indices[ti_source] = CV_MinComplexTypeIndex; } - - // PCH and /Z7 objs have default min type index set to CV_MinComplexTypeIndex - // but type servers can bump up the lower bound. In practice nobody does this. - // But to cover all our bases loop through type servers and compute max - // lower bound. - for EachInRange(ts_idx, input.ts_obj_range) { - CV_DebugT *debug_t = &input.debug_t_arr[ts_idx]; - for EachIndex(ti_source, CV_TypeIndexSource_COUNT) { - input.min_type_indices[ti_source] = Max(input.min_type_indices[ti_source], debug_t->ti_ranges[ti_source].min); - } - } - - ProfBegin("Make Symbol Inputs"); - { - // count symbol blocks - for EachIndex(obj_idx, input.count) { - String8List s = cv_sub_section_from_debug_s(input.debug_s_arr[obj_idx], CV_C13SubSectionKind_Symbols); - input.symbol_input_count += s.node_count; - } - - // alloc block pointers - input.symbol_inputs = push_array_no_zero(tp_arena->v[0], LNK_SymbolInput, input.symbol_input_count); - - U64 symbol_input_count = 0; - for EachIndex(obj_idx, input.count) { - String8List s = cv_sub_section_from_debug_s(input.debug_s_arr[obj_idx], CV_C13SubSectionKind_Symbols); - for EachNode(n, String8Node, s.first) { - Assert(symbol_input_count < input.symbol_input_count); - LNK_SymbolInput *in = &input.symbol_inputs[symbol_input_count++]; - in->obj_idx = obj_idx; - in->raw_symbols = n->string; - } - } - - ProfBegin("Make Ranges"); - - U64 total_input_size = 0; - for EachIndex(i, input.symbol_input_count) { total_input_size += input.symbol_inputs[i].raw_symbols.size; } - - U64 max_weight = CeilIntegerDiv(total_input_size, tp->worker_count); - U64 cursor = 0; - input.symbol_input_ranges = push_array(tp_arena->v[0], Rng1U64, tp->worker_count); - for EachIndex(i, tp->worker_count) { - if (cursor >= input.symbol_input_count) { break; } - U64 begin = cursor; - U64 weight = 0; - for (; cursor < input.symbol_input_count; cursor += 1) { - if (weight >= max_weight) { break; } - weight += input.symbol_inputs[cursor].raw_symbols.size; - } - input.symbol_input_ranges[i] = r1u64(begin, cursor); - } - - ProfEnd(); - - if (input.symbol_input_count) { - ProfBegin("Balance Symbol Inputs"); - - U64 task_cap = Min(input.symbol_input_count, tp->worker_count * 16); - U64 task_weight = Max(1, CeilIntegerDiv(total_input_size, task_cap)); - - input.symbol_patch_task = push_array_no_zero(tp_arena->v[0], LNK_SymbolInputTask, task_cap); - - cursor = 0; - while (cursor < input.symbol_input_count) { - U64 begin = cursor; - U64 weight = 0; - do { - weight += input.symbol_inputs[cursor++].raw_symbols.size; - } while (cursor < input.symbol_input_count && weight < task_weight); - - Assert(input.symbol_patch_task_count < task_cap); - LNK_SymbolInputTask *task = &input.symbol_patch_task[input.symbol_patch_task_count++]; - task->input_range = r1u64(begin, cursor); - task->weight = weight; - } - - radsort(input.symbol_patch_task, input.symbol_patch_task_count, lnk_symbol_input_task_is_before); - - ProfEnd(); - } - } - ProfEnd(); scratch_end(scratch); ProfEnd(); @@ -1306,10 +1296,8 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf if (sub_ti >= debug_t->ti_ranges[sub_ti_n->source].max) { // discard type - U32 leaf_idx = curr_ti - debug_t->ti_ranges[curr_ti_source].min; - U8 *leaf_header = debug_t->data.str + debug_t->offsets[leaf_idx]; - memory_write16(leaf_header + OffsetOf(CV_LeafHeader, kind), CV_LeafKind_NOTYPE); - memory_write16(leaf_header + OffsetOf(CV_LeafHeader, size), sizeof(CV_LeafKind)); + U32 leaf_idx = curr_ti - debug_t->ti_ranges[curr_ti_source].min; + bit_array_set_bit32(input->is_leaf_discarded[obj_idx], leaf_idx, 1); // reset hasher lnk_hasher_init(&hasher, input->config->debug_types_hash); @@ -1328,10 +1316,8 @@ lnk_hash_cv_leaf(LNK_CodeViewInput *input, LNK_LeafRef leaf_ref, CV_TypeIndexInf B32 is_type_graph_cyclic = discard_cycles && sub_ti > 0 && sub_ti > curr_ti; if (is_type_graph_cyclic) { // discard type - U32 leaf_idx = curr_ti - debug_t->ti_ranges[curr_ti_source].min; - U8 *leaf_header = debug_t->data.str + debug_t->offsets[leaf_idx]; - memory_write16(leaf_header + OffsetOf(CV_LeafHeader, kind), CV_LeafKind_NOTYPE); - memory_write16(leaf_header + OffsetOf(CV_LeafHeader, size), sizeof(CV_LeafKind)); + U32 leaf_idx = curr_ti - debug_t->ti_ranges[curr_ti_source].min; + bit_array_set_bit32(input->is_leaf_discarded[obj_idx], leaf_idx, 1); // reset hasher lnk_hasher_init(&hasher, input->config->debug_types_hash); @@ -1463,9 +1449,10 @@ internal THREAD_POOL_TASK_FUNC(lnk_hash_debug_t_task) { ProfBeginFunction(); - LNK_MergeTypes *task = raw_task; - U32 obj_idx = task->indices.v[task_id]; - CV_DebugT *debug_t = &task->input->debug_t_arr[obj_idx]; + LNK_MergeTypes *task = raw_task; + U32 obj_idx = task->indices.v[task_id]; + CV_DebugT *debug_t = &task->input->debug_t_arr[obj_idx]; + for EachIndex(leaf_idx, debug_t->count) { Temp temp = temp_begin(task->fixed_arenas[worker_id]); CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, leaf_idx); @@ -1533,24 +1520,25 @@ THREAD_POOL_TASK_FUNC(lnk_populate_leaf_ht) internal THREAD_POOL_TASK_FUNC(lnk_leaf_dedup_task) { - LNK_MergeTypes *task = raw_task; - U64 obj_idx = task->indices.v[task_id]; - CV_DebugT *debug_t = &task->input->debug_t_arr[obj_idx]; - CV_DebugH *debug_h = &task->input->debug_h_arr[obj_idx]; + LNK_MergeTypes *task = raw_task; + U64 obj_idx = task->indices.v[task_id]; + CV_DebugT *debug_t = &task->input->debug_t_arr[obj_idx]; + CV_DebugH *debug_h = &task->input->debug_h_arr[obj_idx]; + U32Array is_discarded = task->input->is_leaf_discarded[obj_idx]; ProfBeginDynamic("dedup in obj 0x%llx (%.*s) leaf count %llu", obj_idx, str8_varg(task->input->obj_arr[obj_idx]->path), debug_t->count); for EachIndex(leaf_idx, debug_t->count) { + LNK_LeafRef leaf_ref = lnk_leaf_ref_make(obj_idx, leaf_idx); + if (bit_array_is_set32(is_discarded, leaf_idx)) { continue; } - B32 is_inserted_or_updated = 1; - - LNK_LeafRef leaf_ref = lnk_leaf_ref_make(obj_idx, leaf_idx); CV_LeafHeader *header = cv_debug_t_get_leaf_header(debug_t, leaf_idx); // leaf index -> leaf header CV_LeafKind kind = memory_read16(MemberFromPtr(CV_LeafHeader, header, kind)); // leaf header -> leaf kind CV_TypeIndexSource leaf_source = cv_type_index_source_from_leaf_kind(kind); // leaf kind -> type stream LNK_LeafHashTable *leaf_ht = &task->leaf_ht_arr[leaf_source]; // type stream -> hash table U64 best_idx = debug_h->v[leaf_idx] % leaf_ht->cap; // leaf ref -> hash -> bucket index U64 idx = best_idx; + B32 is_ok = 1; do { // load leaf ref @@ -1576,9 +1564,9 @@ THREAD_POOL_TASK_FUNC(lnk_leaf_dedup_task) idx = ((idx + 1) == leaf_ht->cap ? 0 : (idx + 1)); } while (idx != best_idx); - is_inserted_or_updated = 0; + is_ok = 0; exit:; - Assert(is_inserted_or_updated); + Assert(is_ok); } ProfEnd(); diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index d74f23fd..58865f65 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -90,6 +90,7 @@ typedef struct CV_DebugT *debug_t_arr; CV_DebugH *debug_h_arr; U64 *obj_to_ts; + U32Array *is_leaf_discarded; String8List *debug_s_list_arr; diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 228291e7..15520252 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -875,12 +875,14 @@ THREAD_POOL_TASK_FUNC(lnk_collect_obj_chunks_task) internal String8List * lnk_collect_obj_sections(TP_Context *tp, TP_Arena *arena, U64 objs_count, LNK_Obj **objs, String8 name, B32 collect_discarded) { + ProfBeginFunction(); LNK_SectionCollector task = {0}; task.objs = objs; task.name = name; task.collect_discarded = collect_discarded; task.out_lists = push_array(arena->v[0], String8List, objs_count); tp_for_parallel(tp, arena, objs_count, lnk_collect_obj_chunks_task, &task); + ProfEnd(); return task.out_lists; } From 4add147a098ccdf326ba7e508dee26d5dffa7c26 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 19 Sep 2026 21:13:14 -0700 Subject: [PATCH 171/251] radlink: refactor MSF writer to allow allocation of contiguous stream backing * remove MSF reader API * remove PDB reader API --- src/linker/lnk_debug_info.c | 80 +- src/linker/pdb_ext/msf_builder.c | 2156 ++++++------------------------ src/linker/pdb_ext/msf_builder.h | 205 +-- src/linker/pdb_ext/pdb_builder.c | 425 +----- src/linker/pdb_ext/pdb_builder.h | 15 +- src/linker/pdb_ext/pdb_helpers.c | 20 - src/linker/pdb_ext/pdb_helpers.h | 2 - src/linker/tests/linker_tests.c | 150 ++- 8 files changed, 653 insertions(+), 2400 deletions(-) diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 154d902d..870ca736 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -2697,17 +2697,18 @@ THREAD_POOL_TASK_FUNC(lnk_write_pdb_modules) if (mod->sn == MSF_INVALID_STREAM_NUMBER) { continue; } CV_DebugS debug_s = task->cv->debug_s_arr[obj_idx]; - String8List mod_data = msf_data_from_sn(temp.arena, task->pdb->msf, mod->sn); + String8 mod_data = msf_stream_data(task->pdb->msf, mod->sn); - if (mod_data.node_count) { - String8Node buf = *mod_data.first; + if (mod_data.size) { + String8Node buf = { .string = mod_data }; U64 pos = 0; lnk_write_debug_s_to_pdb_module(mod, debug_s, &buf, &pos); - // sub range symbol data pages and patch symbol tree offsets + // Patch the contiguous symbol buffer before handing it to output. if (mod->sym_data_size) { Rng1U64 sym_data_range = r1u64(sizeof(CV_Signature), mod->sym_data_size); - String8List mod_symbols = str8_list_substr(temp.arena, mod_data, sym_data_range); + String8List mod_symbols = {0}; + str8_list_push(temp.arena, &mod_symbols, str8_substr(mod_data, sym_data_range)); Assert(mod_symbols.total_size == dim_1u64(sym_data_range)); cv_patch_symbol_tree_offsets(mod_symbols, sizeof(CV_Signature), PDB_SYMBOL_ALIGN); } @@ -2754,50 +2755,38 @@ THREAD_POOL_TASK_FUNC(lnk_write_pdb_modules) if (mod->sn == MSF_INVALID_STREAM_NUMBER) { continue; } - String8List mod_data = msf_data_from_sn(temp.arena, task->pdb->msf, mod->sn); + String8 mod_data = msf_stream_data(task->pdb->msf, mod->sn); Rng1U64 c13_data_range = r1u64(mod->sym_data_size + mod->c11_data_size, mod->sym_data_size + mod->c11_data_size + mod->c13_data_size); - String8List c13_data = str8_list_substr(temp.arena, mod_data, c13_data_range); + String8 c13_data = str8_substr(mod_data, c13_data_range); CV_DebugS debug_s = task->cv->debug_s_arr[obj_idx]; String8 string_table = cv_string_table_from_debug_s(debug_s); // checksum is always at the head of C13 data - String8List file_chksms_raw = {0}; + String8 file_chksms_raw = {0}; { - String8Node buf = *c13_data.first; - U64 buf_pos = 0; - CV_C13SubSectionHeader header = {0}; - str8_buffer_read(&buf, &buf_pos, sizeof(header), &header); + str8_deserial_read_struct(c13_data, 0, &header); if (header.kind == CV_C13SubSectionKind_FileChksms) { Rng1U64 file_chksms_range = r1u64(sizeof(CV_C13SubSectionHeader), sizeof(CV_C13SubSectionHeader) + header.size); - file_chksms_raw = str8_list_substr(temp.arena, c13_data, file_chksms_range); + file_chksms_raw = str8_substr(c13_data, file_chksms_range); } } // fixup file name offsets in checksum headers - if (file_chksms_raw.total_size) { - String8Node buf = *file_chksms_raw.first; - U64 buf_pos = 0; + if (file_chksms_raw.size) { U64 cursor = 0; for (;;) { CV_C13Checksum header = {0}; - if (str8_buffer_peek(&buf, &buf_pos, sizeof(header), &header) != sizeof(header)) { break; } + if (str8_deserial_read_struct(file_chksms_raw, cursor, &header) != sizeof(header)) { break; } String8 name = str8_cstring_capped(string_table.str + header.name_off, string_table.str + string_table.size); CV_StringBucket *bucket = cv_string_hash_table_lookup(task->string_ht, name); U64 name_off = task->string_table_base_offset + bucket->u.offset; - // update name offset - { - String8Node buf_copy = buf; - U64 buf_pos_copy = buf_pos; - str8_buffer_skip(&buf_copy, &buf_pos_copy, OffsetOf(CV_C13Checksum, name_off)); - str8_buffer_write_u32(&buf_copy, &buf_pos_copy, safe_cast_u32(name_off)); - } - - str8_buffer_skip(&buf, &buf_pos, AlignPow2(sizeof(header) + header.len, CV_FileCheckSumsAlign)); + memory_write32(file_chksms_raw.str + cursor + OffsetOf(CV_C13Checksum, name_off), safe_cast_u32(name_off)); + cursor += AlignPow2(sizeof(header) + header.len, CV_FileCheckSumsAlign); } } @@ -2889,29 +2878,6 @@ typedef struct U64 sealed_stream_cap; } LNK_PdbOutput; -typedef struct LNK_MsfPageCursor -{ - MSF_PageDataNode *node; - U64 node_idx; - U64 pages_per_node; -} LNK_MsfPageCursor; - -internal U8 * -lnk_msf_data_from_pn(LNK_MsfPageCursor *cursor, MSF_Context *msf, MSF_PageNumber pn) -{ - U64 node_idx = pn / cursor->pages_per_node; - if (node_idx < cursor->node_idx) { - cursor->node = msf->page_data_list.first; - cursor->node_idx = 0; - } - while (cursor->node_idx < node_idx) { - cursor->node = cursor->node->next; - cursor->node_idx += 1; - } - Assert(cursor->node != 0); - return cursor->node->data + (pn % cursor->pages_per_node) * msf->page_size; -} - internal void lnk_pdb_output_enqueue_stream(LNK_PdbOutput *output, MSF_Context *msf, MSF_StreamNumber sn) { @@ -2923,16 +2889,14 @@ lnk_pdb_output_enqueue_stream(LNK_PdbOutput *output, MSF_Context *msf, MSF_Strea Assert(stream != 0); if (stream->page_list.count == 0) { return; } - LNK_MsfPageCursor cursor = { - .node = msf->page_data_list.first, - .pages_per_node = msf_get_data_node_size(msf->page_size) / msf->page_size, - }; + U64 stream_offset = 0; MSF_PageNumber run_first_pn = 0; MSF_PageNumber run_last_pn = 0; U8 *run_data = 0; U64 run_size = 0; for EachNode(page, MSF_PageNode, stream->page_list.first) { - U8 *page_data = lnk_msf_data_from_pn(&cursor, msf, page->pn); + U8 *page_data = stream->data + stream_offset; + stream_offset += msf->page_size; if (run_data != 0 && page->pn == run_last_pn + 1 && page_data == run_data + run_size) { run_last_pn = page->pn; run_size += msf->page_size; @@ -2973,15 +2937,11 @@ lnk_pdb_output_enqueue_remaining(LNK_PdbOutput *output, MSF_Context *msf) } } - LNK_MsfPageCursor cursor = { - .node = msf->page_data_list.first, - .pages_per_node = msf_get_data_node_size(msf->page_size) / msf->page_size, - }; U64 run_first_pn = 0; U8 *run_data = 0; U64 run_size = 0; for EachIndex(pn, page_count) { - U8 *page_data = lnk_msf_data_from_pn(&cursor, msf, pn); + U8 *page_data = msf_data_from_pn(msf, pn).str; U64 page_size = Min(msf->page_size, save_size - pn * msf->page_size); if (!is_written[pn]) { if (run_data != 0 && page_data == run_data + run_size) { @@ -3183,7 +3143,7 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config pdb_build_dbi_info(tp, task.pdb, task.string_ht, 0, cv->is_stripped, &build_hooks); MSF_Error msf_err = msf_build(task.pdb->msf); - if (msf_err != MSF_Error_OK) { + if (msf_err != MSF_Error_Ok) { lnk_error(LNK_Error_UnableToSerializeMsf, "unable to serialize MSF: %s", msf_error_to_string(msf_err)); } diff --git a/src/linker/pdb_ext/msf_builder.c b/src/linker/pdb_ext/msf_builder.c index b996bdf2..37b3bbd7 100644 --- a/src/linker/pdb_ext/msf_builder.c +++ b/src/linker/pdb_ext/msf_builder.c @@ -1,140 +1,80 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -internal U64 -msf_get_data_node_size(MSF_UInt page_size) +internal MSF_UInt +msf_count_pages(MSF_UInt page_size, U64 data_size) { - U64 interval = msf_get_fpm_interval_correct(page_size); - U64 bytes_per_interval = interval * (U64)page_size; - return bytes_per_interval; + return safe_cast_u32(CeilIntegerDiv(data_size, page_size)); } -internal void -msf_page_data_list_push(Arena *arena, MSF_PageDataList *list, MSF_UInt page_size, MSF_UInt count) +internal MSF_PageBlock * +msf_block_from_index(MSF_Context *msf, U64 idx) { - U64 data_size = msf_get_data_node_size(page_size); - for (MSF_UInt i = 0; i < count; i += 1) { - // TODO: clearing memory to zero here is expensive, - // with 4KiB pages we have to zero-out 128 MiB - // memory block - // - // we can make API for stream allocation to let user - // choose between zeroed and dirty allocations - // - U8 *data = push_array_aligned(arena, U8, data_size, page_size); - - // init node - MSF_PageDataNode *node = push_array_no_zero(arena, MSF_PageDataNode, 1); - node->prev = 0; - node->next = 0; - node->data = data; - - // push node to list - DLLPushBack(list->first, list->last, node); - list->count += 1; - } + MSF_PageBlock *block = msf->first_block; + for (U64 i = 0; i < idx && block != 0; i += 1) { block = block->next; } + Assert(block != 0); + return block; } -internal MSF_PageDataList -msf_page_data_list_pop(MSF_PageDataList *list, MSF_UInt count) +internal U8 ** +msf_page_data_slot(MSF_Context *msf, MSF_PageNumber pn) { - MSF_PageDataList result = {0}; - - MSF_UInt to_remove = Min(count, list->count); - for (MSF_UInt i = 0; i < to_remove; i += 1) { - MSF_PageDataNode *node = list->last; - DLLRemove(list->first, list->last, node); - - node->prev = 0; - node->next = 0; - - DLLPushBack(result.first, result.last, node); - result.count += 1; - } - list->count -= to_remove; - - return result; -} - -internal void -msf_page_data_list_concat_in_place(MSF_PageDataList *list, MSF_PageDataList *to_concat) -{ - DLLConcatInPlace(list, to_concat); -} - -internal void -msf_set_page_data_list(Arena *arena, MSF_PageDataList *list, MSF_UInt page_size, String8 data) -{ - ProfBeginFunction(); - - U64 node_size = msf_get_data_node_size(page_size); - U64 node_count = CeilIntegerDiv(data.size, node_size); - - U64 node_idx; - for (node_idx = 0; node_idx < node_count - 1; node_idx += 1) { - MSF_PageDataNode *node = push_array(arena, MSF_PageDataNode, 1); - node->data = data.str + node_idx * node_size; - SLLQueuePush(list->first, list->last, node); - list->count += 1; - } - - ProfBegin("Last Page Handle"); - B32 is_last_node_size_aligned = (data.size & (node_size - 1)) == 0; - U8 *last_node_data = 0; - if (is_last_node_size_aligned) { - last_node_data = data.str + node_idx * node_size; - } else { - U64 last_node_size = data.size % node_size; - last_node_data = push_array_no_zero(arena, U8, node_size); - MemoryCopy(last_node_data, data.str + node_idx * node_size, last_node_size); - } - ProfEnd(); - - MSF_PageDataNode *last_node = push_array(arena, MSF_PageDataNode, 1); - last_node->data = last_node_data; - SLLQueuePush(list->first, list->last, last_node); - list->count += 1; - - ProfEnd(); + U64 interval = (U64)msf->page_size * MSF_BITS_PER_CHAR; + MSF_PageBlock *block = msf_block_from_index(msf, pn / interval); + return &block->data[pn % interval]; } internal String8 -msf_data_from_pn(MSF_PageDataList list, MSF_UInt page_size, MSF_PageNumber pn) +msf_data_from_pn(MSF_Context *msf, MSF_PageNumber pn) { - U64 node_size = msf_get_data_node_size(page_size); - U64 page_offset = (U64)pn * (U64)page_size; - U64 data_node_idx = page_offset / node_size; - MSF_PageDataNode *node = list.first; - for (U64 i = 0; i < data_node_idx; i += 1) { - node = node->next; + U8 *data = *msf_page_data_slot(msf, pn); + return str8(data != 0 ? data : msf->zero_page, msf->page_size); +} + +internal B32 +msf_grow_page_table(MSF_Context *msf) +{ + U64 interval = (U64)msf->page_size * MSF_BITS_PER_CHAR; + if (((U64)msf->block_count + 1) * interval > MSF_PN_MAX) { return 0; } + + MSF_PageBlock *block = push_array(msf->arena, MSF_PageBlock, 1); + block->free_bits = push_array_no_zero(msf->arena, U32, msf->page_size / sizeof(U32)); + block->fpm_data = push_array_no_zero(msf->arena, U8, msf->page_size); + block->data = push_array(msf->arena, U8 *, interval); + MemorySet(block->free_bits, 0xff, msf->page_size); + SLLQueuePush(msf->first_block, msf->last_block, block); + U64 block_idx = msf->block_count++; + + // MS readers reserve slots 1 and 2 every page_size pages, even though one + // FPM page describes page_size*8 pages; keep those slots out of streams + U32Array bits = { .v = block->free_bits, .count = msf->page_size / sizeof(U32) }; + for (U64 i = 0; i < interval; i += msf->page_size) { + bit_array_set_bit32(bits, i + MSF_FPM0, MSF_PAGE_STATE_ALLOC); + bit_array_set_bit32(bits, i + MSF_FPM1, MSF_PAGE_STATE_ALLOC); + block->data[i + MSF_FPM0] = msf->empty_fpm; + block->data[i + MSF_FPM1] = msf->empty_fpm; } - U64 node_offset = page_offset % node_size; - U8 *ptr = node->data + node_offset; - String8 data = str8(ptr, page_size); - return data; + + // the bitmap pages are spaced page_size pages apart. Both copies describe + // the same fresh file; this builder does not edit existing files + *msf_page_data_slot(msf, safe_cast_u32(block_idx * msf->page_size + MSF_FPM0)) = block->fpm_data; + *msf_page_data_slot(msf, safe_cast_u32(block_idx * msf->page_size + MSF_FPM1)) = block->fpm_data; + + return 1; } -//////////////////////////////// - -internal MSF_StreamNode * -msf_stream_list_push(Arena *arena, MSF_StreamList *list) +internal MSF_PageNode * +msf_page_list_pop_last(MSF_PageList *list) { - MSF_StreamNode *n = push_array(arena, MSF_StreamNode, 1); - DLLPushBack(list->first, list->last, n); - list->count += 1; - return n; + MSF_PageNode *node = list->last; + if (node) { + DLLRemove(list->first, list->last, node); + list->count -= 1; + node->next = node->prev = 0; + } + return node; } -internal void -msf_stream_list_remove(MSF_StreamList *list, MSF_StreamNode *node) -{ - Assert(list->count > 0); - DLLRemove(list->first, list->last, node); - list->count -= 1; -} - -//////////////////////////////// - internal void msf_page_list_push_node(MSF_PageList *list, MSF_PageNode *node) { @@ -142,670 +82,143 @@ msf_page_list_push_node(MSF_PageList *list, MSF_PageNode *node) list->count += 1; } -internal MSF_PageNode * -msf_page_list_push(Arena *arena, MSF_PageList *list) +internal MSF_PageList +msf_alloc_pages(MSF_Context *msf, MSF_UInt count) { - MSF_PageNode *node = push_array(arena, MSF_PageNode, 1); - msf_page_list_push_node(list, node); - return node; -} - -internal MSF_PageNode * -msf_page_list_pop_last(MSF_PageList *list) -{ - MSF_PageNode *node = NULL; - if (list->count) { - node = list->last; - DLLRemove(list->first, list->last, node); - list->count -= 1; - } - return node; -} - -internal void -msf_page_list_concat_in_place(MSF_PageList *list, MSF_PageList *to_concat) -{ - DLLConcatInPlace(list, to_concat); -} - -internal MSF_PageNumber * -msf_page_list_to_arr(Arena *arena, MSF_PageList list) -{ - MSF_PageNumber *arr = push_array(arena, MSF_PageNumber, list.count); - MSF_UInt i = 0; - for (MSF_PageNode *node = list.first; node != 0; node = node->next, i += 1) { - arr[i] = node->pn; - } - return arr; -} - -internal MSF_PageNode * -msf_page_from_index(MSF_PageList page_list, MSF_UInt index) -{ - MSF_PageNode *page; - - B32 scan_from_last_node = index > page_list.count/2; - if (scan_from_last_node) { - page = page_list.last; - if (page_list.count > 0) { - for (MSF_UInt i = page_list.count - 1; i > index; i -= 1) { - page = page->prev; - if (!page) { - return 0; - } - } + MSF_PageList result = {0}; + U64 interval = (U64)msf->page_size * MSF_BITS_PER_CHAR; + while (result.count < count) { + U64 block_idx = msf->fpm_rover / interval; + if (block_idx == msf->block_count && !msf_grow_page_table(msf)) { + msf_free_pages(msf, &result); + break; } - } else { - page = page_list.first; - for (MSF_UInt i = 0; i < index; i += 1) { - page = page->next; - if (!page) { - return 0; - } - } - } - return page; -} -internal void -msf_page_list_push_extant_page_arr(Arena *arena, MSF_PageList *list, - MSF_PageDataList page_data_list, MSF_UInt page_size, - MSF_PageNumber *pn_arr, MSF_UInt pn_count) -{ - U64 node_size = msf_get_data_node_size(page_size); - U64 data_max = page_data_list.count * node_size; - for (MSF_PageNumber *pn_ptr = pn_arr, *pn_opl = pn_ptr + pn_count; pn_ptr < pn_opl; pn_ptr += 1) { - // is page number valid? - Assert(*pn_ptr * page_size + page_size <= data_max); + MSF_PageBlock *block = msf_block_from_index(msf, block_idx); + U32Array bits = { .v = block->free_bits, .count = msf->page_size / sizeof(U32) }; + U64 bit = bit_array_scan_left_to_right32(bits, msf->fpm_rover % interval, interval, MSF_PAGE_STATE_FREE); + if (bit >= interval) { + msf->fpm_rover = safe_cast_u32((block_idx + 1) * interval); + continue; + } + + MSF_PageNumber pn = safe_cast_u32(block_idx * interval + bit); + bit_array_set_bit32(bits, bit, MSF_PAGE_STATE_ALLOC); - // init page node - MSF_PageNode *page_node = msf_page_list_push(arena, list); - page_node->pn = *pn_ptr; + MSF_PageNode *node = msf_page_list_pop_last(&msf->page_pool); + if (node == 0) { node = push_array(msf->arena, MSF_PageNode, 1); } + + node->pn = pn; + msf_page_list_push_node(&result, node); + msf->fpm_rover = pn + 1; } + return result; } internal void -msf_page_list_push_extant_page(Arena *arena, MSF_PageList *list, MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumber pn) +msf_free_pages(MSF_Context *msf, MSF_PageList *pages) { - msf_page_list_push_extant_page_arr(arena, list, page_data_list, page_size, &pn, 1); -} - -#if LNK_PARANOID -internal void -msf_check_fpm_bits_for_page_list(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumber active_fpm, MSF_PageList page_list, MSF_UInt test_state) -{ - for (MSF_PageNode *page_node = page_list.first; page_node != 0; page_node = page_node->next) { - MSF_UInt state = msf_get_fpm_page_bit_state(page_data_list, page_size, active_fpm, page_node->pn); - if (state != test_state) { - //Assert(!"state bit doesn't match"); - } + U64 interval = (U64)msf->page_size * MSF_BITS_PER_CHAR; + for (MSF_PageNode *node; (node = msf_page_list_pop_last(pages)) != 0; ) { + MSF_PageBlock *block = msf_block_from_index(msf, node->pn / interval); + U64 bit = node->pn % interval; + U32Array bits = { .v = block->free_bits, .count = msf->page_size / sizeof(U32) }; + Assert(bit_array_get_bit32(bits, bit) == MSF_PAGE_STATE_ALLOC); + bit_array_set_bit32(bits, bit, MSF_PAGE_STATE_FREE); + block->data[bit] = 0; + msf->fpm_rover = Min(msf->fpm_rover, node->pn); + msf_page_list_push_node(&msf->page_pool, node); } } -#endif - -//////////////////////////////// - -internal MSF_UInt -msf_count_pages(MSF_UInt page_size, U64 data_size) -{ - MSF_UInt page_count = CeilIntegerDiv(data_size, page_size); - return page_count; -} - -internal MSF_PageNumber -msf_get_page_count_cap(MSF_PageDataList page_data_list, MSF_UInt page_size) -{ - U64 node_size = msf_get_data_node_size(page_size); - U64 file_size = page_data_list.count * node_size; - U64 count = CeilIntegerDiv(file_size, (U64)page_size); - return safe_cast_u32(count); -} //////////////////////////////// -internal U32Array -msf_fpm_data_from_pn(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumber fpm_pn) -{ - String8 raw_fpm = msf_data_from_pn(page_data_list, page_size, fpm_pn); - U32Array fpm_data; - fpm_data.count = raw_fpm.size / sizeof(fpm_data.v[0]); - fpm_data.v = (U32*)raw_fpm.str; - return fpm_data; -} - -internal MSF_UInt -msf_get_fpm_interval_correct(MSF_UInt page_size) -{ - return page_size * MSF_BITS_PER_CHAR; -} - -internal MSF_UInt -msf_get_fpm_interval_wrong(MSF_UInt page_size) -{ - return page_size; -} - -internal MSF_UInt -msf_get_fpm_idx_from_pn(MSF_UInt page_size, MSF_PageNumber pn) -{ - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(page_size); - MSF_UInt fpm_idx = pn / fpm_interval_correct; - return fpm_idx; -} - -internal MSF_UInt -msf_get_fpm_page_count(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_UInt fpm_interval) -{ - U64 node_size = msf_get_data_node_size(page_size); - U64 file_size = (U64)page_data_list.count * node_size; - U64 file_page_count = CeilIntegerDiv(file_size, page_size); - U64 fpm_page_count = CeilIntegerDiv(file_page_count, (U64)fpm_interval); - return safe_cast_u32(fpm_page_count); -} - -internal MSF_PageNumberArray -msf_get_fpm_page_arr(Arena *arena, MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_UInt active_fpm) -{ - Assert(active_fpm == MSF_FPM0 || active_fpm == MSF_FPM1); - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(page_size); - MSF_UInt fpm_interval_wrong = msf_get_fpm_interval_wrong(page_size); - MSF_UInt page_count = msf_get_page_count_cap(page_data_list, page_size); - MSF_PageNumberArray arr; - arr.count = CeilIntegerDiv(page_count, fpm_interval_correct); - arr.v = push_array(arena, MSF_PageNumber, arr.count); - for (MSF_UInt interval_idx = 0; interval_idx < arr.count; interval_idx += 1) { - arr.v[interval_idx] = active_fpm + interval_idx * fpm_interval_wrong; - } - return arr; -} - -internal MSF_PageNumber -msf_get_fpm_from_page_number(MSF_UInt page_size, MSF_PageNumber active_fpm, MSF_PageNumber pn) -{ - Assert(active_fpm == 1 || active_fpm == 2); - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(page_size); - MSF_UInt fpm_interval_wrong = msf_get_fpm_interval_wrong(page_size); - MSF_PageNumber fpm_pn = active_fpm; - fpm_pn += (pn / fpm_interval_correct) * fpm_interval_wrong; - return fpm_pn; -} - -internal MSF_UInt -msf_get_fpm_page_bit_state(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumber active_fpm, MSF_PageNumber pn) -{ - // fetch FPM - MSF_PageNumber fpm_pn = msf_get_fpm_from_page_number(page_size, active_fpm, pn); - U32Array fpm_data = msf_fpm_data_from_pn(page_data_list, page_size, fpm_pn); - - // get page bit - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(page_size); - MSF_UInt page_bit_idx = pn % fpm_interval_correct; - MSF_UInt state = bit_array_get_bit32(fpm_data, page_bit_idx); - - return state; -} - -internal void -msf_set_fpm_bit_(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumber active_fpm, MSF_PageNumber pn, B32 state) -{ - // fetch FPM - MSF_PageNumber fpm_pn = msf_get_fpm_from_page_number(page_size, active_fpm, pn); - U32Array fpm_data = msf_fpm_data_from_pn(page_data_list, page_size, fpm_pn); - - // set page bit - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(page_size); - MSF_UInt page_bit_idx = pn % fpm_interval_correct; - bit_array_set_bit32(fpm_data, page_bit_idx, state); -} - -internal void -msf_set_fpm_bit(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumber active_fpm, MSF_PageNumber pn, B32 state) -{ - msf_set_fpm_bit_(page_data_list, page_size, active_fpm, pn, state); -} - internal B32 -msf_grow(MSF_Context *msf, MSF_PageNumber new_page_count) +msf_stream_reserve_capacity(MSF_Context *msf, MSF_Stream *stream, U64 size) { - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(msf->page_size); - MSF_UInt fpm_interval_wrong = msf_get_fpm_interval_wrong(msf->page_size); - - // check alloc limit - U64 new_page_count64 = AlignPow2((U64)new_page_count, (U64)fpm_interval_correct); - B32 is_overflowed = new_page_count64 > MSF_PN_MAX; - if (is_overflowed) { - return 0; - } - - // check can only grow MSF here - new_page_count = safe_cast_u32(new_page_count64); - if (new_page_count < msf->page_count) { - return 0; + if (size >= MSF_DELETED_STREAM_STAMP) { return 0; } + + U64 capacity = AlignPow2(size, (U64)msf->page_size); + MSF_UInt page_count = msf_count_pages(msf->page_size, size); + B32 remap = 0; + + if (page_count > stream->page_list.count) { + MSF_UInt count = page_count - stream->page_list.count; + MSF_PageList pages = msf_alloc_pages(msf, count); + if (pages.count != count) { return 0; } + DLLConcatInPlace(&stream->page_list, &pages); + remap = 1; } - // compute number of FPM pages to allocate - // - // we allocate 8 times more FPMs because in MS impl they use wrong interval: - // https://github.com/microsoft/microsoft-pdb/blob/master/PDB/msf/msf.cpp#L509 - // - MSF_PageNumber prev_fpm_page_cap_wrong = msf_get_fpm_page_count(msf->page_data_list, msf->page_size, fpm_interval_wrong); - MSF_PageNumber curr_fpm_page_cap_wrong = CeilIntegerDiv(new_page_count, fpm_interval_wrong); - MSF_PageNumber alloc_count_wrong = curr_fpm_page_cap_wrong - prev_fpm_page_cap_wrong; - MSF_PageNumber next_pn_wrong = prev_fpm_page_cap_wrong * fpm_interval_wrong; - MSF_PageNumber end_pn_wrong = next_pn_wrong + alloc_count_wrong * fpm_interval_wrong; - - // compute correct number of FPM pages to grow - MSF_PageNumber prev_fpm_page_cap_correct = msf_get_fpm_page_count(msf->page_data_list, msf->page_size, fpm_interval_correct); - MSF_PageNumber curr_fpm_page_cap_correct = CeilIntegerDiv(new_page_count, fpm_interval_correct); - MSF_PageNumber alloc_count_correct = curr_fpm_page_cap_correct - prev_fpm_page_cap_correct; - MSF_PageNumber next_pn_correct = prev_fpm_page_cap_correct * fpm_interval_correct; - MSF_PageNumber end_pn_correct = next_pn_correct + alloc_count_correct * fpm_interval_correct; - - MSF_PageNumber to_alloc = alloc_count_correct; - - // are there unused data nodes? - if (msf->page_data_pool.count) { - MSF_PageNumber pool_alloc_count = Min(msf->page_data_pool.count, alloc_count_correct); - MSF_PageDataList page_data_list = msf_page_data_list_pop(&msf->page_data_pool, pool_alloc_count); - msf_page_data_list_concat_in_place(&msf->page_data_list, &page_data_list); - to_alloc -= pool_alloc_count; + if (capacity > stream->capacity) { + // sized callers allocate once. Incremental writers must reserve before + // retaining views; arena growth leaves previous views obsolete + U8 *data = push_array(msf->arena, U8, capacity); + if (stream->size != 0) { MemoryCopy(data, stream->data, stream->size); } + stream->data = data; + stream->capacity = capacity; + remap = 1; } - - // push enough data nodes to encompass allocated FPMs - msf_page_data_list_push(msf->arena, &msf->page_data_list, msf->page_size, to_alloc); - - // set FPM bits to free - for (MSF_PageNumber pn = next_pn_wrong; pn < end_pn_wrong; pn += fpm_interval_wrong) { - MSF_PageNumber fpm0_pn = pn + MSF_FPM0; - MSF_PageNumber fpm1_pn = pn + MSF_FPM1; - String8 fpm0_data = msf_data_from_pn(msf->page_data_list, msf->page_size, fpm0_pn); - String8 fpm1_data = msf_data_from_pn(msf->page_data_list, msf->page_size, fpm1_pn); - MemorySet(fpm0_data.str, 0xFF, msf->page_size); - MemorySet(fpm1_data.str, 0xFF, msf->page_size); + + if (remap) { + U64 offset = 0; + for EachNode(page, MSF_PageNode, stream->page_list.first) { + *msf_page_data_slot(msf, page->pn) = stream->data + offset; + offset += msf->page_size; + } } - - // set correct FPM bits - for (MSF_PageNumber pn = next_pn_correct; pn < end_pn_correct; pn += fpm_interval_correct) { - MSF_PageNumber fpm0_pn = pn + MSF_FPM0; - MSF_PageNumber fpm1_pn = pn + MSF_FPM1; - msf_set_fpm_bit(msf->page_data_list, msf->page_size, MSF_FPM0, fpm0_pn, MSF_PAGE_STATE_ALLOC); - msf_set_fpm_bit(msf->page_data_list, msf->page_size, MSF_FPM0, fpm1_pn, MSF_PAGE_STATE_ALLOC); - msf_set_fpm_bit(msf->page_data_list, msf->page_size, MSF_FPM1, fpm0_pn, MSF_PAGE_STATE_ALLOC); - msf_set_fpm_bit(msf->page_data_list, msf->page_size, MSF_FPM1, fpm1_pn, MSF_PAGE_STATE_ALLOC); - } - - // update context - msf->page_count += alloc_count_wrong * 2; return 1; } -#if 0 internal B32 -msf_shrink(MSF_Context *msf, MSF_PageNumber new_page_count) +msf_stream_resize_ex(MSF_Context *msf, MSF_Stream *stream, MSF_UInt size) { - MSF_UInt fpm_interval_wrong = msf_get_fpm_interval_wrong(msf->page_size); - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(msf->page_size); - - U64 new_page_count64 = AlignPow2((U64)new_page_count, (U64)fpm_interval_correct); - new_page_count = safe_cast_u32(new_page_count64); - Assert(new_page_count < msf->page_count); - - // compute number of FPM pages to deallocate - MSF_PageNumber prev_fpm_page_count_wrong = msf_get_fpm_page_count(msf->page_data_list, msf->page_size, fpm_interval_wrong); - MSF_PageNumber curr_fpm_page_count_wrong = CeilIntegerDiv(new_page_count, fpm_interval_wrong); - MSF_PageNumber dealloc_count_wrong = prev_fpm_page_count_wrong - curr_fpm_page_count_wrong; - - // compute next FPM page number - MSF_PageNumber next_pn = prev_fpm_page_count_wrong * fpm_interval_wrong; - MSF_PageNumber end_pn = next_pn - dealloc_count_wrong * fpm_interval_wrong; - - // pop data nodes - MSF_PageNumber prev_fpm_page_count_correct = msf_get_fpm_page_count(msf->page_data_list, msf->page_size, fpm_interval_correct); - MSF_PageNumber curr_fpm_page_count_correct = CeilIntegerDiv(new_page_count, fpm_interval_correct); - MSF_PageNumber dealloc_count_correct = prev_fpm_page_count_correct - curr_fpm_page_count_correct; - MSF_PageDataList free_page_data_list = msf_page_data_list_pop(&msf->page_data_list, dealloc_count_correct); - msf_page_data_list_concat_in_place(&msf->page_data_pool, &free_page_data_list); - - for (MSF_PageNumber pn = next_pn; pn > end_pn; pn -= fpm_interval_wrong) { - MSF_PageNumber fpm0_pn = pn + MSF_FPM0; - MSF_PageNumber fpm1_pn = pn + MSF_FPM1; - - // free FPM pages - msf_set_fpm_bit(msf->page_data_list, msf->page_size, 1, fpm0_pn, MSF_PAGE_STATE_FREE); - msf_set_fpm_bit(msf->page_data_list, msf->page_size, 1, fpm1_pn, MSF_PAGE_STATE_FREE); - msf_set_fpm_bit(msf->page_data_list, msf->page_size, 2, fpm0_pn, MSF_PAGE_STATE_FREE); - msf_set_fpm_bit(msf->page_data_list, msf->page_size, 2, fpm1_pn, MSF_PAGE_STATE_FREE); - } - - // update context - msf->page_count -= dealloc_count_wrong * 2; - - return true; -} -#endif + if (!msf_stream_reserve_capacity(msf, stream, size)) { return 0; } -internal MSF_PageNumber * -msf_alloc_pn_arr(Arena *arena, MSF_Context *msf, MSF_UInt alloc_count) -{ - // make sure FPM has enough space for new page numbers - // - // grow FPM at correct intervals here because we pre-alloc unused FPM pages ahead of time - MSF_UInt curr_page_cap = msf_get_page_count_cap(msf->page_data_list, msf->page_size); - for (;;) { - U64 new_page_count = (U64)msf->page_count + alloc_count; - if (new_page_count <= curr_page_cap) { - break; - } - if (new_page_count > MSF_PN_MAX || !msf_grow(msf, (MSF_PageNumber)new_page_count)) { - return 0; - } - curr_page_cap = msf_get_page_count_cap(msf->page_data_list, msf->page_size); + MSF_UInt page_count = msf_count_pages(msf->page_size, size); + MSF_PageList removed = {0}; + + while (stream->page_list.count > page_count) { + msf_page_list_push_node(&removed, msf_page_list_pop_last(&stream->page_list)); } - Temp scratch = scratch_begin(&arena, 1); + msf_free_pages(msf, &removed); - // reserve memory for page numbers - MSF_PageNumber *pn_arr = push_array(arena, MSF_PageNumber, alloc_count); - - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(msf->page_size); - MSF_UInt fpm_interval_wrong = msf_get_fpm_interval_wrong(msf->page_size); - - // get first FPM page - MSF_PageNumberArray fpm_pn_arr = msf_get_fpm_page_arr(scratch.arena, msf->page_data_list, msf->page_size, msf->active_fpm); - - for (MSF_UInt alloc_idx = 0; alloc_idx < alloc_count; ) { - // get FPM bits - MSF_UInt fpm_idx = msf->fpm_rover / fpm_interval_correct; - Assert(fpm_idx < fpm_pn_arr.count); - MSF_PageNumber fpm_pn = fpm_pn_arr.v[fpm_idx]; - U32Array fpm_data = msf_fpm_data_from_pn(msf->page_data_list, msf->page_size, fpm_pn); - - // scan FPM for free bit - MSF_UInt fpm_rover_page_relative = msf->fpm_rover % fpm_interval_correct; - U32 bit_idx = bit_array_scan_left_to_right32(fpm_data, fpm_rover_page_relative, fpm_interval_correct, MSF_PAGE_STATE_FREE); - - B32 is_full = (bit_idx >= fpm_interval_correct); - if (is_full) { - msf->fpm_rover = (fpm_idx + 1) * fpm_interval_correct; - continue; - } - - // compute page number - MSF_PageNumber pn = bit_idx + (fpm_idx * fpm_interval_correct); - - // make sure unused FPMs aren't allocated for regular streams, - // we used to mark with free bits unused FPMs but in VS2022 - // update they started to check for these bits and VS began - // to error out with "PDB format is not supported" message - B32 is_pn_valid = (pn % fpm_interval_wrong) != MSF_FPM0 && - (pn % fpm_interval_wrong) != MSF_FPM1; - if (is_pn_valid) { - // update FPM - bit_array_set_bit32(fpm_data, bit_idx, MSF_PAGE_STATE_ALLOC); - - // store page number - pn_arr[alloc_idx++] = pn; - } - - // advance FPM rover - msf->fpm_rover = pn + 1; + if (size < stream->size) { MemoryZero(stream->data + size, stream->size - size); } + + stream->size = size; + stream->pos = Min(stream->pos, size); + + return 1; +} + +internal MSF_StreamNode * +msf_find_stream_node(MSF_Context *msf, MSF_StreamNumber sn) +{ + for EachNode(node, MSF_StreamNode, msf->sectab.first) { + if (node->data.sn == sn) { return node; } } - - // update context - msf->page_count += alloc_count; - - scratch_end(scratch); - return pn_arr; + return 0; } -internal void -msf_free_pn_arr(MSF_Context *msf, MSF_PageNumber *pn_arr, MSF_UInt pn_count) +internal MSF_Stream * +msf_find_stream(MSF_Context *msf, MSF_StreamNumber sn) { - // set FPM bits - for (MSF_UInt i = 0; i < pn_count; i += 1) { - MSF_PageNumber pn = pn_arr[i]; - msf_set_fpm_bit(msf->page_data_list, msf->page_size, msf->active_fpm, pn, MSF_PAGE_STATE_FREE); - - // update FPM cursor - msf->fpm_rover = Min(msf->fpm_rover, pn); - } - - // update context - Assert(msf->page_count >= pn_count); - msf->page_count -= pn_count; -} - -internal MSF_PageList -msf_alloc_pages(MSF_Context *msf, MSF_UInt alloc_count) -{ - Temp scratch = scratch_begin(0, 0); - - MSF_PageList alloc_list = {0}; - MSF_PageNumber *pn_arr = msf_alloc_pn_arr(scratch.arena, msf, alloc_count); - if (pn_arr) { - for (MSF_UInt page_idx = 0; page_idx < alloc_count; page_idx += 1) { - // get page node - MSF_PageNode *page_node = 0; - if (msf->page_pool.count) { - page_node = msf_page_list_pop_last(&msf->page_pool); - msf_page_list_push_node(&alloc_list, page_node); - } else { - page_node = msf_page_list_push(msf->arena, &alloc_list); - } - - // copy page number - page_node->pn = pn_arr[page_idx]; - } - } - - scratch_end(scratch); - return alloc_list; -} - -internal void -msf_free_pages(MSF_Context *msf, MSF_PageList *page_list) -{ - Temp scratch = scratch_begin(0, 0); - - // free page numbers - MSF_PageNumber *pn_arr = msf_page_list_to_arr(scratch.arena, *page_list); - msf_free_pn_arr(msf, pn_arr, page_list->count); - - // push free nodes - msf_page_list_concat_in_place(&msf->page_pool, page_list); - - scratch_end(scratch); -} - -internal MSF_PageNumber -msf_find_max_pn_(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumberArray fpm_pn_arr) -{ - MSF_PageNumber max_pn = 0; - - MSF_UInt fpm_interval_correct = msf_get_fpm_interval_correct(page_size); - MSF_UInt fpm_interval_wrong = msf_get_fpm_interval_wrong(page_size); - MSF_UInt fpm_page_count = fpm_interval_correct / fpm_interval_wrong; - for (MSF_Int fpm_pn_idx = (MSF_Int)fpm_pn_arr.count - 1; fpm_pn_idx >= 0; fpm_pn_idx -= 1) { - MSF_PageNumber fpm_pn = fpm_pn_arr.v[fpm_pn_idx]; - U32Array fpm_data = msf_fpm_data_from_pn(page_data_list, page_size, fpm_pn); - - U64 bit_idx = max_U64; - U64 hi = fpm_data.count*32; - while (hi > 0) { - U64 msb_idx = bit_array_scan_right_to_left32(fpm_data, 0, hi, MSF_PAGE_STATE_ALLOC); - - // FPM is empty - if (msb_idx >= hi) { break; } - - // Skip reserved FPM pages, but not slot 0: after the file header's - // interval, that slot can contain stream data. - U64 k = msb_idx % fpm_interval_wrong; - if (k == MSF_FPM0 || k == MSF_FPM1) { - hi = msb_idx; - continue; - } - - bit_idx = msb_idx; - break; - } - - // stop if there is a page - if (bit_idx != max_U64) { - max_pn = Max(bit_idx + fpm_pn_idx * fpm_interval_correct, MSF_FPM1); - break; - } - } - - return max_pn; -} - -internal MSF_PageNumber -msf_find_max_pn(MSF_PageDataList page_data_list, MSF_UInt page_size) -{ - Temp scratch = scratch_begin(0, 0); - MSF_PageNumberArray fpm0_pn_arr = msf_get_fpm_page_arr(scratch.arena, page_data_list, page_size, MSF_FPM0); - MSF_PageNumberArray fpm1_pn_arr = msf_get_fpm_page_arr(scratch.arena, page_data_list, page_size, MSF_FPM1); - MSF_PageNumber fpm0_max = msf_find_max_pn_(page_data_list, page_size, fpm0_pn_arr); - MSF_PageNumber fpm1_max = msf_find_max_pn_(page_data_list, page_size, fpm1_pn_arr); - MSF_PageNumber max_pn = Max(fpm0_max, fpm1_max); - Assert(max_pn > 1); - scratch_end(scratch); - return max_pn; -} - -//////////////////////////////// - -internal B32 -msf_write__(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNode **page_ptr, MSF_UInt *pos_ptr, void *buffer, MSF_UInt buffer_size) -{ - MSF_PageNode *start_page = *page_ptr; - MSF_UInt start_pos = *pos_ptr; - - MSF_UInt buffer_pos = 0; - while (*page_ptr) { - MSF_UInt page_offset = *pos_ptr % page_size; - - // compute copy size - MSF_UInt buffer_bytes_left = buffer_size - buffer_pos; - MSF_UInt page_bytes_left = page_size - page_offset; - MSF_UInt copy_size = Min(buffer_bytes_left, page_bytes_left); - - // fetch page bytes - MSF_PageNumber page_number = (*page_ptr)->pn; - String8 page_bytes = msf_data_from_pn(page_data_list, page_size, page_number); - - // copy bytes to buffer - U8 *buffer_copy_ptr = (U8*)buffer + buffer_pos; - U8 *page_bytes_ptr = page_bytes.str + page_offset; - MemoryCopy(page_bytes_ptr, buffer_copy_ptr, copy_size); - - // advance - buffer_pos += copy_size; - *pos_ptr += copy_size; - - // have we used all bytes in this page? - if (page_bytes_left <= copy_size) { - *page_ptr = (*page_ptr)->next; - } - - // have we copied all bytes? - if (buffer_bytes_left <= copy_size) { - break; - } - } - - B32 is_write_ok = (buffer_pos == buffer_size); - - // not enough bytes to perform write - restore positions - if (!is_write_ok) { - *page_ptr = start_page; - *pos_ptr = start_pos; - } - - return is_write_ok; -} - -internal MSF_UInt -msf_read__(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNode **page_ptr, MSF_UInt *pos_ptr, void *buffer, MSF_UInt buffer_size) -{ - MSF_UInt buffer_pos = 0; - while (*page_ptr) { - MSF_UInt page_offset = *pos_ptr % page_size; - - // compute copy size - MSF_UInt buffer_bytes_left = buffer_size - buffer_pos; - MSF_UInt page_bytes_left = page_size - page_offset; - MSF_UInt copy_size = Min(buffer_bytes_left, page_bytes_left); - - // fetch page bytes - MSF_PageNumber page_number = (*page_ptr)->pn; - String8 page_bytes = msf_data_from_pn(page_data_list, page_size, page_number); - - // copy bytes to buffer - U8 *buffer_ptr = (U8*)buffer + buffer_pos; - U8 *page_bytes_ptr = page_bytes.str + page_offset; - MemoryCopy(buffer_ptr, page_bytes_ptr, copy_size); - - // advance - buffer_pos += copy_size; - *pos_ptr += copy_size; - - // no more bytes left in this page - if (page_bytes_left <= copy_size) { - *page_ptr = (*page_ptr)->next; - } - - // have we copied all bytes? - if (buffer_bytes_left <= copy_size) { - break; - } - } - - MSF_UInt bytes_read = buffer_pos; - //Assert(bytes_read == buffer_size); - - return bytes_read; -} - -internal B32 -msf_write(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageList page_list, MSF_UInt offset, void *buffer, MSF_UInt buffer_size) -{ - MSF_UInt page_idx = offset / page_size; - MSF_PageNode *page = msf_page_from_index(page_list, page_idx); - B32 is_write_ok = msf_write__(page_data_list, page_size, &page, &offset, buffer, buffer_size); - return is_write_ok; -} - -internal MSF_UInt -msf_read(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageList page_list, MSF_UInt offset, void *buffer, MSF_UInt buffer_size) -{ - MSF_UInt page_idx = offset / page_size; - MSF_PageNode *page = msf_page_from_index(page_list, page_idx); - MSF_UInt bytes_read = msf_read__(page_data_list, page_size, &page, &offset, buffer, buffer_size); - return bytes_read; -} - -//////////////////////////////// - -internal MSF_StreamNode * -msf_stream_alloc_(Arena *arena, MSF_StreamList *list) -{ - Assert(list->count < MSF_STREAM_NUMBER_MAX); - MSF_UInt sn = list->count; - MSF_StreamNode *stream_node = msf_stream_list_push(arena, list); - MSF_Stream *stream = &stream_node->data; - stream->sn = safe_cast_u16(sn); - return stream_node; + MSF_StreamNode *node = msf_find_stream_node(msf, sn); + return node != 0 && node->data.size != MSF_DELETED_STREAM_STAMP ? &node->data : 0; } internal MSF_StreamNumber msf_stream_alloc_ex(MSF_Context *msf, MSF_UInt size) { - MSF_StreamNode *node = msf_stream_alloc_(msf->arena, &msf->sectab); - MSF_Stream *stream = &node->data; - msf_stream_resize_ex(msf, stream, size); - return stream->sn; + Assert(msf->sectab.count < MSF_STREAM_NUMBER_MAX); + MSF_StreamNode *node = push_array(msf->arena, MSF_StreamNode, 1); + node->data.sn = safe_cast_u16(msf->sectab.count); + if (!msf_stream_resize_ex(msf, &node->data, size)) { return MSF_INVALID_STREAM_NUMBER; } + DLLPushBack(msf->sectab.first, msf->sectab.last, node); + msf->sectab.count += 1; + return node->data.sn; } internal MSF_StreamNumber @@ -815,132 +228,51 @@ msf_stream_alloc(MSF_Context *msf) } internal B32 -msf_stream_resize_ex(MSF_Context *msf, MSF_Stream *stream, MSF_UInt size) +msf_stream_free(MSF_Context *msf, MSF_StreamNumber sn) { - MSF_UInt new_page_count = msf_count_pages(msf->page_size, size); - MSF_UInt cur_page_count = stream->page_list.count; - - if (new_page_count > cur_page_count) { - MSF_UInt alloc_count = new_page_count - cur_page_count; - MSF_PageList page_list = msf_alloc_pages(msf, alloc_count); - msf_page_list_concat_in_place(&stream->page_list, &page_list); - } else { - MSF_PageList free_page_list = {0}; - for (MSF_UInt i = cur_page_count; i > new_page_count; i -= 1) { - MSF_PageNode *page_node = msf_page_list_pop_last(&stream->page_list); - msf_page_list_push_node(&free_page_list, page_node); - } - msf_free_pages(msf, &free_page_list); - } - - // update stream - stream->size = size; - stream->pos = Min(stream->pos, stream->size); - stream->pos_page = 0; - + MSF_Stream *stream = msf_find_stream(msf, sn); + if (stream == 0) { return 0; } + msf_free_pages(msf, &stream->page_list); + stream->size = MSF_DELETED_STREAM_STAMP; + stream->pos = 0; return 1; } internal B32 -msf_stream_resize(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt new_size) +msf_stream_resize(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt size) { MSF_Stream *stream = msf_find_stream(msf, sn); - B32 is_resized = 0; - if (stream) { - is_resized = msf_stream_resize_ex(msf, stream, new_size); - } - return is_resized; -} - -internal B32 -msf_stream_free(MSF_Context *msf, MSF_StreamNumber sn) -{ - B32 is_free_ok = 0; - MSF_StreamNode *stream_node = msf_find_stream_node(msf, sn); - if (stream_node) { - msf_stream_list_remove(&msf->sectab, stream_node); - msf_stream_resize_ex(msf, &stream_node->data, 0); - stream_node->data.size = MSF_DELETED_STREAM_STAMP; - is_free_ok = 1; - } - return is_free_ok; -} - -internal void -msf_stream_set_size(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt size) -{ - MSF_Stream *stream = msf_find_stream(msf, sn); - if (stream) { - stream->size = Min(size, stream->page_list.count * msf->page_size); - } else { - Assert(!"invalid stream number"); - } + return stream != 0 && msf_stream_resize_ex(msf, stream, size); } internal MSF_UInt msf_stream_get_size(MSF_Context *msf, MSF_StreamNumber sn) { - MSF_UInt size = MSF_UINT_MAX; MSF_Stream *stream = msf_find_stream(msf, sn); - if (stream) { - size = stream->size; - } - return size; -} - -internal MSF_UInt -msf_stream_get_cap__(MSF_Context *msf, MSF_Stream *stream) -{ - return stream->page_list.count * msf->page_size; -} - -internal MSF_UInt -msf_stream_get_cap(MSF_Context *msf, MSF_StreamNumber sn) -{ - MSF_Stream *stream = msf_find_stream(msf, sn); - MSF_UInt cap = 0; - if (stream) { - cap = msf_stream_get_cap__(msf, stream); - } - return cap; -} - -internal MSF_UInt -msf_stream_get_pos__(MSF_Context *msf, MSF_Stream *stream) -{ - return stream->pos; + return stream != 0 ? stream->size : MSF_UINT_MAX; } internal MSF_UInt msf_stream_get_pos(MSF_Context *msf, MSF_StreamNumber sn) { MSF_Stream *stream = msf_find_stream(msf, sn); - MSF_UInt pos = MSF_UINT_MAX; - if (stream) { - pos = msf_stream_get_pos__(msf, stream); - } - return pos; + return stream != 0 ? stream->pos : MSF_UINT_MAX; } internal B32 -msf_stream_seek__(MSF_Context *msf, MSF_Stream *stream, MSF_UInt new_pos) -{ (void)msf; - stream->pos = Min(new_pos, stream->size); - stream->pos_page = 0; - return 1; +msf_stream_reserve(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt size) +{ + MSF_Stream *stream = msf_find_stream(msf, sn); + return stream != 0 && msf_stream_reserve_capacity(msf, stream, (U64)stream->pos + size); } -internal B32 -msf_stream_seek(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt new_pos) +internal B32 +msf_stream_seek(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt pos) { - B32 is_seek_ok = 0; MSF_Stream *stream = msf_find_stream(msf, sn); - if (stream) { - is_seek_ok = msf_stream_seek__(msf, stream, new_pos); - } else { - Assert(!"failed to stream seek"); - } - return is_seek_ok; + if (stream == 0) { return 0; } + stream->pos = Min(pos, stream->size); + return 1; } internal B32 @@ -949,517 +281,142 @@ msf_stream_seek_start(MSF_Context *msf, MSF_StreamNumber sn) return msf_stream_seek(msf, sn, 0); } -internal B32 -msf_stream_seek_end(MSF_Context *msf, MSF_StreamNumber sn) +internal String8 +msf_stream_data(MSF_Context *msf, MSF_StreamNumber sn) { - MSF_UInt end = msf_stream_get_size(msf, sn); - return msf_stream_seek(msf, sn, end); + MSF_Stream *stream = msf_find_stream(msf, sn); + return stream != 0 ? str8(stream->data, stream->size) : str8_zero(); } - -internal B32 -msf_stream_write__(MSF_Context *msf, MSF_Stream *stream, void *buffer, MSF_UInt buffer_size) +internal B32 +msf_stream_write(MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt size) { - B32 is_write_ok = 0; + MSF_Stream *stream = msf_find_stream(msf, sn); + if (stream == 0) { return 0; } - // are we writing over limit? - Assert((U64)stream->pos + (U64)buffer_size <= (U64)MSF_UINT_MAX); - - // make sure we have enough space to write buffer - MSF_UInt stream_cap = msf_stream_get_cap__(msf, stream); - MSF_UInt stream_pos_opl = stream->pos + buffer_size; - B32 grow_stream = stream_pos_opl > stream_cap; - if (grow_stream) { - B32 is_resize_ok = msf_stream_resize_ex(msf, stream, stream_pos_opl); - if (!is_resize_ok) { - goto exit; - } + U64 end = (U64)stream->pos + size; + if (end >= MSF_DELETED_STREAM_STAMP) { return 0; } + + if (end > stream->capacity || msf_count_pages(msf->page_size, end) > stream->page_list.count) { + if (!msf_stream_reserve_capacity(msf, stream, end)) { return 0; } } - - if (buffer) { - // lookup page for current stream position - if (!stream->pos_page) { - MSF_UInt page_idx = stream->pos / msf->page_size; - stream->pos_page = msf_page_from_index(stream->page_list, page_idx); - } - - // make write - is_write_ok = msf_write__(msf->page_data_list, msf->page_size, &stream->pos_page, &stream->pos, buffer, buffer_size); - } else { - stream->pos += buffer_size; - stream->pos_page = 0; - is_write_ok = 1; + + if (size != 0) { + if (buffer != 0) { MemoryCopy(stream->data + stream->pos, buffer, size); } + else { MemoryZero(stream->data + stream->pos, size); } } - - // update stream size + + stream->pos = safe_cast_u32(end); stream->size = Max(stream->size, stream->pos); -exit:; - Assert(is_write_ok); - return is_write_ok; -} - -internal MSF_UInt -msf_stream_reserve__(MSF_Context *msf, MSF_Stream *stream, MSF_UInt res) -{ - ProfBeginV("MSF Reserve %m", res); - - B32 is_ok = 1; - - MSF_UInt cap = msf_stream_get_cap__(msf, stream); - MSF_UInt pos = msf_stream_get_pos__(msf, stream); - MSF_UInt cur = cap - pos; - - if (cur < res) { - is_ok = msf_stream_write__(msf, stream, 0, res); - AssertAlways(is_ok); - - is_ok = msf_stream_seek__(msf, stream, pos); - AssertAlways(is_ok); - } - - ProfEnd(); - return is_ok; -} - -internal B32 -msf_stream_reserve(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt res) -{ - MSF_Stream *stream = msf_find_stream(msf, sn); - B32 is_res_ok = 0; - if (stream) { - is_res_ok = msf_stream_reserve__(msf, stream, res); - } - return is_res_ok; -} - -internal B32 -msf_stream_write(MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt buffer_size) -{ - B32 is_write_ok = 0; - MSF_Stream *stream = msf_find_stream(msf, sn); - if (stream) { - is_write_ok = msf_stream_write__(msf, stream, buffer, buffer_size); - } - return is_write_ok; + return 1; } internal B32 msf_stream_write_string(MSF_Context *msf, MSF_StreamNumber sn, String8 string) { - return msf_stream_write(msf, sn, string.str, string.size); + return msf_stream_write(msf, sn, string.str, safe_cast_u32(string.size)); } internal B32 msf_stream_write_list(MSF_Context *msf, MSF_StreamNumber sn, String8List list) { - B32 is_write_ok = 0; - MSF_Stream *stream = msf_find_stream(msf, sn); - if (stream) { - for (String8Node *node = list.first; node != 0; node = node->next) { - is_write_ok = msf_stream_write__(msf, stream, node->string.str, node->string.size); - if (!is_write_ok) { - break; - } - } - } - return is_write_ok; -} + if (!msf_stream_reserve(msf, sn, safe_cast_u32(list.total_size))) { return 0; } -internal B32 -msf_stream_write_uint(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt value) -{ - return msf_stream_write_struct(msf, sn, &value); + for EachNode(node, String8Node, list.first) { + if (!msf_stream_write_string(msf, sn, node->string)) { return 0; } + } + + return 1; } internal B32 msf_stream_write_cstr(MSF_Context *msf, MSF_StreamNumber sn, String8 string) { - B32 is_string_written = msf_stream_write_string(msf, sn, string); - B32 is_null_written = msf_stream_write(msf, sn, 0, 1); - return is_string_written && is_null_written; + return msf_stream_write_string(msf, sn, string) && msf_stream_write_u8(msf, sn, 0); } -internal B32 -msf_stream_write_u8(MSF_Context *msf, MSF_StreamNumber sn, U8 value) -{ - return msf_stream_write(msf, sn, &value, sizeof(value)); -} +#define MSF_WRITE_SCALAR(T, suffix) \ +internal B32 msf_stream_write_##suffix(MSF_Context *msf, MSF_StreamNumber sn, T value) \ +{ return msf_stream_write(msf, sn, &value, sizeof(value)); } +MSF_WRITE_SCALAR(U8, u8) +MSF_WRITE_SCALAR(U16, u16) +MSF_WRITE_SCALAR(U32, u32) +MSF_WRITE_SCALAR(U64, u64) +MSF_WRITE_SCALAR(S8, s8) +MSF_WRITE_SCALAR(S16, s16) +MSF_WRITE_SCALAR(S32, s32) +MSF_WRITE_SCALAR(S64, s64) +#undef MSF_WRITE_SCALAR -internal B32 -msf_stream_write_u16(MSF_Context *msf, MSF_StreamNumber sn, U16 value) +internal void +msf_stream_align(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt align) { - return msf_stream_write(msf, sn, &value, sizeof(value)); -} - -internal B32 -msf_stream_write_u32(MSF_Context *msf, MSF_StreamNumber sn, U32 value) -{ - return msf_stream_write(msf, sn, &value, sizeof(value)); -} - -internal B32 -msf_stream_write_u64(MSF_Context *msf, MSF_StreamNumber sn, U64 value) -{ - return msf_stream_write(msf, sn, &value, sizeof(value)); -} - -internal B32 -msf_stream_write_s8(MSF_Context *msf, MSF_StreamNumber sn, S8 value) -{ - return msf_stream_write(msf, sn, &value, sizeof(value)); -} - -internal B32 -msf_stream_write_s16(MSF_Context *msf, MSF_StreamNumber sn, S16 value) -{ - return msf_stream_write(msf, sn, &value, sizeof(value)); -} - -internal B32 -msf_stream_write_s32(MSF_Context *msf, MSF_StreamNumber sn, S32 value) -{ - return msf_stream_write(msf, sn, &value, sizeof(value)); -} - -internal B32 -msf_stream_write_s64(MSF_Context *msf, MSF_StreamNumber sn, S64 value) -{ - return msf_stream_write(msf, sn, &value, sizeof(value)); + MSF_UInt pos = msf_stream_get_pos(msf, sn); + msf_stream_write(msf, sn, 0, AlignPadPow2(pos, align)); } internal THREAD_POOL_TASK_FUNC(msf_write_task) { - ProfBeginFunction(); MSF_WriteTask *task = raw_task; - - Rng1U64 range = task->range_arr[task_id]; - String8 data = str8_substr(task->data, range); - MSF_UInt data_pos = range.min + task->stream_pos; - - MSF_UInt page_idx = data_pos / task->page_size; - MSF_PageNode *page = msf_page_from_index(task->page_list, page_idx); - - if (!msf_write__(task->page_data_list, task->page_size, &page, &data_pos, data.str, data.size)) { - InvalidPath; - } - ProfEnd(); + Rng1U64 range = task->ranges[task_id]; + if (dim_1u64(range) != 0) { MemoryCopy(task->dst + range.min, task->src + range.min, dim_1u64(range)); } } internal B32 -msf_stream_write_parallel(TP_Context *tp, MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt buffer_size) +msf_stream_write_parallel(TP_Context *tp, MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt size) { - ProfBeginV("MSF Write Parallel %m", buffer_size); + if (buffer == 0 || size < KB(64)) { return msf_stream_write(msf, sn, buffer, size); } + + if (!msf_stream_reserve(msf, sn, size)) { return 0; } + + Temp scratch = scratch_begin(0, 0); MSF_Stream *stream = msf_find_stream(msf, sn); + MSF_WriteTask task = { + .dst = stream->data + stream->pos, + .src = buffer, + .ranges = tp_divide_work(scratch.arena, size, tp->worker_count), + }; - B32 is_write_ok = msf_stream_reserve__(msf, stream, buffer_size); + tp_for_parallel(tp, 0, tp->worker_count, msf_write_task, &task); - if (is_write_ok) { - U64 expected_pos = stream->pos + buffer_size; + stream->pos += size; + stream->size = Max(stream->size, stream->pos); - MSF_UInt pre_size = Min(AlignPadPow2(stream->pos, msf->page_size), buffer_size); - MSF_UInt mid_size = AlignDownPow2(buffer_size - pre_size, msf->page_size); - MSF_UInt end_size = buffer_size - (pre_size + mid_size); - - U8 *pre_ptr = (U8*)buffer; - U8 *mid_ptr = (U8*)buffer + pre_size; - U8 *end_ptr = (U8*)buffer + pre_size + mid_size; - - ProfBeginV("Write Buffer Pre %M", pre_size); - B32 is_pre_written = msf_stream_write__(msf, stream, pre_ptr, pre_size); - AssertAlways(is_pre_written); - ProfEnd(); - - // write buffer mid - if (mid_size > 0) { - Temp scratch = scratch_begin(0,0); - - Assert(stream->pos % msf->page_size == 0); - Assert(mid_size % msf->page_size == 0); - - MSF_WriteTask task; - task.page_size = msf->page_size; - task.page_data_list = msf->page_data_list; - task.page_list = stream->page_list; - task.stream_pos = stream->pos; - task.data = str8(mid_ptr, mid_size); - task.range_arr = tp_divide_work(scratch.arena, mid_size, tp->worker_count); - tp_for_parallel(tp, 0, tp->worker_count, msf_write_task, &task); - - // we rely on low-level msf_write__ to copy bytes which doesn't advance stream pos - U64 after_mid = stream->pos + mid_size; - B32 is_seek_ok = msf_stream_seek__(msf, stream, after_mid); - AssertAlways(is_seek_ok); - - scratch_end(scratch); - } - - ProfBeginV("Write Buffer End %M", end_size); - B32 is_end_ok = msf_stream_write__(msf, stream, end_ptr, end_size); - AssertAlways(is_end_ok); - ProfEnd(); - - // did we write bytes correctly? - AssertAlways(stream->pos == expected_pos); - } - - ProfEnd(); - return is_write_ok; -} - -internal B32 -msf_stream_write_string_parallel(TP_Context *tp, MSF_Context *msf, MSF_StreamNumber sn, String8 string) -{ - return msf_stream_write_parallel(tp, msf, sn, string.str, string.size); -} - -internal String8List -msf_data_from_sn(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn) -{ - String8List result = {0}; - - MSF_Stream *stream = msf_find_stream(msf, sn); - String8 acc = {0}; - - MSF_PageNode *n; - for (n = stream->page_list.first; n != stream->page_list.last; n = n->next) { - String8 page = msf_data_from_pn(msf->page_data_list, msf->page_size, n->pn); - - if (acc.str + acc.size != page.str) { - if (acc.size) { - str8_list_push(arena, &result, acc); - } - acc = page; - } else { - acc.size += page.size; - } - } - - if (acc.size) { - str8_list_push(arena, &result, acc); - MemoryZeroStruct(&acc); - } - - if (n) { - String8 page = msf_data_from_pn(msf->page_data_list, msf->page_size, n->pn); - if (acc.str + acc.size != page.str) { - acc = page; - } else { - acc.size += page.size; - } - - acc.size = Min(stream->size - result.total_size, acc.size); - - if (acc.size) { - str8_list_push(arena, &result, acc); - } - } - - Assert(result.total_size == stream->size); - return result; + scratch_end(scratch); + return 1; } //////////////////////////////// -internal MSF_UInt -msf_stream_read__(MSF_Context *msf, MSF_Stream *stream, void *buffer, MSF_UInt buffer_size) -{ - // are we reading over limit? - Assert((U64)stream->pos + (U64)buffer_size <= (U64)MSF_UINT_MAX); - - // lookup page for current stream position - if (!stream->pos_page) { - MSF_UInt pos_page_idx = stream->pos / msf->page_size; - stream->pos_page = msf_page_from_index(stream->page_list, pos_page_idx); - } - - MSF_UInt bytes_read = msf_read__(msf->page_data_list, msf->page_size, &stream->pos_page, &stream->pos, buffer, buffer_size); - return bytes_read; -} - -internal MSF_UInt -msf_stream_read(MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt buffer_size) -{ - MSF_Stream *stream = msf_find_stream(msf, sn); - if (stream) { - return msf_stream_read__(msf, stream, buffer, buffer_size); - } - return 0; -} - -internal S8 -msf_stream_read_s8(MSF_Context *msf, MSF_StreamNumber sn) -{ - S8 result = 0; - msf_stream_read_struct(msf, sn, &result); - return result; -} - -internal S16 -msf_stream_read_s16(MSF_Context *msf, MSF_StreamNumber sn) -{ - S16 result = 0; - msf_stream_read_struct(msf, sn, &result); - return result; -} - -internal S32 -msf_stream_read_s32(MSF_Context *msf, MSF_StreamNumber sn) -{ - S32 result = 0; - msf_stream_read_struct(msf, sn, &result); - return result; -} - -internal S64 -msf_stream_read_s64(MSF_Context *msf, MSF_StreamNumber sn) -{ - S64 result = 0; - msf_stream_read_struct(msf, sn, &result); - return result; -} - -internal U8 -msf_stream_read_u8(MSF_Context *msf, MSF_StreamNumber sn) -{ - U8 result = 0; - msf_stream_read_struct(msf, sn, &result); - return result; -} - -internal U16 -msf_stream_read_u16(MSF_Context *msf, MSF_StreamNumber sn) -{ - U16 result = 0; - msf_stream_read_struct(msf, sn, &result); - return result; -} - -internal U32 -msf_stream_read_u32(MSF_Context *msf, MSF_StreamNumber sn) -{ - U32 result = 0; - msf_stream_read_struct(msf, sn, &result); - return result; -} - -internal U64 -msf_stream_read_u64(MSF_Context *msf, MSF_StreamNumber sn) -{ - U64 result = 0; - msf_stream_read_struct(msf, sn, &result); - return result; -} - -internal String8 -msf_stream_read_block(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, U64 block_size) -{ - U8 *block_buffer = push_array(arena, U8, block_size); - MSF_UInt block_read = msf_stream_read(msf, sn, block_buffer, block_size); - Assert((U64)block_read == block_size); - String8 block = str8(block_buffer, block_size); - return block; -} - -internal String8 -msf_stream_read_string(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn) -{ - MSF_UInt start_pos = msf_stream_get_pos(msf, sn); - U64 size = 0; - for (;; size += 1) { - U8 cp = msf_stream_read_u8(msf, sn); - if (cp == 0) { - break; - } - } - - msf_stream_seek(msf, sn, start_pos); - String8 string = msf_stream_read_block(arena, msf, sn, size); - msf_stream_seek(msf, sn, start_pos + size + 1); // skip null - - return string; -} - -internal void -msf_stream_align(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt align) -{ - local_persist U8 zeroes[64]; - MSF_UInt pos = msf_stream_get_pos(msf, sn); - MSF_UInt pad = AlignPadPow2(pos, align); - if (pad > 0) { - if (pad < sizeof(zeroes)) { - msf_stream_write(msf, sn, zeroes, pad); - } else { - NotImplemented; - } - } -} - -//////////////////////////////// - -internal MSF_Context * -msf_alloc__(Arena *arena, MSF_UInt page_size, MSF_PageNumber active_fpm) -{ - ProfBeginFunction(); - Assert(active_fpm == MSF_FPM0 || active_fpm == MSF_FPM1); - Assert(IsPow2(page_size)); - - MSF_Context *msf = push_array(arena, MSF_Context, 1); - msf->arena = arena; - msf->page_size = page_size; - msf->active_fpm = active_fpm; - - ProfEnd(); - return msf; -} - internal MSF_Context * msf_alloc_(Arena *arena, MSF_UInt page_size, MSF_UInt active_fpm) { - MSF_Context *msf = msf_alloc__(arena, page_size, active_fpm); - - // reserve first page for header - msf->header_page_list = msf_alloc_pages(msf, 1); - Assert(msf->header_page_list.count > 0); - Assert(msf->header_page_list.first->pn == 0); - - // reserve root page close to start of the file so we don't have to seek too far (not required) - msf->root_page_list = msf_alloc_pages(msf, 1); - Assert(msf->root_page_list.count == 1); - Assert(msf->root_page_list.first->pn == 3); - + Assert(IsPow2(page_size) && MSF_MIN_PAGE_SIZE <= page_size && page_size <= MSF_MAX_PAGE_SIZE); + Assert(active_fpm == MSF_FPM0 || active_fpm == MSF_FPM1); + + MSF_Context *msf = push_array(arena, MSF_Context, 1); + msf->arena = arena; + msf->page_size = page_size; + msf->active_fpm = active_fpm; + msf->zero_page = push_array(arena, U8, page_size); + msf->empty_fpm = push_array_no_zero(arena, U8, page_size); + MemorySet(msf->empty_fpm, 0xff, page_size); + + AssertAlways(msf_stream_resize_ex(msf, &msf->header, page_size)); + AssertAlways(msf_stream_resize_ex(msf, &msf->root, page_size)); + Assert(msf->header.page_list.first->pn == 0); + Assert(msf->root.page_list.first->pn == 3); + return msf; } internal MSF_Context * msf_alloc(MSF_UInt page_size, MSF_UInt active_fpm) { - return msf_alloc_(arena_alloc( .name = "MSF"), page_size, active_fpm); -} - -internal MSF_StreamNode * -msf_find_stream_node(MSF_Context *msf, MSF_StreamNumber sn) -{ - MSF_StreamNode *node; - for (node = msf->sectab.first; node != 0; node = node->next) { - if (node->data.sn == sn) { - break; - } - } - return node; -} - -internal MSF_Stream * -msf_find_stream(MSF_Context *msf, MSF_StreamNumber sn) -{ - MSF_StreamNode *node = msf_find_stream_node(msf, sn); - MSF_Stream *data = 0; - if (node) { - data = &node->data; - } - return data; + return msf_alloc_(arena_alloc(.name = "MSF"), page_size, active_fpm); } internal void @@ -1468,537 +425,170 @@ msf_release(MSF_Context *msf) arena_release(msf->arena); } -internal String8List -msf_build_stream_table_data(Arena *arena, MSF_StreamList *sectab, MSF_UInt page_size, MSF_UInt page_count) +internal U64 +msf_get_save_size(MSF_Context *msf) { - ProfBeginFunction(); - - MSF_UInt *stream_count_ptr = push_array(arena, MSF_UInt, 1); - *stream_count_ptr = sectab->count; + U64 interval = (U64)msf->page_size * MSF_BITS_PER_CHAR; + U64 max_pn = MSF_FPM1; + U64 base = 0; - MSF_UInt *stream_size_arr = push_array(arena, MSF_UInt, sectab->count); - MSF_UInt stream_page_count = 0; - - MSF_PageNumber *stream_pages_arr = push_array(arena, MSF_PageNumber, page_count); - - for (MSF_StreamNode *stream_node = sectab->first; stream_node != 0; stream_node = stream_node->next) { - MSF_Stream *stream = &stream_node->data; - - // is page list correct? - MSF_UInt expected_stream_page_count = msf_count_pages(page_size, stream->size); - if (expected_stream_page_count > stream->page_list.count) { - Assert(!"invalid page list "); - } - - // store stream sizes - stream_size_arr[stream->sn] = stream->size; - - // store stream pages - for (MSF_PageNode *page_node = stream->page_list.first; page_node != 0; page_node = page_node->next) { - // first three pages are reserved for header, FPM0, and FPM1 - Assert(page_node->pn > 2); - - // it's not necessarily a bug to use interval FPM pages, - // but for sake of correctness make sure there is no stream - // aside from FPM that uses these pages - // - // also, actual FPM pages should be asserted on: pn % (msf->page_size * MSF_BITS_PER_CHAR) - Assert((page_node->pn % page_size) != 1); - Assert((page_node->pn % page_size) != 2); - - // is there a stream with too many page nodes? - Assert(stream_page_count < page_count); - - // is this page number allocated? - //Assert(msf_get_fpm_page_bit_state(msf, page_node->pn) == MSF_PAGE_STATE_ALLOC); - - stream_pages_arr[stream_page_count] = page_node->pn; - stream_page_count += 1; + for EachNode(block, MSF_PageBlock, msf->first_block) { + U32Array bits = { .v = block->free_bits, .count = msf->page_size / sizeof(U32) }; + U64 hi = interval; + while (hi != 0) { + U64 bit = bit_array_scan_right_to_left32(bits, 0, hi, MSF_PAGE_STATE_ALLOC); + if (bit >= hi) { break; } + U64 slot = bit % msf->page_size; + if (slot != MSF_FPM0 && slot != MSF_FPM1) { + max_pn = Max(max_pn, base + bit); + break; + } + hi = bit; } + base += interval; } - - // on disk stream table: - // MSF_UInt stream_count; - // MSF_UInt stream_size[stream_count]; - // MSF_PageNumber pages[stream_count][*]; - String8List st_data_list = {0}; - str8_list_push(arena, &st_data_list, str8((U8*)stream_count_ptr, sizeof(*stream_count_ptr))); - str8_list_push(arena, &st_data_list, str8((U8*)stream_size_arr, sizeof(*stream_size_arr) * (*stream_count_ptr))); - str8_list_push(arena, &st_data_list, str8((U8*)stream_pages_arr, sizeof(*stream_pages_arr) * stream_page_count)); - - ProfEnd(); - return st_data_list; + + return (max_pn + 1) * msf->page_size; } internal MSF_Error -msf_build_stream_table(MSF_Context *msf, MSF_UInt *stream_table_size_out) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - MSF_Error error = MSF_Error_OK; - - String8List st_data_list = msf_build_stream_table_data(scratch.arena, &msf->sectab, msf->page_size, msf->page_count); - - MSF_UInt st_page_count = msf_count_pages(msf->page_size, st_data_list.total_size); - msf_free_pages(msf, &msf->st_page_list); // TODO: page reuse - msf->st_page_list = msf_alloc_pages(msf, st_page_count); - - MSF_UInt cursor = 0; - for (String8Node *node = st_data_list.first; node != 0; node = node->next) { - B32 is_data_written = msf_write(msf->page_data_list, msf->page_size, msf->st_page_list, cursor, node->string.str, node->string.size); - if (!is_data_written) { - error = MSF_BuildError_UNABLE_TO_WRITE_STREAM_TABLE; - goto exit; - } - cursor += node->string.size; - } - - *stream_table_size_out = st_data_list.total_size; - - exit:; - scratch_end(scratch); - ProfEnd(); - return error; -} - -internal MSF_Error -msf_build_root_directory(MSF_Context *msf) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - MSF_Error error = MSF_Error_OK; - - // MS impl doesn't handle root directory with page count above 1. - MSF_UInt max_page_count_in_root_directory = msf->page_size / sizeof(MSF_PageNumber); - if (msf->st_page_list.count > max_page_count_in_root_directory) { - error = MSF_Error_STREAM_TABLE_HAS_TOO_MANY_PAGES; - goto exit; - } - - // collect stream table page numbers - MSF_PageNumber *pn_arr = push_array(scratch.arena, MSF_PageNumber, msf->st_page_list.count); - MSF_UInt pn_count = 0; - for (MSF_PageNode *page = msf->st_page_list.first; page != 0; page = page->next) { - pn_arr[pn_count++] = page->pn; - } - - MSF_UInt root_page_count = msf_count_pages(msf->page_size, pn_count * sizeof(pn_arr[0])); - Assert(root_page_count == 1); - - msf_free_pages(msf, &msf->root_page_list); // TODO: page reuse - msf->root_page_list = msf_alloc_pages(msf, root_page_count); - B32 is_root_written = msf_write(msf->page_data_list, msf->page_size, msf->root_page_list, 0, pn_arr, sizeof(pn_arr[0]) * pn_count); - if (!is_root_written) { - error = MSF_BuildError_UNABLE_TO_WRITE_ROOT_DIRECTORY; - goto exit; - } - -exit:; - scratch_end(scratch); - ProfEnd(); - return error; -} - -internal MSF_Error -msf_build_header(MSF_Context *msf, MSF_UInt stream_table_size) -{ - ProfBeginFunction(); - MSF_Error error = MSF_Error_OK; - - MSF_Header70 header; - MemoryCopy(&header.magic[0], &msf_msf70_magic[0], sizeof(msf_msf70_magic)); - header.page_size = msf->page_size; - header.active_fpm = msf->active_fpm; - // The allocator also counts reserved FPM pages beyond the serialized extent. - // The stream table and root are finalized; use the same extent as the writer. - header.page_count = (MSF_UInt)(msf_get_save_size(msf) / msf->page_size); - header.stream_table_size = stream_table_size; - header.unknown = 0; - header.root_pn = msf->root_page_list.first->pn; - - B32 is_header_written = msf_write(msf->page_data_list, msf->page_size, msf->header_page_list, 0, &header, sizeof(header)); - if (!is_header_written) { - error = MSF_BuildError_UNABLE_TO_WRITE_HEADER; - goto exit; - } - - exit:; - ProfEnd(); - return error; -} - -internal MSF_Error msf_build(MSF_Context *msf) { ProfBeginFunction(); - - MSF_Error err; - do { - MSF_UInt stream_table_size; - err = msf_build_stream_table(msf, &stream_table_size); - if (err != MSF_Error_OK) { - break; + + U64 table_size = sizeof(MSF_UInt) * (1 + (U64)msf->sectab.count); + for EachNode(node, MSF_StreamNode, msf->sectab.first) { + if (node->data.size != MSF_DELETED_STREAM_STAMP) { + table_size += sizeof(MSF_PageNumber) * (U64)msf_count_pages(msf->page_size, node->data.size); } - - err = msf_build_root_directory(msf); - if (err != MSF_Error_OK) { - break; + } + + U64 table_pages = CeilIntegerDiv(table_size, msf->page_size); + if (table_pages > msf->page_size / sizeof(MSF_PageNumber)) { + ProfEnd(); + return MSF_Error_StreamTableHasTooManyPages; + } + + if (!msf_stream_resize_ex(msf, &msf->stream_table, safe_cast_u32(table_size))) { + ProfEnd(); + return MSF_Error_FailedToWriteStreamTable; + } + + U32 *sizes = (U32 *)msf->stream_table.data; + *sizes++ = msf->sectab.count; + + MSF_PageNumber *pages = (MSF_PageNumber *)(sizes + msf->sectab.count); + for EachNode(node, MSF_StreamNode, msf->sectab.first) { + MSF_Stream *stream = &node->data; + sizes[stream->sn] = stream->size; + if (stream->size == MSF_DELETED_STREAM_STAMP) { continue; } + MSF_UInt count = msf_count_pages(msf->page_size, stream->size); + Assert(count <= stream->page_list.count); + MSF_PageNode *page = stream->page_list.first; + for EachIndex(i, count) { + *pages++ = page->pn; + page = page->next; } - - err = msf_build_header(msf, stream_table_size); - if (err != MSF_Error_OK) { - break; + } + + // keep the root's allocation-time page number, including on repeated builds + MemoryZero(msf->root.data, msf->root.capacity); + MSF_PageNumber *root = (MSF_PageNumber *)msf->root.data; + for EachNode(page, MSF_PageNode, msf->stream_table.page_list.first) { *root++ = page->pn; } + + MSF_Header70 header = {0}; + MemoryCopy(&header.magic[0], &msf_msf70_magic[0], sizeof(header.magic)); + header.page_size = msf->page_size; + header.active_fpm = msf->active_fpm; + header.page_count = safe_cast_u32(msf_get_save_size(msf) / msf->page_size); + header.stream_table_size = safe_cast_u32(table_size); + header.root_pn = msf->root.page_list.first->pn; + MemoryCopy(msf->header.data, &header, sizeof(header)); + + // allocation reserves future FPM slots too, but on disk all pages beyond + // EOF must be free; MS readers validate those trailing bits + U64 interval = (U64)msf->page_size * MSF_BITS_PER_CHAR; + U64 base = 0; + for EachNode(block, MSF_PageBlock, msf->first_block) { + MemoryCopy(block->fpm_data, block->free_bits, msf->page_size); + U64 used_bits = header.page_count > base ? Min((U64)header.page_count - base, interval) : 0; + U64 byte = used_bits / 8; + if (used_bits % 8) { + block->fpm_data[byte++] |= (U8)(0xff << (used_bits % 8)); } - } while (0); + MemorySet(block->fpm_data + byte, 0xff, msf->page_size - byte); + base += interval; + } ProfEnd(); - return err; + return MSF_Error_Ok; } +//////////////////////////////// + internal String8List msf_get_page_data_nodes(Arena *arena, MSF_Context *msf) { - ProfBeginFunction(); + String8List result = {0}; - String8List list; MemoryZeroStruct(&list); + U64 page_count = msf_get_save_size(msf) / msf->page_size; + U64 interval = (U64)msf->page_size * MSF_BITS_PER_CHAR; + U64 pn = 0; - U64 total_size = msf_get_save_size(msf); - U64 bytes_left = total_size; - U64 node_size = msf_get_data_node_size(msf->page_size); - - for (MSF_PageDataNode *data_node = msf->page_data_list.first; data_node != 0; data_node = data_node->next) { - // compute byte count for the node - U64 to_copy = Min(bytes_left, node_size); - bytes_left -= to_copy; - - String8 data = str8(data_node->data, to_copy); - str8_list_push(arena, &list, data); + for EachNode(block, MSF_PageBlock, msf->first_block) { + for (U64 i = 0; i < interval && pn < page_count; i += 1, pn += 1) { + U8 *data = block->data[i] != 0 ? block->data[i] : msf->zero_page; + if (result.last != 0 && result.last->string.str + result.last->string.size == data) { + result.last->string.size += msf->page_size; + result.total_size += msf->page_size; + } else { + str8_list_push(arena, &result, str8(data, msf->page_size)); + } + } } - ProfEnd(); - return list; -} - -internal U64 -msf_get_save_size(MSF_Context *msf) -{ - MSF_PageNumber max_pn = msf_find_max_pn(msf->page_data_list, msf->page_size); - U64 page_count = max_pn + 1; - U64 size = page_count * msf->page_size; - return size; + return result; } internal B32 msf_save(MSF_Context *msf, void *buffer, U64 buffer_size) { - ProfBeginFunction(); + if (buffer_size != msf_get_save_size(msf)) { return 0; } - U64 node_size = msf_get_data_node_size(msf->page_size); - U64 cursor = 0; - - for (MSF_PageDataNode *node = msf->page_data_list.first; node != 0; node = node->next) { - // compute byte count for the copy - U64 bytes_in_buffer = buffer_size - cursor; - U64 to_copy = Min(bytes_in_buffer, node_size); - - // copy MSF bytes to output buffer - U8 *dst = (U8 *)buffer + cursor; - U8 *src = node->data; - MemoryCopy(dst, src, to_copy); - - // advance cursor - cursor += to_copy; - - // is output buffer full? - if (to_copy == 0) { - break; - } + Temp scratch = scratch_begin(0, 0); + String8List spans = msf_get_page_data_nodes(scratch.arena, msf); + U64 pos = 0; + for EachNode(node, String8Node, spans.first) { + MemoryCopy((U8 *)buffer + pos, node->string.str, node->string.size); + pos += node->string.size; } - B32 is_save_ok = (cursor == buffer_size); - Assert(is_save_ok); - - ProfEnd(); - return is_save_ok; + scratch_end(scratch); + return pos == buffer_size; } internal MSF_Error msf_save_arena(Arena *arena, MSF_Context *msf, String8 *data_out) { - ProfBeginFunction(); - MSF_Error err = msf_build(msf); - if (err == MSF_Error_OK) { - U64 buffer_size = msf_get_save_size(msf); - U8 *buffer = push_array(arena, U8, buffer_size); - B32 is_saved = msf_save(msf, buffer, buffer_size); - if (is_saved) { - *data_out = str8(buffer, buffer_size); - } else { - arena_pop(arena, buffer_size); - } + MSF_Error error = msf_build(msf); + if (error == MSF_Error_Ok) { + U64 size = msf_get_save_size(msf); + U8 *data = push_array_no_zero(arena, U8, size); + if (!msf_save(msf, data, size)) { return MSF_Error_FailedToWriteHeader; } + *data_out = str8(data, size); } - ProfEnd(); - return err; + return error; } internal char * msf_error_to_string(MSF_Error code) { - char *str = ""; switch (code) { - case MSF_Error_OK: break; - - case MSF_Error_STREAM_TABLE_HAS_TOO_MANY_PAGES: str = "stream table exceeds page limit"; break; - - case MSF_OpenError_NOT_ENOUGH_BYTES_TO_READ_HEADER: str = "input does not have enough bytes to read header"; break; - case MSF_OpenError_INVALID_MAGIC: str = "magic value does not match"; break; - case MSF_OpenError_PAGE_SIZE_IS_NOT_POW2: str = "page size is not power of two"; break; - case MSF_OpenError_INVALID_PAGE_SIZE: str = "invalid page size"; break; - case MSF_OpenError_NOT_ENOUGH_PAGES_TO_INIT: str = "not enough pages to initialize MSF"; break; - case MSF_OpenError_INVALID_ROOT_STREAM_PAGE_NUMBER: str = "invalid root stream page number"; break; - case MSF_OpenError_UNABLE_TO_READ_STREAM_TABLE_PAGE_NUMBERS: str = "unable to read stream table's page numbers"; break; - case MSF_OpenError_STREAM_COUNT_OVERFLOW: str = "stream count is overflown"; break; - case MSF_OpenError_UNABLE_TO_READ_STREAM_SIZES: str = "unable to read streams sizes"; break; - case MSF_OpenError_INVALID_STREAM_TABLE: str = "invalid stream table"; break; - case MSF_OpenError_INVALID_ACTIVE_FPM: str = "invalid active FPM"; break; - case MSF_OpenError_PAGE_COUNT_DOESNT_MATCH_DATA_SIZE: str = "page count from MSF header does not match data page count"; break; - - case MSF_BuildError_UNABLE_TO_WRITE_STREAM_TABLE: str = "unable to write stream table"; break; - case MSF_BuildError_UNABLE_TO_WRITE_STREAM_TABLE_PAGE_NUMBER_DIRECTORY: str = "unable to write stream table page number directory"; break; - case MSF_BuildError_UNABLE_TO_WRITE_ROOT_DIRECTORY: str = "unable to write root directory"; break; - case MSF_BuildError_UNABLE_TO_WRITE_HEADER: str = "unable to write header"; break; + case MSF_Error_Ok: return ""; + case MSF_Error_StreamTableHasTooManyPages: return "stream table exceeds page limit"; + case MSF_Error_FailedToWriteStreamTable: return "failed to write stream table"; + case MSF_Error_FailedToWriteHeader: return "failed to write header"; } - - return str; + return "unknown MSF error"; } -//////////////////////////////// - -/* - Multi-Stream-Format is a database type of format for storing debug info - but in principle can store anything you want. MSF divides file - into fixed-sized pages (default page size is 4KiB) and puts them - together into streams. A stream is made up from a non-contigous - number of pages and supports following operations: alloc, free, open, write, read. - Current MSF 7.0 allows creating up to 64K of streams, where each stream can potentially - contain 2GiB of data (assuming default page size). - - Free Page Map assigns a bit to each page to indicate page alloc state. 0 = allocated and 1 = free. - FPM is alloced at fixed intervals of 'page_size * MSF_BITS_PER_CHAR'. At the begining of interval - two pages are reserved for status bits. The 'active_fpm' field in the MSF header tells which FPM page - is in use. On commit time MSF alternates between two pages, this way they support atomic read and write. - - FPM Bug: - Let's say you have a MSF file with page size 0x1000 bytes, you can represent 0x1000 * 8 = 0x8000 pages - or 0x8000 * 0x1000 = 128MiB. And when file exceeds this size a new FPM group should be allocated - at page numbers 0x8001 and 0x8002. However, in MS impl there is a bug where they don't multiply - interval by 8 and each FPM group is allocated at intervals of page size or 0x1000, so each FPM group is placed - at page numbers 0x1001, 0x1002, 0x2001, 0x2002, and so on. This way MSF files end up allocating 8 times more pages. - - Also, MS impl marks unused pages as allocated thus leaving them empty but LLVM repurposes them - for regular allocations and things work out fine because of the fact that MS computes correct - number of FPM pages when they save and load and the trailing pages aren't being touched: - https://github.com/microsoft/microsoft-pdb/blob/master/PDB/msf/msf.cpp#L2512 - - Root directory is a single paged stored as a page number in 'MSF_Header70.root_pn'. - The directory contains an array of page numbers needed to read the stream table. This is a late - addition introduced in version 7.0 that lets us have bigger stream tables. However, there is a limit - if stream table exceeds root directory, MSF becomes invalid. MS impl isn't - clear what should happen in this case, so we tried to contiguously allocate root pages - but VS and LLVM error out. In practice you can double page size to work around the limit. - -TODO: explain stream table - - */ - -#if 0 - -internal void -msf_bytedump_stream(char *file_name, MSF_Context *msf, MSF_StreamNumber sn, U64 start, U64 byte_count) -{ - Temp scratch = scratch_begin(0, 0); - U64 pos = msf_stream_get_pos(msf, sn); - msf_stream_seek(msf, sn, start); - U64 buffer_size = byte_count; - U8 *buffer = push_array(scratch.arena, U8, buffer_size); - MSF_UInt read_size = msf_stream_read(msf, sn, buffer, buffer_size); - os_write_file(str8_cstring(file_name), str8(buffer, read_size)); - msf_stream_seek(msf, sn, pos); - scratch_end(scratch); -} - -internal void -msf_hexdump_stream(FILE *file, MSF_Context *msf, MSF_StreamNumber sn, U64 start, U64 byte_count, U64 stride) -{ - Temp scratch = scratch_begin(0, 0); - U8 *row_buffer = push_array(scratch.arena, U8, stride); - U64 stream_size = msf_stream_get_size(msf, sn); - U64 cursor = start; - U64 end = Min(start + byte_count, stream_size); - while (cursor < stream_size) { - MSF_UInt read_size = msf_stream_read(msf, sn, row_buffer, stride); - - // print offset - fprintf(file, "%04llX", cursor); - - // print bytes - fprintf(file, " "); - for (U64 i = 0; i < read_size; i += 1) { - if (i > 0) { - fprintf(file, " "); - } - fprintf(file, "%02X", row_buffer[i]); - } - - // print ascii - fprintf(file, " "); - for (U64 i = 0; i < read_size; i += 1) { - U8 print_char = row_buffer[i]; - if (0x20 > print_char || print_char > 0x7E) { - print_char = '.'; - } - fprintf(file, "%c", print_char); - } - - // row is done - fprintf(file, "\n"); - - cursor += stride; - } - - scratch_end(scratch); -} - -internal void -msf_hexdump_stream_to_file(char *name, MSF_Context *msf, MSF_StreamNumber sn, U64 start, U64 byte_count, U64 stride) -{ - FILE *f = fopen(name, "w"); - msf_hexdump_stream(f, msf, sn, start, byte_count, stride); - fclose(f); -} - -#endif - -#if 0 -internal void -test_msf_open_save(void) -{ - Temp scratch = scratch_begin(0, 0); - - U32 item0 = 123; - U32 item1 = 321; - - MSF_StreamNumber stream; - String8 data; - { - MSF_Context *msf = msf_alloc(MSF_DEFAULT_PAGE_SIZE, MSF_DEFAULT_FPM); - stream = msf_stream_alloc(msf); - msf_stream_write_u32(msf, stream, item0); - msf_stream_write_u32(msf, stream, item1); - data = msf_save_arena(scratch.arena, msf); - msf_release(&msf); - } - - String8 data1; - { - MSF_Context *msf = 0; - MSF_Error err = msf_open(data, &msf); - Assert(err == MSF_Error_OK); - U32 read0 = msf_stream_read_u32(msf, stream); - Assert(read0 == item0); - U32 read1 = msf_stream_read_u32(msf, stream); - Assert(read1 == item1); - data1 = msf_save_arena(scratch.arena, msf); - msf_release(&msf); - } - - { - MSF_Context *msf = 0; - MSF_Error err = msf_open(data, &msf); - Assert(err == MSF_Error_OK); - U32 read0 = msf_stream_read_u32(msf, stream); - Assert(read0 == item0); - U32 read1 = msf_stream_read_u32(msf, stream); - Assert(read1 == item1); - msf_release(&msf); - } - - scratch_end(scratch); -} - -internal void -test_size_limit(void) -{ - Temp scratch = scratch_begin(0, 0); - - MSF_Context *msf = msf_alloc(8192, MSF_DEFAULT_FPM); - Assert(msf); - - U64 c = (1 * 1024 * 1024 * 1024) / msf->page_size; - U64 stream_count = 8; - - U64 data_size = msf->page_size; - U8 *data = push_array(scratch.arena, U8, data_size); - - for (U64 stream_idx = 0; stream_idx < stream_count; stream_idx += 1) { - MSF_StreamNumber stream = msf_stream_alloc(msf); - Assert(stream != MSF_INVALID_STREAM_NUMBER); - - MemorySet(&data[0], 1 + stream_idx, data_size); - - msf_stream_resize(msf, stream, c * msf->page_size); - - for (U64 i = 0; i < c; i += 1) { - B32 is_written = msf_stream_write(msf, stream, data, data_size); - Assert(is_written); - } - } - - //msf_grow(msf, MSF_PN_MAX); - - msf_stream_free(msf, 7); - msf_stream_free(msf, 6); - msf_stream_free(msf, 5); - - stream_count -= 3; - - String8 msf_data = msf_save_arena(scratch.arena, msf); - Assert(msf_data.size > 0); - msf_release(&msf); - - //os_write_file(str8_lit("test.msf"), msf_data); - - MSF_Error err = msf_open(msf_data, &msf); - Assert(err == MSF_Error_OK); - -#if 1 - U8 *buffer = push_array(scratch.arena, U8, data_size); - for (U64 stream_idx = 0; stream_idx < stream_count; stream_idx += 1) { - MSF_StreamNumber sn = (MSF_StreamNumber)stream_idx; - - MemorySet(&data[0], 1 + stream_idx, data_size); - - for (U64 i = 0; i < c; i += 1) { - MSF_UInt read_size = msf_stream_read(msf, sn, buffer, data_size); - Assert(read_size == data_size); - - int cmp = MemoryCompare(buffer, data, data_size); - Assert(cmp == 0); - } - } -#endif - - msf_release(&msf); - scratch_end(scratch); -} - -internal void -test_msf(void) -{ - test_size_limit(); - test_msf_open_save(); -} -#endif - diff --git a/src/linker/pdb_ext/msf_builder.h b/src/linker/pdb_ext/msf_builder.h index c07b582a..0cbaf29d 100644 --- a/src/linker/pdb_ext/msf_builder.h +++ b/src/linker/pdb_ext/msf_builder.h @@ -5,25 +5,18 @@ #define MSF_PAGE_STATE_FREE 1 #define MSF_PAGE_STATE_ALLOC 0 - #define MSF_FPM0 1 #define MSF_FPM1 2 - #define MSF_DEFAULT_PAGE_SIZE 4096 #define MSF_DEFAULT_FPM MSF_FPM0 -typedef struct MSF_PageNumberArray +typedef struct MSF_PageNode MSF_PageNode; +struct MSF_PageNode { - U64 count; - MSF_PageNumber *v; -} MSF_PageNumberArray; - -typedef struct MSF_PageNode -{ - struct MSF_PageNode *next; - struct MSF_PageNode *prev; - MSF_PageNumber pn; -} MSF_PageNode; + MSF_PageNode *next; + MSF_PageNode *prev; + MSF_PageNumber pn; +}; typedef struct MSF_PageList { @@ -34,19 +27,21 @@ typedef struct MSF_PageList typedef struct MSF_Stream { - MSF_StreamNumber sn; - MSF_UInt size; - MSF_UInt pos; - MSF_PageNode *pos_page; - MSF_PageList page_list; + MSF_StreamNumber sn; + MSF_UInt size; + MSF_UInt pos; + U64 capacity; + U8 *data; + MSF_PageList page_list; } MSF_Stream; -typedef struct MSF_StreamNode +typedef struct MSF_StreamNode MSF_StreamNode; +struct MSF_StreamNode { - struct MSF_StreamNode *next; - struct MSF_StreamNode *prev; - MSF_Stream data; -} MSF_StreamNode; + MSF_StreamNode *next; + MSF_StreamNode *prev; + MSF_Stream data; +}; typedef struct MSF_StreamList { @@ -55,125 +50,84 @@ typedef struct MSF_StreamList MSF_StreamNode *last; } MSF_StreamList; -typedef struct MSF_PageDataNode +typedef struct MSF_PageBlock MSF_PageBlock; +struct MSF_PageBlock { - struct MSF_PageDataNode *next; - struct MSF_PageDataNode *prev; - U8 *data; -} MSF_PageDataNode; - -typedef struct MSF_PageDataList -{ - MSF_PageDataNode *first; - MSF_PageDataNode *last; - MSF_UInt count; -} MSF_PageDataList; + MSF_PageBlock *next; + U32 *free_bits; + U8 *fpm_data; + U8 **data; +}; typedef struct MSF_Context { - Arena *arena; - MSF_UInt page_size; - MSF_UInt active_fpm; - MSF_UInt fpm_rover; - MSF_PageNumber page_count; - MSF_PageDataList page_data_list; - MSF_PageDataList page_data_pool; - MSF_PageList header_page_list; - MSF_PageList root_page_list; - MSF_PageList st_page_list; - MSF_PageList page_pool; - MSF_StreamList sectab; + Arena *arena; + MSF_UInt page_size; + MSF_UInt active_fpm; + MSF_PageNumber fpm_rover; + MSF_UInt block_count; + MSF_PageBlock *first_block; + MSF_PageBlock *last_block; + U8 *zero_page; + U8 *empty_fpm; + MSF_PageList page_pool; + MSF_Stream header; + MSF_Stream root; + MSF_Stream stream_table; + MSF_StreamList sectab; } MSF_Context; typedef enum MSF_Error { - MSF_Error_OK, - - // if you get this error this means stream table was divided into too many - // pages, and to fix this you need to bump up the page size - MSF_Error_STREAM_TABLE_HAS_TOO_MANY_PAGES, - - MSF_OpenError_NOT_ENOUGH_BYTES_TO_READ_HEADER, - MSF_OpenError_INVALID_MAGIC, - MSF_OpenError_PAGE_SIZE_IS_NOT_POW2, - MSF_OpenError_INVALID_PAGE_SIZE, - MSF_OpenError_NOT_ENOUGH_PAGES_TO_INIT, - MSF_OpenError_INVALID_ROOT_STREAM_PAGE_NUMBER, - MSF_OpenError_UNABLE_TO_READ_STREAM_TABLE_PAGE_NUMBERS, - MSF_OpenError_STREAM_COUNT_OVERFLOW, - MSF_OpenError_UNABLE_TO_READ_STREAM_SIZES, - MSF_OpenError_INVALID_STREAM_TABLE, - MSF_OpenError_INVALID_ACTIVE_FPM, - MSF_OpenError_PAGE_COUNT_DOESNT_MATCH_DATA_SIZE, - - MSF_BuildError_UNABLE_TO_WRITE_STREAM_TABLE, - MSF_BuildError_UNABLE_TO_WRITE_STREAM_TABLE_PAGE_NUMBER_DIRECTORY, - MSF_BuildError_UNABLE_TO_WRITE_ROOT_DIRECTORY, - MSF_BuildError_UNABLE_TO_WRITE_HEADER, + MSF_Error_Ok, + MSF_Error_StreamTableHasTooManyPages, + MSF_Error_FailedToWriteStreamTable, + MSF_Error_FailedToWriteHeader, } MSF_Error; -//////////////////////////////// - -typedef struct +typedef struct MSF_WriteTask { - MSF_UInt page_size; - MSF_PageDataList page_data_list; - MSF_PageList page_list; - MSF_UInt stream_pos; - String8 data; - Rng1U64 *range_arr; + U8 *dst; + U8 *src; + Rng1U64 *ranges; } MSF_WriteTask; -//////////////////////////////// +internal MSF_Context *msf_alloc(MSF_UInt page_size, MSF_UInt active_fpm); +internal void msf_release(MSF_Context *msf); +internal MSF_Error msf_build(MSF_Context *msf); +internal U64 msf_get_save_size(MSF_Context *msf); -internal MSF_Context * msf_alloc(MSF_UInt page_size, MSF_UInt active_fpm); -internal void msf_release(MSF_Context *msf_ptr); -internal MSF_Error msf_build(MSF_Context *msf); -internal U64 msf_get_save_size(MSF_Context *msf); -internal String8List msf_get_page_data_nodes(Arena *arena, MSF_Context *msf); -internal B32 msf_save(MSF_Context *msf, void *buffer, U64 buffer_size); -internal MSF_Error msf_save_arena(Arena *arena, MSF_Context *msf, String8 *data_out); -internal MSF_StreamNode * msf_find_stream_node(MSF_Context *msf, MSF_StreamNumber sn); -internal MSF_Stream * msf_find_stream(MSF_Context *msf, MSF_StreamNumber sn); -internal B32 msf_grow(MSF_Context *msf, MSF_PageNumber page_count); -internal MSF_PageNumber * msf_alloc_pn_arr(Arena *arena, MSF_Context *msf, MSF_UInt alloc_count); -internal void msf_free_pn_arr(MSF_Context *msf, MSF_PageNumber *pn_arr, MSF_UInt pn_count); -internal MSF_PageList msf_alloc_pages(MSF_Context *msf, MSF_UInt alloc_count); -internal void msf_free_pages(MSF_Context *msf, MSF_PageList *page_list); +// File-order spans borrow stream/metadata storage and remain valid until it +// is modified or released. No second copy of the stream payloads is made. +internal String8List msf_get_page_data_nodes(Arena *arena, MSF_Context *msf); +internal String8 msf_data_from_pn(MSF_Context *msf, MSF_PageNumber pn); +internal B32 msf_save(MSF_Context *msf, void *buffer, U64 buffer_size); +internal MSF_Error msf_save_arena(Arena *arena, MSF_Context *msf, String8 *data_out); +internal MSF_StreamNode *msf_find_stream_node(MSF_Context *msf, MSF_StreamNumber sn); +internal MSF_Stream *msf_find_stream(MSF_Context *msf, MSF_StreamNumber sn); +internal MSF_PageList msf_alloc_pages(MSF_Context *msf, MSF_UInt count); +internal void msf_free_pages(MSF_Context *msf, MSF_PageList *pages); internal MSF_StreamNumber msf_stream_alloc_ex(MSF_Context *msf, MSF_UInt size); internal MSF_StreamNumber msf_stream_alloc(MSF_Context *msf); internal B32 msf_stream_free(MSF_Context *msf, MSF_StreamNumber sn); -internal B32 msf_stream_resize(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt new_size); +internal B32 msf_stream_resize(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt size); internal B32 msf_stream_resize_ex(MSF_Context *msf, MSF_Stream *stream, MSF_UInt size); -internal void msf_stream_set_size(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt size); internal MSF_UInt msf_stream_get_size(MSF_Context *msf, MSF_StreamNumber sn); -internal MSF_UInt msf_stream_get_cap(MSF_Context *msf, MSF_StreamNumber); internal MSF_UInt msf_stream_get_pos(MSF_Context *msf, MSF_StreamNumber sn); -internal void msf_stream_align(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt align); internal B32 msf_stream_reserve(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt size); -internal B32 msf_stream_seek(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt new_pos); +internal B32 msf_stream_seek(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt pos); internal B32 msf_stream_seek_start(MSF_Context *msf, MSF_StreamNumber sn); -internal B32 msf_stream_seek_end(MSF_Context *msf, MSF_StreamNumber sn); +internal void msf_stream_align(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt align); -internal MSF_UInt msf_stream_read(MSF_Context *msf, MSF_StreamNumber sn, void *dst, MSF_UInt dst_len); -internal String8 msf_stream_read_block(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, U64 block_size); -internal String8 msf_stream_read_string(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn); -internal S8 msf_stream_read_s8(MSF_Context *msf, MSF_StreamNumber sn); -internal S16 msf_stream_read_s16(MSF_Context *msf, MSF_StreamNumber sn); -internal S32 msf_stream_read_s32(MSF_Context *msf, MSF_StreamNumber sn); -internal S64 msf_stream_read_s64(MSF_Context *msf, MSF_StreamNumber sn); -internal U8 msf_stream_read_u8(MSF_Context *msf, MSF_StreamNumber sn); -internal U16 msf_stream_read_u16(MSF_Context *msf, MSF_StreamNumber sn); -internal U32 msf_stream_read_u32(MSF_Context *msf, MSF_StreamNumber sn); -internal U64 msf_stream_read_u64(MSF_Context *msf, MSF_StreamNumber sn); -#define msf_stream_read_array(msf, sn, ptr, count) msf_stream_read(msf, sn, ptr, sizeof(*ptr) * (count)) -#define msf_stream_read_struct(msf, sn, ptr) msf_stream_read_array(msf, sn, ptr, 1) +internal String8 msf_stream_data(MSF_Context *msf, MSF_StreamNumber sn); +// Writable, contiguous construction storage. Reserve/resize before taking a +// view: growth beyond capacity invalidates views, but preserves page numbers. +// Finalized streams must not be changed while asynchronous output borrows them. -internal B32 msf_stream_write(MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt buffer_size); +internal B32 msf_stream_write(MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt size); internal B32 msf_stream_write_string(MSF_Context *msf, MSF_StreamNumber sn, String8 string); internal B32 msf_stream_write_list(MSF_Context *msf, MSF_StreamNumber sn, String8List list); -internal B32 msf_stream_write_uint(MSF_Context *msf, MSF_StreamNumber sn, MSF_UInt value); internal B32 msf_stream_write_cstr(MSF_Context *msf, MSF_StreamNumber sn, String8 string); internal B32 msf_stream_write_u8(MSF_Context *msf, MSF_StreamNumber sn, U8 value); internal B32 msf_stream_write_u16(MSF_Context *msf, MSF_StreamNumber sn, U16 value); @@ -183,21 +137,10 @@ internal B32 msf_stream_write_s8(MSF_Context *msf, MSF_StreamNumber sn, S8 value internal B32 msf_stream_write_s16(MSF_Context *msf, MSF_StreamNumber sn, S16 value); internal B32 msf_stream_write_s32(MSF_Context *msf, MSF_StreamNumber sn, S32 value); internal B32 msf_stream_write_s64(MSF_Context *msf, MSF_StreamNumber sn, S64 value); -internal B32 msf_stream_write_parallel(TP_Context *tp, MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt buffer_size); -#define msf_stream_write_array(m, s, v, c) msf_stream_write(m, s, (void*)(v), sizeof(*(v)) * (c)) -#define msf_stream_write_struct(m, s, v ) msf_stream_write_array(m, s, v, 1) +internal B32 msf_stream_write_parallel(TP_Context *tp, MSF_Context *msf, MSF_StreamNumber sn, void *buffer, MSF_UInt size); +#define msf_stream_write_array(m, s, v, c) msf_stream_write(m, s, (void *)(v), sizeof(*(v)) * (c)) +#define msf_stream_write_struct(m, s, v) msf_stream_write_array(m, s, v, 1) -internal String8List msf_data_from_sn(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn); - -internal MSF_UInt msf_count_pages(MSF_UInt page_size, U64 data_size); -internal MSF_PageNumber msf_get_page_count_cap(MSF_PageDataList page_data_list, MSF_UInt page_size); -internal MSF_UInt msf_get_fpm_interval_correct(MSF_UInt page_size); -internal MSF_UInt msf_get_fpm_interval_wrong(MSF_UInt page_size); -internal MSF_UInt msf_get_fpm_idx_from_pn(MSF_UInt page_size, MSF_PageNumber pn); -internal MSF_UInt msf_get_fpm_page_bit_state(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumber active_fpm, MSF_PageNumber pn); -internal void msf_set_fpm_bit(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageNumber active_fpm, MSF_PageNumber pn, B32 state); -internal B32 msf_write(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageList page_list, MSF_UInt offset, void *buffer, MSF_UInt buffer_size); -internal MSF_UInt msf_read(MSF_PageDataList page_data_list, MSF_UInt page_size, MSF_PageList page_list, MSF_UInt offset, void *buffer, MSF_UInt buffer_size); - -internal char * msf_error_to_string(MSF_Error code); +internal MSF_UInt msf_count_pages(MSF_UInt page_size, U64 data_size); +internal char *msf_error_to_string(MSF_Error code); diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index 1c6a523e..f72c203c 100644 --- a/src/linker/pdb_ext/pdb_builder.c +++ b/src/linker/pdb_ext/pdb_builder.c @@ -594,6 +594,7 @@ pdb_strtab_build(PDB_StringTable *strtab, MSF_Context *msf, MSF_StreamNumber sn) MSF_UInt reserve_size = sizeof(header) + sizeof(strtab->size) + strtab->size + + sizeof(strtab->bucket_max) + sizeof(bucket_offset_arr[0]) * strtab->bucket_max + sizeof(strtab->bucket_count); msf_stream_reserve(msf, sn, reserve_size); @@ -890,11 +891,32 @@ pdb_type_hash_stream_build(TP_Context *tp, ProfBeginFunction(); Temp scratch = scratch_begin(0,0); + String8 hash_adj_data = {0}; + if (ts->hash_adj.count) { + hash_adj_data = pdb_data_from_hash_adj_hash_table(scratch.arena, &ts->hash_adj, strtab); + } + + PDB_OffsetSize hash_vals = { .off = 0, .size = sizeof(U32) * ts->leaf_list.node_count }; + PDB_OffsetSize hint_offs = { .off = hash_vals.size, .size = sizeof(hint_arr[0]) * hint_count }; + PDB_OffsetSize hash_adj = {0}; + + if (ts->hash_adj.count) { + hash_adj.off = hint_offs.off + hint_offs.size; + hash_adj.size = hash_adj_data.size; + } + + U64 stream_size = hash_vals.size + hint_offs.size + hash_adj_data.size; + if (ts->hash_sn == MSF_INVALID_STREAM_NUMBER) { ts->hash_sn = msf_stream_alloc(msf); } + + AssertAlways(msf_stream_resize(msf, ts->hash_sn, safe_cast_u32(stream_size))); + String8 stream_data = msf_stream_data(msf, ts->hash_sn); + // write (type index -> bucket index) map // // zero-out entire map so non-UDTs type indices, that are NOT in the hash table, // map to zero offset - U32 *type_to_bucket_map = push_array(scratch.arena, U32, ts->leaf_list.node_count); + U32 *type_to_bucket_map = (U32 *)stream_data.str; + if (hash_vals.size != 0) { MemoryZero(type_to_bucket_map, hash_vals.size); } { ProfBegin("Bucket Map"); PDB_WriteTypeToBucketMap type_to_bucket_task; @@ -904,33 +926,11 @@ pdb_type_hash_stream_build(TP_Context *tp, ProfEnd(); } - ProfBegin("MSF Write"); - // write data to stream - if (ts->hash_sn == MSF_INVALID_STREAM_NUMBER) { - ts->hash_sn = msf_stream_alloc(msf); - } - msf_stream_seek_start(msf, ts->hash_sn); - - PDB_OffsetSize hash_vals; - hash_vals.off = msf_stream_get_pos(msf, ts->hash_sn); - hash_vals.size = sizeof(type_to_bucket_map[0]) * ts->leaf_list.node_count; - msf_stream_write(msf, ts->hash_sn, &type_to_bucket_map[0], hash_vals.size); - - PDB_OffsetSize hint_offs; - hint_offs.off = msf_stream_get_pos(msf, ts->hash_sn); - hint_offs.size = sizeof(hint_arr[0]) * hint_count; + msf_stream_seek(msf, ts->hash_sn, hint_offs.off); msf_stream_write(msf, ts->hash_sn, &hint_arr[0], hint_offs.size); - - PDB_OffsetSize hash_adj = {0}; - if (ts->hash_adj.count) { - // write bucket adjust info - String8 hash_adj_data = pdb_data_from_hash_adj_hash_table(scratch.arena, &ts->hash_adj, strtab); - hash_adj.off = msf_stream_get_pos(msf, ts->hash_sn); - hash_adj.size = hash_adj_data.size; - msf_stream_write_string(msf, ts->hash_sn, hash_adj_data); - } + msf_stream_write_string(msf, ts->hash_sn, hash_adj_data); ProfEnd(); @@ -990,7 +990,7 @@ pdb_type_server_build(TP_Context *tp, PDB_TypeServer *ts, PDB_StringTable *strta U64 lf_buf_size = 0; U64 lf_node_idx = 0; U64 lf_arr_idx = 0; - for (String8Node *lf = ts->leaf_list.first; lf != 0; lf = lf->next) { + for EachNode(lf, String8Node, ts->leaf_list.first) { if (lf_node_idx == lf_range_arr[lf_arr_idx].min) { // :thread_pool_dummy_range lf_cursor_arr[lf_arr_idx] = lf_buf_size; lf_arr[lf_arr_idx] = lf; @@ -1012,7 +1012,8 @@ pdb_type_server_build(TP_Context *tp, PDB_TypeServer *ts, PDB_StringTable *strta write_types_task.lf_arr = lf_arr; write_types_task.lf_range_arr = lf_range_arr; write_types_task.lf_cursor_arr = lf_cursor_arr; - write_types_task.lf_buf = push_array_no_zero(scratch.arena, U8, lf_buf_size); + AssertAlways(msf_stream_resize(msf, sn, safe_cast_u32(sizeof(PDB_TpiHeader) + lf_buf_size))); + write_types_task.lf_buf = msf_stream_data(msf, sn).str + sizeof(PDB_TpiHeader); write_types_task.lf_buf_size = lf_buf_size; tp_for_parallel(tp, 0, tp->worker_count, pdb_write_types_task, &write_types_task); @@ -1040,7 +1041,7 @@ pdb_type_server_build(TP_Context *tp, PDB_TypeServer *ts, PDB_StringTable *strta ProfBegin("MSF Commit"); msf_stream_seek_start(msf, sn); msf_stream_write_struct(msf, sn, &header); - msf_stream_write_parallel(tp, msf, sn, write_types_task.lf_buf, lf_buf_size); + msf_stream_seek(msf, sn, sizeof(header) + lf_buf_size); ProfEnd(); scratch_end(scratch); @@ -1278,135 +1279,6 @@ pdb_type_server_push_parallel(TP_Context *tp, PDB_TypeServer *type_server, U64 l ProfEnd(); } -#if 0 -internal CV_LeafNode * -pdb_type_server_leaf_from_string(PDB_TypeServer *ts, String8 string) -{ - ProfBeginFunction(); - U32 hash = pdb_hash_v1(string); - U32 bucket_idx = hash % ts->bucket_count; - PDB_TypeBucket *head_bucket = ts->bucket_table[bucket_idx]; - CV_LeafNode *result = 0; - for (PDB_TypeBucket *i = head_bucket; i != 0; i = i->next) { - CV_LeafNode *leaf = i->leaf_node; - String8 leaf_name = cv_get_leaf_name(leaf->data.kind, leaf->data.data); - if (str8_match(leaf_name, string, 0)) { - result = leaf; - break; - } - } - ProfEnd(); - return result; -} -#endif - -//////////////////////////////// - -#if 0 -internal PDB_TypeIndexMap * -pdb_load_types_from_leaf_list(PDB_TypeServer **type_server_arr, CV_LeafList leaf_list) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - // 1. redistribute leaves in parallel - CV_LeafList leaf_list_arr[CV_TypeIndexSource_COUNT] = {0}; - for (CV_LeafNode *curr = leaf_list.first, *next = 0; curr != 0; curr = next) { - next = curr->next; - curr->next = 0; - CV_TypeIndexSource ti_source = cv_type_index_source_from_leaf_kind(curr->data.kind); - CV_LeafList *list = &leaf_list_arr[ti_source]; - SLLQueuePush(list->first, list->last, curr); - list->count += 1; - } - - // 2. reserve type leafs on main thread - PDB_TypeLeaf *leaf_arr_arr[CV_TypeIndexSource_COUNT]; - for (U64 source_idx = 0; source_idx < ArrayCount(leaf_list_arr); source_idx += 1) { - PDB_TypeServer *type_server = type_server_arr[source_idx]; - CV_LeafList input_leaf_list = leaf_list_arr[source_idx]; - PDB_TypeLeaf *leaf_arr = pdb_type_server_reserve(type_server, input_leaf_list.count); - leaf_arr_arr[source_idx] = leaf_arr; - } - - // 3. populate type index map in parallel - PDB_TypeIndexMap *ti_map = pdb_type_index_map_alloc(); - for (U64 source_idx = 0; source_idx < ArrayCount(leaf_list_arr); source_idx += 1) { - CV_LeafList input_leaf_list = leaf_list_arr[source_idx]; - PDB_TypeLeaf *leaf_arr = leaf_arr_arr[source_idx]; - for (U64 leaf_idx = 0; leaf_idx < input_leaf_list.count; leaf_idx += 1) { - CV_TypeIndex external_ti = ti_map->min_itype[source_idx] + leaf_idx; - CV_TypeIndex internal_ti = leaf_arr[leaf_idx].type_index; - pdb_type_index_map_add(ti_map, (CV_TypeIndexSource)source_idx, external_ti, internal_ti); - } - } - - // 4. patch type indices in parallel - for (U64 source_idx = 0; source_idx < ArrayCount(leaf_list_arr); source_idx += 1) { - CV_LeafList list = leaf_list_arr[source_idx]; - for (CV_LeafNode *node = list.first; node != 0; node = node->next) { - Temp temp = temp_begin(scratch.arena); - - // get offsets for type indices in data blob - CV_Leaf *leaf = &node->data; - CV_TypeIndexInfoList ti_info_list = cv_get_leaf_type_index_offsets(temp.arena, leaf->kind, leaf->data); - - for (CV_TypeIndexInfo *ti_info = ti_info_list.first; ti_info != 0; ti_info = ti_info->next) { - Assert(ti_info->offset + sizeof(CV_TypeIndex) <= leaf->data.size); - CV_TypeIndex *ti_ptr = (CV_TypeIndex *)(leaf->data.str + ti_info->offset); - CV_TypeIndex external_ti = *ti_ptr; - - B32 is_complex_type = external_ti >= ti_map->min_itype[ti_info->source]; - if (is_complex_type) { - // search external type index - CV_TypeIndex internal_tpi_idx = pdb_type_index_map_search(ti_map, CV_TypeIndexSource_TPI, external_ti); - CV_TypeIndex internal_ipi_idx = pdb_type_index_map_search(ti_map, CV_TypeIndexSource_IPI, external_ti); - - // error checks - if (internal_tpi_idx == 0 && internal_ipi_idx == 0) { - lnk_invalid_path("unable to find match for external type index 0x%X", external_ti); - continue; - } - if (internal_tpi_idx != 0 && internal_ipi_idx != 0) { - lnk_invalid_path("both TPI and IPI matched for external type index 0x%X", external_ti); - continue; - } - - // rewrite index - CV_TypeIndex internal_ti = internal_tpi_idx ? internal_tpi_idx : internal_ipi_idx; - *ti_ptr = internal_ti; - } - } - - temp_end(temp); - } - } - - // 5. push types to hash table on main thread - for (U64 source_idx = 0; source_idx < ArrayCount(leaf_list_arr); source_idx += 1) { - PDB_TypeServer *type_server = type_server_arr[source_idx]; - CV_LeafList list = leaf_list_arr[source_idx]; - PDB_TypeLeaf *leaf_arr = leaf_arr_arr[source_idx]; - U64 leaf_idx = 0; - for (CV_LeafNode *node = list.first; node != 0; node = node->next, leaf_idx += 1) { - CV_Leaf *external_leaf = &node->data; - - // move patched type data - PDB_TypeLeaf *internal_leaf = leaf_arr + leaf_idx; - internal_leaf->kind = external_leaf->kind; - internal_leaf->data = push_str8_copy(type_server->arena, external_leaf->data); - - // push leaf to type server - pdb_type_server_push_(type_server, internal_leaf); - } - } - - scratch_end(scratch); - ProfEnd(); - return ti_map; -} -#endif - //////////////////////////////// internal PDB_InfoContext * @@ -1491,6 +1363,7 @@ pdb_info_build_src_header_block(PDB_InfoContext *info, MSF_Context *msf) MemoryZeroStruct(&src_header.pad); // write to stream + msf_stream_reserve(msf, src_header_block_sn, safe_cast_u32(src_header_stream_size)); B32 is_header_written = msf_stream_write_struct(msf, src_header_block_sn, &src_header); B32 is_hash_table_written = msf_stream_write_string(msf, src_header_block_sn, hash_table_data); AssertAlways(is_header_written); @@ -1710,8 +1583,7 @@ internal void gsi_write_build_result(TP_Context *tp, PDB_GsiBuildResult build, MSF_Context *msf, - MSF_StreamNumber gsi_sn, - MSF_StreamNumber symbols_sn) + MSF_StreamNumber gsi_sn) { ProfBeginFunction(); @@ -1724,10 +1596,6 @@ gsi_write_build_result(TP_Context *tp, msf_stream_reserve(msf, gsi_sn, gsi_size); ProfEnd(); - ProfBeginV("Reserve %M for symbols", build.symbol_data.size); - msf_stream_reserve(msf, symbols_sn, build.symbol_data.size); - ProfEnd(); - ProfBegin("Write GSI header"); msf_stream_write_struct(msf, gsi_sn, &build.header); ProfEnd(); @@ -1744,10 +1612,6 @@ gsi_write_build_result(TP_Context *tp, msf_stream_write(msf, gsi_sn, &build.compressed_bucket_arr[0], compressed_bucket_arr_size); ProfEnd(); - ProfBegin("Write symbols [%M]", build.symbol_data.size); - msf_stream_write_string_parallel(tp, msf, symbols_sn, build.symbol_data); - ProfEnd(); - ProfEnd(); } @@ -2242,7 +2106,7 @@ THREAD_POOL_TASK_FUNC(gsi_serialize_symbols_task) } internal PDB_GsiBuildResult -gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, U64 symbol_data_base, B32 is_pub32, U64 msf_page_size) +gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, MSF_Context *msf, MSF_StreamNumber symbols_sn, B32 is_pub32) { ProfBeginFunction(); Temp scratch = scratch_begin(&arena,1); @@ -2250,6 +2114,7 @@ gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, U64 symbol_data_ ProfBegin("Serialize & Sort Symbols"); PDB_GsiSerializeSymbolsTask serial_task = {0}; + U64 symbol_data_base = msf_stream_get_pos(msf, symbols_sn); serial_task.symbol_data_base = symbol_data_base; serial_task.symbol_align = gsi->symbol_align; serial_task.bucket_arr = gsi->bucket_arr; @@ -2260,7 +2125,11 @@ gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, U64 symbol_data_ // prepare serial buffer U64 buffer_size = sum_array_u64(gsi->bucket_count, serial_task.bucket_size_arr); - serial_task.buffer = push_array_no_zero(arena, U8, buffer_size); + U64 symbol_data_end = symbol_data_base + buffer_size; + if (symbol_data_end > msf_stream_get_size(msf, symbols_sn)) { + AssertAlways(msf_stream_resize(msf, symbols_sn, safe_cast_u32(symbol_data_end))); + } + serial_task.buffer = msf_stream_data(msf, symbols_sn).str + symbol_data_base; serial_task.bucket_off_arr = push_array_copy_u64(scratch.arena, serial_task.bucket_size_arr, gsi->bucket_count); u64_array_counts_to_offsets(gsi->bucket_count, serial_task.bucket_off_arr); @@ -2275,6 +2144,7 @@ gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, U64 symbol_data_ // fill out sort records & serialize symbols TP_TaskFunc *serial_func = is_pub32 ? gsi_serialize_pub32 : gsi_serialize_symbols_task; tp_for_parallel(tp, 0, gsi->bucket_count, serial_func, &serial_task); + msf_stream_seek(msf, symbols_sn, safe_cast_u32(symbol_data_end)); ProfEnd(); @@ -2325,7 +2195,6 @@ gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, U64 symbol_data_ result.compressed_bucket_count = compressed_offset_count; result.compressed_bucket_arr = compressed_offset_arr; result.total_hash_size = sizeof(header) + header.hash_record_arr_size + header.bucket_data_size; - result.symbol_data = str8(serial_task.buffer, buffer_size); scratch_end(scratch); ProfEnd(); @@ -2338,9 +2207,8 @@ gsi_build(TP_Context *tp, PDB_GsiContext *gsi, MSF_Context *msf, MSF_StreamNumbe ProfBeginFunction(); Temp scratch = scratch_begin(0,0); - U64 symbol_data_base = msf_stream_get_pos(msf, symbols_sn); - PDB_GsiBuildResult build = gsi_build_ex(tp, scratch.arena, gsi, symbol_data_base, /* is_pub32: */ 0, msf->page_size); - gsi_write_build_result(tp, build, msf, sn, symbols_sn); + PDB_GsiBuildResult build = gsi_build_ex(tp, scratch.arena, gsi, msf, symbols_sn, /* is_pub32: */ 0); + gsi_write_build_result(tp, build, msf, sn); scratch_end(scratch); ProfEnd(); @@ -2471,8 +2339,7 @@ psi_build(TP_Context *tp, PDB_PsiContext *psi, MSF_Context *msf, MSF_StreamNumbe ProfBeginFunction(); Temp scratch = scratch_begin(0,0); - U64 symbol_data_base = msf_stream_get_pos(msf, symbols_sn); - PDB_GsiBuildResult gsi_build = gsi_build_ex(tp, scratch.arena, psi->gsi, symbol_data_base, /* is_pub32: */ 1, msf->page_size); + PDB_GsiBuildResult gsi_build = gsi_build_ex(tp, scratch.arena, psi->gsi, msf, symbols_sn, /* is_pub32: */ 1); ProfBegin("Address Map"); @@ -2493,8 +2360,9 @@ psi_build(TP_Context *tp, PDB_PsiContext *psi, MSF_Context *msf, MSF_StreamNumbe header.sec_count = 0; ProfBegin("MSF Write"); + msf_stream_reserve(msf, sn, safe_cast_u32(sizeof(header) + gsi_build.total_hash_size + addr_map_size)); msf_stream_write_struct(msf, sn, &header); - gsi_write_build_result(tp, gsi_build, msf, sn, symbols_sn); + gsi_write_build_result(tp, gsi_build, msf, sn); msf_stream_write_array(msf, sn, &addr_map[0], addr_map_count); ProfEnd(); @@ -2554,149 +2422,6 @@ dbi_alloc(COFF_MachineType machine, U32 age) return dbi; } -internal String8List * -dbi_open_file_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - - MSF_UInt file_info_pos = sizeof(PDB_DbiHeader) + - dbi_header->module_info_size + - dbi_header->sec_con_size + - dbi_header->sec_map_size; - msf_stream_seek(msf, sn, file_info_pos); - - U16 mod_count = msf_stream_read_u16(msf, sn); - U16 total_file_count16 = msf_stream_read_u16(msf, sn); - - CV_ModIndex *imod_array = push_array(scratch.arena, CV_ModIndex, mod_count); - msf_stream_read_array(msf, sn, &imod_array[0], mod_count); - - U16 *mod_file_count = push_array(scratch.arena, U16, mod_count); - msf_stream_read_array(msf, sn, &mod_file_count[0], mod_count); - - U64 total_file_count = 0; - for (U16 imod = 0; imod < mod_count; imod += 1) { - total_file_count += mod_file_count[imod]; - } - - U32 *file_name_offset_array = push_array(scratch.arena, U32, total_file_count); - msf_stream_read_array(msf, sn, &file_name_offset_array[0], total_file_count); - - U64 file_name_buffer_offset = sizeof(mod_count) + - sizeof(total_file_count16) + - sizeof(imod_array[0]) * mod_count + - sizeof(mod_file_count[0]) * mod_count + - sizeof(file_name_offset_array[0]) * total_file_count; - Assert(dbi_header->file_info_size >= file_name_buffer_offset); - U64 file_name_buffer_size = dbi_header->file_info_size - file_name_buffer_offset; - char *file_name_buffer = push_array(arena, char, file_name_buffer_size + 1); - msf_stream_read_array(msf, sn, &file_name_buffer[0], file_name_buffer_size); - - String8List *file_info = push_array(arena, String8List, mod_count + 1); - - U32 *file_name_offset_ptr = &file_name_offset_array[0]; - for (U64 mod_idx = 0; mod_idx < mod_count; ++mod_idx) { - String8List *file_list = &file_info[mod_idx]; - U16 file_count = mod_file_count[mod_idx]; - for (U16 ifile = 0; ifile < file_count; ifile += 1, file_name_offset_ptr += 1) { - Assert(*file_name_offset_ptr <= file_name_buffer_size); - String8 file_path = str8_cstring(file_name_buffer + *file_name_offset_ptr); - str8_list_push(arena, file_list, file_path); - } - } - - scratch_end(scratch); - ProfEnd(); - return file_info; -} - -internal PDB_DbiModuleList -dbi_open_module_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header, String8List *file_info) -{ - ProfBeginFunction(); - - PDB_DbiModuleList list = {0}; - - MSF_UInt module_info_pos = sizeof(PDB_DbiHeader); - msf_stream_seek(msf, sn, module_info_pos); - - MSF_UInt module_info_opl = module_info_pos + dbi_header->module_info_size; - while (msf_stream_get_pos(msf, sn) < module_info_opl) { - PDB_DbiCompUnitHeader header = {0}; - msf_stream_read_struct(msf, sn, &header); - String8 obj_path = msf_stream_read_string(arena, msf, sn); - String8 lib_path = msf_stream_read_string(arena, msf, sn); - msf_stream_align(msf, sn, PDB_MODULE_ALIGN); - - String8List source_file_list = {0}; - if (header.contribution.base.mod != CV_ModIndex_Invalid) { - source_file_list = file_info[header.contribution.base.mod]; - } - - PDB_DbiModule *mod = push_array(arena, PDB_DbiModule, 1); - mod->next = 0; - mod->sn = header.sn; - mod->imod = header.contribution.base.mod; - mod->sym_data_size = header.symbols_size; - mod->c11_data_size = header.c11_lines_size; - mod->c13_data_size = header.c13_lines_size; - mod->source_file_list = source_file_list; - mod->obj_path = obj_path; - mod->lib_path = lib_path; - mod->first_sc = header.contribution; - - SLLQueuePush(list.first, list.last, mod); - list.count += 1; - } - - ProfEnd(); - return list; -} - -internal PDB_DbiSCArray -dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header) -{ - ProfBeginFunction(); - - PDB_DbiSCArray sec_contribs = {0}; - - if (dbi_header->sec_con_size > sizeof(PDB_DbiSC)) { - Temp scratch = scratch_begin(&arena, 1); - - // seek to start of section contrib info - MSF_UInt sec_con_pos = sizeof(PDB_DbiHeader) + dbi_header->module_info_size; - msf_stream_seek(msf, sn, sec_con_pos); - - // read header - PDB_DbiSCVersion version = 0; - msf_stream_read_struct(msf, sn, &version); - - // parse contrib items - switch (version) { - case PDB_DbiSCVersion_1: { - U64 contrib_count = dbi_header->sec_con_size / sizeof(PDB_DbiSC); - sec_contribs.v = push_array_no_zero(arena, PDB_DbiSC, contrib_count); - sec_contribs.count = contrib_count; - sec_contribs.cap = contrib_count; - MSF_UInt sec_con_read = msf_stream_read_array(msf, sn, sec_contribs.v, contrib_count); - Assert(sec_con_read == sizeof(sec_contribs.v[0]) * contrib_count); - } break; - case PDB_DbiSCVersion_2: { - NotImplemented; - } break; - default: Assert(!"unknown section contrib version"); break; - } - - // have we exhausted sec-con bytes? - Assert(sec_con_pos + dbi_header->sec_con_size == msf_stream_get_pos(msf, sn)); - scratch_end(scratch); - } - - ProfEnd(); - return sec_contribs; -} - internal void dbi_build_section_header_stream(PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber sn) { @@ -3357,47 +3082,6 @@ dbi_module_push_section_contrib(PDB_DbiContext *dbi, ProfEnd(); } -internal String8 -dbi_module_read_symbol_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod) -{ - String8 symbol_data = str8(0,0); - if (mod->sn != MSF_INVALID_STREAM_NUMBER) { - B32 is_seek_ok = msf_stream_seek(msf, mod->sn, 0); - if (is_seek_ok) { - symbol_data = msf_stream_read_block(arena, msf, mod->sn, mod->sym_data_size); - } - } - return symbol_data; -} - -internal String8 -dbi_module_read_c11_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod) -{ - String8 c11_data = str8(0,0); - if (mod->sn != MSF_INVALID_STREAM_NUMBER) { - MSF_UInt c11_data_pos = mod->sym_data_size; - B32 is_seek_ok = msf_stream_seek(msf, mod->sn, c11_data_pos); - if (is_seek_ok) { - c11_data = msf_stream_read_block(arena, msf, mod->sn, mod->c13_data_size); - } - } - return c11_data; -} - -internal String8 -dbi_module_read_c13_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod) -{ - String8 c13_data = str8(0,0); - if (mod->sn != MSF_INVALID_STREAM_NUMBER) { - MSF_UInt c13_data_pos = mod->sym_data_size + mod->c11_data_size; - B32 is_seek_ok = msf_stream_seek(msf, mod->sn, c13_data_pos); - if (is_seek_ok) { - c13_data = msf_stream_read_block(arena, msf, mod->sn, mod->c13_data_size); - } - } - return c13_data; -} - internal void dbi_push_section(PDB_DbiContext *dbi, COFF_SectionHeader *hdr) { @@ -3523,6 +3207,27 @@ pdb_build_gsi_psi(TP_Context *tp, PDB_Context *pdb) { PDB_DbiContext *dbi = pdb->dbi; + // Publics and globals share one symbol stream. Size both before either + // serializer borrows the backing so the second append does not copy it. + if (pdb->psi->gsi->symbol_count || pdb->gsi->symbol_count) { + Temp scratch = scratch_begin(0, 0); + PDB_GsiContext *contexts[] = { pdb->psi->gsi, pdb->gsi }; + U64 size = 0; + for EachElement(i, contexts) { + PDB_GsiSerializeSymbolsTask task = { + .symbol_align = contexts[i]->symbol_align, + .bucket_arr = contexts[i]->bucket_arr, + .bucket_size_arr = push_array(scratch.arena, U64, contexts[i]->bucket_count), + }; + tp_for_parallel(tp, 0, contexts[i]->bucket_count, gsi_size_buckets_task, &task); + size += sum_array_u64(contexts[i]->bucket_count, task.bucket_size_arr); + } + if (dbi->symbols_sn == MSF_INVALID_STREAM_NUMBER) { dbi->symbols_sn = msf_stream_alloc(pdb->msf); } + AssertAlways(msf_stream_resize(pdb->msf, dbi->symbols_sn, safe_cast_u32(size))); + msf_stream_seek_start(pdb->msf, dbi->symbols_sn); + scratch_end(scratch); + } + if (pdb->psi->gsi->symbol_count) { if (dbi->publics_sn == MSF_INVALID_STREAM_NUMBER) { dbi->publics_sn = msf_stream_alloc(pdb->msf); } if (dbi->symbols_sn == MSF_INVALID_STREAM_NUMBER) { dbi->symbols_sn = msf_stream_alloc(pdb->msf); } diff --git a/src/linker/pdb_ext/pdb_builder.h b/src/linker/pdb_ext/pdb_builder.h index 016e4aeb..3de6e583 100644 --- a/src/linker/pdb_ext/pdb_builder.h +++ b/src/linker/pdb_ext/pdb_builder.h @@ -204,7 +204,6 @@ typedef struct PDB_GsiBuildResult U64 compressed_bucket_count; U32 *compressed_bucket_arr; U64 total_hash_size; - String8 symbol_data; } PDB_GsiBuildResult; typedef struct PDB_GsiSerializeSymbolsTask @@ -364,8 +363,8 @@ internal PDB_SrcError pdb_add_src(PDB_InfoContext *info, MSF_Context *msf, internal PDB_GsiContext * gsi_alloc(void); internal void gsi_build(TP_Context *tp, PDB_GsiContext *gsi, MSF_Context *msf, MSF_StreamNumber gsi_sn, MSF_StreamNumber symbols_sn); internal void gsi_release(PDB_GsiContext *gsi); -internal void gsi_write_build_result(TP_Context *tp, PDB_GsiBuildResult build, MSF_Context *msf, MSF_StreamNumber sn, MSF_StreamNumber symbols_sn); -internal PDB_GsiBuildResult gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, U64 symbol_data_base, B32 export_symbol_ptr_arr, U64 msf_page_size); +internal void gsi_write_build_result(TP_Context *tp, PDB_GsiBuildResult build, MSF_Context *msf, MSF_StreamNumber sn); +internal PDB_GsiBuildResult gsi_build_ex(TP_Context *tp, Arena *arena, PDB_GsiContext *gsi, MSF_Context *msf, MSF_StreamNumber symbols_sn, B32 is_pub32); internal U32 gsi_hash(PDB_GsiContext *gsi, String8 input); internal CV_SymbolNode * gsi_push(PDB_GsiContext *gsi, CV_Symbol *symbol); internal void gsi_push_many_arr(TP_Context *tp, PDB_GsiContext *gsi, U64 count, CV_SymbolNode **symbol_arr); @@ -391,16 +390,7 @@ internal PDB_DbiContext * dbi_alloc(COFF_MachineType machine, U32 age); internal void dbi_build(TP_Context *tp, PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber dbi_sn, CV_StringHashTable string_ht, B32 is_stripped); internal void dbi_release(PDB_DbiContext *dbi); internal PDB_DbiModule * dbi_push_module(PDB_DbiContext *dbi, String8 obj_path, String8 lib_path); -internal String8 dbi_module_read_symbol_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); -internal String8 dbi_module_read_c11_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); -internal String8 dbi_module_read_c13_data(Arena *arena, MSF_Context *msf, PDB_DbiModule *mod); internal void dbi_module_push_section_contrib(PDB_DbiContext *dbi, PDB_DbiModule *mod, ISectOff isect_off, U32 size, U32 data_crc, U32 reloc_crc, COFF_SectionFlags flags); -internal String8List * dbi_open_file_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); -internal PDB_DbiModuleList dbi_open_module_info(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header, String8List *file_info); -internal PDB_DbiSCArray dbi_open_sec_contrib(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); -internal PDB_StringTable dbi_open_ec_names(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); -internal void dbi_open_dbg_streams(MSF_StreamNumber *dbg_streams, MSF_Context *msf, MSF_StreamNumber sn, PDB_DbiHeader *dbi_header); -internal PDB_DbiSectionList dbi_open_section_headers(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn); internal void dbi_build_section_header_stream(PDB_DbiContext *dbi, MSF_Context *msf, MSF_StreamNumber sn); //////////////////////////////// @@ -450,7 +440,6 @@ internal U32 pdb_strtab_hash(PDB_StringTable *strtab, Strin // Type Server internal PDB_TypeServer * pdb_type_server_alloc(U64 bucket_count); -internal PDB_TypeServer * pdb_type_server_open_v80(MSF_Context *msf, MSF_StreamNumber sn, PDB_StringTable *strtab); internal void pdb_type_server_build(TP_Context *tp, PDB_TypeServer *ts, PDB_StringTable *strtab, MSF_Context *msf, MSF_StreamNumber sn); internal void pdb_type_server_release(PDB_TypeServer **serv_ptr); internal void pdb_type_server_push(PDB_TypeServer *ts, String8 raw_leaf); diff --git a/src/linker/pdb_ext/pdb_helpers.c b/src/linker/pdb_ext/pdb_helpers.c index 8bdef67e..061d96c5 100644 --- a/src/linker/pdb_ext/pdb_helpers.c +++ b/src/linker/pdb_ext/pdb_helpers.c @@ -25,25 +25,6 @@ pdb_read_bit_vector_string(String8 data, U64 offset, U32Array *bits_out) return read_size; } -internal U64 -pdb_read_bit_vector_msf(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, U32Array *bits_out) -{ - // peek word count - MSF_UInt pos = msf_stream_get_pos(msf, sn); - U32 word_count = msf_stream_read_u32(msf, sn); - msf_stream_seek(msf, sn, pos); - - // read out header + packed words - U64 buffer_size = sizeof(word_count) + word_count * sizeof(U32); - U8 *buffer = push_array(arena, U8, buffer_size); - MSF_UInt read_size = msf_stream_read(msf, sn, buffer, buffer_size); - Assert(read_size == buffer_size); - - // parse words - U64 parse_size = pdb_read_bit_vector_string(str8(buffer, buffer_size), 0, bits_out); - return parse_size; -} - internal B32 pdb_write_bit_vector(MSF_Context *msf, MSF_StreamNumber sn, B32 *flag_array, U64 flag_count) { @@ -86,4 +67,3 @@ pdb_get_bit_vector_size(U32 bucket_count) return result; } - diff --git a/src/linker/pdb_ext/pdb_helpers.h b/src/linker/pdb_ext/pdb_helpers.h index 007eb64f..72363bc8 100644 --- a/src/linker/pdb_ext/pdb_helpers.h +++ b/src/linker/pdb_ext/pdb_helpers.h @@ -7,8 +7,6 @@ internal U32 pdb_hash_v1(String8 data); internal U32 pdb_hash_udt(CV_UDTInfo udt_info, String8 data); internal U64 pdb_read_bit_vector_string(String8 data, U64 offset, U32Array *bits_out); -internal U64 pdb_read_bit_vector_msf(Arena *arena, MSF_Context *msf, MSF_StreamNumber sn, U32Array *bits_out); internal B32 pdb_write_bit_vector(MSF_Context *msf, MSF_StreamNumber sn, B32 *flag_array, U64 flag_count); internal U64 pdb_get_bit_vector_size(U32 bucket_count); - diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 8622aec9..63fde26f 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -7779,7 +7779,7 @@ TEST(cyclic_type) T_Ok(is_cycle_detected); } -TEST(get_msf_stream_pages) +TEST(msf_contiguous_stream) { MSF_Context *msf = msf_alloc(MSF_DEFAULT_PAGE_SIZE, MSF_DEFAULT_FPM); @@ -7792,44 +7792,107 @@ TEST(get_msf_stream_pages) MemorySet(test, 0xca, stream_size/2); MemorySet(test + stream_size/2, 0xbe, stream_size/2); - String8List stream_data = msf_data_from_sn(arena, msf, sn); - T_Ok(stream_data.total_size == stream_size); - T_Ok(stream_data.node_count == 12); + String8 stream_data = msf_stream_data(msf, sn); + T_Ok(stream_data.size == stream_size); + MemoryCopy(stream_data.str, test, stream_size); - String8Array a = str8_array_from_list(arena, &stream_data); - T_Ok(a.v[0].size == 0xffd000); - T_Ok(a.v[1].size == 0xffe000); - T_Ok(a.v[2].size == 0xffe000); - T_Ok(a.v[3].size == 0xffe000); - T_Ok(a.v[4].size == 0xffe000); - T_Ok(a.v[5].size == 0xffe000); - T_Ok(a.v[6].size == 0xffe000); - T_Ok(a.v[7].size == 0xffd000); - T_Ok(a.v[8].size == 0x1000); - T_Ok(a.v[9].size == 0xffe000); - T_Ok(a.v[10].size == 0x613000); - T_Ok(a.v[11].size == 1); - - String8Node buf = *stream_data.first; - U64 buf_pos = 0; - str8_buffer_write(&buf, &buf_pos, str8(test, stream_size)); - - String8 cmp = msf_stream_read_block(arena, msf, sn, stream_size); + // Physical pages alias the one contiguous construction allocation, even + // across reserved FPM slots and bitmap intervals. + MSF_Stream *stream = msf_find_stream(msf, sn); + U64 offset = 0; + for EachNode(page, MSF_PageNode, stream->page_list.first) { + T_Ok(msf_data_from_pn(msf, page->pn).str == stream_data.str + offset); + offset += msf->page_size; + } + MSF_PageNumber first_pn = stream->page_list.first->pn; + MSF_PageNumber last_pn = stream->page_list.last->pn; + U64 before_build = arena_pos(msf->arena); + String8 saved = {0}; + T_Ok(msf_save_arena(arena, msf, &saved) == MSF_Error_Ok); + // Building adds metadata, not another 150 MiB payload allocation. + T_Ok(arena_pos(msf->arena) - before_build < MB(1)); + T_Ok(stream->data == stream_data.str); + T_Ok(stream->page_list.first->pn == first_pn); + T_Ok(stream->page_list.last->pn == last_pn); + MSF_Parsed *parsed = msf_parsed_from_data(arena, saved); + T_Ok(parsed != 0); + String8 cmp = msf_data_from_stream(parsed, sn); T_Ok(cmp.size == stream_size); T_Ok(MemoryCompare(cmp.str, test, stream_size) == 0); } { MSF_StreamNumber sn = msf_stream_alloc_ex(msf, 1); - String8List stream_data = msf_data_from_sn(arena, msf, sn); - T_Ok(stream_data.node_count == 1); - T_Ok(stream_data.total_size == 1); - T_Ok(stream_data.first->string.size == 1); + String8 stream_data = msf_stream_data(msf, sn); + T_Ok(stream_data.size == 1); } msf_release(msf); } +TEST(msf_stream_growth_and_page_references) +{ + MSF_Context *msf = msf_alloc(512, MSF_FPM1); + U64 P = msf->page_size; + MSF_StreamNumber a = msf_stream_alloc_ex(msf, P + 3); + MSF_StreamNumber b = msf_stream_alloc_ex(msf, P); + MSF_StreamNumber empty = msf_stream_alloc(msf); + MSF_Stream *stream = msf_find_stream(msf, a); + MSF_PageNumber first_pn = stream->page_list.first->pn; + MSF_PageNumber second_pn = stream->page_list.last->pn; + MSF_PageNumber b_pn = msf_find_stream(msf, b)->page_list.first->pn; + MemorySet(stream->data, 0x5a, stream->size); + + T_Ok(msf_stream_resize(msf, a, 3 * P + 7)); + T_Ok(stream->page_list.first->pn == first_pn); + T_Ok(stream->page_list.first->next->pn == second_pn); + T_Ok(stream->page_list.first->next->next->pn != second_pn + 1); + String8 data = msf_stream_data(msf, a); + for EachIndex(i, P + 3) { T_Ok(data.str[i] == 0x5a); } + for (U64 i = P + 3; i < data.size; i += 1) { T_Ok(data.str[i] == 0); } + + // Store a page reference before build and patch a field crossing the + // boundary between two nonadjacent physical output pages. + memory_write32(msf_stream_data(msf, b).str, first_pn); + U8 patch[] = {1, 2, 3, 4, 5, 6, 7, 8}; + MemoryCopy(data.str + 2 * P - 3, patch, sizeof(patch)); + String8 expected = push_str8_copy(arena, data); + + // Reservation assigns identities but does not extend the logical stream. + T_Ok(msf_stream_reserve(msf, empty, 2 * P)); + T_Ok(msf_stream_get_size(msf, empty) == 0); + String8 saved = {0}; + T_Ok(msf_save_arena(arena, msf, &saved) == MSF_Error_Ok); + MSF_RawStreamTable *table = msf_raw_stream_table_from_data(arena, saved); + T_Ok(table->streams[a].u.page_indices_u32[0] == first_pn); + T_Ok(table->streams[a].u.page_indices_u32[1] == second_pn); + T_Ok(table->streams[empty].page_count == 0); + T_Ok(str8_match(msf_data_from_stream_number(arena, saved, table, a), expected, 0)); + String8 b_data = msf_data_from_stream_number(arena, saved, table, b); + T_Ok(memory_read32(b_data.str) == first_pn); + + // Removing a stream leaves a directory hole; later stream numbers remain + // stable, and its physical page can be reused without stale payload bytes. + T_Ok(msf_stream_free(msf, b)); + MSF_StreamNumber c = msf_stream_alloc_ex(msf, 1); + T_Ok(c > empty); + T_Ok(msf_find_stream(msf, c)->page_list.first->pn == b_pn); + T_Ok(msf_stream_data(msf, c).str[0] == 0); + T_Ok(msf_stream_write_u8(msf, c, 0x71)); + T_Ok(msf_stream_resize(msf, a, P + 1)); + T_Ok(msf_save_arena(arena, msf, &saved) == MSF_Error_Ok); + table = msf_raw_stream_table_from_data(arena, saved); + T_Ok(table->stream_count == c + 1); + T_Ok(table->streams[b].size == 0); + T_Ok(table->streams[empty].size == 0); + T_Ok(str8_match(msf_data_from_stream_number(arena, saved, table, a), str8_prefix(expected, P + 1), 0)); + T_Ok(msf_data_from_stream_number(arena, saved, table, c).str[0] == 0x71); + // The final partial page is padded, not populated with truncated bytes. + U8 *tail = saved.str + (U64)second_pn * P; + for (U64 i = 1; i < P; i += 1) { T_Ok(tail[i] == 0); } + msf_release(msf); +} + TEST(msf_header_matches_saved_extent) { MSF_UInt page_sizes[] = {512, KB(4)}; @@ -7891,9 +7954,9 @@ TEST(msf_header_matches_saved_extent) // Free the low pages so metadata can reuse them while the payload remains at // high page numbers. Unused pages below EOF exercise sparse MSF page layouts. msf_free_pages(msf, &gap); - T_Ok(msf_build(msf) == MSF_Error_OK); - T_Ok(msf->root_page_list.last->pn < last_pn); - T_Ok(msf->st_page_list.last->pn < last_pn); + T_Ok(msf_build(msf) == MSF_Error_Ok); + T_Ok(msf->root.page_list.last->pn < last_pn); + T_Ok(msf->stream_table.page_list.last->pn < last_pn); // validate that last page was assigned to the payload U64 save_size = msf_get_save_size(msf); @@ -7907,6 +7970,31 @@ TEST(msf_header_matches_saved_extent) MSF_Header70 *header = (MSF_Header70 *)saved.str; T_Ok((U64)header->page_count * header->page_size == saved.size); + // Validate the serialized allocation bitmap, including free holes + // and the reserved slots at every physical FPM interval. + U8 *used = push_array(arena, U8, header->page_count); + used[0] = 1; + for (U64 pn = 0; pn < header->page_count; pn += 1) { + if (pn % P == MSF_FPM0 || pn % P == MSF_FPM1) { used[pn] = 1; } + } + for EachNode(page, MSF_PageNode, msf->root.page_list.first) { used[page->pn] = 1; } + for EachNode(page, MSF_PageNode, msf->stream_table.page_list.first) { used[page->pn] = 1; } + for EachNode(page, MSF_PageNode, msf_find_stream(msf, sn)->page_list.first) { used[page->pn] = 1; } + for (U64 pn = 0; pn < header->page_count; pn += 1) { + U64 fpm_pn = (pn / (P * 8)) * P + header->active_fpm; + U64 bit = pn % (P * 8); + U8 free_bit = (saved.str[fpm_pn * P + bit / 8] >> (bit % 8)) & 1; + T_Ok(free_bit == !used[pn]); + } + // CDB/DIA reject an otherwise readable PDB if the final bitmap + // marks future reserved FPM slots beyond EOF as allocated. + U64 last_fpm_pn = ((header->page_count - 1) / (P * 8)) * P + header->active_fpm; + U64 end_bit = ((U64)header->page_count - 1) % (P * 8) + 1; + for (U64 bit = end_bit; bit < P * 8; bit += 1) { + U8 free_bit = (saved.str[last_fpm_pn * P + bit / 8] >> (bit % 8)) & 1; + T_Ok(free_bit == 1); + } + // match internal page data list to the serialized file bytes String8List pages = msf_get_page_data_nodes(arena, msf); T_Ok(str8_match(saved, str8_list_join(arena, &pages, 0), 0)); @@ -7952,7 +8040,7 @@ data_from_pdb(Arena *arena, PDB_Context *pdb) TP_Arena *tp_arena = tp_arena_alloc(tp); pdb_build(tp, tp_arena, pdb, (CV_StringHashTable){0}, 1, 0, 0); - AssertAlways(msf_build(pdb->msf) == MSF_Error_OK); + AssertAlways(msf_build(pdb->msf) == MSF_Error_Ok); String8List raw_msf_list = msf_get_page_data_nodes(arena, pdb->msf); AssertAlways(t_write_file_list(str8_lit("test.pdb"), raw_msf_list)); From f9950ef4e9be6dd814df542ef2e2449d2713b0dc Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 21 Sep 2026 14:05:13 -0700 Subject: [PATCH 172/251] Revert "x64 does not belong in base - base is intended to be things we can assume everywhere, which explicitly disqualifies x64 by definition." This reverts commit 4e4639c2a04348a981037cf6e83756f49f22c506. --- src/base/base_core.h | 1 - src/base/base_inc.c | 3 ++ src/base/base_inc.h | 1 + src/demon/generated/demon.meta.c | 35 +++++++++++++++++ src/demon/generated/demon.meta.h | 58 +++++++++++++++++++++++++++++ src/linux/base/linux_base.c | 54 +++++++++++++-------------- src/linux/base/linux_base.h | 21 ++++------- src/radbin/radbin_main.c | 2 - src/raddbg/raddbg_main.c | 4 +- src/third_party/radsort/radsort.h | 2 +- src/torture/torture_main.c | 2 - src/x64/x64.h | 62 +++++++++++++++---------------- 12 files changed, 164 insertions(+), 81 deletions(-) diff --git a/src/base/base_core.h b/src/base/base_core.h index 2f2c4c48..580089af 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -21,7 +21,6 @@ #define STB_SPRINTF_DECORATE(name) raddbg_##name #define STB_SPRINTF_STATIC #include "third_party/stb/stb_sprintf.h" -#include "third_party/radsort/radsort.h" //////////////////////////////// //~ rjf: Codebase Keywords diff --git a/src/base/base_inc.c b/src/base/base_inc.c index 5a102437..1cff7bf2 100644 --- a/src/base/base_inc.c +++ b/src/base/base_inc.c @@ -30,6 +30,9 @@ #include "base_test.c" #include "base_entry_point.c" +// TODO: move x64 to base layer +#include "x64/x64.c" + #if OS_WINDOWS # include "win32/base/win32_base.c" #elif OS_LINUX diff --git a/src/base/base_inc.h b/src/base/base_inc.h index f83f8858..3aa43a74 100644 --- a/src/base/base_inc.h +++ b/src/base/base_inc.h @@ -32,6 +32,7 @@ #include "base_test.h" #include "base_entry_point.h" +#include "x64/x64.h" #if OS_WINDOWS # include "win32/base/win32_base.h" #elif OS_LINUX diff --git a/src/demon/generated/demon.meta.c b/src/demon/generated/demon.meta.c index 66404aeb..4569dfb2 100644 --- a/src/demon/generated/demon.meta.c +++ b/src/demon/generated/demon.meta.c @@ -39,5 +39,40 @@ str8_lit_comp("MemoryExecute"), str8_lit_comp("CppThrow"), }; +String8 dmn_event_kind_string_table[22] = +{ +str8_lit_comp("Null"), +str8_lit_comp("Error"), +str8_lit_comp("HandshakeComplete"), +str8_lit_comp("CreateProcess"), +str8_lit_comp("ExitProcess"), +str8_lit_comp("CreateThread"), +str8_lit_comp("ExitThread"), +str8_lit_comp("LoadModule"), +str8_lit_comp("UnloadModule"), +str8_lit_comp("ModuleDebugInfo"), +str8_lit_comp("Breakpoint"), +str8_lit_comp("Trap"), +str8_lit_comp("SingleStep"), +str8_lit_comp("Exception"), +str8_lit_comp("Halt"), +str8_lit_comp("Memory"), +str8_lit_comp("DebugString"), +str8_lit_comp("SetThreadName"), +str8_lit_comp("SetThreadColor"), +str8_lit_comp("SetBreakpoint"), +str8_lit_comp("UnsetBreakpoint"), +str8_lit_comp("SetVAddrRangeNote"), +}; + +String8 dmn_exception_kind_string_table[5] = +{ +str8_lit_comp("Null"), +str8_lit_comp("MemoryRead"), +str8_lit_comp("MemoryWrite"), +str8_lit_comp("MemoryExecute"), +str8_lit_comp("CppThrow"), +}; + C_LINKAGE_END diff --git a/src/demon/generated/demon.meta.h b/src/demon/generated/demon.meta.h index bbbfc943..153e238d 100644 --- a/src/demon/generated/demon.meta.h +++ b/src/demon/generated/demon.meta.h @@ -62,9 +62,67 @@ DMN_ExceptionKind_CppThrow, DMN_ExceptionKind_COUNT, } DMN_ExceptionKind; +typedef enum DMN_EventKind +{ +DMN_EventKind_Null, +DMN_EventKind_Error, +DMN_EventKind_HandshakeComplete, +DMN_EventKind_CreateProcess, +DMN_EventKind_ExitProcess, +DMN_EventKind_CreateThread, +DMN_EventKind_ExitThread, +DMN_EventKind_LoadModule, +DMN_EventKind_UnloadModule, +DMN_EventKind_ModuleDebugInfo, +DMN_EventKind_Breakpoint, +DMN_EventKind_Trap, +DMN_EventKind_SingleStep, +DMN_EventKind_Exception, +DMN_EventKind_Halt, +DMN_EventKind_Memory, +DMN_EventKind_DebugString, +DMN_EventKind_SetThreadName, +DMN_EventKind_SetThreadColor, +DMN_EventKind_SetBreakpoint, +DMN_EventKind_UnsetBreakpoint, +DMN_EventKind_SetVAddrRangeNote, +DMN_EventKind_COUNT, +} DMN_EventKind; + +typedef enum DMN_ErrorKind +{ +DMN_ErrorKind_Null, +DMN_ErrorKind_NotAttached, +DMN_ErrorKind_UnexpectedFailure, +DMN_ErrorKind_InvalidHandle, +DMN_ErrorKind_COUNT, +} DMN_ErrorKind; + +typedef enum DMN_MemoryEventKind +{ +DMN_MemoryEventKind_Null, +DMN_MemoryEventKind_Commit, +DMN_MemoryEventKind_Reserve, +DMN_MemoryEventKind_Decommit, +DMN_MemoryEventKind_Release, +DMN_MemoryEventKind_COUNT, +} DMN_MemoryEventKind; + +typedef enum DMN_ExceptionKind +{ +DMN_ExceptionKind_Null, +DMN_ExceptionKind_MemoryRead, +DMN_ExceptionKind_MemoryWrite, +DMN_ExceptionKind_MemoryExecute, +DMN_ExceptionKind_CppThrow, +DMN_ExceptionKind_COUNT, +} DMN_ExceptionKind; + C_LINKAGE_BEGIN extern String8 dmn_event_kind_string_table[22]; extern String8 dmn_exception_kind_string_table[5]; +extern String8 dmn_event_kind_string_table[22]; +extern String8 dmn_exception_kind_string_table[5]; C_LINKAGE_END diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index e95e2be8..fec7a0da 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -285,7 +285,7 @@ shared_memory_alloc(U64 size, String8 name) SharedMemory result = { max_U64 }; if(size == 0 || size > max_S64) { return result; } - + int id; if(name.size == 0) { @@ -304,7 +304,7 @@ shared_memory_alloc(U64 size, String8 name) if(ftruncate(id, size) == 0) { result.u64[0] = (U64)id; } else { close(id); } } - + return result; } @@ -1107,15 +1107,15 @@ lnx_memory_fault_thread(void *unused) { LNX_MemoryFaultRequest *request = 0; ssize_t count = LNX_RETRY_ON_EINTR(read(lnx_state.demand_memory.requests[0], &request, sizeof(request))); - + if(count == 0) { break; } if(count != sizeof(request)) { _exit(1); } if(request == 0) { break; } - + lnx_in_memory_fault_callback = 1; B32 handled = lnx_state.demand_memory.fault(request->address, lnx_state.demand_memory.user_data); lnx_in_memory_fault_callback = 0; - + U32 *completion = &request->result; ins_atomic_u32_eval_assign(completion, handled ? 2 : 1); syscall(SYS_futex, completion, FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); @@ -1148,25 +1148,25 @@ internal B32 lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) { LNX_DemandMemory *memory = &lnx_state.demand_memory; - + #if ARCH_X64 || ARCH_X86 if(sig != SIGSEGV || info == 0 || context == 0 || - (info->si_code != SEGV_ACCERR && info->si_code != SEGV_MAPERR) || - lnx_in_memory_fault_callback || - ins_atomic_u32_eval(&memory->active) == 0) + (info->si_code != SEGV_ACCERR && info->si_code != SEGV_MAPERR) || + lnx_in_memory_fault_callback || + ins_atomic_u32_eval(&memory->active) == 0) { return 0; } - + ucontext_t *state = context; - if((state->uc_mcontext.gregs[REG_TRAPNO] != X64_ExceptionCode_PF) || + if((state->uc_mcontext.gregs[REG_TRAPNO] != X64_Exception_PF) || (state->uc_mcontext.gregs[REG_ERR] & X64_PageFaultError_Write) || (state->uc_mcontext.gregs[REG_ERR] & X64_PageFaultError_InstructionFetch) || memory->owner_pid != getpid()) { return 0; } - + int saved_errno = errno; LNX_MemoryFaultRequest request = { .address = info->si_addr }; LNX_MemoryFaultRequest *ptr = &request; @@ -1176,7 +1176,7 @@ lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) errno = saved_errno; return 0; } - + // wait for the page fault handler to complete while(!ins_atomic_u32_eval(&request.result)) { @@ -1185,10 +1185,10 @@ lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) _exit(1); } } - + B32 handled = request.result == 2; errno = saved_errno; - + return handled; #else NotImplemented; @@ -1210,7 +1210,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) { return 0; } - + memory->fault = func; memory->user_data = user_data; memory->owner_pid = getpid(); @@ -1229,7 +1229,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) } memory->workers[memory->worker_count++] = worker; } - + ins_atomic_u32_eval_assign(&memory->active, 1); } else if(memory->worker_count) @@ -1238,7 +1238,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) { return 0; } - + ins_atomic_u32_eval_assign(&memory->active, 0); return lnx_memory_fault_workers_stop(memory); } @@ -1605,7 +1605,7 @@ process_launch(ProcessLaunchParams *params) posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP); posix_spawnattr_setpgroup(&attr, 0); } - + // package argv char **argv = push_array(scratch.arena, char *, params->cmd_line.node_count + 1); { @@ -1822,7 +1822,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) { // page fault handler if(lnx_dispatch_memory_read_fault(sig, info, arg)) { return; } - + // crash handler { local_persist volatile U32 first = 0; @@ -1833,10 +1833,10 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) sleep(UINT32_MAX); } } - + local_persist void *ips[4096]; int ips_count = backtrace(ips, ArrayCount(ips)); - + fprintf(stderr, "A fatal signal was received: %s (%d). The process is terminating.\n", strsignal(sig), sig); fprintf(stderr, "Create a new issue with this report at %s.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); fprintf(stderr, "Callstack:\n"); @@ -1844,7 +1844,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) { Dl_info info = {0}; dladdr(ips[i], &info); - + char cmd[2048]; snprintf(cmd, sizeof(cmd), "llvm-symbolizer --relative-address -f -e %s %lu", info.dli_fname, (unsigned long)ips[i] - (unsigned long)info.dli_fbase); FILE *f = popen(cmd, "r"); @@ -1858,12 +1858,12 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) String8 module = str8_skip_last_slash(str8_cstring(info.dli_fname)); String8 file = str8_skip_last_slash(str8_cstring_capped(file_name, file_name + sizeof(file_name))); if(file.size > 0) file.size -= 1; - + B32 no_func = str8_match(func, str8_lit("??"), StringMatchFlag_RightSideSloppy); B32 no_file = str8_match(file, str8_lit("??"), StringMatchFlag_RightSideSloppy); if(no_func) { func = str8_zero(); } if(no_file) { file = str8_zero(); } - + fprintf(stderr, "%ld. [0x%016lx] %.*s%s%.*s %.*s\n", i+1, (unsigned long)ips[i], (int)module.size, module.str, (!no_func || !no_file) ? ", " : "", (int)func.size, func.str, (int)file.size, file.str); } pclose(f); @@ -1874,7 +1874,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) } } fprintf(stderr, "\nVersion: %s%s\n\n", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); - + _exit(1); } } @@ -1902,7 +1902,7 @@ main(int argc, char **argv) U64 pages = (U64)sysconf(_SC_PHYS_PAGES); U64 page_size = (U64)sysconf(_SC_PAGESIZE); U64 commit_size = (U64)sysconf(_SC_AVPHYS_PAGES); - + SystemInfo *info = &lnx_state.system_info; info->logical_processor_count = (U32)get_nprocs(); info->page_size = (U64)getpagesize(); diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index b5cce089..87998e59 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -35,13 +35,6 @@ #include #include -// NOTE(rjf): this is required because we need to use architecture-specific code for "base" -// OS functionality - we may want to reevaluate if such things should be in base - but for -// now just pulling in the whole x64 layer, just for linux. -#if ARCH_X64 || ARCH_X86 -# include "x64/x64.h" -#endif - pid_t gettid(void); int pthread_setname_np(pthread_t thread, const char *name); int pthread_getname_np(pthread_t thread, char *name, size_t size); @@ -53,13 +46,13 @@ typedef struct timespec timespec; //~ rjf: Linux Call Interruption Retry Helper #define LNX_RETRY_ON_EINTR(expr) \ -(__extension__({ \ -__typeof__(expr) __ret; \ -do { \ -__ret = (expr); \ -} while ((__ret == -1) && errno == EINTR); \ -__ret; \ -})) + (__extension__({ \ + __typeof__(expr) __ret; \ + do { \ + __ret = (expr); \ + } while ((__ret == -1) && errno == EINTR); \ + __ret; \ + })) //////////////////////////////// diff --git a/src/radbin/radbin_main.c b/src/radbin/radbin_main.c index 3c1fb526..47e351dd 100644 --- a/src/radbin/radbin_main.c +++ b/src/radbin/radbin_main.c @@ -12,7 +12,6 @@ //- rjf: [h] #include "base/base_inc.h" -#include "x64/x64.h" #include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" #include "rdi/rdi_local.h" @@ -47,7 +46,6 @@ //- rjf: [c] #include "base/base_inc.c" -#include "x64/x64.c" #include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" #include "rdi/rdi_local.c" diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index abe98430..b2307cb8 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -376,8 +376,8 @@ //- rjf: [h] #include "base/base_inc.h" -#include "x64/x64.h" #include "win32/win32_inc.h" +#include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" #include "artifact_cache/artifact_cache.h" #include "rdi/rdi_local.h" @@ -438,8 +438,8 @@ //- rjf: [c] #include "base/base_inc.c" -#include "x64/x64.c" #include "win32/win32_inc.c" +#include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" #include "artifact_cache/artifact_cache.c" #include "rdi/rdi_local.c" diff --git a/src/third_party/radsort/radsort.h b/src/third_party/radsort/radsort.h index 7cf3146e..dbfbd11c 100644 --- a/src/third_party/radsort/radsort.h +++ b/src/third_party/radsort/radsort.h @@ -137,7 +137,7 @@ radsortmovesize( sizeof( (start)[0] ) ), \ //=================================================================================================== // small heap sort - this sort is around 200 bytes compiled - can use directly when size is important -static RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper ) +internal RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper ) { void * left; void * right; diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 1b3a6915..e7fc5fb1 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -20,7 +20,6 @@ //////////////////////////////// #include "base/base_inc.h" -#include "x64/x64.h" #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" @@ -104,7 +103,6 @@ #include "torture.h" #include "base/base_inc.c" -#include "x64/x64.c" #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" diff --git a/src/x64/x64.h b/src/x64/x64.h index 2ee37845..7d9c1fd1 100644 --- a/src/x64/x64.h +++ b/src/x64/x64.h @@ -212,40 +212,39 @@ X64_DebugControlFlags; //////////////////////////////// //~ Exception Codes -typedef enum X64_ExceptionCode +typedef enum { - X64_ExceptionCode_DE = 0, // Divide error: division by zero or quotient overflow - X64_ExceptionCode_DB = 1, // Debug exception (single stepping, hardware breakpoint) - X64_ExceptionCode_NMI = 2, // Non-maskable interrupt - X64_ExceptionCode_BP = 3, // Breakpoint (int3) - X64_ExceptionCode_OF = 4, // Overflow - X64_ExceptionCode_BR = 5, // Bound range exceeded - X64_ExceptionCode_UD = 6, // Invalid opcode - X64_ExceptionCode_NM = 7, // Device not available - X64_ExceptionCode_DF = 8, // Double fault - X64_ExceptionCode_9 = 9, // Coprocessor segment overrun (obsolete) - X64_ExceptionCode_TS = 10, // Invalid task-state segment - X64_ExceptionCode_NP = 11, // Segment not present - X64_ExceptionCode_SS = 12, // Stack-segment fault - X64_ExceptionCode_GP = 13, // General-protection fault - X64_ExceptionCode_PF = 14, // Page fault - X64_ExceptionCode_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS - X64_ExceptionCode_MF = 16, // x87 floating-point exception - X64_ExceptionCode_AC = 17, // Alignment check - X64_ExceptionCode_MC = 18, // Machine check - X64_ExceptionCode_XF = 19, // SIMD floating-point exception - X64_ExceptionCode_VE = 20, // Virtualization exception - X64_ExceptionCode_CP = 21, // Control-protection exception - X64_ExceptionCode_Amd_HV = 28, // Hypervisor injection exception - X64_ExceptionCode_Amd_VC = 29, // VMM communication exception - X64_ExceptionCode_Amd_SX = 30, // Security exception -} -X64_ExceptionCode; + X64_Exception_DE = 0, // Divide error: division by zero or quotient overflow + X64_Exception_DB = 1, // Debug exception (single stepping, hardware breakpoint) + X64_Exception_NMI = 2, // Non-maskable interrupt + X64_Exception_BP = 3, // Breakpoint (int3) + X64_Exception_OF = 4, // Overflow + X64_Exception_BR = 5, // Bound range exceeded + X64_Exception_UD = 6, // Invalid opcode + X64_Exception_NM = 7, // Device not available + X64_Exception_DF = 8, // Double fault + X64_Exception_9 = 9, // Coprocessor segment overrun (obsolete) + X64_Exception_TS = 10, // Invalid task-state segment + X64_Exception_NP = 11, // Segment not present + X64_Exception_SS = 12, // Stack-segment fault + X64_Exception_GP = 13, // General-protection fault + X64_Exception_PF = 14, // Page fault + X64_Exception_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS + X64_Exception_MF = 16, // x87 floating-point exception + X64_Exception_AC = 17, // Alignment check + X64_Exception_MC = 18, // Machine check + X64_Exception_XF = 19, // SIMD floating-point exception + X64_Exception_VE = 20, // Virtualization exception + X64_Exception_CP = 21, // Control-protection exception + X64_Exception_Amd_HV = 28, // Hypervisor injection exception + X64_Exception_Amd_VC = 29, // VMM communication exception + X64_Exception_Amd_SX = 30, // Security exception +} X64_ExceptionCode; //////////////////////////////// //~ Page Fault Flags -typedef enum X64_PageFaultError +typedef enum { X64_PageFaultError_Protection = (1u << 0), X64_PageFaultError_Write = (1u << 1), @@ -254,8 +253,7 @@ typedef enum X64_PageFaultError X64_PageFaultError_InstructionFetch = (1u << 4), X64_PageFaultError_ProtectionKey = (1u << 5), X64_PageFaultError_ShadowStack = (1u << 6), -} -X64_PageFaultError; +} X64_PageFaultError; //////////////////////////////// //~ rjf: Generated @@ -282,4 +280,4 @@ internal X64_XSaveLayout x64_get_xsave_layout(U64 xcr0); internal void x64_set_debug_break(U64 *drs, U64 trap_idx, U64 addr, U64 size, X64_BreakpointType bp_type, X64_DebugBreakType break_type); -#endif // X64_H +#endif // BASE_X64_H From 6133e8f335f443b02f77a978af5408c739abab2c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 22 Sep 2026 03:28:55 -0700 Subject: [PATCH 173/251] Revert "Revert "x64 does not belong in base - base is intended to be things we can assume everywhere, which explicitly disqualifies x64 by definition."" This reverts commit f9950ef4e9be6dd814df542ef2e2449d2713b0dc. --- src/base/base_core.h | 1 + src/base/base_inc.c | 3 -- src/base/base_inc.h | 1 - src/demon/generated/demon.meta.c | 35 ----------------- src/demon/generated/demon.meta.h | 58 ----------------------------- src/linux/base/linux_base.c | 54 +++++++++++++-------------- src/linux/base/linux_base.h | 21 +++++++---- src/radbin/radbin_main.c | 2 + src/raddbg/raddbg_main.c | 4 +- src/third_party/radsort/radsort.h | 2 +- src/torture/torture_main.c | 2 + src/x64/x64.h | 62 ++++++++++++++++--------------- 12 files changed, 81 insertions(+), 164 deletions(-) diff --git a/src/base/base_core.h b/src/base/base_core.h index 580089af..2f2c4c48 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -21,6 +21,7 @@ #define STB_SPRINTF_DECORATE(name) raddbg_##name #define STB_SPRINTF_STATIC #include "third_party/stb/stb_sprintf.h" +#include "third_party/radsort/radsort.h" //////////////////////////////// //~ rjf: Codebase Keywords diff --git a/src/base/base_inc.c b/src/base/base_inc.c index 1cff7bf2..5a102437 100644 --- a/src/base/base_inc.c +++ b/src/base/base_inc.c @@ -30,9 +30,6 @@ #include "base_test.c" #include "base_entry_point.c" -// TODO: move x64 to base layer -#include "x64/x64.c" - #if OS_WINDOWS # include "win32/base/win32_base.c" #elif OS_LINUX diff --git a/src/base/base_inc.h b/src/base/base_inc.h index 3aa43a74..f83f8858 100644 --- a/src/base/base_inc.h +++ b/src/base/base_inc.h @@ -32,7 +32,6 @@ #include "base_test.h" #include "base_entry_point.h" -#include "x64/x64.h" #if OS_WINDOWS # include "win32/base/win32_base.h" #elif OS_LINUX diff --git a/src/demon/generated/demon.meta.c b/src/demon/generated/demon.meta.c index 4569dfb2..66404aeb 100644 --- a/src/demon/generated/demon.meta.c +++ b/src/demon/generated/demon.meta.c @@ -39,40 +39,5 @@ str8_lit_comp("MemoryExecute"), str8_lit_comp("CppThrow"), }; -String8 dmn_event_kind_string_table[22] = -{ -str8_lit_comp("Null"), -str8_lit_comp("Error"), -str8_lit_comp("HandshakeComplete"), -str8_lit_comp("CreateProcess"), -str8_lit_comp("ExitProcess"), -str8_lit_comp("CreateThread"), -str8_lit_comp("ExitThread"), -str8_lit_comp("LoadModule"), -str8_lit_comp("UnloadModule"), -str8_lit_comp("ModuleDebugInfo"), -str8_lit_comp("Breakpoint"), -str8_lit_comp("Trap"), -str8_lit_comp("SingleStep"), -str8_lit_comp("Exception"), -str8_lit_comp("Halt"), -str8_lit_comp("Memory"), -str8_lit_comp("DebugString"), -str8_lit_comp("SetThreadName"), -str8_lit_comp("SetThreadColor"), -str8_lit_comp("SetBreakpoint"), -str8_lit_comp("UnsetBreakpoint"), -str8_lit_comp("SetVAddrRangeNote"), -}; - -String8 dmn_exception_kind_string_table[5] = -{ -str8_lit_comp("Null"), -str8_lit_comp("MemoryRead"), -str8_lit_comp("MemoryWrite"), -str8_lit_comp("MemoryExecute"), -str8_lit_comp("CppThrow"), -}; - C_LINKAGE_END diff --git a/src/demon/generated/demon.meta.h b/src/demon/generated/demon.meta.h index 153e238d..bbbfc943 100644 --- a/src/demon/generated/demon.meta.h +++ b/src/demon/generated/demon.meta.h @@ -62,67 +62,9 @@ DMN_ExceptionKind_CppThrow, DMN_ExceptionKind_COUNT, } DMN_ExceptionKind; -typedef enum DMN_EventKind -{ -DMN_EventKind_Null, -DMN_EventKind_Error, -DMN_EventKind_HandshakeComplete, -DMN_EventKind_CreateProcess, -DMN_EventKind_ExitProcess, -DMN_EventKind_CreateThread, -DMN_EventKind_ExitThread, -DMN_EventKind_LoadModule, -DMN_EventKind_UnloadModule, -DMN_EventKind_ModuleDebugInfo, -DMN_EventKind_Breakpoint, -DMN_EventKind_Trap, -DMN_EventKind_SingleStep, -DMN_EventKind_Exception, -DMN_EventKind_Halt, -DMN_EventKind_Memory, -DMN_EventKind_DebugString, -DMN_EventKind_SetThreadName, -DMN_EventKind_SetThreadColor, -DMN_EventKind_SetBreakpoint, -DMN_EventKind_UnsetBreakpoint, -DMN_EventKind_SetVAddrRangeNote, -DMN_EventKind_COUNT, -} DMN_EventKind; - -typedef enum DMN_ErrorKind -{ -DMN_ErrorKind_Null, -DMN_ErrorKind_NotAttached, -DMN_ErrorKind_UnexpectedFailure, -DMN_ErrorKind_InvalidHandle, -DMN_ErrorKind_COUNT, -} DMN_ErrorKind; - -typedef enum DMN_MemoryEventKind -{ -DMN_MemoryEventKind_Null, -DMN_MemoryEventKind_Commit, -DMN_MemoryEventKind_Reserve, -DMN_MemoryEventKind_Decommit, -DMN_MemoryEventKind_Release, -DMN_MemoryEventKind_COUNT, -} DMN_MemoryEventKind; - -typedef enum DMN_ExceptionKind -{ -DMN_ExceptionKind_Null, -DMN_ExceptionKind_MemoryRead, -DMN_ExceptionKind_MemoryWrite, -DMN_ExceptionKind_MemoryExecute, -DMN_ExceptionKind_CppThrow, -DMN_ExceptionKind_COUNT, -} DMN_ExceptionKind; - C_LINKAGE_BEGIN extern String8 dmn_event_kind_string_table[22]; extern String8 dmn_exception_kind_string_table[5]; -extern String8 dmn_event_kind_string_table[22]; -extern String8 dmn_exception_kind_string_table[5]; C_LINKAGE_END diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index fec7a0da..e95e2be8 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -285,7 +285,7 @@ shared_memory_alloc(U64 size, String8 name) SharedMemory result = { max_U64 }; if(size == 0 || size > max_S64) { return result; } - + int id; if(name.size == 0) { @@ -304,7 +304,7 @@ shared_memory_alloc(U64 size, String8 name) if(ftruncate(id, size) == 0) { result.u64[0] = (U64)id; } else { close(id); } } - + return result; } @@ -1107,15 +1107,15 @@ lnx_memory_fault_thread(void *unused) { LNX_MemoryFaultRequest *request = 0; ssize_t count = LNX_RETRY_ON_EINTR(read(lnx_state.demand_memory.requests[0], &request, sizeof(request))); - + if(count == 0) { break; } if(count != sizeof(request)) { _exit(1); } if(request == 0) { break; } - + lnx_in_memory_fault_callback = 1; B32 handled = lnx_state.demand_memory.fault(request->address, lnx_state.demand_memory.user_data); lnx_in_memory_fault_callback = 0; - + U32 *completion = &request->result; ins_atomic_u32_eval_assign(completion, handled ? 2 : 1); syscall(SYS_futex, completion, FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); @@ -1148,25 +1148,25 @@ internal B32 lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) { LNX_DemandMemory *memory = &lnx_state.demand_memory; - + #if ARCH_X64 || ARCH_X86 if(sig != SIGSEGV || info == 0 || context == 0 || - (info->si_code != SEGV_ACCERR && info->si_code != SEGV_MAPERR) || - lnx_in_memory_fault_callback || - ins_atomic_u32_eval(&memory->active) == 0) + (info->si_code != SEGV_ACCERR && info->si_code != SEGV_MAPERR) || + lnx_in_memory_fault_callback || + ins_atomic_u32_eval(&memory->active) == 0) { return 0; } - + ucontext_t *state = context; - if((state->uc_mcontext.gregs[REG_TRAPNO] != X64_Exception_PF) || + if((state->uc_mcontext.gregs[REG_TRAPNO] != X64_ExceptionCode_PF) || (state->uc_mcontext.gregs[REG_ERR] & X64_PageFaultError_Write) || (state->uc_mcontext.gregs[REG_ERR] & X64_PageFaultError_InstructionFetch) || memory->owner_pid != getpid()) { return 0; } - + int saved_errno = errno; LNX_MemoryFaultRequest request = { .address = info->si_addr }; LNX_MemoryFaultRequest *ptr = &request; @@ -1176,7 +1176,7 @@ lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) errno = saved_errno; return 0; } - + // wait for the page fault handler to complete while(!ins_atomic_u32_eval(&request.result)) { @@ -1185,10 +1185,10 @@ lnx_dispatch_memory_read_fault(int sig, siginfo_t *info, void *context) _exit(1); } } - + B32 handled = request.result == 2; errno = saved_errno; - + return handled; #else NotImplemented; @@ -1210,7 +1210,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) { return 0; } - + memory->fault = func; memory->user_data = user_data; memory->owner_pid = getpid(); @@ -1229,7 +1229,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) } memory->workers[memory->worker_count++] = worker; } - + ins_atomic_u32_eval_assign(&memory->active, 1); } else if(memory->worker_count) @@ -1238,7 +1238,7 @@ memory_read_fault_handler_set(MemoryReadFaultFunction *func, void *user_data) { return 0; } - + ins_atomic_u32_eval_assign(&memory->active, 0); return lnx_memory_fault_workers_stop(memory); } @@ -1605,7 +1605,7 @@ process_launch(ProcessLaunchParams *params) posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP); posix_spawnattr_setpgroup(&attr, 0); } - + // package argv char **argv = push_array(scratch.arena, char *, params->cmd_line.node_count + 1); { @@ -1822,7 +1822,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) { // page fault handler if(lnx_dispatch_memory_read_fault(sig, info, arg)) { return; } - + // crash handler { local_persist volatile U32 first = 0; @@ -1833,10 +1833,10 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) sleep(UINT32_MAX); } } - + local_persist void *ips[4096]; int ips_count = backtrace(ips, ArrayCount(ips)); - + fprintf(stderr, "A fatal signal was received: %s (%d). The process is terminating.\n", strsignal(sig), sig); fprintf(stderr, "Create a new issue with this report at %s.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); fprintf(stderr, "Callstack:\n"); @@ -1844,7 +1844,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) { Dl_info info = {0}; dladdr(ips[i], &info); - + char cmd[2048]; snprintf(cmd, sizeof(cmd), "llvm-symbolizer --relative-address -f -e %s %lu", info.dli_fname, (unsigned long)ips[i] - (unsigned long)info.dli_fbase); FILE *f = popen(cmd, "r"); @@ -1858,12 +1858,12 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) String8 module = str8_skip_last_slash(str8_cstring(info.dli_fname)); String8 file = str8_skip_last_slash(str8_cstring_capped(file_name, file_name + sizeof(file_name))); if(file.size > 0) file.size -= 1; - + B32 no_func = str8_match(func, str8_lit("??"), StringMatchFlag_RightSideSloppy); B32 no_file = str8_match(file, str8_lit("??"), StringMatchFlag_RightSideSloppy); if(no_func) { func = str8_zero(); } if(no_file) { file = str8_zero(); } - + fprintf(stderr, "%ld. [0x%016lx] %.*s%s%.*s %.*s\n", i+1, (unsigned long)ips[i], (int)module.size, module.str, (!no_func || !no_file) ? ", " : "", (int)func.size, func.str, (int)file.size, file.str); } pclose(f); @@ -1874,7 +1874,7 @@ lnx_signal_handler(int sig, siginfo_t *info, void *arg) } } fprintf(stderr, "\nVersion: %s%s\n\n", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); - + _exit(1); } } @@ -1902,7 +1902,7 @@ main(int argc, char **argv) U64 pages = (U64)sysconf(_SC_PHYS_PAGES); U64 page_size = (U64)sysconf(_SC_PAGESIZE); U64 commit_size = (U64)sysconf(_SC_AVPHYS_PAGES); - + SystemInfo *info = &lnx_state.system_info; info->logical_processor_count = (U32)get_nprocs(); info->page_size = (U64)getpagesize(); diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index 87998e59..b5cce089 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -35,6 +35,13 @@ #include #include +// NOTE(rjf): this is required because we need to use architecture-specific code for "base" +// OS functionality - we may want to reevaluate if such things should be in base - but for +// now just pulling in the whole x64 layer, just for linux. +#if ARCH_X64 || ARCH_X86 +# include "x64/x64.h" +#endif + pid_t gettid(void); int pthread_setname_np(pthread_t thread, const char *name); int pthread_getname_np(pthread_t thread, char *name, size_t size); @@ -46,13 +53,13 @@ typedef struct timespec timespec; //~ rjf: Linux Call Interruption Retry Helper #define LNX_RETRY_ON_EINTR(expr) \ - (__extension__({ \ - __typeof__(expr) __ret; \ - do { \ - __ret = (expr); \ - } while ((__ret == -1) && errno == EINTR); \ - __ret; \ - })) +(__extension__({ \ +__typeof__(expr) __ret; \ +do { \ +__ret = (expr); \ +} while ((__ret == -1) && errno == EINTR); \ +__ret; \ +})) //////////////////////////////// diff --git a/src/radbin/radbin_main.c b/src/radbin/radbin_main.c index 47e351dd..3c1fb526 100644 --- a/src/radbin/radbin_main.c +++ b/src/radbin/radbin_main.c @@ -12,6 +12,7 @@ //- rjf: [h] #include "base/base_inc.h" +#include "x64/x64.h" #include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" #include "rdi/rdi_local.h" @@ -46,6 +47,7 @@ //- rjf: [c] #include "base/base_inc.c" +#include "x64/x64.c" #include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" #include "rdi/rdi_local.c" diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index b2307cb8..abe98430 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -376,8 +376,8 @@ //- rjf: [h] #include "base/base_inc.h" +#include "x64/x64.h" #include "win32/win32_inc.h" -#include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" #include "artifact_cache/artifact_cache.h" #include "rdi/rdi_local.h" @@ -438,8 +438,8 @@ //- rjf: [c] #include "base/base_inc.c" +#include "x64/x64.c" #include "win32/win32_inc.c" -#include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" #include "artifact_cache/artifact_cache.c" #include "rdi/rdi_local.c" diff --git a/src/third_party/radsort/radsort.h b/src/third_party/radsort/radsort.h index dbfbd11c..7cf3146e 100644 --- a/src/third_party/radsort/radsort.h +++ b/src/third_party/radsort/radsort.h @@ -137,7 +137,7 @@ radsortmovesize( sizeof( (start)[0] ) ), \ //=================================================================================================== // small heap sort - this sort is around 200 bytes compiled - can use directly when size is important -internal RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper ) +static RSFORCEINLINE void radheapsortinteral( void * start, size_t len, size_t element_size, is_before_func * is_before, swap_func * swapper ) { void * left; void * right; diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index e7fc5fb1..1b3a6915 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -20,6 +20,7 @@ //////////////////////////////// #include "base/base_inc.h" +#include "x64/x64.h" #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" @@ -103,6 +104,7 @@ #include "torture.h" #include "base/base_inc.c" +#include "x64/x64.c" #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" diff --git a/src/x64/x64.h b/src/x64/x64.h index 7d9c1fd1..2ee37845 100644 --- a/src/x64/x64.h +++ b/src/x64/x64.h @@ -212,39 +212,40 @@ X64_DebugControlFlags; //////////////////////////////// //~ Exception Codes -typedef enum +typedef enum X64_ExceptionCode { - X64_Exception_DE = 0, // Divide error: division by zero or quotient overflow - X64_Exception_DB = 1, // Debug exception (single stepping, hardware breakpoint) - X64_Exception_NMI = 2, // Non-maskable interrupt - X64_Exception_BP = 3, // Breakpoint (int3) - X64_Exception_OF = 4, // Overflow - X64_Exception_BR = 5, // Bound range exceeded - X64_Exception_UD = 6, // Invalid opcode - X64_Exception_NM = 7, // Device not available - X64_Exception_DF = 8, // Double fault - X64_Exception_9 = 9, // Coprocessor segment overrun (obsolete) - X64_Exception_TS = 10, // Invalid task-state segment - X64_Exception_NP = 11, // Segment not present - X64_Exception_SS = 12, // Stack-segment fault - X64_Exception_GP = 13, // General-protection fault - X64_Exception_PF = 14, // Page fault - X64_Exception_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS - X64_Exception_MF = 16, // x87 floating-point exception - X64_Exception_AC = 17, // Alignment check - X64_Exception_MC = 18, // Machine check - X64_Exception_XF = 19, // SIMD floating-point exception - X64_Exception_VE = 20, // Virtualization exception - X64_Exception_CP = 21, // Control-protection exception - X64_Exception_Amd_HV = 28, // Hypervisor injection exception - X64_Exception_Amd_VC = 29, // VMM communication exception - X64_Exception_Amd_SX = 30, // Security exception -} X64_ExceptionCode; + X64_ExceptionCode_DE = 0, // Divide error: division by zero or quotient overflow + X64_ExceptionCode_DB = 1, // Debug exception (single stepping, hardware breakpoint) + X64_ExceptionCode_NMI = 2, // Non-maskable interrupt + X64_ExceptionCode_BP = 3, // Breakpoint (int3) + X64_ExceptionCode_OF = 4, // Overflow + X64_ExceptionCode_BR = 5, // Bound range exceeded + X64_ExceptionCode_UD = 6, // Invalid opcode + X64_ExceptionCode_NM = 7, // Device not available + X64_ExceptionCode_DF = 8, // Double fault + X64_ExceptionCode_9 = 9, // Coprocessor segment overrun (obsolete) + X64_ExceptionCode_TS = 10, // Invalid task-state segment + X64_ExceptionCode_NP = 11, // Segment not present + X64_ExceptionCode_SS = 12, // Stack-segment fault + X64_ExceptionCode_GP = 13, // General-protection fault + X64_ExceptionCode_PF = 14, // Page fault + X64_ExceptionCode_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS + X64_ExceptionCode_MF = 16, // x87 floating-point exception + X64_ExceptionCode_AC = 17, // Alignment check + X64_ExceptionCode_MC = 18, // Machine check + X64_ExceptionCode_XF = 19, // SIMD floating-point exception + X64_ExceptionCode_VE = 20, // Virtualization exception + X64_ExceptionCode_CP = 21, // Control-protection exception + X64_ExceptionCode_Amd_HV = 28, // Hypervisor injection exception + X64_ExceptionCode_Amd_VC = 29, // VMM communication exception + X64_ExceptionCode_Amd_SX = 30, // Security exception +} +X64_ExceptionCode; //////////////////////////////// //~ Page Fault Flags -typedef enum +typedef enum X64_PageFaultError { X64_PageFaultError_Protection = (1u << 0), X64_PageFaultError_Write = (1u << 1), @@ -253,7 +254,8 @@ typedef enum X64_PageFaultError_InstructionFetch = (1u << 4), X64_PageFaultError_ProtectionKey = (1u << 5), X64_PageFaultError_ShadowStack = (1u << 6), -} X64_PageFaultError; +} +X64_PageFaultError; //////////////////////////////// //~ rjf: Generated @@ -280,4 +282,4 @@ internal X64_XSaveLayout x64_get_xsave_layout(U64 xcr0); internal void x64_set_debug_break(U64 *drs, U64 trap_idx, U64 addr, U64 size, X64_BreakpointType bp_type, X64_DebugBreakType break_type); -#endif // BASE_X64_H +#endif // X64_H From 0e134ade56a5579f747745f89c72a2bbdb7befeb Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 22 Sep 2026 03:44:36 -0700 Subject: [PATCH 174/251] separate x64 exception enums from stuff that assumes x64 host + includes regs/data-tables/etc.; just include *that* in linux base --- src/eval/eval_core.h | 2 +- src/linux/base/linux_base.h | 4 +-- src/radbin/radbin_main.c | 4 +-- src/raddbg/raddbg_main.c | 2 +- src/torture/torture_main.c | 2 +- src/x64/x64.h | 72 +++++++------------------------------ src/x64/x64_exceptions.h | 55 ++++++++++++++++++++++++++++ src/x64/x64_inc.c | 4 +++ src/x64/x64_inc.h | 10 ++++++ 9 files changed, 88 insertions(+), 67 deletions(-) create mode 100644 src/x64/x64_exceptions.h create mode 100644 src/x64/x64_inc.c create mode 100644 src/x64/x64_inc.h diff --git a/src/eval/eval_core.h b/src/eval/eval_core.h index ddddebe1..87638a63 100644 --- a/src/eval/eval_core.h +++ b/src/eval/eval_core.h @@ -1278,7 +1278,7 @@ internal E_DbgInfo *e_dbg_info_from_type_key(E_TypeKey type_key); // // key // ________________________________________________ -// / / | \ +// _/ / | \_ // text -> expression -> ir tree and type -> interpretation result // // Each one of these calls refers to one stage in this pipeline. The cache will diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index b5cce089..b35c175a 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -37,9 +37,9 @@ // NOTE(rjf): this is required because we need to use architecture-specific code for "base" // OS functionality - we may want to reevaluate if such things should be in base - but for -// now just pulling in the whole x64 layer, just for linux. +// now just pulling in this portion ox 64, just for linux. #if ARCH_X64 || ARCH_X86 -# include "x64/x64.h" +# include "x64/x64_exceptions.h" #endif pid_t gettid(void); diff --git a/src/radbin/radbin_main.c b/src/radbin/radbin_main.c index 3c1fb526..8999d8f1 100644 --- a/src/radbin/radbin_main.c +++ b/src/radbin/radbin_main.c @@ -12,7 +12,7 @@ //- rjf: [h] #include "base/base_inc.h" -#include "x64/x64.h" +#include "x64/x64_inc.h" #include "linker/hash_table.h" #include "linker/base_ext/base_bit_array.h" #include "rdi/rdi_local.h" @@ -47,7 +47,7 @@ //- rjf: [c] #include "base/base_inc.c" -#include "x64/x64.c" +#include "x64/x64_inc.c" #include "linker/hash_table.c" #include "linker/base_ext/base_bit_array.c" #include "rdi/rdi_local.c" diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index abe98430..46536a93 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -376,7 +376,7 @@ //- rjf: [h] #include "base/base_inc.h" -#include "x64/x64.h" +#include "x64/x64_inc.h" #include "win32/win32_inc.h" #include "linker/base_ext/base_bit_array.h" #include "artifact_cache/artifact_cache.h" diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 1b3a6915..71cb36bb 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -20,7 +20,7 @@ //////////////////////////////// #include "base/base_inc.h" -#include "x64/x64.h" +#include "x64/x64_inc.h" #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" diff --git a/src/x64/x64.h b/src/x64/x64.h index 2ee37845..459ea5c0 100644 --- a/src/x64/x64.h +++ b/src/x64/x64.h @@ -37,18 +37,18 @@ enum typedef struct X64_FXSave X64_FXSave; struct AlignType(16) X64_FXSave { - U16 fcw; - U16 fsw; - U8 ftw; - U8 reserved1; - U16 fop; - U64 fip; - U64 fdp; - U32 mxcsr; - U32 mxcsr_mask; + U16 fcw; + U16 fsw; + U8 ftw; + U8 reserved1; + U16 fop; + U64 fip; + U64 fdp; + U32 mxcsr; + U32 mxcsr_mask; U128 st_space[8]; U128 xmm_space[16]; - U8 padding[96]; + U8 padding[96]; }; StaticAssert(sizeof(X64_FXSave) == 512, g_x64_xsave_legacy_size_check); @@ -57,14 +57,14 @@ struct X64_XSaveHeader { U64 xstate_bv; U64 xcomp_bv; - U8 reserved[48]; + U8 reserved[48]; }; StaticAssert(sizeof(X64_XSaveHeader) == 64, g_x64_xsave_header_size_check); typedef struct X64_XSave X64_XSave; struct AlignType(64) X64_XSave { - X64_FXSave fxsave; + X64_FXSave fxsave; X64_XSaveHeader header; // U8 ext_area[0]; }; @@ -209,54 +209,6 @@ typedef enum X64_DebugControlFlags } X64_DebugControlFlags; -//////////////////////////////// -//~ Exception Codes - -typedef enum X64_ExceptionCode -{ - X64_ExceptionCode_DE = 0, // Divide error: division by zero or quotient overflow - X64_ExceptionCode_DB = 1, // Debug exception (single stepping, hardware breakpoint) - X64_ExceptionCode_NMI = 2, // Non-maskable interrupt - X64_ExceptionCode_BP = 3, // Breakpoint (int3) - X64_ExceptionCode_OF = 4, // Overflow - X64_ExceptionCode_BR = 5, // Bound range exceeded - X64_ExceptionCode_UD = 6, // Invalid opcode - X64_ExceptionCode_NM = 7, // Device not available - X64_ExceptionCode_DF = 8, // Double fault - X64_ExceptionCode_9 = 9, // Coprocessor segment overrun (obsolete) - X64_ExceptionCode_TS = 10, // Invalid task-state segment - X64_ExceptionCode_NP = 11, // Segment not present - X64_ExceptionCode_SS = 12, // Stack-segment fault - X64_ExceptionCode_GP = 13, // General-protection fault - X64_ExceptionCode_PF = 14, // Page fault - X64_ExceptionCode_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS - X64_ExceptionCode_MF = 16, // x87 floating-point exception - X64_ExceptionCode_AC = 17, // Alignment check - X64_ExceptionCode_MC = 18, // Machine check - X64_ExceptionCode_XF = 19, // SIMD floating-point exception - X64_ExceptionCode_VE = 20, // Virtualization exception - X64_ExceptionCode_CP = 21, // Control-protection exception - X64_ExceptionCode_Amd_HV = 28, // Hypervisor injection exception - X64_ExceptionCode_Amd_VC = 29, // VMM communication exception - X64_ExceptionCode_Amd_SX = 30, // Security exception -} -X64_ExceptionCode; - -//////////////////////////////// -//~ Page Fault Flags - -typedef enum X64_PageFaultError -{ - X64_PageFaultError_Protection = (1u << 0), - X64_PageFaultError_Write = (1u << 1), - X64_PageFaultError_User = (1u << 2), - X64_PageFaultError_ReservedBit = (1u << 3), - X64_PageFaultError_InstructionFetch = (1u << 4), - X64_PageFaultError_ProtectionKey = (1u << 5), - X64_PageFaultError_ShadowStack = (1u << 6), -} -X64_PageFaultError; - //////////////////////////////// //~ rjf: Generated diff --git a/src/x64/x64_exceptions.h b/src/x64/x64_exceptions.h new file mode 100644 index 00000000..e7df87d5 --- /dev/null +++ b/src/x64/x64_exceptions.h @@ -0,0 +1,55 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef X64_EXCEPTIONS_H +#define X64_EXCEPTIONS_H + +//////////////////////////////// +//~ Exception Codes + +typedef enum X64_ExceptionCode +{ + X64_ExceptionCode_DE = 0, // Divide error: division by zero or quotient overflow + X64_ExceptionCode_DB = 1, // Debug exception (single stepping, hardware breakpoint) + X64_ExceptionCode_NMI = 2, // Non-maskable interrupt + X64_ExceptionCode_BP = 3, // Breakpoint (int3) + X64_ExceptionCode_OF = 4, // Overflow + X64_ExceptionCode_BR = 5, // Bound range exceeded + X64_ExceptionCode_UD = 6, // Invalid opcode + X64_ExceptionCode_NM = 7, // Device not available + X64_ExceptionCode_DF = 8, // Double fault + X64_ExceptionCode_9 = 9, // Coprocessor segment overrun (obsolete) + X64_ExceptionCode_TS = 10, // Invalid task-state segment + X64_ExceptionCode_NP = 11, // Segment not present + X64_ExceptionCode_SS = 12, // Stack-segment fault + X64_ExceptionCode_GP = 13, // General-protection fault + X64_ExceptionCode_PF = 14, // Page fault + X64_ExceptionCode_15 = 15, // Reserved; Linux names it X86_TRAP_SPURIOUS + X64_ExceptionCode_MF = 16, // x87 floating-point exception + X64_ExceptionCode_AC = 17, // Alignment check + X64_ExceptionCode_MC = 18, // Machine check + X64_ExceptionCode_XF = 19, // SIMD floating-point exception + X64_ExceptionCode_VE = 20, // Virtualization exception + X64_ExceptionCode_CP = 21, // Control-protection exception + X64_ExceptionCode_Amd_HV = 28, // Hypervisor injection exception + X64_ExceptionCode_Amd_VC = 29, // VMM communication exception + X64_ExceptionCode_Amd_SX = 30, // Security exception +} +X64_ExceptionCode; + +//////////////////////////////// +//~ Page Fault Flags + +typedef enum X64_PageFaultError +{ + X64_PageFaultError_Protection = (1u << 0), + X64_PageFaultError_Write = (1u << 1), + X64_PageFaultError_User = (1u << 2), + X64_PageFaultError_ReservedBit = (1u << 3), + X64_PageFaultError_InstructionFetch = (1u << 4), + X64_PageFaultError_ProtectionKey = (1u << 5), + X64_PageFaultError_ShadowStack = (1u << 6), +} +X64_PageFaultError; + +#endif // X64_EXCEPTIONS_H diff --git a/src/x64/x64_inc.c b/src/x64/x64_inc.c new file mode 100644 index 00000000..2b64f3ff --- /dev/null +++ b/src/x64/x64_inc.c @@ -0,0 +1,4 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "x64.c" diff --git a/src/x64/x64_inc.h b/src/x64/x64_inc.h new file mode 100644 index 00000000..48cae4d3 --- /dev/null +++ b/src/x64/x64_inc.h @@ -0,0 +1,10 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef X64_INC_H +#define X64_INC_H + +#include "x64.h" +#include "x64_exceptions.h" + +#endif // X64_INC_H From 99f35bb3426dfe9c5af4a969d264a24e62c98cc6 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 23 Sep 2026 11:28:24 -0600 Subject: [PATCH 175/251] rdi_from_dwarf: cfa push opcodes --- src/rdi_from_dwarf/rdi_from_dwarf.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index ce77f122..174fb4f3 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -3703,6 +3703,12 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) push_vals[0].is_addr = 1; }break; + //- rjf: call frame CFA + case DW_ExprOp_CallFrameCfa: + { + rdim_bytecode_push_op(arena, &dst_bytecode, RDI_EvalOp_PushCfa, 0); + }break; + //- rjf: memory reads case DW_ExprOp_Deref: memread_size = unit_parse_ctx->addr_size; goto deref; case DW_ExprOp_DerefSize: memread_size = operand_u64s[0]; goto deref; From cc8212753798812021fd3c7e5f64cddd3ed4a8fd Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 23 Sep 2026 11:53:59 -0600 Subject: [PATCH 176/251] eh_frame_unwind: correctly commit cfa_out - it is still unclear if we need this channel, since all we do right now is commit cfa to the sp. so presumably callers could just look at the parent's sp? but my understanding of this is not strong enough to know if that is 100% reliable, since my understanding is that the cfa is an abstract thing specifically because it may *not* be the sp. but not sure, need to look more into it later. --- src/eh_frame/unwind/eh_frame_unwind.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/eh_frame/unwind/eh_frame_unwind.c b/src/eh_frame/unwind/eh_frame_unwind.c index 47a5dba6..979c77c8 100644 --- a/src/eh_frame/unwind/eh_frame_unwind.c +++ b/src/eh_frame/unwind/eh_frame_unwind.c @@ -687,6 +687,10 @@ eh_uwnd_step(Arch arch, MemoryMap *memory_map, UWND_ModuleInfo *module_info, U64 //- rjf: commit new register values, if we succeeded if(!done) { + if(cfa_out) + { + cfa_out[0] = cfa; + } result.status = UWND_StepStatus_Good; } } From ca0764c2e2b61d1a0b5724f9e1021bf198d72134 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 23 Sep 2026 16:56:50 -0600 Subject: [PATCH 177/251] linux_demon: ptrace stop vs. clone notes - we cannot rely on the order! --- src/linux/demon/linux_demon.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index b78c528d..acab5d68 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -2103,9 +2103,17 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // case PTRACE_EVENT_CLONE: { - // NOTE: kernel stopped the parent just before scheduling the child to - // give us a chance to prepare to trace it; next event for the child - // will be a PTRACE_EVENT_STOP + // NOTE(rjf): we get this event from the creator thread when it has + // cloned itself to create a child. immediately after creation, the + // child will raise a stop event. but importantly, the Linux kernel + // offers *NO GUARANTEES* about whether we receive the *clone* or + // the *stop* first. + // + // TODO(rjf): to account for this, upon STOP events of unrecognized + // threads, we need to accumulate a pending ID map, and then if a + // cloned thread's ID is in that map, we just immediately move it + // to a stopped state. + // pid_t new_tid = 0; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid)) >= 0) { From df1de0541a3cf6bbe9d68fc0213260c66a419697 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 24 Sep 2026 10:12:22 -0600 Subject: [PATCH 178/251] linux_demon: robustness to clone-then-stop, *or* stop-then-clone --- src/linux/demon/linux_demon.c | 65 +++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index acab5d68..d0e88a14 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1596,6 +1596,15 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) //////////////////////////// //- rjf: wait the next signal & generate events // + typedef struct StoppedPreCloneThreadID StoppedPreCloneThreadID; + struct StoppedPreCloneThreadID + { + StoppedPreCloneThreadID *next; + StoppedPreCloneThreadID *prev; + pid_t tid; + }; + StoppedPreCloneThreadID *first_stopped_pre_clone_thread_id = 0; + StoppedPreCloneThreadID *last_stopped_pre_clone_thread_id = 0; B32 is_halt_done = 0; for(;;) { @@ -2101,28 +2110,44 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) //////////////////// //- rjf: clone // + // NOTE(rjf): we get this event from the creator thread when it has + // cloned itself to create a child. immediately after creation, the + // child will raise a stop event. but importantly, the Linux kernel + // offers *NO GUARANTEES* about whether we receive the *clone* or + // the *stop* first. + // case PTRACE_EVENT_CLONE: { - // NOTE(rjf): we get this event from the creator thread when it has - // cloned itself to create a child. immediately after creation, the - // child will raise a stop event. but importantly, the Linux kernel - // offers *NO GUARANTEES* about whether we receive the *clone* or - // the *stop* first. - // - // TODO(rjf): to account for this, upon STOP events of unrecognized - // threads, we need to accumulate a pending ID map, and then if a - // cloned thread's ID is in that map, we just immediately move it - // to a stopped state. - // pid_t new_tid = 0; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid)) >= 0) { + // rjf: first, check if we have already seen a stop event for this TID. if so, + // remove from pre-clone stop thread ID List. + B32 already_stopped = 0; + for(StoppedPreCloneThreadID *n = first_stopped_pre_clone_thread_id; n != 0; n = n->next) + { + if(n->tid == new_tid) + { + already_stopped = 1; + DLLRemove(first_stopped_pre_clone_thread_id, last_stopped_pre_clone_thread_id, n); + break; + } + } + + // rjf: set up thread creation thread_new = 1; thread_new_pid = new_tid; - thread_new_report_events = 0; - thread_new_state = LNX_DMN_ThreadState_PendingCreation; + thread_new_report_events = already_stopped; + thread_new_state = already_stopped ? LNX_DMN_ThreadState_Stopped : LNX_DMN_ThreadState_PendingCreation; thread_new_parent = thread->process; - lnx_dmn_state->threads_pending_creation += 1; + if(already_stopped) + { + lnx_dmn_state->threads_pending_creation -= 1; + } + else + { + lnx_dmn_state->threads_pending_creation += 1; + } } else { @@ -2179,6 +2204,18 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // TODO(rjf): e->stack_pointer = ???; e->tls_root_vaddr = thread->dtv_base_vaddr; } + + // rjf: stop event for unrecognized thread -> this references a thread we + // haven't seen cloned yet (we cannot rely on the order of clone/stop). so, + // we will gather the ID, and increase the pending threads count, and stop + // the pending state when we see the CLONE instead. + else if(thread == 0) + { + lnx_dmn_state->threads_pending_creation += 1; + StoppedPreCloneThreadID *n = push_array(scratch.arena, StoppedPreCloneThreadID, 1); + DLLPushBack(first_stopped_pre_clone_thread_id, last_stopped_pre_clone_thread_id, n); + n->tid = wait_id; + } }break; } } From 31d10c3b4feb2f86d817ca0cd68773a5dffe1dc4 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 24 Sep 2026 11:24:42 -0600 Subject: [PATCH 179/251] rdi_from_dwarf: preserve unit dir in generated source file paths. do not assume relativity to binary, often incorrect --- src/rdi_from_dwarf/rdi_from_dwarf.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index 174fb4f3..6554251a 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -432,6 +432,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) //- rjf: build path -> src file map for EachIndex(unit_idx, unit_count) { + DW2_ParseCtx *ctx = &unit_parse_ctxs[unit_idx]; DW2_LineTableHeader *hdr = &unit_line_table_headers[unit_idx]; for EachIndex(file_idx, hdr->files.count) { @@ -439,7 +440,10 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) if(f->file_name.size != 0) { DW2_LineTableFile *dir = &hdr->dirs.v[f->dir_idx]; - String8 full_file_path = str8f(scratch2.arena, "%S%s%S", dir->file_name, dir->file_name.size != 0 ? "/" : "", f->file_name); + String8 full_file_path = str8f(scratch2.arena, "%S%s%S%s%S", + ctx->unit_dir, ctx->unit_dir.size != 0 ? "/" : "", + dir->file_name, dir->file_name.size != 0 ? "/" : "", + f->file_name); U64 hash = u64_hash_from_str8(full_file_path); U64 slot_idx = hash%slots_count; SrcFileNode *node = 0; From 9fdcc88cab256600264e0e99449c16e2e085389d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 24 Sep 2026 11:37:24 -0600 Subject: [PATCH 180/251] rdi_from_dwarf: correctly use unit_dir only when line table directories are relative; correctly use unit compilation unit name as first file in pre-dwarf-5 --- src/dwarf/dwarf_parse.c | 5 ++++- src/rdi_from_dwarf/rdi_from_dwarf.c | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/dwarf/dwarf_parse.c b/src/dwarf/dwarf_parse.c index 6b3f3fe5..a7845622 100644 --- a/src/dwarf/dwarf_parse.c +++ b/src/dwarf/dwarf_parse.c @@ -676,6 +676,7 @@ internal void dw2_parse_ctx_equip_unit_root_tag(DW2_ParseCtx *ctx_out, DW2_Tag *tag, DW2_OffsetTableSet *offset_tables) { // rjf: unpack attributes + DW2_Attrib *name_attrib = &dw2_attrib_nil; DW2_Attrib *low_pc_attrib = &dw2_attrib_nil; DW2_Attrib *lang_attrib = &dw2_attrib_nil; DW2_Attrib *rnglists_base_off_attrib = &dw2_attrib_nil; @@ -689,6 +690,7 @@ dw2_parse_ctx_equip_unit_root_tag(DW2_ParseCtx *ctx_out, DW2_Tag *tag, DW2_Offse { default:{}break; #define Case(name, name_upper) case DW_AttribKind_##name_upper:{name##_attrib = &n->v;}break + Case(name, Name); Case(low_pc, LowPc); Case(lang, Language); Case(rnglists_base_off, RngListsBase); @@ -704,6 +706,7 @@ dw2_parse_ctx_equip_unit_root_tag(DW2_ParseCtx *ctx_out, DW2_Tag *tag, DW2_Offse ctx_out->unit_base_addr = low_pc_attrib->val.addr; ctx_out->language = lang_attrib->val.u128.u64[0]; ctx_out->unit_dir = comp_dir_attrib->val.string; + ctx_out->unit_file = comp_dir_attrib->val.string; // rjf: find offset tables Rng1U64Array rnglists_tables_ranges_array = {offset_tables->rnglists_tables_ranges, offset_tables->rnglists_tables_count}; @@ -1115,7 +1118,7 @@ dw2_read_line_table_header(Arena *arena, DW_Raw *raw, DW2_ParseCtx *ctx, String8 DW2_LineTableFile *entry_out = &table.v[idx]; for(U64 entry_format_idx = 0; entry_format_idx < entry_formats_count; entry_format_idx += 1) { - DW_LNCT lnct = (DW_LNCT)entry_formats[entry_format_idx*2 + 0]; + DW_LNCT lnct = (DW_LNCT)entry_formats[entry_format_idx*2 + 0]; DW_FormKind form_kind = (DW_FormKind)entry_formats[entry_format_idx*2 + 1]; DW2_FormVal form_val = {0}; off += dw2_read_form_val(raw, ctx, data, off, form_kind, 0, &form_val); diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index 6554251a..ff5d9571 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -440,10 +440,16 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) if(f->file_name.size != 0) { DW2_LineTableFile *dir = &hdr->dirs.v[f->dir_idx]; + String8 root_dir = {0}; + if(path_style_from_str8(dir->file_name) == PathStyle_Relative) + { + root_dir = ctx->unit_dir; + } String8 full_file_path = str8f(scratch2.arena, "%S%s%S%s%S", - ctx->unit_dir, ctx->unit_dir.size != 0 ? "/" : "", + root_dir, root_dir.size != 0 ? "/" : "", dir->file_name, dir->file_name.size != 0 ? "/" : "", f->file_name); + full_file_path = path_normalized_from_string(scratch2.arena, full_file_path); U64 hash = u64_hash_from_str8(full_file_path); U64 slot_idx = hash%slots_count; SrcFileNode *node = 0; From bc7434f7edc4398a37ed2935446e1cec21e1ef9d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 25 Sep 2026 10:29:16 -0600 Subject: [PATCH 181/251] linux_demon: do not fail when there is no dynamic loader present --- src/linux/demon/linux_demon.c | 80 ++++++++++++++++++++--------------- src/linux/demon/linux_demon.h | 2 +- src/raddbg/raddbg_main.c | 9 ++++ src/raddbg/raddbg_widgets.c | 61 +++++++++++++------------- 4 files changed, 87 insertions(+), 65 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index d0e88a14..1229e2ab 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -367,7 +367,7 @@ lnx_dmn_elf_hdr64_from_vaddr(int memory_fd, U64 vaddr) } internal Rng1U64 -lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) +lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) { Rng1U64 result = {.min = max_U64}; for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); @@ -382,10 +382,10 @@ lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U phdr = elf_phdr64_from_class_data(elf_class, phdr_data); scratch_end(scratch); } - if(phdr.p_type == ELF_PType_Load) + if(phdr.p_type == ELF_PType_Load) { - U64 min = rebase + phdr.p_vaddr; - U64 max = rebase + phdr.p_vaddr + phdr.p_memsz; + U64 min = phdr.p_vaddr; + U64 max = phdr.p_vaddr + phdr.p_memsz; result.min = Min(result.min, min); result.max = Max(result.max, max); } @@ -491,9 +491,8 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 ELF_Class module_eclass = module_ehdr.e_ident[ELF_Identifier_Class]; // rjf: unpack module's vaddr range - U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; - Rng1U64 module_vrange = lnx_dmn_vaddr_range_from_phdrs(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); + U64 module_phdr_vaddr = base_vaddr + module_ehdr.e_phoff; + Rng1U64 module_vrange = lnx_dmn_vaddr_range_from_phdrs(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); // rjf: read TLS index and TLS offset U64 tls_index = max_U64; @@ -2519,21 +2518,20 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) //- rjf: unpack context Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); - U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); - U64 rebase = (exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0); - Rng1U64 image_vrange = lnx_dmn_vaddr_range_from_phdrs(new_process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); + Rng1U64 image_vrange = lnx_dmn_vaddr_range_from_phdrs(new_process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], auxv.phdr, auxv.phent, auxv.phnum); - //- rjf: read dynamically loaded ELF header + //- rjf: read ELF header for dynamic loader ELF_Hdr64 dl_ehdr = lnx_dmn_elf_hdr64_from_vaddr(new_process->fd, auxv.base); ELF_Class dl_class = dl_ehdr.e_ident[ELF_Identifier_Class]; //- rjf: compute rdebug vaddr U64 rdebug_vaddr = 0; + if(dl_class != ELF_Class_None) { Temp scratch = scratch_begin(&arena, 1); // rjf: unpack header - U64 rebase = dl_ehdr.e_type == ELF_Type_Dyn ? auxv.base : 0; + U64 dl_rebase = (dl_ehdr.e_type == ELF_Type_Dyn ? auxv.base : 0); // rjf: find dynamic program header U64 dynamic_vaddr = 0; @@ -2553,7 +2551,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } if(phdr.p_type == ELF_PType_Dynamic) { - dynamic_vaddr = rebase + phdr.p_vaddr; + dynamic_vaddr = dl_rebase + phdr.p_vaddr; break; } } @@ -2562,8 +2560,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // rjf: parse dynamic info for this module LNX_DMN_DynamicInfo dynamic_info = {0}; { - B32 is_rebased = !!(process_new_flags & LNX_DMN_CreateProcessFlag_Rebased); - U64 dynamic_info_rebase = is_rebased ? 0 : rebase; + B32 is_dl_rebased = !!(process_new_flags & LNX_DMN_CreateProcessFlag_Rebased); + U64 dynamic_info_dl_rebase = is_dl_rebased ? 0 : dl_rebase; for(U64 dynamic_cursor = dynamic_vaddr;; dynamic_cursor += elf_dyn_size_from_class(dl_class)) { // rjf: read next dyn entry @@ -2583,12 +2581,12 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) switch(dyn.tag) { default:{}break; - case ELF_DynTag_Strtab: {dynamic_info.strtab_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_Strtab: {dynamic_info.strtab_vaddr = dynamic_info_dl_rebase + dyn.val;}break; case ELF_DynTag_Strsz: {dynamic_info.strtab_size = dyn.val;}break; - case ELF_DynTag_Symtab: {dynamic_info.symtab_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_Symtab: {dynamic_info.symtab_vaddr = dynamic_info_dl_rebase + dyn.val;}break; case ELF_DynTag_Syment: {dynamic_info.symtab_entry_size = dyn.val;}break; - case ELF_DynTag_Hash: {dynamic_info.hash_vaddr = dynamic_info_rebase + dyn.val;}break; - case ELF_DynTag_GNU_Hash: {dynamic_info.gnu_hash_vaddr = dynamic_info_rebase + dyn.val;}break; + case ELF_DynTag_Hash: {dynamic_info.hash_vaddr = dynamic_info_dl_rebase + dyn.val;}break; + case ELF_DynTag_GNU_Hash: {dynamic_info.gnu_hash_vaddr = dynamic_info_dl_rebase + dyn.val;}break; } } } @@ -2708,7 +2706,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(symbol_type == ELF_SymType_Object && symbol.st_size > 0) { got_rdebug = 1; - rdebug_vaddr = rebase + symbol.st_value; + rdebug_vaddr = dl_rebase + symbol.st_value; } } @@ -2750,7 +2748,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { Temp scratch = scratch_begin(&arena, 1); - // rjf: get dl path for this pid + // rjf: get dynamic loader path for this pid String8 dl_path = {0}; { int maps_fd = LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/maps", wait_id).str, O_RDONLY)); @@ -2811,29 +2809,41 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } // was line parsed correctly? - if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; } + B32 good_parse = 1; + if(parts.node_count < 5) + { + good_parse = 0; + // TODO(rjf): log + } // parse map virtual range - String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); - if(vaddr_list.node_count != 2) { Assert(0 && "failed to parse virtual range portion of map line"); continue; } + String8List vaddr_list = {0}; + if(good_parse) + { + vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); + if(vaddr_list.node_count != 2) + { + good_parse = 0; + // TODO(rjf): log - failed to parse virtual range portion of map line + } + } // does the low part match DL base address? - U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16); - if(lo_vaddr == auxv.base) + U64 lo_vaddr = 0; + if(vaddr_list.first != 0) { - dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; - dl_path = push_str8_copy(scratch.arena, dl_path); - break; + lo_vaddr = u64_from_str8(vaddr_list.first->string, 16); + if(lo_vaddr == auxv.base) + { + dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; + dl_path = push_str8_copy(scratch.arena, dl_path); + break; + } } } LNX_RETRY_ON_EINTR(close(maps_fd)); } - else - { - Assert(0 && "failed to open DL fd"); - } - Assert(dl_path.size); } // rjf: read probes for this dl @@ -3008,7 +3018,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) ctx->xsave_layout = xsave_layout; //- rjf: create main module - LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, new_process->fd, base_vaddr, auxv.execfn, 1, 1); + LNX_DMN_Module *main_module = lnx_dmn_module_alloc(ctx, new_process->fd, image_vrange.min, auxv.execfn, 1, 1); //- rjf: glibc has a shortcut mapping for the main module; ensure 0 base address goes to it { diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index 0e95a32f..92052139 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -418,7 +418,7 @@ internal LNX_DMN_ActiveTrap *lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap); //~ rjf: In-Memory ELF Parsing Helpers internal ELF_Hdr64 lnx_dmn_elf_hdr64_from_vaddr(int memory_fd, U64 vaddr); -internal Rng1U64 lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); +internal Rng1U64 lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); //////////////////////////////// //~ rjf: Entity Functions diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 46536a93..a58d8b09 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -1,6 +1,15 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +//////////////////////////////// +//~ rjf: linux TODO notes +// +// [ ] multithreaded stepping - trap hits reported on other threads. +// [ ] statically linked EXEs - busted unwinding +// [ ] jai exes - busted call stacks / unwind info / paths? +// [ ] stale memory - things evaluating to stale values that are definitely not zero +// [x] statically linked EXEs, not parsing maps file correctly + //////////////////////////////// //~ rjf: evaluation system pass TODO notes // diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index d4d7ac73..1ba63380 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -617,44 +617,47 @@ rd_title_fstrs_from_ctrl_entity(Arena *arena, D_Entity *entity, B32 include_extr //- rjf: modules get debug info status extras if(entity->kind == D_EntityKind_Module && include_extras) { - Access *access = access_open(); D_Entity *debug_info_path = d_entity_child_from_kind(entity, D_EntityKind_DebugInfoPath); - DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(debug_info_path); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); - if(rdi == &di_rdi_parsed_nil_waiting) + if(!str8_match(debug_info_path->string, entity->string, 0)) { - UI_TagF("pop") + Access *access = access_open(); + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(debug_info_path); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); + if(rdi == &di_rdi_parsed_nil_waiting) { - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, s("Loading debug info"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_CircleFilled], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); - dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + UI_TagF("pop") + { + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, s("Loading debug info"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_CircleFilled], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + } } - } - else if(rdi->raw_data_size == 0) - { - UI_TagF("bad_pop") + else if(rdi->raw_data_size == 0) { - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, s("Debug info not found"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_X], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); - dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + UI_TagF("bad_pop") + { + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, s("Debug info not found"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_X], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + } } - } - else - { - UI_TagF("good_pop") + else { - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, str8_skip_last_slash(debug_info_path->string), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_Check], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); - dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + UI_TagF("good_pop") + { + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, str8_skip_last_slash(debug_info_path->string), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_Check], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); + } } + access_close(access); } - access_close(access); } return result; From 0317f4c8107b8c165a377f9cbeb6a8c9074a3ade Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 25 Sep 2026 11:15:07 -0600 Subject: [PATCH 182/251] linux_demon: correct rebasing / phdr address parsing for both static/dynamic ELFs --- src/linux/demon/linux_demon.c | 16 +++++++++------- src/linux/demon/linux_demon.h | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 1229e2ab..4274b0e0 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -175,9 +175,9 @@ lnx_dmn_module_info_from_process_module(Arena *arena, pid_t pid, int memory_fd, } //- rjf: determine shdrs/phdrs addresses - U64 phdrs_vaddr = module_rebase + phdrs_off; + U64 phdrs_vaddr = base_vaddr + phdrs_off; U64 phdrs_vaddr_opl = phdrs_vaddr + phdrs_count*phdrs_entry_size; - U64 shdrs_vaddr = module_rebase + shdrs_off; + U64 shdrs_vaddr = base_vaddr + shdrs_off; //- rjf: scan phdrs, unpack info U64 image_vsize = 0; @@ -367,7 +367,7 @@ lnx_dmn_elf_hdr64_from_vaddr(int memory_fd, U64 vaddr) } internal Rng1U64 -lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) +lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) { Rng1U64 result = {.min = max_U64}; for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); @@ -384,8 +384,8 @@ lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 e_phaddr, } if(phdr.p_type == ELF_PType_Load) { - U64 min = phdr.p_vaddr; - U64 max = phdr.p_vaddr + phdr.p_memsz; + U64 min = rebase + phdr.p_vaddr; + U64 max = rebase + phdr.p_vaddr + phdr.p_memsz; result.min = Min(result.min, min); result.max = Max(result.max, max); } @@ -492,7 +492,8 @@ lnx_dmn_module_alloc(LNX_DMN_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 // rjf: unpack module's vaddr range U64 module_phdr_vaddr = base_vaddr + module_ehdr.e_phoff; - Rng1U64 module_vrange = lnx_dmn_vaddr_range_from_phdrs(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); + U64 module_phdr_rebase = (module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0); + Rng1U64 module_vrange = lnx_dmn_vaddr_range_from_phdrs(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_phdr_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); // rjf: read TLS index and TLS offset U64 tls_index = max_U64; @@ -2518,7 +2519,8 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) //- rjf: unpack context Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); - Rng1U64 image_vrange = lnx_dmn_vaddr_range_from_phdrs(new_process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], auxv.phdr, auxv.phent, auxv.phnum); + U64 rebase = (exe_ehdr.e_type == ELF_Type_Dyn ? (auxv.phdr - exe_ehdr.e_phoff) : 0); + Rng1U64 image_vrange = lnx_dmn_vaddr_range_from_phdrs(new_process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); //- rjf: read ELF header for dynamic loader ELF_Hdr64 dl_ehdr = lnx_dmn_elf_hdr64_from_vaddr(new_process->fd, auxv.base); diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index 92052139..0e95a32f 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -418,7 +418,7 @@ internal LNX_DMN_ActiveTrap *lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap); //~ rjf: In-Memory ELF Parsing Helpers internal ELF_Hdr64 lnx_dmn_elf_hdr64_from_vaddr(int memory_fd, U64 vaddr); -internal Rng1U64 lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); +internal Rng1U64 lnx_dmn_vaddr_range_from_phdrs(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); //////////////////////////////// //~ rjf: Entity Functions From 0d93e80f84dca22624c95e40a2b5ce833324d6fb Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 25 Sep 2026 11:19:24 -0600 Subject: [PATCH 183/251] linux notes --- src/raddbg/raddbg_main.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index a58d8b09..fc7ce5d1 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -5,9 +5,14 @@ //~ rjf: linux TODO notes // // [ ] multithreaded stepping - trap hits reported on other threads. -// [ ] statically linked EXEs - busted unwinding -// [ ] jai exes - busted call stacks / unwind info / paths? +// [ ] .eh_frame unwinding without .eh_frame_hdr // [ ] stale memory - things evaluating to stale values that are definitely not zero +// [ ] clipboard +// [ ] bitmaps / geometry +// [ ] wayland +// [ ] asan +// [x] jai exes - busted call stacks / unwind info / paths? +// [x] statically linked EXEs - busted unwinding // [x] statically linked EXEs, not parsing maps file correctly //////////////////////////////// From eb845405e087e2767b6d32067e8ccf2219934aa1 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 25 Sep 2026 13:10:08 -0600 Subject: [PATCH 184/251] linux_window_manager: clipboard support --- build.sh | 6 +- .../window_manager/linux_window_manager.c | 120 +++++++++++++++++- .../window_manager/linux_window_manager.h | 9 ++ src/raddbg/raddbg_main.c | 21 +-- src/window_manager/window_manager.c | 18 +-- 5 files changed, 148 insertions(+), 26 deletions(-) diff --git a/build.sh b/build.sh index 4f015187..ae425e18 100755 --- a/build.sh +++ b/build.sh @@ -27,14 +27,14 @@ cc_link="-lpthread -lm -lrt -ldl" cc_link_dll="-fPIC" # --- External Libraries ------------------------------------------------------ -# sudo apt install -y pkg-config libfreetype6-dev libx11-dev libxext-dev libgl-dev libegl-dev +# sudo apt install -y pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libgl-dev libegl-dev if [[ -x "$(command -v pkg-config)" ]]; then cc_font_provider="$(pkg-config --cflags --libs freetype2)" - cc_os_gfx="$(pkg-config --cflags --libs x11 xext)" + cc_os_gfx="$(pkg-config --cflags --libs x11 xext xfixes)" cc_render="$(pkg-config --cflags --libs gl egl)" else cc_font_provider="-I/usr/include/freetype2 -lfreetype" - cc_os_gfx="-lX11 -lXext" + cc_os_gfx="-lX11 -lXext -lXFixes" cc_render="-lGL -lEGL" fi diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 83a92f9d..412ebdf5 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -342,11 +342,35 @@ wm_init(void) lnx_wm_state->arena = arena; lnx_wm_state->display = XOpenDisplay(0); + //- rjf: create invisible global window for stuff like clipboard + { + lnx_wm_state->global_invisible_window = XCreateWindow(lnx_wm_state->display, XDefaultRootWindow(lnx_wm_state->display), -1, -1, 1, 1, 0, 0, 0, 0, 0, 0); + } + + //- rjf: allocate clipboard arena + lnx_wm_state->clipboard_arena = arena_alloc(); + //- rjf: calculate atoms + lnx_wm_state->clipboard = XInternAtom(lnx_wm_state->display, "CLIPBOARD", 0); + lnx_wm_state->targets = XInternAtom(lnx_wm_state->display, "TARGETS", 0); + lnx_wm_state->utf8_string = XInternAtom(lnx_wm_state->display, "UTF8_STRING", 0); lnx_wm_state->wm_delete_window_atom = XInternAtom(lnx_wm_state->display, "WM_DELETE_WINDOW", 0); lnx_wm_state->wm_sync_request_atom = XInternAtom(lnx_wm_state->display, "_NET_WM_SYNC_REQUEST", 0); lnx_wm_state->wm_sync_request_counter_atom = XInternAtom(lnx_wm_state->display, "_NET_WM_SYNC_REQUEST_COUNTER", 0); + //- rjf: determine if we have xfixes extension, for clipboard notification events + { + int xfixes_version = 0; + int xfixes_err = 0; + lnx_wm_state->xfixes_present = XQueryExtension(lnx_wm_state->display, "XFIXES", &xfixes_version, &lnx_wm_state->xfixes_selection_event_code, &xfixes_err); + + // rjf: if we have xfixes -> get CLIPBOARD updates + if(lnx_wm_state->xfixes_present) + { + XFixesSelectSelectionInput(lnx_wm_state->display, lnx_wm_state->global_invisible_window, lnx_wm_state->clipboard, XFixesSetSelectionOwnerNotifyMask); + } + } + //- rjf: open im lnx_wm_state->xim = XOpenIM(lnx_wm_state->display, 0, 0, 0); @@ -513,13 +537,15 @@ wm_get_system_info(void) internal void wm_set_clipboard_text(String8 string) { - + arena_clear(lnx_wm_state->clipboard_arena); + lnx_wm_state->clipboard_text = str8_copy(lnx_wm_state->clipboard_arena, string); + XSetSelectionOwner(lnx_wm_state->display, lnx_wm_state->clipboard, lnx_wm_state->global_invisible_window, CurrentTime); } internal String8 wm_get_clipboard_text(Arena *arena) { - String8 result = {0}; + String8 result = str8_copy(arena, lnx_wm_state->clipboard_text); return result; } @@ -1065,7 +1091,6 @@ wm_send_wakeup_event(void) { U64 dummy = 1; ssize_t size = LNX_RETRY_ON_EINTR(write(lnx_wm_state->wakeup_fd, &dummy, sizeof(dummy))); - Assert(size == sizeof(dummy)); } internal WM_EventList @@ -1083,7 +1108,6 @@ wm_get_events(Arena *arena, B32 wait) }; int timeout = wait && evts.count == 0 ? -1 : 0; int poll_status = poll(poll_fds, ArrayCount(poll_fds), timeout); - Assert(poll_status >= 0); if(poll_fds[1].revents & POLLIN) { U64 dummy = 0; @@ -1091,8 +1115,15 @@ wm_get_events(Arena *arena, B32 wait) wait = 0; } } + B32 try_again = 0; while(XPending(lnx_wm_state->display)) { + //- rjf: no xfixes? -> request new clipboard state + if(!lnx_wm_state->xfixes_present) + { + XConvertSelection(lnx_wm_state->display, lnx_wm_state->clipboard, lnx_wm_state->utf8_string, lnx_wm_state->clipboard, lnx_wm_state->global_invisible_window, CurrentTime); + } + //- rjf: get next event XEvent evt = {0}; XNextEvent(lnx_wm_state->display, &evt); @@ -1101,7 +1132,84 @@ wm_get_events(Arena *arena, B32 wait) B32 set_mouse_cursor = 0; switch(evt.type) { - default:{}break; + default: + { + //- rjf: xfixes clipboard update event + if(evt.type == lnx_wm_state->xfixes_selection_event_code && lnx_wm_state->xfixes_present) + { + XFixesSelectionNotifyEvent *notification = (XFixesSelectionNotifyEvent *)&evt; + if(notification->subtype == XFixesSelectionNotify && notification->owner != lnx_wm_state->global_invisible_window) + { + XConvertSelection(lnx_wm_state->display, lnx_wm_state->clipboard, lnx_wm_state->utf8_string, lnx_wm_state->clipboard, lnx_wm_state->global_invisible_window, CurrentTime); + } + } + }break; + + //- rjf: external application wants to paste from us + case SelectionRequest: + { + XSelectionRequestEvent *req = &evt.xselectionrequest; + Atom targets = lnx_wm_state->targets; + Atom utf8_string = lnx_wm_state->utf8_string; + Atom dst_property = req->property == None ? req->target : req->property; + + // rjf: requesting the set of targets we support + if(req->target == targets) + { + Atom formats[] = + { + lnx_wm_state->utf8_string, + XA_STRING, + }; + XChangeProperty(req->display, req->requestor, req->property, XA_ATOM, 32, PropModeReplace, (U8 *)formats, ArrayCount(formats)); + } + + // rjf: requesting a string paste + else if(req->target == utf8_string || req->target == XA_STRING) + { + XChangeProperty(req->display, req->requestor, req->property, req->target, 8, PropModeReplace, lnx_wm_state->clipboard_text.str, lnx_wm_state->clipboard_text.size); + } + + // rjf: notify receiver + XEvent response; + MemoryZeroStruct(&response); + response.type = SelectionNotify; + response.xselection.display = req->display; + response.xselection.requestor = req->requestor; + response.xselection.selection = req->selection; + response.xselection.target = req->target; + response.xselection.time = req->time; + response.xselection.property = dst_property; + XSendEvent(req->display, req->requestor, 1, 0, &response); + XFlush(req->display); + + // rjf: try the event loop again + try_again = 1; + }break; + + //- rjf: external application is pasting to us + case SelectionNotify: + { + // rjf: unpack property + Atom type = 0; + int fmt = 0; + U64 num_items = 0; + U64 bytes_left = 0; + U8 *data = 0; + int result = XGetWindowProperty(lnx_wm_state->display, lnx_wm_state->global_invisible_window, lnx_wm_state->clipboard, 0, MB(256), 0, lnx_wm_state->utf8_string, &type, &fmt, &num_items, &bytes_left, &data); + + // rjf: store + if(result == Success && fmt == 8) + { + arena_clear(lnx_wm_state->clipboard_arena); + lnx_wm_state->clipboard_text = str8_copy(lnx_wm_state->clipboard_arena, str8(data, num_items)); + XFree(data); + XDeleteProperty(lnx_wm_state->display, lnx_wm_state->global_invisible_window, lnx_wm_state->clipboard); + } + + // rjf: try the event loop again + try_again = 1; + }break; //- rjf: key presses/releases case KeyPress: @@ -1343,7 +1451,7 @@ wm_get_events(Arena *arena, B32 wait) } } } - if(evts.count > 0 || (wait == 0 && evts.count == 0)) + if(!try_again && (evts.count > 0 || (wait == 0 && evts.count == 0))) { break; } diff --git a/src/linux/window_manager/linux_window_manager.h b/src/linux/window_manager/linux_window_manager.h index ae8f61db..36f9dba8 100644 --- a/src/linux/window_manager/linux_window_manager.h +++ b/src/linux/window_manager/linux_window_manager.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -69,13 +70,21 @@ struct LNX_WM_State { Arena *arena; Display *display; + Window global_invisible_window; + Arena *clipboard_arena; + String8 clipboard_text; XIM xim; LNX_WM_Window *first_window; LNX_WM_Window *last_window; LNX_WM_Window *free_window; + Atom clipboard; + Atom targets; + Atom utf8_string; Atom wm_delete_window_atom; Atom wm_sync_request_atom; Atom wm_sync_request_counter_atom; + B32 xfixes_present; + int xfixes_selection_event_code; Cursor cursors[WM_Cursor_COUNT]; WM_Cursor last_set_cursor; WM_SystemInfo gfx_info; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index fc7ce5d1..89454070 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -4,13 +4,18 @@ //////////////////////////////// //~ rjf: linux TODO notes // -// [ ] multithreaded stepping - trap hits reported on other threads. -// [ ] .eh_frame unwinding without .eh_frame_hdr -// [ ] stale memory - things evaluating to stale values that are definitely not zero -// [ ] clipboard -// [ ] bitmaps / geometry -// [ ] wayland -// [ ] asan +// [ ] control +// [ ] multithreaded stepping - trap hits reported on other threads. +// [ ] stale memory - things evaluating to stale values that are definitely not zero +// [ ] support +// [ ] .eh_frame unwinding without .eh_frame_hdr +// [ ] asan +// [ ] windowing +// [ ] wayland +// [ ] rendering +// [ ] bitmaps / geometry +// +// [x] clipboard // [x] jai exes - busted call stacks / unwind info / paths? // [x] statically linked EXEs - busted unwinding // [x] statically linked EXEs, not parsing maps file correctly @@ -105,8 +110,8 @@ // recent files, watches?, etc. // // [ ] linux/dwarf fixes -// [ ] excessive CPU usage on async threads - barrier impl? // [ ] type views for `MyByte *` example do not match correctly +// [x] excessive CPU usage on async threads - barrier impl? // // [ ] many threads hitting conditional breakpoints -> causes 0x8000003 exception! // [ ] string conditional breakpoints -> size != 0 check seems to fail, can test w/ "rd_init" subprogram type gen in d2r2 diff --git a/src/window_manager/window_manager.c b/src/window_manager/window_manager.c index c6008c13..e64cf883 100644 --- a/src/window_manager/window_manager.c +++ b/src/window_manager/window_manager.c @@ -42,15 +42,15 @@ wm_string_from_event_kind(WM_EventKind kind) case WM_EventKind_Null: case WM_EventKind_COUNT: {}break; - case WM_EventKind_Press: {result = str8_lit("Press");}break; - case WM_EventKind_Release: {result = str8_lit("Release");}break; - case WM_EventKind_MouseMove: {result = str8_lit("MouseMove");}break; - case WM_EventKind_Text: {result = str8_lit("Text");}break; - case WM_EventKind_Scroll: {result = str8_lit("Scroll");}break; - case WM_EventKind_WindowLoseFocus: {result = str8_lit("WindowLoseFocus");}break; - case WM_EventKind_WindowClose: {result = str8_lit("WindowClose");}break; - case WM_EventKind_FileDrop: {result = str8_lit("FileDrop");}break; - case WM_EventKind_Wakeup: {result = str8_lit("Wakeup");}break; + case WM_EventKind_Press: {result = s("Press");}break; + case WM_EventKind_Release: {result = s("Release");}break; + case WM_EventKind_MouseMove: {result = s("MouseMove");}break; + case WM_EventKind_Text: {result = s("Text");}break; + case WM_EventKind_Scroll: {result = s("Scroll");}break; + case WM_EventKind_WindowLoseFocus: {result = s("WindowLoseFocus");}break; + case WM_EventKind_WindowClose: {result = s("WindowClose");}break; + case WM_EventKind_FileDrop: {result = s("FileDrop");}break; + case WM_EventKind_Wakeup: {result = s("Wakeup");}break; } return result; } From ed10cd771a595af38ece4ba111b5bfb6268a4d36 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 25 Sep 2026 13:15:31 -0600 Subject: [PATCH 185/251] ci xfixes --- .github/workflows/builds.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index c881c968..75a0c00b 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -40,7 +40,7 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install --no-install-recommends -y pkg-config libfreetype6-dev libx11-dev libxext-dev libgl1-mesa-dev libegl1-mesa-dev + sudo apt-get install --no-install-recommends -y pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev - name: Install clang-17 Dependencies if: ${{ matrix.compiler == 'clang-17' }} @@ -162,7 +162,7 @@ jobs: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-add-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-22 main" sudo apt-get update - sudo apt-get install -y clang-22 lld-22 llvm-22 gcc-14 pkg-config libfreetype6-dev libx11-dev libxext-dev libgl1-mesa-dev libegl1-mesa-dev + sudo apt-get install -y clang-22 lld-22 llvm-22 gcc-14 pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev # crash callstack symbolizer depends on LLVM sudo ln -s /usr/bin/llvm-symbolizer-22 /usr/bin/llvm-symbolizer - name: Run From 315175e34d7563cb0cf2be74868a76af7fbd45c3 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 25 Sep 2026 13:45:31 -0600 Subject: [PATCH 186/251] keep debug info status viz, just omit string if it matches the module (usual linux case) --- project.raddbg | 10 +++++++ src/raddbg/raddbg_widgets.c | 57 +++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/project.raddbg b/project.raddbg index 83038133..1ed437e5 100755 --- a/project.raddbg +++ b/project.raddbg @@ -35,5 +35,15 @@ target: executable: "build/raddbg" working_directory: build arguments: "--user:raddbg_test.user" +} +target: +{ + executable: "build/metagen" + working_directory: build +} +target: +{ + executable: "../jai/examples/invaders/invaders" + working_directory: "../jai/examples/invaders" enabled: 1 } diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 1ba63380..c9810b4c 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -618,46 +618,47 @@ rd_title_fstrs_from_ctrl_entity(Arena *arena, D_Entity *entity, B32 include_extr if(entity->kind == D_EntityKind_Module && include_extras) { D_Entity *debug_info_path = d_entity_child_from_kind(entity, D_EntityKind_DebugInfoPath); - if(!str8_match(debug_info_path->string, entity->string, 0)) + B32 need_debug_info_name = !str8_match(debug_info_path->string, entity->string, 0); + Access *access = access_open(); + DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(debug_info_path); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); + if(rdi == &di_rdi_parsed_nil_waiting) { - Access *access = access_open(); - DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(debug_info_path); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); - if(rdi == &di_rdi_parsed_nil_waiting) + UI_TagF("pop") { - UI_TagF("pop") - { - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, s("Loading debug info"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_CircleFilled], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); - dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); - } + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, s("Loading debug info"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_CircleFilled], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); } - else if(rdi->raw_data_size == 0) + } + else if(rdi->raw_data_size == 0) + { + UI_TagF("bad_pop") { - UI_TagF("bad_pop") - { - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, s("Debug info not found"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_X], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); - dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); - } + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, s("Debug info not found"), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_X], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); } - else + } + else + { + UI_TagF("good_pop") { - UI_TagF("good_pop") + if(need_debug_info_name) { dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); dr_fstrs_push_new(arena, &result, ¶ms, str8_skip_last_slash(debug_info_path->string), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main), .size = extras_size, .color = secondary_color); - dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); - dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_Check], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); - dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); } + dr_fstrs_push_new(arena, &result, ¶ms, s(" ")); + dr_fstrs_push_new(arena, &result, ¶ms, rd_icon_kind_text_table[RD_IconKind_Check], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons), .size = extras_size, .color = ui_color_from_name(s("background"))); + dr_fstrs_push_new(arena, &result, ¶ms, s(" "), .font = rd_font_from_slot(RD_FontSlot_Main), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Main)); } - access_close(access); } + access_close(access); } return result; From 2c226d8a4bb6b1bf13540196bf32670b811aeecd Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 25 Sep 2026 14:28:54 -0600 Subject: [PATCH 187/251] selected-thread-only breakpoints; user setting for adding that by default; do this for 'run-to-line' operations --- src/dbg_engine/dbg_engine_core.h | 7 ++++--- src/dbg_engine/dbg_engine_ctrl.c | 18 ++++++++++++++++++ src/dbg_engine/dbg_engine_user.c | 10 ++++++---- src/dbg_engine/dbg_engine_user.h | 3 +-- src/raddbg/generated/raddbg.meta.c | 4 ++-- src/raddbg/raddbg.mdesk | 5 +++++ src/raddbg/raddbg_core.c | 17 +++++++++++++---- src/raddbg/raddbg_widgets.c | 17 +++++++++++++++++ 8 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/dbg_engine/dbg_engine_core.h b/src/dbg_engine/dbg_engine_core.h index c40df17d..971a21f8 100644 --- a/src/dbg_engine/dbg_engine_core.h +++ b/src/dbg_engine/dbg_engine_core.h @@ -77,9 +77,10 @@ struct D_HandleArray typedef U32 D_BreakpointFlags; enum { - D_BreakpointFlag_BreakOnWrite = (1<<0), - D_BreakpointFlag_BreakOnRead = (1<<1), - D_BreakpointFlag_BreakOnExecute = (1<<2), + D_BreakpointFlag_BreakOnWrite = (1<<0), + D_BreakpointFlag_BreakOnRead = (1<<1), + D_BreakpointFlag_BreakOnExecute = (1<<2), + D_BreakpointFlag_BreakSelectedThreadOnly = (1<<3), }; typedef struct D_Breakpoint D_Breakpoint; diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index ba69ae65..2f7d3f66 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -5346,6 +5346,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { Temp temp = temp_begin(scratch.arena); String8List conditions = {0}; + B32 filter_unselected_threads = 0; // rjf: entry breakpoints for(DMN_TrapChunkNode *n = entry_traps.first; n != 0; n = n->next) @@ -5383,6 +5384,10 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { str8_list_push(temp.arena, &conditions, user_bp->condition); } + if(user_bp != 0 && !(trap->id & bit64) && user_bp->flags & D_BreakpointFlag_BreakSelectedThreadOnly) + { + filter_unselected_threads = 1; + } } } } @@ -5396,6 +5401,19 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) hit_user_bp = 1; } + // rjf: if this breakpoint filters unselected threads, check that the current thread matches + // the selected, *or* that we have no selected thread at all. + if(!hit_conditional_bp_but_filtered && filter_unselected_threads) + { + B32 thread_is_selected = (d_handle_match(thread->handle, msg->entity) || d_handle_match(msg->entity, d_handle_zero())); + if(!thread_is_selected) + { + hit_user_bp = 0; + hit_conditional_bp_but_filtered = 1; + log_infof("thread_filtered_breakpoint_hit: 'not selected thread, and so filtered'\n"); + } + } + // rjf: evaluate hit stop conditions if(conditions.node_count != 0) ProfScope("evaluate hit stop conditions") { diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index 4ea507a3..038f1107 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -1569,7 +1569,7 @@ d_next_cmd(D_Cmd **cmd) //~ rjf: Main Layer Top-Level Calls internal D_EventList -d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_PathMapArray *path_maps, U64 exception_code_filters[(D_ExceptionCodeKind_COUNT+63)/64], B32 auto_download_debug_info) +d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_PathMapArray *path_maps, D_Handle selected_thread, U64 exception_code_filters[(D_ExceptionCodeKind_COUNT+63)/64], B32 auto_download_debug_info) { ProfBeginFunction(); Temp scratch = scratch_begin(&arena, 1); @@ -1683,6 +1683,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P // rjf: build data strings of all param data String8List strings = {0}; { + str8_list_push(scratch.arena, &strings, str8_struct(&selected_thread)); D_EntityArray threads = d_entity_array_from_kind(D_EntityKind_Thread); for EachIndex(idx, threads.count) { @@ -1696,6 +1697,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P for(U64 idx = 0; idx < breakpoints->count; idx += 1) { D_Breakpoint *bp = &breakpoints->v[idx]; + str8_list_push(scratch.arena, &strings, str8_struct(&bp->flags)); str8_list_push(scratch.arena, &strings, bp->file_path); str8_list_push(scratch.arena, &strings, str8_struct(&bp->pt)); str8_list_push(scratch.arena, &strings, bp->vaddr_expr); @@ -1994,7 +1996,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P { need_run = 1; run_kind = d_user_state->ctrl_last_run_kind; - run_thread = d_entity_from_handle(d_user_state->ctrl_last_run_thread_handle); + run_thread = d_entity_from_handle(selected_thread); run_flags = d_user_state->ctrl_last_run_flags; run_traps = d_user_state->ctrl_last_run_traps; }break; @@ -2030,6 +2032,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P { run_extra_bps.v[0].vaddr_expr = push_str8f(scratch.arena, "0x%I64x", params->vaddr); } + run_extra_bps.v[0].flags |= D_BreakpointFlag_BreakSelectedThreadOnly; d_cmd(D_CmdKind_Run); }break; case D_CmdKind_RunToName: @@ -2130,7 +2133,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P { need_run = 1; run_kind = d_user_state->ctrl_last_run_kind; - run_thread = d_entity_from_handle(d_user_state->ctrl_last_run_thread_handle); + run_thread = d_entity_from_handle(selected_thread); run_flags = d_user_state->ctrl_last_run_flags; run_traps = d_user_state->ctrl_last_run_traps; } @@ -2252,7 +2255,6 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P arena_clear(d_user_state->ctrl_last_run_arena); d_user_state->ctrl_last_run_kind = run_kind; d_user_state->ctrl_last_run_frame_idx = d_frame_index(); - d_user_state->ctrl_last_run_thread_handle = run_thread->handle; d_user_state->ctrl_last_run_flags = run_flags; d_user_state->ctrl_last_run_traps = d_trap_list_copy(d_user_state->ctrl_last_run_arena, &run_traps_copy); d_user_state->ctrl_last_run_extra_bps = d_breakpoint_array_copy(d_user_state->ctrl_last_run_arena, &run_extra_bps_copy); diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index ba467329..7cf59551 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -366,7 +366,6 @@ struct D_UserState Arena *ctrl_last_run_arena; D_RunKind ctrl_last_run_kind; U64 ctrl_last_run_frame_idx; - D_Handle ctrl_last_run_thread_handle; D_RunFlags ctrl_last_run_flags; D_TrapList ctrl_last_run_traps; D_BreakpointArray ctrl_last_run_extra_bps; @@ -481,6 +480,6 @@ internal B32 d_next_cmd(D_Cmd **cmd); //////////////////////////////// //~ rjf: Main Layer Top-Level Calls -internal D_EventList d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_PathMapArray *path_maps, U64 exception_code_filters[(D_ExceptionCodeKind_COUNT+63)/64], B32 auto_download_debug_info); +internal D_EventList d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_PathMapArray *path_maps, D_Handle selected_thread, U64 exception_code_filters[(D_ExceptionCodeKind_COUNT+63)/64], B32 auto_download_debug_info); #endif // DBG_ENGINE_USER_H diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 52ab0f05..7a9d7690 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -438,7 +438,7 @@ RD_VocabInfo rd_vocab_info_table[370] = RD_NameSchemaInfo rd_name_schema_info_table[39] = { -{str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('Use Alternative Font For UI') @description(\"Uses an alternative font for non-code UI elements.\")\n @default(1) 'use_alternative_font_for_ui': bool,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n @expand_if(\"!$.use_alternative_font_for_ui\") @display_name('Font') @description(\"The name of, or path to, the font used in all UI.\")\n @default('') 'font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n\n //- rjf: system installation\n @display_name('Install To System') @description(\"Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.\") 'install_to_system': bool,\n}\n")}, +{str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('Use Alternative Font For UI') @description(\"Uses an alternative font for non-code UI elements.\")\n @default(1) 'use_alternative_font_for_ui': bool,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n @expand_if(\"!$.use_alternative_font_for_ui\") @display_name('Font') @description(\"The name of, or path to, the font used in all UI.\")\n @default('') 'font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: breakpoint behavior\n @default(0) @display_name('Lock New Breakpoints To Selected Thread') @description(\"When placing breakpoints, locks them to the selected thread by default.\")\n 'lock_new_breakpoints_to_selected_thread': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n\n //- rjf: system installation\n @display_name('Install To System') @description(\"Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.\") 'install_to_system': bool,\n}\n")}, {str8_lit_comp("project"), 0, str8_lit_comp("@expand_commands(edit_project_theme) x:\n{\n @display_name('Project Name') 'name': string,\n @default(2) @display_name('Project Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: visualizers\n @display_name('Display Pointer Addresses Before Contents') @description(\"When visualizing pointers, always shows the address first, before showing contents at the pointer's address.\")\n @default(0) display_pointer_addresses_before_contents: bool,\n @display_name('Use Default C++ STL Type Visualizers') @description(\"Enables the built-in type views for C++ STL types.\")\n @default(1) use_default_stl_type_views: bool,\n @display_name('Use Default Unreal Engine Type Visualizers') @description(\"Enables the built-in type views for Unreal Engine types.\")\n @default(1) use_default_ue_type_views: bool,\n\n //- rjf: theme\n @default(\"None\") @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme': string,\n @no_expand @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme_colors': set,\n\n //- rjf: exception settings\n @default(1) @display_name(\"Break On Win32 Control-C Exceptions\") @description(\"Code: 0x40010005\")\n win32_ctrl_c: bool;\n @default(1) @display_name(\"Break On Win32 Control-Break Exceptions\") @description(\"Code: 0x40010008\")\n win32_ctrl_break: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Originate Error Exceptions\") @description(\"Code: 0x40080201\")\n win32_win_rt_originate_error: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Transform Error Exceptions\") @description(\"Code: 0x40080202\")\n win32_win_rt_transform_error: bool;\n @default(0) @display_name(\"Break On Win32 RPC Call Cancelled Exceptions\") @description(\"Code: 0x0000071a\")\n win32_rpc_call_cancelled: bool;\n @default(0) @display_name(\"Break On Win32 Data Type Misalignment Exceptions\") @description(\"Code: 0x80000002\")\n win32_datatype_misalignment: bool;\n @default(1) @display_name(\"Break On Win32 Access Violation Exceptions\") @description(\"Code: 0xc0000005\")\n win32_access_violation: bool;\n @default(0) @display_name(\"Break On Win32 In Page Error Exceptions\") @description(\"Code: 0xc0000006\")\n win32_in_page_error: bool;\n @default(1) @display_name(\"Break On Win32 Invalid Handle Specified Exceptions\") @description(\"Code: 0xc0000008\")\n win32_invalid_handle: bool;\n @default(0) @display_name(\"Break On Win32 Not Enough Quota Exceptions\") @description(\"Code: 0xc0000017\")\n win32_not_enough_quota: bool;\n @default(0) @display_name(\"Break On Win32 Illegal Instruction Exceptions\") @description(\"Code: 0xc000001d\")\n win32_illegal_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Cannot Continue From Exception Exceptions\") @description(\"Code: 0xc0000025\")\n win32_cannot_continue_exception: bool;\n @default(0) @display_name(\"Break On Win32 Invalid Exception Disposition Returned By Handler Exceptions\") @description(\"Code: 0xc0000026\")\n win32_invalid_exception_disposition: bool;\n @default(0) @display_name(\"Break On Win32 Array Bounds Exceeded Exceptions\") @description(\"Code: 0xc000008c\")\n win32_array_bounds_exceeded: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Denormal Operand Exceptions\") @description(\"Code: 0xc000008d\")\n win32_floating_point_denormal_operand: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Division By Zero Exceptions\") @description(\"Code: 0xc000008e\")\n win32_floating_point_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Inexact Result Exceptions\") @description(\"Code: 0xc000008f\")\n win32_floating_point_inexact_result: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Invalid Operation Exceptions\") @description(\"Code: 0xc0000090\")\n win32_floating_point_invalid_operation: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Overflow Exceptions\") @description(\"Code: 0xc0000091\")\n win32_floating_point_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Stack Check Exceptions\") @description(\"Code: 0xc0000092\")\n win32_floating_point_stack_check: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Underflow Exceptions\") @description(\"Code: 0xc0000093\")\n win32_floating_point_underflow: bool;\n @default(0) @display_name(\"Break On Win32 Integer Division By Zero Exceptions\") @description(\"Code: 0xc0000094\")\n win32_integer_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Integer Overflow Exceptions\") @description(\"Code: 0xc0000095\")\n win32_integer_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Privileged Instruction Exceptions\") @description(\"Code: 0xc0000096\")\n win32_privileged_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Stack Overflow Exceptions\") @description(\"Code: 0xc00000fd\")\n win32_stack_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Unable To Locate DLL Exceptions\") @description(\"Code: 0xc0000135\")\n win32_unable_to_locate_dll: bool;\n @default(0) @display_name(\"Break On Win32 Ordinal Not Found Exceptions\") @description(\"Code: 0xc0000138\")\n win32_ordinal_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Entry Point Not Found Exceptions\") @description(\"Code: 0xc0000139\")\n win32_entry_point_not_found: bool;\n @default(0) @display_name(\"Break On Win32 DLL Initialization Failed Exceptions\") @description(\"Code: 0xc0000142\")\n win32_dll_initialization_failed: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Faults Exceptions\") @description(\"Code: 0xc00002b4\")\n win32_floating_point_sse_multiple_faults: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Traps Exceptions\") @description(\"Code: 0xc00002b5\")\n win32_floating_point_sse_multiple_traps: bool;\n @default(1) @display_name(\"Break On Win32 Assertion Failed Exceptions\") @description(\"Code: 0xc0000420\")\n win32_assertion_failed: bool;\n @default(0) @display_name(\"Break On Win32 Module Not Found Exceptions\") @description(\"Code: 0xc06d007e\")\n win32_module_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Procedure Not Found Exceptions\") @description(\"Code: 0xc06d007f\")\n win32_procedure_not_found: bool;\n @default(1) @display_name(\"Break On Win32 Sanitizer Error Detected Exceptions\") @description(\"Code: 0xe073616e\")\n win32_sanitizer_error_detected: bool;\n @default(0) @display_name(\"Break On Win32 Sanitizer Raw Access Violation Exceptions\") @description(\"Code: 0xe0736171\")\n win32_sanitizer_raw_access_violation: bool;\n @default(1) @display_name(\"Break On Win32 DirectX Debug Layer Exceptions\") @description(\"Code: 0x0000087a\")\n win32_directx_debug_layer: bool;\n}\n")}, {str8_lit_comp("theme_color"), 0, str8_lit_comp("@collection_commands(add_theme_color, fork_theme, save_theme, save_and_set_theme)\n@row_commands(duplicate_cfg, remove_cfg)\nx:\n{\n @display_name('Tags') tags: string,\n @display_name('Value') value: @color @hex u32,\n}\n")}, {str8_lit_comp("window"), 0, str8_lit_comp("x:\n{\n //- rjf: text rasterization settings\n @default(1) @display_name('Smooth UI Text') @description(\"Controls whether or not UI text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_ui_text': bool,\n @default(1) @display_name('Hint UI Text') @description(\"Controls whether or not UI text is hinted, for better text readability at small sizes.\")\n 'hint_ui_text': bool,\n @default(0) @display_name('Smooth Code Text') @description(\"Controls whether or not code text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_code_text': bool,\n @default(1) @display_name('Hint Code Text') @description(\"Controls whether or not code text is hinted, for better text readability at small sizes.\")\n 'hint_code_text': bool,\n @default(11) @display_name('Window Font Size') @description(\"Controls the window's default font size. Does not apply to tabs with their own font size set.\")\n 'font_size': @range[6, 72] u64,\n\n //- rjf: size settings\n @default(3.f) @display_name('Window Row Height') @description(\"Controls the window's default row height, in multiples of the font size. Does not apply to tabs with their own row height set.\")\n 'row_height': @range[1.75f, 5.f] f32,\n @default(3.f) @description(\"Controls the height of tabs, in multiples of the font size.\")\n 'tab_height': @range[1.75f, 5.f] f32,\n\n //- rjf: theme settings\n @default(1) @display_name('Use Project Theme') @description(\"Prefer using the project theme for this window, if any. If off, only the user's theme settings will be used.\")\n 'use_project_theme': bool,\n}\n")}, @@ -467,7 +467,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("geo3d"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @display_name(\"Expression\") @description(\"An expression to describe the base address of the index buffer.\")\n 'expression': expr_string,\n 'count': expr_string,\n 'vtx': expr_string,\n 'vtx_size': expr_string,\n 'yaw': @range[0, 1] f32,\n 'pitch': @range[-0.5, 0] f32,\n 'zoom': @range[0, 100] f32,\n}\n")}, {str8_lit_comp("getting_started"), 0, str8_lit_comp("@inherit(tab) x:\n{\n}\n")}, {str8_lit_comp("target"), 0, str8_lit_comp("@row_commands(@cmd_line save_cfg_to_project, enable_cfg, launch_and_run, launch_and_step_into, duplicate_cfg, remove_cfg)\n@collection_commands(add_target)\nx:\n{\n 'label': code_string,\n 'executable': path,\n 'arguments': string,\n 'working_directory': @folder path,\n 'entry_point': expr_string,\n @default(\"output\") 'output_label': code_string,\n 'stdout_path': @can_not_exist @no_relativize path,\n 'stderr_path': @can_not_exist @no_relativize path,\n 'stdin_path': @can_not_exist @no_relativize path,\n 'environment': set,\n 'debug_subprocesses': bool,\n @no_revert @no_expand @default(0) 'enabled': bool,\n}\n")}, -{str8_lit_comp("breakpoint"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(toggle_breakpoint, add_breakpoint, add_address_breakpoint, add_function_breakpoint, clear_breakpoints)\nx:\n{\n 'label': code_string,\n 'condition': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n 'hit_count': u64,\n 'address_range_size': @or(0, 1, 2, 4, 8) u64,\n 'break_on_write': bool,\n 'break_on_read': bool,\n 'break_on_execute': bool,\n @no_revert @no_expand @default(1) 'enabled': bool,\n}\n")}, +{str8_lit_comp("breakpoint"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(toggle_breakpoint, add_breakpoint, add_address_breakpoint, add_function_breakpoint, clear_breakpoints)\nx:\n{\n 'label': code_string,\n 'condition': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n 'hit_count': u64,\n 'address_range_size': @or(0, 1, 2, 4, 8) u64,\n 'break_on_write': bool,\n 'break_on_read': bool,\n 'break_on_execute': bool,\n @display_name(\"Break Selected Thread Only\") 'break_selected_thread_only': bool,\n @no_revert @no_expand @default(1) 'enabled': bool,\n}\n")}, {str8_lit_comp("watch_pin"), 0, str8_lit_comp("@row_commands(duplicate_cfg, remove_cfg)\n@collection_commands(add_watch_pin, toggle_watch_pin)\nx:\n{\n 'expression': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n}\n")}, {str8_lit_comp("debug_info"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(load_debug_info)\nx:\n{\n 'path': @no_relativize path,\n @no_revert @no_expand @default(1) 'enabled': bool,\n @query 'guid': string,\n}\n")}, {str8_lit_comp("file_path_map"), 0, str8_lit_comp("@collection_commands(add_file_path_map) @row_commands(remove_cfg) x:{'source': @no_relativize path, 'dest': @no_relativize path}")}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index a1ec9285..2c9d75a8 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -326,6 +326,10 @@ RD_VocabTable: @default(0) @display_name('Auto Download Debug Info') @description("Automatically tries to download missing debug info from symbol server(s).") 'auto_download_debug_info': bool, + //- rjf: breakpoint behavior + @default(0) @display_name('Lock New Breakpoints To Selected Thread') @description("When placing breakpoints, locks them to the selected thread by default.") + 'lock_new_breakpoints_to_selected_thread': bool, + //- rjf: update check @default(1) @display_name('Check For Updates') @description("Enables a small network request to check if there is a newer release version available.") 'check_for_updates': bool, @@ -756,6 +760,7 @@ RD_VocabTable: 'break_on_write': bool, 'break_on_read': bool, 'break_on_execute': bool, + @display_name("Break Selected Thread Only") 'break_selected_thread_only': bool, @no_revert @no_expand @default(1) 'enabled': bool, } ```, diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 02feb823..ca3898bc 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -16864,6 +16864,11 @@ rd_frame(void) rd_cmd(RD_CmdKind_PushQuery, .expr = push_str8f(scratch.arena, "query:config.$%I64x", bp->id), .do_lister = 0); } str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "$%I64x", bp->id); + if(rd_setting_b32_from_name(s("lock_new_breakpoints_to_selected_thread"))) + { + CFG_Node *filter = cfg_node_new(rd_state->cfg, bp, s("break_selected_thread_only")); + cfg_node_new(rd_state->cfg, filter, s("1")); + } } } }break; @@ -17997,18 +18002,22 @@ rd_frame(void) //- rjf: compute breakpoint flags D_BreakpointFlags flags = 0; - if(str8_match(cfg_node_child_from_string(src_bp, str8_lit("break_on_write"))->first->string, str8_lit("1"), 0)) + if(str8_match(cfg_node_child_from_string(src_bp, s("break_on_write"))->first->string, s("1"), 0)) { flags |= D_BreakpointFlag_BreakOnWrite; } - if(str8_match(cfg_node_child_from_string(src_bp, str8_lit("break_on_read"))->first->string, str8_lit("1"), 0)) + if(str8_match(cfg_node_child_from_string(src_bp, s("break_on_read"))->first->string, s("1"), 0)) { flags |= D_BreakpointFlag_BreakOnRead; } - if(str8_match(cfg_node_child_from_string(src_bp, str8_lit("break_on_execute"))->first->string, str8_lit("1"), 0)) + if(str8_match(cfg_node_child_from_string(src_bp, s("break_on_execute"))->first->string, s("1"), 0)) { flags |= D_BreakpointFlag_BreakOnExecute; } + if(str8_match(cfg_node_child_from_string(src_bp, s("break_selected_thread_only"))->first->string, s("1"), 0)) + { + flags |= D_BreakpointFlag_BreakSelectedThreadOnly; + } //- rjf: compute address range size U64 addr_range_size = 0; @@ -18073,7 +18082,7 @@ rd_frame(void) } U64 cmd_count_pre_tick = rd_state->cmds[0].count; B32 soft_halt_issued = d_user_state->ctrl_soft_halt_issued; - D_EventList engine_events = d_tick(scratch.arena, &targets, &breakpoints, &path_maps, exception_code_filters, rd_state->auto_download_debug_info); + D_EventList engine_events = d_tick(scratch.arena, &targets, &breakpoints, &path_maps, rd_base_regs()->thread, exception_code_filters, rd_state->auto_download_debug_info); //////////////////////////// //- rjf: process debug engine events diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index c9810b4c..ffe9a842 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -1225,6 +1225,7 @@ struct RD_BreakpointBoxDrawExtData B32 do_glow; B32 is_disabled; B32 is_conditioned; + B32 is_global; }; internal UI_BOX_CUSTOM_DRAW(rd_bp_box_draw_extensions) @@ -1308,6 +1309,20 @@ internal UI_BOX_CUSTOM_DRAW(rd_bp_box_draw_extensions) scratch_end(scratch); } + // rjf: draw non-global marker + if(!u->is_global) UI_TagF(u->is_disabled ? "weak" : "") + { + Temp scratch = scratch_begin(0, 0); + Vec4F32 color = ui_color_from_name(s("text")); + FNT_Run run = fnt_run_from_string(rd_font_from_slot(RD_FontSlot_Icons), box->font_size*0.85f, 0, 0, FNT_RasterFlag_Smooth, rd_icon_kind_text_table[RD_IconKind_Thread]); + Vec2F32 box_dim = dim_2f32(box->rect); + Vec2F32 p = center_2f32(box->rect); + p.x += box_dim.x*0.1f; + p.y += box_dim.y*0.4f; + dr_text_run(p, color, run); + scratch_end(scratch); + } + // rjf: draw disabled marker if(u->is_disabled) { @@ -1801,6 +1816,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred { bp_rgba = v4f32(bp_rgba.x*0.45f, bp_rgba.y*0.45f, bp_rgba.z*0.45f, bp_rgba.w*0.45f); } + B32 bp_is_global = !str8_match(cfg_node_child_from_string(bp, s("break_selected_thread_only"))->first->string, s("1"), 0); // rjf: prep custom rendering data RD_BreakpointBoxDrawExtData *bp_draw = push_array(ui_build_arena(), RD_BreakpointBoxDrawExtData, 1); @@ -1814,6 +1830,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred bp_draw->do_glow = do_bp_glow; bp_draw->is_disabled = bp_is_disabled; bp_draw->is_conditioned = (cfg_node_child_from_string(bp, s("condition"))->first->string.size != 0); + bp_draw->is_global = bp_is_global; if(params->line_vaddrs[line_idx] == 0) { D_LineList *lines = ¶ms->line_infos[line_idx]; From 1ac8fb520285db493bf4274e9a6e375088224c51 Mon Sep 17 00:00:00 2001 From: kwy404 Date: Sat, 26 Sep 2026 05:11:25 -0300 Subject: [PATCH 188/251] base: fix data4 byte order in try_guid_from_string The last two GUID groups were packed into a U64 and copied to data4 in host byte order, so on little endian data4 came out reversed and string_from_guid did not round trip (for example /RAD_GUID). Store the value big endian so data4[0] is the first byte of the fourth group. --- src/base/base_strings.c | 2 +- src/base/tests/base_tests.c | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 60c69eed..54d32921 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -2487,7 +2487,7 @@ try_guid_from_string(String8 string, Guid *guid_out) guid_out->data1 = (U32)data1; guid_out->data2 = (U16)data2; guid_out->data3 = (U16)data3; - U64 data4 = (data4_hi << 48) | data4_lo; + U64 data4 = from_be_u64((data4_hi << 48) | data4_lo); MemoryCopy(&guid_out->data4[0], &data4, sizeof(data4)); is_parsed = 1; } diff --git a/src/base/tests/base_tests.c b/src/base/tests/base_tests.c index 522e7844..bc1a4bc9 100644 --- a/src/base/tests/base_tests.c +++ b/src/base/tests/base_tests.c @@ -102,6 +102,17 @@ TEST(count_digits) T_Ok(count_digits_u64(999999, 10) == 6); } +TEST(guid_from_string) +{ + String8 string = str8_lit("01234567-89AB-CDEF-0011-223344556677"); + U8 data4[8] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }; + Guid guid = {0}; + T_Ok(try_guid_from_string(string, &guid)); + T_Ok(guid.data1 == 0x01234567 && guid.data2 == 0x89AB && guid.data3 == 0xCDEF); + T_Ok(MemoryMatch(guid.data4, data4, sizeof(data4))); + T_Ok(str8_match(string_from_guid(arena, guid), string, 0)); +} + TEST(match_wildcard) { // empty strings From d66094161df7b932b5f3d67707d1187d35a6b532 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 28 Sep 2026 08:31:05 -0600 Subject: [PATCH 189/251] rdi_from_dwarf: do not assume non-zero root tags in each unit --- src/rdi_from_dwarf/rdi_from_dwarf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index ff5d9571..84831699 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -1102,7 +1102,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) { U64 root_tags_in_this_unit = unit_info_root_tag_offs[unit_idx].count; U64 root_tags_per_work = (root_tags_in_this_unit + lane_count()-1) / lane_count(); - U64 works_per_this_unit = (root_tags_in_this_unit+root_tags_per_work-1) / root_tags_per_work; + U64 works_per_this_unit = root_tags_per_work ? ((root_tags_in_this_unit+root_tags_per_work-1) / root_tags_per_work) : 0; if(build) { D2R_SubUnitWork *works = sub_unit_works + sub_unit_work_idx; From e7a9c430f8a89c224df43b70ed6d4b30cadb5808 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 28 Sep 2026 08:38:30 -0600 Subject: [PATCH 190/251] rdi_from_dwarf: fix incorrect argument slot used in breg conversion --- src/raddbg/raddbg_main.c | 7 +++++-- src/rdi_from_dwarf/rdi_from_dwarf.c | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 89454070..87b887ea 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -4,17 +4,20 @@ //////////////////////////////// //~ rjf: linux TODO notes // +// initial alpha: // [ ] control // [ ] multithreaded stepping - trap hits reported on other threads. // [ ] stale memory - things evaluating to stale values that are definitely not zero // [ ] support // [ ] .eh_frame unwinding without .eh_frame_hdr // [ ] asan -// [ ] windowing -// [ ] wayland // [ ] rendering // [ ] bitmaps / geometry // +// post initial alpha: +// [ ] windowing +// [ ] wayland +// // [x] clipboard // [x] jai exes - busted call stacks / unwind info / paths? // [x] statically linked EXEs - busted unwinding diff --git a/src/rdi_from_dwarf/rdi_from_dwarf.c b/src/rdi_from_dwarf/rdi_from_dwarf.c index 84831699..41048658 100644 --- a/src/rdi_from_dwarf/rdi_from_dwarf.c +++ b/src/rdi_from_dwarf/rdi_from_dwarf.c @@ -3526,7 +3526,7 @@ d2r_convert(Arena *arena, D2R_ConvertParams *params) case DW_ExprOp_BReg30: case DW_ExprOp_BReg31: { regcode_dw = (U64)(opcode - DW_ExprOp_BReg0); - regval_off = operand_s64s[1]; + regval_off = operand_s64s[0]; regread_is_addr = 1; }goto reg_read; case DW_ExprOp_BRegX: From 4e7a6127d86265ce0b955176816e3beef32b280d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 28 Sep 2026 09:18:59 -0600 Subject: [PATCH 191/251] dbg_engine: process memory cache - always retry on memgen 'tearing', do not enforce size restriction. linux_demon: correctly relativize loaded module paths when needed per the loading process' working directory. raddbg: disasm view, do not skip too far back - can go into unmapped space. eval: file/folder filtering - hoist exact matches to the top. --- project.raddbg | 2 +- src/dbg_engine/dbg_engine_ctrl.c | 7 ++----- src/eval/eval_types.c | 18 ++++++++++++++++-- src/linux/base/linux_base.h | 1 - src/linux/demon/linux_demon.c | 18 ++++++++++++++++++ src/raddbg/raddbg_main.c | 2 ++ src/raddbg/raddbg_views.c | 4 ++-- src/win32/demon/win32_demon.c | 3 --- 8 files changed, 41 insertions(+), 14 deletions(-) diff --git a/project.raddbg b/project.raddbg index 1ed437e5..174178b1 100755 --- a/project.raddbg +++ b/project.raddbg @@ -35,6 +35,7 @@ target: executable: "build/raddbg" working_directory: build arguments: "--user:raddbg_test.user" + enabled: 1 } target: { @@ -45,5 +46,4 @@ target: { executable: "../jai/examples/invaders/invaders" working_directory: "../jai/examples/invaders" - enabled: 1 } diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 2f7d3f66..a0eaab39 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -2782,7 +2782,6 @@ d_ctrl_thread__close_dump_process(D_MsgID msg_id, D_Handle process) evt->kind = D_EventKind_EndModule; evt->msg_id = msg_id; evt->entity = node->modules[idx].module_handle; - evt->string = node->modules[idx].path; d_c2u_push_events(&evts); scratch_end(scratch); } @@ -3335,7 +3334,6 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D //- rjf: unload module case DMN_EventKind_UnloadModule: - ProfScope("unload module %.*s", str8_varg(event->string)) { //- rjf: unpack module D_Handle module_handle = d_handle_from_dmn(D_MachineID_Local, event->module); @@ -3360,7 +3358,6 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->kind = D_EventKind_EndModule; out_evt->msg_id = msg->msg_id; out_evt->entity = module_handle; - out_evt->string = module_path; //- rjf: close debug info DI_Key dbgi_key = d_dbgi_key_from_module(module_ent); @@ -6103,8 +6100,8 @@ d_memory_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, arena_release(range_arena); } - //- rjf: retry on mem gen "tearing", and if the range is non-empty - if(pre_read_mem_gen != post_read_mem_gen && range_size != 0) + //- rjf: retry on mem gen "tearing" + if(pre_read_mem_gen != post_read_mem_gen) { status_out[0] = AC_Status_NeedRetry; } diff --git a/src/eval/eval_types.c b/src/eval/eval_types.c index de41c9a3..bfe585de 100644 --- a/src/eval/eval_types.c +++ b/src/eval/eval_types.c @@ -3111,11 +3111,25 @@ E_TYPE_EXPAND_INFO_FUNCTION_DEF(folder) { if(info.props.flags & FilePropertyFlag_IsFolder) { - str8_list_push(scratch.arena, &folder_paths, str8_copy(arena, info.name)); + if(str8_match(info.name, local_filter, 0)) + { + str8_list_push_front(scratch.arena, &folder_paths, str8_copy(arena, info.name)); + } + else + { + str8_list_push(scratch.arena, &folder_paths, str8_copy(arena, info.name)); + } } else { - str8_list_push(scratch.arena, &file_paths, str8_copy(arena, info.name)); + if(str8_match(info.name, local_filter, 0)) + { + str8_list_push_front(scratch.arena, &file_paths, str8_copy(arena, info.name)); + } + else + { + str8_list_push(scratch.arena, &file_paths, str8_copy(arena, info.name)); + } } } } diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index b35c175a..230f7f18 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -9,7 +9,6 @@ #include #include -#include #include #include #include diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 4274b0e0..10e9efa9 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -302,6 +302,24 @@ lnx_dmn_module_info_from_process_module(Arena *arena, pid_t pid, int memory_fd, //- rjf: read module path String8 module_path = lnx_dmn_read_string(arena, memory_fd, name_vaddr); + //- rjf: if the module path is relative, we need to convert to absolute using the + // process' current working directory. the process' working directory may, of course, + // change across time, and so we need to re-read it every time we encounter this + // scenario. + if(path_style_from_str8(module_path) == PathStyle_Relative) + { + char *process_cwd_path_name = (char *)str8f(scratch.arena, "/proc/%d/cwd", pid).str; + U64 read_cap = PATH_MAX; + U8 *read_buffer = push_array(scratch.arena, U8, read_cap); + ssize_t read_size = readlink(process_cwd_path_name, (char *)read_buffer, read_cap); + if(read_size != 0) + { + String8 process_cwd = str8(read_buffer, read_size); + String8 module_path_absolute = str8f(scratch.arena, "%S/%S", process_cwd, module_path); + module_path = path_normalized_from_string(arena, module_path_absolute); + } + } + //- rjf: decide on debug info key info String8 debug_info_path = module_path; Guid debug_info_guid = {0}; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 87b887ea..b30d7849 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -5,6 +5,8 @@ //~ rjf: linux TODO notes // // initial alpha: +// [x] .so loading/debugging - missing debug info +// [x] missing disasm // [ ] control // [ ] multithreaded stepping - trap hits reported on other threads. // [ ] stale memory - things evaluating to stale values that are definitely not zero diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index a9aac20a..77fe5910 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -2720,13 +2720,13 @@ RD_VIEW_UI_FUNCTION_DEF(disasm) { auto_selected = 1; auto_space = rd_eval_space_from_ctrl_entity(d_entity_from_handle(dv->temp_look_process), D_EvalSpaceKind_Entity); - eval = e_eval_from_stringf("(0x%I64x & (~(0x4000 - 1)))", dv->temp_look_vaddr); + eval = e_eval_from_stringf("(0x%I64x & (~(0x1000 - 1)))", dv->temp_look_vaddr); } else { auto_selected = 1; auto_space = rd_eval_space_from_ctrl_entity(d_entity_from_handle(rd_regs()->process), D_EvalSpaceKind_Entity); - eval = e_eval_from_stringf("(reg:rip & (~(0x4000 - 1)))"); + eval = e_eval_from_stringf("(reg:rip & (~(0x1000 - 1)))"); } } diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index 8972c3e8..8c906a9f 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -2228,7 +2228,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->kind = DMN_EventKind_UnloadModule; e->process = w32_dmn_handle_from_entity(process); e->module = w32_dmn_handle_from_entity(child); - e->string = w32_dmn_full_path_from_module(arena, child); }break; } } @@ -2393,7 +2392,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->kind = DMN_EventKind_UnloadModule; e->process = w32_dmn_handle_from_entity(process); e->module = w32_dmn_handle_from_entity(module); - e->string = w32_dmn_full_path_from_module(arena, module); } // rjf: release entity storage @@ -3145,7 +3143,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->kind = DMN_EventKind_UnloadModule; e->process = w32_dmn_handle_from_entity(process); e->module = w32_dmn_handle_from_entity(child); - e->string = w32_dmn_full_path_from_module(arena, child); } } From 2933143b0805a8f8435df1e7e2bff21bd1dca056 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 28 Sep 2026 10:10:16 -0600 Subject: [PATCH 192/251] win32_demon: make conflicting trap net / user bp trap behavior identical to linux - always find info for *first* trap in list. second... dbg_engine: store trap net traps *first*, so that trap net behavior is preferred when trap nets conflict with user bp traps. --- src/dbg_engine/dbg_engine_ctrl.c | 2 +- src/win32/demon/win32_demon.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index a0eaab39..3272c8ec 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -4878,8 +4878,8 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) // DMN_TrapChunkList joined_traps = {0}; { - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &user_traps); dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &trap_net_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &user_traps); } ////////////////////////////// diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index 8c906a9f..1f1a0e5d 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -2478,10 +2478,11 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { hit_user_trap = 1; user_trap_id = n->v[idx].id; - break; + goto end_find_user_trap; } } } + end_find_user_trap:; } //- rjf: check if trap is explicit in the actual code memory From 0b91cab00a97da7a2c57719718524e893aa2dbfd Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 29 Sep 2026 12:28:05 -0600 Subject: [PATCH 193/251] linux_demon: PTRACE_EVENT_STOPs can sometimes be reported even if a thread has already hit a trap (e.g. a trap net or breakpoint trap). this would sometimes cause spurious SIGTRAPs after issuing a PTRACE_INTERRUPT - so we need to flag halted threads to not treat that as a 'real' trap. furthermore, even though that trap was hit, it does not guarantee that the debugger would next see a SIGTRAP. that would cause spurious SIGILLs, because we'd resume threads *after* the trap, despite not seeing the trap, so they'd just see a malformed instruction (the bytes subsequent the initial trap). so in those cases, we need to roll such threads back, so that they re-execute the trap, and so that we see a regular SIGTRAP, and handle the trap correctly. --- src/linux/demon/linux_demon.c | 108 +++++++++++++++++++++------------- src/linux/demon/linux_demon.h | 1 + src/raddbg/raddbg_main.c | 13 ++-- 3 files changed, 75 insertions(+), 47 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 10e9efa9..46cce896 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1770,6 +1770,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { if(n->v != thread && n->v->state == LNX_DMN_ThreadState_Running) { + n->v->ghost_trap = 1; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, n->v->tid, 0, 0)) < 0) { log_infof("ptrace error: Couldn't send PTRACE_INTERRUPT to TID %I64u.", n->v->tid); @@ -2029,16 +2030,23 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) if(hit_user_trap) { U64 ip = lnx_dmn_ip_from_thread(thread); - lnx_dmn_thread_write_ip(thread, ip - 1); + lnx_dmn_thread_write_ip(thread, ip - arch_info->trap_instruction.size); } - // rjf: generate event - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = hit_user_trap ? DMN_EventKind_Breakpoint : DMN_EventKind_Trap; - e->process = lnx_dmn_handle_from_process(process); - e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_ip_from_thread(thread); - e->user_data = hit_user_trap ? hit_user_trap->id : 0; + // rjf: generate event, if this isn't a ghost trap + if(thread->ghost_trap) + { + thread->ghost_trap = 0; + } + else + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = hit_user_trap ? DMN_EventKind_Breakpoint : DMN_EventKind_Trap; + e->process = lnx_dmn_handle_from_process(process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->instruction_pointer = lnx_dmn_ip_from_thread(thread); + e->user_data = hit_user_trap ? hit_user_trap->id : 0; + } } }break; @@ -2223,6 +2231,26 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->tls_root_vaddr = thread->dtv_base_vaddr; } + // rjf: stop event for other threads we're attached to, due to a stop in + // another thread - check if this hit a trap and roll back + else if(thread != 0 && thread->state == LNX_DMN_ThreadState_Stopped) + { + //- rjf: unpack thread IP / process + U64 ip = lnx_dmn_ip_from_thread(thread); + DMN_Handle process_handle = lnx_dmn_handle_from_process(process); + + //- rjf: check previous bytes against trap instruction. if this thread + // executed a trap, we need to roll back before continuing this thread. + // we CANNOT RELY on there being a subsequently-queued SIGTRAP. + U8 *hit_trap_bytes_maybe = push_array(scratch.arena, U8, arch_info->trap_instruction.size); + dmn_process_read(process_handle, r1u64(ip - arch_info->trap_instruction.size, ip), hit_trap_bytes_maybe); + String8 hit_trap_data_maybe = str8(hit_trap_bytes_maybe, arch_info->trap_instruction.size); + if(str8_match(hit_trap_data_maybe, arch_info->trap_instruction, 0)) + { + lnx_dmn_thread_write_ip(thread, ip - arch_info->trap_instruction.size); + } + } + // rjf: stop event for unrecognized thread -> this references a thread we // haven't seen cloned yet (we cannot rely on the order of clone/stop). so, // we will gather the ID, and increase the pending threads count, and stop @@ -2263,44 +2291,42 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // else { - LNX_DMN_Thread *thread = lnx_dmn_thread_from_pid(wait_id); thread->pass_through_signal = 1; thread->pass_through_signo = wstopsig; local_persist B8 is_repeatable[] = { 0, // null - 1, // 1 SIGHUP - 1, // 2 SIGINT - 1, // 3 SIGQUIT - 1, // 4 SIGTRAP - 1, // 5 SIGABRT - 1, // 6 SIGIOT - 1, // 7 SIGBUS - 1, // 8 SIGFPE - 1, // 9 SIGKILL - 1, // 10 SIGUSR1 - 1, // 11 SIGSEGV - 1, // 12 SIGUSR2 - 1, // 13 SIGPIPE - 1, // 14 SIGALRM - 1, // 15 SIGTERM - 1, // 16 SIGTKFLT - 0, // 17 SIGCHLD - 0, // 18 SIGCONT - 1, // 19 SIGSTOP - 1, // 20 SIGTSP - 1, // 21 SIGTTIN - 1, // 22 SIGTTOU - 0, // 23 SIGURG - 1, // 24 SIGXCPU - 1, // 25 SIGXFSZ - 1, // 26 SIGVTALRM - 1, // 27 SIGPROF - 0, // 28 SIGWINCH - 1, // 29 SIGIO - 1, // 30 SIGPWR - 1, // 31 SIGSYS - 1, // 32 SIGUNUSED + 1, // SIGHUP 1 + 1, // SIGINT 2 + 1, // SIGQUIT 3 + 1, // SIGILL 4 + 1, // SIGTRAP 5 + 1, // SIGABRT/SIGIOT 6 + 1, // SIGBUS 7 + 1, // SIGFPE 8 + 1, // SIGKILL 9 + 1, // SIGUSR1 10 + 1, // SIGSEGV 11 + 1, // SIGUSR2 12 + 1, // SIGPIPE 13 + 1, // SIGALRM 14 + 1, // SIGTERM 15 + 1, // SIGSTKFLT 16 + 0, // SIGCHLD 17 + 0, // SIGCONT 18 + 1, // SIGSTOP 19 + 1, // SIGTSTP 20 + 1, // SIGTTIN 21 + 1, // SIGTTOU 22 + 0, // SIGURG 23 + 1, // SIGXCPU 24 + 1, // SIGXFSZ 25 + 1, // SIGVTALRM 26 + 1, // SIGPROF 27 + 0, // SIGWINCH 28 + 1, // SIGIO 29 + 1, // SIGPWR 30 + 1, // SIGSYS/SIGUNUSED31 }; DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_Exception; diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index 0e95a32f..ae81b419 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -178,6 +178,7 @@ struct LNX_DMN_Thread void *reg_block; B32 is_reg_block_dirty; B32 pass_through_signal; + B32 ghost_trap; U64 pass_through_signo; U64 orig_rax; U64 dtv_base_vaddr; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index b30d7849..c17b9203 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -7,23 +7,24 @@ // initial alpha: // [x] .so loading/debugging - missing debug info // [x] missing disasm +// [ ] recycling semaphore files - or alt strat? // [ ] control -// [ ] multithreaded stepping - trap hits reported on other threads. -// [ ] stale memory - things evaluating to stale values that are definitely not zero -// [ ] support -// [ ] .eh_frame unwinding without .eh_frame_hdr -// [ ] asan +// [ ] stale memory - things evaluating to stale values that are definitely not zero // [ ] rendering -// [ ] bitmaps / geometry +// [ ] bitmaps / geometry // // post initial alpha: // [ ] windowing // [ ] wayland +// [ ] support +// [ ] .eh_frame unwinding without .eh_frame_hdr // // [x] clipboard // [x] jai exes - busted call stacks / unwind info / paths? // [x] statically linked EXEs - busted unwinding // [x] statically linked EXEs, not parsing maps file correctly +// [x] asan +// [x] multithreaded stepping - trap hits reported on other threads. //////////////////////////////// //~ rjf: evaluation system pass TODO notes From 7082c7b67ddfda6245d22c6315a0764df029213d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 29 Sep 2026 13:09:41 -0600 Subject: [PATCH 194/251] linux_demon: thread names --- src/artifact_cache/artifact_cache.c | 1 + src/linux/base/linux_base.c | 3 +- src/linux/base/linux_base.h | 1 + src/linux/demon/linux_demon.c | 44 ++++++++++++++++++++++++++++- src/linux/demon/linux_demon.h | 2 ++ src/raddbg/raddbg_main.c | 13 +++++---- 6 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/artifact_cache/artifact_cache.c b/src/artifact_cache/artifact_cache.c index b9b0db58..7a1e1e30 100644 --- a/src/artifact_cache/artifact_cache.c +++ b/src/artifact_cache/artifact_cache.c @@ -681,6 +681,7 @@ ac_async_tick(void) internal void ac_cancel_thread_entry_point(void *p) { + ThreadNameF("ac_cancel_thread"); for(;;) { sleep_ms(50); diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index e95e2be8..28069770 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -367,8 +367,7 @@ set_platform_thread_name(String8 name) { Temp scratch = scratch_begin(0, 0); String8 name_copy = str8_copy(scratch.arena, name); - pthread_t current_thread = pthread_self(); - pthread_setname_np(current_thread, (char *)name_copy.str); + prctl(PR_SET_NAME, (char *)name_copy.str); scratch_end(scratch); } diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index 230f7f18..27cad030 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 46cce896..2d5429db 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -3272,6 +3272,48 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->error_kind = DMN_ErrorKind_NotAttached; } + ////////////////////////////// + //- rjf: gather thread names + // + for(LNX_DMN_Process *process = lnx_dmn_state->first_process; process != 0; process = process->next) + { + U64 main_thread_name_hash = 0; + for(LNX_DMN_Thread *thread = process->first_thread; thread != 0; thread = thread->next) + { + if(thread->tid == process->pid) + { + main_thread_name_hash = thread->last_name_hash; + break; + } + } + for(LNX_DMN_Thread *thread = process->first_thread; thread != 0; thread = thread->next) + { + if(thread->last_name_hash == 0 || thread->name_gather_time_us+1000000 <= now_time_us()) + { + Temp temp = temp_begin(scratch.arena); + char *thread_name_path = (char *)str8f(temp.arena, "/proc/%d/task/%d/comm", process->pid, thread->tid).str; + int thread_name_file = LNX_RETRY_ON_EINTR(open(thread_name_path, O_RDONLY)); + if(thread_name_file >= 0) + { + String8 thread_name = lnx_dmn_read_string_capped(temp.arena, thread_name_file, 0, 1024); + U64 hash = u64_hash_from_str8(thread_name); + if(thread_name.size != 0 && hash != thread->last_name_hash && (hash != main_thread_name_hash || thread->tid == process->pid)) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_SetThreadName; + e->process = lnx_dmn_handle_from_process(process); + e->thread = lnx_dmn_handle_from_thread(thread); + e->string = str8_copy(arena, thread_name); + } + thread->last_name_hash = hash; + } + thread->name_gather_time_us = now_time_us(); + LNX_RETRY_ON_EINTR(close(thread_name_file)); + temp_end(temp); + } + } + } + ////////////////////////////// //- rjf: re-allow halts // @@ -3297,7 +3339,7 @@ dmn_halt(U64 code, U64 user_data) for EachNode(process, LNX_DMN_Process, lnx_dmn_state->first_process) { int kill_result = LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)); - int x = 0; + (void)kill_result; } } } diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index ae81b419..22ff00fb 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -179,6 +179,8 @@ struct LNX_DMN_Thread B32 is_reg_block_dirty; B32 pass_through_signal; B32 ghost_trap; + U64 last_name_hash; + U64 name_gather_time_us; U64 pass_through_signo; U64 orig_rax; U64 dtv_base_vaddr; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index c17b9203..2225d739 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -5,19 +5,15 @@ //~ rjf: linux TODO notes // // initial alpha: -// [x] .so loading/debugging - missing debug info -// [x] missing disasm // [ ] recycling semaphore files - or alt strat? -// [ ] control -// [ ] stale memory - things evaluating to stale values that are definitely not zero // [ ] rendering // [ ] bitmaps / geometry // // post initial alpha: // [ ] windowing -// [ ] wayland +// [ ] wayland // [ ] support -// [ ] .eh_frame unwinding without .eh_frame_hdr +// [ ] .eh_frame unwinding without .eh_frame_hdr // // [x] clipboard // [x] jai exes - busted call stacks / unwind info / paths? @@ -25,6 +21,11 @@ // [x] statically linked EXEs, not parsing maps file correctly // [x] asan // [x] multithreaded stepping - trap hits reported on other threads. +// [x] .so loading/debugging - missing debug info +// [x] missing disasm +// [x] control +// [x] stale memory - things evaluating to stale values that are definitely not zero +// [x] thread names //////////////////////////////// //~ rjf: evaluation system pass TODO notes From f781622a547e1aa7e0e30146854280ed0b475c53 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 29 Sep 2026 16:34:18 -0700 Subject: [PATCH 195/251] checkpoint on sockets layer --- project.raddbg | 2 +- src/socket/socket.h | 11 +- src/socket/socket_stub.c | 4 +- src/win32/socket/win32_socket.c | 237 +++++++++++++++++++++++++------- src/win32/socket/win32_socket.h | 15 +- 5 files changed, 209 insertions(+), 60 deletions(-) diff --git a/project.raddbg b/project.raddbg index 174178b1..a0b9f4ed 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -35,7 +36,6 @@ target: executable: "build/raddbg" working_directory: build arguments: "--user:raddbg_test.user" - enabled: 1 } target: { diff --git a/src/socket/socket.h b/src/socket/socket.h index cc07e715..0656cfb6 100644 --- a/src/socket/socket.h +++ b/src/socket/socket.h @@ -34,6 +34,9 @@ struct SOCK_Endpoint { U8 address_u8[16]; U16 address_u16[8]; + U32 address_u32[4]; + U64 address_u64[2]; + U128 address_u128[1]; }; }; @@ -55,13 +58,13 @@ internal void sock_async_tick(void); //////////////////////////////// //~ rjf: @per_os_impl Sends -internal U64 sock_send(U8 *ptr, U64 size, SOCK_Protocol *protocol_in, SOCK_Endpoint *endpoint_in, U64 endt_us); -#define sock_send_struct(ptr, protocol_in, endpoint_in, endt_us) sock_send((ptr), sizeof(*(ptr)), (endpoint_in), (endt_us)) +internal B32 sock_send(SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us); +#define sock_send_struct(protocol_in, endpoint_in, ptr, endt_us) sock_send((protocol_in), (endpoint_in), str8_struct(ptr), (endt_us)) //////////////////////////////// //~ rjf: @per_os_impl Receives -internal U64 sock_recv(U8 *ptr, U64 size, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, U64 endt_us); -#define sock_recv_struct(ptr, protocol_out, endpoint_out, endt_us) sock_recv((ptr), sizeof(*(ptr)), (endpoint_out), (endt_us)) +internal B32 sock_recv(Arena *arena, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us); +#define sock_recv_struct(arena, protocol_out, endpoint_out, data_out, endt_us) sock_recv((arena), (protocol_out), (endpoint_out), (data_out), (endt_us)) #endif // SOCKET_H diff --git a/src/socket/socket_stub.c b/src/socket/socket_stub.c index 639c9a8a..2b1fd97d 100644 --- a/src/socket/socket_stub.c +++ b/src/socket/socket_stub.c @@ -3,5 +3,5 @@ internal void sock_init(void){} internal void sock_async_tick(void){} -internal U64 sock_send(U8 *ptr, U64 size, SOCK_Protocol *protocol_in, SOCK_Endpoint *endpoint_in, U64 endt_us){return 0;} -internal U64 sock_recv(U8 *ptr, U64 size, SOCK_Protocol *protocol_in, SOCK_Endpoint *endpoint_in, U64 endt_us){return 0;} +internal B32 sock_send(SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us){return 0;} +internal B32 sock_recv(Arena *arena, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us){return 0;} diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index 00adcc40..1a33f80b 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -5,29 +5,66 @@ //~ rjf: Listener Threads internal void -w32_sock_tcp_listener_thread_entry_point(void *p) +w32_sock_listener_thread_entry_point(void *p) { - ThreadNameF("w32_sock_tcp_listener_thread"); + ThreadNameF("w32_sock_listener_thread_%I64u", (U64)p); + SOCK_Protocol protocol = (SOCK_Protocol)p; for(;;) { - SOCKET new_socket = accept(w32_sock_state->tcp_listen_socket, 0, 0); + //- rjf: + + //- rjf: accept new connection on the TCP listener + struct sockaddr_storage addr = {0}; + int addr_size = sizeof(addr); + SOCKET new_socket = accept(w32_sock_state->tcp_listen_socket, (struct sockaddr *)&addr, &addr_size); + + //- rjf: got new TCP connection socket -> store connection if(new_socket != INVALID_SOCKET) { - // TODO(rjf) - } - } -} - -internal void -w32_sock_udp_listener_thread_entry_point(void *p) -{ - ThreadNameF("w32_sock_udp_listener_thread"); - for(;;) - { - SOCKET new_socket = accept(w32_sock_state->udp_listen_socket, 0, 0); - if(new_socket != INVALID_SOCKET) - { - // TODO(rjf) + // rjf: unpack socket's endpoint info + SOCK_Endpoint endpoint = {0}; + { + switch(addr.ss_family) + { + default:{}break; + case AF_INET: + { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)&addr; + endpoint.port = ntohs(ipv4->sin_port); + endpoint.address_u32[0] = ipv4->sin_addr.s_addr; + }break; + case AF_INET6: + { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&addr; + endpoint.port = ntohs(ipv6->sin6_port); + MemoryCopy(endpoint.address_u8, ipv6->sin6_addr.u.Byte, sizeof(endpoint.address_u8)); + }break; + } + } + + // rjf: unpack endpoint + U64 hash = u64_hash_from_str8(str8_struct(&endpoint)); + U64 slot_idx = hash%w32_sock_state->connection_slots_count; + W32_SOCK_ConnectionSlot *slot = &w32_sock_state->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&w32_sock_state->connection_stripes, slot_idx); + + // rjf: store new connection + RWMutexScope(stripe->rw_mutex, 1) + { + W32_SOCK_Connection *con = (W32_SOCK_Connection *)stripe->free; + if(con != 0) + { + stripe->free = con->next; + } + else + { + con = push_array(stripe->arena, W32_SOCK_Connection, 1); + } + con->endpoint = endpoint; + con->protocol = SOCK_Protocol_TCP; + con->socket = new_socket; + DLLPushBack(slot->first, slot->last, con); + } } } } @@ -48,8 +85,8 @@ sock_init(void) w32_sock_state->s2u_ring = guarded_ring_alloc(arena, KB(256)); //- rjf: create listener sockets - w32_sock_state->tcp_listen_socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); - w32_sock_state->udp_listen_socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + w32_sock_state->tcp_listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + w32_sock_state->udp_listen_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); { DWORD ipv6only = 0; setsockopt(w32_sock_state->tcp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); @@ -59,78 +96,178 @@ sock_init(void) //- rjf: bind listener sockets { struct sockaddr_in server_addr; - server_addr.sin_family = AF_INET6; + server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(SOCKET_PORT); bind(w32_sock_state->tcp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); bind(w32_sock_state->udp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); } - //- rjf: start listening + //- rjf: start listening for TCP connections { listen(w32_sock_state->tcp_listen_socket, SOMAXCONN); - listen(w32_sock_state->udp_listen_socket, SOMAXCONN); } + //- rjf: set up connection cache + w32_sock_state->connection_slots_count = 8; + w32_sock_state->connection_slots = push_array(arena, W32_SOCK_ConnectionSlot, w32_sock_state->connection_slots_count); + w32_sock_state->connection_stripes = stripe_array_alloc(arena); + //- rjf: launch one-off listener threads to block & accept connections - w32_sock_state->tcp_listener_thread = thread_launch(w32_sock_tcp_listener_thread_entry_point, 0); - w32_sock_state->udp_listener_thread = thread_launch(w32_sock_udp_listener_thread_entry_point, 0); + w32_sock_state->tcp_listener_thread = thread_launch(w32_sock_listener_thread_entry_point, (void *)SOCK_Protocol_TCP); } internal void sock_async_tick(void) { + Temp scratch = scratch_begin(0, 0); + ////////////////////////////// + //- rjf: gather send tasks + // + typedef struct SendTask SendTask; + struct SendTask + { + SendTask *next; + SOCK_Endpoint endpoint; + String8 data; + }; + SendTask *first_tcp_send = 0; + SendTask *last_tcp_send = 0; + SendTask *first_udp_send = 0; + SendTask *last_udp_send = 0; + if(lane_idx() == 0) + { + for(;;) + { + B32 got_more = 0; + RingGuard g = guarded_ring_open(w32_sock_state->u2s_ring); + { + U64 header[5] = {0}; + if(guarded_ring_try_read(&g, sizeof(header), header)) + { + got_more = 1; + SOCK_Protocol protocol = (SOCK_Protocol)header[0]; + U16 port = (U16)header[1]; + SOCK_Endpoint endpoint = {0}; + endpoint.address_u64[0] = header[2]; + endpoint.address_u64[1] = header[3]; + U64 data_size = header[4]; + U8 *data = push_array(scratch.arena, U8, data_size); + guarded_ring_read_or_wait(&g, data_size, data, max_U64); + SendTask *t = push_array(scratch.arena, SendTask, 1); + t->endpoint = endpoint; + t->data = str8(data, data_size); + switch(protocol) + { + default:{}break; + case SOCK_Protocol_TCP:{SLLQueuePush(first_tcp_send, last_tcp_send, t);}break; + case SOCK_Protocol_UDP:{SLLQueuePush(first_tcp_send, last_tcp_send, t);}break; + } + } + } + guarded_ring_close(&g); + if(!got_more) + { + break; + } + } + } + lane_sync(); + + ////////////////////////////// + //- rjf: do TCP sends + // + for(SendTask *t = first_tcp_send; t != 0; t = t->next) + { + U64 hash = u64_hash_from_str8(str8_struct(&t->endpoint)); + U64 slot_idx = hash%w32_sock_state->connection_slots_count; + W32_SOCK_ConnectionSlot *slot = &w32_sock_state->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&w32_sock_state->connection_stripes, slot_idx); + RWMutexScope(stripe->rw_mutex, 1) + { + for(W32_SOCK_Connection *c = slot->first; c != 0; c = c->next) + { + if(MemoryMatchStruct(&c->endpoint, &t->endpoint)) + { + if(send(c->socket, t->data.str, t->data.size, 0) == SOCKET_ERROR) + { + int error = WSAGetLastError(); + if(error == WSAECONNRESET) + { + DLLRemove(slot->first, slot->last, c); + c->next = stripe->free; + stripe->free = c; + } + } + } + } + } + } + + scratch_end(scratch); } //////////////////////////////// //~ rjf: @per_os_impl Sends -internal U64 -sock_send(U8 *ptr, U64 size, SOCK_Protocol *protocol_in, SOCK_Endpoint *endpoint_in, U64 endt_us) +internal B32 +sock_send(SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us) { - U64 result = 0; - U64 needed_size = sizeof(*protocol_in) + sizeof(*endpoint_in) + sizeof(U64) + size; + B32 result = 0; RingGuard guard = guarded_ring_open(w32_sock_state->u2s_ring); { - void *dst = guarded_ring_push_or_wait(&guard, needed_size, endt_us); - if(dst != 0) + U64 header[5] = {0}; + U64 size_cap = w32_sock_state->u2s_ring->ring->size - sizeof(header); + U64 size = Min(data.size, size_cap); { - MemoryCopy((U8 *)dst + 0, protocol_in, sizeof(*protocol_in)); - MemoryCopy((U8 *)dst + sizeof(*protocol_in), endpoint_in, sizeof(*endpoint_in)); - MemoryCopy((U8 *)dst + sizeof(*protocol_in) + sizeof(endpoint_in), &size, sizeof(size)); - MemoryCopy((U8 *)dst + sizeof(*protocol_in) + sizeof(endpoint_in) + sizeof(U64), ptr, size); - result = needed_size; + header[0] = (U64)protocol; + header[1] = (U64)endpoint.port; + header[2] = endpoint.address_u64[0]; + header[3] = endpoint.address_u64[1]; + header[4] = size; + } + if(guarded_ring_write_or_wait(&guard, sizeof(header), header, endt_us)) + { + guarded_ring_write_or_wait(&guard, size, data.str, max_U64); + result = 1; } } guarded_ring_close(&guard); + if(result) + { + ins_atomic_u32_eval_assign(&async_loop_again, 1); + cond_var_broadcast(async_tick_start_cond_var); + } return result; } //////////////////////////////// //~ rjf: @per_os_impl Receives -internal U64 -sock_recv(U8 *ptr, U64 size, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, U64 endt_us) +internal B32 +sock_recv(Arena *arena, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us) { - U64 received_size = 0; + B32 result = 0; U64 header_size = sizeof(*protocol_out) + sizeof(*endpoint_out) + sizeof(U64); { - RingGuard guard = guarded_ring_open(w32_sock_state->u2s_ring); + RingGuard guard = guarded_ring_open(w32_sock_state->s2u_ring); { - void *src = guarded_ring_pop_or_wait(&guard, header_size, endt_us); - if(src != 0) + U64 header[5] = {0}; + if(guarded_ring_read_or_wait(&guard, sizeof(header), header, endt_us)) { - U64 payload_size = 0; - MemoryCopy(protocol_out, (U8 *)src + 0, sizeof(*protocol_out)); - MemoryCopy(endpoint_out, (U8 *)src + sizeof(*protocol_out), sizeof(*endpoint_out)); - MemoryCopy(&payload_size, (U8 *)src + sizeof(*protocol_out) + sizeof(*endpoint_out), sizeof(U64)); - void *payload = guarded_ring_pop_or_wait(&guard, payload_size, max_U64); - MemoryCopy(ptr, payload, payload_size); - received_size = payload_size; + U64 size_cap = w32_sock_state->s2u_ring->ring->size - sizeof(header); + protocol_out[0] = (SOCK_Protocol)header[0]; + endpoint_out->port = (U16)header[1]; + endpoint_out->address_u64[0] = header[2]; + endpoint_out->address_u64[1] = header[3]; + data_out->size = Min(size_cap, header[4]); + data_out->str = push_array(arena, U8, data_out->size); + guarded_ring_read_or_wait(&guard, data_out->size, data_out->str, max_U64); + result = 1; } } guarded_ring_close(&guard); } - return received_size; + return result; } diff --git a/src/win32/socket/win32_socket.h b/src/win32/socket/win32_socket.h index d411836e..38f46df8 100644 --- a/src/win32/socket/win32_socket.h +++ b/src/win32/socket/win32_socket.h @@ -11,11 +11,19 @@ typedef struct W32_SOCK_Connection W32_SOCK_Connection; struct W32_SOCK_Connection { W32_SOCK_Connection *next; + W32_SOCK_Connection *prev; SOCK_Endpoint endpoint; SOCK_Protocol protocol; SOCKET socket; }; +typedef struct W32_SOCK_ConnectionSlot W32_SOCK_ConnectionSlot; +struct W32_SOCK_ConnectionSlot +{ + W32_SOCK_Connection *first; + W32_SOCK_Connection *last; +}; + typedef struct W32_SOCK_State W32_SOCK_State; struct W32_SOCK_State { @@ -25,7 +33,9 @@ struct W32_SOCK_State SOCKET tcp_listen_socket; SOCKET udp_listen_socket; Thread tcp_listener_thread; - Thread udp_listener_thread; + U64 connection_slots_count; + StripeArray connection_stripes; + W32_SOCK_ConnectionSlot *connection_slots; }; global W32_SOCK_State *w32_sock_state = 0; @@ -33,7 +43,6 @@ global W32_SOCK_State *w32_sock_state = 0; //////////////////////////////// //~ rjf: Listener Threads -internal void w32_sock_tcp_listener_thread_entry_point(void *p); -internal void w32_sock_udp_listener_thread_entry_point(void *p); +internal void w32_sock_listener_thread_entry_point(void *p); #endif // WIN32_SOCKET_H From e270def19f714b07b5ac3f066857e18be3936125 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 29 Sep 2026 21:08:34 -0700 Subject: [PATCH 196/251] socket: checkpoint on socket layer / win32 impl; plug into ipc driving --- src/raddbg/raddbg_main.c | 54 +++++++++++++- src/socket/socket.h | 10 +++ src/socket/socket_stub.c | 1 + src/win32/socket/win32_socket.c | 128 ++++++++++++++++++++++++++++---- src/win32/socket/win32_socket.h | 11 ++- 5 files changed, 187 insertions(+), 17 deletions(-) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 2225d739..6076cc69 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -413,7 +413,7 @@ #include "mdesk/mdesk.h" #include "window_manager/window_manager_inc.h" #include "shell/shell_inc.h" -// #include "socket/socket_inc.h" +#include "socket/socket_inc.h" #include "http/http_inc.h" #include "symbol_server/symbol_server_inc.h" #include "config/config_inc.h" @@ -475,7 +475,7 @@ #include "mdesk/mdesk.c" #include "window_manager/window_manager_inc.c" #include "shell/shell_inc.c" -// #include "socket/socket_inc.c" +#include "socket/socket_inc.c" #include "http/http_inc.c" #include "symbol_server/symbol_server_inc.c" #include "config/config_inc.c" @@ -728,6 +728,7 @@ entry_point(CmdLine *cmd_line) fnt_init(); rd_init(cmd_line); d_set_wakeup_hook(wakeup_hook_ctrl); + sock_set_wakeup_hook(wakeup_hook_ctrl); } //- rjf: set up shared resources for ipc to this instance; launch IPC signaler thread @@ -832,6 +833,55 @@ entry_point(CmdLine *cmd_line) scratch_end(scratch); } + //- rjf: receive socket data, run as IPC messages + { + Temp scratch = scratch_begin(0, 0); + SOCK_Protocol protocol = SOCK_Protocol_TCP; + SOCK_Endpoint endpoint = {0}; + String8 msg = {0}; + if(sock_recv(scratch.arena, &protocol, &endpoint, &msg, 0)) + { + String8List cmd_parts_of_msg = str8_split(scratch.arena, msg, (U8 *)";", 1, 0); + RD_WindowState *dst_ws = rd_state->first_window_state; + for(RD_WindowState *ws = dst_ws; ws != &rd_nil_window_state; ws = ws->order_next) + { + if(wm_window_is_focused(ws->os)) + { + dst_ws = ws; + break; + } + } + if(dst_ws != &rd_nil_window_state) + { + dst_ws->window_temporarily_focused_ipc = 1; + RD_RegsScope() + { + if(dst_ws->cfg_id != rd_regs()->window) + { + Temp scratch = scratch_begin(0, 0); + CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, cfg_node_from_id(dst_ws->cfg_id)); + rd_regs()->window = dst_ws->cfg_id; + rd_regs()->panel = panel_tree.focused->cfg->id; + rd_regs()->tab = panel_tree.focused->selected_tab->id; + rd_regs()->view = panel_tree.focused->selected_tab->id; + scratch_end(scratch); + } + for EachNode(n, String8Node, cmd_parts_of_msg.first) + { + String8 cmd_string = str8_skip_chop_whitespace(n->string); + if(cmd_string.size != 0) + { + rd_cmd(RD_CmdKind_RunExternalDriverTextCommand, .string = cmd_string); + } + } + rd_request_frame(); + ipc_command_frame = 1; + } + } + } + scratch_end(scratch); + } + //- rjf: update quit = update(); diff --git a/src/socket/socket.h b/src/socket/socket.h index 0656cfb6..99bd72be 100644 --- a/src/socket/socket.h +++ b/src/socket/socket.h @@ -4,6 +4,9 @@ #ifndef SOCKET_H #define SOCKET_H +//////////////////////////////// +//~ rjf: Socket Types + #if !defined(SOCKET_PORT) # define SOCKET_PORT 7423 #endif @@ -40,6 +43,12 @@ struct SOCK_Endpoint }; }; +//////////////////////////////// +//~ rjf: Wakeup Hook Function Types + +#define SOCK_WAKEUP_FUNCTION_DEF(name) void name(void) +typedef SOCK_WAKEUP_FUNCTION_DEF(SOCK_WakeupFunctionType); + //////////////////////////////// //~ rjf: Helpers @@ -54,6 +63,7 @@ internal SOCK_Endpoint sock_endpoint_from_string(String8 address_and_port); #endif internal void sock_init(void); internal void sock_async_tick(void); +internal void sock_set_wakeup_hook(SOCK_WakeupFunctionType *hook); //////////////////////////////// //~ rjf: @per_os_impl Sends diff --git a/src/socket/socket_stub.c b/src/socket/socket_stub.c index 2b1fd97d..d5da5e91 100644 --- a/src/socket/socket_stub.c +++ b/src/socket/socket_stub.c @@ -3,5 +3,6 @@ internal void sock_init(void){} internal void sock_async_tick(void){} +internal void sock_set_wakeup_hook(SOCK_WakeupFunctionType *hook){} internal B32 sock_send(SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us){return 0;} internal B32 sock_recv(Arena *arena, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us){return 0;} diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index 1a33f80b..b0ab9c8a 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -11,16 +11,26 @@ w32_sock_listener_thread_entry_point(void *p) SOCK_Protocol protocol = (SOCK_Protocol)p; for(;;) { - //- rjf: + //- rjf: get next completion from IOCP + DWORD byte_count = 0; + U64 completion_key = 0; + OVERLAPPED *overlapped_ptr = 0; + B32 success = GetQueuedCompletionStatus(w32_sock_state->iocp, &byte_count, &completion_key, &overlapped_ptr, INFINITE); - //- rjf: accept new connection on the TCP listener - struct sockaddr_storage addr = {0}; - int addr_size = sizeof(addr); - SOCKET new_socket = accept(w32_sock_state->tcp_listen_socket, (struct sockaddr *)&addr, &addr_size); - - //- rjf: got new TCP connection socket -> store connection - if(new_socket != INVALID_SOCKET) + //- rjf: call wakeup hook + if(w32_sock_state->wakeup_hook) { + w32_sock_state->wakeup_hook(); + } + + //- rjf: overlapped_ptr == accept overlapped? -> new connection + if(overlapped_ptr == &w32_sock_state->tcp_accept_overlapped) + { + // rjf: unpack new socket + SOCKET new_socket = w32_sock_state->tcp_accept_socket; + struct sockaddr_storage addr = {0}; + MemoryCopy(&addr, w32_sock_state->tcp_accept_buffer + sizeof(struct sockaddr_storage) + 16, sizeof(addr)); + // rjf: unpack socket's endpoint info SOCK_Endpoint endpoint = {0}; { @@ -49,6 +59,9 @@ w32_sock_listener_thread_entry_point(void *p) Stripe *stripe = stripe_from_slot_idx(&w32_sock_state->connection_stripes, slot_idx); // rjf: store new connection + WSABUF buf = {0}; + DWORD *recv_size = 0; + OVERLAPPED *recv_overlapped = 0; RWMutexScope(stripe->rw_mutex, 1) { W32_SOCK_Connection *con = (W32_SOCK_Connection *)stripe->free; @@ -64,6 +77,62 @@ w32_sock_listener_thread_entry_point(void *p) con->protocol = SOCK_Protocol_TCP; con->socket = new_socket; DLLPushBack(slot->first, slot->last, con); + buf.len = sizeof(con->recv_buffer); + buf.buf = con->recv_buffer; + recv_size = &con->recv_size; + recv_overlapped = &con->recv_overlapped; + } + + // rjf: kick off receive + DWORD flags = MSG_PUSH_IMMEDIATE; + WSARecv(new_socket, &buf, 1, recv_size, &flags, recv_overlapped, 0); + + // rjf: create new accept socket, associate with iocp, zero overlapped, kick off next accept + w32_sock_state->tcp_accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + CreateIoCompletionPort((HANDLE)w32_sock_state->tcp_accept_socket, w32_sock_state->iocp, 0, 0); + MemoryZeroStruct(&w32_sock_state->tcp_accept_overlapped); + w32_sock_state->lpfnAcceptEx(w32_sock_state->tcp_listen_socket, w32_sock_state->tcp_accept_socket, w32_sock_state->tcp_accept_buffer, 0, sizeof(struct sockaddr_storage) + 16, sizeof(struct sockaddr_storage) + 16, &w32_sock_state->tcp_accept_size_out, &w32_sock_state->tcp_accept_overlapped); + } + + //- rjf: overlapped ptr anywhere else -> completion of async recv + else + { + // rjf: unpack associated connection + W32_SOCK_Connection *con = CastFromMember(W32_SOCK_Connection, recv_overlapped, overlapped_ptr); + SOCK_Endpoint endpoint = con->endpoint; + U64 hash = u64_hash_from_str8(str8_struct(&endpoint)); + U64 slot_idx = hash%w32_sock_state->connection_slots_count; + W32_SOCK_ConnectionSlot *slot = &w32_sock_state->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&w32_sock_state->connection_stripes, slot_idx); + + // rjf: success? -> push result to user. kick off next recv + if(success) + { + RingGuard g = guarded_ring_open(w32_sock_state->s2u_ring); + U64 header[5] = + { + (U64)SOCK_Protocol_TCP, + (U64)endpoint.port, + endpoint.address_u64[0], + endpoint.address_u64[1], + (U64)byte_count, + }; + guarded_ring_write_or_wait(&g, sizeof(header), header, max_U64); + guarded_ring_write_or_wait(&g, byte_count, con->recv_buffer, max_U64); + MemoryZeroStruct(&con->recv_overlapped); + WSABUF buf = {sizeof(con->recv_buffer), con->recv_buffer}; + DWORD flags = MSG_PUSH_IMMEDIATE; + WSARecv(con->socket, &buf, 1, &con->recv_size, &flags, &con->recv_overlapped, 0); + guarded_ring_close(&g); + } + + // rjf: no success? -> connection closed. + else RWMutexScope(stripe->rw_mutex, 1) + { + closesocket(con->socket); + DLLRemove(slot->first, slot->last, con); + con->next = stripe->free; + stripe->free = con; } } } @@ -84,13 +153,14 @@ sock_init(void) w32_sock_state->u2s_ring = guarded_ring_alloc(arena, KB(256)); w32_sock_state->s2u_ring = guarded_ring_alloc(arena, KB(256)); - //- rjf: create listener sockets - w32_sock_state->tcp_listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - w32_sock_state->udp_listen_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + //- rjf: set up IOCP + w32_sock_state->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); + + //- rjf: create listener(s) + w32_sock_state->tcp_listen_socket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); { DWORD ipv6only = 0; setsockopt(w32_sock_state->tcp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); - setsockopt(w32_sock_state->udp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); } //- rjf: bind listener sockets @@ -100,7 +170,6 @@ sock_init(void) server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(SOCKET_PORT); bind(w32_sock_state->tcp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); - bind(w32_sock_state->udp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); } //- rjf: start listening for TCP connections @@ -108,13 +177,38 @@ sock_init(void) listen(w32_sock_state->tcp_listen_socket, SOMAXCONN); } + //- rjf: associate listener sockets with IOCP + { + CreateIoCompletionPort((HANDLE)w32_sock_state->tcp_listen_socket, w32_sock_state->iocp, 0, 0); + } + + //- rjf: load AcceptEx function + DWORD dwBytes = 0; + GUID AcceptEx_guid = WSAID_ACCEPTEX; + WSAIoctl(w32_sock_state->tcp_listen_socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &AcceptEx_guid, sizeof(AcceptEx_guid), &w32_sock_state->lpfnAcceptEx, sizeof(w32_sock_state->lpfnAcceptEx), &dwBytes, 0, 0); + + //- rjf: create accepting socket + { + w32_sock_state->tcp_accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } + + //- rjf: associate accepting socket with IOCP + { + CreateIoCompletionPort((HANDLE)w32_sock_state->tcp_accept_socket, w32_sock_state->iocp, 0, 0); + } + + //- rjf: kick off accept + { + w32_sock_state->lpfnAcceptEx(w32_sock_state->tcp_listen_socket, w32_sock_state->tcp_accept_socket, w32_sock_state->tcp_accept_buffer, 0, sizeof(struct sockaddr_storage) + 16, sizeof(struct sockaddr_storage) + 16, &w32_sock_state->tcp_accept_size_out, &w32_sock_state->tcp_accept_overlapped); + } + //- rjf: set up connection cache w32_sock_state->connection_slots_count = 8; w32_sock_state->connection_slots = push_array(arena, W32_SOCK_ConnectionSlot, w32_sock_state->connection_slots_count); w32_sock_state->connection_stripes = stripe_array_alloc(arena); //- rjf: launch one-off listener threads to block & accept connections - w32_sock_state->tcp_listener_thread = thread_launch(w32_sock_listener_thread_entry_point, (void *)SOCK_Protocol_TCP); + w32_sock_state->tcp_listener_thread = thread_launch(w32_sock_listener_thread_entry_point, 0); } internal void @@ -208,6 +302,12 @@ sock_async_tick(void) scratch_end(scratch); } +internal void +sock_set_wakeup_hook(SOCK_WakeupFunctionType *hook) +{ + w32_sock_state->wakeup_hook = hook; +} + //////////////////////////////// //~ rjf: @per_os_impl Sends diff --git a/src/win32/socket/win32_socket.h b/src/win32/socket/win32_socket.h index 38f46df8..4d0be4c8 100644 --- a/src/win32/socket/win32_socket.h +++ b/src/win32/socket/win32_socket.h @@ -15,6 +15,9 @@ struct W32_SOCK_Connection SOCK_Endpoint endpoint; SOCK_Protocol protocol; SOCKET socket; + U8 recv_buffer[4096]; + DWORD recv_size; + OVERLAPPED recv_overlapped; }; typedef struct W32_SOCK_ConnectionSlot W32_SOCK_ConnectionSlot; @@ -30,12 +33,18 @@ struct W32_SOCK_State Arena *arena; GuardedRing *u2s_ring; GuardedRing *s2u_ring; + HANDLE iocp; SOCKET tcp_listen_socket; - SOCKET udp_listen_socket; + SOCKET tcp_accept_socket; + OVERLAPPED tcp_accept_overlapped; + U8 tcp_accept_buffer[4096]; + DWORD tcp_accept_size_out; Thread tcp_listener_thread; U64 connection_slots_count; StripeArray connection_stripes; W32_SOCK_ConnectionSlot *connection_slots; + LPFN_ACCEPTEX lpfnAcceptEx; + SOCK_WakeupFunctionType *wakeup_hook; }; global W32_SOCK_State *w32_sock_state = 0; From db645e5c905949a751c7f28307562ad802aec866 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 29 Sep 2026 21:14:43 -0700 Subject: [PATCH 197/251] build fixes --- build.sh | 2 +- src/socket/socket.c | 2 ++ src/torture/torture_main.c | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index ae425e18..da58b7da 100755 --- a/build.sh +++ b/build.sh @@ -34,7 +34,7 @@ if [[ -x "$(command -v pkg-config)" ]]; then cc_render="$(pkg-config --cflags --libs gl egl)" else cc_font_provider="-I/usr/include/freetype2 -lfreetype" - cc_os_gfx="-lX11 -lXext -lXFixes" + cc_os_gfx="-lX11 -lXext -lXfixes" cc_render="-lGL -lEGL" fi diff --git a/src/socket/socket.c b/src/socket/socket.c index 8accf484..b52c0b84 100644 --- a/src/socket/socket.c +++ b/src/socket/socket.c @@ -70,6 +70,8 @@ sock_endpoint_from_string_port(String8 address, U16 port) } }break; } + + return ep; } internal SOCK_Endpoint diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 71cb36bb..a555e1ac 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -33,6 +33,7 @@ #include "metagen/metagen.h" #include "window_manager/window_manager_inc.h" #include "shell/shell_inc.h" +#include "socket/socket_inc.h" #include "http/http_inc.h" #include "symbol_server/symbol_server_inc.h" #include "config/config_inc.h" @@ -117,6 +118,7 @@ #include "metagen/metagen.c" #include "window_manager/window_manager_inc.c" #include "shell/shell_inc.c" +#include "socket/socket_inc.c" #include "http/http_inc.c" #include "symbol_server/symbol_server_inc.c" #include "config/config_inc.c" From 00d4a1a56fb6b10feaeec5fbb7059f14df45a15c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 07:55:55 -0700 Subject: [PATCH 198/251] dbg_engine: heuristically clear memory map & grab new stack page when advancing to new regions of stack for large unwinds --- src/dbg_engine/dbg_engine_ctrl.c | 52 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 3272c8ec..020cb26c 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1905,24 +1905,9 @@ d_unwind_from_thread(Arena *arena, D_Handle thread, U64 endt_us) void *regs_block_restore = push_array(scratch.arena, U8, arch_reg_block_size); ////////////////////////////// - //- rjf: grab initial memory - // - // NOTE(rjf): we can pre-fill memory that we can expect to load here - - // otherwise we will lazily evaluate it via the unwinding backends below + //- rjf: set up empty memory map (will be lazily filled out via unwinding) // MemoryMap memory_map = {0}; - if(regs_block_good) - { - U64 sp = arch_sp_from_reg_block(arch_info, regs_block); - U64 sp_rounded_down = AlignDownPow2(sp, KB(4)); - Rng1U64 top_of_stack_vaddr_range = r1u64(sp_rounded_down, sp_rounded_down+KB(4)); - D_ProcessMemorySlice slice = d_process_memory_slice_from_vaddr_range(scratch.arena, process_entity->handle, top_of_stack_vaddr_range, 1, endt_us); - String8 data = slice.data; - if(!slice.stale) - { - memory_map_push(scratch.arena, &memory_map, top_of_stack_vaddr_range, data.str); - } - } ////////////////////////////// //- rjf: push one frame for register block we already have @@ -1970,6 +1955,33 @@ d_unwind_from_thread(Arena *arena, D_Handle thread, U64 endt_us) U64 tls_vaddr = 0; d_thread_get_module_tls_vaddr(thread_entity->handle, module_entity->handle, &tls_vaddr); + //- rjf: clear memory map every so many frames, heuristically. preemptively pull in next 4k of stack. + if(frame_node_count%10 == 0) + { + // rjf: compute the next 4k of stack memory addresses + U64 sp_rounded_down = AlignDownPow2(start_sp, KB(4)); + Rng1U64 top_of_stack_vaddr_range = r1u64(sp_rounded_down, sp_rounded_down+KB(4)); + + // rjf: determine if we already have this stack + B32 already_have_this_portion_of_stack = 0; + for(MemoryMapRangeNode *n = memory_map.first_range; n != 0; n = n->next) + { + if(n->v.vaddr_range.min == top_of_stack_vaddr_range.min && n->v.vaddr_range.max == top_of_stack_vaddr_range.max) + { + already_have_this_portion_of_stack = 1; + break; + } + } + + // rjf: if we've moved to a new 4k region of stack, clear memory map, read the new portion of stack, & push + if(!already_have_this_portion_of_stack) + { + MemoryZeroStruct(&memory_map); + String8 data = d_data_from_process_vaddr_range(scratch.arena, process_entity->handle, top_of_stack_vaddr_range, 0); + memory_map_push(scratch.arena, &memory_map, top_of_stack_vaddr_range, data.str); + } + } + //- rjf: do one unwind step B32 step_is_good = 0; for(;!unwind.flags && !step_is_good;) @@ -2026,9 +2038,9 @@ d_unwind_from_thread(Arena *arena, D_Handle thread, U64 endt_us) // (b) advance ip to 0 // (c) do not modify either ip *or* sp (just one is fine) // - B32 step_made_process = (arch_ip_from_reg_block(arch_info, regs_block) != start_ip || - arch_sp_from_reg_block(arch_info, regs_block) != start_sp); - if(step_is_good && step_made_process && arch_ip_from_reg_block(arch_info, regs_block) != 0) + B32 step_made_progress = (arch_ip_from_reg_block(arch_info, regs_block) != start_ip || + arch_sp_from_reg_block(arch_info, regs_block) != start_sp); + if(step_is_good && step_made_progress && arch_ip_from_reg_block(arch_info, regs_block) != 0) { D_UnwindFrameNode *frame_node = push_array(scratch.arena, D_UnwindFrameNode, 1); D_UnwindFrame *f = &frame_node->v; @@ -2042,7 +2054,7 @@ d_unwind_from_thread(Arena *arena, D_Handle thread, U64 endt_us) access_close(access); //- rjf: exit if we made no progress on the unwind - if(!step_made_process) + if(!step_made_progress) { break; } From be3fa56927ad5186ba922d9c80f87ba88bc35dff Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 07:56:33 -0700 Subject: [PATCH 199/251] base: memory map -> push new ranges to front, rather than end - this makes the common case of iterative retry loops asking for memory, then immediately needing it, faster --- src/base/base_memory_map.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/base_memory_map.c b/src/base/base_memory_map.c index e774ca81..0e2bb0e1 100644 --- a/src/base/base_memory_map.c +++ b/src/base/base_memory_map.c @@ -10,7 +10,7 @@ memory_map_push(Arena *arena, MemoryMap *map, Rng1U64 vaddr_range, void *data) MemoryMapRangeNode *n = push_array(arena, MemoryMapRangeNode, 1); n->v.vaddr_range = vaddr_range; n->v.base = data; - SLLQueuePush(map->first_range, map->last_range, n); + SLLQueuePushFront(map->first_range, map->last_range, n); } internal U64 From 7ee21690c2a7421791e399a93e2132d205ecceb6 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 08:12:47 -0700 Subject: [PATCH 200/251] ui: fix scroll increment for more consistency with default native windows behavior; linux_window_manager: make consistent with windows scroll events --- src/linux/window_manager/linux_window_manager.c | 7 +++++-- src/ui/ui_core.c | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 412ebdf5..81f5c609 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -1328,12 +1328,15 @@ wm_get_events(Arena *arena, B32 wait) e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); } else if(evt.xbutton.button == Button4 || - evt.xbutton.button == Button5) + evt.xbutton.button == Button5 || + evt.xbutton.button == 6 || + evt.xbutton.button == 7) { WM_Event *e = wm_event_list_push_new(arena, &evts, WM_EventKind_Scroll); e->window.u64[0] = (U64)window; e->modifiers = modifiers; - e->delta = v2f32(0, evt.xbutton.button == Button4 ? -1.f : +1.f); + e->delta = v2f32(evt.xbutton.button == 6 ? -120.f : evt.xbutton.button == 7 ? +120.f : 0.f, + evt.xbutton.button == Button4 ? -120.f : evt.xbutton.button == Button5 ? +120.f : 0.f); e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); } } diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index bc4f58a4..43f5d13b 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -2957,7 +2957,7 @@ ui_signal_from_box(UI_Box *box) { Swap(F32, delta.x, delta.y); } - Vec2S16 delta16 = v2s16((S16)(delta.x/30.f), (S16)(delta.y/30.f)); + Vec2S16 delta16 = v2s16((S16)(delta.x/40.f), (S16)(delta.y/40.f)); if(delta.x > 0 && delta16.x == 0) { delta16.x = +1; } if(delta.x < 0 && delta16.x == 0) { delta16.x = -1; } if(delta.y > 0 && delta16.y == 0) { delta16.y = +1; } From 6a52e6deecef0d6512f2900e45ce62d8be527363 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 08:31:54 -0700 Subject: [PATCH 201/251] sock: bucket socket connection pools / requests by 'session' - one session = one listener port, one pool of connections / io ring buffers - we will want many of these, since for ipc, the debugger wants to act as a server on the UI thread, and for remote, the debugger wants to act as a client on the ctrl thread. --- src/raddbg/raddbg_main.c | 6 +- src/socket/socket.h | 28 ++-- src/socket/socket_stub.c | 7 +- src/win32/socket/win32_socket.c | 259 ++++++++++++++++++-------------- src/win32/socket/win32_socket.h | 18 ++- 5 files changed, 191 insertions(+), 127 deletions(-) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 6076cc69..7a1dc2e3 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -728,7 +728,6 @@ entry_point(CmdLine *cmd_line) fnt_init(); rd_init(cmd_line); d_set_wakeup_hook(wakeup_hook_ctrl); - sock_set_wakeup_hook(wakeup_hook_ctrl); } //- rjf: set up shared resources for ipc to this instance; launch IPC signaler thread @@ -767,6 +766,9 @@ entry_point(CmdLine *cmd_line) scratch_end(scratch); } + //- rjf: set up socket session for ICP + SOCK_Session icp_sock_session = sock_session_open(7423, wakeup_hook_ctrl); + //- rjf: main application loop { for(B32 quit = 0; !quit;) @@ -839,7 +841,7 @@ entry_point(CmdLine *cmd_line) SOCK_Protocol protocol = SOCK_Protocol_TCP; SOCK_Endpoint endpoint = {0}; String8 msg = {0}; - if(sock_recv(scratch.arena, &protocol, &endpoint, &msg, 0)) + if(sock_recv(scratch.arena, icp_sock_session, &protocol, &endpoint, &msg, 0)) { String8List cmd_parts_of_msg = str8_split(scratch.arena, msg, (U8 *)";", 1, 0); RD_WindowState *dst_ws = rd_state->first_window_state; diff --git a/src/socket/socket.h b/src/socket/socket.h index 99bd72be..76330d78 100644 --- a/src/socket/socket.h +++ b/src/socket/socket.h @@ -7,10 +7,6 @@ //////////////////////////////// //~ rjf: Socket Types -#if !defined(SOCKET_PORT) -# define SOCKET_PORT 7423 -#endif - typedef enum SOCK_Protocol { SOCK_Protocol_TCP, @@ -43,6 +39,15 @@ struct SOCK_Endpoint }; }; +//////////////////////////////// +//~ rjf: Session Handle + +typedef struct SOCK_Session SOCK_Session; +struct SOCK_Session +{ + U64 u64[1]; +}; + //////////////////////////////// //~ rjf: Wakeup Hook Function Types @@ -63,18 +68,23 @@ internal SOCK_Endpoint sock_endpoint_from_string(String8 address_and_port); #endif internal void sock_init(void); internal void sock_async_tick(void); -internal void sock_set_wakeup_hook(SOCK_WakeupFunctionType *hook); + +//////////////////////////////// +//~ rjf: @per_os_impl Session Creation/Closing + +internal SOCK_Session sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook); +internal void sock_session_close(SOCK_Session session); //////////////////////////////// //~ rjf: @per_os_impl Sends -internal B32 sock_send(SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us); -#define sock_send_struct(protocol_in, endpoint_in, ptr, endt_us) sock_send((protocol_in), (endpoint_in), str8_struct(ptr), (endt_us)) +internal B32 sock_send(SOCK_Session session, SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us); +#define sock_send_struct(session, protocol_in, endpoint_in, ptr, endt_us) sock_send((session), (protocol_in), (endpoint_in), str8_struct(ptr), (endt_us)) //////////////////////////////// //~ rjf: @per_os_impl Receives -internal B32 sock_recv(Arena *arena, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us); -#define sock_recv_struct(arena, protocol_out, endpoint_out, data_out, endt_us) sock_recv((arena), (protocol_out), (endpoint_out), (data_out), (endt_us)) +internal B32 sock_recv(Arena *arena, SOCK_Session session, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us); +#define sock_recv_struct(arena, session, protocol_out, endpoint_out, data_out, endt_us) sock_recv((arena), (session), (protocol_out), (endpoint_out), (data_out), (endt_us)) #endif // SOCKET_H diff --git a/src/socket/socket_stub.c b/src/socket/socket_stub.c index d5da5e91..38b38656 100644 --- a/src/socket/socket_stub.c +++ b/src/socket/socket_stub.c @@ -3,6 +3,7 @@ internal void sock_init(void){} internal void sock_async_tick(void){} -internal void sock_set_wakeup_hook(SOCK_WakeupFunctionType *hook){} -internal B32 sock_send(SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us){return 0;} -internal B32 sock_recv(Arena *arena, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us){return 0;} +internal SOCK_Session sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook){SOCK_Session s = {0}; return s;} +internal void sock_session_close(SOCK_Session session){} +internal B32 sock_send(SOCK_Session session, SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us){return 0;} +internal B32 sock_recv(Arena *arena, SOCK_Session session, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us){return 0;} diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index b0ab9c8a..417e167c 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -7,29 +7,29 @@ internal void w32_sock_listener_thread_entry_point(void *p) { - ThreadNameF("w32_sock_listener_thread_%I64u", (U64)p); - SOCK_Protocol protocol = (SOCK_Protocol)p; + ThreadNameF("w32_sock_listener_thread_%I64x", (U64)p); + W32_SOCK_Session *session = (W32_SOCK_Session *)p; for(;;) { //- rjf: get next completion from IOCP DWORD byte_count = 0; U64 completion_key = 0; OVERLAPPED *overlapped_ptr = 0; - B32 success = GetQueuedCompletionStatus(w32_sock_state->iocp, &byte_count, &completion_key, &overlapped_ptr, INFINITE); + B32 success = GetQueuedCompletionStatus(session->iocp, &byte_count, &completion_key, &overlapped_ptr, INFINITE); //- rjf: call wakeup hook - if(w32_sock_state->wakeup_hook) + if(session->wakeup_hook) { - w32_sock_state->wakeup_hook(); + session->wakeup_hook(); } //- rjf: overlapped_ptr == accept overlapped? -> new connection - if(overlapped_ptr == &w32_sock_state->tcp_accept_overlapped) + if(overlapped_ptr == &session->tcp_accept_overlapped) { // rjf: unpack new socket - SOCKET new_socket = w32_sock_state->tcp_accept_socket; + SOCKET new_socket = session->tcp_accept_socket; struct sockaddr_storage addr = {0}; - MemoryCopy(&addr, w32_sock_state->tcp_accept_buffer + sizeof(struct sockaddr_storage) + 16, sizeof(addr)); + MemoryCopy(&addr, session->tcp_accept_buffer + sizeof(struct sockaddr_storage) + 16, sizeof(addr)); // rjf: unpack socket's endpoint info SOCK_Endpoint endpoint = {0}; @@ -54,9 +54,9 @@ w32_sock_listener_thread_entry_point(void *p) // rjf: unpack endpoint U64 hash = u64_hash_from_str8(str8_struct(&endpoint)); - U64 slot_idx = hash%w32_sock_state->connection_slots_count; - W32_SOCK_ConnectionSlot *slot = &w32_sock_state->connection_slots[slot_idx]; - Stripe *stripe = stripe_from_slot_idx(&w32_sock_state->connection_stripes, slot_idx); + U64 slot_idx = hash%session->connection_slots_count; + W32_SOCK_ConnectionSlot *slot = &session->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&session->connection_stripes, slot_idx); // rjf: store new connection WSABUF buf = {0}; @@ -88,10 +88,10 @@ w32_sock_listener_thread_entry_point(void *p) WSARecv(new_socket, &buf, 1, recv_size, &flags, recv_overlapped, 0); // rjf: create new accept socket, associate with iocp, zero overlapped, kick off next accept - w32_sock_state->tcp_accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - CreateIoCompletionPort((HANDLE)w32_sock_state->tcp_accept_socket, w32_sock_state->iocp, 0, 0); - MemoryZeroStruct(&w32_sock_state->tcp_accept_overlapped); - w32_sock_state->lpfnAcceptEx(w32_sock_state->tcp_listen_socket, w32_sock_state->tcp_accept_socket, w32_sock_state->tcp_accept_buffer, 0, sizeof(struct sockaddr_storage) + 16, sizeof(struct sockaddr_storage) + 16, &w32_sock_state->tcp_accept_size_out, &w32_sock_state->tcp_accept_overlapped); + session->tcp_accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + CreateIoCompletionPort((HANDLE)session->tcp_accept_socket, session->iocp, 0, 0); + MemoryZeroStruct(&session->tcp_accept_overlapped); + session->lpfnAcceptEx(session->tcp_listen_socket, session->tcp_accept_socket, session->tcp_accept_buffer, 0, sizeof(struct sockaddr_storage) + 16, sizeof(struct sockaddr_storage) + 16, &session->tcp_accept_size_out, &session->tcp_accept_overlapped); } //- rjf: overlapped ptr anywhere else -> completion of async recv @@ -101,14 +101,14 @@ w32_sock_listener_thread_entry_point(void *p) W32_SOCK_Connection *con = CastFromMember(W32_SOCK_Connection, recv_overlapped, overlapped_ptr); SOCK_Endpoint endpoint = con->endpoint; U64 hash = u64_hash_from_str8(str8_struct(&endpoint)); - U64 slot_idx = hash%w32_sock_state->connection_slots_count; - W32_SOCK_ConnectionSlot *slot = &w32_sock_state->connection_slots[slot_idx]; - Stripe *stripe = stripe_from_slot_idx(&w32_sock_state->connection_stripes, slot_idx); + U64 slot_idx = hash%session->connection_slots_count; + W32_SOCK_ConnectionSlot *slot = &session->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&session->connection_stripes, slot_idx); // rjf: success? -> push result to user. kick off next recv if(success) { - RingGuard g = guarded_ring_open(w32_sock_state->s2u_ring); + RingGuard g = guarded_ring_open(session->s2u_ring); U64 header[5] = { (U64)SOCK_Protocol_TCP, @@ -146,69 +146,11 @@ sock_init(void) { // NOTE(rjf): winsock2 is already initialized by the base layer for RIO function grabbing. - //- rjf: set up state + //- rjf: set up top-level state Arena *arena = arena_alloc(); w32_sock_state = push_array(arena, W32_SOCK_State, 1); w32_sock_state->arena = arena; - w32_sock_state->u2s_ring = guarded_ring_alloc(arena, KB(256)); - w32_sock_state->s2u_ring = guarded_ring_alloc(arena, KB(256)); - - //- rjf: set up IOCP - w32_sock_state->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); - - //- rjf: create listener(s) - w32_sock_state->tcp_listen_socket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); - { - DWORD ipv6only = 0; - setsockopt(w32_sock_state->tcp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); - } - - //- rjf: bind listener sockets - { - struct sockaddr_in server_addr; - server_addr.sin_family = AF_INET; - server_addr.sin_addr.s_addr = INADDR_ANY; - server_addr.sin_port = htons(SOCKET_PORT); - bind(w32_sock_state->tcp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); - } - - //- rjf: start listening for TCP connections - { - listen(w32_sock_state->tcp_listen_socket, SOMAXCONN); - } - - //- rjf: associate listener sockets with IOCP - { - CreateIoCompletionPort((HANDLE)w32_sock_state->tcp_listen_socket, w32_sock_state->iocp, 0, 0); - } - - //- rjf: load AcceptEx function - DWORD dwBytes = 0; - GUID AcceptEx_guid = WSAID_ACCEPTEX; - WSAIoctl(w32_sock_state->tcp_listen_socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &AcceptEx_guid, sizeof(AcceptEx_guid), &w32_sock_state->lpfnAcceptEx, sizeof(w32_sock_state->lpfnAcceptEx), &dwBytes, 0, 0); - - //- rjf: create accepting socket - { - w32_sock_state->tcp_accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - } - - //- rjf: associate accepting socket with IOCP - { - CreateIoCompletionPort((HANDLE)w32_sock_state->tcp_accept_socket, w32_sock_state->iocp, 0, 0); - } - - //- rjf: kick off accept - { - w32_sock_state->lpfnAcceptEx(w32_sock_state->tcp_listen_socket, w32_sock_state->tcp_accept_socket, w32_sock_state->tcp_accept_buffer, 0, sizeof(struct sockaddr_storage) + 16, sizeof(struct sockaddr_storage) + 16, &w32_sock_state->tcp_accept_size_out, &w32_sock_state->tcp_accept_overlapped); - } - - //- rjf: set up connection cache - w32_sock_state->connection_slots_count = 8; - w32_sock_state->connection_slots = push_array(arena, W32_SOCK_ConnectionSlot, w32_sock_state->connection_slots_count); - w32_sock_state->connection_stripes = stripe_array_alloc(arena); - - //- rjf: launch one-off listener threads to block & accept connections - w32_sock_state->tcp_listener_thread = thread_launch(w32_sock_listener_thread_entry_point, 0); + w32_sock_state->session_rw_mutex = rw_mutex_alloc(); } internal void @@ -223,6 +165,7 @@ sock_async_tick(void) struct SendTask { SendTask *next; + SOCK_Session session; SOCK_Endpoint endpoint; String8 data; }; @@ -235,32 +178,38 @@ sock_async_tick(void) for(;;) { B32 got_more = 0; - RingGuard g = guarded_ring_open(w32_sock_state->u2s_ring); + RWMutexScope(w32_sock_state->session_rw_mutex, 0) { - U64 header[5] = {0}; - if(guarded_ring_try_read(&g, sizeof(header), header)) + for EachNode(s, W32_SOCK_Session, w32_sock_state->first_session) { - got_more = 1; - SOCK_Protocol protocol = (SOCK_Protocol)header[0]; - U16 port = (U16)header[1]; - SOCK_Endpoint endpoint = {0}; - endpoint.address_u64[0] = header[2]; - endpoint.address_u64[1] = header[3]; - U64 data_size = header[4]; - U8 *data = push_array(scratch.arena, U8, data_size); - guarded_ring_read_or_wait(&g, data_size, data, max_U64); - SendTask *t = push_array(scratch.arena, SendTask, 1); - t->endpoint = endpoint; - t->data = str8(data, data_size); - switch(protocol) + RingGuard g = guarded_ring_open(s->u2s_ring); { - default:{}break; - case SOCK_Protocol_TCP:{SLLQueuePush(first_tcp_send, last_tcp_send, t);}break; - case SOCK_Protocol_UDP:{SLLQueuePush(first_tcp_send, last_tcp_send, t);}break; + U64 header[5] = {0}; + if(guarded_ring_try_read(&g, sizeof(header), header)) + { + got_more = 1; + SOCK_Protocol protocol = (SOCK_Protocol)header[0]; + U16 port = (U16)header[1]; + SOCK_Endpoint endpoint = {0}; + endpoint.address_u64[0] = header[2]; + endpoint.address_u64[1] = header[3]; + U64 data_size = header[4]; + U8 *data = push_array(scratch.arena, U8, data_size); + guarded_ring_read_or_wait(&g, data_size, data, max_U64); + SendTask *t = push_array(scratch.arena, SendTask, 1); + t->endpoint = endpoint; + t->data = str8(data, data_size); + switch(protocol) + { + default:{}break; + case SOCK_Protocol_TCP:{SLLQueuePush(first_tcp_send, last_tcp_send, t);}break; + case SOCK_Protocol_UDP:{SLLQueuePush(first_tcp_send, last_tcp_send, t);}break; + } + } } + guarded_ring_close(&g); } } - guarded_ring_close(&g); if(!got_more) { break; @@ -274,10 +223,11 @@ sock_async_tick(void) // for(SendTask *t = first_tcp_send; t != 0; t = t->next) { + W32_SOCK_Session *session = (W32_SOCK_Session *)t->session.u64[0]; U64 hash = u64_hash_from_str8(str8_struct(&t->endpoint)); - U64 slot_idx = hash%w32_sock_state->connection_slots_count; - W32_SOCK_ConnectionSlot *slot = &w32_sock_state->connection_slots[slot_idx]; - Stripe *stripe = stripe_from_slot_idx(&w32_sock_state->connection_stripes, slot_idx); + U64 slot_idx = hash%session->connection_slots_count; + W32_SOCK_ConnectionSlot *slot = &session->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&session->connection_stripes, slot_idx); RWMutexScope(stripe->rw_mutex, 1) { for(W32_SOCK_Connection *c = slot->first; c != 0; c = c->next) @@ -302,23 +252,109 @@ sock_async_tick(void) scratch_end(scratch); } -internal void -sock_set_wakeup_hook(SOCK_WakeupFunctionType *hook) +//////////////////////////////// +//~ rjf: @per_os_impl Session Creation/Closing + +internal SOCK_Session +sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) { - w32_sock_state->wakeup_hook = hook; + //- rjf: set up state + Arena *arena = arena_alloc(); + W32_SOCK_Session *session = push_array(arena, W32_SOCK_Session, 1); + session->arena = arena; + session->u2s_ring = guarded_ring_alloc(arena, KB(256)); + session->s2u_ring = guarded_ring_alloc(arena, KB(256)); + + //- rjf: set up IOCP + session->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); + + //- rjf: create listener(s) + session->tcp_listen_socket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); + { + DWORD ipv6only = 0; + setsockopt(session->tcp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); + } + + //- rjf: bind listener sockets + { + struct sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(listener_port); + bind(session->tcp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); + } + + //- rjf: start listening for TCP connections + { + listen(session->tcp_listen_socket, SOMAXCONN); + } + + //- rjf: associate listener sockets with IOCP + { + CreateIoCompletionPort((HANDLE)session->tcp_listen_socket, session->iocp, 0, 0); + } + + //- rjf: load AcceptEx function + DWORD dwBytes = 0; + GUID AcceptEx_guid = WSAID_ACCEPTEX; + WSAIoctl(session->tcp_listen_socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &AcceptEx_guid, sizeof(AcceptEx_guid), &session->lpfnAcceptEx, sizeof(session->lpfnAcceptEx), &dwBytes, 0, 0); + + //- rjf: create accepting socket + { + session->tcp_accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } + + //- rjf: associate accepting socket with IOCP + { + CreateIoCompletionPort((HANDLE)session->tcp_accept_socket, session->iocp, 0, 0); + } + + //- rjf: kick off accept + { + session->lpfnAcceptEx(session->tcp_listen_socket, session->tcp_accept_socket, session->tcp_accept_buffer, 0, sizeof(struct sockaddr_storage) + 16, sizeof(struct sockaddr_storage) + 16, &session->tcp_accept_size_out, &session->tcp_accept_overlapped); + } + + //- rjf: set up connection cache + session->connection_slots_count = 8; + session->connection_slots = push_array(arena, W32_SOCK_ConnectionSlot, session->connection_slots_count); + session->connection_stripes = stripe_array_alloc(arena); + + //- rjf: set wakeup hook + session->wakeup_hook = wakeup_hook; + + //- rjf: launch one-off listener threads to block & accept connections + session->tcp_listener_thread = thread_launch(w32_sock_listener_thread_entry_point, session); + + //- rjf: connect to top-level state + RWMutexScope(w32_sock_state->session_rw_mutex, 1) + { + DLLPushBack(w32_sock_state->first_session, w32_sock_state->last_session, session); + } + + //- rjf: bundle as handle + SOCK_Session result = {(U64)session}; + return result; +} + +internal void +sock_session_close(SOCK_Session session) +{ + W32_SOCK_Session *s = (W32_SOCK_Session *)session.u64[0]; + arena_release(s->arena); } //////////////////////////////// //~ rjf: @per_os_impl Sends internal B32 -sock_send(SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us) +sock_send(SOCK_Session session, SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us) { B32 result = 0; - RingGuard guard = guarded_ring_open(w32_sock_state->u2s_ring); + W32_SOCK_Session *s = (W32_SOCK_Session *)session.u64[0]; + RingGuard guard = guarded_ring_open(s->u2s_ring); { U64 header[5] = {0}; - U64 size_cap = w32_sock_state->u2s_ring->ring->size - sizeof(header); + U64 size_cap = s->u2s_ring->ring->size - sizeof(header); U64 size = Min(data.size, size_cap); { header[0] = (U64)protocol; @@ -346,17 +382,18 @@ sock_send(SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt //~ rjf: @per_os_impl Receives internal B32 -sock_recv(Arena *arena, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us) +sock_recv(Arena *arena, SOCK_Session session, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us) { B32 result = 0; U64 header_size = sizeof(*protocol_out) + sizeof(*endpoint_out) + sizeof(U64); { - RingGuard guard = guarded_ring_open(w32_sock_state->s2u_ring); + W32_SOCK_Session *s = (W32_SOCK_Session *)session.u64[0]; + RingGuard guard = guarded_ring_open(s->s2u_ring); { U64 header[5] = {0}; if(guarded_ring_read_or_wait(&guard, sizeof(header), header, endt_us)) { - U64 size_cap = w32_sock_state->s2u_ring->ring->size - sizeof(header); + U64 size_cap = s->s2u_ring->ring->size - sizeof(header); protocol_out[0] = (SOCK_Protocol)header[0]; endpoint_out->port = (U16)header[1]; endpoint_out->address_u64[0] = header[2]; diff --git a/src/win32/socket/win32_socket.h b/src/win32/socket/win32_socket.h index 4d0be4c8..f53992a7 100644 --- a/src/win32/socket/win32_socket.h +++ b/src/win32/socket/win32_socket.h @@ -27,9 +27,11 @@ struct W32_SOCK_ConnectionSlot W32_SOCK_Connection *last; }; -typedef struct W32_SOCK_State W32_SOCK_State; -struct W32_SOCK_State +typedef struct W32_SOCK_Session W32_SOCK_Session; +struct W32_SOCK_Session { + W32_SOCK_Session *next; + W32_SOCK_Session *prev; Arena *arena; GuardedRing *u2s_ring; GuardedRing *s2u_ring; @@ -47,6 +49,18 @@ struct W32_SOCK_State SOCK_WakeupFunctionType *wakeup_hook; }; +typedef struct W32_SOCK_State W32_SOCK_State; +struct W32_SOCK_State +{ + Arena *arena; + RWMutex session_rw_mutex; + W32_SOCK_Session *first_session; + W32_SOCK_Session *last_session; +}; + +//////////////////////////////// +//~ rjf: Globals + global W32_SOCK_State *w32_sock_state = 0; //////////////////////////////// From d2140ace3d841b3cc7607044b1956e22046c7458 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 08:42:15 -0700 Subject: [PATCH 202/251] raddbg: allow overriding ipc port from command line --- src/raddbg/raddbg_main.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 7a1dc2e3..d82b85a6 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -641,33 +641,40 @@ entry_point(CmdLine *cmd_line) U64 jit_pid = 0; U64 jit_code = 0; U64 jit_addr = 0; + U64 ipc_port = 7423; { - if(cmd_line_has_flag(cmd_line, str8_lit("ipc"))) + if(cmd_line_has_flag(cmd_line, s("ipc"))) { exec_mode = ExecMode_IPCSender; } - else if(cmd_line_has_flag(cmd_line, str8_lit("bin"))) + else if(cmd_line_has_flag(cmd_line, s("bin"))) { exec_mode = ExecMode_BinaryUtility; } - else if(cmd_line_has_flag(cmd_line, str8_lit("?")) || - cmd_line_has_flag(cmd_line, str8_lit("help"))) + else if(cmd_line_has_flag(cmd_line, s("?")) || + cmd_line_has_flag(cmd_line, s("help"))) { exec_mode = ExecMode_Help; } - auto_run = cmd_line_has_flag(cmd_line, str8_lit("auto_run")); - auto_step = cmd_line_has_flag(cmd_line, str8_lit("auto_step")); - String8 jit_pid_string = cmd_line_string(cmd_line, str8_lit("jit_pid")); - String8 jit_code_string = cmd_line_string(cmd_line, str8_lit("jit_code")); - String8 jit_addr_string = cmd_line_string(cmd_line, str8_lit("jit_addr")); + auto_run = cmd_line_has_flag(cmd_line, s("auto_run")); + auto_step = cmd_line_has_flag(cmd_line, s("auto_step")); + String8 jit_pid_string = cmd_line_string(cmd_line, s("jit_pid")); + String8 jit_code_string = cmd_line_string(cmd_line, s("jit_code")); + String8 jit_addr_string = cmd_line_string(cmd_line, s("jit_addr")); try_u64_from_str8_c_rules(jit_pid_string, &jit_pid); try_u64_from_str8_c_rules(jit_code_string, &jit_code); try_u64_from_str8_c_rules(jit_addr_string, &jit_addr); + String8 ipc_port_string = cmd_line_string(cmd_line, s("ipc_port")); + U64 ipc_port_try = 0; + if(try_u64_from_str8_c_rules(ipc_port_string, &ipc_port_try)) + { + ipc_port = ipc_port_try; + } jit_attach = (jit_addr != 0); } // init log - g_logs_folder = cmd_line_string(cmd_line, str8_lit("logs")); + g_logs_folder = cmd_line_string(cmd_line, s("logs")); if(g_logs_folder.size == 0) { String8 user_program_logs_data_path = get_process_info()->user_program_logs_data_path; @@ -767,7 +774,7 @@ entry_point(CmdLine *cmd_line) } //- rjf: set up socket session for ICP - SOCK_Session icp_sock_session = sock_session_open(7423, wakeup_hook_ctrl); + SOCK_Session icp_sock_session = sock_session_open((U16)ipc_port, wakeup_hook_ctrl); //- rjf: main application loop { From 82aaccd945fe14d94eff4f829f630cf64ca53d79 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 08:57:11 -0700 Subject: [PATCH 203/251] sock: fix sends, raddbg_core: send ipc results through socket, back to endpoint from which ipc command(s) originated --- src/raddbg/raddbg_core.c | 95 +++++++++++++++++---------------- src/raddbg/raddbg_main.c | 16 ++++-- src/win32/socket/win32_socket.c | 6 ++- 3 files changed, 66 insertions(+), 51 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index ca3898bc..6c8fd3c0 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -13748,11 +13748,12 @@ rd_frame(void) }break; case RD_CmdKind_State: { - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "state:\n{\n"); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " running: %i\n", d_ctrl_targets_running()); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " run_gen: %I64u\n", d_run_gen()); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " stop_count: %I64u\n", d_stop_count()); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip: 0x%I64x\n", d_ctrl_last_stop_event().rip_vaddr); + String8List state_parts = {0}; + str8_list_pushf(scratch.arena, &state_parts, "state:\n{\n"); + str8_list_pushf(scratch.arena, &state_parts, " running: %i\n", d_ctrl_targets_running()); + str8_list_pushf(scratch.arena, &state_parts, " run_gen: %I64u\n", d_run_gen()); + str8_list_pushf(scratch.arena, &state_parts, " stop_count: %I64u\n", d_stop_count()); + str8_list_pushf(scratch.arena, &state_parts, " ip: 0x%I64x\n", d_ctrl_last_stop_event().rip_vaddr); { Access *access = access_open(); D_Event evt = d_ctrl_last_stop_event(); @@ -13766,28 +13767,28 @@ rd_frame(void) RDI_Symbol *procedure = rdi_procedure_from_voff(rdi, voff); String8 name = fully_qualified_str8_from_rdi_symbol(scratch.arena, rdi, procedure); name = escaped_from_raw_str8(scratch.arena, name); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip_module: \"%S\"\n", str8_skip_last_slash(module->string)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip_voff: 0x%I64x\n", voff); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip_voff_symbol: \"%S\"\n", name); + str8_list_pushf(scratch.arena, &state_parts, " ip_module: \"%S\"\n", str8_skip_last_slash(module->string)); + str8_list_pushf(scratch.arena, &state_parts, " ip_voff: 0x%I64x\n", voff); + str8_list_pushf(scratch.arena, &state_parts, " ip_voff_symbol: \"%S\"\n", name); access_close(access); } { D_Event evt = d_ctrl_last_stop_event(); DR_FStrList explanation_fstrs = rd_stop_explanation_fstrs_from_ctrl_event(scratch.arena, &evt); String8 explanation_string = dr_string_from_fstrs(scratch.arena, &explanation_fstrs); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " stop_event:\n {\n"); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " arch: %S\n", string_from_arch(evt.arch)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " vaddr_range: [0x%I64x, 0x%I64x)\n", evt.vaddr_rng.min, evt.vaddr_rng.max); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip_vaddr: 0x%I64x\n", evt.rip_vaddr); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " stack_base: 0x%I64x\n", evt.stack_base); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " tls_root: 0x%I64x\n", evt.tls_root); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " tls_index: 0x%I64x\n", evt.tls_index); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " timestamp: 0x%I64x\n", evt.timestamp); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " exception_code: 0x%I64x\n", (U64)evt.exception_code); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " bp_flags: 0x%I64x\n", (U64)evt.bp_flags); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " string: \"%S\"\n", escaped_from_raw_str8(scratch.arena, evt.string)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " explanation: \"%S\"\n", escaped_from_raw_str8(scratch.arena, explanation_string)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + str8_list_pushf(scratch.arena, &state_parts, " stop_event:\n {\n"); + str8_list_pushf(scratch.arena, &state_parts, " arch: %S\n", string_from_arch(evt.arch)); + str8_list_pushf(scratch.arena, &state_parts, " vaddr_range: [0x%I64x, 0x%I64x)\n", evt.vaddr_rng.min, evt.vaddr_rng.max); + str8_list_pushf(scratch.arena, &state_parts, " ip_vaddr: 0x%I64x\n", evt.rip_vaddr); + str8_list_pushf(scratch.arena, &state_parts, " stack_base: 0x%I64x\n", evt.stack_base); + str8_list_pushf(scratch.arena, &state_parts, " tls_root: 0x%I64x\n", evt.tls_root); + str8_list_pushf(scratch.arena, &state_parts, " tls_index: 0x%I64x\n", evt.tls_index); + str8_list_pushf(scratch.arena, &state_parts, " timestamp: 0x%I64x\n", evt.timestamp); + str8_list_pushf(scratch.arena, &state_parts, " exception_code: 0x%I64x\n", (U64)evt.exception_code); + str8_list_pushf(scratch.arena, &state_parts, " bp_flags: 0x%I64x\n", (U64)evt.bp_flags); + str8_list_pushf(scratch.arena, &state_parts, " string: \"%S\"\n", escaped_from_raw_str8(scratch.arena, evt.string)); + str8_list_pushf(scratch.arena, &state_parts, " explanation: \"%S\"\n", escaped_from_raw_str8(scratch.arena, explanation_string)); + str8_list_pushf(scratch.arena, &state_parts, " }\n"); } { Access *access = access_open(); @@ -13800,12 +13801,12 @@ rd_frame(void) DI_Key dbgi_key = d_dbgi_key_from_module(module); RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); E_String2NumMap *locals_map = e_push_locals_map_from_rdi_voff(scratch.arena, rdi, voff); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " locals:\n {\n"); + str8_list_pushf(scratch.arena, &state_parts, " locals:\n {\n"); for(E_String2NumMapNode *n = locals_map->first; n != 0; n = n->order_next) { - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " %S\n", n->string); + str8_list_pushf(scratch.arena, &state_parts, " %S\n", n->string); } - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + str8_list_pushf(scratch.arena, &state_parts, " }\n"); access_close(access); } { @@ -13819,48 +13820,50 @@ rd_frame(void) String8 dbgi_path = dbg_path->string; DI_Key dbgi_key = d_dbgi_key_from_debug_info_path(dbg_path); D_LineList lines = d_lines_from_dbgi_key_path_voff(scratch.arena, dbgi_key, dbgi_path, voff); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " lines:\n {\n"); + str8_list_pushf(scratch.arena, &state_parts, " lines:\n {\n"); for EachNode(n, D_LineNode, lines.first) { D_Line line = n->v; String8 line_file_name = str8_skip_last_slash(line.file_path); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " {\n"); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " file_name: \"%S\"\n", line_file_name); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " line_num: %I64d\n", line.pt.line); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " column_num: %I64d\n", line.pt.column); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " voff_range: [0x%I64x, 0x%I64x)\n", line.voff_range.min, line.voff_range.max); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + str8_list_pushf(scratch.arena, &state_parts, " {\n"); + str8_list_pushf(scratch.arena, &state_parts, " file_name: \"%S\"\n", line_file_name); + str8_list_pushf(scratch.arena, &state_parts, " line_num: %I64d\n", line.pt.line); + str8_list_pushf(scratch.arena, &state_parts, " column_num: %I64d\n", line.pt.column); + str8_list_pushf(scratch.arena, &state_parts, " voff_range: [0x%I64x, 0x%I64x)\n", line.voff_range.min, line.voff_range.max); + str8_list_pushf(scratch.arena, &state_parts, " }\n"); } - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + str8_list_pushf(scratch.arena, &state_parts, " }\n"); } { - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " threads:\n {\n"); + str8_list_pushf(scratch.arena, &state_parts, " threads:\n {\n"); D_EntityArray threads = d_entity_array_from_kind(D_EntityKind_Thread); for EachIndex(idx, threads.count) { D_Entity *thread = threads.v[idx]; - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " {\n"); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " name: \"%S\"\n", escaped_from_raw_str8(scratch.arena, thread->string)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " id: %I64u\n", thread->id); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " ip: 0x%I64x\n", d_query_cached_rip_from_thread(thread)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + str8_list_pushf(scratch.arena, &state_parts, " {\n"); + str8_list_pushf(scratch.arena, &state_parts, " name: \"%S\"\n", escaped_from_raw_str8(scratch.arena, thread->string)); + str8_list_pushf(scratch.arena, &state_parts, " id: %I64u\n", thread->id); + str8_list_pushf(scratch.arena, &state_parts, " ip: 0x%I64x\n", d_query_cached_rip_from_thread(thread)); + str8_list_pushf(scratch.arena, &state_parts, " }\n"); } - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + str8_list_pushf(scratch.arena, &state_parts, " }\n"); } { - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " modules:\n {\n"); + str8_list_pushf(scratch.arena, &state_parts, " modules:\n {\n"); D_EntityArray modules = d_entity_array_from_kind(D_EntityKind_Module); for EachIndex(idx, modules.count) { D_Entity *module = modules.v[idx]; - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " {\n"); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " name: \"%S\"\n", escaped_from_raw_str8(scratch.arena, module->string)); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " vaddr_range: [0x%I64x, 0x%I64x)\n", module->vaddr_range.min, module->vaddr_range.max); - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + str8_list_pushf(scratch.arena, &state_parts, " {\n"); + str8_list_pushf(scratch.arena, &state_parts, " name: \"%S\"\n", escaped_from_raw_str8(scratch.arena, module->string)); + str8_list_pushf(scratch.arena, &state_parts, " vaddr_range: [0x%I64x, 0x%I64x)\n", module->vaddr_range.min, module->vaddr_range.max); + str8_list_pushf(scratch.arena, &state_parts, " }\n"); } - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, " }\n"); + str8_list_pushf(scratch.arena, &state_parts, " }\n"); } - str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "}\n"); + str8_list_pushf(scratch.arena, &state_parts, "}\n"); + String8 state = str8_list_join(rd_state->cmd_output_arena, &state_parts, 0); + str8_list_push(rd_state->cmd_output_arena, &rd_state->cmd_outputs, state); }break; case RD_CmdKind_Eval: { diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index d82b85a6..72c302d4 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -843,12 +843,12 @@ entry_point(CmdLine *cmd_line) } //- rjf: receive socket data, run as IPC messages + SOCK_Protocol ipc_protocol = SOCK_Protocol_TCP; + SOCK_Endpoint ipc_endpoint = {0}; { Temp scratch = scratch_begin(0, 0); - SOCK_Protocol protocol = SOCK_Protocol_TCP; - SOCK_Endpoint endpoint = {0}; String8 msg = {0}; - if(sock_recv(scratch.arena, icp_sock_session, &protocol, &endpoint, &msg, 0)) + if(sock_recv(scratch.arena, icp_sock_session, &ipc_protocol, &ipc_endpoint, &msg, 0)) { String8List cmd_parts_of_msg = str8_split(scratch.arena, msg, (U8 *)";", 1, 0); RD_WindowState *dst_ws = rd_state->first_window_state; @@ -916,6 +916,7 @@ entry_point(CmdLine *cmd_line) } //- rjf: gather command outputs & write them +#if 0 if(ipc_command_frame) { if(ipc_main2sender_shared_memory_base != 0 && @@ -932,6 +933,15 @@ entry_point(CmdLine *cmd_line) semaphore_drop(ipc_main2sender_lock_semaphore); } } +#endif + + //- rjf: gather command outputs & send them back back to sender + if(ipc_command_frame) + { + StringJoin join = {s(""), s("\0"), s("")}; + String8 msg = str8_list_join(scratch.arena, &rd_state->cmd_outputs, &join); + sock_send(icp_sock_session, ipc_protocol, ipc_endpoint, msg, now_time_us()+5000000); + } } } diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index 417e167c..8bcfcdb9 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -165,7 +165,7 @@ sock_async_tick(void) struct SendTask { SendTask *next; - SOCK_Session session; + W32_SOCK_Session *session; SOCK_Endpoint endpoint; String8 data; }; @@ -193,10 +193,12 @@ sock_async_tick(void) SOCK_Endpoint endpoint = {0}; endpoint.address_u64[0] = header[2]; endpoint.address_u64[1] = header[3]; + endpoint.port = port; U64 data_size = header[4]; U8 *data = push_array(scratch.arena, U8, data_size); guarded_ring_read_or_wait(&g, data_size, data, max_U64); SendTask *t = push_array(scratch.arena, SendTask, 1); + t->session = s; t->endpoint = endpoint; t->data = str8(data, data_size); switch(protocol) @@ -223,7 +225,7 @@ sock_async_tick(void) // for(SendTask *t = first_tcp_send; t != 0; t = t->next) { - W32_SOCK_Session *session = (W32_SOCK_Session *)t->session.u64[0]; + W32_SOCK_Session *session = t->session; U64 hash = u64_hash_from_str8(str8_struct(&t->endpoint)); U64 slot_idx = hash%session->connection_slots_count; W32_SOCK_ConnectionSlot *slot = &session->connection_slots[slot_idx]; From 29119335effe37adc0bc758d915ce358fc467b70 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 09:27:47 -0700 Subject: [PATCH 204/251] socket: client connections/sends; raddbg: move ipc sender mode to using socket layer --- src/raddbg/raddbg_main.c | 42 ++++++++++++ src/win32/socket/win32_socket.c | 118 +++++++++++++++++++++++++++++--- 2 files changed, 152 insertions(+), 8 deletions(-) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 72c302d4..d8ddb79a 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -940,6 +940,10 @@ entry_point(CmdLine *cmd_line) { StringJoin join = {s(""), s("\0"), s("")}; String8 msg = str8_list_join(scratch.arena, &rd_state->cmd_outputs, &join); + if(msg.size == 0) + { + msg = s("done"); + } sock_send(icp_sock_session, ipc_protocol, ipc_endpoint, msg, now_time_us()+5000000); } } @@ -952,6 +956,43 @@ entry_point(CmdLine *cmd_line) { Temp scratch = scratch_begin(0, 0); + //- rjf: create socket session + SOCK_Session ipc_sock_session = sock_session_open(0, 0); + + //- rjf: form target raddbg endpoint address + SOCK_Endpoint endpoint = {0}; + { + endpoint.address_u8[0] = 127; + endpoint.address_u8[3] = 1; + endpoint.port = (U16)ipc_port; + } + + //- rjf: form message from command line inputs + String8List parts = {0}; + { + for EachIndex(idx, cmd_line->argc-1) + { + str8_list_push(scratch.arena, &parts, str8_cstring(cmd_line->argv[idx+1])); + } + } + StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; + String8 msg = str8_list_join(scratch.arena, &parts, &join); + + //- rjf: send to server, get outputs + String8List outputs = {0}; + if(sock_send(ipc_sock_session, SOCK_Protocol_TCP, endpoint, msg, now_time_us()+10000000)) + { + SOCK_Protocol outputs_protocol = SOCK_Protocol_TCP; + SOCK_Endpoint outputs_endpoint = {0}; + String8 output_data = {0}; + if(sock_recv(scratch.arena, ipc_sock_session, &outputs_protocol, &outputs_endpoint, &output_data, now_time_us()+10000000)) + { + U8 split_char = 0; + outputs = str8_split(scratch.arena, output_data, &split_char, 1, 0); + } + } + +#if 0 //- rjf: grab explicit PID argument U32 dst_pid = 0; if(cmd_line_has_argument(cmd_line, str8_lit("pid"))) @@ -1040,6 +1081,7 @@ entry_point(CmdLine *cmd_line) semaphore_drop(ipc_main2sender_lock_semaphore); } } +#endif //- rjf: write outputs to stdout for(String8Node *n = outputs.first; n != 0; n = n->next) diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index 8bcfcdb9..ecf69a04 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -76,6 +76,7 @@ w32_sock_listener_thread_entry_point(void *p) con->endpoint = endpoint; con->protocol = SOCK_Protocol_TCP; con->socket = new_socket; + MemoryZeroStruct(&con->recv_overlapped); DLLPushBack(slot->first, slot->last, con); buf.len = sizeof(con->recv_buffer); buf.buf = con->recv_buffer; @@ -226,25 +227,126 @@ sock_async_tick(void) for(SendTask *t = first_tcp_send; t != 0; t = t->next) { W32_SOCK_Session *session = t->session; + + // rjf: unpack endpoint U64 hash = u64_hash_from_str8(str8_struct(&t->endpoint)); U64 slot_idx = hash%session->connection_slots_count; W32_SOCK_ConnectionSlot *slot = &session->connection_slots[slot_idx]; Stripe *stripe = stripe_from_slot_idx(&session->connection_stripes, slot_idx); - RWMutexScope(stripe->rw_mutex, 1) + + // rjf: get existing socket for this endpoint + SOCKET ep_socket = -1; + RWMutexScope(stripe->rw_mutex, 0) { for(W32_SOCK_Connection *c = slot->first; c != 0; c = c->next) { if(MemoryMatchStruct(&c->endpoint, &t->endpoint)) { - if(send(c->socket, t->data.str, t->data.size, 0) == SOCKET_ERROR) + ep_socket = c->socket; + break; + } + } + } + + // rjf: didn't get a socket? -> open socket + if(ep_socket == -1) RWMutexScope(stripe->rw_mutex, 1) + { + // rjf: try to get socket again, now that we have the write lock + W32_SOCK_Connection *con = 0; + for(W32_SOCK_Connection *c = slot->first; c != 0; c = c->next) + { + if(MemoryMatchStruct(&c->endpoint, &t->endpoint)) + { + con = c; + break; + } + } + + // rjf: no socket still? -> create + if(con == 0) + { + // rjf: convert endpoint -> sockaddr + struct sockaddr_storage endpoint_sockaddr = {0}; + int endpoint_sockaddr_size = 0; + { + switch(t->endpoint.kind) { - int error = WSAGetLastError(); - if(error == WSAECONNRESET) + default:{}break; + case SOCK_EndpointKind_IPv4: { - DLLRemove(slot->first, slot->last, c); - c->next = stripe->free; - stripe->free = c; - } + struct sockaddr_in *dst = (struct sockaddr_in *)(&endpoint_sockaddr); + endpoint_sockaddr_size = sizeof(*dst); + dst->sin_family = AF_INET; + dst->sin_port = htons(t->endpoint.port); + MemoryCopy(&dst->sin_addr, &t->endpoint.address_u32[0], sizeof(U32)); + }break; + case SOCK_EndpointKind_IPv6: + { + struct sockaddr_in6 *dst = (struct sockaddr_in6 *)(&endpoint_sockaddr); + endpoint_sockaddr_size = sizeof(*dst); + dst->sin6_family = AF_INET6; + dst->sin6_port = htons(t->endpoint.port); + MemoryCopy(&dst->sin6_addr, &t->endpoint.address_u128[0], sizeof(U128)); + }break; + } + } + + // rjf: create + SOCKET new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + connect(new_socket, (struct sockaddr *)&endpoint_sockaddr, endpoint_sockaddr_size); + + // rjf: store in cache + con = (W32_SOCK_Connection *)stripe->free; + if(con != 0) + { + stripe->free = con->next; + } + else + { + con = push_array(stripe->arena, W32_SOCK_Connection, 1); + } + con->endpoint = t->endpoint; + con->protocol = SOCK_Protocol_TCP; + con->socket = new_socket; + DLLPushBack(slot->first, slot->last, con); + + // rjf: associate this socket with iocp + CreateIoCompletionPort((HANDLE)new_socket, session->iocp, 0, 0); + MemoryZeroStruct(&con->recv_overlapped); + + // rjf: kick off receive on this socket + WSABUF buf = {sizeof(con->recv_buffer), con->recv_buffer}; + DWORD flags = MSG_PUSH_IMMEDIATE; + WSARecv(con->socket, &buf, 1, &con->recv_size, &flags, &con->recv_overlapped, 0); + } + + // rjf: get socket from cache + ep_socket = con->socket; + } + + // rjf: got socket? -> send + B32 send_failed = 0; + if(ep_socket != -1 && send(ep_socket, t->data.str, t->data.size, 0) == SOCKET_ERROR) + { + int error = WSAGetLastError(); + if(error == WSAECONNRESET) + { + send_failed = 1; + } + } + + // rjf: got a socket, but send failed? -> connection closed + if(ep_socket != -1 && send_failed) + { + RWMutexScope(stripe->rw_mutex, 1) + { + for(W32_SOCK_Connection *c = slot->first; c != 0; c = c->next) + { + if(MemoryMatchStruct(&c->endpoint, &t->endpoint)) + { + DLLRemove(slot->first, slot->last, c); + c->next = stripe->free; + stripe->free = c; } } } From 98657d9b38e2351cb86ee443cc223878a75e7337 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 09:32:02 -0700 Subject: [PATCH 205/251] raddbg: eliminate old ipc mechanism; allow ipc network address override via command line argument --- src/raddbg/raddbg_main.c | 276 +-------------------------------------- 1 file changed, 6 insertions(+), 270 deletions(-) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index d8ddb79a..969a225f 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -536,67 +536,6 @@ typedef enum ExecMode } ExecMode; -typedef struct IPCInfo IPCInfo; -struct IPCInfo -{ - U64 msg_size; -}; - -//////////////////////////////// -//~ rjf: Globals - -//- rjf: IPC resources -#define IPC_SHARED_MEMORY_BUFFER_SIZE KB(64) -StaticAssert(IPC_SHARED_MEMORY_BUFFER_SIZE > sizeof(IPCInfo), ipc_buffer_size_requirement); -global Semaphore ipc_sender2main_signal_semaphore = {0}; -global Semaphore ipc_sender2main_lock_semaphore = {0}; -global U8 *ipc_sender2main_shared_memory_base = 0; -global Semaphore ipc_main2sender_signal_semaphore = {0}; -global Semaphore ipc_main2sender_lock_semaphore = {0}; -global U8 *ipc_main2sender_shared_memory_base = 0; -global U8 ipc_s2m_ring_buffer[KB(64)] = {0}; -global U64 ipc_s2m_ring_write_pos = 0; -global U64 ipc_s2m_ring_read_pos = 0; -global Mutex ipc_s2m_ring_mutex = {0}; -global CondVar ipc_s2m_ring_cv = {0}; - -//////////////////////////////// -//~ rjf: IPC Signaler Thread - -internal void -ipc_signaler_thread__entry_point(void *p) -{ - ThreadNameF("rd_ipc_signaler_thread"); - for(;;) - { - if(semaphore_take(ipc_sender2main_signal_semaphore, max_U64)) - { - if(semaphore_take(ipc_sender2main_lock_semaphore, max_U64)) - { - IPCInfo *ipc_info = (IPCInfo *)ipc_sender2main_shared_memory_base; - String8 msg = str8((U8 *)(ipc_info+1), ipc_info->msg_size); - msg.size = Min(msg.size, IPC_SHARED_MEMORY_BUFFER_SIZE - sizeof(IPCInfo)); - MutexScope(ipc_s2m_ring_mutex) for(;;) - { - U64 unconsumed_size = ipc_s2m_ring_write_pos - ipc_s2m_ring_read_pos; - U64 available_size = (sizeof(ipc_s2m_ring_buffer) - unconsumed_size); - if(available_size >= sizeof(U64)+sizeof(msg.size)) - { - ipc_s2m_ring_write_pos += wrapped_write_struct(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_write_pos, &msg.size); - ipc_s2m_ring_write_pos += wrapped_write(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_write_pos, msg.str, msg.size); - break; - } - cond_var_wait(ipc_s2m_ring_cv, ipc_s2m_ring_mutex, max_U64); - } - cond_var_broadcast(ipc_s2m_ring_cv); - wm_send_wakeup_event(); - ipc_info->msg_size = 0; - semaphore_drop(ipc_sender2main_lock_semaphore); - } - } - } -} - //////////////////////////////// //~ rjf: Ctrl -> Main Thread Wakeup Hook @@ -737,42 +676,6 @@ entry_point(CmdLine *cmd_line) d_set_wakeup_hook(wakeup_hook_ctrl); } - //- rjf: set up shared resources for ipc to this instance; launch IPC signaler thread - { - Temp scratch = scratch_begin(0, 0); - U32 instance_pid = get_process_info()->pid; - - // rjf: set up cross-process sender -> main ring buffer - String8 ipc_sender2main_shared_memory_name = str8f(scratch.arena, "_raddbg_ipc_sender2main_shared_memory_%i_", instance_pid); - String8 ipc_sender2main_signal_semaphore_name = str8f(scratch.arena, "_raddbg_ipc_sender2main_signal_semaphore_%i_", instance_pid); - String8 ipc_sender2main_lock_semaphore_name = str8f(scratch.arena, "_raddbg_ipc_sender2main_lock_semaphore_%i_", instance_pid); - SharedMemory ipc_sender2main_shared_memory = shared_memory_alloc(IPC_SHARED_MEMORY_BUFFER_SIZE, ipc_sender2main_shared_memory_name); - ipc_sender2main_shared_memory_base = (U8 *)shared_memory_view_open(ipc_sender2main_shared_memory, r1u64(0, IPC_SHARED_MEMORY_BUFFER_SIZE)); - ipc_sender2main_signal_semaphore = semaphore_alloc(0, 1, ipc_sender2main_signal_semaphore_name); - ipc_sender2main_lock_semaphore = semaphore_alloc(1, 1, ipc_sender2main_lock_semaphore_name); - - // rjf: set up cross-process main -> sender ring buffer - String8 ipc_main2sender_shared_memory_name = str8f(scratch.arena, "_raddbg_ipc_main2sender_shared_memory_%i_", instance_pid); - String8 ipc_main2sender_signal_semaphore_name = str8f(scratch.arena, "_raddbg_ipc_main2sender_signal_semaphore_%i_", instance_pid); - String8 ipc_main2sender_lock_semaphore_name = str8f(scratch.arena, "_raddbg_ipc_main2sender_lock_semaphore_%i_", instance_pid); - SharedMemory ipc_main2sender_shared_memory = shared_memory_alloc(IPC_SHARED_MEMORY_BUFFER_SIZE, ipc_main2sender_shared_memory_name); - ipc_main2sender_shared_memory_base = (U8 *)shared_memory_view_open(ipc_main2sender_shared_memory, r1u64(0, IPC_SHARED_MEMORY_BUFFER_SIZE)); - ipc_main2sender_signal_semaphore = semaphore_alloc(0, 1, ipc_main2sender_signal_semaphore_name); - ipc_main2sender_lock_semaphore = semaphore_alloc(1, 1, ipc_main2sender_lock_semaphore_name); - - // rjf: set up ipc-receiver -> main thread ring buffer; launch signaler thread - ipc_s2m_ring_mutex = mutex_alloc(); - ipc_s2m_ring_cv = cond_var_alloc(); - IPCInfo *ipc_info = (IPCInfo *)ipc_sender2main_shared_memory_base; - if(ipc_sender2main_shared_memory_base != 0) - { - MemoryZeroStruct(ipc_info); - thread_launch(ipc_signaler_thread__entry_point, 0); - } - - scratch_end(scratch); - } - //- rjf: set up socket session for ICP SOCK_Session icp_sock_session = sock_session_open((U16)ipc_port, wakeup_hook_ctrl); @@ -780,69 +683,8 @@ entry_point(CmdLine *cmd_line) { for(B32 quit = 0; !quit;) { - //- rjf: consume IPC messages, dispatch UI commands - B32 ipc_command_frame = 0; - { - Temp scratch = scratch_begin(0, 0); - B32 consumed = 0; - String8 msg = {0}; - MutexScope(ipc_s2m_ring_mutex) - { - U64 unconsumed_size = ipc_s2m_ring_write_pos - ipc_s2m_ring_read_pos; - if(unconsumed_size >= sizeof(U64)) - { - consumed = 1; - ipc_command_frame = 1; - ipc_s2m_ring_read_pos += wrapped_read_struct(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_read_pos, &msg.size); - msg.size = Min(msg.size, unconsumed_size); - msg.str = push_array(scratch.arena, U8, msg.size); - ipc_s2m_ring_read_pos += wrapped_read(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_read_pos, msg.str, msg.size); - } - } - if(consumed) - { - cond_var_broadcast(ipc_s2m_ring_cv); - } - if(msg.size != 0) - { - log_infof("ipc_msg: \"%S\"", msg); - String8List cmd_parts_of_msg = str8_split(scratch.arena, msg, (U8 *)";", 1, 0); - RD_WindowState *dst_ws = rd_state->first_window_state; - for(RD_WindowState *ws = dst_ws; ws != &rd_nil_window_state; ws = ws->order_next) - { - if(wm_window_is_focused(ws->os)) - { - dst_ws = ws; - break; - } - } - if(dst_ws != &rd_nil_window_state) - { - dst_ws->window_temporarily_focused_ipc = 1; - RD_RegsScope() - { - if(dst_ws->cfg_id != rd_regs()->window) - { - Temp scratch = scratch_begin(0, 0); - CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, cfg_node_from_id(dst_ws->cfg_id)); - rd_regs()->window = dst_ws->cfg_id; - rd_regs()->panel = panel_tree.focused->cfg->id; - rd_regs()->tab = panel_tree.focused->selected_tab->id; - rd_regs()->view = panel_tree.focused->selected_tab->id; - scratch_end(scratch); - } - for EachNode(n, String8Node, cmd_parts_of_msg.first) - { - rd_cmd(RD_CmdKind_RunExternalDriverTextCommand, .string = n->string); - } - rd_request_frame(); - } - } - } - scratch_end(scratch); - } - //- rjf: receive socket data, run as IPC messages + B32 ipc_command_frame = 0; SOCK_Protocol ipc_protocol = SOCK_Protocol_TCP; SOCK_Endpoint ipc_endpoint = {0}; { @@ -915,26 +757,6 @@ entry_point(CmdLine *cmd_line) rd_cmd(RD_CmdKind_Attach, .pid = jit_pid); } - //- rjf: gather command outputs & write them -#if 0 - if(ipc_command_frame) - { - if(ipc_main2sender_shared_memory_base != 0 && - semaphore_take(ipc_main2sender_lock_semaphore, now_time_us()+5000000)) - { - IPCInfo *ipc_info = (IPCInfo *)ipc_main2sender_shared_memory_base; - U8 *buffer = (U8 *)(ipc_info+1); - U64 buffer_max = IPC_SHARED_MEMORY_BUFFER_SIZE - sizeof(IPCInfo); - StringJoin join = {str8_lit(""), str8_lit("\0"), str8_lit("")}; - String8 msg = str8_list_join(scratch.arena, &rd_state->cmd_outputs, &join); - ipc_info->msg_size = Min(buffer_max, msg.size); - MemoryCopy(buffer, msg.str, ipc_info->msg_size); - semaphore_drop(ipc_main2sender_signal_semaphore); - semaphore_drop(ipc_main2sender_lock_semaphore); - } - } -#endif - //- rjf: gather command outputs & send them back back to sender if(ipc_command_frame) { @@ -965,6 +787,11 @@ entry_point(CmdLine *cmd_line) endpoint.address_u8[0] = 127; endpoint.address_u8[3] = 1; endpoint.port = (U16)ipc_port; + String8 explicit_ipc_addr_arg = cmd_line_string(cmd_line, s("ipc_addr")); + if(explicit_ipc_addr_arg.size != 0) + { + endpoint = sock_endpoint_from_string(explicit_ipc_addr_arg); + } } //- rjf: form message from command line inputs @@ -992,97 +819,6 @@ entry_point(CmdLine *cmd_line) } } -#if 0 - //- rjf: grab explicit PID argument - U32 dst_pid = 0; - if(cmd_line_has_argument(cmd_line, str8_lit("pid"))) - { - String8 dst_pid_string = cmd_line_string(cmd_line, str8_lit("pid")); - U64 dst_pid_u64 = 0; - if(dst_pid_string.size != 0 && - try_u64_from_str8_c_rules(dst_pid_string, &dst_pid_u64)) - { - dst_pid = (U32)dst_pid_u64; - } - } - - //- rjf: no explicit PID? -> find PID to send message to, by looking for other raddbg instances - if(dst_pid == 0) - { - U32 this_pid = get_process_info()->pid; - DMN_ProcessIter it = {0}; - dmn_process_iter_begin(&it); - for(DMN_ProcessInfo info = {0}; dmn_process_iter_next(scratch.arena, &it, &info);) - { - if(str8_match(str8_skip_last_slash(str8_chop_last_dot(cmd_line->exe_name)), str8_skip_last_slash(str8_chop_last_dot(info.name)), StringMatchFlag_CaseInsensitive) && - this_pid != info.pid) - { - dst_pid = info.pid; - break; - } - } - dmn_process_iter_end(&it); - } - - //- rjf: grab destination instance's shared memory resources - String8 ipc_sender2main_shared_memory_name = push_str8f(scratch.arena, "_raddbg_ipc_sender2main_shared_memory_%i_", dst_pid); - String8 ipc_sender2main_signal_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_sender2main_signal_semaphore_%i_", dst_pid); - String8 ipc_sender2main_lock_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_sender2main_lock_semaphore_%i_", dst_pid); - SharedMemory ipc_sender2main_shared_memory = shared_memory_alloc(IPC_SHARED_MEMORY_BUFFER_SIZE, ipc_sender2main_shared_memory_name); - ipc_sender2main_shared_memory_base = (U8 *)shared_memory_view_open(ipc_sender2main_shared_memory, r1u64(0, IPC_SHARED_MEMORY_BUFFER_SIZE)); - ipc_sender2main_signal_semaphore = semaphore_alloc(0, 1, ipc_sender2main_signal_semaphore_name); - ipc_sender2main_lock_semaphore = semaphore_alloc(1, 1, ipc_sender2main_lock_semaphore_name); - String8 ipc_main2sender_shared_memory_name = push_str8f(scratch.arena, "_raddbg_ipc_main2sender_shared_memory_%i_", dst_pid); - String8 ipc_main2sender_signal_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_main2sender_signal_semaphore_%i_", dst_pid); - String8 ipc_main2sender_lock_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_main2sender_lock_semaphore_%i_", dst_pid); - SharedMemory ipc_main2sender_shared_memory = shared_memory_alloc(IPC_SHARED_MEMORY_BUFFER_SIZE, ipc_main2sender_shared_memory_name); - ipc_main2sender_shared_memory_base = (U8 *)shared_memory_view_open(ipc_main2sender_shared_memory, r1u64(0, IPC_SHARED_MEMORY_BUFFER_SIZE)); - ipc_main2sender_signal_semaphore = semaphore_alloc(0, 1, ipc_main2sender_signal_semaphore_name); - ipc_main2sender_lock_semaphore = semaphore_alloc(1, 1, ipc_main2sender_lock_semaphore_name); - - //- rjf: got resources -> write message - B32 wrote_message = 0; - if(dst_pid != 0 && - ipc_sender2main_shared_memory_base != 0 && - semaphore_take(ipc_sender2main_lock_semaphore, max_U64)) - { - wrote_message = 1; - IPCInfo *ipc_info = (IPCInfo *)ipc_sender2main_shared_memory_base; - U8 *buffer = (U8 *)(ipc_info+1); - U64 buffer_max = IPC_SHARED_MEMORY_BUFFER_SIZE - sizeof(IPCInfo); - String8List parts = {0}; - { - for EachIndex(idx, cmd_line->argc-1) - { - str8_list_push(scratch.arena, &parts, str8_cstring(cmd_line->argv[idx+1])); - } - } - StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; - String8 msg = str8_list_join(scratch.arena, &parts, &join); - ipc_info->msg_size = Min(buffer_max, msg.size); - MemoryCopy(buffer, msg.str, ipc_info->msg_size); - semaphore_drop(ipc_sender2main_signal_semaphore); - semaphore_drop(ipc_sender2main_lock_semaphore); - } - - //- rjf: wrote message -> wait for outputs, read outputs - String8List outputs = {0}; - if(wrote_message && - ipc_main2sender_shared_memory_base != 0 && - semaphore_take(ipc_main2sender_signal_semaphore, now_time_us()+10000000)) - { - if(semaphore_take(ipc_main2sender_lock_semaphore, max_U64)) - { - IPCInfo *ipc_info = (IPCInfo *)ipc_main2sender_shared_memory_base; - String8 msg = str8((U8 *)(ipc_info+1), ipc_info->msg_size); - msg.size = Min(msg.size, IPC_SHARED_MEMORY_BUFFER_SIZE - sizeof(IPCInfo)); - U8 split_char = 0; - outputs = str8_split(scratch.arena, msg, &split_char, 1, 0); - semaphore_drop(ipc_main2sender_lock_semaphore); - } - } -#endif - //- rjf: write outputs to stdout for(String8Node *n = outputs.first; n != 0; n = n->next) { From 50b61822a8041764f5f95fa559ddf7b0123db523 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 09:48:45 -0700 Subject: [PATCH 206/251] stub out linux sockets --- src/linux/socket/linux_socket.c | 50 +++++++++++++++++++++++++++++++++ src/linux/socket/linux_socket.h | 7 +++++ 2 files changed, 57 insertions(+) create mode 100644 src/linux/socket/linux_socket.c create mode 100644 src/linux/socket/linux_socket.h diff --git a/src/linux/socket/linux_socket.c b/src/linux/socket/linux_socket.c new file mode 100644 index 00000000..c38e13af --- /dev/null +++ b/src/linux/socket/linux_socket.c @@ -0,0 +1,50 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: @per_os_impl Top-Level Layer Calls + +internal void +sock_init(void) +{ + +} + +internal void +sock_async_tick(void) +{ + +} + +//////////////////////////////// +//~ rjf: @per_os_impl Session Creation/Closing + +internal SOCK_Session +sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) +{ + +} + +internal void +sock_session_close(SOCK_Session session) +{ + +} + +//////////////////////////////// +//~ rjf: @per_os_impl Sends + +internal B32 +sock_send(SOCK_Session session, SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us) +{ + +} + +//////////////////////////////// +//~ rjf: @per_os_impl Receives + +internal B32 +sock_recv(Arena *arena, SOCK_Session session, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us) +{ + +} diff --git a/src/linux/socket/linux_socket.h b/src/linux/socket/linux_socket.h new file mode 100644 index 00000000..de932a2c --- /dev/null +++ b/src/linux/socket/linux_socket.h @@ -0,0 +1,7 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef LINUX_SOCKET_H +#define LINUX_SOCKET_H + +#endif // LINUX_SOCKET_H From b5c02dd4fa3116f1ed2ee4c88db5c43f889f3a77 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 11:29:40 -0700 Subject: [PATCH 207/251] linux_socket: checkpoint, get accepting/receiving working --- project.4coder | 4 +- src/linux/socket/linux_socket.c | 210 +++++++++++++++++++++++++++++++- src/linux/socket/linux_socket.h | 57 +++++++++ src/socket/socket.h | 2 - src/socket/socket_inc.c | 2 + src/socket/socket_inc.h | 2 + src/win32/socket/win32_socket.c | 12 +- 7 files changed, 272 insertions(+), 17 deletions(-) diff --git a/project.4coder b/project.4coder index 5de5073c..fe1beb78 100644 --- a/project.4coder +++ b/project.4coder @@ -49,10 +49,10 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] - // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [scratch] // .f2 = { .win = "pushd build && for /l %i in (1, 1, 3) do (radbin --rdi raddbg.exe --out:raddbg_%i.rdi && radbin --dump raddbg_%i.rdi --out:raddbg_%i.dump)", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/linux/socket/linux_socket.c b/src/linux/socket/linux_socket.c index c38e13af..53477298 100644 --- a/src/linux/socket/linux_socket.c +++ b/src/linux/socket/linux_socket.c @@ -1,13 +1,135 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +//////////////////////////////// +//~ rjf: Listener Thread + +internal void +lnx_sock_listener_thread_entry_point(void *p) +{ + ThreadNameF("lnx_sock_listener_thread_%I64x", p); + LNX_SOCK_Session *session = (LNX_SOCK_Session *)p; + for(;;) + { + //- rjf: wait for next event + struct epoll_event evts[1] = {0}; + int wait_result = LNX_RETRY_ON_EINTR(epoll_wait(session->epoll_fd, evts, ArrayCount(evts), -1)); + + //- rjf: code is 0 -> listener is ready for accept + if(evts[0].data.u64 == 0) + { + // rjf: accept new connection + struct sockaddr_storage addr = {0}; + socklen_t addr_size = sizeof(addr); + int new_socket = LNX_RETRY_ON_EINTR(accept(session->tcp_listen_socket, (struct sockaddr *)&addr, &addr_size)); + + // rjf: unpack socket's endpoint info + SOCK_Endpoint endpoint = {0}; + { + switch(addr.ss_family) + { + default:{}break; + case AF_INET: + { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)&addr; + endpoint.port = ntohs(ipv4->sin_port); + endpoint.address_u32[0] = ipv4->sin_addr.s_addr; + }break; + case AF_INET6: + { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&addr; + endpoint.port = ntohs(ipv6->sin6_port); + MemoryCopy(endpoint.address_u8, ipv6->sin6_addr.s6_addr, sizeof(endpoint.address_u8)); + }break; + } + } + + // rjf: unpack endpoint + U64 hash = u64_hash_from_str8(str8_struct(&endpoint)); + U64 slot_idx = hash%session->connection_slots_count; + LNX_SOCK_ConnectionSlot *slot = &session->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&session->connection_stripes, slot_idx); + + // rjf: store new connection + U64 con_ptr_u64 = 0; + RWMutexScope(stripe->rw_mutex, 1) + { + LNX_SOCK_Connection *con = (LNX_SOCK_Connection *)stripe->free; + if(con != 0) + { + stripe->free = con->next; + } + else + { + con = push_array(stripe->arena, LNX_SOCK_Connection, 1); + } + con->endpoint = endpoint; + con->protocol = SOCK_Protocol_TCP; + con->socket = new_socket; + DLLPushBack(slot->first, slot->last, con); + con_ptr_u64 = (U64)con; + } + + // rjf: add socket to epoll + struct epoll_event evt = {0}; + evt.events = EPOLLIN; + evt.data.u64 = con_ptr_u64; + LNX_RETRY_ON_EINTR(epoll_ctl(session->epoll_fd, EPOLL_CTL_ADD, new_socket, &evt)); + } + + //- rjf: code is nonzero -> ready to read on a connection. + else + { + // rjf: unpack associated connection + LNX_SOCK_Connection *con = (LNX_SOCK_Connection *)evts[0].data.u64; + SOCK_Endpoint endpoint = con->endpoint; + U64 hash = u64_hash_from_str8(str8_struct(&endpoint)); + U64 slot_idx = hash%session->connection_slots_count; + LNX_SOCK_ConnectionSlot *slot = &session->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&session->connection_stripes, slot_idx); + + // rjf: do read + U8 buffer[4096] = {0}; + ssize_t recv_result = LNX_RETRY_ON_EINTR(recv(con->socket, buffer, sizeof(buffer), MSG_DONTWAIT)); + + // rjf: bytes received -> push result to user. + if(recv_result >= 0) + { + RingGuard g = guarded_ring_open(session->s2u_ring); + U64 header[5] = + { + (U64)SOCK_Protocol_TCP, + (U64)endpoint.port, + endpoint.address_u64[0], + endpoint.address_u64[1], + (U64)recv_result, + }; + guarded_ring_write_or_wait(&g, sizeof(header), header, max_U64); + guarded_ring_write_or_wait(&g, recv_result, buffer, max_U64); + guarded_ring_close(&g); + } + + // rjf: error -> socket was disconnected + else RWMutexScope(stripe->rw_mutex, 1) + { + close(con->socket); + DLLRemove(slot->first, slot->last, con); + con->next = stripe->free; + stripe->free = con; + } + } + } +} + //////////////////////////////// //~ rjf: @per_os_impl Top-Level Layer Calls internal void sock_init(void) { - + Arena *arena = arena_alloc(); + lnx_sock_state = push_array(arena, LNX_SOCK_State, 1); + lnx_sock_state->arena = arena; } internal void @@ -22,7 +144,41 @@ sock_async_tick(void) internal SOCK_Session sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) { + //- rjf: set up state + Arena *arena = arena_alloc(); + LNX_SOCK_Session *session = push_array(arena, LNX_SOCK_Session, 1); + session->arena = arena; + session->u2s_ring = guarded_ring_alloc(arena, KB(256)); + session->s2u_ring = guarded_ring_alloc(arena, KB(256)); + session->epoll_fd = epoll_create(1); + //- rjf: set up listener + { + session->tcp_listen_socket = socket(AF_INET, SOCK_STREAM, 0); + U32 ipv6only = 0; + setsockopt(session->tcp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); + struct sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(listener_port); + bind(session->tcp_listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)); + listen(session->tcp_listen_socket, SOMAXCONN); + struct epoll_event evt = {0}; + evt.events = EPOLLIN; + epoll_ctl(session->epoll_fd, EPOLL_CTL_ADD, session->tcp_listen_socket, &evt); + } + + //- rjf: set up connection cache + session->connection_slots_count = 8; + session->connection_stripes = stripe_array_alloc(arena); + session->connection_slots = push_array(arena, LNX_SOCK_ConnectionSlot, session->connection_slots_count); + + //- rjf: launch listener thread + session->listener_thread = thread_launch(lnx_sock_listener_thread_entry_point, session); + + //- rjf: bundle as handle + SOCK_Session s = {(U64)session}; + return s; } internal void @@ -37,7 +193,33 @@ sock_session_close(SOCK_Session session) internal B32 sock_send(SOCK_Session session, SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us) { - + B32 result = 0; + LNX_SOCK_Session *s = (LNX_SOCK_Session *)session.u64[0]; + RingGuard guard = guarded_ring_open(s->u2s_ring); + { + U64 header[5] = {0}; + U64 size_cap = s->u2s_ring->ring->size - sizeof(header); + U64 size = Min(data.size, size_cap); + { + header[0] = (U64)protocol; + header[1] = (U64)endpoint.port; + header[2] = endpoint.address_u64[0]; + header[3] = endpoint.address_u64[1]; + header[4] = size; + } + if(guarded_ring_write_or_wait(&guard, sizeof(header), header, endt_us)) + { + guarded_ring_write_or_wait(&guard, size, data.str, max_U64); + result = 1; + } + } + guarded_ring_close(&guard); + if(result) + { + ins_atomic_u32_eval_assign(&async_loop_again, 1); + cond_var_broadcast(async_tick_start_cond_var); + } + return result; } //////////////////////////////// @@ -46,5 +228,27 @@ sock_send(SOCK_Session session, SOCK_Protocol protocol, SOCK_Endpoint endpoint, internal B32 sock_recv(Arena *arena, SOCK_Session session, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us) { - + B32 result = 0; + U64 header_size = sizeof(*protocol_out) + sizeof(*endpoint_out) + sizeof(U64); + { + LNX_SOCK_Session *s = (LNX_SOCK_Session *)session.u64[0]; + RingGuard guard = guarded_ring_open(s->s2u_ring); + { + U64 header[5] = {0}; + if(guarded_ring_read_or_wait(&guard, sizeof(header), header, endt_us)) + { + U64 size_cap = s->s2u_ring->ring->size - sizeof(header); + protocol_out[0] = (SOCK_Protocol)header[0]; + endpoint_out->port = (U16)header[1]; + endpoint_out->address_u64[0] = header[2]; + endpoint_out->address_u64[1] = header[3]; + data_out->size = Min(size_cap, header[4]); + data_out->str = push_array(arena, U8, data_out->size); + guarded_ring_read_or_wait(&guard, data_out->size, data_out->str, max_U64); + result = 1; + } + } + guarded_ring_close(&guard); + } + return result; } diff --git a/src/linux/socket/linux_socket.h b/src/linux/socket/linux_socket.h index de932a2c..877101f1 100644 --- a/src/linux/socket/linux_socket.h +++ b/src/linux/socket/linux_socket.h @@ -4,4 +4,61 @@ #ifndef LINUX_SOCKET_H #define LINUX_SOCKET_H +//////////////////////////////// +//~ rjf: Includes + +#include +#include +#include + +//////////////////////////////// +//~ rjf: Implementation Types + +typedef struct LNX_SOCK_Connection LNX_SOCK_Connection; +struct LNX_SOCK_Connection +{ + LNX_SOCK_Connection *next; + LNX_SOCK_Connection *prev; + SOCK_Endpoint endpoint; + SOCK_Protocol protocol; + int socket; +}; + +typedef struct LNX_SOCK_ConnectionSlot LNX_SOCK_ConnectionSlot; +struct LNX_SOCK_ConnectionSlot +{ + LNX_SOCK_Connection *first; + LNX_SOCK_Connection *last; +}; + +typedef struct LNX_SOCK_Session LNX_SOCK_Session; +struct LNX_SOCK_Session +{ + Arena *arena; + GuardedRing *u2s_ring; + GuardedRing *s2u_ring; + int epoll_fd; + int tcp_listen_socket; + U64 connection_slots_count; + StripeArray connection_stripes; + LNX_SOCK_ConnectionSlot *connection_slots; + Thread listener_thread; +}; + +typedef struct LNX_SOCK_State LNX_SOCK_State; +struct LNX_SOCK_State +{ + Arena *arena; +}; + +//////////////////////////////// +//~ rjf: Globals + +global LNX_SOCK_State *lnx_sock_state = 0; + +//////////////////////////////// +//~ rjf: Listener Thread + +internal void lnx_sock_listener_thread_entry_point(void *p); + #endif // LINUX_SOCKET_H diff --git a/src/socket/socket.h b/src/socket/socket.h index 76330d78..f0307022 100644 --- a/src/socket/socket.h +++ b/src/socket/socket.h @@ -79,12 +79,10 @@ internal void sock_session_close(SOCK_Session session); //~ rjf: @per_os_impl Sends internal B32 sock_send(SOCK_Session session, SOCK_Protocol protocol, SOCK_Endpoint endpoint, String8 data, U64 endt_us); -#define sock_send_struct(session, protocol_in, endpoint_in, ptr, endt_us) sock_send((session), (protocol_in), (endpoint_in), str8_struct(ptr), (endt_us)) //////////////////////////////// //~ rjf: @per_os_impl Receives internal B32 sock_recv(Arena *arena, SOCK_Session session, SOCK_Protocol *protocol_out, SOCK_Endpoint *endpoint_out, String8 *data_out, U64 endt_us); -#define sock_recv_struct(arena, session, protocol_out, endpoint_out, data_out, endt_us) sock_recv((arena), (session), (protocol_out), (endpoint_out), (data_out), (endt_us)) #endif // SOCKET_H diff --git a/src/socket/socket_inc.c b/src/socket/socket_inc.c index 28e10284..e2fb9bac 100644 --- a/src/socket/socket_inc.c +++ b/src/socket/socket_inc.c @@ -4,6 +4,8 @@ #include "socket.c" #if OS_WINDOWS # include "win32/socket/win32_socket.c" +#elif OS_LINUX +# include "linux/socket/linux_socket.c" #else # include "socket_stub.c" #endif diff --git a/src/socket/socket_inc.h b/src/socket/socket_inc.h index 56928a79..359877cf 100644 --- a/src/socket/socket_inc.h +++ b/src/socket/socket_inc.h @@ -7,6 +7,8 @@ #include "socket.h" #if OS_WINDOWS # include "win32/socket/win32_socket.h" +#elif OS_LINUX +# include "linux/socket/linux_socket.h" #else # include "socket_stub.h" #endif diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index ecf69a04..a467be72 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -372,24 +372,16 @@ sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) //- rjf: set up IOCP session->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); - //- rjf: create listener(s) - session->tcp_listen_socket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); + //- rjf: set up listener(s) { + session->tcp_listen_socket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); DWORD ipv6only = 0; setsockopt(session->tcp_listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, sizeof(ipv6only)); - } - - //- rjf: bind listener sockets - { struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(listener_port); bind(session->tcp_listen_socket, (SOCKADDR *)&server_addr, sizeof(server_addr)); - } - - //- rjf: start listening for TCP connections - { listen(session->tcp_listen_socket, SOMAXCONN); } From a2988333a53de80546142d61bae9e1f6f8c1fd29 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 11:42:03 -0700 Subject: [PATCH 208/251] linux_sock: meet windows in functionality - send/recv on both client/server --- src/linux/socket/linux_socket.c | 194 +++++++++++++++++++++++++++++++- src/linux/socket/linux_socket.h | 5 + src/win32/socket/win32_socket.c | 10 +- 3 files changed, 200 insertions(+), 9 deletions(-) diff --git a/src/linux/socket/linux_socket.c b/src/linux/socket/linux_socket.c index 53477298..ab976978 100644 --- a/src/linux/socket/linux_socket.c +++ b/src/linux/socket/linux_socket.c @@ -113,6 +113,7 @@ lnx_sock_listener_thread_entry_point(void *p) else RWMutexScope(stripe->rw_mutex, 1) { close(con->socket); + LNX_RETRY_ON_EINTR(epoll_ctl(session->epoll_fd, EPOLL_CTL_DEL, con->socket, 0)); DLLRemove(slot->first, slot->last, con); con->next = stripe->free; stripe->free = con; @@ -130,12 +131,197 @@ sock_init(void) Arena *arena = arena_alloc(); lnx_sock_state = push_array(arena, LNX_SOCK_State, 1); lnx_sock_state->arena = arena; + lnx_sock_state->session_rw_mutex = rw_mutex_alloc(); } internal void sock_async_tick(void) { + Temp scratch = scratch_begin(0, 0); + ////////////////////////////// + //- rjf: gather send tasks + // + typedef struct SendTask SendTask; + struct SendTask + { + SendTask *next; + LNX_SOCK_Session *session; + SOCK_Endpoint endpoint; + String8 data; + }; + SendTask *first_tcp_send = 0; + SendTask *last_tcp_send = 0; + if(lane_idx() == 0) + { + for(;;) + { + B32 got_more = 0; + RWMutexScope(lnx_sock_state->session_rw_mutex, 0) + { + for EachNode(s, LNX_SOCK_Session, lnx_sock_state->first_session) + { + RingGuard g = guarded_ring_open(s->u2s_ring); + { + U64 header[5] = {0}; + if(guarded_ring_try_read(&g, sizeof(header), header)) + { + got_more = 1; + SOCK_Protocol protocol = (SOCK_Protocol)header[0]; + U16 port = (U16)header[1]; + SOCK_Endpoint endpoint = {0}; + endpoint.address_u64[0] = header[2]; + endpoint.address_u64[1] = header[3]; + endpoint.port = port; + U64 data_size = header[4]; + U8 *data = push_array(scratch.arena, U8, data_size); + guarded_ring_read_or_wait(&g, data_size, data, max_U64); + SendTask *t = push_array(scratch.arena, SendTask, 1); + t->session = s; + t->endpoint = endpoint; + t->data = str8(data, data_size); + SLLQueuePush(first_tcp_send, last_tcp_send, t); + } + } + guarded_ring_close(&g); + } + } + if(!got_more) + { + break; + } + } + } + lane_sync(); + + ////////////////////////////// + //- rjf: do TCP sends + // + for(SendTask *t = first_tcp_send; t != 0; t = t->next) + { + LNX_SOCK_Session *session = t->session; + + // rjf: unpack endpoint + U64 hash = u64_hash_from_str8(str8_struct(&t->endpoint)); + U64 slot_idx = hash%session->connection_slots_count; + LNX_SOCK_ConnectionSlot *slot = &session->connection_slots[slot_idx]; + Stripe *stripe = stripe_from_slot_idx(&session->connection_stripes, slot_idx); + + // rjf: get existing socket for this endpoint + int ep_socket = -1; + RWMutexScope(stripe->rw_mutex, 0) + { + for(LNX_SOCK_Connection *c = slot->first; c != 0; c = c->next) + { + if(MemoryMatchStruct(&c->endpoint, &t->endpoint)) + { + ep_socket = c->socket; + break; + } + } + } + + // rjf: didn't get a socket? -> open socket + if(ep_socket == -1) RWMutexScope(stripe->rw_mutex, 1) + { + // rjf: try to get socket again, now that we have the write lock + LNX_SOCK_Connection *con = 0; + for(LNX_SOCK_Connection *c = slot->first; c != 0; c = c->next) + { + if(MemoryMatchStruct(&c->endpoint, &t->endpoint)) + { + con = c; + break; + } + } + + // rjf: no socket still? -> create + if(con == 0) + { + // rjf: convert endpoint -> sockaddr + struct sockaddr_storage endpoint_sockaddr = {0}; + int endpoint_sockaddr_size = 0; + { + switch(t->endpoint.kind) + { + default:{}break; + case SOCK_EndpointKind_IPv4: + { + struct sockaddr_in *dst = (struct sockaddr_in *)(&endpoint_sockaddr); + endpoint_sockaddr_size = sizeof(*dst); + dst->sin_family = AF_INET; + dst->sin_port = htons(t->endpoint.port); + MemoryCopy(&dst->sin_addr, &t->endpoint.address_u32[0], sizeof(U32)); + }break; + case SOCK_EndpointKind_IPv6: + { + struct sockaddr_in6 *dst = (struct sockaddr_in6 *)(&endpoint_sockaddr); + endpoint_sockaddr_size = sizeof(*dst); + dst->sin6_family = AF_INET6; + dst->sin6_port = htons(t->endpoint.port); + MemoryCopy(&dst->sin6_addr, &t->endpoint.address_u128[0], sizeof(U128)); + }break; + } + } + + // rjf: create + int new_socket = socket(AF_INET, SOCK_STREAM, 0); + connect(new_socket, (struct sockaddr *)&endpoint_sockaddr, endpoint_sockaddr_size); + + // rjf: store in cache + con = (LNX_SOCK_Connection *)stripe->free; + if(con != 0) + { + stripe->free = con->next; + } + else + { + con = push_array(stripe->arena, LNX_SOCK_Connection, 1); + } + con->endpoint = t->endpoint; + con->protocol = SOCK_Protocol_TCP; + con->socket = new_socket; + DLLPushBack(slot->first, slot->last, con); + + // rjf: hook up to session epoll + struct epoll_event evt = {0}; + evt.events = EPOLLIN; + evt.data.u64 = (U64)con; + LNX_RETRY_ON_EINTR(epoll_ctl(session->epoll_fd, EPOLL_CTL_ADD, new_socket, &evt)); + } + + // rjf: get socket from cache + ep_socket = con->socket; + } + + // rjf: got socket? -> send + B32 send_failed = 0; + if(ep_socket != -1 && send(ep_socket, t->data.str, t->data.size, 0) == -1) + { + send_failed = 1; + } + + // rjf: got a socket, but send failed? -> connection closed + if(ep_socket != -1 && send_failed) + { + RWMutexScope(stripe->rw_mutex, 1) + { + for(LNX_SOCK_Connection *c = slot->first; c != 0; c = c->next) + { + if(MemoryMatchStruct(&c->endpoint, &t->endpoint)) + { + close(c->socket); + LNX_RETRY_ON_EINTR(epoll_ctl(session->epoll_fd, EPOLL_CTL_DEL, c->socket, 0)); + DLLRemove(slot->first, slot->last, c); + c->next = stripe->free; + stripe->free = c; + } + } + } + } + } + + scratch_end(scratch); } //////////////////////////////// @@ -176,6 +362,12 @@ sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) //- rjf: launch listener thread session->listener_thread = thread_launch(lnx_sock_listener_thread_entry_point, session); + //- rjf: link into top-level storage + RWMutexScope(lnx_sock_state->session_rw_mutex, 1) + { + DLLPushBack(lnx_sock_state->first_session, lnx_sock_state->last_session, session); + } + //- rjf: bundle as handle SOCK_Session s = {(U64)session}; return s; @@ -184,7 +376,7 @@ sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) internal void sock_session_close(SOCK_Session session) { - + // TODO(rjf) } //////////////////////////////// diff --git a/src/linux/socket/linux_socket.h b/src/linux/socket/linux_socket.h index 877101f1..17c4396c 100644 --- a/src/linux/socket/linux_socket.h +++ b/src/linux/socket/linux_socket.h @@ -34,6 +34,8 @@ struct LNX_SOCK_ConnectionSlot typedef struct LNX_SOCK_Session LNX_SOCK_Session; struct LNX_SOCK_Session { + LNX_SOCK_Session *next; + LNX_SOCK_Session *prev; Arena *arena; GuardedRing *u2s_ring; GuardedRing *s2u_ring; @@ -49,6 +51,9 @@ typedef struct LNX_SOCK_State LNX_SOCK_State; struct LNX_SOCK_State { Arena *arena; + RWMutex session_rw_mutex; + LNX_SOCK_Session *first_session; + LNX_SOCK_Session *last_session; }; //////////////////////////////// diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index a467be72..a141a3f8 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -202,12 +202,7 @@ sock_async_tick(void) t->session = s; t->endpoint = endpoint; t->data = str8(data, data_size); - switch(protocol) - { - default:{}break; - case SOCK_Protocol_TCP:{SLLQueuePush(first_tcp_send, last_tcp_send, t);}break; - case SOCK_Protocol_UDP:{SLLQueuePush(first_tcp_send, last_tcp_send, t);}break; - } + SLLQueuePush(first_tcp_send, last_tcp_send, t); } } guarded_ring_close(&g); @@ -435,8 +430,7 @@ sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) internal void sock_session_close(SOCK_Session session) { - W32_SOCK_Session *s = (W32_SOCK_Session *)session.u64[0]; - arena_release(s->arena); + // TODO(rjf) } //////////////////////////////// From 4195ce4bba106f038ff9150f391bfd80980a9476 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 12:28:01 -0700 Subject: [PATCH 209/251] raddbg: bitmap cache on ui thread - no win from async. incompatible with gl. --- project.4coder | 4 +- src/raddbg/raddbg_core.c | 32 +++++++++++-- src/raddbg/raddbg_core.h | 30 ++++++++++++ src/raddbg/raddbg_main.c | 9 ++-- src/raddbg/raddbg_views.c | 98 ++++++++++++++------------------------- 5 files changed, 99 insertions(+), 74 deletions(-) diff --git a/project.4coder b/project.4coder index fe1beb78..5de5073c 100644 --- a/project.4coder +++ b/project.4coder @@ -49,10 +49,10 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] - .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [scratch] // .f2 = { .win = "pushd build && for /l %i in (1, 1, 3) do (radbin --rdi raddbg.exe --out:raddbg_%i.rdi && radbin --dump raddbg_%i.rdi --out:raddbg_%i.dump)", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 6c8fd3c0..70a0dfb5 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -11508,7 +11508,7 @@ rd_init(CmdLine *cmdln) rd_cmd(RD_CmdKind_OpenCrashDump, .file_path = implicit_dmp_arg); } - // rjf: set up user / project paths + //- rjf: set up user / project paths { Temp scratch2 = scratch_begin(&scratch.arena, 1); @@ -11563,7 +11563,7 @@ rd_init(CmdLine *cmdln) scratch_end(scratch2); } - // rjf: unpack icon image data + //- rjf: unpack icon image data { Temp scratch = scratch_begin(0, 0); String8 data = rd_icon_file_bytes; @@ -11658,7 +11658,13 @@ rd_init(CmdLine *cmdln) scratch_end(scratch); } - // rjf: check initial installation status + //- rjf: set up bitmap cache + { + rd_state->bitmap_cache_slots_count = 64; + rd_state->bitmap_cache_slots = push_array(arena, RD_BitmapCacheSlot, rd_state->bitmap_cache_slots_count); + } + + //- rjf: check initial installation status rd_state->installed = sh_install_or_uninstall_self(0, 0); ProfEnd(); @@ -18585,6 +18591,26 @@ rd_frame(void) } } + ////////////////////////////// + //- rjf: garbage collect untouched bitmaps + // + { + for EachIndex(slot_idx, rd_state->bitmap_cache_slots_count) + { + RD_BitmapCacheSlot *slot = &rd_state->bitmap_cache_slots[slot_idx]; + for(RD_BitmapCacheNode *n = rd_state->bitmap_cache_slots[slot_idx].first, *next = 0; n != 0; n = next) + { + next = n->next; + if(n->last_touched_frame_idx + 100 < rd_state->frame_index) + { + r_tex2d_release(n->texture); + DLLRemove(slot->first, slot->last, n); + SLLStackPush(rd_state->bitmap_cache_free_node, n); + } + } + } + } + ////////////////////////////// //- rjf: simulate lag // diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index 5ae1b37a..7015ffd5 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -360,6 +360,31 @@ struct RD_WindowStateSlot //////////////////////////////// //~ rjf: Main Per-Process Graphical State +typedef struct RD_BitmapTopology RD_BitmapTopology; +struct RD_BitmapTopology +{ + Vec2S16 dim; + R_Tex2DFormat fmt; +}; + +typedef struct RD_BitmapCacheNode RD_BitmapCacheNode; +struct RD_BitmapCacheNode +{ + RD_BitmapCacheNode *next; + RD_BitmapCacheNode *prev; + U64 last_touched_frame_idx; + U128 hash; + RD_BitmapTopology top; + R_Handle texture; +}; + +typedef struct RD_BitmapCacheSlot RD_BitmapCacheSlot; +struct RD_BitmapCacheSlot +{ + RD_BitmapCacheNode *first; + RD_BitmapCacheNode *last; +}; + typedef struct RD_LoadedDbgInfoNode RD_LoadedDbgInfoNode; struct RD_LoadedDbgInfoNode { @@ -530,6 +555,11 @@ struct RD_State // rjf: icon texture R_Handle icon_texture; + // rjf: bitmap cache + RD_BitmapCacheSlot *bitmap_cache_slots; + U64 bitmap_cache_slots_count; + RD_BitmapCacheNode *bitmap_cache_free_node; + // rjf: fixed ui keys UI_Key drop_completion_key; UI_Key ctx_menu_key; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 969a225f..86802f7b 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -4,17 +4,14 @@ //////////////////////////////// //~ rjf: linux TODO notes // -// initial alpha: -// [ ] recycling semaphore files - or alt strat? -// [ ] rendering -// [ ] bitmaps / geometry -// // post initial alpha: // [ ] windowing // [ ] wayland // [ ] support // [ ] .eh_frame unwinding without .eh_frame_hdr +// [ ] geometry rendering // +// [x] recycling semaphore files - or alt strat? // [x] clipboard // [x] jai exes - busted call stacks / unwind info / paths? // [x] statically linked EXEs - busted unwinding @@ -26,6 +23,8 @@ // [x] control // [x] stale memory - things evaluating to stale values that are definitely not zero // [x] thread names +// [x] rendering +// [x] bitmaps //////////////////////////////// //~ rjf: evaluation system pass TODO notes diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 77fe5910..fd790fe2 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -4789,13 +4789,6 @@ EV_EXPAND_RULE_INFO_FUNCTION_DEF(graph) //////////////////////////////// //~ rjf: bitmap @view_hook_impl -typedef struct RD_BitmapTopology RD_BitmapTopology; -struct RD_BitmapTopology -{ - Vec2S16 dim; - R_Tex2DFormat fmt; -}; - typedef struct RD_BitmapBoxDrawData RD_BitmapBoxDrawData; struct RD_BitmapBoxDrawData { @@ -4814,46 +4807,6 @@ struct RD_BitmapCanvasBoxDrawData F32 zoom; }; -internal AC_Artifact -rd_bitmap_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 *gen_out) -{ - Access *access = access_open(); - - //- rjf: unpack key - U128 hash = {0}; - RD_BitmapTopology top = {0}; - { - U64 key_read_off = 0; - key_read_off += str8_deserial_read_struct(key, key_read_off, &hash); - key_read_off += str8_deserial_read_struct(key, key_read_off, &top); - } - String8 data = c_data_from_hash(access, hash); - - //- rjf: create texture - R_Handle texture = {0}; - if(top.dim.x > 0 && top.dim.y > 0 && - data.size >= (U64)top.dim.x*(U64)top.dim.y*(U64)r_tex2d_format_bytes_per_pixel_table[top.fmt]) - { - texture = r_tex2d_alloc(R_ResourceKind_Static, v2s32(top.dim.x, top.dim.y), top.fmt, data.str); - } - - //- rjf: bundle as artifact - AC_Artifact artifact = {0}; - StaticAssert(sizeof(artifact) >= sizeof(texture), tex_artifact_size_check); - MemoryCopy(&artifact, &texture, Min(sizeof(texture), sizeof(artifact))); - - access_close(access); - return artifact; -} - -internal void -rd_bitmap_artifact_destroy(AC_Artifact artifact) -{ - R_Handle texture = {0}; - MemoryCopy(&texture, &artifact, Min(sizeof(texture), sizeof(artifact))); - r_tex2d_release(texture); -} - internal Vec2F32 rd_bitmap_screen_from_canvas_pos(Vec2F32 view_center_pos, F32 zoom, Rng2F32 rect, Vec2F32 cvs) { @@ -4993,29 +4946,46 @@ RD_VIEW_UI_FUNCTION_DEF(bitmap) RD_BitmapTopology topology = {v2s16(dim.x, dim.y), fmt}; U128 data_hash = {0}; R_Handle texture = {0}; - for EachIndex(rewind_idx, C_KEY_HASH_HISTORY_COUNT) + String8 data = {0}; { - U128 hash = c_hash_from_key(texture_key, rewind_idx); -#pragma pack(push, 1) - struct + data_hash = c_hash_from_key(texture_key, 0); + data = c_data_from_hash(access, data_hash); + U64 slot_idx = data_hash.u64[1]%rd_state->bitmap_cache_slots_count; + RD_BitmapCacheSlot *slot = &rd_state->bitmap_cache_slots[slot_idx]; + B32 found = 0; + for(RD_BitmapCacheNode *n = slot->first; n != 0; n = n->next) { - U128 hash; - RD_BitmapTopology top; + if(u128_match(data_hash, n->hash) && MemoryMatchStruct(&topology, &n->top)) + { + texture = n->texture; + found = 1; + n->last_touched_frame_idx = rd_state->frame_index; + break; + } } - key_data = {hash, topology}; -#pragma pack(pop) - String8 key = str8_struct(&key_data); - AC_Artifact artifact = ac_artifact_from_key(access, key, rd_bitmap_artifact_create, rd_bitmap_artifact_destroy, 0); - R_Handle texture_candidate = {0}; - MemoryCopy(&texture_candidate, &artifact, Min(sizeof(texture_candidate), sizeof(artifact))); - if(!r_handle_match(texture_candidate, r_handle_zero())) + if(!found) { - data_hash = hash; - texture = texture_candidate; - break; + RD_BitmapCacheNode *node = rd_state->bitmap_cache_free_node; + if(node) + { + SLLStackPop(rd_state->bitmap_cache_free_node); + } + else + { + node = push_array(rd_state->arena, RD_BitmapCacheNode, 1); + } + DLLPushBack(slot->first, slot->last, node); + node->hash = data_hash; + node->top = topology; + node->last_touched_frame_idx = rd_state->frame_index; + MemoryZeroStruct(&node->texture); + if(topology.dim.x > 0 && topology.dim.y > 0 && data.size >= (U64)topology.dim.x*(U64)topology.dim.y*(U64)r_tex2d_format_bytes_per_pixel_table[topology.fmt]) + { + node->texture = r_tex2d_alloc(R_ResourceKind_Static, v2s32(dim.x, dim.y), topology.fmt, data.str); + } + texture = node->texture; } } - String8 data = c_data_from_hash(access, data_hash); ////////////////////////////// //- rjf: equip loading info From 35423aa22293d8a328b0db04ac6f4b42ecfb8fbf Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 12:57:34 -0700 Subject: [PATCH 210/251] readme - linux x64 build setup instructions --- README.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 142 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 37093768..2966f638 100644 --- a/README.md +++ b/README.md @@ -83,20 +83,19 @@ A benchmark of the linker's performance is below: --- -# Project Development Setup Instructions +# Project Development Setup / Local Build Instructions -**NOTE: Currently, only x64 Windows development is supported for the project.** +## Windows x64 -## 1. Installing the Required Tools (MSVC & Windows SDK) +### 1. Installing the Required Tools (MSVC & Windows SDK) -In order to work with the codebase, you'll need the [Microsoft C/C++ Build Tools -v15 (2017) or later](https://aka.ms/vs/17/release/vs_BuildTools.exe), for both -the Windows SDK and the MSVC compiler and linker. +First, you'll need the [Microsoft C/C++ Build Tools v15 (2017) or later](https://aka.ms/vs/17/release/vs_BuildTools.exe), +for the Windows SDK, and the MSVC compiler and linker. If the Windows SDK is installed (e.g. via installation of the Microsoft C/C++ Build Tools), you may also build with [Clang](https://releases.llvm.org/). -## 2. Build Environment Setup +### 2. Build Environment Setup Building the codebase can be done in a terminal which is equipped with the ability to call either MSVC or Clang from command line. @@ -169,6 +168,142 @@ build radlink release build radbin release ``` +## Linux x64 + +### 1. Installing the Required Tools (GCC or Clang, Libraries) + +First, you'll need either GCC or Clang, if you don't already have them. They +can be obtained by running one of the following commands, depending on your +toolchain of choice and distribution: + +#### GCC on Ubuntu / Debian / Mint +``` +sudo apt update && sudo apt install build-essential +``` + +#### Clang on Ubuntu / Debian / Mint +``` +sudo apt update && sudo apt install clang llvm +``` + +#### GCC on Arch / Manjaro +``` +sudo pacman -S base-devel +``` + +#### Clang on Arch / Manjaro +``` +sudo pacman -S clang llvm +``` + +If you've installed the Clang and LLVM tooling required, you can run: + +``` +clang --version && llvm-ar --version +``` + +You should see output similar to the following: + +``` +Ubuntu clang version 18.1.3 (1ubuntu1) +Target: x86_64-pc-linux-gnu +Thread model: posix +InstalledDir: /usr/bin +Ubuntu LLVM version 18.1.3 + Optimized build. +``` + +If you've installed the GCC tooling required, you can run: + +``` +gcc --version && gcc-ar --version +``` + +You should see output similar to the following: + +``` +gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 +Copyright (C) 2023 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +GNU ar (GNU Binutils for Ubuntu) 2.42 +Copyright (C) 2024 Free Software Foundation, Inc. +This program is free software; you may redistribute it under the terms of +the GNU General Public License version 3 or (at your option) any later version. +This program has absolutely no warranty. +``` + +### 2. Installing Dependencies + +The debugger project relies on a few dynamically linked libraries being present +on the system: + +- `libfreetype` +- `libx11` +- `libxext` +- `libxfixes` +- `libgl` +- `libegl` + +You'll need the development packages for them in order to build. These can be +installed using one of the following commands, depending on your distribution: + +#### Ubuntu / Debian / Mint + +``` +sudo apt install -y libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libgl-dev libegl-dev +``` + +#### Arch / Manjaro + +``` +sudo pacman -S --needed freetype2 libx11 libxext libxfixes libglvnd +``` + +### 3. Building + +To build, `cd` to the root directory of the codebase, and run the `build.sh` +script: + +``` +./build.sh +``` + +You should see something similar to the following output: + +``` +[clang compile] +[debug mode] +[building metagen] +searching /mnt/c/devel/raddebugger/src... 562 files found +parsing metadesk... 23 metadesk files parsed +gathering tables... 138 tables found +generating layer code... +``` + +If everything worked correctly, there will be a `build` folder in the root +level of the codebase, and it will contain a freshly-built `raddbg` binary. + +This `raddbg` will have been built in **debug mode**, which is not built with +optimizations, and may perform worse. To produce a **release mode executable**, +run `build.sh` with a `release` argument: + +``` +./build.sh release +``` + +This build will take significantly longer. + +By default, `build.sh` only builds the debugger if no arguments (or just +`release`) are passed, but additional arguments can be passed to build the RAD +Linker, or the `radbin` CLI binary file utility: + +``` +./build.sh radlink release +./build.sh radbin release +``` + --- # Project Roadmap From 39867def8572a44ecb8df09e4914efdb63d86895 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 13:00:12 -0700 Subject: [PATCH 211/251] header links --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 2966f638..0dfb01e9 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ A benchmark of the linker's performance is below: # Project Development Setup / Local Build Instructions +The project is actively developed both on [Windows x64](#windows-x64) and +[Linux x64](#linux-x64) development machines. Click on whichever you'd like to +use. + ## Windows x64 ### 1. Installing the Required Tools (MSVC & Windows SDK) From ec07bce92fdcec1c5aa518eeff0117bd8397a3c8 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 13:24:15 -0700 Subject: [PATCH 212/251] v0.9.29 debugger changelog --- CHANGELOG.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c69c219..f91d35ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,34 @@ # v0.9.29-alpha ## Debugger Changes +- **The debugger now has preliminary support for native Linux x64 debugging.** + It's still *very early*, and there is surely a lot to fix, so please expect a + less stable experience than on Windows! But it is now finally in a state that + we feel it'd be helpful for people to try it, so that we can begin + battle-testing the debugger on Linux also. Here are a list of known issues + and caveats: + - We are not releasing any binaries for Linux yet, so we've provided + instructions for locally building the debugger in the codebase's + `README.md`. + - We do not support debugging of code which uses `fork` and `vfork` yet. + - When computing locations of thread-local storage, we currently assume that + the debuggee uses the same libc version as the debugger. + - We do not currently correctly unwind when the `.eh_frame_hdr` section is + missing. This is normally there by default, but this may explain some + broken callstacks you encounter. It can be enabled by passing the + `--eh-frame-hdr` flag to the linker, if it's not enabled by default. + - Bitfield type info is not yet supported correctly. + - In some cases, our type deduplication (when converting from DWARF to RDI) + is not working correctly. (#959) + - Sometimes, a keyboard combination will incorrectly send text events. (#954) +- The debugger now has preliminary support for loading Minidump (`.dmp`) files. + They can be opened with the `Open Crash Dump` command. +- Added a breakpoint setting which causes breakpoints to only break if they're + hit by the currently selected thread, if there is a selected thread. If there + isn't a selected thread yet, all threads will still cause a break. +- Added a setting, `Lock New Breakpoints To Selected Thread`, which turns on + the aforementioned breakpoint setting by default on all newly placed + breakpoints. - Reintroduced `Browse` buttons for cells that edit file paths, which spawn a file picker, to more conveniently choose paths. - The debugger now tags watch expressions with the selected project, which @@ -10,6 +38,27 @@ visualized) in source and disassembly views is now clickable, as a fastpath for `Run To Line`. - The position of the destination of a `Run To Line` command is now visualized. +- The debugger now better visualizes the status of loading debug info in the + `Modules` tab. +- The debugger's IPC system is now built on sockets rather than shared memory + ring buffers. This is mostly an implementation detail, but it has introduced + some noteworthy changes. First, communication with instances of the graphical + debugger is now done through network ports, rather than through process IDs. + By default, this port is `7423`, but it can be changed per-instance by + passing the `--ipc_port` command line option, e.g. `raddbg --ipc_port=1234`. + Furthermore, this also means that IPC commands can be sent over the network. + This is unlikely to be especially helpful right now. But to do this, you can + pass an IP address and port when sending a command, e.g.: + `raddbg --ipc --ipc_addr=123.45.67.89:1234`. +- The debugger now allows using a single font for the entire interface, if you + want the UI elements and code to be drawn in a more uniform style. This is + available through the `Use Alternative Font For UI` setting. +- Fixed a performance issue when computing very large call stacks with many + very small frames (fixes missing call stacks in some stack overflow cases). + (#963) +- Fixed `raddbg.com` adding the debugger as a command line target to itself + incorrectly. (#946) +- Fixed many other minor bugs and instabilities. # v0.9.28-alpha From 194595904365dcf4aa0bb8d28eeed2e9cc98f9f1 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 14:42:47 -0700 Subject: [PATCH 213/251] linker notes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f91d35ab..b7d983c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,10 @@ incorrectly. (#946) - Fixed many other minor bugs and instabilities. +## Linker Changes +- Improved memory usage and performance. +- Fixes. + # v0.9.28-alpha ## Debugger Changes From cd41ba199bbe091d348a9b2be5a3528cb8acbff0 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 30 Sep 2026 14:54:59 -0700 Subject: [PATCH 214/251] win32_socket: fix clang warnings --- src/win32/socket/win32_socket.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index a141a3f8..169db89a 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -79,7 +79,7 @@ w32_sock_listener_thread_entry_point(void *p) MemoryZeroStruct(&con->recv_overlapped); DLLPushBack(slot->first, slot->last, con); buf.len = sizeof(con->recv_buffer); - buf.buf = con->recv_buffer; + buf.buf = (char *)con->recv_buffer; recv_size = &con->recv_size; recv_overlapped = &con->recv_overlapped; } @@ -121,7 +121,7 @@ w32_sock_listener_thread_entry_point(void *p) guarded_ring_write_or_wait(&g, sizeof(header), header, max_U64); guarded_ring_write_or_wait(&g, byte_count, con->recv_buffer, max_U64); MemoryZeroStruct(&con->recv_overlapped); - WSABUF buf = {sizeof(con->recv_buffer), con->recv_buffer}; + WSABUF buf = {sizeof(con->recv_buffer), (char *)con->recv_buffer}; DWORD flags = MSG_PUSH_IMMEDIATE; WSARecv(con->socket, &buf, 1, &con->recv_size, &flags, &con->recv_overlapped, 0); guarded_ring_close(&g); @@ -310,7 +310,7 @@ sock_async_tick(void) MemoryZeroStruct(&con->recv_overlapped); // rjf: kick off receive on this socket - WSABUF buf = {sizeof(con->recv_buffer), con->recv_buffer}; + WSABUF buf = {sizeof(con->recv_buffer), (char *)con->recv_buffer}; DWORD flags = MSG_PUSH_IMMEDIATE; WSARecv(con->socket, &buf, 1, &con->recv_size, &flags, &con->recv_overlapped, 0); } @@ -321,7 +321,7 @@ sock_async_tick(void) // rjf: got socket? -> send B32 send_failed = 0; - if(ep_socket != -1 && send(ep_socket, t->data.str, t->data.size, 0) == SOCKET_ERROR) + if(ep_socket != -1 && send(ep_socket, (char *)t->data.str, t->data.size, 0) == SOCKET_ERROR) { int error = WSAGetLastError(); if(error == WSAECONNRESET) From 3ee3ecfeeebf23af86c0eb0493c70cf1127cd2b9 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 09:12:29 -0700 Subject: [PATCH 215/251] do not require git to build correctly; optionally mix in IDs in builds, do not reference git hash without checking in raddbg; do not build/run metaprogram by default in build.sh; also match build.bat behavior of single 'release' arg implying raddbg build --- build.bat | 11 +++++++---- build.sh | 18 ++++++++++-------- src/raddbg/raddbg_core.c | 2 ++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/build.bat b/build.bat index 684a30d3..9d384c56 100644 --- a/build.bat +++ b/build.bat @@ -69,6 +69,13 @@ if "%pgo%"=="1" ( ) ) +:: --- Mix Git Commit ID ------------------------------------------------------ +git rev-parse --is-inside-work-tree >nul 2>&1 && set in_git_tree=1 +if "%in_git_tree%"=="1" ( + for /f %%i in ('call git describe --always --dirty') do set auto_compile_flags=!auto_compile_flags! -DBUILD_GIT_HASH=\"%%i\" + for /f %%i in ('call git rev-parse HEAD') do set auto_compile_flags=!auto_compile_flags! -DBUILD_GIT_HASH_FULL=\"%%i\" +) + :: --- Compile/Link Line Definitions ------------------------------------------ set cl_common= /I..\src\ /I..\local\ /nologo /FC /Z7 /Zc:preprocessor set cl_debug= call cl /Od /Ob1 /DBUILD_DEBUG=1 %cl_common% %auto_compile_flags% @@ -127,10 +134,6 @@ pushd build %rc% /nologo /fo logo.res ..\data\logo.rc || exit /b 1 popd -:: --- Get Current Git Commit Id ---------------------------------------------- -for /f %%i in ('call git describe --always --dirty') do set compile=%compile% -DBUILD_GIT_HASH=\"%%i\" -for /f %%i in ('call git rev-parse HEAD') do set compile=%compile% -DBUILD_GIT_HASH_FULL=\"%%i\" - :: --- Build & Run Metaprogram ------------------------------------------------ pushd build if "%meta%"=="1" ( diff --git a/build.sh b/build.sh index da58b7da..dfa98c5a 100755 --- a/build.sh +++ b/build.sh @@ -3,22 +3,24 @@ set -eu cd "$(dirname "$0")" # --- Unpack Arguments -------------------------------------------------------- +auto_compile_flags="" for arg in "$@"; do declare $arg='1'; done if [[ "$#" == "0" ]]; then raddbg='1'; fi -cc_sanitize="" +if [[ "$#" == "1" && "${release:-0}" == "1" ]]; then raddbg='1'; fi if [[ "${asan:-0}" == "1" ]]; then echo "[asan enabled]" - cc_sanitize="-fsanitize=address" + auto_compile_flags="-fsanitize=address" fi -# --- Get Current Git Commit Id ----------------------------------------------- -git_hash=$(git describe --always --dirty) -git_hash_full=$(git rev-parse HEAD) +# --- Mix Git Commit ID ------------------------------------------------------- +if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + auto_compile_flags="-DBUILD_GIT_HASH=\"$(git describe --always --dirty)\" -DBUILD_GIT_HASH_FULL=$(git rev-parse HEAD)" +fi # --- Compile/Link Line Definitions ------------------------------------------- cc_cflags_gcc="" -cc_cflags_clang=${cc_sanitize}" -fdiagnostics-absolute-paths -Wno-for-loop-analysis -Wno-incompatible-pointer-types-discards-qualifiers -Wno-initializer-overrides -Wno-compare-distinct-pointer-types -Wno-single-bit-bitfield-constant-conversion -Wno-deprecated-declarations -Wno-writable-strings -Wno-unknown-warning-option -Wno-deprecated-register -Wno-unused-local-typedef -msse2" -cc_common="-mcx16 -I../src/ -I../local/ -D_GNU_SOURCE -g -DBUILD_GIT_HASH=\"$git_hash\" -DBUILD_GIT_HASH_FULL=\"$git_hash_full\" -Wall -Wno-missing-braces -Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-value -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf" +cc_cflags_clang=" -fdiagnostics-absolute-paths -Wno-for-loop-analysis -Wno-incompatible-pointer-types-discards-qualifiers -Wno-initializer-overrides -Wno-compare-distinct-pointer-types -Wno-single-bit-bitfield-constant-conversion -Wno-deprecated-declarations -Wno-writable-strings -Wno-unknown-warning-option -Wno-deprecated-register -Wno-unused-local-typedef -msse2" +cc_common=${auto_compile_flags}"-mcx16 -I../src/ -I../local/ -D_GNU_SOURCE -g -Wall -Wno-missing-braces -Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-value -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf" cc_debug="-g -O0 -DBUILD_DEBUG=1 ${cc_common}" cc_release="-g -O2 -DBUILD_DEBUG=0 ${cc_common}" cc_link="-lpthread -lm -lrt -ldl" @@ -58,7 +60,7 @@ compile() { mkdir -p build local # --- Build & Run Metaprogram ------------------------------------------------- -if [[ "${no_meta:-0}" == "0" ]] +if [[ "${meta:-0}" == "1" ]] then echo "[building metagen]" cd build diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 70a0dfb5..05e46e50 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -1830,7 +1830,9 @@ rd_view_ui(Rng2F32 rect) UI_TagF("weak") { ui_label(s(BUILD_VERSION_STRING_LITERAL " " BUILD_RELEASE_PHASE_STRING_LITERAL)); +#if defined(BUILD_GIT_HASH) ui_label(s(BUILD_GIT_HASH)); +#endif } } From 8c1c35508af257627267c8a517e33872661683e9 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 09:16:22 -0700 Subject: [PATCH 216/251] bump to 30 --- src/base/base_context_cracking.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/base_context_cracking.h b/src/base/base_context_cracking.h index e846c62f..04dfb91d 100644 --- a/src/base/base_context_cracking.h +++ b/src/base/base_context_cracking.h @@ -163,7 +163,7 @@ #endif #if !defined(BUILD_VERSION_PATCH) -# define BUILD_VERSION_PATCH 29 +# define BUILD_VERSION_PATCH 30 #endif #define BUILD_VERSION_STRING_LITERAL Stringify(BUILD_VERSION_MAJOR) "." Stringify(BUILD_VERSION_MINOR) "." Stringify(BUILD_VERSION_PATCH) From a256e5c48a923dfed9b651260f136eddd1aa2a91 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 10:33:24 -0700 Subject: [PATCH 217/251] sketch out linux non-host layer, for mapping numeric exception codes to metadata on debugger frontend. also do not always call XQueryKeymap/XQueryPointer; these are very expensive and we only use them in scenarios where we can take the most recently cached value from events. --- src/dbg_engine/dbg_engine.mdesk | 76 -------- src/dbg_engine/dbg_engine_ctrl.c | 3 +- src/linux/generated/linux.meta.c | 5 + src/linux/generated/linux.meta.h | 9 + src/linux/linux.c | 4 + src/linux/linux.h | 9 + src/linux/linux.mdesk | 38 ++++ src/linux/linux_inc.c | 4 + src/linux/linux_inc.h | 9 + .../window_manager/linux_window_manager.c | 27 +-- .../window_manager/linux_window_manager.h | 2 + src/raddbg/raddbg_main.c | 2 + src/win32/generated/win32.meta.c | 176 ++++++++++++++++++ src/win32/generated/win32.meta.h | 60 ++++++ src/win32/win32.c | 4 + src/win32/win32.h | 9 + src/win32/win32.mdesk | 154 +++++++++++++++ src/win32/win32_inc.c | 1 + src/win32/win32_inc.h | 1 + 19 files changed, 503 insertions(+), 90 deletions(-) create mode 100644 src/linux/generated/linux.meta.c create mode 100644 src/linux/generated/linux.meta.h create mode 100644 src/linux/linux.c create mode 100644 src/linux/linux.h create mode 100644 src/linux/linux.mdesk create mode 100644 src/linux/linux_inc.c create mode 100644 src/linux/linux_inc.h create mode 100644 src/win32/generated/win32.meta.c create mode 100644 src/win32/generated/win32.meta.h create mode 100644 src/win32/win32.c create mode 100644 src/win32/win32.h create mode 100644 src/win32/win32.mdesk diff --git a/src/dbg_engine/dbg_engine.mdesk b/src/dbg_engine/dbg_engine.mdesk index c7c9b904..13cd3753 100644 --- a/src/dbg_engine/dbg_engine.mdesk +++ b/src/dbg_engine/dbg_engine.mdesk @@ -211,79 +211,3 @@ D_ExceptionCodeKindTable: `0`; @expand(D_ExceptionCodeKindTable a) `$(a.default)`; } - -//////////////////////////////// -//~ rjf: Exception Sub-Codes - -@table(name, value) -DL_ExceptionSubCodeKindTable: -{ - { W32_FAST_FAIL_LEGACY_GS_VIOLATION 0 } - { W32_FAST_FAIL_VTGUARD_CHECK_FAILURE 1 } - { W32_FAST_FAIL_STACK_COOKIE_CHECK_FAILURE 2 } - { W32_FAST_FAIL_CORRUPT_LIST_ENTRY 3 } - { W32_FAST_FAIL_INCORRECT_STACK 4 } - { W32_FAST_FAIL_INVALID_ARG 5 } - { W32_FAST_FAIL_GS_COOKIE_INIT 6 } - { W32_FAST_FAIL_FATAL_APP_EXIT 7 } - { W32_FAST_FAIL_RANGE_CHECK_FAILURE 8 } - { W32_FAST_FAIL_UNSAFE_REGISTRY_ACCESS 9 } - { W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE 10 } - { W32_FAST_FAIL_GUARD_WRITE_CHECK_FAILURE 11 } - { W32_FAST_FAIL_INVALID_FIBER_SWITCH 12 } - { W32_FAST_FAIL_INVALID_SET_OF_CONTEXT 13 } - { W32_FAST_FAIL_INVALID_REFERENCE_COUNT 14 } - { W32_FAST_FAIL_INVALID_JUMP_BUFFER 18 } - { W32_FAST_FAIL_MRDATA_MODIFIED 19 } - { W32_FAST_FAIL_CERTIFICATION_FAILURE 20 } - { W32_FAST_FAIL_INVALID_EXCEPTION_CHAIN 21 } - { W32_FAST_FAIL_CRYPTO_LIBRARY 22 } - { W32_FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT 23 } - { W32_FAST_FAIL_INVALID_IMAGE_BASE 24 } - { W32_FAST_FAIL_DLOAD_PROTECTION_FAILURE 25 } - { W32_FAST_FAIL_UNSAFE_EXTENSION_CALL 26 } - { W32_FAST_FAIL_DEPRECATED_SERVICE_INVOKED 27 } - { W32_FAST_FAIL_INVALID_BUFFER_ACCESS 28 } - { W32_FAST_FAIL_INVALID_BALANCED_TREE 29 } - { W32_FAST_FAIL_INVALID_NEXT_THREAD 30 } - { W32_FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED 31 } - { W32_FAST_FAIL_APCS_DISABLED 32 } - { W32_FAST_FAIL_INVALID_IDLE_STATE 33 } - { W32_FAST_FAIL_MRDATA_PROTECTION_FAILURE 34 } - { W32_FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION 35 } - { W32_FAST_FAIL_INVALID_LOCK_STATE 36 } - { W32_FAST_FAIL_GUARD_JUMPTABLE 37 } - { W32_FAST_FAIL_INVALID_LONGJUMP_TARGET 38 } - { W32_FAST_FAIL_INVALID_DISPATCH_CONTEXT 39 } - { W32_FAST_FAIL_INVALID_THREAD 40 } - { W32_FAST_FAIL_INVALID_SYSCALL_NUMBER 41 } - { W32_FAST_FAIL_INVALID_FILE_OPERATION 42 } - { W32_FAST_FAIL_LPAC_ACCESS_DENIED 43 } - { W32_FAST_FAIL_GUARD_SS_FAILURE 44 } - { W32_FAST_FAIL_LOADER_CONTINUITY_FAILURE 45 } - { W32_FAST_FAIL_GUARD_EXPORT_SUPPRESSION_FAILURE 46 } - { W32_FAST_FAIL_INVALID_CONTROL_STACK 47 } - { W32_FAST_FAIL_SET_CONTEXT_DENIED 48 } - { W32_FAST_FAIL_INVALID_IAT 49 } - { W32_FAST_FAIL_HEAP_METADATA_CORRUPTION 50 } - { W32_FAST_FAIL_PAYLOAD_RESTRICTION_VIOLATION 51 } - { W32_FAST_FAIL_LOW_LABEL_ACCESS_DENIED 52 } - { W32_FAST_FAIL_ENCLAVE_CALL_FAILURE 53 } - { W32_FAST_FAIL_UNHANDLED_LSS_EXCEPTON 54 } - { W32_FAST_FAIL_ADMINLESS_ACCESS_DENIED 55 } - { W32_FAST_FAIL_UNEXPECTED_CALL 56 } - { W32_FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS 57 } - { W32_FAST_FAIL_UNEXPECTED_HOST_BEHAVIOR 58 } - { W32_FAST_FAIL_FLAGS_CORRUPTION 59 } - { W32_FAST_FAIL_VEH_CORRUPTION 60 } - { W32_FAST_FAIL_ETW_CORRUPTION 61 } - { W32_FAST_FAIL_RIO_ABORT 62 } - { W32_FAST_FAIL_INVALID_PFN 63 } - { W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE_XFG 64 } - { W32_FAST_FAIL_CAST_GUARD 65 } - { W32_FAST_FAIL_HOST_VISIBILITY_CHANGE 66 } - { W32_FAST_FAIL_KERNEL_CET_SHADOW_STACK_ASSIST 67 } - { W32_FAST_FAIL_PATCH_CALLBACK_FAILED 68 } - { W32_FAST_FAIL_NTDLL_PATCH_FAILED 69 } - { W32_FAST_FAIL_INVALID_FLS_DATA 70 } -} diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 020cb26c..4efe2707 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1339,6 +1339,7 @@ d_entity_store_apply_events(D_EntityCtxRWStore *store, D_EventList *list) if(process != &d_entity_nil) { D_Entity *thread = d_entity_alloc(store, process, D_EntityKind_Thread, event->arch, event->entity, (U64)event->entity_id); + thread->os = process->os; D_Entity *first_thread = d_entity_child_from_kind(process, D_EntityKind_Thread); if(first_thread == thread) { @@ -1369,7 +1370,6 @@ d_entity_store_apply_events(D_EntityCtxRWStore *store, D_EventList *list) thread->stack_base = event->stack_base; thread->tls_root_vaddr = event->tls_root; } - //d_cached_ip_from_thread(&store->ctx, event->entity); }break; case D_EventKind_EndThread: { @@ -1446,6 +1446,7 @@ d_entity_store_apply_events(D_EntityCtxRWStore *store, D_EventList *list) d_entity_equip_string(store, module, event->string); module->timestamp = event->timestamp; module->vaddr_range = event->vaddr_rng; + module->os = process->os; D_Entity *first_module = d_entity_child_from_kind(process, D_EntityKind_Module); if(first_module == module && process->string.size == 0) { diff --git a/src/linux/generated/linux.meta.c b/src/linux/generated/linux.meta.c new file mode 100644 index 00000000..0378fad9 --- /dev/null +++ b/src/linux/generated/linux.meta.c @@ -0,0 +1,5 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + diff --git a/src/linux/generated/linux.meta.h b/src/linux/generated/linux.meta.h new file mode 100644 index 00000000..84082495 --- /dev/null +++ b/src/linux/generated/linux.meta.h @@ -0,0 +1,9 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +#ifndef LINUX_META_H +#define LINUX_META_H + +#endif // LINUX_META_H diff --git a/src/linux/linux.c b/src/linux/linux.c new file mode 100644 index 00000000..478fc32d --- /dev/null +++ b/src/linux/linux.c @@ -0,0 +1,4 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "linux/generated/linux.meta.c" diff --git a/src/linux/linux.h b/src/linux/linux.h new file mode 100644 index 00000000..2d97ad1b --- /dev/null +++ b/src/linux/linux.h @@ -0,0 +1,9 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef LINUX_H +#define LINUX_H + +#include "linux/generated/linux.meta.h" + +#endif // LINUX_H diff --git a/src/linux/linux.mdesk b/src/linux/linux.mdesk new file mode 100644 index 00000000..4bcee0fc --- /dev/null +++ b/src/linux/linux.mdesk @@ -0,0 +1,38 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +@table(name code repeatable) LNX_SignalCodeTable: +{ + { SIGHUP 1 1 } + { SIGINT 2 1 } + { SIGQUIT 3 1 } + { SIGILL 4 1 } + { SIGTRAP 5 1 } + { SIGABRT 6 1 } + { SIGIOT 6 1 } + { SIGBUS 7 1 } + { SIGFPE 8 1 } + { SIGKILL 9 1 } + { SIGUSR1 10 1 } + { SIGSEGV 11 1 } + { SIGUSR2 12 1 } + { SIGPIPE 13 1 } + { SIGALRM 14 1 } + { SIGTERM 15 1 } + { SIGSTKFLT 16 1 } + { SIGCHLD 17 0 } + { SIGCONT 18 0 } + { SIGSTOP 19 1 } + { SIGTSTP 20 1 } + { SIGTTIN 21 1 } + { SIGTTOU 22 1 } + { SIGURG 23 0 } + { SIGXCPU 24 1 } + { SIGXFSZ 25 1 } + { SIGVTALRM 26 1 } + { SIGPROF 27 1 } + { SIGWINCH 28 0 } + { SIGIO 29 1 } + { SIGPWR 30 1 } + { SIGSYS 31 1 } +} diff --git a/src/linux/linux_inc.c b/src/linux/linux_inc.c new file mode 100644 index 00000000..31120e72 --- /dev/null +++ b/src/linux/linux_inc.c @@ -0,0 +1,4 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "linux.c" diff --git a/src/linux/linux_inc.h b/src/linux/linux_inc.h new file mode 100644 index 00000000..ea471525 --- /dev/null +++ b/src/linux/linux_inc.h @@ -0,0 +1,9 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef LINUX_INC_H +#define LINUX_INC_H + +#include "linux.h" + +#endif // LINUX_INC_H diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 81f5c609..a7b501a3 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -1231,6 +1231,18 @@ wm_get_events(Arena *arena, B32 wait) B32 is_right_sided = 0; WM_Key key = lnx_wm_key_from_keysym(keysym, &is_right_sided); + // rjf: update modifier / key down state + if(evt.type == KeyPress) + { + lnx_wm_state->modifier_state |= modifiers; + lnx_wm_state->key_down_state[key/64] |= (1ull<<(key%64)); + } + else + { + lnx_wm_state->modifier_state &= ~modifiers; + lnx_wm_state->key_down_state[key/64] &= ~(1ull<<(key%64)); + } + // rjf: push text event if(evt.type == KeyPress && text_size != 0) { @@ -1465,25 +1477,14 @@ wm_get_events(Arena *arena, B32 wait) internal WM_Modifiers wm_get_modifiers(void) { - WM_Modifiers mods = 0; - if(wm_key_is_down(WM_Key_Shift)) { mods |= WM_Modifier_Shift; } - if(wm_key_is_down(WM_Key_Alt)) { mods |= WM_Modifier_Alt; } - if(wm_key_is_down(WM_Key_Ctrl)) { mods |= WM_Modifier_Ctrl; } + WM_Modifiers mods = lnx_wm_state->modifier_state; return mods; } internal B32 wm_key_is_down(WM_Key key) { - B32 result = 0; - { - char keys[32]; - XQueryKeymap(lnx_wm_state->display, keys); - KeySym sym = lnx_wm_keysym_from_key(key); - KeyCode code = XKeysymToKeycode(lnx_wm_state->display, sym); - B32 is_down = !!(keys[code/8] & (1u<<(code%8))); - result = is_down; - } + B32 result = !!(lnx_wm_state->key_down_state[key/64] & (1ull<<(key%64))); return result; } diff --git a/src/linux/window_manager/linux_window_manager.h b/src/linux/window_manager/linux_window_manager.h index 36f9dba8..9a4f4622 100644 --- a/src/linux/window_manager/linux_window_manager.h +++ b/src/linux/window_manager/linux_window_manager.h @@ -95,6 +95,8 @@ struct LNX_WM_State LNX_WM_WindowClientArea *free_client_area; long *icon_image_data; U64 icon_image_data_count; + WM_Modifiers modifier_state; + U64 key_down_state[(WM_Key_COUNT+63)/64]; }; //////////////////////////////// diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 86802f7b..753b0150 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -403,6 +403,7 @@ #include "base/base_inc.h" #include "x64/x64_inc.h" #include "win32/win32_inc.h" +#include "linux/linux_inc.h" #include "linker/base_ext/base_bit_array.h" #include "artifact_cache/artifact_cache.h" #include "rdi/rdi_local.h" @@ -465,6 +466,7 @@ #include "base/base_inc.c" #include "x64/x64.c" #include "win32/win32_inc.c" +#include "linux/linux_inc.c" #include "linker/base_ext/base_bit_array.c" #include "artifact_cache/artifact_cache.c" #include "rdi/rdi_local.c" diff --git a/src/win32/generated/win32.meta.c b/src/win32/generated/win32.meta.c new file mode 100644 index 00000000..8f03f81d --- /dev/null +++ b/src/win32/generated/win32.meta.c @@ -0,0 +1,176 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +C_LINKAGE_BEGIN +U32 w32_exception_code_kind_code_table[38] = +{ +0, +0x40010005, +0x40010008, +0x40080201, +0x40080202, +0x0000071a, +0x80000002, +0xc0000005, +0xc0000006, +0xc0000008, +0xc0000017, +0xc000001d, +0xc0000025, +0xc0000026, +0xc000008c, +0xc000008d, +0xc000008e, +0xc000008f, +0xc0000090, +0xc0000091, +0xc0000092, +0xc0000093, +0xc0000094, +0xc0000095, +0xc0000096, +0xc00000fd, +0xc0000135, +0xc0000138, +0xc0000139, +0xc0000142, +0xc00002b4, +0xc00002b5, +0xc0000420, +0xc06d007e, +0xc06d007f, +0xe073616e, +0xe0736171, +0x0000087a, +}; + +String8 w32_exception_code_kind_display_string_table[38] = +{ +{0}, +str8_lit_comp("Control-C"), +str8_lit_comp("Control-Break"), +str8_lit_comp("WinRT Originate Error"), +str8_lit_comp("WinRT Transform Error"), +str8_lit_comp("RPC Call Cancelled"), +str8_lit_comp("Data Type Misalignment"), +str8_lit_comp("Access Violation"), +str8_lit_comp("In Page Error"), +str8_lit_comp("Invalid Handle Specified"), +str8_lit_comp("Not Enough Quota"), +str8_lit_comp("Illegal Instruction"), +str8_lit_comp("Cannot Continue From Exception"), +str8_lit_comp("Invalid Exception Disposition Returned By Handler"), +str8_lit_comp("Array Bounds Exceeded"), +str8_lit_comp("Floating-Point Denormal Operand"), +str8_lit_comp("Floating-Point Division By Zero"), +str8_lit_comp("Floating-Point Inexact Result"), +str8_lit_comp("Floating-Point Invalid Operation"), +str8_lit_comp("Floating-Point Overflow"), +str8_lit_comp("Floating-Point Stack Check"), +str8_lit_comp("Floating-Point Underflow"), +str8_lit_comp("Integer Division By Zero"), +str8_lit_comp("Integer Overflow"), +str8_lit_comp("Privileged Instruction"), +str8_lit_comp("Stack Overflow"), +str8_lit_comp("Unable To Locate DLL"), +str8_lit_comp("Ordinal Not Found"), +str8_lit_comp("Entry Point Not Found"), +str8_lit_comp("DLL Initialization Failed"), +str8_lit_comp("Floating Point SSE Multiple Faults"), +str8_lit_comp("Floating Point SSE Multiple Traps"), +str8_lit_comp("Assertion Failed"), +str8_lit_comp("Module Not Found"), +str8_lit_comp("Procedure Not Found"), +str8_lit_comp("Sanitizer Error Detected"), +str8_lit_comp("Sanitizer Raw Access Violation"), +str8_lit_comp("DirectX Debug Layer"), +}; + +String8 w32_exception_code_kind_lowercase_code_string_table[38] = +{ +{0}, +str8_lit_comp("ctrl_c"), +str8_lit_comp("ctrl_break"), +str8_lit_comp("win_rt_originate_error"), +str8_lit_comp("win_rt_transform_error"), +str8_lit_comp("rpc_call_cancelled"), +str8_lit_comp("datatype_misalignment"), +str8_lit_comp("access_violation"), +str8_lit_comp("in_page_error"), +str8_lit_comp("invalid_handle"), +str8_lit_comp("not_enough_quota"), +str8_lit_comp("illegal_instruction"), +str8_lit_comp("cannot_continue_exception"), +str8_lit_comp("invalid_exception_disposition"), +str8_lit_comp("array_bounds_exceeded"), +str8_lit_comp("floating_point_denormal_operand"), +str8_lit_comp("floating_point_division_by_zero"), +str8_lit_comp("floating_point_inexact_result"), +str8_lit_comp("floating_point_invalid_operation"), +str8_lit_comp("floating_point_overflow"), +str8_lit_comp("floating_point_stack_check"), +str8_lit_comp("floating_point_underflow"), +str8_lit_comp("integer_division_by_zero"), +str8_lit_comp("integer_overflow"), +str8_lit_comp("privileged_instruction"), +str8_lit_comp("stack_overflow"), +str8_lit_comp("unable_to_locate_dll"), +str8_lit_comp("ordinal_not_found"), +str8_lit_comp("entry_point_not_found"), +str8_lit_comp("dll_initialization_failed"), +str8_lit_comp("floating_point_sse_multiple_faults"), +str8_lit_comp("floating_point_sse_multiple_traps"), +str8_lit_comp("assertion_failed"), +str8_lit_comp("module_not_found"), +str8_lit_comp("procedure_not_found"), +str8_lit_comp("sanitizer_error_detected"), +str8_lit_comp("sanitizer_raw_access_violation"), +str8_lit_comp("directx_debug_layer"), +}; + +B8 w32_exception_code_kind_default_enable_table[38] = +{ +0, +1, +1, +0, +0, +0, +0, +1, +0, +1, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +1, +0, +0, +1, +0, +1, +}; + +C_LINKAGE_END + diff --git a/src/win32/generated/win32.meta.h b/src/win32/generated/win32.meta.h new file mode 100644 index 00000000..8104ddad --- /dev/null +++ b/src/win32/generated/win32.meta.h @@ -0,0 +1,60 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +#ifndef WIN32_META_H +#define WIN32_META_H + +typedef enum W32_ExceptionCodeKind +{ +W32_ExceptionCodeKind_Null, +W32_ExceptionCodeKind_CtrlC, +W32_ExceptionCodeKind_CtrlBreak, +W32_ExceptionCodeKind_WinRTOriginateError, +W32_ExceptionCodeKind_WinRTTransformError, +W32_ExceptionCodeKind_RPCCallCancelled, +W32_ExceptionCodeKind_DatatypeMisalignment, +W32_ExceptionCodeKind_AccessViolation, +W32_ExceptionCodeKind_InPageError, +W32_ExceptionCodeKind_InvalidHandle, +W32_ExceptionCodeKind_NotEnoughQuota, +W32_ExceptionCodeKind_IllegalInstruction, +W32_ExceptionCodeKind_CannotContinueException, +W32_ExceptionCodeKind_InvalidExceptionDisposition, +W32_ExceptionCodeKind_ArrayBoundsExceeded, +W32_ExceptionCodeKind_FloatingPointDenormalOperand, +W32_ExceptionCodeKind_FloatingPointDivisionByZero, +W32_ExceptionCodeKind_FloatingPointInexactResult, +W32_ExceptionCodeKind_FloatingPointInvalidOperation, +W32_ExceptionCodeKind_FloatingPointOverflow, +W32_ExceptionCodeKind_FloatingPointStackCheck, +W32_ExceptionCodeKind_FloatingPointUnderflow, +W32_ExceptionCodeKind_IntegerDivisionByZero, +W32_ExceptionCodeKind_IntegerOverflow, +W32_ExceptionCodeKind_PrivilegedInstruction, +W32_ExceptionCodeKind_StackOverflow, +W32_ExceptionCodeKind_UnableToLocateDLL, +W32_ExceptionCodeKind_OrdinalNotFound, +W32_ExceptionCodeKind_EntryPointNotFound, +W32_ExceptionCodeKind_DLLInitializationFailed, +W32_ExceptionCodeKind_FloatingPointSSEMultipleFaults, +W32_ExceptionCodeKind_FloatingPointSSEMultipleTraps, +W32_ExceptionCodeKind_AssertionFailed, +W32_ExceptionCodeKind_ModuleNotFound, +W32_ExceptionCodeKind_ProcedureNotFound, +W32_ExceptionCodeKind_SanitizerErrorDetected, +W32_ExceptionCodeKind_SanitizerRawAccessViolation, +W32_ExceptionCodeKind_DirectXDebugLayer, +W32_ExceptionCodeKind_COUNT, +} W32_ExceptionCodeKind; + +C_LINKAGE_BEGIN +extern U32 w32_exception_code_kind_code_table[38]; +extern String8 w32_exception_code_kind_display_string_table[38]; +extern String8 w32_exception_code_kind_lowercase_code_string_table[38]; +extern B8 w32_exception_code_kind_default_enable_table[38]; + +C_LINKAGE_END + +#endif // WIN32_META_H diff --git a/src/win32/win32.c b/src/win32/win32.c new file mode 100644 index 00000000..642f431f --- /dev/null +++ b/src/win32/win32.c @@ -0,0 +1,4 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "win32/generated/win32.meta.c" diff --git a/src/win32/win32.h b/src/win32/win32.h new file mode 100644 index 00000000..b71adaff --- /dev/null +++ b/src/win32/win32.h @@ -0,0 +1,9 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef WIN32_H +#define WIN32_H + +#include "win32/generated/win32.meta.h" + +#endif // WIN32_H diff --git a/src/win32/win32.mdesk b/src/win32/win32.mdesk new file mode 100644 index 00000000..5b6370dd --- /dev/null +++ b/src/win32/win32.mdesk @@ -0,0 +1,154 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Exception Codes + +@table(name lower_name code default display_string) +W32_ExceptionCodeKindTable: +{ + {CtrlC ctrl_c 0x40010005 1 "Control-C" } + {CtrlBreak ctrl_break 0x40010008 1 "Control-Break" } + {WinRTOriginateError win_rt_originate_error 0x40080201 0 "WinRT Originate Error" } + {WinRTTransformError win_rt_transform_error 0x40080202 0 "WinRT Transform Error" } + {RPCCallCancelled rpc_call_cancelled 0x0000071a 0 "RPC Call Cancelled" } + {DatatypeMisalignment datatype_misalignment 0x80000002 0 "Data Type Misalignment" } + {AccessViolation access_violation 0xc0000005 1 "Access Violation" } + {InPageError in_page_error 0xc0000006 0 "In Page Error" } + {InvalidHandle invalid_handle 0xc0000008 1 "Invalid Handle Specified" } + {NotEnoughQuota not_enough_quota 0xc0000017 0 "Not Enough Quota" } + {IllegalInstruction illegal_instruction 0xc000001d 0 "Illegal Instruction" } + {CannotContinueException cannot_continue_exception 0xc0000025 0 "Cannot Continue From Exception" } + {InvalidExceptionDisposition invalid_exception_disposition 0xc0000026 0 "Invalid Exception Disposition Returned By Handler" } + {ArrayBoundsExceeded array_bounds_exceeded 0xc000008c 0 "Array Bounds Exceeded" } + {FloatingPointDenormalOperand floating_point_denormal_operand 0xc000008d 0 "Floating-Point Denormal Operand" } + {FloatingPointDivisionByZero floating_point_division_by_zero 0xc000008e 0 "Floating-Point Division By Zero" } + {FloatingPointInexactResult floating_point_inexact_result 0xc000008f 0 "Floating-Point Inexact Result" } + {FloatingPointInvalidOperation floating_point_invalid_operation 0xc0000090 0 "Floating-Point Invalid Operation" } + {FloatingPointOverflow floating_point_overflow 0xc0000091 0 "Floating-Point Overflow" } + {FloatingPointStackCheck floating_point_stack_check 0xc0000092 0 "Floating-Point Stack Check" } + {FloatingPointUnderflow floating_point_underflow 0xc0000093 0 "Floating-Point Underflow" } + {IntegerDivisionByZero integer_division_by_zero 0xc0000094 0 "Integer Division By Zero" } + {IntegerOverflow integer_overflow 0xc0000095 0 "Integer Overflow" } + {PrivilegedInstruction privileged_instruction 0xc0000096 0 "Privileged Instruction" } + {StackOverflow stack_overflow 0xc00000fd 0 "Stack Overflow" } + {UnableToLocateDLL unable_to_locate_dll 0xc0000135 0 "Unable To Locate DLL" } + {OrdinalNotFound ordinal_not_found 0xc0000138 0 "Ordinal Not Found" } + {EntryPointNotFound entry_point_not_found 0xc0000139 0 "Entry Point Not Found" } + {DLLInitializationFailed dll_initialization_failed 0xc0000142 0 "DLL Initialization Failed" } + {FloatingPointSSEMultipleFaults floating_point_sse_multiple_faults 0xc00002b4 0 "Floating Point SSE Multiple Faults" } + {FloatingPointSSEMultipleTraps floating_point_sse_multiple_traps 0xc00002b5 0 "Floating Point SSE Multiple Traps" } + {AssertionFailed assertion_failed 0xc0000420 1 "Assertion Failed" } + {ModuleNotFound module_not_found 0xc06d007e 0 "Module Not Found" } + {ProcedureNotFound procedure_not_found 0xc06d007f 0 "Procedure Not Found" } + {SanitizerErrorDetected sanitizer_error_detected 0xe073616e 1 "Sanitizer Error Detected" } + {SanitizerRawAccessViolation sanitizer_raw_access_violation 0xe0736171 0 "Sanitizer Raw Access Violation" } + {DirectXDebugLayer directx_debug_layer 0x0000087a 1 "DirectX Debug Layer" } +} + +@enum W32_ExceptionCodeKind: +{ + Null, + @expand(W32_ExceptionCodeKindTable a) `$(a.name)`, + COUNT, +} + +@data(U32) w32_exception_code_kind_code_table: +{ + `0`; + @expand(W32_ExceptionCodeKindTable a) `$(a.code)`; +} + +@data(String8) w32_exception_code_kind_display_string_table: +{ + `{0}`; + @expand(W32_ExceptionCodeKindTable a) `str8_lit_comp("$(a.display_string)")`; +} + +@data(String8) w32_exception_code_kind_lowercase_code_string_table: +{ + `{0}`; + @expand(W32_ExceptionCodeKindTable a) `str8_lit_comp("$(a.lower_name)")`; +} + +@data(B8) w32_exception_code_kind_default_enable_table: +{ + `0`; + @expand(W32_ExceptionCodeKindTable a) `$(a.default)`; +} + +//////////////////////////////// +//~ rjf: Exception Sub-Codes + +@table(name, value) +W32_ExceptionSubCodeKindTable: +{ + { W32_FAST_FAIL_LEGACY_GS_VIOLATION 0 } + { W32_FAST_FAIL_VTGUARD_CHECK_FAILURE 1 } + { W32_FAST_FAIL_STACK_COOKIE_CHECK_FAILURE 2 } + { W32_FAST_FAIL_CORRUPT_LIST_ENTRY 3 } + { W32_FAST_FAIL_INCORRECT_STACK 4 } + { W32_FAST_FAIL_INVALID_ARG 5 } + { W32_FAST_FAIL_GS_COOKIE_INIT 6 } + { W32_FAST_FAIL_FATAL_APP_EXIT 7 } + { W32_FAST_FAIL_RANGE_CHECK_FAILURE 8 } + { W32_FAST_FAIL_UNSAFE_REGISTRY_ACCESS 9 } + { W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE 10 } + { W32_FAST_FAIL_GUARD_WRITE_CHECK_FAILURE 11 } + { W32_FAST_FAIL_INVALID_FIBER_SWITCH 12 } + { W32_FAST_FAIL_INVALID_SET_OF_CONTEXT 13 } + { W32_FAST_FAIL_INVALID_REFERENCE_COUNT 14 } + { W32_FAST_FAIL_INVALID_JUMP_BUFFER 18 } + { W32_FAST_FAIL_MRDATA_MODIFIED 19 } + { W32_FAST_FAIL_CERTIFICATION_FAILURE 20 } + { W32_FAST_FAIL_INVALID_EXCEPTION_CHAIN 21 } + { W32_FAST_FAIL_CRYPTO_LIBRARY 22 } + { W32_FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT 23 } + { W32_FAST_FAIL_INVALID_IMAGE_BASE 24 } + { W32_FAST_FAIL_DLOAD_PROTECTION_FAILURE 25 } + { W32_FAST_FAIL_UNSAFE_EXTENSION_CALL 26 } + { W32_FAST_FAIL_DEPRECATED_SERVICE_INVOKED 27 } + { W32_FAST_FAIL_INVALID_BUFFER_ACCESS 28 } + { W32_FAST_FAIL_INVALID_BALANCED_TREE 29 } + { W32_FAST_FAIL_INVALID_NEXT_THREAD 30 } + { W32_FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED 31 } + { W32_FAST_FAIL_APCS_DISABLED 32 } + { W32_FAST_FAIL_INVALID_IDLE_STATE 33 } + { W32_FAST_FAIL_MRDATA_PROTECTION_FAILURE 34 } + { W32_FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION 35 } + { W32_FAST_FAIL_INVALID_LOCK_STATE 36 } + { W32_FAST_FAIL_GUARD_JUMPTABLE 37 } + { W32_FAST_FAIL_INVALID_LONGJUMP_TARGET 38 } + { W32_FAST_FAIL_INVALID_DISPATCH_CONTEXT 39 } + { W32_FAST_FAIL_INVALID_THREAD 40 } + { W32_FAST_FAIL_INVALID_SYSCALL_NUMBER 41 } + { W32_FAST_FAIL_INVALID_FILE_OPERATION 42 } + { W32_FAST_FAIL_LPAC_ACCESS_DENIED 43 } + { W32_FAST_FAIL_GUARD_SS_FAILURE 44 } + { W32_FAST_FAIL_LOADER_CONTINUITY_FAILURE 45 } + { W32_FAST_FAIL_GUARD_EXPORT_SUPPRESSION_FAILURE 46 } + { W32_FAST_FAIL_INVALID_CONTROL_STACK 47 } + { W32_FAST_FAIL_SET_CONTEXT_DENIED 48 } + { W32_FAST_FAIL_INVALID_IAT 49 } + { W32_FAST_FAIL_HEAP_METADATA_CORRUPTION 50 } + { W32_FAST_FAIL_PAYLOAD_RESTRICTION_VIOLATION 51 } + { W32_FAST_FAIL_LOW_LABEL_ACCESS_DENIED 52 } + { W32_FAST_FAIL_ENCLAVE_CALL_FAILURE 53 } + { W32_FAST_FAIL_UNHANDLED_LSS_EXCEPTON 54 } + { W32_FAST_FAIL_ADMINLESS_ACCESS_DENIED 55 } + { W32_FAST_FAIL_UNEXPECTED_CALL 56 } + { W32_FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS 57 } + { W32_FAST_FAIL_UNEXPECTED_HOST_BEHAVIOR 58 } + { W32_FAST_FAIL_FLAGS_CORRUPTION 59 } + { W32_FAST_FAIL_VEH_CORRUPTION 60 } + { W32_FAST_FAIL_ETW_CORRUPTION 61 } + { W32_FAST_FAIL_RIO_ABORT 62 } + { W32_FAST_FAIL_INVALID_PFN 63 } + { W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE_XFG 64 } + { W32_FAST_FAIL_CAST_GUARD 65 } + { W32_FAST_FAIL_HOST_VISIBILITY_CHANGE 66 } + { W32_FAST_FAIL_KERNEL_CET_SHADOW_STACK_ASSIST 67 } + { W32_FAST_FAIL_PATCH_CALLBACK_FAILED 68 } + { W32_FAST_FAIL_NTDLL_PATCH_FAILED 69 } + { W32_FAST_FAIL_INVALID_FLS_DATA 70 } +} diff --git a/src/win32/win32_inc.c b/src/win32/win32_inc.c index 6f8f5874..4fe735a8 100644 --- a/src/win32/win32_inc.c +++ b/src/win32/win32_inc.c @@ -1,6 +1,7 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +#include "win32/win32.c" #if defined(X64_H) # include "win32/x64/win32_x64.c" #endif diff --git a/src/win32/win32_inc.h b/src/win32/win32_inc.h index 062fd9e5..f64fabb1 100644 --- a/src/win32/win32_inc.h +++ b/src/win32/win32_inc.h @@ -4,6 +4,7 @@ #ifndef WIN32_INC_H #define WIN32_INC_H +#include "win32/win32.h" #if defined(X64_H) # include "win32/x64/win32_x64.h" #endif From 56073cfff0b55e539c6c06da85aaa0891db68c78 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 10:47:46 -0700 Subject: [PATCH 218/251] linux_window_manager: query default refresh rate via xrandr, rather than assuming 60 (oops!) --- .github/workflows/builds.yml | 4 +-- README.md | 5 +-- build.sh | 6 ++-- .../window_manager/linux_window_manager.c | 32 ++++++++++++++++++- .../window_manager/linux_window_manager.h | 1 + 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 75a0c00b..08915074 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -40,7 +40,7 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install --no-install-recommends -y pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev + sudo apt-get install --no-install-recommends -y pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libxrandr-dev libgl1-mesa-dev libegl1-mesa-dev - name: Install clang-17 Dependencies if: ${{ matrix.compiler == 'clang-17' }} @@ -162,7 +162,7 @@ jobs: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-add-repository -y "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-22 main" sudo apt-get update - sudo apt-get install -y clang-22 lld-22 llvm-22 gcc-14 pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libgl1-mesa-dev libegl1-mesa-dev + sudo apt-get install -y clang-22 lld-22 llvm-22 gcc-14 pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libxrandr-dev libgl1-mesa-dev libegl1-mesa-dev # crash callstack symbolizer depends on LLVM sudo ln -s /usr/bin/llvm-symbolizer-22 /usr/bin/llvm-symbolizer - name: Run diff --git a/README.md b/README.md index 0dfb01e9..05403355 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,7 @@ on the system: - `libx11` - `libxext` - `libxfixes` +- `libxrandr` - `libgl` - `libegl` @@ -256,13 +257,13 @@ installed using one of the following commands, depending on your distribution: #### Ubuntu / Debian / Mint ``` -sudo apt install -y libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libgl-dev libegl-dev +sudo apt install -y libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libxrandr-dev libgl-dev libegl-dev ``` #### Arch / Manjaro ``` -sudo pacman -S --needed freetype2 libx11 libxext libxfixes libglvnd +sudo pacman -S --needed freetype2 libx11 libxext libxfixes libxrandr libglvnd ``` ### 3. Building diff --git a/build.sh b/build.sh index dfa98c5a..6ad190be 100755 --- a/build.sh +++ b/build.sh @@ -29,14 +29,14 @@ cc_link="-lpthread -lm -lrt -ldl" cc_link_dll="-fPIC" # --- External Libraries ------------------------------------------------------ -# sudo apt install -y pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libgl-dev libegl-dev +# sudo apt install -y pkg-config libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libxrandr-dev libgl-dev libegl-dev if [[ -x "$(command -v pkg-config)" ]]; then cc_font_provider="$(pkg-config --cflags --libs freetype2)" - cc_os_gfx="$(pkg-config --cflags --libs x11 xext xfixes)" + cc_os_gfx="$(pkg-config --cflags --libs x11 xext xfixes xrandr)" cc_render="$(pkg-config --cflags --libs gl egl)" else cc_font_provider="-I/usr/include/freetype2 -lfreetype" - cc_os_gfx="-lX11 -lXext -lXfixes" + cc_os_gfx="-lX11 -lXext -lXfixes -lXrandr" cc_render="-lGL -lEGL" fi diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index a7b501a3..cda0a06a 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -374,11 +374,41 @@ wm_init(void) //- rjf: open im lnx_wm_state->xim = XOpenIM(lnx_wm_state->display, 0, 0, 0); - //- rjf: fill out gfx info + //- rjf: fill out default gfx info lnx_wm_state->gfx_info.double_click_time = 0.5f; lnx_wm_state->gfx_info.caret_blink_time = 0.5f; lnx_wm_state->gfx_info.default_refresh_rate = 60.f; + //- rjf: determine default refresh rate + { + XRRScreenResources *res = XRRGetScreenResourcesCurrent(lnx_wm_state->display, DefaultRootWindow(lnx_wm_state->display)); + for EachIndex(idx, res->noutput) + { + XRROutputInfo *out_info = XRRGetOutputInfo(lnx_wm_state->display, res, res->outputs[idx]); + if(out_info && out_info->connection == RR_Connected && out_info->crtc != None) + { + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(lnx_wm_state->display, res, out_info->crtc); + if(crtc_info) + { + for EachIndex(mode_idx, res->nmode) + { + if(res->modes[mode_idx].id == crtc_info->mode) + { + XRRModeInfo *mode_info = &res->modes[mode_idx]; + if(mode_info->vTotal != 0 && mode_info->hTotal != 0) + { + lnx_wm_state->gfx_info.default_refresh_rate = (F32)mode_info->dotClock / ((F32)mode_info->hTotal * (F32)mode_info->vTotal); + } + } + } + XRRFreeCrtcInfo(crtc_info); + } + XRRFreeOutputInfo(out_info); + } + } + XRRFreeScreenResources(res); + } + //- rjf: fill out cursors { struct diff --git a/src/linux/window_manager/linux_window_manager.h b/src/linux/window_manager/linux_window_manager.h index 9a4f4622..be652d72 100644 --- a/src/linux/window_manager/linux_window_manager.h +++ b/src/linux/window_manager/linux_window_manager.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include From cc9951b5ef4cca70a384a4d6c9dc28a77132e55b Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 11:49:53 -0700 Subject: [PATCH 219/251] linux signal codes, user settings to enable breaking on them, feed into debug engine --- src/dbg_engine/dbg_engine.mdesk | 64 ++--- src/dbg_engine/generated/dbg_engine.meta.c | 279 ++++++++++++++++++--- src/dbg_engine/generated/dbg_engine.meta.h | 40 ++- src/linux/linux.mdesk | 65 +++-- src/raddbg/generated/raddbg.meta.c | 2 +- src/raddbg/raddbg.mdesk | 142 ++++++++--- src/raddbg/raddbg_main.c | 5 + src/torture/torture_main.c | 6 +- 8 files changed, 437 insertions(+), 166 deletions(-) diff --git a/src/dbg_engine/dbg_engine.mdesk b/src/dbg_engine/dbg_engine.mdesk index 13cd3753..cbcf517e 100644 --- a/src/dbg_engine/dbg_engine.mdesk +++ b/src/dbg_engine/dbg_engine.mdesk @@ -139,75 +139,45 @@ D_EntityKindTable: //////////////////////////////// //~ rjf: Exception Codes -@table(name lower_name code default display_string) -D_ExceptionCodeKindTable: -{ - {Win32CtrlC win32_ctrl_c 0x40010005 1 "(Win32) Control-C" } - {Win32CtrlBreak win32_ctrl_break 0x40010008 1 "(Win32) Control-Break" } - {Win32WinRTOriginateError win32_win_rt_originate_error 0x40080201 0 "(Win32) WinRT Originate Error" } - {Win32WinRTTransformError win32_win_rt_transform_error 0x40080202 0 "(Win32) WinRT Transform Error" } - {Win32RPCCallCancelled win32_rpc_call_cancelled 0x0000071a 0 "(Win32) RPC Call Cancelled" } - {Win32DatatypeMisalignment win32_datatype_misalignment 0x80000002 0 "(Win32) Data Type Misalignment" } - {Win32AccessViolation win32_access_violation 0xc0000005 1 "(Win32) Access Violation" } - {Win32InPageError win32_in_page_error 0xc0000006 0 "(Win32) In Page Error" } - {Win32InvalidHandle win32_invalid_handle 0xc0000008 1 "(Win32) Invalid Handle Specified" } - {Win32NotEnoughQuota win32_not_enough_quota 0xc0000017 0 "(Win32) Not Enough Quota" } - {Win32IllegalInstruction win32_illegal_instruction 0xc000001d 0 "(Win32) Illegal Instruction" } - {Win32CannotContinueException win32_cannot_continue_exception 0xc0000025 0 "(Win32) Cannot Continue From Exception" } - {Win32InvalidExceptionDisposition win32_invalid_exception_disposition 0xc0000026 0 "(Win32) Invalid Exception Disposition Returned By Handler" } - {Win32ArrayBoundsExceeded win32_array_bounds_exceeded 0xc000008c 0 "(Win32) Array Bounds Exceeded" } - {Win32FloatingPointDenormalOperand win32_floating_point_denormal_operand 0xc000008d 0 "(Win32) Floating-Point Denormal Operand" } - {Win32FloatingPointDivisionByZero win32_floating_point_division_by_zero 0xc000008e 0 "(Win32) Floating-Point Division By Zero" } - {Win32FloatingPointInexactResult win32_floating_point_inexact_result 0xc000008f 0 "(Win32) Floating-Point Inexact Result" } - {Win32FloatingPointInvalidOperation win32_floating_point_invalid_operation 0xc0000090 0 "(Win32) Floating-Point Invalid Operation" } - {Win32FloatingPointOverflow win32_floating_point_overflow 0xc0000091 0 "(Win32) Floating-Point Overflow" } - {Win32FloatingPointStackCheck win32_floating_point_stack_check 0xc0000092 0 "(Win32) Floating-Point Stack Check" } - {Win32FloatingPointUnderflow win32_floating_point_underflow 0xc0000093 0 "(Win32) Floating-Point Underflow" } - {Win32IntegerDivisionByZero win32_integer_division_by_zero 0xc0000094 0 "(Win32) Integer Division By Zero" } - {Win32IntegerOverflow win32_integer_overflow 0xc0000095 0 "(Win32) Integer Overflow" } - {Win32PrivilegedInstruction win32_privileged_instruction 0xc0000096 0 "(Win32) Privileged Instruction" } - {Win32StackOverflow win32_stack_overflow 0xc00000fd 0 "(Win32) Stack Overflow" } - {Win32UnableToLocateDLL win32_unable_to_locate_dll 0xc0000135 0 "(Win32) Unable To Locate DLL" } - {Win32OrdinalNotFound win32_ordinal_not_found 0xc0000138 0 "(Win32) Ordinal Not Found" } - {Win32EntryPointNotFound win32_entry_point_not_found 0xc0000139 0 "(Win32) Entry Point Not Found" } - {Win32DLLInitializationFailed win32_dll_initialization_failed 0xc0000142 0 "(Win32) DLL Initialization Failed" } - {Win32FloatingPointSSEMultipleFaults win32_floating_point_sse_multiple_faults 0xc00002b4 0 "(Win32) Floating Point SSE Multiple Faults" } - {Win32FloatingPointSSEMultipleTraps win32_floating_point_sse_multiple_traps 0xc00002b5 0 "(Win32) Floating Point SSE Multiple Traps" } - {Win32AssertionFailed win32_assertion_failed 0xc0000420 1 "(Win32) Assertion Failed" } - {Win32ModuleNotFound win32_module_not_found 0xc06d007e 0 "(Win32) Module Not Found" } - {Win32ProcedureNotFound win32_procedure_not_found 0xc06d007f 0 "(Win32) Procedure Not Found" } - {Win32SanitizerErrorDetected win32_sanitizer_error_detected 0xe073616e 1 "(Win32) Sanitizer Error Detected" } - {Win32SanitizerRawAccessViolation win32_sanitizer_raw_access_violation 0xe0736171 0 "(Win32) Sanitizer Raw Access Violation" } - {Win32DirectXDebugLayer win32_directx_debug_layer 0x0000087a 1 "(Win32) DirectX Debug Layer" } -} - @enum D_ExceptionCodeKind: { Null, - @expand(D_ExceptionCodeKindTable a) `$(a.name)`, + @expand(W32_ExceptionCodeKindTable a) `Win32$(a.name)`, + @expand(LNX_SignalCodeTable a) `Linux$(a.name)`, COUNT, } @data(U32) d_exception_code_kind_code_table: { `0`; - @expand(D_ExceptionCodeKindTable a) `$(a.code)`; + @expand(W32_ExceptionCodeKindTable a) `$(a.code)`; + @expand(LNX_SignalCodeTable a) `$(a.code)`, +} + +@data(OperatingSystem) d_exception_code_kind_os_table: +{ + `OperatingSystem_Null`; + @expand(W32_ExceptionCodeKindTable a) `OperatingSystem_Windows`; + @expand(LNX_SignalCodeTable a) `OperatingSystem_Linux`, } @data(String8) d_exception_code_kind_display_string_table: { `{0}`; - @expand(D_ExceptionCodeKindTable a) `str8_lit_comp("$(a.display_string)")`; + @expand(W32_ExceptionCodeKindTable a) `str8_lit_comp("$(a.display_string)")`; + @expand(LNX_SignalCodeTable a) `str8_lit_comp("$(a.name) ($(a.desc))")`, } @data(String8) d_exception_code_kind_lowercase_code_string_table: { `{0}`; - @expand(D_ExceptionCodeKindTable a) `str8_lit_comp("$(a.lower_name)")`; + @expand(W32_ExceptionCodeKindTable a) `str8_lit_comp("win32_$(a.lower_name)")`; + @expand(LNX_SignalCodeTable a) `str8_lit_comp("linux_$(a.name_lower)")`, } @data(B8) d_exception_code_kind_default_enable_table: { `0`; - @expand(D_ExceptionCodeKindTable a) `$(a.default)`; + @expand(W32_ExceptionCodeKindTable a) `$(a.default)`; + @expand(LNX_SignalCodeTable a) `$(a.break_default)`; } diff --git a/src/dbg_engine/generated/dbg_engine.meta.c b/src/dbg_engine/generated/dbg_engine.meta.c index e322fded..c7a54839 100644 --- a/src/dbg_engine/generated/dbg_engine.meta.c +++ b/src/dbg_engine/generated/dbg_engine.meta.c @@ -38,7 +38,7 @@ str8_lit_comp("Breakpoint"), str8_lit_comp("AddressRangeAnnotation"), }; -U32 d_exception_code_kind_code_table[38] = +U32 d_exception_code_kind_code_table[69] = { 0, 0x40010005, @@ -78,51 +78,186 @@ U32 d_exception_code_kind_code_table[38] = 0xe073616e, 0xe0736171, 0x0000087a, +1, +2, +3, +4, +5, +6, +7, +8, +9, +10, +11, +12, +13, +14, +15, +16, +17, +18, +19, +20, +21, +22, +23, +24, +25, +26, +27, +28, +29, +30, +31, }; -String8 d_exception_code_kind_display_string_table[38] = +OperatingSystem d_exception_code_kind_os_table[69] = +{ +OperatingSystem_Null, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Windows, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +OperatingSystem_Linux, +}; + +String8 d_exception_code_kind_display_string_table[69] = { {0}, -str8_lit_comp("(Win32) Control-C"), -str8_lit_comp("(Win32) Control-Break"), -str8_lit_comp("(Win32) WinRT Originate Error"), -str8_lit_comp("(Win32) WinRT Transform Error"), -str8_lit_comp("(Win32) RPC Call Cancelled"), -str8_lit_comp("(Win32) Data Type Misalignment"), -str8_lit_comp("(Win32) Access Violation"), -str8_lit_comp("(Win32) In Page Error"), -str8_lit_comp("(Win32) Invalid Handle Specified"), -str8_lit_comp("(Win32) Not Enough Quota"), -str8_lit_comp("(Win32) Illegal Instruction"), -str8_lit_comp("(Win32) Cannot Continue From Exception"), -str8_lit_comp("(Win32) Invalid Exception Disposition Returned By Handler"), -str8_lit_comp("(Win32) Array Bounds Exceeded"), -str8_lit_comp("(Win32) Floating-Point Denormal Operand"), -str8_lit_comp("(Win32) Floating-Point Division By Zero"), -str8_lit_comp("(Win32) Floating-Point Inexact Result"), -str8_lit_comp("(Win32) Floating-Point Invalid Operation"), -str8_lit_comp("(Win32) Floating-Point Overflow"), -str8_lit_comp("(Win32) Floating-Point Stack Check"), -str8_lit_comp("(Win32) Floating-Point Underflow"), -str8_lit_comp("(Win32) Integer Division By Zero"), -str8_lit_comp("(Win32) Integer Overflow"), -str8_lit_comp("(Win32) Privileged Instruction"), -str8_lit_comp("(Win32) Stack Overflow"), -str8_lit_comp("(Win32) Unable To Locate DLL"), -str8_lit_comp("(Win32) Ordinal Not Found"), -str8_lit_comp("(Win32) Entry Point Not Found"), -str8_lit_comp("(Win32) DLL Initialization Failed"), -str8_lit_comp("(Win32) Floating Point SSE Multiple Faults"), -str8_lit_comp("(Win32) Floating Point SSE Multiple Traps"), -str8_lit_comp("(Win32) Assertion Failed"), -str8_lit_comp("(Win32) Module Not Found"), -str8_lit_comp("(Win32) Procedure Not Found"), -str8_lit_comp("(Win32) Sanitizer Error Detected"), -str8_lit_comp("(Win32) Sanitizer Raw Access Violation"), -str8_lit_comp("(Win32) DirectX Debug Layer"), +str8_lit_comp("Control-C"), +str8_lit_comp("Control-Break"), +str8_lit_comp("WinRT Originate Error"), +str8_lit_comp("WinRT Transform Error"), +str8_lit_comp("RPC Call Cancelled"), +str8_lit_comp("Data Type Misalignment"), +str8_lit_comp("Access Violation"), +str8_lit_comp("In Page Error"), +str8_lit_comp("Invalid Handle Specified"), +str8_lit_comp("Not Enough Quota"), +str8_lit_comp("Illegal Instruction"), +str8_lit_comp("Cannot Continue From Exception"), +str8_lit_comp("Invalid Exception Disposition Returned By Handler"), +str8_lit_comp("Array Bounds Exceeded"), +str8_lit_comp("Floating-Point Denormal Operand"), +str8_lit_comp("Floating-Point Division By Zero"), +str8_lit_comp("Floating-Point Inexact Result"), +str8_lit_comp("Floating-Point Invalid Operation"), +str8_lit_comp("Floating-Point Overflow"), +str8_lit_comp("Floating-Point Stack Check"), +str8_lit_comp("Floating-Point Underflow"), +str8_lit_comp("Integer Division By Zero"), +str8_lit_comp("Integer Overflow"), +str8_lit_comp("Privileged Instruction"), +str8_lit_comp("Stack Overflow"), +str8_lit_comp("Unable To Locate DLL"), +str8_lit_comp("Ordinal Not Found"), +str8_lit_comp("Entry Point Not Found"), +str8_lit_comp("DLL Initialization Failed"), +str8_lit_comp("Floating Point SSE Multiple Faults"), +str8_lit_comp("Floating Point SSE Multiple Traps"), +str8_lit_comp("Assertion Failed"), +str8_lit_comp("Module Not Found"), +str8_lit_comp("Procedure Not Found"), +str8_lit_comp("Sanitizer Error Detected"), +str8_lit_comp("Sanitizer Raw Access Violation"), +str8_lit_comp("DirectX Debug Layer"), +str8_lit_comp("SIGHUP (Hangup detected on controlling terminal or death of controlling process)"), +str8_lit_comp("SIGINT (Interrupt from keyboard)"), +str8_lit_comp("SIGQUIT (Quit from keyboard)"), +str8_lit_comp("SIGILL (Illegal Instruction)"), +str8_lit_comp("SIGTRAP (Trace/breakpoint trap)"), +str8_lit_comp("SIGABRT (Abort signal)"), +str8_lit_comp("SIGBUS (Bus error (bad address))"), +str8_lit_comp("SIGFPE (Erroneous arithmetic operation)"), +str8_lit_comp("SIGKILL (Kill signal)"), +str8_lit_comp("SIGUSR1 (User-defined signal 1)"), +str8_lit_comp("SIGSEGV (Invalid memory reference)"), +str8_lit_comp("SIGUSR2 (User-defined signal 2)"), +str8_lit_comp("SIGPIPE (Broken pipe; write to pipe with no readers)"), +str8_lit_comp("SIGALRM (Timer signal)"), +str8_lit_comp("SIGTERM (Termination signal)"), +str8_lit_comp("SIGSTKFLT (Stack fault on coprocessor)"), +str8_lit_comp("SIGCHLD (Child stopped, terminated, or continued)"), +str8_lit_comp("SIGCONT (Continue if stopped)"), +str8_lit_comp("SIGSTOP (Stop process)"), +str8_lit_comp("SIGTSTP (Stop typed at terminal)"), +str8_lit_comp("SIGTTIN (Terminal input for background process)"), +str8_lit_comp("SIGTTOU (Terminal output for background process)"), +str8_lit_comp("SIGURG (Urgent condition on socket)"), +str8_lit_comp("SIGXCPU (CPU time limit exceeded)"), +str8_lit_comp("SIGXFSZ (File size limit exceeded)"), +str8_lit_comp("SIGVTALRM (Virtual alarm clock)"), +str8_lit_comp("SIGPROF (Profiling timer expired)"), +str8_lit_comp("SIGWINCH (Window resize signal)"), +str8_lit_comp("SIGIO (I/O now possible)"), +str8_lit_comp("SIGPWR (Power failure)"), +str8_lit_comp("SIGSYS (Bad system call)"), }; -String8 d_exception_code_kind_lowercase_code_string_table[38] = +String8 d_exception_code_kind_lowercase_code_string_table[69] = { {0}, str8_lit_comp("win32_ctrl_c"), @@ -162,9 +297,40 @@ str8_lit_comp("win32_procedure_not_found"), str8_lit_comp("win32_sanitizer_error_detected"), str8_lit_comp("win32_sanitizer_raw_access_violation"), str8_lit_comp("win32_directx_debug_layer"), +str8_lit_comp("linux_sighup"), +str8_lit_comp("linux_sigint"), +str8_lit_comp("linux_sigquit"), +str8_lit_comp("linux_sigill"), +str8_lit_comp("linux_sigtrap"), +str8_lit_comp("linux_sigabrt"), +str8_lit_comp("linux_sigbus"), +str8_lit_comp("linux_sigfpe"), +str8_lit_comp("linux_sigkill"), +str8_lit_comp("linux_sigusr1"), +str8_lit_comp("linux_sigsegv"), +str8_lit_comp("linux_sigusr2"), +str8_lit_comp("linux_sigpipe"), +str8_lit_comp("linux_sigalrm"), +str8_lit_comp("linux_sigterm"), +str8_lit_comp("linux_sigstkflt"), +str8_lit_comp("linux_sigchld"), +str8_lit_comp("linux_sigcont"), +str8_lit_comp("linux_sigstop"), +str8_lit_comp("linux_sigtstp"), +str8_lit_comp("linux_sigttin"), +str8_lit_comp("linux_sigttou"), +str8_lit_comp("linux_sigurg"), +str8_lit_comp("linux_sigxcpu"), +str8_lit_comp("linux_sigxfsz"), +str8_lit_comp("linux_sigvtalrm"), +str8_lit_comp("linux_sigprof"), +str8_lit_comp("linux_sigwinch"), +str8_lit_comp("linux_sigio"), +str8_lit_comp("linux_sigpwr"), +str8_lit_comp("linux_sigsys"), }; -B8 d_exception_code_kind_default_enable_table[38] = +B8 d_exception_code_kind_default_enable_table[69] = { 0, 1, @@ -204,6 +370,37 @@ B8 d_exception_code_kind_default_enable_table[38] = 1, 0, 1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +0, +1, +1, +0, +0, +0, +1, +1, +0, +0, +0, +0, +1, +1, }; C_LINKAGE_END diff --git a/src/dbg_engine/generated/dbg_engine.meta.h b/src/dbg_engine/generated/dbg_engine.meta.h index 87e6284a..2391d4d2 100644 --- a/src/dbg_engine/generated/dbg_engine.meta.h +++ b/src/dbg_engine/generated/dbg_engine.meta.h @@ -106,6 +106,37 @@ D_ExceptionCodeKind_Win32ProcedureNotFound, D_ExceptionCodeKind_Win32SanitizerErrorDetected, D_ExceptionCodeKind_Win32SanitizerRawAccessViolation, D_ExceptionCodeKind_Win32DirectXDebugLayer, +D_ExceptionCodeKind_LinuxSIGHUP, +D_ExceptionCodeKind_LinuxSIGINT, +D_ExceptionCodeKind_LinuxSIGQUIT, +D_ExceptionCodeKind_LinuxSIGILL, +D_ExceptionCodeKind_LinuxSIGTRAP, +D_ExceptionCodeKind_LinuxSIGABRT, +D_ExceptionCodeKind_LinuxSIGBUS, +D_ExceptionCodeKind_LinuxSIGFPE, +D_ExceptionCodeKind_LinuxSIGKILL, +D_ExceptionCodeKind_LinuxSIGUSR1, +D_ExceptionCodeKind_LinuxSIGSEGV, +D_ExceptionCodeKind_LinuxSIGUSR2, +D_ExceptionCodeKind_LinuxSIGPIPE, +D_ExceptionCodeKind_LinuxSIGALRM, +D_ExceptionCodeKind_LinuxSIGTERM, +D_ExceptionCodeKind_LinuxSIGSTKFLT, +D_ExceptionCodeKind_LinuxSIGCHLD, +D_ExceptionCodeKind_LinuxSIGCONT, +D_ExceptionCodeKind_LinuxSIGSTOP, +D_ExceptionCodeKind_LinuxSIGTSTP, +D_ExceptionCodeKind_LinuxSIGTTIN, +D_ExceptionCodeKind_LinuxSIGTTOU, +D_ExceptionCodeKind_LinuxSIGURG, +D_ExceptionCodeKind_LinuxSIGXCPU, +D_ExceptionCodeKind_LinuxSIGXFSZ, +D_ExceptionCodeKind_LinuxSIGVTALRM, +D_ExceptionCodeKind_LinuxSIGPROF, +D_ExceptionCodeKind_LinuxSIGWINCH, +D_ExceptionCodeKind_LinuxSIGIO, +D_ExceptionCodeKind_LinuxSIGPWR, +D_ExceptionCodeKind_LinuxSIGSYS, D_ExceptionCodeKind_COUNT, } D_ExceptionCodeKind; @@ -133,10 +164,11 @@ struct {B32 *value_ptr; String8 name;} DEV_toggle_table[] = C_LINKAGE_BEGIN extern String8 d_entity_kind_code_name_table[13]; extern String8 d_entity_kind_display_string_table[13]; -extern U32 d_exception_code_kind_code_table[38]; -extern String8 d_exception_code_kind_display_string_table[38]; -extern String8 d_exception_code_kind_lowercase_code_string_table[38]; -extern B8 d_exception_code_kind_default_enable_table[38]; +extern U32 d_exception_code_kind_code_table[69]; +extern OperatingSystem d_exception_code_kind_os_table[69]; +extern String8 d_exception_code_kind_display_string_table[69]; +extern String8 d_exception_code_kind_lowercase_code_string_table[69]; +extern B8 d_exception_code_kind_default_enable_table[69]; C_LINKAGE_END diff --git a/src/linux/linux.mdesk b/src/linux/linux.mdesk index 4bcee0fc..b510bb4f 100644 --- a/src/linux/linux.mdesk +++ b/src/linux/linux.mdesk @@ -1,38 +1,37 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -@table(name code repeatable) LNX_SignalCodeTable: +@table(name name_lower code repeatable break_default desc) LNX_SignalCodeTable: { - { SIGHUP 1 1 } - { SIGINT 2 1 } - { SIGQUIT 3 1 } - { SIGILL 4 1 } - { SIGTRAP 5 1 } - { SIGABRT 6 1 } - { SIGIOT 6 1 } - { SIGBUS 7 1 } - { SIGFPE 8 1 } - { SIGKILL 9 1 } - { SIGUSR1 10 1 } - { SIGSEGV 11 1 } - { SIGUSR2 12 1 } - { SIGPIPE 13 1 } - { SIGALRM 14 1 } - { SIGTERM 15 1 } - { SIGSTKFLT 16 1 } - { SIGCHLD 17 0 } - { SIGCONT 18 0 } - { SIGSTOP 19 1 } - { SIGTSTP 20 1 } - { SIGTTIN 21 1 } - { SIGTTOU 22 1 } - { SIGURG 23 0 } - { SIGXCPU 24 1 } - { SIGXFSZ 25 1 } - { SIGVTALRM 26 1 } - { SIGPROF 27 1 } - { SIGWINCH 28 0 } - { SIGIO 29 1 } - { SIGPWR 30 1 } - { SIGSYS 31 1 } + { SIGHUP sighup 1 1 1 "Hangup detected on controlling terminal or death of controlling process" } + { SIGINT sigint 2 1 1 "Interrupt from keyboard"} + { SIGQUIT sigquit 3 1 1 "Quit from keyboard"} + { SIGILL sigill 4 1 1 "Illegal Instruction"} + { SIGTRAP sigtrap 5 1 1 "Trace/breakpoint trap"} + { SIGABRT sigabrt 6 1 1 "Abort signal"} + { SIGBUS sigbus 7 1 1 "Bus error (bad address)"} + { SIGFPE sigfpe 8 1 1 "Erroneous arithmetic operation"} + { SIGKILL sigkill 9 1 1 "Kill signal"} + { SIGUSR1 sigusr1 10 1 1 "User-defined signal 1"} + { SIGSEGV sigsegv 11 1 1 "Invalid memory reference"} + { SIGUSR2 sigusr2 12 1 1 "User-defined signal 2"} + { SIGPIPE sigpipe 13 1 1 "Broken pipe; write to pipe with no readers"} + { SIGALRM sigalrm 14 1 1 "Timer signal"} + { SIGTERM sigterm 15 1 1 "Termination signal"} + { SIGSTKFLT sigstkflt 16 1 1 "Stack fault on coprocessor"} + { SIGCHLD sigchld 17 0 1 "Child stopped, terminated, or continued"} + { SIGCONT sigcont 18 0 0 "Continue if stopped"} + { SIGSTOP sigstop 19 1 1 "Stop process"} + { SIGTSTP sigtstp 20 1 1 "Stop typed at terminal"} + { SIGTTIN sigttin 21 1 0 "Terminal input for background process"} + { SIGTTOU sigttou 22 1 0 "Terminal output for background process"} + { SIGURG sigurg 23 0 0 "Urgent condition on socket"} + { SIGXCPU sigxcpu 24 1 1 "CPU time limit exceeded"} + { SIGXFSZ sigxfsz 25 1 1 "File size limit exceeded"} + { SIGVTALRM sigvtalrm 26 1 0 "Virtual alarm clock"} + { SIGPROF sigprof 27 1 0 "Profiling timer expired"} + { SIGWINCH sigwinch 28 0 0 "Window resize signal"} + { SIGIO sigio 29 1 0 "I/O now possible"} + { SIGPWR sigpwr 30 1 1 "Power failure"} + { SIGSYS sigsys 31 1 1 "Bad system call"} } diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 7a9d7690..fa347d9d 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -439,7 +439,7 @@ RD_VocabInfo rd_vocab_info_table[370] = RD_NameSchemaInfo rd_name_schema_info_table[39] = { {str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('Use Alternative Font For UI') @description(\"Uses an alternative font for non-code UI elements.\")\n @default(1) 'use_alternative_font_for_ui': bool,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n @expand_if(\"!$.use_alternative_font_for_ui\") @display_name('Font') @description(\"The name of, or path to, the font used in all UI.\")\n @default('') 'font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: breakpoint behavior\n @default(0) @display_name('Lock New Breakpoints To Selected Thread') @description(\"When placing breakpoints, locks them to the selected thread by default.\")\n 'lock_new_breakpoints_to_selected_thread': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n\n //- rjf: system installation\n @display_name('Install To System') @description(\"Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.\") 'install_to_system': bool,\n}\n")}, -{str8_lit_comp("project"), 0, str8_lit_comp("@expand_commands(edit_project_theme) x:\n{\n @display_name('Project Name') 'name': string,\n @default(2) @display_name('Project Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: visualizers\n @display_name('Display Pointer Addresses Before Contents') @description(\"When visualizing pointers, always shows the address first, before showing contents at the pointer's address.\")\n @default(0) display_pointer_addresses_before_contents: bool,\n @display_name('Use Default C++ STL Type Visualizers') @description(\"Enables the built-in type views for C++ STL types.\")\n @default(1) use_default_stl_type_views: bool,\n @display_name('Use Default Unreal Engine Type Visualizers') @description(\"Enables the built-in type views for Unreal Engine types.\")\n @default(1) use_default_ue_type_views: bool,\n\n //- rjf: theme\n @default(\"None\") @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme': string,\n @no_expand @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme_colors': set,\n\n //- rjf: exception settings\n @default(1) @display_name(\"Break On Win32 Control-C Exceptions\") @description(\"Code: 0x40010005\")\n win32_ctrl_c: bool;\n @default(1) @display_name(\"Break On Win32 Control-Break Exceptions\") @description(\"Code: 0x40010008\")\n win32_ctrl_break: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Originate Error Exceptions\") @description(\"Code: 0x40080201\")\n win32_win_rt_originate_error: bool;\n @default(0) @display_name(\"Break On Win32 WinRT Transform Error Exceptions\") @description(\"Code: 0x40080202\")\n win32_win_rt_transform_error: bool;\n @default(0) @display_name(\"Break On Win32 RPC Call Cancelled Exceptions\") @description(\"Code: 0x0000071a\")\n win32_rpc_call_cancelled: bool;\n @default(0) @display_name(\"Break On Win32 Data Type Misalignment Exceptions\") @description(\"Code: 0x80000002\")\n win32_datatype_misalignment: bool;\n @default(1) @display_name(\"Break On Win32 Access Violation Exceptions\") @description(\"Code: 0xc0000005\")\n win32_access_violation: bool;\n @default(0) @display_name(\"Break On Win32 In Page Error Exceptions\") @description(\"Code: 0xc0000006\")\n win32_in_page_error: bool;\n @default(1) @display_name(\"Break On Win32 Invalid Handle Specified Exceptions\") @description(\"Code: 0xc0000008\")\n win32_invalid_handle: bool;\n @default(0) @display_name(\"Break On Win32 Not Enough Quota Exceptions\") @description(\"Code: 0xc0000017\")\n win32_not_enough_quota: bool;\n @default(0) @display_name(\"Break On Win32 Illegal Instruction Exceptions\") @description(\"Code: 0xc000001d\")\n win32_illegal_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Cannot Continue From Exception Exceptions\") @description(\"Code: 0xc0000025\")\n win32_cannot_continue_exception: bool;\n @default(0) @display_name(\"Break On Win32 Invalid Exception Disposition Returned By Handler Exceptions\") @description(\"Code: 0xc0000026\")\n win32_invalid_exception_disposition: bool;\n @default(0) @display_name(\"Break On Win32 Array Bounds Exceeded Exceptions\") @description(\"Code: 0xc000008c\")\n win32_array_bounds_exceeded: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Denormal Operand Exceptions\") @description(\"Code: 0xc000008d\")\n win32_floating_point_denormal_operand: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Division By Zero Exceptions\") @description(\"Code: 0xc000008e\")\n win32_floating_point_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Inexact Result Exceptions\") @description(\"Code: 0xc000008f\")\n win32_floating_point_inexact_result: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Invalid Operation Exceptions\") @description(\"Code: 0xc0000090\")\n win32_floating_point_invalid_operation: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Overflow Exceptions\") @description(\"Code: 0xc0000091\")\n win32_floating_point_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Stack Check Exceptions\") @description(\"Code: 0xc0000092\")\n win32_floating_point_stack_check: bool;\n @default(0) @display_name(\"Break On Win32 Floating-Point Underflow Exceptions\") @description(\"Code: 0xc0000093\")\n win32_floating_point_underflow: bool;\n @default(0) @display_name(\"Break On Win32 Integer Division By Zero Exceptions\") @description(\"Code: 0xc0000094\")\n win32_integer_division_by_zero: bool;\n @default(0) @display_name(\"Break On Win32 Integer Overflow Exceptions\") @description(\"Code: 0xc0000095\")\n win32_integer_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Privileged Instruction Exceptions\") @description(\"Code: 0xc0000096\")\n win32_privileged_instruction: bool;\n @default(0) @display_name(\"Break On Win32 Stack Overflow Exceptions\") @description(\"Code: 0xc00000fd\")\n win32_stack_overflow: bool;\n @default(0) @display_name(\"Break On Win32 Unable To Locate DLL Exceptions\") @description(\"Code: 0xc0000135\")\n win32_unable_to_locate_dll: bool;\n @default(0) @display_name(\"Break On Win32 Ordinal Not Found Exceptions\") @description(\"Code: 0xc0000138\")\n win32_ordinal_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Entry Point Not Found Exceptions\") @description(\"Code: 0xc0000139\")\n win32_entry_point_not_found: bool;\n @default(0) @display_name(\"Break On Win32 DLL Initialization Failed Exceptions\") @description(\"Code: 0xc0000142\")\n win32_dll_initialization_failed: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Faults Exceptions\") @description(\"Code: 0xc00002b4\")\n win32_floating_point_sse_multiple_faults: bool;\n @default(0) @display_name(\"Break On Win32 Floating Point SSE Multiple Traps Exceptions\") @description(\"Code: 0xc00002b5\")\n win32_floating_point_sse_multiple_traps: bool;\n @default(1) @display_name(\"Break On Win32 Assertion Failed Exceptions\") @description(\"Code: 0xc0000420\")\n win32_assertion_failed: bool;\n @default(0) @display_name(\"Break On Win32 Module Not Found Exceptions\") @description(\"Code: 0xc06d007e\")\n win32_module_not_found: bool;\n @default(0) @display_name(\"Break On Win32 Procedure Not Found Exceptions\") @description(\"Code: 0xc06d007f\")\n win32_procedure_not_found: bool;\n @default(1) @display_name(\"Break On Win32 Sanitizer Error Detected Exceptions\") @description(\"Code: 0xe073616e\")\n win32_sanitizer_error_detected: bool;\n @default(0) @display_name(\"Break On Win32 Sanitizer Raw Access Violation Exceptions\") @description(\"Code: 0xe0736171\")\n win32_sanitizer_raw_access_violation: bool;\n @default(1) @display_name(\"Break On Win32 DirectX Debug Layer Exceptions\") @description(\"Code: 0x0000087a\")\n win32_directx_debug_layer: bool;\n}\n")}, +{str8_lit_comp("project"), 0, str8_lit_comp("@expand_commands(edit_project_theme) x:\n{\n @display_name('Project Name') 'name': string,\n @default(2) @display_name('Project Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: visualizers\n @display_name('Display Pointer Addresses Before Contents') @description(\"When visualizing pointers, always shows the address first, before showing contents at the pointer's address.\")\n @default(0) display_pointer_addresses_before_contents: bool,\n @display_name('Use Default C++ STL Type Visualizers') @description(\"Enables the built-in type views for C++ STL types.\")\n @default(1) use_default_stl_type_views: bool,\n @display_name('Use Default Unreal Engine Type Visualizers') @description(\"Enables the built-in type views for Unreal Engine types.\")\n @default(1) use_default_ue_type_views: bool,\n\n //- rjf: theme\n @default(\"None\") @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme': string,\n @no_expand @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme_colors': set,\n\n //- rjf: win32 exception settings\n @default(1) @display_name(\"Break on Win32 Control-C Exceptions\") @description(\"Code: 0x40010005\")\n win32_ctrl_c: bool;\n @default(1) @display_name(\"Break on Win32 Control-Break Exceptions\") @description(\"Code: 0x40010008\")\n win32_ctrl_break: bool;\n @default(0) @display_name(\"Break on Win32 WinRT Originate Error Exceptions\") @description(\"Code: 0x40080201\")\n win32_win_rt_originate_error: bool;\n @default(0) @display_name(\"Break on Win32 WinRT Transform Error Exceptions\") @description(\"Code: 0x40080202\")\n win32_win_rt_transform_error: bool;\n @default(0) @display_name(\"Break on Win32 RPC Call Cancelled Exceptions\") @description(\"Code: 0x0000071a\")\n win32_rpc_call_cancelled: bool;\n @default(0) @display_name(\"Break on Win32 Data Type Misalignment Exceptions\") @description(\"Code: 0x80000002\")\n win32_datatype_misalignment: bool;\n @default(1) @display_name(\"Break on Win32 Access Violation Exceptions\") @description(\"Code: 0xc0000005\")\n win32_access_violation: bool;\n @default(0) @display_name(\"Break on Win32 In Page Error Exceptions\") @description(\"Code: 0xc0000006\")\n win32_in_page_error: bool;\n @default(1) @display_name(\"Break on Win32 Invalid Handle Specified Exceptions\") @description(\"Code: 0xc0000008\")\n win32_invalid_handle: bool;\n @default(0) @display_name(\"Break on Win32 Not Enough Quota Exceptions\") @description(\"Code: 0xc0000017\")\n win32_not_enough_quota: bool;\n @default(0) @display_name(\"Break on Win32 Illegal Instruction Exceptions\") @description(\"Code: 0xc000001d\")\n win32_illegal_instruction: bool;\n @default(0) @display_name(\"Break on Win32 Cannot Continue From Exception Exceptions\") @description(\"Code: 0xc0000025\")\n win32_cannot_continue_exception: bool;\n @default(0) @display_name(\"Break on Win32 Invalid Exception Disposition Returned By Handler Exceptions\") @description(\"Code: 0xc0000026\")\n win32_invalid_exception_disposition: bool;\n @default(0) @display_name(\"Break on Win32 Array Bounds Exceeded Exceptions\") @description(\"Code: 0xc000008c\")\n win32_array_bounds_exceeded: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Denormal Operand Exceptions\") @description(\"Code: 0xc000008d\")\n win32_floating_point_denormal_operand: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Division By Zero Exceptions\") @description(\"Code: 0xc000008e\")\n win32_floating_point_division_by_zero: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Inexact Result Exceptions\") @description(\"Code: 0xc000008f\")\n win32_floating_point_inexact_result: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Invalid Operation Exceptions\") @description(\"Code: 0xc0000090\")\n win32_floating_point_invalid_operation: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Overflow Exceptions\") @description(\"Code: 0xc0000091\")\n win32_floating_point_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Stack Check Exceptions\") @description(\"Code: 0xc0000092\")\n win32_floating_point_stack_check: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Underflow Exceptions\") @description(\"Code: 0xc0000093\")\n win32_floating_point_underflow: bool;\n @default(0) @display_name(\"Break on Win32 Integer Division By Zero Exceptions\") @description(\"Code: 0xc0000094\")\n win32_integer_division_by_zero: bool;\n @default(0) @display_name(\"Break on Win32 Integer Overflow Exceptions\") @description(\"Code: 0xc0000095\")\n win32_integer_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Privileged Instruction Exceptions\") @description(\"Code: 0xc0000096\")\n win32_privileged_instruction: bool;\n @default(0) @display_name(\"Break on Win32 Stack Overflow Exceptions\") @description(\"Code: 0xc00000fd\")\n win32_stack_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Unable To Locate DLL Exceptions\") @description(\"Code: 0xc0000135\")\n win32_unable_to_locate_dll: bool;\n @default(0) @display_name(\"Break on Win32 Ordinal Not Found Exceptions\") @description(\"Code: 0xc0000138\")\n win32_ordinal_not_found: bool;\n @default(0) @display_name(\"Break on Win32 Entry Point Not Found Exceptions\") @description(\"Code: 0xc0000139\")\n win32_entry_point_not_found: bool;\n @default(0) @display_name(\"Break on Win32 DLL Initialization Failed Exceptions\") @description(\"Code: 0xc0000142\")\n win32_dll_initialization_failed: bool;\n @default(0) @display_name(\"Break on Win32 Floating Point SSE Multiple Faults Exceptions\") @description(\"Code: 0xc00002b4\")\n win32_floating_point_sse_multiple_faults: bool;\n @default(0) @display_name(\"Break on Win32 Floating Point SSE Multiple Traps Exceptions\") @description(\"Code: 0xc00002b5\")\n win32_floating_point_sse_multiple_traps: bool;\n @default(1) @display_name(\"Break on Win32 Assertion Failed Exceptions\") @description(\"Code: 0xc0000420\")\n win32_assertion_failed: bool;\n @default(0) @display_name(\"Break on Win32 Module Not Found Exceptions\") @description(\"Code: 0xc06d007e\")\n win32_module_not_found: bool;\n @default(0) @display_name(\"Break on Win32 Procedure Not Found Exceptions\") @description(\"Code: 0xc06d007f\")\n win32_procedure_not_found: bool;\n @default(1) @display_name(\"Break on Win32 Sanitizer Error Detected Exceptions\") @description(\"Code: 0xe073616e\")\n win32_sanitizer_error_detected: bool;\n @default(0) @display_name(\"Break on Win32 Sanitizer Raw Access Violation Exceptions\") @description(\"Code: 0xe0736171\")\n win32_sanitizer_raw_access_violation: bool;\n @default(1) @display_name(\"Break on Win32 DirectX Debug Layer Exceptions\") @description(\"Code: 0x0000087a\")\n win32_directx_debug_layer: bool;\n\n //- rjf: linux exception settings\n @display_name(\"Break on Linux SIGHUP\") @default(1) @description(\"Hangup detected on controlling terminal or death of controlling process (Code: 1)\")\n linux_sighup: bool,\n @display_name(\"Break on Linux SIGINT\") @default(1) @description(\"Interrupt from keyboard (Code: 2)\")\n linux_sigint: bool,\n @display_name(\"Break on Linux SIGQUIT\") @default(1) @description(\"Quit from keyboard (Code: 3)\")\n linux_sigquit: bool,\n @display_name(\"Break on Linux SIGILL\") @default(1) @description(\"Illegal Instruction (Code: 4)\")\n linux_sigill: bool,\n @display_name(\"Break on Linux SIGTRAP\") @default(1) @description(\"Trace/breakpoint trap (Code: 5)\")\n linux_sigtrap: bool,\n @display_name(\"Break on Linux SIGABRT\") @default(1) @description(\"Abort signal (Code: 6)\")\n linux_sigabrt: bool,\n @display_name(\"Break on Linux SIGBUS\") @default(1) @description(\"Bus error (bad address) (Code: 7)\")\n linux_sigbus: bool,\n @display_name(\"Break on Linux SIGFPE\") @default(1) @description(\"Erroneous arithmetic operation (Code: 8)\")\n linux_sigfpe: bool,\n @display_name(\"Break on Linux SIGKILL\") @default(1) @description(\"Kill signal (Code: 9)\")\n linux_sigkill: bool,\n @display_name(\"Break on Linux SIGUSR1\") @default(1) @description(\"User-defined signal 1 (Code: 10)\")\n linux_sigusr1: bool,\n @display_name(\"Break on Linux SIGSEGV\") @default(1) @description(\"Invalid memory reference (Code: 11)\")\n linux_sigsegv: bool,\n @display_name(\"Break on Linux SIGUSR2\") @default(1) @description(\"User-defined signal 2 (Code: 12)\")\n linux_sigusr2: bool,\n @display_name(\"Break on Linux SIGPIPE\") @default(1) @description(\"Broken pipe; write to pipe with no readers (Code: 13)\")\n linux_sigpipe: bool,\n @display_name(\"Break on Linux SIGALRM\") @default(1) @description(\"Timer signal (Code: 14)\")\n linux_sigalrm: bool,\n @display_name(\"Break on Linux SIGTERM\") @default(1) @description(\"Termination signal (Code: 15)\")\n linux_sigterm: bool,\n @display_name(\"Break on Linux SIGSTKFLT\") @default(1) @description(\"Stack fault on coprocessor (Code: 16)\")\n linux_sigstkflt: bool,\n @display_name(\"Break on Linux SIGCHLD\") @default(1) @description(\"Child stopped, terminated, or continued (Code: 17)\")\n linux_sigchld: bool,\n @display_name(\"Break on Linux SIGCONT\") @default(1) @description(\"Continue if stopped (Code: 18)\")\n linux_sigcont: bool,\n @display_name(\"Break on Linux SIGSTOP\") @default(1) @description(\"Stop process (Code: 19)\")\n linux_sigstop: bool,\n @display_name(\"Break on Linux SIGTSTP\") @default(1) @description(\"Stop typed at terminal (Code: 20)\")\n linux_sigtstp: bool,\n @display_name(\"Break on Linux SIGTTIN\") @default(1) @description(\"Terminal input for background process (Code: 21)\")\n linux_sigttin: bool,\n @display_name(\"Break on Linux SIGTTOU\") @default(1) @description(\"Terminal output for background process (Code: 22)\")\n linux_sigttou: bool,\n @display_name(\"Break on Linux SIGURG\") @default(1) @description(\"Urgent condition on socket (Code: 23)\")\n linux_sigurg: bool,\n @display_name(\"Break on Linux SIGXCPU\") @default(1) @description(\"CPU time limit exceeded (Code: 24)\")\n linux_sigxcpu: bool,\n @display_name(\"Break on Linux SIGXFSZ\") @default(1) @description(\"File size limit exceeded (Code: 25)\")\n linux_sigxfsz: bool,\n @display_name(\"Break on Linux SIGVTALRM\") @default(1) @description(\"Virtual alarm clock (Code: 26)\")\n linux_sigvtalrm: bool,\n @display_name(\"Break on Linux SIGPROF\") @default(1) @description(\"Profiling timer expired (Code: 27)\")\n linux_sigprof: bool,\n @display_name(\"Break on Linux SIGWINCH\") @default(1) @description(\"Window resize signal (Code: 28)\")\n linux_sigwinch: bool,\n @display_name(\"Break on Linux SIGIO\") @default(1) @description(\"I/O now possible (Code: 29)\")\n linux_sigio: bool,\n @display_name(\"Break on Linux SIGPWR\") @default(1) @description(\"Power failure (Code: 30)\")\n linux_sigpwr: bool,\n @display_name(\"Break on Linux SIGSYS\") @default(1) @description(\"Bad system call (Code: 31)\")\n linux_sigsys: bool,\n}\n")}, {str8_lit_comp("theme_color"), 0, str8_lit_comp("@collection_commands(add_theme_color, fork_theme, save_theme, save_and_set_theme)\n@row_commands(duplicate_cfg, remove_cfg)\nx:\n{\n @display_name('Tags') tags: string,\n @display_name('Value') value: @color @hex u32,\n}\n")}, {str8_lit_comp("window"), 0, str8_lit_comp("x:\n{\n //- rjf: text rasterization settings\n @default(1) @display_name('Smooth UI Text') @description(\"Controls whether or not UI text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_ui_text': bool,\n @default(1) @display_name('Hint UI Text') @description(\"Controls whether or not UI text is hinted, for better text readability at small sizes.\")\n 'hint_ui_text': bool,\n @default(0) @display_name('Smooth Code Text') @description(\"Controls whether or not code text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_code_text': bool,\n @default(1) @display_name('Hint Code Text') @description(\"Controls whether or not code text is hinted, for better text readability at small sizes.\")\n 'hint_code_text': bool,\n @default(11) @display_name('Window Font Size') @description(\"Controls the window's default font size. Does not apply to tabs with their own font size set.\")\n 'font_size': @range[6, 72] u64,\n\n //- rjf: size settings\n @default(3.f) @display_name('Window Row Height') @description(\"Controls the window's default row height, in multiples of the font size. Does not apply to tabs with their own row height set.\")\n 'row_height': @range[1.75f, 5.f] f32,\n @default(3.f) @description(\"Controls the height of tabs, in multiples of the font size.\")\n 'tab_height': @range[1.75f, 5.f] f32,\n\n //- rjf: theme settings\n @default(1) @display_name('Use Project Theme') @description(\"Prefer using the project theme for this window, if any. If off, only the user's theme settings will be used.\")\n 'use_project_theme': bool,\n}\n")}, {str8_lit_comp("tab"), 0, str8_lit_comp("@row_commands(@file copy_tab_full_path, @file show_file_in_explorer, duplicate_tab, close_tab)\nx:\n{\n @override @display_name('Tab Font Size') @description(\"Controls the tab's font size.\") @no_callee_helper\n 'font_size': @range[6, 72] u64,\n}\n")}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 2c9d75a8..6d970676 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -329,7 +329,7 @@ RD_VocabTable: //- rjf: breakpoint behavior @default(0) @display_name('Lock New Breakpoints To Selected Thread') @description("When placing breakpoints, locks them to the selected thread by default.") 'lock_new_breakpoints_to_selected_thread': bool, - + //- rjf: update check @default(1) @display_name('Check For Updates') @description("Enables a small network request to check if there is a newer release version available.") 'check_for_updates': bool, @@ -368,81 +368,145 @@ RD_VocabTable: @no_expand @display_name('Project Theme') @description("The project's theme, which describes all colors used throughout the UI, and can override the user's theme.") 'theme_colors': set, - //- rjf: exception settings - @default(1) @display_name("Break On Win32 Control-C Exceptions") @description("Code: 0x40010005") + //- rjf: win32 exception settings + @default(1) @display_name("Break on Win32 Control-C Exceptions") @description("Code: 0x40010005") win32_ctrl_c: bool; - @default(1) @display_name("Break On Win32 Control-Break Exceptions") @description("Code: 0x40010008") + @default(1) @display_name("Break on Win32 Control-Break Exceptions") @description("Code: 0x40010008") win32_ctrl_break: bool; - @default(0) @display_name("Break On Win32 WinRT Originate Error Exceptions") @description("Code: 0x40080201") + @default(0) @display_name("Break on Win32 WinRT Originate Error Exceptions") @description("Code: 0x40080201") win32_win_rt_originate_error: bool; - @default(0) @display_name("Break On Win32 WinRT Transform Error Exceptions") @description("Code: 0x40080202") + @default(0) @display_name("Break on Win32 WinRT Transform Error Exceptions") @description("Code: 0x40080202") win32_win_rt_transform_error: bool; - @default(0) @display_name("Break On Win32 RPC Call Cancelled Exceptions") @description("Code: 0x0000071a") + @default(0) @display_name("Break on Win32 RPC Call Cancelled Exceptions") @description("Code: 0x0000071a") win32_rpc_call_cancelled: bool; - @default(0) @display_name("Break On Win32 Data Type Misalignment Exceptions") @description("Code: 0x80000002") + @default(0) @display_name("Break on Win32 Data Type Misalignment Exceptions") @description("Code: 0x80000002") win32_datatype_misalignment: bool; - @default(1) @display_name("Break On Win32 Access Violation Exceptions") @description("Code: 0xc0000005") + @default(1) @display_name("Break on Win32 Access Violation Exceptions") @description("Code: 0xc0000005") win32_access_violation: bool; - @default(0) @display_name("Break On Win32 In Page Error Exceptions") @description("Code: 0xc0000006") + @default(0) @display_name("Break on Win32 In Page Error Exceptions") @description("Code: 0xc0000006") win32_in_page_error: bool; - @default(1) @display_name("Break On Win32 Invalid Handle Specified Exceptions") @description("Code: 0xc0000008") + @default(1) @display_name("Break on Win32 Invalid Handle Specified Exceptions") @description("Code: 0xc0000008") win32_invalid_handle: bool; - @default(0) @display_name("Break On Win32 Not Enough Quota Exceptions") @description("Code: 0xc0000017") + @default(0) @display_name("Break on Win32 Not Enough Quota Exceptions") @description("Code: 0xc0000017") win32_not_enough_quota: bool; - @default(0) @display_name("Break On Win32 Illegal Instruction Exceptions") @description("Code: 0xc000001d") + @default(0) @display_name("Break on Win32 Illegal Instruction Exceptions") @description("Code: 0xc000001d") win32_illegal_instruction: bool; - @default(0) @display_name("Break On Win32 Cannot Continue From Exception Exceptions") @description("Code: 0xc0000025") + @default(0) @display_name("Break on Win32 Cannot Continue From Exception Exceptions") @description("Code: 0xc0000025") win32_cannot_continue_exception: bool; - @default(0) @display_name("Break On Win32 Invalid Exception Disposition Returned By Handler Exceptions") @description("Code: 0xc0000026") + @default(0) @display_name("Break on Win32 Invalid Exception Disposition Returned By Handler Exceptions") @description("Code: 0xc0000026") win32_invalid_exception_disposition: bool; - @default(0) @display_name("Break On Win32 Array Bounds Exceeded Exceptions") @description("Code: 0xc000008c") + @default(0) @display_name("Break on Win32 Array Bounds Exceeded Exceptions") @description("Code: 0xc000008c") win32_array_bounds_exceeded: bool; - @default(0) @display_name("Break On Win32 Floating-Point Denormal Operand Exceptions") @description("Code: 0xc000008d") + @default(0) @display_name("Break on Win32 Floating-Point Denormal Operand Exceptions") @description("Code: 0xc000008d") win32_floating_point_denormal_operand: bool; - @default(0) @display_name("Break On Win32 Floating-Point Division By Zero Exceptions") @description("Code: 0xc000008e") + @default(0) @display_name("Break on Win32 Floating-Point Division By Zero Exceptions") @description("Code: 0xc000008e") win32_floating_point_division_by_zero: bool; - @default(0) @display_name("Break On Win32 Floating-Point Inexact Result Exceptions") @description("Code: 0xc000008f") + @default(0) @display_name("Break on Win32 Floating-Point Inexact Result Exceptions") @description("Code: 0xc000008f") win32_floating_point_inexact_result: bool; - @default(0) @display_name("Break On Win32 Floating-Point Invalid Operation Exceptions") @description("Code: 0xc0000090") + @default(0) @display_name("Break on Win32 Floating-Point Invalid Operation Exceptions") @description("Code: 0xc0000090") win32_floating_point_invalid_operation: bool; - @default(0) @display_name("Break On Win32 Floating-Point Overflow Exceptions") @description("Code: 0xc0000091") + @default(0) @display_name("Break on Win32 Floating-Point Overflow Exceptions") @description("Code: 0xc0000091") win32_floating_point_overflow: bool; - @default(0) @display_name("Break On Win32 Floating-Point Stack Check Exceptions") @description("Code: 0xc0000092") + @default(0) @display_name("Break on Win32 Floating-Point Stack Check Exceptions") @description("Code: 0xc0000092") win32_floating_point_stack_check: bool; - @default(0) @display_name("Break On Win32 Floating-Point Underflow Exceptions") @description("Code: 0xc0000093") + @default(0) @display_name("Break on Win32 Floating-Point Underflow Exceptions") @description("Code: 0xc0000093") win32_floating_point_underflow: bool; - @default(0) @display_name("Break On Win32 Integer Division By Zero Exceptions") @description("Code: 0xc0000094") + @default(0) @display_name("Break on Win32 Integer Division By Zero Exceptions") @description("Code: 0xc0000094") win32_integer_division_by_zero: bool; - @default(0) @display_name("Break On Win32 Integer Overflow Exceptions") @description("Code: 0xc0000095") + @default(0) @display_name("Break on Win32 Integer Overflow Exceptions") @description("Code: 0xc0000095") win32_integer_overflow: bool; - @default(0) @display_name("Break On Win32 Privileged Instruction Exceptions") @description("Code: 0xc0000096") + @default(0) @display_name("Break on Win32 Privileged Instruction Exceptions") @description("Code: 0xc0000096") win32_privileged_instruction: bool; - @default(0) @display_name("Break On Win32 Stack Overflow Exceptions") @description("Code: 0xc00000fd") + @default(0) @display_name("Break on Win32 Stack Overflow Exceptions") @description("Code: 0xc00000fd") win32_stack_overflow: bool; - @default(0) @display_name("Break On Win32 Unable To Locate DLL Exceptions") @description("Code: 0xc0000135") + @default(0) @display_name("Break on Win32 Unable To Locate DLL Exceptions") @description("Code: 0xc0000135") win32_unable_to_locate_dll: bool; - @default(0) @display_name("Break On Win32 Ordinal Not Found Exceptions") @description("Code: 0xc0000138") + @default(0) @display_name("Break on Win32 Ordinal Not Found Exceptions") @description("Code: 0xc0000138") win32_ordinal_not_found: bool; - @default(0) @display_name("Break On Win32 Entry Point Not Found Exceptions") @description("Code: 0xc0000139") + @default(0) @display_name("Break on Win32 Entry Point Not Found Exceptions") @description("Code: 0xc0000139") win32_entry_point_not_found: bool; - @default(0) @display_name("Break On Win32 DLL Initialization Failed Exceptions") @description("Code: 0xc0000142") + @default(0) @display_name("Break on Win32 DLL Initialization Failed Exceptions") @description("Code: 0xc0000142") win32_dll_initialization_failed: bool; - @default(0) @display_name("Break On Win32 Floating Point SSE Multiple Faults Exceptions") @description("Code: 0xc00002b4") + @default(0) @display_name("Break on Win32 Floating Point SSE Multiple Faults Exceptions") @description("Code: 0xc00002b4") win32_floating_point_sse_multiple_faults: bool; - @default(0) @display_name("Break On Win32 Floating Point SSE Multiple Traps Exceptions") @description("Code: 0xc00002b5") + @default(0) @display_name("Break on Win32 Floating Point SSE Multiple Traps Exceptions") @description("Code: 0xc00002b5") win32_floating_point_sse_multiple_traps: bool; - @default(1) @display_name("Break On Win32 Assertion Failed Exceptions") @description("Code: 0xc0000420") + @default(1) @display_name("Break on Win32 Assertion Failed Exceptions") @description("Code: 0xc0000420") win32_assertion_failed: bool; - @default(0) @display_name("Break On Win32 Module Not Found Exceptions") @description("Code: 0xc06d007e") + @default(0) @display_name("Break on Win32 Module Not Found Exceptions") @description("Code: 0xc06d007e") win32_module_not_found: bool; - @default(0) @display_name("Break On Win32 Procedure Not Found Exceptions") @description("Code: 0xc06d007f") + @default(0) @display_name("Break on Win32 Procedure Not Found Exceptions") @description("Code: 0xc06d007f") win32_procedure_not_found: bool; - @default(1) @display_name("Break On Win32 Sanitizer Error Detected Exceptions") @description("Code: 0xe073616e") + @default(1) @display_name("Break on Win32 Sanitizer Error Detected Exceptions") @description("Code: 0xe073616e") win32_sanitizer_error_detected: bool; - @default(0) @display_name("Break On Win32 Sanitizer Raw Access Violation Exceptions") @description("Code: 0xe0736171") + @default(0) @display_name("Break on Win32 Sanitizer Raw Access Violation Exceptions") @description("Code: 0xe0736171") win32_sanitizer_raw_access_violation: bool; - @default(1) @display_name("Break On Win32 DirectX Debug Layer Exceptions") @description("Code: 0x0000087a") + @default(1) @display_name("Break on Win32 DirectX Debug Layer Exceptions") @description("Code: 0x0000087a") win32_directx_debug_layer: bool; + + //- rjf: linux exception settings + @display_name("Break on Linux SIGHUP") @default(1) @description("Hangup detected on controlling terminal or death of controlling process (Code: 1)") + linux_sighup: bool, + @display_name("Break on Linux SIGINT") @default(1) @description("Interrupt from keyboard (Code: 2)") + linux_sigint: bool, + @display_name("Break on Linux SIGQUIT") @default(1) @description("Quit from keyboard (Code: 3)") + linux_sigquit: bool, + @display_name("Break on Linux SIGILL") @default(1) @description("Illegal Instruction (Code: 4)") + linux_sigill: bool, + @display_name("Break on Linux SIGTRAP") @default(1) @description("Trace/breakpoint trap (Code: 5)") + linux_sigtrap: bool, + @display_name("Break on Linux SIGABRT") @default(1) @description("Abort signal (Code: 6)") + linux_sigabrt: bool, + @display_name("Break on Linux SIGBUS") @default(1) @description("Bus error (bad address) (Code: 7)") + linux_sigbus: bool, + @display_name("Break on Linux SIGFPE") @default(1) @description("Erroneous arithmetic operation (Code: 8)") + linux_sigfpe: bool, + @display_name("Break on Linux SIGKILL") @default(1) @description("Kill signal (Code: 9)") + linux_sigkill: bool, + @display_name("Break on Linux SIGUSR1") @default(1) @description("User-defined signal 1 (Code: 10)") + linux_sigusr1: bool, + @display_name("Break on Linux SIGSEGV") @default(1) @description("Invalid memory reference (Code: 11)") + linux_sigsegv: bool, + @display_name("Break on Linux SIGUSR2") @default(1) @description("User-defined signal 2 (Code: 12)") + linux_sigusr2: bool, + @display_name("Break on Linux SIGPIPE") @default(1) @description("Broken pipe; write to pipe with no readers (Code: 13)") + linux_sigpipe: bool, + @display_name("Break on Linux SIGALRM") @default(1) @description("Timer signal (Code: 14)") + linux_sigalrm: bool, + @display_name("Break on Linux SIGTERM") @default(1) @description("Termination signal (Code: 15)") + linux_sigterm: bool, + @display_name("Break on Linux SIGSTKFLT") @default(1) @description("Stack fault on coprocessor (Code: 16)") + linux_sigstkflt: bool, + @display_name("Break on Linux SIGCHLD") @default(1) @description("Child stopped, terminated, or continued (Code: 17)") + linux_sigchld: bool, + @display_name("Break on Linux SIGCONT") @default(1) @description("Continue if stopped (Code: 18)") + linux_sigcont: bool, + @display_name("Break on Linux SIGSTOP") @default(1) @description("Stop process (Code: 19)") + linux_sigstop: bool, + @display_name("Break on Linux SIGTSTP") @default(1) @description("Stop typed at terminal (Code: 20)") + linux_sigtstp: bool, + @display_name("Break on Linux SIGTTIN") @default(1) @description("Terminal input for background process (Code: 21)") + linux_sigttin: bool, + @display_name("Break on Linux SIGTTOU") @default(1) @description("Terminal output for background process (Code: 22)") + linux_sigttou: bool, + @display_name("Break on Linux SIGURG") @default(1) @description("Urgent condition on socket (Code: 23)") + linux_sigurg: bool, + @display_name("Break on Linux SIGXCPU") @default(1) @description("CPU time limit exceeded (Code: 24)") + linux_sigxcpu: bool, + @display_name("Break on Linux SIGXFSZ") @default(1) @description("File size limit exceeded (Code: 25)") + linux_sigxfsz: bool, + @display_name("Break on Linux SIGVTALRM") @default(1) @description("Virtual alarm clock (Code: 26)") + linux_sigvtalrm: bool, + @display_name("Break on Linux SIGPROF") @default(1) @description("Profiling timer expired (Code: 27)") + linux_sigprof: bool, + @display_name("Break on Linux SIGWINCH") @default(1) @description("Window resize signal (Code: 28)") + linux_sigwinch: bool, + @display_name("Break on Linux SIGIO") @default(1) @description("I/O now possible (Code: 29)") + linux_sigio: bool, + @display_name("Break on Linux SIGPWR") @default(1) @description("Power failure (Code: 30)") + linux_sigpwr: bool, + @display_name("Break on Linux SIGSYS") @default(1) @description("Bad system call (Code: 31)") + linux_sigsys: bool, } ``` } diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 753b0150..339b38a7 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -5,6 +5,11 @@ //~ rjf: linux TODO notes // // post initial alpha: +// [ ] 0x1b (SIGPROF) - need to ignore by default +// [ ] line stepping over function epilogue seems to end up just resuming in some cases +// [ ] function breakpoint resolution racing +// [ ] conditional breakpoint evaluation bug +// // [ ] windowing // [ ] wayland // [ ] support diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index a555e1ac..5bec4b84 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -21,6 +21,8 @@ #include "base/base_inc.h" #include "x64/x64_inc.h" +#include "win32/win32_inc.h" +#include "linux/linux_inc.h" #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" @@ -105,7 +107,9 @@ #include "torture.h" #include "base/base_inc.c" -#include "x64/x64.c" +#include "x64/x64_inc.c" +#include "win32/win32_inc.c" +#include "linux/linux_inc.c" #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" From 5acbed46baa0eef7eabf30bba5bf4949ca283707 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 12:58:54 -0600 Subject: [PATCH 220/251] raddbg: adjust default linux signal filtering settings to match lldb's defaults --- src/raddbg/generated/raddbg.meta.c | 2 +- src/raddbg/raddbg.mdesk | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index fa347d9d..87f97090 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -439,7 +439,7 @@ RD_VocabInfo rd_vocab_info_table[370] = RD_NameSchemaInfo rd_name_schema_info_table[39] = { {str8_lit_comp("user"), 0, str8_lit_comp("@expand_commands(edit_user_theme) x:\n{\n //- rjf: animations\n @display_name('Animations') @description(\"Enables animations.\")\n @default(1) 'animations': bool,\n @display_name('Scrolling Animations') @description(\"Enables scrolling animations.\")\n @expand_if(\"$.animations\") @default(1) 'scrolling_animations': bool,\n @display_name('Tooltip Animations') @description(\"Enables tooltip animations.\")\n @expand_if(\"$.animations\") @default(1) 'tooltip_animations': bool,\n @display_name('Menu Animations') @description(\"Enables menu animations.\")\n @expand_if(\"$.animations\") @default(1) 'menu_animations': bool,\n\n //- rjf: fonts\n @display_name('Use Alternative Font For UI') @description(\"Uses an alternative font for non-code UI elements.\")\n @default(1) 'use_alternative_font_for_ui': bool,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('UI Font') @description(\"The name of, or path to, the font used when displaying non-code UI elements.\")\n @default('') 'main_font': string,\n @expand_if(\"$.use_alternative_font_for_ui\") @display_name('Code Font') @description(\"The name of, or path to, the font used when displaying code.\")\n @default('') 'code_font': string,\n @expand_if(\"!$.use_alternative_font_for_ui\") @display_name('Font') @description(\"The name of, or path to, the font used in all UI.\")\n @default('') 'font': string,\n\n //- rjf: theme\n @default(\"Default (Dark)\") @display_name('User Theme')\n @description(\"The user's theme, which describes all colors used throughout the UI.\")\n 'theme': string,\n @no_expand @display_name('User Theme')\n 'theme_colors': set,\n\n //- rjf: auto eval\n @display_name('Show Auto Watches In Source / Disassembly') @description(\"Enables the display of auto watch expressions inline in source and disassembly views.\") @default(1)\n 'show_autos_in_src_and_disasm': bool,\n\n //- rjf: autocompletion\n @display_name('Autocompletion Lister') @description(\"Enables the autocompletion lister while typing expressions.\") @default(1)\n 'autocompletion_lister': bool,\n @display_name('View Call Argument Helper') @description(\"Enables the view call argument helper, which shows view arguments and documentation, while typing expressions.\") @default(1)\n 'view_call_argument_helper': bool,\n\n //- rjf: scope decorations\n @default(1) @display_name('Cursor Scope Lines') @description(\"Controls whether or not scopes containing the cursor in text views are drawn.\")\n 'cursor_scope_lines': bool,\n @default(1) @display_name('Cursor Scope End Annotations') @description(\"Controls whether or not ending annotations for scopes containing the cursor are drawn.\")\n 'cursor_scope_end_annotations': bool,\n\n //- rjf: cursor decorations\n @default(1) @display_name('Cursor Trail') @expand_if(\"$.animations\") @description(\"Controls whether or not a movement trail animation of the cursor is drawn.\")\n 'cursor_trail': bool,\n\n //- rjf: thread & breakpoint decorations\n @default(1) @display_name('Thread Lines') @description(\"Controls whether or not a long horizontal line is drawn before the next line or instruction that the selected thread will execute in source and disassembly views.\")\n 'thread_lines': bool,\n @default(1) @display_name('Thread Glow') @description(\"Controls whether or not a glowing effect is drawn on the selected thread in source and disassembly views.\")\n 'thread_glow': bool,\n @default(1) @display_name('Breakpoint Lines') @description(\"Controls whether or not a long horizontal line is drawn before the line or instruction at which a breakpoint is placed, in source and disassembly views.\")\n 'breakpoint_lines': bool,\n @default(1) @display_name('Breakpoint Glow') @description(\"Controls whether or not a glowing effect is drawn on breakpoints in source and disassembly views.\")\n 'breakpoint_glow': bool,\n\n //- rjf: occluding background settings\n @default(0) @display_name('Opaque Backgrounds') @description(\"Controls whether or not all floating background colors are forced to be fully opaque.\")\n 'opaque_backgrounds': bool,\n @default(1) @display_name('Background Blur') @description(\"Controls whether or not occluded regions behind floating elements are blurred.\")\n 'background_blur': bool,\n\n //- rjf: appearance settings\n @default(1) @display_name('Drop Shadows') @description(\"Controls whether or not drop shadows are drawn.\")\n 'drop_shadows': bool,\n @default(1.f) @display_name('Rounded Corner Amount') @description(\"Controls the degree to which UI corners are rounded.\")\n 'rounded_corner_amount': @range[0, 1] f32,\n\n //- rjf: code formatting settings\n @default(2) @display_name('User Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: windows style menu bar\n @default(1) @display_name('Focus Menu Bar With Alt') @description(\"Mimics standard Windows behavior of focusing the menu bar using the Alt key.\")\n 'focus_menu_bar_with_alt': bool,\n\n //- rjf: native filesystem dialogues\n @default(0) @display_name('Use Native File System Dialog') @description(\"Uses the operating system's file system dialog box, rather than the debugger's built-in UI.\")\n 'use_native_file_system_dialog': bool,\n\n //- rjf: transient tabs\n @default(1) @display_name('Transient Tabs') @description(\"When snapping to source code locations, opens new files in a 'transient' tab if they are not already open. Transient tabs are replaced on subsequent snaps automatically.\")\n 'transient_tabs': bool,\n\n //- rjf: auto-load last project\n @default(0) @display_name('Auto Load Last Project') @description(\"Enables loading the most recently opened project, if one is not specified from the command line.\")\n 'auto_load_last_project': bool,\n\n //- rjf: symbol server\n @default(0) @display_name('Auto Download Debug Info') @description(\"Automatically tries to download missing debug info from symbol server(s).\")\n 'auto_download_debug_info': bool,\n\n //- rjf: breakpoint behavior\n @default(0) @display_name('Lock New Breakpoints To Selected Thread') @description(\"When placing breakpoints, locks them to the selected thread by default.\")\n 'lock_new_breakpoints_to_selected_thread': bool,\n\n //- rjf: update check\n @default(1) @display_name('Check For Updates') @description(\"Enables a small network request to check if there is a newer release version available.\")\n 'check_for_updates': bool,\n\n //- rjf: system installation\n @display_name('Install To System') @description(\"Controls whether or not associated installation files are created on the system, for tighter integration with the operating system shell.\") 'install_to_system': bool,\n}\n")}, -{str8_lit_comp("project"), 0, str8_lit_comp("@expand_commands(edit_project_theme) x:\n{\n @display_name('Project Name') 'name': string,\n @default(2) @display_name('Project Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: visualizers\n @display_name('Display Pointer Addresses Before Contents') @description(\"When visualizing pointers, always shows the address first, before showing contents at the pointer's address.\")\n @default(0) display_pointer_addresses_before_contents: bool,\n @display_name('Use Default C++ STL Type Visualizers') @description(\"Enables the built-in type views for C++ STL types.\")\n @default(1) use_default_stl_type_views: bool,\n @display_name('Use Default Unreal Engine Type Visualizers') @description(\"Enables the built-in type views for Unreal Engine types.\")\n @default(1) use_default_ue_type_views: bool,\n\n //- rjf: theme\n @default(\"None\") @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme': string,\n @no_expand @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme_colors': set,\n\n //- rjf: win32 exception settings\n @default(1) @display_name(\"Break on Win32 Control-C Exceptions\") @description(\"Code: 0x40010005\")\n win32_ctrl_c: bool;\n @default(1) @display_name(\"Break on Win32 Control-Break Exceptions\") @description(\"Code: 0x40010008\")\n win32_ctrl_break: bool;\n @default(0) @display_name(\"Break on Win32 WinRT Originate Error Exceptions\") @description(\"Code: 0x40080201\")\n win32_win_rt_originate_error: bool;\n @default(0) @display_name(\"Break on Win32 WinRT Transform Error Exceptions\") @description(\"Code: 0x40080202\")\n win32_win_rt_transform_error: bool;\n @default(0) @display_name(\"Break on Win32 RPC Call Cancelled Exceptions\") @description(\"Code: 0x0000071a\")\n win32_rpc_call_cancelled: bool;\n @default(0) @display_name(\"Break on Win32 Data Type Misalignment Exceptions\") @description(\"Code: 0x80000002\")\n win32_datatype_misalignment: bool;\n @default(1) @display_name(\"Break on Win32 Access Violation Exceptions\") @description(\"Code: 0xc0000005\")\n win32_access_violation: bool;\n @default(0) @display_name(\"Break on Win32 In Page Error Exceptions\") @description(\"Code: 0xc0000006\")\n win32_in_page_error: bool;\n @default(1) @display_name(\"Break on Win32 Invalid Handle Specified Exceptions\") @description(\"Code: 0xc0000008\")\n win32_invalid_handle: bool;\n @default(0) @display_name(\"Break on Win32 Not Enough Quota Exceptions\") @description(\"Code: 0xc0000017\")\n win32_not_enough_quota: bool;\n @default(0) @display_name(\"Break on Win32 Illegal Instruction Exceptions\") @description(\"Code: 0xc000001d\")\n win32_illegal_instruction: bool;\n @default(0) @display_name(\"Break on Win32 Cannot Continue From Exception Exceptions\") @description(\"Code: 0xc0000025\")\n win32_cannot_continue_exception: bool;\n @default(0) @display_name(\"Break on Win32 Invalid Exception Disposition Returned By Handler Exceptions\") @description(\"Code: 0xc0000026\")\n win32_invalid_exception_disposition: bool;\n @default(0) @display_name(\"Break on Win32 Array Bounds Exceeded Exceptions\") @description(\"Code: 0xc000008c\")\n win32_array_bounds_exceeded: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Denormal Operand Exceptions\") @description(\"Code: 0xc000008d\")\n win32_floating_point_denormal_operand: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Division By Zero Exceptions\") @description(\"Code: 0xc000008e\")\n win32_floating_point_division_by_zero: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Inexact Result Exceptions\") @description(\"Code: 0xc000008f\")\n win32_floating_point_inexact_result: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Invalid Operation Exceptions\") @description(\"Code: 0xc0000090\")\n win32_floating_point_invalid_operation: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Overflow Exceptions\") @description(\"Code: 0xc0000091\")\n win32_floating_point_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Stack Check Exceptions\") @description(\"Code: 0xc0000092\")\n win32_floating_point_stack_check: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Underflow Exceptions\") @description(\"Code: 0xc0000093\")\n win32_floating_point_underflow: bool;\n @default(0) @display_name(\"Break on Win32 Integer Division By Zero Exceptions\") @description(\"Code: 0xc0000094\")\n win32_integer_division_by_zero: bool;\n @default(0) @display_name(\"Break on Win32 Integer Overflow Exceptions\") @description(\"Code: 0xc0000095\")\n win32_integer_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Privileged Instruction Exceptions\") @description(\"Code: 0xc0000096\")\n win32_privileged_instruction: bool;\n @default(0) @display_name(\"Break on Win32 Stack Overflow Exceptions\") @description(\"Code: 0xc00000fd\")\n win32_stack_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Unable To Locate DLL Exceptions\") @description(\"Code: 0xc0000135\")\n win32_unable_to_locate_dll: bool;\n @default(0) @display_name(\"Break on Win32 Ordinal Not Found Exceptions\") @description(\"Code: 0xc0000138\")\n win32_ordinal_not_found: bool;\n @default(0) @display_name(\"Break on Win32 Entry Point Not Found Exceptions\") @description(\"Code: 0xc0000139\")\n win32_entry_point_not_found: bool;\n @default(0) @display_name(\"Break on Win32 DLL Initialization Failed Exceptions\") @description(\"Code: 0xc0000142\")\n win32_dll_initialization_failed: bool;\n @default(0) @display_name(\"Break on Win32 Floating Point SSE Multiple Faults Exceptions\") @description(\"Code: 0xc00002b4\")\n win32_floating_point_sse_multiple_faults: bool;\n @default(0) @display_name(\"Break on Win32 Floating Point SSE Multiple Traps Exceptions\") @description(\"Code: 0xc00002b5\")\n win32_floating_point_sse_multiple_traps: bool;\n @default(1) @display_name(\"Break on Win32 Assertion Failed Exceptions\") @description(\"Code: 0xc0000420\")\n win32_assertion_failed: bool;\n @default(0) @display_name(\"Break on Win32 Module Not Found Exceptions\") @description(\"Code: 0xc06d007e\")\n win32_module_not_found: bool;\n @default(0) @display_name(\"Break on Win32 Procedure Not Found Exceptions\") @description(\"Code: 0xc06d007f\")\n win32_procedure_not_found: bool;\n @default(1) @display_name(\"Break on Win32 Sanitizer Error Detected Exceptions\") @description(\"Code: 0xe073616e\")\n win32_sanitizer_error_detected: bool;\n @default(0) @display_name(\"Break on Win32 Sanitizer Raw Access Violation Exceptions\") @description(\"Code: 0xe0736171\")\n win32_sanitizer_raw_access_violation: bool;\n @default(1) @display_name(\"Break on Win32 DirectX Debug Layer Exceptions\") @description(\"Code: 0x0000087a\")\n win32_directx_debug_layer: bool;\n\n //- rjf: linux exception settings\n @display_name(\"Break on Linux SIGHUP\") @default(1) @description(\"Hangup detected on controlling terminal or death of controlling process (Code: 1)\")\n linux_sighup: bool,\n @display_name(\"Break on Linux SIGINT\") @default(1) @description(\"Interrupt from keyboard (Code: 2)\")\n linux_sigint: bool,\n @display_name(\"Break on Linux SIGQUIT\") @default(1) @description(\"Quit from keyboard (Code: 3)\")\n linux_sigquit: bool,\n @display_name(\"Break on Linux SIGILL\") @default(1) @description(\"Illegal Instruction (Code: 4)\")\n linux_sigill: bool,\n @display_name(\"Break on Linux SIGTRAP\") @default(1) @description(\"Trace/breakpoint trap (Code: 5)\")\n linux_sigtrap: bool,\n @display_name(\"Break on Linux SIGABRT\") @default(1) @description(\"Abort signal (Code: 6)\")\n linux_sigabrt: bool,\n @display_name(\"Break on Linux SIGBUS\") @default(1) @description(\"Bus error (bad address) (Code: 7)\")\n linux_sigbus: bool,\n @display_name(\"Break on Linux SIGFPE\") @default(1) @description(\"Erroneous arithmetic operation (Code: 8)\")\n linux_sigfpe: bool,\n @display_name(\"Break on Linux SIGKILL\") @default(1) @description(\"Kill signal (Code: 9)\")\n linux_sigkill: bool,\n @display_name(\"Break on Linux SIGUSR1\") @default(1) @description(\"User-defined signal 1 (Code: 10)\")\n linux_sigusr1: bool,\n @display_name(\"Break on Linux SIGSEGV\") @default(1) @description(\"Invalid memory reference (Code: 11)\")\n linux_sigsegv: bool,\n @display_name(\"Break on Linux SIGUSR2\") @default(1) @description(\"User-defined signal 2 (Code: 12)\")\n linux_sigusr2: bool,\n @display_name(\"Break on Linux SIGPIPE\") @default(1) @description(\"Broken pipe; write to pipe with no readers (Code: 13)\")\n linux_sigpipe: bool,\n @display_name(\"Break on Linux SIGALRM\") @default(1) @description(\"Timer signal (Code: 14)\")\n linux_sigalrm: bool,\n @display_name(\"Break on Linux SIGTERM\") @default(1) @description(\"Termination signal (Code: 15)\")\n linux_sigterm: bool,\n @display_name(\"Break on Linux SIGSTKFLT\") @default(1) @description(\"Stack fault on coprocessor (Code: 16)\")\n linux_sigstkflt: bool,\n @display_name(\"Break on Linux SIGCHLD\") @default(1) @description(\"Child stopped, terminated, or continued (Code: 17)\")\n linux_sigchld: bool,\n @display_name(\"Break on Linux SIGCONT\") @default(1) @description(\"Continue if stopped (Code: 18)\")\n linux_sigcont: bool,\n @display_name(\"Break on Linux SIGSTOP\") @default(1) @description(\"Stop process (Code: 19)\")\n linux_sigstop: bool,\n @display_name(\"Break on Linux SIGTSTP\") @default(1) @description(\"Stop typed at terminal (Code: 20)\")\n linux_sigtstp: bool,\n @display_name(\"Break on Linux SIGTTIN\") @default(1) @description(\"Terminal input for background process (Code: 21)\")\n linux_sigttin: bool,\n @display_name(\"Break on Linux SIGTTOU\") @default(1) @description(\"Terminal output for background process (Code: 22)\")\n linux_sigttou: bool,\n @display_name(\"Break on Linux SIGURG\") @default(1) @description(\"Urgent condition on socket (Code: 23)\")\n linux_sigurg: bool,\n @display_name(\"Break on Linux SIGXCPU\") @default(1) @description(\"CPU time limit exceeded (Code: 24)\")\n linux_sigxcpu: bool,\n @display_name(\"Break on Linux SIGXFSZ\") @default(1) @description(\"File size limit exceeded (Code: 25)\")\n linux_sigxfsz: bool,\n @display_name(\"Break on Linux SIGVTALRM\") @default(1) @description(\"Virtual alarm clock (Code: 26)\")\n linux_sigvtalrm: bool,\n @display_name(\"Break on Linux SIGPROF\") @default(1) @description(\"Profiling timer expired (Code: 27)\")\n linux_sigprof: bool,\n @display_name(\"Break on Linux SIGWINCH\") @default(1) @description(\"Window resize signal (Code: 28)\")\n linux_sigwinch: bool,\n @display_name(\"Break on Linux SIGIO\") @default(1) @description(\"I/O now possible (Code: 29)\")\n linux_sigio: bool,\n @display_name(\"Break on Linux SIGPWR\") @default(1) @description(\"Power failure (Code: 30)\")\n linux_sigpwr: bool,\n @display_name(\"Break on Linux SIGSYS\") @default(1) @description(\"Bad system call (Code: 31)\")\n linux_sigsys: bool,\n}\n")}, +{str8_lit_comp("project"), 0, str8_lit_comp("@expand_commands(edit_project_theme) x:\n{\n @display_name('Project Name') 'name': string,\n @default(2) @display_name('Project Tab Width') 'tab_width': @range[1, 32] u64,\n\n //- rjf: visualizers\n @display_name('Display Pointer Addresses Before Contents') @description(\"When visualizing pointers, always shows the address first, before showing contents at the pointer's address.\")\n @default(0) display_pointer_addresses_before_contents: bool,\n @display_name('Use Default C++ STL Type Visualizers') @description(\"Enables the built-in type views for C++ STL types.\")\n @default(1) use_default_stl_type_views: bool,\n @display_name('Use Default Unreal Engine Type Visualizers') @description(\"Enables the built-in type views for Unreal Engine types.\")\n @default(1) use_default_ue_type_views: bool,\n\n //- rjf: theme\n @default(\"None\") @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme': string,\n @no_expand @display_name('Project Theme') @description(\"The project's theme, which describes all colors used throughout the UI, and can override the user's theme.\")\n 'theme_colors': set,\n\n //- rjf: win32 exception settings\n @default(1) @display_name(\"Break on Win32 Control-C Exceptions\") @description(\"Code: 0x40010005\")\n win32_ctrl_c: bool;\n @default(1) @display_name(\"Break on Win32 Control-Break Exceptions\") @description(\"Code: 0x40010008\")\n win32_ctrl_break: bool;\n @default(0) @display_name(\"Break on Win32 WinRT Originate Error Exceptions\") @description(\"Code: 0x40080201\")\n win32_win_rt_originate_error: bool;\n @default(0) @display_name(\"Break on Win32 WinRT Transform Error Exceptions\") @description(\"Code: 0x40080202\")\n win32_win_rt_transform_error: bool;\n @default(0) @display_name(\"Break on Win32 RPC Call Cancelled Exceptions\") @description(\"Code: 0x0000071a\")\n win32_rpc_call_cancelled: bool;\n @default(0) @display_name(\"Break on Win32 Data Type Misalignment Exceptions\") @description(\"Code: 0x80000002\")\n win32_datatype_misalignment: bool;\n @default(1) @display_name(\"Break on Win32 Access Violation Exceptions\") @description(\"Code: 0xc0000005\")\n win32_access_violation: bool;\n @default(0) @display_name(\"Break on Win32 In Page Error Exceptions\") @description(\"Code: 0xc0000006\")\n win32_in_page_error: bool;\n @default(1) @display_name(\"Break on Win32 Invalid Handle Specified Exceptions\") @description(\"Code: 0xc0000008\")\n win32_invalid_handle: bool;\n @default(0) @display_name(\"Break on Win32 Not Enough Quota Exceptions\") @description(\"Code: 0xc0000017\")\n win32_not_enough_quota: bool;\n @default(0) @display_name(\"Break on Win32 Illegal Instruction Exceptions\") @description(\"Code: 0xc000001d\")\n win32_illegal_instruction: bool;\n @default(0) @display_name(\"Break on Win32 Cannot Continue From Exception Exceptions\") @description(\"Code: 0xc0000025\")\n win32_cannot_continue_exception: bool;\n @default(0) @display_name(\"Break on Win32 Invalid Exception Disposition Returned By Handler Exceptions\") @description(\"Code: 0xc0000026\")\n win32_invalid_exception_disposition: bool;\n @default(0) @display_name(\"Break on Win32 Array Bounds Exceeded Exceptions\") @description(\"Code: 0xc000008c\")\n win32_array_bounds_exceeded: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Denormal Operand Exceptions\") @description(\"Code: 0xc000008d\")\n win32_floating_point_denormal_operand: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Division By Zero Exceptions\") @description(\"Code: 0xc000008e\")\n win32_floating_point_division_by_zero: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Inexact Result Exceptions\") @description(\"Code: 0xc000008f\")\n win32_floating_point_inexact_result: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Invalid Operation Exceptions\") @description(\"Code: 0xc0000090\")\n win32_floating_point_invalid_operation: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Overflow Exceptions\") @description(\"Code: 0xc0000091\")\n win32_floating_point_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Stack Check Exceptions\") @description(\"Code: 0xc0000092\")\n win32_floating_point_stack_check: bool;\n @default(0) @display_name(\"Break on Win32 Floating-Point Underflow Exceptions\") @description(\"Code: 0xc0000093\")\n win32_floating_point_underflow: bool;\n @default(0) @display_name(\"Break on Win32 Integer Division By Zero Exceptions\") @description(\"Code: 0xc0000094\")\n win32_integer_division_by_zero: bool;\n @default(0) @display_name(\"Break on Win32 Integer Overflow Exceptions\") @description(\"Code: 0xc0000095\")\n win32_integer_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Privileged Instruction Exceptions\") @description(\"Code: 0xc0000096\")\n win32_privileged_instruction: bool;\n @default(0) @display_name(\"Break on Win32 Stack Overflow Exceptions\") @description(\"Code: 0xc00000fd\")\n win32_stack_overflow: bool;\n @default(0) @display_name(\"Break on Win32 Unable To Locate DLL Exceptions\") @description(\"Code: 0xc0000135\")\n win32_unable_to_locate_dll: bool;\n @default(0) @display_name(\"Break on Win32 Ordinal Not Found Exceptions\") @description(\"Code: 0xc0000138\")\n win32_ordinal_not_found: bool;\n @default(0) @display_name(\"Break on Win32 Entry Point Not Found Exceptions\") @description(\"Code: 0xc0000139\")\n win32_entry_point_not_found: bool;\n @default(0) @display_name(\"Break on Win32 DLL Initialization Failed Exceptions\") @description(\"Code: 0xc0000142\")\n win32_dll_initialization_failed: bool;\n @default(0) @display_name(\"Break on Win32 Floating Point SSE Multiple Faults Exceptions\") @description(\"Code: 0xc00002b4\")\n win32_floating_point_sse_multiple_faults: bool;\n @default(0) @display_name(\"Break on Win32 Floating Point SSE Multiple Traps Exceptions\") @description(\"Code: 0xc00002b5\")\n win32_floating_point_sse_multiple_traps: bool;\n @default(1) @display_name(\"Break on Win32 Assertion Failed Exceptions\") @description(\"Code: 0xc0000420\")\n win32_assertion_failed: bool;\n @default(0) @display_name(\"Break on Win32 Module Not Found Exceptions\") @description(\"Code: 0xc06d007e\")\n win32_module_not_found: bool;\n @default(0) @display_name(\"Break on Win32 Procedure Not Found Exceptions\") @description(\"Code: 0xc06d007f\")\n win32_procedure_not_found: bool;\n @default(1) @display_name(\"Break on Win32 Sanitizer Error Detected Exceptions\") @description(\"Code: 0xe073616e\")\n win32_sanitizer_error_detected: bool;\n @default(0) @display_name(\"Break on Win32 Sanitizer Raw Access Violation Exceptions\") @description(\"Code: 0xe0736171\")\n win32_sanitizer_raw_access_violation: bool;\n @default(1) @display_name(\"Break on Win32 DirectX Debug Layer Exceptions\") @description(\"Code: 0x0000087a\")\n win32_directx_debug_layer: bool;\n\n //- rjf: linux exception settings\n @display_name(\"Break on Linux SIGHUP\") @default(1) @description(\"Hangup detected on controlling terminal or death of controlling process (Code: 1)\")\n linux_sighup: bool,\n @display_name(\"Break on Linux SIGINT\") @default(1) @description(\"Interrupt from keyboard (Code: 2)\")\n linux_sigint: bool,\n @display_name(\"Break on Linux SIGQUIT\") @default(1) @description(\"Quit from keyboard (Code: 3)\")\n linux_sigquit: bool,\n @display_name(\"Break on Linux SIGILL\") @default(1) @description(\"Illegal Instruction (Code: 4)\")\n linux_sigill: bool,\n @display_name(\"Break on Linux SIGTRAP\") @default(1) @description(\"Trace/breakpoint trap (Code: 5)\")\n linux_sigtrap: bool,\n @display_name(\"Break on Linux SIGABRT\") @default(1) @description(\"Abort signal (Code: 6)\")\n linux_sigabrt: bool,\n @display_name(\"Break on Linux SIGBUS\") @default(1) @description(\"Bus error (bad address) (Code: 7)\")\n linux_sigbus: bool,\n @display_name(\"Break on Linux SIGFPE\") @default(1) @description(\"Erroneous arithmetic operation (Code: 8)\")\n linux_sigfpe: bool,\n @display_name(\"Break on Linux SIGKILL\") @default(1) @description(\"Kill signal (Code: 9)\")\n linux_sigkill: bool,\n @display_name(\"Break on Linux SIGUSR1\") @default(1) @description(\"User-defined signal 1 (Code: 10)\")\n linux_sigusr1: bool,\n @display_name(\"Break on Linux SIGSEGV\") @default(1) @description(\"Invalid memory reference (Code: 11)\")\n linux_sigsegv: bool,\n @display_name(\"Break on Linux SIGUSR2\") @default(1) @description(\"User-defined signal 2 (Code: 12)\")\n linux_sigusr2: bool,\n @display_name(\"Break on Linux SIGPIPE\") @default(0) @description(\"Broken pipe; write to pipe with no readers (Code: 13)\")\n linux_sigpipe: bool,\n @display_name(\"Break on Linux SIGALRM\") @default(0) @description(\"Timer signal (Code: 14)\")\n linux_sigalrm: bool,\n @display_name(\"Break on Linux SIGTERM\") @default(1) @description(\"Termination signal (Code: 15)\")\n linux_sigterm: bool,\n @display_name(\"Break on Linux SIGSTKFLT\") @default(1) @description(\"Stack fault on coprocessor (Code: 16)\")\n linux_sigstkflt: bool,\n @display_name(\"Break on Linux SIGCHLD\") @default(0) @description(\"Child stopped, terminated, or continued (Code: 17)\")\n linux_sigchld: bool,\n @display_name(\"Break on Linux SIGCONT\") @default(0) @description(\"Continue if stopped (Code: 18)\")\n linux_sigcont: bool,\n @display_name(\"Break on Linux SIGSTOP\") @default(1) @description(\"Stop process (Code: 19)\")\n linux_sigstop: bool,\n @display_name(\"Break on Linux SIGTSTP\") @default(1) @description(\"Stop typed at terminal (Code: 20)\")\n linux_sigtstp: bool,\n @display_name(\"Break on Linux SIGTTIN\") @default(1) @description(\"Terminal input for background process (Code: 21)\")\n linux_sigttin: bool,\n @display_name(\"Break on Linux SIGTTOU\") @default(1) @description(\"Terminal output for background process (Code: 22)\")\n linux_sigttou: bool,\n @display_name(\"Break on Linux SIGURG\") @default(0) @description(\"Urgent condition on socket (Code: 23)\")\n linux_sigurg: bool,\n @display_name(\"Break on Linux SIGXCPU\") @default(1) @description(\"CPU time limit exceeded (Code: 24)\")\n linux_sigxcpu: bool,\n @display_name(\"Break on Linux SIGXFSZ\") @default(1) @description(\"File size limit exceeded (Code: 25)\")\n linux_sigxfsz: bool,\n @display_name(\"Break on Linux SIGVTALRM\") @default(0) @description(\"Virtual alarm clock (Code: 26)\")\n linux_sigvtalrm: bool,\n @display_name(\"Break on Linux SIGPROF\") @default(0) @description(\"Profiling timer expired (Code: 27)\")\n linux_sigprof: bool,\n @display_name(\"Break on Linux SIGWINCH\") @default(0) @description(\"Window resize signal (Code: 28)\")\n linux_sigwinch: bool,\n @display_name(\"Break on Linux SIGIO\") @default(0) @description(\"I/O now possible (Code: 29)\")\n linux_sigio: bool,\n @display_name(\"Break on Linux SIGPWR\") @default(1) @description(\"Power failure (Code: 30)\")\n linux_sigpwr: bool,\n @display_name(\"Break on Linux SIGSYS\") @default(1) @description(\"Bad system call (Code: 31)\")\n linux_sigsys: bool,\n}\n")}, {str8_lit_comp("theme_color"), 0, str8_lit_comp("@collection_commands(add_theme_color, fork_theme, save_theme, save_and_set_theme)\n@row_commands(duplicate_cfg, remove_cfg)\nx:\n{\n @display_name('Tags') tags: string,\n @display_name('Value') value: @color @hex u32,\n}\n")}, {str8_lit_comp("window"), 0, str8_lit_comp("x:\n{\n //- rjf: text rasterization settings\n @default(1) @display_name('Smooth UI Text') @description(\"Controls whether or not UI text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_ui_text': bool,\n @default(1) @display_name('Hint UI Text') @description(\"Controls whether or not UI text is hinted, for better text readability at small sizes.\")\n 'hint_ui_text': bool,\n @default(0) @display_name('Smooth Code Text') @description(\"Controls whether or not code text is fully anti-aliased, for a smoother appearance.\")\n 'smooth_code_text': bool,\n @default(1) @display_name('Hint Code Text') @description(\"Controls whether or not code text is hinted, for better text readability at small sizes.\")\n 'hint_code_text': bool,\n @default(11) @display_name('Window Font Size') @description(\"Controls the window's default font size. Does not apply to tabs with their own font size set.\")\n 'font_size': @range[6, 72] u64,\n\n //- rjf: size settings\n @default(3.f) @display_name('Window Row Height') @description(\"Controls the window's default row height, in multiples of the font size. Does not apply to tabs with their own row height set.\")\n 'row_height': @range[1.75f, 5.f] f32,\n @default(3.f) @description(\"Controls the height of tabs, in multiples of the font size.\")\n 'tab_height': @range[1.75f, 5.f] f32,\n\n //- rjf: theme settings\n @default(1) @display_name('Use Project Theme') @description(\"Prefer using the project theme for this window, if any. If off, only the user's theme settings will be used.\")\n 'use_project_theme': bool,\n}\n")}, {str8_lit_comp("tab"), 0, str8_lit_comp("@row_commands(@file copy_tab_full_path, @file show_file_in_explorer, duplicate_tab, close_tab)\nx:\n{\n @override @display_name('Tab Font Size') @description(\"Controls the tab's font size.\") @no_callee_helper\n 'font_size': @range[6, 72] u64,\n}\n")}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 6d970676..5dbf075f 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -469,17 +469,17 @@ RD_VocabTable: linux_sigsegv: bool, @display_name("Break on Linux SIGUSR2") @default(1) @description("User-defined signal 2 (Code: 12)") linux_sigusr2: bool, - @display_name("Break on Linux SIGPIPE") @default(1) @description("Broken pipe; write to pipe with no readers (Code: 13)") + @display_name("Break on Linux SIGPIPE") @default(0) @description("Broken pipe; write to pipe with no readers (Code: 13)") linux_sigpipe: bool, - @display_name("Break on Linux SIGALRM") @default(1) @description("Timer signal (Code: 14)") + @display_name("Break on Linux SIGALRM") @default(0) @description("Timer signal (Code: 14)") linux_sigalrm: bool, @display_name("Break on Linux SIGTERM") @default(1) @description("Termination signal (Code: 15)") linux_sigterm: bool, @display_name("Break on Linux SIGSTKFLT") @default(1) @description("Stack fault on coprocessor (Code: 16)") linux_sigstkflt: bool, - @display_name("Break on Linux SIGCHLD") @default(1) @description("Child stopped, terminated, or continued (Code: 17)") + @display_name("Break on Linux SIGCHLD") @default(0) @description("Child stopped, terminated, or continued (Code: 17)") linux_sigchld: bool, - @display_name("Break on Linux SIGCONT") @default(1) @description("Continue if stopped (Code: 18)") + @display_name("Break on Linux SIGCONT") @default(0) @description("Continue if stopped (Code: 18)") linux_sigcont: bool, @display_name("Break on Linux SIGSTOP") @default(1) @description("Stop process (Code: 19)") linux_sigstop: bool, @@ -489,19 +489,19 @@ RD_VocabTable: linux_sigttin: bool, @display_name("Break on Linux SIGTTOU") @default(1) @description("Terminal output for background process (Code: 22)") linux_sigttou: bool, - @display_name("Break on Linux SIGURG") @default(1) @description("Urgent condition on socket (Code: 23)") + @display_name("Break on Linux SIGURG") @default(0) @description("Urgent condition on socket (Code: 23)") linux_sigurg: bool, @display_name("Break on Linux SIGXCPU") @default(1) @description("CPU time limit exceeded (Code: 24)") linux_sigxcpu: bool, @display_name("Break on Linux SIGXFSZ") @default(1) @description("File size limit exceeded (Code: 25)") linux_sigxfsz: bool, - @display_name("Break on Linux SIGVTALRM") @default(1) @description("Virtual alarm clock (Code: 26)") + @display_name("Break on Linux SIGVTALRM") @default(0) @description("Virtual alarm clock (Code: 26)") linux_sigvtalrm: bool, - @display_name("Break on Linux SIGPROF") @default(1) @description("Profiling timer expired (Code: 27)") + @display_name("Break on Linux SIGPROF") @default(0) @description("Profiling timer expired (Code: 27)") linux_sigprof: bool, - @display_name("Break on Linux SIGWINCH") @default(1) @description("Window resize signal (Code: 28)") + @display_name("Break on Linux SIGWINCH") @default(0) @description("Window resize signal (Code: 28)") linux_sigwinch: bool, - @display_name("Break on Linux SIGIO") @default(1) @description("I/O now possible (Code: 29)") + @display_name("Break on Linux SIGIO") @default(0) @description("I/O now possible (Code: 29)") linux_sigio: bool, @display_name("Break on Linux SIGPWR") @default(1) @description("Power failure (Code: 30)") linux_sigpwr: bool, From b7ee36e35e228e0bd1fb4465521bc6ed311781e7 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 13:11:58 -0600 Subject: [PATCH 221/251] linux_demon: do not report exception repeats on first-chance signals --- project.raddbg | 4 ++-- src/linux/demon/linux_demon.c | 36 ----------------------------------- 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/project.raddbg b/project.raddbg index a0b9f4ed..04783f6f 100755 --- a/project.raddbg +++ b/project.raddbg @@ -1,4 +1,4 @@ -// raddbg 0.9.29 project file +// raddbg 0.9.30 project file name: "The RAD Debugger" target: @@ -7,7 +7,6 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { @@ -36,6 +35,7 @@ target: executable: "build/raddbg" working_directory: build arguments: "--user:raddbg_test.user" + enabled: 1 } target: { diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 2d5429db..9c7037a0 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -2293,41 +2293,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) { thread->pass_through_signal = 1; thread->pass_through_signo = wstopsig; - local_persist B8 is_repeatable[] = - { - 0, // null - 1, // SIGHUP 1 - 1, // SIGINT 2 - 1, // SIGQUIT 3 - 1, // SIGILL 4 - 1, // SIGTRAP 5 - 1, // SIGABRT/SIGIOT 6 - 1, // SIGBUS 7 - 1, // SIGFPE 8 - 1, // SIGKILL 9 - 1, // SIGUSR1 10 - 1, // SIGSEGV 11 - 1, // SIGUSR2 12 - 1, // SIGPIPE 13 - 1, // SIGALRM 14 - 1, // SIGTERM 15 - 1, // SIGSTKFLT 16 - 0, // SIGCHLD 17 - 0, // SIGCONT 18 - 1, // SIGSTOP 19 - 1, // SIGTSTP 20 - 1, // SIGTTIN 21 - 1, // SIGTTOU 22 - 0, // SIGURG 23 - 1, // SIGXCPU 24 - 1, // SIGXFSZ 25 - 1, // SIGVTALRM 26 - 1, // SIGPROF 27 - 0, // SIGWINCH 28 - 1, // SIGIO 29 - 1, // SIGPWR 30 - 1, // SIGSYS/SIGUNUSED31 - }; DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_Exception; e->process = lnx_dmn_handle_from_process(thread->process); @@ -2335,7 +2300,6 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) e->instruction_pointer = lnx_dmn_ip_from_thread(thread); e->address = e->instruction_pointer; e->code = wstopsig; - e->exception_repeated = wstopsig < ArrayCount(is_repeatable) ? is_repeatable[wstopsig] : 0; if(wstopsig == SIGSEGV) { siginfo_t si = {0}; From 53192ff3fa9e12d90d36be6d63b49ec8d1e06d6a Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 13:20:16 -0600 Subject: [PATCH 222/251] linux_demon: detecting first vs. second chance signals correctly; feed back in exception events. used for exception event filtering logic --- src/linux/demon/linux_demon.c | 31 +++++++++++++++++++++++++++++-- src/linux/demon/linux_demon.h | 2 ++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index 9c7037a0..e7e202ad 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -1792,6 +1792,7 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // wifexited -> normal process exit via _exit or exit() // wifsignaled -> exit because child did not handle a signal // + B32 is_exception = 0; if(!signal_handled && (wifexited || wifsignaled)) { thread_done = 1; @@ -2291,15 +2292,32 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) // else { + is_exception = 1; + + // rjf: unpack ip + U64 exception_ip = lnx_dmn_ip_from_thread(thread); + + // rjf: pass this signal back thread->pass_through_signal = 1; thread->pass_through_signo = wstopsig; + + // rjf: determine if this is a repeat + B32 is_second_chance = (thread->last_exception_ip == exception_ip && + thread->last_exception_signo == wstopsig); + + // rjf: store this last exception info + thread->last_exception_ip = exception_ip; + thread->last_exception_signo = wstopsig; + + // rjf: report as exception DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_Exception; e->process = lnx_dmn_handle_from_process(thread->process); e->thread = lnx_dmn_handle_from_thread(thread); - e->instruction_pointer = lnx_dmn_ip_from_thread(thread); - e->address = e->instruction_pointer; + e->instruction_pointer = exception_ip; + e->address = exception_ip; e->code = wstopsig; + e->exception_repeated = is_second_chance; if(wstopsig == SIGSEGV) { siginfo_t si = {0}; @@ -2309,6 +2327,15 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) } } + ////////////////////////// + //- rjf: not exception? -> clear this thread's last exception info + // + if(!is_exception && thread != 0) + { + thread->last_exception_ip = 0; + thread->last_exception_signo = 0; + } + ////////////////////////// //- rjf: signal not handled -> log // diff --git a/src/linux/demon/linux_demon.h b/src/linux/demon/linux_demon.h index 22ff00fb..ed6a62a3 100644 --- a/src/linux/demon/linux_demon.h +++ b/src/linux/demon/linux_demon.h @@ -179,6 +179,8 @@ struct LNX_DMN_Thread B32 is_reg_block_dirty; B32 pass_through_signal; B32 ghost_trap; + U64 last_exception_ip; + U64 last_exception_signo; U64 last_name_hash; U64 name_gather_time_us; U64 pass_through_signo; From 0956d2fa14d8cf42f41ad7991a2c8d5be6c502e6 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 14:22:51 -0700 Subject: [PATCH 223/251] linux_demon: never report signal repeats for signals which cannot ever be repeated --- src/linux/demon/linux_demon.c | 46 ++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/linux/demon/linux_demon.c b/src/linux/demon/linux_demon.c index e7e202ad..e2d9c2b9 100644 --- a/src/linux/demon/linux_demon.c +++ b/src/linux/demon/linux_demon.c @@ -2302,12 +2302,52 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) thread->pass_through_signo = wstopsig; // rjf: determine if this is a repeat - B32 is_second_chance = (thread->last_exception_ip == exception_ip && + local_persist B8 signo_is_repeatable_table[] = + { + 0, // null + 1, // SIGHUP 1 + 1, // SIGINT 2 + 1, // SIGQUIT 3 + 1, // SIGILL 4 + 1, // SIGTRAP 5 + 1, // SIGABRT/SIGIOT 6 + 1, // SIGBUS 7 + 1, // SIGFPE 8 + 1, // SIGKILL 9 + 1, // SIGUSR1 10 + 1, // SIGSEGV 11 + 1, // SIGUSR2 12 + 1, // SIGPIPE 13 + 1, // SIGALRM 14 + 1, // SIGTERM 15 + 1, // SIGSTKFLT 16 + 0, // SIGCHLD 17 + 0, // SIGCONT 18 + 1, // SIGSTOP 19 + 1, // SIGTSTP 20 + 1, // SIGTTIN 21 + 1, // SIGTTOU 22 + 0, // SIGURG 23 + 1, // SIGXCPU 24 + 1, // SIGXFSZ 25 + 1, // SIGVTALRM 26 + 1, // SIGPROF 27 + 0, // SIGWINCH 28 + 1, // SIGIO 29 + 1, // SIGPWR 30 + 1, // SIGSYS/SIGUNUSED31 + }; + B32 is_repeatable = (wstopsig < ArrayCount(signo_is_repeatable_table)) ? signo_is_repeatable_table[wstopsig] : 0; + B32 is_second_chance = (is_repeatable && + thread->last_exception_ip == exception_ip && thread->last_exception_signo == wstopsig); // rjf: store this last exception info - thread->last_exception_ip = exception_ip; - thread->last_exception_signo = wstopsig; + if(is_repeatable) + { + thread->last_exception_ip = exception_ip; + thread->last_exception_signo = wstopsig; + } // rjf: report as exception DMN_Event *e = dmn_event_list_push(arena, &events); From f6b4a38134652886239b91f940cd7a67fedf689d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 1 Oct 2026 16:31:18 -0600 Subject: [PATCH 224/251] dwarf: fix incorrect offset table parsing in cases of rnglists. extra offset field & different interpretation of table entries. --- src/dwarf/dwarf_parse.c | 59 ++++++++++++++++++++++++++++++----------- src/dwarf/dwarf_parse.h | 5 ++-- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/dwarf/dwarf_parse.c b/src/dwarf/dwarf_parse.c index a7845622..6faabc27 100644 --- a/src/dwarf/dwarf_parse.c +++ b/src/dwarf/dwarf_parse.c @@ -1202,7 +1202,7 @@ dw2_read_line_table_header(Arena *arena, DW_Raw *raw, DW2_ParseCtx *ctx, String8 //~ rjf: String Offset Table Parsing (.debug_str_offsets) internal U64 -dw2_read_offset_table(String8 data, U64 off, DW2_OffsetTable *out) +dw2_read_offset_table(String8 data, U64 off, B32 offset_count_entry, DW2_OffsetTable *out) { U64 start_off = off; { @@ -1225,11 +1225,34 @@ dw2_read_offset_table(String8 data, U64 off, DW2_OffsetTable *out) off += str8_deserial_read_struct(data, off, &addr_size); off += str8_deserial_read_struct(data, off, &segment_selector_size); - // rjf: determine entry size - U64 entry_size = dw_addr_size_from_format(format); - if(addr_size != 0) + // rjf: 0 addr size / segment selector size -> this is an offset table (e.g. stroffs). + // this means the table immediately follows the header, and the entry size is based + // on the format. + U64 entry_size = 0; + U64 entry_count = 0; + if(addr_size == 0 && segment_selector_size == 0) + { + entry_size = dw_addr_size_from_format(format); + entry_count = (unit_data_off_opl - off) / entry_size; + } + + // rjf: nonzero addr size / segment selector size -> if we have an explicitly + // stored offset count entry, we need to parse it + else if(offset_count_entry) + { + entry_size = dw_addr_size_from_format(format); + off += str8_deserial_read(data, off, &entry_count, 4, 4); + U64 entry_count_cap = (unit_data_off_opl - off) / entry_size; + entry_count = Min(entry_count, entry_count_cap); + } + + // rjf: nonzero addr size / segment selector size -> no stored offset count, + // then we infer the entry count from the data remaining, as we are just + // storing address pairs. + else { entry_size = addr_size + segment_selector_size; + entry_count = (unit_data_off_opl - off) / entry_size; } // rjf: fill table info @@ -1238,7 +1261,8 @@ dw2_read_offset_table(String8 data, U64 off, DW2_OffsetTable *out) out->addr_size = addr_size; out->segment_selector_size = segment_selector_size; out->entry_size = entry_size; - out->entries_count = (unit_data_off_opl - off) / out->entry_size; + out->entries_count = entry_count; + out->entries_are_addrs = (!offset_count_entry && addr_size != 0); out->entries = data.str + off; // rjf: skip table @@ -1250,14 +1274,14 @@ dw2_read_offset_table(String8 data, U64 off, DW2_OffsetTable *out) } internal DW2_OffsetTableList -dw2_offset_table_list_from_data(Arena *arena, String8 data) +dw2_offset_table_list_from_data(Arena *arena, String8 data, B32 offset_count_entry) { DW2_OffsetTableList list = {0}; for(U64 off = 0; off < data.size;) { U64 start_off = off; DW2_OffsetTable table = {0}; - off += dw2_read_offset_table(data, off, &table); + off += dw2_read_offset_table(data, off, offset_count_entry, &table); if(table.entries != 0) { DW2_OffsetTableNode *n = push_array(arena, DW2_OffsetTableNode, 1); @@ -1282,7 +1306,7 @@ dw2_try_offset_from_table_idx(DW2_OffsetTable *tbl, U64 idx, U64 *out) { U64 entry_size = tbl->entry_size; U64 entry_off = idx * entry_size; - if(tbl->addr_size != 0) + if(tbl->entries_are_addrs) { U64 segment_off = entry_off; U64 addr_off = entry_off + tbl->segment_selector_size; @@ -1310,22 +1334,24 @@ dw2_offset_table_set_from_raw(Arena *arena, DW_Raw *raw) struct { String8 data; + B32 offset_count_entry; U64 *tables_count_out; DW2_OffsetTable **tables_out; Rng1U64 **tables_ranges_out; } tasks[] = { - {raw->sec[DW_SectionKind_StrOffsets].data, &result.str_offsets_tables_count, &result.str_offsets_tables, &result.str_offsets_tables_ranges}, - {raw->sec[DW_SectionKind_RngLists].data, &result.rnglists_tables_count, &result.rnglists_tables, &result.rnglists_tables_ranges}, - {raw->sec[DW_SectionKind_Addr].data, &result.addr_tables_count, &result.addr_tables, &result.addr_tables_ranges}, - {raw->sec[DW_SectionKind_LocLists].data, &result.loclists_tables_count, &result.loclists_tables, &result.loclists_tables_ranges}, + {raw->sec[DW_SectionKind_StrOffsets].data, 0, &result.str_offsets_tables_count, &result.str_offsets_tables, &result.str_offsets_tables_ranges}, + {raw->sec[DW_SectionKind_RngLists].data, 1, &result.rnglists_tables_count, &result.rnglists_tables, &result.rnglists_tables_ranges}, + {raw->sec[DW_SectionKind_Addr].data, 0, &result.addr_tables_count, &result.addr_tables, &result.addr_tables_ranges}, + {raw->sec[DW_SectionKind_LocLists].data, 1, &result.loclists_tables_count, &result.loclists_tables, &result.loclists_tables_ranges}, }; for EachElement(task_idx, tasks) { Temp scratch = scratch_begin(&arena, 1); String8 data = tasks[task_idx].data; - DW2_OffsetTableList tables = dw2_offset_table_list_from_data(scratch.arena, data); + B32 offset_count_entry = tasks[task_idx].offset_count_entry; + DW2_OffsetTableList tables = dw2_offset_table_list_from_data(scratch.arena, data, offset_count_entry); tasks[task_idx].tables_count_out[0] = tables.count; tasks[task_idx].tables_out[0] = push_array(arena, DW2_OffsetTable, tables.count); tasks[task_idx].tables_ranges_out[0] = push_array(arena, Rng1U64, tables.count); @@ -1421,7 +1447,10 @@ dw2_rnglist_from_form_val(Arena *arena, DW2_ParseCtx *ctx, DW_Raw *raw, DW2_Form if(ctx->rnglists_table != 0) { U64 rnglist_off_idx = form_val.u128.u64[0]; - dw2_try_offset_from_table_idx(ctx->rnglists_table, rnglist_off_idx, &rnglist_off); + U64 rnglist_off_from_off_array = 0; + dw2_try_offset_from_table_idx(ctx->rnglists_table, rnglist_off_idx, &rnglist_off_from_off_array); + U64 rnglist_off_array_base_off = (U64)((U8 *)ctx->rnglists_table->entries - data.str); + rnglist_off = rnglist_off_array_base_off + rnglist_off_from_off_array; }break; } @@ -1624,7 +1653,7 @@ dw2_loclist_from_form_val(Arena *arena, DW2_ParseCtx *ctx, DW_Raw *raw, DW2_Form loclist_off = form_val.u128.u64[0]; }break; case DW_FormKind_LocListx: - if(ctx->rnglists_table != 0) + if(ctx->loclists_table != 0) { U64 loclist_off_idx = form_val.u128.u64[0]; dw2_try_offset_from_table_idx(ctx->loclists_table, loclist_off_idx, &loclist_off); diff --git a/src/dwarf/dwarf_parse.h b/src/dwarf/dwarf_parse.h index b8bd3554..23bd304c 100644 --- a/src/dwarf/dwarf_parse.h +++ b/src/dwarf/dwarf_parse.h @@ -104,6 +104,7 @@ struct DW2_OffsetTable U8 segment_selector_size; U64 entry_size; U64 entries_count; + B32 entries_are_addrs; void *entries; }; @@ -430,8 +431,8 @@ internal U64 dw2_read_line_table_header(Arena *arena, DW_Raw *raw, DW2_ParseCtx //////////////////////////////// //~ rjf: Offset Table Parsing (.debug_str_offsets, .debug_rnglists) -internal U64 dw2_read_offset_table(String8 data, U64 off, DW2_OffsetTable *out); -internal DW2_OffsetTableList dw2_offset_table_list_from_data(Arena *arena, String8 data); +internal U64 dw2_read_offset_table(String8 data, U64 off, B32 offset_count_entry, DW2_OffsetTable *out); +internal DW2_OffsetTableList dw2_offset_table_list_from_data(Arena *arena, String8 data, B32 offset_count_entry); internal B32 dw2_try_offset_from_table_idx(DW2_OffsetTable *tbl, U64 idx, U64 *out); internal DW2_OffsetTableSet dw2_offset_table_set_from_raw(Arena *arena, DW_Raw *raw); From 2db9ec131279f665c6d5d8764c26e22328e70a38 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 2 Oct 2026 08:17:06 -0700 Subject: [PATCH 225/251] fix ub reported by gcc builds --- src/draw/draw.c | 3 --- src/eval/eval_core.c | 6 +++--- src/font_cache/font_cache.c | 4 ++-- src/linux/shell/linux_shell.c | 2 +- src/linux/window_manager/linux_window_manager.c | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/draw/draw.c b/src/draw/draw.c index 517cad79..1a747687 100644 --- a/src/draw/draw.c +++ b/src/draw/draw.c @@ -437,13 +437,10 @@ dr_mesh(R_Handle mesh_vertices, R_Handle mesh_indices, R_GeoTopologyKind mesh_ge U64 buffer[] = { mesh_vertices.u64[0], - mesh_vertices.u64[1], mesh_indices.u64[0], - mesh_indices.u64[1], (U64)mesh_geo_topology, (U64)mesh_geo_vertex_flags, albedo_tex.u64[0], - albedo_tex.u64[1], (U64)dr_top_tex2d_sample_kind(), }; hash = dr_hash_from_string(str8((U8 *)buffer, sizeof(buffer))); diff --git a/src/eval/eval_core.c b/src/eval/eval_core.c index e46fcf29..dee9fde4 100644 --- a/src/eval/eval_core.c +++ b/src/eval/eval_core.c @@ -1251,11 +1251,11 @@ e_value_eval_from_eval(E_Eval eval) { default:{}break; case E_TypeKind_Char8: - case E_TypeKind_S8: {eval.value.s64 = (S64)*((S8 *)&eval.value.u64);}break; + case E_TypeKind_S8: {eval.value.s64 = (S64)*((S8 *)&eval.value.u128.u8[0]);}break; case E_TypeKind_Char16: - case E_TypeKind_S16: {eval.value.s64 = (S64)*((S16 *)&eval.value.u64);}break; + case E_TypeKind_S16: {eval.value.s64 = (S64)*((S16 *)&eval.value.u128.u8[0]);}break; case E_TypeKind_Char32: - case E_TypeKind_S32: {eval.value.s64 = (S64)*((S32 *)&eval.value.u64);}break; + case E_TypeKind_S32: {eval.value.s64 = (S64)*((S32 *)&eval.value.u128.u8[0]);}break; } } } diff --git a/src/font_cache/font_cache.c b/src/font_cache/font_cache.c index c84cb17b..061261a6 100644 --- a/src/font_cache/font_cache.c +++ b/src/font_cache/font_cache.c @@ -516,12 +516,12 @@ fnt_hash2style_from_tag_size_flags(FNT_Tag tag, F32 size, FNT_RasterFlags flags) //- rjf: tag * size -> style hash U64 style_hash = {0}; { - F64 size_f64 = size; + union { F64 size_f64; U64 size_u64; } size_pun = {.size_f64 = (F64)size}; U64 buffer[] = { tag.u64[0], tag.u64[1], - *(U64 *)(&size_f64), + size_pun.size_u64, (U64)flags, }; style_hash = fnt_little_hash_from_string(5381, str8((U8 *)buffer, sizeof(buffer))); diff --git a/src/linux/shell/linux_shell.c b/src/linux/shell/linux_shell.c index 06ce8320..abbd453f 100644 --- a/src/linux/shell/linux_shell.c +++ b/src/linux/shell/linux_shell.c @@ -189,7 +189,7 @@ sh_install_or_uninstall_self(B32 write, B32 install) //- rjf: make symlink in binary path to this exe make_directory(str8_chop_last_slash(symlink_path)); - symlink((char *)get_process_info()->binary_file_path.str, (char *)symlink_path.str); + LNX_RETRY_ON_EINTR(symlink((char *)get_process_info()->binary_file_path.str, (char *)symlink_path.str)); } //- rjf: uninstall -> delete files diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index cda0a06a..f2957383 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -1141,7 +1141,7 @@ wm_get_events(Arena *arena, B32 wait) if(poll_fds[1].revents & POLLIN) { U64 dummy = 0; - read(lnx_wm_state->wakeup_fd, &dummy, sizeof(dummy)); + LNX_RETRY_ON_EINTR(read(lnx_wm_state->wakeup_fd, &dummy, sizeof(dummy))); wait = 0; } } From bf000289db62a8c82038ce9e34debc75ce7e2f93 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 2 Oct 2026 12:57:26 -0600 Subject: [PATCH 226/251] raddbg: fix potentially stale cfg lookups, do not hash cfg IDs since they are just monotonically increasing --- build.sh | 4 ++-- src/base/base_strings.h | 2 ++ src/config/config_core.c | 33 +++++++++++++++++++++++---------- src/raddbg/raddbg_core.c | 10 +++++----- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/build.sh b/build.sh index 6ad190be..e40fd48f 100755 --- a/build.sh +++ b/build.sh @@ -9,12 +9,12 @@ if [[ "$#" == "0" ]]; then raddbg='1'; fi if [[ "$#" == "1" && "${release:-0}" == "1" ]]; then raddbg='1'; fi if [[ "${asan:-0}" == "1" ]]; then echo "[asan enabled]" - auto_compile_flags="-fsanitize=address" + auto_compile_flags="$auto_compile_flags -fsanitize=address" fi # --- Mix Git Commit ID ------------------------------------------------------- if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - auto_compile_flags="-DBUILD_GIT_HASH=\"$(git describe --always --dirty)\" -DBUILD_GIT_HASH_FULL=$(git rev-parse HEAD)" + auto_compile_flags="$auto_compile_flags -DBUILD_GIT_HASH=\"$(git describe --always --dirty)\" -DBUILD_GIT_HASH_FULL=$(git rev-parse HEAD)" fi # --- Compile/Link Line Definitions ------------------------------------------- diff --git a/src/base/base_strings.h b/src/base/base_strings.h index 27cdc9d1..349a8619 100644 --- a/src/base/base_strings.h +++ b/src/base/base_strings.h @@ -234,6 +234,8 @@ internal String8 str8f(Arena *arena, char *fmt, ...); #define push_str8f(arena, ...) str8f((arena), __VA_ARGS__) internal String8 push_cstr(Arena *arena, String8 str); // TODO(rjf): this is unnecessary - this is implied by `push_str8_copy`. need to remove. +#define sf(...) str8f((scratch.arena), __VA_ARGS__) + //////////////////////////////// //~ rjf: String <=> Integer Conversions diff --git a/src/config/config_core.c b/src/config/config_core.c index e58d5320..7d9f4cd4 100644 --- a/src/config/config_core.c +++ b/src/config/config_core.c @@ -184,19 +184,19 @@ cfg_node_from_id(CFG_ID id) } else { - U64 hash = u64_hash_from_str8(str8_struct(&id)); + U64 hash = id; U64 slot_idx = hash%cfg_ctx->id_slots_count; for(CFG_NodePtrNode *n = cfg_ctx->id_slots[slot_idx].first; n != 0; n = n->next) { if(n->v->id == id) { result = n->v; + cfg_ctx->last_accessed_id = id; + cfg_ctx->last_accessed = result; break; } } } - cfg_ctx->last_accessed_id = id; - cfg_ctx->last_accessed = result; return result; } @@ -619,7 +619,7 @@ cfg_node_alloc(CFG_State *state) { cfg_id_node = push_array(state->arena, CFG_NodePtrNode, 1); } - U64 hash = u64_hash_from_str8(str8_struct(&result->id)); + U64 hash = result->id; U64 slot_idx = hash%state->ctx.id_slots_count; DLLPushBack(state->ctx.id_slots[slot_idx].first, state->ctx.id_slots[slot_idx].last, cfg_id_node); cfg_id_node->v = result; @@ -632,7 +632,6 @@ internal void cfg_node_release(CFG_State *state, CFG_Node *node) { state->ctx.change_gen += 1; - Temp scratch = scratch_begin(0, 0); // rjf: unhook from context @@ -648,23 +647,37 @@ cfg_node_release(CFG_State *state, CFG_Node *node) // rjf: release all nodes for(CFG_NodePtrNode *n = nodes.first; n != 0; n = n->next) { + // rjf: unpack node CFG_Node *c = n->v; - U64 hash = u64_hash_from_str8(str8_struct(&c->id)); + U64 hash = c->id; U64 slot_idx = hash%state->ctx.id_slots_count; + + // rjf: release string, recycle node cfg_string_release(state, c->string); SLLStackPush(state->free, c); + + // rjf: zero node's contents c->first = c->last = c->prev = c->parent = 0; c->id = 0; c->string = str8_zero(); - for(CFG_NodePtrNode *n = state->ctx.id_slots[slot_idx].first; n != 0; n = n->next) + + // rjf: remove from id -> node map + for(CFG_NodePtrNode *id_n = state->ctx.id_slots[slot_idx].first; id_n != 0; id_n = id_n->next) { - if(n->v == c) + if(id_n->v == c) { - DLLRemove(state->ctx.id_slots[slot_idx].first, state->ctx.id_slots[slot_idx].last, n); - SLLStackPush(state->free_id_node, n); + DLLRemove(state->ctx.id_slots[slot_idx].first, state->ctx.id_slots[slot_idx].last, id_n); + SLLStackPush(state->free_id_node, id_n); break; } } + + // rjf: if this was the last accessed node, clear that slot + if(c == state->ctx.last_accessed) + { + state->ctx.last_accessed = &cfg_nil_node; + state->ctx.last_accessed_id = 0; + } } scratch_end(scratch); diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 05e46e50..68a18e02 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -11861,13 +11861,13 @@ rd_frame(void) // if(rd_state->frame_depth == 1) { - CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), str8_lit("transient")); + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); for(CFG_Node *tln = transient->first, *next = &cfg_nil_node; tln != &cfg_nil_node; tln = next) { next = tln->next; - if(str8_match(tln->string, str8_lit("immediate"), 0)) + if(str8_match(tln->string, s("immediate"), 0)) { - if(cfg_node_child_from_string(tln, str8_lit("hot")) == &cfg_nil_node) + if(cfg_node_child_from_string(tln, s("hot")) == &cfg_nil_node) { cfg_node_release(rd_state->cfg, tln); } @@ -11875,12 +11875,12 @@ rd_frame(void) } for(CFG_Node *tln = transient->first; tln != &cfg_nil_node; tln = tln->next) { - if(str8_match(tln->string, str8_lit("immediate"), 0)) + if(str8_match(tln->string, s("immediate"), 0)) { for(CFG_Node *child = tln->first, *next = &cfg_nil_node; child != &cfg_nil_node; child = next) { next = child->next; - if(str8_match(child->string, str8_lit("hot"), 0)) + if(str8_match(child->string, s("hot"), 0)) { cfg_node_release(rd_state->cfg, child); } From 34a0ff4e80a4d25242dc804f2141715911eecb68 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 2 Oct 2026 20:57:47 -0700 Subject: [PATCH 227/251] raddbg_server: checkpoint --- build.bat | 1 + src/raddbg_server/raddbg_server.c | 2 + src/raddbg_server/raddbg_server.h | 92 ++++++++ src/raddbg_server/raddbg_server_main.c | 300 +++++++++++++++++++++++++ src/socket/socket.c | 28 +++ src/socket/socket.h | 1 + 6 files changed, 424 insertions(+) create mode 100644 src/raddbg_server/raddbg_server.c create mode 100644 src/raddbg_server/raddbg_server.h create mode 100644 src/raddbg_server/raddbg_server_main.c diff --git a/build.bat b/build.bat index 9d384c56..19c07e29 100644 --- a/build.bat +++ b/build.bat @@ -195,6 +195,7 @@ pushd build if "%raddbg%"=="1" set didbuild=1 && %compile% ..\src\raddbg\raddbg_main.c %compile_link% %link_icon% %out%raddbg.exe || exit /b 1 if "%raddbg_non_graphical%"=="1" set didbuild=1 && %compile% -DWM_STUB=1 -DR_BACKEND=R_BACKEND_STUB ..\src\raddbg\raddbg_main.c %compile_link% %link_icon% %out%raddbg_non_graphical.exe || exit /b 1 if "%com_shim%"=="1" set didbuild=1 && %compile% ..\src\com_shim\com_shim_main.c %compile_link% %out%com_shim.exe || exit /b 1 +if "%raddbg_server%"=="1" set didbuild=1 && %compile% ..\src\raddbg_server\raddbg_server_main.c %compile_link% %out%raddbg_server.exe || exit /b 1 if "%radlink%"=="1" set didbuild=1 && %compile% ..\src\linker\lnk.c %compile_link% %linker% /NOIMPLIB %linker% /NATVIS:"%~dp0\src\linker\linker.natvis" %out%radlink.exe || exit /b 1 if "%radbin%"=="1" set didbuild=1 && %compile% ..\src\radbin\radbin_main.c %compile_link% %out%radbin.exe || exit /b 1 if "%raddump%"=="1" set didbuild=1 && %compile% ..\src\raddump\raddump_main.c %compile_link% %out%raddump.exe || exit /b 1 diff --git a/src/raddbg_server/raddbg_server.c b/src/raddbg_server/raddbg_server.c new file mode 100644 index 00000000..ae447aab --- /dev/null +++ b/src/raddbg_server/raddbg_server.c @@ -0,0 +1,2 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) diff --git a/src/raddbg_server/raddbg_server.h b/src/raddbg_server/raddbg_server.h new file mode 100644 index 00000000..807cc683 --- /dev/null +++ b/src/raddbg_server/raddbg_server.h @@ -0,0 +1,92 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef RADDBG_SERVER_H +#define RADDBG_SERVER_H + +//////////////////////////////// +//~ rjf: Messages (Client -> Server) + +#define RDS_MSG_MAGIC 0x7273 + +typedef enum RDS_MsgKind +{ + RDS_MsgKind_Null, + + //- rjf: debuggee attaching/detaching ops + RDS_MsgKind_Launch, + RDS_MsgKind_Attach, + RDS_MsgKind_Kill, + RDS_MsgKind_Detach, + + //- rjf: running ops + RDS_MsgKind_Run, + RDS_MsgKind_SingleStep, + RDS_MsgKind_Halt, + + //- rjf: memory ops + RDS_MsgKind_MemoryReserve, + RDS_MsgKind_MemoryCommit, + RDS_MsgKind_MemoryDecommit, + RDS_MsgKind_MemoryRelease, + RDS_MsgKind_MemoryProtect, + RDS_MsgKind_MemoryRead, + RDS_MsgKind_MemoryWrite, + + //- rjf: thread ops + RDS_MsgKind_ThreadRegBlockRead, + RDS_MsgKind_ThreadRegBlockWrite, + RDS_MsgKind_ThreadGetModuleTLSVAddr, + + //- rjf: system ops + RDS_MsgKind_ListSystemProcesses, +} +RDS_MsgKind; + +typedef U32 RDS_MsgFlags; +enum +{ + RDS_MsgFlag_InheritEnv = (1<<0), + RDS_MsgFlag_DebugSubprocesses = (1<<1), + RDS_MsgFlag_IgnorePreviousException = (1<<2), + RDS_MsgFlag_RunEntitiesAreUnfrozen = (1<<3), + RDS_MsgFlag_RunEntitiesAreProcesses = (1<<4), +}; + +typedef struct RDS_Msg RDS_Msg; +struct RDS_Msg +{ + RDS_MsgKind kind; + RDS_MsgFlags flags; + U64 id; + + //- rjf: general parameters + Rng1U64 vaddr_range; + AccessFlags access_flags; + DMN_Handle entity; + U64 pid; + + //- rjf: process launching parameters + String8List command_line; + String8 path; + String8List env; + + //- rjf: running parameters + DMN_TrapChunkList traps; + DMN_HandleArray run_entities; +}; + +//////////////////////////////// +//~ rjf: Responses (Server -> Client responses to messages) + +typedef struct RDS_Response RDS_Response; +struct RDS_Response +{ + U64 id; + String8 data; + DMN_Handle entity; + U64 pid; + U64 demon_event_count; +}; + +#endif // RADDBG_SERVER_H diff --git a/src/raddbg_server/raddbg_server_main.c b/src/raddbg_server/raddbg_server_main.c new file mode 100644 index 00000000..ced90431 --- /dev/null +++ b/src/raddbg_server/raddbg_server_main.c @@ -0,0 +1,300 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Build Options + +#define BUILD_TITLE "The RAD Debugger Server" +#define BUILD_CONSOLE_INTERFACE 1 + +//////////////////////////////// +//~ rjf: Includes + +//- rjf: [h] +#include "base/base_inc.h" +#include "x64/x64_inc.h" +#include "arch/arch_inc.h" +#include "socket/socket_inc.h" +#include "coff/coff.h" +#include "coff/coff_parse.h" +#include "pe/pe.h" +#include "elf/elf.h" +#include "gnu/gnu.h" +#include "gnu/gnu_parse.h" +#include "elf/elf_parse.h" +#include "demon/demon_inc.h" +#include "raddbg_server/raddbg_server.h" + +//- rjf: [c] +#include "base/base_inc.c" +#include "x64/x64_inc.c" +#include "arch/arch_inc.c" +#include "socket/socket_inc.c" +#include "coff/coff.c" +#include "coff/coff_parse.c" +#include "pe/pe.c" +#include "elf/elf.c" +#include "gnu/gnu.c" +#include "gnu/gnu_parse.c" +#include "elf/elf_parse.c" +#include "demon/demon_inc.c" +#include "raddbg_server/raddbg_server.c" + +//////////////////////////////// +//~ rjf: Globals + +typedef struct RDS_State RDS_State; +struct RDS_State +{ + Arena *arena; + SOCK_Session sock_session; + + //- rjf: client -> demon messages + RWMutex c2d_rw_mutex; + CondVar c2d_cv; + Arena *c2d_arena; + String8List c2d_msgs; + + //- rjf: demon run state + B32 demon_is_running; +}; + +global RDS_State *rds_state = 0; + +//////////////////////////////// +//~ rjf: Demon Thread + +internal void +dmn_thread__entry_point(void *p) +{ + ThreadNameF("dmn_thread"); + DMN_CtrlCtx *ctrl_ctx = dmn_ctrl_begin(); + for(;;) + { + Temp scratch = scratch_begin(0, 0); + + //////////////////////////// + //- rjf: get next messages + // + String8List msgs = {0}; + RWMutexScope(rds_state->c2d_rw_mutex, 1) for(;;) + { + if(rds_state->c2d_msgs.node_count != 0) + { + msgs = str8_list_copy(scratch.arena, &rds_state->c2d_msgs); + MemoryZeroStruct(&rds_state->c2d_msgs); + arena_clear(rds_state->c2d_arena); + break; + } + cond_var_wait_rw_w(rds_state->c2d_cv, rds_state->c2d_rw_mutex, max_U64); + } + + //////////////////////////// + //- rjf: do messages + // + for(String8Node *msg_n = msgs.first; msg_n != 0; msg_n = msg_n->next) + { + ////////////////////////// + //- rjf: unpack message + // + String8 data = msg_n->string; + U16 magic_maybe = 0; + str8_deserial_read_struct(data, 0, &magic_maybe); + + ////////////////////////// + //- rjf: parse message + // + RDS_Msg msg = {0}; + if(magic_maybe == RDS_MSG_MAGIC) + { + U64 off = sizeof(magic_maybe); + + // rjf: read flat parts + off += str8_deserial_read_struct(data, off, &msg.kind); + off += str8_deserial_read_struct(data, off, &msg.flags); + off += str8_deserial_read_struct(data, off, &msg.id); + off += str8_deserial_read_struct(data, off, &msg.vaddr_range); + off += str8_deserial_read_struct(data, off, &msg.access_flags); + off += str8_deserial_read_struct(data, off, &msg.entity); + off += str8_deserial_read_struct(data, off, &msg.pid); + + // rjf: read command line strings + { + U64 cmd_line_string_count = 0; + off += str8_deserial_read_struct(data, off, &cmd_line_string_count); + for EachIndex(idx, cmd_line_string_count) + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + str8_list_push(scratch.arena, &msg.command_line, string); + } + } + + // rjf: read path + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + msg.path = string; + } + + // rjf: read environment strings + { + U64 env_string_count = 0; + off += str8_deserial_read_struct(data, off, &env_string_count); + for EachIndex(idx, env_string_count) + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + str8_list_push(scratch.arena, &msg.env, string); + } + } + + // rjf: read traps + { + U64 trap_count = 0; + off += str8_deserial_read_struct(data, off, &trap_count); + for EachIndex(idx, trap_count) + { + DMN_Trap trap = {0}; + off += str8_deserial_read_struct(data, off, &trap.process); + off += str8_deserial_read_struct(data, off, &trap.vaddr); + off += str8_deserial_read_struct(data, off, &trap.id); + off += str8_deserial_read_struct(data, off, &trap.flags); + off += str8_deserial_read_struct(data, off, &trap.size); + dmn_trap_chunk_list_push(scratch.arena, &msg.traps, trap_count, &trap); + } + } + } + + ////////////////////////// + //- rjf: do message + // + switch(msg.kind) + { + default:{}break; + + //- rjf: debuggee attaching/detaching ops + + case RDS_MsgKind_Launch:{}break; + case RDS_MsgKind_Attach:{}break; + case RDS_MsgKind_Kill:{}break; + case RDS_MsgKind_Detach:{}break; + + //- rjf: running ops + + case RDS_MsgKind_Run:{}break; + case RDS_MsgKind_SingleStep:{}break; + case RDS_MsgKind_Halt:{}break; + + //- rjf: memory ops + + case RDS_MsgKind_MemoryReserve:{}break; + case RDS_MsgKind_MemoryCommit:{}break; + case RDS_MsgKind_MemoryDecommit:{}break; + case RDS_MsgKind_MemoryRelease:{}break; + case RDS_MsgKind_MemoryProtect:{}break; + case RDS_MsgKind_MemoryRead:{}break; + case RDS_MsgKind_MemoryWrite:{}break; + + //- rjf: thread ops + + case RDS_MsgKind_ThreadRegBlockRead:{}break; + case RDS_MsgKind_ThreadRegBlockWrite:{}break; + case RDS_MsgKind_ThreadGetModuleTLSVAddr:{}break; + + //- rjf: system ops + + case RDS_MsgKind_ListSystemProcesses: + { + + }break; + } + } + + scratch_end(scratch); + } +} + +//////////////////////////////// +//~ rjf: Entry Point + +internal void +entry_point(CmdLine *cmd_line) +{ + //- rjf: get port argument + U64 port = 7273; + { + U64 port_maybe = 0; + String8 port_arg_string = cmd_line_string(cmd_line, s("port")); + if(try_u64_from_str8_c_rules(port_arg_string, &port_maybe)) + { + port = port_maybe; + } + } + + //- rjf: set up shared state + Arena *arena = arena_alloc(); + rds_state = push_array(arena, RDS_State, 1); + rds_state->arena = arena; + rds_state->sock_session = sock_session_open((U16)port, 0); + rds_state->c2d_rw_mutex = rw_mutex_alloc(); + rds_state->c2d_cv = cond_var_alloc(); + rds_state->c2d_arena = arena_alloc(); + + //- rjf: launch demon thread + thread_launch(dmn_thread__entry_point, 0); + + //- rjf: loop: wait for messages, issue commands to demon + for(;;) + { + Temp scratch = scratch_begin(0, 0); + + //- rjf: get next message from client + fprintf(stderr, "[waiting for message]\n"); + SOCK_Protocol protocol = SOCK_Protocol_TCP; + SOCK_Endpoint endpoint = {0}; + String8 data = {0}; + sock_recv(scratch.arena, rds_state->sock_session, &protocol, &endpoint, &data, max_U64); + + //- rjf: parse magic prefix + U16 magic_maybe = 0; + str8_deserial_read_struct(data, 0, &magic_maybe); + + //- rjf: log message info + { + String8 endpoint_string = sock_string_from_endpoint(scratch.arena, endpoint); + fprintf(stderr, "[got message]\n"); + fprintf(stderr, " sender: %.*s\n", str8_varg(endpoint_string)); + fprintf(stderr, " magic: 0x%x\n", (int)magic_maybe); + } + + //- rjf: the only kind of message we handle on the main thread is halting, + // since we need to interrupt the actual controller. just look for that one + // code here, and handle it - otherwise, all we need to do is send the message + // blob to the demon thread. + RDS_MsgKind peek_kind = RDS_MsgKind_Null; + if(magic_maybe == RDS_MSG_MAGIC) + { + str8_deserial_read_struct(data, 2, &peek_kind); + if(peek_kind == RDS_MsgKind_Halt && rds_state->demon_is_running) + { + dmn_halt(0, 0); + } + } + + //- rjf: not halt? -> okay, send to demon thread, if it's ready. + if(magic_maybe == RDS_MSG_MAGIC && peek_kind != RDS_MsgKind_Halt && !rds_state->demon_is_running) + { + RWMutexScope(rds_state->c2d_rw_mutex, 1) + { + str8_list_push(rds_state->c2d_arena, &rds_state->c2d_msgs, data); + } + cond_var_broadcast(rds_state->c2d_cv); + } + + scratch_end(scratch); + } +} diff --git a/src/socket/socket.c b/src/socket/socket.c index b52c0b84..e6cb2996 100644 --- a/src/socket/socket.c +++ b/src/socket/socket.c @@ -96,3 +96,31 @@ sock_endpoint_from_string(String8 address_and_port) SOCK_Endpoint ep = sock_endpoint_from_string_port(addr, port); return ep; } + +internal String8 +sock_string_from_endpoint(Arena *arena, SOCK_Endpoint endpoint) +{ + String8 result = {0}; + switch(endpoint.kind) + { + default:{}break; + case SOCK_EndpointKind_IPv4: + { + result = str8f(arena, "%i.%i.%i.%i:%i", (int)endpoint.address_u8[0], (int)endpoint.address_u8[1], (int)endpoint.address_u8[2], (int)endpoint.address_u8[3], (int)endpoint.port); + }break; + case SOCK_EndpointKind_IPv6: + { + result = str8f(arena, "[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]:%i", + (int)endpoint.address_u16[0], + (int)endpoint.address_u16[1], + (int)endpoint.address_u16[2], + (int)endpoint.address_u16[3], + (int)endpoint.address_u16[4], + (int)endpoint.address_u16[5], + (int)endpoint.address_u16[6], + (int)endpoint.address_u16[7], + (int)endpoint.port); + }break; + } + return result; +} diff --git a/src/socket/socket.h b/src/socket/socket.h index f0307022..2903915b 100644 --- a/src/socket/socket.h +++ b/src/socket/socket.h @@ -59,6 +59,7 @@ typedef SOCK_WAKEUP_FUNCTION_DEF(SOCK_WakeupFunctionType); internal SOCK_Endpoint sock_endpoint_from_string_port(String8 address, U16 port); internal SOCK_Endpoint sock_endpoint_from_string(String8 address_and_port); +internal String8 sock_string_from_endpoint(Arena *arena, SOCK_Endpoint endpoint); //////////////////////////////// //~ rjf: @per_os_impl Top-Level Layer Calls From 7da38c5f3bd306fe72a850d88fa2b5db159b1362 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 2 Oct 2026 21:08:49 -0700 Subject: [PATCH 228/251] raddbg_server: adjust split between main / demon thread - main thread can handle many things, like memory & regs reads/writes, but cannot handle ctrl-thread-only operations. --- src/raddbg_server/raddbg_server.c | 79 +++++++++++++++ src/raddbg_server/raddbg_server.h | 11 ++- src/raddbg_server/raddbg_server_main.c | 129 ++++--------------------- 3 files changed, 107 insertions(+), 112 deletions(-) diff --git a/src/raddbg_server/raddbg_server.c b/src/raddbg_server/raddbg_server.c index ae447aab..96e81200 100644 --- a/src/raddbg_server/raddbg_server.c +++ b/src/raddbg_server/raddbg_server.c @@ -1,2 +1,81 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Message Parsing + +internal RDS_Msg +rds_msg_from_data(Arena *arena, String8 data) +{ + //- rjf: unpack message + U16 magic_maybe = 0; + str8_deserial_read_struct(data, 0, &magic_maybe); + + //- rjf: parse message + RDS_Msg msg = {0}; + if(magic_maybe == RDS_MSG_MAGIC) + { + U64 off = sizeof(magic_maybe); + + // rjf: read flat parts + off += str8_deserial_read_struct(data, off, &msg.kind); + off += str8_deserial_read_struct(data, off, &msg.flags); + off += str8_deserial_read_struct(data, off, &msg.id); + off += str8_deserial_read_struct(data, off, &msg.vaddr_range); + off += str8_deserial_read_struct(data, off, &msg.access_flags); + off += str8_deserial_read_struct(data, off, &msg.entity); + off += str8_deserial_read_struct(data, off, &msg.pid); + + // rjf: read command line strings + { + U64 cmd_line_string_count = 0; + off += str8_deserial_read_struct(data, off, &cmd_line_string_count); + for EachIndex(idx, cmd_line_string_count) + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + str8_list_push(arena, &msg.command_line, string); + } + } + + // rjf: read path + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + msg.path = string; + } + + // rjf: read environment strings + { + U64 env_string_count = 0; + off += str8_deserial_read_struct(data, off, &env_string_count); + for EachIndex(idx, env_string_count) + { + String8 string = {0}; + off += str8_deserial_read_struct(data, off, &string.size); + string = str8_substr(data, r1u64(off, off+string.size)); + str8_list_push(arena, &msg.env, string); + } + } + + // rjf: read traps + { + U64 trap_count = 0; + off += str8_deserial_read_struct(data, off, &trap_count); + for EachIndex(idx, trap_count) + { + DMN_Trap trap = {0}; + off += str8_deserial_read_struct(data, off, &trap.process); + off += str8_deserial_read_struct(data, off, &trap.vaddr); + off += str8_deserial_read_struct(data, off, &trap.id); + off += str8_deserial_read_struct(data, off, &trap.flags); + off += str8_deserial_read_struct(data, off, &trap.size); + dmn_trap_chunk_list_push(arena, &msg.traps, trap_count, &trap); + } + } + } + + return msg; +} diff --git a/src/raddbg_server/raddbg_server.h b/src/raddbg_server/raddbg_server.h index 807cc683..47b0fc51 100644 --- a/src/raddbg_server/raddbg_server.h +++ b/src/raddbg_server/raddbg_server.h @@ -14,14 +14,16 @@ typedef enum RDS_MsgKind RDS_MsgKind_Null, //- rjf: debuggee attaching/detaching ops - RDS_MsgKind_Launch, + RDS_MsgKind_Launch, RDS_MsgKind_DemonThreadHandledFirst = RDS_MsgKind_Launch, RDS_MsgKind_Attach, RDS_MsgKind_Kill, RDS_MsgKind_Detach, //- rjf: running ops RDS_MsgKind_Run, - RDS_MsgKind_SingleStep, + RDS_MsgKind_SingleStep, RDS_MsgKind_DemonThreadHandledLast = RDS_MsgKind_SingleStep, + + //- rjf: halting RDS_MsgKind_Halt, //- rjf: memory ops @@ -89,4 +91,9 @@ struct RDS_Response U64 demon_event_count; }; +//////////////////////////////// +//~ rjf: Message Parsing + +internal RDS_Msg rds_msg_from_data(Arena *arena, String8 data); + #endif // RADDBG_SERVER_H diff --git a/src/raddbg_server/raddbg_server_main.c b/src/raddbg_server/raddbg_server_main.c index ced90431..430d2cce 100644 --- a/src/raddbg_server/raddbg_server_main.c +++ b/src/raddbg_server/raddbg_server_main.c @@ -94,84 +94,7 @@ dmn_thread__entry_point(void *p) // for(String8Node *msg_n = msgs.first; msg_n != 0; msg_n = msg_n->next) { - ////////////////////////// - //- rjf: unpack message - // - String8 data = msg_n->string; - U16 magic_maybe = 0; - str8_deserial_read_struct(data, 0, &magic_maybe); - - ////////////////////////// - //- rjf: parse message - // - RDS_Msg msg = {0}; - if(magic_maybe == RDS_MSG_MAGIC) - { - U64 off = sizeof(magic_maybe); - - // rjf: read flat parts - off += str8_deserial_read_struct(data, off, &msg.kind); - off += str8_deserial_read_struct(data, off, &msg.flags); - off += str8_deserial_read_struct(data, off, &msg.id); - off += str8_deserial_read_struct(data, off, &msg.vaddr_range); - off += str8_deserial_read_struct(data, off, &msg.access_flags); - off += str8_deserial_read_struct(data, off, &msg.entity); - off += str8_deserial_read_struct(data, off, &msg.pid); - - // rjf: read command line strings - { - U64 cmd_line_string_count = 0; - off += str8_deserial_read_struct(data, off, &cmd_line_string_count); - for EachIndex(idx, cmd_line_string_count) - { - String8 string = {0}; - off += str8_deserial_read_struct(data, off, &string.size); - string = str8_substr(data, r1u64(off, off+string.size)); - str8_list_push(scratch.arena, &msg.command_line, string); - } - } - - // rjf: read path - { - String8 string = {0}; - off += str8_deserial_read_struct(data, off, &string.size); - string = str8_substr(data, r1u64(off, off+string.size)); - msg.path = string; - } - - // rjf: read environment strings - { - U64 env_string_count = 0; - off += str8_deserial_read_struct(data, off, &env_string_count); - for EachIndex(idx, env_string_count) - { - String8 string = {0}; - off += str8_deserial_read_struct(data, off, &string.size); - string = str8_substr(data, r1u64(off, off+string.size)); - str8_list_push(scratch.arena, &msg.env, string); - } - } - - // rjf: read traps - { - U64 trap_count = 0; - off += str8_deserial_read_struct(data, off, &trap_count); - for EachIndex(idx, trap_count) - { - DMN_Trap trap = {0}; - off += str8_deserial_read_struct(data, off, &trap.process); - off += str8_deserial_read_struct(data, off, &trap.vaddr); - off += str8_deserial_read_struct(data, off, &trap.id); - off += str8_deserial_read_struct(data, off, &trap.flags); - off += str8_deserial_read_struct(data, off, &trap.size); - dmn_trap_chunk_list_push(scratch.arena, &msg.traps, trap_count, &trap); - } - } - } - - ////////////////////////// - //- rjf: do message - // + RDS_Msg msg = rds_msg_from_data(scratch.arena, msg_n->string); switch(msg.kind) { default:{}break; @@ -187,30 +110,6 @@ dmn_thread__entry_point(void *p) case RDS_MsgKind_Run:{}break; case RDS_MsgKind_SingleStep:{}break; - case RDS_MsgKind_Halt:{}break; - - //- rjf: memory ops - - case RDS_MsgKind_MemoryReserve:{}break; - case RDS_MsgKind_MemoryCommit:{}break; - case RDS_MsgKind_MemoryDecommit:{}break; - case RDS_MsgKind_MemoryRelease:{}break; - case RDS_MsgKind_MemoryProtect:{}break; - case RDS_MsgKind_MemoryRead:{}break; - case RDS_MsgKind_MemoryWrite:{}break; - - //- rjf: thread ops - - case RDS_MsgKind_ThreadRegBlockRead:{}break; - case RDS_MsgKind_ThreadRegBlockWrite:{}break; - case RDS_MsgKind_ThreadGetModuleTLSVAddr:{}break; - - //- rjf: system ops - - case RDS_MsgKind_ListSystemProcesses: - { - - }break; } } @@ -271,22 +170,32 @@ entry_point(CmdLine *cmd_line) fprintf(stderr, " magic: 0x%x\n", (int)magic_maybe); } - //- rjf: the only kind of message we handle on the main thread is halting, - // since we need to interrupt the actual controller. just look for that one - // code here, and handle it - otherwise, all we need to do is send the message - // blob to the demon thread. + //- rjf: try to handle this message on the main thread, before sending to + // demon. the demon thread trades runtimes with debuggees, and so it is + // only able to handle messages when it is not running - e.g. launching, + // attaching, detaching, and so on. other operations, like reading/writing + // memory/registers, listing processes, and halting, we want to just do + // on the main thread without interrupting the demon at all. RDS_MsgKind peek_kind = RDS_MsgKind_Null; if(magic_maybe == RDS_MSG_MAGIC) { str8_deserial_read_struct(data, 2, &peek_kind); - if(peek_kind == RDS_MsgKind_Halt && rds_state->demon_is_running) + if(peek_kind < RDS_MsgKind_DemonThreadHandledFirst || RDS_MsgKind_DemonThreadHandledLast < peek_kind) { - dmn_halt(0, 0); + RDS_Msg msg = rds_msg_from_data(scratch.arena, data); + switch(msg.kind) + { + default:{}break; + case RDS_MsgKind_Halt: + { + dmn_halt(0, 0); + }break; + } } } - //- rjf: not halt? -> okay, send to demon thread, if it's ready. - if(magic_maybe == RDS_MSG_MAGIC && peek_kind != RDS_MsgKind_Halt && !rds_state->demon_is_running) + //- rjf: not handled by main thread? -> okay, send to demon thread, if it's ready. + if(magic_maybe == RDS_MSG_MAGIC && !rds_state->demon_is_running && (RDS_MsgKind_DemonThreadHandledFirst <= peek_kind && peek_kind <= RDS_MsgKind_DemonThreadHandledLast)) { RWMutexScope(rds_state->c2d_rw_mutex, 1) { From 98467d08682a189d15e6f4af63c6fbd7892e386d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 6 Oct 2026 18:10:49 -0600 Subject: [PATCH 229/251] raddbg: explicit packet headers for ipc; render_opengl: fix bad scissoring; linux_socket: fix not waking up, fix SIGPIPE; dbg_engine: checkpoint on sketching out multi-demon attaching abstraction, machine ID -> demon ID --- project.4coder | 2 +- project.raddbg | 1 + src/dbg_engine/dbg_engine_core.h | 6 +- src/dbg_engine/dbg_engine_ctrl.c | 129 +++++++++++------- src/dbg_engine/dbg_engine_ctrl.h | 66 ++++++++- src/dbg_engine/dbg_engine_user.c | 8 +- src/linux/socket/linux_socket.c | 17 ++- src/linux/socket/linux_socket.h | 1 + .../window_manager/linux_window_manager.c | 2 +- src/raddbg/raddbg_core.c | 12 +- src/raddbg/raddbg_main.c | 68 +++++++-- src/render/opengl/render_opengl.c | 6 +- 12 files changed, 230 insertions(+), 88 deletions(-) diff --git a/project.4coder b/project.4coder index 5de5073c..a6fa152c 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg debug", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/project.raddbg b/project.raddbg index 04783f6f..fc0f4280 100755 --- a/project.raddbg +++ b/project.raddbg @@ -47,3 +47,4 @@ target: executable: "../jai/examples/invaders/invaders" working_directory: "../jai/examples/invaders" } +linux_sigpipe: 1 diff --git a/src/dbg_engine/dbg_engine_core.h b/src/dbg_engine/dbg_engine_core.h index 971a21f8..7192ae36 100644 --- a/src/dbg_engine/dbg_engine_core.h +++ b/src/dbg_engine/dbg_engine_core.h @@ -22,7 +22,7 @@ struct D_Eval //~ rjf: ID Types typedef U64 D_MsgID; -typedef U32 D_MachineID; +typedef U32 D_DemonID; typedef U32 D_ControllerKind; typedef enum D_ControllerKindEnum { @@ -31,7 +31,7 @@ typedef enum D_ControllerKindEnum } D_ControllerKindEnum; -#define D_MachineID_Local (1) +#define D_DemonID_LocalHost (1) //////////////////////////////// //~ rjf: Entity Handle Types @@ -39,7 +39,7 @@ D_ControllerKindEnum; typedef struct D_Handle D_Handle; struct D_Handle { - D_MachineID machine_id; + D_DemonID demon_id; D_ControllerKind controller_kind; U64 entity_id; }; diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 4efe2707..8eafd57c 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1,13 +1,40 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +//////////////////////////////// +//~ rjf: Local Host Demon Interface + +D_DemonInterface d_local_demon = +{ + dmn_ctrl_exclusive_access_begin, + dmn_ctrl_exclusive_access_end, + dmn_ctrl_launch, + dmn_ctrl_attach, + dmn_ctrl_kill, + dmn_ctrl_detach, + dmn_ctrl_run, + dmn_halt, + dmn_access_open, + dmn_access_close, + dmn_process_memory_reserve, + dmn_process_memory_commit, + dmn_process_memory_decommit, + dmn_process_memory_release, + dmn_process_memory_protect, + dmn_process_read, + dmn_process_write, + dmn_thread_read_reg_block, + dmn_thread_write_reg_block, + dmn_thread_get_module_tls_vaddr, +}; + //////////////////////////////// //~ rjf: Basic Type Functions internal U64 d_hash_from_handle(D_Handle handle) { - U64 buf[] = {handle.machine_id, handle.controller_kind, handle.entity_id}; + U64 buf[] = {handle.demon_id, handle.controller_kind, handle.entity_id}; U64 hash = u64_hash_from_str8(str8_struct(&handle)); return hash; } @@ -182,7 +209,7 @@ d_handle_array_from_list(Arena *arena, D_HandleList *src) internal String8 d_string_from_handle(Arena *arena, D_Handle handle) { - String8 result = str8f(arena, "$%x$%x$%I64x", handle.machine_id, handle.controller_kind, handle.entity_id); + String8 result = str8f(arena, "$%x$%x$%I64x", handle.demon_id, handle.controller_kind, handle.entity_id); return result; } @@ -196,10 +223,10 @@ d_handle_from_string(String8 string) String8List parts = str8_split(scratch.arena, string, &split, 1, 0); if(parts.first && parts.first->next && parts.first->next->next) { - D_MachineID machine_id = (U32)u64_from_str8(parts.first->string, 16); + D_DemonID demon_id = (U32)u64_from_str8(parts.first->string, 16); D_ControllerKind controller_kind = (U32)u64_from_str8(parts.first->next->string, 16); U64 entity_id = u64_from_str8(parts.first->next->next->string, 16); - handle.machine_id = machine_id; + handle.demon_id = demon_id; handle.controller_kind = controller_kind; handle.entity_id = entity_id; } @@ -220,11 +247,11 @@ d_dmn_from_handle(D_Handle handle) } internal D_Handle -d_handle_from_dmn(D_MachineID machine_id, DMN_Handle handle) +d_handle_from_dmn(D_DemonID demon_id, DMN_Handle handle) { D_Handle result = {0}; { - result.machine_id = machine_id; + result.demon_id = demon_id; result.controller_kind = D_ControllerKind_Demon; result.entity_id = handle.u64[0]; } @@ -232,11 +259,11 @@ d_handle_from_dmn(D_MachineID machine_id, DMN_Handle handle) } internal D_Handle -d_dump_handle_make(D_MachineID machine_id, U64 id) +d_dump_handle_make(D_DemonID demon_id, U64 id) { D_Handle result = {0}; { - result.machine_id = machine_id; + result.demon_id = demon_id; result.controller_kind = D_ControllerKind_Dump; result.entity_id = id; } @@ -915,7 +942,7 @@ d_entity_ctx_rw_store_alloc(void) store->ctx.hash_slots_count = 1024; store->ctx.hash_slots = push_array(arena, D_EntityHashSlot, store->ctx.hash_slots_count); D_Entity *root = store->ctx.root = d_entity_alloc(store, &d_entity_nil, D_EntityKind_Root, Arch_Null, d_handle_zero(), 0); - D_Entity *local_machine = d_entity_alloc(store, root, D_EntityKind_Machine, Arch_CURRENT, d_handle_from_dmn(D_MachineID_Local, dmn_handle_zero()), 0); + D_Entity *local_machine = d_entity_alloc(store, root, D_EntityKind_Machine, Arch_CURRENT, d_handle_from_dmn(D_DemonID_LocalHost, dmn_handle_zero()), 0); Temp scratch = scratch_begin(0, 0); String8 local_machine_name = push_str8f(scratch.arena, "This PC (%S)", get_system_info()->machine_name); d_entity_equip_string(store, local_machine, local_machine_name); @@ -1306,7 +1333,7 @@ d_entity_store_apply_events(D_EntityCtxRWStore *store, D_EventList *list) //- rjf: processes case D_EventKind_NewProc: { - D_Entity *machine = d_entity_from_handle(d_handle_from_dmn(event->entity.machine_id, dmn_handle_zero())); + D_Entity *machine = d_entity_from_handle(d_handle_from_dmn(event->entity.demon_id, dmn_handle_zero())); if(machine != &d_entity_nil) { D_Entity *process = d_entity_alloc(store, machine, D_EntityKind_Process, event->arch, event->entity, (U64)event->entity_id); @@ -2934,7 +2961,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)) { Access *access = access_open(); - D_Handle process_handle = d_handle_from_dmn(D_MachineID_Local, ev->process); + D_Handle process_handle = d_handle_from_dmn(D_DemonID_LocalHost, ev->process); D_Entity *process = d_entity_from_handle(process_handle); D_Entity *module = d_module_from_process_vaddr(process, ev->instruction_pointer); if(module != &d_entity_nil) @@ -3135,7 +3162,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_NewProc; out_evt->msg_id = msg_id; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->arch = event->arch; out_evt->entity_id = event->code; out_evt->os = OperatingSystem_CURRENT; // TODO: operating system of the remote target machine @@ -3148,8 +3175,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_NewThread; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->thread); - out_evt->parent = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->arch = event->arch; out_evt->entity_id = event->code; out_evt->stack_base = event->stack_pointer; @@ -3164,8 +3191,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D Temp scratch2 = scratch_begin(&scratch.arena, 1); //- rjf: unpack module - D_Handle process_handle = d_handle_from_dmn(D_MachineID_Local, event->process); - D_Handle module_handle = d_handle_from_dmn(D_MachineID_Local, event->module); + D_Handle process_handle = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + D_Handle module_handle = d_handle_from_dmn(D_DemonID_LocalHost, event->module); String8 module_path = path_normalized_from_string(scratch2.arena, event->string); U64 exe_timestamp = properties_from_file_path(module_path).modified; DMN_ModuleInfo *module_info = event->module_info; @@ -3330,7 +3357,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_EndProc; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->u64_code = event->code; d_ctrl_state->process_counter -= 1; }break; @@ -3341,7 +3368,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_EndThread; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->thread); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); out_evt->entity_id = event->code; }break; @@ -3349,7 +3376,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D case DMN_EventKind_UnloadModule: { //- rjf: unpack module - D_Handle module_handle = d_handle_from_dmn(D_MachineID_Local, event->module); + D_Handle module_handle = d_handle_from_dmn(D_DemonID_LocalHost, event->module); D_Entity *module_ent = d_entity_from_handle(module_handle); D_Entity *process_ent = d_process_from_entity(module_ent); String8 module_path = event->string; @@ -3386,8 +3413,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_DebugString; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->thread); - out_evt->parent = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->string = str8_substr(event->string, r1u64(string_idx*d_ctrl_state->c2u_ring_max_string_size, (string_idx+1)*d_ctrl_state->c2u_ring_max_string_size)); } }break; @@ -3398,8 +3425,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_ThreadName; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->thread); - out_evt->parent = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->string = event->string; out_evt->entity_id = event->code; }break; @@ -3410,8 +3437,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_ThreadColor; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->thread); - out_evt->parent = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->entity_id = event->code; out_evt->rgba = event->user_data; }break; @@ -3421,7 +3448,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_SetVAddrRangeNote; - out_evt->parent = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->msg_id = msg->msg_id; out_evt->vaddr_rng = r1u64(event->address, event->address + event->size); out_evt->string = event->string; @@ -3432,8 +3459,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_SetBreakpoint; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->thread); - out_evt->parent = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); out_evt->bp_flags = d_breakpoint_flags_from_dmn_trap_flags(event->flags); }break; @@ -3444,8 +3471,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D // TODO(rjf): this needs to be reflected in the resolved trap list too!!!!!!!! D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_UnsetBreakpoint; - out_evt->entity = d_handle_from_dmn(D_MachineID_Local, event->thread); - out_evt->parent = d_handle_from_dmn(D_MachineID_Local, event->process); + out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); out_evt->bp_flags = d_breakpoint_flags_from_dmn_trap_flags(event->flags); }break; @@ -4111,7 +4138,7 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) // rjf: create process handle d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; - process = d_dump_handle_make(D_MachineID_Local, d_ctrl_state->ctrl_thread_dump_handle_id_gen); + process = d_dump_handle_make(D_DemonID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); // rjf: gather threads dump_threads_count = threads_count; @@ -4128,7 +4155,7 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; MDMP_Thread *thread = &threads[idx]; - D_Handle thread_handle = d_dump_handle_make(D_MachineID_Local, d_ctrl_state->ctrl_thread_dump_handle_id_gen); + D_Handle thread_handle = d_dump_handle_make(D_DemonID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); dump_threads[idx].thread_handle = thread_handle; dump_threads[idx].id = thread->id; dump_threads[idx].context_foff = thread->thread_context.foff; @@ -4233,7 +4260,7 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) // rjf: store d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; - dump_modules[idx].module_handle = d_dump_handle_make(D_MachineID_Local, d_ctrl_state->ctrl_thread_dump_handle_id_gen); + dump_modules[idx].module_handle = d_dump_handle_make(D_DemonID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); dump_modules[idx].vaddr_range = vaddr_range; dump_modules[idx].path = str8_copy(arena, module_name); dump_modules[idx].file = module_file; @@ -4589,7 +4616,7 @@ d_ctrl_thread__kill_all(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) default:{}break; case DMN_EventKind_CreateProcess: { - D_Entity *new_process = d_entity_from_handle(d_handle_from_dmn(D_MachineID_Local, event->process)); + D_Entity *new_process = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->process)); Task *t = push_array(scratch.arena, Task, 1); t->process = new_process; DLLPushBack(first_task, last_task, t); @@ -4810,7 +4837,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) node != 0; node = node->next) { - D_Handle thread = d_handle_from_dmn(D_MachineID_Local, node->v); + D_Handle thread = d_handle_from_dmn(D_DemonID_LocalHost, node->v); U64 thread_pre_rip = d_ip_from_thread(thread); U64 thread_post_rip = thread_pre_rip; for(B32 done = 0; !done;) @@ -4829,7 +4856,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { default:{}break; case DMN_EventKind_ExitThread: - if(d_handle_match(d_handle_from_dmn(D_MachineID_Local, event->thread), thread)) + if(d_handle_match(d_handle_from_dmn(D_DemonID_LocalHost, event->thread), thread)) { stop_cause = D_EventCause_Error; goto stop; @@ -4992,7 +5019,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_EvalScope *eval_scope = d_ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, &d_entity_nil); { DMN_TrapChunkList new_traps = {0}; - d_ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, eval_scope, d_handle_from_dmn(D_MachineID_Local, event->process), &msg->user_bps, &new_traps); + d_ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, eval_scope, d_handle_from_dmn(D_DemonID_LocalHost, event->process), &msg->user_bps, &new_traps); log_infof("step_rule: create_process -> resolve traps\n"); log_infof("new_traps:\n{\n"); for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) @@ -5011,11 +5038,11 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) }break; case DMN_EventKind_LoadModule: { - D_Entity *thread = d_entity_from_handle(d_handle_from_dmn(D_MachineID_Local, event->thread)); + D_Entity *thread = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->thread)); D_EvalScope *eval_scope = d_ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, thread); { DMN_TrapChunkList new_traps = {0}; - d_ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, eval_scope, d_handle_from_dmn(D_MachineID_Local, event->process), d_handle_from_dmn(D_MachineID_Local, event->module), &msg->user_bps, &new_traps); + d_ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, eval_scope, d_handle_from_dmn(D_DemonID_LocalHost, event->process), d_handle_from_dmn(D_DemonID_LocalHost, event->module), &msg->user_bps, &new_traps); log_infof("step_rule: load_module -> resolve traps\n"); log_infof("new_traps:\n{\n"); for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) @@ -5036,7 +5063,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { D_Entity *bp = &d_entity_nil; { - D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_MachineID_Local, event->process)); + D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->process)); for(D_Entity *child = process->first; child != &d_entity_nil; child = child->next) { if(child->kind == D_EntityKind_Breakpoint && @@ -5068,7 +5095,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) Access *access = access_open(); //- rjf: unpack process/module info - D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_MachineID_Local, event->process)); + D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->process)); D_Entity *module = d_entity_child_from_kind(process, D_EntityKind_Module); U64 module_base_vaddr = module->vaddr_range.min; DI_Key dbgi_key = d_dbgi_key_from_module(module); @@ -5294,8 +5321,8 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) ////////////////////////// //- rjf: unpack info about thread attached to event // - D_Entity *thread = d_entity_from_handle(d_handle_from_dmn(D_MachineID_Local, event->thread)); - D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_MachineID_Local, event->process)); + D_Entity *thread = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->thread)); + D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->process)); Arch arch = thread->arch; U64 thread_rip_vaddr = d_ip_from_thread(thread->handle); D_Entity *module = d_module_from_process_vaddr(process, thread_rip_vaddr); @@ -5483,7 +5510,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_EventCause cond_bp_single_step_stop_cause = D_EventCause_Null; if(hit_conditional_bp_but_filtered) LogInfoNamedBlockF("conditional_bp_hit_single_step") { - D_Handle thread = d_handle_from_dmn(D_MachineID_Local, event->thread); + D_Handle thread = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); U64 thread_pre_rip = d_ip_from_thread(thread); U64 thread_post_rip = thread_pre_rip; for(B32 single_step_done = 0; !single_step_done;) @@ -5513,7 +5540,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) }break; case DMN_EventKind_SingleStep: { - single_step_done = d_handle_match(d_handle_from_dmn(D_MachineID_Local, event->thread), thread); + single_step_done = d_handle_match(d_handle_from_dmn(D_DemonID_LocalHost, event->thread), thread); cond_bp_single_step_stop_cause = d_event_cause_from_dmn_event_kind(event->kind); }break; } @@ -5674,7 +5701,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_EventCause step_past_trap_net_stop_cause = D_EventCause_Null; if(step_past_trap_net) LogInfoNamedBlockF("trap_net__single_step_past_trap_net") { - D_Handle thread = d_handle_from_dmn(D_MachineID_Local, event->thread); + D_Handle thread = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); U64 thread_pre_rip = d_ip_from_thread(thread); U64 thread_post_rip = thread_pre_rip; for(B32 single_step_done = 0; single_step_done == 0;) @@ -5703,7 +5730,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) }break; case DMN_EventKind_SingleStep: { - single_step_done = d_handle_match(d_handle_from_dmn(D_MachineID_Local, event->thread), thread); + single_step_done = d_handle_match(d_handle_from_dmn(D_DemonID_LocalHost, event->thread), thread); step_past_trap_net_stop_cause = d_event_cause_from_dmn_event_kind(event->kind); }break; } @@ -5763,8 +5790,8 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_Event *event = d_event_list_push(scratch.arena, &evts); event->kind = D_EventKind_Stopped; event->cause = stop_cause; - event->entity = d_handle_from_dmn(D_MachineID_Local, stop_event->thread); - event->parent = d_handle_from_dmn(D_MachineID_Local, stop_event->process); + event->entity = d_handle_from_dmn(D_DemonID_LocalHost, stop_event->thread); + event->parent = d_handle_from_dmn(D_DemonID_LocalHost, stop_event->process); event->exception_code = stop_event->code; event->exception_kind = d_exception_kind_from_dmn(stop_event->exception_kind); event->vaddr_rng = r1u64(stop_event->address, stop_event->address); @@ -5847,8 +5874,8 @@ d_ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) event->cause = stop_cause; if(stop_event != 0) { - event->entity = d_handle_from_dmn(D_MachineID_Local, stop_event->thread); - event->parent = d_handle_from_dmn(D_MachineID_Local, stop_event->process); + event->entity = d_handle_from_dmn(D_DemonID_LocalHost, stop_event->thread); + event->parent = d_handle_from_dmn(D_DemonID_LocalHost, stop_event->process); event->exception_code = stop_event->code; event->exception_kind = d_exception_kind_from_dmn(stop_event->exception_kind); event->vaddr_rng = r1u64(stop_event->address, stop_event->address); diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index a003bd0f..c1bd93bc 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -313,6 +313,68 @@ struct D_ModuleReqCacheNode #define D_WAKEUP_FUNCTION_DEF(name) void name(void) typedef D_WAKEUP_FUNCTION_DEF(D_WakeupFunctionType); +//////////////////////////////// +//~ rjf: Demon Interface V-Table + +typedef struct D_DemonInterface D_DemonInterface; +struct D_DemonInterface +{ + //- rjf: control + void (*ctrl_exclusive_access_begin)(void); + void (*ctrl_exclusive_access_end)(void); + U32 (*launch)(DMN_CtrlCtx *ctx, ProcessLaunchParams *params); + B32 (*attach)(DMN_CtrlCtx *ctx, U32 pid); + B32 (*kill)(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code); + B32 (*detach)(DMN_CtrlCtx *ctx, DMN_Handle process); + DMN_EventList (*run)(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls); + + //- rjf: halting + void (*halt)(U64 code, U64 user_data); + + //- rjf: non-blocking-control-thread access barriers + B32 (*access_open)(void); + void (*access_close)(void); + + //- rjf: process memory + U64 (*reserve_memory)(DMN_Handle process, U64 vaddr, U64 size); + void (*commit_memory)(DMN_Handle process, U64 vaddr, U64 size); + void (*decommit_memory)(DMN_Handle process, U64 vaddr, U64 size); + void (*release_memory)(DMN_Handle process, U64 vaddr, U64 size); + void (*protect_memory)(DMN_Handle process, U64 vaddr, U64 size, AccessFlags flags); + U64 (*read_memory)(DMN_Handle process, Rng1U64 range, void *dst); + B32 (*write_memory)(DMN_Handle process, Rng1U64 range, void *src); + + //- rjf: threads + B32 (*thread_read_reg_block)(DMN_Handle thread, void *dst); + B32 (*thread_write_reg_block)(DMN_Handle thread, void *src); + B32 (*thread_get_module_tls_vaddr)(DMN_Handle thread, DMN_Handle module, U64 *vaddr_out); +}; + +typedef struct D_DemonState D_DemonState; +struct D_DemonState +{ + D_DemonState *next; + D_DemonState *prev; + D_DemonID id; + D_DemonInterface *fn; + Arena *event_arena; + DMN_EventList events; +}; + +typedef struct D_DemonStateSlot D_DemonStateSlot; +struct D_DemonStateSlot +{ + D_DemonState *first; + D_DemonState *last; +}; + +typedef struct D_DemonCache D_DemonCache; +struct D_DemonCache +{ + U64 slots_count; + D_DemonStateSlot *slots; +}; + //////////////////////////////// //~ rjf: Main State Types @@ -426,8 +488,8 @@ internal D_HandleArray d_handle_array_from_list(Arena *arena, D_HandleList *src internal String8 d_string_from_handle(Arena *arena, D_Handle handle); internal D_Handle d_handle_from_string(String8 string); internal DMN_Handle d_dmn_from_handle(D_Handle handle); -internal D_Handle d_handle_from_dmn(D_MachineID machine_id, DMN_Handle handle); -internal D_Handle d_dump_handle_make(D_MachineID machine_id, U64 id); +internal D_Handle d_handle_from_dmn(D_DemonID demon_id, DMN_Handle handle); +internal D_Handle d_dump_handle_make(D_DemonID demon_id, U64 id); //////////////////////////////// //~ rjf: Trap Type Functions diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index 038f1107..8a9215ac 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -2104,13 +2104,13 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P }break; case D_CmdKind_FreezeLocalMachine: { - D_MachineID machine_id = D_MachineID_Local; - d_cmd(D_CmdKind_FreezeMachine, .entity = d_handle_from_dmn(machine_id, dmn_handle_zero())); + D_DemonID demon_id = D_DemonID_LocalHost; + d_cmd(D_CmdKind_FreezeMachine, .entity = d_handle_from_dmn(demon_id, dmn_handle_zero())); }break; case D_CmdKind_ThawLocalMachine: { - D_MachineID machine_id = D_MachineID_Local; - d_cmd(D_CmdKind_ThawMachine, .entity = d_handle_from_dmn(machine_id, dmn_handle_zero())); + D_DemonID demon_id = D_DemonID_LocalHost; + d_cmd(D_CmdKind_ThawMachine, .entity = d_handle_from_dmn(demon_id, dmn_handle_zero())); }break; case D_CmdKind_FreezeEntity: case D_CmdKind_ThawEntity: diff --git a/src/linux/socket/linux_socket.c b/src/linux/socket/linux_socket.c index ab976978..ed792d18 100644 --- a/src/linux/socket/linux_socket.c +++ b/src/linux/socket/linux_socket.c @@ -15,6 +15,12 @@ lnx_sock_listener_thread_entry_point(void *p) struct epoll_event evts[1] = {0}; int wait_result = LNX_RETRY_ON_EINTR(epoll_wait(session->epoll_fd, evts, ArrayCount(evts), -1)); + //- rjf: call wakeup hook + if(session->wakeup_hook) + { + session->wakeup_hook(); + } + //- rjf: code is 0 -> listener is ready for accept if(evts[0].data.u64 == 0) { @@ -93,7 +99,7 @@ lnx_sock_listener_thread_entry_point(void *p) ssize_t recv_result = LNX_RETRY_ON_EINTR(recv(con->socket, buffer, sizeof(buffer), MSG_DONTWAIT)); // rjf: bytes received -> push result to user. - if(recv_result >= 0) + if(recv_result > 0) { RingGuard g = guarded_ring_open(session->s2u_ring); U64 header[5] = @@ -113,7 +119,8 @@ lnx_sock_listener_thread_entry_point(void *p) else RWMutexScope(stripe->rw_mutex, 1) { close(con->socket); - LNX_RETRY_ON_EINTR(epoll_ctl(session->epoll_fd, EPOLL_CTL_DEL, con->socket, 0)); + struct epoll_event evt = {0}; // NOTE(rjf): required on Linux pre-2.6.9 + LNX_RETRY_ON_EINTR(epoll_ctl(session->epoll_fd, EPOLL_CTL_DEL, con->socket, &evt)); DLLRemove(slot->first, slot->last, con); con->next = stripe->free; stripe->free = con; @@ -296,7 +303,7 @@ sock_async_tick(void) // rjf: got socket? -> send B32 send_failed = 0; - if(ep_socket != -1 && send(ep_socket, t->data.str, t->data.size, 0) == -1) + if(ep_socket != -1 && send(ep_socket, t->data.str, t->data.size, MSG_NOSIGNAL) == -1) { send_failed = 1; } @@ -311,7 +318,8 @@ sock_async_tick(void) if(MemoryMatchStruct(&c->endpoint, &t->endpoint)) { close(c->socket); - LNX_RETRY_ON_EINTR(epoll_ctl(session->epoll_fd, EPOLL_CTL_DEL, c->socket, 0)); + struct epoll_event evt = {0}; // NOTE(rjf): required on Linux pre-2.6.9 + LNX_RETRY_ON_EINTR(epoll_ctl(session->epoll_fd, EPOLL_CTL_DEL, c->socket, &evt)); DLLRemove(slot->first, slot->last, c); c->next = stripe->free; stripe->free = c; @@ -337,6 +345,7 @@ sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) session->u2s_ring = guarded_ring_alloc(arena, KB(256)); session->s2u_ring = guarded_ring_alloc(arena, KB(256)); session->epoll_fd = epoll_create(1); + session->wakeup_hook = wakeup_hook; //- rjf: set up listener { diff --git a/src/linux/socket/linux_socket.h b/src/linux/socket/linux_socket.h index 17c4396c..1d277d70 100644 --- a/src/linux/socket/linux_socket.h +++ b/src/linux/socket/linux_socket.h @@ -40,6 +40,7 @@ struct LNX_SOCK_Session GuardedRing *u2s_ring; GuardedRing *s2u_ring; int epoll_fd; + SOCK_WakeupFunctionType *wakeup_hook; int tcp_listen_socket; U64 connection_slots_count; StripeArray connection_stripes; diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index f2957383..8cff4df2 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -1136,7 +1136,7 @@ wm_get_events(Arena *arena, B32 wait) { .fd = ConnectionNumber(lnx_wm_state->display), .events = POLLIN }, { .fd = lnx_wm_state->wakeup_fd, .events = POLLIN }, }; - int timeout = wait && evts.count == 0 ? -1 : 0; + int timeout = (wait && evts.count == 0 ? -1 : 0); int poll_status = poll(poll_fds, ArrayCount(poll_fds), timeout); if(poll_fds[1].revents & POLLIN) { diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 68a18e02..a992380a 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -711,7 +711,7 @@ rd_ctrl_entity_from_eval_space(E_Space space) space.kind == RD_EvalSpaceKind_MetaUnattachedProcess) { D_Handle handle; - handle.machine_id = (U32)((space.u64s[0] & 0xffffffff00000000ull) >> 32); + handle.demon_id = (U32)((space.u64s[0] & 0xffffffff00000000ull) >> 32); handle.controller_kind = (U32)((space.u64s[0] & 0x00000000ffffffffull) >> 0); handle.entity_id = space.u64s[1]; entity = d_entity_from_handle(handle); @@ -726,7 +726,7 @@ rd_eval_space_from_ctrl_entity(D_Entity *entity, E_SpaceKind kind) if(entity != &d_entity_nil) { space = e_space_make(kind); - space.u64s[0] = (((U64)entity->handle.machine_id) << 32) | (((U64)entity->handle.controller_kind) << 0); + space.u64s[0] = (((U64)entity->handle.demon_id) << 32) | (((U64)entity->handle.controller_kind) << 0); space.u64s[1] = entity->handle.entity_id; } return space; @@ -2828,7 +2828,7 @@ rd_view_ui(Rng2F32 rect) CFG_Node *locked = cfg_node_child_from_string_or_alloc(rd_state->cfg, cfg, s("locked")); cfg_node_release_all_children(rd_state->cfg, locked); CFG_Node *mid = cfg_node_new(rd_state->cfg, locked, s("mid")); - cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.machine_id); + cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.demon_id); CFG_Node *pid = cfg_node_new(rd_state->cfg, locked, s("pid")); cfg_node_newf(rd_state->cfg, pid, "%I64u", entity->id); CFG_Node *addr = cfg_node_new(rd_state->cfg, locked, s("addr")); @@ -4631,7 +4631,7 @@ rd_view_ui(Rng2F32 rect) CFG_Node *locked = cfg_node_child_from_string_or_alloc(rd_state->cfg, cfg, s("locked")); cfg_node_release_all_children(rd_state->cfg, locked); CFG_Node *mid = cfg_node_new(rd_state->cfg, locked, s("mid")); - cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.machine_id); + cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.demon_id); CFG_Node *pid = cfg_node_new(rd_state->cfg, locked, s("pid")); cfg_node_newf(rd_state->cfg, pid, "%I64u", entity->id); CFG_Node *addr = cfg_node_new(rd_state->cfg, locked, s("addr")); @@ -6367,7 +6367,7 @@ rd_window_frame(void) ID(panel); ID(view); #undef ID -#define Handle(name) ui_labelf("%s: [0x%x, 0x%x, 0x%I64x]", #name, (regs->name).machine_id, (regs->name).controller_kind, (regs->name).entity_id) +#define Handle(name) ui_labelf("%s: [0x%x, 0x%x, 0x%I64x]", #name, (regs->name).demon_id, (regs->name).controller_kind, (regs->name).entity_id) Handle(machine); Handle(process); Handle(module); @@ -12822,7 +12822,7 @@ rd_frame(void) { e_string2expr_map_insert(scratch.arena, macro_map, entity->string, expr); } - if(kind == D_EntityKind_Machine && entity->handle.machine_id == D_MachineID_Local) + if(kind == D_EntityKind_Machine && entity->handle.demon_id == D_DemonID_LocalHost) { e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("local_machine"), expr); } diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 339b38a7..4b6dd33a 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -628,6 +628,7 @@ entry_point(CmdLine *cmd_line) make_directory(g_logs_folder); //- rjf: dispatch to top-level codepath based on execution mode + U32 ipc_packet_magic_start = 0x72726300; switch(exec_mode) { //- rjf: normal execution @@ -689,16 +690,34 @@ entry_point(CmdLine *cmd_line) { for(B32 quit = 0; !quit;) { - //- rjf: receive socket data, run as IPC messages + //- rjf: receive packets, run as IPC messages B32 ipc_command_frame = 0; SOCK_Protocol ipc_protocol = SOCK_Protocol_TCP; SOCK_Endpoint ipc_endpoint = {0}; { Temp scratch = scratch_begin(0, 0); - String8 msg = {0}; - if(sock_recv(scratch.arena, icp_sock_session, &ipc_protocol, &ipc_endpoint, &msg, 0)) + String8 packet = {0}; + if(sock_recv(scratch.arena, icp_sock_session, &ipc_protocol, &ipc_endpoint, &packet, 0)) { + // rjf: use header to determine message portion + String8 msg = {0}; + { + U64 msg_size = 0; + U64 header_size = sizeof(ipc_packet_magic_start) + sizeof(U64); + if(str8_match(packet, str8_struct(&ipc_packet_magic_start), StringMatchFlag_RightSideSloppy) && + packet.size >= header_size) + { + U64 msg_size_cap = packet.size - header_size; + msg_size = *(U64 *)(packet.str + sizeof(ipc_packet_magic_start)); + msg_size = Min(msg_size, msg_size_cap); + msg = str8_substr(packet, r1u64(header_size, header_size+msg_size)); + } + } + + // rjf: split command by ;s String8List cmd_parts_of_msg = str8_split(scratch.arena, msg, (U8 *)";", 1, 0); + + // rjf: pick target window RD_WindowState *dst_ws = rd_state->first_window_state; for(RD_WindowState *ws = dst_ws; ws != &rd_nil_window_state; ws = ws->order_next) { @@ -708,6 +727,8 @@ entry_point(CmdLine *cmd_line) break; } } + + // rjf: got a window -> send command if(dst_ws != &rd_nil_window_state) { dst_ws->window_temporarily_focused_ipc = 1; @@ -766,12 +787,10 @@ entry_point(CmdLine *cmd_line) //- rjf: gather command outputs & send them back back to sender if(ipc_command_frame) { - StringJoin join = {s(""), s("\0"), s("")}; - String8 msg = str8_list_join(scratch.arena, &rd_state->cmd_outputs, &join); - if(msg.size == 0) - { - msg = s("done"); - } + U64 packet_size = rd_state->cmd_outputs.total_size; + str8_list_push_front(scratch.arena, &rd_state->cmd_outputs, str8_struct(&packet_size)); + str8_list_push_front(scratch.arena, &rd_state->cmd_outputs, str8_struct(&ipc_packet_magic_start)); + String8 msg = str8_list_join(scratch.arena, &rd_state->cmd_outputs, 0); sock_send(icp_sock_session, ipc_protocol, ipc_endpoint, msg, now_time_us()+5000000); } } @@ -803,13 +822,23 @@ entry_point(CmdLine *cmd_line) //- rjf: form message from command line inputs String8List parts = {0}; { + String8List text_parts = {0}; for EachIndex(idx, cmd_line->argc-1) { - str8_list_push(scratch.arena, &parts, str8_cstring(cmd_line->argv[idx+1])); + String8 arg = str8_cstring(cmd_line->argv[idx+1]); + if(!str8_match(arg, s("--ipc"), 0) && !str8_match(arg, s("-ipc"), 0)) + { + str8_list_push(scratch.arena, &text_parts, arg); + } } + StringJoin join = {.sep = s(" ")}; + String8 text = str8_list_join(scratch.arena, &text_parts, &join); + U64 msg_size = text_parts.total_size; + str8_list_push(scratch.arena, &parts, str8_struct(&ipc_packet_magic_start)); + str8_list_push(scratch.arena, &parts, str8_struct(&msg_size)); + str8_list_push(scratch.arena, &parts, text); } - StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; - String8 msg = str8_list_join(scratch.arena, &parts, &join); + String8 msg = str8_list_join(scratch.arena, &parts, 0); //- rjf: send to server, get outputs String8List outputs = {0}; @@ -817,15 +846,24 @@ entry_point(CmdLine *cmd_line) { SOCK_Protocol outputs_protocol = SOCK_Protocol_TCP; SOCK_Endpoint outputs_endpoint = {0}; - String8 output_data = {0}; - if(sock_recv(scratch.arena, ipc_sock_session, &outputs_protocol, &outputs_endpoint, &output_data, now_time_us()+10000000)) + String8 packet = {0}; + if(sock_recv(scratch.arena, ipc_sock_session, &outputs_protocol, &outputs_endpoint, &packet, now_time_us()+10000000) && + str8_match(packet, str8_struct(&ipc_packet_magic_start), StringMatchFlag_RightSideSloppy) && + packet.size >= sizeof(ipc_packet_magic_start) + sizeof(U64)) { + U64 msg_size = *(U64 *)(packet.str + sizeof(ipc_packet_magic_start)); + String8 msg = str8_skip(packet, sizeof(ipc_packet_magic_start) + sizeof(U64)); + msg.size = Min(msg.size, msg_size); U8 split_char = 0; - outputs = str8_split(scratch.arena, output_data, &split_char, 1, 0); + outputs = str8_split(scratch.arena, msg, &split_char, 1, 0); } } //- rjf: write outputs to stdout + if(outputs.node_count == 0) + { + fprintf(stdout, "(response received, no output)\n"); + } for(String8Node *n = outputs.first; n != 0; n = n->next) { fwrite(n->string.str, 1, n->string.size, stdout); diff --git a/src/render/opengl/render_opengl.c b/src/render/opengl/render_opengl.c index 4d06297e..7432616f 100644 --- a/src/render/opengl/render_opengl.c +++ b/src/render/opengl/render_opengl.c @@ -108,7 +108,11 @@ r_ogl_scissor(Rng2F32 clip, Vec2F32 viewport_dim) { glScissor(x0, (S32)viewport_dim.y - y1, width, height); } - return width > 0 && height > 0; + else + { + glScissor(0, 0, 0, 0); + } + return width > 0 || height > 0; } //////////////////////////////// From 4f5e795ae9c8d0197dc234f09e7c35f1a6053786 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 7 Oct 2026 12:58:24 -0600 Subject: [PATCH 230/251] dbg_server, remote controller connections checkpoint --- project.4coder | 2 +- src/dbg_engine/dbg_engine.mdesk | 4 + src/dbg_engine/dbg_engine_ctrl.c | 4 + src/dbg_engine/dbg_engine_user.c | 18 +++ src/dbg_engine/dbg_engine_user.h | 2 + src/dbg_engine/generated/dbg_engine.meta.h | 2 + .../dbg_server.c} | 21 +++- src/dbg_server/dbg_server.h | 118 ++++++++++++++++++ src/dbg_server/dbg_server_client.c | 24 ++++ src/dbg_server/dbg_server_client.h | 13 ++ src/dbg_server/dbg_server_host.c | 28 +++++ src/dbg_server/dbg_server_host.h | 15 +++ src/dbg_server/dbg_server_remote.c | 2 + src/dbg_server/dbg_server_remote.h | 7 ++ src/raddbg/generated/raddbg.meta.c | 12 +- src/raddbg/generated/raddbg.meta.h | 6 +- src/raddbg/raddbg.mdesk | 4 +- src/raddbg/raddbg_core.c | 60 ++++++++- src/raddbg/raddbg_main.c | 7 ++ src/raddbg_server/raddbg_server.h | 99 --------------- 20 files changed, 338 insertions(+), 110 deletions(-) rename src/{raddbg_server/raddbg_server.c => dbg_server/dbg_server.c} (87%) create mode 100644 src/dbg_server/dbg_server.h create mode 100644 src/dbg_server/dbg_server_client.c create mode 100644 src/dbg_server/dbg_server_client.h create mode 100644 src/dbg_server/dbg_server_host.c create mode 100644 src/dbg_server/dbg_server_host.h create mode 100644 src/dbg_server/dbg_server_remote.c create mode 100644 src/dbg_server/dbg_server_remote.h delete mode 100644 src/raddbg_server/raddbg_server.h diff --git a/project.4coder b/project.4coder index a6fa152c..360ba491 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg debug", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg debug meta", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/dbg_engine/dbg_engine.mdesk b/src/dbg_engine/dbg_engine.mdesk index cbcf517e..df8d4e09 100644 --- a/src/dbg_engine/dbg_engine.mdesk +++ b/src/dbg_engine/dbg_engine.mdesk @@ -58,6 +58,10 @@ D_CmdTable: // | | | //- rjf: crash dump opening {OpenCrashDump 1 1 `folder:\\"$input\\"` FilePath 0 0 0 0 0 1 1 FileOutline "open_crash_dump" "Open Crash Dump" "Opens a crash dump file and recreates debuggee state at the time of the crash." "" "" } + + //- rjf: remote debugger connecting + {OpenRemoteDebugger 1 1 "" String 0 0 0 0 0 1 1 Machine "open_remote_debugger" "Open Remote Debugger" "Opens a remote debugger by IP address." "" "" } + {CloseRemoteDebugger 1 1 "query:machines" CtrlEntity 0 0 0 0 0 1 1 Machine "close_remote_debugger" "Close Remote Debugger" "Closes a remote debugger." "" "" } } @enum D_CmdKind: diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 8eafd57c..1b5ba1c7 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -2527,6 +2527,10 @@ d_ctrl_thread__entry_point(void *p) case D_MsgKind_Null: case D_MsgKind_COUNT:{}break; + //- rjf: remote debuggers + case D_MsgKind_OpenRemoteDebugger:{}break; + case D_MsgKind_CloseRemoteDebugger:{}break; + //- rjf: target operations case D_MsgKind_Launch: {d_ctrl_thread__launch (ctrl_ctx, msg);}break; case D_MsgKind_Attach: {d_ctrl_thread__attach (ctrl_ctx, msg);}break; diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index 8a9215ac..e11e2b50 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -2191,6 +2191,24 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P MemoryCopyArray(msg->exception_code_filters, exception_code_filters); msg->auto_download_debug_info = auto_download_debug_info; }break; + + //- rjf: opening/closing remote debuggers + case D_CmdKind_OpenRemoteDebugger: + { + D_Msg *msg = d_msg_list_push(scratch.arena, &ctrl_msgs); + msg->kind = D_MsgKind_OpenRemoteDebugger; + msg->path = str8_copy(scratch.arena, params->string); + MemoryCopyArray(msg->exception_code_filters, exception_code_filters); + msg->auto_download_debug_info = auto_download_debug_info; + }break; + case D_CmdKind_CloseRemoteDebugger: + { + D_Msg *msg = d_msg_list_push(scratch.arena, &ctrl_msgs); + msg->kind = D_MsgKind_CloseRemoteDebugger; + msg->entity = params->entity; + MemoryCopyArray(msg->exception_code_filters, exception_code_filters); + msg->auto_download_debug_info = auto_download_debug_info; + }break; } // rjf: do run if needed diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index 7cf59551..c4efad3b 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -160,6 +160,8 @@ struct D_RunLocalsCache typedef enum D_MsgKind { D_MsgKind_Null, + D_MsgKind_OpenRemoteDebugger, + D_MsgKind_CloseRemoteDebugger, D_MsgKind_Launch, D_MsgKind_Attach, D_MsgKind_OpenCrashDump, diff --git a/src/dbg_engine/generated/dbg_engine.meta.h b/src/dbg_engine/generated/dbg_engine.meta.h index 2391d4d2..e3943a97 100644 --- a/src/dbg_engine/generated/dbg_engine.meta.h +++ b/src/dbg_engine/generated/dbg_engine.meta.h @@ -45,6 +45,8 @@ D_CmdKind_SetEntityName, D_CmdKind_DownloadModuleDebugInfo, D_CmdKind_Attach, D_CmdKind_OpenCrashDump, +D_CmdKind_OpenRemoteDebugger, +D_CmdKind_CloseRemoteDebugger, D_CmdKind_COUNT, } D_CmdKind; diff --git a/src/raddbg_server/raddbg_server.c b/src/dbg_server/dbg_server.c similarity index 87% rename from src/raddbg_server/raddbg_server.c rename to src/dbg_server/dbg_server.c index 96e81200..9dce96f0 100644 --- a/src/raddbg_server/raddbg_server.c +++ b/src/dbg_server/dbg_server.c @@ -4,16 +4,16 @@ //////////////////////////////// //~ rjf: Message Parsing -internal RDS_Msg -rds_msg_from_data(Arena *arena, String8 data) +internal DS_Msg +ds_msg_from_serialized(Arena *arena, String8 data) { //- rjf: unpack message U16 magic_maybe = 0; str8_deserial_read_struct(data, 0, &magic_maybe); //- rjf: parse message - RDS_Msg msg = {0}; - if(magic_maybe == RDS_MSG_MAGIC) + DS_Msg msg = {0}; + if(magic_maybe == DS_MSG_MAGIC) { U64 off = sizeof(magic_maybe); @@ -79,3 +79,16 @@ rds_msg_from_data(Arena *arena, String8 data) return msg; } + +internal String8 +ds_serialized_from_msg(Arena *arena, DS_Msg *msg) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List srl = {0}; + { + // TODO(rjf) + } + scratch_end(scratch); + String8 result = str8_list_join(arena, &srl, 0); + return result; +} diff --git a/src/dbg_server/dbg_server.h b/src/dbg_server/dbg_server.h new file mode 100644 index 00000000..7f1520c0 --- /dev/null +++ b/src/dbg_server/dbg_server.h @@ -0,0 +1,118 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DBG_SERVER_H +#define DBG_SERVER_H + +//////////////////////////////// +//~ rjf: Messages (Client -> Server) + +#define DS_MSG_MAGIC 0x7273 + +typedef enum DS_MsgKind +{ + DS_MsgKind_Null, + + //- rjf: debuggee attaching/detaching ops + DS_MsgKind_Launch, + DS_MsgKind_Attach, + DS_MsgKind_Kill, + DS_MsgKind_Detach, + + //- rjf: running ops + DS_MsgKind_Run, + DS_MsgKind_SingleStep, + + //- rjf: halting + DS_MsgKind_Halt, + + //- rjf: memory ops + DS_MsgKind_MemoryReserve, + DS_MsgKind_MemoryCommit, + DS_MsgKind_MemoryDecommit, + DS_MsgKind_MemoryRelease, + DS_MsgKind_MemoryProtect, + DS_MsgKind_MemoryRead, + DS_MsgKind_MemoryWrite, + + //- rjf: thread ops + DS_MsgKind_ThreadRegBlockRead, + DS_MsgKind_ThreadRegBlockWrite, + DS_MsgKind_ThreadGetModuleTLSVAddr, + + //- rjf: system ops + DS_MsgKind_ListSystemProcesses, +} +DS_MsgKind; + +typedef U32 DS_MsgFlags; +enum +{ + DS_MsgFlag_InheritEnv = (1<<0), + DS_MsgFlag_DebugSubprocesses = (1<<1), + DS_MsgFlag_IgnorePreviousException = (1<<2), + DS_MsgFlag_RunEntitiesAreUnfrozen = (1<<3), + DS_MsgFlag_RunEntitiesAreProcesses = (1<<4), +}; + +typedef struct DS_Msg DS_Msg; +struct DS_Msg +{ + DS_MsgKind kind; + DS_MsgFlags flags; + U64 id; + + //- rjf: general parameters + Rng1U64 vaddr_range; + AccessFlags access_flags; + DMN_Handle entity; + U64 pid; + + //- rjf: process launching parameters + String8List command_line; + String8 path; + String8List env; + + //- rjf: running parameters + DMN_TrapChunkList traps; + DMN_HandleArray run_entities; +}; + +//////////////////////////////// +//~ rjf: Server Instance + +typedef enum DS_InstKind +{ + DS_InstKind_Host, + DS_InstKind_Remote, +} +DS_InstKind; + +typedef struct DS_Inst DS_Inst; +struct DS_Inst +{ + DS_InstKind kind; + U64 u64[1]; +}; + +//////////////////////////////// +//~ rjf: Responses (Server -> Client responses to messages) + +typedef struct DS_Response DS_Response; +struct DS_Response +{ + U64 id; + DS_Inst inst; + String8 data; + DMN_Handle entity; + U64 pid; + DMN_EventList events; +}; + +//////////////////////////////// +//~ rjf: Message Parsing + +internal DS_Msg ds_msg_from_serialized(Arena *arena, String8 data); +internal String8 ds_serialized_from_msg(Arena *arena, DS_Msg msg); + +#endif // DBG_SERVER_H diff --git a/src/dbg_server/dbg_server_client.c b/src/dbg_server/dbg_server_client.c new file mode 100644 index 00000000..7d253777 --- /dev/null +++ b/src/dbg_server/dbg_server_client.c @@ -0,0 +1,24 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal B32 +ds_push_msg(GuardedRing *out_ring, DS_Inst inst, DS_Msg *msg, U64 endt_us) +{ + B32 result = 0; + switch(inst.kind) + { + default:{}break; + case DS_InstKind_Host: + { + result = ds_push_msg__host(out_ring, msg, endt_us); + }break; + } + return result; +} + +internal B32 +ds_pop_response(Arena *arena, GuardedRing *out_ring, DS_Response *response_out, U64 endt_us) +{ + B32 result = 0; + return result; +} diff --git a/src/dbg_server/dbg_server_client.h b/src/dbg_server/dbg_server_client.h new file mode 100644 index 00000000..cffb3eca --- /dev/null +++ b/src/dbg_server/dbg_server_client.h @@ -0,0 +1,13 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DBG_SERVER_CLIENT_H +#define DBG_SERVER_CLIENT_H + +//////////////////////////////// +//~ rjf: Messages/Responses + +internal B32 ds_push_msg(GuardedRing *out_ring, DS_Inst inst, DS_Msg *msg, U64 endt_us); +internal B32 ds_pop_response(Arena *arena, GuardedRing *out_ring, DS_Response *response_out, U64 endt_us); + +#endif // DBG_SERVER_CLIENT_H diff --git a/src/dbg_server/dbg_server_host.c b/src/dbg_server/dbg_server_host.c new file mode 100644 index 00000000..4b9e5811 --- /dev/null +++ b/src/dbg_server/dbg_server_host.c @@ -0,0 +1,28 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal B32 +ds_push_msg__host(GuardedRing *out_ring, DS_Msg *msg, U64 endt_us) +{ + B32 result = 1; + switch(msg->kind) + { + default:{}break; + + //- rjf: launching + case DS_MsgKind_Launch: + { + ProcessLaunchParams params = {0}; + { + params.cmd_line = msg->command_line; + params.path = msg->path; + params.env = msg->env; + params.inherit_env = !!(msg->flags & DS_MsgFlag_InheritEnv); + params.debug_subprocesses = !!(msg->flags & DS_MsgFlag_DebugSubprocesses); + } + U32 pid = dmn_ctrl_launch(0, ¶ms); + }break; + + } + return result; +} diff --git a/src/dbg_server/dbg_server_host.h b/src/dbg_server/dbg_server_host.h new file mode 100644 index 00000000..1e6a8e67 --- /dev/null +++ b/src/dbg_server/dbg_server_host.h @@ -0,0 +1,15 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DBG_SERVER_HOST_H +#define DBG_SERVER_HOST_H + +typedef struct DS_HostState DS_HostState; +struct DS_HostState +{ + Arena *arena; +}; + +internal B32 ds_push_msg__host(GuardedRing *out_ring, DS_Msg *msg, U64 endt_us); + +#endif // DBG_SERVER_HOST_H diff --git a/src/dbg_server/dbg_server_remote.c b/src/dbg_server/dbg_server_remote.c new file mode 100644 index 00000000..ae447aab --- /dev/null +++ b/src/dbg_server/dbg_server_remote.c @@ -0,0 +1,2 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) diff --git a/src/dbg_server/dbg_server_remote.h b/src/dbg_server/dbg_server_remote.h new file mode 100644 index 00000000..9cb853c2 --- /dev/null +++ b/src/dbg_server/dbg_server_remote.h @@ -0,0 +1,7 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DBG_SERVER_REMOTE_H +#define DBG_SERVER_REMOTE_H + +#endif // DBG_SERVER_REMOTE_H diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 87f97090..cbe210e7 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -62,7 +62,7 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[370] = +RD_VocabInfo rd_vocab_info_table[374] = { {str8_lit_comp("type_view"), str8_lit_comp("type_views"), str8_lit_comp("Type View"), str8_lit_comp("Type Views"), RD_IconKind_Binoculars}, {str8_lit_comp("file_path_map"), str8_lit_comp("file_path_maps"), str8_lit_comp("File Path Map"), str8_lit_comp("File Path Maps"), RD_IconKind_FileOutline}, @@ -214,6 +214,8 @@ RD_VocabInfo rd_vocab_info_table[370] = {str8_lit_comp("download_module_debug_info"), str8_lit_comp(""), str8_lit_comp("Download Module Debug Info"), str8_lit_comp(""), RD_IconKind_Module}, {str8_lit_comp("attach"), str8_lit_comp(""), str8_lit_comp("Attach"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("open_crash_dump"), str8_lit_comp(""), str8_lit_comp("Open Crash Dump"), str8_lit_comp(""), RD_IconKind_FileOutline}, +{str8_lit_comp("open_remote_debugger"), str8_lit_comp(""), str8_lit_comp("Open Remote Debugger"), str8_lit_comp(""), RD_IconKind_Machine}, +{str8_lit_comp("close_remote_debugger"), str8_lit_comp(""), str8_lit_comp("Close Remote Debugger"), str8_lit_comp(""), RD_IconKind_Machine}, {str8_lit_comp("exit"), str8_lit_comp(""), str8_lit_comp("Exit"), str8_lit_comp(""), RD_IconKind_X}, {str8_lit_comp("open_palette"), str8_lit_comp(""), str8_lit_comp("Open Palette"), str8_lit_comp(""), RD_IconKind_List}, {str8_lit_comp("run_command"), str8_lit_comp(""), str8_lit_comp("Run Command"), str8_lit_comp(""), RD_IconKind_Null}, @@ -221,6 +223,8 @@ RD_VocabInfo rd_vocab_info_table[370] = {str8_lit_comp("state"), str8_lit_comp(""), str8_lit_comp("State"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp("Eval"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp("Line From Virtual Address"), str8_lit_comp(""), RD_IconKind_Null}, +{str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp("Remote Controller Connect"), str8_lit_comp(""), RD_IconKind_Null}, +{str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp("Remote Controller Connect"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp("OS Event"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("select_thread"), str8_lit_comp(""), str8_lit_comp("Select Thread"), str8_lit_comp(""), RD_IconKind_Thread}, {str8_lit_comp("select_unwind"), str8_lit_comp(""), str8_lit_comp("Select Unwind"), str8_lit_comp(""), RD_IconKind_Null}, @@ -599,7 +603,7 @@ Rng1U64 rd_reg_slot_range_table[56] = {OffsetOf(RD_Regs, wm_event), OffsetOf(RD_Regs, wm_event) + sizeof(WM_Event *)}, }; -RD_CmdKindInfo rd_cmd_kind_info_table[257] = +RD_CmdKindInfo rd_cmd_kind_info_table[261] = { {0}, { str8_lit_comp("launch_and_run"), str8_lit_comp("Starts debugging a new instance of a target, then runs."), str8_lit_comp("launch,start,run,target"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:targets")}}, @@ -638,6 +642,8 @@ RD_CmdKindInfo rd_cmd_kind_info_table[257] = { str8_lit_comp("download_module_debug_info"), str8_lit_comp("Tries to download a module's debug info from symbol server(s)."), str8_lit_comp(""), str8_lit_comp("{module_commands}"), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CtrlEntity, str8_lit_comp("query:modules")}}, { str8_lit_comp("attach"), str8_lit_comp("Attaches to a process that is already running on the local machine."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_PID, str8_lit_comp("query:unattached_processes")}}, { str8_lit_comp("open_crash_dump"), str8_lit_comp("Opens a crash dump file and recreates debuggee state at the time of the crash."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, +{ str8_lit_comp("open_remote_debugger"), str8_lit_comp("Opens a remote debugger by IP address."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_String, str8_lit_comp("")}}, +{ str8_lit_comp("close_remote_debugger"), str8_lit_comp("Closes a remote debugger."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CtrlEntity, str8_lit_comp("query:machines")}}, { str8_lit_comp("exit"), str8_lit_comp("Exits the debugger."), str8_lit_comp("quit,close,abort"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("open_palette"), str8_lit_comp("Opens the palette."), str8_lit_comp("help,cmd,lister"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("run_command"), str8_lit_comp("Runs a command from the command palette."), str8_lit_comp("help,cmd"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CmdName, str8_lit_comp("query:commands")}}, @@ -645,6 +651,8 @@ RD_CmdKindInfo rd_cmd_kind_info_table[257] = { str8_lit_comp("state"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Expr, str8_lit_comp("")}}, { str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Vaddr, str8_lit_comp("")}}, +{ str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("select_thread"), str8_lit_comp("Selects a thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Thread, str8_lit_comp("query:threads")}}, { str8_lit_comp("select_unwind"), str8_lit_comp("Selects an unwind frame number for the selected thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("query:call_stack")}}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 8ce32f7e..950f11d3 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -106,6 +106,8 @@ RD_CmdKind_SetEntityName, RD_CmdKind_DownloadModuleDebugInfo, RD_CmdKind_Attach, RD_CmdKind_OpenCrashDump, +RD_CmdKind_OpenRemoteDebugger, +RD_CmdKind_CloseRemoteDebugger, RD_CmdKind_Exit, RD_CmdKind_OpenPalette, RD_CmdKind_RunCommand, @@ -113,6 +115,8 @@ RD_CmdKind_RunExternalDriverTextCommand, RD_CmdKind_State, RD_CmdKind_Eval, RD_CmdKind_LineFromVAddr, +RD_CmdKind_RemoteControllerConnect, +RD_CmdKind_RemoteControllerDisconnect, RD_CmdKind_WMEvent, RD_CmdKind_SelectThread, RD_CmdKind_SelectUnwind, @@ -630,7 +634,7 @@ Z(getting_started)\ C_LINKAGE_BEGIN extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; -extern RD_VocabInfo rd_vocab_info_table[370]; +extern RD_VocabInfo rd_vocab_info_table[374]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; extern String8 rd_reg_slot_code_name_table[56]; extern Rng1U64 rd_reg_slot_range_table[56]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 5dbf075f..836bff3e 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1046,7 +1046,9 @@ RD_CmdTable: // | | | | {State 0 0 1 "" Null 0 0 0 0 0 0 0 Null "state" "State" "" "" "" } {Eval 0 0 1 "" Expr 0 0 0 0 0 0 0 Null "eval" "Eval" "" "" "" } {LineFromVAddr 0 0 1 "" Vaddr 0 0 0 0 0 0 0 Null "line_from_vaddr" "Line From Virtual Address" "" "" "" } - + {RemoteControllerConnect 0 0 1 "" Null 0 0 0 0 0 0 0 Null "remote_controller_connect" "Remote Controller Connect" "" "" "" } + {RemoteControllerDisconnect 0 0 1 "" Null 0 0 0 0 0 0 0 Null "remote_controller_connect" "Remote Controller Connect" "" "" "" } + //- rjf: os event passthrough {WMEvent 0 0 0 "" Null 0 0 0 0 0 0 0 Null "wm_event" "OS Event" "" "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index a992380a..1e1dfd1c 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -7903,12 +7903,50 @@ rd_window_frame(void) } } + // rjf: remote controller viz + { + CFG_Node *root = cfg_node_root(); + CFG_Node *transient = cfg_node_child_from_string(root, s("transient")); + CFG_NodePtrList remote_controllers = cfg_node_child_list_from_string(scratch.arena, transient, s("remote_controller")); + for EachNode(n, CFG_NodePtrNode, remote_controllers.first) + { + CFG_Node *rc = n->v; + String8 ip_addr = rc->first->string; + UI_VisualMargin(ui_top_font_size()*0.5f) + UI_CornerRadius(ui_top_font_size()*0.5f) + { + ui_set_next_pref_width(ui_children_sum(1)); + ui_set_next_child_layout_axis(Axis2_X); + UI_Box *rc_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects, + "###rc_button"); + wm_window_push_custom_title_bar_client_area(ws->os, rc_box->rect); + UI_Parent(rc_box) + UI_PrefWidth(ui_text_dim(10, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_Padding(ui_em(0.5f, 1.f)) + RD_Font(RD_FontSlot_Icons) + UI_TagF("pop") + { + ui_label(rd_icon_kind_text_table[RD_IconKind_Machine]); + } + UI_Signal rc_sig = ui_signal_from_box(rc_box); + if(ui_hovering(rc_sig)) UI_Tooltip + { + ui_label(ip_addr); + } + } + } + } + // rjf: loaded project viz if(do_user_prof) { CFG_Node *root = cfg_node_root(); - CFG_Node *project = cfg_node_child_from_string(root, str8_lit("project")); - CFG_Node *name = cfg_node_child_from_string(project, str8_lit("name")); + CFG_Node *project = cfg_node_child_from_string(root, s("project")); + CFG_Node *name = cfg_node_child_from_string(project, s("name")); String8 project_name = name->first->string; if(project_name.size == 0) { @@ -13934,6 +13972,24 @@ rd_frame(void) } str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "}\n"); }break; + case RD_CmdKind_RemoteControllerConnect: + { + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); + CFG_Node *ctrlr = cfg_node_new(rd_state->cfg, transient, s("remote_controller")); + cfg_node_new(rd_state->cfg, ctrlr, rd_regs()->string); + }break; + case RD_CmdKind_RemoteControllerDisconnect: + { + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); + for(CFG_Node *child = transient; child != &cfg_nil_node; child = child->next) + { + if(str8_match(child->string, s("remote_controller"), 0) && str8_match(child->first->string, rd_regs()->string, 0)) + { + cfg_node_release(rd_state->cfg, child); + break; + } + } + }break; //- rjf: exiting case RD_CmdKind_Exit: diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 4b6dd33a..139750f7 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -1,6 +1,13 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +//////////////////////////////// +//~ rjf: remote TODO notes +// +// [ ] ui -> ui? +// [ ] ui (w/ debug info) -> ui (w/out debug info)? +// [ ] ctrl -> slim server? + //////////////////////////////// //~ rjf: linux TODO notes // diff --git a/src/raddbg_server/raddbg_server.h b/src/raddbg_server/raddbg_server.h deleted file mode 100644 index 47b0fc51..00000000 --- a/src/raddbg_server/raddbg_server.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef RADDBG_SERVER_H -#define RADDBG_SERVER_H - -//////////////////////////////// -//~ rjf: Messages (Client -> Server) - -#define RDS_MSG_MAGIC 0x7273 - -typedef enum RDS_MsgKind -{ - RDS_MsgKind_Null, - - //- rjf: debuggee attaching/detaching ops - RDS_MsgKind_Launch, RDS_MsgKind_DemonThreadHandledFirst = RDS_MsgKind_Launch, - RDS_MsgKind_Attach, - RDS_MsgKind_Kill, - RDS_MsgKind_Detach, - - //- rjf: running ops - RDS_MsgKind_Run, - RDS_MsgKind_SingleStep, RDS_MsgKind_DemonThreadHandledLast = RDS_MsgKind_SingleStep, - - //- rjf: halting - RDS_MsgKind_Halt, - - //- rjf: memory ops - RDS_MsgKind_MemoryReserve, - RDS_MsgKind_MemoryCommit, - RDS_MsgKind_MemoryDecommit, - RDS_MsgKind_MemoryRelease, - RDS_MsgKind_MemoryProtect, - RDS_MsgKind_MemoryRead, - RDS_MsgKind_MemoryWrite, - - //- rjf: thread ops - RDS_MsgKind_ThreadRegBlockRead, - RDS_MsgKind_ThreadRegBlockWrite, - RDS_MsgKind_ThreadGetModuleTLSVAddr, - - //- rjf: system ops - RDS_MsgKind_ListSystemProcesses, -} -RDS_MsgKind; - -typedef U32 RDS_MsgFlags; -enum -{ - RDS_MsgFlag_InheritEnv = (1<<0), - RDS_MsgFlag_DebugSubprocesses = (1<<1), - RDS_MsgFlag_IgnorePreviousException = (1<<2), - RDS_MsgFlag_RunEntitiesAreUnfrozen = (1<<3), - RDS_MsgFlag_RunEntitiesAreProcesses = (1<<4), -}; - -typedef struct RDS_Msg RDS_Msg; -struct RDS_Msg -{ - RDS_MsgKind kind; - RDS_MsgFlags flags; - U64 id; - - //- rjf: general parameters - Rng1U64 vaddr_range; - AccessFlags access_flags; - DMN_Handle entity; - U64 pid; - - //- rjf: process launching parameters - String8List command_line; - String8 path; - String8List env; - - //- rjf: running parameters - DMN_TrapChunkList traps; - DMN_HandleArray run_entities; -}; - -//////////////////////////////// -//~ rjf: Responses (Server -> Client responses to messages) - -typedef struct RDS_Response RDS_Response; -struct RDS_Response -{ - U64 id; - String8 data; - DMN_Handle entity; - U64 pid; - U64 demon_event_count; -}; - -//////////////////////////////// -//~ rjf: Message Parsing - -internal RDS_Msg rds_msg_from_data(Arena *arena, String8 data); - -#endif // RADDBG_SERVER_H From 297cb626f517dcbfe17547d12839e80cc8494572 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 7 Oct 2026 12:24:13 -0700 Subject: [PATCH 231/251] fix bad msg_size calculation --- src/raddbg/raddbg_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 139750f7..84f55d04 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -840,7 +840,7 @@ entry_point(CmdLine *cmd_line) } StringJoin join = {.sep = s(" ")}; String8 text = str8_list_join(scratch.arena, &text_parts, &join); - U64 msg_size = text_parts.total_size; + U64 msg_size = text.size; str8_list_push(scratch.arena, &parts, str8_struct(&ipc_packet_magic_start)); str8_list_push(scratch.arena, &parts, str8_struct(&msg_size)); str8_list_push(scratch.arena, &parts, text); From 9262938b2f4c7215c6060da057990c07517e4d4c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 7 Oct 2026 13:05:08 -0700 Subject: [PATCH 232/251] raddbg: store connected remote controller address correctly, ui: fix tooltips escaping window --- project.raddbg | 2 +- src/raddbg/generated/raddbg.meta.c | 4 ++-- src/raddbg/raddbg.mdesk | 4 ++-- src/raddbg/raddbg_core.c | 19 +++++++++++-------- src/raddbg/raddbg_core.h | 1 + src/raddbg/raddbg_main.c | 9 +++++---- src/ui/ui_core.c | 10 +++++----- 7 files changed, 27 insertions(+), 22 deletions(-) diff --git a/project.raddbg b/project.raddbg index fc0f4280..08f38bb0 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -35,7 +36,6 @@ target: executable: "build/raddbg" working_directory: build arguments: "--user:raddbg_test.user" - enabled: 1 } target: { diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index cbe210e7..039ab39d 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -224,7 +224,7 @@ RD_VocabInfo rd_vocab_info_table[374] = {str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp("Eval"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp("Line From Virtual Address"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp("Remote Controller Connect"), str8_lit_comp(""), RD_IconKind_Null}, -{str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp("Remote Controller Connect"), str8_lit_comp(""), RD_IconKind_Null}, +{str8_lit_comp("remote_controller_disconnect"), str8_lit_comp(""), str8_lit_comp("Remote Controller Disconnect"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp("OS Event"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("select_thread"), str8_lit_comp(""), str8_lit_comp("Select Thread"), str8_lit_comp(""), RD_IconKind_Thread}, {str8_lit_comp("select_unwind"), str8_lit_comp(""), str8_lit_comp("Select Unwind"), str8_lit_comp(""), RD_IconKind_Null}, @@ -652,7 +652,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[261] = { str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Expr, str8_lit_comp("")}}, { str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Vaddr, str8_lit_comp("")}}, { str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, -{ str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("remote_controller_disconnect"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("select_thread"), str8_lit_comp("Selects a thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Thread, str8_lit_comp("query:threads")}}, { str8_lit_comp("select_unwind"), str8_lit_comp("Selects an unwind frame number for the selected thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("query:call_stack")}}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 836bff3e..c069fac5 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1047,8 +1047,8 @@ RD_CmdTable: // | | | | {Eval 0 0 1 "" Expr 0 0 0 0 0 0 0 Null "eval" "Eval" "" "" "" } {LineFromVAddr 0 0 1 "" Vaddr 0 0 0 0 0 0 0 Null "line_from_vaddr" "Line From Virtual Address" "" "" "" } {RemoteControllerConnect 0 0 1 "" Null 0 0 0 0 0 0 0 Null "remote_controller_connect" "Remote Controller Connect" "" "" "" } - {RemoteControllerDisconnect 0 0 1 "" Null 0 0 0 0 0 0 0 Null "remote_controller_connect" "Remote Controller Connect" "" "" "" } - + {RemoteControllerDisconnect 0 0 1 "" Null 0 0 0 0 0 0 0 Null "remote_controller_disconnect" "Remote Controller Disconnect" "" "" "" } + //- rjf: os event passthrough {WMEvent 0 0 0 "" Null 0 0 0 0 0 0 0 Null "wm_event" "OS Event" "" "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 1e1dfd1c..f1406967 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -7912,16 +7912,19 @@ rd_window_frame(void) { CFG_Node *rc = n->v; String8 ip_addr = rc->first->string; - UI_VisualMargin(ui_top_font_size()*0.5f) - UI_CornerRadius(ui_top_font_size()*0.5f) { ui_set_next_pref_width(ui_children_sum(1)); ui_set_next_child_layout_axis(Axis2_X); - UI_Box *rc_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| - UI_BoxFlag_DrawBackground| - UI_BoxFlag_DrawHotEffects| - UI_BoxFlag_DrawActiveEffects, - "###rc_button"); + UI_Box *rc_box = &ui_nil_box; + UI_VisualMargin(ui_top_font_size()*0.5f) + UI_CornerRadius(ui_top_font_size()*0.5f) + { + rc_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects, + "###rc_button"); + } wm_window_push_custom_title_bar_client_area(ws->os, rc_box->rect); UI_Parent(rc_box) UI_PrefWidth(ui_text_dim(10, 0)) @@ -13976,7 +13979,7 @@ rd_frame(void) { CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); CFG_Node *ctrlr = cfg_node_new(rd_state->cfg, transient, s("remote_controller")); - cfg_node_new(rd_state->cfg, ctrlr, rd_regs()->string); + cfg_node_new(rd_state->cfg, ctrlr, sock_string_from_endpoint(scratch.arena, rd_state->ipc_sock_endpoint)); }break; case RD_CmdKind_RemoteControllerDisconnect: { diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index 7015ffd5..1884b2ec 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -421,6 +421,7 @@ struct RD_State B32 override_focus_on_stop; S32 frame_depth; U64 frame_eval_memread_endt_us; + SOCK_Endpoint ipc_sock_endpoint; // rjf: installation setting state B32 installed; diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 84f55d04..a3f69200 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -690,8 +690,8 @@ entry_point(CmdLine *cmd_line) d_set_wakeup_hook(wakeup_hook_ctrl); } - //- rjf: set up socket session for ICP - SOCK_Session icp_sock_session = sock_session_open((U16)ipc_port, wakeup_hook_ctrl); + //- rjf: set up socket session for IPC + SOCK_Session ipc_sock_session = sock_session_open((U16)ipc_port, wakeup_hook_ctrl); //- rjf: main application loop { @@ -704,7 +704,7 @@ entry_point(CmdLine *cmd_line) { Temp scratch = scratch_begin(0, 0); String8 packet = {0}; - if(sock_recv(scratch.arena, icp_sock_session, &ipc_protocol, &ipc_endpoint, &packet, 0)) + if(sock_recv(scratch.arena, ipc_sock_session, &ipc_protocol, &ipc_endpoint, &packet, 0)) { // rjf: use header to determine message portion String8 msg = {0}; @@ -761,6 +761,7 @@ entry_point(CmdLine *cmd_line) } rd_request_frame(); ipc_command_frame = 1; + rd_state->ipc_sock_endpoint = ipc_endpoint; } } } @@ -798,7 +799,7 @@ entry_point(CmdLine *cmd_line) str8_list_push_front(scratch.arena, &rd_state->cmd_outputs, str8_struct(&packet_size)); str8_list_push_front(scratch.arena, &rd_state->cmd_outputs, str8_struct(&ipc_packet_magic_start)); String8 msg = str8_list_join(scratch.arena, &rd_state->cmd_outputs, 0); - sock_send(icp_sock_session, ipc_protocol, ipc_endpoint, msg, now_time_us()+5000000); + sock_send(ipc_sock_session, ipc_protocol, ipc_endpoint, msg, now_time_us()+5000000); } } } diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index 43f5d13b..e383d7a2 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -1256,8 +1256,8 @@ ui_end_build(void) } else { - ui_state->tooltip_root->rect.x0 = 10000; - ui_state->tooltip_root->rect.y0 = 10000; + ui_state->tooltip_root->rect.x0 = ui_state->tooltip_root->rect.x1 = 10000; + ui_state->tooltip_root->rect.y0 = ui_state->tooltip_root->rect.y1 = 10000; } } @@ -1951,8 +1951,8 @@ ui_tooltip_begin_base(void) ui_push_flags(0); ui_push_text_raster_flags(ui_bottom_text_raster_flags()); ui_push_font_size(ui_bottom_font_size()); - ui_push_tag(str8_lit(".")); - ui_push_tag(str8_lit("floating")); + ui_push_tag(s(".")); + ui_push_tag(s("floating")); } internal void @@ -1973,7 +1973,7 @@ ui_tooltip_begin(void) ui_tooltip_begin_base(); ui_set_next_squish(0.1f-ui_state->tooltip_open_t*0.1f); ui_set_next_transparency(1-ui_state->tooltip_open_t); - UI_Flags(UI_BoxFlag_Floating|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBackgroundBlur|UI_BoxFlag_DrawDropShadow|UI_BoxFlag_SquishAnchored) + UI_Flags(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBackgroundBlur|UI_BoxFlag_DrawDropShadow|UI_BoxFlag_SquishAnchored) UI_PrefWidth(ui_children_sum(1)) UI_PrefHeight(ui_children_sum(1)) UI_CornerRadius(ui_top_font_size()*0.25f) From 9549d096fa2f0e79f9e200ca429563b872a207a8 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 7 Oct 2026 13:13:19 -0700 Subject: [PATCH 233/251] build metaprogram if not present even if option not passed, run always unless specified otherwise --- build.bat | 1 + build.sh | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/build.bat b/build.bat index 19c07e29..07991d37 100644 --- a/build.bat +++ b/build.bat @@ -136,6 +136,7 @@ popd :: --- Build & Run Metaprogram ------------------------------------------------ pushd build +if not exist metagen.exe set meta=1 if "%meta%"=="1" ( echo [building metagen] %compile_debug% ..\src\metagen\metagen_main.c %compile_link% %out%metagen.exe || exit /b 1 diff --git a/build.sh b/build.sh index e40fd48f..dec9001c 100755 --- a/build.sh +++ b/build.sh @@ -60,14 +60,17 @@ compile() { mkdir -p build local # --- Build & Run Metaprogram ------------------------------------------------- +cd build +if [[ ! -f metagen ]]; then meta='1'; fi if [[ "${meta:-0}" == "1" ]] then echo "[building metagen]" - cd build $compiler $cc_debug ../src/metagen/metagen_main.c $cc_link -o metagen - ./metagen - cd .. fi +if [[ ! -v no_meta ]] then + ./metagen +fi +cd .. # --- Assemble BLAKE3 --------------------------------------------------------- if [[ ! -f "build/blake3.a" ]] @@ -134,6 +137,7 @@ then fi # --- Warn On Debug Builds (if debug not explicitly specified) ---------------- -if [[ ! -v debug && ! -v release ]] then +if [[ ! -v debug && ! -v release ]] +then echo "[INFO] Debug build complete. For a faster build, call this script with the \`release\` argument (this will take significantly longer than a debug build)." fi From f9eef60935bf1bbd3228b3c137a3b08ec284f260 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 7 Oct 2026 13:20:37 -0700 Subject: [PATCH 234/251] build.sh: fix missing ;s in bash for older interpreters --- build.sh | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/build.sh b/build.sh index dec9001c..5d4e72ee 100755 --- a/build.sh +++ b/build.sh @@ -62,19 +62,17 @@ mkdir -p build local # --- Build & Run Metaprogram ------------------------------------------------- cd build if [[ ! -f metagen ]]; then meta='1'; fi -if [[ "${meta:-0}" == "1" ]] -then +if [[ "${meta:-0}" == "1" ]]; then echo "[building metagen]" $compiler $cc_debug ../src/metagen/metagen_main.c $cc_link -o metagen fi -if [[ ! -v no_meta ]] then +if [[ ! -v no_meta ]]; then ./metagen fi cd .. # --- Assemble BLAKE3 --------------------------------------------------------- -if [[ ! -f "build/blake3.a" ]] -then +if [[ ! -f "build/blake3.a" ]]; then echo "[assembling blake3]" $compiler -c -g -o build/blake3_sse2_x86-64_unix.o src/third_party/blake3/blake3_sse2_x86-64_unix.S $compiler -c -g -o build/blake3_sse41_x86-64_unix.o src/third_party/blake3/blake3_sse41_x86-64_unix.S @@ -130,14 +128,12 @@ if [[ "${torture:-0}" == "1" ]]; then didbuild=1 && compile ../src/ cd .. # --- Warn On No Builds ------------------------------------------------------- -if [[ "${didbuild:-0}" == "0" ]] -then +if [[ "${didbuild:-0}" == "0" ]]; then echo "[WARNING] no valid build target specified; must use build target names as arguments to this script, like \`./build.sh raddbg\` or \`./build.sh radlink\`." exit 1 fi # --- Warn On Debug Builds (if debug not explicitly specified) ---------------- -if [[ ! -v debug && ! -v release ]] -then +if [[ ! -v debug && ! -v release ]]; then echo "[INFO] Debug build complete. For a faster build, call this script with the \`release\` argument (this will take significantly longer than a debug build)." fi From ece977e2edb58cc742704df18a50e01a2689ef3d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 7 Oct 2026 13:22:33 -0700 Subject: [PATCH 235/251] linux_window_manager :fall back to 60 Hz if default refresh rate couldn't be detected --- src/linux/window_manager/linux_window_manager.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/linux/window_manager/linux_window_manager.c b/src/linux/window_manager/linux_window_manager.c index 8cff4df2..72c85ea2 100644 --- a/src/linux/window_manager/linux_window_manager.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -407,6 +407,10 @@ wm_init(void) } } XRRFreeScreenResources(res); + if(lnx_wm_state->gfx_info.default_refresh_rate == 0) + { + lnx_wm_state->gfx_info.default_refresh_rate = 60.f; + } } //- rjf: fill out cursors From b6d8c3fd9eaf7b55960d80738365742a8fba9e29 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 7 Oct 2026 13:55:58 -0700 Subject: [PATCH 236/251] dwarf_parse: fix incorrect expectation of max_ops_per_inst in pre-dwarf-4; fix incorrect comp unit file --- src/dwarf/dwarf_parse.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dwarf/dwarf_parse.c b/src/dwarf/dwarf_parse.c index 6faabc27..bf58d85a 100644 --- a/src/dwarf/dwarf_parse.c +++ b/src/dwarf/dwarf_parse.c @@ -706,7 +706,7 @@ dw2_parse_ctx_equip_unit_root_tag(DW2_ParseCtx *ctx_out, DW2_Tag *tag, DW2_Offse ctx_out->unit_base_addr = low_pc_attrib->val.addr; ctx_out->language = lang_attrib->val.u128.u64[0]; ctx_out->unit_dir = comp_dir_attrib->val.string; - ctx_out->unit_file = comp_dir_attrib->val.string; + ctx_out->unit_file = name_attrib->val.string; // rjf: find offset tables Rng1U64Array rnglists_tables_ranges_array = {offset_tables->rnglists_tables_ranges, offset_tables->rnglists_tables_count}; @@ -960,7 +960,10 @@ dw2_read_line_table_header(Arena *arena, DW_Raw *raw, DW2_ParseCtx *ctx, String8 off += dw2_read_fmt_u64(data, off, format, &header_length); opl_header_length_off = off; off += str8_deserial_read_struct(data, off, &min_inst_length); - off += str8_deserial_read_struct(data, off, &max_ops_per_inst); + if(version >= DW_Version_4) + { + off += str8_deserial_read_struct(data, off, &max_ops_per_inst); + } off += str8_deserial_read_struct(data, off, &default_is_stmt); off += str8_deserial_read_struct(data, off, &line_base); off += str8_deserial_read_struct(data, off, &line_range); From c269823e1505a2c3bb0bffe7dffcfd5283fb2ed4 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 9 Oct 2026 07:49:24 -0700 Subject: [PATCH 237/251] debug server checkpoint --- project.4coder | 2 +- src/dbg_engine/dbg_engine_ctrl.c | 27 --------- src/dbg_engine/dbg_engine_ctrl.h | 62 -------------------- src/dbg_server/dbg_server.c | 16 ++++- src/dbg_server/dbg_server.h | 37 +++++------- src/dbg_server/dbg_server_client.c | 24 -------- src/dbg_server/dbg_server_client.h | 13 ----- src/dbg_server/dbg_server_host.c | 28 --------- src/dbg_server/dbg_server_host.h | 15 ----- src/dbg_server/dbg_server_remote.c | 2 - src/dbg_server/dbg_server_remote.h | 7 --- src/raddbg_server/raddbg_server_main.c | 81 ++++++++++++++++++++------ 12 files changed, 94 insertions(+), 220 deletions(-) delete mode 100644 src/dbg_server/dbg_server_client.c delete mode 100644 src/dbg_server/dbg_server_client.h delete mode 100644 src/dbg_server/dbg_server_host.c delete mode 100644 src/dbg_server/dbg_server_host.h delete mode 100644 src/dbg_server/dbg_server_remote.c delete mode 100644 src/dbg_server/dbg_server_remote.h diff --git a/project.4coder b/project.4coder index 360ba491..da10909d 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg debug meta", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg_server telemetry debug", .linux = "./build.sh raddbg debug meta", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 1b5ba1c7..0aa67934 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1,33 +1,6 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -//////////////////////////////// -//~ rjf: Local Host Demon Interface - -D_DemonInterface d_local_demon = -{ - dmn_ctrl_exclusive_access_begin, - dmn_ctrl_exclusive_access_end, - dmn_ctrl_launch, - dmn_ctrl_attach, - dmn_ctrl_kill, - dmn_ctrl_detach, - dmn_ctrl_run, - dmn_halt, - dmn_access_open, - dmn_access_close, - dmn_process_memory_reserve, - dmn_process_memory_commit, - dmn_process_memory_decommit, - dmn_process_memory_release, - dmn_process_memory_protect, - dmn_process_read, - dmn_process_write, - dmn_thread_read_reg_block, - dmn_thread_write_reg_block, - dmn_thread_get_module_tls_vaddr, -}; - //////////////////////////////// //~ rjf: Basic Type Functions diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index c1bd93bc..b6adaf01 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -313,68 +313,6 @@ struct D_ModuleReqCacheNode #define D_WAKEUP_FUNCTION_DEF(name) void name(void) typedef D_WAKEUP_FUNCTION_DEF(D_WakeupFunctionType); -//////////////////////////////// -//~ rjf: Demon Interface V-Table - -typedef struct D_DemonInterface D_DemonInterface; -struct D_DemonInterface -{ - //- rjf: control - void (*ctrl_exclusive_access_begin)(void); - void (*ctrl_exclusive_access_end)(void); - U32 (*launch)(DMN_CtrlCtx *ctx, ProcessLaunchParams *params); - B32 (*attach)(DMN_CtrlCtx *ctx, U32 pid); - B32 (*kill)(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code); - B32 (*detach)(DMN_CtrlCtx *ctx, DMN_Handle process); - DMN_EventList (*run)(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls); - - //- rjf: halting - void (*halt)(U64 code, U64 user_data); - - //- rjf: non-blocking-control-thread access barriers - B32 (*access_open)(void); - void (*access_close)(void); - - //- rjf: process memory - U64 (*reserve_memory)(DMN_Handle process, U64 vaddr, U64 size); - void (*commit_memory)(DMN_Handle process, U64 vaddr, U64 size); - void (*decommit_memory)(DMN_Handle process, U64 vaddr, U64 size); - void (*release_memory)(DMN_Handle process, U64 vaddr, U64 size); - void (*protect_memory)(DMN_Handle process, U64 vaddr, U64 size, AccessFlags flags); - U64 (*read_memory)(DMN_Handle process, Rng1U64 range, void *dst); - B32 (*write_memory)(DMN_Handle process, Rng1U64 range, void *src); - - //- rjf: threads - B32 (*thread_read_reg_block)(DMN_Handle thread, void *dst); - B32 (*thread_write_reg_block)(DMN_Handle thread, void *src); - B32 (*thread_get_module_tls_vaddr)(DMN_Handle thread, DMN_Handle module, U64 *vaddr_out); -}; - -typedef struct D_DemonState D_DemonState; -struct D_DemonState -{ - D_DemonState *next; - D_DemonState *prev; - D_DemonID id; - D_DemonInterface *fn; - Arena *event_arena; - DMN_EventList events; -}; - -typedef struct D_DemonStateSlot D_DemonStateSlot; -struct D_DemonStateSlot -{ - D_DemonState *first; - D_DemonState *last; -}; - -typedef struct D_DemonCache D_DemonCache; -struct D_DemonCache -{ - U64 slots_count; - D_DemonStateSlot *slots; -}; - //////////////////////////////// //~ rjf: Main State Types diff --git a/src/dbg_server/dbg_server.c b/src/dbg_server/dbg_server.c index 9dce96f0..2922db8b 100644 --- a/src/dbg_server/dbg_server.c +++ b/src/dbg_server/dbg_server.c @@ -20,7 +20,7 @@ ds_msg_from_serialized(Arena *arena, String8 data) // rjf: read flat parts off += str8_deserial_read_struct(data, off, &msg.kind); off += str8_deserial_read_struct(data, off, &msg.flags); - off += str8_deserial_read_struct(data, off, &msg.id); + off += str8_deserial_read_struct(data, off, &msg.msg_id); off += str8_deserial_read_struct(data, off, &msg.vaddr_range); off += str8_deserial_read_struct(data, off, &msg.access_flags); off += str8_deserial_read_struct(data, off, &msg.entity); @@ -92,3 +92,17 @@ ds_serialized_from_msg(Arena *arena, DS_Msg *msg) String8 result = str8_list_join(arena, &srl, 0); return result; } + +internal DS_Response +ds_response_from_serialized(Arena *arena, String8 data) +{ + DS_Response result = {0}; + return result; +} + +internal String8 +ds_serialized_from_response(Arena *arena, DS_Response *response) +{ + String8 result = {0}; + return result; +} diff --git a/src/dbg_server/dbg_server.h b/src/dbg_server/dbg_server.h index 7f1520c0..a960511b 100644 --- a/src/dbg_server/dbg_server.h +++ b/src/dbg_server/dbg_server.h @@ -60,13 +60,14 @@ struct DS_Msg { DS_MsgKind kind; DS_MsgFlags flags; - U64 id; + U64 msg_id; //- rjf: general parameters Rng1U64 vaddr_range; AccessFlags access_flags; DMN_Handle entity; U64 pid; + U64 code; //- rjf: process launching parameters String8List command_line; @@ -78,41 +79,33 @@ struct DS_Msg DMN_HandleArray run_entities; }; -//////////////////////////////// -//~ rjf: Server Instance - -typedef enum DS_InstKind -{ - DS_InstKind_Host, - DS_InstKind_Remote, -} -DS_InstKind; - -typedef struct DS_Inst DS_Inst; -struct DS_Inst -{ - DS_InstKind kind; - U64 u64[1]; -}; - //////////////////////////////// //~ rjf: Responses (Server -> Client responses to messages) typedef struct DS_Response DS_Response; struct DS_Response { - U64 id; - DS_Inst inst; + U64 msg_id; String8 data; DMN_Handle entity; U64 pid; + B32 good; DMN_EventList events; }; +typedef struct DS_ResponseNode DS_ResponseNode; +struct DS_ResponseNode +{ + DS_ResponseNode *next; + DS_Response v; +}; + //////////////////////////////// -//~ rjf: Message Parsing +//~ rjf: Message/Response Parsing internal DS_Msg ds_msg_from_serialized(Arena *arena, String8 data); -internal String8 ds_serialized_from_msg(Arena *arena, DS_Msg msg); +internal String8 ds_serialized_from_msg(Arena *arena, DS_Msg *msg); +internal DS_Response ds_response_from_serialized(Arena *arena, String8 data); +internal String8 ds_serialized_from_response(Arena *arena, DS_Response *response); #endif // DBG_SERVER_H diff --git a/src/dbg_server/dbg_server_client.c b/src/dbg_server/dbg_server_client.c deleted file mode 100644 index 7d253777..00000000 --- a/src/dbg_server/dbg_server_client.c +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -internal B32 -ds_push_msg(GuardedRing *out_ring, DS_Inst inst, DS_Msg *msg, U64 endt_us) -{ - B32 result = 0; - switch(inst.kind) - { - default:{}break; - case DS_InstKind_Host: - { - result = ds_push_msg__host(out_ring, msg, endt_us); - }break; - } - return result; -} - -internal B32 -ds_pop_response(Arena *arena, GuardedRing *out_ring, DS_Response *response_out, U64 endt_us) -{ - B32 result = 0; - return result; -} diff --git a/src/dbg_server/dbg_server_client.h b/src/dbg_server/dbg_server_client.h deleted file mode 100644 index cffb3eca..00000000 --- a/src/dbg_server/dbg_server_client.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef DBG_SERVER_CLIENT_H -#define DBG_SERVER_CLIENT_H - -//////////////////////////////// -//~ rjf: Messages/Responses - -internal B32 ds_push_msg(GuardedRing *out_ring, DS_Inst inst, DS_Msg *msg, U64 endt_us); -internal B32 ds_pop_response(Arena *arena, GuardedRing *out_ring, DS_Response *response_out, U64 endt_us); - -#endif // DBG_SERVER_CLIENT_H diff --git a/src/dbg_server/dbg_server_host.c b/src/dbg_server/dbg_server_host.c deleted file mode 100644 index 4b9e5811..00000000 --- a/src/dbg_server/dbg_server_host.c +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -internal B32 -ds_push_msg__host(GuardedRing *out_ring, DS_Msg *msg, U64 endt_us) -{ - B32 result = 1; - switch(msg->kind) - { - default:{}break; - - //- rjf: launching - case DS_MsgKind_Launch: - { - ProcessLaunchParams params = {0}; - { - params.cmd_line = msg->command_line; - params.path = msg->path; - params.env = msg->env; - params.inherit_env = !!(msg->flags & DS_MsgFlag_InheritEnv); - params.debug_subprocesses = !!(msg->flags & DS_MsgFlag_DebugSubprocesses); - } - U32 pid = dmn_ctrl_launch(0, ¶ms); - }break; - - } - return result; -} diff --git a/src/dbg_server/dbg_server_host.h b/src/dbg_server/dbg_server_host.h deleted file mode 100644 index 1e6a8e67..00000000 --- a/src/dbg_server/dbg_server_host.h +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef DBG_SERVER_HOST_H -#define DBG_SERVER_HOST_H - -typedef struct DS_HostState DS_HostState; -struct DS_HostState -{ - Arena *arena; -}; - -internal B32 ds_push_msg__host(GuardedRing *out_ring, DS_Msg *msg, U64 endt_us); - -#endif // DBG_SERVER_HOST_H diff --git a/src/dbg_server/dbg_server_remote.c b/src/dbg_server/dbg_server_remote.c deleted file mode 100644 index ae447aab..00000000 --- a/src/dbg_server/dbg_server_remote.c +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) diff --git a/src/dbg_server/dbg_server_remote.h b/src/dbg_server/dbg_server_remote.h deleted file mode 100644 index 9cb853c2..00000000 --- a/src/dbg_server/dbg_server_remote.h +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef DBG_SERVER_REMOTE_H -#define DBG_SERVER_REMOTE_H - -#endif // DBG_SERVER_REMOTE_H diff --git a/src/raddbg_server/raddbg_server_main.c b/src/raddbg_server/raddbg_server_main.c index 430d2cce..9466c917 100644 --- a/src/raddbg_server/raddbg_server_main.c +++ b/src/raddbg_server/raddbg_server_main.c @@ -23,7 +23,7 @@ #include "gnu/gnu_parse.h" #include "elf/elf_parse.h" #include "demon/demon_inc.h" -#include "raddbg_server/raddbg_server.h" +#include "dbg_server/dbg_server.h" //- rjf: [c] #include "base/base_inc.c" @@ -38,7 +38,7 @@ #include "gnu/gnu_parse.c" #include "elf/elf_parse.c" #include "demon/demon_inc.c" -#include "raddbg_server/raddbg_server.c" +#include "dbg_server/dbg_server.c" //////////////////////////////// //~ rjf: Globals @@ -90,27 +90,72 @@ dmn_thread__entry_point(void *p) } //////////////////////////// - //- rjf: do messages + //- rjf: do messages, push responses // for(String8Node *msg_n = msgs.first; msg_n != 0; msg_n = msg_n->next) { - RDS_Msg msg = rds_msg_from_data(scratch.arena, msg_n->string); + //- rjf: unpack message + DS_Msg msg = ds_msg_from_serialized(scratch.arena, msg_n->string); + + //- rjf: do action, get response + DS_Response response = {0}; switch(msg.kind) { default:{}break; - //- rjf: debuggee attaching/detaching ops + //- rjf: launching + case DS_MsgKind_Launch: + { + ProcessLaunchParams params = {0}; + { + params.cmd_line = msg.command_line; + params.path = msg.path; + params.env = msg.env; + params.inherit_env = !!(msg.flags & DS_MsgFlag_InheritEnv); + params.debug_subprocesses = !!(msg.flags & DS_MsgFlag_DebugSubprocesses); + } + response.pid = dmn_ctrl_launch(ctrl_ctx, ¶ms); + }break; - case RDS_MsgKind_Launch:{}break; - case RDS_MsgKind_Attach:{}break; - case RDS_MsgKind_Kill:{}break; - case RDS_MsgKind_Detach:{}break; + //- rjf: attaching + case DS_MsgKind_Attach: + { + response.good = dmn_ctrl_attach(ctrl_ctx, msg.pid); + }break; + + //- rjf: killing + case DS_MsgKind_Kill: + { + response.good = dmn_ctrl_kill(ctrl_ctx, msg.entity, (U32)msg.code); + }break; + + //- rjf: detaching + case DS_MsgKind_Detach: + { + response.good = dmn_ctrl_detach(ctrl_ctx, msg.entity); + }break; //- rjf: running ops - - case RDS_MsgKind_Run:{}break; - case RDS_MsgKind_SingleStep:{}break; + case DS_MsgKind_Run: + case DS_MsgKind_SingleStep: + { + DMN_RunCtrls ctrls = {0}; + { + ctrls.priority_thread = msg.entity; + ctrls.single_step_thread = (msg.kind == DS_MsgKind_SingleStep) ? msg.entity : dmn_handle_zero(); + ctrls.run_entities = msg.run_entities.handles; + ctrls.run_entity_count = msg.run_entities.count; + ctrls.traps = msg.traps; + ctrls.ignore_previous_exception = !!(msg.flags & DS_MsgFlag_IgnorePreviousException); + ctrls.run_entities_are_unfrozen = !!(msg.flags & DS_MsgFlag_RunEntitiesAreUnfrozen); + ctrls.run_entities_are_processes = !!(msg.flags & DS_MsgFlag_RunEntitiesAreProcesses); + }; + response.events = dmn_ctrl_run(scratch.arena, ctrl_ctx, &ctrls); + }break; } + + //- rjf: pack response & push + String8 data = ds_serialized_from_response(scratch.arena, &response); } scratch_end(scratch); @@ -176,17 +221,17 @@ entry_point(CmdLine *cmd_line) // attaching, detaching, and so on. other operations, like reading/writing // memory/registers, listing processes, and halting, we want to just do // on the main thread without interrupting the demon at all. - RDS_MsgKind peek_kind = RDS_MsgKind_Null; - if(magic_maybe == RDS_MSG_MAGIC) + DS_MsgKind peek_kind = DS_MsgKind_Null; + if(magic_maybe == DS_MSG_MAGIC) { str8_deserial_read_struct(data, 2, &peek_kind); - if(peek_kind < RDS_MsgKind_DemonThreadHandledFirst || RDS_MsgKind_DemonThreadHandledLast < peek_kind) + if(peek_kind < DS_MsgKind_Launch || DS_MsgKind_SingleStep < peek_kind) { - RDS_Msg msg = rds_msg_from_data(scratch.arena, data); + DS_Msg msg = ds_msg_from_serialized(scratch.arena, data); switch(msg.kind) { default:{}break; - case RDS_MsgKind_Halt: + case DS_MsgKind_Halt: { dmn_halt(0, 0); }break; @@ -195,7 +240,7 @@ entry_point(CmdLine *cmd_line) } //- rjf: not handled by main thread? -> okay, send to demon thread, if it's ready. - if(magic_maybe == RDS_MSG_MAGIC && !rds_state->demon_is_running && (RDS_MsgKind_DemonThreadHandledFirst <= peek_kind && peek_kind <= RDS_MsgKind_DemonThreadHandledLast)) + if(magic_maybe == DS_MSG_MAGIC && !rds_state->demon_is_running && (DS_MsgKind_Launch <= peek_kind && peek_kind <= DS_MsgKind_SingleStep)) { RWMutexScope(rds_state->c2d_rw_mutex, 1) { From 6cfaae9a22b6dd96babdb5cbe9178291f4106512 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 9 Oct 2026 09:41:28 -0700 Subject: [PATCH 238/251] raddbg/dbg_engine: machine -> server; dbg_server: checkpoint; raddbg_server: hook up response sending (to sender client), and event sending (to all connected clients) --- project.4coder | 2 +- src/dbg_engine/dbg_engine.mdesk | 18 +- src/dbg_engine/dbg_engine_ctrl.c | 38 ++-- src/dbg_engine/dbg_engine_ctrl.h | 2 + src/dbg_engine/dbg_engine_user.c | 24 +-- src/dbg_engine/dbg_engine_user.h | 10 +- src/dbg_engine/generated/dbg_engine.meta.c | 4 +- src/dbg_engine/generated/dbg_engine.meta.h | 14 +- src/dbg_server/dbg_server.c | 234 ++++++++++++++++++++- src/dbg_server/dbg_server.h | 30 ++- src/raddbg/generated/raddbg.meta.c | 45 ++-- src/raddbg/generated/raddbg.meta.h | 26 ++- src/raddbg/raddbg.mdesk | 11 +- src/raddbg/raddbg_core.c | 60 +++--- src/raddbg/raddbg_eval.c | 4 +- src/raddbg/raddbg_views.c | 2 +- src/raddbg/raddbg_widgets.c | 4 +- src/raddbg_server/raddbg_server_main.c | 130 ++++++++++-- 18 files changed, 499 insertions(+), 159 deletions(-) diff --git a/project.4coder b/project.4coder index da10909d..360ba491 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg_server telemetry debug", .linux = "./build.sh raddbg debug meta", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg debug meta", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/dbg_engine/dbg_engine.mdesk b/src/dbg_engine/dbg_engine.mdesk index df8d4e09..6ef862e9 100644 --- a/src/dbg_engine/dbg_engine.mdesk +++ b/src/dbg_engine/dbg_engine.mdesk @@ -39,10 +39,10 @@ D_CmdTable: // | | | {ThawThread 1 1 "query:threads" Thread 0 0 0 0 0 1 1 Unlocked "thaw_thread" "Thaw Thread" "Thaws the passed thread." "" "" } {FreezeProcess 1 1 "query:processes" Process 0 0 0 0 0 1 1 Locked "freeze_process" "Freeze Process" "Freezes the passed process." "" "" } {ThawProcess 1 1 "query:processes" Process 0 0 0 0 0 1 1 Unlocked "thaw_process" "Thaw Process" "Thaws the passed process." "" "" } - {FreezeMachine 0 1 "query:machines" Machine 0 0 0 0 0 1 1 Locked "freeze_machine" "Freeze Machine" "Freezes the passed machine." "" "" } - {ThawMachine 0 1 "query:machines" Machine 0 0 0 0 0 1 1 Unlocked "thaw_machine" "Thaw Machine" "Thaws the passed machine." "" "" } - {FreezeLocalMachine 1 1 "" Null 0 0 0 0 0 0 0 Machine "freeze_local_machine" "Freeze Local Machine" "Freezes the local machine." "" "" } - {ThawLocalMachine 1 1 "" Null 0 0 0 0 0 0 0 Machine "thaw_local_machine" "Thaw Local Machine" "Thaws the local machine." "" "" } + {FreezeServer 0 1 "query:servers" Server 0 0 0 0 0 1 1 Locked "freeze_server " "Freeze Server " "Freezes the passed server." "" "" } + {ThawServer 0 1 "query:servers" Server 0 0 0 0 0 1 1 Unlocked "thaw_server" "Thaw Server" "Thaws the passed server." "" "" } + {FreezeLocalServer 1 1 "" Null 0 0 0 0 0 0 0 Machine "freeze_local_server" "Freeze Local Server" "Freezes the local server." "" "" } + {ThawLocalServer 1 1 "" Null 0 0 0 0 0 0 0 Machine "thaw_local_server" "Thaw Local Server" "Thaws the local server." "" "" } {FreezeEntity 0 0 "" Null 0 0 0 0 0 0 0 Unlocked "freeze_entity" "Freeze Entity" "Freezes an entity." "" "" } {ThawEntity 0 0 "" Null 0 0 0 0 0 0 0 Locked "thaw_entity" "Thaw Entity" "Thaws an entity." "" "" } @@ -58,10 +58,10 @@ D_CmdTable: // | | | //- rjf: crash dump opening {OpenCrashDump 1 1 `folder:\\"$input\\"` FilePath 0 0 0 0 0 1 1 FileOutline "open_crash_dump" "Open Crash Dump" "Opens a crash dump file and recreates debuggee state at the time of the crash." "" "" } - - //- rjf: remote debugger connecting - {OpenRemoteDebugger 1 1 "" String 0 0 0 0 0 1 1 Machine "open_remote_debugger" "Open Remote Debugger" "Opens a remote debugger by IP address." "" "" } - {CloseRemoteDebugger 1 1 "query:machines" CtrlEntity 0 0 0 0 0 1 1 Machine "close_remote_debugger" "Close Remote Debugger" "Closes a remote debugger." "" "" } + + //- rjf: remote debug server connecting + {OpenRemoteServer 1 1 "" String 0 0 0 0 0 1 1 Machine "open_remote_server" "Open Remote Server" "Opens a remote debug server by IP address." "" "" } + {CloseRemoteServer 1 1 "query:servers" CtrlEntity 0 0 0 0 0 1 1 Machine "close_remote_server" "Close Remote Server" "Closes a remote debug server." "" "" } } @enum D_CmdKind: @@ -108,7 +108,7 @@ D_DevToggleTable: D_EntityKindTable: { {Root root "Root" } - {Machine machine "Machine" } + {Server server "Server" } {Process process "Process" } {Thread thread "Thread" } {Module module "Module" } diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 0aa67934..50631345 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -915,10 +915,10 @@ d_entity_ctx_rw_store_alloc(void) store->ctx.hash_slots_count = 1024; store->ctx.hash_slots = push_array(arena, D_EntityHashSlot, store->ctx.hash_slots_count); D_Entity *root = store->ctx.root = d_entity_alloc(store, &d_entity_nil, D_EntityKind_Root, Arch_Null, d_handle_zero(), 0); - D_Entity *local_machine = d_entity_alloc(store, root, D_EntityKind_Machine, Arch_CURRENT, d_handle_from_dmn(D_DemonID_LocalHost, dmn_handle_zero()), 0); + D_Entity *local_server = d_entity_alloc(store, root, D_EntityKind_Server, Arch_CURRENT, d_handle_from_dmn(D_DemonID_LocalHost, dmn_handle_zero()), 0); Temp scratch = scratch_begin(0, 0); - String8 local_machine_name = push_str8f(scratch.arena, "This PC (%S)", get_system_info()->machine_name); - d_entity_equip_string(store, local_machine, local_machine_name); + String8 local_server_name = str8f(scratch.arena, "Local (%S)", get_system_info()->machine_name); + d_entity_equip_string(store, local_server, local_server_name); scratch_end(scratch); return store; } @@ -2501,8 +2501,8 @@ d_ctrl_thread__entry_point(void *p) case D_MsgKind_COUNT:{}break; //- rjf: remote debuggers - case D_MsgKind_OpenRemoteDebugger:{}break; - case D_MsgKind_CloseRemoteDebugger:{}break; + case D_MsgKind_OpenRemoteServer: {d_ctrl_thread__open_remote_server(ctrl_ctx, msg);}break; + case D_MsgKind_CloseRemoteServer:{d_ctrl_thread__close_remote_server(ctrl_ctx, msg);}break; //- rjf: target operations case D_MsgKind_Launch: {d_ctrl_thread__launch (ctrl_ctx, msg);}break; @@ -3567,12 +3567,12 @@ d_ctrl_thread__eval_scope_begin(Arena *arena, D_BreakpointList *user_bps, D_Enti { U64 eval_module_idx = 0; U64 eval_dbg_info_idx = 0; - for(D_Entity *machine = entity_ctx->root->first; - machine != &d_entity_nil; - machine = machine->next) + for(D_Entity *server = entity_ctx->root->first; + server != &d_entity_nil; + server = server->next) { - if(machine->kind != D_EntityKind_Machine) { continue; } - for(D_Entity *process = machine->first; + if(server->kind != D_EntityKind_Server) { continue; } + for(D_Entity *process = server->first; process != &d_entity_nil; process = process->next) { @@ -3810,6 +3810,18 @@ d_ctrl_thread__end_and_flush_log(void) //- rjf: msg kind implementations +internal void +d_ctrl_thread__open_remote_server(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) +{ + +} + +internal void +d_ctrl_thread__close_remote_server(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) +{ + +} + internal void d_ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { @@ -4702,7 +4714,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) machine != &d_entity_nil; machine = machine->next) { - if(machine->kind != D_EntityKind_Machine) { continue; } + if(machine->kind != D_EntityKind_Server) { continue; } for(D_Entity *process = machine->first; process != &d_entity_nil; process = process->next) { if(process->kind != D_EntityKind_Process) { continue; } @@ -4759,7 +4771,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) machine != &d_entity_nil; machine = machine->next) { - if(machine->kind != D_EntityKind_Machine) { continue; } + if(machine->kind != D_EntityKind_Server) { continue; } for(D_Entity *process = machine->first; process != &d_entity_nil; process = process->next) { if(process->kind != D_EntityKind_Process) { continue; } @@ -4864,7 +4876,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) machine != &d_entity_nil; machine = machine->next) { - if(machine->kind != D_EntityKind_Machine) { continue; } + if(machine->kind != D_EntityKind_Server) { continue; } for(D_Entity *process = machine->first; process != &d_entity_nil; process = process->next) { if(process->kind != D_EntityKind_Process) { continue; } diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index b6adaf01..15c30306 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -624,6 +624,8 @@ internal void d_ctrl_thread__eval_scope_end(D_EvalScope *scope); internal void d_ctrl_thread__end_and_flush_log(void); //- rjf: msg kind implementations +internal void d_ctrl_thread__open_remote_server(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg); +internal void d_ctrl_thread__close_remote_server(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg); internal void d_ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg); internal void d_ctrl_thread__attach(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg); internal void d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg); diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index e11e2b50..2a83fd5b 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -2047,7 +2047,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P D_EntityArray processes = d_entity_array_from_kind(D_EntityKind_Process); if(processes.count != 0) { - d_cmd(D_CmdKind_Continue, .machine = params->machine, .process = params->process, .thread = params->thread); + d_cmd(D_CmdKind_Continue, .server = params->server, .process = params->process, .thread = params->thread); } else if(!d_ctrl_targets_running()) { @@ -2092,25 +2092,25 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P case D_CmdKind_ThawThread: case D_CmdKind_FreezeProcess: case D_CmdKind_ThawProcess: - case D_CmdKind_FreezeMachine: - case D_CmdKind_ThawMachine: + case D_CmdKind_FreezeServer: + case D_CmdKind_ThawServer: { D_CmdKind disptch_kind = ((cmd->kind == D_CmdKind_FreezeThread || cmd->kind == D_CmdKind_FreezeProcess || - cmd->kind == D_CmdKind_FreezeMachine) + cmd->kind == D_CmdKind_FreezeServer) ? D_CmdKind_FreezeEntity : D_CmdKind_ThawEntity); d_push_cmd(disptch_kind, params); }break; - case D_CmdKind_FreezeLocalMachine: + case D_CmdKind_FreezeLocalServer: { D_DemonID demon_id = D_DemonID_LocalHost; - d_cmd(D_CmdKind_FreezeMachine, .entity = d_handle_from_dmn(demon_id, dmn_handle_zero())); + d_cmd(D_CmdKind_FreezeServer, .entity = d_handle_from_dmn(demon_id, dmn_handle_zero())); }break; - case D_CmdKind_ThawLocalMachine: + case D_CmdKind_ThawLocalServer: { D_DemonID demon_id = D_DemonID_LocalHost; - d_cmd(D_CmdKind_ThawMachine, .entity = d_handle_from_dmn(demon_id, dmn_handle_zero())); + d_cmd(D_CmdKind_ThawServer, .entity = d_handle_from_dmn(demon_id, dmn_handle_zero())); }break; case D_CmdKind_FreezeEntity: case D_CmdKind_ThawEntity: @@ -2193,18 +2193,18 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P }break; //- rjf: opening/closing remote debuggers - case D_CmdKind_OpenRemoteDebugger: + case D_CmdKind_OpenRemoteServer: { D_Msg *msg = d_msg_list_push(scratch.arena, &ctrl_msgs); - msg->kind = D_MsgKind_OpenRemoteDebugger; + msg->kind = D_MsgKind_OpenRemoteServer; msg->path = str8_copy(scratch.arena, params->string); MemoryCopyArray(msg->exception_code_filters, exception_code_filters); msg->auto_download_debug_info = auto_download_debug_info; }break; - case D_CmdKind_CloseRemoteDebugger: + case D_CmdKind_CloseRemoteServer: { D_Msg *msg = d_msg_list_push(scratch.arena, &ctrl_msgs); - msg->kind = D_MsgKind_CloseRemoteDebugger; + msg->kind = D_MsgKind_CloseRemoteServer; msg->entity = params->entity; MemoryCopyArray(msg->exception_code_filters, exception_code_filters); msg->auto_download_debug_info = auto_download_debug_info; diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index c4efad3b..953e41b8 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -59,7 +59,7 @@ D_RunKind; typedef struct D_CmdParams D_CmdParams; struct D_CmdParams { - D_Handle machine; + D_Handle server; D_Handle process; D_Handle thread; D_Handle entity; @@ -160,8 +160,8 @@ struct D_RunLocalsCache typedef enum D_MsgKind { D_MsgKind_Null, - D_MsgKind_OpenRemoteDebugger, - D_MsgKind_CloseRemoteDebugger, + D_MsgKind_OpenRemoteServer, + D_MsgKind_CloseRemoteServer, D_MsgKind_Launch, D_MsgKind_Attach, D_MsgKind_OpenCrashDump, @@ -265,6 +265,10 @@ typedef enum D_EventKind D_EventKind_MemDecommit, D_EventKind_MemRelease, + //- rjf: remote connections + D_EventKind_RemoteControllerConnect, + D_EventKind_RemoteControllerDisconnect, + D_EventKind_COUNT } D_EventKind; diff --git a/src/dbg_engine/generated/dbg_engine.meta.c b/src/dbg_engine/generated/dbg_engine.meta.c index c7a54839..d05a1c46 100644 --- a/src/dbg_engine/generated/dbg_engine.meta.c +++ b/src/dbg_engine/generated/dbg_engine.meta.c @@ -8,7 +8,7 @@ String8 d_entity_kind_code_name_table[13] = { {0}, str8_lit_comp("root"), -str8_lit_comp("machine"), +str8_lit_comp("server"), str8_lit_comp("process"), str8_lit_comp("thread"), str8_lit_comp("module"), @@ -25,7 +25,7 @@ String8 d_entity_kind_display_string_table[13] = { {0}, str8_lit_comp("Root"), -str8_lit_comp("Machine"), +str8_lit_comp("Server"), str8_lit_comp("Process"), str8_lit_comp("Thread"), str8_lit_comp("Module"), diff --git a/src/dbg_engine/generated/dbg_engine.meta.h b/src/dbg_engine/generated/dbg_engine.meta.h index e3943a97..233d9e61 100644 --- a/src/dbg_engine/generated/dbg_engine.meta.h +++ b/src/dbg_engine/generated/dbg_engine.meta.h @@ -34,10 +34,10 @@ D_CmdKind_FreezeThread, D_CmdKind_ThawThread, D_CmdKind_FreezeProcess, D_CmdKind_ThawProcess, -D_CmdKind_FreezeMachine, -D_CmdKind_ThawMachine, -D_CmdKind_FreezeLocalMachine, -D_CmdKind_ThawLocalMachine, +D_CmdKind_FreezeServer, +D_CmdKind_ThawServer, +D_CmdKind_FreezeLocalServer, +D_CmdKind_ThawLocalServer, D_CmdKind_FreezeEntity, D_CmdKind_ThawEntity, D_CmdKind_SetEntityColor, @@ -45,8 +45,8 @@ D_CmdKind_SetEntityName, D_CmdKind_DownloadModuleDebugInfo, D_CmdKind_Attach, D_CmdKind_OpenCrashDump, -D_CmdKind_OpenRemoteDebugger, -D_CmdKind_CloseRemoteDebugger, +D_CmdKind_OpenRemoteServer, +D_CmdKind_CloseRemoteServer, D_CmdKind_COUNT, } D_CmdKind; @@ -54,7 +54,7 @@ typedef enum D_EntityKind { D_EntityKind_Null, D_EntityKind_Root, -D_EntityKind_Machine, +D_EntityKind_Server, D_EntityKind_Process, D_EntityKind_Thread, D_EntityKind_Module, diff --git a/src/dbg_server/dbg_server.c b/src/dbg_server/dbg_server.c index 2922db8b..1347addc 100644 --- a/src/dbg_server/dbg_server.c +++ b/src/dbg_server/dbg_server.c @@ -7,15 +7,9 @@ internal DS_Msg ds_msg_from_serialized(Arena *arena, String8 data) { - //- rjf: unpack message - U16 magic_maybe = 0; - str8_deserial_read_struct(data, 0, &magic_maybe); - - //- rjf: parse message DS_Msg msg = {0}; - if(magic_maybe == DS_MSG_MAGIC) { - U64 off = sizeof(magic_maybe); + U64 off = 0; // rjf: read flat parts off += str8_deserial_read_struct(data, off, &msg.kind); @@ -76,7 +70,6 @@ ds_msg_from_serialized(Arena *arena, String8 data) } } } - return msg; } @@ -85,11 +78,61 @@ ds_serialized_from_msg(Arena *arena, DS_Msg *msg) { Temp scratch = scratch_begin(&arena, 1); String8List srl = {0}; + str8_serial_begin(scratch.arena, &srl); { - // TODO(rjf) + // rjf: write flat parts + str8_serial_push_struct(scratch.arena, &srl, &msg->kind); + str8_serial_push_struct(scratch.arena, &srl, &msg->flags); + str8_serial_push_struct(scratch.arena, &srl, &msg->msg_id); + str8_serial_push_struct(scratch.arena, &srl, &msg->vaddr_range); + str8_serial_push_struct(scratch.arena, &srl, &msg->access_flags); + str8_serial_push_struct(scratch.arena, &srl, &msg->entity); + str8_serial_push_struct(scratch.arena, &srl, &msg->pid); + + // rjf: write command line strings + { + str8_serial_push_struct(scratch.arena, &srl, &msg->command_line.node_count); + for EachNode(n, String8Node, msg->command_line.first) + { + str8_serial_push_struct(scratch.arena, &srl, &n->string.size); + str8_serial_push_string(scratch.arena, &srl, n->string); + } + } + + // rjf: write path + { + str8_serial_push_struct(scratch.arena, &srl, &msg->path.size); + str8_serial_push_string(scratch.arena, &srl, msg->path); + } + + // rjf: write environment strings + { + str8_serial_push_struct(scratch.arena, &srl, &msg->env.node_count); + for EachNode(n, String8Node, msg->command_line.first) + { + str8_serial_push_struct(scratch.arena, &srl, &n->string.size); + str8_serial_push_string(scratch.arena, &srl, n->string); + } + } + + // rjf: write traps + { + str8_serial_push_struct(scratch.arena, &srl, &msg->traps.trap_count); + for EachNode(n, DMN_TrapChunkNode, msg->traps.first) + { + for EachIndex(idx, n->count) + { + str8_serial_push_struct(scratch.arena, &srl, &n->v[idx].process); + str8_serial_push_struct(scratch.arena, &srl, &n->v[idx].vaddr); + str8_serial_push_struct(scratch.arena, &srl, &n->v[idx].id); + str8_serial_push_struct(scratch.arena, &srl, &n->v[idx].flags); + str8_serial_push_struct(scratch.arena, &srl, &n->v[idx].size); + } + } + } } + String8 result = str8_serial_end(arena, &srl); scratch_end(scratch); - String8 result = str8_list_join(arena, &srl, 0); return result; } @@ -97,12 +140,181 @@ internal DS_Response ds_response_from_serialized(Arena *arena, String8 data) { DS_Response result = {0}; + { + U64 off = 0; + + // rjf: read flat parts + off += str8_deserial_read_struct(data, off, &result.msg_id); + off += str8_deserial_read_struct(data, off, &result.entity); + off += str8_deserial_read_struct(data, off, &result.pid); + off += str8_deserial_read_struct(data, off, &result.good); + + // rjf: read data payload + off += str8_deserial_read_struct(data, off, &result.data.size); + result.data.str = push_array(arena, U8, result.data.size); + off += str8_deserial_read(data, off, result.data.str, result.data.size, result.data.size); + } return result; } internal String8 ds_serialized_from_response(Arena *arena, DS_Response *response) { - String8 result = {0}; + Temp scratch = scratch_begin(&arena, 1); + String8List srl = {0}; + str8_serial_begin(scratch.arena, &srl); + { + // rjf: write flat parts + str8_serial_push_struct(scratch.arena, &srl, &response->msg_id); + str8_serial_push_struct(scratch.arena, &srl, &response->entity); + str8_serial_push_struct(scratch.arena, &srl, &response->pid); + str8_serial_push_struct(scratch.arena, &srl, &response->good); + + // rjf: write data payload + str8_serial_push_struct(scratch.arena, &srl, &response->data.size); + str8_serial_push_string(scratch.arena, &srl, response->data); + } + String8 result = str8_serial_end(arena, &srl); + scratch_end(scratch); + return result; +} + +internal DMN_EventList +ds_dmn_events_from_serialized(Arena *arena, String8 data) +{ + DMN_EventList evts = {0}; + { + U64 off = 0; + U64 demon_event_count = 0; + off += str8_deserial_read_struct(data, off, &demon_event_count); + for EachIndex(idx, demon_event_count) + { + DMN_Event *evt = dmn_event_list_push(arena, &evts); + + // rjf: read flat parts + off += str8_deserial_read_struct(data, off, &evt->kind); + off += str8_deserial_read_struct(data, off, &evt->error_kind); + off += str8_deserial_read_struct(data, off, &evt->memory_kind); + off += str8_deserial_read_struct(data, off, &evt->exception_kind); + off += str8_deserial_read_struct(data, off, &evt->process); + off += str8_deserial_read_struct(data, off, &evt->thread); + off += str8_deserial_read_struct(data, off, &evt->module); + off += str8_deserial_read_struct(data, off, &evt->arch); + off += str8_deserial_read_struct(data, off, &evt->address); + off += str8_deserial_read_struct(data, off, &evt->size); + off += str8_deserial_read_struct(data, off, &evt->code); + off += str8_deserial_read_struct(data, off, &evt->flags); + off += str8_deserial_read_struct(data, off, &evt->instruction_pointer); + off += str8_deserial_read_struct(data, off, &evt->stack_pointer); + off += str8_deserial_read_struct(data, off, &evt->user_data); + off += str8_deserial_read_struct(data, off, &evt->exception_repeated); + off += str8_deserial_read_struct(data, off, &evt->tls_root_vaddr); + + // rjf: read string + off += str8_deserial_read_struct(data, off, &evt->string.size); + evt->string.str = push_array(arena, U8, evt->string.size); + off += str8_deserial_read(data, off, evt->string.str, evt->string.size, 1); + + // rjf: read module info + B8 has_module_info = 0; + off += str8_deserial_read_struct(data, off, &has_module_info); + if(!has_module_info) + { + evt->module_info = &dmn_module_info_nil; + } + else + { + evt->module_info = push_array(arena, DMN_ModuleInfo, 1); + + // rjf: read flat parts + off += str8_deserial_read_struct(data, off, &evt->module_info->arch); + off += str8_deserial_read_struct(data, off, &evt->module_info->vsize); + off += str8_deserial_read_struct(data, off, &evt->module_info->entry_point_voff); + off += str8_deserial_read_struct(data, off, &evt->module_info->debug_info_guid); + off += str8_deserial_read_struct(data, off, &evt->module_info->debug_info_timestamp); + off += str8_deserial_read_struct(data, off, &evt->module_info->debug_info_age); + off += str8_deserial_read_struct(data, off, &evt->module_info->tls_index); + off += str8_deserial_read_struct(data, off, &evt->module_info->tls_offset); + off += str8_deserial_read_struct(data, off, &evt->module_info->pe_intel_pdatas_vaddr_range); + off += str8_deserial_read_struct(data, off, &evt->module_info->eh_frame_header_vaddr_range); + off += str8_deserial_read_struct(data, off, &evt->module_info->raddbg_info_voff_range); + off += str8_deserial_read_struct(data, off, &evt->module_info->raddbg_is_attached_marker_voff); + + // rjf: read paths + off += str8_deserial_read_struct(data, off, &evt->module_info->module_path.size); + evt->module_info->module_path.str = push_array(arena, U8, evt->module_info->module_path.size); + off += str8_deserial_read(data, off, evt->module_info->module_path.str, evt->module_info->module_path.size, 1); + off += str8_deserial_read_struct(data, off, &evt->module_info->debug_info_path.size); + evt->module_info->debug_info_path.str = push_array(arena, U8, evt->module_info->debug_info_path.size); + off += str8_deserial_read(data, off, evt->module_info->debug_info_path.str, evt->module_info->debug_info_path.size, 1); + } + } + } + return evts; +} + +internal String8 +ds_serialized_from_dmn_events(Arena *arena, DMN_EventList evts) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List srl = {0}; + str8_serial_begin(scratch.arena, &srl); + { + // rjf: write events + str8_serial_push_struct(scratch.arena, &srl, &evts.count); + for EachNode(n, DMN_EventNode, evts.first) + { + // rjf: write flat parts + str8_serial_push_struct(scratch.arena, &srl, &n->v.kind); + str8_serial_push_struct(scratch.arena, &srl, &n->v.error_kind); + str8_serial_push_struct(scratch.arena, &srl, &n->v.memory_kind); + str8_serial_push_struct(scratch.arena, &srl, &n->v.exception_kind); + str8_serial_push_struct(scratch.arena, &srl, &n->v.process); + str8_serial_push_struct(scratch.arena, &srl, &n->v.thread); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module); + str8_serial_push_struct(scratch.arena, &srl, &n->v.arch); + str8_serial_push_struct(scratch.arena, &srl, &n->v.address); + str8_serial_push_struct(scratch.arena, &srl, &n->v.size); + str8_serial_push_struct(scratch.arena, &srl, &n->v.code); + str8_serial_push_struct(scratch.arena, &srl, &n->v.flags); + str8_serial_push_struct(scratch.arena, &srl, &n->v.instruction_pointer); + str8_serial_push_struct(scratch.arena, &srl, &n->v.stack_pointer); + str8_serial_push_struct(scratch.arena, &srl, &n->v.user_data); + str8_serial_push_struct(scratch.arena, &srl, &n->v.exception_repeated); + str8_serial_push_struct(scratch.arena, &srl, &n->v.tls_root_vaddr); + + // rjf: write string + str8_serial_push_struct(scratch.arena, &srl, &n->v.string.size); + str8_serial_push_string(scratch.arena, &srl, n->v.string); + + // rjf: write module info + B8 has_module_info = (n->v.module_info != 0 && n->v.module_info != &dmn_module_info_nil); + str8_serial_push_struct(scratch.arena, &srl, &has_module_info); + if(has_module_info) + { + // rjf: write flat parts + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->arch); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->vsize); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->entry_point_voff); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->debug_info_guid); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->debug_info_timestamp); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->debug_info_age); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->tls_index); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->tls_offset); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->pe_intel_pdatas_vaddr_range); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->eh_frame_header_vaddr_range); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->raddbg_info_voff_range); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->raddbg_is_attached_marker_voff); + + // rjf: write paths + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->module_path.size); + str8_serial_push_string(scratch.arena, &srl, n->v.module_info->module_path); + str8_serial_push_struct(scratch.arena, &srl, &n->v.module_info->debug_info_path.size); + str8_serial_push_string(scratch.arena, &srl, n->v.module_info->debug_info_path); + } + } + } + String8 result = str8_serial_end(arena, &srl); + scratch_end(scratch); return result; } diff --git a/src/dbg_server/dbg_server.h b/src/dbg_server/dbg_server.h index a960511b..7ae209ce 100644 --- a/src/dbg_server/dbg_server.h +++ b/src/dbg_server/dbg_server.h @@ -5,9 +5,30 @@ #define DBG_SERVER_H //////////////////////////////// -//~ rjf: Messages (Client -> Server) +//~ rjf: Packet Headers -#define DS_MSG_MAGIC 0x7273 +#define DS_PACKET_MAGIC 0x7273 +#define DS_PACKET_VERSION 0x0 + +typedef U8 DS_PacketKind; +enum +{ + DS_PacketKind_Msg, + DS_PacketKind_Response, + DS_PacketKind_Event, +}; + +typedef struct DS_PacketHeader DS_PacketHeader; +struct DS_PacketHeader +{ + U16 magic; + DS_PacketKind kind; + U8 version; + U32 payload_size; +}; + +//////////////////////////////// +//~ rjf: Messages (Client -> Server) typedef enum DS_MsgKind { @@ -86,11 +107,10 @@ typedef struct DS_Response DS_Response; struct DS_Response { U64 msg_id; - String8 data; DMN_Handle entity; U64 pid; B32 good; - DMN_EventList events; + String8 data; }; typedef struct DS_ResponseNode DS_ResponseNode; @@ -107,5 +127,7 @@ internal DS_Msg ds_msg_from_serialized(Arena *arena, String8 data); internal String8 ds_serialized_from_msg(Arena *arena, DS_Msg *msg); internal DS_Response ds_response_from_serialized(Arena *arena, String8 data); internal String8 ds_serialized_from_response(Arena *arena, DS_Response *response); +internal DMN_EventList ds_dmn_events_from_serialized(Arena *arena, String8 data); +internal String8 ds_serialized_from_dmn_events(Arena *arena, DMN_EventList evts); #endif // DBG_SERVER_H diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 039ab39d..f7045efc 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -49,7 +49,7 @@ str8_lit_comp("query:watch_pins"), str8_lit_comp("query:debug_infos"), str8_lit_comp("query:threads"), str8_lit_comp("query:processes"), -str8_lit_comp("query:machines"), +str8_lit_comp("query:servers"), str8_lit_comp("query:modules"), str8_lit_comp("query:file_path_maps"), str8_lit_comp("query:type_views"), @@ -62,7 +62,7 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[374] = +RD_VocabInfo rd_vocab_info_table[373] = { {str8_lit_comp("type_view"), str8_lit_comp("type_views"), str8_lit_comp("Type View"), str8_lit_comp("Type Views"), RD_IconKind_Binoculars}, {str8_lit_comp("file_path_map"), str8_lit_comp("file_path_maps"), str8_lit_comp("File Path Map"), str8_lit_comp("File Path Maps"), RD_IconKind_FileOutline}, @@ -122,6 +122,7 @@ RD_VocabInfo rd_vocab_info_table[374] = {str8_lit_comp("process"), str8_lit_comp("processes"), str8_lit_comp("Process"), str8_lit_comp("Processes"), RD_IconKind_Scheduler}, {str8_lit_comp("processes"), str8_lit_comp(""), str8_lit_comp("Processes"), str8_lit_comp(""), RD_IconKind_Scheduler}, {str8_lit_comp("machine"), str8_lit_comp("machines"), str8_lit_comp("Machine"), str8_lit_comp("Machines"), RD_IconKind_Machine}, +{str8_lit_comp("server"), str8_lit_comp("servers"), str8_lit_comp("Server"), str8_lit_comp("Servers"), RD_IconKind_Machine}, {str8_lit_comp("module"), str8_lit_comp("modules"), str8_lit_comp("Module"), str8_lit_comp("Modules"), RD_IconKind_Module}, {str8_lit_comp("getting_started"), str8_lit_comp(""), str8_lit_comp("Getting Started"), str8_lit_comp(""), RD_IconKind_QuestionMark}, {str8_lit_comp("disasm"), str8_lit_comp(""), str8_lit_comp("Disassembly"), str8_lit_comp(""), RD_IconKind_Glasses}, @@ -203,10 +204,10 @@ RD_VocabInfo rd_vocab_info_table[374] = {str8_lit_comp("thaw_thread"), str8_lit_comp(""), str8_lit_comp("Thaw Thread"), str8_lit_comp(""), RD_IconKind_Unlocked}, {str8_lit_comp("freeze_process"), str8_lit_comp(""), str8_lit_comp("Freeze Process"), str8_lit_comp(""), RD_IconKind_Locked}, {str8_lit_comp("thaw_process"), str8_lit_comp(""), str8_lit_comp("Thaw Process"), str8_lit_comp(""), RD_IconKind_Unlocked}, -{str8_lit_comp("freeze_machine"), str8_lit_comp(""), str8_lit_comp("Freeze Machine"), str8_lit_comp(""), RD_IconKind_Locked}, -{str8_lit_comp("thaw_machine"), str8_lit_comp(""), str8_lit_comp("Thaw Machine"), str8_lit_comp(""), RD_IconKind_Unlocked}, -{str8_lit_comp("freeze_local_machine"), str8_lit_comp(""), str8_lit_comp("Freeze Local Machine"), str8_lit_comp(""), RD_IconKind_Machine}, -{str8_lit_comp("thaw_local_machine"), str8_lit_comp(""), str8_lit_comp("Thaw Local Machine"), str8_lit_comp(""), RD_IconKind_Machine}, +{str8_lit_comp("freeze_server "), str8_lit_comp(""), str8_lit_comp("Freeze Server "), str8_lit_comp(""), RD_IconKind_Locked}, +{str8_lit_comp("thaw_server"), str8_lit_comp(""), str8_lit_comp("Thaw Server"), str8_lit_comp(""), RD_IconKind_Unlocked}, +{str8_lit_comp("freeze_local_server"), str8_lit_comp(""), str8_lit_comp("Freeze Local Server"), str8_lit_comp(""), RD_IconKind_Machine}, +{str8_lit_comp("thaw_local_server"), str8_lit_comp(""), str8_lit_comp("Thaw Local Server"), str8_lit_comp(""), RD_IconKind_Machine}, {str8_lit_comp("freeze_entity"), str8_lit_comp(""), str8_lit_comp("Freeze Entity"), str8_lit_comp(""), RD_IconKind_Unlocked}, {str8_lit_comp("thaw_entity"), str8_lit_comp(""), str8_lit_comp("Thaw Entity"), str8_lit_comp(""), RD_IconKind_Locked}, {str8_lit_comp("set_entity_color"), str8_lit_comp(""), str8_lit_comp("Set Entity Color"), str8_lit_comp(""), RD_IconKind_Null}, @@ -214,8 +215,8 @@ RD_VocabInfo rd_vocab_info_table[374] = {str8_lit_comp("download_module_debug_info"), str8_lit_comp(""), str8_lit_comp("Download Module Debug Info"), str8_lit_comp(""), RD_IconKind_Module}, {str8_lit_comp("attach"), str8_lit_comp(""), str8_lit_comp("Attach"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("open_crash_dump"), str8_lit_comp(""), str8_lit_comp("Open Crash Dump"), str8_lit_comp(""), RD_IconKind_FileOutline}, -{str8_lit_comp("open_remote_debugger"), str8_lit_comp(""), str8_lit_comp("Open Remote Debugger"), str8_lit_comp(""), RD_IconKind_Machine}, -{str8_lit_comp("close_remote_debugger"), str8_lit_comp(""), str8_lit_comp("Close Remote Debugger"), str8_lit_comp(""), RD_IconKind_Machine}, +{str8_lit_comp("open_remote_server"), str8_lit_comp(""), str8_lit_comp("Open Remote Server"), str8_lit_comp(""), RD_IconKind_Machine}, +{str8_lit_comp("close_remote_server"), str8_lit_comp(""), str8_lit_comp("Close Remote Server"), str8_lit_comp(""), RD_IconKind_Machine}, {str8_lit_comp("exit"), str8_lit_comp(""), str8_lit_comp("Exit"), str8_lit_comp(""), RD_IconKind_X}, {str8_lit_comp("open_palette"), str8_lit_comp(""), str8_lit_comp("Open Palette"), str8_lit_comp(""), RD_IconKind_List}, {str8_lit_comp("run_command"), str8_lit_comp(""), str8_lit_comp("Run Command"), str8_lit_comp(""), RD_IconKind_Null}, @@ -223,8 +224,6 @@ RD_VocabInfo rd_vocab_info_table[374] = {str8_lit_comp("state"), str8_lit_comp(""), str8_lit_comp("State"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp("Eval"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp("Line From Virtual Address"), str8_lit_comp(""), RD_IconKind_Null}, -{str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp("Remote Controller Connect"), str8_lit_comp(""), RD_IconKind_Null}, -{str8_lit_comp("remote_controller_disconnect"), str8_lit_comp(""), str8_lit_comp("Remote Controller Disconnect"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp("OS Event"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("select_thread"), str8_lit_comp(""), str8_lit_comp("Select Thread"), str8_lit_comp(""), RD_IconKind_Thread}, {str8_lit_comp("select_unwind"), str8_lit_comp(""), str8_lit_comp("Select Unwind"), str8_lit_comp(""), RD_IconKind_Null}, @@ -427,7 +426,7 @@ RD_VocabInfo rd_vocab_info_table[374] = {str8_lit_comp("debug_infos"), str8_lit_comp(""), str8_lit_comp("Debug Info"), str8_lit_comp(""), RD_IconKind_Module}, {str8_lit_comp("threads"), str8_lit_comp(""), str8_lit_comp("Threads"), str8_lit_comp(""), RD_IconKind_Threads}, {str8_lit_comp("processes"), str8_lit_comp(""), str8_lit_comp("Processes"), str8_lit_comp(""), RD_IconKind_Scheduler}, -{str8_lit_comp("machines"), str8_lit_comp(""), str8_lit_comp("Machines"), str8_lit_comp(""), RD_IconKind_Machine}, +{str8_lit_comp("servers"), str8_lit_comp(""), str8_lit_comp("Servers"), str8_lit_comp(""), RD_IconKind_Machine}, {str8_lit_comp("modules"), str8_lit_comp(""), str8_lit_comp("Modules"), str8_lit_comp(""), RD_IconKind_Module}, {str8_lit_comp("file_path_maps"), str8_lit_comp(""), str8_lit_comp("File Path Map"), str8_lit_comp(""), RD_IconKind_FileOutline}, {str8_lit_comp("type_views"), str8_lit_comp(""), str8_lit_comp("Type Views"), str8_lit_comp(""), RD_IconKind_Binoculars}, @@ -477,7 +476,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("file_path_map"), 0, str8_lit_comp("@collection_commands(add_file_path_map) @row_commands(remove_cfg) x:{'source': @no_relativize path, 'dest': @no_relativize path}")}, {str8_lit_comp("type_view"), 0, str8_lit_comp("@collection_commands(add_type_view) @row_commands(remove_cfg) x:{'type':expr_string, 'expr':expr_string}")}, {str8_lit_comp("recent_project"), 0, str8_lit_comp("x:{'path':path, 'name':string}")}, -{str8_lit_comp("machine"), 0, str8_lit_comp("x:{'label':code_string, @no_expand 'active':bool, 'unattached_processes':set, 'processes':set}")}, +{str8_lit_comp("server"), 0, str8_lit_comp("x:{'label':code_string, @no_expand 'active':bool, 'unattached_processes':set, 'processes':set}")}, {str8_lit_comp("process"), 0, str8_lit_comp("x:{'label':code_string, 'id':u64, @no_expand 'active':bool, 'modules':set, 'threads':set, @query @no_expand 'memory':address_space}")}, {str8_lit_comp("module"), 0, str8_lit_comp("x:{'exe':path, 'dbg':path, 'vaddr_range':vaddr_range}")}, {str8_lit_comp("thread"), 0, str8_lit_comp("x:{'label':code_string, 'id':u64, @no_expand 'active':bool, 'call_stack':set}")}, @@ -486,7 +485,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = String8 rd_reg_slot_code_name_table[56] = { {0}, -str8_lit_comp("machine"), +str8_lit_comp("server"), str8_lit_comp("module"), str8_lit_comp("process"), str8_lit_comp("thread"), @@ -546,7 +545,7 @@ str8_lit_comp("wm_event"), Rng1U64 rd_reg_slot_range_table[56] = { {0}, -{OffsetOf(RD_Regs, machine), OffsetOf(RD_Regs, machine) + sizeof(D_Handle)}, +{OffsetOf(RD_Regs, server), OffsetOf(RD_Regs, server) + sizeof(D_Handle)}, {OffsetOf(RD_Regs, module), OffsetOf(RD_Regs, module) + sizeof(D_Handle)}, {OffsetOf(RD_Regs, process), OffsetOf(RD_Regs, process) + sizeof(D_Handle)}, {OffsetOf(RD_Regs, thread), OffsetOf(RD_Regs, thread) + sizeof(D_Handle)}, @@ -603,7 +602,7 @@ Rng1U64 rd_reg_slot_range_table[56] = {OffsetOf(RD_Regs, wm_event), OffsetOf(RD_Regs, wm_event) + sizeof(WM_Event *)}, }; -RD_CmdKindInfo rd_cmd_kind_info_table[261] = +RD_CmdKindInfo rd_cmd_kind_info_table[259] = { {0}, { str8_lit_comp("launch_and_run"), str8_lit_comp("Starts debugging a new instance of a target, then runs."), str8_lit_comp("launch,start,run,target"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:targets")}}, @@ -631,10 +630,10 @@ RD_CmdKindInfo rd_cmd_kind_info_table[261] = { str8_lit_comp("thaw_thread"), str8_lit_comp("Thaws the passed thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Thread, str8_lit_comp("query:threads")}}, { str8_lit_comp("freeze_process"), str8_lit_comp("Freezes the passed process."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Process, str8_lit_comp("query:processes")}}, { str8_lit_comp("thaw_process"), str8_lit_comp("Thaws the passed process."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Process, str8_lit_comp("query:processes")}}, -{ str8_lit_comp("freeze_machine"), str8_lit_comp("Freezes the passed machine."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Machine, str8_lit_comp("query:machines")}}, -{ str8_lit_comp("thaw_machine"), str8_lit_comp("Thaws the passed machine."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Machine, str8_lit_comp("query:machines")}}, -{ str8_lit_comp("freeze_local_machine"), str8_lit_comp("Freezes the local machine."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, -{ str8_lit_comp("thaw_local_machine"), str8_lit_comp("Thaws the local machine."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("freeze_server "), str8_lit_comp("Freezes the passed server."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Server, str8_lit_comp("query:servers")}}, +{ str8_lit_comp("thaw_server"), str8_lit_comp("Thaws the passed server."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Server, str8_lit_comp("query:servers")}}, +{ str8_lit_comp("freeze_local_server"), str8_lit_comp("Freezes the local server."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("thaw_local_server"), str8_lit_comp("Thaws the local server."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("freeze_entity"), str8_lit_comp("Freezes an entity."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("thaw_entity"), str8_lit_comp("Thaws an entity."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("set_entity_color"), str8_lit_comp("Sets the passed entity's color."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, @@ -642,8 +641,8 @@ RD_CmdKindInfo rd_cmd_kind_info_table[261] = { str8_lit_comp("download_module_debug_info"), str8_lit_comp("Tries to download a module's debug info from symbol server(s)."), str8_lit_comp(""), str8_lit_comp("{module_commands}"), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CtrlEntity, str8_lit_comp("query:modules")}}, { str8_lit_comp("attach"), str8_lit_comp("Attaches to a process that is already running on the local machine."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_PID, str8_lit_comp("query:unattached_processes")}}, { str8_lit_comp("open_crash_dump"), str8_lit_comp("Opens a crash dump file and recreates debuggee state at the time of the crash."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_FilePath, str8_lit_comp("folder:\"$input\"")}}, -{ str8_lit_comp("open_remote_debugger"), str8_lit_comp("Opens a remote debugger by IP address."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_String, str8_lit_comp("")}}, -{ str8_lit_comp("close_remote_debugger"), str8_lit_comp("Closes a remote debugger."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CtrlEntity, str8_lit_comp("query:machines")}}, +{ str8_lit_comp("open_remote_server"), str8_lit_comp("Opens a remote debug server by IP address."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_String, str8_lit_comp("")}}, +{ str8_lit_comp("close_remote_server"), str8_lit_comp("Closes a remote debug server."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CtrlEntity, str8_lit_comp("query:servers")}}, { str8_lit_comp("exit"), str8_lit_comp("Exits the debugger."), str8_lit_comp("quit,close,abort"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("open_palette"), str8_lit_comp("Opens the palette."), str8_lit_comp("help,cmd,lister"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("run_command"), str8_lit_comp("Runs a command from the command palette."), str8_lit_comp("help,cmd"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_CmdName, str8_lit_comp("query:commands")}}, @@ -651,8 +650,6 @@ RD_CmdKindInfo rd_cmd_kind_info_table[261] = { str8_lit_comp("state"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Expr, str8_lit_comp("")}}, { str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Vaddr, str8_lit_comp("")}}, -{ str8_lit_comp("remote_controller_connect"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, -{ str8_lit_comp("remote_controller_disconnect"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("select_thread"), str8_lit_comp("Selects a thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Thread, str8_lit_comp("query:threads")}}, { str8_lit_comp("select_unwind"), str8_lit_comp("Selects an unwind frame number for the selected thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("query:call_stack")}}, @@ -855,7 +852,7 @@ RD_CmdKindInfo rd_cmd_kind_info_table[261] = { str8_lit_comp("debug_infos"), str8_lit_comp("Opens a Debug Info tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, { str8_lit_comp("threads"), str8_lit_comp("Opens a Threads tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, { str8_lit_comp("processes"), str8_lit_comp("Opens a Processes tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, -{ str8_lit_comp("machines"), str8_lit_comp("Opens a Machines tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, +{ str8_lit_comp("servers"), str8_lit_comp("Opens a Servers tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, { str8_lit_comp("modules"), str8_lit_comp("Opens a Modules tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, { str8_lit_comp("file_path_maps"), str8_lit_comp("Opens a File Path Map tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, { str8_lit_comp("type_views"), str8_lit_comp("Opens a Type Views tab."), {0}, str8_lit_comp("{tab_commands}"), RD_CmdKindFlag_ListInUI|RD_CmdKindFlag_ListInIPCDocs}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 950f11d3..d519b33e 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -9,7 +9,7 @@ typedef enum RD_RegSlot { RD_RegSlot_Null, -RD_RegSlot_Machine, +RD_RegSlot_Server, RD_RegSlot_Module, RD_RegSlot_Process, RD_RegSlot_Thread, @@ -95,10 +95,10 @@ RD_CmdKind_FreezeThread, RD_CmdKind_ThawThread, RD_CmdKind_FreezeProcess, RD_CmdKind_ThawProcess, -RD_CmdKind_FreezeMachine, -RD_CmdKind_ThawMachine, -RD_CmdKind_FreezeLocalMachine, -RD_CmdKind_ThawLocalMachine, +RD_CmdKind_FreezeServer, +RD_CmdKind_ThawServer, +RD_CmdKind_FreezeLocalServer, +RD_CmdKind_ThawLocalServer, RD_CmdKind_FreezeEntity, RD_CmdKind_ThawEntity, RD_CmdKind_SetEntityColor, @@ -106,8 +106,8 @@ RD_CmdKind_SetEntityName, RD_CmdKind_DownloadModuleDebugInfo, RD_CmdKind_Attach, RD_CmdKind_OpenCrashDump, -RD_CmdKind_OpenRemoteDebugger, -RD_CmdKind_CloseRemoteDebugger, +RD_CmdKind_OpenRemoteServer, +RD_CmdKind_CloseRemoteServer, RD_CmdKind_Exit, RD_CmdKind_OpenPalette, RD_CmdKind_RunCommand, @@ -115,8 +115,6 @@ RD_CmdKind_RunExternalDriverTextCommand, RD_CmdKind_State, RD_CmdKind_Eval, RD_CmdKind_LineFromVAddr, -RD_CmdKind_RemoteControllerConnect, -RD_CmdKind_RemoteControllerDisconnect, RD_CmdKind_WMEvent, RD_CmdKind_SelectThread, RD_CmdKind_SelectUnwind, @@ -319,7 +317,7 @@ RD_CmdKind_OpenWatchPins, RD_CmdKind_OpenDebugInfos, RD_CmdKind_OpenThreads, RD_CmdKind_OpenProcesses, -RD_CmdKind_OpenMachines, +RD_CmdKind_OpenServers, RD_CmdKind_OpenModules, RD_CmdKind_OpenFilePathMaps, RD_CmdKind_OpenTypeViews, @@ -470,7 +468,7 @@ String8 schema; typedef struct RD_Regs RD_Regs; struct RD_Regs { -D_Handle machine; +D_Handle server; D_Handle module; D_Handle process; D_Handle thread; @@ -561,7 +559,7 @@ X(watch_pins) \ X(debug_infos) \ X(threads) \ X(processes) \ -X(machines) \ +X(servers) \ X(modules) \ X(file_path_maps) \ X(type_views) \ @@ -575,7 +573,7 @@ Y(geo3d, geo3d, "")\ Z(getting_started)\ #define rd_regs_lit_init_top \ -.machine = rd_regs()->machine,\ +.server = rd_regs()->server,\ .module = rd_regs()->module,\ .process = rd_regs()->process,\ .thread = rd_regs()->thread,\ @@ -634,7 +632,7 @@ Z(getting_started)\ C_LINKAGE_BEGIN extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; -extern RD_VocabInfo rd_vocab_info_table[374]; +extern RD_VocabInfo rd_vocab_info_table[373]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; extern String8 rd_reg_slot_code_name_table[56]; extern Rng1U64 rd_reg_slot_range_table[56]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index c069fac5..8c041e13 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -36,7 +36,7 @@ RD_WatchTabFastPathTable: {DebugInfos "Debug Info" debug_infos 1 Module "Displays, and allows editing of, the list of all debug info files that the debugger has loaded."} {Threads "Threads" threads 1 Threads "Displays the list of all threads in all processes to which the debugger is attached."} {Processes "Processes" processes 1 Scheduler "Displays the list of all processes to which the debugger is attached."} - {Machines "Machines" machines 1 Machine "Displays the list of all machines to which the debugger is connected."} + {Servers "Servers" servers 1 Machine "Displays the list of all servers to which the debugger is connected."} {Modules "Modules" modules 1 Module "Displays the list of all modules in all processes to which the debugger is attached."} {FilePathMaps "File Path Map" file_path_maps 1 FileOutline "Displays, and allows editing of, the list of all file path maps. This allows remapping source code paths referenced by debug information to other paths on your local machine."} {TypeViews "Type Views" type_views 1 Binoculars "Displays, and allows editing of, the list of all type views, which allow automatically adjusting the visualizations for evaluations of a certain type."} @@ -147,6 +147,7 @@ RD_VocabTable: {process processes "Process" "Processes" Scheduler } {processes "" "Processes" "" Scheduler } {machine _ "Machine" _ Machine } + {server _ "Server" _ Machine } {module _ "Module" _ Module } {getting_started "" "Getting Started" "" QuestionMark } {disasm "" "Disassembly" "" Glasses } @@ -878,9 +879,9 @@ RD_VocabTable: ```x:{'path':path, 'name':string}```, } - //- rjf: @schema machine + //- rjf: @schema server { - machine, 0, + server, 0, ```x:{'label':code_string, @no_expand 'active':bool, 'unattached_processes':set, 'processes':set}```, } @@ -922,7 +923,7 @@ RD_VocabTable: RD_RegTable: { // rjf: ctrl entities - {D_Handle machine Machine } + {D_Handle server Server } {D_Handle module Module } {D_Handle process Process } {D_Handle thread Thread } @@ -1046,8 +1047,6 @@ RD_CmdTable: // | | | | {State 0 0 1 "" Null 0 0 0 0 0 0 0 Null "state" "State" "" "" "" } {Eval 0 0 1 "" Expr 0 0 0 0 0 0 0 Null "eval" "Eval" "" "" "" } {LineFromVAddr 0 0 1 "" Vaddr 0 0 0 0 0 0 0 Null "line_from_vaddr" "Line From Virtual Address" "" "" "" } - {RemoteControllerConnect 0 0 1 "" Null 0 0 0 0 0 0 0 Null "remote_controller_connect" "Remote Controller Connect" "" "" "" } - {RemoteControllerDisconnect 0 0 1 "" Null 0 0 0 0 0 0 0 Null "remote_controller_disconnect" "Remote Controller Disconnect" "" "" "" } //- rjf: os event passthrough {WMEvent 0 0 0 "" Null 0 0 0 0 0 0 0 Null "wm_event" "OS Event" "" "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index f1406967..2234ff53 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2496,7 +2496,7 @@ rd_view_ui(Rng2F32 rect) else if(entity->kind == D_EntityKind_Thread) { rd_regs()->thread = entity->handle; } else if(entity->kind == D_EntityKind_Module) { rd_regs()->module = entity->handle; } else if(entity->kind == D_EntityKind_Process) { rd_regs()->process = entity->handle; } - else if(entity->kind == D_EntityKind_Machine) { rd_regs()->machine = entity->handle; } + else if(entity->kind == D_EntityKind_Server) { rd_regs()->server = entity->handle; } rd_cmd(RD_CmdKind_CompleteQuery); } }break; @@ -4728,7 +4728,7 @@ rd_view_ui(Rng2F32 rect) RD_RegsScope(.ctrl_entity = cell_info.entity->handle) switch(cell_info.entity->kind) { default:{rd_drag_begin(RD_RegSlot_CtrlEntity);}break; - case D_EntityKind_Machine:{RD_RegsScope(.machine = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Machine);}break; + case D_EntityKind_Server:{RD_RegsScope(.server = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Server);}break; case D_EntityKind_Process:{RD_RegsScope(.process = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Process);}break; case D_EntityKind_Module:{RD_RegsScope(.module = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Module);}break; case D_EntityKind_Thread:{RD_RegsScope(.thread = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Thread);}break; @@ -6141,7 +6141,7 @@ rd_window_frame(void) //////////////////////// //- rjf: control entity tooltips // - case RD_RegSlot_Machine: {ctrl_entity = d_entity_from_handle(regs->machine); }goto ctrl_entity_tooltip; + case RD_RegSlot_Server: {ctrl_entity = d_entity_from_handle(regs->server); }goto ctrl_entity_tooltip; case RD_RegSlot_Process: {ctrl_entity = d_entity_from_handle(regs->process); }goto ctrl_entity_tooltip; case RD_RegSlot_Module: {ctrl_entity = d_entity_from_handle(regs->module); }goto ctrl_entity_tooltip; case RD_RegSlot_Thread: {ctrl_entity = d_entity_from_handle(regs->thread); }goto ctrl_entity_tooltip; @@ -6368,7 +6368,7 @@ rd_window_frame(void) ID(view); #undef ID #define Handle(name) ui_labelf("%s: [0x%x, 0x%x, 0x%I64x]", #name, (regs->name).demon_id, (regs->name).controller_kind, (regs->name).entity_id) - Handle(machine); + Handle(server); Handle(process); Handle(module); Handle(thread); @@ -11078,7 +11078,7 @@ rd_regs_fill_slot_from_string(RD_RegSlot slot, String8 query_expr, String8 strin }break; //- rjf: ctrl entities - case RD_RegSlot_Machine: + case RD_RegSlot_Server: case RD_RegSlot_Module: case RD_RegSlot_Process: case RD_RegSlot_Thread: @@ -12840,7 +12840,7 @@ rd_frame(void) //- rjf: add macros for evallable control entities String8 evallable_ctrl_names[] = { - str8_lit("machine"), + str8_lit("server"), str8_lit("process"), str8_lit("thread"), str8_lit("module"), @@ -12863,9 +12863,9 @@ rd_frame(void) { e_string2expr_map_insert(scratch.arena, macro_map, entity->string, expr); } - if(kind == D_EntityKind_Machine && entity->handle.demon_id == D_DemonID_LocalHost) + if(kind == D_EntityKind_Server && entity->handle.demon_id == D_DemonID_LocalHost) { - e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("local_machine"), expr); + e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("local_server"), expr); } if(kind == D_EntityKind_Thread && d_handle_match(rd_base_regs()->thread, entity->handle)) { @@ -13623,7 +13623,7 @@ rd_frame(void) if(D_CmdKind_Null < (D_CmdKind)kind && (D_CmdKind)kind < D_CmdKind_COUNT) { D_CmdParams params = {0}; - params.machine = rd_regs()->machine; + params.server = rd_regs()->server; params.process = rd_regs()->process; params.thread = rd_regs()->thread; params.entity = rd_regs()->ctrl_entity; @@ -13687,7 +13687,7 @@ rd_frame(void) str8_list_pushf(scratch.arena, &exprs, "query:targets"); str8_list_pushf(scratch.arena, &exprs, "query:breakpoints"); str8_list_pushf(scratch.arena, &exprs, "query:recent_projects"); - str8_list_pushf(scratch.arena, &exprs, "query:machines"); + str8_list_pushf(scratch.arena, &exprs, "query:servers"); str8_list_pushf(scratch.arena, &exprs, "query:processes"); str8_list_pushf(scratch.arena, &exprs, "query:threads"); str8_list_pushf(scratch.arena, &exprs, "query:modules"); @@ -13975,24 +13975,6 @@ rd_frame(void) } str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "}\n"); }break; - case RD_CmdKind_RemoteControllerConnect: - { - CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); - CFG_Node *ctrlr = cfg_node_new(rd_state->cfg, transient, s("remote_controller")); - cfg_node_new(rd_state->cfg, ctrlr, sock_string_from_endpoint(scratch.arena, rd_state->ipc_sock_endpoint)); - }break; - case RD_CmdKind_RemoteControllerDisconnect: - { - CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); - for(CFG_Node *child = transient; child != &cfg_nil_node; child = child->next) - { - if(str8_match(child->string, s("remote_controller"), 0) && str8_match(child->first->string, rd_regs()->string, 0)) - { - cfg_node_release(rd_state->cfg, child); - break; - } - } - }break; //- rjf: exiting case RD_CmdKind_Exit: @@ -17366,13 +17348,13 @@ rd_frame(void) D_Entity *thread = d_entity_from_handle(rd_regs()->thread); D_Entity *process = d_entity_ancestor_from_kind(thread, D_EntityKind_Process); D_Entity *module = d_module_from_process_vaddr(process, d_cached_ip_from_thread(thread->handle)); - D_Entity *machine = d_entity_ancestor_from_kind(process, D_EntityKind_Machine); + D_Entity *server = d_entity_ancestor_from_kind(process, D_EntityKind_Server); rd_state->base_regs.v.unwind_count = 0; rd_state->base_regs.v.inline_depth = 0; rd_state->base_regs.v.thread = thread->handle; rd_state->base_regs.v.module = module->handle; rd_state->base_regs.v.process = process->handle; - rd_state->base_regs.v.machine = machine->handle; + rd_state->base_regs.v.server = server->handle; rd_cmd(RD_CmdKind_FindThread, .thread = thread->handle, .unwind_count = 0, .inline_depth = 0); }break; case RD_CmdKind_SelectUnwind: @@ -18382,6 +18364,24 @@ rd_frame(void) MTX_Op op = {r1u64(max_U64, max_U64), evt->string}; mtx_push_op(key, op); }break; + case D_EventKind_RemoteControllerConnect: + { + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); + CFG_Node *ctrlr = cfg_node_new(rd_state->cfg, transient, s("remote_controller")); + cfg_node_new(rd_state->cfg, ctrlr, sock_string_from_endpoint(scratch.arena, rd_state->ipc_sock_endpoint)); + }break; + case D_EventKind_RemoteControllerDisconnect: + { + CFG_Node *transient = cfg_node_child_from_string(cfg_node_root(), s("transient")); + for(CFG_Node *child = transient; child != &cfg_nil_node; child = child->next) + { + if(str8_match(child->string, s("remote_controller"), 0) && str8_match(child->first->string, rd_regs()->string, 0)) + { + cfg_node_release(rd_state->cfg, child); + break; + } + } + }break; } } diff --git a/src/raddbg/raddbg_eval.c b/src/raddbg/raddbg_eval.c index ab9f89e2..ba471cf7 100644 --- a/src/raddbg/raddbg_eval.c +++ b/src/raddbg/raddbg_eval.c @@ -1705,14 +1705,14 @@ E_TYPE_EXPAND_INFO_FUNCTION_DEF(unattached_processes) //- rjf: gather all machines we're searching through D_EntityArray machines = {0}; - if(lhs_entity->kind == D_EntityKind_Machine) + if(lhs_entity->kind == D_EntityKind_Server) { machines.v = &lhs_entity; machines.count = 1; } else { - machines = d_entity_array_from_kind(D_EntityKind_Machine); + machines = d_entity_array_from_kind(D_EntityKind_Server); } //- rjf: gather system processes from this machine diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index fd790fe2..7e05b46b 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -1607,7 +1607,7 @@ rd_watch_row_info_from_row(Arena *arena, EV_Row *row) rd_watch_cell_list_push_new(arena, &info.cells, RD_WatchCellKind_Eval, row->eval, .flags = RD_WatchCellFlag_Expr|RD_WatchCellFlag_NoEval|RD_WatchCellFlag_Indented|RD_WatchCellFlag_Button, .pct = 1.f); - if(entity->kind == D_EntityKind_Machine || + if(entity->kind == D_EntityKind_Server || entity->kind == D_EntityKind_Process || entity->kind == D_EntityKind_Thread) { diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index ffe9a842..38d09ca2 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -476,7 +476,7 @@ rd_title_fstrs_from_ctrl_entity(Arena *arena, D_Entity *entity, B32 include_extr switch(entity->kind) { default:{}break; - case D_EntityKind_Machine: {icon_kind = RD_IconKind_Machine;}break; + case D_EntityKind_Server: {icon_kind = RD_IconKind_Machine;}break; case D_EntityKind_Process: {icon_kind = RD_IconKind_Threads;}break; case D_EntityKind_Thread: {icon_kind = RD_IconKind_Thread; name_is_code = 1;}break; case D_EntityKind_Module: {icon_kind = RD_IconKind_Module;}break; @@ -493,7 +493,7 @@ rd_title_fstrs_from_ctrl_entity(Arena *arena, D_Entity *entity, B32 include_extr } //- rjf: push frozen icon, if frozen - if((entity->kind == D_EntityKind_Machine || + if((entity->kind == D_EntityKind_Server || entity->kind == D_EntityKind_Process || entity->kind == D_EntityKind_Thread) && d_entity_tree_is_frozen(entity)) diff --git a/src/raddbg_server/raddbg_server_main.c b/src/raddbg_server/raddbg_server_main.c index 9466c917..194f1145 100644 --- a/src/raddbg_server/raddbg_server_main.c +++ b/src/raddbg_server/raddbg_server_main.c @@ -43,17 +43,50 @@ //////////////////////////////// //~ rjf: Globals +typedef struct RDS_Msg RDS_Msg; +struct RDS_Msg +{ + RDS_Msg *next; + SOCK_Endpoint endpoint; + String8 msg_raw; +}; + +typedef struct RDS_Client RDS_Client; +struct RDS_Client +{ + RDS_Client *order_next; + RDS_Client *order_prev; + RDS_Client *hash_next; + RDS_Client *hash_prev; + SOCK_Endpoint endpoint; +}; + +typedef struct RDS_ClientSlot RDS_ClientSlot; +struct RDS_ClientSlot +{ + RDS_Client *first; + RDS_Client *last; +}; + typedef struct RDS_State RDS_State; struct RDS_State { Arena *arena; SOCK_Session sock_session; + //- rjf: connected clients + RDS_ClientSlot *client_slots; + U64 client_slots_count; + RDS_Client *first_client; + RDS_Client *last_client; + RDS_Client *free_client; + //- rjf: client -> demon messages RWMutex c2d_rw_mutex; CondVar c2d_cv; Arena *c2d_arena; - String8List c2d_msgs; + RDS_Msg *c2d_msg_first; + RDS_Msg *c2d_msg_last; //- rjf: demon run state B32 demon_is_running; @@ -76,14 +109,21 @@ dmn_thread__entry_point(void *p) //////////////////////////// //- rjf: get next messages // - String8List msgs = {0}; + RDS_Msg *msg_first = 0; + RDS_Msg *msg_last = 0; RWMutexScope(rds_state->c2d_rw_mutex, 1) for(;;) { - if(rds_state->c2d_msgs.node_count != 0) + if(rds_state->c2d_msg_first != 0) { - msgs = str8_list_copy(scratch.arena, &rds_state->c2d_msgs); - MemoryZeroStruct(&rds_state->c2d_msgs); + for EachNode(src, RDS_Msg, rds_state->c2d_msg_first) + { + RDS_Msg *dst = push_array(scratch.arena, RDS_Msg, 1); + SLLQueuePush(msg_first, msg_last, dst); + dst->endpoint = src->endpoint; + dst->msg_raw = str8_copy(scratch.arena, src->msg_raw); + } arena_clear(rds_state->c2d_arena); + rds_state->c2d_msg_first = rds_state->c2d_msg_last = 0; break; } cond_var_wait_rw_w(rds_state->c2d_cv, rds_state->c2d_rw_mutex, max_U64); @@ -92,13 +132,14 @@ dmn_thread__entry_point(void *p) //////////////////////////// //- rjf: do messages, push responses // - for(String8Node *msg_n = msgs.first; msg_n != 0; msg_n = msg_n->next) + for(RDS_Msg *msg_n = msg_first; msg_n != 0; msg_n = msg_n->next) { //- rjf: unpack message - DS_Msg msg = ds_msg_from_serialized(scratch.arena, msg_n->string); + DS_Msg msg = ds_msg_from_serialized(scratch.arena, msg_n->msg_raw); //- rjf: do action, get response DS_Response response = {0}; + DMN_EventList events = {0}; switch(msg.kind) { default:{}break; @@ -150,12 +191,26 @@ dmn_thread__entry_point(void *p) ctrls.run_entities_are_unfrozen = !!(msg.flags & DS_MsgFlag_RunEntitiesAreUnfrozen); ctrls.run_entities_are_processes = !!(msg.flags & DS_MsgFlag_RunEntitiesAreProcesses); }; - response.events = dmn_ctrl_run(scratch.arena, ctrl_ctx, &ctrls); + response.good = 1; + events = dmn_ctrl_run(scratch.arena, ctrl_ctx, &ctrls); }break; } - //- rjf: pack response & push - String8 data = ds_serialized_from_response(scratch.arena, &response); + //- rjf: pack response & send back to the originating endpoint of the message + { + String8 data = ds_serialized_from_response(scratch.arena, &response); + sock_send(rds_state->sock_session, SOCK_Protocol_TCP, msg_n->endpoint, data, max_U64); + } + + //- rjf: pack events & send to all connected client + if(events.count != 0) + { + String8 data = ds_serialized_from_dmn_events(scratch.arena, events); + for(RDS_Client *c = rds_state->first_client; c != 0; c = c->order_next) + { + sock_send(rds_state->sock_session, SOCK_Protocol_TCP, c->endpoint, data, max_U64); + } + } } scratch_end(scratch); @@ -203,16 +258,50 @@ entry_point(CmdLine *cmd_line) String8 data = {0}; sock_recv(scratch.arena, rds_state->sock_session, &protocol, &endpoint, &data, max_U64); - //- rjf: parse magic prefix - U16 magic_maybe = 0; - str8_deserial_read_struct(data, 0, &magic_maybe); + //- rjf: store this client, if we don't have it + { + U64 hash = u64_hash_from_str8(str8_struct(&endpoint)); + U64 slot_idx = hash%rds_state->client_slots_count; + RDS_Client *client = 0; + for(RDS_Client *c = rds_state->client_slots[slot_idx].first; c != 0; c = c->hash_next) + { + if(MemoryMatchStruct(&c->endpoint, &endpoint)) + { + client = c; + break; + } + } + if(client == 0) + { + client = rds_state->free_client; + if(client != 0) + { + SLLStackPop_N(rds_state->free_client, order_next); + } + else + { + client = push_array(rds_state->arena, RDS_Client, 1); + } + DLLPushBack_NP(rds_state->client_slots[slot_idx].first, rds_state->client_slots[slot_idx].last, client, hash_next, hash_prev); + DLLPushBack_NP(rds_state->first_client, rds_state->last_client, client, order_next, order_prev); + client->endpoint = endpoint; + } + } + + //- rjf: get packet header + DS_PacketHeader header = {0}; + if(data.size >= sizeof(header)) + { + MemoryCopy(&header, data.str, sizeof(header)); + } + B32 header_is_good = (header.magic == DS_PACKET_MAGIC && header.kind == DS_PacketKind_Msg && header.version == DS_PACKET_VERSION); //- rjf: log message info { String8 endpoint_string = sock_string_from_endpoint(scratch.arena, endpoint); fprintf(stderr, "[got message]\n"); fprintf(stderr, " sender: %.*s\n", str8_varg(endpoint_string)); - fprintf(stderr, " magic: 0x%x\n", (int)magic_maybe); + fprintf(stderr, " magic: 0x%x\n", (int)header.magic); } //- rjf: try to handle this message on the main thread, before sending to @@ -222,12 +311,14 @@ entry_point(CmdLine *cmd_line) // memory/registers, listing processes, and halting, we want to just do // on the main thread without interrupting the demon at all. DS_MsgKind peek_kind = DS_MsgKind_Null; - if(magic_maybe == DS_MSG_MAGIC) + if(header_is_good) { - str8_deserial_read_struct(data, 2, &peek_kind); + String8 msg_raw = str8_skip(data, sizeof(header)); + str8_deserial_read_struct(msg_raw, 2, &peek_kind); if(peek_kind < DS_MsgKind_Launch || DS_MsgKind_SingleStep < peek_kind) { DS_Msg msg = ds_msg_from_serialized(scratch.arena, data); + DS_Response response = {0}; switch(msg.kind) { default:{}break; @@ -240,11 +331,14 @@ entry_point(CmdLine *cmd_line) } //- rjf: not handled by main thread? -> okay, send to demon thread, if it's ready. - if(magic_maybe == DS_MSG_MAGIC && !rds_state->demon_is_running && (DS_MsgKind_Launch <= peek_kind && peek_kind <= DS_MsgKind_SingleStep)) + if(header_is_good && (DS_MsgKind_Launch <= peek_kind && peek_kind <= DS_MsgKind_SingleStep)) { RWMutexScope(rds_state->c2d_rw_mutex, 1) { - str8_list_push(rds_state->c2d_arena, &rds_state->c2d_msgs, data); + String8 msg_raw = str8_skip(data, sizeof(header)); + RDS_Msg *msg = push_array(rds_state->c2d_arena, RDS_Msg, 1); + msg->endpoint = endpoint; + msg->msg_raw = str8_copy(rds_state->c2d_arena, msg_raw); } cond_var_broadcast(rds_state->c2d_cv); } From 41598aaf081b183805cc6ca867bc352f26d2a3e4 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 12 Oct 2026 08:43:45 -0700 Subject: [PATCH 239/251] dbg_server / dbg_engine: checkpoint on remote debug servers --- src/dbg_engine/dbg_engine_core.c | 1 + src/dbg_engine/dbg_engine_core.h | 6 +- src/dbg_engine/dbg_engine_ctrl.c | 157 +++++++++++++------ src/dbg_engine/dbg_engine_ctrl.h | 6 +- src/dbg_engine/dbg_engine_user.c | 8 +- src/dbg_engine/dbg_engine_user.h | 6 +- src/dbg_server/dbg_server.c | 209 ++++++++++++++++--------- src/dbg_server/dbg_server.h | 13 +- src/linux/socket/linux_socket.c | 1 + src/raddbg/raddbg_core.c | 12 +- src/raddbg/raddbg_main.c | 40 ++--- src/raddbg_server/raddbg_server_main.c | 6 +- src/win32/socket/win32_socket.c | 1 + 13 files changed, 301 insertions(+), 165 deletions(-) diff --git a/src/dbg_engine/dbg_engine_core.c b/src/dbg_engine/dbg_engine_core.c index fe6cf195..7968a0c2 100644 --- a/src/dbg_engine/dbg_engine_core.c +++ b/src/dbg_engine/dbg_engine_core.c @@ -104,6 +104,7 @@ d_init(void) Arena *arena = arena_alloc(); d_ctrl_state = push_array(arena, D_CtrlState, 1); d_ctrl_state->arena = arena; + d_ctrl_state->sock_session = sock_session_open(0, 0); for EachEnumVal(Arch, arch) { ARCH_Info *arch_info = arch_info_from_arch(arch); diff --git a/src/dbg_engine/dbg_engine_core.h b/src/dbg_engine/dbg_engine_core.h index 7192ae36..8e789810 100644 --- a/src/dbg_engine/dbg_engine_core.h +++ b/src/dbg_engine/dbg_engine_core.h @@ -22,7 +22,7 @@ struct D_Eval //~ rjf: ID Types typedef U64 D_MsgID; -typedef U32 D_DemonID; +typedef U32 D_ServerID; typedef U32 D_ControllerKind; typedef enum D_ControllerKindEnum { @@ -31,7 +31,7 @@ typedef enum D_ControllerKindEnum } D_ControllerKindEnum; -#define D_DemonID_LocalHost (1) +#define D_ServerID_LocalHost (1) //////////////////////////////// //~ rjf: Entity Handle Types @@ -39,7 +39,7 @@ D_ControllerKindEnum; typedef struct D_Handle D_Handle; struct D_Handle { - D_DemonID demon_id; + D_ServerID server_id; D_ControllerKind controller_kind; U64 entity_id; }; diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 50631345..e8f50689 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -7,7 +7,7 @@ internal U64 d_hash_from_handle(D_Handle handle) { - U64 buf[] = {handle.demon_id, handle.controller_kind, handle.entity_id}; + U64 buf[] = {handle.server_id, handle.controller_kind, handle.entity_id}; U64 hash = u64_hash_from_str8(str8_struct(&handle)); return hash; } @@ -182,7 +182,7 @@ d_handle_array_from_list(Arena *arena, D_HandleList *src) internal String8 d_string_from_handle(Arena *arena, D_Handle handle) { - String8 result = str8f(arena, "$%x$%x$%I64x", handle.demon_id, handle.controller_kind, handle.entity_id); + String8 result = str8f(arena, "$%x$%x$%I64x", handle.server_id, handle.controller_kind, handle.entity_id); return result; } @@ -196,10 +196,10 @@ d_handle_from_string(String8 string) String8List parts = str8_split(scratch.arena, string, &split, 1, 0); if(parts.first && parts.first->next && parts.first->next->next) { - D_DemonID demon_id = (U32)u64_from_str8(parts.first->string, 16); + D_ServerID server_id = (U32)u64_from_str8(parts.first->string, 16); D_ControllerKind controller_kind = (U32)u64_from_str8(parts.first->next->string, 16); U64 entity_id = u64_from_str8(parts.first->next->next->string, 16); - handle.demon_id = demon_id; + handle.server_id = server_id; handle.controller_kind = controller_kind; handle.entity_id = entity_id; } @@ -220,11 +220,11 @@ d_dmn_from_handle(D_Handle handle) } internal D_Handle -d_handle_from_dmn(D_DemonID demon_id, DMN_Handle handle) +d_handle_from_dmn(D_ServerID server_id, DMN_Handle handle) { D_Handle result = {0}; { - result.demon_id = demon_id; + result.server_id = server_id; result.controller_kind = D_ControllerKind_Demon; result.entity_id = handle.u64[0]; } @@ -232,11 +232,11 @@ d_handle_from_dmn(D_DemonID demon_id, DMN_Handle handle) } internal D_Handle -d_dump_handle_make(D_DemonID demon_id, U64 id) +d_dump_handle_make(D_ServerID server_id, U64 id) { D_Handle result = {0}; { - result.demon_id = demon_id; + result.server_id = server_id; result.controller_kind = D_ControllerKind_Dump; result.entity_id = id; } @@ -915,7 +915,7 @@ d_entity_ctx_rw_store_alloc(void) store->ctx.hash_slots_count = 1024; store->ctx.hash_slots = push_array(arena, D_EntityHashSlot, store->ctx.hash_slots_count); D_Entity *root = store->ctx.root = d_entity_alloc(store, &d_entity_nil, D_EntityKind_Root, Arch_Null, d_handle_zero(), 0); - D_Entity *local_server = d_entity_alloc(store, root, D_EntityKind_Server, Arch_CURRENT, d_handle_from_dmn(D_DemonID_LocalHost, dmn_handle_zero()), 0); + D_Entity *local_server = d_entity_alloc(store, root, D_EntityKind_Server, Arch_CURRENT, d_handle_from_dmn(D_ServerID_LocalHost, dmn_handle_zero()), 0); Temp scratch = scratch_begin(0, 0); String8 local_server_name = str8f(scratch.arena, "Local (%S)", get_system_info()->machine_name); d_entity_equip_string(store, local_server, local_server_name); @@ -1306,7 +1306,7 @@ d_entity_store_apply_events(D_EntityCtxRWStore *store, D_EventList *list) //- rjf: processes case D_EventKind_NewProc: { - D_Entity *machine = d_entity_from_handle(d_handle_from_dmn(event->entity.demon_id, dmn_handle_zero())); + D_Entity *machine = d_entity_from_handle(d_handle_from_dmn(event->entity.server_id, dmn_handle_zero())); if(machine != &d_entity_nil) { D_Entity *process = d_entity_alloc(store, machine, D_EntityKind_Process, event->arch, event->entity, (U64)event->entity_id); @@ -1505,6 +1505,20 @@ d_entity_store_apply_events(D_EntityCtxRWStore *store, D_EventList *list) annotation->vaddr_range = event->vaddr_rng; d_entity_equip_string(store, annotation, event->string); }break; + + //- rjf: remote server connections + case D_EventKind_ServerConnect: + { + D_Handle handle = {0}; + handle.server_id = event->entity_id; + D_Entity *server = d_entity_alloc(store, d_entity_ctx->root, D_EntityKind_Server, Arch_Null, handle, 0); + d_entity_equip_string(store, server, event->string); + }break; + case D_EventKind_ServerDisconnect: + { + D_Entity *server = d_entity_from_handle(event->entity); + d_entity_release(store, server); + }break; } } @@ -2938,7 +2952,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)) { Access *access = access_open(); - D_Handle process_handle = d_handle_from_dmn(D_DemonID_LocalHost, ev->process); + D_Handle process_handle = d_handle_from_dmn(D_ServerID_LocalHost, ev->process); D_Entity *process = d_entity_from_handle(process_handle); D_Entity *module = d_module_from_process_vaddr(process, ev->instruction_pointer); if(module != &d_entity_nil) @@ -3139,7 +3153,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_NewProc; out_evt->msg_id = msg_id; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->arch = event->arch; out_evt->entity_id = event->code; out_evt->os = OperatingSystem_CURRENT; // TODO: operating system of the remote target machine @@ -3152,8 +3166,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_NewThread; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); - out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->arch = event->arch; out_evt->entity_id = event->code; out_evt->stack_base = event->stack_pointer; @@ -3168,8 +3182,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D Temp scratch2 = scratch_begin(&scratch.arena, 1); //- rjf: unpack module - D_Handle process_handle = d_handle_from_dmn(D_DemonID_LocalHost, event->process); - D_Handle module_handle = d_handle_from_dmn(D_DemonID_LocalHost, event->module); + D_Handle process_handle = d_handle_from_dmn(D_ServerID_LocalHost, event->process); + D_Handle module_handle = d_handle_from_dmn(D_ServerID_LocalHost, event->module); String8 module_path = path_normalized_from_string(scratch2.arena, event->string); U64 exe_timestamp = properties_from_file_path(module_path).modified; DMN_ModuleInfo *module_info = event->module_info; @@ -3334,7 +3348,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_EndProc; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->u64_code = event->code; d_ctrl_state->process_counter -= 1; }break; @@ -3345,7 +3359,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_EndThread; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); out_evt->entity_id = event->code; }break; @@ -3353,7 +3367,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D case DMN_EventKind_UnloadModule: { //- rjf: unpack module - D_Handle module_handle = d_handle_from_dmn(D_DemonID_LocalHost, event->module); + D_Handle module_handle = d_handle_from_dmn(D_ServerID_LocalHost, event->module); D_Entity *module_ent = d_entity_from_handle(module_handle); D_Entity *process_ent = d_process_from_entity(module_ent); String8 module_path = event->string; @@ -3390,8 +3404,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_DebugString; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); - out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->string = str8_substr(event->string, r1u64(string_idx*d_ctrl_state->c2u_ring_max_string_size, (string_idx+1)*d_ctrl_state->c2u_ring_max_string_size)); } }break; @@ -3402,8 +3416,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_ThreadName; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); - out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->string = event->string; out_evt->entity_id = event->code; }break; @@ -3414,8 +3428,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_ThreadColor; out_evt->msg_id = msg->msg_id; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); - out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->entity_id = event->code; out_evt->rgba = event->user_data; }break; @@ -3425,7 +3439,7 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_SetVAddrRangeNote; - out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->parent = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->msg_id = msg->msg_id; out_evt->vaddr_rng = r1u64(event->address, event->address + event->size); out_evt->string = event->string; @@ -3436,8 +3450,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D { D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_SetBreakpoint; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); - out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); out_evt->bp_flags = d_breakpoint_flags_from_dmn_trap_flags(event->flags); }break; @@ -3448,8 +3462,8 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D // TODO(rjf): this needs to be reflected in the resolved trap list too!!!!!!!! D_Event *out_evt = d_event_list_push(scratch.arena, &evts); out_evt->kind = D_EventKind_UnsetBreakpoint; - out_evt->entity = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); - out_evt->parent = d_handle_from_dmn(D_DemonID_LocalHost, event->process); + out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); + out_evt->parent = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); out_evt->bp_flags = d_breakpoint_flags_from_dmn_trap_flags(event->flags); }break; @@ -3813,13 +3827,52 @@ d_ctrl_thread__end_and_flush_log(void) internal void d_ctrl_thread__open_remote_server(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { + SOCK_Endpoint endpoint = sock_endpoint_from_string(msg->path); + //- rjf: handshake & get state + { + Temp scratch = scratch_begin(0, 0); + DS_Msg msg = {0}; + { + msg.kind = DS_MsgKind_Handshake; + msg.msg_id = 1; + } + String8 msg_raw = ds_packet_from_msg(scratch.arena, &msg); + DS_PacketHeader header = + { + .magic = DS_PACKET_MAGIC, + .kind = DS_PacketKind_Msg, + .version = DS_PACKET_VERSION, + .payload_size = msg_raw.size, + }; + scratch_end(scratch); + } + + //- rjf: push connect event + { + Temp scratch = scratch_begin(0, 0); + D_EventList evts = {0}; + D_Event *evt = d_event_list_push(scratch.arena, &evts); + d_ctrl_state->server_id_gen += 1; + evt->kind = D_EventKind_ServerConnect; + evt->entity_id = D_ServerID_LocalHost + d_ctrl_state->server_id_gen; + evt->string = msg->path; + d_c2u_push_events(&evts); + scratch_end(scratch); + } } internal void d_ctrl_thread__close_remote_server(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { - + Temp scratch = scratch_begin(0, 0); + D_Entity *machine = d_entity_from_handle(msg->entity); + D_EventList evts = {0}; + D_Event *evt = d_event_list_push(scratch.arena, &evts); + evt->kind = D_EventKind_ServerDisconnect; + evt->entity_id = machine->handle.server_id; + d_c2u_push_events(&evts); + scratch_end(scratch); } internal void @@ -4127,7 +4180,7 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) // rjf: create process handle d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; - process = d_dump_handle_make(D_DemonID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); + process = d_dump_handle_make(D_ServerID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); // rjf: gather threads dump_threads_count = threads_count; @@ -4144,7 +4197,7 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; MDMP_Thread *thread = &threads[idx]; - D_Handle thread_handle = d_dump_handle_make(D_DemonID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); + D_Handle thread_handle = d_dump_handle_make(D_ServerID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); dump_threads[idx].thread_handle = thread_handle; dump_threads[idx].id = thread->id; dump_threads[idx].context_foff = thread->thread_context.foff; @@ -4249,7 +4302,7 @@ d_ctrl_thread__open_crash_dump(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) // rjf: store d_ctrl_state->ctrl_thread_dump_handle_id_gen += 1; - dump_modules[idx].module_handle = d_dump_handle_make(D_DemonID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); + dump_modules[idx].module_handle = d_dump_handle_make(D_ServerID_LocalHost, d_ctrl_state->ctrl_thread_dump_handle_id_gen); dump_modules[idx].vaddr_range = vaddr_range; dump_modules[idx].path = str8_copy(arena, module_name); dump_modules[idx].file = module_file; @@ -4605,7 +4658,7 @@ d_ctrl_thread__kill_all(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) default:{}break; case DMN_EventKind_CreateProcess: { - D_Entity *new_process = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->process)); + D_Entity *new_process = d_entity_from_handle(d_handle_from_dmn(D_ServerID_LocalHost, event->process)); Task *t = push_array(scratch.arena, Task, 1); t->process = new_process; DLLPushBack(first_task, last_task, t); @@ -4826,7 +4879,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) node != 0; node = node->next) { - D_Handle thread = d_handle_from_dmn(D_DemonID_LocalHost, node->v); + D_Handle thread = d_handle_from_dmn(D_ServerID_LocalHost, node->v); U64 thread_pre_rip = d_ip_from_thread(thread); U64 thread_post_rip = thread_pre_rip; for(B32 done = 0; !done;) @@ -4845,7 +4898,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { default:{}break; case DMN_EventKind_ExitThread: - if(d_handle_match(d_handle_from_dmn(D_DemonID_LocalHost, event->thread), thread)) + if(d_handle_match(d_handle_from_dmn(D_ServerID_LocalHost, event->thread), thread)) { stop_cause = D_EventCause_Error; goto stop; @@ -5008,7 +5061,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_EvalScope *eval_scope = d_ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, &d_entity_nil); { DMN_TrapChunkList new_traps = {0}; - d_ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, eval_scope, d_handle_from_dmn(D_DemonID_LocalHost, event->process), &msg->user_bps, &new_traps); + d_ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, eval_scope, d_handle_from_dmn(D_ServerID_LocalHost, event->process), &msg->user_bps, &new_traps); log_infof("step_rule: create_process -> resolve traps\n"); log_infof("new_traps:\n{\n"); for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) @@ -5027,11 +5080,11 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) }break; case DMN_EventKind_LoadModule: { - D_Entity *thread = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->thread)); + D_Entity *thread = d_entity_from_handle(d_handle_from_dmn(D_ServerID_LocalHost, event->thread)); D_EvalScope *eval_scope = d_ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, thread); { DMN_TrapChunkList new_traps = {0}; - d_ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, eval_scope, d_handle_from_dmn(D_DemonID_LocalHost, event->process), d_handle_from_dmn(D_DemonID_LocalHost, event->module), &msg->user_bps, &new_traps); + d_ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, eval_scope, d_handle_from_dmn(D_ServerID_LocalHost, event->process), d_handle_from_dmn(D_ServerID_LocalHost, event->module), &msg->user_bps, &new_traps); log_infof("step_rule: load_module -> resolve traps\n"); log_infof("new_traps:\n{\n"); for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) @@ -5052,7 +5105,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { D_Entity *bp = &d_entity_nil; { - D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->process)); + D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_ServerID_LocalHost, event->process)); for(D_Entity *child = process->first; child != &d_entity_nil; child = child->next) { if(child->kind == D_EntityKind_Breakpoint && @@ -5084,7 +5137,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) Access *access = access_open(); //- rjf: unpack process/module info - D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->process)); + D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_ServerID_LocalHost, event->process)); D_Entity *module = d_entity_child_from_kind(process, D_EntityKind_Module); U64 module_base_vaddr = module->vaddr_range.min; DI_Key dbgi_key = d_dbgi_key_from_module(module); @@ -5310,8 +5363,8 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) ////////////////////////// //- rjf: unpack info about thread attached to event // - D_Entity *thread = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->thread)); - D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_DemonID_LocalHost, event->process)); + D_Entity *thread = d_entity_from_handle(d_handle_from_dmn(D_ServerID_LocalHost, event->thread)); + D_Entity *process = d_entity_from_handle(d_handle_from_dmn(D_ServerID_LocalHost, event->process)); Arch arch = thread->arch; U64 thread_rip_vaddr = d_ip_from_thread(thread->handle); D_Entity *module = d_module_from_process_vaddr(process, thread_rip_vaddr); @@ -5499,7 +5552,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_EventCause cond_bp_single_step_stop_cause = D_EventCause_Null; if(hit_conditional_bp_but_filtered) LogInfoNamedBlockF("conditional_bp_hit_single_step") { - D_Handle thread = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + D_Handle thread = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); U64 thread_pre_rip = d_ip_from_thread(thread); U64 thread_post_rip = thread_pre_rip; for(B32 single_step_done = 0; !single_step_done;) @@ -5529,7 +5582,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) }break; case DMN_EventKind_SingleStep: { - single_step_done = d_handle_match(d_handle_from_dmn(D_DemonID_LocalHost, event->thread), thread); + single_step_done = d_handle_match(d_handle_from_dmn(D_ServerID_LocalHost, event->thread), thread); cond_bp_single_step_stop_cause = d_event_cause_from_dmn_event_kind(event->kind); }break; } @@ -5690,7 +5743,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_EventCause step_past_trap_net_stop_cause = D_EventCause_Null; if(step_past_trap_net) LogInfoNamedBlockF("trap_net__single_step_past_trap_net") { - D_Handle thread = d_handle_from_dmn(D_DemonID_LocalHost, event->thread); + D_Handle thread = d_handle_from_dmn(D_ServerID_LocalHost, event->thread); U64 thread_pre_rip = d_ip_from_thread(thread); U64 thread_post_rip = thread_pre_rip; for(B32 single_step_done = 0; single_step_done == 0;) @@ -5719,7 +5772,7 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) }break; case DMN_EventKind_SingleStep: { - single_step_done = d_handle_match(d_handle_from_dmn(D_DemonID_LocalHost, event->thread), thread); + single_step_done = d_handle_match(d_handle_from_dmn(D_ServerID_LocalHost, event->thread), thread); step_past_trap_net_stop_cause = d_event_cause_from_dmn_event_kind(event->kind); }break; } @@ -5779,8 +5832,8 @@ d_ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) D_Event *event = d_event_list_push(scratch.arena, &evts); event->kind = D_EventKind_Stopped; event->cause = stop_cause; - event->entity = d_handle_from_dmn(D_DemonID_LocalHost, stop_event->thread); - event->parent = d_handle_from_dmn(D_DemonID_LocalHost, stop_event->process); + event->entity = d_handle_from_dmn(D_ServerID_LocalHost, stop_event->thread); + event->parent = d_handle_from_dmn(D_ServerID_LocalHost, stop_event->process); event->exception_code = stop_event->code; event->exception_kind = d_exception_kind_from_dmn(stop_event->exception_kind); event->vaddr_rng = r1u64(stop_event->address, stop_event->address); @@ -5863,8 +5916,8 @@ d_ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) event->cause = stop_cause; if(stop_event != 0) { - event->entity = d_handle_from_dmn(D_DemonID_LocalHost, stop_event->thread); - event->parent = d_handle_from_dmn(D_DemonID_LocalHost, stop_event->process); + event->entity = d_handle_from_dmn(D_ServerID_LocalHost, stop_event->thread); + event->parent = d_handle_from_dmn(D_ServerID_LocalHost, stop_event->process); event->exception_code = stop_event->code; event->exception_kind = d_exception_kind_from_dmn(stop_event->exception_kind); event->vaddr_rng = r1u64(stop_event->address, stop_event->address); diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index 15c30306..cb6241d5 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -321,6 +321,7 @@ struct D_CtrlState { Arena *arena; D_WakeupFunctionType *wakeup_hook; + SOCK_Session sock_session; // rjf: name -> register hash tables for eval E_String2NumMap arch_string2reg_tables[Arch_COUNT]; @@ -371,6 +372,7 @@ struct D_CtrlState U64 exception_code_filters[(D_ExceptionCodeKind_COUNT+63)/64]; B32 auto_download_debug_info; U64 process_counter; + D_ServerID server_id_gen; Arena *dbg_dir_arena; D_DbgDirNode *dbg_dir_root; U64 module_req_cache_slots_count; @@ -426,8 +428,8 @@ internal D_HandleArray d_handle_array_from_list(Arena *arena, D_HandleList *src internal String8 d_string_from_handle(Arena *arena, D_Handle handle); internal D_Handle d_handle_from_string(String8 string); internal DMN_Handle d_dmn_from_handle(D_Handle handle); -internal D_Handle d_handle_from_dmn(D_DemonID demon_id, DMN_Handle handle); -internal D_Handle d_dump_handle_make(D_DemonID demon_id, U64 id); +internal D_Handle d_handle_from_dmn(D_ServerID server_id, DMN_Handle handle); +internal D_Handle d_dump_handle_make(D_ServerID server_id, U64 id); //////////////////////////////// //~ rjf: Trap Type Functions diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index 2a83fd5b..c0314859 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -2104,13 +2104,13 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P }break; case D_CmdKind_FreezeLocalServer: { - D_DemonID demon_id = D_DemonID_LocalHost; - d_cmd(D_CmdKind_FreezeServer, .entity = d_handle_from_dmn(demon_id, dmn_handle_zero())); + D_ServerID server_id = D_ServerID_LocalHost; + d_cmd(D_CmdKind_FreezeServer, .entity = d_handle_from_dmn(server_id, dmn_handle_zero())); }break; case D_CmdKind_ThawLocalServer: { - D_DemonID demon_id = D_DemonID_LocalHost; - d_cmd(D_CmdKind_ThawServer, .entity = d_handle_from_dmn(demon_id, dmn_handle_zero())); + D_ServerID server_id = D_ServerID_LocalHost; + d_cmd(D_CmdKind_ThawServer, .entity = d_handle_from_dmn(server_id, dmn_handle_zero())); }break; case D_CmdKind_FreezeEntity: case D_CmdKind_ThawEntity: diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index 953e41b8..3c02a872 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -265,7 +265,11 @@ typedef enum D_EventKind D_EventKind_MemDecommit, D_EventKind_MemRelease, - //- rjf: remote connections + //- rjf: remote server connections + D_EventKind_ServerConnect, + D_EventKind_ServerDisconnect, + + //- rjf: remote controllers D_EventKind_RemoteControllerConnect, D_EventKind_RemoteControllerDisconnect, diff --git a/src/dbg_server/dbg_server.c b/src/dbg_server/dbg_server.c index 1347addc..a6250862 100644 --- a/src/dbg_server/dbg_server.c +++ b/src/dbg_server/dbg_server.c @@ -5,30 +5,41 @@ //~ rjf: Message Parsing internal DS_Msg -ds_msg_from_serialized(Arena *arena, String8 data) +ds_msg_from_packet(Arena *arena, String8 data) { + //- rjf: read packet header + DS_PacketHeader header = {0}; + { + str8_deserial_read_struct(data, 0, &header); + } + + //- rjf: get message payload part + String8 payload = str8_substr(data, r1u64(sizeof(header), sizeof(header)+header.payload_size)); + + //- rjf: read message DS_Msg msg = {0}; + if(header.magic == DS_PACKET_MAGIC && header.kind == DS_PacketKind_Msg && header.version == DS_PACKET_VERSION) { U64 off = 0; // rjf: read flat parts - off += str8_deserial_read_struct(data, off, &msg.kind); - off += str8_deserial_read_struct(data, off, &msg.flags); - off += str8_deserial_read_struct(data, off, &msg.msg_id); - off += str8_deserial_read_struct(data, off, &msg.vaddr_range); - off += str8_deserial_read_struct(data, off, &msg.access_flags); - off += str8_deserial_read_struct(data, off, &msg.entity); - off += str8_deserial_read_struct(data, off, &msg.pid); + off += str8_deserial_read_struct(payload, off, &msg.kind); + off += str8_deserial_read_struct(payload, off, &msg.flags); + off += str8_deserial_read_struct(payload, off, &msg.msg_id); + off += str8_deserial_read_struct(payload, off, &msg.vaddr_range); + off += str8_deserial_read_struct(payload, off, &msg.access_flags); + off += str8_deserial_read_struct(payload, off, &msg.entity); + off += str8_deserial_read_struct(payload, off, &msg.pid); // rjf: read command line strings { U64 cmd_line_string_count = 0; - off += str8_deserial_read_struct(data, off, &cmd_line_string_count); + off += str8_deserial_read_struct(payload, off, &cmd_line_string_count); for EachIndex(idx, cmd_line_string_count) { String8 string = {0}; - off += str8_deserial_read_struct(data, off, &string.size); - string = str8_substr(data, r1u64(off, off+string.size)); + off += str8_deserial_read_struct(payload, off, &string.size); + string = str8_substr(payload, r1u64(off, off+string.size)); str8_list_push(arena, &msg.command_line, string); } } @@ -36,20 +47,20 @@ ds_msg_from_serialized(Arena *arena, String8 data) // rjf: read path { String8 string = {0}; - off += str8_deserial_read_struct(data, off, &string.size); - string = str8_substr(data, r1u64(off, off+string.size)); + off += str8_deserial_read_struct(payload, off, &string.size); + string = str8_substr(payload, r1u64(off, off+string.size)); msg.path = string; } // rjf: read environment strings { U64 env_string_count = 0; - off += str8_deserial_read_struct(data, off, &env_string_count); + off += str8_deserial_read_struct(payload, off, &env_string_count); for EachIndex(idx, env_string_count) { String8 string = {0}; - off += str8_deserial_read_struct(data, off, &string.size); - string = str8_substr(data, r1u64(off, off+string.size)); + off += str8_deserial_read_struct(payload, off, &string.size); + string = str8_substr(payload, r1u64(off, off+string.size)); str8_list_push(arena, &msg.env, string); } } @@ -57,15 +68,15 @@ ds_msg_from_serialized(Arena *arena, String8 data) // rjf: read traps { U64 trap_count = 0; - off += str8_deserial_read_struct(data, off, &trap_count); + off += str8_deserial_read_struct(payload, off, &trap_count); for EachIndex(idx, trap_count) { DMN_Trap trap = {0}; - off += str8_deserial_read_struct(data, off, &trap.process); - off += str8_deserial_read_struct(data, off, &trap.vaddr); - off += str8_deserial_read_struct(data, off, &trap.id); - off += str8_deserial_read_struct(data, off, &trap.flags); - off += str8_deserial_read_struct(data, off, &trap.size); + off += str8_deserial_read_struct(payload, off, &trap.process); + off += str8_deserial_read_struct(payload, off, &trap.vaddr); + off += str8_deserial_read_struct(payload, off, &trap.id); + off += str8_deserial_read_struct(payload, off, &trap.flags); + off += str8_deserial_read_struct(payload, off, &trap.size); dmn_trap_chunk_list_push(arena, &msg.traps, trap_count, &trap); } } @@ -74,12 +85,14 @@ ds_msg_from_serialized(Arena *arena, String8 data) } internal String8 -ds_serialized_from_msg(Arena *arena, DS_Msg *msg) +ds_packet_from_msg(Arena *arena, DS_Msg *msg) { Temp scratch = scratch_begin(&arena, 1); String8List srl = {0}; str8_serial_begin(scratch.arena, &srl); { + // NOTE(rjf): (header wrote to beginning of `srl` at end of path) + // rjf: write flat parts str8_serial_push_struct(scratch.arena, &srl, &msg->kind); str8_serial_push_struct(scratch.arena, &srl, &msg->flags); @@ -130,6 +143,16 @@ ds_serialized_from_msg(Arena *arena, DS_Msg *msg) } } } + + // rjf: push packet header (to front) + DS_PacketHeader header = + { + .magic = DS_PACKET_MAGIC, + .kind = DS_PacketKind_Msg, + .version = DS_PACKET_VERSION, + .payload_size = (U32)srl.total_size, + }; + str8_list_push_front(scratch.arena, &srl, str8_copy(scratch.arena, str8_struct(&header))); } String8 result = str8_serial_end(arena, &srl); scratch_end(scratch); @@ -137,33 +160,46 @@ ds_serialized_from_msg(Arena *arena, DS_Msg *msg) } internal DS_Response -ds_response_from_serialized(Arena *arena, String8 data) +ds_response_from_packet(Arena *arena, String8 data) { + //- rjf: read packet header + DS_PacketHeader header = {0}; + { + str8_deserial_read_struct(data, 0, &header); + } + + //- rjf: get message payload part + String8 payload = str8_substr(data, r1u64(sizeof(header), sizeof(header)+header.payload_size)); + + //- rjf: read response DS_Response result = {0}; + if(header.magic == DS_PACKET_MAGIC && header.kind == DS_PacketKind_Response && header.version == DS_PACKET_VERSION) { U64 off = 0; // rjf: read flat parts - off += str8_deserial_read_struct(data, off, &result.msg_id); - off += str8_deserial_read_struct(data, off, &result.entity); - off += str8_deserial_read_struct(data, off, &result.pid); - off += str8_deserial_read_struct(data, off, &result.good); + off += str8_deserial_read_struct(payload, off, &result.msg_id); + off += str8_deserial_read_struct(payload, off, &result.entity); + off += str8_deserial_read_struct(payload, off, &result.pid); + off += str8_deserial_read_struct(payload, off, &result.good); // rjf: read data payload - off += str8_deserial_read_struct(data, off, &result.data.size); + off += str8_deserial_read_struct(payload, off, &result.data.size); result.data.str = push_array(arena, U8, result.data.size); - off += str8_deserial_read(data, off, result.data.str, result.data.size, result.data.size); + off += str8_deserial_read(payload, off, result.data.str, result.data.size, result.data.size); } return result; } internal String8 -ds_serialized_from_response(Arena *arena, DS_Response *response) +ds_packet_from_response(Arena *arena, DS_Response *response) { Temp scratch = scratch_begin(&arena, 1); String8List srl = {0}; str8_serial_begin(scratch.arena, &srl); { + // NOTE(rjf): (header wrote to beginning of `srl` at end of path) + // rjf: write flat parts str8_serial_push_struct(scratch.arena, &srl, &response->msg_id); str8_serial_push_struct(scratch.arena, &srl, &response->entity); @@ -173,6 +209,16 @@ ds_serialized_from_response(Arena *arena, DS_Response *response) // rjf: write data payload str8_serial_push_struct(scratch.arena, &srl, &response->data.size); str8_serial_push_string(scratch.arena, &srl, response->data); + + // rjf: push packet header (to front) + DS_PacketHeader header = + { + .magic = DS_PACKET_MAGIC, + .kind = DS_PacketKind_Response, + .version = DS_PACKET_VERSION, + .payload_size = (U32)srl.total_size, + }; + str8_list_push_front(scratch.arena, &srl, str8_copy(scratch.arena, str8_struct(&header))); } String8 result = str8_serial_end(arena, &srl); scratch_end(scratch); @@ -180,44 +226,55 @@ ds_serialized_from_response(Arena *arena, DS_Response *response) } internal DMN_EventList -ds_dmn_events_from_serialized(Arena *arena, String8 data) +ds_dmn_events_from_packet(Arena *arena, String8 data) { + //- rjf: read packet header + DS_PacketHeader header = {0}; + { + str8_deserial_read_struct(data, 0, &header); + } + + //- rjf: get message payload part + String8 payload = str8_substr(data, r1u64(sizeof(header), sizeof(header)+header.payload_size)); + + //- rjf: read events DMN_EventList evts = {0}; + if(header.magic == DS_PACKET_MAGIC && header.kind == DS_PacketKind_Event && header.version == DS_PACKET_VERSION) { U64 off = 0; U64 demon_event_count = 0; - off += str8_deserial_read_struct(data, off, &demon_event_count); + off += str8_deserial_read_struct(payload, off, &demon_event_count); for EachIndex(idx, demon_event_count) { DMN_Event *evt = dmn_event_list_push(arena, &evts); // rjf: read flat parts - off += str8_deserial_read_struct(data, off, &evt->kind); - off += str8_deserial_read_struct(data, off, &evt->error_kind); - off += str8_deserial_read_struct(data, off, &evt->memory_kind); - off += str8_deserial_read_struct(data, off, &evt->exception_kind); - off += str8_deserial_read_struct(data, off, &evt->process); - off += str8_deserial_read_struct(data, off, &evt->thread); - off += str8_deserial_read_struct(data, off, &evt->module); - off += str8_deserial_read_struct(data, off, &evt->arch); - off += str8_deserial_read_struct(data, off, &evt->address); - off += str8_deserial_read_struct(data, off, &evt->size); - off += str8_deserial_read_struct(data, off, &evt->code); - off += str8_deserial_read_struct(data, off, &evt->flags); - off += str8_deserial_read_struct(data, off, &evt->instruction_pointer); - off += str8_deserial_read_struct(data, off, &evt->stack_pointer); - off += str8_deserial_read_struct(data, off, &evt->user_data); - off += str8_deserial_read_struct(data, off, &evt->exception_repeated); - off += str8_deserial_read_struct(data, off, &evt->tls_root_vaddr); + off += str8_deserial_read_struct(payload, off, &evt->kind); + off += str8_deserial_read_struct(payload, off, &evt->error_kind); + off += str8_deserial_read_struct(payload, off, &evt->memory_kind); + off += str8_deserial_read_struct(payload, off, &evt->exception_kind); + off += str8_deserial_read_struct(payload, off, &evt->process); + off += str8_deserial_read_struct(payload, off, &evt->thread); + off += str8_deserial_read_struct(payload, off, &evt->module); + off += str8_deserial_read_struct(payload, off, &evt->arch); + off += str8_deserial_read_struct(payload, off, &evt->address); + off += str8_deserial_read_struct(payload, off, &evt->size); + off += str8_deserial_read_struct(payload, off, &evt->code); + off += str8_deserial_read_struct(payload, off, &evt->flags); + off += str8_deserial_read_struct(payload, off, &evt->instruction_pointer); + off += str8_deserial_read_struct(payload, off, &evt->stack_pointer); + off += str8_deserial_read_struct(payload, off, &evt->user_data); + off += str8_deserial_read_struct(payload, off, &evt->exception_repeated); + off += str8_deserial_read_struct(payload, off, &evt->tls_root_vaddr); // rjf: read string - off += str8_deserial_read_struct(data, off, &evt->string.size); + off += str8_deserial_read_struct(payload, off, &evt->string.size); evt->string.str = push_array(arena, U8, evt->string.size); - off += str8_deserial_read(data, off, evt->string.str, evt->string.size, 1); + off += str8_deserial_read(payload, off, evt->string.str, evt->string.size, 1); // rjf: read module info B8 has_module_info = 0; - off += str8_deserial_read_struct(data, off, &has_module_info); + off += str8_deserial_read_struct(payload, off, &has_module_info); if(!has_module_info) { evt->module_info = &dmn_module_info_nil; @@ -227,26 +284,26 @@ ds_dmn_events_from_serialized(Arena *arena, String8 data) evt->module_info = push_array(arena, DMN_ModuleInfo, 1); // rjf: read flat parts - off += str8_deserial_read_struct(data, off, &evt->module_info->arch); - off += str8_deserial_read_struct(data, off, &evt->module_info->vsize); - off += str8_deserial_read_struct(data, off, &evt->module_info->entry_point_voff); - off += str8_deserial_read_struct(data, off, &evt->module_info->debug_info_guid); - off += str8_deserial_read_struct(data, off, &evt->module_info->debug_info_timestamp); - off += str8_deserial_read_struct(data, off, &evt->module_info->debug_info_age); - off += str8_deserial_read_struct(data, off, &evt->module_info->tls_index); - off += str8_deserial_read_struct(data, off, &evt->module_info->tls_offset); - off += str8_deserial_read_struct(data, off, &evt->module_info->pe_intel_pdatas_vaddr_range); - off += str8_deserial_read_struct(data, off, &evt->module_info->eh_frame_header_vaddr_range); - off += str8_deserial_read_struct(data, off, &evt->module_info->raddbg_info_voff_range); - off += str8_deserial_read_struct(data, off, &evt->module_info->raddbg_is_attached_marker_voff); + off += str8_deserial_read_struct(payload, off, &evt->module_info->arch); + off += str8_deserial_read_struct(payload, off, &evt->module_info->vsize); + off += str8_deserial_read_struct(payload, off, &evt->module_info->entry_point_voff); + off += str8_deserial_read_struct(payload, off, &evt->module_info->debug_info_guid); + off += str8_deserial_read_struct(payload, off, &evt->module_info->debug_info_timestamp); + off += str8_deserial_read_struct(payload, off, &evt->module_info->debug_info_age); + off += str8_deserial_read_struct(payload, off, &evt->module_info->tls_index); + off += str8_deserial_read_struct(payload, off, &evt->module_info->tls_offset); + off += str8_deserial_read_struct(payload, off, &evt->module_info->pe_intel_pdatas_vaddr_range); + off += str8_deserial_read_struct(payload, off, &evt->module_info->eh_frame_header_vaddr_range); + off += str8_deserial_read_struct(payload, off, &evt->module_info->raddbg_info_voff_range); + off += str8_deserial_read_struct(payload, off, &evt->module_info->raddbg_is_attached_marker_voff); // rjf: read paths - off += str8_deserial_read_struct(data, off, &evt->module_info->module_path.size); + off += str8_deserial_read_struct(payload, off, &evt->module_info->module_path.size); evt->module_info->module_path.str = push_array(arena, U8, evt->module_info->module_path.size); - off += str8_deserial_read(data, off, evt->module_info->module_path.str, evt->module_info->module_path.size, 1); - off += str8_deserial_read_struct(data, off, &evt->module_info->debug_info_path.size); + off += str8_deserial_read(payload, off, evt->module_info->module_path.str, evt->module_info->module_path.size, 1); + off += str8_deserial_read_struct(payload, off, &evt->module_info->debug_info_path.size); evt->module_info->debug_info_path.str = push_array(arena, U8, evt->module_info->debug_info_path.size); - off += str8_deserial_read(data, off, evt->module_info->debug_info_path.str, evt->module_info->debug_info_path.size, 1); + off += str8_deserial_read(payload, off, evt->module_info->debug_info_path.str, evt->module_info->debug_info_path.size, 1); } } } @@ -254,12 +311,14 @@ ds_dmn_events_from_serialized(Arena *arena, String8 data) } internal String8 -ds_serialized_from_dmn_events(Arena *arena, DMN_EventList evts) +ds_packet_from_dmn_events(Arena *arena, DMN_EventList evts) { Temp scratch = scratch_begin(&arena, 1); String8List srl = {0}; str8_serial_begin(scratch.arena, &srl); { + // NOTE(rjf): (header wrote to beginning of `srl` at end of path) + // rjf: write events str8_serial_push_struct(scratch.arena, &srl, &evts.count); for EachNode(n, DMN_EventNode, evts.first) @@ -313,6 +372,16 @@ ds_serialized_from_dmn_events(Arena *arena, DMN_EventList evts) str8_serial_push_string(scratch.arena, &srl, n->v.module_info->debug_info_path); } } + + // rjf: push packet header (to front) + DS_PacketHeader header = + { + .magic = DS_PACKET_MAGIC, + .kind = DS_PacketKind_Event, + .version = DS_PACKET_VERSION, + .payload_size = (U32)srl.total_size, + }; + str8_list_push_front(scratch.arena, &srl, str8_copy(scratch.arena, str8_struct(&header))); } String8 result = str8_serial_end(arena, &srl); scratch_end(scratch); diff --git a/src/dbg_server/dbg_server.h b/src/dbg_server/dbg_server.h index 7ae209ce..1c929482 100644 --- a/src/dbg_server/dbg_server.h +++ b/src/dbg_server/dbg_server.h @@ -34,6 +34,9 @@ typedef enum DS_MsgKind { DS_MsgKind_Null, + //- rjf: connection handshake + DS_MsgKind_Handshake, + //- rjf: debuggee attaching/detaching ops DS_MsgKind_Launch, DS_MsgKind_Attach, @@ -123,11 +126,11 @@ struct DS_ResponseNode //////////////////////////////// //~ rjf: Message/Response Parsing -internal DS_Msg ds_msg_from_serialized(Arena *arena, String8 data); -internal String8 ds_serialized_from_msg(Arena *arena, DS_Msg *msg); -internal DS_Response ds_response_from_serialized(Arena *arena, String8 data); -internal String8 ds_serialized_from_response(Arena *arena, DS_Response *response); -internal DMN_EventList ds_dmn_events_from_serialized(Arena *arena, String8 data); +internal DS_Msg ds_msg_from_packet(Arena *arena, String8 data); +internal String8 ds_packet_from_msg(Arena *arena, DS_Msg *msg); +internal DS_Response ds_response_from_packet(Arena *arena, String8 data); +internal String8 ds_packet_from_response(Arena *arena, DS_Response *response); +internal DMN_EventList ds_dmn_events_from_packet(Arena *arena, String8 data); internal String8 ds_serialized_from_dmn_events(Arena *arena, DMN_EventList evts); #endif // DBG_SERVER_H diff --git a/src/linux/socket/linux_socket.c b/src/linux/socket/linux_socket.c index ed792d18..22d1d65b 100644 --- a/src/linux/socket/linux_socket.c +++ b/src/linux/socket/linux_socket.c @@ -348,6 +348,7 @@ sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) session->wakeup_hook = wakeup_hook; //- rjf: set up listener + if(listener_port != 0) { session->tcp_listen_socket = socket(AF_INET, SOCK_STREAM, 0); U32 ipv6only = 0; diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 2234ff53..047510ad 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -711,7 +711,7 @@ rd_ctrl_entity_from_eval_space(E_Space space) space.kind == RD_EvalSpaceKind_MetaUnattachedProcess) { D_Handle handle; - handle.demon_id = (U32)((space.u64s[0] & 0xffffffff00000000ull) >> 32); + handle.server_id = (U32)((space.u64s[0] & 0xffffffff00000000ull) >> 32); handle.controller_kind = (U32)((space.u64s[0] & 0x00000000ffffffffull) >> 0); handle.entity_id = space.u64s[1]; entity = d_entity_from_handle(handle); @@ -726,7 +726,7 @@ rd_eval_space_from_ctrl_entity(D_Entity *entity, E_SpaceKind kind) if(entity != &d_entity_nil) { space = e_space_make(kind); - space.u64s[0] = (((U64)entity->handle.demon_id) << 32) | (((U64)entity->handle.controller_kind) << 0); + space.u64s[0] = (((U64)entity->handle.server_id) << 32) | (((U64)entity->handle.controller_kind) << 0); space.u64s[1] = entity->handle.entity_id; } return space; @@ -2828,7 +2828,7 @@ rd_view_ui(Rng2F32 rect) CFG_Node *locked = cfg_node_child_from_string_or_alloc(rd_state->cfg, cfg, s("locked")); cfg_node_release_all_children(rd_state->cfg, locked); CFG_Node *mid = cfg_node_new(rd_state->cfg, locked, s("mid")); - cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.demon_id); + cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.server_id); CFG_Node *pid = cfg_node_new(rd_state->cfg, locked, s("pid")); cfg_node_newf(rd_state->cfg, pid, "%I64u", entity->id); CFG_Node *addr = cfg_node_new(rd_state->cfg, locked, s("addr")); @@ -4631,7 +4631,7 @@ rd_view_ui(Rng2F32 rect) CFG_Node *locked = cfg_node_child_from_string_or_alloc(rd_state->cfg, cfg, s("locked")); cfg_node_release_all_children(rd_state->cfg, locked); CFG_Node *mid = cfg_node_new(rd_state->cfg, locked, s("mid")); - cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.demon_id); + cfg_node_newf(rd_state->cfg, mid, "%I64u", entity->handle.server_id); CFG_Node *pid = cfg_node_new(rd_state->cfg, locked, s("pid")); cfg_node_newf(rd_state->cfg, pid, "%I64u", entity->id); CFG_Node *addr = cfg_node_new(rd_state->cfg, locked, s("addr")); @@ -6367,7 +6367,7 @@ rd_window_frame(void) ID(panel); ID(view); #undef ID -#define Handle(name) ui_labelf("%s: [0x%x, 0x%x, 0x%I64x]", #name, (regs->name).demon_id, (regs->name).controller_kind, (regs->name).entity_id) +#define Handle(name) ui_labelf("%s: [0x%x, 0x%x, 0x%I64x]", #name, (regs->name).server_id, (regs->name).controller_kind, (regs->name).entity_id) Handle(server); Handle(process); Handle(module); @@ -12863,7 +12863,7 @@ rd_frame(void) { e_string2expr_map_insert(scratch.arena, macro_map, entity->string, expr); } - if(kind == D_EntityKind_Server && entity->handle.demon_id == D_DemonID_LocalHost) + if(kind == D_EntityKind_Server && entity->handle.server_id == D_ServerID_LocalHost) { e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("local_server"), expr); } diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index a3f69200..d5af608e 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -463,6 +463,7 @@ #include "disasm/disasm_inc.h" #include "stap/stap_parse.h" #include "demon/demon_inc.h" +#include "dbg_server/dbg_server.h" #include "eval/eval_inc.h" #include "eval2/eval2.h" #include "dbg_engine/dbg_engine_inc.h" @@ -526,6 +527,7 @@ #include "disasm/disasm_inc.c" #include "stap/stap_parse.c" #include "demon/demon_inc.c" +#include "dbg_server/dbg_server.c" #include "eval/eval_inc.c" #include "eval2/eval2.c" #include "dbg_engine/dbg_engine_inc.c" @@ -892,25 +894,25 @@ entry_point(CmdLine *cmd_line) case ExecMode_Help: { sh_message(0, - str8_lit("The RAD Debugger - Help"), - str8_lit("The following options may be used when starting the RAD Debugger from the command line:\n\n" - "--user:\n" - "Use to specify the location of a user file which should be used. User files are used to store settings for users, including window and panel setups, path mapping, and visual settings. If this file does not exist, it will be created as necessary. This file will be autosaved as user-related changes are made.\n\n" - "--project:\n" - "Use to specify the location of a project file which should be used. Project files are used to store settings for users and projects. If this file does not exist, it will be created as necessary. This file will be autosaved as project-related changes are made.\n\n" - "--auto_step\n" - "This will step into all active targets after the debugger initially starts.\n\n" - "--auto_run\n" - "This will run all active targets after the debugger initially starts.\n\n" - "--quit_after_success (or -q)\n" - "This will close the debugger automatically after all processes exit, if they all exited successfully (with code 0), and ran with no interruptions.\n\n" - "--ipc \n" - "This will launch the debugger in the non-graphical IPC mode, which is used to communicate with another running instance of the debugger. The debugger instance will launch, send the specified command, then immediately terminate. This may be used by editors or other programs to control the debugger.\n\n" - "--gen_crash_dump\n" - "Generate mini dump on crash.\n\n" - "--logs:\n" - "Overrides default path to the folder with logs.\n" - )); + s("The RAD Debugger - Help"), + s("The following options may be used when starting the RAD Debugger from the command line:\n\n" + "--user:\n" + "Use to specify the location of a user file which should be used. User files are used to store settings for users, including window and panel setups, path mapping, and visual settings. If this file does not exist, it will be created as necessary. This file will be autosaved as user-related changes are made.\n\n" + "--project:\n" + "Use to specify the location of a project file which should be used. Project files are used to store settings for users and projects. If this file does not exist, it will be created as necessary. This file will be autosaved as project-related changes are made.\n\n" + "--auto_step\n" + "This will step into all active targets after the debugger initially starts.\n\n" + "--auto_run\n" + "This will run all active targets after the debugger initially starts.\n\n" + "--quit_after_success (or -q)\n" + "This will close the debugger automatically after all processes exit, if they all exited successfully (with code 0), and ran with no interruptions.\n\n" + "--ipc \n" + "This will launch the debugger in the non-graphical IPC mode, which is used to communicate with another running instance of the debugger. The debugger instance will launch, send the specified command, then immediately terminate. This may be used by editors or other programs to control the debugger.\n\n" + "--gen_crash_dump\n" + "Generate mini dump on crash.\n\n" + "--logs:\n" + "Overrides default path to the folder with logs.\n" + )); }break; } diff --git a/src/raddbg_server/raddbg_server_main.c b/src/raddbg_server/raddbg_server_main.c index 194f1145..4374eee2 100644 --- a/src/raddbg_server/raddbg_server_main.c +++ b/src/raddbg_server/raddbg_server_main.c @@ -135,7 +135,7 @@ dmn_thread__entry_point(void *p) for(RDS_Msg *msg_n = msg_first; msg_n != 0; msg_n = msg_n->next) { //- rjf: unpack message - DS_Msg msg = ds_msg_from_serialized(scratch.arena, msg_n->msg_raw); + DS_Msg msg = ds_msg_from_packet(scratch.arena, msg_n->msg_raw); //- rjf: do action, get response DS_Response response = {0}; @@ -198,7 +198,7 @@ dmn_thread__entry_point(void *p) //- rjf: pack response & send back to the originating endpoint of the message { - String8 data = ds_serialized_from_response(scratch.arena, &response); + String8 data = ds_packet_from_response(scratch.arena, &response); sock_send(rds_state->sock_session, SOCK_Protocol_TCP, msg_n->endpoint, data, max_U64); } @@ -317,7 +317,7 @@ entry_point(CmdLine *cmd_line) str8_deserial_read_struct(msg_raw, 2, &peek_kind); if(peek_kind < DS_MsgKind_Launch || DS_MsgKind_SingleStep < peek_kind) { - DS_Msg msg = ds_msg_from_serialized(scratch.arena, data); + DS_Msg msg = ds_msg_from_packet(scratch.arena, data); DS_Response response = {0}; switch(msg.kind) { diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index 169db89a..01c23e16 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -368,6 +368,7 @@ sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) session->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); //- rjf: set up listener(s) + if(listener_port != 0) { session->tcp_listen_socket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); DWORD ipv6only = 0; From 4a9b10bb7b54a25ce4013c763835ab439543132f Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 12 Oct 2026 08:48:06 -0700 Subject: [PATCH 240/251] hidden mode commands/state, fix torture build --- src/raddbg/generated/raddbg.meta.c | 8 ++++++-- src/raddbg/generated/raddbg.meta.h | 4 +++- src/raddbg/raddbg.mdesk | 2 ++ src/raddbg/raddbg_core.h | 3 +++ src/torture/torture_main.c | 2 ++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index f7045efc..6c44182a 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -62,7 +62,7 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[373] = +RD_VocabInfo rd_vocab_info_table[375] = { {str8_lit_comp("type_view"), str8_lit_comp("type_views"), str8_lit_comp("Type View"), str8_lit_comp("Type Views"), RD_IconKind_Binoculars}, {str8_lit_comp("file_path_map"), str8_lit_comp("file_path_maps"), str8_lit_comp("File Path Map"), str8_lit_comp("File Path Maps"), RD_IconKind_FileOutline}, @@ -224,6 +224,8 @@ RD_VocabInfo rd_vocab_info_table[373] = {str8_lit_comp("state"), str8_lit_comp(""), str8_lit_comp("State"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp("Eval"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp("Line From Virtual Address"), str8_lit_comp(""), RD_IconKind_Null}, +{str8_lit_comp("enable_hidden_mode"), str8_lit_comp(""), str8_lit_comp("Enable Hidden Mode"), str8_lit_comp(""), RD_IconKind_Null}, +{str8_lit_comp("disable_hidden_mode"), str8_lit_comp(""), str8_lit_comp("Disable Hidden Mode"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp("OS Event"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("select_thread"), str8_lit_comp(""), str8_lit_comp("Select Thread"), str8_lit_comp(""), RD_IconKind_Thread}, {str8_lit_comp("select_unwind"), str8_lit_comp(""), str8_lit_comp("Select Unwind"), str8_lit_comp(""), RD_IconKind_Null}, @@ -602,7 +604,7 @@ Rng1U64 rd_reg_slot_range_table[56] = {OffsetOf(RD_Regs, wm_event), OffsetOf(RD_Regs, wm_event) + sizeof(WM_Event *)}, }; -RD_CmdKindInfo rd_cmd_kind_info_table[259] = +RD_CmdKindInfo rd_cmd_kind_info_table[261] = { {0}, { str8_lit_comp("launch_and_run"), str8_lit_comp("Starts debugging a new instance of a target, then runs."), str8_lit_comp("launch,start,run,target"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:targets")}}, @@ -650,6 +652,8 @@ RD_CmdKindInfo rd_cmd_kind_info_table[259] = { str8_lit_comp("state"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Expr, str8_lit_comp("")}}, { str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Vaddr, str8_lit_comp("")}}, +{ str8_lit_comp("enable_hidden_mode"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("disable_hidden_mode"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("select_thread"), str8_lit_comp("Selects a thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Thread, str8_lit_comp("query:threads")}}, { str8_lit_comp("select_unwind"), str8_lit_comp("Selects an unwind frame number for the selected thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("query:call_stack")}}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index d519b33e..f85ab146 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -115,6 +115,8 @@ RD_CmdKind_RunExternalDriverTextCommand, RD_CmdKind_State, RD_CmdKind_Eval, RD_CmdKind_LineFromVAddr, +RD_CmdKind_EnableHiddenMode, +RD_CmdKind_DisableHiddenMode, RD_CmdKind_WMEvent, RD_CmdKind_SelectThread, RD_CmdKind_SelectUnwind, @@ -632,7 +634,7 @@ Z(getting_started)\ C_LINKAGE_BEGIN extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; -extern RD_VocabInfo rd_vocab_info_table[373]; +extern RD_VocabInfo rd_vocab_info_table[375]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; extern String8 rd_reg_slot_code_name_table[56]; extern Rng1U64 rd_reg_slot_range_table[56]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 8c041e13..c9229d9e 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1047,6 +1047,8 @@ RD_CmdTable: // | | | | {State 0 0 1 "" Null 0 0 0 0 0 0 0 Null "state" "State" "" "" "" } {Eval 0 0 1 "" Expr 0 0 0 0 0 0 0 Null "eval" "Eval" "" "" "" } {LineFromVAddr 0 0 1 "" Vaddr 0 0 0 0 0 0 0 Null "line_from_vaddr" "Line From Virtual Address" "" "" "" } + {EnableHiddenMode 0 0 1 "" Null 0 0 0 0 0 0 0 Null "enable_hidden_mode" "Enable Hidden Mode" "" "" "" } + {DisableHiddenMode 0 0 1 "" Null 0 0 0 0 0 0 0 Null "disable_hidden_mode" "Disable Hidden Mode" "" "" "" } //- rjf: os event passthrough {WMEvent 0 0 0 "" Null 0 0 0 0 0 0 0 Null "wm_event" "OS Event" "" "" "" } diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index 1884b2ec..d155c441 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -434,6 +434,9 @@ struct RD_State String8List update_check_response_body_pieces; GuardedRing *update_check_http_ring; + // rjf: hidden mode + B32 hidden_mode; + // rjf: config bucket paths Arena *user_path_arena; String8 user_path; diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 5bec4b84..93d2cc29 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -79,6 +79,7 @@ #include "disasm/disasm_inc.h" #include "stap/stap_parse.h" #include "demon/demon_inc.h" +#include "dbg_server/dbg_server.h" #include "eval/eval_inc.h" #include "eval2/eval2.h" #include "dbg_engine/dbg_engine_inc.h" @@ -165,6 +166,7 @@ #include "disasm/disasm_inc.c" #include "stap/stap_parse.c" #include "demon/demon_inc.c" +#include "dbg_server/dbg_server.c" #include "eval/eval_inc.c" #include "eval2/eval2.c" #include "dbg_engine/dbg_engine_inc.c" From 5ec3156d8ec33b52f9c86b630152c7754ad9acdd Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 12 Oct 2026 08:58:00 -0700 Subject: [PATCH 241/251] fix incorrect listener socket creation on client-only win32 socket sessions; win32 window manager -> make robust to zero handles --- src/raddbg/generated/raddbg.meta.c | 8 +- src/raddbg/generated/raddbg.meta.h | 4 +- src/raddbg/raddbg.mdesk | 4 +- src/raddbg/raddbg_core.c | 19 +-- src/win32/socket/win32_socket.c | 3 + .../window_manager/win32_window_manager.c | 110 +++++++++++------- 6 files changed, 93 insertions(+), 55 deletions(-) diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 6c44182a..4ccc3127 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -224,8 +224,8 @@ RD_VocabInfo rd_vocab_info_table[375] = {str8_lit_comp("state"), str8_lit_comp(""), str8_lit_comp("State"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp("Eval"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp("Line From Virtual Address"), str8_lit_comp(""), RD_IconKind_Null}, -{str8_lit_comp("enable_hidden_mode"), str8_lit_comp(""), str8_lit_comp("Enable Hidden Mode"), str8_lit_comp(""), RD_IconKind_Null}, -{str8_lit_comp("disable_hidden_mode"), str8_lit_comp(""), str8_lit_comp("Disable Hidden Mode"), str8_lit_comp(""), RD_IconKind_Null}, +{str8_lit_comp("hide_ui"), str8_lit_comp(""), str8_lit_comp("Hide UI"), str8_lit_comp(""), RD_IconKind_Null}, +{str8_lit_comp("show_ui"), str8_lit_comp(""), str8_lit_comp("Show UI"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp("OS Event"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("select_thread"), str8_lit_comp(""), str8_lit_comp("Select Thread"), str8_lit_comp(""), RD_IconKind_Thread}, {str8_lit_comp("select_unwind"), str8_lit_comp(""), str8_lit_comp("Select Unwind"), str8_lit_comp(""), RD_IconKind_Null}, @@ -652,8 +652,8 @@ RD_CmdKindInfo rd_cmd_kind_info_table[261] = { str8_lit_comp("state"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("eval"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Expr, str8_lit_comp("")}}, { str8_lit_comp("line_from_vaddr"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Vaddr, str8_lit_comp("")}}, -{ str8_lit_comp("enable_hidden_mode"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, -{ str8_lit_comp("disable_hidden_mode"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("hide_ui"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("show_ui"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("wm_event"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("select_thread"), str8_lit_comp("Selects a thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Thread, str8_lit_comp("query:threads")}}, { str8_lit_comp("select_unwind"), str8_lit_comp("Selects an unwind frame number for the selected thread."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("query:call_stack")}}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index f85ab146..d18bb78e 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -115,8 +115,8 @@ RD_CmdKind_RunExternalDriverTextCommand, RD_CmdKind_State, RD_CmdKind_Eval, RD_CmdKind_LineFromVAddr, -RD_CmdKind_EnableHiddenMode, -RD_CmdKind_DisableHiddenMode, +RD_CmdKind_HideUI, +RD_CmdKind_ShowUI, RD_CmdKind_WMEvent, RD_CmdKind_SelectThread, RD_CmdKind_SelectUnwind, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index c9229d9e..e6867e3e 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1047,8 +1047,8 @@ RD_CmdTable: // | | | | {State 0 0 1 "" Null 0 0 0 0 0 0 0 Null "state" "State" "" "" "" } {Eval 0 0 1 "" Expr 0 0 0 0 0 0 0 Null "eval" "Eval" "" "" "" } {LineFromVAddr 0 0 1 "" Vaddr 0 0 0 0 0 0 0 Null "line_from_vaddr" "Line From Virtual Address" "" "" "" } - {EnableHiddenMode 0 0 1 "" Null 0 0 0 0 0 0 0 Null "enable_hidden_mode" "Enable Hidden Mode" "" "" "" } - {DisableHiddenMode 0 0 1 "" Null 0 0 0 0 0 0 0 Null "disable_hidden_mode" "Disable Hidden Mode" "" "" "" } + {HideUI 0 0 1 "" Null 0 0 0 0 0 0 0 Null "hide_ui" "Hide UI" "" "" "" } + {ShowUI 0 0 1 "" Null 0 0 0 0 0 0 0 Null "show_ui" "Show UI" "" "" "" } //- rjf: os event passthrough {WMEvent 0 0 0 "" Null 0 0 0 0 0 0 0 Null "wm_event" "OS Event" "" "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 047510ad..c542b65a 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -5509,7 +5509,7 @@ rd_push_window_title(Arena *arena) prof_path = str8_chop_last_dot(prof_path); project_name = str8_skip_last_slash(prof_path); } - String8 result = push_str8f(arena, "%S%s%s", project_name, project_name.size != 0 ? " - " : "", BUILD_TITLE " (" BUILD_VERSION_STRING_LITERAL " " BUILD_RELEASE_PHASE_STRING_LITERAL ")"); + String8 result = str8f(arena, "%S%s%s", project_name, project_name.size != 0 ? " - " : "", BUILD_TITLE " (" BUILD_VERSION_STRING_LITERAL " " BUILD_RELEASE_PHASE_STRING_LITERAL ")"); return result; } @@ -5519,7 +5519,7 @@ rd_window_from_cfg(CFG_Node *cfg) CFG_Node *result = &cfg_nil_node; for(CFG_Node *c = cfg; c != &cfg_nil_node; c = c->parent) { - if(c->parent->parent == cfg_node_root() && str8_match(c->string, str8_lit("window"), 0)) + if(c->parent->parent == cfg_node_root() && str8_match(c->string, s("window"), 0)) { result = c; break; @@ -5569,10 +5569,10 @@ rd_window_state_from_cfg(CFG_Node *cfg) Vec2F32 size = {0}; WM_Monitor preferred_monitor = {0}; { - CFG_Node *pos_cfg = cfg_node_child_from_string(window_cfg, str8_lit("pos")); + CFG_Node *pos_cfg = cfg_node_child_from_string(window_cfg, s("pos")); has_pos = (pos_cfg != &cfg_nil_node); - CFG_Node *size_cfg = cfg_node_child_from_string(window_cfg, str8_lit("size")); - CFG_Node *monitor_cfg = cfg_node_child_from_string(window_cfg, str8_lit("monitor")); + CFG_Node *size_cfg = cfg_node_child_from_string(window_cfg, s("size")); + CFG_Node *monitor_cfg = cfg_node_child_from_string(window_cfg, s("monitor")); pos.x = (F32)f64_from_str8(pos_cfg->first->string); pos.y = (F32)f64_from_str8(pos_cfg->first->next->string); size.x = (F32)f64_from_str8(size_cfg->first->string); @@ -11311,9 +11311,10 @@ rd_init(CmdLine *cmdln) Arena *arena = arena_alloc(); rd_state = push_array(arena, RD_State, 1); rd_state->arena = arena; - rd_state->quit_after_success = (cmd_line_has_flag(cmdln, str8_lit("quit_after_success")) || - cmd_line_has_flag(cmdln, str8_lit("q"))); + rd_state->quit_after_success = (cmd_line_has_flag(cmdln, s("quit_after_success")) || + cmd_line_has_flag(cmdln, s("q"))); rd_state->override_focus_on_stop = (cmd_line_has_flag(cmdln, s("no_focus_on_stop"))); + rd_state->hidden_mode = cmd_line_has_flag(cmdln, s("hide_ui")); rd_state->user_path_arena = arena_alloc(); rd_state->project_path_arena = arena_alloc(); rd_state->theme_path_arena = arena_alloc(); @@ -13975,6 +13976,8 @@ rd_frame(void) } str8_list_pushf(rd_state->cmd_output_arena, &rd_state->cmd_outputs, "}\n"); }break; + case RD_CmdKind_HideUI:{rd_state->hidden_mode = 1;}break; + case RD_CmdKind_ShowUI:{rd_state->hidden_mode = 0;}break; //- rjf: exiting case RD_CmdKind_Exit: @@ -18709,7 +18712,7 @@ rd_frame(void) ////////////////////////////// //- rjf: submit rendering to all windows // - ProfScope("submit rendering to all windows") + ProfScope("submit rendering to all windows") if(!rd_state->hidden_mode) { r_begin_frame(); for(RD_WindowState *w = rd_state->first_window_state; w != &rd_nil_window_state; w = w->order_next) diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index 01c23e16..89abd19d 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -392,16 +392,19 @@ sock_session_open(U16 listener_port, SOCK_WakeupFunctionType *wakeup_hook) WSAIoctl(session->tcp_listen_socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &AcceptEx_guid, sizeof(AcceptEx_guid), &session->lpfnAcceptEx, sizeof(session->lpfnAcceptEx), &dwBytes, 0, 0); //- rjf: create accepting socket + if(listener_port != 0) { session->tcp_accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } //- rjf: associate accepting socket with IOCP + if(listener_port != 0) { CreateIoCompletionPort((HANDLE)session->tcp_accept_socket, session->iocp, 0, 0); } //- rjf: kick off accept + if(listener_port != 0) { session->lpfnAcceptEx(session->tcp_listen_socket, session->tcp_accept_socket, session->tcp_accept_buffer, 0, sizeof(struct sockaddr_storage) + 16, sizeof(struct sockaddr_storage) + 16, &session->tcp_accept_size_out, &session->tcp_accept_overlapped); } diff --git a/src/win32/window_manager/win32_window_manager.c b/src/win32/window_manager/win32_window_manager.c index 4f06c111..bc1e6561 100644 --- a/src/win32/window_manager/win32_window_manager.c +++ b/src/win32/window_manager/win32_window_manager.c @@ -1132,7 +1132,10 @@ internal void wm_window_close(WM_Window handle) { W32_WM_Window *window = w32_wm_window_from_handle(handle); - w32_wm_window_release(window); + if(window != 0) + { + w32_wm_window_release(window); + } } internal void @@ -1140,8 +1143,11 @@ wm_window_set_title(WM_Window handle, String8 title) { Temp scratch = scratch_begin(0, 0); W32_WM_Window *window = w32_wm_window_from_handle(handle); - String16 title16 = str16_from_8(scratch.arena, title); - SetWindowTextW(window->hwnd, (WCHAR *)title16.str); + if(window != 0) + { + String16 title16 = str16_from_8(scratch.arena, title); + SetWindowTextW(window->hwnd, (WCHAR *)title16.str); + } scratch_end(scratch); } @@ -1149,11 +1155,14 @@ internal void wm_window_first_paint(WM_Window window_handle) { W32_WM_Window *window = w32_wm_window_from_handle(window_handle); - window->first_paint_done = 1; - ShowWindow(window->hwnd, SW_SHOW); - if(window->maximized) + if(window != 0) { - ShowWindow(window->hwnd, SW_MAXIMIZE); + window->first_paint_done = 1; + ShowWindow(window->hwnd, SW_SHOW); + if(window->maximized) + { + ShowWindow(window->hwnd, SW_MAXIMIZE); + } } } @@ -1161,58 +1170,74 @@ internal void wm_window_focus(WM_Window handle) { W32_WM_Window *window = w32_wm_window_from_handle(handle); - SetForegroundWindow(window->hwnd); - SetFocus(window->hwnd); + if(window != 0) + { + SetForegroundWindow(window->hwnd); + SetFocus(window->hwnd); + } } internal B32 wm_window_is_focused(WM_Window handle) { + B32 result = 0; W32_WM_Window *window = w32_wm_window_from_handle(handle); - HWND active_hwnd = GetForegroundWindow(); - return active_hwnd == window->hwnd; + if(window != 0) + { + HWND active_hwnd = GetForegroundWindow(); + result = active_hwnd == window->hwnd; + } + return result; } internal B32 wm_window_is_fullscreen(WM_Window handle) { + B32 result = 0; W32_WM_Window *window = w32_wm_window_from_handle(handle); - DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); - return !(window_style & WS_OVERLAPPEDWINDOW); + if(window != 0) + { + DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); + result = !(window_style & WS_OVERLAPPEDWINDOW); + } + return result; } internal void wm_window_set_fullscreen(WM_Window handle, B32 fullscreen) { W32_WM_Window *window = w32_wm_window_from_handle(handle); - DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); - B32 is_fullscreen_already = wm_window_is_fullscreen(handle); - if(fullscreen) + if(window != 0) { - if(!is_fullscreen_already) + DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); + B32 is_fullscreen_already = wm_window_is_fullscreen(handle); + if(fullscreen) { - GetWindowPlacement(window->hwnd, &window->last_window_placement); + if(!is_fullscreen_already) + { + GetWindowPlacement(window->hwnd, &window->last_window_placement); + } + MONITORINFO monitor_info = {sizeof(monitor_info)}; + if(GetMonitorInfo(MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info)) + { + SetWindowLong(window->hwnd, GWL_STYLE, window_style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(window->hwnd, HWND_TOP, + monitor_info.rcMonitor.left, + monitor_info.rcMonitor.top, + monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, + monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } } - MONITORINFO monitor_info = {sizeof(monitor_info)}; - if(GetMonitorInfo(MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info)) + else { - SetWindowLong(window->hwnd, GWL_STYLE, window_style & ~WS_OVERLAPPEDWINDOW); - SetWindowPos(window->hwnd, HWND_TOP, - monitor_info.rcMonitor.left, - monitor_info.rcMonitor.top, - monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, - monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, + SetWindowLong(window->hwnd, GWL_STYLE, window_style | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(window->hwnd, &window->last_window_placement); + SetWindowPos(window->hwnd, 0, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } } - else - { - SetWindowLong(window->hwnd, GWL_STYLE, window_style | WS_OVERLAPPEDWINDOW); - SetWindowPlacement(window->hwnd, &window->last_window_placement); - SetWindowPos(window->hwnd, 0, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - } } internal B32 @@ -1290,8 +1315,9 @@ internal void wm_window_set_monitor(WM_Window window_handle, WM_Monitor monitor) { W32_WM_Window *window = w32_wm_window_from_handle(window_handle); - HMONITOR hmonitor = (HMONITOR)monitor.u64[0]; + if(window != 0) { + HMONITOR hmonitor = (HMONITOR)monitor.u64[0]; MONITORINFOEXW info; info.cbSize = sizeof(MONITORINFOEXW); if(GetMonitorInfoW(hmonitor, (MONITORINFO *)&info)) @@ -1311,7 +1337,7 @@ internal void wm_window_clear_custom_border_data(WM_Window handle) { W32_WM_Window *window = w32_wm_window_from_handle(handle); - if(window->custom_border) + if(window && window->custom_border) { arena_clear(window->paint_arena); window->first_title_bar_client_area = window->last_title_bar_client_area = 0; @@ -1324,21 +1350,27 @@ internal void wm_window_push_custom_title_bar(WM_Window handle, F32 thickness) { W32_WM_Window *window = w32_wm_window_from_handle(handle); - window->custom_border_title_thickness = thickness; + if(window != 0) + { + window->custom_border_title_thickness = thickness; + } } internal void wm_window_push_custom_edges(WM_Window handle, F32 thickness) { W32_WM_Window *window = w32_wm_window_from_handle(handle); - window->custom_border_edge_thickness = thickness; + if(window != 0) + { + window->custom_border_edge_thickness = thickness; + } } internal void wm_window_push_custom_title_bar_client_area(WM_Window handle, Rng2F32 rect) { W32_WM_Window *window = w32_wm_window_from_handle(handle); - if(window->custom_border) + if(window && window->custom_border) { W32_WM_TitleBarClientArea *area = push_array(window->paint_arena, W32_WM_TitleBarClientArea, 1); if(area != 0) From a244990b82476cfd8b3b129ee36b33b860327aa9 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 12 Oct 2026 09:53:34 -0700 Subject: [PATCH 242/251] raddbg: hidden mode --- src/raddbg/raddbg_core.c | 99 ++++++++++++------- src/render/d3d11/render_d3d11.c | 29 +++--- .../linux/egl/render_opengl_linux_egl.c | 3 +- src/render/opengl/render_opengl.c | 7 +- .../window_manager/win32_window_manager.c | 8 +- 5 files changed, 92 insertions(+), 54 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index c542b65a..692acbc0 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -5559,10 +5559,46 @@ rd_window_state_from_cfg(CFG_Node *cfg) } //- rjf: allocate/open new window if one was not found + B32 need_wm_state = !rd_state->hidden_mode; if(window_cfg != &cfg_nil_node && ws == &rd_nil_window_state) + { + // rjf: allocate window + ws = rd_state->free_window_state; + if(ws != 0) + { + SLLStackPop_N(rd_state->free_window_state, order_next); + } + else + { + ws = push_array_no_zero(rd_state->arena, RD_WindowState, 1); + } + MemoryZeroStruct(ws); + + // rjf: fill out window + ws->cfg_id = id; + ws->arena = arena_alloc(); + ws->ui = ui_state_alloc(); + ws->drop_completion_arena = arena_alloc(); + ws->query_arena = arena_alloc(); + ws->hover_eval_arena = arena_alloc(); + ws->autocomp_arena = arena_alloc(); + + // rjf: hook up window links + U64 hash = d_hash_from_string(str8_struct(&id)); + U64 slot_idx = hash%rd_state->window_state_slots_count; + RD_WindowStateSlot *slot = &rd_state->window_state_slots[slot_idx]; + DLLPushBack_NPZ(&rd_nil_window_state, rd_state->first_window_state, rd_state->last_window_state, ws, order_next, order_prev); + DLLPushBack_NP(slot->first, slot->last, ws, hash_next, hash_prev); + } + + //- rjf: if we need a real window, and we don't have one -> create + if(ws != &rd_nil_window_state && need_wm_state && wm_window_match(ws->os, wm_window_zero())) { Temp scratch = scratch_begin(0, 0); + // rjf: reset 'frames alive' counter - controls first window show, etc. + ws->frames_alive = 0; + // rjf: unpack configuration options B32 has_pos = 0; Vec2F32 pos = {0}; @@ -5589,56 +5625,37 @@ rd_window_state_from_cfg(CFG_Node *cfg) } } - // rjf: allocate window - ws = rd_state->free_window_state; - if(ws != 0) - { - SLLStackPop_N(rd_state->free_window_state, order_next); - } - else - { - ws = push_array_no_zero(rd_state->arena, RD_WindowState, 1); - } - MemoryZeroStruct(ws); - - // rjf: fill out window - ws->cfg_id = id; - ws->arena = arena_alloc(); - { - String8 title = rd_push_window_title(scratch.arena); - ws->os = wm_window_open(r2f32p(pos.x, pos.y, pos.x+size.x, pos.y+size.y), (!has_pos*WM_WindowFlag_UseDefaultPosition)|WM_WindowFlag_CustomBorder, title); - } + // rjf: create wm window, equip for rendering + String8 title = rd_push_window_title(scratch.arena); + ws->os = wm_window_open(r2f32p(pos.x, pos.y, pos.x+size.x, pos.y+size.y), (!has_pos*WM_WindowFlag_UseDefaultPosition)|WM_WindowFlag_CustomBorder, title); ws->r = r_window_equip(ws->os); - ws->ui = ui_state_alloc(); - ws->drop_completion_arena = arena_alloc(); - ws->query_arena = arena_alloc(); - ws->hover_eval_arena = arena_alloc(); - ws->autocomp_arena = arena_alloc(); ws->last_dpi = wm_dpi_from_window(ws->os); WM_Monitor zero_monitor = {0}; if(!wm_monitor_match(zero_monitor, preferred_monitor)) { wm_window_set_monitor(ws->os, preferred_monitor); } - if(cfg_node_child_from_string(window_cfg, str8_lit("fullscreen")) != &cfg_nil_node) + if(cfg_node_child_from_string(window_cfg, s("fullscreen")) != &cfg_nil_node) { wm_window_set_fullscreen(ws->os, 1); } - if(cfg_node_child_from_string(window_cfg, str8_lit("maximized")) != &cfg_nil_node) + if(cfg_node_child_from_string(window_cfg, s("maximized")) != &cfg_nil_node) { wm_window_set_maximized(ws->os, 1); } - // rjf: hook up window links - U64 hash = d_hash_from_string(str8_struct(&id)); - U64 slot_idx = hash%rd_state->window_state_slots_count; - RD_WindowStateSlot *slot = &rd_state->window_state_slots[slot_idx]; - DLLPushBack_NPZ(&rd_nil_window_state, rd_state->first_window_state, rd_state->last_window_state, ws, order_next, order_prev); - DLLPushBack_NP(slot->first, slot->last, ws, hash_next, hash_prev); - scratch_end(scratch); } + //- rjf: if we don't want a real window, but we have one -> destroy + else if(ws != &rd_nil_window_state && !need_wm_state && !wm_window_match(ws->os, wm_window_zero())) + { + r_window_unequip(ws->os, ws->r); + wm_window_close(ws->os); + MemoryZeroStruct(&ws->os); + MemoryZeroStruct(&ws->r); + } + //- rjf: touch window for this frame if(ws != &rd_nil_window_state) { @@ -5682,9 +5699,9 @@ rd_window_frame(void) ////////////////////////////// //- rjf: @window_frame_part unpack context // - CFG_Node *window = cfg_node_from_id(rd_regs()->window); + CFG_Node *window = cfg_node_from_id(rd_regs()->window); RD_WindowState *ws = rd_window_state_from_cfg(cfg_node_from_id(rd_regs()->window)); - CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, window); + CFG_PanelTree panel_tree= cfg_panel_tree_from_cfg(scratch.arena, window); B32 window_is_focused = (wm_window_is_focused(ws->os) || ws->window_temporarily_focused_ipc); B32 popup_is_open = (rd_state->popup_active); B32 query_is_open = (ws->query_top != 0); @@ -18603,9 +18620,19 @@ rd_frame(void) ////////////////////////////// //- rjf: update/render all windows // + if(rd_state->hidden_mode) + { + CFG_NodePtrList windows = cfg_node_top_level_list_from_string(scratch.arena, s("window")); + for(CFG_NodePtrNode *n = windows.first; n != 0; n = n->next) + { + CFG_Node *window = n->v; + rd_window_state_from_cfg(window); + } + } + else { dr_begin_frame(rd_font_from_slot(RD_FontSlot_Icons)); - CFG_NodePtrList windows = cfg_node_top_level_list_from_string(scratch.arena, str8_lit("window")); + CFG_NodePtrList windows = cfg_node_top_level_list_from_string(scratch.arena, s("window")); for(CFG_NodePtrNode *n = windows.first; n != 0; n = n->next) { CFG_Node *window = n->v; diff --git a/src/render/d3d11/render_d3d11.c b/src/render/d3d11/render_d3d11.c index 121aebde..2d40d4f1 100644 --- a/src/render/d3d11/render_d3d11.c +++ b/src/render/d3d11/render_d3d11.c @@ -562,19 +562,22 @@ r_window_unequip(WM_Window handle, R_Handle equip_handle) MutexScopeW(r_d3d11_state->device_rw_mutex) { R_D3D11_Window *window = r_d3d11_window_from_handle(equip_handle); - window->stage_color_srv->lpVtbl->Release(window->stage_color_srv); - window->stage_color_rtv->lpVtbl->Release(window->stage_color_rtv); - window->stage_color->lpVtbl->Release(window->stage_color); - window->stage_scratch_color_srv->lpVtbl->Release(window->stage_scratch_color_srv); - window->stage_scratch_color_rtv->lpVtbl->Release(window->stage_scratch_color_rtv); - window->stage_scratch_color->lpVtbl->Release(window->stage_scratch_color); - window->framebuffer_rtv->lpVtbl->Release(window->framebuffer_rtv); - window->framebuffer->lpVtbl->Release(window->framebuffer); - window->swapchain->lpVtbl->Release(window->swapchain); - window->generation += 1; - SLLStackPush(r_d3d11_state->first_free_window, window); - r_d3d11_state->window_count -= 1; - r_d3d11_state->dxgi_device->lpVtbl->SetMaximumFrameLatency(r_d3d11_state->dxgi_device, Clamp(1, r_d3d11_state->window_count, 16)); + if(window != 0) + { + window->stage_color_srv->lpVtbl->Release(window->stage_color_srv); + window->stage_color_rtv->lpVtbl->Release(window->stage_color_rtv); + window->stage_color->lpVtbl->Release(window->stage_color); + window->stage_scratch_color_srv->lpVtbl->Release(window->stage_scratch_color_srv); + window->stage_scratch_color_rtv->lpVtbl->Release(window->stage_scratch_color_rtv); + window->stage_scratch_color->lpVtbl->Release(window->stage_scratch_color); + window->framebuffer_rtv->lpVtbl->Release(window->framebuffer_rtv); + window->framebuffer->lpVtbl->Release(window->framebuffer); + window->swapchain->lpVtbl->Release(window->swapchain); + window->generation += 1; + SLLStackPush(r_d3d11_state->first_free_window, window); + r_d3d11_state->window_count -= 1; + r_d3d11_state->dxgi_device->lpVtbl->SetMaximumFrameLatency(r_d3d11_state->dxgi_device, Clamp(1, r_d3d11_state->window_count, 16)); + } } ProfEnd(); } diff --git a/src/render/opengl/linux/egl/render_opengl_linux_egl.c b/src/render/opengl/linux/egl/render_opengl_linux_egl.c index 481cf082..9dded8c7 100644 --- a/src/render/opengl/linux/egl/render_opengl_linux_egl.c +++ b/src/render/opengl/linux/egl/render_opengl_linux_egl.c @@ -180,9 +180,10 @@ internal void r_ogl_os_window_unequip(WM_Window os, R_Handle r) { R_OGL_LNX_Window *w = (R_OGL_LNX_Window *)r.u64[0]; + if(w != 0) { + SLLStackPush(r_ogl_lnx_state->free_window, w); } - SLLStackPush(r_ogl_lnx_state->free_window, w); } internal void diff --git a/src/render/opengl/render_opengl.c b/src/render/opengl/render_opengl.c index 7432616f..983c9e04 100644 --- a/src/render/opengl/render_opengl.c +++ b/src/render/opengl/render_opengl.c @@ -255,8 +255,11 @@ r_hook void r_window_unequip(WM_Window window, R_Handle window_equip) { R_OGL_Window *w = (R_OGL_Window *)window_equip.u64[0]; - r_ogl_os_window_unequip(window, w->os); - SLLStackPush(r_ogl_state->free_window, w); + if(w != 0) + { + r_ogl_os_window_unequip(window, w->os); + SLLStackPush(r_ogl_state->free_window, w); + } } //- rjf: textures diff --git a/src/win32/window_manager/win32_window_manager.c b/src/win32/window_manager/win32_window_manager.c index bc1e6561..7276dd48 100644 --- a/src/win32/window_manager/win32_window_manager.c +++ b/src/win32/window_manager/win32_window_manager.c @@ -1480,8 +1480,12 @@ internal WM_Monitor wm_monitor_from_window(WM_Window window) { W32_WM_Window *w = w32_wm_window_from_handle(window); - HMONITOR handle = MonitorFromWindow(w->hwnd, MONITOR_DEFAULTTOPRIMARY); - WM_Monitor result = {(U64)handle}; + WM_Monitor result = {0}; + if(w != 0) + { + HMONITOR handle = MonitorFromWindow(w->hwnd, MONITOR_DEFAULTTOPRIMARY); + result.u64[0] = (U64)handle; + } return result; } From d5cbced499843ad9ef7873240d98dea6c29bbc4c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 12 Oct 2026 09:58:27 -0700 Subject: [PATCH 243/251] raddbg: never use a flag argument as the first command line target argument. never coherent --- src/raddbg/raddbg_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 692acbc0..be5f449e 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -11500,7 +11500,7 @@ rd_init(CmdLine *cmdln) { after_first_non_flag = 1; } - if(after_first_non_flag && !is_cfg && !is_dmp) + if(after_first_non_flag && !is_cfg && !is_dmp && (!is_flag || target_args.node_count != 0)) { str8_list_push(scratch2.arena, &target_args, arg); } From 933f05949c295bf11f27c0835ed016e76452b2d5 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 14 Oct 2026 20:53:32 -0700 Subject: [PATCH 244/251] raddbg_server: checkpoint; raddbg: zoom panels --- project.4coder | 2 +- project.raddbg | 7 + src/base/base_entry_point.c | 2 +- src/config/config_panels.c | 91 ++-- src/config/config_panels.h | 1 + src/dbg_engine/dbg_engine_ctrl.c | 37 +- src/dbg_server/dbg_server.c | 3 + src/dbg_server/dbg_server.h | 2 +- src/raddbg/generated/raddbg.meta.c | 14 +- src/raddbg/generated/raddbg.meta.h | 6 +- src/raddbg/raddbg.mdesk | 12 + src/raddbg/raddbg_core.c | 682 ++++++++++++++++--------- src/raddbg/raddbg_core.h | 4 + src/raddbg_server/raddbg_server_main.c | 107 +++- 14 files changed, 658 insertions(+), 312 deletions(-) diff --git a/project.4coder b/project.4coder index 360ba491..0fca26cd 100644 --- a/project.4coder +++ b/project.4coder @@ -49,7 +49,7 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg telemetry debug", .linux = "./build.sh raddbg debug meta", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg raddbg_server telemetry debug", .linux = "./build.sh raddbg debug meta", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/project.raddbg b/project.raddbg index 08f38bb0..ef217025 100755 --- a/project.raddbg +++ b/project.raddbg @@ -10,6 +10,13 @@ target: enabled: 1 } target: +{ + executable: "build/raddbg_server.exe" + working_directory: build + debug_subprocesses: 1 + arguments: "mule_main.exe" +} +target: { executable: "build/metagen.exe" working_directory: build diff --git a/src/base/base_entry_point.c b/src/base/base_entry_point.c index 169f2573..35c4ee10 100644 --- a/src/base/base_entry_point.c +++ b/src/base/base_entry_point.c @@ -82,7 +82,7 @@ main_thread_base_entry_point(int arguments_count, char **arguments) #if defined(DBG_INFO_H) && !defined(DI_INIT_MANUAL) di_init(&cmdline); #endif -#if defined(DEMON_CORE_H) && !defined(DMN_INIT_MANUAL) +#if defined(DEMON_H) && !defined(DMN_INIT_MANUAL) dmn_init(); #endif #if defined(DBG_ENGINE_CORE_H) && !defined(D_INIT_MANUAL) diff --git a/src/config/config_panels.c b/src/config/config_panels.c index 3746fad0..45396a1c 100644 --- a/src/config/config_panels.c +++ b/src/config/config_panels.c @@ -7,7 +7,7 @@ cfg_window_from_cfg(CFG_Node *cfg) CFG_Node *result = &cfg_nil_node; for(CFG_Node *c = cfg; c != &cfg_nil_node; c = c->parent) { - if(c->parent->parent == cfg_node_root() && str8_match(c->string, str8_lit("window"), 0)) + if(c->parent->parent == cfg_node_root() && str8_match(c->string, s("window"), 0)) { result = c; break; @@ -21,56 +21,69 @@ cfg_panel_tree_from_cfg(Arena *arena, CFG_Node *cfg_root) { Temp scratch = scratch_begin(&arena, 1); CFG_Node *wcfg = cfg_window_from_cfg(cfg_root); - CFG_Node *src_root = cfg_node_child_from_string(wcfg, str8_lit("panels")); + CFG_Node *src_root = cfg_node_child_from_string(wcfg, s("panels")); CFG_PanelNode *dst_root = &cfg_nil_panel_node; CFG_PanelNode *dst_focused = &cfg_nil_panel_node; + CFG_PanelNode *dst_zoom_root = &cfg_nil_panel_node; { - Axis2 active_split_axis = cfg_node_child_from_string(wcfg, str8_lit("split_x")) != &cfg_nil_node ? Axis2_X : Axis2_Y; + Axis2 active_split_axis = cfg_node_child_from_string(wcfg, s("split_x")) != &cfg_nil_node ? Axis2_X : Axis2_Y; CFG_NodeRec rec = {0}; CFG_PanelNode *dst_active_parent = &cfg_nil_panel_node; for(CFG_Node *src = src_root; src != &cfg_nil_node; src = rec.next) { - // rjf: build a panel node - CFG_PanelNode *dst = push_array(arena, CFG_PanelNode, 1); - MemoryCopyStruct(dst, &cfg_nil_panel_node); - dst->parent = dst_active_parent; - if(dst_active_parent != &cfg_nil_panel_node) - { - DLLPushBack_NPZ(&cfg_nil_panel_node, dst_active_parent->first, dst_active_parent->last, dst, next, prev); - dst_active_parent->child_count += 1; - } - if(dst_root == &cfg_nil_panel_node) - { - dst_root = dst; - } + // rjf: unpack this config string + MD_TokenizeResult tokenize = md_tokenize_from_text(scratch.arena, src->string); - // rjf: extract cfg info + // rjf: build a panel node if this is a child panel + CFG_PanelNode *dst = &cfg_nil_panel_node; B32 panel_has_children = 0; - dst->cfg = src; - dst->pct_of_parent = (src == src_root ? 1.f : (F32)f64_from_str8(src->string)); - dst->tab_side = (cfg_node_child_from_string(src, str8_lit("tabs_on_bottom")) != &cfg_nil_node ? Side_Max : Side_Min); - dst->split_axis = active_split_axis; - for(CFG_Node *src_child = src->first; src_child != &cfg_nil_node; src_child = src_child->next) + if(src == src_root || (tokenize.tokens.count == 1 && tokenize.tokens.v[0].flags & MD_TokenFlag_Numeric)) { - MD_TokenizeResult tokenize = md_tokenize_from_text(scratch.arena, src_child->string); - if(tokenize.tokens.count == 1 && tokenize.tokens.v[0].flags & MD_TokenFlag_Numeric) + // rjf: allocate + dst = push_array(arena, CFG_PanelNode, 1); + MemoryCopyStruct(dst, &cfg_nil_panel_node); + dst->parent = dst_active_parent; + if(dst_active_parent != &cfg_nil_panel_node) { - panel_has_children = 1; + DLLPushBack_NPZ(&cfg_nil_panel_node, dst_active_parent->first, dst_active_parent->last, dst, next, prev); + dst_active_parent->child_count += 1; } - else if(str8_match(src_child->string, str8_lit("tabs_on_bottom"), 0)) + if(dst_root == &cfg_nil_panel_node) { - // NOTE(rjf): skip - this is a panel option. + dst_root = dst; } - else if(str8_match(src_child->string, str8_lit("selected"), 0)) + + // rjf: extract cfg info + dst->cfg = src; + dst->pct_of_parent = (src == src_root ? 1.f : (F32)f64_from_str8(src->string)); + dst->tab_side = (cfg_node_child_from_string(src, s("tabs_on_bottom")) != &cfg_nil_node ? Side_Max : Side_Min); + dst->split_axis = active_split_axis; + for(CFG_Node *src_child = src->first; src_child != &cfg_nil_node; src_child = src_child->next) { - dst_focused = dst; - } - else if(tokenize.tokens.count == 1 && tokenize.tokens.v[0].flags & MD_TokenFlag_Identifier) - { - cfg_node_ptr_list_push(arena, &dst->tabs, src_child); - if(cfg_node_child_from_string(src_child, str8_lit("selected")) != &cfg_nil_node) + MD_TokenizeResult tokenize = md_tokenize_from_text(scratch.arena, src_child->string); + if(tokenize.tokens.count == 1 && tokenize.tokens.v[0].flags & MD_TokenFlag_Numeric) { - dst->selected_tab = src_child; + panel_has_children = 1; + } + if(str8_match(src_child->string, s("tabs_on_bottom"), 0)) + { + // NOTE(rjf): skip - this is a panel option. + } + else if(str8_match(src_child->string, s("selected"), 0)) + { + dst_focused = dst; + } + else if(str8_match(src_child->string, s("zoom_root"), 0)) + { + dst_zoom_root = dst; + } + else if(tokenize.tokens.count == 1 && tokenize.tokens.v[0].flags & MD_TokenFlag_Identifier) + { + cfg_node_ptr_list_push(arena, &dst->tabs, src_child); + if(cfg_node_child_from_string(src_child, s("selected")) != &cfg_nil_node) + { + dst->selected_tab = src_child; + } } } } @@ -103,7 +116,7 @@ cfg_panel_tree_from_cfg(Arena *arena, CFG_Node *cfg_root) } } scratch_end(scratch); - CFG_PanelTree tree = {dst_root, dst_focused}; + CFG_PanelTree tree = {dst_root, dst_focused, dst_zoom_root}; return tree; } @@ -179,7 +192,7 @@ cfg_target_rect_from_panel_node(Rng2F32 root_rect, CFG_PanelNode *root, CFG_Pane // rjf: count ancestors U64 ancestor_count = 0; - for(CFG_PanelNode *p = panel->parent; p != &cfg_nil_panel_node; p = p->parent) + for(CFG_PanelNode *p = panel->parent; p != root->parent; p = p->parent) { ancestor_count += 1; } @@ -188,7 +201,7 @@ cfg_target_rect_from_panel_node(Rng2F32 root_rect, CFG_PanelNode *root, CFG_Pane CFG_PanelNode **ancestors = push_array(scratch.arena, CFG_PanelNode *, ancestor_count); { U64 ancestor_idx = 0; - for(CFG_PanelNode *p = panel->parent; p != &cfg_nil_panel_node; p = p->parent) + for(CFG_PanelNode *p = panel->parent; p != root->parent; p = p->parent) { ancestors[ancestor_idx] = p; ancestor_idx += 1; @@ -203,7 +216,7 @@ cfg_target_rect_from_panel_node(Rng2F32 root_rect, CFG_PanelNode *root, CFG_Pane { CFG_PanelNode *ancestor = ancestors[ancestor_idx]; CFG_PanelNode *parent = ancestor->parent; - if(parent != &cfg_nil_panel_node) + if(parent != root->parent) { parent_rect = cfg_target_rect_from_panel_node_child(parent_rect, parent, ancestor); } diff --git a/src/config/config_panels.h b/src/config/config_panels.h index 5733ccf8..a2f0c7d6 100644 --- a/src/config/config_panels.h +++ b/src/config/config_panels.h @@ -33,6 +33,7 @@ struct CFG_PanelTree { CFG_PanelNode *root; CFG_PanelNode *focused; + CFG_PanelNode *zoom_root; }; typedef struct CFG_PanelNodeRec CFG_PanelNodeRec; diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index e8f50689..81a2703a 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -3830,21 +3830,38 @@ d_ctrl_thread__open_remote_server(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) SOCK_Endpoint endpoint = sock_endpoint_from_string(msg->path); //- rjf: handshake & get state + B32 good_handshake = 1; { Temp scratch = scratch_begin(0, 0); - DS_Msg msg = {0}; + + // rjf: send handshake packet { - msg.kind = DS_MsgKind_Handshake; - msg.msg_id = 1; + DS_Msg server_msg = {0}; + { + server_msg.kind = DS_MsgKind_Handshake; + server_msg.msg_id = 1; + } + String8 send_packet = ds_packet_from_msg(scratch.arena, &server_msg); + sock_send(d_ctrl_state->sock_session, SOCK_Protocol_TCP, endpoint, send_packet, max_U64); } - String8 msg_raw = ds_packet_from_msg(scratch.arena, &msg); - DS_PacketHeader header = + + // rjf: receive state packet(s) { - .magic = DS_PACKET_MAGIC, - .kind = DS_PacketKind_Msg, - .version = DS_PACKET_VERSION, - .payload_size = msg_raw.size, - }; + SOCK_Protocol recv_protocol = SOCK_Protocol_TCP; + SOCK_Endpoint recv_endpoint = {0}; + String8 recv_packet = {0}; + sock_recv(scratch.arena, d_ctrl_state->sock_session, &recv_protocol, &recv_endpoint, &recv_packet, now_time_us()+10000000); + if(recv_packet.size == 0) + { + good_handshake = 0; + } + } + + // rjf: report failure + if(!good_handshake) + { + log_user_errorf("Couldn't successfully connect to server at %S (%S).", sock_string_from_endpoint(scratch.arena, endpoint), msg->path); + } scratch_end(scratch); } diff --git a/src/dbg_server/dbg_server.c b/src/dbg_server/dbg_server.c index a6250862..d2960b0b 100644 --- a/src/dbg_server/dbg_server.c +++ b/src/dbg_server/dbg_server.c @@ -40,6 +40,7 @@ ds_msg_from_packet(Arena *arena, String8 data) String8 string = {0}; off += str8_deserial_read_struct(payload, off, &string.size); string = str8_substr(payload, r1u64(off, off+string.size)); + off += string.size; str8_list_push(arena, &msg.command_line, string); } } @@ -49,6 +50,7 @@ ds_msg_from_packet(Arena *arena, String8 data) String8 string = {0}; off += str8_deserial_read_struct(payload, off, &string.size); string = str8_substr(payload, r1u64(off, off+string.size)); + off += string.size; msg.path = string; } @@ -61,6 +63,7 @@ ds_msg_from_packet(Arena *arena, String8 data) String8 string = {0}; off += str8_deserial_read_struct(payload, off, &string.size); string = str8_substr(payload, r1u64(off, off+string.size)); + off += string.size; str8_list_push(arena, &msg.env, string); } } diff --git a/src/dbg_server/dbg_server.h b/src/dbg_server/dbg_server.h index 1c929482..58583463 100644 --- a/src/dbg_server/dbg_server.h +++ b/src/dbg_server/dbg_server.h @@ -131,6 +131,6 @@ internal String8 ds_packet_from_msg(Arena *arena, DS_Msg *msg); internal DS_Response ds_response_from_packet(Arena *arena, String8 data); internal String8 ds_packet_from_response(Arena *arena, DS_Response *response); internal DMN_EventList ds_dmn_events_from_packet(Arena *arena, String8 data); -internal String8 ds_serialized_from_dmn_events(Arena *arena, DMN_EventList evts); +internal String8 ds_packet_from_dmn_events(Arena *arena, DMN_EventList evts); #endif // DBG_SERVER_H diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 4ccc3127..7e57eff5 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -62,7 +62,7 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[375] = +RD_VocabInfo rd_vocab_info_table[379] = { {str8_lit_comp("type_view"), str8_lit_comp("type_views"), str8_lit_comp("Type View"), str8_lit_comp("Type Views"), RD_IconKind_Binoculars}, {str8_lit_comp("file_path_map"), str8_lit_comp("file_path_maps"), str8_lit_comp("File Path Map"), str8_lit_comp("File Path Maps"), RD_IconKind_FileOutline}, @@ -254,11 +254,15 @@ RD_VocabInfo rd_vocab_info_table[375] = {str8_lit_comp("rotate_panel_columns"), str8_lit_comp(""), str8_lit_comp("Rotate Panel Columns"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("next_panel"), str8_lit_comp(""), str8_lit_comp("Focus Next Panel"), str8_lit_comp(""), RD_IconKind_RightArrow}, {str8_lit_comp("prev_panel"), str8_lit_comp(""), str8_lit_comp("Focus Previous Panel"), str8_lit_comp(""), RD_IconKind_LeftArrow}, +{str8_lit_comp("parent_panel"), str8_lit_comp(""), str8_lit_comp("Focus Parent Panel"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("focus_panel"), str8_lit_comp(""), str8_lit_comp("Focus Panel"), str8_lit_comp(""), RD_IconKind_Null}, {str8_lit_comp("focus_panel_right"), str8_lit_comp(""), str8_lit_comp("Focus Panel Right"), str8_lit_comp(""), RD_IconKind_RightArrow}, {str8_lit_comp("focus_panel_left"), str8_lit_comp(""), str8_lit_comp("Focus Panel Left"), str8_lit_comp(""), RD_IconKind_LeftArrow}, {str8_lit_comp("focus_panel_up"), str8_lit_comp(""), str8_lit_comp("Focus Panel Up"), str8_lit_comp(""), RD_IconKind_UpArrow}, {str8_lit_comp("focus_panel_down"), str8_lit_comp(""), str8_lit_comp("Focus Panel Down"), str8_lit_comp(""), RD_IconKind_DownArrow}, +{str8_lit_comp("zoom_panel"), str8_lit_comp(""), str8_lit_comp("Zoom Panel"), str8_lit_comp(""), RD_IconKind_Window}, +{str8_lit_comp("unzoom_panel"), str8_lit_comp(""), str8_lit_comp("Unzoom Panel"), str8_lit_comp(""), RD_IconKind_Window}, +{str8_lit_comp("toggle_zoom_panel"), str8_lit_comp(""), str8_lit_comp("Toggle Panel Zoom"), str8_lit_comp(""), RD_IconKind_Window}, {str8_lit_comp("undo"), str8_lit_comp(""), str8_lit_comp("Undo"), str8_lit_comp(""), RD_IconKind_Undo}, {str8_lit_comp("redo"), str8_lit_comp(""), str8_lit_comp("Redo"), str8_lit_comp(""), RD_IconKind_Redo}, {str8_lit_comp("go_back"), str8_lit_comp(""), str8_lit_comp("Go Back"), str8_lit_comp(""), RD_IconKind_LeftArrow}, @@ -471,7 +475,7 @@ RD_NameSchemaInfo rd_name_schema_info_table[39] = {str8_lit_comp("color"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @display_name(\"Value\") @description(\"An expression to describe the value or location of the color.\")\n 'expression': expr_string,\n}\n")}, {str8_lit_comp("geo3d"), 1, str8_lit_comp("@inherit(tab) x:\n{\n @display_name(\"Expression\") @description(\"An expression to describe the base address of the index buffer.\")\n 'expression': expr_string,\n 'count': expr_string,\n 'vtx': expr_string,\n 'vtx_size': expr_string,\n 'yaw': @range[0, 1] f32,\n 'pitch': @range[-0.5, 0] f32,\n 'zoom': @range[0, 100] f32,\n}\n")}, {str8_lit_comp("getting_started"), 0, str8_lit_comp("@inherit(tab) x:\n{\n}\n")}, -{str8_lit_comp("target"), 0, str8_lit_comp("@row_commands(@cmd_line save_cfg_to_project, enable_cfg, launch_and_run, launch_and_step_into, duplicate_cfg, remove_cfg)\n@collection_commands(add_target)\nx:\n{\n 'label': code_string,\n 'executable': path,\n 'arguments': string,\n 'working_directory': @folder path,\n 'entry_point': expr_string,\n @default(\"output\") 'output_label': code_string,\n 'stdout_path': @can_not_exist @no_relativize path,\n 'stderr_path': @can_not_exist @no_relativize path,\n 'stdin_path': @can_not_exist @no_relativize path,\n 'environment': set,\n 'debug_subprocesses': bool,\n @no_revert @no_expand @default(0) 'enabled': bool,\n}\n")}, +{str8_lit_comp("target"), 0, str8_lit_comp("@row_commands(@cmd_line save_cfg_to_project, enable_cfg, launch_and_run, launch_and_step_into, duplicate_cfg, remove_cfg)\n@collection_commands(add_target)\nx:\n{\n 'label': code_string,\n 'executable': path,\n 'arguments': string,\n 'working_directory': @folder path,\n 'entry_point': expr_string,\n @default(\"output\") 'output_label': code_string,\n 'stdout_path': @can_not_exist @no_relativize path,\n 'stderr_path': @can_not_exist @no_relativize path,\n 'stdin_path': @can_not_exist @no_relativize path,\n 'environment': set,\n 'debug_subprocesses': bool,\n @display_name('Remote')\n 'remote': bool,\n @expand_if(\"$.remote\") @display_name('Deploy')\n 'deploy': bool,\n @expand_if(\"$.remote\") @display_name('Address')\n 'address': string,\n @no_revert @no_expand @default(0) 'enabled': bool,\n}\n")}, {str8_lit_comp("breakpoint"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(toggle_breakpoint, add_breakpoint, add_address_breakpoint, add_function_breakpoint, clear_breakpoints)\nx:\n{\n 'label': code_string,\n 'condition': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n 'hit_count': u64,\n 'address_range_size': @or(0, 1, 2, 4, 8) u64,\n 'break_on_write': bool,\n 'break_on_read': bool,\n 'break_on_execute': bool,\n @display_name(\"Break Selected Thread Only\") 'break_selected_thread_only': bool,\n @no_revert @no_expand @default(1) 'enabled': bool,\n}\n")}, {str8_lit_comp("watch_pin"), 0, str8_lit_comp("@row_commands(duplicate_cfg, remove_cfg)\n@collection_commands(add_watch_pin, toggle_watch_pin)\nx:\n{\n 'expression': expr_string,\n 'source_location': path_pt,\n 'address_location': expr_string,\n}\n")}, {str8_lit_comp("debug_info"), 0, str8_lit_comp("@row_commands(enable_cfg, duplicate_cfg, remove_cfg)\n@collection_commands(load_debug_info)\nx:\n{\n 'path': @no_relativize path,\n @no_revert @no_expand @default(1) 'enabled': bool,\n @query 'guid': string,\n}\n")}, @@ -604,7 +608,7 @@ Rng1U64 rd_reg_slot_range_table[56] = {OffsetOf(RD_Regs, wm_event), OffsetOf(RD_Regs, wm_event) + sizeof(WM_Event *)}, }; -RD_CmdKindInfo rd_cmd_kind_info_table[261] = +RD_CmdKindInfo rd_cmd_kind_info_table[265] = { {0}, { str8_lit_comp("launch_and_run"), str8_lit_comp("Starts debugging a new instance of a target, then runs."), str8_lit_comp("launch,start,run,target"), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*1)|(RD_QueryFlag_Required*1), RD_RegSlot_Cfg, str8_lit_comp("query:targets")}}, @@ -682,11 +686,15 @@ RD_CmdKindInfo rd_cmd_kind_info_table[261] = { str8_lit_comp("rotate_panel_columns"), str8_lit_comp("Rotates all panels at the closest column level of the panel hierarchy."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("next_panel"), str8_lit_comp("Cycles the active panel forward."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("prev_panel"), str8_lit_comp("Cycles the active panel backwards."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("parent_panel"), str8_lit_comp("Focuses the parent of the currently selected panel."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("focus_panel"), str8_lit_comp("Focuses a new panel."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("focus_panel_right"), str8_lit_comp("Focuses a panel rightward of the currently focused panel."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("focus_panel_left"), str8_lit_comp("Focuses a panel leftward of the currently focused panel."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("focus_panel_up"), str8_lit_comp("Focuses a panel upward of the currently focused panel."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("focus_panel_down"), str8_lit_comp("Focuses a panel downward of the currently focused panel."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("zoom_panel"), str8_lit_comp("Zooms a panel, making it occupy the entire window."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("unzoom_panel"), str8_lit_comp("Unzooms the currently zoomed panel."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, +{ str8_lit_comp("toggle_zoom_panel"), str8_lit_comp("Zooms a panel, making it occupy the entire window, or unzooms it, if already zoomed."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*1)|(RD_CmdKindFlag_ListInIPCDocs*1), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("undo"), str8_lit_comp("Undoes the previous action."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("redo"), str8_lit_comp("Redoes the first previously undone action."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, { str8_lit_comp("go_back"), str8_lit_comp("Returns to the previously selected panel and tab in recorded history."), str8_lit_comp(""), str8_lit_comp(""), (RD_CmdKindFlag_ListInUI*0)|(RD_CmdKindFlag_ListInIPCDocs*0), {(RD_QueryFlag_AllowFiles*0)|(RD_QueryFlag_AllowFolders*0)|(RD_QueryFlag_CodeInput*0)|(RD_QueryFlag_KeepOldInput*0)|(RD_QueryFlag_SelectOldInput*0)|(RD_QueryFlag_Floating*0)|(RD_QueryFlag_Required*0), RD_RegSlot_Null, str8_lit_comp("")}}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index d18bb78e..c16a335e 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -145,11 +145,15 @@ RD_CmdKind_SplitPanel, RD_CmdKind_RotatePanelColumns, RD_CmdKind_NextPanel, RD_CmdKind_PrevPanel, +RD_CmdKind_ParentPanel, RD_CmdKind_FocusPanel, RD_CmdKind_FocusPanelRight, RD_CmdKind_FocusPanelLeft, RD_CmdKind_FocusPanelUp, RD_CmdKind_FocusPanelDown, +RD_CmdKind_ZoomPanel, +RD_CmdKind_UnzoomPanel, +RD_CmdKind_ToggleZoomPanel, RD_CmdKind_Undo, RD_CmdKind_Redo, RD_CmdKind_GoBack, @@ -634,7 +638,7 @@ Z(getting_started)\ C_LINKAGE_BEGIN extern String8 rd_tab_fast_path_view_name_table[25]; extern String8 rd_tab_fast_path_query_name_table[25]; -extern RD_VocabInfo rd_vocab_info_table[375]; +extern RD_VocabInfo rd_vocab_info_table[379]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; extern String8 rd_reg_slot_code_name_table[56]; extern Rng1U64 rd_reg_slot_range_table[56]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index e6867e3e..9604e5ce 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -803,6 +803,12 @@ RD_VocabTable: 'stdin_path': @can_not_exist @no_relativize path, 'environment': set, 'debug_subprocesses': bool, + @display_name('Remote') + 'remote': bool, + @expand_if("$.remote") @display_name('Deploy') + 'deploy': bool, + @expand_if("$.remote") @display_name('Address') + 'address': string, @no_revert @no_expand @default(0) 'enabled': bool, } ```, @@ -1095,12 +1101,18 @@ RD_CmdTable: // | | | | //- rjf: focused panel changing {NextPanel 0 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "next_panel" "Focus Next Panel" "Cycles the active panel forward." "" "" } {PrevPanel 0 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "prev_panel" "Focus Previous Panel" "Cycles the active panel backwards." "" "" } + {ParentPanel 0 1 1 "" Null 0 0 0 0 0 0 0 Null "parent_panel" "Focus Parent Panel" "Focuses the parent of the currently selected panel." "" "" } {FocusPanel 0 0 0 "" Null 0 0 0 0 0 0 0 Null "focus_panel" "Focus Panel" "Focuses a new panel." "" "" } {FocusPanelRight 0 1 1 "" Null 0 0 0 0 0 0 0 RightArrow "focus_panel_right" "Focus Panel Right" "Focuses a panel rightward of the currently focused panel." "" "" } {FocusPanelLeft 0 1 1 "" Null 0 0 0 0 0 0 0 LeftArrow "focus_panel_left" "Focus Panel Left" "Focuses a panel leftward of the currently focused panel." "" "" } {FocusPanelUp 0 1 1 "" Null 0 0 0 0 0 0 0 UpArrow "focus_panel_up" "Focus Panel Up" "Focuses a panel upward of the currently focused panel." "" "" } {FocusPanelDown 0 1 1 "" Null 0 0 0 0 0 0 0 DownArrow "focus_panel_down" "Focus Panel Down" "Focuses a panel downward of the currently focused panel." "" "" } + //- rjf: panel zooming + {ZoomPanel 0 1 1 "" Null 0 0 0 0 0 0 0 Window "zoom_panel" "Zoom Panel" "Zooms a panel, making it occupy the entire window." "" "" } + {UnzoomPanel 0 1 1 "" Null 0 0 0 0 0 0 0 Window "unzoom_panel" "Unzoom Panel" "Unzooms the currently zoomed panel." "" "" } + {ToggleZoomPanel 0 1 1 "" Null 0 0 0 0 0 0 0 Window "toggle_zoom_panel" "Toggle Panel Zoom" "Zooms a panel, making it occupy the entire window, or unzooms it, if already zoomed." "" "" } + //- rjf: undo/redo {Undo 0 0 0 "" Null 0 0 0 0 0 0 0 Undo "undo" "Undo" "Undoes the previous action." "" "" } {Redo 0 0 0 "" Null 0 0 0 0 0 0 0 Redo "redo" "Redo" "Redoes the first previously undone action." "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index be5f449e..41075094 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -5724,7 +5724,7 @@ rd_window_frame(void) // rd_regs()->panel = panel_tree.focused->cfg->id; rd_regs()->tab = panel_tree.focused->selected_tab->id; - rd_regs()->view = panel_tree.focused->selected_tab->id; + rd_regs()->view = panel_tree.focused->selected_tab->id; ////////////////////////////// //- rjf: @window_frame_part compute window's theme @@ -6054,8 +6054,8 @@ rd_window_frame(void) ui_push_pref_height(ui_px(floor_f32(ui_top_font_size()*3.f), 1.f)); ui_push_blur_size(10.f); FNT_RasterFlags text_raster_flags = 0; - if(rd_setting_b32_from_name(str8_lit("smooth_ui_text"))) {text_raster_flags |= FNT_RasterFlag_Smooth;} - if(rd_setting_b32_from_name(str8_lit("hint_ui_text"))) {text_raster_flags |= FNT_RasterFlag_Hinted;} + if(rd_setting_b32_from_name(s("smooth_ui_text"))) {text_raster_flags |= FNT_RasterFlag_Smooth;} + if(rd_setting_b32_from_name(s("hint_ui_text"))) {text_raster_flags |= FNT_RasterFlag_Hinted;} ui_push_text_raster_flags(text_raster_flags); } @@ -7538,6 +7538,10 @@ rd_window_frame(void) rd_cmd_kind_info_table[RD_CmdKind_NextPanel].string, rd_cmd_kind_info_table[RD_CmdKind_PrevPanel].string, {0},//- + rd_cmd_kind_info_table[RD_CmdKind_ZoomPanel].string, + rd_cmd_kind_info_table[RD_CmdKind_UnzoomPanel].string, + rd_cmd_kind_info_table[RD_CmdKind_ToggleZoomPanel].string, + {0},//- rd_cmd_kind_info_table[RD_CmdKind_TabBarTop].string, rd_cmd_kind_info_table[RD_CmdKind_TabBarBottom].string, {0},//- @@ -7554,6 +7558,10 @@ rd_window_frame(void) 'n', 'p', 0,//- + 'z', + 'm', + 't', + 0,//-- 0, 0, 0,//- @@ -8236,53 +8244,150 @@ rd_window_frame(void) // B32 is_changing_panel_boundaries = 0; ProfScope("non-leaf panel UI") - for(CFG_PanelNode *panel = panel_tree.root; - panel != &cfg_nil_panel_node; - panel = cfg_panel_node_rec__depth_first_pre(panel_tree.root, panel).next) { - ////////////////////////// - //- rjf: continue on leaf panels - // - if(panel->first == &cfg_nil_panel_node) + CFG_PanelNode *panel_tree_root = (panel_tree.zoom_root != &cfg_nil_panel_node) ? panel_tree.zoom_root : panel_tree.root; + for(CFG_PanelNode *panel = panel_tree_root; + panel != &cfg_nil_panel_node; + panel = cfg_panel_node_rec__depth_first_pre(panel_tree_root, panel).next) { - continue; - } - - ////////////////////////// - //- rjf: grab info - // - Axis2 split_axis = panel->split_axis; - Rng2F32 panel_rect = cfg_target_rect_from_panel_node(content_rect, panel_tree.root, panel); - - ////////////////////////// - //- rjf: boundary tab-drag/drop sites - // - { - CFG_Node *drag_view = cfg_node_from_id(rd_state->drag_drop_regs->view); - if(rd_drag_is_active() && rd_state->drag_drop_regs_slot == RD_RegSlot_View && drag_view != &cfg_nil_node) + ////////////////////////// + //- rjf: continue on leaf panels + // + if(panel->first == &cfg_nil_panel_node) { - //- rjf: params - F32 drop_site_major_dim_px = ceil_f32(ui_top_font_size()*7.f); - F32 drop_site_minor_dim_px = ceil_f32(ui_top_font_size()*5.f); - F32 corner_radius = ui_top_font_size()*0.5f; - F32 padding = ceil_f32(ui_top_font_size()*0.5f); - - //- rjf: special case - build Y boundary drop sites on root panel - // - // (this does not naturally follow from the below algorithm, since the - // root level panel only splits on X) - if(panel == panel_tree.root) UI_CornerRadius(corner_radius) + continue; + } + + ////////////////////////// + //- rjf: grab info + // + Axis2 split_axis = panel->split_axis; + Rng2F32 panel_rect = cfg_target_rect_from_panel_node(content_rect, panel_tree_root, panel); + if(panel == panel_tree.zoom_root) + { + panel_rect = content_rect; + } + + ////////////////////////// + //- rjf: boundary tab-drag/drop sites + // + { + CFG_Node *drag_view = cfg_node_from_id(rd_state->drag_drop_regs->view); + if(rd_drag_is_active() && rd_state->drag_drop_regs_slot == RD_RegSlot_View && drag_view != &cfg_nil_node) { - Vec2F32 panel_rect_center = center_2f32(panel_rect); - Axis2 axis = axis2_flip(panel_tree.root->split_axis); - for EachEnumVal(Side, side) + //- rjf: params + F32 drop_site_major_dim_px = ceil_f32(ui_top_font_size()*7.f); + F32 drop_site_minor_dim_px = ceil_f32(ui_top_font_size()*5.f); + F32 corner_radius = ui_top_font_size()*0.5f; + F32 padding = ceil_f32(ui_top_font_size()*0.5f); + + //- rjf: special case - build Y boundary drop sites on root panel + // + // (this does not naturally follow from the below algorithm, since the + // root level panel only splits on X) + if(panel == panel_tree_root) UI_CornerRadius(corner_radius) { - UI_Key key = ui_key_from_stringf(ui_key_zero(), "root_extra_split_%i", side); - Rng2F32 site_rect = panel_rect; - site_rect.p0.v[axis2_flip(axis)] = panel_rect_center.v[axis2_flip(axis)] - drop_site_major_dim_px/2; - site_rect.p1.v[axis2_flip(axis)] = panel_rect_center.v[axis2_flip(axis)] + drop_site_major_dim_px/2; - site_rect.p0.v[axis] = panel_rect.v[side].v[axis] - drop_site_minor_dim_px/2; - site_rect.p1.v[axis] = panel_rect.v[side].v[axis] + drop_site_minor_dim_px/2; + Vec2F32 panel_rect_center = center_2f32(panel_rect); + Axis2 axis = axis2_flip(panel_tree_root->split_axis); + for EachEnumVal(Side, side) + { + UI_Key key = ui_key_from_stringf(ui_key_zero(), "root_extra_split_%i", side); + Rng2F32 site_rect = panel_rect; + site_rect.p0.v[axis2_flip(axis)] = panel_rect_center.v[axis2_flip(axis)] - drop_site_major_dim_px/2; + site_rect.p1.v[axis2_flip(axis)] = panel_rect_center.v[axis2_flip(axis)] + drop_site_major_dim_px/2; + site_rect.p0.v[axis] = panel_rect.v[side].v[axis] - drop_site_minor_dim_px/2; + site_rect.p1.v[axis] = panel_rect.v[side].v[axis] + drop_site_minor_dim_px/2; + + // rjf: build + UI_Box *site_box = &ui_nil_box; + { + F32 site_open_t = ui_anim(ui_key_from_stringf(key, "open_t"), 1.f, .rate = rd_state->menu_animation_rate); + UI_Rect(site_rect) UI_Squish(0.1f-0.1f*site_open_t) UI_Transparency(1-site_open_t) + { + site_box = ui_build_box_from_key(UI_BoxFlag_DropSite|UI_BoxFlag_DrawHotEffects, key); + ui_signal_from_box(site_box); + } + UI_Box *site_box_viz = &ui_nil_box; + UI_Parent(site_box) UI_WidthFill UI_HeightFill + UI_Padding(ui_px(padding, 1.f)) + UI_Column + UI_Padding(ui_px(padding, 1.f)) + UI_GroupKey(key) + { + ui_set_next_child_layout_axis(axis2_flip(axis)); + site_box_viz = ui_build_box_from_key(UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawDropShadow| + UI_BoxFlag_DrawBackgroundBlur| + UI_BoxFlag_DrawHotEffects, ui_key_zero()); + } + UI_Parent(site_box_viz) UI_WidthFill UI_HeightFill UI_Padding(ui_px(padding, 1.f)) + { + ui_set_next_child_layout_axis(axis); + UI_Box *row_or_column = ui_build_box_from_key(0, ui_key_zero()); UI_Parent(row_or_column) UI_Padding(ui_px(padding, 1.f)) + { + ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero()); + ui_spacer(ui_px(padding, 1.f)); + ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero()); + } + } + } + + // rjf: viz + if(ui_key_match(site_box->key, ui_drop_hot_key())) + { + Rng2F32 future_split_rect_target = site_rect; + future_split_rect_target.p0.v[axis] -= drop_site_major_dim_px; + future_split_rect_target.p1.v[axis] += drop_site_major_dim_px; + future_split_rect_target.p0.v[axis2_flip(axis)] = panel_rect.p0.v[axis2_flip(axis)]; + future_split_rect_target.p1.v[axis2_flip(axis)] = panel_rect.p1.v[axis2_flip(axis)]; + future_split_rect_target = pad_2f32(future_split_rect_target, -ui_top_font_size()*2.f); + Vec2F32 future_split_rect_target_center = center_2f32(future_split_rect_target); + Rng2F32 future_split_rect = + { + ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v0"), future_split_rect_target.x0, .initial = future_split_rect_target_center.x, .rate = rd_state->menu_animation_rate), + ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v1"), future_split_rect_target.y0, .initial = future_split_rect_target_center.y, .rate = rd_state->menu_animation_rate), + ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v2"), future_split_rect_target.x1, .initial = future_split_rect_target_center.x, .rate = rd_state->menu_animation_rate), + ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v3"), future_split_rect_target.y1, .initial = future_split_rect_target_center.y, .rate = rd_state->menu_animation_rate), + }; + UI_Rect(future_split_rect) UI_TagF("drop_site") UI_CornerRadius(ui_top_font_size()*2.f) + { + ui_build_box_from_key(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder, ui_key_zero()); + } + } + + // rjf: drop + if(ui_key_match(site_box->key, ui_drop_hot_key()) && rd_drag_drop()) + { + Dir2 dir = (axis == Axis2_Y ? (side == Side_Min ? Dir2_Up : Dir2_Down) : + axis == Axis2_X ? (side == Side_Min ? Dir2_Left : Dir2_Right) : + Dir2_Invalid); + if(dir != Dir2_Invalid) + { + CFG_PanelNode *split_panel = panel; + rd_cmd(RD_CmdKind_SplitPanel, + .dst_panel = split_panel->cfg->id, + .panel = rd_state->drag_drop_regs->panel, + .view = rd_state->drag_drop_regs->view, + .dir2 = dir); + } + } + } + } + + //- rjf: iterate all children, build boundary drop sites + Axis2 split_axis = panel->split_axis; + UI_CornerRadius(corner_radius) for(CFG_PanelNode *child = panel->first;; child = child->next) + { + // rjf: form rect + Rng2F32 child_rect = cfg_target_rect_from_panel_node_child(panel_rect, panel, child); + Vec2F32 child_rect_center = center_2f32(child_rect); + UI_Key key = ui_key_from_stringf(ui_key_zero(), "drop_boundary_%p_%p", panel->cfg, child->cfg); + Rng2F32 site_rect = r2f32(child_rect_center, child_rect_center); + site_rect.p0.v[split_axis] = child_rect.p0.v[split_axis] - drop_site_minor_dim_px/2; + site_rect.p1.v[split_axis] = child_rect.p0.v[split_axis] + drop_site_minor_dim_px/2; + site_rect.p0.v[axis2_flip(split_axis)] -= drop_site_major_dim_px/2; + site_rect.p1.v[axis2_flip(split_axis)] += drop_site_major_dim_px/2; // rjf: build UI_Box *site_box = &ui_nil_box; @@ -8300,7 +8405,7 @@ rd_window_frame(void) UI_Padding(ui_px(padding, 1.f)) UI_GroupKey(key) { - ui_set_next_child_layout_axis(axis2_flip(axis)); + ui_set_next_child_layout_axis(axis2_flip(split_axis)); site_box_viz = ui_build_box_from_key(UI_BoxFlag_DrawBackground| UI_BoxFlag_DrawBorder| UI_BoxFlag_DrawDropShadow| @@ -8309,7 +8414,7 @@ rd_window_frame(void) } UI_Parent(site_box_viz) UI_WidthFill UI_HeightFill UI_Padding(ui_px(padding, 1.f)) { - ui_set_next_child_layout_axis(axis); + ui_set_next_child_layout_axis(split_axis); UI_Box *row_or_column = ui_build_box_from_key(0, ui_key_zero()); UI_Parent(row_or_column) UI_Padding(ui_px(padding, 1.f)) { ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero()); @@ -8323,10 +8428,10 @@ rd_window_frame(void) if(ui_key_match(site_box->key, ui_drop_hot_key())) { Rng2F32 future_split_rect_target = site_rect; - future_split_rect_target.p0.v[axis] -= drop_site_major_dim_px; - future_split_rect_target.p1.v[axis] += drop_site_major_dim_px; - future_split_rect_target.p0.v[axis2_flip(axis)] = panel_rect.p0.v[axis2_flip(axis)]; - future_split_rect_target.p1.v[axis2_flip(axis)] = panel_rect.p1.v[axis2_flip(axis)]; + future_split_rect_target.p0.v[split_axis] -= drop_site_major_dim_px; + future_split_rect_target.p1.v[split_axis] += drop_site_major_dim_px; + future_split_rect_target.p0.v[axis2_flip(split_axis)] = child_rect.p0.v[axis2_flip(split_axis)]; + future_split_rect_target.p1.v[axis2_flip(split_axis)] = child_rect.p1.v[axis2_flip(split_axis)]; future_split_rect_target = pad_2f32(future_split_rect_target, -ui_top_font_size()*2.f); Vec2F32 future_split_rect_target_center = center_2f32(future_split_rect_target); Rng2F32 future_split_rect = @@ -8345,187 +8450,97 @@ rd_window_frame(void) // rjf: drop if(ui_key_match(site_box->key, ui_drop_hot_key()) && rd_drag_drop()) { - Dir2 dir = (axis == Axis2_Y ? (side == Side_Min ? Dir2_Up : Dir2_Down) : - axis == Axis2_X ? (side == Side_Min ? Dir2_Left : Dir2_Right) : - Dir2_Invalid); - if(dir != Dir2_Invalid) + Dir2 dir = (panel->split_axis == Axis2_X ? Dir2_Left : Dir2_Up); + CFG_PanelNode *split_panel = child; + if(split_panel == &cfg_nil_panel_node) { - CFG_PanelNode *split_panel = panel; - rd_cmd(RD_CmdKind_SplitPanel, - .dst_panel = split_panel->cfg->id, - .panel = rd_state->drag_drop_regs->panel, - .view = rd_state->drag_drop_regs->view, - .dir2 = dir); + split_panel = panel->last; + dir = (panel->split_axis == Axis2_X ? Dir2_Right : Dir2_Down); } + rd_cmd(RD_CmdKind_SplitPanel, + .dst_panel = split_panel->cfg->id, + .panel = rd_state->drag_drop_regs->panel, + .view = rd_state->drag_drop_regs->view, + .dir2 = dir); + } + + // rjf: exit on opl child + if(child == &cfg_nil_panel_node) + { + break; } } } - - //- rjf: iterate all children, build boundary drop sites - Axis2 split_axis = panel->split_axis; - UI_CornerRadius(corner_radius) for(CFG_PanelNode *child = panel->first;; child = child->next) - { - // rjf: form rect - Rng2F32 child_rect = cfg_target_rect_from_panel_node_child(panel_rect, panel, child); - Vec2F32 child_rect_center = center_2f32(child_rect); - UI_Key key = ui_key_from_stringf(ui_key_zero(), "drop_boundary_%p_%p", panel->cfg, child->cfg); - Rng2F32 site_rect = r2f32(child_rect_center, child_rect_center); - site_rect.p0.v[split_axis] = child_rect.p0.v[split_axis] - drop_site_minor_dim_px/2; - site_rect.p1.v[split_axis] = child_rect.p0.v[split_axis] + drop_site_minor_dim_px/2; - site_rect.p0.v[axis2_flip(split_axis)] -= drop_site_major_dim_px/2; - site_rect.p1.v[axis2_flip(split_axis)] += drop_site_major_dim_px/2; - - // rjf: build - UI_Box *site_box = &ui_nil_box; - { - F32 site_open_t = ui_anim(ui_key_from_stringf(key, "open_t"), 1.f, .rate = rd_state->menu_animation_rate); - UI_Rect(site_rect) UI_Squish(0.1f-0.1f*site_open_t) UI_Transparency(1-site_open_t) - { - site_box = ui_build_box_from_key(UI_BoxFlag_DropSite|UI_BoxFlag_DrawHotEffects, key); - ui_signal_from_box(site_box); - } - UI_Box *site_box_viz = &ui_nil_box; - UI_Parent(site_box) UI_WidthFill UI_HeightFill - UI_Padding(ui_px(padding, 1.f)) - UI_Column - UI_Padding(ui_px(padding, 1.f)) - UI_GroupKey(key) - { - ui_set_next_child_layout_axis(axis2_flip(split_axis)); - site_box_viz = ui_build_box_from_key(UI_BoxFlag_DrawBackground| - UI_BoxFlag_DrawBorder| - UI_BoxFlag_DrawDropShadow| - UI_BoxFlag_DrawBackgroundBlur| - UI_BoxFlag_DrawHotEffects, ui_key_zero()); - } - UI_Parent(site_box_viz) UI_WidthFill UI_HeightFill UI_Padding(ui_px(padding, 1.f)) - { - ui_set_next_child_layout_axis(split_axis); - UI_Box *row_or_column = ui_build_box_from_key(0, ui_key_zero()); UI_Parent(row_or_column) UI_Padding(ui_px(padding, 1.f)) - { - ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero()); - ui_spacer(ui_px(padding, 1.f)); - ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero()); - } - } - } - - // rjf: viz - if(ui_key_match(site_box->key, ui_drop_hot_key())) - { - Rng2F32 future_split_rect_target = site_rect; - future_split_rect_target.p0.v[split_axis] -= drop_site_major_dim_px; - future_split_rect_target.p1.v[split_axis] += drop_site_major_dim_px; - future_split_rect_target.p0.v[axis2_flip(split_axis)] = child_rect.p0.v[axis2_flip(split_axis)]; - future_split_rect_target.p1.v[axis2_flip(split_axis)] = child_rect.p1.v[axis2_flip(split_axis)]; - future_split_rect_target = pad_2f32(future_split_rect_target, -ui_top_font_size()*2.f); - Vec2F32 future_split_rect_target_center = center_2f32(future_split_rect_target); - Rng2F32 future_split_rect = - { - ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v0"), future_split_rect_target.x0, .initial = future_split_rect_target_center.x, .rate = rd_state->menu_animation_rate), - ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v1"), future_split_rect_target.y0, .initial = future_split_rect_target_center.y, .rate = rd_state->menu_animation_rate), - ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v2"), future_split_rect_target.x1, .initial = future_split_rect_target_center.x, .rate = rd_state->menu_animation_rate), - ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v3"), future_split_rect_target.y1, .initial = future_split_rect_target_center.y, .rate = rd_state->menu_animation_rate), - }; - UI_Rect(future_split_rect) UI_TagF("drop_site") UI_CornerRadius(ui_top_font_size()*2.f) - { - ui_build_box_from_key(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder, ui_key_zero()); - } - } - - // rjf: drop - if(ui_key_match(site_box->key, ui_drop_hot_key()) && rd_drag_drop()) - { - Dir2 dir = (panel->split_axis == Axis2_X ? Dir2_Left : Dir2_Up); - CFG_PanelNode *split_panel = child; - if(split_panel == &cfg_nil_panel_node) - { - split_panel = panel->last; - dir = (panel->split_axis == Axis2_X ? Dir2_Right : Dir2_Down); - } - rd_cmd(RD_CmdKind_SplitPanel, - .dst_panel = split_panel->cfg->id, - .panel = rd_state->drag_drop_regs->panel, - .view = rd_state->drag_drop_regs->view, - .dir2 = dir); - } - - // rjf: exit on opl child - if(child == &cfg_nil_panel_node) - { - break; - } - } - } - } - - ////////////////////////// - //- rjf: do UI for drag boundaries between all children - // - for(CFG_PanelNode *child = panel->first; - child != &cfg_nil_panel_node && child->next != &cfg_nil_panel_node; - child = child->next) - { - CFG_PanelNode *min_child = child; - CFG_PanelNode *max_child = min_child->next; - Rng2F32 min_child_rect = cfg_target_rect_from_panel_node_child(panel_rect, panel, min_child); - Rng2F32 max_child_rect = cfg_target_rect_from_panel_node_child(panel_rect, panel, max_child); - Rng2F32 boundary_rect = {0}; - { - boundary_rect.p0.v[split_axis] = min_child_rect.p1.v[split_axis] - ui_top_font_size()/3; - boundary_rect.p1.v[split_axis] = max_child_rect.p0.v[split_axis] + ui_top_font_size()/3; - boundary_rect.p0.v[axis2_flip(split_axis)] = panel_rect.p0.v[axis2_flip(split_axis)]; - boundary_rect.p1.v[axis2_flip(split_axis)] = panel_rect.p1.v[axis2_flip(split_axis)]; } - UI_Rect(boundary_rect) + ////////////////////////// + //- rjf: do UI for drag boundaries between all children + // + for(CFG_PanelNode *child = panel->first; + child != &cfg_nil_panel_node && child->next != &cfg_nil_panel_node; + child = child->next) { - ui_set_next_hover_cursor(split_axis == Axis2_X ? WM_Cursor_LeftRight : WM_Cursor_UpDown); - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "###%p_%p", min_child->cfg, max_child->cfg); - UI_Signal sig = ui_signal_from_box(box); - if(ui_double_clicked(sig)) + CFG_PanelNode *min_child = child; + CFG_PanelNode *max_child = min_child->next; + Rng2F32 min_child_rect = cfg_target_rect_from_panel_node_child(panel_rect, panel, min_child); + Rng2F32 max_child_rect = cfg_target_rect_from_panel_node_child(panel_rect, panel, max_child); + Rng2F32 boundary_rect = {0}; { - ui_kill_action(); - F32 sum_pct = min_child->pct_of_parent + max_child->pct_of_parent; - min_child->pct_of_parent = 0.5f * sum_pct; - max_child->pct_of_parent = 0.5f * sum_pct; - cfg_node_equip_stringf(rd_state->cfg, min_child->cfg, "%f", min_child->pct_of_parent); - cfg_node_equip_stringf(rd_state->cfg, max_child->cfg, "%f", max_child->pct_of_parent); + boundary_rect.p0.v[split_axis] = min_child_rect.p1.v[split_axis] - ui_top_font_size()/3; + boundary_rect.p1.v[split_axis] = max_child_rect.p0.v[split_axis] + ui_top_font_size()/3; + boundary_rect.p0.v[axis2_flip(split_axis)] = panel_rect.p0.v[axis2_flip(split_axis)]; + boundary_rect.p1.v[axis2_flip(split_axis)] = panel_rect.p1.v[axis2_flip(split_axis)]; } - else if(ui_pressed(sig)) + + UI_Rect(boundary_rect) { - Vec2F32 v = {min_child->pct_of_parent, max_child->pct_of_parent}; - ui_store_drag_struct(&v); - } - else if(ui_dragging(sig)) - { - Vec2F32 v = *ui_get_drag_struct(Vec2F32); - Vec2F32 mouse_delta = ui_drag_delta(); - F32 total_size = dim_2f32(panel_rect).v[split_axis]; - F32 min_pct__before = v.v[0]; - F32 min_pixels__before = min_pct__before * total_size; - F32 min_pixels__after = min_pixels__before + mouse_delta.v[split_axis]; - if(min_pixels__after < 50.f) + ui_set_next_hover_cursor(split_axis == Axis2_X ? WM_Cursor_LeftRight : WM_Cursor_UpDown); + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "###%p_%p", min_child->cfg, max_child->cfg); + UI_Signal sig = ui_signal_from_box(box); + if(ui_double_clicked(sig)) { - min_pixels__after = 50.f; + ui_kill_action(); + F32 sum_pct = min_child->pct_of_parent + max_child->pct_of_parent; + min_child->pct_of_parent = 0.5f * sum_pct; + max_child->pct_of_parent = 0.5f * sum_pct; + cfg_node_equip_stringf(rd_state->cfg, min_child->cfg, "%f", min_child->pct_of_parent); + cfg_node_equip_stringf(rd_state->cfg, max_child->cfg, "%f", max_child->pct_of_parent); } - F32 min_pct__after = min_pixels__after / total_size; - F32 pct_delta = min_pct__after - min_pct__before; - F32 max_pct__before = v.v[1]; - F32 max_pct__after = max_pct__before - pct_delta; - F32 max_pixels__after = max_pct__after * total_size; - if(max_pixels__after < 50.f) + else if(ui_pressed(sig)) { - max_pixels__after = 50.f; - max_pct__after = max_pixels__after / total_size; - pct_delta = -(max_pct__after - max_pct__before); - min_pct__after = min_pct__before + pct_delta; + Vec2F32 v = {min_child->pct_of_parent, max_child->pct_of_parent}; + ui_store_drag_struct(&v); + } + else if(ui_dragging(sig)) + { + Vec2F32 v = *ui_get_drag_struct(Vec2F32); + Vec2F32 mouse_delta = ui_drag_delta(); + F32 total_size = dim_2f32(panel_rect).v[split_axis]; + F32 min_pct__before = v.v[0]; + F32 min_pixels__before = min_pct__before * total_size; + F32 min_pixels__after = min_pixels__before + mouse_delta.v[split_axis]; + if(min_pixels__after < 50.f) + { + min_pixels__after = 50.f; + } + F32 min_pct__after = min_pixels__after / total_size; + F32 pct_delta = min_pct__after - min_pct__before; + F32 max_pct__before = v.v[1]; + F32 max_pct__after = max_pct__before - pct_delta; + F32 max_pixels__after = max_pct__after * total_size; + if(max_pixels__after < 50.f) + { + max_pixels__after = 50.f; + max_pct__after = max_pixels__after / total_size; + pct_delta = -(max_pct__after - max_pct__before); + min_pct__after = min_pct__before + pct_delta; + } + min_child->pct_of_parent = min_pct__after; + max_child->pct_of_parent = max_pct__after; + cfg_node_equip_stringf(rd_state->cfg, min_child->cfg, "%f", min_pct__after); + cfg_node_equip_stringf(rd_state->cfg, max_child->cfg, "%f", max_pct__after); + is_changing_panel_boundaries = 1; } - min_child->pct_of_parent = min_pct__after; - max_child->pct_of_parent = max_pct__after; - cfg_node_equip_stringf(rd_state->cfg, min_child->cfg, "%f", min_pct__after); - cfg_node_equip_stringf(rd_state->cfg, max_child->cfg, "%f", max_pct__after); - is_changing_panel_boundaries = 1; } } } @@ -8544,7 +8559,20 @@ rd_window_frame(void) panel != &cfg_nil_panel_node; panel = cfg_panel_node_rec__depth_first_pre(panel_tree.root, panel).next) { - Rng2F32 target_rect_px = cfg_target_rect_from_panel_node(content_rect, panel_tree.root, panel); + B32 is_in_zoom = 0; + for(CFG_PanelNode *p = panel; p != &cfg_nil_panel_node; p = p->parent) + { + if(p == panel_tree.zoom_root) + { + is_in_zoom = 1; + break; + } + } + Rng2F32 target_rect_px = cfg_target_rect_from_panel_node(content_rect, is_in_zoom ? panel_tree.zoom_root : panel_tree.root, panel); + if(panel == panel_tree.zoom_root) + { + target_rect_px = content_rect; + } Rng2F32 target_rect_pct = r2f32p(target_rect_px.x0/content_rect_dim.x, target_rect_px.y0/content_rect_dim.y, target_rect_px.x1/content_rect_dim.x, @@ -8565,12 +8593,23 @@ rd_window_frame(void) if(content_rect.x1 > content_rect.x0 && content_rect.y1 > content_rect.y0) { B32 any_disasm_open = 0; - ProfScope("leaf panel UI") - for(CFG_PanelNode *panel = panel_tree.root; - panel != &cfg_nil_panel_node; - panel = cfg_panel_node_rec__depth_first_pre(panel_tree.root, panel).next) + if(ws->panel_hoist_cfg_id != panel_tree.zoom_root->cfg->id && panel_tree.zoom_root->cfg->id != 0) { - if(panel->first != &cfg_nil_panel_node) {continue;} + ws->panel_hoist_cfg_id = panel_tree.zoom_root->cfg->id; + } + CFG_PanelNode *front_panel = cfg_panel_node_from_tree_cfg(panel_tree.root, cfg_node_from_id(ws->panel_hoist_cfg_id)); + CFG_PanelNode *panel_trees[] = {front_panel, panel_tree.root}; + F32 panel_zoom_t = ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_zoom_t"), (F32)(panel_tree.zoom_root != &cfg_nil_panel_node), .initial = (F32)(panel_tree.zoom_root != &cfg_nil_panel_node)); + ProfScope("leaf panel UI") + for EachElement(panel_tree_idx, panel_trees) + for(CFG_PanelNode *panel = panel_trees[panel_tree_idx]; + panel != &cfg_nil_panel_node; + panel = cfg_panel_node_rec__depth_first_pre(panel_trees[panel_tree_idx], panel).next) + { + if(panel_trees[panel_tree_idx] != front_panel && panel == front_panel) + { + continue; + } B32 panel_is_focused = (window_is_focused && !rd_state->popup_active && !ws->menu_bar_focused && @@ -8586,12 +8625,26 @@ rd_window_frame(void) } ProfScope("leaf panel UI work - %.*s: %.*s", str8_varg(selected_tab->string), str8_varg(rd_expr_from_cfg(selected_tab))) UI_Focus(panel_is_focused ? UI_FocusKind_Null : UI_FocusKind_Off) + UI_Transparency(panel_trees[panel_tree_idx] != front_panel ? panel_zoom_t : 0) { ////////////////////////// //- rjf: calculate UI rectangles // Vec2F32 content_rect_dim = dim_2f32(content_rect); - Rng2F32 target_rect_px = cfg_target_rect_from_panel_node(content_rect, panel_tree.root, panel); + B32 is_in_zoom = 0; + for(CFG_PanelNode *p = panel; p != &cfg_nil_panel_node; p = p->parent) + { + if(p == panel_tree.zoom_root) + { + is_in_zoom = 1; + break; + } + } + Rng2F32 target_rect_px = cfg_target_rect_from_panel_node(content_rect, is_in_zoom ? panel_tree.zoom_root : panel_tree.root, panel); + if(panel == panel_tree.zoom_root) + { + target_rect_px = content_rect; + } Rng2F32 target_rect_pct = r2f32p(target_rect_px.x0 / content_rect_dim.x, target_rect_px.y0 / content_rect_dim.y, target_rect_px.x1 / content_rect_dim.x, @@ -8623,9 +8676,17 @@ rd_window_frame(void) content_rect = intersect_2f32(content_rect, panel_rect); ////////////////////////// - //- rjf: decide to skip this panel (e.g. if it is too small + //- rjf: for non-leaf panels with focus (subtree selection) -> draw border for all children // - B32 build_panel = (content_rect.x1 > content_rect.x0 && content_rect.y1 > content_rect.y0); + if(panel->first != &cfg_nil_panel_node && panel == panel_tree.focused) UI_Rect(pad_2f32(panel_rect, ui_top_font_size()*0.2f)) UI_Focus(UI_FocusKind_On) + { + ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DisableFocusOverlay, "###"); + } + + ////////////////////////// + //- rjf: decide to skip this panel (e.g. if it non-leaf, or is too small) + // + B32 build_panel = ((panel->first == &cfg_nil_panel_node) && content_rect.x1 > content_rect.x0 && content_rect.y1 > content_rect.y0 && ui_top_transparency() < 0.99f); ////////////////////////// //- rjf: build combined split+movetab drag/drop sites @@ -8822,6 +8883,16 @@ rd_window_frame(void) ui_build_box_from_key(UI_BoxFlag_DrawBackground, ui_key_zero()); } + ////////////////////////// + //- rjf: determine how much this panel is occluding others + // + B32 is_occluding = 0; + if(panel_trees[panel_tree_idx] == front_panel) + { + is_occluding = (abs_f32(panel_rect.x0 - target_rect_px.x0) > 2 || + abs_f32(panel_rect.y0 - target_rect_px.y0) > 2); + } + ////////////////////////// //- rjf: build panel container box // @@ -8833,6 +8904,7 @@ rd_window_frame(void) UI_BoxFlag_Clip| UI_BoxFlag_DrawBorder| UI_BoxFlag_DisableFocusOverlay| + (is_occluding*UI_BoxFlag_DrawBackground)| ((panel_tree.focused != panel)*UI_BoxFlag_DisableFocusBorder), panel_key); } @@ -8971,6 +9043,70 @@ rd_window_frame(void) } } + ////////////////////////// + //- rjf: build zoom button + // + if(panel_is_focused) + { + UI_TextAlignment(UI_TextAlign_Center) + UI_TagF(".") + { + ui_set_next_child_layout_axis(Axis2_Y); + UI_Box *container = &ui_nil_box; + UI_Rect(r2f32p(tab_bar_rect.x1 - tab_bar_vheight, tab_bar_rect.y0, tab_bar_rect.x1, tab_bar_rect.y1)) + { + container = ui_build_box_from_stringf(0, "###zoom_panel"); + } + UI_Parent(container) + { + if(panel->tab_side == Side_Max) + { + ui_spacer(ui_px(tab_bar_rv_diff-1.f, 1.f)); + } + else + { + ui_spacer(ui_px(1.f, 1.f)); + } + { + UI_Box *zoom_box = &ui_nil_box; + RD_Font(RD_FontSlot_Icons) + UI_CornerRadius((tab_bar_vheight - tab_bar_vheight/4.f) / 3.f) + UI_VisualMargin(tab_bar_vheight/5.f) + UI_TagF("implicit") + UI_TagF("weak") + UI_FontSize(ui_top_font_size()*0.65f) + { + zoom_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects| + UI_BoxFlag_Clickable| + UI_BoxFlag_DisableTextTrunc, + "%S##zoom_panel_button_%p", + rd_icon_kind_text_table[panel_tree.zoom_root == panel ? RD_IconKind_WindowRestore : RD_IconKind_Window], + panel->cfg); + } + UI_Signal sig = ui_signal_from_box(zoom_box); + if(ui_pressed(sig)) + { + rd_cmd(RD_CmdKind_ToggleZoomPanel, .panel = panel->cfg->id); + } + if(ui_hovering(sig)) UI_Tooltip + { + ui_state->tooltip_anchor_key = zoom_box->key; + ui_set_next_pref_width(ui_children_sum(1)); + UI_Row + { + ui_labelf("Toggle Zoom Panel"); + rd_cmd_binding_buttons(rd_cmd_kind_info_table[RD_CmdKind_ToggleZoomPanel].string, s(""), 1, RD_CmdBindingButtonFlag_NoEdit); + } + } + } + } + } + } + ////////////////////////// //- rjf: compute tab build tasks // @@ -8987,7 +9123,7 @@ rd_window_frame(void) U64 tab_task_count = 0; F32 tab_close_width_px = ui_top_font_size()*2.5f; F32 max_tab_width_px = ui_top_font_size()*20.f; - if(build_panel) UI_TagF("tab") + UI_TagF("tab") { B32 reset = (ws->window_layout_reset || ws->frames_alive < 5 || is_changing_panel_boundaries); for(CFG_NodePtrNode *n = panel->tabs.first; n != 0; n = n->next) @@ -8999,19 +9135,24 @@ rd_window_frame(void) } UI_TagF(tab != panel->selected_tab ? "inactive" : "") { - TabTask *t = push_array(scratch.arena, TabTask, 1); - t->tab = tab; - t->fstrs = rd_title_fstrs_from_cfg(scratch.arena, tab, 0); - F32 tab_width_target = dr_dim_from_fstrs(ui_top_tab_size(), &t->fstrs).x + tab_close_width_px + ui_top_font_size()*1.f; + DR_FStrList tab_title_fstrs = rd_title_fstrs_from_cfg(scratch.arena, tab, 0); + F32 tab_width_target = dr_dim_from_fstrs(ui_top_tab_size(), &tab_title_fstrs).x + tab_close_width_px + ui_top_font_size()*1.f; B32 tab_is_selected = (tab == panel->selected_tab); if(tab_is_selected && panel_tree.focused == panel) { tab_width_target += tab_close_width_px; } tab_width_target = Min(max_tab_width_px, tab_width_target); - t->tab_width = floor_f32(ui_anim(ui_key_from_stringf(ui_key_zero(), "tab_width_%p", tab), tab_width_target, .initial = reset ? tab_width_target : 0, .rate = rd_state->menu_animation_rate)); - SLLQueuePush(first_tab_task, last_tab_task, t); - tab_task_count += 1; + F32 tab_width = floor_f32(ui_anim(ui_key_from_stringf(ui_key_zero(), "tab_width_%p", tab), tab_width_target, .initial = reset ? tab_width_target : 0, .rate = rd_state->menu_animation_rate)); + if(build_panel) + { + TabTask *t = push_array(scratch.arena, TabTask, 1); + t->tab = tab; + t->fstrs = tab_title_fstrs; + t->tab_width = tab_width; + SLLQueuePush(first_tab_task, last_tab_task, t); + tab_task_count += 1; + } } } } @@ -9361,6 +9502,7 @@ rd_window_frame(void) ////////////////////////// //- rjf: accept file drops // + if(build_panel) { for(UI_Event *evt = 0; ui_next_event(&evt);) { @@ -11459,9 +11601,9 @@ rd_init(CmdLine *cmdln) for(U64 idx = 1; idx < cmdln->argc; idx += 1) { String8 arg = str8_cstring(cmdln->argv[idx]); - B32 is_flag = (str8_match(str8_prefix(arg, 1), str8_lit("-"), 0) || - str8_match(str8_prefix(arg, 1), str8_lit("--"), 0) || - str8_match(str8_prefix(arg, 1), str8_lit("/"), 0)); + B32 is_flag = (str8_match(str8_prefix(arg, 1), s("-"), 0) || + str8_match(str8_prefix(arg, 1), s("--"), 0) || + str8_match(str8_prefix(arg, 1), s("/"), 0)); B32 is_cfg = 0; B32 is_dmp = 0; if(!is_flag && !after_first_non_flag) @@ -11479,12 +11621,12 @@ rd_init(CmdLine *cmdln) String8 header_suffix = {0}; header_suffix.str = header_suffix_buffer; header_suffix.size = file_read(file, r1u64(10, 10+256), header_suffix_buffer); - String8 header_type_suffix = str8_skip(header_suffix, str8_find_needle(header_suffix, 0, str8_lit(" "), 0)+1); - if(str8_match(header_type_suffix, str8_lit("user"), StringMatchFlag_RightSideSloppy)) + String8 header_type_suffix = str8_skip(header_suffix, str8_find_needle(header_suffix, 0, s(" "), 0)+1); + if(str8_match(header_type_suffix, s("user"), StringMatchFlag_RightSideSloppy)) { implicit_user_arg = path_absolute_dst_from_relative_dst_src(scratch.arena, arg, get_process_info()->initial_path); } - else if(str8_match(header_type_suffix, str8_lit("project"), StringMatchFlag_RightSideSloppy)) + else if(str8_match(header_type_suffix, s("project"), StringMatchFlag_RightSideSloppy)) { implicit_project_arg = path_absolute_dst_from_relative_dst_src(scratch.arena, arg, get_process_info()->initial_path); } @@ -14545,6 +14687,11 @@ rd_frame(void) CFG_Node *split_panel_prev = panel->prev->cfg; CFG_Node *new_parent = cfg_node_alloc(rd_state->cfg); CFG_Node *new_sibling = cfg_node_alloc(rd_state->cfg); + if(panel == panel_tree.zoom_root) + { + CFG_Node *zoom_panel = cfg_node_child_from_string_or_alloc(rd_state->cfg, split_panel, s("zoom_root")); + cfg_node_insert_child(rd_state->cfg, new_parent, &cfg_nil_node, zoom_panel); + } cfg_node_equip_string(rd_state->cfg, new_parent, split_panel->string); cfg_node_equip_string(rd_state->cfg, split_panel, str8_lit("0.5")); cfg_node_equip_string(rd_state->cfg, new_sibling, str8_lit("0.5")); @@ -14726,6 +14873,17 @@ rd_frame(void) } rd_cmd(RD_CmdKind_FocusPanel, .panel = next_focused->cfg->id); }break; + case RD_CmdKind_ParentPanel: + { + CFG_Node *panel = cfg_node_from_id(rd_regs()->panel); + CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, cfg_node_from_id(rd_regs()->window)); + CFG_PanelNode *panel_node = cfg_panel_node_from_tree_cfg(panel_tree.root, panel); + CFG_PanelNode *parent_node = panel_node->parent; + if(parent_node != &cfg_nil_panel_node) + { + rd_cmd(RD_CmdKind_FocusPanel, .panel = parent_node->cfg->id); + } + }break; case RD_CmdKind_FocusPanel: { CFG_Node *panel = cfg_node_from_id(rd_regs()->panel); @@ -14804,6 +14962,35 @@ rd_frame(void) } }break; + //- rjf: panel zooming + case RD_CmdKind_ToggleZoomPanel: + case RD_CmdKind_ZoomPanel: + case RD_CmdKind_UnzoomPanel: + { + CFG_Node *window = cfg_node_from_id(rd_regs()->window); + CFG_Node *panel = cfg_node_from_id(rd_regs()->panel); + CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, window); + RD_WindowState *ws = rd_window_state_from_cfg(window); + ws->panel_hoist_cfg_id = rd_regs()->panel; + + // rjf: remove existing zoom + B32 already_zoomed = 0; + { + CFG_PanelNode *zoom_root = panel_tree.zoom_root; + if(zoom_root->cfg != &cfg_nil_node) + { + already_zoomed = 1; + cfg_node_release(rd_state->cfg, cfg_node_child_from_string(zoom_root->cfg, s("zoom_root"))); + } + } + + // rjf: not already zoomed? -> zoom + if(!already_zoomed || kind == RD_CmdKind_ZoomPanel) + { + cfg_node_child_from_string_or_alloc(rd_state->cfg, panel, s("zoom_root")); + } + }break; + //- rjf: undo/redo case RD_CmdKind_Undo:{}break; case RD_CmdKind_Redo:{}break; @@ -18595,13 +18782,14 @@ rd_frame(void) F32 scrolling_animations_f = (F32)!!rd_setting_b32_from_name(str8_lit("scrolling_animations")); F32 tooltip_animations_f = (F32)!!rd_setting_b32_from_name(str8_lit("tooltip_animations")); F32 menu_animations_f = (F32)!!rd_setting_b32_from_name(str8_lit("menu_animations")); - rd_state->catchall_animation_rate = 1 - master_animations_f*pow_f32(2, (-60.f * rd_state->frame_dt)); - rd_state->menu_animation_rate = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-70.f * rd_state->frame_dt)); - rd_state->menu_animation_rate__slow = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-50.f * rd_state->frame_dt)); - rd_state->entity_alive_animation_rate = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-30.f * rd_state->frame_dt)); - rd_state->rich_hover_animation_rate = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-50.f * rd_state->frame_dt)); - rd_state->scrolling_animation_rate = 1 - master_animations_f*scrolling_animations_f*pow_f32(2, (-60.f * rd_state->frame_dt)); - rd_state->tooltip_animation_rate = 1 - master_animations_f*tooltip_animations_f*pow_f32(2, (-60.f * rd_state->frame_dt)); + rd_state->catchall_animation_rate = 1 - master_animations_f*pow_f32(2, (-60.f * rd_state->frame_dt)); + rd_state->menu_animation_rate = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-70.f * rd_state->frame_dt)); + rd_state->menu_animation_rate__slow = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-50.f * rd_state->frame_dt)); + rd_state->menu_animation_rate__very_slow = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-20.f * rd_state->frame_dt)); + rd_state->entity_alive_animation_rate = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-30.f * rd_state->frame_dt)); + rd_state->rich_hover_animation_rate = 1 - master_animations_f*menu_animations_f*pow_f32(2, (-50.f * rd_state->frame_dt)); + rd_state->scrolling_animation_rate = 1 - master_animations_f*scrolling_animations_f*pow_f32(2, (-60.f * rd_state->frame_dt)); + rd_state->tooltip_animation_rate = 1 - master_animations_f*tooltip_animations_f*pow_f32(2, (-60.f * rd_state->frame_dt)); } ////////////////////////////// diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index d155c441..8b6ba824 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -324,6 +324,9 @@ struct RD_WindowState CFG_ID query_last_bottom_view_id; RD_QueryView *query_top; + // rjf: panel 'hoisting' state - bring-to-front + CFG_ID panel_hoist_cfg_id; + // rjf: hover eval state B32 hover_eval_focused; Arena *hover_eval_arena; @@ -457,6 +460,7 @@ struct RD_State F32 catchall_animation_rate; F32 menu_animation_rate; F32 menu_animation_rate__slow; + F32 menu_animation_rate__very_slow; F32 entity_alive_animation_rate; F32 rich_hover_animation_rate; F32 scrolling_animation_rate; diff --git a/src/raddbg_server/raddbg_server_main.c b/src/raddbg_server/raddbg_server_main.c index 4374eee2..aa82857c 100644 --- a/src/raddbg_server/raddbg_server_main.c +++ b/src/raddbg_server/raddbg_server_main.c @@ -48,7 +48,7 @@ struct RDS_Msg { RDS_Msg *next; SOCK_Endpoint endpoint; - String8 msg_raw; + String8 packet; }; typedef struct RDS_Client RDS_Client; @@ -87,9 +87,6 @@ struct RDS_State Arena *c2d_arena; RDS_Msg *c2d_msg_first; RDS_Msg *c2d_msg_last; - - //- rjf: demon run state - B32 demon_is_running; }; global RDS_State *rds_state = 0; @@ -120,7 +117,7 @@ dmn_thread__entry_point(void *p) RDS_Msg *dst = push_array(scratch.arena, RDS_Msg, 1); SLLQueuePush(msg_first, msg_last, dst); dst->endpoint = src->endpoint; - dst->msg_raw = str8_copy(scratch.arena, src->msg_raw); + dst->packet = str8_copy(scratch.arena, src->packet); } arena_clear(rds_state->c2d_arena); rds_state->c2d_msg_first = rds_state->c2d_msg_last = 0; @@ -135,7 +132,7 @@ dmn_thread__entry_point(void *p) for(RDS_Msg *msg_n = msg_first; msg_n != 0; msg_n = msg_n->next) { //- rjf: unpack message - DS_Msg msg = ds_msg_from_packet(scratch.arena, msg_n->msg_raw); + DS_Msg msg = ds_msg_from_packet(scratch.arena, msg_n->packet); //- rjf: do action, get response DS_Response response = {0}; @@ -197,6 +194,7 @@ dmn_thread__entry_point(void *p) } //- rjf: pack response & send back to the originating endpoint of the message + if(msg_n->endpoint.port != 0) { String8 data = ds_packet_from_response(scratch.arena, &response); sock_send(rds_state->sock_session, SOCK_Protocol_TCP, msg_n->endpoint, data, max_U64); @@ -205,7 +203,7 @@ dmn_thread__entry_point(void *p) //- rjf: pack events & send to all connected client if(events.count != 0) { - String8 data = ds_serialized_from_dmn_events(scratch.arena, events); + String8 data = ds_packet_from_dmn_events(scratch.arena, events); for(RDS_Client *c = rds_state->first_client; c != 0; c = c->order_next) { sock_send(rds_state->sock_session, SOCK_Protocol_TCP, c->endpoint, data, max_U64); @@ -223,6 +221,8 @@ dmn_thread__entry_point(void *p) internal void entry_point(CmdLine *cmd_line) { + Temp scratch = scratch_begin(0, 0); + //- rjf: get port argument U64 port = 7273; { @@ -234,6 +234,42 @@ entry_point(CmdLine *cmd_line) } } + //- rjf: get launch targets from command line + typedef struct Target Target; + struct Target + { + Target *next; + String8List cmd_line; + }; + Target *first_target = 0; + Target *last_target = 0; + { + B32 past_first_non_flag = 0; + for(U64 idx = 1; idx < cmd_line->argc; idx += 1) + { + String8 arg = str8_cstring(cmd_line->argv[idx]); + B32 is_flag = (str8_match(str8_prefix(arg, 1), s("-"), 0) || + str8_match(str8_prefix(arg, 1), s("--"), 0) || + str8_match(str8_prefix(arg, 1), s("/"), 0)); + past_first_non_flag = (past_first_non_flag || !is_flag); + if(past_first_non_flag) + { + // rjf: create new target after ;s, or if we don't have one + if(str8_match(arg, s(";"), 0) || first_target == 0) + { + Target *t = push_array(scratch.arena, Target, 1); + SLLQueuePush(first_target, last_target, t); + } + + // rjf: push this string to the most recently added target + if(last_target != 0) + { + str8_list_push(scratch.arena, &last_target->cmd_line, arg); + } + } + } + } + //- rjf: set up shared state Arena *arena = arena_alloc(); rds_state = push_array(arena, RDS_State, 1); @@ -242,10 +278,41 @@ entry_point(CmdLine *cmd_line) rds_state->c2d_rw_mutex = rw_mutex_alloc(); rds_state->c2d_cv = cond_var_alloc(); rds_state->c2d_arena = arena_alloc(); + rds_state->client_slots_count = 64; + rds_state->client_slots = push_array(arena, RDS_ClientSlot, rds_state->client_slots_count); //- rjf: launch demon thread thread_launch(dmn_thread__entry_point, 0); + //- rjf: do initial launch of all command-line-specified targets + { + String8 current_path = get_current_path(scratch.arena); + for EachNode(t, Target, first_target) + { + fprintf(stderr, "[launching command line target]\n"); + fprintf(stderr, " "); + for EachNode(n, String8Node, t->cmd_line.first) + { + fprintf(stderr, " %.*s", str8_varg(n->string)); + } + fprintf(stderr, "\n"); + DS_Msg msg = + { + .kind = DS_MsgKind_Launch, + .command_line = t->cmd_line, + .path = current_path, + }; + String8 packet = ds_packet_from_msg(scratch.arena, &msg); + RWMutexScope(rds_state->c2d_rw_mutex, 1) + { + RDS_Msg *msg = push_array(rds_state->c2d_arena, RDS_Msg, 1); + msg->packet = str8_copy(rds_state->c2d_arena, packet); + SLLQueuePush(rds_state->c2d_msg_first, rds_state->c2d_msg_last, msg); + } + cond_var_broadcast(rds_state->c2d_cv); + } + } + //- rjf: loop: wait for messages, issue commands to demon for(;;) { @@ -318,15 +385,35 @@ entry_point(CmdLine *cmd_line) if(peek_kind < DS_MsgKind_Launch || DS_MsgKind_SingleStep < peek_kind) { DS_Msg msg = ds_msg_from_packet(scratch.arena, data); + + // rjf: form base response info DS_Response response = {0}; + response.msg_id = msg.msg_id; + response.good = 1; + + // rjf: do message specifics switch(msg.kind) { default:{}break; + + //- rjf: response + case DS_MsgKind_Handshake: + { + // TODO(rjf): send initial state + }break; + + //- rjf: halting case DS_MsgKind_Halt: { dmn_halt(0, 0); }break; } + + // rjf: send response + { + String8 packet = ds_packet_from_response(scratch.arena, &response); + sock_send(rds_state->sock_session, protocol, endpoint, packet, now_time_us()+10000000); + } } } @@ -335,14 +422,16 @@ entry_point(CmdLine *cmd_line) { RWMutexScope(rds_state->c2d_rw_mutex, 1) { - String8 msg_raw = str8_skip(data, sizeof(header)); RDS_Msg *msg = push_array(rds_state->c2d_arena, RDS_Msg, 1); msg->endpoint = endpoint; - msg->msg_raw = str8_copy(rds_state->c2d_arena, msg_raw); + msg->packet = str8_copy(rds_state->c2d_arena, data); + SLLQueuePush(rds_state->c2d_msg_first, rds_state->c2d_msg_last, msg); } cond_var_broadcast(rds_state->c2d_cv); } scratch_end(scratch); } + + scratch_end(scratch); } From 8036f7bed03f096e19cdf02ba0c90cda77ed4e16 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 14 Oct 2026 21:17:38 -0700 Subject: [PATCH 245/251] raddbg: eliminate preemptive glyph rasterization. we actually just pay more for it than we get. --- src/raddbg/raddbg_core.c | 42 ---------------------------------------- 1 file changed, 42 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 41075094..60ff7229 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -5859,48 +5859,6 @@ rd_window_frame(void) ws->font_slot_raster_flags[RD_FontSlot_Code] = (rd_setting_b32_from_name(str8_lit("smooth_code_text"))*FNT_RasterFlag_Smooth)|(rd_setting_b32_from_name(str8_lit("hint_code_text"))*FNT_RasterFlag_Hinted); } - ////////////////////////////// - //- rjf: @window_frame_part pre-emptively rasterize common glyphs on the first frame - // - if(rd_state->first_window_state == ws && rd_state->last_window_state == ws && ws->frames_alive == 0) - { - F32 font_size = rd_font_size(); - RD_FontSlot english_font_slots[] = {RD_FontSlot_Main, RD_FontSlot_Code}; - RD_FontSlot icon_font_slot = RD_FontSlot_Icons; - for(U64 idx = 0; idx < ArrayCount(english_font_slots); idx += 1) - { - Temp scratch = scratch_begin(0, 0); - RD_FontSlot slot = english_font_slots[idx]; - String8 sample_text = str8_lit("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890~!@#$%^&*()-_+=[{]}\\|;:'\",<.>/?"); - fnt_run_from_string(rd_font_from_slot(slot), - font_size, - 0, 0, 0, - sample_text); - fnt_run_from_string(rd_font_from_slot(slot), - font_size, - 0, 0, 0, - sample_text); - scratch_end(scratch); - } - for(RD_IconKind icon_kind = RD_IconKind_Null; icon_kind < RD_IconKind_COUNT; icon_kind = (RD_IconKind)(icon_kind+1)) - { - Temp scratch = scratch_begin(0, 0); - fnt_run_from_string(rd_font_from_slot(icon_font_slot), - font_size, - 0, 0, FNT_RasterFlag_Smooth, - rd_icon_kind_text_table[icon_kind]); - fnt_run_from_string(rd_font_from_slot(icon_font_slot), - font_size, - 0, 0, FNT_RasterFlag_Smooth, - rd_icon_kind_text_table[icon_kind]); - fnt_run_from_string(rd_font_from_slot(icon_font_slot), - font_size, - 0, 0, FNT_RasterFlag_Smooth, - rd_icon_kind_text_table[icon_kind]); - scratch_end(scratch); - } - } - ////////////////////////////// //- rjf: @window_frame_part commit window's position/status to underlying cfg tree // From 0d44ddea29a57fa6884a3749a7c59d9f5593a26a Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 15 Oct 2026 13:23:18 -0700 Subject: [PATCH 246/251] dbg_engine: multi-server debugging checkpoint - hash cache of (server ID -> state), where state is {ID * dmn event queue}. when dequeueing demon events, pull from servers in round-robin style before running. when running, resume-all. if the local server returns -> halt all remote servers. if a remote server returns before local -> halt local. still need to correctly supply run controls to all servers (dbg_engine run controls need to not just be dmn but multi-server), and need to adjust halting logic such that all remote servers are halted if another remote server generated an event too. --- project.raddbg | 2 +- src/dbg_engine/dbg_engine_core.c | 16 +- src/dbg_engine/dbg_engine_core.h | 2 + src/dbg_engine/dbg_engine_ctrl.c | 776 ++++++++++++++++--------- src/dbg_engine/dbg_engine_ctrl.h | 59 +- src/dbg_engine/dbg_engine_user.c | 5 +- src/dbg_engine/dbg_engine_user.h | 2 + src/dbg_server/dbg_server.c | 234 ++++++-- src/dbg_server/dbg_server.h | 36 +- src/demon/demon.c | 14 + src/demon/demon.h | 1 + src/linux/socket/linux_socket.c | 5 +- src/raddbg/raddbg_core.c | 38 +- src/raddbg_server/raddbg_server_main.c | 30 +- src/socket/socket.c | 6 + src/socket/socket.h | 1 + src/win32/socket/win32_socket.c | 5 +- 17 files changed, 842 insertions(+), 390 deletions(-) diff --git a/project.raddbg b/project.raddbg index ef217025..0e4d6ca1 100755 --- a/project.raddbg +++ b/project.raddbg @@ -14,7 +14,7 @@ target: executable: "build/raddbg_server.exe" working_directory: build debug_subprocesses: 1 - arguments: "mule_main.exe" + enabled: 1 } target: { diff --git a/src/dbg_engine/dbg_engine_core.c b/src/dbg_engine/dbg_engine_core.c index 7968a0c2..8d552ae6 100644 --- a/src/dbg_engine/dbg_engine_core.c +++ b/src/dbg_engine/dbg_engine_core.c @@ -104,7 +104,7 @@ d_init(void) Arena *arena = arena_alloc(); d_ctrl_state = push_array(arena, D_CtrlState, 1); d_ctrl_state->arena = arena; - d_ctrl_state->sock_session = sock_session_open(0, 0); + d_ctrl_state->sock_session = sock_session_open(0, d_ctrl_sock_wakeup); for EachEnumVal(Arch, arch) { ARCH_Info *arch_info = arch_info_from_arch(arch); @@ -145,9 +145,21 @@ d_init(void) d_ctrl_state->ctrl_thread_entity_store = d_entity_ctx_rw_store_alloc(); d_ctrl_state->ctrl_thread_eval_cache = e_cache_alloc(); d_ctrl_state->ctrl_thread_msg_process_arena = arena_alloc(); + d_ctrl_state->server_slots_count = 16; + d_ctrl_state->server_slots = push_array(arena, D_ServerSlot, d_ctrl_state->server_slots_count); + { + D_ServerState *ss = push_array(arena, D_ServerState, 1); + ss->server_id = D_ServerID_LocalHost; + ss->dmn_event_arena = arena_alloc(); + U64 slot_idx = ss->server_id%d_ctrl_state->server_slots_count; + DLLPushBack_NP(d_ctrl_state->first_server, d_ctrl_state->last_server, ss, order_next, order_prev); + DLLPushBack_NP(d_ctrl_state->server_slots[slot_idx].first, d_ctrl_state->server_slots[slot_idx].last, ss, hash_next, hash_prev); + d_ctrl_state->next_dequeue_server = ss; + } +#if 0 d_ctrl_state->dmn_event_arena = arena_alloc(); +#endif d_ctrl_state->user_entry_point_arena = arena_alloc(); - d_ctrl_state->dbg_dir_arena = arena_alloc(); for(D_ExceptionCodeKind k = (D_ExceptionCodeKind)0; k < D_ExceptionCodeKind_COUNT; k = (D_ExceptionCodeKind)(k+1)) { if(d_exception_code_kind_default_enable_table[k]) diff --git a/src/dbg_engine/dbg_engine_core.h b/src/dbg_engine/dbg_engine_core.h index 8e789810..c8726de9 100644 --- a/src/dbg_engine/dbg_engine_core.h +++ b/src/dbg_engine/dbg_engine_core.h @@ -251,8 +251,10 @@ struct D_Target String8 stdout_path; String8 stderr_path; String8 stdin_path; + String8 remote_address; D_MsgID msg_id; B32 debug_subprocesses; + B32 deploy; String8List env; }; diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 81a2703a..033b7651 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -380,8 +380,13 @@ d_serialized_string_from_msg_list(Arena *arena, D_MsgList *msgs) str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_inherit); str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->debug_subprocesses); str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->auto_download_debug_info); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->deploy); str8_serial_push_array (scratch.arena, &msgs_srlzed, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); + // rjf: write address string + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->address.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, msg->address.str, msg->address.size); + // rjf: write path string str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->path.size); str8_serial_push_data(scratch.arena, &msgs_srlzed, msg->path.str, msg->path.size); @@ -481,8 +486,14 @@ d_msg_list_from_serialized_string(Arena *arena, String8 string) read_off += str8_deserial_read_struct(string, read_off, &msg->env_inherit); read_off += str8_deserial_read_struct(string, read_off, &msg->debug_subprocesses); read_off += str8_deserial_read_struct(string, read_off, &msg->auto_download_debug_info); + read_off += str8_deserial_read_struct(string, read_off, &msg->deploy); read_off += str8_deserial_read_array (string, read_off, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); + // rjf: read address string + read_off += str8_deserial_read_struct(string, read_off, &msg->address.size); + msg->address.str = push_array_no_zero(arena, U8, msg->address.size); + read_off += str8_deserial_read(string, read_off, msg->address.str, msg->address.size, 1); + // rjf: read path string read_off += str8_deserial_read_struct(string, read_off, &msg->path.size); msg->path.str = push_array_no_zero(arena, U8, msg->path.size); @@ -2879,158 +2890,170 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D ProfBeginFunction(); DMN_Event *event = push_array(arena, DMN_Event, 1); Temp scratch = scratch_begin(&arena, 1); - D_EntityCtx *entity_ctx = &d_ctrl_state->ctrl_thread_entity_store->ctx; - //- rjf: loop -> try to get event, run, repeat + ////////////////////////////// + //- rjf: loop -> try to get event, run if we don't have any, repeat + // U64 spoof_old_ip_value = 0; - ProfScope("loop -> try to get event, run, repeat") for(B32 got_event = 0; got_event == 0;) + ProfScope("loop -> try to get event, run, repeat") for(B32 got_event = 0; !got_event;) { + //////////////////////////// //- rjf: get next event + // ProfScope("get next event") { - // rjf: grab first event - DMN_EventNode *next_event_node = d_ctrl_state->first_dmn_event_node; - - // rjf: log event - if(next_event_node != 0) + B32 first = 1; + for(D_ServerState *ss = d_ctrl_state->next_dequeue_server; + ss != d_ctrl_state->next_dequeue_server || first; + (first = 0, ss = (ss->order_next == 0 ? d_ctrl_state->first_server : ss->order_next), d_ctrl_state->next_dequeue_server = ss)) { - DMN_Event *ev = &next_event_node->v; - LogInfoNamedBlockF("dmn_event") + //- rjf: grab first event from this server + DMN_EventNode *next_event_node = ss->first_dmn_event_node; + if(next_event_node != 0) { - log_infof("kind: %S\n", dmn_event_kind_string_table[ev->kind]); - log_infof("exception_kind: %S\n", dmn_exception_kind_string_table[ev->exception_kind]); - log_infof("process: [%I64u]\n", ev->process.u64[0]); - log_infof("thread: [%I64u]\n", ev->thread.u64[0]); - log_infof("module: [%I64u]\n", ev->module.u64[0]); - log_infof("arch: %S\n", string_from_arch(ev->arch)); - log_infof("address: 0x%I64x\n", ev->address); - log_infof("string: \"%S\"\n", ev->string); - log_infof("ip_vaddr: 0x%I64x\n", ev->instruction_pointer); - } - } - - // rjf: determine if we should filter - B32 should_filter_event = 0; - if(next_event_node != 0) - { - DMN_Event *ev = &next_event_node->v; - switch(ev->kind) - { - default:{}break; - case DMN_EventKind_Exception: + DMN_Event *ev = &next_event_node->v; + LogInfoNamedBlockF("dmn_event") { - // NOTE(rjf): first chance exceptions -> try ignoring - should_filter_event = (ev->exception_repeated == 0 && (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)); - - // rjf: exception code -> kind - D_ExceptionCodeKind code_kind = D_ExceptionCodeKind_Null; - if(should_filter_event) + log_infof("kind: %S\n", dmn_event_kind_string_table[ev->kind]); + log_infof("exception_kind: %S\n", dmn_exception_kind_string_table[ev->exception_kind]); + log_infof("process: [%I64u]\n", ev->process.u64[0]); + log_infof("thread: [%I64u]\n", ev->thread.u64[0]); + log_infof("module: [%I64u]\n", ev->module.u64[0]); + log_infof("arch: %S\n", string_from_arch(ev->arch)); + log_infof("address: 0x%I64x\n", ev->address); + log_infof("string: \"%S\"\n", ev->string); + log_infof("ip_vaddr: 0x%I64x\n", ev->instruction_pointer); + } + } + + //- rjf: determine if we should filter + B32 should_filter_event = 0; + if(next_event_node != 0) + { + DMN_Event *ev = &next_event_node->v; + switch(ev->kind) + { + default:{}break; + case DMN_EventKind_Exception: { - for(D_ExceptionCodeKind k = (D_ExceptionCodeKind)0; k < D_ExceptionCodeKind_COUNT; k = (D_ExceptionCodeKind)(k+1)) + // NOTE(rjf): first chance exceptions -> try ignoring + should_filter_event = (ev->exception_repeated == 0 && (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)); + + // rjf: exception code -> kind + D_ExceptionCodeKind code_kind = D_ExceptionCodeKind_Null; + if(should_filter_event) { - if(d_exception_code_kind_code_table[k] == ev->code) + for(D_ExceptionCodeKind k = (D_ExceptionCodeKind)0; k < D_ExceptionCodeKind_COUNT; k = (D_ExceptionCodeKind)(k+1)) { - code_kind = k; - break; - } - } - } - - // rjf: exception code kind -> shouldn't stop? if so, do not filter - if(should_filter_event) - { - B32 shouldnt_filter = !!(d_ctrl_state->exception_code_filters[code_kind/64] & (1ull<<(code_kind%64))); - if(should_filter_event && shouldnt_filter) - { - should_filter_event = 0; - } - } - - // rjf: special case: be gracious with ASan modules or symbols if - // they do their cute little 0xc0000005 exception trick... - if(!should_filter_event && ev->code == 0xc0000005 && - (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)) - { - Access *access = access_open(); - D_Handle process_handle = d_handle_from_dmn(D_ServerID_LocalHost, ev->process); - D_Entity *process = d_entity_from_handle(process_handle); - D_Entity *module = d_module_from_process_vaddr(process, ev->instruction_pointer); - if(module != &d_entity_nil) - { - // rjf: determine base address of asan shadow space - U64 asan_shadow_base_vaddr = 0; - B32 asan_shadow_variable_exists_but_is_zero = 0; - DI_Key dbgi_key = d_dbgi_key_from_module(module); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, max_U64); - RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_GlobalVariables); - { - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, unparsed_map, &map); - String8 name = str8_lit("__asan_shadow_memory_dynamic_address"); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - if(node != 0) + if(d_exception_code_kind_code_table[k] == ev->code) { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - RDI_Symbol *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, ids[0]); - U64 global_var_voff = rdi_voff_from_location(global_var->location); - U64 global_var_vaddr = global_var_voff + module->vaddr_range.min; - Arch arch = process->arch; - U64 addr_size = bit_size_from_arch(arch)/8; - d_process_read(process_handle, r1u64(global_var_vaddr, global_var_vaddr+addr_size), &asan_shadow_base_vaddr); - asan_shadow_variable_exists_but_is_zero = (asan_shadow_base_vaddr == 0); - } + code_kind = k; + break; } } - - // rjf: determine if this was a read/write to the shadow space - B32 violation_in_shadow_space = 0; - if(asan_shadow_base_vaddr != 0) - { - U64 asan_shadow_space_size = TB(128)/8; - if(asan_shadow_base_vaddr <= ev->address && ev->address < asan_shadow_base_vaddr+asan_shadow_space_size) - { - violation_in_shadow_space = 1; - } - } - - // rjf: filter event if this violation occurred in asan's shadow space - if(violation_in_shadow_space || asan_shadow_variable_exists_but_is_zero) - { - should_filter_event = 1; - } } - access_close(access); - } - }break; + // rjf: exception code kind -> shouldn't stop? if so, do not filter + if(should_filter_event) + { + B32 shouldnt_filter = !!(d_ctrl_state->exception_code_filters[code_kind/64] & (1ull<<(code_kind%64))); + if(should_filter_event && shouldnt_filter) + { + should_filter_event = 0; + } + } + + // rjf: special case: be gracious with ASan modules or symbols if + // they do their cute little 0xc0000005 exception trick... + if(!should_filter_event && ev->code == 0xc0000005 && (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)) + { + Access *access = access_open(); + D_Handle process_handle = d_handle_from_dmn(D_ServerID_LocalHost, ev->process); + D_Entity *process = d_entity_from_handle(process_handle); + D_Entity *module = d_module_from_process_vaddr(process, ev->instruction_pointer); + if(module != &d_entity_nil) + { + // rjf: determine base address of asan shadow space + U64 asan_shadow_base_vaddr = 0; + B32 asan_shadow_variable_exists_but_is_zero = 0; + DI_Key dbgi_key = d_dbgi_key_from_module(module); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, max_U64); + RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_GlobalVariables); + { + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, unparsed_map, &map); + String8 name = str8_lit("__asan_shadow_memory_dynamic_address"); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + RDI_Symbol *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, ids[0]); + U64 global_var_voff = rdi_voff_from_location(global_var->location); + U64 global_var_vaddr = global_var_voff + module->vaddr_range.min; + Arch arch = process->arch; + U64 addr_size = bit_size_from_arch(arch)/8; + d_process_read(process_handle, r1u64(global_var_vaddr, global_var_vaddr+addr_size), &asan_shadow_base_vaddr); + asan_shadow_variable_exists_but_is_zero = (asan_shadow_base_vaddr == 0); + } + } + } + + // rjf: determine if this was a read/write to the shadow space + B32 violation_in_shadow_space = 0; + if(asan_shadow_base_vaddr != 0) + { + U64 asan_shadow_space_size = TB(128)/8; + if(asan_shadow_base_vaddr <= ev->address && ev->address < asan_shadow_base_vaddr+asan_shadow_space_size) + { + violation_in_shadow_space = 1; + } + } + + // rjf: filter event if this violation occurred in asan's shadow space + if(violation_in_shadow_space || asan_shadow_variable_exists_but_is_zero) + { + should_filter_event = 1; + } + } + + access_close(access); + } + }break; + } + } + + //- rjf: good event & unfiltered? -> pop from queue & grab as result + if(next_event_node != 0 && !should_filter_event) + { + got_event = 1; + SLLQueuePop(ss->first_dmn_event_node, ss->last_dmn_event_node); + dmn_event_deep_copy(arena, event, &next_event_node->v); + run_ctrls->ignore_previous_exception = 1; + } + + //- rjf: good event but filtered? pop from queue, but we don't have a result yet. + if(next_event_node != 0 && should_filter_event) + { + SLLQueuePop(ss->first_dmn_event_node, ss->last_dmn_event_node); + run_ctrls->ignore_previous_exception = 0; + } + + //- rjf: out of queued up demon events -> clear event arena + if(ss->first_dmn_event_node == 0) + { + ss->free_dmn_event_node = 0; + arena_clear(ss->dmn_event_arena); } - } - - // rjf: good event & unfiltered? -> pop from queue & grab as result - if(next_event_node != 0 && !should_filter_event) - { - got_event = 1; - SLLQueuePop(d_ctrl_state->first_dmn_event_node, d_ctrl_state->last_dmn_event_node); - MemoryCopyStruct(event, &next_event_node->v); - event->string = push_str8_copy(arena, event->string); - run_ctrls->ignore_previous_exception = 1; - } - - // rjf: good event but filtered? pop from queue - if(next_event_node != 0 && should_filter_event) - { - SLLQueuePop(d_ctrl_state->first_dmn_event_node, d_ctrl_state->last_dmn_event_node); - run_ctrls->ignore_previous_exception = 0; } } - //- rjf: no event -> dmn_ctrl_run for a new one - if(got_event == 0) ProfScope("no event -> dmn_ctrl_run for a new one") + //- rjf: no event -> run all servers until we get a new one + if(!got_event) ProfScope("no event -> run all servers until we get a new one") { - // rjf: prep spoof + //- rjf: prep spoof B32 do_spoof = (spoof != 0 && dmn_handle_match(run_ctrls->single_step_thread, dmn_handle_zero())); U64 size_of_spoof = 0; if(do_spoof) ProfScope("prep spoof") @@ -3041,16 +3064,17 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D d_process_read(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); } - // rjf: set spoof + //- rjf: set spoof if(do_spoof) ProfScope("set spoof") { d_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof->new_ip_value); } - // rjf: run for new events + //- rjf: run for new events ProfScope("run for new events") { - LogInfoNamedBlockF("dmn_ctrl_run") + //- rjf: log run controls + LogInfoNamedBlockF("run") { log_infof("single_step_thread: [0x%I64x]\n", run_ctrls->single_step_thread); log_infof("ignore_previous_exception: %i\n", !!run_ctrls->ignore_previous_exception); @@ -3071,41 +3095,131 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D } } + //- rjf: send run message to all *remote* servers - shouldn't matter much, but send starting at the + // current server position, so that we also fairly order the runs (assuming equal network latency) + U64 num_servers_running = 0; + { + B32 first = 1; + for(D_ServerState *ss = d_ctrl_state->next_dequeue_server; + ss != d_ctrl_state->next_dequeue_server || first; + (first = 0, ss = (ss->order_next == 0 ? d_ctrl_state->first_server : ss->order_next), d_ctrl_state->next_dequeue_server = ss)) + { + if(ss->server_id != D_ServerID_LocalHost) + { + // rjf: unpack server + D_Handle server_handle = {.server_id = ss->server_id}; + D_Entity *server = d_entity_from_handle(server_handle); + String8 server_addr = server->string; + SOCK_Endpoint server_endpoint = sock_endpoint_from_string(server_addr); + + // rjf: form run message + DS_Msg msg = {0}; + { + msg.kind = DS_MsgKind_Run; + // TODO(rjf) + } + + // rjf: message -> packet + String8 packet = ds_packet_from_msg(scratch.arena, &msg); + + // rjf: send run message + if(!sock_send(d_ctrl_state->sock_session, SOCK_Protocol_TCP, server_endpoint, packet, now_time_us()+10000000)) + { + // TODO(rjf) + } + else + { + num_servers_running += 1; + } + } + } + } + + //- rjf: run the local server synchronously - if we get a response from another server, it will + // halt the local one. + d_ctrl_state->ctrl_thread_dmn_run_block = 1; DMN_EventList events = dmn_ctrl_run(scratch.arena, ctrl_ctx, run_ctrls); + d_ctrl_state->ctrl_thread_dmn_run_block = 0; + + //- rjf: if we got significant events back (anything except a NotAttached), then we + // need to halt all connected remote servers. + if(events.first != 0 && (events.first->v.kind != DMN_EventKind_Error || events.first->v.error_kind != DMN_ErrorKind_NotAttached)) + { + B32 first = 1; + for(D_ServerState *ss = d_ctrl_state->next_dequeue_server; + ss != d_ctrl_state->next_dequeue_server || first; + (first = 0, ss = (ss->order_next == 0 ? d_ctrl_state->first_server : ss->order_next), d_ctrl_state->next_dequeue_server = ss)) + { + if(ss->server_id != D_ServerID_LocalHost) + { + // rjf: unpack server + D_Handle server_handle = {.server_id = ss->server_id}; + D_Entity *server = d_entity_from_handle(server_handle); + String8 server_addr = server->string; + SOCK_Endpoint server_endpoint = sock_endpoint_from_string(server_addr); + + // rjf: form halt message / packet + DS_Msg msg = {DS_MsgKind_Halt}; + String8 packet = ds_packet_from_msg(scratch.arena, &msg); + + // rjf: send + if(!sock_send(d_ctrl_state->sock_session, SOCK_Protocol_TCP, server_endpoint, packet, now_time_us()+10000000)) + { + // TODO(rjf) + } + } + } + } + + //- rjf: copy locally returned events to local server queue + { + D_ServerID server_id = D_ServerID_LocalHost; + U64 slot_idx = server_id%d_ctrl_state->server_slots_count; + for(D_ServerState *ss = d_ctrl_state->server_slots[slot_idx].first; ss != 0; ss = ss->order_next) + { + if(ss->server_id == server_id) + { + for EachNode(src_n, DMN_EventNode, events.first) + { + DMN_EventNode *dst_n = ss->free_dmn_event_node; + if(dst_n != 0) + { + SLLStackPop(ss->free_dmn_event_node); + } + else + { + dst_n = push_array(ss->dmn_event_arena, DMN_EventNode, 1); + } + dmn_event_deep_copy(ss->dmn_event_arena, &dst_n->v, &src_n->v); + SLLQueuePush(ss->first_dmn_event_node, ss->last_dmn_event_node, dst_n); + } + } + } + } + + //- rjf: wait for all running servers to stop (either we're waiting on them for events, *or* we generated + // a halt, and we're waiting for that) + if(events.first != 0 && events.count == 1 && events.first->v.kind == DMN_EventKind_Error && events.first->v.error_kind == DMN_ErrorKind_NotAttached) + { + for(;num_servers_running > 0;) + { + // TODO(rjf) + } + } + + //- rjf: bump all debuggee state generations ins_atomic_u64_inc_eval(&d_ctrl_state->mem_gen); ins_atomic_u64_inc_eval(&d_ctrl_state->reg_gen); ins_atomic_u64_inc_eval(&d_ctrl_state->run_gen); - for(DMN_EventNode *src_n = events.first; src_n != 0; src_n = src_n->next) - { - DMN_EventNode *dst_n = d_ctrl_state->free_dmn_event_node; - if(dst_n != 0) - { - SLLStackPop(d_ctrl_state->free_dmn_event_node); - } - else - { - dst_n = push_array(d_ctrl_state->dmn_event_arena, DMN_EventNode, 1); - } - MemoryCopyStruct(&dst_n->v, &src_n->v); - dst_n->v.string = push_str8_copy(d_ctrl_state->dmn_event_arena, dst_n->v.string); - if(src_n->v.module_info != &dmn_module_info_nil) - { - dst_n->v.module_info = push_array(d_ctrl_state->dmn_event_arena, DMN_ModuleInfo, 1); - MemoryCopyStruct(dst_n->v.module_info, src_n->v.module_info); - dst_n->v.module_info->module_path = str8_copy(d_ctrl_state->dmn_event_arena, dst_n->v.module_info->module_path); - dst_n->v.module_info->debug_info_path = str8_copy(d_ctrl_state->dmn_event_arena, dst_n->v.module_info->debug_info_path); - } - SLLQueuePush(d_ctrl_state->first_dmn_event_node, d_ctrl_state->last_dmn_event_node, dst_n); - } } - // rjf: unset spoof + //- rjf: unset spoof if(do_spoof) ProfScope("unset spoof") { d_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); } - // rjf: irrespective of what event came back, we should ALWAYS check the + //- rjf: irrespective of what event came back, we should ALWAYS check the // spoof's thread and see if it hit the spoof address, because we may have // simply been sent other debug events first if(spoof != 0) @@ -3157,7 +3271,6 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->arch = event->arch; out_evt->entity_id = event->code; out_evt->os = OperatingSystem_CURRENT; // TODO: operating system of the remote target machine - d_ctrl_state->process_counter += 1; }break; //- rjf: create thread @@ -3350,7 +3463,6 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D out_evt->msg_id = msg->msg_id; out_evt->entity = d_handle_from_dmn(D_ServerID_LocalHost, event->process); out_evt->u64_code = event->code; - d_ctrl_state->process_counter -= 1; }break; //- rjf: exit thread @@ -3470,21 +3582,6 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D } d_c2u_push_events(&evts); - //- rjf: if this is the first process in a session, clear the debug directory - // cache state - if(d_ctrl_state->process_counter == 1 && event->kind == DMN_EventKind_CreateProcess) - { - arena_clear(d_ctrl_state->dbg_dir_arena); - d_ctrl_state->dbg_dir_root = push_array(d_ctrl_state->dbg_dir_arena, D_DbgDirNode, 1); - } - - //- rjf: out of queued up demon events -> clear event arena - if(d_ctrl_state->first_dmn_event_node == 0) - { - d_ctrl_state->free_dmn_event_node = 0; - arena_clear(d_ctrl_state->dmn_event_arena); - } - scratch_end(scratch); ProfEnd(); return(event); @@ -3803,6 +3900,126 @@ d_ctrl_thread__eval_scope_end(D_EvalScope *scope) access_close(scope->access); } +//- rjf: server connection lookups + +internal D_Handle +d_ctrl_thread__handle_from_addr_or_connect(String8 addr) +{ + D_Handle handle = {0}; + + //- rjf: try to find existing handle for this server address + B32 found = 0; + { + D_EntityArray servers = d_entity_array_from_kind(D_EntityKind_Server); + for EachIndex(idx, servers.count) + { + if(str8_match(servers.v[idx]->string, addr, 0)) + { + handle = servers.v[idx]->handle; + found = 1; + break; + } + } + } + + //- rjf: if not found, connect to server & handshake + if(!found) + { + SOCK_Endpoint endpoint = sock_endpoint_from_string(addr); + + // rjf: handshake & get state + B32 good_handshake = 1; + { + Temp scratch = scratch_begin(0, 0); + + // rjf: send handshake packet + { + DS_Msg server_msg = {0}; + { + server_msg.kind = DS_MsgKind_Handshake; + server_msg.msg_id = 1; + } + String8 send_packet = ds_packet_from_msg(scratch.arena, &server_msg); + sock_send(d_ctrl_state->sock_session, SOCK_Protocol_TCP, endpoint, send_packet, max_U64); + } + + // rjf: receive state packet(s) + { + SOCK_Protocol recv_protocol = SOCK_Protocol_TCP; + SOCK_Endpoint recv_endpoint = {0}; + String8 recv_packet = {0}; + sock_recv(scratch.arena, d_ctrl_state->sock_session, &recv_protocol, &recv_endpoint, &recv_packet, now_time_us()+10000000); + if(recv_packet.size == 0) + { + good_handshake = 0; + } + } + + // rjf: report failure + if(!good_handshake) + { + log_user_errorf("Couldn't successfully connect to server at %S (%S).", sock_string_from_endpoint(scratch.arena, endpoint), addr); + } + scratch_end(scratch); + } + + // rjf: push connect event + D_ServerID server_id = 0; + if(good_handshake) + { + Temp scratch = scratch_begin(0, 0); + D_EventList evts = {0}; + D_Event *evt = d_event_list_push(scratch.arena, &evts); + d_ctrl_state->server_id_gen += 1; + server_id = D_ServerID_LocalHost + d_ctrl_state->server_id_gen; + evt->kind = D_EventKind_ServerConnect; + evt->entity_id = server_id; + evt->string = addr; + d_c2u_push_events(&evts); + scratch_end(scratch); + } + + // rjf: set up server state + if(good_handshake) + { + D_ServerState *ss = d_ctrl_state->free_server; + if(ss != 0) + { + SLLStackPop_N(d_ctrl_state->free_server, order_next); + } + else + { + ss = push_array_no_zero(d_ctrl_state->arena, D_ServerState, 1); + } + MemoryZeroStruct(ss); + ss->server_id = server_id; + ss->dmn_event_arena = arena_alloc(); + DLLPushBack_NP(d_ctrl_state->first_server, d_ctrl_state->last_server, ss, order_next, order_prev); + U64 slot_idx = server_id%d_ctrl_state->server_slots_count; + DLLPushBack_NP(d_ctrl_state->server_slots[slot_idx].first, d_ctrl_state->server_slots[slot_idx].last, ss, hash_next, hash_prev); + } + + // rjf: form handle + handle.server_id = server_id; + } + + return handle; +} + +//- rjf: control system socket wakeup function + +internal void +d_ctrl_sock_wakeup(void) +{ + // NOTE(rjf): if we receive something from a socket while in a run state + // (e.g. blocked locally on a dmn_ctrl_run), then we want to generate a + // halt, so that we can wake up and process the received packet. + if(d_ctrl_state->ctrl_thread_dmn_run_block) + { + dmn_halt(0, 0); + } +} + //- rjf: log flusher internal void @@ -3827,56 +4044,7 @@ d_ctrl_thread__end_and_flush_log(void) internal void d_ctrl_thread__open_remote_server(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { - SOCK_Endpoint endpoint = sock_endpoint_from_string(msg->path); - - //- rjf: handshake & get state - B32 good_handshake = 1; - { - Temp scratch = scratch_begin(0, 0); - - // rjf: send handshake packet - { - DS_Msg server_msg = {0}; - { - server_msg.kind = DS_MsgKind_Handshake; - server_msg.msg_id = 1; - } - String8 send_packet = ds_packet_from_msg(scratch.arena, &server_msg); - sock_send(d_ctrl_state->sock_session, SOCK_Protocol_TCP, endpoint, send_packet, max_U64); - } - - // rjf: receive state packet(s) - { - SOCK_Protocol recv_protocol = SOCK_Protocol_TCP; - SOCK_Endpoint recv_endpoint = {0}; - String8 recv_packet = {0}; - sock_recv(scratch.arena, d_ctrl_state->sock_session, &recv_protocol, &recv_endpoint, &recv_packet, now_time_us()+10000000); - if(recv_packet.size == 0) - { - good_handshake = 0; - } - } - - // rjf: report failure - if(!good_handshake) - { - log_user_errorf("Couldn't successfully connect to server at %S (%S).", sock_string_from_endpoint(scratch.arena, endpoint), msg->path); - } - scratch_end(scratch); - } - - //- rjf: push connect event - { - Temp scratch = scratch_begin(0, 0); - D_EventList evts = {0}; - D_Event *evt = d_event_list_push(scratch.arena, &evts); - d_ctrl_state->server_id_gen += 1; - evt->kind = D_EventKind_ServerConnect; - evt->entity_id = D_ServerID_LocalHost + d_ctrl_state->server_id_gen; - evt->string = msg->path; - d_c2u_push_events(&evts); - scratch_end(scratch); - } + d_ctrl_thread__handle_from_addr_or_connect(msg->address); } internal void @@ -3897,52 +4065,69 @@ d_ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { Temp scratch = scratch_begin(0, 0); - //- rjf: produce full stdout/stderr/stdin paths - String8 stdout_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stdout_path, msg->path); - String8 stdin_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stdin_path, msg->path); - String8 stderr_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stderr_path, msg->path); + ////////////////////////////// + //- rjf: message -> launch parameters for debug servers + // + DS_LaunchParams launch_params = + { + .command_line = msg->cmd_line_string_list, + .path = msg->path, + .env = msg->env_string_list, + .stdout_path = msg->stdout_path, + .stdin_path = msg->stdin_path, + .stderr_path = msg->stderr_path, + .debug_subprocesses = msg->debug_subprocesses, + .inherit_env = msg->env_inherit, + }; - //- rjf: obtain stdout/stderr/stdin handles - File stdout_handle = {0}; - File stderr_handle = {0}; - File stdin_handle = {0}; - if(stdout_path.size != 0) + ////////////////////////////// + //- rjf: remote? -> get server handle + // + D_Handle server = {.server_id = D_ServerID_LocalHost}; + if(msg->address.size != 0) { - File f = file_open(AccessFlag_Write|AccessFlag_Read, stdout_path); - file_close(f); - stdout_handle = file_open(AccessFlag_Write|AccessFlag_Append|AccessFlag_ShareRead|AccessFlag_ShareWrite|AccessFlag_Inherited, msg->stdout_path); - } - if(stderr_path.size != 0) - { - File f = file_open(AccessFlag_Write|AccessFlag_Read, stderr_path); - file_close(f); - stderr_handle = file_open(AccessFlag_Write|AccessFlag_Append|AccessFlag_ShareRead|AccessFlag_ShareWrite|AccessFlag_Inherited, msg->stderr_path); - } - if(stdin_path.size != 0) - { - stdin_handle = file_open(AccessFlag_Read|AccessFlag_ShareRead|AccessFlag_ShareWrite|AccessFlag_Inherited, stdin_path); + server = d_ctrl_thread__handle_from_addr_or_connect(msg->address); } - //- rjf: launch - ProcessLaunchParams params = {0}; + ////////////////////////////// + //- rjf: remote? -> send message to launch, wait on new PID + // + U64 pid = 0; + if(msg->address.size != 0) { - params.cmd_line = msg->cmd_line_string_list; - params.path = msg->path; - params.env = msg->env_string_list; - params.inherit_env = msg->env_inherit; - params.debug_subprocesses = msg->debug_subprocesses; - params.stdout_file = stdout_handle; - params.stderr_file = stderr_handle; - params.stdin_file = stdin_handle; + SOCK_Endpoint server_endpoint = sock_endpoint_from_string(msg->address); + DS_Msg server_msg = {DS_MsgKind_Launch, .launch_params = &launch_params}; + String8 server_packet = ds_packet_from_msg(scratch.arena, &server_msg); + String8 server_response_packet = {0}; + SOCK_Endpoint server_response_endpoint = {0}; + if(!sock_send(d_ctrl_state->sock_session, SOCK_Protocol_TCP, server_endpoint, server_packet, now_time_us()+10000000)) + { + log_user_errorf("Couldn't successfully send launch command to %S (%S).", sock_string_from_endpoint(scratch.arena, server_endpoint), msg->address); + } + else if(!sock_recv(scratch.arena, d_ctrl_state->sock_session, 0, &server_response_endpoint, &server_response_packet, now_time_us()+10000000)) + { + log_user_errorf("Didn't receive launch command response from %S (%S) in time.", sock_string_from_endpoint(scratch.arena, server_endpoint), msg->address); + } + else + { + DS_Response response = ds_response_from_packet(scratch.arena, server_response_packet); + pid = response.pid; + } } - U32 id = dmn_ctrl_launch(ctrl_ctx, ¶ms); - //- rjf: close stdout/stderr/stdin files - file_close(stdout_handle); - file_close(stderr_handle); - file_close(stdin_handle); + ////////////////////////////// + //- rjf: local? -> launch locally + // + else + { + pid = ds_ctrl_launch(ctrl_ctx, &launch_params); + } + ////////////////////////////// //- rjf: record (id -> entry points), and (id -> msg_id) so that we know custom entry points for this PID + // + // TODO(rjf): PID is not unique across machines. these should be under a server. + // D_EntityCtxRWStore *entity_ctx_rw_store = d_ctrl_state->ctrl_thread_entity_store; MutexScopeW(d_ctrl_state->ctrl_thread_entity_ctx_rw_mutex) { @@ -3950,11 +4135,11 @@ d_ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) { { String8 string = n->string; - D_Entity *entry = d_entity_alloc(entity_ctx_rw_store, entity_ctx_rw_store->ctx.root, D_EntityKind_EntryPoint, Arch_Null, d_handle_zero(), (U64)id); + D_Entity *entry = d_entity_alloc(entity_ctx_rw_store, entity_ctx_rw_store->ctx.root, D_EntityKind_EntryPoint, Arch_Null, d_handle_zero(), pid); d_entity_equip_string(entity_ctx_rw_store, entry, string); } { - D_Entity *entry = d_entity_alloc(entity_ctx_rw_store, entity_ctx_rw_store->ctx.root, D_EntityKind_LaunchMsgID, Arch_Null, d_handle_zero(), (U64)id); + D_Entity *entry = d_entity_alloc(entity_ctx_rw_store, entity_ctx_rw_store->ctx.root, D_EntityKind_LaunchMsgID, Arch_Null, d_handle_zero(), pid); entry->src_msg_id = msg->msg_id; } } @@ -3969,35 +4154,50 @@ d_ctrl_thread__attach(DMN_CtrlCtx *ctrl_ctx, D_Msg *msg) ProfBeginFunction(); Temp scratch = scratch_begin(0, 0); - //- rjf: attach - B32 attach_successful = dmn_ctrl_attach(ctrl_ctx, msg->entity_id); - - //- rjf: run to handshake - if(attach_successful) + ////////////////////////////// + //- rjf: remote? -> send message to attach, wait on success + // + B32 attach_successful = 0; + if(msg->address.size != 0) { - DMN_Handle unfrozen_process = {0}; - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities_are_processes = 1; - for(B32 done = 0; done == 0;) + // TODO(rjf) + } + + ////////////////////////////// + //- rjf: local? -> attach locally + // + else + { + //- rjf: attach + attach_successful = dmn_ctrl_attach(ctrl_ctx, msg->entity_id); + + //- rjf: run to handshake + if(attach_successful) { - DMN_Event *event = d_ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - switch(event->kind) + DMN_Handle unfrozen_process = {0}; + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities_are_processes = 1; + for(B32 done = 0; done == 0;) { - default:{}break; - case DMN_EventKind_CreateProcess: + DMN_Event *event = d_ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + switch(event->kind) { - unfrozen_process = event->process; - run_ctrls.run_entities = &unfrozen_process; - run_ctrls.run_entity_count = 1; - }break; - case DMN_EventKind_Halt: - case DMN_EventKind_Exception: - case DMN_EventKind_Error: - case DMN_EventKind_HandshakeComplete: - { - done = 1; - }break; + default:{}break; + case DMN_EventKind_CreateProcess: + { + unfrozen_process = event->process; + run_ctrls.run_entities = &unfrozen_process; + run_ctrls.run_entity_count = 1; + }break; + case DMN_EventKind_Halt: + case DMN_EventKind_Exception: + case DMN_EventKind_Error: + case DMN_EventKind_HandshakeComplete: + { + done = 1; + }break; + } } } } diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index cb6241d5..64a98138 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -313,6 +313,45 @@ struct D_ModuleReqCacheNode #define D_WAKEUP_FUNCTION_DEF(name) void name(void) typedef D_WAKEUP_FUNCTION_DEF(D_WakeupFunctionType); +//////////////////////////////// +//~ rjf: Server State Types +// +// Each call to `dmn_ctrl_run` can generate a sequence of debug events. We handle +// these one at a time, and so after a `dmn_ctrl_run`, we need to store all returned +// events in a queue, and process the full queue before doing another `dmn_ctrl_run`. +// +// Initially, there was just one of these queues, but on moving to multi-server +// debugging, we will have a sequence of events generated per-server. Then, we +// will just want to round-robin drain the queues before we dispatch another run +// to all servers. +// + +typedef struct D_ServerState D_ServerState; +struct D_ServerState +{ + // rjf: links + D_ServerState *hash_next; + D_ServerState *hash_prev; + D_ServerState *order_next; + D_ServerState *order_prev; + + // rjf: server identifier + D_ServerID server_id; + + // rjf: demon event queue + Arena *dmn_event_arena; + DMN_EventNode *first_dmn_event_node; + DMN_EventNode *last_dmn_event_node; + DMN_EventNode *free_dmn_event_node; +}; + +typedef struct D_ServerSlot D_ServerSlot; +struct D_ServerSlot +{ + D_ServerState *first; + D_ServerState *last; +}; + //////////////////////////////// //~ rjf: Main State Types @@ -326,7 +365,7 @@ struct D_CtrlState // rjf: name -> register hash tables for eval E_String2NumMap arch_string2reg_tables[Arch_COUNT]; - // rjf: caches + // rjf: shared caches D_ThreadRegCache thread_reg_cache; D_ModuleInfoCache module_info_cache; D_DumpCache dump_cache; @@ -363,18 +402,24 @@ struct D_CtrlState D_EntityCtxRWStore *ctrl_thread_entity_store; E_Cache *ctrl_thread_eval_cache; Arena *ctrl_thread_msg_process_arena; + U64 server_slots_count; + D_ServerSlot *server_slots; + D_ServerState *first_server; + D_ServerState *last_server; + D_ServerState *free_server; + D_ServerState *next_dequeue_server; + B32 ctrl_thread_dmn_run_block; +#if 0 Arena *dmn_event_arena; DMN_EventNode *first_dmn_event_node; DMN_EventNode *last_dmn_event_node; DMN_EventNode *free_dmn_event_node; +#endif Arena *user_entry_point_arena; String8List user_entry_points; U64 exception_code_filters[(D_ExceptionCodeKind_COUNT+63)/64]; B32 auto_download_debug_info; - U64 process_counter; D_ServerID server_id_gen; - Arena *dbg_dir_arena; - D_DbgDirNode *dbg_dir_root; U64 module_req_cache_slots_count; D_ModuleReqCacheNode **module_req_cache_slots; String8List msg_user_bp_touched_files; @@ -622,6 +667,12 @@ internal B32 d_ctrl_eval_space_read(E_Space space, void *out, E_SpaceRangeInfo * internal D_EvalScope *d_ctrl_thread__eval_scope_begin(Arena *arena, D_BreakpointList *user_bps, D_Entity *thread); internal void d_ctrl_thread__eval_scope_end(D_EvalScope *scope); +//- rjf: server connection lookups +internal D_Handle d_ctrl_thread__handle_from_addr_or_connect(String8 addr); + +//- rjf: control system socket wakeup function +internal void d_ctrl_sock_wakeup(void); + //- rjf: log flusher internal void d_ctrl_thread__end_and_flush_log(void); diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c index c0314859..fd821c44 100644 --- a/src/dbg_engine/dbg_engine_user.c +++ b/src/dbg_engine/dbg_engine_user.c @@ -1774,6 +1774,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P String8 stdout_path = str8_skip_chop_whitespace(target->stdout_path); String8 stderr_path = str8_skip_chop_whitespace(target->stderr_path); String8 stdin_path = str8_skip_chop_whitespace(target->stdin_path); + String8 remote_address = str8_skip_chop_whitespace(target->remote_address); String8List env = target->env; if(working_directory.size == 0) { @@ -1813,12 +1814,14 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P D_Msg *msg = d_msg_list_push(scratch.arena, &ctrl_msgs); msg->msg_id = target->msg_id; msg->kind = D_MsgKind_Launch; + msg->address = str8_copy(scratch.arena, remote_address); msg->path = str8_copy(scratch.arena, working_directory); msg->cmd_line_string_list = str8_list_copy(scratch.arena, &cmdln_strings); msg->stdout_path = str8_copy(scratch.arena, stdout_path); msg->stderr_path = str8_copy(scratch.arena, stderr_path); msg->stdin_path = str8_copy(scratch.arena, stdin_path); msg->debug_subprocesses = target->debug_subprocesses; + msg->deploy = target->deploy; msg->env_inherit = 1; MemoryCopyArray(msg->exception_code_filters, exception_code_filters); msg->auto_download_debug_info = auto_download_debug_info; @@ -2197,7 +2200,7 @@ d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_P { D_Msg *msg = d_msg_list_push(scratch.arena, &ctrl_msgs); msg->kind = D_MsgKind_OpenRemoteServer; - msg->path = str8_copy(scratch.arena, params->string); + msg->address = str8_copy(scratch.arena, params->string); MemoryCopyArray(msg->exception_code_filters, exception_code_filters); msg->auto_download_debug_info = auto_download_debug_info; }break; diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h index 3c02a872..d2f7d1ed 100644 --- a/src/dbg_engine/dbg_engine_user.h +++ b/src/dbg_engine/dbg_engine_user.h @@ -197,7 +197,9 @@ struct D_Msg B32 env_inherit; B32 debug_subprocesses; B32 auto_download_debug_info; + B32 deploy; U64 exception_code_filters[(D_ExceptionCodeKind_COUNT+63)/64]; + String8 address; String8 path; String8List entry_points; String8List cmd_line_string_list; diff --git a/src/dbg_server/dbg_server.c b/src/dbg_server/dbg_server.c index d2960b0b..16f3fc05 100644 --- a/src/dbg_server/dbg_server.c +++ b/src/dbg_server/dbg_server.c @@ -31,40 +31,65 @@ ds_msg_from_packet(Arena *arena, String8 data) off += str8_deserial_read_struct(payload, off, &msg.entity); off += str8_deserial_read_struct(payload, off, &msg.pid); - // rjf: read command line strings + // rjf: read launch parameters extension { - U64 cmd_line_string_count = 0; - off += str8_deserial_read_struct(payload, off, &cmd_line_string_count); - for EachIndex(idx, cmd_line_string_count) + B8 has_launch_params = 0; + off += str8_deserial_read_struct(payload, off, &has_launch_params); + if(has_launch_params) { - String8 string = {0}; - off += str8_deserial_read_struct(payload, off, &string.size); - string = str8_substr(payload, r1u64(off, off+string.size)); - off += string.size; - str8_list_push(arena, &msg.command_line, string); - } - } - - // rjf: read path - { - String8 string = {0}; - off += str8_deserial_read_struct(payload, off, &string.size); - string = str8_substr(payload, r1u64(off, off+string.size)); - off += string.size; - msg.path = string; - } - - // rjf: read environment strings - { - U64 env_string_count = 0; - off += str8_deserial_read_struct(payload, off, &env_string_count); - for EachIndex(idx, env_string_count) - { - String8 string = {0}; - off += str8_deserial_read_struct(payload, off, &string.size); - string = str8_substr(payload, r1u64(off, off+string.size)); - off += string.size; - str8_list_push(arena, &msg.env, string); + msg.launch_params = push_array(arena, DS_LaunchParams, 1); + + // rjf: read command line strings + { + U64 cmd_line_string_count = 0; + off += str8_deserial_read_struct(payload, off, &cmd_line_string_count); + for EachIndex(idx, cmd_line_string_count) + { + String8 string = {0}; + off += str8_deserial_read_struct(payload, off, &string.size); + string = str8_substr(payload, r1u64(off, off+string.size)); + off += string.size; + str8_list_push(arena, &msg.launch_params->command_line, string); + } + } + + // rjf: read path + { + off += str8_deserial_read_struct(payload, off, &msg.launch_params->path.size); + msg.launch_params->path = str8_substr(payload, r1u64(off, off+msg.launch_params->path.size)); + off += msg.launch_params->path.size; + } + + // rjf: read environment strings + { + U64 env_string_count = 0; + off += str8_deserial_read_struct(payload, off, &env_string_count); + for EachIndex(idx, env_string_count) + { + String8 string = {0}; + off += str8_deserial_read_struct(payload, off, &string.size); + string = str8_substr(payload, r1u64(off, off+string.size)); + off += string.size; + str8_list_push(arena, &msg.launch_params->env, string); + } + } + + // rjf: read std i/o paths + { + off += str8_deserial_read_struct(payload, off, &msg.launch_params->stdout_path.size); + msg.launch_params->stdout_path = str8_substr(payload, r1u64(off, off+msg.launch_params->stdout_path.size)); + off += msg.launch_params->stdout_path.size; + off += str8_deserial_read_struct(payload, off, &msg.launch_params->stdin_path.size); + msg.launch_params->stdin_path = str8_substr(payload, r1u64(off, off+msg.launch_params->stdin_path.size)); + off += msg.launch_params->stdin_path.size; + off += str8_deserial_read_struct(payload, off, &msg.launch_params->stderr_path.size); + msg.launch_params->stderr_path = str8_substr(payload, r1u64(off, off+msg.launch_params->stderr_path.size)); + off += msg.launch_params->stderr_path.size; + } + + // rjf: read bits + off += str8_deserial_read_struct(payload, off, &msg.launch_params->debug_subprocesses); + off += str8_deserial_read_struct(payload, off, &msg.launch_params->inherit_env); } } @@ -105,30 +130,50 @@ ds_packet_from_msg(Arena *arena, DS_Msg *msg) str8_serial_push_struct(scratch.arena, &srl, &msg->entity); str8_serial_push_struct(scratch.arena, &srl, &msg->pid); - // rjf: write command line strings + // rjf: write launch parameters extension + B8 has_launch_params = (msg->launch_params != 0); + str8_serial_push_struct(scratch.arena, &srl, &has_launch_params); + if(has_launch_params) { - str8_serial_push_struct(scratch.arena, &srl, &msg->command_line.node_count); - for EachNode(n, String8Node, msg->command_line.first) + // rjf: write command line strings { - str8_serial_push_struct(scratch.arena, &srl, &n->string.size); - str8_serial_push_string(scratch.arena, &srl, n->string); + str8_serial_push_struct(scratch.arena, &srl, &msg->launch_params->command_line.node_count); + for EachNode(n, String8Node, msg->launch_params->command_line.first) + { + str8_serial_push_struct(scratch.arena, &srl, &n->string.size); + str8_serial_push_string(scratch.arena, &srl, n->string); + } } - } - - // rjf: write path - { - str8_serial_push_struct(scratch.arena, &srl, &msg->path.size); - str8_serial_push_string(scratch.arena, &srl, msg->path); - } - - // rjf: write environment strings - { - str8_serial_push_struct(scratch.arena, &srl, &msg->env.node_count); - for EachNode(n, String8Node, msg->command_line.first) + + // rjf: write path { - str8_serial_push_struct(scratch.arena, &srl, &n->string.size); - str8_serial_push_string(scratch.arena, &srl, n->string); + str8_serial_push_struct(scratch.arena, &srl, &msg->launch_params->path.size); + str8_serial_push_string(scratch.arena, &srl, msg->launch_params->path); } + + // rjf: write environment strings + { + str8_serial_push_struct(scratch.arena, &srl, &msg->launch_params->env.node_count); + for EachNode(n, String8Node, msg->launch_params->env.first) + { + str8_serial_push_struct(scratch.arena, &srl, &n->string.size); + str8_serial_push_string(scratch.arena, &srl, n->string); + } + } + + // rjf: write std i/o paths + { + str8_serial_push_struct(scratch.arena, &srl, &msg->launch_params->stdout_path.size); + str8_serial_push_string(scratch.arena, &srl, msg->launch_params->stdout_path); + str8_serial_push_struct(scratch.arena, &srl, &msg->launch_params->stdin_path.size); + str8_serial_push_string(scratch.arena, &srl, msg->launch_params->stdin_path); + str8_serial_push_struct(scratch.arena, &srl, &msg->launch_params->stderr_path.size); + str8_serial_push_string(scratch.arena, &srl, msg->launch_params->stderr_path); + } + + // rjf: write bits + str8_serial_push_struct(scratch.arena, &srl, &msg->launch_params->debug_subprocesses); + str8_serial_push_struct(scratch.arena, &srl, &msg->launch_params->inherit_env); } // rjf: write traps @@ -189,7 +234,7 @@ ds_response_from_packet(Arena *arena, String8 data) // rjf: read data payload off += str8_deserial_read_struct(payload, off, &result.data.size); result.data.str = push_array(arena, U8, result.data.size); - off += str8_deserial_read(payload, off, result.data.str, result.data.size, result.data.size); + off += str8_deserial_read(payload, off, result.data.str, result.data.size, 1); } return result; } @@ -390,3 +435,88 @@ ds_packet_from_dmn_events(Arena *arena, DMN_EventList evts) scratch_end(scratch); return result; } + +//////////////////////////////// +//~ rjf: Local Server Functions + +internal U64 +ds_ctrl_launch(DMN_CtrlCtx *ctrl_ctx, DS_LaunchParams *params) +{ + Temp scratch = scratch_begin(0, 0); + + //- rjf: produce full stdout/stderr/stdin paths + String8 stdout_path = path_absolute_dst_from_relative_dst_src(scratch.arena, params->stdout_path, params->path); + String8 stdin_path = path_absolute_dst_from_relative_dst_src(scratch.arena, params->stdin_path, params->path); + String8 stderr_path = path_absolute_dst_from_relative_dst_src(scratch.arena, params->stderr_path, params->path); + + //- rjf: obtain stdout/stderr/stdin handles + File stdout_handle = {0}; + File stderr_handle = {0}; + File stdin_handle = {0}; + if(stdout_path.size != 0) + { + File f = file_open(AccessFlag_Write|AccessFlag_Read, stdout_path); + file_close(f); + stdout_handle = file_open(AccessFlag_Write|AccessFlag_Append|AccessFlag_ShareRead|AccessFlag_ShareWrite|AccessFlag_Inherited, params->stdout_path); + } + if(stderr_path.size != 0) + { + File f = file_open(AccessFlag_Write|AccessFlag_Read, stderr_path); + file_close(f); + stderr_handle = file_open(AccessFlag_Write|AccessFlag_Append|AccessFlag_ShareRead|AccessFlag_ShareWrite|AccessFlag_Inherited, params->stderr_path); + } + if(stdin_path.size != 0) + { + stdin_handle = file_open(AccessFlag_Read|AccessFlag_ShareRead|AccessFlag_ShareWrite|AccessFlag_Inherited, stdin_path); + } + + //- rjf: launch + ProcessLaunchParams dmn_launch_params = {0}; + { + dmn_launch_params.cmd_line = params->command_line; + dmn_launch_params.path = params->path; + dmn_launch_params.env = params->env; + dmn_launch_params.inherit_env = params->inherit_env; + dmn_launch_params.debug_subprocesses = params->debug_subprocesses; + dmn_launch_params.stdout_file = stdout_handle; + dmn_launch_params.stderr_file = stderr_handle; + dmn_launch_params.stdin_file = stdin_handle; + } + U64 pid = dmn_ctrl_launch(ctrl_ctx, &dmn_launch_params); + + //- rjf: close stdout/stderr/stdin files + file_close(stdout_handle); + file_close(stderr_handle); + file_close(stdin_handle); + + scratch_end(scratch); + return pid; +} + +internal B32 +ds_ctrl_attach(DMN_CtrlCtx *ctrl_ctx, U32 pid) +{ + B32 result = dmn_ctrl_attach(ctrl_ctx, pid); + return result; +} + +internal B32 +ds_ctrl_kill(DMN_CtrlCtx *ctrl_ctx, DMN_Handle process, U32 exit_code) +{ + B32 result = dmn_ctrl_kill(ctrl_ctx, process, exit_code); + return result; +} + +internal B32 +ds_ctrl_detach(DMN_CtrlCtx *ctrl_ctx, DMN_Handle process) +{ + B32 result = dmn_ctrl_detach(ctrl_ctx, process); + return result; +} + +internal DMN_EventList +ds_ctrl_run(Arena *arena, DMN_CtrlCtx *ctrl_ctx, DMN_RunCtrls *ctrls) +{ + DMN_EventList events = dmn_ctrl_run(arena, ctrl_ctx, ctrls); + return events; +} diff --git a/src/dbg_server/dbg_server.h b/src/dbg_server/dbg_server.h index 58583463..4b2fbc4f 100644 --- a/src/dbg_server/dbg_server.h +++ b/src/dbg_server/dbg_server.h @@ -72,11 +72,22 @@ DS_MsgKind; typedef U32 DS_MsgFlags; enum { - DS_MsgFlag_InheritEnv = (1<<0), - DS_MsgFlag_DebugSubprocesses = (1<<1), - DS_MsgFlag_IgnorePreviousException = (1<<2), - DS_MsgFlag_RunEntitiesAreUnfrozen = (1<<3), - DS_MsgFlag_RunEntitiesAreProcesses = (1<<4), + DS_MsgFlag_IgnorePreviousException = (1<<0), + DS_MsgFlag_RunEntitiesAreUnfrozen = (1<<1), + DS_MsgFlag_RunEntitiesAreProcesses = (1<<2), +}; + +typedef struct DS_LaunchParams DS_LaunchParams; +struct DS_LaunchParams +{ + String8List command_line; + String8 path; + String8List env; + String8 stdout_path; + String8 stdin_path; + String8 stderr_path; + B32 debug_subprocesses; + B32 inherit_env; }; typedef struct DS_Msg DS_Msg; @@ -93,10 +104,8 @@ struct DS_Msg U64 pid; U64 code; - //- rjf: process launching parameters - String8List command_line; - String8 path; - String8List env; + //- rjf: process launching parameter extension + DS_LaunchParams *launch_params; //- rjf: running parameters DMN_TrapChunkList traps; @@ -133,4 +142,13 @@ internal String8 ds_packet_from_response(Arena *arena, DS_Response *response); internal DMN_EventList ds_dmn_events_from_packet(Arena *arena, String8 data); internal String8 ds_packet_from_dmn_events(Arena *arena, DMN_EventList evts); +//////////////////////////////// +//~ rjf: Local Server Functions + +internal U64 ds_ctrl_launch(DMN_CtrlCtx *ctrl_ctx, DS_LaunchParams *params); +internal B32 ds_ctrl_attach(DMN_CtrlCtx *ctrl_ctx, U32 pid); +internal B32 ds_ctrl_kill(DMN_CtrlCtx *ctrl_ctx, DMN_Handle process, U32 exit_code); +internal B32 ds_ctrl_detach(DMN_CtrlCtx *ctrl_ctx, DMN_Handle process); +internal DMN_EventList ds_ctrl_run(Arena *arena, DMN_CtrlCtx *ctrl_ctx, DMN_RunCtrls *ctrls); + #endif // DBG_SERVER_H diff --git a/src/demon/demon.c b/src/demon/demon.c index a136ad07..7d5e8f9e 100644 --- a/src/demon/demon.c +++ b/src/demon/demon.c @@ -122,3 +122,17 @@ dmn_event_list_push(Arena *arena, DMN_EventList *list) result->module_info = &dmn_module_info_nil; return result; } + +internal void +dmn_event_deep_copy(Arena *arena, DMN_Event *dst, DMN_Event *src) +{ + MemoryCopyStruct(dst, src); + dst->string = str8_copy(arena, dst->string); + if(dst->module_info != 0) + { + dst->module_info = push_array(arena, DMN_ModuleInfo, 1); + MemoryCopyStruct(dst->module_info, src->module_info); + dst->module_info->module_path = str8_copy(arena, dst->module_info->module_path); + dst->module_info->debug_info_path = str8_copy(arena, dst->module_info->debug_info_path); + } +} diff --git a/src/demon/demon.h b/src/demon/demon.h index 7c9d8a5e..bfe95a16 100644 --- a/src/demon/demon.h +++ b/src/demon/demon.h @@ -232,6 +232,7 @@ internal DMN_HandleArray dmn_handle_array_copy(Arena *arena, DMN_HandleArray *sr //- rjf: event list building internal DMN_Event *dmn_event_list_push(Arena *arena, DMN_EventList *list); +internal void dmn_event_deep_copy(Arena *arena, DMN_Event *dst, DMN_Event *src); //////////////////////////////// //~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) diff --git a/src/linux/socket/linux_socket.c b/src/linux/socket/linux_socket.c index 22d1d65b..45b0901d 100644 --- a/src/linux/socket/linux_socket.c +++ b/src/linux/socket/linux_socket.c @@ -440,7 +440,10 @@ sock_recv(Arena *arena, SOCK_Session session, SOCK_Protocol *protocol_out, SOCK_ if(guarded_ring_read_or_wait(&guard, sizeof(header), header, endt_us)) { U64 size_cap = s->s2u_ring->ring->size - sizeof(header); - protocol_out[0] = (SOCK_Protocol)header[0]; + if(protocol_out != 0) + { + protocol_out[0] = (SOCK_Protocol)header[0]; + } endpoint_out->port = (U16)header[1]; endpoint_out->address_u64[0] = header[2]; endpoint_out->address_u64[1] = header[3]; diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 60ff7229..34b469a5 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -263,22 +263,28 @@ internal D_Target rd_target_from_cfg(Arena *arena, CFG_Node *cfg) { D_Target target = {0}; - target.exe = cfg_node_child_from_string(cfg, str8_lit("executable"))->first->string; - target.args = cfg_node_child_from_string(cfg, str8_lit("arguments"))->first->string; - target.working_directory = cfg_node_child_from_string(cfg, str8_lit("working_directory"))->first->string; - target.custom_entry_point_name = cfg_node_child_from_string(cfg, str8_lit("entry_point"))->first->string; - target.stdout_path = cfg_node_child_from_string(cfg, str8_lit("stdout_path"))->first->string; - target.stderr_path = cfg_node_child_from_string(cfg, str8_lit("stderr_path"))->first->string; - target.stdin_path = cfg_node_child_from_string(cfg, str8_lit("stdin_path"))->first->string; + target.exe = cfg_node_child_from_string(cfg, s("executable"))->first->string; + target.args = cfg_node_child_from_string(cfg, s("arguments"))->first->string; + target.working_directory = cfg_node_child_from_string(cfg, s("working_directory"))->first->string; + target.custom_entry_point_name = cfg_node_child_from_string(cfg, s("entry_point"))->first->string; + target.stdout_path = cfg_node_child_from_string(cfg, s("stdout_path"))->first->string; + target.stderr_path = cfg_node_child_from_string(cfg, s("stderr_path"))->first->string; + target.stdin_path = cfg_node_child_from_string(cfg, s("stdin_path"))->first->string; target.msg_id = cfg->id; - target.debug_subprocesses = !!e_value_from_string(cfg_node_child_from_string(cfg, str8_lit("debug_subprocesses"))->first->string).u64; + target.debug_subprocesses = !!e_value_from_string(cfg_node_child_from_string(cfg, s("debug_subprocesses"))->first->string).u64; for(CFG_Node *child = cfg->first; child != &cfg_nil_node; child = child->next) { - if(str8_match(child->string, str8_lit("environment"), 0)) + if(str8_match(child->string, s("environment"), 0)) { str8_list_push(arena, &target.env, child->first->string); } } + B32 is_remote = !!e_value_from_string(cfg_node_child_from_string(cfg, s("remote"))->first->string).u64; + if(is_remote) + { + target.remote_address = cfg_node_child_from_string(cfg, s("address"))->first->string; + target.deploy = !!e_value_from_string(cfg_node_child_from_string(cfg, s("deploy"))->first->string).u64;\ + } return target; } @@ -14805,10 +14811,11 @@ rd_frame(void) cycle:; { CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, cfg_node_from_id(rd_regs()->window)); + CFG_PanelNode *panel_nav_root = (panel_tree.zoom_root != &cfg_nil_panel_node ? panel_tree.zoom_root : panel_tree.root); CFG_PanelNode *next_focused = &cfg_nil_panel_node; for(CFG_PanelNode *p = panel_tree.focused; p != &cfg_nil_panel_node; - p = cfg_panel_node_rec__depth_first(panel_tree.root, p, panel_sib_off, panel_child_off).next) + p = cfg_panel_node_rec__depth_first(panel_nav_root, p, panel_sib_off, panel_child_off).next) { if(p != panel_tree.focused && p->first == &cfg_nil_panel_node) { @@ -14818,9 +14825,9 @@ rd_frame(void) } if(next_focused == &cfg_nil_panel_node) { - for(CFG_PanelNode *p = panel_tree.root; + for(CFG_PanelNode *p = panel_nav_root; p != &cfg_nil_panel_node; - p = cfg_panel_node_rec__depth_first(panel_tree.root, p, panel_sib_off, panel_child_off).next) + p = cfg_panel_node_rec__depth_first(panel_nav_root, p, panel_sib_off, panel_child_off).next) { if(p != panel_tree.focused && p->first == &cfg_nil_panel_node) { @@ -14885,20 +14892,21 @@ rd_frame(void) { CFG_Node *window = cfg_node_from_id(rd_regs()->window); CFG_PanelTree panel_tree = cfg_panel_tree_from_cfg(scratch.arena, window); + CFG_PanelNode *panel_nav_root = (panel_tree.zoom_root != &cfg_nil_panel_node ? panel_tree.zoom_root : panel_tree.root); CFG_PanelNode *src_panel = panel_tree.focused; - Rng2F32 src_panel_rect = cfg_target_rect_from_panel_node(r2f32(v2f32(0, 0), v2f32(1000, 1000)), panel_tree.root, src_panel); + Rng2F32 src_panel_rect = cfg_target_rect_from_panel_node(r2f32(v2f32(0, 0), v2f32(1000, 1000)), panel_nav_root, src_panel); Vec2F32 src_panel_center = center_2f32(src_panel_rect); Vec2F32 src_panel_half_dim = scale_2f32(dim_2f32(src_panel_rect), 0.5f); Vec2F32 travel_dim = add_2f32(src_panel_half_dim, v2f32(10.f, 10.f)); Vec2F32 travel_dst = add_2f32(src_panel_center, mul_2f32(travel_dim, v2f32((F32)panel_change_dir.x, (F32)panel_change_dir.y))); CFG_PanelNode *dst_root = &cfg_nil_panel_node; - for(CFG_PanelNode *p = panel_tree.root; p != &cfg_nil_panel_node; p = cfg_panel_node_rec__depth_first_pre(panel_tree.root, p).next) + for(CFG_PanelNode *p = panel_nav_root; p != &cfg_nil_panel_node; p = cfg_panel_node_rec__depth_first_pre(panel_nav_root, p).next) { if(p == src_panel || p->first != &cfg_nil_panel_node) { continue; } - Rng2F32 p_rect = cfg_target_rect_from_panel_node(r2f32(v2f32(0, 0), v2f32(1000, 1000)), panel_tree.root, p); + Rng2F32 p_rect = cfg_target_rect_from_panel_node(r2f32(v2f32(0, 0), v2f32(1000, 1000)), panel_nav_root, p); if(contains_2f32(p_rect, travel_dst)) { dst_root = p; diff --git a/src/raddbg_server/raddbg_server_main.c b/src/raddbg_server/raddbg_server_main.c index aa82857c..dc842733 100644 --- a/src/raddbg_server/raddbg_server_main.c +++ b/src/raddbg_server/raddbg_server_main.c @@ -143,34 +143,27 @@ dmn_thread__entry_point(void *p) //- rjf: launching case DS_MsgKind_Launch: + if(msg.launch_params != 0) { - ProcessLaunchParams params = {0}; - { - params.cmd_line = msg.command_line; - params.path = msg.path; - params.env = msg.env; - params.inherit_env = !!(msg.flags & DS_MsgFlag_InheritEnv); - params.debug_subprocesses = !!(msg.flags & DS_MsgFlag_DebugSubprocesses); - } - response.pid = dmn_ctrl_launch(ctrl_ctx, ¶ms); + response.pid = ds_ctrl_launch(ctrl_ctx, msg.launch_params); }break; //- rjf: attaching case DS_MsgKind_Attach: { - response.good = dmn_ctrl_attach(ctrl_ctx, msg.pid); + response.good = ds_ctrl_attach(ctrl_ctx, msg.pid); }break; //- rjf: killing case DS_MsgKind_Kill: { - response.good = dmn_ctrl_kill(ctrl_ctx, msg.entity, (U32)msg.code); + response.good = ds_ctrl_kill(ctrl_ctx, msg.entity, (U32)msg.code); }break; //- rjf: detaching case DS_MsgKind_Detach: { - response.good = dmn_ctrl_detach(ctrl_ctx, msg.entity); + response.good = ds_ctrl_detach(ctrl_ctx, msg.entity); }break; //- rjf: running ops @@ -189,7 +182,7 @@ dmn_thread__entry_point(void *p) ctrls.run_entities_are_processes = !!(msg.flags & DS_MsgFlag_RunEntitiesAreProcesses); }; response.good = 1; - events = dmn_ctrl_run(scratch.arena, ctrl_ctx, &ctrls); + events = ds_ctrl_run(scratch.arena, ctrl_ctx, &ctrls); }break; } @@ -296,11 +289,16 @@ entry_point(CmdLine *cmd_line) fprintf(stderr, " %.*s", str8_varg(n->string)); } fprintf(stderr, "\n"); - DS_Msg msg = + DS_LaunchParams launch_params = { - .kind = DS_MsgKind_Launch, .command_line = t->cmd_line, .path = current_path, + .inherit_env = 1, + }; + DS_Msg msg = + { + .kind = DS_MsgKind_Launch, + .launch_params = &launch_params, }; String8 packet = ds_packet_from_msg(scratch.arena, &msg); RWMutexScope(rds_state->c2d_rw_mutex, 1) @@ -381,7 +379,7 @@ entry_point(CmdLine *cmd_line) if(header_is_good) { String8 msg_raw = str8_skip(data, sizeof(header)); - str8_deserial_read_struct(msg_raw, 2, &peek_kind); + str8_deserial_read_struct(msg_raw, 0, &peek_kind); if(peek_kind < DS_MsgKind_Launch || DS_MsgKind_SingleStep < peek_kind) { DS_Msg msg = ds_msg_from_packet(scratch.arena, data); diff --git a/src/socket/socket.c b/src/socket/socket.c index e6cb2996..0580d4c7 100644 --- a/src/socket/socket.c +++ b/src/socket/socket.c @@ -124,3 +124,9 @@ sock_string_from_endpoint(Arena *arena, SOCK_Endpoint endpoint) } return result; } + +internal B32 +sock_endpoint_match(SOCK_Endpoint a, SOCK_Endpoint b) +{ + B32 result = (a.port == b.port && a.kind == b.kind && MemoryMatchArray(a.address_u8, b.address_u8)); +} diff --git a/src/socket/socket.h b/src/socket/socket.h index 2903915b..b9b61160 100644 --- a/src/socket/socket.h +++ b/src/socket/socket.h @@ -60,6 +60,7 @@ typedef SOCK_WAKEUP_FUNCTION_DEF(SOCK_WakeupFunctionType); internal SOCK_Endpoint sock_endpoint_from_string_port(String8 address, U16 port); internal SOCK_Endpoint sock_endpoint_from_string(String8 address_and_port); internal String8 sock_string_from_endpoint(Arena *arena, SOCK_Endpoint endpoint); +internal B32 sock_endpoint_match(SOCK_Endpoint a, SOCK_Endpoint b); //////////////////////////////// //~ rjf: @per_os_impl Top-Level Layer Calls diff --git a/src/win32/socket/win32_socket.c b/src/win32/socket/win32_socket.c index 89abd19d..2cbd1d75 100644 --- a/src/win32/socket/win32_socket.c +++ b/src/win32/socket/win32_socket.c @@ -488,7 +488,10 @@ sock_recv(Arena *arena, SOCK_Session session, SOCK_Protocol *protocol_out, SOCK_ if(guarded_ring_read_or_wait(&guard, sizeof(header), header, endt_us)) { U64 size_cap = s->s2u_ring->ring->size - sizeof(header); - protocol_out[0] = (SOCK_Protocol)header[0]; + if(protocol_out != 0) + { + protocol_out[0] = (SOCK_Protocol)header[0]; + } endpoint_out->port = (U16)header[1]; endpoint_out->address_u64[0] = header[2]; endpoint_out->address_u64[1] = header[3]; From e6707738bfe64acba7b59a6a2027162abc196419 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 12 Oct 2026 16:56:33 +0200 Subject: [PATCH 247/251] radlink: For non-debug sections, propagate /RAD_REMOVE_SECTION to the section flags. This fixes issues where .gfids sections contain relocation data. In that case radlink dropped the section, but still applied them at for example `.text + 0`. --- src/linker/lnk_obj.c | 11 +++++++++++ src/linker/lnk_obj.h | 1 + 2 files changed, 12 insertions(+) diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 15520252..1adf3cfe 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -122,6 +122,16 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) str8_ends_with(sect_name, str8_lit("$fo_bdd$"), 0)) { *section_flags |= COFF_SectionFlag_LnkInfo; } + if (~*section_flags & LNK_SECTION_FLAG_DEBUG) { + String8 sect_base_name, sect_postfix; + coff_parse_section_name(sect_name, §_base_name, §_postfix); + for EachNode(name_n, String8Node, task->remove_sections.first) { + if (str8_match(sect_base_name, name_n->string, 0)) { + *section_flags |= COFF_SectionFlag_LnkRemove; + break; + } + } + } if (str8_match(sect_name, str8_lit(".debug$T"), 0)) { debug_t_section_number = section_number; @@ -421,6 +431,7 @@ lnk_obj_from_input_many(TP_Context *tp, TP_Arena *arena, LNK_Config *config, U64 .inputs = inputs, .objs = objs, .machine = config->machine, + .remove_sections = config->remove_sections, }; tp_for_parallel(tp, arena, inputs_count, lnk_obj_initer, &task); } diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index e3d50cdb..d4ddbd6f 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -211,6 +211,7 @@ typedef struct LNK_ObjNode *objs; U64 obj_id_base; U32 machine; + String8List remove_sections; } LNK_ObjIniter; typedef struct From 959de0d83468b49c4c74a0cc482f181ca3ca61b8 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 16 Oct 2026 08:18:10 -0700 Subject: [PATCH 248/251] update rdi_parse samples, add voff -> procedure name --- src/lib_rdi/rdi_parse.h | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib_rdi/rdi_parse.h b/src/lib_rdi/rdi_parse.h index 5349348d..06a8d58a 100644 --- a/src/lib_rdi/rdi_parse.h +++ b/src/lib_rdi/rdi_parse.h @@ -15,13 +15,13 @@ { RDI_Parsed *rdi = ...; char *name = "mule_main"; - RDI_Procedure *procedure = rdi_procedure_from_name_cstr(rdi, name); // 1. name -> procedure + RDI_Symbol *procedure = rdi_procedure_from_name_cstr(rdi, name); // 1. name -> procedure RDI_U64 procedure_first_voff = rdi_first_voff_from_procedure(rdi, procedure); // 2. procedure -> virtual offset RDI_Line line = rdi_line_from_voff(rdi, procedure_first_voff); // 3. virtual offset -> line RDI_SourceFile *file = rdi_source_file_from_line(rdi, &line); // 4. line -> source file RDI_U64 file_path_size = 0; // 5. source file -> path RDI_U8 *file_path = rdi_normal_path_from_source_file(rdi, file, &file_path_size); - printf("%s is at %.*s:%u\n", name, (int)file_path_size, file_path, line.line_num); + printf("%s is at %.*s:%u\n", name, (int)file_path_size, file_path, line.line_num); // " is at :" } // Line -> Procedure Name @@ -31,10 +31,20 @@ RDI_U32 line_num = 2557; RDI_SourceFile *file = rdi_source_file_from_normal_path_cstr(rdi, path); // 1. path -> source file RDI_U64 voff = rdi_first_voff_from_source_file_line_num(rdi, file, line_num); // 2. (source file, line) -> virtual offset - RDI_Procedure *procedure = rdi_procedure_from_voff(rdi, voff); // 3. virtual offset -> procedure + RDI_Symbol *procedure = rdi_procedure_from_voff(rdi, voff); // 3. virtual offset -> procedure RDI_U64 name_size = 0; // 4. procedure -> name RDI_U8 *name = rdi_name_from_procedure(rdi, procedure, &name_size); - printf("%s:%u is inside %.*s\n", path, line_num, (int)name_size, name); + printf("%s:%u is inside %.*s\n", path, line_num, (int)name_size, name); // ": is inside " +} + +// Virtual Offset -> Procedure Name +{ + RDI_Parsed *rdi = ...; + RDI_U64 voff = ...; + RDI_Symbol *procedure = rdi_procedure_from_voff(rdi, voff); // 1. voff -> procedure + RDI_U64 name_size = 0; // 2. procedure -> name + RDI_U8 *name = rdi_name_from_procedure(rdi, procedure, &name_size); + printf("0x%llx is inside %.*s\n", voff, (int)name_size, name); // " is inside " } #endif From fc0ee681e0ea287e749a70425a4f8dacfeaf8e84 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 16 Oct 2026 08:23:21 -0700 Subject: [PATCH 249/251] rdi_parse: initial parse / decompression sample usage --- src/lib_rdi/rdi_parse.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib_rdi/rdi_parse.h b/src/lib_rdi/rdi_parse.h index 06a8d58a..464856dc 100644 --- a/src/lib_rdi/rdi_parse.h +++ b/src/lib_rdi/rdi_parse.h @@ -11,6 +11,26 @@ //~ Usage Samples // #if 0 +// File Data Blob -> RDI +{ + RDI_Parsed rdi = {0}; + RDI_ParseStatus status = rdi_parse(data, size, &rdi); +} + +// Compressed File Data Blob -> RDI +{ + RDI_Parsed rdi = {0}; + RDI_ParseStatus status = RDI_ParseStatus_Good; + { + RDI_Parsed rdi_compressed = {0}; + RDI_ParseStatus rdi_compressed_status = rdi_parse(compressed_data, compressed_size, &rdi_compressed); + RDI_U64 decompressed_size = rdi_decompressed_size_from_parsed(&rdi_compressed); + RDI_U8 *decompressed_data = allocate(decompressed_size); + rdi_decompress_parsed(decompressed_data, decompressed_size, &rdi_compressed); + status = rdi_parse(decompressed_data, decompressed_size, &rdi); + } +} + // Procedure Name -> Line { RDI_Parsed *rdi = ...; From c55a14f63474f522f00f2aabc9de66b68f202cca Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 16 Oct 2026 12:46:23 -0700 Subject: [PATCH 250/251] dbg_info: swap for (dbg infos) for (name map tables) loops in debug info name identifier resolution - prefer *all* global variables first, for example, before types. --- project.raddbg | 3 +-- src/dbg_engine/dbg_engine_ctrl.c | 2 ++ src/dbg_info/dbg_info.c | 25 +++++++++++++------------ src/socket/socket.c | 1 + 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/project.raddbg b/project.raddbg index 0e4d6ca1..d14d1956 100755 --- a/project.raddbg +++ b/project.raddbg @@ -7,19 +7,18 @@ target: working_directory: build arguments: "--user:raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { executable: "build/raddbg_server.exe" working_directory: build debug_subprocesses: 1 - enabled: 1 } target: { executable: "build/metagen.exe" working_directory: build + enabled: 1 } target: { diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 033b7651..cec6be92 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -3201,10 +3201,12 @@ d_ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, D_Msg *msg, D // a halt, and we're waiting for that) if(events.first != 0 && events.count == 1 && events.first->v.kind == DMN_EventKind_Error && events.first->v.error_kind == DMN_ErrorKind_NotAttached) { +#if 0 for(;num_servers_running > 0;) { // TODO(rjf) } +#endif } //- rjf: bump all debuggee state generations diff --git a/src/dbg_info/dbg_info.c b/src/dbg_info/dbg_info.c index 350e54d4..334946f2 100644 --- a/src/dbg_info/dbg_info.c +++ b/src/dbg_info/dbg_info.c @@ -1703,20 +1703,21 @@ di_match_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, RDI_SectionKind_Procedures, RDI_SectionKind_TypeNodes, }; - Rng1U64 range = lane_range(dbgi_keys.count); - for EachInRange(dbgi_idx, range) + for EachElement(name_map_kind_idx, name_map_kinds) { - DI_Key dbgi_key = dbgi_keys.v[dbgi_idx]; - if(!allow_other_dbgis && !di_key_match(preferred_key, dbgi_key)) + Rng1U64 range = lane_range(dbgi_keys.count); + for EachInRange(dbgi_idx, range) { - continue; - } - Access *access = access_open(); - { - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); - RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); - for EachElement(name_map_kind_idx, name_map_kinds) + DI_Key dbgi_key = dbgi_keys.v[dbgi_idx]; + if(!allow_other_dbgis && !di_key_match(preferred_key, dbgi_key)) { + continue; + } + Access *access = access_open(); + { + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); + RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); + // rjf: unpack name map RDI_NameMap *name_map = rdi_element_from_name_idx(rdi, NameMaps, name_map_kinds[name_map_kind_idx]); RDI_ParsedNameMap parsed_name_map = {0}; @@ -1821,8 +1822,8 @@ di_match_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, lane_matches[lane_idx()].count = filtered_matches_count; } } + access_close(access); } - access_close(access); } } } diff --git a/src/socket/socket.c b/src/socket/socket.c index 0580d4c7..5b63263f 100644 --- a/src/socket/socket.c +++ b/src/socket/socket.c @@ -129,4 +129,5 @@ internal B32 sock_endpoint_match(SOCK_Endpoint a, SOCK_Endpoint b) { B32 result = (a.port == b.port && a.kind == b.kind && MemoryMatchArray(a.address_u8, b.address_u8)); + return result; } From 62b715a30e5da6b8d7e02372a8e7458feab1df97 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 16 Oct 2026 19:04:28 -0700 Subject: [PATCH 251/251] update codebase readme w.r.t. linux --- README.md | 77 +++---------------------------------------------------- 1 file changed, 4 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 05403355..e8151797 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ local copy has been built. You can find pre-built release binaries [here](https://github.com/EpicGames/raddebugger/releases)._ The RAD Debugger is a native, user-mode, multi-process, graphical debugger. It -currently only supports local-machine Windows x64 debugging with PDBs, with -plans to expand and port in the future. In the future we'll expand to also -support native Linux debugging and DWARF debug info. +currently only supports local-machine x64 debugging on both Windows and Linux +(although Linux support is still very early). We plan to expand support to +other architectures, and to implement remote and multi-machine debugging. The debugger is currently in *ALPHA*. In order to get the debugger bullet-proof, it'd greatly help out if you submitted the issues you find @@ -27,8 +27,7 @@ additional related technologies: **(1)** the RAD Debug Info (RDI) format, and The RAD Debug Info (RDI) format is our custom debug information format, which the debugger parses and uses, rather than the debug information natively produced by toolchains, like PDB or DWARF. To work with these existing -toolchains, we convert PDB (and eventually PE/ELF files with embedded DWARF) -into the RDI format on-demand. +toolchains, we convert PDB and DWARF to the RDI format on-demand. The RDI format is currently specified in code, in the files within the `src/lib_rdi` folder. In [`rdi.h`](src/lib_rdi/rdi.h) and @@ -311,74 +310,6 @@ Linker, or the `radbin` CLI binary file utility: --- -# Project Roadmap - -### The Initial Alpha Battle-Testing Phase - -The first priority for the project is to ensure that the most crucial components -are functioning extremely reliably for local, x64, Windows development. -For the debugger, this would include parts like debug info conversion, debug -info loading, process control, stepping, evaluation (correct usage of both -location info and type info), and a robust frontend which ensures the lower -level parts are usable. For the linker, this is a matter of reliability and -convergence with existing linker behavior. - -We feel that we've already come a long way in all of these respects, but given -the massive set of possible combinations of languages, build settings, -toolchains, used language features, and patterns of generated code, we still -expect some issues, and are prioritizing these issues being resolved first. - -We also hope to continue to improve performance in this phase. For the debugger, -this primarily includes frontend performance, introducing caches when economical -to do so, and tightening existing systems up. For the linker, it has been mostly -tuned thus far for giant projects, and so we'd like to improve linking speed for -small-to-mid sized projects as well. - -For the linker, there are also a number of features to come, like -dead-code-elimination (`/opt:ref`), and link-time-optimizations with the help -of `clang` (we won't support LTCG from MSVC, since it is undocumented). - -### Local x64 Linux Debugging Phase - -The next priority for the project is to take the rock solid x64 Windows -debugging experience, and port all of the relevant pieces to support local x64 -Linux debugging also. - -The debugger has been written to abstract over the parts that need to differ on -either Linux or Windows, and this is mainly going to be a task in building out -different backends for those abstraction layers. - -The major parts of this phase are: - -- Porting the `src/demon` layer to implement the Demon local process control -abstraction API. -- Implementing an x64 ELF Linux unwinder in the `src/ctrl` layer. -- Creating a DWARF-to-RDI converter (in the same way that we've built a -PDB-to-RDI converter). A partial implementation of this is in -`src/rdi_from_dwarf`. -- Porting the `src/render` layer to implement all of the rendering features the -frontend needs on a Linux-compatible API (the backend used on Windows is D3D11). -- Porting the `src/font_provider` layer to a Linux-compatible font -rasterization backend, like FreeType (the backend used on Windows is -DirectWrite). -- Porting the `src/os` layers to Linux. This includes core operating system -abstraction (virtual memory allocation, threading and synchronization -primitives, and so on), and graphical operating system abstraction (windows, -input events, and so on). - -Once the above list is complete, and once every part is rock solid, the Windows -debugging experience we'll have worked diligently to create will also be -available natively on Linux machines. - -### And Beyond! - -There are several directions we might take after these two major phases, -like remote debugging, porting to different architectures, further improving -the debugger's features (like improving the visualization engine), and so on. -But for now, we're mostly focused on those first two phases. - ---- - # Codebase Introduction ## Top-Level Directory Descriptions