Skip to content

Commit 0565fde

Browse files
WIP add support for GNU LibC TLS model
Unlike on Windows, TLS index isn't part of the module format. glibc stores it in the opaque link_map struct. There are many different heuristics a debugger can use to locate it but most of them are unreliable and prone to errors, or don't work when debugger attaches to a process. We adopt the newer approach of looking for special symbols in glibc that provide TLS index offset. These symbols were added in 2021, so programs linked with older glibc won't have TLS support.
1 parent 7347d2f commit 0565fde

File tree

8 files changed

+359
-236
lines changed

8 files changed

+359
-236
lines changed

src/ctrl/ctrl_core.c

Lines changed: 31 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ ctrl_exception_kind_from_dmn(DMN_ExceptionKind kind)
6060
return result;
6161
}
6262

63+
internal CTRL_TlsModel
64+
ctrl_dynamic_linker_type_from_dmn(DMN_TlsModel type)
65+
{
66+
CTRL_TlsModel result = CTRL_TlsModel_Null;
67+
switch(type)
68+
{
69+
default:{}break;
70+
case DMN_TlsModel_Null: {result = CTRL_TlsModel_Null;}break;
71+
case DMN_TlsModel_WinodwsNt: {result = CTRL_TlsModel_WinodwsNt;}break;
72+
case DMN_TlsModel_Gnu: {result = CTRL_TlsModel_Gnu;}break;
73+
}
74+
return result;
75+
}
76+
6377
internal String8
6478
ctrl_string_from_event_kind(CTRL_EventKind kind)
6579
{
@@ -616,6 +630,9 @@ ctrl_serialized_string_from_event(Arena *arena, CTRL_Event *event, U64 max)
616630
str8_serial_push_struct(scratch.arena, &srl, &event->exception_code);
617631
str8_serial_push_struct(scratch.arena, &srl, &event->rgba);
618632
str8_serial_push_struct(scratch.arena, &srl, &event->bp_flags);
633+
str8_serial_push_struct(scratch.arena, &srl, &event->tls_model);
634+
str8_serial_push_struct(scratch.arena, &srl, &event->tls_index);
635+
str8_serial_push_struct(scratch.arena, &srl, &event->tls_offset);
619636
String8 string = event->string;
620637
string.size = Min(string.size, max-srl.total_size);
621638
str8_serial_push_struct(scratch.arena, &srl, &string.size);
@@ -649,6 +666,9 @@ ctrl_event_from_serialized_string(Arena *arena, String8 string)
649666
read_off += str8_deserial_read_struct(string, read_off, &event.exception_code);
650667
read_off += str8_deserial_read_struct(string, read_off, &event.rgba);
651668
read_off += str8_deserial_read_struct(string, read_off, &event.bp_flags);
669+
read_off += str8_deserial_read_struct(string, read_off, &event.tls_model);
670+
read_off += str8_deserial_read_struct(string, read_off, &event.tls_index);
671+
read_off += str8_deserial_read_struct(string, read_off, &event.tls_offset);
652672
read_off += str8_deserial_read_struct(string, read_off, &event.string.size);
653673
event.string.str = push_array_no_zero(arena, U8, event.string.size);
654674
read_off += str8_deserial_read(string, read_off, event.string.str, event.string.size, 1);
@@ -1249,6 +1269,7 @@ ctrl_entity_store_apply_events(CTRL_EntityCtxRWStore *store, CTRL_EventList *lis
12491269
{
12501270
CTRL_Entity *machine = ctrl_entity_from_handle(&store->ctx, ctrl_handle_make(event->entity.machine_id, dmn_handle_zero()));
12511271
CTRL_Entity *process = ctrl_entity_alloc(store, machine, CTRL_EntityKind_Process, event->arch, event->entity, (U64)event->entity_id);
1272+
process->tls_model = event->tls_model;
12521273
}break;
12531274
case CTRL_EventKind_EndProc:
12541275
{
@@ -1370,6 +1391,8 @@ ctrl_entity_store_apply_events(CTRL_EntityCtxRWStore *store, CTRL_EventList *lis
13701391
ctrl_entity_equip_string(store, module, event->string);
13711392
module->timestamp = event->timestamp;
13721393
module->vaddr_range = event->vaddr_rng;
1394+
module->tls_index = event->tls_index;
1395+
module->tls_offset = event->tls_offset;
13731396
CTRL_Entity *first_module = ctrl_entity_child_from_kind(process, CTRL_EntityKind_Module);
13741397
if(first_module == module)
13751398
{
@@ -1723,26 +1746,6 @@ ctrl_entry_point_voff_from_module(CTRL_Handle module_handle)
17231746
return result;
17241747
}
17251748

1726-
internal Rng1U64
1727-
ctrl_tls_vaddr_range_from_module(CTRL_Handle module_handle)
1728-
{
1729-
Rng1U64 result = {0};
1730-
U64 hash = ctrl_hash_from_handle(module_handle);
1731-
U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count;
1732-
U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count;
1733-
CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx];
1734-
CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx];
1735-
MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next)
1736-
{
1737-
if(ctrl_handle_match(n->module, module_handle))
1738-
{
1739-
result = n->tls_vaddr_range;
1740-
break;
1741-
}
1742-
}
1743-
return result;
1744-
}
1745-
17461749
internal String8
17471750
ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_Handle module_handle)
17481751
{
@@ -3734,7 +3737,6 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
37343737

37353738
Arena *arena = arena_alloc();
37363739
U64 entry_point_voff = 0;
3737-
Rng1U64 tls_vaddr_range = {0};
37383740
U32 rdi_dbg_time = 0;
37393741
Guid rdi_dbg_guid = {0};
37403742
String8 exe_dbg_path = {0};
@@ -3855,34 +3857,6 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
38553857
dmn_process_read(process.dmn_handle, r1u64(vaddr_range.min + pdatas_voff_range.min, vaddr_range.min + pdatas_voff_range.max), pdatas);
38563858
}
38573859

3858-
// rjf: extract tls header
3859-
PE_TLSHeader64 tls_header = {0};
3860-
if(data_dir_count > PE_DataDirectoryIndex_TLS)
3861-
{
3862-
PE_DataDirectory dir = {0};
3863-
dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_TLS, &dir);
3864-
Rng1U64 tls_voff_range = r1u64((U64)dir.virt_off, (U64)dir.virt_off + (U64)dir.virt_size);
3865-
switch(file_header.machine)
3866-
{
3867-
default:{}break;
3868-
case COFF_MachineType_X86:
3869-
{
3870-
PE_TLSHeader32 tls_header32 = {0};
3871-
dmn_process_read_struct(process.dmn_handle, vaddr_range.min + tls_voff_range.min, &tls_header32);
3872-
tls_header.raw_data_start = (U64)tls_header32.raw_data_start;
3873-
tls_header.raw_data_end = (U64)tls_header32.raw_data_end;
3874-
tls_header.index_address = (U64)tls_header32.index_address;
3875-
tls_header.callbacks_address = (U64)tls_header32.callbacks_address;
3876-
tls_header.zero_fill_size = (U64)tls_header32.zero_fill_size;
3877-
tls_header.characteristics = (U64)tls_header32.characteristics;
3878-
}break;
3879-
case COFF_MachineType_X64:
3880-
{
3881-
dmn_process_read_struct(process.dmn_handle, vaddr_range.min + tls_voff_range.min, &tls_header);
3882-
}break;
3883-
}
3884-
}
3885-
38863860
// rjf: extract sections
38873861
U64 sec_array_off = opt_ext_off_range.max;
38883862
U64 sec_count = file_header.section_count;
@@ -3892,9 +3866,6 @@ ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_
38923866
// rjf: grab entry point vaddr
38933867
entry_point_voff = entry_point;
38943868

3895-
// rjf: calculate TLS vaddr range
3896-
tls_vaddr_range = r1u64(tls_header.index_address, tls_header.index_address+sizeof(U32));
3897-
38983869
// rjf: grab data about debug info
38993870
if(data_dir_count > PE_DataDirectoryIndex_DEBUG)
39003871
{
@@ -4469,11 +4440,12 @@ ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg,
44694440
case DMN_EventKind_CreateProcess:
44704441
{
44714442
CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts);
4472-
out_evt->kind = CTRL_EventKind_NewProc;
4473-
out_evt->msg_id = msg->msg_id;
4474-
out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->process);
4475-
out_evt->arch = event->arch;
4476-
out_evt->entity_id = event->code;
4443+
out_evt->kind = CTRL_EventKind_NewProc;
4444+
out_evt->msg_id = msg->msg_id;
4445+
out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->process);
4446+
out_evt->arch = event->arch;
4447+
out_evt->entity_id = event->code;
4448+
out_evt->tls_model = ctrl_dynamic_linker_type_from_dmn(event->tls_model);
44774449
ctrl_state->process_counter += 1;
44784450
}break;
44794451
case DMN_EventKind_CreateThread:
@@ -4508,6 +4480,8 @@ ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg,
45084480
out_evt1->rip_vaddr = event->address;
45094481
out_evt1->timestamp = exe_timestamp;
45104482
out_evt1->string = module_path;
4483+
out_evt1->tls_index = event->tls_index;
4484+
out_evt1->tls_offset = event->tls_offset;
45114485
CTRL_Event *out_evt2 = ctrl_event_list_push(scratch.arena, &evts);
45124486
String8 initial_debug_info_path = ctrl_initial_debug_info_path_from_module(scratch.arena, module_handle);
45134487
U64 debug_info_timestamp = os_properties_from_file_path(initial_debug_info_path).modified;

src/ctrl/ctrl_core.h

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ struct CTRL_UserBreakpointList
5959
U64 count;
6060
};
6161

62+
////////////////////////////////
63+
//~ Dynamic Linker Types
64+
65+
typedef U32 CTRL_TlsModel;
66+
enum
67+
{
68+
CTRL_TlsModel_Null,
69+
CTRL_TlsModel_WinodwsNt,
70+
CTRL_TlsModel_Gnu
71+
};
72+
6273
////////////////////////////////
6374
//~ rjf: Entity Handle Types
6475

@@ -119,6 +130,9 @@ struct CTRL_Entity
119130
U64 timestamp;
120131
CTRL_UserBreakpointFlags bp_flags;
121132
String8 string;
133+
CTRL_TlsModel tls_model;
134+
U64 tls_index;
135+
U64 tls_offset;
122136
};
123137

124138
typedef struct CTRL_EntityNode CTRL_EntityNode;
@@ -512,11 +526,14 @@ struct CTRL_Event
512526
U64 rip_vaddr;
513527
U64 stack_base;
514528
U64 tls_root;
529+
U64 tls_index;
530+
U64 tls_offset;
515531
U64 timestamp;
516532
U32 exception_code;
517533
U32 rgba;
518534
CTRL_UserBreakpointFlags bp_flags;
519535
String8 string;
536+
CTRL_TlsModel tls_model;
520537
};
521538

522539
typedef struct CTRL_EventNode CTRL_EventNode;
@@ -603,7 +620,6 @@ struct CTRL_ModuleImageInfoCacheNode
603620
EH_FrameHdr eh_frame_hdr;
604621
EH_PtrCtx eh_ptr_ctx;
605622
U64 entry_point_voff;
606-
Rng1U64 tls_vaddr_range;
607623
String8 initial_debug_info_path;
608624
Rng1U64 raddbg_section_voff_range;
609625
String8 raddbg_data;
@@ -776,6 +792,7 @@ internal U64 ctrl_hash_from_string(String8 string);
776792
internal U64 ctrl_hash_from_handle(CTRL_Handle handle);
777793
internal CTRL_EventCause ctrl_event_cause_from_dmn_event_kind(DMN_EventKind event_kind);
778794
internal CTRL_ExceptionKind ctrl_exception_kind_from_dmn(DMN_ExceptionKind kind);
795+
internal CTRL_TlsModel ctrl_dynamic_linker_type_from_dmn(DMN_TlsModel type);
779796
internal String8 ctrl_string_from_event_kind(CTRL_EventKind kind);
780797
internal String8 ctrl_string_from_msg_kind(CTRL_MsgKind kind);
781798
internal CTRL_EntityKind ctrl_entity_kind_from_string(String8 string);
@@ -916,7 +933,6 @@ internal B32 ctrl_thread_write_reg_block(CTRL_Handle thread, void *block);
916933
//- rjf: cache lookups
917934
internal PE_IntelPdata *ctrl_intel_pdata_from_module_voff(Arena *arena, CTRL_Handle module_handle, U64 voff);
918935
internal U64 ctrl_entry_point_voff_from_module(CTRL_Handle module_handle);
919-
internal Rng1U64 ctrl_tls_vaddr_range_from_module(CTRL_Handle module_handle);
920936
internal String8 ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_Handle module_handle);
921937
internal String8 ctrl_raddbg_data_from_module(Arena *arena, CTRL_Handle module_handle);
922938

@@ -993,7 +1009,7 @@ internal void ctrl_thread__module_close(CTRL_Handle process, CTRL_Handle module,
9931009
//- rjf: attached process running/event gathering
9941010
internal DMN_Event *ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg, DMN_RunCtrls *run_ctrls, CTRL_Spoof *spoof);
9951011

996-
//- rjf: eval helpers
1012+
//- rjf: eval helpers
9971013
internal U64 ctrl_eval_space_gen(E_Space space);
9981014
internal B32 ctrl_eval_space_read(E_Space space, void *out, Rng1U64 vaddr_range);
9991015

src/dbg_engine/dbg_engine_core.c

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -984,80 +984,68 @@ d_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64
984984
{
985985
ProfBeginFunction();
986986
U64 base_vaddr = 0;
987-
Temp scratch = scratch_begin(0, 0);
988987
if(!d_ctrl_targets_running())
989988
{
989+
Temp scratch = scratch_begin(0, 0);
990+
990991
//- rjf: unpack module info
991-
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr);
992-
Rng1U64 tls_vaddr_range = ctrl_tls_vaddr_range_from_module(module->handle);
993-
U64 addr_size = bit_size_from_arch(process->arch)/8;
994-
995-
//- rjf: read module's TLS index
996-
U64 tls_index = 0;
997-
if(addr_size != 0)
998-
{
999-
CTRL_ProcessMemorySlice tls_index_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, tls_vaddr_range, 0, 0);
1000-
if(tls_index_slice.data.size >= addr_size)
1001-
{
1002-
tls_index = *(U64 *)tls_index_slice.data.str;
1003-
}
1004-
}
1005-
1006-
//- rjf: PE path
1007-
if(addr_size != 0)
992+
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr);
993+
U64 addr_size = byte_size_from_arch(process->arch);
994+
995+
switch(process->tls_model)
1008996
{
1009-
U64 thread_info_addr = root_vaddr;
1010-
U64 tls_addr_off = tls_index*addr_size;
1011-
U64 tls_addr_array = 0;
1012-
CTRL_ProcessMemorySlice tls_addr_array_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(thread_info_addr, thread_info_addr+addr_size), 0, 0);
1013-
String8 tls_addr_array_data = tls_addr_array_slice.data;
1014-
if(tls_addr_array_data.size >= 8)
997+
case CTRL_TlsModel_Null: {}break;
998+
case CTRL_TlsModel_WinodwsNt:
1015999
{
1016-
MemoryCopy(&tls_addr_array, tls_addr_array_data.str, sizeof(U64));
1017-
}
1018-
CTRL_ProcessMemorySlice result_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(tls_addr_array + tls_addr_off, tls_addr_array + tls_addr_off + addr_size), 0, 0);
1019-
String8 result_data = result_slice.data;
1020-
if(result_data.size >= 8)
1021-
{
1022-
MemoryCopy(&base_vaddr, result_data.str, sizeof(U64));
1023-
}
1024-
}
1025-
1026-
//- rjf: non-PE path (not implemented)
1027-
#if 0
1028-
if(!bin_is_pe)
1029-
{
1030-
// TODO(rjf): not supported. old code from the prototype that Nick had sketched out:
1031-
// TODO(nick): This code works only if the linked c runtime library is glibc.
1032-
// Implement CRT detection here.
1033-
1034-
U64 dtv_addr = UINT64_MAX;
1035-
demon_read_memory(process->demon_handle, &dtv_addr, thread_info_addr, addr_size);
1036-
1037-
/*
1038-
union delta_thread_vector
1000+
// read thread local base pointer out of TEB
1001+
U64 thread_local_base = root_vaddr;
1002+
U64 tls_addr_array = 0;
1003+
CTRL_ProcessMemorySlice tls_addr_array_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(thread_local_base, thread_local_base + addr_size), 0, 0);
1004+
String8 tls_array_vaddr_data = tls_addr_array_slice.data;
1005+
if(tls_array_vaddr_data.size == addr_size)
10391006
{
1040-
size_t counter;
1041-
struct
1007+
U64 tls_array_vaddr = 0;
1008+
MemoryCopyStr8(&tls_array_vaddr, tls_array_vaddr_data);
1009+
1010+
// read thread local storage pointer (array of TLS pointers, one per module)
1011+
U64 tls_ptr_vaddr = tls_array_vaddr + module->tls_index * addr_size;
1012+
CTRL_ProcessMemorySlice result_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(tls_ptr_vaddr, tls_ptr_vaddr + addr_size), 0, 0);
1013+
String8 result_data = result_slice.data;
1014+
if(result_data.size == addr_size)
10421015
{
1043-
void *value;
1044-
void *to_free;
1045-
} pointer;
1046-
};
1047-
*/
1048-
1049-
U64 dtv_size = 16;
1050-
U64 dtv_count = 0;
1051-
demon_read_memory(process->demon_handle, &dtv_count, dtv_addr - dtv_size, addr_size);
1052-
1053-
if (tls_index > 0 && tls_index < dtv_count)
1016+
MemoryCopyStr8(&base_vaddr, result_data);
1017+
}
1018+
}
1019+
}break;
1020+
case CTRL_TlsModel_Gnu:
10541021
{
1055-
demon_read_memory(process->demon_handle, &result, dtv_addr + dtv_size*tls_index, addr_size);
1056-
}
1022+
if(module->tls_index > 0) // zero is reserved for the generation counter
1023+
{
1024+
// read dynamic thread vector pointer (one per dynamic module)
1025+
U64 dtv_base = root_vaddr;
1026+
U64 dtv_size = addr_size * 2; // union dtv { size_t counter; struct dtv_pointer { void *val, *to_free; }; };
1027+
U64 dtv_pointer_vaddr = dtv_base + module->tls_index * dtv_size;
1028+
CTRL_ProcessMemorySlice dtv_pointer_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(dtv_pointer_vaddr, dtv_pointer_vaddr + dtv_size), 0, 0);
1029+
String8 dtv_pointer_data = dtv_pointer_slice.data;
1030+
if(dtv_pointer_data.size == dtv_size)
1031+
{
1032+
U64 dtv_pointer = 0;
1033+
MemoryCopyStr8(&dtv_pointer, dtv_pointer_data);
1034+
1035+
// verify that TLS block was allocated
1036+
U64 tls_dtv_unallocated = addr_size == 4 ? max_U32 : max_U64;
1037+
if(dtv_pointer != tls_dtv_unallocated)
1038+
{
1039+
base_vaddr = dtv_pointer + module->tls_offset; // add tls_offset because DT_NEEDED modules share TLS block with the main exe
1040+
}
1041+
}
1042+
}
1043+
}break;
1044+
default: {InvalidPath;}break;
10571045
}
1058-
#endif
1046+
1047+
scratch_end(scratch);
10591048
}
1060-
scratch_end(scratch);
10611049
ProfEnd();
10621050
return base_vaddr;
10631051
}

0 commit comments

Comments
 (0)