// Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// //~ rjf: Generated Code #include "generated/linux_demon.meta.c" //////////////////////////////// //~ rjf: Memory R/W Helpers internal U64 lnx_dmn_read(int memory_fd, Rng1U64 range, void *dst) { size_t cursor = 0; size_t size = (size_t)dim_1u64(range); for(;cursor < size;) { 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; } cursor += actual_read; } return (U64)cursor; } internal B32 lnx_dmn_write(int memory_fd, Rng1U64 range, void *src) { 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)); if(actual_write <= 0) { break; } cursor += actual_write; } B32 is_written = (cursor == size); 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) { String8 result = {0}; U64 string_size = 0; for(U64 vaddr = base_vaddr; string_size < cap_size; vaddr += 1, string_size += 1) { char byte = 0; if(LNX_RETRY_ON_EINTR(pread(memory_fd, &byte, sizeof(byte), vaddr) <= 0)) { break; } if(byte == '\0' || byte == '\n') { break; } } if(string_size != 0) { char *buf = push_array_no_zero(arena, char, string_size+1); LNX_RETRY_ON_EINTR(pread(memory_fd, buf, string_size, base_vaddr)); buf[string_size] = '\0'; result = str8((U8 *)buf, string_size); } return result; } internal String8 lnx_dmn_read_string(Arena *arena, int memory_fd, U64 vaddr) { return lnx_dmn_read_string_capped(arena, memory_fd, vaddr, 4096); } internal MACHINE_OP_MEM_READ(lnx_dmn_machine_op_mem_read) { U64 read_size = lnx_dmn_read(*(int *)ud, r1u64(addr, addr + buffer_size), buffer); return read_size == buffer_size ? MachineOpResult_Ok : MachineOpResult_Fail; } internal int lnx_dmn_ptrace_seize(pid_t pid) { // TODO: PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEFORK return LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE)); } //////////////////////////////// //~ 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 name_vaddr, B32 is_main) { DMN_ModuleInfo *info = push_array(arena, DMN_ModuleInfo, 1); Temp scratch = scratch_begin(&arena, 1); { //- rjf: read ELF signature U8 elf_sig_maybe[ELF_Identifier_Max] = {0}; lnx_dmn_read(memory_fd, r1u64(base_vaddr, base_vaddr + sizeof(elf_sig_maybe)), elf_sig_maybe); //- rjf: unpack elf header info Arch arch = Arch_Null; U64 entry_point_voff = 0; ELF_Type elf_type = ELF_Type_None; U64 phdrs_off = 0; U64 phdrs_entry_size = 0; U64 phdrs_count = 0; U64 shdrs_off = 0; U64 shdrs_entry_size = 0; U64 shdrs_count = 0; U64 shdrs_strings_idx = 0; { // rjf: read ehdr ELF_Hdr64 ehdr = {0}; switch(elf_sig_maybe[ELF_Identifier_Class]) { default: case ELF_Class_None: {}break; case ELF_Class_32: { ELF_Hdr32 ehdr32 = {0}; lnx_dmn_read_struct(memory_fd, base_vaddr, &ehdr32); MemoryCopy(ehdr.e_ident, ehdr32.e_ident, sizeof(ehdr.e_ident)); ehdr.e_type = ehdr32.e_type; ehdr.e_machine = ehdr32.e_machine; ehdr.e_version = ehdr32.e_version; ehdr.e_entry = (U64)ehdr32.e_entry; ehdr.e_phoff = (U64)ehdr32.e_phoff; ehdr.e_shoff = (U64)ehdr32.e_shoff; ehdr.e_flags = ehdr32.e_flags; ehdr.e_ehsize = ehdr32.e_ehsize; ehdr.e_phentsize = ehdr32.e_phentsize; ehdr.e_phnum = ehdr32.e_phnum; ehdr.e_shentsize = ehdr32.e_shentsize; ehdr.e_shnum = ehdr32.e_shnum; ehdr.e_shstrndx = ehdr32.e_shstrndx; }break; case ELF_Class_64: { lnx_dmn_read_struct(memory_fd, base_vaddr, &ehdr); }break; } // rjf: unpack ehdr arch = arch_from_elf_machine(ehdr.e_machine); entry_point_voff = ehdr.e_entry - base_vaddr; elf_type = ehdr.e_type; phdrs_off = ehdr.e_phoff; phdrs_entry_size = ehdr.e_phentsize; phdrs_count = ehdr.e_phnum; shdrs_off = ehdr.e_shoff; shdrs_entry_size = ehdr.e_shentsize; shdrs_count = ehdr.e_shnum; shdrs_strings_idx = ehdr.e_shstrndx; } //- rjf: decide on rebase for this module U64 module_rebase = 0; if(elf_type == ELF_Type_Dyn) { module_rebase = base_vaddr; } //- rjf: determine shdrs/phdrs addresses U64 phdrs_vaddr = base_vaddr + phdrs_off; U64 phdrs_vaddr_opl = phdrs_vaddr + phdrs_count*phdrs_entry_size; U64 shdrs_vaddr = base_vaddr + shdrs_off; //- rjf: scan phdrs, unpack info U64 image_vsize = 0; Rng1U64 eh_frame_header_vaddr_range = {0}; { for EachIndex(phdr_idx, phdrs_count) { U64 phdr_vaddr = phdrs_vaddr + phdr_idx*phdrs_entry_size; ELF_Phdr64 phdr = {0}; switch(elf_sig_maybe[ELF_Identifier_Class]) { default:{}break; case ELF_Class_32: { ELF_Phdr32 phdr32 = {0}; lnx_dmn_read_struct(memory_fd, phdr_vaddr, &phdr32); phdr.p_type = phdr32.p_type; phdr.p_flags = phdr32.p_flags; phdr.p_offset = (U64)phdr32.p_offset; phdr.p_vaddr = (U64)phdr32.p_vaddr; phdr.p_paddr = (U64)phdr32.p_paddr; phdr.p_filesz = (U64)phdr32.p_filesz; phdr.p_memsz = (U64)phdr32.p_memsz; phdr.p_align = (U64)phdr32.p_align; }break; case ELF_Class_64: { lnx_dmn_read_struct(memory_fd, phdr_vaddr, &phdr); }break; } if(phdr.p_type == ELF_PType_Load) { image_vsize = (module_rebase + phdr.p_vaddr + phdr.p_memsz) - base_vaddr; } if(phdr.p_type == ELF_PType_GnuEHFrame) { eh_frame_header_vaddr_range = r1u64(module_rebase + phdr.p_vaddr, module_rebase + phdr.p_vaddr + phdr.p_memsz); } } } //- rjf: scan shdrs, unpack info Rng1U64 raddbg_info_voff_range = {0}; U64 raddbg_is_attached_marker_voff = 0; { // rjf: read all shdrs ELF_Shdr64 *shdrs = push_array(scratch.arena, ELF_Shdr64, shdrs_count); for EachIndex(shdr_idx, shdrs_count) { U64 shdr_vaddr = shdrs_vaddr + shdr_idx*shdrs_entry_size; switch(elf_sig_maybe[ELF_Identifier_Class]) { case ELF_Class_32: { ELF_Shdr32 shdr32 = {0}; lnx_dmn_read_struct(memory_fd, shdr_vaddr, &shdr32); shdrs[shdr_idx].sh_name = shdr32.sh_name; shdrs[shdr_idx].sh_type = shdr32.sh_type; shdrs[shdr_idx].sh_flags = (U64)shdr32.sh_flags; shdrs[shdr_idx].sh_addr = (U64)shdr32.sh_addr; shdrs[shdr_idx].sh_offset = (U64)shdr32.sh_offset; shdrs[shdr_idx].sh_size = (U64)shdr32.sh_size; shdrs[shdr_idx].sh_link = shdr32.sh_link; shdrs[shdr_idx].sh_info = shdr32.sh_info; shdrs[shdr_idx].sh_addralign= (U64)shdr32.sh_addralign; shdrs[shdr_idx].sh_entsize = (U64)shdr32.sh_entsize; }break; case ELF_Class_64: { lnx_dmn_read_struct(memory_fd, shdr_vaddr, &shdrs[shdr_idx]); }break; } } // rjf: get string shdr ELF_Shdr64 *string_shdr = 0; if(shdrs_strings_idx < shdrs_count) { string_shdr = &shdrs[shdrs_strings_idx]; } // rjf: scan shdrs, using names if(string_shdr != 0) { for EachIndex(shdr_idx, shdrs_count) { ELF_Shdr64 *shdr = &shdrs[shdr_idx]; U64 name_vaddr = string_shdr->sh_addr + shdr->sh_name; String8 shdr_name = lnx_dmn_read_string(scratch.arena, memory_fd, name_vaddr); if(str8_match(shdr_name, s(".raddbg"), 0)) { raddbg_info_voff_range = r1u64(shdr->sh_addr - base_vaddr, shdr->sh_addr - base_vaddr + shdr->sh_size); } if(str8_match(shdr_name, s(".rdbgia"), 0)) { raddbg_is_attached_marker_voff = (shdr->sh_addr - base_vaddr); } } } } //- rjf: unpack tls info 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); tls_modid_range.max = Min(tls_modid_range.max, tls_modid_range.min + sizeof(tls_index)); tls_offset_range.max = Min(tls_offset_range.max, tls_offset_range.min + sizeof(tls_offset)); lnx_dmn_read(memory_fd, tls_modid_range, &tls_index); lnx_dmn_read(memory_fd, tls_offset_range, &tls_offset); } //- 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}; U64 debug_info_timestamp = 0; U64 debug_info_age = 0; //- rjf: fill result info->arch = arch; info->vsize = image_vsize; info->entry_point_voff = entry_point_voff; info->module_path = module_path; info->debug_info_path = debug_info_path; info->debug_info_guid = debug_info_guid; info->debug_info_timestamp = debug_info_timestamp; info->debug_info_age = debug_info_age; info->tls_index = tls_index; info->tls_offset = tls_offset; info->eh_frame_header_vaddr_range = eh_frame_header_vaddr_range; info->raddbg_info_voff_range = raddbg_info_voff_range; info->raddbg_is_attached_marker_voff = raddbg_is_attached_marker_voff; } scratch_end(scratch); return info; } //////////////////////////////// //~ rjf: Trap Setting internal LNX_DMN_ActiveTrap * lnx_dmn_set_trap(Arena *arena, DMN_Trap *trap) { ARCH_Info *arch = arch_info_from_arch(Arch_CURRENT); String8 trap_inst = arch->trap_instruction; U8 *swap_bytes = push_array(arena, U8, trap_inst.size); B32 good_read = dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr + trap_inst.size), swap_bytes); B32 good_write = 0; if(good_read) { good_write = dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr + trap_inst.size), trap_inst.str); } LNX_DMN_ActiveTrap *result = push_array(arena, LNX_DMN_ActiveTrap, 1); result->good = (good_read && good_write); result->trap = trap; result->swap_bytes = str8(swap_bytes, trap_inst.size); return result; } //////////////////////////////// //~ 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) { 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}; { 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; U64 max = rebase + phdr.p_vaddr + phdr.p_memsz; result.min = Min(result.min, min); result.max = Max(result.max, max); } } return result; } //////////////////////////////// //~ rjf: Entity Functions //- rjf: base allocation / deallocation internal LNX_DMN_Entity * lnx_dmn_entity_alloc(LNX_DMN_EntityKind kind) { LNX_DMN_Entity *entity = lnx_dmn_state->free_entity; if(entity != 0) { SLLStackPop(lnx_dmn_state->free_entity); } else { entity = push_array(lnx_dmn_state->entities_arena, LNX_DMN_Entity, 1); lnx_dmn_state->entities_count += 1; } U32 gen = entity->gen; entity->gen += 1; entity->kind = 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) { Temp scratch = scratch_begin(0, 0); LNX_DMN_Process *process = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_Process)->process; process->pid = pid; process->fd = LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/mem", pid).str, O_RDWR)); process->state = state; process->debug_subprocesses = debug_subprocesses; process->is_cow = is_cow; process->parent_process = parent_process; // update pending process tracker if(state != LNX_DMN_ProcessState_Normal) { lnx_dmn_state->process_pending_creation += 1; } // add process to the list DLLPushBack(lnx_dmn_state->first_process, lnx_dmn_state->last_process, process); lnx_dmn_state->process_count += 1; // 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; } 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) { //- 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; { for(LNX_DMN_Module *m = ctx->module_slots[slot_idx].first; m != 0; m = m->hash_next) { if(m->base_vaddr == base_vaddr) { module = m; break; } } } //- rjf: load this module if it's not already loaded if(module == 0) { Temp scratch = scratch_begin(0, 0); // rjf: read module's ELF header 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]; // rjf: unpack module's vaddr range U64 module_phdr_vaddr = base_vaddr + module_ehdr.e_phoff; 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; 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); 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; 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; // rjf: link into context's module 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); } scratch_end(scratch); } return module; } internal void lnx_dmn_module_release(LNX_DMN_ProcessCtx *ctx, LNX_DMN_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; // 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); } //- rjf: context cloning internal LNX_DMN_ProcessCtx * lnx_dmn_process_ctx_clone(LNX_DMN_Process *new_owner, LNX_DMN_ProcessCtx *ctx) { LNX_DMN_ProcessCtx *result = &lnx_dmn_entity_alloc(LNX_DMN_EntityKind_ProcessCtx)->process_ctx; result->arena = arena_alloc(); result->arch = ctx->arch; result->rdebug_vaddr = ctx->rdebug_vaddr; result->dl_class = ctx->dl_class; 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; // clone probes 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[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); dst->args = stap_arg_array_copy(result->arena, src->args); dst->pc = src->pc; dst->semaphore = src->semaphore; } // clone probe traps for EachNode(src, LNX_DMN_ActiveTrap, ctx->first_probe_trap) { DMN_Trap *src_trap = src->trap; DMN_Trap *dst_trap = push_array(result->arena, DMN_Trap, 1); dst_trap->process = lnx_dmn_handle_from_process(new_owner); dst_trap->vaddr = src_trap->vaddr; dst_trap->id = src_trap->id; dst_trap->flags = src_trap->flags; dst_trap->size = src_trap->size; LNX_DMN_ActiveTrap *dst = push_array(result->arena, LNX_DMN_ActiveTrap, 1); dst->trap = dst_trap; dst->swap_bytes = str8_copy(result->arena, src->swap_bytes); SLLQueuePush(result->first_probe_trap, result->last_probe_trap, dst); } // clone modules 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); 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; } //- rjf: entity <-> handle internal DMN_Handle lnx_dmn_handle_from_entity(LNX_DMN_Entity *entity) { DMN_Handle handle = {0}; U64 index = (U64)(entity - lnx_dmn_state->entities_base); if(index <= max_U32) { handle.u32[0] = index; handle.u32[1] = entity->gen; } return handle; } internal DMN_Handle 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_thread(LNX_DMN_Thread *thread) { return lnx_dmn_handle_from_entity((LNX_DMN_Entity *)thread); } internal DMN_Handle lnx_dmn_handle_from_module(LNX_DMN_Module *module) { return lnx_dmn_handle_from_entity((LNX_DMN_Entity *)module); } internal LNX_DMN_Entity * 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]; 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) { result = &lnx_dmn_state->entities_base[index]; } } return result; } internal LNX_DMN_Process * 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_Thread * lnx_dmn_thread_from_handle(DMN_Handle thread_handle) { return (LNX_DMN_Thread *)lnx_dmn_entity_from_handle(thread_handle, LNX_DMN_EntityKind_Thread); } internal LNX_DMN_Module * 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) { 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) { 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; } //////////////////////////////// //~ rjf: Thread Helpers internal U64 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); return result; } internal void lnx_dmn_thread_write_ip(LNX_DMN_Thread *thread, U64 ip) { ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); arch_reg_block_write_ip(arch_info, thread->reg_block, ip); thread->is_reg_block_dirty = 1; } internal B32 lnx_dmn_thread_read_reg_block(LNX_DMN_Thread *thread) { B32 is_reg_block_read = 0; if(thread->state == LNX_DMN_ThreadState_Stopped) { switch(thread->process->ctx->arch) { default:{}break; case Arch_x64: { LNX_DMN_ProcessCtx *process_ctx = thread->process->ctx; X64_RegBlock *dst = thread->reg_block; // general purpose registers { LNX_DMN_GprsX64 src; int ptrace_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_len = sizeof(src), .iov_base = &src })); if(ptrace_result < 0) { goto exit; } 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->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; thread->orig_rax = src.orig_rax; } // xsave { Temp scratch = scratch_begin(0, 0); X64_XSave *xsave = 0; X64_FXSave *fxsave = 0; // get xsave if(x64_is_xsave_supported()) { 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; fxsave = &xsave->fxsave; } // get fxsave if(fxsave == 0) { 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; } // copy fxsave registers if(fxsave) { X64_FXSave *src = fxsave; // copy x87 registers dst->fcw = src->fcw; dst->fsw = src->fsw; dst->ftw = src->ftw; dst->fop = src->fop; dst->fip = src->fip; dst->fdp = src->fdp; dst->mxcsr = src->mxcsr; dst->mxcsr_mask = src->mxcsr_mask; for EachIndex(i, 8) { MemoryCopy(&dst->st0 + i, src->st_space + i, sizeof(U80)); } // SSE registers are always available in x64 { U128 *xmm_d = fxsave->xmm_space; U512 *zmm_s = &dst->zmm0; for EachIndex(i, 16) { MemoryCopy(&zmm_s[i], &xmm_d[i], sizeof(*xmm_d)); } } } // copy xsave registers // // compact register layout is not supported (xsave->header.xcomp_bv == 0) // if(xsave && xsave->header.xcomp_bv == 0) { if(xsave->header.xstate_bv & X64_XStateComponentFlag_AVX && process_ctx->xsave_layout.avx_offset + 16*sizeof(U128) <= process_ctx->xsave_size) { U128 *avx_s = (U128 *)((U8 *)xsave + process_ctx->xsave_layout.avx_offset); U512 *zmm_d = &dst->zmm0; for EachIndex(n, 16) { MemoryCopy(&zmm_d[n].u8[16], &avx_s[n], sizeof(U128)); } } if(xsave->header.xstate_bv & X64_XStateComponentFlag_OPMASK && process_ctx->xsave_layout.opmask_offset + sizeof(U64) * 8 <= process_ctx->xsave_size) { U64 *kmask_s = (U64 *)((U8 *)xsave + process_ctx->xsave_layout.opmask_offset); U64 *kmask_d = &dst->k0; for EachIndex(n, 8) { MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(U64)); } } if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM_H && process_ctx->xsave_layout.zmm_h_offset + sizeof(U256) * 16 <= process_ctx->xsave_size) { U256 *avx512h_s = (U256 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_h_offset); U512 *zmmh_d = &dst->zmm0; for EachIndex(n, 16) { MemoryCopy(&zmmh_d[n].u8[32], &avx512h_s[n], sizeof(U256)); } } if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM && process_ctx->xsave_layout.zmm_offset + sizeof(U512) * 16 <= process_ctx->xsave_size) { U512 *avx512_s = (U512 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_offset); U512 *zmm_d = &dst->zmm16; for EachIndex(n, 16) { MemoryCopy(&zmm_d[n], &avx512_s[n], sizeof(U512)); } } if(xsave->header.xstate_bv & X64_XStateComponentFlag_CETU && process_ctx->xsave_layout.cet_u_offset + sizeof(U64)*2 <= process_ctx->xsave_size) { U64 *cet_u = (U64 *)((U8 *)xsave + process_ctx->xsave_layout.cet_u_offset); dst->cetmsr = cet_u[0]; dst->cetssp = cet_u[1]; } } scratch_end(scratch); } // debug registers { U64 *dr_d = &dst->dr0; for EachIndex(n, 8) { if(n != 4 && n != 5) { U64 offset = OffsetOf(LNX_DMN_UserX64, u_debugreg[n]); errno = 0; long peek_result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_PEEKUSER, thread->tid, PtrFromInt(offset), 0)); if(errno != 0) { goto exit; } dr_d[n] = peek_result; } } } is_reg_block_read = 1; }break; } } exit:; return is_reg_block_read; } internal B32 lnx_dmn_thread_write_reg_block(LNX_DMN_Thread *thread) { B32 is_reg_block_written = 0; if(thread->state == LNX_DMN_ThreadState_Stopped) { switch(thread->process->ctx->arch) { case Arch_arm64: case Arch_arm32: case Arch_x86: case Arch_COUNT: case Arch_Null:{}break; case Arch_x64: { LNX_DMN_ProcessCtx *process_ctx = thread->process->ctx; X64_RegBlock *src = thread->reg_block; B32 good = 1; // rjf: write general purpose registers { 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) { good = 0; } } // 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.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) { U128 *avx_d = (U128 *)(xsave_raw + process_ctx->xsave_layout.avx_offset); U512 *zmm_s = &src->zmm0; for EachIndex(n, 16) { MemoryCopy(&avx_d[n], &zmm_s[n].u8[16], sizeof(U128)); } 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) { U64 *kmask_d = (U64 *)(xsave_raw + process_ctx->xsave_layout.opmask_offset); U64 *kmask_s = &src->k0; for EachIndex(n, 8) { MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(U64)); } dst->header.xstate_bv |= X64_XStateComponentFlag_OPMASK; } } if(process_ctx->xsave_layout.zmm_h_offset) { if(process_ctx->xsave_layout.zmm_h_offset + sizeof(U256) * 16 <= process_ctx->xsave_size) { U256 *avx512h_d = (U256 *)(xsave_raw + process_ctx->xsave_layout.zmm_h_offset); U512 *zmmh_s = &src->zmm0; for EachIndex(n, 16) { MemoryCopy(&avx512h_d[n], &zmmh_s[n].u8[32], sizeof(U256)); } dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM_H; } } if(process_ctx->xsave_layout.zmm_offset) { if(process_ctx->xsave_layout.zmm_offset + sizeof(U512) * 16 <= process_ctx->xsave_size) { U512 *avx512_d = (U512 *)(xsave_raw + process_ctx->xsave_layout.zmm_offset); U512 *zmm_s = &src->zmm16; for EachIndex(n, 16) { MemoryCopy(&avx512_d[n], &zmm_s[n], sizeof(U512)); } dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM; } } if(process_ctx->xsave_layout.cet_u_offset) { if(process_ctx->xsave_layout.cet_u_offset + sizeof(U64) * 2 <= process_ctx->xsave_size) { U64 *cet_u = (U64 *)(xsave_raw + process_ctx->xsave_layout.cet_u_offset); cet_u[0] = src->cetmsr; cet_u[1] = src->cetssp; dst->header.xstate_bv |= X64_XStateComponentFlag_CETU; } } 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) { log_infof("ptrace error: PTRACE_SETREGSET failed, when trying to write xsave registers."); } scratch_end(scratch); } // rjf: write debug registers if(good) { src->dr7 |= (1 << 10); U64 *dr_s = &src->dr0; for EachIndex(n, 8) { 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 = good; }break; } } return is_reg_block_written; } internal B32 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_x86: case Arch_arm32: case Arch_arm64: case Arch_COUNT: case Arch_Null:{}break; case Arch_x64: { X64_RegBlock *reg_block = thread->reg_block; if(is_on) { reg_block->rflags |= X64_RFlag_Trap; } else { reg_block->rflags &= ~X64_RFlag_Trap; } thread->is_reg_block_dirty = 1; is_flag_set = 1; }break; } return is_flag_set; } internal U64 lnx_dmn_tls_root_vaddr_from_reg_block(int fd, Arch arch, void *reg_block) { U64 result = 0; switch(arch) { case Arch_x86: case Arch_arm32: case Arch_arm64: case Arch_Null: case Arch_COUNT:{}break; case Arch_x64: { X64_RegBlock *reg_block_x64 = (X64_RegBlock *)reg_block; U64 dtv_pointer = 0; if(lnx_dmn_read_struct(fd, reg_block_x64->fsbase + 8, &dtv_pointer)) { result = dtv_pointer; } }break; } return result; } //////////////////////////////// //~ Debug Event Pushers internal void lnx_dmn_push_event_load_module(Arena *arena, DMN_EventList *events, LNX_DMN_Thread *thread, LNX_DMN_Module *module) { 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); e->kind = DMN_EventKind_LoadModule; e->process = lnx_dmn_handle_from_process(thread->process); e->thread = lnx_dmn_handle_from_thread(thread); e->module = lnx_dmn_handle_from_module(module); e->arch = module_info->arch; e->address = module->base_vaddr; e->size = module->size; e->string = module_name; e->module_info = module_info; } } internal void lnx_dmn_push_event_unload_module(Arena *arena, DMN_EventList *events, LNX_DMN_Process *process, LNX_DMN_Module *module) { DMN_Event *e = dmn_event_list_push(arena, events); e->kind = DMN_EventKind_UnloadModule; e->process = lnx_dmn_handle_from_process(process); e->module = lnx_dmn_handle_from_module(module); } //////////////////////////////// //~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) internal void dmn_init(void) { 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->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"); if(tls_modid_desc && tls_offset_desc) { 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; } } } } //////////////////////////////// //~ rjf: @dmn_os_hooks Blocking Control Thread Operations (Implemented Per-OS) internal DMN_CtrlCtx * dmn_ctrl_begin(void) { DMN_CtrlCtx *ctx = (DMN_CtrlCtx *)1; lnx_dmn_ctrl_thread = 1; return ctx; } internal void dmn_ctrl_exclusive_access_begin(void) { MutexScope(lnx_dmn_state->access_mutex) { lnx_dmn_state->access_run_state = 1; } } internal void dmn_ctrl_exclusive_access_end(void) { MutexScope(lnx_dmn_state->access_mutex) { lnx_dmn_state->access_run_state = 0; } } internal U32 dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) { Temp scratch = scratch_begin(0, 0); //- rjf: setup target command line U64 argc = params->cmd_line.node_count + 1; char **argv = push_array(scratch.arena, char *, argc); { U64 idx = 0; for EachNode(n, String8Node, params->cmd_line.first) { argv[idx] = (char *)str8_copy(scratch.arena, n->string).str; idx += 1; } } //- rjf: setup target environment U64 envc = lnx_state.default_env_count + params->env.node_count + 1; char **envp = push_array(scratch.arena, char *, envc); { // copy default environment MemoryCopyTyped(envp, lnx_state.default_env, lnx_state.default_env_count); // copy user environment U64 idx = lnx_state.default_env_count; for EachNode(n, String8Node, params->env.first) { envp[idx] = (char *)str8_copy(scratch.arena, n->string).str; idx += 1; } } //- rjf: create zero-terminated work directory path char *work_dir_path = (char *)str8_copy(scratch.arena, params->path).str; //- rjf: fork process pid_t pid = fork(); //- rjf: child process if(pid == 0) { // wait for seize if(LNX_RETRY_ON_EINTR(raise(SIGSTOP)) < 0) { goto child_exit; } // change work directory to tracee if(LNX_RETRY_ON_EINTR(chdir(work_dir_path)) < 0) { goto child_exit; } // replace process with target program if(LNX_RETRY_ON_EINTR(execve(argv[0], argv, envp)) < 0) { goto child_exit; } child_exit:; exit(0); } //- rjf: parent process else if(pid > 0) { // try to seize child process if(lnx_dmn_ptrace_seize(pid) >= 0) { // tracee was successfully seized, create process lnx_dmn_process_alloc(pid, LNX_DMN_ProcessState_Launch, 0, params->debug_subprocesses, 0); } else { Assert(0 && "failed to ptrace child process"); LNX_RETRY_ON_EINTR(kill(SIGKILL, pid)); LNX_RETRY_ON_EINTR(waitpid(pid, 0, WNOHANG)); pid = 0; } } scratch_end(scratch); return pid; } internal B32 dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) { B32 is_attached = 0; if(lnx_dmn_ptrace_seize(pid) >= 0) { if(LNX_RETRY_ON_EINTR(kill(pid, SIGSTOP)) >= 0) { lnx_dmn_process_alloc(pid, LNX_DMN_ProcessState_Attach, 0, 1, 0); is_attached = 1; } else { LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, pid, 0, 0)); } } return is_attached; } internal B32 dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process_handle, U32 exit_code) { B32 result = 0; mutex_take(lnx_dmn_state->halter_mutex); LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); if(process) { result = LNX_RETRY_ON_EINTR(kill(process->pid, SIGKILL)) >= 0; } mutex_drop(lnx_dmn_state->halter_mutex); return result; } internal B32 dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process_handle) { B32 result = 0; mutex_take(lnx_dmn_state->halter_mutex); LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); if(process) { result = LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, process->pid, 0, 0)) >= 0; } mutex_drop(lnx_dmn_state->halter_mutex); 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) { Temp scratch = scratch_begin(&arena, 1); DMN_EventList events = {0}; ////////////////////////////// //- 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); ////////////////////////////// //- rjf: wait for signals from the running threads // if(lnx_dmn_state->process_count > 0) { ARCH_Info *arch_info = arch_info_from_arch(Arch_CURRENT); //////////////////////////// //- rjf: read initial bytes at all trap locations // 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); { U64 trap_idx = 0; 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_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); } } } } //////////////////////////// //- 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); if(single_step_thread) { lnx_dmn_set_single_step_flag(single_step_thread, 1); } } //////////////////////////// //- rjf: schedule threads to run // 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) { //- rjf: determine if this process is frozen B32 process_is_frozen = 0; if(ctrls->run_entities_are_processes) { for EachIndex(idx, ctrls->run_entity_count) { if(dmn_handle_match(ctrls->run_entities[idx], lnx_dmn_handle_from_process(process))) { process_is_frozen = 1; break; } } } //- rjf: schedule threads in this process for EachNode(thread, LNX_DMN_Thread, process->first_thread) { //- rjf: determine if this thread is frozen B32 is_frozen = 0; // rjf: not single-stepping? determine based on run controls freezing info if(dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread)) { if(ctrls->run_entities_are_processes) { is_frozen = process_is_frozen; } else { for EachIndex(idx, ctrls->run_entity_count) { if(dmn_handle_match(ctrls->run_entities[idx], lnx_dmn_handle_from_thread(thread))) { is_frozen = 1; break; } } } if(ctrls->run_entities_are_unfrozen) { is_frozen ^= 1; } } // rjf: single-step? freeze if not the single-step thread. else { is_frozen = !dmn_handle_match(lnx_dmn_handle_from_thread(thread), ctrls->single_step_thread); } // resume thread if(!is_frozen) { AssertAlways(thread->state == LNX_DMN_ThreadState_Stopped); // write registers if(thread->is_reg_block_dirty) { thread->is_reg_block_dirty = !lnx_dmn_thread_write_reg_block(thread); } // pass signal to the child process void *sig_code = 0; if(thread->pass_through_signal) { thread->pass_through_signal = 0; if(!ctrls->ignore_previous_exception) { sig_code = (void *)(uintptr_t)thread->pass_through_signo; } } // resume thread if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, thread->tid, 0, sig_code)) >= 0) { thread->state = LNX_DMN_ThreadState_Running; thread->is_reg_block_dirty = 1; 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 { if(errno == ESRCH) { // Race note: In multithreaded programs, ptrace(PTRACE_CONT) may fail if a running thread calls // exit_group while we are resuming threads. The kernel begins tearing down the thread group so // the target might not be in a ptrace-soppped state anymore. Handle this by ignoring, on waitpid // we will get chance to report exit event. // // TODO: unfortunatley, kernel also sends us ESRCH whenever ptrace(PTRACE_CONT) is sent to a tracee // that is not in a ptace-stop, so how can we tell these errors apart? } else { InvalidPath; } } } } } } //////////////////////////// //- 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(;;) { ////////////////////////// //- rjf: wait for a signal (unblock halts) // int status = 0; pid_t wait_id = 0; { mutex_drop(lnx_dmn_state->halter_mutex); wait_id = LNX_RETRY_ON_EINTR(waitpid(-1, &status, __WALL|__WNOTHREAD)); mutex_take(lnx_dmn_state->halter_mutex); } ////////////////////////// //- 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); if(process == 0 && thread != 0) { process = thread->process; } int wifexited = WIFEXITED(status); int wifsignaled = WIFSIGNALED(status); int wifstopped = WIFSTOPPED(status); int wstopsig = WSTOPSIG(status); int event_code = (status >> 16); ////////////////////////// //- 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; 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; 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 switch(process->state) { default:{}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; signal_handled = 1; } else { log_infof("ptrace error: Failed to resume tracee."); } } }break; case LNX_DMN_ProcessState_WaitForExec: if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) { 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) { process_done = 1; signal_handled = 1; }break; case LNX_DMN_ProcessState_Attach: if(wstopsig == SIGSTOP) { process_done = 1; process_new = 1; process_new_flags = process->debug_subprocesses ? LNX_DMN_CreateProcessFlag_DebugSubprocesses : 0; signal_handled = 1; }break; } //- 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; } else { log_infof("ptrace error: Couldn't successfully kill process; releasing debugger state."); process_done = 1; } } } ////////////////////////// //- rjf: any thread stop -> update thread state, halt other threads if needed // if(!signal_handled && (wifexited || wifsignaled || wifstopped)) { B32 thread_running = (thread && thread->state == LNX_DMN_ThreadState_Running); // rjf: update thread running state if(thread_running) { if(wifstopped && !wifsignaled && !wifexited) { thread->state = LNX_DMN_ThreadState_Stopped; } else if(!wifstopped && (wifsignaled || wifexited)) { thread->state = LNX_DMN_ThreadState_Exited; } } // 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) { 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); } } } } // rjf: decrement running thread count if(thread_running) { running_thread_count -= 1; } } ////////////////////////// //- rjf: wifexited / wifsignaled -> thread exits // // wifexited -> normal process exit via _exit or exit() // wifsignaled -> exit because child did not handle a signal // if(!signal_handled && (wifexited || wifsignaled)) { thread_done = 1; thread_done_exit_code = wifexited ? WEXITSTATUS(status) : WTERMSIG(status); thread_done_report_events = 1; signal_handled = 1; } ////////////////////////// //- rjf: wifstopped -> thread or group stop // else if(!signal_handled && wifstopped) { signal_handled = 1; //////////////////////// //- rjf: read stop thread's new registers // if(thread && thread->state != LNX_DMN_ThreadState_PendingCreation) { thread->is_reg_block_dirty = !lnx_dmn_thread_read_reg_block(thread); } //////////////////////// //- rjf: trap signal // if(wstopsig == SIGTRAP) { switch(event_code) { default:{}break; //////////////////// //- rjf: 0 -> general trap path // case 0: { siginfo_t siginfo = {0}; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0) { switch(siginfo.si_code) { //- rjf: regular traps or breakpoints case SI_KERNEL: case TRAP_BRKPT: { //- rjf: unpack thread IP / process U64 ip = lnx_dmn_ip_from_thread(thread); DMN_Handle process_handle = lnx_dmn_handle_from_process(process); //- rjf: IP -> find associated user trap DMN_Trap *hit_user_trap = 0; { 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_handle_match(trap->process, process_handle) && trap->vaddr == ip - bytes_per_trap) { hit_user_trap = trap; goto dbl_break_find_user_trap; } } } dbl_break_find_user_trap:; } //- rjf: no user trap -> correllate w/ probe 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; } } } //- rjf: probe trap: (module) initialization / relocation complete if(probe_kind == LNX_DMN_ProbeKind_InitComplete || probe_kind == LNX_DMN_ProbeKind_RelocComplete) { // 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); // 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; } // 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 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(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; 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) { 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 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) { 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(first_module_to_release != 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, ModuleNode, first_module_to_release) { lnx_dmn_push_event_unload_module(arena, &events, process, module_n->m); lnx_dmn_module_release(process->ctx, module_n->m); } scratch_end(scratch); } is_unmap_completed = 1; unmap_complete_exit:; AssertAlways(is_unmap_completed); } //- rjf: no associated probe -> either trap or breakpoint if(probe_kind == LNX_DMN_ProbeKind_Null) { // rjf: on user breakpoint trap -> roll back IP if(hit_user_trap) { U64 ip = lnx_dmn_ip_from_thread(thread); lnx_dmn_thread_write_ip(thread, ip - arch_info->trap_instruction.size); } // 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; //- 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_ip_from_thread(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 { log_infof("ptrace error: Failed to get signal info via PTRACE_GETSIGINFO."); } }break; //////////////////// //- rjf: fork / vforks // case PTRACE_EVENT_FORK: { NotImplemented; }break; case PTRACE_EVENT_VFORK: { NotImplemented; }break; case PTRACE_EVENT_VFORK_DONE: { NotImplemented; }break; //////////////////// //- 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: { 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 = already_stopped; thread_new_state = already_stopped ? LNX_DMN_ThreadState_Stopped : LNX_DMN_ThreadState_PendingCreation; thread_new_parent = thread->process; if(already_stopped) { lnx_dmn_state->threads_pending_creation -= 1; } else { 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."); } }break; //////////////////// //- rjf: exec // case PTRACE_EVENT_EXEC: { LNX_DMN_Process *process = thread->process; 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; } else { 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: thread stops // case PTRACE_EVENT_STOP: { // rjf: stop event for pending threads -> thread is created & stopped if(thread != 0 && thread->state == LNX_DMN_ThreadState_PendingCreation) { 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; } // 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 // 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; } } //////////////////////// //- rjf: sigstop - potentially halt // else if(wstopsig == SIGSTOP) { siginfo_t siginfo = {0}; if(LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0) { // finish halting if this is our SIGSTOP if(siginfo.si_pid == lnx_dmn_state->halter_tid) { is_halt_done = 1; } else { // TODO: handle SIGSTOP } } } //////////////////////// //- rjf: catchall exception case // else { 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); e->thread = lnx_dmn_handle_from_thread(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; if(wstopsig == SIGSEGV) { siginfo_t si = {0}; LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, thread->tid, 0, &si)); e->address = (U64)si.si_addr; } } } ////////////////////////// //- rjf: signal not handled -> log // if(!signal_handled) { // TODO(rjf) } ////////////////////////// //- rjf: thread done -> release // if(thread_done && thread) { LNX_DMN_Process *thread_process = thread->process; // 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 if(thread_done_report_events) { 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 { // 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) { // rjf: push module events if(thread_done_report_events) { 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); } } // rjf: push process exit event if(thread_done_report_events) { 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: turn on process-end path process = thread_process; process_done = 1; } } ////////////////////////// //- rjf: process done -> release // if(process_done && 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)); { 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; 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; 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 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) { 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(&arena, 1); // 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}; { 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_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; } // rjf: terminate on null entries if(auxv_entry.a_type == ELF_AuxType_Null) { break; } } LNX_RETRY_ON_EINTR(close(auxv_fd)); } scratch_end(scratch); } //- rjf: unpack context Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); 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); 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 dl_rebase = (dl_ehdr.e_type == ELF_Type_Dyn ? auxv.base : 0); // 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 + 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(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(dl_class, phdr_data); scratch_end(scratch); } if(phdr.p_type == ELF_PType_Dynamic) { dynamic_vaddr = dl_rebase + phdr.p_vaddr; break; } } } // rjf: parse dynamic info for this module LNX_DMN_DynamicInfo dynamic_info = {0}; { 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 ELF_Dyn64 dyn = {0}; { Temp scratch = scratch_begin(&arena, 1); 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(dl_class, dyn_data); scratch_end(scratch); } // 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_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_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_dl_rebase + dyn.val;}break; case ELF_DynTag_GNU_Hash: {dynamic_info.gnu_hash_vaddr = dynamic_info_dl_rebase + dyn.val;}break; } } } // rjf: parse symbol table count for this module U64 symbol_count = 0; { // rjf: base hash implementation if(dynamic_info.hash_vaddr) { 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; } } // rjf: GNU hash table implementation 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)) { // 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) { 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; } } // rjf: no symbols in hash table - total count is symoffset if(max_bucket < symoffset) { symbol_count = symoffset; } // rjf: else -> walk chain from max_bucket until terminator (entry w/ LSB set) else { 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)) { break; } } } } } } // 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(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(dl_class, sym_data); temp_end(temp); } // rjf: read symbol's name 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); } // 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 = dl_rebase + symbol.st_value; } } temp_end(temp); if(got_rdebug) { break; } } } scratch_end(scratch); } //- rjf: [x64] 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 { log_infof("ptrace error: PTRACE_GETREGSET failed, when trying to determine xsave layout."); } } //- 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(&arena, 1); // 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)); 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? B32 good_parse = 1; if(parts.node_count < 5) { good_parse = 0; // TODO(rjf): log } // parse map virtual range 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 = 0; if(vaddr_list.first != 0) { 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)); } } // 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 = 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}; { 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; 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}; { 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)); shdr = elf_shdr64_from_class_data(eclass, shdr_data); scratch_end(scratch); } 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(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; } 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(ctx_arena, arch, args); probe.pc = pc + probe_rebase; probe.semaphore = semaphore ? semaphore + probe_rebase : 0; } LNX_DMN_ProbeNode *n = push_array(ctx_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: allocate & fill 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->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)); ctx->xsave_layout = xsave_layout; //- rjf: create main module 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 { 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); } } //////////////////////// //- 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: report process creation // { 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; } //////////////////////// //- rjf: create main thread // { thread_new = 1; thread_new_state = LNX_DMN_ThreadState_Stopped; thread_new_parent = new_process; thread_new_report_events = 1; thread_new_pid = wait_id; } } ////////////////////////// //- 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 = thread_new_pid; 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 id -> thread { 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); } } // 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(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); } } ////////////////////////// //- 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_thread_count == 0 && lnx_dmn_state->process_pending_creation == 0 && lnx_dmn_state->threads_pending_creation == 0) { break; } } //////////////////////////// //- rjf: halted -> push halt event, reset halter state // if(is_halt_done) { DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_Halt; lnx_dmn_state->halter_tid = 0; lnx_dmn_state->halt_code = 0; lnx_dmn_state->halt_user_data = 0; lnx_dmn_state->is_halting = 0; } //////////////////////////// //- rjf: unset all traps - restore original bytes // { U64 trap_idx = 0; 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), trap_swap_bytes + trap_idx*bytes_per_trap); } trap_idx += 1; } } } } ////////////////////////////// //- rjf: no events, no threads -> not attached // if(events.count == 0 && lnx_dmn_state->process_count == 0) { DMN_Event *e = dmn_event_list_push(arena, &events); e->kind = DMN_EventKind_Error; e->error_kind = DMN_ErrorKind_NotAttached; } ////////////////////////////// //- rjf: re-allow halts // mutex_drop(lnx_dmn_state->halter_mutex); scratch_end(scratch); return events; } //////////////////////////////// //~ rjf: @dmn_os_hooks Halting (Implemented Per-OS) internal void dmn_halt(U64 code, U64 user_data) { MutexScope(lnx_dmn_state->halter_mutex) { if(lnx_dmn_state->process_count) { 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) { int kill_result = LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)); int x = 0; } } } } //////////////////////////////// //~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS) //- rjf: non-blocking-control-thread access barriers internal B32 dmn_access_open(void) { B32 result = 0; if(lnx_dmn_ctrl_thread) { result = 1; } else { mutex_take(lnx_dmn_state->access_mutex); result = !lnx_dmn_state->access_run_state; } return result; } internal void dmn_access_close(void) { if(!lnx_dmn_ctrl_thread) { mutex_drop(lnx_dmn_state->access_mutex); } } //- rjf: processes 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) { U64 result = 0; DMN_AccessScope { LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); if(process) { result = lnx_dmn_read(process->fd, range, dst); } } return result; } internal B32 dmn_process_write(DMN_Handle process_handle, Rng1U64 range, void *src) { B32 result = 0; DMN_AccessScope { LNX_DMN_Process *process = lnx_dmn_process_from_handle(process_handle); if(process) { result = lnx_dmn_write(process->fd, range, src); } } return result; } //- rjf: threads internal B32 dmn_thread_read_reg_block(DMN_Handle thread_handle, void *reg_block) { B32 result = 0; DMN_AccessScope { LNX_DMN_Thread *thread = lnx_dmn_thread_from_handle(thread_handle); if(thread) { ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); U64 reg_block_size = arch_info->reg_block_size; MemoryCopy(reg_block, thread->reg_block, reg_block_size); } result = 1; } return result; } internal B32 dmn_thread_write_reg_block(DMN_Handle thread_handle, void *reg_block) { B32 result = 0; DMN_AccessScope { LNX_DMN_Thread *thread = lnx_dmn_thread_from_handle(thread_handle); if(thread) { ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); U64 reg_block_size = arch_info->reg_block_size; MemoryCopy(thread->reg_block, reg_block, reg_block_size); thread->is_reg_block_dirty = 1; result = 1; } } return result; } internal B32 dmn_thread_get_module_tls_vaddr(DMN_Handle thread_handle, DMN_Handle module_handle, U64 *vaddr_out) { B32 result = 0; DMN_AccessScope { LNX_DMN_Thread *thread = lnx_dmn_thread_from_handle(thread_handle); if(thread != 0) { LNX_DMN_Process *process = thread->process; LNX_DMN_Module *module = lnx_dmn_module_from_handle(module_handle); if(process != 0 && module != 0) { Arch arch = thread->process->ctx->arch; U64 addr_size = byte_size_from_arch(arch); U64 dtv_base_vaddr = thread->dtv_base_vaddr; if(dtv_base_vaddr != 0) { 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) { 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; } } } } } } return result; } //- rjf: system process listing internal void dmn_process_iter_begin(DMN_ProcessIter *iter) { DIR *dir = opendir("/proc"); MemoryZeroStruct(iter); iter->v[0] = IntFromPtr(dir); } internal B32 dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out) { // rjf: scan for the next process ID in the directory B32 got_pid = 0; String8 pid_string = {0}; { DIR *dir = (DIR*)PtrFromInt(iter->v[0]); if(dir != 0 && iter->v[1] == 0) { for(;;) { // rjf: get next entry struct dirent *d = readdir(dir); if(d == 0) { break; } // rjf: check file name is integer String8 file_name = str8_cstring((char*)d->d_name); B32 is_integer = str8_is_integer(file_name, 10); // rjf: break on integers (which represent processes) if(is_integer) { got_pid = 1; pid_string = file_name; break; } } } } // rjf: if we found a process id, map id => info B32 result = 0; if(got_pid) { pid_t pid = u64_from_str8(pid_string, 10); 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)"); } info_out->name = name; info_out->pid = pid; result = 1; } return result; } internal void dmn_process_iter_end(DMN_ProcessIter *iter) { DIR *dir = (DIR*)PtrFromInt(iter->v[0]); if(dir != 0) { closedir(dir); } MemoryZeroStruct(iter); }