Skip to content

Commit f9561cb

Browse files
add support for CFA in the eval system
Canonical Frame Address is a DWARF-only concept encoded in the unwind info. Debugger must store this data for each call frame and provide it to the eval system based on selected stack frame. To abstract over format details, we introduce a "Frame Unwind Context". For DWARF, this context maintains the intermediate unwind state and exposes the CFA, whereas for PE, it remains empty.
1 parent 2a6d0d7 commit f9561cb

File tree

9 files changed

+227
-102
lines changed

9 files changed

+227
-102
lines changed

src/ctrl/ctrl_core.c

Lines changed: 165 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,7 +1838,7 @@ DW_MEM_READ(ctrl_unwind_mem_read_dwarf_x64)
18381838
B32 is_read = ctrl_process_memory_read(ctx->process_handle, r1u64(addr, addr + size), &is_stale, buffer, ctx->endt_us);
18391839

18401840
DW_UnwindStatus status = DW_UnwindStatus_Fail;
1841-
if(is_stale && is_read)
1841+
if(is_stale)
18421842
{
18431843
status = DW_UnwindStatus_Maybe;
18441844
}
@@ -1903,12 +1903,35 @@ DW_REG_WRITE(ctrl_unwind_reg_write_dwarf_x64)
19031903
}
19041904

19051905
internal CTRL_UnwindStepResult
1906-
ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us)
1906+
ctrl_unwind_step_result_from_dwarf_status(DW_UnwindStatus s)
19071907
{
1908-
Temp scratch = scratch_begin(0, 0);
1909-
1908+
CTRL_UnwindStepResult result = {0};
1909+
switch(s)
1910+
{
1911+
case DW_UnwindStatus_Ok:
1912+
{
1913+
result.flags &= ~(CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale);
1914+
}break;
1915+
case DW_UnwindStatus_Fail:
1916+
{
1917+
result.flags |= CTRL_UnwindFlag_Error;
1918+
}break;
1919+
case DW_UnwindStatus_Maybe:
1920+
{
1921+
result.flags &= ~CTRL_UnwindFlag_Error;
1922+
result.flags |= CTRL_UnwindFlag_Stale;
1923+
}break;
1924+
default: { InvalidPath; } break;
1925+
}
1926+
return result;
1927+
}
1928+
1929+
internal CTRL_UnwindStepResult
1930+
ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us, CTRL_FrameUnwindContext *ctx_out)
1931+
{
1932+
Temp scratch = scratch_begin(&arena, 1);
19101933
CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error };
1911-
1934+
19121935
// gather context for virtual stack unwinder
19131936
U64 cfi_rebase = 0;
19141937
B32 is_unwind_eh = 0;
@@ -1944,9 +1967,9 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, A
19441967
if(fde_addr != max_U64)
19451968
{
19461969
// parse call frame info
1947-
DW_CIE cie = {0};
1948-
DW_FDE fde = {0};
1949-
B32 is_cfi_parsed = 0;
1970+
DW_CIE cie = {0};
1971+
DW_FDE fde = {0};
1972+
B32 is_cfi_parsed = 0;
19501973
if(is_unwind_eh)
19511974
{
19521975
B32 is_stale = 0;
@@ -2051,16 +2074,14 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, A
20512074
}
20522075

20532076
// find register rules for IP
2054-
DW_CFI_Row *cfi_row = dw_cfi_row_from_pc(scratch.arena, arch, &cie, &fde, decode_ptr_func, decode_ptr_ctx, ip);
2077+
DW_CFI_Row *cfi_row = dw_cfi_row_from_pc(arena, arch, &cie, &fde, decode_ptr_func, decode_ptr_ctx, ip);
20552078
if(cfi_row)
20562079
{
20572080
// setup machine ops
20582081
void *mem_read_ctx = 0;
20592082
void *reg_read_ctx = 0;
2060-
void *reg_write_ctx = 0;
20612083
DW_MemRead *mem_read_func = 0;
20622084
DW_RegRead *reg_read_func = 0;
2063-
DW_RegWrite *reg_write_func = 0;
20642085
switch(arch)
20652086
{
20662087
case Arch_Null: break;
@@ -2072,64 +2093,101 @@ ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, A
20722093

20732094
mem_read_ctx = mem_read_ctx_x64;
20742095
reg_read_ctx = regs;
2075-
reg_write_ctx = regs;
20762096

20772097
mem_read_func = ctrl_unwind_mem_read_dwarf_x64;
20782098
reg_read_func = ctrl_unwind_reg_read_dwarf_x64;
2079-
reg_write_func = ctrl_unwind_reg_write_dwarf_x64;
20802099
}break;
20812100
case Arch_x86:
20822101
case Arch_arm64:
20832102
case Arch_arm32:
20842103
{
20852104
NotImplemented;
20862105
}break;
2087-
default: { InvalidPath; } break;
2106+
default: { InvalidPath; }break;
20882107
}
2089-
2090-
// apply register rules to the context
2091-
DW_UnwindStatus cfi_uw_status = dw_cfi_apply_register_rules(arch,
2092-
&cie,
2093-
cfi_row,
2094-
mem_read_func,
2095-
mem_read_ctx,
2096-
reg_read_func,
2097-
reg_read_ctx,
2098-
reg_write_func,
2099-
reg_write_ctx);
2100-
2101-
// last frame typically has undefined rule for IP
2102-
if(cfi_row->regs[cie.ret_addr_reg].rule == DW_CFI_RegisterRule_Undefined)
2103-
{
2104-
regs_arch_block_write_rip(arch, regs, 0);
2105-
}
2106-
2107-
// translate unwind status code to control layer's result flags
2108-
switch(cfi_uw_status)
2108+
2109+
// compute CFA for the row
2110+
U64 cfa = 0;
2111+
DW_UnwindStatus unwind_status = dw_compute_cfa(arch, cfi_row, mem_read_func, mem_read_ctx, reg_read_func, reg_read_ctx, &cfa);
2112+
2113+
// on success fill out output
2114+
if(unwind_status == DW_UnwindStatus_Ok)
21092115
{
2110-
case DW_UnwindStatus_Ok:
2111-
{
2112-
result.flags &= ~(CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale);
2113-
}break;
2114-
case DW_UnwindStatus_Fail:
2115-
{
2116-
result.flags |= CTRL_UnwindFlag_Error;
2117-
}break;
2118-
case DW_UnwindStatus_Maybe:
2119-
{
2120-
result.flags &= ~CTRL_UnwindFlag_Error;
2121-
result.flags |= CTRL_UnwindFlag_Stale;
2122-
}break;
2123-
default: { InvalidPath; } break;
2116+
ctx_out->cfa = cfa;
2117+
ctx_out->cfi_row = cfi_row;
2118+
ctx_out->ret_addr_reg = cie.ret_addr_reg;
21242119
}
2120+
2121+
// translate unwind status code
2122+
result = ctrl_unwind_step_result_from_dwarf_status(unwind_status);
21252123
}
21262124
}
21272125
}
2128-
else
2126+
2127+
scratch_end(scratch);
2128+
return result;
2129+
}
2130+
2131+
internal CTRL_UnwindStepResult
2132+
ctrl_unwind_step__dwarf(CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, CTRL_FrameUnwindContext *frame_ctx, U64 endt_us)
2133+
{
2134+
Temp scratch = scratch_begin(0, 0);
2135+
2136+
CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error };
2137+
2138+
// setup machine ops
2139+
void *mem_read_ctx = 0;
2140+
void *reg_read_ctx = 0;
2141+
void *reg_write_ctx = 0;
2142+
DW_MemRead *mem_read_func = 0;
2143+
DW_RegRead *reg_read_func = 0;
2144+
DW_RegWrite *reg_write_func = 0;
2145+
switch(arch)
2146+
{
2147+
case Arch_Null: break;
2148+
case Arch_x64:
2149+
{
2150+
CTRL_MemoryReadContextDwarfX64 *mem_read_ctx_x64 = push_array(scratch.arena, CTRL_MemoryReadContextDwarfX64, 1);
2151+
mem_read_ctx_x64->process_handle = process_handle;
2152+
mem_read_ctx_x64->endt_us = endt_us;
2153+
2154+
mem_read_ctx = mem_read_ctx_x64;
2155+
reg_read_ctx = regs;
2156+
reg_write_ctx = regs;
2157+
2158+
mem_read_func = ctrl_unwind_mem_read_dwarf_x64;
2159+
reg_read_func = ctrl_unwind_reg_read_dwarf_x64;
2160+
reg_write_func = ctrl_unwind_reg_write_dwarf_x64;
2161+
}break;
2162+
case Arch_x86:
2163+
case Arch_arm64:
2164+
case Arch_arm32:
2165+
{
2166+
NotImplemented;
2167+
}break;
2168+
default: { InvalidPath; }break;
2169+
}
2170+
2171+
// apply register rules to the context
2172+
DW_UnwindStatus unwind_status = dw_cfi_apply_register_rules(arch,
2173+
frame_ctx->cfa,
2174+
frame_ctx->cfi_row,
2175+
mem_read_func,
2176+
mem_read_ctx,
2177+
reg_read_func,
2178+
reg_read_ctx,
2179+
reg_write_func,
2180+
reg_write_ctx);
2181+
2182+
// last frame typically has undefined rule for IP
2183+
if(frame_ctx->cfi_row->regs[frame_ctx->ret_addr_reg].rule == DW_CFI_RegisterRule_Undefined)
21292184
{
2130-
// TODO: if IP does not have FDE, does this mean function is a leaf?
2185+
regs_arch_block_write_rip(arch, regs, 0);
21312186
}
21322187

2188+
// translate unwind status code
2189+
result = ctrl_unwind_step_result_from_dwarf_status(unwind_status);
2190+
21332191
scratch_end(scratch);
21342192
return result;
21352193
}
@@ -2977,23 +3035,41 @@ ctrl_unwind_step__pe_x64(CTRL_Handle process_handle, CTRL_Handle module_handle,
29773035
//- rjf: abstracted unwind step
29783036

29793037
internal CTRL_UnwindStepResult
2980-
ctrl_unwind_step(CTRL_Handle process, CTRL_Handle module, U64 module_base_vaddr, Arch arch, void *reg_block, U64 endt_us)
3038+
ctrl_establish_frame_unwind_context(Arena *arena, CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us, CTRL_FrameUnwindContext *ctx_out)
29813039
{
29823040
CTRL_UnwindStepResult result = {0};
2983-
2984-
result = ctrl_unwind_step__dwarf(process, module, arch, reg_block, endt_us);
2985-
2986-
if(result.flags == CTRL_UnwindFlag_Error && ~result.flags & CTRL_UnwindFlag_Stale)
2987-
{
2988-
switch(arch)
2989-
{
2990-
default:{}break;
2991-
case Arch_x64:
2992-
{
2993-
result = ctrl_unwind_step__pe_x64(process, module, module_base_vaddr, (REGS_RegBlockX64 *)reg_block, endt_us);
2994-
}break;
2995-
}
3041+
#if OS_LINUX
3042+
result = ctrl_establish_frame_unwind_context__dwarf(arena, process_handle, module_handle, arch, regs, endt_us, ctx_out);
3043+
#elif OS_WINDOWS
3044+
// windows does not have a concept of frame context
3045+
#else
3046+
# error "unwinder is not defined for current OS"
3047+
#endif
3048+
return result;
3049+
}
3050+
3051+
internal CTRL_UnwindStepResult
3052+
ctrl_unwind_step(CTRL_Handle process, CTRL_Handle module, U64 module_base_vaddr, Arch arch, void *reg_block, CTRL_FrameUnwindContext *frame_ctx, U64 endt_us)
3053+
{
3054+
CTRL_UnwindStepResult result = {0};
3055+
#if OS_LINUX
3056+
result = ctrl_unwind_step__dwarf(process, module, arch, reg_block, frame_ctx, endt_us);
3057+
#elif OS_WINDOWS
3058+
switch(arch)
3059+
{
3060+
case Arch_Null:{}break;
3061+
case Arch_x64:{result = ctrl_unwind_step__pe_x64(process, module, module_base_vaddr, reg_block, endt_us);}break;
3062+
case Arch_x86:
3063+
case Arch_arm32:
3064+
case Arch_arm64:
3065+
{
3066+
NotImplemented;
3067+
}break;
3068+
default:{InvalidPath;}break;
29963069
}
3070+
#else
3071+
# error "unwinder is not defined for current OS"
3072+
#endif
29973073
return result;
29983074
}
29993075

@@ -3004,8 +3080,7 @@ ctrl_unwind_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle thread, U
30043080
{
30053081
ProfBeginFunction();
30063082
Temp scratch = scratch_begin(&arena, 1);
3007-
CTRL_Unwind unwind = {0};
3008-
unwind.flags |= CTRL_UnwindFlag_Error;
3083+
CTRL_Unwind unwind = { .flags = CTRL_UnwindFlag_Error };
30093084

30103085
//- rjf: unpack args
30113086
CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, thread);
@@ -3023,44 +3098,45 @@ ctrl_unwind_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle thread, U
30233098
U64 frame_node_count = 0;
30243099
if(regs_block_good)
30253100
{
3026-
unwind.flags = 0;
3027-
for(;;)
3101+
for(unwind.flags = 0;;)
30283102
{
3029-
// rjf: regs -> rip*module
30303103
U64 rip = regs_rip_from_arch_block(arch, regs_block);
30313104
U64 rsp = regs_rsp_from_arch_block(arch, regs_block);
3032-
CTRL_Entity *module = &ctrl_entity_nil;
3033-
for(CTRL_Entity *m = process_entity->first; m != &ctrl_entity_nil; m = m->next)
3105+
3106+
// rjf: cancel on 0 rip
3107+
if(rip == 0)
30343108
{
3035-
if(m->kind == CTRL_EntityKind_Module && contains_1u64(m->vaddr_range, rip))
3036-
{
3037-
module = m;
3038-
break;
3039-
}
3109+
break;
30403110
}
3041-
3042-
// rjf: cancel on 0 rip/rsp
3043-
if(rsp == 0 && rip == 0)
3111+
3112+
// rip -> module
3113+
CTRL_Entity *module = ctrl_module_from_process_vaddr(process_entity, rip);
3114+
3115+
// establish frame context
3116+
CTRL_FrameUnwindContext frame_ctx = {0};
3117+
CTRL_UnwindStepResult frame_ctx_result = ctrl_establish_frame_unwind_context(scratch.arena, process_entity->handle, module->handle, arch, regs_block, endt_us, &frame_ctx);
3118+
unwind.flags |= frame_ctx_result.flags;
3119+
if(unwind.flags & (CTRL_UnwindFlag_Stale|CTRL_UnwindFlag_Error))
30443120
{
30453121
break;
30463122
}
30473123

30483124
// rjf: valid step -> push frame
30493125
CTRL_UnwindFrameNode *frame_node = push_array(scratch.arena, CTRL_UnwindFrameNode, 1);
30503126
CTRL_UnwindFrame *frame = &frame_node->v;
3127+
frame->cfa = frame_ctx.cfa;
30513128
frame->regs = push_array_no_zero(arena, U8, arch_reg_block_size);
30523129
MemoryCopy(frame->regs, regs_block, arch_reg_block_size);
30533130
DLLPushBack(first_frame_node, last_frame_node, frame_node);
30543131
frame_node_count += 1;
30553132

30563133
// rjf: unwind one step
3057-
CTRL_UnwindStepResult step = ctrl_unwind_step(process_entity->handle, module->handle, module->vaddr_range.min, arch, regs_block, endt_us);
3134+
CTRL_UnwindStepResult step = ctrl_unwind_step(process_entity->handle, module->handle, module->vaddr_range.min, arch, regs_block, &frame_ctx, endt_us);
30583135
unwind.flags |= step.flags;
3059-
if(step.flags & CTRL_UnwindFlag_Error ||
3060-
regs_rsp_from_arch_block(arch, regs_block) == 0 ||
3061-
regs_rip_from_arch_block(arch, regs_block) == 0 ||
3062-
(regs_rsp_from_arch_block(arch, regs_block) == rsp &&
3063-
regs_rip_from_arch_block(arch, regs_block) == rip))
3136+
3137+
// stop unwinding on errors or stale data
3138+
if(unwind.flags & (CTRL_UnwindFlag_Stale|CTRL_UnwindFlag_Error) ||
3139+
(regs_rsp_from_arch_block(arch, regs_block) == rsp && regs_rip_from_arch_block(arch, regs_block) == rip))
30643140
{
30653141
break;
30663142
}
@@ -3133,6 +3209,7 @@ ctrl_call_stack_from_unwind(Arena *arena, CTRL_Entity *process, CTRL_Unwind *bas
31333209
SLLQueuePush(first_frame, last_frame, dst_inline);
31343210
dst_inline->v.unwind_count = base_frame_idx;
31353211
dst_inline->v.regs = src->regs;
3212+
dst_inline->v.cfa = src->cfa;
31363213
frame_count += 1;
31373214
inline_frame_count += 1;
31383215
}
@@ -3142,6 +3219,7 @@ ctrl_call_stack_from_unwind(Arena *arena, CTRL_Entity *process, CTRL_Unwind *bas
31423219
SLLQueuePush(first_frame, last_frame, dst_base);
31433220
dst_base->v.unwind_count = base_frame_idx;
31443221
dst_base->v.regs = src->regs;
3222+
dst_base->v.cfa = src->cfa;
31453223
frame_count += 1;
31463224

31473225
// rjf: hook up inline frames to point to concrete frame, and to account for inline depth
@@ -4898,6 +4976,8 @@ ctrl_thread__eval_scope_begin(Arena *arena, CTRL_UserBreakpointList *user_bps, C
48984976
ctx->frame_base = push_array(arena, U64, 1);
48994977
// TODO(rjf): need to compute this out here somehow... ctx->frame_base[0] = ;
49004978
ctx->tls_base = push_array(arena, U64, 1);
4979+
// TODO: compute CFA
4980+
ctx->cfa = 0;
49014981
}
49024982
e_select_interpret_ctx(&scope->interpret_ctx, eval_dbg_infos_primary->rdi, thread_rip_voff);
49034983

0 commit comments

Comments
 (0)