Files
raddebugger/src/demon/win32/demon_core_win32.c
T

2635 lines
84 KiB
C

// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ rjf: Basic Helpers
internal U64
dmn_w32_hash_from_string(String8 string)
{
U64 result = 5381;
for(U64 i = 0; i < string.size; i += 1)
{
result = ((result << 5) + result) + string.str[i];
}
return result;
}
internal U64
dmn_w32_hash_from_id(U64 id)
{
return dmn_w32_hash_from_string(str8_struct(&id));
}
////////////////////////////////
//~ rjf: Entity Helpers
//- rjf: entity <-> handle
internal DMN_Handle
dmn_w32_handle_from_entity(DMN_W32_Entity *entity)
{
U32 idx = (U32)(entity - dmn_w32_shared->entities_base);
U32 gen = entity->gen;
DMN_Handle handle = {idx, gen};
return handle;
}
internal DMN_W32_Entity *
dmn_w32_entity_from_handle(DMN_Handle handle)
{
U32 idx = handle.u32[0];
U32 gen = handle.u32[1];
DMN_W32_Entity *entity = dmn_w32_shared->entities_base + idx;
if(entity->gen != gen)
{
entity = &dmn_w32_entity_nil;
}
return entity;
}
//- rjf: entity allocation/deallocation
internal DMN_W32_Entity *
dmn_w32_entity_alloc(DMN_W32_Entity *parent, DMN_W32_EntityKind kind, U64 id)
{
// rjf: allocate
DMN_W32_Entity *e = dmn_w32_shared->entities_first_free;
{
if(e != 0)
{
SLLStackPop(dmn_w32_shared->entities_first_free);
}
else
{
e = push_array_no_zero(dmn_w32_shared->entities_arena, DMN_W32_Entity, 1);
dmn_w32_shared->entities_count += 1;
}
U32 gen = e->gen;
MemoryZeroStruct(e);
e->gen = gen+1;
}
// rjf: fill
{
e->kind = kind;
e->id = id;
e->parent = parent;
e->next = e->prev = e->first = e->last = &dmn_w32_entity_nil;
if(parent != &dmn_w32_entity_nil)
{
DLLPushBack_NPZ(&dmn_w32_entity_nil, parent->first, parent->last, e, next, prev);
}
}
// rjf: insert into id -> entity map
if(id != 0)
{
U64 hash = dmn_w32_hash_from_id(id);
U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count;
DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx];
DMN_W32_EntityIDHashNode *node = 0;
for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next)
{
if(n->id == id)
{
node = n;
break;
}
}
if(node == 0)
{
node = dmn_w32_shared->entities_id_hash_node_free;
if(node != 0)
{
SLLStackPop(dmn_w32_shared->entities_id_hash_node_free);
}
else
{
node = push_array(dmn_w32_shared->arena, DMN_W32_EntityIDHashNode, 1);
}
DLLPushBack(slot->first, slot->last, node);
}
node->id = id;
node->entity = e;
}
return e;
}
internal void
dmn_w32_entity_release(DMN_W32_Entity *entity)
{
// rjf: unhook root
if(entity->parent != &dmn_w32_entity_nil)
{
DLLRemove_NPZ(&dmn_w32_entity_nil, entity->parent->first, entity->parent->last, entity, next, prev);
}
// rjf: walk every entity in this tree, free each
if(entity != &dmn_w32_entity_nil)
{
Temp scratch = scratch_begin(0, 0);
typedef struct Task Task;
struct Task
{
Task *next;
DMN_W32_Entity *e;
};
Task start_task = {0, entity};
Task *first_task = &start_task;
Task *last_task = &start_task;
for(Task *t = first_task; t != 0; t = t->next)
{
for(DMN_W32_Entity *child = t->e->first; child != &dmn_w32_entity_nil; child = child->next)
{
Task *t = push_array(scratch.arena, Task, 1);
t->e = child;
SLLQueuePush(first_task, last_task, t);
}
// rjf: free entity
SLLStackPush(dmn_w32_shared->entities_first_free, t->e);
t->e->gen += 1;
if(t->e->kind == DMN_W32_EntityKind_Module)
{
CloseHandle(t->e->handle);
}
// rjf: remove from id -> entity map
if(t->e->id != 0)
{
U64 hash = dmn_w32_hash_from_id(t->e->id);
U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count;
DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx];
DMN_W32_EntityIDHashNode *node = 0;
for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next)
{
if(n->id == t->e->id && n->entity == t->e)
{
DLLRemove(slot->first, slot->last, n);
SLLStackPush(dmn_w32_shared->entities_id_hash_node_free, n);
break;
}
}
}
}
scratch_end(scratch);
}
}
//- rjf: kind*id -> entity
internal DMN_W32_Entity *
dmn_w32_entity_from_kind_id(DMN_W32_EntityKind kind, U64 id)
{
DMN_W32_Entity *result = &dmn_w32_entity_nil;
U64 hash = dmn_w32_hash_from_id(id);
U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count;
DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx];
DMN_W32_EntityIDHashNode *node = 0;
for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next)
{
if(n->entity->kind == kind && n->id == id)
{
node = n;
break;
}
}
if(node != 0)
{
result = node->entity;
}
return result;
}
////////////////////////////////
//~ rjf: Module Info Extraction
internal String8
dmn_w32_full_path_from_module(Arena *arena, DMN_W32_Entity *module)
{
Temp scratch = scratch_begin(&arena, 1);
//- rjf: extract path from module
String16 path16 = {0};
String8 path8 = {0};
{
// rjf: handle -> full path
if(module->handle != 0)
{
DWORD cap16 = GetFinalPathNameByHandleW(module->handle, 0, 0, VOLUME_NAME_DOS);
U16 *buffer16 = push_array_no_zero(scratch.arena, U16, cap16);
DWORD size16 = GetFinalPathNameByHandleW(module->handle, (WCHAR*)buffer16, cap16, VOLUME_NAME_DOS);
path16 = str16(buffer16, size16);
}
// rjf: fallback (main module only): process -> full path
if(path16.size == 0 && module->module.is_main)
{
DMN_W32_Entity *process = module->parent;
DWORD size = KB(4);
U16 *buf = push_array_no_zero(scratch.arena, U16, size);
if(QueryFullProcessImageNameW(process->handle, 0, (WCHAR*)buf, &size))
{
path16 = str16(buf, size);
}
}
// rjf: fallback (any module - no guarantee): address_of_name -> full path
if(path16.size == 0 && module->module.address_of_name_pointer != 0)
{
DMN_W32_Entity *process = module->parent;
U64 ptr_size = bit_size_from_arch(process->arch)/8;
U64 name_pointer = 0;
if(dmn_w32_process_read(process->handle, r1u64(module->module.address_of_name_pointer, module->module.address_of_name_pointer+ptr_size), &name_pointer))
{
if(name_pointer != 0)
{
if(module->module.name_is_unicode)
{
path16 = dmn_w32_read_memory_str16(scratch.arena, process->handle, name_pointer);
}
else
{
path8 = dmn_w32_read_memory_str(scratch.arena, process->handle, name_pointer);
}
}
}
}
}
// rjf: produce finalized result
String8 result = {0};
{
if(path16.size > 0)
{
// rjf: skip the extended path thing if necessary
if(path16.size >= 4 &&
path16.str[0] == L'\\' &&
path16.str[1] == L'\\' &&
path16.str[2] == L'?' &&
path16.str[3] == L'\\')
{
path16.size -= 4;
path16.str += 4;
}
// rjf: convert to UTF-8
result = str8_from_16(arena, path16);
}
else
{
// rjf: skip the extended path thing if necessary
if (path8.size >= 4 &&
path8.str[0] == L'\\' &&
path8.str[1] == L'\\' &&
path8.str[2] == L'?' &&
path8.str[3] == L'\\')
{
path8.size -= 4;
path8.str += 4;
}
// rjf: copy to output arena
result = push_str8_copy(arena, path8);
}
}
scratch_end(scratch);
return result;
}
////////////////////////////////
//~ rjf: Win32-Level Process/Thread Reads/Writes
//- rjf: processes
internal U64
dmn_w32_process_read(HANDLE process, Rng1U64 range, void *dst)
{
U64 bytes_read = 0;
U8 *ptr = (U8*)dst;
U8 *opl = ptr + dim_1u64(range);
U64 cursor = range.min;
for(;ptr < opl;)
{
SIZE_T to_read = (SIZE_T)(opl - ptr);
SIZE_T actual_read = 0;
if(!ReadProcessMemory(process, (LPCVOID)cursor, ptr, to_read, &actual_read))
{
bytes_read += actual_read;
break;
}
ptr += actual_read;
cursor += actual_read;
bytes_read += actual_read;
}
return bytes_read;
}
internal B32
dmn_w32_process_write(HANDLE process, Rng1U64 range, void *src)
{
B32 result = 1;
U8 *ptr = (U8*)src;
U8 *opl = ptr + dim_1u64(range);
U64 cursor = range.min;
for(;ptr < opl;)
{
SIZE_T to_write = (SIZE_T)(opl - ptr);
SIZE_T actual_write = 0;
if(!WriteProcessMemory(process, (LPVOID)cursor, ptr, to_write, &actual_write))
{
result = 0;
break;
}
ptr += actual_write;
cursor += actual_write;
}
ins_atomic_u64_inc_eval(&dmn_w32_shared->mem_gen);
return result;
}
internal String8
dmn_w32_read_memory_str(Arena *arena, HANDLE process_handle, U64 address)
{
// TODO(rjf): @rewrite
//
// OLD: this could be done better with a demon_w32_read_memory
// that returns a read amount instead of a success/fail.
//
// (dmn_w32_process_read now does this, so we can switch to it)
// scan piece by piece
Temp scratch = scratch_begin(&arena, 1);
String8List list = {0};
U64 max_cap = 256;
U64 cap = max_cap;
U64 read_p = address;
for (;;){
U8 *block = push_array(scratch.arena, U8, cap);
for (;cap > 0;){
if (dmn_w32_process_read(process_handle, r1u64(read_p, read_p+cap), block)){
break;
}
cap /= 2;
}
read_p += cap;
U64 block_opl = 0;
for (;block_opl < cap; block_opl += 1){
if (block[block_opl] == 0){
break;
}
}
if (block_opl > 0){
str8_list_push(scratch.arena, &list, str8(block, block_opl));
}
if (block_opl < cap || cap == 0){
break;
}
}
// assemble results
String8 result = str8_list_join(arena, &list, 0);
scratch_end(scratch);
return(result);
}
internal String16
dmn_w32_read_memory_str16(Arena *arena, HANDLE process_handle, U64 address)
{
// TODO(rjf): @rewrite
//
// OLD: this could be done better with a demon_w32_read_memory
// that returns a read amount instead of a success/fail.
//
// (dmn_w32_process_read now does this, so we can switch to it)
// scan piece by piece
Temp scratch = scratch_begin(&arena, 1);
String8List list = {0};
U64 max_cap = 256;
U64 cap = max_cap;
U64 read_p = address;
for (;;){
U8 *block = push_array(scratch.arena, U8, cap);
for (;cap > 1;){
if (dmn_w32_process_read(process_handle, r1u64(read_p, read_p+cap), block)){
break;
}
cap /= 2;
}
read_p += cap;
U16 *block16 = (U16*)block;
(void)block16;
U64 block_opl = 0;
for (;block_opl < cap; block_opl += 2){
if (*(U16*)(block + block_opl) == 0){
break;
}
}
if (block_opl > 0){
str8_list_push(scratch.arena, &list, str8(block, block_opl));
}
if (block_opl < cap || cap == 0){
break;
}
}
// assemble results
String8 joined = str8_list_join(arena, &list, 0);
String16 result = {(U16*)joined.str, joined.size/2};
scratch_end(scratch);
return(result);
}
internal DMN_W32_ImageInfo
dmn_w32_image_info_from_process_base_vaddr(HANDLE process, U64 base_vaddr)
{
// rjf: find PE offset
U32 pe_offset = 0;
{
U64 dos_magic_off = base_vaddr;
U16 dos_magic = 0;
dmn_w32_process_read_struct(process, dos_magic_off, &dos_magic);
if(dos_magic == PE_DOS_MAGIC)
{
U64 pe_offset_off = base_vaddr + OffsetOf(PE_DosHeader, coff_file_offset);
dmn_w32_process_read_struct(process, pe_offset_off, &pe_offset);
}
}
// rjf: get COFF header
B32 got_coff_header = 0;
U64 coff_header_off = 0;
COFF_Header coff_header = {0};
if(pe_offset > 0)
{
U64 pe_magic_off = base_vaddr + pe_offset;
U32 pe_magic = 0;
dmn_w32_process_read_struct(process, pe_magic_off, &pe_magic);
if(pe_magic == PE_MAGIC)
{
coff_header_off = pe_magic_off + sizeof(pe_magic);
if(dmn_w32_process_read_struct(process, coff_header_off, &coff_header))
{
got_coff_header = 1;
}
}
}
// rjf: get arch and size
DMN_W32_ImageInfo result = zero_struct;
if(got_coff_header)
{
U64 optional_size_off = 0;
Architecture arch = Architecture_Null;
switch(coff_header.machine)
{
case COFF_MachineType_X86:
{
arch = Architecture_x86;
optional_size_off = OffsetOf(PE_OptionalHeader32, sizeof_image);
}break;
case COFF_MachineType_X64:
{
arch = Architecture_x64;
optional_size_off = OffsetOf(PE_OptionalHeader32Plus, sizeof_image);
}break;
default:
{}break;
}
if(arch != Architecture_Null)
{
U64 optional_off = coff_header_off + sizeof(coff_header);
U32 size = 0;
if(dmn_w32_process_read_struct(process, optional_off+optional_size_off, &size) >= sizeof(size))
{
result.arch = arch;
result.size = size;
}
}
}
return result;
}
//- rjf: threads
internal U16
dmn_w32_real_tag_word_from_xsave(XSAVE_FORMAT *fxsave)
{
U16 result = 0;
U32 top = (fxsave->StatusWord >> 11) & 7;
for(U32 fpr = 0; fpr < 8; fpr += 1)
{
U32 tag = 3;
if(fxsave->TagWord & (1 << fpr))
{
U32 st = (fpr - top)&7;
REGS_Reg80 *fp = (REGS_Reg80*)&fxsave->FloatRegisters[st*16];
U16 exponent = fp->sign1_exp15 & bitmask15;
U64 integer_part = fp->int1_frac63 >> 63;
U64 fraction_part = fp->int1_frac63 & bitmask63;
// tag: 0 - normal; 1 - zero; 2 - special
tag = 2;
if(exponent == 0)
{
if(integer_part == 0 && fraction_part == 0)
{
tag = 1;
}
}
else if(exponent != bitmask15 && integer_part != 0)
{
tag = 0;
}
}
result |= tag << (2 * fpr);
}
return result;
}
internal U16
dmn_w32_xsave_tag_word_from_real_tag_word(U16 ftw)
{
U16 compact = 0;
for(U32 fpr = 0; fpr < 8; fpr++)
{
U32 tag = (ftw >> (fpr * 2)) & 3;
if(tag != 3)
{
compact |= (1 << fpr);
}
}
return compact;
}
internal B32
dmn_w32_thread_read_reg_block(Architecture arch, HANDLE thread, void *reg_block)
{
B32 result = 0;
switch(arch)
{
////////////////////////////
//- rjf: unimplemented win32/arch combos
//
case Architecture_Null:
case Architecture_COUNT:
{}break;
case Architecture_arm64:
case Architecture_arm32:
{NotImplemented;}break;
////////////////////////////
//- rjf: x86
//
case Architecture_x86:
{
REGS_RegBlockX86 *dst = (REGS_RegBlockX86 *)reg_block;
//- rjf: get thread context
WOW64_CONTEXT ctx = {0};
ctx.ContextFlags = DMN_W32_CTX_X86_ALL;
if(!Wow64GetThreadContext(thread, (WOW64_CONTEXT *)&ctx))
{
break;
}
result = 1;
//- rjf: convert WOW64_CONTEXT -> REGS_RegBlockX86
XSAVE_FORMAT *fxsave = (XSAVE_FORMAT *)ctx.ExtendedRegisters;
dst->eax.u32 = ctx.Eax;
dst->ebx.u32 = ctx.Ebx;
dst->ecx.u32 = ctx.Ecx;
dst->edx.u32 = ctx.Edx;
dst->esi.u32 = ctx.Esi;
dst->edi.u32 = ctx.Edi;
dst->esp.u32 = ctx.Esp;
dst->ebp.u32 = ctx.Ebp;
dst->eip.u32 = ctx.Eip;
dst->cs.u16 = ctx.SegCs;
dst->ds.u16 = ctx.SegDs;
dst->es.u16 = ctx.SegEs;
dst->fs.u16 = ctx.SegFs;
dst->gs.u16 = ctx.SegGs;
dst->ss.u16 = ctx.SegSs;
dst->dr0.u32 = ctx.Dr0;
dst->dr1.u32 = ctx.Dr1;
dst->dr2.u32 = ctx.Dr2;
dst->dr3.u32 = ctx.Dr3;
dst->dr6.u32 = ctx.Dr6;
dst->dr7.u32 = ctx.Dr7;
// NOTE(rjf): this bit is "supposed to always be 1", according to old info.
// may need to be investigated.
dst->eflags.u32 = ctx.EFlags | 0x2;
dst->fcw.u16 = fxsave->ControlWord;
dst->fsw.u16 = fxsave->StatusWord;
dst->ftw.u16 = dmn_w32_real_tag_word_from_xsave(fxsave);
dst->fop.u16 = fxsave->ErrorOpcode;
dst->fip.u32 = fxsave->ErrorOffset;
dst->fcs.u16 = fxsave->ErrorSelector;
dst->fdp.u32 = fxsave->DataOffset;
dst->fds.u16 = fxsave->DataSelector;
dst->mxcsr.u32 = fxsave->MxCsr;
dst->mxcsr_mask.u32 = fxsave->MxCsr_Mask;
{
M128A *float_s = fxsave->FloatRegisters;
REGS_Reg80 *float_d = &dst->fpr0;
for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1)
{
MemoryCopy(float_d, float_s, sizeof(*float_d));
}
}
{
M128A *xmm_s = fxsave->XmmRegisters;
REGS_Reg256 *xmm_d = &dst->ymm0;
for(U32 n = 0; n < 8; n += 1, xmm_s += 1, xmm_d += 1)
{
MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_s));
}
}
//- rjf: read FS/GS base
WOW64_LDT_ENTRY ldt = {0};
if(Wow64GetThreadSelectorEntry(thread, ctx.SegFs, &ldt))
{
U32 base = (ldt.BaseLow) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.HighWord.Bytes.BaseHi << 24);
dst->fsbase.u32 = base;
}
if(Wow64GetThreadSelectorEntry(thread, ctx.SegGs, &ldt))
{
U32 base = (ldt.BaseLow) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.HighWord.Bytes.BaseHi << 24);
dst->gsbase.u32 = base;
}
}break;
////////////////////////////
//- rjf: x64
//
case Architecture_x64:
{
Temp scratch = scratch_begin(0, 0);
REGS_RegBlockX64 *dst = (REGS_RegBlockX64 *)reg_block;
//- rjf: unpack info about available features
U32 feature_mask = GetEnabledXStateFeatures();
B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX);
//- rjf: set up context
CONTEXT *ctx = 0;
U32 ctx_flags = DMN_W32_CTX_X64_ALL;
if(avx_enabled)
{
ctx_flags |= DMN_W32_CTX_INTEL_XSTATE;
}
DWORD size = 0;
InitializeContext(0, ctx_flags, 0, &size);
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
void *ctx_memory = push_array(scratch.arena, U8, size);
if(!InitializeContext(ctx_memory, ctx_flags, &ctx, &size))
{
ctx = 0;
}
}
//- rjf: unpack features available on this context
B32 avx_available = 0;
if(ctx != 0)
{
if(avx_enabled)
{
SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX);
}
DWORD64 xstate_flags = 0;
if(GetXStateFeaturesMask(ctx, &xstate_flags))
{
avx_available = !!(xstate_flags & XSTATE_MASK_AVX);
}
}
//- rjf: get thread context
if(!GetThreadContext(thread, ctx))
{
ctx = 0;
}
//- rjf: bad context -> abort
if(ctx == 0)
{
break;
}
result = 1;
//- rjf: convert context -> REGS_RegBlockX64
XSAVE_FORMAT *xsave = &ctx->FltSave;
dst->rax.u64 = ctx->Rax;
dst->rcx.u64 = ctx->Rcx;
dst->rdx.u64 = ctx->Rdx;
dst->rbx.u64 = ctx->Rbx;
dst->rsp.u64 = ctx->Rsp;
dst->rbp.u64 = ctx->Rbp;
dst->rsi.u64 = ctx->Rsi;
dst->rdi.u64 = ctx->Rdi;
dst->r8.u64 = ctx->R8;
dst->r9.u64 = ctx->R9;
dst->r10.u64 = ctx->R10;
dst->r11.u64 = ctx->R11;
dst->r12.u64 = ctx->R12;
dst->r13.u64 = ctx->R13;
dst->r14.u64 = ctx->R14;
dst->r15.u64 = ctx->R15;
dst->rip.u64 = ctx->Rip;
dst->cs.u16 = ctx->SegCs;
dst->ds.u16 = ctx->SegDs;
dst->es.u16 = ctx->SegEs;
dst->fs.u16 = ctx->SegFs;
dst->gs.u16 = ctx->SegGs;
dst->ss.u16 = ctx->SegSs;
dst->dr0.u32 = ctx->Dr0;
dst->dr1.u32 = ctx->Dr1;
dst->dr2.u32 = ctx->Dr2;
dst->dr3.u32 = ctx->Dr3;
dst->dr6.u32 = ctx->Dr6;
dst->dr7.u32 = ctx->Dr7;
// NOTE(rjf): this bit is "supposed to always be 1", according to old info.
// may need to be investigated.
dst->rflags.u64 = ctx->EFlags | 0x2;
dst->fcw.u16 = xsave->ControlWord;
dst->fsw.u16 = xsave->StatusWord;
dst->ftw.u16 = dmn_w32_real_tag_word_from_xsave(xsave);
dst->fop.u16 = xsave->ErrorOpcode;
dst->fcs.u16 = xsave->ErrorSelector;
dst->fds.u16 = xsave->DataSelector;
dst->fip.u32 = xsave->ErrorOffset;
dst->fdp.u32 = xsave->DataOffset;
dst->mxcsr.u32 = xsave->MxCsr;
dst->mxcsr_mask.u32 = xsave->MxCsr_Mask;
{
M128A *float_s = xsave->FloatRegisters;
REGS_Reg80 *float_d = &dst->fpr0;
for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1)
{
MemoryCopy(float_d, float_s, sizeof(*float_d));
}
}
if(!avx_available)
{
M128A *xmm_s = xsave->XmmRegisters;
REGS_Reg256 *xmm_d = &dst->ymm0;
for(U32 n = 0; n < 16; n += 1, xmm_s += 1, xmm_d += 1)
{
MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_s));
}
}
if(avx_available)
{
DWORD part0_length = 0;
M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length);
DWORD part1_length = 0;
M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length);
Assert(part0_length == part1_length);
DWORD count = part0_length/sizeof(part0[0]);
count = ClampTop(count, 16);
REGS_Reg256 *ymm_d = &dst->ymm0;
for (DWORD i = 0; i < count; i += 1, part0 += 1, part1 += 1, ymm_d += 1)
{
// TODO(rjf): confirm ordering of writes
ymm_d->u64[3] = part0->Low;
ymm_d->u64[2] = part0->High;
ymm_d->u64[1] = part1->Low;
ymm_d->u64[0] = part1->High;
}
}
scratch_end(scratch);
}break;
}
return result;
}
internal B32
dmn_w32_thread_write_reg_block(Architecture arch, HANDLE thread, void *reg_block)
{
B32 result = 0;
switch(arch)
{
////////////////////////////
//- rjf: unimplemented win32/arch combos
//
case Architecture_Null:
case Architecture_COUNT:
{}break;
case Architecture_arm64:
case Architecture_arm32:
{NotImplemented;}break;
////////////////////////////
//- rjf: x86
//
case Architecture_x86:
{
REGS_RegBlockX86 *src = (REGS_RegBlockX86 *)reg_block;
//- rjf: convert REGS_RegBlockX86 -> WOW64_CONTEXT
WOW64_CONTEXT ctx = {0};
XSAVE_FORMAT *fxsave = (XSAVE_FORMAT*)ctx.ExtendedRegisters;
ctx.ContextFlags = DMN_W32_CTX_X86_ALL;
ctx.Eax = src->eax.u32;
ctx.Ebx = src->ebx.u32;
ctx.Ecx = src->ecx.u32;
ctx.Edx = src->edx.u32;
ctx.Esi = src->esi.u32;
ctx.Edi = src->edi.u32;
ctx.Esp = src->esp.u32;
ctx.Ebp = src->ebp.u32;
ctx.Eip = src->eip.u32;
ctx.SegCs = src->cs.u16;
ctx.SegDs = src->ds.u16;
ctx.SegEs = src->es.u16;
ctx.SegFs = src->fs.u16;
ctx.SegGs = src->gs.u16;
ctx.SegSs = src->ss.u16;
ctx.Dr0 = src->dr0.u32;
ctx.Dr1 = src->dr1.u32;
ctx.Dr2 = src->dr2.u32;
ctx.Dr3 = src->dr3.u32;
ctx.Dr6 = src->dr6.u32;
ctx.Dr7 = src->dr7.u32;
ctx.EFlags = src->eflags.u32;
fxsave->ControlWord = src->fcw.u16;
fxsave->StatusWord = src->fsw.u16;
fxsave->TagWord = dmn_w32_xsave_tag_word_from_real_tag_word(src->ftw.u16);
fxsave->ErrorOpcode = src->fop.u16;
fxsave->ErrorSelector = src->fcs.u16;
fxsave->DataSelector = src->fds.u16;
fxsave->ErrorOffset = src->fip.u32;
fxsave->DataOffset = src->fdp.u32;
fxsave->MxCsr = src->mxcsr.u32 & src->mxcsr_mask.u32;
fxsave->MxCsr_Mask = src->mxcsr_mask.u32;
{
M128A *float_d = fxsave->FloatRegisters;
REGS_Reg80 *float_s = &src->fpr0;
for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1)
{
MemoryCopy(float_d, float_s, 10);
}
}
{
M128A *xmm_d = fxsave->XmmRegisters;
REGS_Reg256 *xmm_s = &src->ymm0;
for(U32 n = 0; n < 8; n += 1, xmm_d += 1, xmm_s += 1)
{
MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_d));
}
}
//- rjf: set thread context
B32 result = 0;
if(Wow64SetThreadContext(thread, &ctx))
{
result = 1;
}
}break;
////////////////////////////
//- rjf: x64
//
case Architecture_x64:
{
Temp scratch = scratch_begin(0, 0);
REGS_RegBlockX64 *src = (REGS_RegBlockX64 *)reg_block;
//- rjf: unpack info about available features
U32 feature_mask = GetEnabledXStateFeatures();
B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX);
//- rjf: set up context
CONTEXT *ctx = 0;
U32 ctx_flags = DMN_W32_CTX_X64_ALL;
if(avx_enabled)
{
ctx_flags |= DMN_W32_CTX_INTEL_XSTATE;
}
DWORD size = 0;
InitializeContext(0, ctx_flags, 0, &size);
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
void *ctx_memory = push_array(scratch.arena, U8, size);
if(!InitializeContext(ctx_memory, ctx_flags, &ctx, &size))
{
ctx = 0;
}
}
//- rjf: unpack features available on this context
B32 avx_available = 0;
if(ctx != 0)
{
if(avx_enabled)
{
SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX);
}
DWORD64 xstate_flags = 0;
if(GetXStateFeaturesMask(ctx, &xstate_flags))
{
avx_available = !!(xstate_flags & XSTATE_MASK_AVX);
}
}
//- rjf: get thread context
if(!GetThreadContext(thread, ctx))
{
ctx = 0;
}
//- rjf: bad context -> abort
if(ctx == 0)
{
DWORD error = GetLastError();
break;
}
//- rjf: convert REGS_RegBlockX64 -> CONTEXT
XSAVE_FORMAT *fxsave = &ctx->FltSave;
ctx->ContextFlags = ctx_flags;
ctx->MxCsr = src->mxcsr.u32 & src->mxcsr_mask.u32;
ctx->Rax = src->rax.u64;
ctx->Rcx = src->rcx.u64;
ctx->Rdx = src->rdx.u64;
ctx->Rbx = src->rbx.u64;
ctx->Rsp = src->rsp.u64;
ctx->Rbp = src->rbp.u64;
ctx->Rsi = src->rsi.u64;
ctx->Rdi = src->rdi.u64;
ctx->R8 = src->r8.u64;
ctx->R9 = src->r9.u64;
ctx->R10 = src->r10.u64;
ctx->R11 = src->r11.u64;
ctx->R12 = src->r12.u64;
ctx->R13 = src->r13.u64;
ctx->R14 = src->r14.u64;
ctx->R15 = src->r15.u64;
ctx->Rip = src->rip.u64;
ctx->SegCs = src->cs.u16;
ctx->SegDs = src->ds.u16;
ctx->SegEs = src->es.u16;
ctx->SegFs = src->fs.u16;
ctx->SegGs = src->gs.u16;
ctx->SegSs = src->ss.u16;
ctx->Dr0 = src->dr0.u32;
ctx->Dr1 = src->dr1.u32;
ctx->Dr2 = src->dr2.u32;
ctx->Dr3 = src->dr3.u32;
ctx->Dr6 = src->dr6.u32;
ctx->Dr7 = src->dr7.u32;
ctx->EFlags = src->rflags.u64;
fxsave->ControlWord = src->fcw.u16;
fxsave->StatusWord = src->fsw.u16;
fxsave->TagWord = dmn_w32_xsave_tag_word_from_real_tag_word(src->ftw.u16);
fxsave->ErrorOpcode = src->fop.u16;
fxsave->ErrorSelector = src->fcs.u16;
fxsave->DataSelector = src->fds.u16;
fxsave->ErrorOffset = src->fip.u32;
fxsave->DataOffset = src->fdp.u32;
{
M128A *float_d = fxsave->FloatRegisters;
REGS_Reg80 *float_s = &src->fpr0;
for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1)
{
MemoryCopy(float_d, float_s, 10);
}
}
if(!avx_available)
{
M128A *xmm_d = fxsave->XmmRegisters;
REGS_Reg256 *xmm_s = &src->ymm0;
for(U32 n = 0; n < 8; n += 1, xmm_d += 1, xmm_s += 1)
{
MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_d));
}
}
if(avx_available)
{
DWORD part0_length = 0;
M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length);
DWORD part1_length = 0;
M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length);
Assert(part0_length == part1_length);
DWORD count = part0_length/sizeof(part0[0]);
count = ClampTop(count, 16);
REGS_Reg256 *ymm_d = &src->ymm0;
for(DWORD i = 0; i < count; i += 1, part0 += 1, part1 += 1, ymm_d += 1)
{
// TODO(allen): Are we writing these out in the right order? Seems weird right?
part0->Low = ymm_d->u64[3];
part0->High = ymm_d->u64[2];
part1->Low = ymm_d->u64[1];
part1->High = ymm_d->u64[0];
}
}
//- rjf: set thread context
if(SetThreadContext(thread, ctx))
{
result = 1;
}
scratch_end(scratch);
}break;
}
ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen);
return result;
}
//- rjf: remote thread injection
internal DWORD
dmn_w32_inject_thread(HANDLE process, U64 start_address)
{
LPTHREAD_START_ROUTINE start = (LPTHREAD_START_ROUTINE)start_address;
DWORD thread_id = 0;
HANDLE thread = CreateRemoteThread(process, 0, 0, start, 0, 0, &thread_id);
if(thread != 0)
{
CloseHandle(thread);
}
return thread_id;
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS)
internal void
dmn_init(void)
{
Arena *arena = arena_alloc();
dmn_w32_shared = push_array(arena, DMN_W32_Shared, 1);
dmn_w32_shared->arena = arena;
dmn_w32_shared->access_mutex = os_mutex_alloc();
dmn_w32_shared->detach_arena = arena_alloc();
dmn_w32_shared->entities_arena = arena_alloc__sized(GB(8), KB(64));
dmn_w32_shared->entities_base = dmn_w32_entity_alloc(&dmn_w32_entity_nil, DMN_W32_EntityKind_Root, 0);
dmn_w32_shared->entities_id_hash_slots_count = 4096;
dmn_w32_shared->entities_id_hash_slots = push_array(arena, DMN_W32_EntityIDHashSlot, dmn_w32_shared->entities_id_hash_slots_count);
// rjf: load Windows 10+ GetThreadDescription API
{
dmn_w32_GetThreadDescription = (DMN_W32_GetThreadDescriptionFunctionType *)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "GetThreadDescription");
}
// rjf: setup environment variables
{
WCHAR *this_proc_env = GetEnvironmentStringsW();
U64 start_idx = 0;
for(U64 idx = 0;; idx += 1)
{
if(this_proc_env[idx] == 0)
{
if(start_idx == idx)
{
break;
}
else
{
String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx);
String8 string = str8_from_16(dmn_w32_shared->arena, string16);
str8_list_push(dmn_w32_shared->arena, &dmn_w32_shared->env_strings, string);
start_idx = idx+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;
dmn_w32_ctrl_thread = 1;
return ctx;
}
internal void
dmn_ctrl_exclusive_access_begin(void)
{
OS_MutexScope(dmn_w32_shared->access_mutex)
{
dmn_w32_shared->access_run_state = 1;
}
}
internal void
dmn_ctrl_exclusive_access_end(void)
{
OS_MutexScope(dmn_w32_shared->access_mutex)
{
dmn_w32_shared->access_run_state = 0;
}
}
internal U32
dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_LaunchOptions *options)
{
Temp scratch = scratch_begin(0, 0);
U32 result = 0;
DMN_AccessScope
{
//- rjf: produce exe / arguments string
String8 cmd = {0};
if(options->cmd_line.first != 0)
{
String8List args = {0};
String8 exe_path = options->cmd_line.first->string;
str8_list_pushf(scratch.arena, &args, "\"%S\"", exe_path);
for(String8Node *n = options->cmd_line.first->next; n != 0; n = n->next)
{
str8_list_push(scratch.arena, &args, n->string);
}
StringJoin join_params = {0};
join_params.sep = str8_lit(" ");
cmd = str8_list_join(scratch.arena, &args, &join_params);
}
//- rjf: produce environment strings
String8 env = {0};
{
String8List all_opts = options->env;
if(options->inherit_env != 0)
{
MemoryZeroStruct(&all_opts);
str8_list_push(scratch.arena, &all_opts, str8_lit("_NO_DEBUG_HEAP=1"));
for(String8Node *n = options->env.first; n != 0; n = n->next)
{
str8_list_push(scratch.arena, &all_opts, n->string);
}
for(String8Node *n = dmn_w32_shared->env_strings.first; n != 0; n = n->next)
{
str8_list_push(scratch.arena, &all_opts, n->string);
}
}
StringJoin join_params2 = {0};
join_params2.sep = str8_lit("\0");
join_params2.post = str8_lit("\0");
env = str8_list_join(scratch.arena, &all_opts, &join_params2);
}
//- rjf: produce utf-16 strings
String16 cmd16 = str16_from_8(scratch.arena, cmd);
String16 dir16 = str16_from_8(scratch.arena, options->path);
String16 env16 = str16_from_8(scratch.arena, env);
//- rjf: launch
DWORD access_flags = CREATE_UNICODE_ENVIRONMENT|DEBUG_PROCESS;
STARTUPINFOW startup_info = {sizeof(startup_info)};
PROCESS_INFORMATION process_info = {0};
AllocConsole();
if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 1, access_flags, (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info))
{
// check if we are 32-bit app, and just close it immediately
BOOL is_wow = 0;
IsWow64Process(process_info.hProcess, &is_wow);
if(is_wow)
{
MessageBox(0, "Sorry, The RAD Debugger only debugs 64-bit applications currently.", "Process error", MB_OK|MB_ICONSTOP);
DebugActiveProcessStop(process_info.dwProcessId);
TerminateProcess(process_info.hProcess,0xffffffff);
}
else
{
result = process_info.dwProcessId;
dmn_w32_shared->new_process_pending = 1;
}
CloseHandle(process_info.hProcess);
CloseHandle(process_info.hThread);
}
else
{
MessageBox(0, "Error starting process.", "Process error", MB_OK|MB_ICONSTOP);
}
FreeConsole();
//- rjf: eliminate all handles which have stuck around from the AllocConsole
{
SetStdHandle(STD_INPUT_HANDLE, 0);
SetStdHandle(STD_OUTPUT_HANDLE, 0);
SetStdHandle(STD_ERROR_HANDLE, 0);
}
}
scratch_end(scratch);
return result;
}
internal B32
dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid)
{
B32 result = 0;
DMN_AccessScope if(DebugActiveProcess((DWORD)pid))
{
result = 1;
dmn_w32_shared->new_process_pending = 1;
}
return result;
}
internal B32
dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code)
{
B32 result = 0;
DMN_AccessScope
{
DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process);
if(TerminateProcess(process_entity->handle, exit_code))
{
result = 1;
}
}
return result;
}
internal B32
dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process)
{
B32 result = 0;
DMN_AccessScope
{
DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process);
// rjf: resume threads
for(DMN_W32_Entity *child = process_entity->first;
child != &dmn_w32_entity_nil;
child = child->next)
{
if(child->kind == DMN_W32_EntityKind_Thread)
{
DWORD resume_result = ResumeThread(child->handle);
(void)resume_result;
}
}
// rjf: detach
{
DWORD pid = (DWORD)process_entity->id;
if(DebugActiveProcessStop(pid))
{
result = 1;
}
}
// rjf: push into list of processes to generate events for later
if(result != 0)
{
dmn_handle_list_push(dmn_w32_shared->detach_arena, &dmn_w32_shared->detach_processes, process);
}
}
return result;
}
internal DMN_EventList
dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls)
{
DMN_EventList events = {0};
dmn_access_open();
//////////////////////////////
//- rjf: determine event generation path
//
typedef enum DMN_W32_EventGenPath
{
DMN_W32_EventGenPath_NotAttached,
DMN_W32_EventGenPath_Run,
DMN_W32_EventGenPath_DetachProcesses,
}
DMN_W32_EventGenPath;
DMN_W32_EventGenPath event_gen_path = DMN_W32_EventGenPath_Run;
if(dmn_w32_shared->detach_processes.first != 0)
{
event_gen_path = DMN_W32_EventGenPath_DetachProcesses;
}
else
{
B32 any_processes_live = dmn_w32_shared->new_process_pending;
if(!any_processes_live)
{
for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first; process != &dmn_w32_entity_nil; process = process->next)
{
if(process->kind == DMN_W32_EntityKind_Process)
{
any_processes_live = 1;
break;
}
}
}
if(!any_processes_live)
{
event_gen_path = DMN_W32_EventGenPath_NotAttached;
}
}
//////////////////////////////
//- rjf: produce debug events
//
switch(event_gen_path)
{
////////////////////////////
//- rjf: produce not-attached error events
//
case DMN_W32_EventGenPath_NotAttached:
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_Error;
e->error_kind = DMN_ErrorKind_NotAttached;
}break;
////////////////////////////
//- rjf: produce debug events from regular running
//
case DMN_W32_EventGenPath_Run:
{
Temp scratch = scratch_begin(&arena, 1);
//////////////////////////
//- rjf: set single step bit
//
if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero()))
{
DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread);
Architecture arch = thread->arch;
switch(arch)
{
//- rjf: unimplemented win32/arch combos
case Architecture_Null:
case Architecture_COUNT:
{}break;
case Architecture_arm64:
case Architecture_arm32:
{NotImplemented;}break;
//- rjf: x86/64
case Architecture_x86:
{
REGS_RegBlockX86 regs = {0};
dmn_thread_read_reg_block(ctrls->single_step_thread, &regs);
regs.eflags.u32 |= 0x100;
dmn_thread_write_reg_block(ctrls->single_step_thread, &regs);
}break;
case Architecture_x64:
{
REGS_RegBlockX64 regs = {0};
dmn_thread_read_reg_block(ctrls->single_step_thread, &regs);
regs.rflags.u64 |= 0x100;
dmn_thread_write_reg_block(ctrls->single_step_thread, &regs);
}break;
}
}
//////////////////////////
//- rjf: write all traps into memory
//
U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, ctrls->traps.trap_count);
{
U64 trap_idx = 0;
for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next)
{
for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1)
{
DMN_Trap *trap = n->v+n_idx;
trap_swap_bytes[trap_idx] = 0xCC;
dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr+1), trap_swap_bytes+trap_idx);
U8 int3 = 0xCC;
dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &int3);
}
}
}
//////////////////////////
//- rjf: produce list of threads which will run
//
DMN_W32_EntityNode *first_run_thread = 0;
DMN_W32_EntityNode *last_run_thread = 0;
{
//- rjf: scan all processes
for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first;
process != &dmn_w32_entity_nil;
process = process->next)
{
if(process->kind != DMN_W32_EntityKind_Process) {continue;}
//- rjf: determine if this process is frozen
B32 process_is_frozen = 0;
if(ctrls->run_entities_are_processes)
{
for(U64 idx = 0; idx < ctrls->run_entity_count; idx += 1)
{
if(dmn_handle_match(ctrls->run_entities[idx], dmn_w32_handle_from_entity(process)))
{
process_is_frozen = 1;
break;
}
}
}
//- rjf: scan all threads in this process
for(DMN_W32_Entity *thread = process->first;
thread != &dmn_w32_entity_nil;
thread = thread->next)
{
if(thread->kind != DMN_W32_EntityKind_Thread) {continue;}
//- rjf: determine if this thread is frozen
B32 is_frozen = 0;
{
// rjf: single-step? freeze if not the single-step thread.
if(!dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread) &&
!dmn_handle_match(dmn_w32_handle_from_entity(thread), ctrls->single_step_thread))
{
is_frozen = 1;
}
// rjf: not single-stepping? determine based on run controls freezing info
else
{
if(ctrls->run_entities_are_processes)
{
is_frozen = process_is_frozen;
}
else for(U64 idx = 0; idx < ctrls->run_entity_count; idx += 1)
{
if(dmn_handle_match(ctrls->run_entities[idx], dmn_w32_handle_from_entity(thread)))
{
is_frozen = 1;
break;
}
}
if(ctrls->run_entities_are_unfrozen)
{
is_frozen ^= 1;
}
}
}
//- rjf: disregard all other rules if this is the halter thread
if(dmn_w32_shared->halter_tid == thread->id)
{
is_frozen = 0;
}
//- rjf: add to list
if(!is_frozen)
{
DMN_W32_EntityNode *n = push_array(scratch.arena, DMN_W32_EntityNode, 1);
n->v = thread;
SLLQueuePush(first_run_thread, last_run_thread, n);
}
}
}
}
//////////////////////////
//- rjf: resume threads which will run
//
for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next)
{
DMN_W32_Entity *thread = n->v;
DWORD resume_result = ResumeThread(thread->handle);
switch(resume_result)
{
case 0xffffffffu:
{
// TODO(rjf): error - unknown cause. need to do GetLastError, FormatMessage
}break;
default:
{
DWORD desired_counter = 0;
DWORD current_counter = resume_result - 1;
if(current_counter != desired_counter)
{
// NOTE(rjf): Warning. The user has manually suspended this thread,
// so even though from Demon's perspective it thinks this thread
// should run, it will not, because the user has manually called
// SuspendThread or used CREATE_SUSPENDED or whatever.
}
}break;
}
}
//////////////////////////
//- rjf: if run threads are marked as having reported an explicit trap
// on their last run, shift their RIPs past that trap instruction, so
// that they may continue
//
for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next)
{
DMN_W32_Entity *thread = n->v;
if(thread->thread.last_run_reported_trap)
{
Temp temp = temp_begin(scratch.arena);
U64 regs_block_size = regs_block_size_from_architecture(thread->arch);
void *regs_block = push_array(temp.arena, U8, regs_block_size);
B32 good = dmn_w32_thread_read_reg_block(thread->arch, thread->handle, regs_block);
U64 pre_rip = regs_rip_from_arch_block(thread->arch, regs_block);
if(good && pre_rip == thread->thread.last_run_reported_trap_pre_rip)
{
regs_arch_block_write_rip(thread->arch, regs_block, thread->thread.last_run_reported_trap_post_rip);
dmn_w32_thread_write_reg_block(thread->arch, thread->handle, regs_block);
}
temp_end(temp);
thread->thread.last_run_reported_trap = 0;
thread->thread.last_run_reported_trap_post_rip = 0;
}
}
//////////////////////////
//- rjf: choose win32 resume code
//
DWORD resume_code = DBG_CONTINUE;
{
if(dmn_w32_shared->exception_not_handled && !ctrls->ignore_previous_exception)
{
dmn_w32_shared->exception_not_handled = 0;
resume_code = DBG_EXCEPTION_NOT_HANDLED;
}
}
//////////////////////////
//- rjf: inform windows that we're resuming, run, & obtain next debug event
//
DEBUG_EVENT evt = {0};
B32 evt_good = 0;
{
B32 resume_good = 1;
if(dmn_w32_shared->resume_needed)
{
dmn_w32_shared->resume_needed = 0;
resume_good = !!ContinueDebugEvent(dmn_w32_shared->resume_pid, dmn_w32_shared->resume_tid, resume_code);
dmn_w32_shared->resume_needed = 0;
dmn_w32_shared->resume_tid = 0;
dmn_w32_shared->resume_pid = 0;
}
if(resume_good)
{
evt_good = !!WaitForDebugEvent(&evt, INFINITE);
if(evt_good)
{
dmn_w32_shared->resume_needed = 1;
dmn_w32_shared->resume_pid = evt.dwProcessId;
dmn_w32_shared->resume_tid = evt.dwThreadId;
}
ins_atomic_u64_inc_eval(&dmn_w32_shared->run_gen);
ins_atomic_u64_inc_eval(&dmn_w32_shared->mem_gen);
ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen);
}
}
//////////////////////////
//- rjf: suspend threads which ran
//
if(evt_good) for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next)
{
DMN_W32_Entity *thread = n->v;
DWORD suspend_result = SuspendThread(thread->handle);
switch(suspend_result)
{
case 0xffffffffu:
{
// TODO(rjf): error - unknown cause. need to do do GetLastError, FormatMessage
//
// NOTE(rjf): this can happen when the event is EXIT_THREAD_DEBUG_EVENT
// or EXIT_PROCESS_DEBUG_EVENT. after such an event, SuspendThread
// gives error code 5 (access denied). this has no adverse effects, but
// if we want to start reporting errors we should take care to avoid
// calling SuspendThread in that case.
}break;
default:
{
DWORD desired_counter = 1;
DWORD current_counter = suspend_result + 1;
if(current_counter != desired_counter)
{
// NOTE(rjf): Warning. We've suspended to something higher than 1.
// In this case, it means the user probably created the thread in
// a suspended state, or they called SuspendThread.
}
}break;
}
}
//////////////////////////
//- rjf: process the new event
//
if(evt_good)
{
switch(evt.dwDebugEventCode)
{
//////////////////////
//- rjf: process was created
//
case CREATE_PROCESS_DEBUG_EVENT:
{
// rjf: zero out "process pending" state
dmn_w32_shared->new_process_pending = 0;
// rjf: unpack event
HANDLE process_handle = evt.u.CreateProcessInfo.hProcess;
HANDLE thread_handle = evt.u.CreateProcessInfo.hThread;
HANDLE module_handle = evt.u.CreateProcessInfo.hFile;
U64 tls_base = (U64)evt.u.CreateProcessInfo.lpThreadLocalBase;
U64 module_base = (U64)evt.u.CreateProcessInfo.lpBaseOfImage;
U64 module_name_vaddr = (U64)evt.u.CreateProcessInfo.lpImageName;
B32 module_name_is_unicode = (evt.u.CreateProcessInfo.fUnicode != 0);
DMN_W32_ImageInfo image_info = dmn_w32_image_info_from_process_base_vaddr(process_handle, module_base);
// rjf: create entities (thread/module are implied for initial - they are not reported by win32)
DMN_W32_Entity *process = dmn_w32_entity_alloc(dmn_w32_shared->entities_base, DMN_W32_EntityKind_Process, evt.dwProcessId);
DMN_W32_Entity *thread = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Thread, evt.dwThreadId);
DMN_W32_Entity *module = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Module, module_base);
{
process->handle = process_handle;
process->arch = image_info.arch;
thread->handle = thread_handle;
thread->arch = image_info.arch;
thread->thread.thread_local_base = tls_base;
module->handle = module_handle;
module->module.vaddr_range = r1u64(module_base, image_info.size);
module->module.is_main = 1;
module->module.address_of_name_pointer = module_name_vaddr;
module->module.name_is_unicode = module_name_is_unicode;
}
// rjf: put thread into suspended state, so it matches expected initial state
SuspendThread(thread_handle);
// rjf: set up per-process injected code (to run halter threads on &
// generate debug events)
{
U8 injection_code[DMN_W32_INJECTED_CODE_SIZE];
MemorySet(injection_code, 0xCC, DMN_W32_INJECTED_CODE_SIZE);
injection_code[0] = 0xC3;
U64 injection_size = DMN_W32_INJECTED_CODE_SIZE + sizeof(DMN_W32_InjectedBreak);
U64 injection_address = (U64)VirtualAllocEx(process_handle, 0, injection_size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE);
dmn_w32_process_write(process_handle, r1u64(injection_address, injection_address+sizeof(injection_code)), injection_code);
process->proc.injection_address = injection_address;
}
// rjf: generate events
{
// rjf: create process
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_CreateProcess;
e->process = dmn_w32_handle_from_entity(process);
e->arch = image_info.arch;
e->code = evt.dwProcessId;
}
// rjf: create thread
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_CreateThread;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(thread);
e->arch = image_info.arch;
e->code = evt.dwThreadId;
}
// rjf: load module
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_LoadModule;
e->process = dmn_w32_handle_from_entity(process);
e->module = dmn_w32_handle_from_entity(module);
e->arch = image_info.arch;
e->address = module_base;
e->size = image_info.size;
e->string = dmn_w32_full_path_from_module(arena, module);
}
}
}break;
//////////////////////
//- rjf: process exited
//
case EXIT_PROCESS_DEBUG_EVENT:
{
DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId);
// rjf: generate events for children
for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next)
{
switch(child->kind)
{
case DMN_W32_EntityKind_Thread:
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_ExitThread;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(child);
}break;
case DMN_W32_EntityKind_Module:
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_UnloadModule;
e->process = dmn_w32_handle_from_entity(process);
e->module = dmn_w32_handle_from_entity(child);
e->string = dmn_w32_full_path_from_module(arena, child);
}break;
}
}
// rjf: generate event for process
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_ExitProcess;
e->process = dmn_w32_handle_from_entity(process);
e->code = evt.u.ExitProcess.dwExitCode;
}
// rjf: release entity storage
dmn_w32_entity_release(process);
// rjf: detach
DebugActiveProcessStop(evt.dwProcessId);
}break;
//////////////////////
//- rjf: thread was created
//
case CREATE_THREAD_DEBUG_EVENT:
{
DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId);
// rjf: create thread entity
DMN_W32_Entity *thread = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Thread, evt.dwThreadId);
{
thread->handle = evt.u.CreateThread.hThread;
thread->arch = process->arch;
thread->thread.thread_local_base = (U64)evt.u.CreateThread.lpThreadLocalBase;
}
// rjf: suspend thread immediately upon creation, to match with expected suspension state
DWORD sus_result = SuspendThread(thread->handle);
(void)sus_result;
// rjf: unpack thread name
String8 thread_name = {0};
if(dmn_w32_GetThreadDescription != 0)
{
WCHAR *thread_name_w = 0;
HRESULT hr = dmn_w32_GetThreadDescription(thread->handle, &thread_name_w);
if(SUCCEEDED(hr))
{
thread_name = str8_from_16(arena, str16_cstring((U16 *)thread_name_w));
LocalFree(thread_name_w);
}
}
// rjf: determine if this is a "halter thread" - the threads we spawn to halt processes
B32 is_halter = (evt.dwThreadId == dmn_w32_shared->halter_tid);
// rjf: generate events for non-halter threads
if(!is_halter)
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_CreateThread;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(thread);
e->arch = thread->arch;
e->code = evt.dwThreadId;
e->string = thread_name;
}
}break;
//////////////////////
//- rjf: thread exited
//
case EXIT_THREAD_DEBUG_EVENT:
{
DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId);
DMN_W32_Entity *process = thread->parent;
// rjf: determine if this is the halter thread
B32 is_halter = (evt.dwThreadId == dmn_w32_shared->halter_tid);
// rjf: generate a halt event if this thread is the halter
if(is_halter)
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_Halt;
dmn_w32_shared->halter_process = dmn_handle_zero();
dmn_w32_shared->halter_tid = 0;
}
// rjf: if this thread is *not* the halter, then generate a regular exit-thread event
if(!is_halter)
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_ExitThread;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(thread);
e->code = evt.u.ExitThread.dwExitCode;
}
// rjf: release entity storage
dmn_w32_entity_release(thread);
}break;
//////////////////////
//- rjf: DLL was loaded
//
case LOAD_DLL_DEBUG_EVENT:
{
DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId);
// rjf: extract image info
U64 module_base = (U64)evt.u.LoadDll.lpBaseOfDll;
DMN_W32_ImageInfo image_info = dmn_w32_image_info_from_process_base_vaddr(process->handle, module_base);
// rjf: create module entity
DMN_W32_Entity *module = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Module, module_base);
{
module->handle = evt.u.LoadDll.hFile;
module->arch = image_info.arch;
module->module.vaddr_range = r1u64(module_base, module_base+image_info.size);
module->module.address_of_name_pointer = (U64)evt.u.LoadDll.lpImageName;
module->module.name_is_unicode = (evt.u.LoadDll.fUnicode != 0);
}
// rjf: generate event
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_LoadModule;
e->process = dmn_w32_handle_from_entity(process);
e->module = dmn_w32_handle_from_entity(module);
e->arch = module->arch;
e->address = module_base;
e->size = image_info.size;
e->string = dmn_w32_full_path_from_module(arena, module);
}
}break;
//////////////////////
//- rjf: DLL was unloaded
//
case UNLOAD_DLL_DEBUG_EVENT:
{
U64 module_base = (U64)evt.u.UnloadDll.lpBaseOfDll;
DMN_W32_Entity *module = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Module, module_base);
DMN_W32_Entity *process = module->parent;
// rjf: generate event
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_UnloadModule;
e->process = dmn_w32_handle_from_entity(process);
e->module = dmn_w32_handle_from_entity(module);
e->string = dmn_w32_full_path_from_module(arena, module);
}
// rjf: release entity storage
dmn_w32_entity_release(module);
}break;
//////////////////////
//- rjf: exception was hit
//
case EXCEPTION_DEBUG_EVENT:
{
// NOTE(rjf): Notes on multithreaded breakpoint events
// (2021/11/1):
//
// When many threads are simultaneously running, multiple threads
// may hit a trap "at the same time". When this happens there will be
// multiple events in an internal queue that we cannot see. If there
// is another event in the queue we will not see it until we call
// ContinueDebugEvent again, in a subsequent call to demon_os_run.
//
// When we get a trap event, the instruction pointer stored
// in the event will have the address of the int 3 instruction that
// was hit. Our RIP register, however, will be one byte past that.
// So, to get the behavior we want, we need to set the RIP register
// back to the address of the int 3.
//
// To deal with the fact that we may get breakpoint events later that
// were actually from this run what we do is:
//
// #1. If we get a trap event, and it corresponds to a user submitted
// trap, then we treat it is a breakpoint event.
// #2. If we get a trap event, and it does NOT correspond to a user
// trap in this call:
// #A. If the actual unmodified instruction byte is NOT an int 3,
// then this is a queued event from a previous run that is no
// longer applicable and we skip it.
// #B. If the actual unmodified instruction is an int 3, then this
// becomes a trap event and we do not reset RIP.
// NOTE(rjf): The exception record struct has a 32-bit version and a
// 64-bit version. We only currently handle the 64-bit version.
//- rjf: unpack
DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId);
DMN_W32_Entity *process = thread->parent;
EXCEPTION_DEBUG_INFO *edi = &evt.u.Exception;
EXCEPTION_RECORD *exception = &edi->ExceptionRecord;
U64 instruction_pointer = (U64)exception->ExceptionAddress;
//- rjf: determine if this is the first breakpoint in a process
// (breakpoint notifying us that the debugger is attached)
B32 first_bp = 0;
if(!process->proc.did_first_bp && exception->ExceptionCode == DMN_W32_EXCEPTION_BREAKPOINT)
{
process->proc.did_first_bp = 1;
first_bp = 1;
}
//- rjf: determine if this exception is a trap
B32 is_trap = (!first_bp &&
(exception->ExceptionCode == DMN_W32_EXCEPTION_BREAKPOINT ||
exception->ExceptionCode == DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN));
//- rjf: check if this trap is a usage-code-specified trap or something else
B32 hit_user_trap = 0;
if(is_trap)
{
for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next)
{
for(U64 idx = 0; idx < n->count; idx += 1)
{
if(dmn_handle_match(n->v[idx].process, dmn_w32_handle_from_entity(process)) && n->v[idx].vaddr == instruction_pointer)
{
hit_user_trap = 1;
break;
}
}
}
}
//- rjf: check if trap is explicit in the actual code memory
B32 hit_explicit_trap = 0;
if(is_trap && !hit_user_trap)
{
U8 instruction_byte = 0;
if(dmn_w32_process_read_struct(process->handle, instruction_pointer, &instruction_byte))
{
hit_explicit_trap = (instruction_byte == 0xCC || instruction_byte == 0xCD);
}
}
//- rjf: determine whether to roll back instruction pointer
B32 should_do_rollback = (is_trap);
//- rjf: roll back thread's instruction pointer
U64 post_trap_rip = 0;
if(should_do_rollback)
{
Temp temp = temp_begin(scratch.arena);
U64 regs_block_size = regs_block_size_from_architecture(thread->arch);
void *regs_block = push_array(scratch.arena, U8, regs_block_size);
if(dmn_w32_thread_read_reg_block(thread->arch, thread->handle, regs_block))
{
post_trap_rip = regs_rip_from_arch_block(thread->arch, regs_block);
regs_arch_block_write_rip(thread->arch, regs_block, instruction_pointer);
dmn_w32_thread_write_reg_block(thread->arch, thread->handle, regs_block);
}
temp_end(temp);
}
//- rjf: not a user trap, not an explicit trap, then it's a trap that
// this thread hit previously but has since skipped
B32 hit_previous_trap = (is_trap && !hit_user_trap && !hit_explicit_trap);
//- rjf: determine whether to skip this event
B32 skip_event = (hit_previous_trap);
//- rjf: generate event
if(!skip_event)
{
// rjf: fill top-level info
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_Exception;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(thread);
e->code = exception->ExceptionCode;
e->flags = exception->ExceptionFlags;
e->instruction_pointer = (U64)exception->ExceptionAddress;
// rjf: explicit trap -> mark this thread as having reported this trap
if(hit_explicit_trap)
{
thread->thread.last_run_reported_trap = 1;
thread->thread.last_run_reported_trap_pre_rip = instruction_pointer;
thread->thread.last_run_reported_trap_post_rip = post_trap_rip;
}
//- rjf: fill according to exception code
switch(exception->ExceptionCode)
{
//- rjf: fill breakpoint event info
case DMN_W32_EXCEPTION_BREAKPOINT:
{
DMN_EventKind report_event_kind = DMN_EventKind_Trap;
if(first_bp)
{
report_event_kind = DMN_EventKind_HandshakeComplete;
}
else if(hit_user_trap)
{
report_event_kind = DMN_EventKind_Breakpoint;
}
e->kind = report_event_kind;
}break;
//- rjf: fill stack buffer overrun event info
case DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN:
{
e->kind = DMN_EventKind_Trap;
}break;
//- rjf: fill single-step event info
case DMN_W32_EXCEPTION_SINGLE_STEP:
{
e->kind = DMN_EventKind_SingleStep;
}break;
//- rjf: fill throw info
case DMN_W32_EXCEPTION_THROW:
{
U64 exception_sp = 0;
U64 exception_ip = 0;
if(exception->NumberParameters >= 3)
{
exception_sp = (U64)exception->ExceptionInformation[1];
exception_ip = (U64)exception->ExceptionInformation[2];
}
e->stack_pointer = exception_sp;
e->exception_kind = DMN_ExceptionKind_CppThrow;
e->exception_repeated = (edi->dwFirstChance == 0);
dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0);
}break;
//- rjf: fill access violation info
case DMN_W32_EXCEPTION_ACCESS_VIOLATION:
case DMN_W32_EXCEPTION_IN_PAGE_ERROR:
{
U64 exception_address = 0;
DMN_ExceptionKind exception_kind = DMN_ExceptionKind_Null;
if(exception->NumberParameters >= 2)
{
switch(exception->ExceptionInformation[0])
{
case 0: exception_kind = DMN_ExceptionKind_MemoryRead; break;
case 1: exception_kind = DMN_ExceptionKind_MemoryWrite; break;
case 8: exception_kind = DMN_ExceptionKind_MemoryExecute; break;
}
exception_address = exception->ExceptionInformation[1];
}
e->address = exception_address;
e->exception_kind = exception_kind;
e->exception_repeated = (edi->dwFirstChance == 0);
dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0);
}break;
//- rjf: fill set-thread-name info
case DMN_W32_EXCEPTION_SET_THREAD_NAME:
if(exception->NumberParameters >= 2)
{
U64 thread_name_address = exception->ExceptionInformation[1];
DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId);
String8List thread_name_strings = {0};
{
U64 read_addr = thread_name_address;
U64 total_string_size = 0;
for(;total_string_size < KB(4);)
{
U8 *buffer = push_array(scratch.arena, U8, 256);
B32 good_read = dmn_w32_process_read(process->handle, r1u64(read_addr, read_addr+256), buffer);
if(good_read)
{
U64 size = 256;
for(U64 idx = 0; idx < 256; idx += 1)
{
if(buffer[idx] == 0)
{
size = idx;
break;
}
}
String8 string_part = str8(buffer, size);
str8_list_push(scratch.arena, &thread_name_strings, string_part);
total_string_size += size;
read_addr += size;
if(size < 256)
{
break;
}
}
else
{
break;
}
}
}
e->kind = DMN_EventKind_SetThreadName;
e->string = str8_list_join(arena, &thread_name_strings, 0);
if(exception->NumberParameters > 2)
{
e->code = exception->ExceptionInformation[2];
}
}break;
//- rjf: unhandled exception case
default:
{
e->exception_repeated = (edi->dwFirstChance == 0);
dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0);
}break;
}
}
}break;
//////////////////////
//- rjf: output debug string was gathered
//
case OUTPUT_DEBUG_STRING_EVENT:
{
// rjf: unpack event
DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId);
DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId);
U64 string_address = (U64)evt.u.DebugString.lpDebugStringData;
U64 string_size = (U64)evt.u.DebugString.nDebugStringLength;
// rjf: read memory
U8 *buffer = push_array_no_zero(arena, U8, string_size + 1);
dmn_w32_process_read(process->handle, r1u64(string_address, string_address+string_size), buffer);
buffer[string_size] = 0;
// rjf: generate event
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_DebugString;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(thread);
e->string = str8(buffer, string_size);
if(string_size != 0 && buffer[string_size-1] == 0)
{
e->string.size -= 1;
}
}
}break;
//////////////////////
//- rjf: a "rip event" - a "system debugging error".
//
case RIP_EVENT:
{
DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId);
DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId);
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_Exception;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(thread);
}break;
//////////////////////
//- rjf: default case - some kind of debugging event that we don't currently consume.
//
default:
{
NoOp;
}break;
}
}
//- rjf: gather new thread-names
if(dmn_w32_GetThreadDescription != 0)
{
for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first;
process != &dmn_w32_entity_nil;
process = process->next)
{
if(process->kind != DMN_W32_EntityKind_Process) { continue; }
for(DMN_W32_Entity *thread = process->first;
thread != &dmn_w32_entity_nil;
thread = thread->next)
{
if(thread->kind != DMN_W32_EntityKind_Thread) { continue; }
if(thread->thread.last_name_hash == 0 ||
thread->thread.name_gather_time_us+1000000 <= os_now_microseconds())
{
String8 name = {0};
{
WCHAR *thread_name_w = 0;
HRESULT hr = dmn_w32_GetThreadDescription(thread->handle, &thread_name_w);
if(SUCCEEDED(hr))
{
name = str8_from_16(scratch.arena, str16_cstring((U16 *)thread_name_w));
LocalFree(thread_name_w);
}
}
U64 name_hash = dmn_w32_hash_from_string(name);
if(name.size != 0 && name_hash != thread->thread.last_name_hash)
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_SetThreadName;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(thread);
e->string = push_str8_copy(arena, name);
}
thread->thread.name_gather_time_us = os_now_microseconds();
thread->thread.last_name_hash = name_hash;
}
}
}
}
//////////////////////////
//- rjf: restore original memory at trap locations
//
{
U64 trap_idx = 0;
for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next)
{
for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1)
{
DMN_Trap *trap = n->v+n_idx;
U8 og_byte = trap_swap_bytes[trap_idx];
if(og_byte != 0xCC)
{
dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &og_byte);
}
}
}
}
//////////////////////////
//- rjf: unset single step bit
//
if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero()))
{
DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread);
Architecture arch = thread->arch;
switch(arch)
{
//- rjf: unimplemented win32/arch combos
case Architecture_Null:
case Architecture_COUNT:
{}break;
case Architecture_arm64:
case Architecture_arm32:
{NotImplemented;}break;
//- rjf: x86/64
case Architecture_x86:
{
REGS_RegBlockX86 regs = {0};
dmn_thread_read_reg_block(ctrls->single_step_thread, &regs);
regs.eflags.u32 &= ~0x100;
dmn_thread_write_reg_block(ctrls->single_step_thread, &regs);
}break;
case Architecture_x64:
{
REGS_RegBlockX64 regs = {0};
dmn_thread_read_reg_block(ctrls->single_step_thread, &regs);
regs.rflags.u64 &= ~0x100;
dmn_thread_write_reg_block(ctrls->single_step_thread, &regs);
}break;
}
}
scratch_end(scratch);
}break;
////////////////////////////
//- rjf: produce debug events from queued up detached processes
//
case DMN_W32_EventGenPath_DetachProcesses:
{
for(DMN_HandleNode *n = dmn_w32_shared->detach_processes.first; n != 0; n = n->next)
{
DMN_W32_Entity *process = dmn_w32_entity_from_handle(n->v);
// rjf: push exit thread events
for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next)
{
if(child->kind == DMN_W32_EntityKind_Thread)
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_ExitThread;
e->process = dmn_w32_handle_from_entity(process);
e->thread = dmn_w32_handle_from_entity(child);
}
}
// rjf: push unload module events
for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next)
{
if(child->kind == DMN_W32_EntityKind_Module)
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_UnloadModule;
e->process = dmn_w32_handle_from_entity(process);
e->module = dmn_w32_handle_from_entity(child);
e->string = dmn_w32_full_path_from_module(arena, child);
}
}
// rjf: push exit process event
{
DMN_Event *e = dmn_event_list_push(arena, &events);
e->kind = DMN_EventKind_ExitProcess;
e->process = dmn_w32_handle_from_entity(process);
}
// rjf: free process
dmn_w32_entity_release(process);
}
// rjf: reset queued up detached processes
MemoryZeroStruct(&dmn_w32_shared->detach_processes);
arena_clear(dmn_w32_shared->detach_arena);
}break;
}
dmn_access_close();
return events;
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS)
internal void
dmn_halt(U64 code, U64 user_data)
{
if(dmn_handle_match(dmn_handle_zero(), dmn_w32_shared->halter_process))
{
DMN_W32_Entity *process = &dmn_w32_entity_nil;
for(DMN_W32_Entity *entity = dmn_w32_shared->entities_base->first;
entity != &dmn_w32_entity_nil;
entity = entity->next)
{
if(entity->kind == DMN_W32_EntityKind_Process)
{
process = entity;
break;
}
}
if(process != &dmn_w32_entity_nil)
{
dmn_w32_shared->halter_process = dmn_w32_handle_from_entity(process);
DMN_W32_InjectedBreak injection = {code, user_data};
U64 data_injection_address = process->proc.injection_address + DMN_W32_INJECTED_CODE_SIZE;
dmn_w32_process_write_struct(process->handle, data_injection_address, &injection);
dmn_w32_shared->halter_tid = dmn_w32_inject_thread(process->handle, process->proc.injection_address);
}
}
}
////////////////////////////////
//~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS)
//- rjf: run/memory/register counters
internal U64
dmn_run_gen(void)
{
U64 result = ins_atomic_u64_eval(&dmn_w32_shared->run_gen);
return result;
}
internal U64
dmn_mem_gen(void)
{
U64 result = ins_atomic_u64_eval(&dmn_w32_shared->mem_gen);
return result;
}
internal U64
dmn_reg_gen(void)
{
U64 result = ins_atomic_u64_eval(&dmn_w32_shared->reg_gen);
return result;
}
//- rjf: non-blocking-control-thread access barriers
internal B32
dmn_access_open(void)
{
B32 result = 0;
if(dmn_w32_ctrl_thread)
{
result = 1;
}
else
{
os_mutex_take(dmn_w32_shared->access_mutex);
result = !dmn_w32_shared->access_run_state;
}
return result;
}
internal void
dmn_access_close(void)
{
if(!dmn_w32_ctrl_thread)
{
os_mutex_drop(dmn_w32_shared->access_mutex);
}
}
//- rjf: processes
internal U64
dmn_process_read(DMN_Handle process, Rng1U64 range, void *dst)
{
U64 result = 0;
DMN_AccessScope
{
DMN_W32_Entity *entity = dmn_w32_entity_from_handle(process);
result = dmn_w32_process_read(entity->handle, range, dst);
}
return result;
}
internal B32
dmn_process_write(DMN_Handle process, Rng1U64 range, void *src)
{
B32 result = 0;
DMN_AccessScope
{
DMN_W32_Entity *entity = dmn_w32_entity_from_handle(process);
result = dmn_w32_process_write(entity->handle, range, src);
}
return result;
}
//- rjf: threads
internal Architecture
dmn_arch_from_thread(DMN_Handle handle)
{
Architecture arch = Architecture_Null;
DMN_AccessScope
{
DMN_W32_Entity *entity = dmn_w32_entity_from_handle(handle);
arch = entity->arch;
}
return arch;
}
internal U64
dmn_stack_base_vaddr_from_thread(DMN_Handle handle)
{
U64 result = 0;
DMN_AccessScope
{
DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle);
if(thread->kind == DMN_W32_EntityKind_Thread)
{
DMN_W32_Entity *process = thread->parent;
U64 tlb = thread->thread.thread_local_base;
switch(thread->arch)
{
case Architecture_Null:
case Architecture_COUNT:
{}break;
case Architecture_arm64:
case Architecture_arm32:
{NotImplemented;}break;
case Architecture_x64:
{
U64 stack_base_addr = tlb + 0x8;
dmn_w32_process_read(process->handle, r1u64(stack_base_addr, stack_base_addr+8), &result);
}break;
case Architecture_x86:
{
U64 stack_base_addr = tlb + 0x4;
dmn_w32_process_read(process->handle, r1u64(stack_base_addr, stack_base_addr+4), &result);
}break;
}
}
}
return result;
}
internal U64
dmn_tls_root_vaddr_from_thread(DMN_Handle handle)
{
U64 result = 0;
DMN_AccessScope
{
DMN_W32_Entity *entity = dmn_w32_entity_from_handle(handle);
if(entity->kind == DMN_W32_EntityKind_Thread)
{
result = entity->thread.thread_local_base;
switch(entity->arch)
{
case Architecture_Null:
case Architecture_COUNT:
{}break;
case Architecture_arm64:
case Architecture_arm32:
{NotImplemented;}break;
case Architecture_x64:
{
result += 88;
}break;
case Architecture_x86:
{
result += 44;
}break;
}
}
}
return result;
}
internal B32
dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block)
{
B32 result = 0;
DMN_AccessScope
{
DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle);
result = dmn_w32_thread_read_reg_block(thread->arch, thread->handle, reg_block);
}
return result;
}
internal B32
dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block)
{
B32 result = 0;
DMN_AccessScope
{
DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle);
result = dmn_w32_thread_write_reg_block(thread->arch, thread->handle, reg_block);
}
return result;
}
//- rjf: system process listing
internal void
dmn_process_iter_begin(DMN_ProcessIter *iter)
{
MemoryZeroStruct(iter);
iter->v[0] = (U64)CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
}
internal B32
dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out)
{
B32 result = 0;
//- rjf: get the next process entry
PROCESSENTRY32W process_entry = {sizeof(process_entry)};
HANDLE snapshot = (HANDLE)iter->v[0];
if(iter->v[1] == 0)
{
if(Process32FirstW(snapshot, &process_entry))
{
result = 1;
}
}
else
{
if(Process32NextW(snapshot, &process_entry))
{
result = 1;
}
}
//- rjf: increment counter
iter->v[1] += 1;
//- rjf: convert to process info
if(result)
{
info_out->name = str8_from_16(arena, str16_cstring((U16*)process_entry.szExeFile));
info_out->pid = (U32)process_entry.th32ProcessID;
}
return result;
}
internal void
dmn_process_iter_end(DMN_ProcessIter *iter)
{
CloseHandle((HANDLE)iter->v[0]);
MemoryZeroStruct(iter);
}