// Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// //~ rjf: Globals global GetThreadDescriptionFunctionType *demon_w32_GetThreadDescription = 0; global B32 demon_w32_resume_needed = 0; global DWORD demon_w32_resume_pid = 0; global DWORD demon_w32_resume_tid = 0; global B32 demon_w32_exception_not_handled = 0; global DEMON_Entity* demon_w32_halter_process = 0; global DWORD demon_w32_halter_thread_id = 0; global B32 demon_w32_new_process_pending = 0; global Arena *demon_w32_ext_arena = 0 ; global DEMON_W32_Ext *demon_w32_proc_ext_free = 0; global Arena *demon_w32_detach_proc_arena = 0; global DEMON_EntityNode *demon_w32_first_detached_proc = 0; global DEMON_EntityNode *demon_w32_last_detached_proc = 0; global String8List demon_w32_environment = {0}; //////////////////////////////// //~ rjf: Helpers internal U64 demon_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 DEMON_W32_Ext* demon_w32_ext_alloc(void){ DEMON_W32_Ext *result = demon_w32_proc_ext_free; if (result != 0){ SLLStackPop(demon_w32_proc_ext_free); } else{ result = push_array_no_zero(demon_w32_ext_arena, DEMON_W32_Ext, 1); } MemoryZeroStruct(result); return(result); } internal DEMON_W32_Ext* demon_w32_ext(DEMON_Entity *entity){ DEMON_W32_Ext *result = (DEMON_W32_Ext*)entity->ext; return(result); } internal U64 demon_w32_read_memory(HANDLE process_handle, void *dst, U64 src_address, U64 size){ U64 bytes_read = 0; U8 *ptr = (U8*)dst; U8 *opl = ptr + size; U64 cursor = src_address; for (;ptr < opl;){ SIZE_T to_read = (SIZE_T)(opl - ptr); SIZE_T actual_read = 0; if (!ReadProcessMemory(process_handle, (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 demon_w32_write_memory(HANDLE process_handle, U64 dst_address, void *src, U64 size){ B32 result = 1; U8 *ptr = (U8*)src; U8 *opl = ptr + size; U64 cursor = dst_address; for (;ptr < opl;){ SIZE_T to_write = (SIZE_T)(opl - ptr); SIZE_T actual_write = 0; if (!WriteProcessMemory(process_handle, (LPVOID)cursor, ptr, to_write, &actual_write)){ result = 0; break; } ptr += actual_write; cursor += actual_write; } return(result); } internal String8 demon_w32_read_memory_str(Arena *arena, HANDLE process_handle, U64 address){ // TODO(allen): this could be done better with a demon_w32_read_memory // that returns a read amount instead of a success/fail. // 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 (demon_w32_read_memory(process_handle, block, read_p, cap)){ 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 demon_w32_read_memory_str16(Arena *arena, HANDLE process_handle, U64 address){ // TODO(allen): this could be done better with a demon_w32_read_memory // that returns a read amount instead of a success/fail. // 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 (demon_w32_read_memory(process_handle, block, read_p, cap)){ 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 DEMON_W32_ImageInfo demon_w32_image_info_from_base(HANDLE process_handle, U64 base){ // find pe offset in dos header U32 pe_offset = 0; { U64 dos_magic_off = base; U16 dos_magic = 0; demon_w32_read_struct(process_handle, &dos_magic, dos_magic_off); if (dos_magic == DEMON_DOS_MAGIC){ U64 pe_offset_off = base + OffsetOf(DEMON_DosHeader, coff_file_offset); demon_w32_read_struct(process_handle, &pe_offset, pe_offset_off); } } // get coff header B32 got_coff_header = 0; U64 coff_header_off = 0; DEMON_CoffHeader coff_header = {0}; if (pe_offset > 0){ U64 pe_magic_off = base + pe_offset; U32 pe_magic = 0; demon_w32_read_struct(process_handle, &pe_magic, pe_magic_off); if (pe_magic == DEMON_PE_MAGIC){ coff_header_off = pe_magic_off + sizeof(pe_magic); if (demon_w32_read_struct(process_handle, &coff_header, coff_header_off)){ got_coff_header = 1; } } } // get arch and size DEMON_W32_ImageInfo result = zero_struct; if (got_coff_header){ U64 optional_size_off = 0; Architecture arch = Architecture_Null; switch (coff_header.machine){ case DEMON_CoffMachineType_X86: { arch = Architecture_x86; optional_size_off = OffsetOf(DEMON_PeOptionalHeader32, sizeof_image); }break; case DEMON_CoffMachineType_X64: { arch = Architecture_x64; optional_size_off = OffsetOf(DEMON_PeOptionalHeader32Plus, sizeof_image); }break; default: {}break; } if (arch != Architecture_Null){ U64 optional_off = coff_header_off + sizeof(coff_header); U32 size = 0; if (demon_w32_read_struct(process_handle, &size, optional_off + optional_size_off)){ result.arch = arch; result.size = size; } } } return(result); } internal DWORD demon_w32_inject_thread(DEMON_Entity *process, U64 start_address){ LPTHREAD_START_ROUTINE start = (LPTHREAD_START_ROUTINE)start_address; DEMON_Entity *entity = process; DEMON_W32_Ext *process_ext = demon_w32_ext(entity); DWORD thread_id = 0; HANDLE thread = CreateRemoteThread(process_ext->proc.handle, 0, 0, start, 0, 0, &thread_id); if (thread != 0){ CloseHandle(thread); } return(thread_id); } internal U16 demon_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 demon_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 DWORD demon_w32_win32_from_memory_protect_flags(DEMON_MemoryProtectFlags flags){ DWORD result = 0; switch (flags){ default: case DEMON_MemoryProtectFlag_Read|DEMON_MemoryProtectFlag_Write: case DEMON_MemoryProtectFlag_Write: { result = PAGE_READWRITE; }break; case DEMON_MemoryProtectFlag_Read|DEMON_MemoryProtectFlag_Write|DEMON_MemoryProtectFlag_Execute: { result = PAGE_EXECUTE_READWRITE; }break; case DEMON_MemoryProtectFlag_Execute: { result = PAGE_EXECUTE; }break; case DEMON_MemoryProtectFlag_Read: { result = PAGE_READONLY; }break; } return(result); } //////////////////////////////// //~ rjf: Experiments internal void demon_w32_peak_at_tls(DEMON_Handle thread_handle){ DEMON_Entity *thread = demon_ent_ptr_from_handle(thread_handle); if (thread != 0 && thread->kind == DEMON_EntityKind_Thread){ DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); U64 tlb = thread_ext->thread.thread_local_base; U8 buffer[0x1000]; demon_os_read_memory(thread->parent, buffer, tlb, 0x1000); int x = 0; (void)x; } } //////////////////////////////// //~ rjf: @demon_os_hooks Main Layer Initialization internal void demon_os_init(void){ demon_w32_ext_arena = arena_alloc(); demon_w32_detach_proc_arena = arena_alloc(); // rjf: load Windows 10+ GetThreadDescription API { demon_w32_GetThreadDescription = (GetThreadDescriptionFunctionType *)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "GetThreadDescription"); } // rjf: setup environment variables { CHAR *this_proc_env = GetEnvironmentStrings(); U64 start_idx = 0; for(U64 idx = 0;; idx += 1) { if(this_proc_env[idx] == 0) { if(start_idx == idx) { break; } else { String8 string = str8((U8 *)this_proc_env + start_idx, idx - start_idx); str8_list_push(demon_w32_ext_arena, &demon_w32_environment, string); start_idx = idx+1; } } } } } //////////////////////////////// //~ rjf: @demon_os_hooks Running/Halting internal DEMON_EventList demon_os_run(Arena *arena, DEMON_OS_RunCtrls *ctrls){ DEMON_EventList result = {0}; if (demon_ent_root == 0){ DEMON_Event *event = demon_push_event(arena, &result, DEMON_EventKind_Error); event->error_kind = DEMON_ErrorKind_NotInitialized; } else if (demon_ent_root->first == 0 && !demon_w32_new_process_pending){ DEMON_Event *event = demon_push_event(arena, &result, DEMON_EventKind_Error); event->error_kind = DEMON_ErrorKind_NotAttached; } else if(demon_w32_first_detached_proc != 0) { for(DEMON_EntityNode *n = demon_w32_first_detached_proc; n != 0; n = n->next) { DEMON_Entity *process = n->entity; // rjf: push exit thread events for(DEMON_Entity *child = process->first; child != 0; child = child->next) { if(child->kind == DEMON_EntityKind_Thread) { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_ExitThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(child); } } // rjf: push unload module events for(DEMON_Entity *child = process->first; child != 0; child = child->next) { if(child->kind == DEMON_EntityKind_Module) { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_UnloadModule); e->process = demon_ent_handle_from_ptr(process); e->module = demon_ent_handle_from_ptr(child); e->string = demon_os_full_path_from_module(arena, child); } } // rjf: push exit process event { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_ExitProcess); e->process = demon_ent_handle_from_ptr(process); e->code = 0; } // rjf: free process demon_ent_release_root_and_children(process); } demon_w32_first_detached_proc = 0; demon_w32_last_detached_proc = 0; arena_clear(demon_w32_detach_proc_arena); } else{ Temp scratch = scratch_begin(&arena, 1); // get the single step thread (if any) DEMON_Entity *single_step_thread = ctrls->single_step_thread; // TODO(allen): dedup per architecture? // set single step bit if (single_step_thread != 0){ // TODO(allen): possibly buggy switch (single_step_thread->arch){ case Architecture_x86: { REGS_RegBlockX86 regs = {0}; demon_os_read_regs_x86(single_step_thread, ®s); regs.eflags.u32 |= 0x100; demon_os_write_regs_x86(single_step_thread, ®s); }break; case Architecture_x64: { REGS_RegBlockX64 regs = {0}; demon_os_read_regs_x64(single_step_thread, ®s); regs.rflags.u64 |= 0x100; demon_os_write_regs_x64(single_step_thread, ®s); }break; } } // TODO(allen): per-Architecture implementation of traps // set traps U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, ctrls->trap_count); { DEMON_OS_Trap *trap = ctrls->traps; for (U64 i = 0; i < ctrls->trap_count; i += 1, trap += 1){ if (demon_os_read_memory(trap->process, trap_swap_bytes + i, trap->address, 1)){ U8 int3 = 0xCC; demon_os_write_memory(trap->process, trap->address, &int3, 1); } else{ trap_swap_bytes[i] = 0xCC; } } } // determine how to resume from the last event DWORD resume_code = DBG_CONTINUE; if (demon_w32_exception_not_handled){ if (!ctrls->ignore_previous_exception){ resume_code = DBG_EXCEPTION_NOT_HANDLED; } } demon_w32_exception_not_handled = 0; // list threads that run this time DEMON_W32_EntityNode *first_run_thread = 0; DEMON_W32_EntityNode *last_run_thread = 0; for(DEMON_Entity *process = demon_ent_root->first; process != 0; process = process->next){ if (process->kind == DEMON_EntityKind_Process){ // determine if this process is frozen B32 process_is_frozen = 0; if (ctrls->run_entities_are_processes){ for (U64 i = 0; i < ctrls->run_entity_count; i += 1){ if (ctrls->run_entities[i] == process){ process_is_frozen = 1; break; } } } for (DEMON_Entity *thread = process->first; thread != 0; thread = thread->next){ if (thread->kind == DEMON_EntityKind_Thread){ // determine if this thread is frozen B32 is_frozen = 0; if (ctrls->single_step_thread != 0 && ctrls->single_step_thread != thread){ is_frozen = 1; } else{ if (ctrls->run_entities_are_processes){ is_frozen = process_is_frozen; } else{ for (U64 i = 0; i < ctrls->run_entity_count; i += 1){ if (ctrls->run_entities[i] == thread){ is_frozen = 1; break; } } } if (ctrls->run_entities_are_unfrozen){ is_frozen = !is_frozen; } } // rjf: disregard all other rules if this is the halter thread if(demon_w32_halter_thread_id == thread->id) { is_frozen = 0; } // add this thread to the list if (!is_frozen){ DEMON_W32_EntityNode *node = push_array_no_zero(scratch.arena, DEMON_W32_EntityNode, 1); SLLQueuePush(first_run_thread, last_run_thread, node); node->entity = thread; } } } } } // prep suspension state of threads that will be allowed to run for(DEMON_W32_EntityNode *node = first_run_thread; node != 0; node = node->next) { DEMON_Entity *thread = node->entity; DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); DWORD resume_result = ResumeThread(thread_ext->thread.handle); if(resume_result == max_U32) { // TODO(allen): Error. Unknown cause (do GetLastError, FromatMessage) } else { 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. } } } // 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(DEMON_W32_EntityNode *node = first_run_thread; node != 0; node = node->next) { DEMON_Entity *thread = node->entity; DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); if(thread_ext->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 = demon_os_read_regs(thread, regs_block); U64 pre_rip = regs_rip_from_arch_block(thread->arch, regs_block); if(good && pre_rip == thread_ext->thread.last_run_reported_trap_pre_rip) { regs_arch_block_write_rip(thread->arch, regs_block, thread_ext->thread.last_run_reported_trap_post_rip); demon_os_write_regs(thread, regs_block); } temp_end(temp); thread_ext->thread.last_run_reported_trap = 0; thread_ext->thread.last_run_reported_trap_post_rip = 0; } } // send last saved continue signal B32 good_state = 1; if (demon_w32_resume_needed != 0){ if (!ContinueDebugEvent(demon_w32_resume_pid, demon_w32_resume_tid, resume_code)){ good_state = 0; } demon_w32_resume_needed = 0; demon_w32_resume_pid = 0; demon_w32_resume_tid = 0; } // wait for a new event from targets DEBUG_EVENT evt = {0}; B32 got_new_event = 0; if (good_state){ got_new_event = WaitForDebugEvent(&evt, INFINITE); } if (got_new_event){ demon_w32_resume_needed = 1; demon_w32_resume_pid = evt.dwProcessId; demon_w32_resume_tid = evt.dwThreadId; } // increment demon time demon_time += 1; // reset all threads to paused state if (got_new_event){ for (DEMON_W32_EntityNode *node = first_run_thread; node != 0; node = node->next){ DEMON_Entity *thread = node->entity; DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); DWORD suspend_result = SuspendThread(thread_ext->thread.handle); if (suspend_result == max_U32){ // TODO(allen): Error. Unknown cause (do GetLastError, FromatMessage) // NOTE(allen): 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 // want to start reporting errors we should take care to avoid calling // SuspendThread in that case. } else{ 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. } } } } // rjf: process event if (got_new_event){ got_new_event = 1; switch (evt.dwDebugEventCode){ case CREATE_PROCESS_DEBUG_EVENT: { // pull outs HANDLE process_handle = evt.u.CreateProcessInfo.hProcess; HANDLE thread_handle = evt.u.CreateProcessInfo.hThread; U64 module_base = (U64)evt.u.CreateProcessInfo.lpBaseOfImage; DEMON_W32_ImageInfo image_info = demon_w32_image_info_from_base(process_handle, module_base); // init process entity DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, evt.dwProcessId); demon_proc_count += 1; process->arch = image_info.arch; DEMON_W32_Ext *process_ext = demon_w32_ext_alloc(); process->ext = process_ext; process_ext->proc.handle = process_handle; demon_w32_new_process_pending = 0; // init new associated entities DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, evt.dwThreadId); demon_thread_count += 1; DEMON_W32_Ext *thread_ext = demon_w32_ext_alloc(); thread->ext = thread_ext; thread_ext->thread.handle = thread_handle; thread_ext->thread.thread_local_base = (U64)evt.u.CreateProcessInfo.lpThreadLocalBase; SuspendThread(thread_ext->thread.handle); DEMON_Entity *module = demon_ent_new(process, DEMON_EntityKind_Module, module_base); demon_module_count += 1; module->addr_range_dim = image_info.size; DEMON_W32_Ext *module_ext = demon_w32_ext_alloc(); module->ext = module_ext; module_ext->module.handle = evt.u.CreateProcessInfo.hFile; module_ext->module.address_of_name_pointer = (U64)evt.u.CreateProcessInfo.lpImageName; module_ext->module.is_main = 1; module_ext->module.name_is_unicode = (evt.u.CreateProcessInfo.fUnicode != 0); // injection memory { U8 injection_code[DEMON_W32_INJECTED_CODE_SIZE]; injection_code[0] = 0xC3; for (U64 i = 1; i < DEMON_W32_INJECTED_CODE_SIZE; i += 1){ injection_code[i] = 0xCC; } U64 injection_size = DEMON_W32_INJECTED_CODE_SIZE + sizeof(DEMON_W32_InjectedBreak); U64 injection_address = (U64)VirtualAllocEx(process_ext->proc.handle, 0, injection_size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE); demon_os_write_memory(process, injection_address, injection_code, sizeof(injection_code)); process_ext->proc.injection_address = injection_address; } // generate events { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_CreateProcess); e->process = demon_ent_handle_from_ptr(process); e->code = evt.dwProcessId; } { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_CreateThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->code = evt.dwThreadId; } { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_LoadModule); e->process = demon_ent_handle_from_ptr(process); e->module = demon_ent_handle_from_ptr(module); e->address = module_base; e->size = image_info.size; e->string = demon_os_full_path_from_module(arena, module); } }break; case EXIT_PROCESS_DEBUG_EVENT: { // get process entity DEMON_Entity *process = demon_ent_map_entity_from_id(DEMON_EntityKind_Process, evt.dwProcessId); if (process != 0){ // update halter process pointer if (process == demon_w32_halter_process){ demon_w32_halter_process = 0; } // generate events for threads & modules for (DEMON_Entity *entity = process->first; entity != 0; entity = entity->next){ if (entity->kind == DEMON_EntityKind_Thread){ DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_ExitThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(entity); } else{ DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_UnloadModule); e->process = demon_ent_handle_from_ptr(process); e->module = demon_ent_handle_from_ptr(entity); e->string = demon_os_full_path_from_module(arena, entity); } } // generate event for self DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_ExitProcess); e->process = demon_ent_handle_from_ptr(process); e->code = evt.u.ExitProcess.dwExitCode; // free entity demon_ent_release_root_and_children(process); // rjf: detach { DWORD pid = evt.dwProcessId; DebugActiveProcessStop(pid); } } }break; case CREATE_THREAD_DEBUG_EVENT: { // get process entity DEMON_Entity *process = demon_ent_map_entity_from_id(DEMON_EntityKind_Process, evt.dwProcessId); if (process != 0){ // init new entity DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, evt.dwThreadId); demon_thread_count += 1; DEMON_W32_Ext *thread_ext = demon_w32_ext_alloc(); thread->ext = thread_ext; thread_ext->thread.handle = evt.u.CreateThread.hThread; thread_ext->thread.thread_local_base = (U64)evt.u.CreateThread.lpThreadLocalBase; DWORD sus_result = SuspendThread(thread_ext->thread.handle); (void)sus_result; // rjf: unpack thread name String8 thread_name = {0}; if(demon_w32_GetThreadDescription != 0) { WCHAR *thread_name_w = 0; HRESULT hr = demon_w32_GetThreadDescription(evt.u.CreateThread.hThread, &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 the halter thread B32 is_halter = (evt.dwThreadId == demon_w32_halter_thread_id); // generate event for non-halters if (is_halter == 0){ DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_CreateThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->code = evt.dwThreadId; e->string = thread_name; } } }break; case EXIT_THREAD_DEBUG_EVENT: { // get thread and process entity DEMON_Entity *thread = demon_ent_map_entity_from_id(DEMON_EntityKind_Thread, evt.dwThreadId); if (thread != 0){ DEMON_Entity *process = thread->parent; // rjf: determine if this is the halter thread B32 is_halter = (evt.dwThreadId == demon_w32_halter_thread_id); // rjf: generate a halt event if we're the halter if (is_halter){ DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_Halt); demon_w32_halter_process = 0; (void)e; } // rjf: generate normal thread exit event on non-halter-thread exist if (!is_halter){ DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_ExitThread); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->code = evt.u.ExitThread.dwExitCode; } // free entity demon_ent_release_root_and_children(thread); } }break; case LOAD_DLL_DEBUG_EVENT: { // get process entity DEMON_Entity *process = demon_ent_map_entity_from_id(DEMON_EntityKind_Process, evt.dwProcessId); if (process != 0){ // get image info DEMON_W32_Ext *process_ext = demon_w32_ext(process); HANDLE process_handle = process_ext->proc.handle; U64 module_base = (U64)evt.u.LoadDll.lpBaseOfDll; DEMON_W32_ImageInfo image_info = demon_w32_image_info_from_base(process_handle, module_base); // init new entity DEMON_Entity *module = demon_ent_new(process, DEMON_EntityKind_Module, module_base); module->addr_range_dim = image_info.size; demon_module_count += 1; DEMON_W32_Ext *module_ext = demon_w32_ext_alloc(); module->ext = module_ext; module_ext->module.handle = evt.u.LoadDll.hFile; module_ext->module.address_of_name_pointer = (U64)evt.u.LoadDll.lpImageName; module_ext->module.is_main = 0; module_ext->module.name_is_unicode = (evt.u.LoadDll.fUnicode != 0); // generate events { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_LoadModule); e->process = demon_ent_handle_from_ptr(process); e->module = demon_ent_handle_from_ptr(module); e->address = module_base; e->size = image_info.size; e->string = demon_os_full_path_from_module(arena, module); } } }break; case UNLOAD_DLL_DEBUG_EVENT: { // get module and process entity U64 module_base = (U64)evt.u.UnloadDll.lpBaseOfDll; DEMON_Entity *module = demon_ent_map_entity_from_id(DEMON_EntityKind_Module, module_base); if (module != 0){ DEMON_Entity *process = module->parent; // generate events { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_UnloadModule); e->process = demon_ent_handle_from_ptr(process); e->module = demon_ent_handle_from_ptr(module); e->string = demon_os_full_path_from_module(arena, module); } // free entity demon_ent_release_root_and_children(module); } }break; 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. // get thread and process entity DEMON_Entity *thread = demon_ent_map_entity_from_id(DEMON_EntityKind_Thread, evt.dwThreadId); if (thread != 0){ DEMON_Entity *process = thread->parent; Assert(process->kind == DEMON_EntityKind_Process); DEMON_W32_Ext *process_ext = demon_w32_ext(process); // TODO(allen): the exception record has two forms, one for 32 bit and one for 64 bit mode. EXCEPTION_DEBUG_INFO *edi = &evt.u.Exception; EXCEPTION_RECORD *exception = &edi->ExceptionRecord; U64 instruction_pointer = (U64)exception->ExceptionAddress; // check if first BP B32 first_bp = 0; if (exception->ExceptionCode == DEMON_W32_EXCEPTION_BREAKPOINT && !process_ext->proc.did_first_bp){ process_ext->proc.did_first_bp = 1; first_bp = 1; } // rjf: check if trap B32 is_trap = (!first_bp && (exception->ExceptionCode == DEMON_W32_EXCEPTION_BREAKPOINT || exception->ExceptionCode == DEMON_W32_EXCEPTION_STACK_BUFFER_OVERRUN)); // rjf: check if this trap is currently registered B32 hit_user_trap = 0; if (is_trap){ DEMON_OS_Trap *trap = ctrls->traps; for (U64 i = 0; i < ctrls->trap_count; i += 1, trap += 1){ if (trap->process == process && trap->address == 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 (demon_os_read_memory(process, &instruction_byte, instruction_pointer, 1)){ // TODO(rjf): x86/x64 specific check // TODO(rjf): do we need to check to make sure the instruction // pointer has not changed? hit_explicit_trap = (instruction_byte == 0xCC || instruction_byte == 0xCD); } } // rjf: determine whether to roll back instruction pointer B32 rollback = (is_trap); // rjf: roll back U64 post_trap_rip = 0; if(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(demon_os_read_regs(thread, 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); demon_os_write_regs(thread, regs_block); } temp_end(temp); } // allen: if this is not a user trap or explicit trap it's a previous trap B32 hit_previous_trap = (is_trap && !hit_user_trap && !hit_explicit_trap); // determine whether to skip this event B32 skip_event = (hit_previous_trap); // emit event if(!skip_event) { // rjf: fill top-level info DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_Exception); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(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) { DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); thread_ext->thread.last_run_reported_trap = 1; thread_ext->thread.last_run_reported_trap_pre_rip = instruction_pointer; thread_ext->thread.last_run_reported_trap_post_rip = post_trap_rip; } // rjf: fill by exception code switch (exception->ExceptionCode){ case DEMON_W32_EXCEPTION_BREAKPOINT: { // rjf: determine event kind DEMON_EventKind report_event_kind = DEMON_EventKind_Trap; if (first_bp){ report_event_kind = DEMON_EventKind_HandshakeComplete; } else if (hit_user_trap){ report_event_kind = DEMON_EventKind_Breakpoint; } // set event kind e->kind = report_event_kind; }break; case DEMON_W32_EXCEPTION_STACK_BUFFER_OVERRUN: { e->kind = DEMON_EventKind_Trap; }break; case DEMON_W32_EXCEPTION_SINGLE_STEP: { e->kind = DEMON_EventKind_SingleStep; }break; case DEMON_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 = DEMON_ExceptionKind_CppThrow; e->exception_repeated = (edi->dwFirstChance == 0); demon_w32_exception_not_handled = (edi->dwFirstChance != 0); }break; case DEMON_W32_EXCEPTION_ACCESS_VIOLATION: case DEMON_W32_EXCEPTION_IN_PAGE_ERROR: { U64 exception_address = 0; DEMON_ExceptionKind exception_kind = DEMON_ExceptionKind_Null; if (exception->NumberParameters >= 2){ switch (exception->ExceptionInformation[0]){ case 0: exception_kind = DEMON_ExceptionKind_MemoryRead; break; case 1: exception_kind = DEMON_ExceptionKind_MemoryWrite; break; case 8: exception_kind = DEMON_ExceptionKind_MemoryExecute; break; } exception_address = exception->ExceptionInformation[1]; } e->address = exception_address; e->exception_kind = exception_kind; e->exception_repeated = (edi->dwFirstChance == 0); demon_w32_exception_not_handled = (edi->dwFirstChance != 0); }break; case DEMON_W32_EXCEPTION_SET_THREAD_NAME: { if(exception->NumberParameters >= 2) { U64 thread_name_address = exception->ExceptionInformation[1]; DEMON_Entity *process = demon_ent_map_entity_from_id(DEMON_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_no_zero(scratch.arena, U8, 256); B32 good_read = demon_os_read_memory(process, buffer, read_addr, 256); 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; } } } } e->kind = DEMON_EventKind_SetThreadName; e->string = str8_list_join(arena, &thread_name_strings, 0); if(exception->NumberParameters > 2) { e->code = exception->ExceptionInformation[2]; } } }break; default: { demon_w32_exception_not_handled = (edi->dwFirstChance != 0); }break; } } } }break; case OUTPUT_DEBUG_STRING_EVENT: { // get process entity DEMON_Entity *process = demon_ent_map_entity_from_id(DEMON_EntityKind_Process, evt.dwProcessId); DEMON_Entity *thread = demon_ent_map_entity_from_id(DEMON_EntityKind_Thread, evt.dwThreadId); U64 string_address = (U64)evt.u.DebugString.lpDebugStringData; U64 string_size = (U64)evt.u.DebugString.nDebugStringLength; // TODO(allen): is the string in UTF-8 or UTF-16? U8 *buffer = push_array_no_zero(arena, U8, string_size + 1); demon_os_read_memory(process, buffer, string_address, string_size); buffer[string_size] = 0; DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_DebugString); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->string = str8(buffer, string_size); if(string_size != 0 && buffer[string_size-1] == 0) { e->string.size -= 1; } }break; case RIP_EVENT: { DEMON_Entity *process = demon_ent_map_entity_from_id(DEMON_EntityKind_Process, evt.dwProcessId); DEMON_Entity *thread = demon_ent_map_entity_from_id(DEMON_EntityKind_Thread, evt.dwThreadId); // TODO(allen): this is not the right way to handle this event DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_Exception); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->flags = 0; e->address = 0; }break; default: { got_new_event = 0; }break; } } //- rjf: gather new thread-names if(demon_w32_GetThreadDescription != 0) { for(DEMON_Entity *process = demon_ent_root->first; process != 0; process = process->next) { if(process->kind != DEMON_EntityKind_Process) { continue; } for(DEMON_Entity *thread = process->first; thread != 0; thread = thread->next) { if(thread->kind != DEMON_EntityKind_Thread) { continue; } DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); if(thread_ext->thread.last_name_hash == 0 || thread_ext->thread.name_gather_time_us+1000000 <= os_now_microseconds()) { String8 name = {0}; { WCHAR *thread_name_w = 0; HRESULT hr = demon_w32_GetThreadDescription(thread_ext->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 = demon_w32_hash_from_string(name); if(name.size != 0 && name_hash != thread_ext->thread.last_name_hash) { DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_SetThreadName); e->process = demon_ent_handle_from_ptr(process); e->thread = demon_ent_handle_from_ptr(thread); e->string = push_str8_copy(arena, name); } thread_ext->thread.name_gather_time_us = os_now_microseconds(); thread_ext->thread.last_name_hash = name_hash; } } } } // TODO(allen): handle errors? if (!got_new_event) ? if (!good_state) ? // TODO(allen): per-Architecture // unset traps { DEMON_OS_Trap *trap = ctrls->traps; for (U64 i = 0; i < ctrls->trap_count; i += 1, trap += 1){ U8 og_byte = trap_swap_bytes[i]; if (og_byte != 0xCC){ demon_os_write_memory(trap->process, trap->address, &og_byte, 1); } } } // TODO(allen): per-Architecture // unset single step bit // the single step bit is automatically unset whenever we single step // but if *something else* happened, it will still be there ready to // confound us later; so here we're just being sure it's taken out. if (single_step_thread != 0){ // TODO(allen): possibly buggy switch (single_step_thread->arch){ case Architecture_x86: { REGS_RegBlockX86 regs = {0}; demon_os_read_regs_x86(single_step_thread, ®s); regs.eflags.u32 &= ~0x100; demon_os_write_regs_x86(single_step_thread, ®s); }break; case Architecture_x64: { REGS_RegBlockX64 regs = {0}; demon_os_read_regs_x64(single_step_thread, ®s); regs.rflags.u64 &= ~0x100; demon_os_write_regs_x64(single_step_thread, ®s); }break; } } scratch_end(scratch); } return(result); } internal void demon_os_halt(U64 code, U64 user_data){ if (demon_ent_root != 0 && demon_w32_halter_process == 0){ DEMON_Entity *process = demon_ent_root->first; if (process != 0){ DEMON_W32_Ext *process_ext = demon_w32_ext(process); demon_w32_halter_process = process; DEMON_W32_InjectedBreak injection = {code, user_data}; U64 data_injection_address = process_ext->proc.injection_address + DEMON_W32_INJECTED_CODE_SIZE; demon_os_write_struct(process, data_injection_address, &injection); demon_w32_halter_thread_id = demon_w32_inject_thread(process, process_ext->proc.injection_address); } } } //////////////////////////////// //~ rjf: @demon_os_hooks Target Process Launching/Attaching/Killing/Detaching/Halting internal U32 demon_os_launch_process(OS_LaunchOptions *options){ Temp scratch = scratch_begin(0, 0); // TODO(allen): maybe a good command line escaper/parser function pair? 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); } StringJoin join_params2 = {0}; join_params2.sep = str8_lit("\0"); join_params2.post = str8_lit("\0"); String8List all_opts = options->env; if(options->inherit_env != 0) { MemoryZeroStruct(&all_opts); for(String8Node *n = options->env.first; n != 0; n = n->next) { str8_list_push(scratch.arena, &all_opts, n->string); } for(String8Node *n = demon_w32_environment.first; n != 0; n = n->next) { str8_list_push(scratch.arena, &all_opts, n->string); } } String8 env = str8_list_join(scratch.arena, &all_opts, &join_params2); 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); U32 result = 0; //- 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); CloseHandle(process_info.hProcess); CloseHandle(process_info.hThread); } else{ CloseHandle(process_info.hProcess); CloseHandle(process_info.hThread); result = process_info.dwProcessId; demon_w32_new_process_pending = 1; } } 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 demon_os_attach_process(U32 pid){ B32 result = 0; if (DebugActiveProcess((DWORD)pid)){ result = 1; demon_w32_new_process_pending = 1; } return(result); } internal B32 demon_os_kill_process(DEMON_Entity *process, U32 exit_code){ B32 result = 0; DEMON_W32_Ext *ext = demon_w32_ext(process); if (TerminateProcess(ext->proc.handle, exit_code)){ result = 1; } return(result); } internal B32 demon_os_detach_process(DEMON_Entity *process){ B32 result = 0; // rjf: resume threads for(DEMON_Entity *child = process->first; child != 0; child = child->next) { if(child->kind == DEMON_EntityKind_Thread) { DEMON_W32_Ext *thread_ext = demon_w32_ext(child); DWORD resume_result = ResumeThread(thread_ext->thread.handle); } } // rjf: detach { DWORD pid = (DWORD)process->id; if(DebugActiveProcessStop(pid)) { result = 1; } } // rjf: push into list if(result != 0) { DEMON_EntityNode *n = push_array(demon_w32_detach_proc_arena, DEMON_EntityNode, 1); n->entity = process; SLLQueuePush(demon_w32_first_detached_proc, demon_w32_last_detached_proc, n); } return(result); } //////////////////////////////// //~ rjf: @demon_os_hooks Entity Functions //- rjf: cleanup internal void demon_os_entity_cleanup(DEMON_Entity *entity) { if (entity->kind == DEMON_EntityKind_Process){ DEMON_W32_Ext *ext = demon_w32_ext(entity); SLLStackPush(demon_w32_proc_ext_free, ext); } else if(entity->kind == DEMON_EntityKind_Module) { DEMON_W32_Ext *ext = demon_w32_ext(entity); CloseHandle(ext->module.handle); } } //- rjf: introspection internal String8 demon_os_full_path_from_module(Arena *arena, DEMON_Entity *module){ ProfBeginFunction(); Temp scratch = scratch_begin(&arena, 1); // object -> handle DEMON_W32_Ext *module_ext = (DEMON_W32_Ext*)module->ext; HANDLE handle = module_ext->module.handle; String16 path16 = {0}; String8 path8 = {0}; // handle -> full path if (handle != 0){ DWORD cap16 = GetFinalPathNameByHandleW(handle, 0, 0, VOLUME_NAME_DOS); U16 *buffer16 = push_array_no_zero(scratch.arena, U16, cap16); DWORD size16 = GetFinalPathNameByHandleW(handle, (WCHAR*)buffer16, cap16, VOLUME_NAME_DOS); path16 = str16(buffer16, size16); } // fallback (main module only): process -> full path if (path16.size == 0 && module_ext->module.is_main){ // process handle DEMON_Entity *process = module->parent; DEMON_W32_Ext *process_ext = (DEMON_W32_Ext*)process->ext; HANDLE process_handle = process_ext->proc.handle; 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); } } // fallback (any module - no gaurantee): address_of_name -> full path if (path16.size == 0 && module_ext->module.address_of_name_pointer != 0){ // process handle DEMON_Entity *process = module->parent; DEMON_W32_Ext *process_ext = (DEMON_W32_Ext*)process->ext; HANDLE process_handle = process_ext->proc.handle; // TODO(allen): address size independence U64 ptr_size = 8; U64 name_pointer = 0; if (demon_w32_read_memory(process_handle, &name_pointer, module_ext->module.address_of_name_pointer, ptr_size)){ if (name_pointer != 0){ if (module_ext->module.name_is_unicode){ path16 = demon_w32_read_memory_str16(scratch.arena, process_handle, name_pointer); } else{ path8 = demon_w32_read_memory_str(scratch.arena, process_handle, name_pointer); } } } } // finalize the result String8 result = {0}; if (path16.size > 0){ // 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; } // convert result result = str8_from_16(arena, path16); } else{ // 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; } // copy the result result = push_str8_copy(arena, path8); } scratch_end(scratch); ProfEnd(); return(result); } internal U64 demon_os_stack_base_vaddr_from_thread(DEMON_Entity *thread){ DEMON_Entity *process = thread->parent; DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); U64 tlb = thread_ext->thread.thread_local_base; U64 result = 0; switch (thread->arch){ case Architecture_x64: { U64 stack_base_addr = tlb + 0x8; demon_os_read_memory(process, &result, stack_base_addr, 8); }break; case Architecture_x86: { U64 stack_base_addr = tlb + 0x4; demon_os_read_memory(process, &result, stack_base_addr, 4); }break; } return(result); } internal U64 demon_os_tls_root_vaddr_from_thread(DEMON_Entity *thread){ DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); U64 result = thread_ext->thread.thread_local_base; switch (thread->arch){ case Architecture_x64: { result += 88; }break; case Architecture_x86: { result += 44; }break; } return(result); } //- rjf: target process memory allocation/protection internal U64 demon_os_reserve_memory(DEMON_Entity *process, U64 size){ DEMON_W32_Ext *ext = demon_w32_ext(process); void *ptr = VirtualAllocEx(ext->proc.handle, 0, size, MEM_RESERVE, PAGE_NOACCESS); U64 result = (U64)ptr; return(result); } internal void demon_os_set_memory_protect_flags(DEMON_Entity *process, U64 page_vaddr, U64 size, DEMON_MemoryProtectFlags flags){ DEMON_W32_Ext *ext = demon_w32_ext(process); DWORD w32_flags = demon_w32_win32_from_memory_protect_flags(flags); DWORD old_flags = 0; DWORD alloc_type = (flags == 0 ? MEM_DECOMMIT : MEM_COMMIT); VirtualAllocEx(ext->proc.handle, (void *)page_vaddr, size, alloc_type, w32_flags); (void)old_flags; } internal void demon_os_release_memory(DEMON_Entity *process, U64 vaddr, U64 size){ DEMON_W32_Ext *ext = demon_w32_ext(process); VirtualFreeEx(ext->proc.handle, (void *)vaddr, 0, MEM_RELEASE); } //- rjf: target process memory reading/writing internal U64 demon_os_read_memory(DEMON_Entity *process, void *dst, U64 src_address, U64 size){ DEMON_W32_Ext *process_ext = demon_w32_ext(process); HANDLE handle = process_ext->proc.handle; U64 result = demon_w32_read_memory(handle, dst, src_address, size); return(result); } internal B32 demon_os_write_memory(DEMON_Entity *process, U64 dst_address, void *src, U64 size){ DEMON_W32_Ext *process_ext = demon_w32_ext(process); HANDLE handle = process_ext->proc.handle; B32 result = demon_w32_write_memory(handle, dst_address, src, size); return(result); } //- rjf: thread registers reading/writing internal B32 demon_os_read_regs_x86(DEMON_Entity *thread, REGS_RegBlockX86 *dst){ B32 result = 0; // NOTE(allen): Get Thread Context WOW64_CONTEXT ctx = {0}; ctx.ContextFlags = DEMON_W32_CTX_X86_ALL; DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); HANDLE handle = thread_ext->thread.handle; if (Wow64GetThreadContext(handle, (WOW64_CONTEXT *)&ctx)){ result = 1; // NOTE(allen): Convert WOW64_CONTEXT -> REGS_RegBlockX86 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(allen): This bit is "supposed to always be 1" I guess. // TODO(allen): Not sure what this is all about but I haven't investigated it yet. // This might be totally not necessary or something. dst->eflags.u32 = ctx.EFlags | 0x2; XSAVE_FORMAT *fxsave = (XSAVE_FORMAT*)ctx.ExtendedRegisters; dst->fcw.u16 = fxsave->ControlWord; dst->fsw.u16 = fxsave->StatusWord; dst->ftw.u16 = demon_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)); } // read FS/GS base WOW64_LDT_ENTRY ldt = {0}; if (Wow64GetThreadSelectorEntry(handle, ctx.SegFs, &ldt)){ U32 base = (ldt.BaseLow) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.HighWord.Bytes.BaseHi << 24); dst->fsbase.u32 = base; } if (Wow64GetThreadSelectorEntry(handle, ctx.SegGs, &ldt)){ U32 base = (ldt.BaseLow) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.HighWord.Bytes.BaseHi << 24); dst->gsbase.u32 = base; } } return(result); } internal B32 demon_os_write_regs_x86(DEMON_Entity *thread, REGS_RegBlockX86 *src){ // NOTE(allen): Convert REGS_RegBlockX86 -> WOW64_CONTEXT WOW64_CONTEXT ctx = {0}; ctx.ContextFlags = DEMON_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; XSAVE_FORMAT *fxsave = (XSAVE_FORMAT*)ctx.ExtendedRegisters; fxsave->ControlWord = src->fcw.u16; fxsave->StatusWord = src->fsw.u16; fxsave->TagWord = demon_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)); } // set thread context DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); HANDLE handle = thread_ext->thread.handle; B32 result = 0; if (Wow64SetThreadContext(handle, &ctx)){ result = 1; } return(result); } internal B32 demon_os_read_regs_x64(DEMON_Entity *thread, REGS_RegBlockX64 *dst){ Temp scratch = scratch_begin(0, 0); // NOTE(allen): Check available features U32 feature_mask = GetEnabledXStateFeatures(); B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX); // NOTE(allen): Setup the context CONTEXT *ctx = 0; U32 ctx_flags = DEMON_W32_CTX_X64_ALL; if (avx_enabled){ ctx_flags |= DEMON_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; } } B32 avx_available = 0; if (ctx != 0){ // NOTE(allen): Finish Context Setup if (avx_enabled){ SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX); } // NOTE(allen): Determine what features are available on this particular ctx // TODO(allen): Experiment carefully with this nonsense. // Does avx_enabled = avx_available in all circumstances or not? DWORD64 xstate_flags = 0; if (GetXStateFeaturesMask(ctx, &xstate_flags)){ if (xstate_flags & XSTATE_MASK_AVX){ avx_available = 1; } } } // get thread context DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); HANDLE thread_handle = thread_ext->thread.handle; if (!GetThreadContext(thread_handle, ctx)){ ctx = 0; } B32 result = 0; if (ctx != 0){ result = 1; // NOTE(allen): Convert CONTEXT -> REGS_RegBlockX64 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(allen): This bit is "supposed to always be 1" I guess. // TODO(allen): Not sure what this is all about but I haven't investigated it yet. // This might be totally not necessary or something. dst->rflags.u64 = ctx->EFlags | 0x2; XSAVE_FORMAT *xsave = &ctx->FltSave; dst->fcw.u16 = xsave->ControlWord; dst->fsw.u16 = xsave->StatusWord; dst->ftw.u16 = demon_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(allen): Are we writing these out in the right order? Seems weird right? 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); return(result); } internal B32 demon_os_write_regs_x64(DEMON_Entity *thread, REGS_RegBlockX64 *src){ Temp scratch = scratch_begin(0, 0); // NOTE(allen): Check available features U32 feature_mask = GetEnabledXStateFeatures(); B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX); // NOTE(allen): Setup the context CONTEXT *ctx = 0; U32 ctx_flags = DEMON_W32_CTX_X64_ALL; if (avx_enabled){ ctx_flags |= DEMON_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; } } B32 avx_available = 0; if (ctx != 0){ // NOTE(allen): Finish Context Setup if (avx_enabled){ SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX); } // NOTE(allen): Determine what features are available on this particular ctx // TODO(allen): Experiment carefully with this nonsense. // Does avx_enabled = avx_available in all circumstances or not? DWORD64 xstate_flags = 0; if (GetXStateFeaturesMask(ctx, &xstate_flags)){ if (xstate_flags & XSTATE_MASK_AVX){ avx_available = 1; } } } B32 result = 0; if (ctx != 0){ // NOTE(allen): Convert REGS_RegBlockX64 -> CONTEXT 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; XSAVE_FORMAT *fxsave = &ctx->FltSave; fxsave->ControlWord = src->fcw.u16; fxsave->StatusWord = src->fsw.u16; fxsave->TagWord = demon_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]; } } //- set thread context DEMON_W32_Ext *thread_ext = demon_w32_ext(thread); HANDLE thread_handle = thread_ext->thread.handle; if (SetThreadContext(thread_handle, ctx)){ result = 1; } } scratch_end(scratch); return(result); } //////////////////////////////// //~ rjf: @demon_os_hooks Process Listing internal void demon_os_proc_iter_begin(DEMON_ProcessIter *iter){ MemoryZeroStruct(iter); iter->v[0] = (U64)CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); } internal B32 demon_os_proc_iter_next(Arena *arena, DEMON_ProcessIter *iter, DEMON_ProcessInfo *info_out){ // get the next process entry B32 result = 0; 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; } } // increment counter iter->v[1] += 1; // 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 demon_os_proc_iter_end(DEMON_ProcessIter *iter){ CloseHandle((HANDLE)iter->v[0]); MemoryZeroStruct(iter); }