diff --git a/project.4coder b/project.4coder index bed46ee1..497a4b4f 100644 --- a/project.4coder +++ b/project.4coder @@ -49,13 +49,13 @@ commands = // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg debug telemetry", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, // .f1 = { .win = "raddbg_stable --ipc kill_all && build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, - .f1 = { .win = "build raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [raddbg wsl] // .f1 = { .win = "wsl ./build.sh raddbg", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [scratch] - .f2 = { .win = "build radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f2 = { .win = "wsl ./build.sh radbin", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, //- rjf: [textperf] // .f1 = { .win = "raddbg_stable --ipc kill_all && build no_meta telemetry textperf && raddbg_stable --ipc bring_to_front && raddbg_stable --ipc run", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, diff --git a/src/arch/arch.h b/src/arch/arch.h index e9e945b9..824cfe7a 100644 --- a/src/arch/arch.h +++ b/src/arch/arch.h @@ -28,7 +28,8 @@ struct ARCH_Info global read_only Rng1U16 arch_reg_code_rng_nil = {0}; global read_only String8 arch_reg_code_name_nil = {0}; global read_only U8 arch_reg_code_u8_nil = 0; -global read_only ARCH_Info arch_info_nil = {0, 0, 0, 0, &arch_reg_code_rng_nil, &arch_reg_code_name_nil, &arch_reg_code_u8_nil, &arch_reg_code_u8_nil}; +global read_only B8 arch_reg_code_b8_nil = 0; +global read_only ARCH_Info arch_info_nil = {0, 0, 0, 0, &arch_reg_code_rng_nil, &arch_reg_code_name_nil, &arch_reg_code_u8_nil, &arch_reg_code_b8_nil}; //////////////////////////////// //~ rjf: Abstracted Architecture Functions diff --git a/src/demon/demon_inc.c b/src/demon/demon_inc.c index d733b612..771aa2e6 100644 --- a/src/demon/demon_inc.c +++ b/src/demon/demon_inc.c @@ -4,9 +4,9 @@ #include "demon/demon_core.c" #if OS_WINDOWS -# include "demon/win32/demon_core_win32.c" +# include "win32/demon/win32_demon.c" #elif OS_LINUX -# include "demon/linux/demon_core_linux.c" +# include "linux/demon/linux_demon.c" #else # error Demon layer backend not defined for this operating system. #endif diff --git a/src/demon/demon_inc.h b/src/demon/demon_inc.h index eec32998..b5e693f1 100644 --- a/src/demon/demon_inc.h +++ b/src/demon/demon_inc.h @@ -7,9 +7,9 @@ #include "demon/demon_core.h" #if OS_WINDOWS -# include "demon/win32/demon_core_win32.h" +# include "win32/demon/win32_demon.h" #elif OS_LINUX -# include "demon/linux/demon_core_linux.h" +# include "linux/demon/linux_demon.h" #else # error Demon layer backend not defined for this operating system. #endif diff --git a/src/demon/linux/demon_os_linux.c b/src/demon/linux/demon_os_linux.c deleted file mode 100644 index 65b4e639..00000000 --- a/src/demon/linux/demon_os_linux.c +++ /dev/null @@ -1,2106 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -// TODO(allen): run controls: ignore_previous_exception - -//////////////////////////////// -//~ allen: Elf Parsing Code - -#include "syms/syms_elf_inc.c" - -//////////////////////////////// -//~ rjf: Globals - -global B32 demon_lnx_already_has_halt_injection = false; -global U64 demon_lnx_halt_code = 0; -global U64 demon_lnx_halt_user_data = 0; - -global B32 demon_lnx_new_process_pending = false; - -global Arena *demon_lnx_event_arena = 0; -global DEMON_EventList demon_lnx_queued_events = {0}; - -global U32 demon_lnx_ptrace_options = (PTRACE_O_TRACEEXIT| - PTRACE_O_EXITKILL| - PTRACE_O_TRACEFORK| - PTRACE_O_TRACEVFORK| - PTRACE_O_TRACECLONE); - -//////////////////////////////// -//~ rjf: Helpers - -internal DEMON_LNX_ThreadExt* -demon_lnx_thread_ext(DEMON_Entity *entity){ - DEMON_LNX_ThreadExt *result = (DEMON_LNX_ThreadExt*)&entity->ext; - return(result); -} - -internal B32 -demon_lnx_attach_pid(Arena *arena, pid_t pid, DEMON_LNX_AttachNode **new_node){ - B32 result = false; - - int attach_result = ptrace(PTRACE_ATTACH, pid, 0, 0); - if (attach_result == -1){ - // TODO(allen): attach denied - } - else{ - // return a new attachment node as soon as the ptrace exists. we use these nodes - // for cleanup on failure *and* for initializing on success. either way we need - // to see all new attachments whether or not they fully initialized correctly. - DEMON_LNX_AttachNode *proc_attachment = push_array_no_zero(arena, DEMON_LNX_AttachNode, 1); - proc_attachment->next = 0; - proc_attachment->pid = pid; - *new_node = proc_attachment; - - int status = 0; - pid_t wait_id = waitpid(pid, &status, __WALL); - // NOTE(allen): if wait_id != pid we don't know what that means; study that case before - // deciding how error handling around it works. - if (wait_id == pid){ - int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(demon_lnx_ptrace_options)); - if (setoptions_result == -1){ - // TODO(allen): setup failed - } - else{ - result = true; - } - } - } - - return(result); -} - -internal String8 -demon_lnx_executable_path_from_pid(Arena *arena, pid_t pid){ - // get symbolic path - Temp scratch = scratch_begin(&arena, 1); - String8 exe_symbol_path = push_str8f(scratch.arena, "/proc/%d/exe", pid); - - // try to read the link for a bit - Temp restore_point = temp_begin(arena); - B32 got_final_result = false; - U8 *buffer = 0; - int size = 0; - S64 cap = PATH_MAX; - for (S64 r = 0; r < 4; cap *= 2, r += 1){ - temp_end(restore_point); - buffer = push_array_no_zero(arena, U8, cap); - size = readlink((char*)exe_symbol_path.str, (char*)buffer, cap); - if (size < cap){ - got_final_result = true; - break; - } - } - - // finalize result - String8 result = {0}; - if (!got_final_result || size == -1){ - temp_end(restore_point); - } - else{ - arena_pop(arena, (cap - size - 1)); - result = str8(buffer, size + 1); - } - - scratch_end(scratch); - return(result); -} - -internal int -demon_lnx_open_memory_fd_for_pid(pid_t pid){ - Temp scratch = scratch_begin(0, 0); - String8 memory_path = push_str8f(scratch.arena, "/proc/%i/mem", pid); - int result = open((char*)memory_path.str, O_RDWR|O_CLOEXEC); - scratch_end(scratch); - return(result); -} - -internal Arch -demon_lnx_arch_from_pid(pid_t pid){ - Temp scratch = scratch_begin(0, 0); - Arch result = Arch_Null; - - // exe path - String8 exe_path = demon_lnx_executable_path_from_pid(scratch.arena, pid); - - // handle to exe - int exe_fd = -1; - if (exe_path.size != 0){ - exe_fd = open((char*)exe_path.str, O_RDONLY|O_CLOEXEC); - } - - // elf identification - B32 is_elf = false; - U8 e_ident[SYMS_ElfIdentifier_NIDENT] = {0}; - if (exe_fd >= 0){ - if (pread(exe_fd, e_ident, sizeof(e_ident), 0) == sizeof(e_ident)){ - is_elf = (e_ident[SYMS_ElfIdentifier_MAG0] == 0x7f && - e_ident[SYMS_ElfIdentifier_MAG1] == 'E' && - e_ident[SYMS_ElfIdentifier_MAG2] == 'L' && - e_ident[SYMS_ElfIdentifier_MAG3] == 'F'); - } - } - - // elf class - U8 elf_class = 0; - if (is_elf){ - elf_class = e_ident[SYMS_ElfIdentifier_CLASS]; - } - - // exe header data - SYMS_ElfEhdr64 ehdr = {0}; - switch (elf_class){ - case 1: - { - SYMS_ElfEhdr32 ehdr32 = {0}; - if (pread(exe_fd, &ehdr32, sizeof(ehdr32), 0) == sizeof(ehdr32)){ - ehdr = syms_elf_ehdr64_from_ehdr32(ehdr32); - } - }break; - - case 2: - { - pread(exe_fd, &ehdr, sizeof(ehdr), 0); - }break; - } - - // determine machine type - switch (ehdr.e_machine){ - case SYMS_ElfMachineKind_386: - { - result = Arch_x86; - }break; - - case SYMS_ElfMachineKind_ARM: - { - result = Arch_arm32; - }break; - - case SYMS_ElfMachineKind_X86_64: - { - result = Arch_x64; - }break; - - case SYMS_ElfMachineKind_AARCH64: - { - result = Arch_arm64; - }break; - } - - scratch_end(scratch); - return(result); -} - -internal DEMON_LNX_ProcessAux -demon_lnx_aux_from_pid(pid_t pid, Arch arch){ - DEMON_LNX_ProcessAux result = {0}; - B32 addr_32bit = (arch == Arch_x86 || arch == Arch_arm32); - - // open aux data - Temp scratch = scratch_begin(0, 0); - String8 auxv_symbol_path = push_str8f(scratch.arena, "/proc/%d/auxv", pid); - int aux_fd = open((char*)auxv_symbol_path.str, O_RDONLY|O_CLOEXEC); - - // scan aux data - if (aux_fd >= 0){ - for (;;){ - result.filled = true; - - // read next aux - U64 type = 0; - U64 val = 0; - if (addr_32bit){ - SYMS_ElfAuxv32 aux; - if (read(aux_fd, &aux, sizeof(aux)) != sizeof(aux)){ - goto brkloop; - } - type = aux.a_type; - val = aux.a_val; - } - else{ - SYMS_ElfAuxv64 aux; - if (read(aux_fd, &aux, sizeof(aux)) != sizeof(aux)){ - goto brkloop; - } - type = aux.a_type; - val = aux.a_val; - } - - // place value in result - switch (type){ - default:break; - case SYMS_ElfAuxType_NULL: goto brkloop; break; - case SYMS_ElfAuxType_PHNUM: result.phnum = val; break; - case SYMS_ElfAuxType_PHENT: result.phent = val; break; - case SYMS_ElfAuxType_PHDR: result.phdr = val; break; - case SYMS_ElfAuxType_EXECFN: result.execfn = val; break; - } - } - brkloop:; - - close(aux_fd); - } - - scratch_end(scratch); - return(result); -} - -internal DEMON_LNX_PhdrInfo -demon_lnx_phdr_info_from_memory(int memory_fd, B32 is_32bit, U64 phvaddr, U64 phentsize, U64 phcount){ - DEMON_LNX_PhdrInfo result = {0}; - result.range.min = max_U64; - - // how much phdr will we read? - U64 phdr_size_expected = (is_32bit?sizeof(SYMS_ElfPhdr32):sizeof(SYMS_ElfPhdr64)); - U64 phdr_stride = (phentsize?phentsize:phdr_size_expected); - U64 phdr_read_size = ClampTop(phdr_stride, phdr_size_expected); - - // scan table - U64 va = phvaddr; - for (U64 i = 0; i < phcount; i += 1, va += phdr_stride){ - - // get type and range - SYMS_ElfPKind p_type = 0; - U64 p_vaddr = 0; - U64 p_memsz = 0; - - if (is_32bit){ - SYMS_ElfPhdr32 phdr32 = {0}; - demon_lnx_read_memory(memory_fd, &phdr32, va, phdr_read_size); - p_type = phdr32.p_type; - p_vaddr = phdr32.p_vaddr; - p_memsz = phdr32.p_memsz; - } - else{ - SYMS_ElfPhdr64 phdr64 = {0}; - demon_lnx_read_memory(memory_fd, &phdr64, va, phdr_read_size); - p_type = phdr64.p_type; - p_vaddr = phdr64.p_vaddr; - p_memsz = phdr64.p_memsz; - } - - // save useful info - switch (p_type){ - case SYMS_ElfPKind_Dynamic: - { - result.dynamic = p_vaddr; - }break; - case SYMS_ElfPKind_Load: - { - U64 min = p_vaddr; - U64 max = p_vaddr + p_memsz; - result.range.min = Min(result.range.min, min); - result.range.max = Max(result.range.max, max); - }break; - } - } - - return(result); -} - -internal DEMON_LNX_ModuleNode* -demon_lnx_module_list_from_process(Arena *arena, DEMON_Entity *process){ - Arch arch = (Arch)process->arch; - B32 is_32bit = (arch == Arch_x86 || arch == Arch_arm32); - int memory_fd = (int)process->ext_u64; - - // aux from pid - DEMON_LNX_ProcessAux aux = demon_lnx_aux_from_pid((pid_t)process->id, arch); - - // extract info from program headers - DEMON_LNX_PhdrInfo phdr_info = demon_lnx_phdr_info_from_memory(memory_fd, is_32bit, - aux.phdr, aux.phent, aux.phnum); - - // linkmap first from memory space & dyn address - U64 first_linkmap_va = 0; - if (phdr_info.dynamic != 0){ - U64 off = phdr_info.dynamic; - for (;;){ - SYMS_ElfDyn64 dyn = {0}; - if (is_32bit){ - SYMS_ElfDyn32 dyn32 = {0}; - demon_lnx_read_memory(memory_fd, &dyn32, off, sizeof(dyn32)); - dyn.tag = dyn32.tag; - dyn.val = dyn32.val; - off += sizeof(dyn32); - } - else{ - demon_lnx_read_memory(memory_fd, &dyn, off, sizeof(dyn)); - off += sizeof(dyn); - } - - if (dyn.tag == SYMS_ElfDynTag_NULL){ - break; - } - - if (dyn.tag == SYMS_ElfDynTag_PLTGOT){ - // True for x86 and x64 - // vas[0] virtual address of .dynamic - // vas[2] callback for resolving function address of relocation and if successful jumps to it. - // - // Code that sets up PLTGOT is in glibc/sysdeps/x86_64/dl_machine.h -> elf_machine_runtime_setup - U64 vas_off = dyn.val; - U64 vas[3] = {0}; - demon_lnx_read_memory(memory_fd, vas, vas_off, sizeof(vas)); - first_linkmap_va = vas[1]; - break; - } - } - } - - // setup output list - DEMON_LNX_ModuleNode *first = 0; - DEMON_LNX_ModuleNode *last = 0; - - // main module - { - DEMON_LNX_ModuleNode *node = push_array(arena, DEMON_LNX_ModuleNode, 1); - SLLQueuePush(first, last, node); - node->vaddr = phdr_info.range.min; - node->size = phdr_info.range.max - phdr_info.range.min; - node->name = aux.execfn; - } - - // iterate link maps - if (first_linkmap_va != 0){ - U64 linkmap_va = first_linkmap_va; - - for (;;){ - SYMS_ElfLinkMap64 linkmap = {0}; - if (is_32bit){ - // TOOD(nick): endian awarness - SYMS_ElfLinkMap32 linkmap32 = {0}; - demon_lnx_read_memory(memory_fd, &linkmap32, linkmap_va, sizeof(linkmap32)); - linkmap.base = linkmap32.base; - linkmap.name = linkmap32.name; - linkmap.ld = linkmap32.ld; - linkmap.next = linkmap32.next; - } - else{ - demon_lnx_read_memory(memory_fd, &linkmap, linkmap_va, sizeof(linkmap)); - } - - if (linkmap.base != 0){ - // find phdrs for this module - SYMS_U64 phvaddr = 0; - SYMS_U64 phentsize = 0; - SYMS_U64 phcount = 0; - - if (is_32bit){ - SYMS_ElfEhdr32 ehdr = {0}; - demon_lnx_read_memory(memory_fd, &ehdr, linkmap.base, sizeof(ehdr)); - phvaddr = ehdr.e_phoff + linkmap.base; - phentsize = ehdr.e_phentsize; - phcount = ehdr.e_phnum; - } - else{ - SYMS_ElfEhdr64 ehdr = {0}; - demon_lnx_read_memory(memory_fd, &ehdr, linkmap.base, sizeof(ehdr)); - phvaddr = ehdr.e_phoff + linkmap.base; - phentsize = ehdr.e_phentsize; - phcount = ehdr.e_phnum; - } - - // extract info from phdrs - DEMON_LNX_PhdrInfo module_phdr_info = demon_lnx_phdr_info_from_memory(memory_fd, is_32bit, - phvaddr, phentsize, phcount); - - // save module node - DEMON_LNX_ModuleNode *node = push_array(arena, DEMON_LNX_ModuleNode, 1); - SLLQueuePush(first, last, node); - node->vaddr = linkmap.base; - node->size = module_phdr_info.range.max - module_phdr_info.range.min; - node->name = linkmap.name; - } - - linkmap_va = linkmap.next; - if (linkmap_va == 0){ - break; - } - } - } - - return(first); -} - -internal U64 -demon_lnx_read_memory(int memory_fd, void *dst, U64 src, U64 size){ - U64 bytes_read = 0; - U8 *ptr = (U8*)dst; - U8 *opl = ptr + size; - U64 cursor = src; - for (;ptr < opl;){ - size_t to_read = (size_t)(opl - ptr); - ssize_t actual_read = pread(memory_fd, ptr, to_read, cursor); - if (actual_read == -1){ - break; - } - ptr += actual_read; - cursor += actual_read; - bytes_read += actual_read; - } - return(bytes_read); -} - -internal B32 -demon_lnx_write_memory(int memory_fd, U64 dst, void *src, U64 size){ - B32 result = true; - U8 *ptr = (U8*)src; - U8 *opl = ptr + size; - U64 cursor = dst; - for (;ptr < opl;){ - size_t to_write = (size_t)(opl - ptr); - ssize_t actual_write = pwrite(memory_fd, ptr, to_write, cursor); - if (actual_write == -1){ - result = false; - break; - } - ptr += actual_write; - cursor += actual_write; - } - return(result); -} - -internal String8 -demon_lnx_read_memory_str(Arena *arena, int memory_fd, U64 address){ - // TODO(allen): this could be done better with a demon_lnx_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_lnx_read_memory(memory_fd, 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 void -demon_lnx_regs_x64_from_usr_regs_x64(SYMS_RegX64 *dst, DEMON_LNX_UserRegsX64 *src){ - dst->rax.u64 = src->rax; - dst->rcx.u64 = src->rcx; - dst->rdx.u64 = src->rdx; - dst->rbx.u64 = src->rbx; - dst->rsp.u64 = src->rsp; - dst->rbp.u64 = src->rbp; - dst->rsi.u64 = src->rsi; - dst->rdi.u64 = src->rdi; - dst->r8.u64 = src->r8; - dst->r9.u64 = src->r9; - dst->r10.u64 = src->r10; - dst->r11.u64 = src->r11; - dst->r12.u64 = src->r12; - dst->r13.u64 = src->r13; - dst->r14.u64 = src->r14; - dst->r15.u64 = src->r15; - dst->cs.u16 = src->cs; - dst->ds.u16 = src->ds; - dst->es.u16 = src->es; - dst->fs.u16 = src->fs; - dst->gs.u16 = src->gs; - dst->ss.u16 = src->ss; - dst->fsbase.u64 = src->fsbase; - dst->gsbase.u64 = src->gsbase; - dst->rip.u64 = src->rip; - dst->rflags.u64 = src->rflags; -} - -internal void -demon_lnx_usr_regs_x64_from_regs_x64(DEMON_LNX_UserRegsX64 *dst, SYMS_RegX64 *src){ - dst->rax = src->rax.u64; - dst->rcx = src->rcx.u64; - dst->rdx = src->rdx.u64; - dst->rbx = src->rbx.u64; - dst->rsp = src->rsp.u64; - dst->rbp = src->rbp.u64; - dst->rsi = src->rsi.u64; - dst->rdi = src->rdi.u64; - dst->r8 = src->r8.u64; - dst->r9 = src->r9.u64; - dst->r10 = src->r10.u64; - dst->r11 = src->r11.u64; - dst->r12 = src->r12.u64; - dst->r13 = src->r13.u64; - dst->r14 = src->r14.u64; - dst->r15 = src->r15.u64; - dst->cs = src->cs.u16; - dst->ds = src->ds.u16; - dst->es = src->es.u16; - dst->fs = src->fs.u16; - dst->gs = src->gs.u16; - dst->ss = src->ss.u16; - dst->fsbase = src->fsbase.u64; - dst->gsbase = src->gsbase.u64; - dst->rip = src->rip.u64; - dst->rflags = src->rflags.u64; -} - -//////////////////////////////// - -internal String8 -demon_lnx_read_int_string(Arena *arena, int fd, int radix){ - String8 integer = str8(0,0); - - int to_read = 0; - int to_seek = 0; - for (;;){ - char b = 0; - if (read(fd, &b, sizeof(b)) == 0){ - break; - } - to_seek += 1; - if ( ! char_is_digit(b, radix)){ - break; - } - to_read += 1; - } - - if (lseek(fd, -to_seek, SEEK_CUR) != -1) { - char *buf = push_array_no_zero(arena, char, to_read + 1); - read(fd, buf, to_read); - buf[to_read] = '\0'; - integer = str8((U8*)buf, (U64)to_read); - } - - return(integer); -} - -internal U64 -demon_lnx_read_u64(int fd, int radix){ - Temp scratch = scratch_begin(0, 0); - String8 integer = demon_lnx_read_int_string(scratch.arena, fd, radix); - U64 result = u64_from_str8(integer, radix); - scratch_end(scratch); - return(result); -} - -internal S64 -demon_lnx_read_s64(int fd, int radix){ - Temp scratch = scratch_begin(0, 0); - String8 integer = demon_lnx_read_int_string(scratch.arena, fd, radix); - S64 result = s64_from_str8(integer, radix); - scratch_end(scratch); - return(result); -} - -internal B32 -demon_lnx_read_expect(int fd, char expect){ - char got = 0; - read(fd, &got, sizeof(got)); - B32 result = (got == expect); - if (!result){ - lseek(fd, -1, SEEK_CUR); - } - return(result); -} - -internal int -demon_lnx_read_whitespace(int fd){ - int whitespace_size = 0; - for (;;){ - if (!demon_lnx_read_expect(fd, ' ')){ - if (!demon_lnx_read_expect(fd, '\t')){ - break; - } - } - whitespace_size += 1; - } - return whitespace_size; -} - -internal String8 -demon_lnx_read_string(Arena *arena, int fd){ - String8 result = str8(0,0); - - int to_read = 0; - int to_seek = 0; - for (;;){ - char b = 0; - if (read(fd, &b, sizeof(b)) == 0) { - break; - } - to_seek += 1; - if (b == '\0' || b == '\n'){ - break; - } - to_read += 1; - } - - if (to_seek > 0 && lseek(fd, -to_seek, SEEK_CUR) != -1){ - char *buf = push_array_no_zero(arena, char, to_read + 1); - read(fd, buf, to_read); - buf[to_read] = '\0'; - result = str8((U8*)buf, to_read); - } - - return(result); -} - -internal int -demon_lnx_open_maps(pid_t pid){ - Temp scratch = scratch_begin(0, 0); - String8 path = push_str8f(scratch.arena, "/proc/%d/maps", pid); - int maps = open((char*)path.str, O_RDONLY|O_CLOEXEC); - scratch_end(scratch); - return(maps); -} - -internal B32 -demon_lnx_next_map(Arena *arena, int maps, DEMON_LNX_MapsEntry *entry_out){ - B32 is_parsed = false; - MemoryZeroStruct(entry_out); - do{ - U64 address_lo = 0; - U64 address_hi = 0; - DEMON_LNX_PermFlags perms = 0; - U64 offset = 0; - U64 dev_major = 0; - U64 dev_minor = 0; - U64 inode = 0; - String8 pathname = str8(0,0); - - // address range - address_lo = demon_lnx_read_u64(maps, 16); - if (!demon_lnx_read_expect(maps, '-')){ - break; - } - address_hi = demon_lnx_read_u64(maps, 16); - if (demon_lnx_read_whitespace(maps) == 0){ - break; - } - - // permission flags - char b; - if (read(maps, &b, sizeof(b)) == 0){ - break; - } - if (b=='r'){ - perms |= DEMON_LNX_PermFlags_Read; - } - if (read(maps, &b, sizeof(b)) == 0){ - break; - } - if (b=='w'){ - perms |= DEMON_LNX_PermFlags_Write; - } - if (read(maps, &b, sizeof(b)) == 0){ - break; - } - if (b=='x'){ - perms |= DEMON_LNX_PermFlags_Exec; - } - if (read(maps, &b, sizeof(b)) == 0){ - break; - } - if (b == 'p'){ - perms |= DEMON_LNX_PermFlags_Private; - } - if (demon_lnx_read_whitespace(maps) == 0){ - break; - } - - // offset - offset = demon_lnx_read_u64(maps, 16); - if (demon_lnx_read_whitespace(maps) == 0){ - break; - } - - // dev - dev_major = demon_lnx_read_u64(maps, 10); - if (!demon_lnx_read_expect(maps, ':')){ - break; - } - dev_minor = demon_lnx_read_u64(maps, 10); - if (demon_lnx_read_whitespace(maps) == 0){ - break; - } - - // inode - inode = demon_lnx_read_u64(maps, 10); - if (demon_lnx_read_whitespace(maps) == 10){ - break; - } - - // pathname - pathname = demon_lnx_read_string(arena, maps); - - // emit entry if en - b = 0; - read(maps, &b, sizeof(b)); - if (b != '\n' && b != '\0') { - break; - } - - // fill result - entry_out->address_lo = address_lo; - entry_out->address_hi = address_hi; - entry_out->perms = perms; - entry_out->offset = offset; - entry_out->dev_major = (U32)dev_major; - entry_out->dev_minor = (U32)dev_minor; - entry_out->inode = inode; - entry_out->pathname = pathname; - entry_out->type = DEMON_LNX_MapsEntryType_Null; - entry_out->stack_tid = 0; - - if (str8_match(pathname, str8_lit("/"), StringMatchFlag_RightSideSloppy)){ - entry_out->type = DEMON_LNX_MapsEntryType_Path; - } else if (str8_match(pathname, str8_lit("[heap]"), 0)){ - entry_out->type = DEMON_LNX_MapsEntryType_Heap; - } else if (str8_match(pathname, str8_lit("[stack]"), 0)){ - entry_out->type = DEMON_LNX_MapsEntryType_Stack; - } else if (str8_match(pathname, str8_lit("[stack:"), StringMatchFlag_RightSideSloppy)){ - entry_out->type = DEMON_LNX_MapsEntryType_Stack; - String8 tid = str8_substr(pathname, r1u64(7, pathname.size - 8)); - entry_out->stack_tid = (pid_t)u64_from_str8(tid, 10); - } - - is_parsed = true; - }while(0); - return(is_parsed); -} - -//////////////////////////////// -//~ rjf: @demon_os_hooks Main Layer Initialization - -internal void -demon_os_init(void){ - demon_lnx_event_arena = arena_alloc(); -} - -//////////////////////////////// -//~ rjf: @demon_os_hooks Running/Halting - -internal DEMON_EventList -demon_os_run(Arena *arena, DEMON_OS_RunCtrls *controls){ - DEMON_EventList result = {0}; - - if (demon_ent_root == 0){ - demon_push_event(arena, &result, DEMON_EventKind_NotInitialized); - } - else if (demon_ent_root->first == 0 && !demon_lnx_new_process_pending){ - demon_push_event(arena, &result, DEMON_EventKind_NotAttached); - } - else{ - Temp scratch = scratch_begin(&arena, 1); - - // use queued events if there are any - if (demon_lnx_queued_events.first != 0){ - // copy event queue - for (DEMON_Event *node = demon_lnx_queued_events.first; - node != 0; - node = node->next){ - DEMON_Event *copy = push_array_no_zero(arena, DEMON_Event, 1); - MemoryCopyStruct(copy, node); - SLLQueuePush(result.first, result.last, copy); - } - result.count = demon_lnx_queued_events.count; - - // zero stored queue - MemoryZeroStruct(&demon_lnx_queued_events); - arena_clear(demon_lnx_event_arena); - } - - // get the single step thread (if any) - DEMON_Entity *single_step_thread = controls->single_step_thread; - - // do setup - B32 did_setup = false; - U8 *trap_swap_bytes = 0; - - if (result.first == 0){ - // TODO(allen): per-Arch implementation of single steps - // set single step bit - if (single_step_thread != 0){ - switch (single_step_thread->arch){ - case Arch_x86: - { - // TODO(allen): possibly buggy - SYMS_RegX86 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 Arch_x64: - { - // TODO(allen): possibly buggy - SYMS_RegX64 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-Arch implementation of traps - trap_swap_bytes = push_array_no_zero(scratch.arena, U8, controls->trap_count); - - { - DEMON_OS_Trap *trap = controls->traps; - for (U64 i = 0; i < controls->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; - } - } - } - - did_setup = true; - } - - // do run - B32 did_run = false; - if (did_setup){ - // continue non-frozen threads - DEMON_LNX_EntityNode *resume_threads = 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 = false; - if (controls->run_entities_are_processes){ - for (U64 i = 0; i < controls->run_entity_count; i += 1){ - if (controls->run_entities[i] == process){ - process_is_frozen = true; - 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 = false; - - if (controls->single_step_thread != 0 && - controls->single_step_thread != thread){ - is_frozen = true; - } - else{ - - if (controls->run_entities_are_processes){ - is_frozen = process_is_frozen; - } - else{ - for (U64 i = 0; i < controls->run_entity_count; i += 1){ - if (controls->run_entities[i] == thread){ - is_frozen = true; - break; - } - } - } - - if (controls->run_entities_are_unfrozen){ - is_frozen = !is_frozen; - } - } - - // continue if not frozen - if (!is_frozen){ - errno = 0; - ptrace(PTRACE_CONT, (pid_t)thread->id, 0, 0); - DEMON_LNX_EntityNode *thread_node = push_array_no_zero(scratch.arena, DEMON_LNX_EntityNode, 1); - SLLStackPush(resume_threads, thread_node); - thread_node->entity = thread; - } - } - } - } - } - - // get next stop - wait_for_stop: - B32 did_dummy_stop = false; - int status = 0; - pid_t wait_id = waitpid(-1, &status, __WALL); - - // increment demon time - demon_time += 1; - - // handle devent - DEMON_Entity *thread = demon_ent_map_entity_from_id(DEMON_EntityKind_Thread, wait_id); - if (thread == 0){ - if (wait_id >= 0){ - // TODO(allen): this isn't a great situation! From what I can tell there's no - // options that I am super happy with for going from unknown tid -> pid. - // We can parse it out of /proc//status; but I don't want to do that until - // I'm forced to, because it seems like this shouldn't happen if the ptrace - // API works correctly and we don't have any bugs in our demon entity system. - } - } - else{ - B32 thread_exit = false; - U64 exit_code = 0; - - DEMON_Entity *process = thread->parent; - // NOTE(allen): hitting this assert should never ever be possible, if our entities - // are wired up correctly. it doesn't matter what ptrace or waitpid are doing. - Assert(process != 0); - - // read register info - U64 instruction_pointer = 0; - union{ SYMS_RegX86 x86; SYMS_RegX64 x64; } regs = {0}; - - switch (thread->arch){ - case Arch_x86: - { - demon_os_read_regs_x86(thread, ®s.x86); - instruction_pointer = regs.x86.eip.u32; - }break; - - case Arch_x64: - { - demon_os_read_regs_x64(thread, ®s.x64); - instruction_pointer = regs.x64.rip.u64; - }break; - } - - // check stop status - if (WIFEXITED(status)){ - thread_exit = true; - } - if (WIFSIGNALED(status)){ - exit_code = WTERMSIG(status); - thread_exit = true; - } - - // extra event list - DEMON_EventList stop_events = {0}; - - if (WIFSTOPPED(status)){ - switch (WSTOPSIG(status)){ - case SIGTRAP: - { - switch (status >> 8){ - case (SIGTRAP | (PTRACE_EVENT_EXIT << 8)): - { - // TODO(allen): (not sure actually, study this part) - thread_exit = true; - }break; - - case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)): - { - // new thread coming - unsigned long new_tid = 0; - int get_message_result = ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid); - if (get_message_result == -1){ - // TODO(allen): this isn't right, time to give up on getting this process. - // this will likely lead to getting unrecognized wait_id s later. So we need - // this stuff in the log to make sense of it still. - } - else{ - // thread entity - DEMON_Entity *new_thread = demon_ent_new(process, DEMON_EntityKind_Thread, new_tid); - demon_thread_count += 1; - DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(new_thread); - thread_ext->expecting_dummy_sigstop = true; - - // thread event - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(new_thread); - } - }break; - - case (SIGTRAP | (PTRACE_EVENT_FORK << 8)): - case (SIGTRAP | (PTRACE_EVENT_VFORK << 8)): - { - // new process coming - unsigned long new_pid = 0; - int get_message_result = ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_pid); - if (get_message_result == -1){ - // TODO(allen): this isn't right, time to give up on getting this process. - // this will likely lead to getting unrecognized wait_id s later. So we need - // this stuff in the log to make sense of it still. - } - else{ - Arch arch = demon_lnx_arch_from_pid(new_pid); - - // process entity - DEMON_Entity *new_process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, new_pid); - new_process->arch = arch; - new_process->ext_u64 = demon_lnx_open_memory_fd_for_pid(new_pid); - - demon_lnx_new_process_pending = false; - - // thread entity - DEMON_Entity *new_thread = demon_ent_new(new_process, DEMON_EntityKind_Thread, new_pid); - demon_thread_count += 1; - DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(new_thread); - thread_ext->expecting_dummy_sigstop = true; - - // process event - { - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateProcess); - e->process = demon_ent_handle_from_ptr(new_process); - } - - // thread event - { - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateThread); - e->process = demon_ent_handle_from_ptr(new_process); - e->thread = demon_ent_handle_from_ptr(new_thread); - } - } - }break; - - default: - { - // check single step - DEMON_EventKind e_kind = DEMON_EventKind_Trap; - if (thread == single_step_thread){ - e_kind = DEMON_EventKind_SingleStep; - } - - // check bp - if (e_kind == DEMON_EventKind_Trap){ - DEMON_OS_Trap *trap = controls->traps; - for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ - if (trap->process == process && trap->address == instruction_pointer - 1){ - e_kind = DEMON_EventKind_Breakpoint; - break; - } - } - } - - // adjust ip after breakpoint - if (e_kind == DEMON_EventKind_Breakpoint){ - // TODO(allen): possibly buggy - switch (thread->arch){ - case Arch_x86: - { - instruction_pointer -= 1; - regs.x86.eip.u32 = instruction_pointer; - demon_os_write_regs_x86(thread, ®s.x86); - }break; - - case Arch_x64: - { - instruction_pointer -= 1; - regs.x64.rip.u64 = instruction_pointer; - demon_os_write_regs_x64(thread, ®s.x64); - }break; - } - } - - // event - DEMON_Event *e = demon_push_event(arena, &stop_events, e_kind); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - e->instruction_pointer = instruction_pointer; - }break; - } - }break; - - case SIGSTOP: - { - // TODO(allen): we need to figure out how we want to tell apart: - // SIGSTOP All-Stop, SIGSTOP Halt, SIGSTOP "User" - // what we're doing right now == big-time race conditions - - DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(thread); - - if (thread_ext->expecting_dummy_sigstop){ - thread_ext->expecting_dummy_sigstop = false; - did_dummy_stop = true; - } - else if (demon_lnx_already_has_halt_injection){ - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_Halt); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - e->instruction_pointer = instruction_pointer; - } - else{ - // TODO(allen): a signal we don't want to mess with (except to record that it happened maybe) - // we should "hand it back" - } - }break; - - default: - { -#if 0 - // these are a little special. the program cannot continue after these - // unless the user first does something to change the state (move the IP, change a variable, w/e) - case SIGABRT:case SIGFPE:case SIGSEGV: -#endif - - // event - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_Exception); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - e->instruction_pointer = instruction_pointer; - e->signo = WSTOPSIG(status); - }break; - } - } - - // entity cleanup - if (thread_exit){ - if (thread->id == process->id){ - // 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); - } - } - - // exit event - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_ExitProcess); - e->process = demon_ent_handle_from_ptr(process); - e->code = exit_code; - - // free entity - demon_ent_release_root_and_children(process); - } - else{ - // exit event - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_ExitThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - e->code = exit_code; - - // free entity - demon_ent_release_root_and_children(thread); - } - } - - // update all module lists (for each process ...) - DEMON_EventList module_change_events = {0}; - - for (DEMON_Entity *proc_node = demon_ent_root->first; - proc_node != 0; - proc_node = proc_node->next){ - DEMON_LNX_ModuleNode *first_module = demon_lnx_module_list_from_process(scratch.arena, proc_node); - - DEMON_LNX_EntityNode *first_unloaded = 0; - DEMON_LNX_EntityNode *last_unloaded = 0; - - // compute the delta (mark known modules, save list of unloaded modules) - for (DEMON_Entity *entity = proc_node->first; - entity != 0; - entity = entity->next){ - if (entity->kind == DEMON_EntityKind_Module){ - U64 base = entity->id; - U64 name = entity->ext_u64; - B32 still_exists = false; - for (DEMON_LNX_ModuleNode *module_node = first_module; - module_node != 0; - module_node = module_node->next){ - if (module_node->vaddr == base && module_node->name == name){ - module_node->already_known = true; - still_exists = true; - break; - } - } - if (!still_exists){ - DEMON_LNX_EntityNode *node = push_array_no_zero(scratch.arena, DEMON_LNX_EntityNode, 1); - SLLQueuePush(first_unloaded, last_unloaded, node); - node->entity = entity; - } - } - } - - // handle unloads - for (DEMON_LNX_EntityNode *unloaded_node = first_unloaded; - unloaded_node != 0; - unloaded_node = unloaded_node->next){ - DEMON_Entity *module = unloaded_node->entity; - - // event - { - DEMON_Event *e = demon_push_event(arena, &module_change_events, DEMON_EventKind_UnloadModule); - e->process = demon_ent_handle_from_ptr(proc_node); - e->module = demon_ent_handle_from_ptr(module); - } - - // free entity - demon_ent_release_root_and_children(module); - } - - // handle loads - for (DEMON_LNX_ModuleNode *module_node = first_module; - module_node != 0; - module_node = module_node->next){ - if (!module_node->already_known){ - // entity - DEMON_Entity *module = demon_ent_new(proc_node, DEMON_EntityKind_Module, module_node->vaddr); - demon_module_count += 1; - module->ext_u64 = module_node->name; - - // event - { - DEMON_Event *e = demon_push_event(arena, &module_change_events, DEMON_EventKind_LoadModule); - e->process = demon_ent_handle_from_ptr(proc_node); - e->module = demon_ent_handle_from_ptr(module); - e->address = module_node->vaddr; - e->size = module_node->size; - } - } - } - } - - // concat the events list (with module changes first) - result.count = module_change_events.count + stop_events.count; - result.first = module_change_events.first; - result.last = module_change_events.last; - if (stop_events.first != 0){ - if (result.first != 0){ - result.last->next = stop_events.first; - result.last = stop_events.last; - } - else{ - result.first = stop_events.first; - result.last = stop_events.last; - } - } - } - - // do we have a reason to keep going? - B32 skip_this_stop = false; - if (did_dummy_stop && result.count == 0){ - skip_this_stop = true; - } - - // ignore this stop, resume and wait again - if (skip_this_stop){ - if (wait_id != 0){ - ptrace(PTRACE_CONT, (pid_t)wait_id, 0, 0); - } - goto wait_for_stop; - } - - // stop all running threads - for (DEMON_LNX_EntityNode *node = resume_threads; - node != 0; - node = node->next){ - DEMON_Entity *thread = node->entity; - pid_t thread_id = (pid_t)thread->id; - if (thread_id != wait_id){ - union sigval sv = {0}; - sigqueue(thread_id, SIGSTOP, sv); - - DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(thread); - thread_ext->expecting_dummy_sigstop = true; - } - } - - did_run = true; - } - - // cleanup - if (did_run){ - // TODO(allen): per-Arch - // unset traps - { - DEMON_OS_Trap *trap = controls->traps; - for (U64 i = 0; i < controls->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-Arch - // 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 Arch_x86: - { - SYMS_RegX86 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 Arch_x64: - { - SYMS_RegX64 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_lnx_already_has_halt_injection){ - DEMON_Entity *process = demon_ent_root->first; - if (process != 0){ - demon_lnx_already_has_halt_injection = true; - demon_lnx_halt_code = code; - demon_lnx_halt_user_data = user_data; - union sigval sv = {0}; - if (sigqueue(process->id, SIGSTOP, sv) == -1){ - demon_lnx_already_has_halt_injection = false; - } - } - } -} - -// NOTE(allen): siginfo hint from old code: -#if 0 -{ - switch (siginfo.si_code){ - // SI_KERNEL (hit int3; 0xCC) - case 0x80: - { - // TODO(allen): breakpoint event - }break; - - // TRAP_UNK, TRAP_HWBKPT, TRAP_BRKPT, TRAP_TRACE - case 0x5: case 0x4: case 0x1: case 0x2: - { - // TODO(allen): breakpoint event (?) - }break; - - case 0x3: case 0x0: - { - // TODO(allen): do nothing I guess? - }break; - } -} -#endif - -//////////////////////////////// -//~ rjf: @demon_os_hooks Target Process Launching/Attaching/Killing/Detaching/Halting - -internal U32 -demon_os_launch_process(OS_LaunchOptions *options){ - U32 result = 0; - Temp scratch = scratch_begin(0, 0); - - // arrange options - char *binary = 0; - char **args = 0; - if (options->cmd_line.node_count > 0){ - args = push_array_no_zero(scratch.arena, char*, options->cmd_line.node_count + 1); - char **arg_ptr = args; - for (String8Node *node = options->cmd_line.first; - node != 0; - node = node->next, arg_ptr += 1){ - String8 string = push_str8_copy(scratch.arena, node->string); - *arg_ptr = (char*)string.str; - } - *arg_ptr = 0; - binary = args[0]; - } - - char *path = 0; - { - String8 string = push_str8_copy(scratch.arena, options->path); - path = (char*)string.str; - } - - char **env = 0; - if (options->env.node_count > 0){ - env = push_array_no_zero(scratch.arena, char*, options->env.node_count + 1); - char **env_ptr = env; - for (String8Node *node = options->env.first; - node != 0; - node = node->next, env_ptr += 1){ - String8 string = push_str8_copy(scratch.arena, node->string); - *env_ptr = (char*)string.str; - } - *env_ptr = 0; - } - - // fork - if (binary != 0){ - pid_t pid = fork(); - if (pid == -1){ - // TODO(allen): fork error - } - else if (pid == 0){ - // NOTE(allen): child process - int ptrace_result = ptrace(PTRACE_TRACEME, 0, 0, 0); - if (ptrace_result != -1){ - int chdir_result = chdir(path); - if (chdir_result != -1){ - execve(binary, args, env); - } - } - // failed to init fully; abort so the parent can clean up the child - abort(); - } - else{ - // NOTE(allen): parent process - - // wait for child - int status = 0; - pid_t wait_id = waitpid(pid, &status, __WALL); - - // determine child launch status - enum{ - LaunchCode_Null, - LaunchCode_FailBeforePtrace, - LaunchCode_FailAfterPtrace, - LaunchCode_Success, - }; - U32 launch_result = LaunchCode_Null; - // NOTE(allen): if wait_id != pid we don't know what that means; study that case before - // deciding how error handling around it works. - if (wait_id == pid){ - if (WIFSTOPPED(status)){ - if (WSTOPSIG(status) == SIGTRAP){ - launch_result = LaunchCode_Success; - } - else{ - launch_result = LaunchCode_FailAfterPtrace; - } - } - else{ - launch_result = LaunchCode_FailBeforePtrace; - } - } - - // handle launch result - switch (launch_result){ - default: - { - // TODO(allen): error that we do not understand - }break; - - case LaunchCode_FailBeforePtrace: - { - // TODO(allen): child ptrace init failed - }break; - - case LaunchCode_FailAfterPtrace: - { - // need to specifically pull the exit status out of the child - // or it will sit around as a zombie forever since it is ptraced. - B32 cleanup_good = false; - int detach_result = ptrace(PTRACE_DETACH, pid, 0, (void*)SIGCONT); - if (detach_result != -1){ - int status_cleanup = 0; - pid_t wait_id_cleanup = waitpid(pid, &status_cleanup, __WALL); - if (wait_id_cleanup == pid){ - cleanup_good = true; - } - } - if (cleanup_good){ - // TODO(allen): child init failed - } - else{ - // TODO(allen): child init failed; something went wrong and a process may have leaked - } - }break; - - case LaunchCode_Success: - { - int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(demon_lnx_ptrace_options)); - if (setoptions_result == -1){ - // TODO(allen): ptrace setup failed; need to kill the child and clean it up - } - else{ - result = pid; - - Arch arch = demon_lnx_arch_from_pid(pid); - - // process entity - DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, pid); - demon_proc_count += 1; - process->arch = arch; - process->ext_u64 = demon_lnx_open_memory_fd_for_pid(pid); - - // thread entity - DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, pid); - demon_thread_count += 1; - - // process event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_CreateProcess); - e->process = demon_ent_handle_from_ptr(process); - } - - // thread event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_CreateThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - } - - // get module list - DEMON_LNX_ModuleNode *module_list = demon_lnx_module_list_from_process(scratch.arena, process); - - // for each module ... - for (DEMON_LNX_ModuleNode *node = module_list; - node != 0; - node = node->next){ - // module entity - DEMON_Entity *module = demon_ent_new(process, DEMON_EntityKind_Module, node->vaddr); - demon_module_count += 1; - module->ext_u64 = node->name; - - // event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_LoadModule); - e->process = demon_ent_handle_from_ptr(process); - e->module = demon_ent_handle_from_ptr(module); - e->address = node->vaddr; - e->size = node->size; - } - } - - // handshake event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_HandshakeComplete); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - } - } - }break; - } - } - } - - scratch_end(scratch); - return(result); -} - -internal B32 -demon_os_attach_process(U32 pid){ - B32 result = false; - - Temp scratch = scratch_begin(0, 0); - DEMON_LNX_AttachNode *attachments = 0; - DEMON_LNX_AttachNode *the_process = 0; - - // TODO(allen): double check that this logic only lets us - // "attach" when pid is the id of the main thread of a process. - - // attach this process - B32 attached_proc = false; - if (kill(pid, 0) == -1){ - // TODO(allen): process does not exist - } - else{ - attached_proc = demon_lnx_attach_pid(scratch.arena, pid, &the_process); - if (the_process != 0){ - SLLStackPush(attachments, the_process); - } - } - - // open thread list - if (attached_proc){ - String8 threads_path = push_str8f(scratch.arena, "/proc/%d/task", pid); - DIR *proc_dir = opendir((char*)threads_path.str); - if (proc_dir == 0){ - // TODO(allen): could not read proc threads somehow; no good! - } - else{ - - // attach all threads - B32 attached_all_threads = true; - for (;;){ - struct dirent *entry = readdir(proc_dir); - if (entry == 0){ - break; - } - - String8 name = str8_cstring(entry->d_name); - if (str8_is_integer(name, 10)){ - pid_t tid = u64_from_str8(name, 10); - if (tid != pid){ - DEMON_LNX_AttachNode *new_attachment = 0; - B32 attached_this_thread = demon_lnx_attach_pid(scratch.arena, tid, &new_attachment); - if (new_attachment != 0){ - SLLStackPush(attachments, new_attachment); - } - if (!attached_this_thread){ - attached_all_threads = false; - break; - } - } - } - } - closedir(proc_dir); - - if (attached_all_threads){ - result = true; - } - } - } - - // initialize new entities on success - if (result){ - Arch arch = demon_lnx_arch_from_pid(the_process->pid); - - // process entity - DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, the_process->pid); - demon_proc_count += 1; - process->arch = arch; - process->ext_u64 = demon_lnx_open_memory_fd_for_pid(the_process->pid); - - // process event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_CreateProcess); - e->process = demon_ent_handle_from_ptr(process); - } - - // TODO(allen): happens on windows here? - - for (DEMON_LNX_AttachNode *node = attachments; - node != 0; - node = node->next){ - DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, node->pid); - demon_thread_count += 1; - - // thread event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_CreateThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - } - } - - // TODO(allen): sync modules in process - } - - // cleanup on failure - else{ - for (DEMON_LNX_AttachNode *node = attachments; - node != 0; - node = node->next){ - ptrace(PTRACE_DETACH, node->pid, 0, (void*)SIGCONT); - } - } - - scratch_end(scratch); - return(result); -} - -internal B32 -demon_os_kill_process(DEMON_Entity *process, U32 exit_code){ - B32 result = false; - if (process != 0){ - if (kill(process->id, SIGKILL) != -1){ - result = true; - } - } - return(result); -} - -internal B32 -demon_os_detach_process(DEMON_Entity *process){ - B32 result = false; - if (process != 0){ - int detach_result = ptrace(PTRACE_DETACH, process->id, 0, 0); - result = (detach_result != -1); - } - return(0); -} - -//////////////////////////////// -//~ rjf: @demon_os_hooks Entity Functions - -//- rjf: cleanup - -internal void -demon_os_entity_cleanup(DEMON_Entity *entity) -{ - // NOTE(rjf): no-op -} - -//- rjf: introspection - -internal String8 -demon_os_full_path_from_module(Arena *arena, DEMON_Entity *module){ - DEMON_Entity *process = module->parent; - int memory_fd = (int)process->ext_u64; - U64 name_va = module->ext_u64; - String8 result = demon_lnx_read_memory_str(arena, memory_fd, name_va); - return(result); -} - -internal U64 -demon_os_stack_base_vaddr_from_thread(DEMON_Entity *thread){ - Temp scratch = scratch_begin(0, 0); - - U64 stack_base = 0; - - DEMON_Entity *process = thread->parent; - - // id for main thread is zero - B32 is_main_thread = (thread->id == process->id); - pid_t match_tid = is_main_thread ? 0 : thread->id; - - // open /proc/$pid/maps - int maps = demon_lnx_open_maps(process->id); - - // look for entry with stack markings and matching thread id - for (;;){ - DEMON_LNX_MapsEntry e; - Temp temp = temp_begin(scratch.arena); - if (!demon_lnx_next_map(temp.arena, maps, &e)){ - break; - } - if (e.type == DEMON_LNX_MapsEntryType_Stack && e.stack_tid == match_tid){ - stack_base = e.address_lo; - break; - } - temp_end(temp); - } - - scratch_end(scratch); - return(stack_base); -} - -internal U64 -demon_os_tls_root_vaddr_from_thread(DEMON_Entity *thread){ - U64 result = 0; - switch (thread->arch){ - case Arch_x64: - case Arch_x86: - { - U32 fsbase = 0; - pid_t tid = (pid_t)thread->id; - if (ptrace(PT_GETFSBASE, tid, (void*)&fsbase, 0) != -1){ - result = (U64)fsbase; - } - if (thread->arch == Arch_x64){ - result += 8; - } - else{ - result += 4; - } - }break; - } - return(result); -} - -//- rjf: target process memory allocation/protection - -internal U64 -demon_os_reserve_memory(DEMON_Entity *process, U64 size){ - U64 result = 0; - NotImplemented; - return(result); -} - -internal void -demon_os_set_memory_protect_flags(DEMON_Entity *process, U64 page_vaddr, U64 size, DEMON_MemoryProtectFlags flags){ - NotImplemented; -} - -internal void -demon_os_release_memory(DEMON_Entity *process, U64 vaddr, U64 size){ - NotImplemented; -} - -//- rjf: target process memory reading/writing - -internal U64 -demon_os_read_memory(DEMON_Entity *process, void *dst, U64 src_address, U64 size){ - int memory_fd = (int)process->ext_u64; - U64 result = demon_lnx_read_memory(memory_fd, dst, src_address, size); - return(result); -} - -internal B32 -demon_os_write_memory(DEMON_Entity *process, U64 dst_address, void *src, U64 size){ - int memory_fd = (int)process->ext_u64; - B32 result = demon_lnx_write_memory(memory_fd, dst_address, src, size); - return(result); -} - -//- rjf: thread registers reading/writing - -internal B32 -demon_os_read_regs_x86(DEMON_Entity *thread, SYMS_RegX86 *dst){ - B32 result = false; - NotImplemented; - return(result); -} - -internal B32 -demon_os_write_regs_x86(DEMON_Entity *thread, SYMS_RegX86 *src){ - B32 result = false; - NotImplemented; - return(result); -} - -internal B32 -demon_os_read_regs_x64(DEMON_Entity *thread, SYMS_RegX64 *dst){ - pid_t tid = (pid_t)thread->id; - - // gpr - B32 got_gpr = false; - DEMON_LNX_UserX64 ctx = {0}; - struct iovec iov_gpr = {0}; - iov_gpr.iov_len = sizeof(ctx); - iov_gpr.iov_base = &ctx; - if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, &iov_gpr) != -1){ - demon_lnx_regs_x64_from_usr_regs_x64(dst, &ctx.regs); - got_gpr = true; - } - - // fpr - B32 got_fpr = false; - if (got_gpr){ - B32 got_xsave = false; - { - U8 xsave_buffer[KB(4)]; - struct iovec iov_xsave = {0}; - iov_xsave.iov_len = sizeof(xsave_buffer); - iov_xsave.iov_base = xsave_buffer; - if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_X86_XSTATE, &iov_xsave) != -1){ - SYMS_XSave *xsave = (SYMS_XSave*)xsave_buffer; - syms_x64_regs__set_full_regs_from_xsave_legacy(dst, &xsave->legacy); - - // TODO(allen): this is a lie; ymm can technically move around - // we need some more low-level-assembly-fu to do this hardcore. - B32 has_ymm_registers = ((xsave->header.xstate_bv & 4) != 0); - if (has_ymm_registers){ - syms_x64_regs__set_full_regs_from_xsave_avx_extension(dst, xsave->ymmh); - } - - got_xsave = true; - } - } - - B32 got_fxsave = false; - if (!got_xsave){ - SYMS_XSaveLegacy fxsave = {0}; - struct iovec iov_fxsave = {0}; - iov_fxsave.iov_len = sizeof(fxsave); - iov_fxsave.iov_base = &fxsave; - if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, &iov_fxsave) != -1){ - syms_x64_regs__set_full_regs_from_xsave_legacy(dst, &fxsave); - got_fxsave = true; - } - } - - if (got_xsave || got_fxsave){ - got_fpr = true; - } - } - - // debug - B32 got_debug = false; - if (got_fpr){ - got_debug = true; - SYMS_Reg32 *dr_d = &dst->dr0; - for (U32 i = 0; i < 8; i += 1, dr_d += 1){ - if (i != 4 && i != 5){ - U64 offset = OffsetOf(DEMON_LNX_UserX64, u_debugreg[i]); - errno = 0; - int peek_result = ptrace(PTRACE_PEEKUSER, tid, PtrFromInt(offset), 0); - if (errno == 0){ - dr_d->u32 = (U32)peek_result; - } - else{ - got_debug = false; - } - } - } - } - - // got everything - B32 result = got_debug; - return(result); -} - -internal B32 -demon_os_write_regs_x64(DEMON_Entity *thread, SYMS_RegX64 *src){ - pid_t tid = (pid_t)thread->id; - - // gpr - DEMON_LNX_UserX64 ctx = {0}; - demon_lnx_usr_regs_x64_from_regs_x64(&ctx.regs, src); - - struct iovec iov_gpr = {0}; - iov_gpr.iov_base = &ctx; - iov_gpr.iov_len = sizeof(ctx); - int gpr_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_PRSTATUS, &iov_gpr); - B32 gpr_success = (gpr_result != -1); - - // fpr - int xsave_result = 0; - int fxsave_result = 0; - - { - U8 xsave_buffer[KB(4)] = {0}; - SYMS_XSave *xsave = (SYMS_XSave*)xsave_buffer; - syms_x64_regs__set_xsave_legacy_from_full_regs(&xsave->legacy, src); - - xsave->header.xstate_bv = 7; - - // TODO(allen): this is a lie; ymm can technically move around - // we need some more low-level-assembly-fu to do this hardcore. - syms_x64_regs__set_xsave_avx_extension_from_full_regs(xsave->ymmh, src); - - { - struct iovec iov_xsave = {0}; - iov_xsave.iov_base = &xsave; - iov_xsave.iov_len = sizeof(xsave); - xsave_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_X86_XSTATE, &iov_xsave); - } - - if (xsave_result == -1){ - struct iovec iov_fxsave = {0}; - iov_fxsave.iov_base = &xsave->legacy; - iov_fxsave.iov_len = sizeof(xsave->legacy); - fxsave_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_FPREGSET, &iov_fxsave); - } - } - - B32 fpr_success = (xsave_result != -1 || fxsave_result != -1); - - // debug - B32 dr_success = true; - { - SYMS_Reg32 *dr_s = &src->dr0; - for (U32 i = 0; i < 8; i += 1, dr_s += 1){ - if (i != 4 && i != 5){ - U64 offset = OffsetOf(DEMON_LNX_UserX64, u_debugreg[i]); - errno = 0; - int poke_result = ptrace(PTRACE_POKEUSER, tid, PtrFromInt(offset), dr_s->u32); - if (poke_result == -1){ - dr_success = false; - } - } - } - } - - // assemble result - B32 result = (gpr_success && fpr_success && dr_success); - - return(result); -} - -//////////////////////////////// -//~ rjf: @demon_os_hooks Process Listing - -internal void -demon_os_proc_iter_begin(DEMON_ProcessIter *iter){ - DIR *dir = opendir("/proc"); - MemoryZeroStruct(iter); - iter->v[0] = IntFromPtr(dir); -} - -internal B32 -demon_os_proc_iter_next(Arena *arena, DEMON_ProcessIter *iter, DEMON_ProcessInfo *info_out){ - // scan for a process id - B32 got_pid = false; - String8 pid_string = {0}; - - DIR *dir = (DIR*)PtrFromInt(iter->v[0]); - if (dir != 0 && iter->v[1] == 0){ - for (;;){ - struct dirent *d = readdir(dir); - if (d == 0){ - break; - } - - // check file name is integer - String8 file_name = str8_cstring((char*)d->d_name); - B32 is_integer = str8_is_integer(file_name, 10); - - // break on integers (which represent processes) - if (is_integer){ - got_pid = true; - pid_string = file_name; - break; - } - } - } - - // mark iterator dead if nothing found - if (!got_pid){ - iter->v[1] = 1; - } - - // if got process id convert pid -> process info - B32 result = false; - if (got_pid){ - // determine the name we will report - pid_t pid = u64_from_str8(pid_string, 10); - String8 name = demon_lnx_executable_path_from_pid(arena, pid); - if (name.size == 0){ - name = str8_lit(""); - } - - // finish conversion - info_out->name = name; - info_out->pid = pid; - result = true; - } - - return(result); -} - -internal void -demon_os_proc_iter_end(DEMON_ProcessIter *iter){ - DIR *dir = (DIR*)PtrFromInt(iter->v[0]); - if (dir != 0){ - closedir(dir); - } - MemoryZeroStruct(iter); -} diff --git a/src/demon/linux/demon_os_linux.h b/src/demon/linux/demon_os_linux.h deleted file mode 100644 index ae737f75..00000000 --- a/src/demon/linux/demon_os_linux.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef DEMON_OS_LINUX_H -#define DEMON_OS_LINUX_H - -// TODO(allen): Potential Upgrades: -// -// memory fd upgrade - Right now for each process we hold open a file -// descriptor for the process's memory (/proc/%d/mem) for the entire lifetime -// of the process; it could be opened and closed with some kind of LRU cache -// to put a finite cap on the number of handles the demon holds -// - -//////////////////////////////// -//~ NOTE(allen): Get The Linux Includes - -#include -#include -#include -#include -#include -#include -#include - -//////////////////////////////// -//~ NOTE(allen): Linux Demon Types - -//- entities - -// Demon Linux Entity Extensions -// Process: ext_u64 set to memory file descriptor -// Thread : ext_u64 cast to DEMON_LNX_ThreadExt -// Module : ext_u64 set to U64 (address of name) - -struct DEMON_LNX_ThreadExt{ - B32 expecting_dummy_sigstop; -}; -StaticAssert(sizeof(DEMON_LNX_ThreadExt) <= sizeof(Member(DEMON_Entity, ext_u64)), check_demon_lnx_thread_ext); - -//- helpers - -struct DEMON_LNX_AttachNode{ - DEMON_LNX_AttachNode *next; - pid_t pid; -}; - -struct DEMON_LNX_ProcessAux{ - B32 filled; - U64 phnum; - U64 phent; - U64 phdr; - U64 execfn; -}; - -struct DEMON_LNX_PhdrInfo{ - Rng1U64 range; - U64 dynamic; -}; - -struct DEMON_LNX_ModuleNode{ - DEMON_LNX_ModuleNode *next; - U64 vaddr; - U64 size; - U64 name; - U64 already_known; -}; - -struct DEMON_LNX_EntityNode{ - DEMON_LNX_EntityNode *next; - DEMON_Entity *entity; -}; - -//////////////////////////////// -//~ NOTE(allen): Linux Demon Register Layouts - -// these are defined in but only for one architecture at a time -// (and we can't really trick it into giving us both in any obvious way) -// we define them here so that we have them all "at once" - -struct DEMON_LNX_UserRegsX64{ - U64 r15; - U64 r14; - U64 r13; - U64 r12; - U64 rbp; - U64 rbx; - U64 r11; - U64 r10; - U64 r9; - U64 r8; - U64 rax; - U64 rcx; - U64 rdx; - U64 rsi; - U64 rdi; - U64 orig_rax; - U64 rip; - U64 cs; - U64 rflags; - U64 rsp; - U64 ss; - U64 fsbase; - U64 gsbase; - U64 ds; - U64 es; - U64 fs; - U64 gs; -}; - -struct DEMON_LNX_UserX64{ - DEMON_LNX_UserRegsX64 regs; - S32 u_fpvalid, _pad0; - SYMS_XSaveLegacy i387; - U64 u_tsize, u_dsize, u_ssize, start_code, start_stack; - U64 signal; - S32 reserved, _pad1; - U64 u_ar0, u_fpstate; - U64 magic; - U8 u_comm[32]; - U64 u_debugreg[8]; -}; - -struct DEMON_LNX_UserRegsX86{ - U32 ebx; - U32 ecx; - U32 edx; - U32 esi; - U32 edi; - U32 ebp; - U32 eax; - U32 ds; - U32 es; - U32 fs; - U32 gs; - U32 orig_eax; - U32 eip; - U32 cs; - U32 eflags; - U32 sp; - U32 ss; -}; - -struct DEMON_LNX_UserX86{ - DEMON_LNX_UserRegsX86 regs; - S32 u_fpvalid; - SYMS_FSave i387; - U32 u_tsize, u_dsize, u_ssize, start_code, start_stack; - S32 signal, reserved; - U32 u_ar0, u_fpstate; - U32 magic; - U8 u_comm[32]; - U32 u_debugreg[8]; -}; - -//////////////////////////////// - -enum -{ - DEMON_LNX_PermFlags_Read = (1 << 0), - DEMON_LNX_PermFlags_Write = (1 << 1), - DEMON_LNX_PermFlags_Exec = (1 << 2), - DEMON_LNX_PermFlags_Private = (1 << 3) -}; -typedef int DEMON_LNX_PermFlags; - -enum -{ - DEMON_LNX_MapsEntryType_Null, - DEMON_LNX_MapsEntryType_Path, - DEMON_LNX_MapsEntryType_Heap, - DEMON_LNX_MapsEntryType_Stack, - DEMON_LNX_MapsEntryType_VDSO, -}; -typedef int DEMON_LNX_MapsEntryType; - -struct DEMON_LNX_MapsEntry -{ - U64 address_lo; - U64 address_hi; - DEMON_LNX_PermFlags perms; - U64 offset; - U32 dev_major; - U32 dev_minor; - U64 inode; - String8 pathname; - DEMON_LNX_MapsEntryType type; - pid_t stack_tid; -}; - -//////////////////////////////// -//~ rjf: Helpers - -internal DEMON_LNX_ThreadExt* demon_lnx_thread_ext(DEMON_Entity *entity); - -internal B32 demon_lnx_attach_pid(Arena *arena, pid_t pid, DEMON_LNX_AttachNode **new_node); - -internal String8 demon_lnx_executable_path_from_pid(Arena *arena, pid_t pid); -internal int demon_lnx_open_memory_fd_for_pid(pid_t pid); - -internal Arch demon_lnx_arch_from_pid(pid_t pid); -internal DEMON_LNX_ProcessAux demon_lnx_aux_from_pid(pid_t pid, Arch arch); -internal DEMON_LNX_PhdrInfo demon_lnx_phdr_info_from_memory(int memory_fd, B32 is_32bit, - U64 phvaddr, U64 phstride, U64 phcount); -internal DEMON_LNX_ModuleNode* demon_lnx_module_list_from_process(Arena *arena, DEMON_Entity *process); - -internal U64 demon_lnx_read_memory(int memory_fd, void *dst, U64 src, U64 size); -internal B32 demon_lnx_write_memory(int memory_fd, U64 dst, void *src, U64 size); -internal String8 demon_lnx_read_memory_str(Arena *arena, int memory_fd, U64 address); - -internal void demon_lnx_regs_x64_from_usr_regs_x64(SYMS_RegX64 *dst, DEMON_LNX_UserRegsX64 *src); -internal void demon_lnx_usr_regs_x64_from_regs_x64(DEMON_LNX_UserRegsX64 *dst, SYMS_RegX64 *src); - -internal String8 demon_lnx_read_int_string(int fd); -internal B32 demon_lnx_read_expect(int fd, char expect); -internal int demon_lnx_read_whitespace(int fd); -internal String8 demon_lnx_read_string(Arena *arena, int fd); - -internal int demon_lnx_open_maps(pid_t pid); -internal B32 demon_lnx_next_map(Arena *arena, int maps, DEMON_LNX_MapsEntry *entry_out); - -#endif //DEMON_OS_LINUX_H diff --git a/src/linker/lnk.c b/src/linker/lnk.c index d0643301..1f2368cf 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -17,7 +17,6 @@ // --- Code Base --------------------------------------------------------------- #include "base/base_inc.h" -#include "os/os_inc.h" #include "hash_table.h" #include "coff/coff.h" #include "coff/coff_parse.h" @@ -37,7 +36,6 @@ #include "llvm/llvm.h" #include "base/base_inc.c" -#include "os/os_inc.c" #include "hash_table.c" #include "coff/coff.c" #include "coff/coff_parse.c" diff --git a/src/linux/base/linux_base.c b/src/linux/base/linux_base.c index ae447aab..eb4fb355 100644 --- a/src/linux/base/linux_base.c +++ b/src/linux/base/linux_base.c @@ -1,2 +1,1573 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Helpers + +internal DateTime +os_lnx_date_time_from_tm(tm in, U32 msec) +{ + DateTime dt = {0}; + dt.sec = in.tm_sec; + dt.min = in.tm_min; + dt.hour = in.tm_hour; + dt.day = in.tm_mday-1; + dt.mon = in.tm_mon; + dt.year = in.tm_year+1900; + dt.msec = msec; + return dt; +} + +internal tm +os_lnx_tm_from_date_time(DateTime dt) +{ + tm result = {0}; + result.tm_sec = dt.sec; + result.tm_min = dt.min; + result.tm_hour= dt.hour; + result.tm_mday= dt.day+1; + result.tm_mon = dt.mon; + result.tm_year= dt.year-1900; + return result; +} + +internal timespec +os_lnx_timespec_from_date_time(DateTime dt) +{ + tm tm_val = os_lnx_tm_from_date_time(dt); + time_t seconds = timegm(&tm_val); + timespec result = {0}; + result.tv_sec = seconds; + return result; +} + +internal DenseTime +os_lnx_dense_time_from_timespec(timespec in) +{ + DenseTime result = 0; + { + struct tm tm_time = {0}; + gmtime_r(&in.tv_sec, &tm_time); + DateTime date_time = os_lnx_date_time_from_tm(tm_time, in.tv_nsec/Million(1)); + result = dense_time_from_date_time(date_time); + } + return result; +} + +internal FileProperties +os_lnx_file_properties_from_stat(struct stat *s) +{ + FileProperties props = {0}; + props.size = s->st_size; + props.created = os_lnx_dense_time_from_timespec(s->st_ctim); + props.modified = os_lnx_dense_time_from_timespec(s->st_mtim); + if(s->st_mode & S_IFDIR) + { + props.flags |= FilePropertyFlag_IsFolder; + } + return props; +} + +internal void +os_lnx_safe_call_sig_handler(int x) +{ + OS_LNX_SafeCallChain *chain = os_lnx_safe_call_chain; + if(chain != 0 && chain->fail_handler != 0) + { + chain->fail_handler(chain->ptr); + } + abort(); +} + +//////////////////////////////// +//~ rjf: Entities + +internal OS_LNX_Entity * +os_lnx_entity_alloc(OS_LNX_EntityKind kind) +{ + OS_LNX_Entity *entity = 0; + DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), + pthread_mutex_unlock(&os_lnx_state.entity_mutex)) + { + entity = os_lnx_state.entity_free; + if(entity) + { + SLLStackPop(os_lnx_state.entity_free); + } + else + { + entity = push_array_no_zero(os_lnx_state.entity_arena, OS_LNX_Entity, 1); + } + } + MemoryZeroStruct(entity); + entity->kind = kind; + return entity; +} + +internal void +os_lnx_entity_release(OS_LNX_Entity *entity) +{ + DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), + pthread_mutex_unlock(&os_lnx_state.entity_mutex)) + { + SLLStackPush(os_lnx_state.entity_free, entity); + } +} + +//////////////////////////////// +//~ rjf: Thread Entry Point + +internal void * +os_lnx_thread_entry_point(void *ptr) +{ + OS_LNX_Entity *entity = (OS_LNX_Entity *)ptr; + ThreadEntryPointFunctionType *func = entity->thread.func; + void *thread_ptr = entity->thread.ptr; + supplement_thread_base_entry_point(func, thread_ptr); + return 0; +} + +//////////////////////////////// +//~ rjf: @per_os_impl Platform Time Functions + +internal U64 +now_time_us(void) +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); + return result; +} + +internal U32 +now_time_unix(void) +{ + time_t t = time(0); + return (U32)t; +} + +internal DateTime +now_time_universal(void) +{ + time_t t = 0; + time(&t); + struct tm universal_tm = {0}; + gmtime_r(&t, &universal_tm); + DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); + return result; +} + +internal DateTime +universal_from_local_time(DateTime *dt) +{ + // rjf: local DateTime -> universal time_t + tm local_tm = os_lnx_tm_from_date_time(*dt); + local_tm.tm_isdst = -1; + time_t universal_t = mktime(&local_tm); + + // rjf: universal time_t -> DateTime + tm universal_tm = {0}; + gmtime_r(&universal_t, &universal_tm); + DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); + return result; +} + +internal DateTime +local_from_universal_time(DateTime *dt) +{ + // rjf: universal DateTime -> local time_t + tm universal_tm = os_lnx_tm_from_date_time(*dt); + universal_tm.tm_isdst = -1; + time_t universal_t = timegm(&universal_tm); + tm local_tm = {0}; + localtime_r(&universal_t, &local_tm); + + // rjf: local tm -> DateTime + DateTime result = os_lnx_date_time_from_tm(local_tm, 0); + return result; +} + +internal void +sleep_ms(U32 ms) +{ + usleep(ms*Thousand(1)); +} + +//////////////////////////////// +//~ rjf: @per_os_impl Platform GUID Functions + +internal Guid +make_guid(void) +{ + Guid guid = {0}; + getrandom(guid.v, sizeof(guid.v), 0); + guid.data3 &= 0x0fff; + guid.data3 |= (4 << 12); + guid.data4[0] &= 0x3f; + guid.data4[0] |= 0x80; + return guid; +} + +//////////////////////////////// +//~ rjf: @per_os_impl Platform Memory Allocation + +//- rjf: basic + +internal void * +reserve_memory(U64 size) +{ + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if(result == MAP_FAILED) + { + result = 0; + } + return result; +} + +internal B32 +commit_memory(void *ptr, U64 size) +{ + mprotect(ptr, size, PROT_READ|PROT_WRITE); + return 1; +} + +internal void +decommit_memory(void *ptr, U64 size) +{ + madvise(ptr, size, MADV_DONTNEED); + mprotect(ptr, size, PROT_NONE); +} + +internal void +release_memory(void *ptr, U64 size) +{ + munmap(ptr, size); +} + +//- rjf: large pages + +internal void * +reserve_memory_large(U64 size) +{ + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0); + if(result == MAP_FAILED) + { + result = 0; + } + return result; +} + +internal B32 +commit_memory_large(void *ptr, U64 size) +{ + mprotect(ptr, size, PROT_READ|PROT_WRITE); + return 1; +} + +//////////////////////////////// +//~ rjf: @os_hooks Shared Memory + +internal SharedMemory +shared_memory_alloc(U64 size, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + int id = shm_open((char *)name_copy.str, O_RDWR|O_CREAT, 0666); + ftruncate(id, size); + SharedMemory result = {(U64)id}; + scratch_end(scratch); + return result; +} + +internal SharedMemory +shared_memory_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + int id = shm_open((char *)name_copy.str, O_RDWR, 0); + SharedMemory result = {(U64)id}; + scratch_end(scratch); + return result; +} + +internal void +shared_memory_close(SharedMemory handle) +{ + if(MemoryIsZeroStruct(&handle)){return;} + int id = (int)handle.u64[0]; + close(id); +} + +internal void * +shared_memory_view_open(SharedMemory handle, Rng1U64 range) +{ + if(MemoryIsZeroStruct(&handle)){return 0;} + int id = (int)handle.u64[0]; + void *base = mmap(0, dim_1u64(range), PROT_READ|PROT_WRITE, MAP_SHARED, id, range.min); + if(base == MAP_FAILED) + { + base = 0; + } + return base; +} + +internal void +shared_memory_view_close(SharedMemory handle, void *ptr, Rng1U64 range) +{ + if(MemoryIsZeroStruct(&handle)){return;} + munmap(ptr, dim_1u64(range)); +} + +//////////////////////////////// +//~ rjf: @per_os_impl System Info + +internal SystemInfo * +get_system_info(void) +{ + return &os_lnx_state.system_info; +} + +//////////////////////////////// +//~ rjf: @per_os_impl Current Thread Info + +internal U32 +tid(void) +{ + U32 result = gettid(); + return result; +} + +internal void +set_platform_thread_name(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String8 name_copy = str8_copy(scratch.arena, name); + pthread_t current_thread = pthread_self(); + pthread_setname_np(current_thread, (char *)name_copy.str); + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: @per_os_impl Thread Functions + +internal Thread +thread_launch(ThreadEntryPointFunctionType *f, void *p) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Thread); + entity->thread.func = f; + entity->thread.ptr = p; + { + int pthread_result = pthread_create(&entity->thread.handle, 0, os_lnx_thread_entry_point, entity); + if(pthread_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + } + Thread handle = {(U64)entity}; + return handle; +} + +internal B32 +thread_join(Thread thread, U64 endt_us) +{ + if(MemoryIsZeroStruct(&thread)) { return 0; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)thread.u64[0]; + int join_result = pthread_join(entity->thread.handle, 0); + B32 result = (join_result == 0); + os_lnx_entity_release(entity); + return result; +} + +internal void +thread_detach(Thread thread) +{ + if(MemoryIsZeroStruct(&thread)) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)thread.u64[0]; + os_lnx_entity_release(entity); +} + +//////////////////////////////// +//~ rjf: @per_os_impl Synchronization Primitive Functions + +//- rjf: recursive mutexes + +internal Mutex +mutex_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Mutex); + int init_result = pthread_mutex_init(&entity->mutex_handle, 0); + if(init_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + Mutex handle = {(U64)entity}; + return handle; +} + +internal void +mutex_release(Mutex mutex) +{ + if(MemoryIsZeroStruct(&mutex)) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + pthread_mutex_destroy(&entity->mutex_handle); + os_lnx_entity_release(entity); +} + +internal void +mutex_take(Mutex mutex) +{ + if(MemoryIsZeroStruct(&mutex)) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + pthread_mutex_lock(&entity->mutex_handle); +} + +internal void +mutex_drop(Mutex mutex) +{ + if(MemoryIsZeroStruct(&mutex)) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + pthread_mutex_unlock(&entity->mutex_handle); +} + +//- rjf: reader/writer mutexes + +internal RWMutex +rw_mutex_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_RWMutex); + int init_result = pthread_rwlock_init(&entity->rwmutex_handle, 0); + if(init_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + RWMutex handle = {(U64)entity}; + return handle; +} + +internal void +rw_mutex_release(RWMutex mutex) +{ + if(MemoryIsZeroStruct(&mutex)) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + pthread_rwlock_destroy(&entity->rwmutex_handle); + os_lnx_entity_release(entity); +} + +internal void +rw_mutex_take(RWMutex mutex, B32 write_mode) +{ + if(MemoryIsZeroStruct(&mutex)) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + if(write_mode) + { + pthread_rwlock_wrlock(&entity->rwmutex_handle); + } + else + { + pthread_rwlock_rdlock(&entity->rwmutex_handle); + } +} + +internal void +rw_mutex_drop(RWMutex mutex, B32 write_mode) +{ + if(MemoryIsZeroStruct(&mutex)) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + pthread_rwlock_unlock(&entity->rwmutex_handle); +} + +//- rjf: condition variables + +internal CondVar +cond_var_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_ConditionVariable); + int init_result = pthread_cond_init(&entity->cv.cond_handle, 0); + if(init_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + int init2_result = 0; + if(entity) + { + init2_result = pthread_mutex_init(&entity->cv.rwlock_mutex_handle, 0); + } + if(init2_result == -1) + { + pthread_cond_destroy(&entity->cv.cond_handle); + os_lnx_entity_release(entity); + entity = 0; + } + CondVar handle = {(U64)entity}; + return handle; +} + +internal void +cond_var_release(CondVar cv) +{ + if(MemoryIsZeroStruct(&cv)) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)cv.u64[0]; + pthread_cond_destroy(&entity->cv.cond_handle); + pthread_mutex_destroy(&entity->cv.rwlock_mutex_handle); + os_lnx_entity_release(entity); +} + +internal B32 +cond_var_wait(CondVar cv, Mutex mutex, U64 endt_us) +{ + if(MemoryIsZeroStruct(&cv)) { return 0; } + if(MemoryIsZeroStruct(&mutex)) { return 0; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + OS_LNX_Entity *mutex_entity = (OS_LNX_Entity *)mutex.u64[0]; + struct timespec endt_timespec; + endt_timespec.tv_sec = endt_us/Million(1); + endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); + int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &mutex_entity->mutex_handle, &endt_timespec); + B32 result = (wait_result != ETIMEDOUT); + return result; +} + +internal B32 +cond_var_wait_rw(CondVar cv, RWMutex mutex_rw, B32 write_mode, U64 endt_us) +{ + // TODO(rjf): because pthread does not supply cv/rw natively, I had to hack + // this together, but this would probably just be a lot better if we just + // implemented the primitives ourselves with e.g. futexes + // + if(MemoryIsZeroStruct(&cv)) { return 0; } + if(MemoryIsZeroStruct(&mutex_rw)) { return 0; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + OS_LNX_Entity *rw_mutex_entity = (OS_LNX_Entity *)mutex_rw.u64[0]; + struct timespec endt_timespec; + endt_timespec.tv_sec = endt_us/Million(1); + endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); + B32 result = 0; + pthread_mutex_lock(&cv_entity->cv.rwlock_mutex_handle); + pthread_rwlock_unlock(&rw_mutex_entity->rwmutex_handle); + for(;;) + { + int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &cv_entity->cv.rwlock_mutex_handle, &endt_timespec); + if(wait_result != ETIMEDOUT) + { + if(write_mode) + { + pthread_rwlock_wrlock(&rw_mutex_entity->rwmutex_handle); + } + else + { + pthread_rwlock_rdlock(&rw_mutex_entity->rwmutex_handle); + } + result = 1; + break; + } + if(wait_result == ETIMEDOUT) + { + if(write_mode) + { + pthread_rwlock_wrlock(&rw_mutex_entity->rwmutex_handle); + } + else + { + pthread_rwlock_rdlock(&rw_mutex_entity->rwmutex_handle); + } + break; + } + } + pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); + return result; +} + +internal void +cond_var_signal(CondVar cv) +{ + if(MemoryIsZeroStruct(&cv)) { return; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + pthread_cond_signal(&cv_entity->cv.cond_handle); +} + +internal void +cond_var_broadcast(CondVar cv) +{ + if(MemoryIsZeroStruct(&cv)) { return; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + pthread_cond_broadcast(&cv_entity->cv.cond_handle); +} + +//- rjf: cross-process semaphores + +internal Semaphore +semaphore_alloc(U32 initial_count, U32 max_count, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + Semaphore result = {0}; + if(name.size > 0) + { + for EachIndex(attempt_idx, 64) + { + String8 name_copy = str8_copy(scratch.arena, name); + sem_t *s = sem_open((char *)name_copy.str, O_CREAT | O_EXCL, 0666, initial_count); + if(s == SEM_FAILED) + { + s = sem_open((char *)name_copy.str, 0); + } + if(s != SEM_FAILED) + { + result.u64[0] = (U64)s; + break; + } + } + } + else + { + sem_t *s = mmap(0, sizeof(*s), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + AssertAlways(s != MAP_FAILED); + int err = sem_init(s, 0, initial_count); + if(err == 0) + { + result.u64[0] = (U64)s; + } + } + scratch_end(scratch); + return result; +} + +internal void +semaphore_release(Semaphore semaphore) +{ + int err = munmap((void*)semaphore.u64[0], sizeof(sem_t)); + AssertAlways(err == 0); +} + +internal Semaphore +semaphore_open(String8 name) +{ + Semaphore result = {0}; + { + Temp scratch = scratch_begin(0, 0); + String8 name_copy = str8_copy(scratch.arena, name); + sem_t *s = sem_open((char *)name_copy.str, 0); + if(s != SEM_FAILED) + { + result.u64[0] = (U64)s; + } + scratch_end(scratch); + } + return result; +} + +internal void +semaphore_close(Semaphore semaphore) +{ + sem_t *s = (sem_t *)semaphore.u64[0]; + sem_close(s); +} + +internal B32 +semaphore_take(Semaphore semaphore, U64 endt_us) +{ + // TODO(rjf): we need to use `sem_timedwait` here. + AssertAlways(endt_us == max_U64); + for(;;) + { + int err = sem_wait((sem_t*)semaphore.u64[0]); + if(err == 0) + { + break; + } + else if(errno == EAGAIN) + { + continue; + } + break; + } + return 1; +} + +internal void +semaphore_drop(Semaphore semaphore) +{ + for(;;) + { + int err = sem_post((sem_t*)semaphore.u64[0]); + if(err == 0) + { + break; + } + else + { + if(errno == EAGAIN) + { + continue; + } + } + break; + } +} + +//- rjf: barriers + +internal Barrier +barrier_alloc(U64 count) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Barrier); + if(entity != 0) + { + pthread_barrier_init(&entity->barrier, 0, count); + } + Barrier result = {IntFromPtr(entity)}; + return result; +} + +internal void +barrier_release(Barrier barrier) +{ + OS_LNX_Entity *entity = (OS_LNX_Entity*)PtrFromInt(barrier.u64[0]); + if(entity != 0) + { + pthread_barrier_destroy(&entity->barrier); + os_lnx_entity_release(entity); + } +} + +internal void +barrier_wait(Barrier barrier) +{ + OS_LNX_Entity *entity = (OS_LNX_Entity*)PtrFromInt(barrier.u64[0]); + if(entity != 0) + { + pthread_barrier_wait(&entity->barrier); + } +} + +//////////////////////////////// +//~ rjf: @per_os_impl Safe Calls + +internal void +safe_call(ThreadEntryPointFunctionType *func, ThreadEntryPointFunctionType *fail_handler, void *ptr) +{ + // rjf: push handler to chain + OS_LNX_SafeCallChain chain = {0}; + SLLStackPush(os_lnx_safe_call_chain, &chain); + chain.fail_handler = fail_handler; + chain.ptr = ptr; + + // rjf: set up sig handler info + struct sigaction new_act = {0}; + new_act.sa_handler = os_lnx_safe_call_sig_handler; + int signals_to_handle[] = + { + SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, + }; + struct sigaction og_act[ArrayCount(signals_to_handle)] = {0}; + + // rjf: attach handler info for all signals + for(U32 i = 0; i < ArrayCount(signals_to_handle); i += 1) + { + sigaction(signals_to_handle[i], &new_act, &og_act[i]); + } + + // rjf: call function + func(ptr); + + // rjf: reset handler info for all signals + for(U32 i = 0; i < ArrayCount(signals_to_handle); i += 1) + { + sigaction(signals_to_handle[i], &og_act[i], 0); + } +} + +//////////////////////////////// +//~ rjf: @per_os_impl File System (Implemented Per-OS) + +//- rjf: files + +internal File +file_open(AccessFlags flags, String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + int lnx_flags = 0; + if(flags & AccessFlag_Read && flags & AccessFlag_Write) + { + lnx_flags = O_RDWR; + } + else if(flags & AccessFlag_Write) + { + lnx_flags = O_WRONLY; + } + else if(flags & AccessFlag_Read) + { + lnx_flags = O_RDONLY; + } + if(flags & AccessFlag_Append) + { + lnx_flags |= O_APPEND; + } + if(flags & (AccessFlag_Write|AccessFlag_Append)) + { + lnx_flags |= O_CREAT; + } + lnx_flags |= O_CLOEXEC; + int fd = open((char *)path_copy.str, lnx_flags, 0755); + File handle = {0}; + if(fd != -1) + { + handle.u64[0] = fd; + } + scratch_end(scratch); + return handle; +} + +internal void +file_close(File file) +{ + if(file_match(file, file_zero())) { return; } + int fd = (int)file.u64[0]; + close(fd); +} + +internal U64 +file_read(File file, Rng1U64 rng, void *out_data) +{ + if(file_match(file, file_zero())) { return 0; } + int fd = (int)file.u64[0]; + U64 total_num_bytes_to_read = dim_1u64(rng); + U64 total_num_bytes_read = 0; + U64 total_num_bytes_left_to_read = total_num_bytes_to_read; + for(;total_num_bytes_left_to_read > 0;) + { + int read_result = pread(fd, (U8 *)out_data + total_num_bytes_read, total_num_bytes_left_to_read, rng.min + total_num_bytes_read); + if(read_result >= 0) + { + total_num_bytes_read += read_result; + total_num_bytes_left_to_read -= read_result; + } + else if(errno != EINTR) + { + break; + } + } + return total_num_bytes_read; +} + +internal U64 +file_write(File file, Rng1U64 rng, void *data) +{ + if(file_match(file, file_zero())) { return 0; } + int fd = (int)file.u64[0]; + U64 total_num_bytes_to_write = dim_1u64(rng); + U64 total_num_bytes_written = 0; + U64 total_num_bytes_left_to_write = total_num_bytes_to_write; + for(;total_num_bytes_left_to_write > 0;) + { + int write_result = pwrite(fd, (U8 *)data + total_num_bytes_written, total_num_bytes_left_to_write, rng.min + total_num_bytes_written); + if(write_result >= 0) + { + total_num_bytes_written += write_result; + total_num_bytes_left_to_write -= write_result; + } + else if(errno != EINTR) + { + break; + } + } + return total_num_bytes_written; +} + +internal B32 +file_set_times(File file, DateTime date_time) +{ + if(file_match(file, file_zero())) { return 0; } + int fd = (int)file.u64[0]; + timespec time = os_lnx_timespec_from_date_time(date_time); + timespec times[2] = {time, time}; + int futimens_result = futimens(fd, times); + B32 good = (futimens_result != -1); + return good; +} + +internal FileProperties +properties_from_file(File file) +{ + if(file_match(file, file_zero())) { return (FileProperties){0}; } + int fd = (int)file.u64[0]; + struct stat fd_stat = {0}; + int fstat_result = fstat(fd, &fd_stat); + FileProperties props = {0}; + if(fstat_result != -1) + { + props = os_lnx_file_properties_from_stat(&fd_stat); + } + return props; +} + +internal FileID +id_from_file(File file) +{ + if(file_match(file, file_zero())) { return (FileID){0}; } + int fd = (int)file.u64[0]; + struct stat fd_stat = {0}; + int fstat_result = fstat(fd, &fd_stat); + FileID id = {0}; + if(fstat_result != -1) + { + id.v[0] = fd_stat.st_dev; + id.v[1] = fd_stat.st_ino; + } + return id; +} + +internal B32 +delete_file_at_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 result = 0; + String8 path_copy = push_str8_copy(scratch.arena, path); + if(remove((char *)path_copy.str) != -1) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +internal B32 +copy_file_path(String8 dst, String8 src) +{ + B32 result = 0; + File src_h = file_open(AccessFlag_Read, src); + File dst_h = file_open(AccessFlag_Write, dst); + if(!file_match(src_h, file_zero()) && + !file_match(dst_h, file_zero())) + { + int src_fd = (int)src_h.u64[0]; + int dst_fd = (int)dst_h.u64[0]; + FileProperties src_props = properties_from_file(src_h); + U64 size = src_props.size; + U64 total_bytes_copied = 0; + U64 bytes_left_to_copy = size; + for(;bytes_left_to_copy > 0;) + { + off_t sendfile_off = total_bytes_copied; + int send_result = sendfile(dst_fd, src_fd, &sendfile_off, bytes_left_to_copy); + if(send_result <= 0) + { + break; + } + U64 bytes_copied = (U64)send_result; + bytes_left_to_copy -= bytes_copied; + total_bytes_copied += bytes_copied; + } + } + file_close(src_h); + file_close(dst_h); + return result; +} + +internal B32 +move_file_path(String8 dst, String8 src) +{ + B32 good = 0; + Temp scratch = scratch_begin(0, 0); + { + char *src_cstr = (char *)str8_copy(scratch.arena, src).str; + char *dst_cstr = (char *)str8_copy(scratch.arena, dst).str; + int rename_result = rename(src_cstr, dst_cstr); + good = (rename_result != -1); + } + scratch_end(scratch); + return good; +} + +internal String8 +full_path_from_path(Arena *arena, String8 path) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 path_copy = str8_copy(scratch.arena, path); + char buffer[PATH_MAX] = {0}; + realpath((char *)path_copy.str, buffer); + String8 result = str8_copy(arena, str8_cstring(buffer)); + scratch_end(scratch); + return result; +} + +internal B32 +file_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + int access_result = access((char *)path_copy.str, F_OK); + B32 result = 0; + if(access_result == 0) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +internal B32 +folder_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 exists = 0; + String8 path_copy = str8_copy(scratch.arena, path); + DIR *handle = opendir((char *)path_copy.str); + if(handle) + { + closedir(handle); + exists = 1; + } + scratch_end(scratch); + return exists; +} + +internal FileProperties +properties_from_file_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = str8_copy(scratch.arena, path); + struct stat f_stat = {0}; + int stat_result = stat((char *)path_copy.str, &f_stat); + FileProperties props = {0}; + if(stat_result != -1) + { + props = os_lnx_file_properties_from_stat(&f_stat); + } + scratch_end(scratch); + return props; +} + +//- rjf: file maps + +internal FileMap +file_map_open(AccessFlags flags, File file) +{ + FileMap map = {file.u64[0]}; + return map; +} + +internal void +file_map_close(FileMap map) +{ + // NOTE(rjf): nothing to do; `map` handles are the same as `file` handles in + // the linux implementation (on Windows they require separate handles) +} + +internal void * +file_map_view_open(FileMap map, AccessFlags flags, Rng1U64 range) +{ + if(MemoryIsZeroStruct(&map)) { return 0; } + int fd = (int)map.u64[0]; + int prot_flags = 0; + if(flags & AccessFlag_Write) { prot_flags |= PROT_WRITE; } + if(flags & AccessFlag_Read) { prot_flags |= PROT_READ; } + int map_flags = MAP_PRIVATE; + void *base = mmap(0, dim_1u64(range), prot_flags, map_flags, fd, range.min); + if(base == MAP_FAILED) + { + base = 0; + } + return base; +} + +internal void +file_map_view_close(FileMap map, void *ptr, Rng1U64 range) +{ + munmap(ptr, dim_1u64(range)); +} + +//- rjf: directory iteration + +internal FileIter * +file_iter_begin(Arena *arena, String8 path, FileIterFlags flags) +{ + FileIter *base_iter = push_array(arena, FileIter, 1); + base_iter->flags = flags; + OS_LNX_FileIter *iter = (OS_LNX_FileIter *)base_iter->memory; + { + String8 path_copy = push_str8_copy(arena, path); + iter->dir = opendir((char *)path_copy.str); + iter->path = path_copy; + } + return base_iter; +} + +internal B32 +file_iter_next(Arena *arena, FileIter *iter, FileInfo *info_out) +{ + B32 good = 0; + OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; + for(;lnx_iter->dir != 0;) + { + // rjf: get next entry + lnx_iter->dp = readdir(lnx_iter->dir); + good = (lnx_iter->dp != 0); + + // rjf: unpack entry info + struct stat st = {0}; + int stat_result = 0; + if(good) + { + Temp scratch = scratch_begin(&arena, 1); + String8 full_path = push_str8f(scratch.arena, "%S/%s", lnx_iter->path, lnx_iter->dp->d_name); + stat_result = stat((char *)full_path.str, &st); + scratch_end(scratch); + } + + // rjf: determine if filtered + B32 filtered = 0; + if(good) + { + filtered = ((st.st_mode == S_IFDIR && iter->flags & FileIterFlag_SkipFolders) || + (st.st_mode == S_IFREG && iter->flags & FileIterFlag_SkipFiles) || + (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == 0) || + (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == '.' && lnx_iter->dp->d_name[2] == 0)); + } + + // rjf: output & exit, if good & unfiltered + if(good && !filtered) + { + info_out->name = push_str8_copy(arena, str8_cstring(lnx_iter->dp->d_name)); + if(stat_result != -1) + { + info_out->props = os_lnx_file_properties_from_stat(&st); + } + break; + } + + // rjf: exit if not good + if(!good) + { + break; + } + } + return good; +} + +internal void +file_iter_end(FileIter *iter) +{ + OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; + closedir(lnx_iter->dir); +} + +//- rjf: directory creation + +internal B32 +make_directory(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 result = 0; + String8 path_copy = push_str8_copy(scratch.arena, path); + if(mkdir((char *)path_copy.str, 0755) != -1) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: @per_os_impl Aborting + +internal void +abort_self(S32 exit_code) +{ + exit(exit_code); +} + +//////////////////////////////// +//~ rjf: @per_os_impl Process Info + +internal ProcessInfo * +get_process_info(void) +{ + return &os_lnx_state.process_info; +} + +internal String8 +get_current_path(Arena *arena) +{ + char *cwdir = getcwd(0, 0); + String8 string = str8_copy(arena, str8_cstring(cwdir)); + free(cwdir); + return string; +} + +internal U32 +get_process_start_time_unix(void) +{ + Temp scratch = scratch_begin(0,0); + U64 start_time = 0; + pid_t pid = getpid(); + String8 path = str8f(scratch.arena, "/proc/%u", pid); + struct stat st; + int err = stat((char *)path.str, &st); + if(err == 0) + { + start_time = st.st_mtime; + } + scratch_end(scratch); + return (U32)start_time; +} + +//////////////////////////////// +//~ rjf: @per_os_impl Child Processes + +internal Process +process_launch(ProcessLaunchParams *params) +{ + Process handle = {0}; + posix_spawn_file_actions_t file_actions = {0}; + int file_actions_init_code = posix_spawn_file_actions_init(&file_actions); + if(file_actions_init_code == 0) + { + int stdout_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stdout_file.u64[0], STDOUT_FILENO); + int stderr_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stderr_file.u64[0], STDERR_FILENO); + int stdin_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stdin_file.u64[0], STDIN_FILENO); + posix_spawnattr_t attr = {0}; + int attr_init_code = posix_spawnattr_init(&attr); + if(attr_init_code == 0) + { + Temp scratch = scratch_begin(0, 0); + + // package argv + char **argv = push_array(scratch.arena, char *, params->cmd_line.node_count + 1); + { + String8List l = str8_split_path(scratch.arena, params->path); + str8_list_push(scratch.arena, &l, params->cmd_line.first->string); + String8 path_to_exe = str8_path_list_join_by_style(scratch.arena, &l, PathStyle_SystemAbsolute); + argv[0] = (char *)path_to_exe.str; + U64 arg_idx = 1; + for EachNode(n, String8Node, params->cmd_line.first->next) + { + argv[arg_idx] = (char *)n->string.str; + arg_idx += 1; + } + } + + // package envp + char **envp = 0; + if(params->inherit_env) + { + envp = os_lnx_state.default_env; + } + else + { + envp = push_array(scratch.arena, char *, params->env.node_count + 2); + U64 env_idx = 0; + for EachNode(n, String8Node, params->cmd_line.first) + { + envp[env_idx] = (char *)n->string.str; + env_idx += 1; + } + } + + // spawn process + pid_t pid = 0; + int spawn_code = posix_spawn(&pid, argv[0], &file_actions, &attr, argv, envp); + if(spawn_code == 0) + { + handle.u64[0] = (U64)pid; + } + + // clean up attributes + int attr_destroy_code = posix_spawnattr_destroy(&attr); + scratch_end(scratch); + } + + // clean up file actions + int file_actions_destroy_code = posix_spawn_file_actions_destroy(&file_actions); + } + return handle; +} + +internal B32 +process_join(Process process, U64 endt_us, U64 *exit_code_out) +{ + pid_t pid = (pid_t)process.u64[0]; + B32 result = 0; + if(endt_us == 0) + { + if(kill(pid, 0) >= 0) + { + result = (errno == ENOENT); + if(result) + { + int status; + waitpid(pid, &status, 0); + } + } + } + else if(endt_us == max_U64) + { + for(;;) + { + int status = 0; + int w = waitpid(pid, &status, 0); + if(w == -1) + { + break; + } + if(WIFEXITED(status) || WIFSTOPPED(status) || WIFSIGNALED(status)) + { + result = 1; + break; + } + } + } + else + { + NotImplemented; + } + return result; +} + +internal void +process_detach(Process process) +{ + // no need to close pid +} + +internal B32 +process_kill(Process process) +{ + int error_code = kill((pid_t)process.u64[0], SIGKILL); + B32 is_killed = error_code == 0; + return is_killed; +} + +//////////////////////////////// +//~ rjf: @per_os_impl Dynamically-Loaded Libraries + +internal Library +library_open(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + char *path_cstr = (char *)str8_copy(scratch.arena, path).str; + void *so = dlopen(path_cstr, RTLD_LAZY|RTLD_LOCAL); + Library lib = { (U64)so }; + scratch_end(scratch); + return lib; +} + +internal void +library_close(Library lib) +{ + void *so = (void *)lib.u64; + dlclose(so); +} + +internal VoidProc * +library_load_proc(Library lib, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + void *so = (void *)lib.u64; + char *name_cstr = (char *)str8_copy(scratch.arena, name).str; + VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); + scratch_end(scratch); + return proc; +} + +//////////////////////////////// +//~ rjf: Entry Point + +internal void +lnx_signal_handler(int sig, siginfo_t *info, void *arg) +{ + local_persist volatile U32 first = 0; + if (ins_atomic_u32_eval_cond_assign(&first, 1, 0) != 0) + { + for(;;) + { + sleep(UINT32_MAX); + } + } + + local_persist void *ips[4096]; + int ips_count = backtrace(ips, ArrayCount(ips)); + + fprintf(stderr, "A fatal signal was received: %s (%d). The process is terminating.\n", strsignal(sig), sig); + fprintf(stderr, "Create a new issue with this report at %s.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); + fprintf(stderr, "Callstack:\n"); + for EachIndex(i, ips_count) + { + Dl_info info = {0}; + dladdr(ips[i], &info); + + char cmd[2048]; + snprintf(cmd, sizeof(cmd), "llvm-symbolizer --relative-address -f -e %s %lu", info.dli_fname, (unsigned long)ips[i] - (unsigned long)info.dli_fbase); + FILE *f = popen(cmd, "r"); + if(f) + { + char func_name[256], file_name[256]; + if(fgets(func_name, sizeof(func_name), f) && fgets(file_name, sizeof(file_name), f)) + { + String8 func = str8_cstring(func_name); + if(func.size > 0) func.size -= 1; + String8 module = str8_skip_last_slash(str8_cstring(info.dli_fname)); + String8 file = str8_skip_last_slash(str8_cstring_capped(file_name, file_name + sizeof(file_name))); + if(file.size > 0) file.size -= 1; + + B32 no_func = str8_match(func, str8_lit("??"), StringMatchFlag_RightSideSloppy); + B32 no_file = str8_match(file, str8_lit("??"), StringMatchFlag_RightSideSloppy); + if(no_func) { func = str8_zero(); } + if(no_file) { file = str8_zero(); } + + fprintf(stderr, "%ld. [0x%016lx] %.*s%s%.*s %.*s\n", i+1, (unsigned long)ips[i], (int)module.size, module.str, (!no_func || !no_file) ? ", " : "", (int)func.size, func.str, (int)file.size, file.str); + } + pclose(f); + } + else + { + fprintf(stderr, "%ld. [0x%016lx] %s\n", i+1, (unsigned long)ips[i], info.dli_fname); + } + } + fprintf(stderr, "\nVersion: %s%s\n\n", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); + + _exit(1); +} + +int +main(int argc, char **argv) +{ + // install signal handler for the crash call stacks + { + struct sigaction handler = { .sa_sigaction = lnx_signal_handler, .sa_flags = SA_SIGINFO, }; + sigfillset(&handler.sa_mask); + sigaction(SIGILL, &handler, NULL); + sigaction(SIGTRAP, &handler, NULL); + sigaction(SIGABRT, &handler, NULL); + sigaction(SIGFPE, &handler, NULL); + sigaction(SIGBUS, &handler, NULL); + sigaction(SIGSEGV, &handler, NULL); + sigaction(SIGQUIT, &handler, NULL); + } + + //- rjf: set up OS layer + { + //- rjf: get statically-allocated system/process info + { + SystemInfo *info = &os_lnx_state.system_info; + info->logical_processor_count = (U32)get_nprocs(); + info->page_size = (U64)getpagesize(); + info->large_page_size = MB(2); + info->allocation_granularity = info->page_size; + } + { + ProcessInfo *info = &os_lnx_state.process_info; + info->pid = (U32)getpid(); + } + + //- rjf: set up thread context + TCTX *tctx = tctx_alloc(); + tctx_select(tctx); + + //- rjf: set up dynamically allocated state + os_lnx_state.arena = arena_alloc(); + os_lnx_state.entity_arena = arena_alloc(); + pthread_mutex_init(&os_lnx_state.entity_mutex, 0); + + // cache default environment + { + U64 env_count = 0; + for(; __environ[env_count] != 0; env_count += 1) {} + char **default_env = push_array(os_lnx_state.arena, char *, env_count+1); + for EachIndex(idx, env_count) + { + default_env[idx] = (char *)str8_copy(os_lnx_state.arena, str8_cstring(__environ[idx])).str; + } + default_env[env_count] = 0; + os_lnx_state.default_env_count = env_count; + os_lnx_state.default_env = default_env; + } + + //- rjf: grab dynamically allocated system info + { + Temp scratch = scratch_begin(0, 0); + SystemInfo *info = &os_lnx_state.system_info; + + // rjf: get machine name + B32 got_final_result = 0; + U8 *buffer = 0; + int size = 0; + for(S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1) + { + scratch_end(scratch); + buffer = push_array(scratch.arena, U8, cap); + int gethostname_result = gethostname((char*)buffer, cap); + size = cstring8_length(buffer); + if(gethostname_result == 0 && size < cap) + { + got_final_result = 1; + break; + } + } + + // rjf: save name to info + if(got_final_result && size > 0) + { + info->machine_name.size = size; + info->machine_name.str = push_array_no_zero(os_lnx_state.arena, U8, info->machine_name.size + 1); + MemoryCopy(info->machine_name.str, buffer, info->machine_name.size); + info->machine_name.str[info->machine_name.size] = 0; + } + + scratch_end(scratch); + } + + //- rjf: grab dynamically allocated process info + { + Temp scratch = scratch_begin(0, 0); + ProcessInfo *info = &os_lnx_state.process_info; + + // rjf: grab binary path + { + // rjf: get self string + B32 got_final_result = 0; + U8 *buffer = 0; + int size = 0; + for(S64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1) + { + scratch_end(scratch); + buffer = push_array_no_zero(scratch.arena, U8, cap); + size = readlink("/proc/self/exe", (char*)buffer, cap); + if(size < cap) + { + got_final_result = 1; + break; + } + } + + // rjf: save + if(got_final_result && size > 0) + { + String8 full_name = str8(buffer, size); + String8 name_chopped = str8_chop_last_slash(full_name); + info->binary_path = push_str8_copy(os_lnx_state.arena, name_chopped); + } + } + + // rjf: grab initial directory + { + info->initial_path = get_current_path(os_lnx_state.arena); + } + + // rjf: grab home directory + { + char *home = getenv("HOME"); + info->user_program_data_path = str8_cstring(home); + } + + scratch_end(scratch); + } + } + + //- rjf: call into "real" entry point + main_thread_base_entry_point(argc, argv); +} diff --git a/src/linux/base/linux_base.h b/src/linux/base/linux_base.h index 2ba3a846..a4b069da 100644 --- a/src/linux/base/linux_base.h +++ b/src/linux/base/linux_base.h @@ -4,4 +4,154 @@ #ifndef LINUX_BASE_H #define LINUX_BASE_H +//////////////////////////////// +//~ rjf: Includes + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +pid_t gettid(void); +int pthread_setname_np(pthread_t thread, const char *name); +int pthread_getname_np(pthread_t thread, char *name, size_t size); + +typedef struct tm tm; +typedef struct timespec timespec; + +//////////////////////////////// +//~ rjf: Linux Call Interruption Retry Helper + +#define OS_LNX_RETRY_ON_EINTR(expr) \ +(__extension__({ \ +__typeof__(expr) __ret; \ +do { \ +__ret = (expr); \ +} while ((__ret == -1) && errno == EINTR); \ +__ret; \ +})) + + +//////////////////////////////// +//~ rjf: File Iterator + +typedef struct OS_LNX_FileIter OS_LNX_FileIter; +struct OS_LNX_FileIter +{ + DIR *dir; + struct dirent *dp; + String8 path; +}; +StaticAssert(sizeof(Member(FileIter, memory)) >= sizeof(OS_LNX_FileIter), os_lnx_file_iter_size_check); + +//////////////////////////////// +//~ rjf: Safe Call Handler Chain + +typedef struct OS_LNX_SafeCallChain OS_LNX_SafeCallChain; +struct OS_LNX_SafeCallChain +{ + OS_LNX_SafeCallChain *next; + ThreadEntryPointFunctionType *fail_handler; + void *ptr; +}; + +//////////////////////////////// +//~ rjf: Entities + +typedef enum OS_LNX_EntityKind +{ + OS_LNX_EntityKind_Thread, + OS_LNX_EntityKind_Mutex, + OS_LNX_EntityKind_RWMutex, + OS_LNX_EntityKind_ConditionVariable, + OS_LNX_EntityKind_Barrier, +} +OS_LNX_EntityKind; + +typedef struct OS_LNX_Entity OS_LNX_Entity; +struct OS_LNX_Entity +{ + OS_LNX_Entity *next; + OS_LNX_EntityKind kind; + union + { + struct + { + pthread_t handle; + ThreadEntryPointFunctionType *func; + void *ptr; + } thread; + pthread_mutex_t mutex_handle; + pthread_rwlock_t rwmutex_handle; + struct + { + pthread_cond_t cond_handle; + pthread_mutex_t rwlock_mutex_handle; + } cv; + pthread_barrier_t barrier; + }; +}; + +//////////////////////////////// +//~ rjf: State + +typedef struct OS_LNX_State OS_LNX_State; +struct OS_LNX_State +{ + Arena *arena; + SystemInfo system_info; + ProcessInfo process_info; + pthread_mutex_t entity_mutex; + Arena *entity_arena; + OS_LNX_Entity *entity_free; + U64 default_env_count; + char **default_env; +}; + +//////////////////////////////// +//~ rjf: Globals + +global OS_LNX_State os_lnx_state = {0}; +thread_static OS_LNX_SafeCallChain *os_lnx_safe_call_chain = 0; + +//////////////////////////////// +//~ rjf: Helpers + +internal DateTime os_lnx_date_time_from_tm(tm in, U32 msec); +internal tm os_lnx_tm_from_date_time(DateTime dt); +internal timespec os_lnx_timespec_from_date_time(DateTime dt); +internal DenseTime os_lnx_dense_time_from_timespec(timespec in); +internal FileProperties os_lnx_file_properties_from_stat(struct stat *s); +internal void os_lnx_safe_call_sig_handler(int x); + +//////////////////////////////// +//~ rjf: Entities + +internal OS_LNX_Entity *os_lnx_entity_alloc(OS_LNX_EntityKind kind); +internal void os_lnx_entity_release(OS_LNX_Entity *entity); + +//////////////////////////////// +//~ rjf: Thread Entry Point + +internal void *os_lnx_thread_entry_point(void *ptr); + #endif // LINUX_BASE_H diff --git a/src/demon/linux/demon_core_linux.c b/src/linux/demon/linux_demon.c similarity index 87% rename from src/demon/linux/demon_core_linux.c rename to src/linux/demon/linux_demon.c index e5619af0..6c1767dc 100644 --- a/src/demon/linux/demon_core_linux.c +++ b/src/linux/demon/linux_demon.c @@ -1,3159 +1,3147 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ Includes - -#include -#include -#include - -//////////////////////////////// - -internal U64 -dmn_lnx_size_from_fd(int memory_fd, U64 cap) -{ - U8 temp[4096]; - size_t cursor = 0; - while(cursor < cap) - { - ssize_t actual_read = OS_LNX_RETRY_ON_EINTR(pread(memory_fd, temp, sizeof(temp), cursor)); - if(actual_read <= 0) { break; } - cursor += (U64)actual_read; - } - return (U64)cursor; -} - -internal U64 -dmn_lnx_read(int memory_fd, Rng1U64 range, void *dst) -{ - size_t cursor = 0, size = dim_1u64(range); - while(cursor < size) - { - size_t to_read = size - cursor; - ssize_t actual_read = OS_LNX_RETRY_ON_EINTR(pread(memory_fd, (U8 *)dst + cursor, to_read, range.min + cursor)); - if(actual_read < 0) { break; } - if(actual_read == 0) { break; } - cursor += actual_read; - } - return cursor; -} - -internal B32 -dmn_lnx_write(int memory_fd, Rng1U64 range, void *src) -{ - size_t cursor = 0, size = dim_1u64(range); - while(cursor < size) - { - size_t to_write = size - cursor; - ssize_t actual_write = OS_LNX_RETRY_ON_EINTR(pwrite(memory_fd, (U8 *)src + cursor, to_write, range.min + cursor)); - if(actual_write <= 0) { break; } - cursor += actual_write; - } - B32 is_written = (cursor == size); - return is_written; -} - -internal String8 -dmn_lnx_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size) -{ - String8 result = {0}; - U64 string_size = 0; - for(U64 vaddr = base_vaddr; string_size < cap_size; vaddr += 1, string_size += 1) - { - char byte = 0; - if(OS_LNX_RETRY_ON_EINTR(pread(memory_fd, &byte, sizeof(byte), vaddr) <= 0)) - { - break; - } - if(byte == '\0' || byte == '\n') - { - break; - } - } - if(string_size != 0) - { - char *buf = push_array_no_zero(arena, char, string_size+1); - OS_LNX_RETRY_ON_EINTR(pread(memory_fd, buf, string_size, base_vaddr)); - buf[string_size] = '\0'; - result = str8((U8 *)buf, string_size); - } - return result; -} - -internal String8 -dmn_lnx_read_string(Arena *arena, int memory_fd, U64 vaddr) -{ - return dmn_lnx_read_string_capped(arena, memory_fd, vaddr, 4096); -} - -internal -MACHINE_OP_MEM_READ(dmn_lnx_machine_op_mem_read) -{ - U64 read_size = dmn_lnx_read(*(int *)ud, r1u64(addr, addr + buffer_size), buffer); - return read_size == buffer_size ? MachineOpResult_Ok : MachineOpResult_Fail; -} - -internal int -dmn_lnx_ptrace_seize(pid_t pid) -{ - // TODO: PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEFORK - return OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE)); -} - -internal String8 -dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid) -{ - Temp scratch = scratch_begin(&arena, 1); - - String8 exe_link_path = str8f(scratch.arena, "/proc/%d/exe", pid); - String8List parts = {0}; - int readlink_result = 0; - for(S64 r = 0, cap = PATH_MAX; r < 4; cap *= 2, r += 1) - { - U8 *buffer = push_array(arena, U8, cap); - readlink_result = readlink((char *)exe_link_path.str, (char *)buffer, cap); - - if(readlink_result < 0) - { - break; - } - - str8_list_push(scratch.arena, &parts, str8(buffer, readlink_result)); - - if(readlink_result < cap) - { - break; - } - } - - String8 result = str8_list_join(arena, &parts, 0); - scratch_end(scratch); - return result; -} - -internal String8 -dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base) -{ - Temp scratch = scratch_begin(&arena, 1); - - String8 dl_path = {0}; - - int maps_fd = OS_LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/maps", pid).str, O_RDONLY)); - if(maps_fd != -1) - { - // read entire /proc/pid/maps - U64 maps_size = dmn_lnx_size_from_fd(maps_fd, MB(1)); - U8 *maps_ptr = push_array(scratch.arena, U8, maps_size); - U64 read_size = dmn_lnx_read(maps_fd, r1u64(0, maps_size), maps_ptr); - - // split map file on lines - String8List lines = str8_split_by_string_chars(scratch.arena, str8(maps_ptr, maps_size), str8_lit("\n"), 0); - - // scan each line until a virtual mapping whose low part matches the DL base address is found - for EachNode(n, String8Node, lines.first) - { - String8 line = n->string; - - // split the string while respecting escape sequences - String8List parts = {0}; - for EachIndex(cursor, line.size) - { - if(parts.node_count > 5) { break; } - if(line.str[cursor] == ' ') { continue; } - - // scan forward to the closing delimiter - U64 token_start = cursor; - for(; cursor < line.size; cursor += 1) - { - if(line.str[cursor] == '\\') - { - cursor += 1; - continue; - } - if(line.str[cursor] == ' ') { break; } - } - - // push sub-string to the list - str8_list_push(scratch.arena, &parts, str8_substr(line, r1u64(token_start, cursor))); - } - - // was line parsed correctly? - if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; } - - // parse map virtual range - String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); - if(vaddr_list.node_count != 2) { Assert(0 && "failed to parse virtual range portion of map line"); continue; } - - // does the low part match DL base address? - U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16); - if(lo_vaddr == auxv_base) - { - dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; - dl_path = push_str8_copy(arena, dl_path); - break; - } - } - - OS_LNX_RETRY_ON_EINTR(close(maps_fd)); - } - else { Assert(0 && "failed to open DL fd"); } - - scratch_end(scratch); - Assert(dl_path.size); - return dl_path; -} - -internal ELF_Hdr64 -dmn_lnx_ehdr_from_pid(pid_t pid) -{ - Temp scratch = scratch_begin(0, 0); - - ELF_Hdr64 exe = {0}; - B32 is_read = 0; - - char *exe_path = (char *)str8f(scratch.arena, "/proc/%d/exe", pid).str; - int exe_fd = OS_LNX_RETRY_ON_EINTR(open(exe_path, O_RDONLY)); - - if(exe_fd >= 0) - { - is_read = elf_read_ehdr(dmn_lnx_machine_op_mem_read, &exe_fd, 0, &exe); - OS_LNX_RETRY_ON_EINTR(close(exe_fd)); - } - - Assert(is_read); - scratch_end(scratch); - return exe; -} - -internal DMN_LNX_Auxv -dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class) -{ - Temp scratch = scratch_begin(0, 0); - DMN_LNX_Auxv result = {0}; - - // rjf: open aux data - String8 auxv_path = str8f(scratch.arena, "/proc/%d/auxv", pid); - int auxv_fd = OS_LNX_RETRY_ON_EINTR(open((char *)auxv_path.str, O_RDONLY)); - - // rjf: scan aux data - if(auxv_fd >= 0) - { - for(;;) - { - // rjf: read next aux - ELF_Auxv64 auxv = {0}; - switch(elf_class) - { - case ELF_Class_None:{}break; - case ELF_Class_32: - { - ELF_Auxv32 auxv32 = {0}; - if(read(auxv_fd, &auxv32, sizeof(auxv32)) != sizeof(auxv32)) { goto brkloop; } - auxv = elf_auxv64_from_auxv32(auxv32); - }break; - case ELF_Class_64: - { - if(read(auxv_fd, &auxv, sizeof(auxv)) != sizeof(auxv)) { goto brkloop; } - }break; - default:{NotImplemented;}break; - } - - // rjf: fill result - switch(auxv.a_type) - { - default:{}break; - case ELF_AuxType_Null: goto brkloop; break; - case ELF_AuxType_Base: result.base = auxv.a_val; break; - case ELF_AuxType_Phnum: result.phnum = auxv.a_val; break; - case ELF_AuxType_Phent: result.phent = auxv.a_val; break; - case ELF_AuxType_Phdr: result.phdr = auxv.a_val; break; - case ELF_AuxType_ExecFn: result.execfn = auxv.a_val; break; - case ELF_AuxType_Pagesz: result.pagesz = auxv.a_val; break; - } - } - brkloop:; - OS_LNX_RETRY_ON_EINTR(close(auxv_fd)); - } - - scratch_end(scratch); - return result; -} - -internal DMN_LNX_Thread * -dmn_lnx_thread_from_pid(pid_t tid) -{ - return hash_table_search_u64_raw(dmn_lnx_state->tid_ht, tid); -} - -internal DMN_LNX_Process * -dmn_lnx_process_from_pid(pid_t pid) -{ - return hash_table_search_u64_raw(dmn_lnx_state->pid_ht, pid); -} - -internal Rng1U64 -dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) -{ - Rng1U64 result = { .min = max_U64 }; - - for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize) - { - ELF_Phdr64 phdr = {0}; - if(elf_read_phdr(dmn_lnx_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok) - { - Assert(0 && "unable to read a program header"); - } - - if(phdr.p_type == ELF_PType_Load) - { - U64 min = rebase + phdr.p_vaddr; - U64 max = rebase + phdr.p_vaddr + phdr.p_memsz; - result.min = Min(result.min, min); - result.max = Max(result.max, max); - } - } - - return result; -} - -internal DMN_LNX_DynamicInfo -dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_class, U64 rebase, U64 dynamic_vaddr) -{ - DMN_LNX_DynamicInfo dynamic_info = {0}; - for(U64 dynamic_cursor = dynamic_vaddr; ; dynamic_cursor += elf_dyn_size_from_class(elf_class)) - { - // rjf: read next dyn entry - ELF_Dyn64 dyn = {0}; - if(elf_read_dyn(dmn_lnx_machine_op_mem_read, &memory_fd, dynamic_cursor, elf_class, &dyn) != MachineOpResult_Ok) { Assert(0 && "unable to read dynamic"); } - - // rjf: break on zero - if(dyn.tag == ELF_DynTag_Null) { break; } - - // extract reuiqred values out of dynamic section - if(dyn.tag == ELF_DynTag_Strtab) - { - dynamic_info.strtab_vaddr = rebase + dyn.val; - } - else if(dyn.tag == ELF_DynTag_Strsz) - { - dynamic_info.strtab_size = dyn.val; - } - else if(dyn.tag == ELF_DynTag_Symtab) - { - dynamic_info.symtab_vaddr = rebase + dyn.val; - } - else if(dyn.tag == ELF_DynTag_Syment) - { - dynamic_info.symtab_entry_size = dyn.val; - } - else if(dyn.tag == ELF_DynTag_Hash) - { - dynamic_info.hash_vaddr = rebase + dyn.val; - } - else if(dyn.tag == ELF_DynTag_GNU_Hash) - { - dynamic_info.gnu_hash_vaddr = rebase + dyn.val; - } - } - return dynamic_info; -} - -internal U64 -dmn_lnx_find_dynamic_phdr(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) -{ - U64 result = max_U64; - - for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize) - { - ELF_Phdr64 phdr = {0}; - if(elf_read_phdr(dmn_lnx_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok) - { - Assert(0 && "unable to read a program header"); - } - - if(phdr.p_type == ELF_PType_Dynamic) - { - result = rebase + phdr.p_vaddr; - break; - } - } - - return result; -} - -internal U64 -dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase, B32 is_rebased) -{ - Temp scratch = scratch_begin(0, 0); - - U64 rdebug_vaddr = 0; - - // load DL's header - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &memory_fd, loader_vbase, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); goto exit; } - - U64 rebase = ehdr.e_type == ELF_Type_Dyn ? loader_vbase : 0; - ELF_Class elf_class = ehdr.e_ident[ELF_Identifier_Class]; - - // find dynamic program header - U64 phdr_vaddr = loader_vbase + ehdr.e_phoff; - U64 dynamic_vaddr = dmn_lnx_find_dynamic_phdr(memory_fd, elf_class, rebase, phdr_vaddr, ehdr.e_phentsize, ehdr.e_phentsize); - - // extract necessary info out of dynamic program header - U64 dynamic_info_rebase = is_rebased ? 0 : rebase; - DMN_LNX_DynamicInfo dynamic_info = dmn_lnx_dynamic_info_from_memory(memory_fd, elf_class, dynamic_info_rebase, dynamic_vaddr); - - // extract symbol table count from available options - U64 symbol_count = 0; - if(dynamic_info.hash_vaddr) - { - U64 hash_entry_size = 4; - if(elf_class == ELF_Class_64 && (ehdr.e_machine == ELF_MachineKind_ALPHA || ehdr.e_machine == ELF_MachineKind_S390 || ehdr.e_machine == ELF_MachineKind_S390_OLD)) - { - hash_entry_size = 8; - } - - U64 chain_count = 0; - if(dmn_lnx_read(memory_fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size) - { - symbol_count = chain_count; - } - else - { - Assert(0 && "failed to read hash table's chain count out of HASH"); - } - } - else - { - // TODO: extract count from GNU_HASH - NotImplemented; - } - - // scan symbol table for the rendezvous symbol - if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count) - { - for EachIndex(symbol_idx, symbol_count) - { - ELF_Sym64 symbol = {0}; - if(elf_read_symbol(dmn_lnx_machine_op_mem_read, &memory_fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol) != MachineOpResult_Ok) - { - Assert(0 && "failed to read symbol table"); - break; - } - - Temp temp = temp_begin(scratch.arena); - - String8 symbol_name = {0}; - if(symbol.st_name < dynamic_info.strtab_size) - { - U64 cap = dynamic_info.strtab_size - symbol.st_name; - symbol_name = dmn_lnx_read_string_capped(temp.arena, memory_fd, dynamic_info.strtab_vaddr + symbol.st_name, cap); - } - - if(str8_match(symbol_name, str8_lit("_r_debug"), 0)) - { - ELF_SymType symbol_type = ELF_ST_TYPE(symbol.st_info); - if(symbol_type == ELF_SymType_Object && symbol.st_size > 0) - { - rdebug_vaddr = rebase + symbol.st_value; - break; - } - } - - temp_end(temp); - } - } - - exit:; - scratch_end(scratch); - return rdebug_vaddr; -} - -internal DMN_LNX_ProbeList -dmn_lnx_read_probes(Arena *arena, int fd, U64 offset, U64 image_base) -{ - Temp scratch = scratch_begin(&arena, 1); - - DMN_LNX_ProbeList probes = {0}; - - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &fd, offset, &ehdr) != MachineOpResult_Ok) { goto exit; } - - U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; - ELF_Shdr64 strtab_shdr = {0}; - if(elf_read_shdr(dmn_lnx_machine_op_mem_read, &fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr) != MachineOpResult_Ok) { goto exit; } - - B32 found_probes = 0; - B32 found_probes_base = 0; - ELF_Shdr64 text_shdr = {0}; - ELF_Shdr64 stapsdt_base_shdr = {0}; - ELF_Shdr64 stapsdt_shdr = {0}; - for(U64 shdr_off = offset + ehdr.e_shoff, shdr_opl = shdr_off + ehdr.e_shentsize * ehdr.e_shnum; - shdr_off < shdr_opl; - shdr_off += ehdr.e_shentsize) { - ELF_Shdr64 shdr = {0}; - if(elf_read_shdr(dmn_lnx_machine_op_mem_read, &fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr) != MachineOpResult_Ok) { goto exit; } - - if(shdr.sh_type == ELF_ShType_Note) - { - U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; - U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; - String8 name = dmn_lnx_read_string_capped(scratch.arena, fd, name_offset, name_cap); - - if(str8_match(name, str8_lit(".note.stapsdt"), 0)) - { - stapsdt_shdr = shdr; - found_probes = 1; - } - } - else if(shdr.sh_type == ELF_ShType_ProgBits) - { - U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; - U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; - String8 name = dmn_lnx_read_string_capped(scratch.arena, fd, name_offset, name_cap); - - if(str8_match(name, str8_lit(".stapsdt.base"), 0)) - { - stapsdt_base_shdr = shdr; - found_probes_base = 1; - } else if(str8_match(name, str8_lit(".text"), 0)) - { - text_shdr = shdr; - } - } - - if(found_probes && found_probes_base) { break; } - } - - if(!found_probes || !found_probes_base) { goto exit; } - - U64 probes_base = stapsdt_base_shdr.sh_addr; - - Rng1U64 note_range = shift_1u64(r1u64(stapsdt_shdr.sh_offset, stapsdt_shdr.sh_offset + stapsdt_shdr.sh_size), offset); - void *raw_note = push_array(arena, U8, stapsdt_shdr.sh_size); - U64 note_read_size = dmn_lnx_read(fd, note_range, raw_note); - if(note_read_size != dim_1u64(note_range)) { goto exit; } - - Arch arch = arch_from_elf_machine(ehdr.e_machine); - ELF_NoteList note = elf_parse_note(scratch.arena, str8(raw_note, dim_1u64(note_range)), ehdr.e_ident[ELF_Identifier_Class], ehdr.e_machine); - - for EachNode(n, ELF_NoteNode, note.first) - { - ELF_Note *note = &n->v; - if(!str8_match(note->owner, str8_lit("stapsdt"), 0)) { continue; } - if(note->type != ELF_NoteType_STapSdt) { continue; } - - DMN_LNX_Probe probe = {0}; - { - U64 cursor = 0; - U64 addr_size = ehdr.e_ident[ELF_Identifier_Class] == ELF_Class_64 ? 8 : 4; - - U64 pc = 0; - U64 pc_size = str8_deserial_read(note->desc, cursor, &pc, addr_size, addr_size); - if (pc_size == 0) { goto exit; } - cursor += pc_size; - - U64 base_addr = 0; - U64 base_addr_size = str8_deserial_read(note->desc, cursor, &base_addr, addr_size, addr_size); - if (base_addr_size == 0) { goto exit; } - cursor += base_addr_size; - - U64 semaphore = 0; - U64 semaphore_size = str8_deserial_read(note->desc, cursor, &semaphore, addr_size, addr_size); - if (semaphore_size == 0) { goto exit; } - cursor += semaphore_size; - - String8 provider = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += provider.size + 1; - if (cursor > note->desc.size) { goto exit; } - - String8 name = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += name.size + 1; - if (cursor > note->desc.size) { goto exit; } - - String8 args = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); - cursor += args.size + 1; - if (cursor > note->desc.size) { goto exit; } - - U64 probe_rebase = image_base + (base_addr - probes_base); - - probe.provider = provider; - probe.name = name; - probe.args = stap_arg_array_from_string(arena, arch, args); - probe.pc = pc + probe_rebase; - probe.semaphore = semaphore ? semaphore + probe_rebase : 0; - } - - DMN_LNX_ProbeNode *n = push_array(arena, DMN_LNX_ProbeNode, 1); - n->v = probe; - SLLQueuePush(probes.first, probes.last, n); - probes.count += 1; - } - - exit:; - scratch_end(scratch); - return probes; -} - -internal -STAP_MEMORY_READ(dmn_lnx_stap_memory_read) -{ - DMN_LNX_Process *process = raw_ctx; - U64 bytes_read = dmn_lnx_read(process->fd, r1u64(addr, addr + read_size), buffer); - return bytes_read == read_size; -} - -internal DMN_LNX_Entity * -dmn_lnx_entity_alloc(DMN_LNX_EntityKind kind) -{ - DMN_LNX_Entity *entity = dmn_lnx_state->free_entity; - if(entity != 0) - { - SLLStackPop(dmn_lnx_state->free_entity); - } - else - { - entity = push_array(dmn_lnx_state->entities_arena, DMN_LNX_Entity, 1); - dmn_lnx_state->entities_count += 1; - } - U32 gen = entity->gen; - entity->gen += 1; - entity->kind = kind; - return entity; -} - -internal DMN_LNX_Process * -dmn_lnx_process_alloc(pid_t pid, DMN_LNX_ProcessState state, DMN_LNX_Process *parent_process, B32 debug_subprocesses, B32 is_cow) -{ - Temp scratch = scratch_begin(0, 0); - - DMN_LNX_Process *process = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Process)->process; - process->pid = pid; - process->fd = OS_LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/mem", pid).str, O_RDWR)); - process->state = state; - process->debug_subprocesses = debug_subprocesses; - process->is_cow = is_cow; - process->parent_process = parent_process; - - // update pending process tracker - if(state != DMN_LNX_ProcessState_Normal) - { - dmn_lnx_state->process_pending_creation += 1; - } - - // add process to the list - DLLPushBack(dmn_lnx_state->first_process, dmn_lnx_state->last_process, process); - dmn_lnx_state->process_count += 1; - - // push pid -> DMN_LNX_Process mapping - hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->pid_ht, pid, process); - - scratch_end(scratch); - return process; -} - -internal DMN_LNX_ProcessCtx * -dmn_lnx_process_ctx_alloc(DMN_LNX_Process *process, B32 is_rebased) -{ - DMN_LNX_ProcessCtx *ctx = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; - - ELF_Hdr64 exe_ehdr = dmn_lnx_ehdr_from_pid(process->pid); - DMN_LNX_Auxv auxv = dmn_lnx_auxv_from_pid(process->pid, exe_ehdr.e_ident[ELF_Identifier_Class]); - Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); - U64 rdebug_vaddr = dmn_lnx_rdebug_vaddr_from_memory(process->fd, auxv.base, is_rebased); - U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); - U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - Rng1U64 image_vrange = dmn_lnx_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); - Arena *ctx_arena = arena_alloc(); - - ELF_Class dl_class; - { - ELF_Hdr64 ehdr = {0}; - if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &process->fd, auxv.base, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); } - dl_class = ehdr.e_ident[ELF_Identifier_Class]; - } - - // query xsave layout - U64 xcr0 = 0; - U64 xsave_size = 0; - X64_XSaveLayout xsave_layout = {0}; - if(arch == Arch_x64) - { - X64_XSave xsave = {0}; - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, process->pid, (void *)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)) - { - // Linux stores xcr0 bits in fxstate padding, - // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 - xcr0 = *(U64 *)((U8 *)&xsave + 464); - xsave_size = x64_get_xsave_size(); - xsave_layout = x64_get_xsave_layout(xcr0); - } - else { Assert(0 && "failed to get xstate"); } - } - - // gather probes - DMN_LNX_Probe **known_probes = push_array(ctx_arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count); - { - Temp scratch = scratch_begin(0, 0); - - String8 dl_path = dmn_lnx_dl_path_from_pid(scratch.arena, process->pid, auxv.base); - int dl_fd = OS_LNX_RETRY_ON_EINTR(open((char *)dl_path.str, O_RDONLY)); - - DMN_LNX_ProbeList probes = {0}; - if(dl_fd >= 0) - { - probes = dmn_lnx_read_probes(ctx_arena, dl_fd, 0, auxv.base); - OS_LNX_RETRY_ON_EINTR(close(dl_fd)); - } - - for EachNode(n, DMN_LNX_ProbeNode, probes.first) - { - DMN_LNX_Probe *p = &n->v; - if(str8_match(p->provider, str8_lit("rtld"), 0)) - { -#define X(_N,_A,_S) if(str8_match(p->name, str8_lit(_S), 0)) { AssertAlways(p->args.count == _A); known_probes[DMN_LNX_ProbeType_##_N] = p; continue ; } - DMN_LNX_Probe_XList -#undef X - } - } - - scratch_end(scratch); - } - - ctx = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; - ctx->arena = arena_alloc(); - ctx->arch = arch; - ctx->rdebug_vaddr = rdebug_vaddr; - ctx->dl_class = dl_class; - ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000); - ctx->probes = known_probes; - ctx->xcr0 = xcr0; - ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); - ctx->xsave_layout = xsave_layout; - - // create main module - DMN_LNX_Module *main_module = dmn_lnx_module_alloc(ctx, process->fd, base_vaddr, auxv.execfn, 1, 1); - - // glibc has a shortcut mapping for the main module - hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); - - return ctx; -} - -internal DMN_LNX_Thread * -dmn_lnx_thread_alloc(DMN_LNX_Process *process, DMN_LNX_ThreadState thread_state, pid_t tid) -{ - void *reg_block; - if(process->ctx->free_reg_blocks.node_count) - { - String8Node *n = str8_list_pop_front(&process->ctx->free_reg_blocks); - reg_block = n->string.str; - str8_list_push_node(&process->ctx->free_reg_block_nodes, n); - } - else - { - ARCH_Info *arch_info = arch_info_from_arch(process->ctx->arch); - U64 reg_block_size = arch_info->reg_block_size; - reg_block = push_array(process->ctx->arena, U8, reg_block_size); - } - - DMN_LNX_Thread *thread = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Thread)->thread; - thread->tid = tid; - thread->state = thread_state; - thread->process = process; - thread->reg_block = reg_block; - if(thread_state == DMN_LNX_ThreadState_Stopped) - { - thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread); - } - - // add thread to the list - DLLPushBack(process->first_thread, process->last_thread, thread); - process->thread_count += 1; - - // push tid -> thread mapping - hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->tid_ht, thread->tid, thread); - - // update global thread counter - if(thread_state == DMN_LNX_ThreadState_PendingCreation) - { - dmn_lnx_state->threads_pending_creation += 1; - } - - return thread; -} - -internal DMN_LNX_Module * -dmn_lnx_module_alloc(DMN_LNX_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main) -{ - DMN_LNX_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, base_vaddr); - if(module) { goto exit; } - - // parse out module's ELF header - ELF_Hdr64 module_ehdr = {0}; - if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &memory_fd, base_vaddr, &module_ehdr) != MachineOpResult_Ok) { goto exit; } - - // gather info about module - U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; - U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; - Rng1U64 module_vrange = dmn_lnx_compute_image_vrange(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); - - // read TLS index and TLS offset - U64 tls_index = max_U64; - U64 tls_offset = max_U64; - if(is_main) - { - tls_index = 1; - tls_offset = 0; - } - else - { - if(dmn_lnx_state->is_tls_detected) - { - Rng1U64 tls_modid_range = r1u64(dmn_lnx_state->tls_modid_desc.offset, dmn_lnx_state->tls_modid_desc.offset + dmn_lnx_state->tls_modid_desc.bit_size / 8); - Rng1U64 tls_offset_range = r1u64(dmn_lnx_state->tls_offset_desc.offset, dmn_lnx_state->tls_offset_desc.offset + dmn_lnx_state->tls_offset_desc.bit_size / 8); - tls_modid_range = shift_1u64(tls_modid_range, base_vaddr); - tls_offset_range = shift_1u64(tls_offset_range, base_vaddr); - if(!dmn_lnx_read(memory_fd, tls_modid_range, &tls_index)) { Assert(0 && "failed to read TLS index"); } - if(!dmn_lnx_read(memory_fd, tls_offset_range, &tls_offset)) { Assert(0 && "failed to read TLS offset"); } - } - } - - module = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Module)->module; - module->base_vaddr = base_vaddr; - module->name_vaddr = name_vaddr; - module->name_space_id = name_space_id; - module->size = dim_1u64(module_vrange); - module->phvaddr = base_vaddr + module_ehdr.e_phoff; - module->phcount = module_ehdr.e_phnum; - module->phentsize = module_ehdr.e_phentsize; - module->tls_index = tls_index; - module->tls_offset = tls_offset; - module->is_main = is_main; - - // add module to the list - DLLPushBack(ctx->first_module, ctx->last_module, module); - ctx->module_count += 1; - - // push base address -> module mapping - hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, base_vaddr, module); - - exit:; - return module; -} - -internal void -dmn_lnx_entity_release(DMN_LNX_Entity *entity) -{ - U32 gen = entity->gen + 1; - MemoryZeroStruct(entity); - entity->gen = gen; - SLLStackPush(dmn_lnx_state->free_entity, entity); -} - -internal void -dmn_lnx_process_release(DMN_LNX_Process *process) -{ - // update global state - AssertAlways(dmn_lnx_state->process_count > 0); - DLLRemove(dmn_lnx_state->first_process, dmn_lnx_state->last_process, process); - dmn_lnx_state->process_count -= 1; - - // update pending process tracker - if(process->state != DMN_LNX_ProcessState_Normal) - { - Assert(dmn_lnx_state->process_pending_creation > 0); - dmn_lnx_state->process_pending_creation -= 1; - } - - // close memory handle - if(OS_LNX_RETRY_ON_EINTR(close(process->fd)) < 0) { Assert(0 && "failed to close memory descriptor"); } - - // remove pid mapping - hash_table_purge_u64(dmn_lnx_state->pid_ht, process->pid); - - // release the context - if(process->ctx) - { - dmn_lnx_process_ctx_release(process->ctx); - } - - // release process entity - dmn_lnx_entity_release((DMN_LNX_Entity *)process); -} - -internal void -dmn_lnx_process_ctx_release(DMN_LNX_ProcessCtx *ctx) -{ - Assert(ctx->ref_count > 0); - ctx->ref_count -= 1; - - if(ctx->ref_count == 0) - { - arena_release(ctx->arena); - dmn_lnx_entity_release((DMN_LNX_Entity *)ctx); - } -} - -internal void -dmn_lnx_thread_release(DMN_LNX_Thread *thread) -{ - DMN_LNX_Process *process = thread->process; - - // purge tid mapping - hash_table_purge_u64(dmn_lnx_state->tid_ht, thread->tid); - - // update global thread counter - if(thread->state == DMN_LNX_ThreadState_PendingCreation) - { - AssertAlways(dmn_lnx_state->threads_pending_creation > 0); - dmn_lnx_state->threads_pending_creation -= 1; - } - - // remove thread from the list - Assert(process->thread_count > 0); - DLLRemove(process->first_thread, process->last_thread, thread); - process->thread_count -= 1; - - // push reg block to the free list - String8Node *reg_block_node; - if(process->ctx->free_reg_block_nodes.node_count) - { - reg_block_node = str8_list_pop_front(&process->ctx->free_reg_block_nodes); - } - else - { - reg_block_node = push_array(process->ctx->arena, String8Node, 1); - } - reg_block_node->string = str8(thread->reg_block, 0); - str8_list_push_node(&process->ctx->free_reg_blocks, reg_block_node); - - dmn_lnx_entity_release((DMN_LNX_Entity *)thread); -} - -internal void -dmn_lnx_module_release(DMN_LNX_ProcessCtx *ctx, DMN_LNX_Module *module) -{ - // remove module from the list - Assert(ctx->module_count > 0); - DLLRemove(ctx->first_module, ctx->last_module, module); - ctx->module_count -= 1; - - // purge base addr -> module mapping - hash_table_purge_u64(ctx->loaded_modules_ht, module->base_vaddr); - - dmn_lnx_entity_release((DMN_LNX_Entity *)module); -} - -internal DMN_LNX_ProcessCtx * -dmn_lnx_process_ctx_clone(DMN_LNX_Process *new_owner, DMN_LNX_ProcessCtx *ctx) -{ - DMN_LNX_ProcessCtx *result = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; - - result->arena = arena_alloc(); - result->arch = ctx->arch; - result->rdebug_vaddr = ctx->rdebug_vaddr; - result->dl_class = ctx->dl_class; - result->loaded_modules_ht = hash_table_init(result->arena, ctx->loaded_modules_ht->cap); - result->xcr0 = ctx->xcr0; - result->xsave_size = ctx->xsave_size; - result->xsave_layout = ctx->xsave_layout; - - // clone probes - result->probes = push_array(result->arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count); - for EachIndex(probe_idx, DMN_LNX_ProbeType_Count) - { - DMN_LNX_Probe *dst = result->probes[probe_idx]; - DMN_LNX_Probe *src = ctx->probes[probe_idx]; - - dst->provider = str8_copy(result->arena, src->provider); - dst->name = str8_copy(result->arena, src->name); - dst->args_string = str8_copy(result->arena, src->args_string); - dst->args = stap_arg_array_copy(result->arena, src->args); - dst->pc = src->pc; - dst->semaphore = src->semaphore; - } - - // clone probe traps - for EachNode(src, DMN_ActiveTrap, ctx->first_probe_trap) - { - DMN_Trap *src_trap = src->trap; - DMN_Trap *dst_trap = push_array(result->arena, DMN_Trap, 1); - dst_trap->process = dmn_lnx_handle_from_process(new_owner); - dst_trap->vaddr = src_trap->vaddr; - dst_trap->id = src_trap->id; - dst_trap->flags = src_trap->flags; - dst_trap->size = src_trap->size; - - DMN_ActiveTrap *dst = push_array(result->arena, DMN_ActiveTrap, 1); - dst->trap = dst_trap; - dst->swap_bytes = str8_copy(result->arena, src->swap_bytes); - - SLLQueuePush(result->first_probe_trap, result->last_probe_trap, dst); - } - - // clone modules - for EachNode(module, DMN_LNX_Module, ctx->first_module) - { - dmn_lnx_module_clone(result, module); - } - - return result; -} - -internal DMN_LNX_Module * -dmn_lnx_module_clone(DMN_LNX_ProcessCtx *process_ctx, DMN_LNX_Module *module) -{ - DMN_LNX_Module *result = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Module)->module; - *result = *module; - result->next = result->prev = 0; - - // clone base addr mapping - hash_table_push_u64_raw(process_ctx->arena, process_ctx->loaded_modules_ht, result->base_vaddr, result); - - // push module to the list - DLLPushBack(process_ctx->first_module, process_ctx->last_module, module); - process_ctx->module_count += 1; - - return result; -} - -internal DMN_Handle -dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity) -{ - DMN_Handle handle = {0}; - U64 index = IntFromPtr(entity - dmn_lnx_state->entities_base); - if(index <= max_U32) - { - handle.u32[0] = index; - handle.u32[1] = entity->gen; - } - else - { - Assert(0 && "failed to make a handle for the entity"); - } - return handle; -} - -internal DMN_Handle -dmn_lnx_handle_from_process(DMN_LNX_Process *process) -{ - return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)process); -} - -internal DMN_Handle -dmn_lnx_handle_from_process_ctx(DMN_LNX_ProcessCtx *process_ctx) -{ - return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)process_ctx); -} - -internal DMN_Handle -dmn_lnx_handle_from_thread(DMN_LNX_Thread *thread) -{ - return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)thread); -} - -internal DMN_Handle -dmn_lnx_handle_from_module(DMN_LNX_Module *module) -{ - return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)module); -} - -internal DMN_LNX_Entity * -dmn_lnx_entity_from_handle(DMN_Handle handle, DMN_LNX_EntityKind expected_kind) -{ - DMN_LNX_Entity *result = 0; - U32 index = handle.u32[0]; - U32 gen = handle.u32[1]; - if(index < dmn_lnx_state->entities_count && dmn_lnx_state->entities_base[index].gen == gen) - { - if(dmn_lnx_state->entities_base[index].kind == expected_kind) - { - result = &dmn_lnx_state->entities_base[index]; - } - } - return result; -} - -internal DMN_LNX_Process * -dmn_lnx_process_from_handle(DMN_Handle process_handle) -{ - return (DMN_LNX_Process *)dmn_lnx_entity_from_handle(process_handle, DMN_LNX_EntityKind_Process); -} - -internal DMN_LNX_ProcessCtx * -dmn_lnx_process_ctx_from_handle(DMN_Handle process_ctx_handle) -{ - return (DMN_LNX_ProcessCtx *)dmn_lnx_entity_from_handle(process_ctx_handle, DMN_LNX_EntityKind_ProcessCtx); -} - -internal DMN_LNX_Thread * -dmn_lnx_thread_from_handle(DMN_Handle thread_handle) -{ - return (DMN_LNX_Thread *)dmn_lnx_entity_from_handle(thread_handle, DMN_LNX_EntityKind_Thread); -} - -internal DMN_LNX_Module * -dmn_lnx_module_from_handle(DMN_Handle module_handle) -{ - return (DMN_LNX_Module *)dmn_lnx_entity_from_handle(module_handle, DMN_LNX_EntityKind_Module); -} - -internal void -dmn_lnx_process_trap_probes(DMN_LNX_Process *process) -{ - for EachIndex(i, DMN_LNX_ProbeType_Count) - { - if(process->ctx->probes[i] == 0) { continue; } - - DMN_Trap *trap = push_array(process->ctx->arena, DMN_Trap, 1); - trap->process = dmn_lnx_handle_from_process(process); - trap->vaddr = process->ctx->probes[i]->pc; - trap->id = i; - - DMN_ActiveTrap *active_trap = dmn_set_trap(process->ctx->arena, trap); - SLLQueuePush(process->ctx->first_probe_trap, process->ctx->last_probe_trap, active_trap); - - if(BUILD_DEBUG && process->ctx->arch == Arch_x64) - { - Assert(active_trap->swap_bytes.size == 1 && active_trap->swap_bytes.str[0] == 0x90); - } - } -} - -internal void -dmn_lnx_process_untrap_probes(DMN_LNX_Process *process) -{ - NotImplemented; -} - -internal U64 -dmn_lnx_thread_read_ip(DMN_LNX_Thread *thread) -{ - ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); - U64 result = arch_ip_from_reg_block(arch_info, thread->reg_block); - return result; -} - -internal U64 -dmn_lnx_thread_read_sp(DMN_LNX_Thread *thread) -{ - ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); - U64 result = arch_sp_from_reg_block(arch_info, thread->reg_block); - return result; -} - -internal void -dmn_lnx_thread_write_ip(DMN_LNX_Thread *thread, U64 ip) -{ - ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); - arch_reg_block_write_ip(arch_info, thread->reg_block, ip); - thread->is_reg_block_dirty = 1; -} - -internal void -dmn_lnx_thread_write_sp(DMN_LNX_Thread *thread, U64 sp) -{ - ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); - arch_reg_block_write_sp(arch_info, thread->reg_block, sp); - thread->is_reg_block_dirty = 1; -} - -internal B32 -dmn_lnx_thread_read_reg_block(DMN_LNX_Thread *thread) -{ - AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); - - B32 is_reg_block_read = 0; - - switch(thread->process->ctx->arch) - { - case Arch_Null: {} break; - case Arch_x64: - { - DMN_LNX_ProcessCtx *process_ctx = thread->process->ctx; - X64_RegBlock *dst = thread->reg_block; - - // general purpose registers - { - OS_LNX_GprsX64 src; - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_len = sizeof(src), .iov_base = &src })); - if(ptrace_result < 0) { goto exit; } - - dst->r15.u64 = src.r15; - dst->r14.u64 = src.r14; - dst->r13.u64 = src.r13; - dst->r12.u64 = src.r12; - dst->rbp.u64 = src.rbp; - dst->rbx.u64 = src.rbx; - dst->r11.u64 = src.r11; - dst->r10.u64 = src.r10; - dst->r9.u64 = src.r9; - dst->r8.u64 = src.r8; - dst->rax.u64 = src.rax; - dst->rcx.u64 = src.rcx; - dst->rdx.u64 = src.rdx; - dst->rsi.u64 = src.rsi; - dst->rdi.u64 = src.rdi; - dst->rip.u64 = src.rip; - dst->cs.u16 = src.cs; - dst->rflags.u64 = src.rflags; - dst->rsp.u64 = src.rsp; - dst->ss.u16 = src.ss; - dst->fsbase.u64 = src.fsbase; - dst->gsbase.u64 = src.gsbase; - dst->ds.u16 = src.ds; - dst->es.u16 = src.es; - dst->fs.u16 = src.fs; - dst->gs.u16 = src.gs; - thread->orig_rax = src.orig_rax; - } - - // xsave - { - Temp scratch = scratch_begin(0, 0); - - X64_XSave *xsave = 0; - X64_FXSave *fxsave = 0; - - // get xsave - if(x64_is_xsave_supported()) - { - void *xsave_raw = push_array(scratch.arena, U8, process_ctx->xsave_size); - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_len = process_ctx->xsave_size, .iov_base = xsave_raw })); - if(ptrace_result < 0) { goto exit; } - - xsave = xsave_raw; - fxsave = &xsave->fxsave; - } - - // get fxsave - if(fxsave == 0) - { - fxsave = push_array(scratch.arena, X64_FXSave, 1); - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_len = sizeof(*fxsave), .iov_base = fxsave })); - if(ptrace_result < 0) { goto exit; } - - fxsave = 0; - } - - // copy fxsave registers - if(fxsave) - { - X64_FXSave *src = fxsave; - - // copy x87 registers - dst->fcw.u16 = src->fcw; - dst->fsw.u16 = src->fsw; - dst->ftw = src->ftw; - dst->fop.u16 = src->fop; - dst->fip.u64 = src->fip; - dst->fdp.u64 = src->fdp; - dst->mxcsr.u32 = src->mxcsr; - dst->mxcsr_mask.u32 = src->mxcsr_mask; - for EachIndex(i, 8) - { - MemoryCopy(&dst->st0 + i, src->st_space + i, sizeof(REGS_Reg80)); - } - - // SSE registers are always available in x64 - { - U128 *xmm_d = fxsave->xmm_space; - REGS_Reg512 *zmm_s = &dst->zmm0; - for EachIndex(i, 16) - { - MemoryCopy(&zmm_s[i], &xmm_d[i], sizeof(*xmm_d)); - } - } - } - - // copy xsave registers - if(xsave) - { - // compact register layout is not supported - AssertAlways(xsave->header.xcomp_bv == 0); - - if(xsave->header.xstate_bv & X64_XStateComponentFlag_AVX) - { - AssertAlways(process_ctx->xsave_layout.avx_offset + 16*sizeof(REGS_Reg128) <= process_ctx->xsave_size); - REGS_Reg128 *avx_s = (REGS_Reg128 *)((U8 *)xsave + process_ctx->xsave_layout.avx_offset); - REGS_Reg512 *zmm_d = &dst->zmm0; - for EachIndex(n, 16) - { - MemoryCopy(&zmm_d[n].v[16], &avx_s[n], sizeof(REGS_Reg128)); - } - } - - if(xsave->header.xstate_bv & X64_XStateComponentFlag_OPMASK) - { - AssertAlways(process_ctx->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process_ctx->xsave_size); - REGS_Reg64 *kmask_s = (REGS_Reg64 *)((U8 *)xsave + process_ctx->xsave_layout.opmask_offset); - REGS_Reg64 *kmask_d = &dst->k0; - for EachIndex(n, 8) - { - MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(REGS_Reg64)); - } - } - - if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM_H) - { - AssertAlways(process_ctx->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process_ctx->xsave_size); - REGS_Reg256 *avx512h_s = (REGS_Reg256 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_h_offset); - REGS_Reg512 *zmmh_d = &dst->zmm0; - for EachIndex(n, 16) - { - MemoryCopy(&zmmh_d[n].v[32], &avx512h_s[n], sizeof(REGS_Reg256)); - } - } - - if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM) - { - AssertAlways(process_ctx->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process_ctx->xsave_size); - REGS_Reg512 *avx512_s = (REGS_Reg512 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_offset); - REGS_Reg512 *zmm_d = &dst->zmm16; - for EachIndex(n, 16) - { - MemoryCopy(&zmm_d[n], &avx512_s[n], sizeof(REGS_Reg512)); - } - } - - if(xsave->header.xstate_bv & X64_XStateComponentFlag_CETU) - { - AssertAlways(process_ctx->xsave_layout.cet_u_offset + sizeof(U64)*2 <= process_ctx->xsave_size); - U64 *cet_u = (U64 *)((U8 *)xsave + process_ctx->xsave_layout.cet_u_offset); - dst->cetmsr.u64 = cet_u[0]; - dst->cetssp.u64 = cet_u[1]; - } - } - - scratch_end(scratch); - } - - // debug registers - { - REGS_Reg64 *dr_d = &dst->dr0; - for EachIndex(n, 8) - { - if(n != 4 && n != 5) - { - U64 offset = OffsetOf(OS_LNX_UserX64, u_debugreg[n]); - errno = 0; - long peek_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_PEEKUSER, thread->tid, PtrFromInt(offset), 0)); - if(errno != 0) { goto exit; } - dr_d[n].u64 = peek_result; - } - } - } - - is_reg_block_read = 1; - } break; - case Arch_x86: - case Arch_arm64: - case Arch_arm32: { NotImplemented; } break; - default: { InvalidPath; } break; - } - - exit:; - return is_reg_block_read; -} - -internal B32 -dmn_lnx_thread_write_reg_block(DMN_LNX_Thread *thread) -{ - AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); - - B32 is_reg_block_written = 0; - - switch(thread->process->ctx->arch) - { - case Arch_Null: {} break; - case Arch_x64: - { - DMN_LNX_ProcessCtx *process_ctx = thread->process->ctx; - X64_RegBlock *src = thread->reg_block; - - // general purpose registers - { - OS_LNX_GprsX64 dst; - dst.r15 = src->r15.u64; - dst.r14 = src->r14.u64; - dst.r13 = src->r13.u64; - dst.r12 = src->r12.u64; - dst.rbp = src->rbp.u64; - dst.rbx = src->rbx.u64; - dst.r11 = src->r11.u64; - dst.r10 = src->r10.u64; - dst.r9 = src->r9.u64; - dst.r8 = src->r8.u64; - dst.rax = src->rax.u64; - dst.rcx = src->rcx.u64; - dst.rdx = src->rdx.u64; - dst.rsi = src->rsi.u64; - dst.rdi = src->rdi.u64; - dst.orig_rax = thread->orig_rax; - dst.rip = src->rip.u64; - dst.cs = src->cs.u16; - dst.rflags = src->rflags.u64; - dst.rsp = src->rsp.u64; - dst.ss = src->ss.u16; - dst.fsbase = src->fsbase.u64; - dst.gsbase = src->gsbase.u64; - dst.ds = src->ds.u16; - dst.es = src->es.u16; - dst.fs = src->fs.u16; - dst.gs = src->gs.u16; - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = &dst, .iov_len = sizeof(dst) })); - if(ptrace_result < 0) { goto exit; } - } - - // xsave - { - Temp scratch = scratch_begin(0, 0); - - X64_FXSave dst_fxsave = {0}; - { - dst_fxsave.fcw = src->fcw.u16; - dst_fxsave.fsw = src->fsw.u16; - dst_fxsave.ftw = src->ftw; - dst_fxsave.fop = src->fop.u16; - dst_fxsave.fip = src->fip.u64; - dst_fxsave.fdp = src->fdp.u64; - dst_fxsave.mxcsr = src->mxcsr.u32; - dst_fxsave.mxcsr_mask = src->mxcsr_mask.u32; - - REGS_Reg128 *st_d = (REGS_Reg128 *)dst_fxsave.st_space; - REGS_Reg80 *st_s = &src->st0; - for EachIndex(n, 8) - { - MemoryCopy(&st_d[n], &st_s[n], sizeof(REGS_Reg80)); - } - - REGS_Reg128 *xmm_d = (REGS_Reg128 *)dst_fxsave.xmm_space; - REGS_Reg512 *xmm_s = &src->zmm0; - for EachIndex(n, 16) - { - MemoryCopy(&xmm_d[n], &xmm_s[n], sizeof(REGS_Reg128)); - } - } - - if(x64_is_xsave_supported()) - { - U8 *xsave_raw = push_array(scratch.arena, U8, process_ctx->xsave_size); - X64_XSave *dst = (X64_XSave *)xsave_raw; - dst->fxsave = dst_fxsave; - dst->header.xstate_bv |= X64_XStateComponentFlag_FP; - dst->header.xstate_bv |= X64_XStateComponentFlag_SSE; - - if(process_ctx->xsave_layout.avx_offset) - { - if(process_ctx->xsave_layout.avx_offset + sizeof(REGS_Reg128) * 16 <= process_ctx->xsave_size) - { - REGS_Reg128 *avx_d = (REGS_Reg128 *)(xsave_raw + process_ctx->xsave_layout.avx_offset); - REGS_Reg512 *zmm_s = &src->zmm0; - for EachIndex(n, 16) - { - MemoryCopy(&avx_d[n], &zmm_s[n].v[16], sizeof(REGS_Reg128)); - } - dst->header.xstate_bv |= X64_XStateComponentFlag_AVX; - } - } - - if(process_ctx->xsave_layout.opmask_offset) - { - if(process_ctx->xsave_layout.opmask_offset + sizeof(REGS_Reg64) * 8 <= process_ctx->xsave_size) - { - REGS_Reg64 *kmask_d = (REGS_Reg64 *)(xsave_raw + process_ctx->xsave_layout.opmask_offset); - REGS_Reg64 *kmask_s = &src->k0; - for EachIndex(n, 8) - { - MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(REGS_Reg64)); - } - dst->header.xstate_bv |= X64_XStateComponentFlag_OPMASK; - } - else { Assert(0 && "invalid xsave size"); goto exit; } - } - - if(process_ctx->xsave_layout.zmm_h_offset) - { - if(process_ctx->xsave_layout.zmm_h_offset + sizeof(REGS_Reg256) * 16 <= process_ctx->xsave_size) - { - REGS_Reg256 *avx512h_d = (REGS_Reg256 *)(xsave_raw + process_ctx->xsave_layout.zmm_h_offset); - REGS_Reg512 *zmmh_s = &src->zmm0; - for EachIndex(n, 16) - { - MemoryCopy(&avx512h_d[n], &zmmh_s[n].v[32], sizeof(REGS_Reg256)); - } - dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM_H; - } - else { Assert(0 && "invalid xsave size"); goto exit; } - } - - if(process_ctx->xsave_layout.zmm_offset) - { - if(process_ctx->xsave_layout.zmm_offset + sizeof(REGS_Reg512) * 16 <= process_ctx->xsave_size) - { - REGS_Reg512 *avx512_d = (REGS_Reg512 *)(xsave_raw + process_ctx->xsave_layout.zmm_offset); - REGS_Reg512 *zmm_s = &src->zmm16; - for EachIndex(n, 16) - { - MemoryCopy(&avx512_d[n], &zmm_s[n], sizeof(REGS_Reg512)); - } - dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM; - } - else { Assert(0 && "invalid xsave size"); goto exit; } - } - - if(process_ctx->xsave_layout.cet_u_offset) - { - if(process_ctx->xsave_layout.cet_u_offset + sizeof(REGS_Reg64) * 2 <= process_ctx->xsave_size) - { - REGS_Reg64 *cet_u = (REGS_Reg64 *)(xsave_raw + process_ctx->xsave_layout.cet_u_offset); - cet_u[0] = src->cetmsr; - cet_u[1] = src->cetssp; - dst->header.xstate_bv |= X64_XStateComponentFlag_CETU; - } - else { Assert(0 && "invalid xsave size"); goto exit; } - } - - // xsave - Assert(dst->header.xcomp_bv == 0); // must always be zero - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_base = dst, .iov_len = process_ctx->xsave_size })); - if(ptrace_result < 0) { goto exit; } - } - - scratch_end(scratch); - } - - // debug registers - { - src->dr7.u64 |= (1 << 10); - - REGS_Reg64 *dr_s = &src->dr0; - for EachIndex(n, 8) - { - if(n != 4 && n != 5) - { - U64 offset = OffsetOf(OS_LNX_UserX64, u_debugreg[n]); - int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_POKEUSER, thread->tid, PtrFromInt(offset), (void*)(uintptr_t)dr_s[n].u64)); - if(ptrace_result < 0) { goto exit; } - } - } - } - - is_reg_block_written = 1; - } break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: { NotImplemented; }break; - default: { InvalidPath; } break; - } - - exit:; - return is_reg_block_written; -} - -internal B32 -dmn_lnx_set_single_step_flag(DMN_LNX_Thread *thread, B32 is_on) -{ - B32 is_flag_set = 0; - switch(thread->process->ctx->arch) - { - case Arch_Null: {} break; - case Arch_x64: - { - X64_RegBlock *reg_block = thread->reg_block; - if(is_on) { reg_block->rflags.u64 |= X64_RFlag_Trap; } - else { reg_block->rflags.u64 &= ~X64_RFlag_Trap; } - thread->is_reg_block_dirty = 1; - is_flag_set = 1; - } break; - case Arch_x86: - case Arch_arm32: - case Arch_arm64: { NotImplemented; }break; - default: { InvalidPath; } break; - } - Assert(is_flag_set); - return is_flag_set; -} - -internal void -dmn_lnx_thread_ptr_list_push_node(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n) -{ - DLLPushBack(list->first, list->last, n); - list->count += 1; -} - -internal DMN_LNX_ThreadPtrNode * -dmn_lnx_thread_ptr_list_push(Arena *arena, DMN_LNX_ThreadPtrList *list, DMN_LNX_Thread *v) -{ - DMN_LNX_ThreadPtrNode *n = push_array(arena, DMN_LNX_ThreadPtrNode, 1); - n->v = v; - dmn_lnx_thread_ptr_list_push_node(list, n); - return n; -} - -internal void -dmn_lnx_thread_ptr_list_remove(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n) -{ - DLLRemove(list->first, list->last, n); - list->count -= 1; -} - -internal DMN_LNX_ModulePtrNode * -dmn_lnx_module_ptr_list_push(Arena *arena, DMN_LNX_ModulePtrList *list, DMN_LNX_Module *v) -{ - DMN_LNX_ModulePtrNode *n = push_array(arena, DMN_LNX_ModulePtrNode, 1); - n->v = v; - SLLQueuePush(list->first, list->last, n); - list->count += 1; - return n; -} - -internal DMN_LNX_ProcessPtrNode * -dmn_lnx_process_ptr_list_push(Arena *arena, DMN_LNX_ProcessPtrList *list, DMN_LNX_Process *v) -{ - DMN_LNX_ProcessPtrNode *n = push_array(arena, DMN_LNX_ProcessPtrNode, 1); - n->v = v; - SLLQueuePush(list->first, list->last, n); - list->count += 1; - return n; -} - -internal void -dmn_lnx_push_event_create_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process) -{ - // push create process event - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_CreateProcess; - e->process = dmn_lnx_handle_from_process(process); - e->arch = process->ctx->arch; - e->code = process->pid; - e->tls_model = DMN_TlsModel_Gnu; // TODO: use dynamic linker path to figure out correct enum here -} - -internal void -dmn_lnx_push_event_exit_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_ExitProcess; - e->process = dmn_lnx_handle_from_process(process); - e->code = process->main_thread_exit_code; -} - -internal void -dmn_lnx_push_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_CreateThread; - e->process = dmn_lnx_handle_from_process(thread->process); - e->thread = dmn_lnx_handle_from_thread(thread); - e->arch = thread->process->ctx->arch; - e->code = thread->tid; -} - -internal void -dmn_lnx_push_event_exit_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 exit_code) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_ExitThread; - e->process = dmn_lnx_handle_from_process(thread->process); - e->thread = dmn_lnx_handle_from_thread(thread); - e->code = exit_code; -} - -internal void -dmn_lnx_push_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, DMN_LNX_Module *module) -{ - // TODO: reporting this module breaks ctrl thread - String8 module_name = dmn_lnx_read_string(arena, thread->process->fd, module->name_vaddr); - if(!str8_match(module_name, str8_lit("linux-vdso.so.1"), 0)) - { - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_LoadModule; - e->process = dmn_lnx_handle_from_process(thread->process); - e->thread = dmn_lnx_handle_from_thread(thread); - e->module = dmn_lnx_handle_from_module(module); - e->arch = thread->process->ctx->arch; - e->address = module->base_vaddr; - e->size = module->size; - e->string = module_name; - e->elf_phdr_vrange = r1u64(module->phvaddr, module->phvaddr + module->phentsize * module->phcount); - e->elf_phdr_entsize = module->phentsize; - e->tls_index = module->tls_index; - e->tls_offset = module->tls_offset; - } -} - -internal void -dmn_lnx_push_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, DMN_LNX_Module *module) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_UnloadModule; - e->process = dmn_lnx_handle_from_process(process); - e->module = dmn_lnx_handle_from_module(module); -} - -internal void -dmn_lnx_push_event_handshake_complete(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_HandshakeComplete; - e->process = dmn_lnx_handle_from_process(process); - e->thread = dmn_lnx_handle_from_thread(process->first_thread); - e->arch = process->ctx->arch; -} - -internal void -dmn_lnx_push_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 address) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Breakpoint; - e->process = dmn_lnx_handle_from_process(thread->process); - e->thread = dmn_lnx_handle_from_thread(thread); - e->instruction_pointer = address; -} - -internal void -dmn_lnx_push_event_single_step(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_SingleStep; - e->process = dmn_lnx_handle_from_process(thread->process); - e->thread = dmn_lnx_handle_from_thread(thread); - e->instruction_pointer = dmn_lnx_thread_read_ip(thread); -} - -internal void -dmn_lnx_push_event_exception(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 signo) -{ - local_persist B8 is_repeatable[] = - { - 0, // null - 1, // 1 SIGHUP - 1, // 2 SIGINT - 1, // 3 SIGQUIT - 1, // 4 SIGTRAP - 1, // 5 SIGABRT - 1, // 6 SIGIOT - 1, // 7 SIGBUS - 1, // 8 SIGFPE - 1, // 9 SIGKILL - 1, // 10 SIGUSR1 - 1, // 11 SIGSEGV - 1, // 12 SIGUSR2 - 1, // 13 SIGPIPE - 1, // 14 SIGALRM - 1, // 15 SIGTERM - 1, // 16 SIGTKFLT - 0, // 17 SIGCHLD - 0, // 18 SIGCONT - 1, // 19 SIGSTOP - 1, // 20 SIGTSP - 1, // 21 SIGTTIN - 1, // 22 SIGTTOU - 0, // 23 SIGURG - 1, // 24 SIGXCPU - 1, // 25 SIGXFSZ - 1, // 26 SIGVTALRM - 1, // 27 SIGPROF - 0, // 28 SIGWINCH - 1, // 29 SIGIO - 1, // 30 SIGPWR - 1, // 31 SIGSYS - 1, // 32 SIGUNUSED - }; - - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Exception; - e->process = dmn_lnx_handle_from_process(thread->process); - e->thread = dmn_lnx_handle_from_thread(thread); - e->instruction_pointer = dmn_lnx_thread_read_ip(thread); - e->signo = signo; - e->exception_repeated = signo < ArrayCount(is_repeatable) ? is_repeatable[signo] : 0; - - if(signo == SIGSEGV) - { - siginfo_t si = {0}; - OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, thread->tid, 0, &si)); - e->address = (U64)si.si_addr; - } -} - -internal void -dmn_lnx_push_event_halt(Arena *arena, DMN_EventList *events) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Halt; -} - -internal void -dmn_lnx_push_event_not_attached(Arena *arena, DMN_EventList *events) -{ - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Error; - e->error_kind = DMN_ErrorKind_NotAttached; -} - -internal DMN_LNX_Thread * -dmn_lnx_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, pid_t tid) -{ - DMN_LNX_Thread *thread = dmn_lnx_thread_alloc(process, DMN_LNX_ThreadState_Stopped, tid); - dmn_lnx_push_event_create_thread(arena, events, thread); - return thread; -} - -internal void -dmn_lnx_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code) -{ - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); - DMN_LNX_Process *process = thread->process; - - // store main thread's exit code - if(thread->tid == thread->process->pid) - { - thread->process->main_thread_exit_code = exit_code; - } - - // push exit event - dmn_lnx_push_event_exit_thread(arena, events, thread, exit_code); - - // release entity - dmn_lnx_thread_release(thread); - - // auto exit process on last thread - if(process->thread_count == 0) - { - dmn_lnx_event_exit_process(arena, events, process->pid); - } -} - -internal DMN_LNX_Process * -dmn_lnx_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, DMN_LNX_Process *parent_process, DMN_LNX_CreateProcessFlags flags) -{ - DMN_LNX_Process *process = dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Normal, parent_process, !!(flags & DMN_LNX_CreateProcessFlag_DebugSubprocesses), !!(flags & DMN_LNX_CreateProcessFlag_Cow)); - - if(flags & DMN_LNX_CreateProcessFlag_ClonedMemory) - { - process->ctx = parent_process->ctx; - } - else - { - process->ctx = dmn_lnx_process_ctx_alloc(process, !!(flags & DMN_LNX_CreateProcessFlag_Rebased)); - } - process->ctx->ref_count += 1; - - // install probes in a process that does not have a cloned memory - if(!(flags & DMN_LNX_CreateProcessFlag_ClonedMemory)) - { - dmn_lnx_process_trap_probes(process); - } - - // create main thread - dmn_lnx_thread_alloc(process, DMN_LNX_ThreadState_Stopped, pid); - - // push events - dmn_lnx_push_event_create_process(arena, events, process); - for EachNode(thread, DMN_LNX_Thread, process->first_thread) - { - dmn_lnx_push_event_create_thread(arena, events, thread); - } - for EachNode(module, DMN_LNX_Module, process->ctx->first_module) - { - dmn_lnx_push_event_load_module(arena, events, process->first_thread, module); - } - dmn_lnx_push_event_handshake_complete(arena, events, process); - - return process; -} - -internal void -dmn_lnx_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid) -{ - DMN_LNX_Process *process = dmn_lnx_process_from_pid(pid); - AssertAlways(process->thread_count == 0); - - // push module events - for EachNode(module, DMN_LNX_Module, process->ctx->first_module) - { - dmn_lnx_push_event_unload_module(arena, events, process, module); - } - - // push process exit event - dmn_lnx_push_event_exit_process(arena, events, process); - - // release process - dmn_lnx_process_release(process); -} - -internal void -dmn_lnx_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr) -{ - DMN_LNX_Process *process = thread->process; - - GNU_LinkMap64 map = {0}; - for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr) - { - // read out new link map item - if(gnu_read_link_map(dmn_lnx_machine_op_mem_read, &process->fd, map_vaddr, process->ctx->dl_class, &map) != MachineOpResult_Ok) { break; } - - // was module already loaded? - DMN_LNX_Module *module = hash_table_search_u64_raw(process->ctx->loaded_modules_ht, map.addr_vaddr); - if(module) { continue; } - - // clone process ctx - if(process->is_cow) - { - process->is_cow = 0; - process->ctx = dmn_lnx_process_ctx_clone(process, process->ctx); - } - - // alloc module - module = dmn_lnx_module_alloc(process->ctx, process->fd, map.addr_vaddr, map.name_vaddr, name_space_id, 0); - - // push load module event - dmn_lnx_push_event_load_module(arena, events, thread, module); - } -} - -internal void -dmn_lnx_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, U64 rdebug_vaddr) -{ - Temp scratch = scratch_begin(&arena, 1); - - DMN_LNX_ProcessCtx *ctx = process->ctx; - - // flag every module as inactive - for EachNode(module, DMN_LNX_Module, ctx->first_module) - { - module->is_live = 0; - } - - // mark live modules - B32 is_64bit = ctx->dl_class == ELF_Class_64; - GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd); - for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) - { - GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, dmn_lnx_machine_op_mem_read, &process->fd); - for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first) - { - DMN_LNX_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, link_map_n->v.addr_vaddr); - module->is_live = 1; - } - } - - // collect unloaded modules - DMN_LNX_ModulePtrList to_release = {0}; - for EachNode(module, DMN_LNX_Module, ctx->first_module) - { - if(!module->is_live) - { - dmn_lnx_module_ptr_list_push(scratch.arena, &to_release, module); - } - } - - // clone process context - if(to_release.count > 0) - { - if(process->is_cow) - { - process->is_cow = 0; - process->ctx = dmn_lnx_process_ctx_clone(process, process->ctx); - } - } - - // push events and clean up unloaded modules - for EachNode(module_n, DMN_LNX_ModulePtrNode, to_release.first) - { - dmn_lnx_push_event_unload_module(arena, events, process, module_n->v); - dmn_lnx_module_release(process->ctx, module_n->v); - } - - scratch_end(scratch); -} - -internal void -dmn_lnx_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid) -{ - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); - DMN_LNX_Process *process = thread->process; - U64 ip = dmn_lnx_thread_read_ip(thread); - - // is this user trap? - DMN_ActiveTrap *hit_user_trap = 0; - { - DMN_Handle process_handle = dmn_lnx_handle_from_process(process); - for EachNode(active_trap, DMN_ActiveTrap, user_traps) - { - if(MemoryCompare(&active_trap->trap->process, &process_handle, sizeof(DMN_Handle)) == 0) - { - if(active_trap->trap->vaddr == ip-1) - { - hit_user_trap = active_trap; - break; - } - } - } - } - - // is this a probe trap? - DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null; - if(hit_user_trap == 0) - { - for EachNode(active_trap, DMN_ActiveTrap, process->ctx->first_probe_trap) - { - if(active_trap->trap->vaddr == ip-1) - { - probe_type = active_trap->trap->id; - break; - } - } - } - - if(probe_type == DMN_LNX_ProbeType_InitComplete) - { - B32 is_init_completed = 0; - - DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_InitComplete]; - U64 name_space_id = 0, rdebug_addr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto init_complete_exit; } - if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_addr)) { goto init_complete_exit; } - - GNU_RDebugInfo64 rdebug = {0}; - if(gnu_read_r_debug(dmn_lnx_machine_op_mem_read, &process->fd, rdebug_addr, process->ctx->arch, &rdebug) != MachineOpResult_Ok) { goto init_complete_exit; } - if(rdebug.r_version < 1) { goto init_complete_exit; } - - dmn_lnx_event_load_module(arena, events, thread, name_space_id, rdebug.r_map); - - is_init_completed = 1; - init_complete_exit:; - AssertAlways(is_init_completed); - } - else if(probe_type == DMN_LNX_ProbeType_RelocComplete) - { - B32 is_reloc_completed = 0; - - DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_RelocComplete]; - U64 name_space_id = 0, new_link_map_addr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto reloc_complete_exit; } - if(!stap_read_arg_u(probe->args.v[2], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &new_link_map_addr)) { goto reloc_complete_exit; } - - dmn_lnx_event_load_module(arena, events, thread, name_space_id, new_link_map_addr); - - is_reloc_completed = 1; - reloc_complete_exit:; - AssertAlways(is_reloc_completed); - } - else if(probe_type == DMN_LNX_ProbeType_UnmapComplete) - { - B32 is_unmap_completed = 0; - - DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_UnmapComplete]; - U64 name_space_id = 0, rdebug_vaddr = 0; - if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto unmap_complete_exit; } - if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_vaddr)) { goto unmap_complete_exit; } - - dmn_lnx_event_unload_module(arena, events, process, rdebug_vaddr); - - is_unmap_completed = 1; - unmap_complete_exit:; - AssertAlways(is_unmap_completed); - } - - if(probe_type == DMN_LNX_ProbeType_Null) - { - // rollback IP on user traps - if(hit_user_trap) - { - U64 ip = dmn_lnx_thread_read_ip(thread); - dmn_lnx_thread_write_ip(thread, ip - 1); - } - - DMN_Event *e = dmn_event_list_push(arena, events); - e->kind = DMN_EventKind_Breakpoint; - e->process = dmn_lnx_handle_from_process(process); - e->thread = dmn_lnx_handle_from_thread(thread); - e->instruction_pointer = dmn_lnx_thread_read_ip(thread); - } -} - -internal void -dmn_lnx_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid) -{ - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); - - B32 is_valid = 1; - U64 address = 0; - switch(thread->process->ctx->arch) - { - case Arch_Null: {} break; - case Arch_x64: - { - X64_RegBlock *regs_x64 = thread->reg_block; - if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B0) - { - address = regs_x64->dr0.u64; - } - else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B1) - { - address = regs_x64->dr1.u64; - } - else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B2) - { - address = regs_x64->dr2.u64; - } - else if(regs_x64->dr6.u64 & X64_DebugStatusFlag_B3) - { - address = regs_x64->dr3.u64; - } - else - { - is_valid = 0; - } - } break; - case Arch_x86: - case Arch_arm32: - case Arch_arm64: - { NotImplemented; } break; - default: { InvalidPath; } break; - } - - if(is_valid) - { - dmn_lnx_push_event_breakpoint(arena, events, thread, address); - } -} - -internal void -dmn_lnx_event_halt(Arena *arena, DMN_EventList *events) -{ - dmn_lnx_push_event_halt(arena, events); -} - -internal void -dmn_lnx_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid) -{ - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); - - // clear single step flag - dmn_lnx_set_single_step_flag(thread, 0); - - // push event - dmn_lnx_push_event_single_step(arena, events, thread); -} - -internal void -dmn_lnx_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo) -{ - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); - - thread->pass_through_signal = 1; - thread->pass_through_signo = signo; - - dmn_lnx_push_event_exception(arena, events, thread, signo); -} - -internal DMN_LNX_Process * -dmn_lnx_event_attach(Arena *arena, DMN_EventList *events, pid_t pid) -{ - Temp scratch = scratch_begin(&arena, 1); - - // create process - DMN_LNX_Process *process = dmn_lnx_event_create_process(arena, events, pid, 0, DMN_LNX_CreateProcessFlag_DebugSubprocesses|DMN_LNX_CreateProcessFlag_Rebased); - - // extract threads from /proc/pid/task - { - String8 task_path = str8f(scratch.arena, "/proc/%d/task", pid); - DIR *task_dirp = opendir((char *)task_path.str); - if(task_dirp) - { - for(;;) - { - struct dirent *dirent = readdir(task_dirp); - if(dirent == 0) { break; } - - String8 tid_str = str8_cstring_capped(dirent->d_name, dirent->d_name + NAME_MAX); - if(str8_match(tid_str, str8_lit(".."), 0) || str8_match(tid_str, str8_lit("."), 0)) { continue; } - U64 tid_64 = u64_from_str8(tid_str, 10); - pid_t tid = (pid_t)tid_64; - AssertAlways(tid == tid_64); - - if(tid == pid) { continue; } // main thread was created during create process sequence - dmn_lnx_event_create_thread(arena, events, process, tid); - } - - OS_LNX_RETRY_ON_EINTR(closedir(task_dirp)); - } - } - - // extract modules from r_debug - { - B32 is_64bit = process->ctx->dl_class == ELF_Class_64; - GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->ctx->rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd); - U64 name_space_id = 0; - for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) - { - dmn_lnx_event_load_module(arena, events, process->first_thread, name_space_id, rdebug_n->v.r_map); - name_space_id += 1; - } - } - - // handshake complete - dmn_lnx_push_event_handshake_complete(arena, events, process); - - scratch_end(scratch); - return process; -} - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) - -internal void -dmn_init(void) -{ - if(dmn_lnx_state == 0) - { - local_persist DMN_LNX_State state; - dmn_lnx_state = &state; - - dmn_lnx_state->arena = arena_alloc(); - dmn_lnx_state->access_mutex = mutex_alloc(); - dmn_lnx_state->entities_arena = arena_alloc(.reserve_size = GB(32), .commit_size = KB(64), .flags = ArenaFlag_NoChain); - dmn_lnx_state->entities_base = push_array(dmn_lnx_state->entities_arena, DMN_LNX_Entity, 0); - dmn_lnx_state->tid_ht = hash_table_init(dmn_lnx_state->arena, 0x2000); - dmn_lnx_state->pid_ht = hash_table_init(dmn_lnx_state->arena, 0x400); - dmn_lnx_state->halter_mutex = mutex_alloc(); - dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Null); - - // find offsets of TLS index and TLS offset in the link_map struct - // - // TODO: assuming that target is using same libc version as debugger - { - DMN_LNX_DbDesc *tls_modid_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_modid"); - DMN_LNX_DbDesc *tls_offset_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_offset"); - if(tls_modid_desc && tls_offset_desc) - { - if(tls_modid_desc->bit_size <= 64 && tls_offset_desc->bit_size <= 64) - { - dmn_lnx_state->tls_modid_desc = *tls_modid_desc; - dmn_lnx_state->tls_offset_desc = *tls_offset_desc; - dmn_lnx_state->is_tls_detected = 1; - } - else { Assert(0 && "invalid TLS desc"); } - } - } - } -} - -//////////////////////////////// -//~ 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_lnx_ctrl_thread = 1; - return ctx; -} - -internal void -dmn_ctrl_exclusive_access_begin(void) -{ - MutexScope(dmn_lnx_state->access_mutex) - { - dmn_lnx_state->access_run_state = 1; - } -} - -internal void -dmn_ctrl_exclusive_access_end(void) -{ - MutexScope(dmn_lnx_state->access_mutex) - { - dmn_lnx_state->access_run_state = 0; - } -} - -internal U32 -dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params) -{ - Temp scratch = scratch_begin(0, 0); - - // setup target command line - U64 argc = params->cmd_line.node_count + 1; - char **argv = push_array(scratch.arena, char *, argc); - { - U64 idx = 0; - for EachNode(n, String8Node, params->cmd_line.first) - { - argv[idx] = (char *)str8_copy(scratch.arena, n->string).str; - idx += 1; - } - } - - // setup target environment - U64 envc = os_lnx_state.default_env_count + params->env.node_count + 1; - char **envp = push_array(scratch.arena, char *, envc); - { - // copy default environment - MemoryCopyTyped(envp, os_lnx_state.default_env, os_lnx_state.default_env_count); - - // copy user environment - U64 idx = os_lnx_state.default_env_count; - for EachNode(n, String8Node, params->env.first) - { - envp[idx] = (char *)str8_copy(scratch.arena, n->string).str; - idx += 1; - } - } - - // create zero-terminated work directory path - char *work_dir_path = (char *)str8_copy(scratch.arena, params->path).str; - - // fork process - pid_t pid = fork(); - - // child process - if(pid == 0) - { - // wait for seize - if(OS_LNX_RETRY_ON_EINTR(raise(SIGSTOP)) < 0) { goto child_exit; } - - // change work directory to tracee - if(OS_LNX_RETRY_ON_EINTR(chdir(work_dir_path)) < 0) { goto child_exit; } - - // replace process with target program - if(OS_LNX_RETRY_ON_EINTR(execve(argv[0], argv, envp)) < 0) { goto child_exit; } - - child_exit:; - exit(0); - } - // parent process - else if(pid > 0) - { - // try to seize child process - if(dmn_lnx_ptrace_seize(pid) >= 0) - { - // tracee was successfully seized, create process - dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Launch, 0, params->debug_subprocesses, 0); - } - else - { - Assert(0 && "failed to ptrace child process"); - OS_LNX_RETRY_ON_EINTR(kill(SIGKILL, pid)); - OS_LNX_RETRY_ON_EINTR(waitpid(pid, 0, WNOHANG)); - pid = 0; - } - } - - scratch_end(scratch); - return pid; -} - -internal B32 -dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) -{ - B32 is_attached = 0; - if(dmn_lnx_ptrace_seize(pid) >= 0) - { - if(OS_LNX_RETRY_ON_EINTR(kill(pid, SIGSTOP)) >= 0) - { - dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Attach, 0, 1, 0); - is_attached = 1; - } - else - { - OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, pid, 0, 0)); - } - } - return is_attached; -} - -internal B32 -dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process_handle, U32 exit_code) -{ - B32 result = 0; - mutex_take(dmn_lnx_state->halter_mutex); - DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); - if(process) - { - result = OS_LNX_RETRY_ON_EINTR(kill(process->pid, SIGKILL)) >= 0; - } - mutex_drop(dmn_lnx_state->halter_mutex); - return result; -} - -internal B32 -dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process_handle) -{ - B32 result = 0; - mutex_take(dmn_lnx_state->halter_mutex); - DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); - if(process) - { - result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, process->pid, 0, 0)) >= 0; - } - mutex_drop(dmn_lnx_state->halter_mutex); - return result; -} - -internal DMN_EventList -dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) -{ - Temp scratch = scratch_begin(&arena, 1); - DMN_EventList events = {0}; - - mutex_take(dmn_lnx_state->halter_mutex); - - // wait for signals from the running threads - if(dmn_lnx_state->process_count > 0) - { - // write traps to memory - DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0; - { - HashTable *process_ht = hash_table_init(scratch.arena, dmn_lnx_state->process_count); - for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) - { - for EachIndex(n_idx, n->count) - { - // skip hardware breakpoints - DMN_Trap *trap = n->v+n_idx; - if(trap->flags) { continue; } - - HashTable *active_trap_ht = hash_table_search_u64_raw(process_ht, trap->process.u64[0]); - if(active_trap_ht == 0) - { - active_trap_ht = hash_table_init(scratch.arena, ctrls->traps.trap_count); - hash_table_push_u64_raw(scratch.arena, process_ht, trap->process.u64[0], active_trap_ht); - } - - // TODO: ctrl sends down duplicate traps - DMN_ActiveTrap *is_set = hash_table_search_u64_raw(active_trap_ht, trap->vaddr); - if(is_set) { continue; } - - // TODO: ctrl sends down traps for exited process - DMN_LNX_Process *process = dmn_lnx_process_from_handle(trap->process); - if(!process) { continue; } - - // trap instruction - DMN_ActiveTrap *active_trap = dmn_set_trap(scratch.arena, trap); - - // add trap to the active list - SLLQueuePush(active_trap_first, active_trap_last, active_trap); - - // add (address -> trap) - hash_table_push_u64_raw(scratch.arena, active_trap_ht, trap->vaddr, active_trap); - } - } - } - - // enable single stepping - if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) - { - DMN_LNX_Thread *single_step_thread = dmn_lnx_thread_from_handle(ctrls->single_step_thread); - if(single_step_thread) - { - dmn_lnx_set_single_step_flag(single_step_thread, 1); - } - else - { - Assert(0 && "invalid single_step_thread handle"); - } - } - - // schedule threads to run - DMN_LNX_ThreadPtrList running_threads = {0}; - { - for EachNode(process, DMN_LNX_Process, dmn_lnx_state->first_process) - { - //- rjf: determine if this process is frozen - B32 process_is_frozen = 0; - if(ctrls->run_entities_are_processes) - { - for EachIndex(idx, ctrls->run_entity_count) - { - if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_process(process))) - { - process_is_frozen = 1; - break; - } - } - } - - for EachNode(thread, DMN_LNX_Thread, process->first_thread) - { - //- rjf: determine if this thread is frozen - B32 is_frozen = 0; - - // rjf: not single-stepping? determine based on run controls freezing info - if(dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread)) - { - if(ctrls->run_entities_are_processes) - { - is_frozen = process_is_frozen; - } - else - { - for EachIndex(idx, ctrls->run_entity_count) - { - if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_thread(thread))) - { - is_frozen = 1; - break; - } - } - } - if(ctrls->run_entities_are_unfrozen) - { - is_frozen ^= 1; - } - } - // rjf: single-step? freeze if not the single-step thread. - else - { - is_frozen = !dmn_handle_match(dmn_lnx_handle_from_thread(thread), ctrls->single_step_thread); - } - - // resume thread - if(!is_frozen) - { - AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); - - // write registers - if(thread->is_reg_block_dirty) - { - thread->is_reg_block_dirty = !dmn_lnx_thread_write_reg_block(thread); - } - - // pass signal to the child process - void *sig_code = 0; - if(thread->pass_through_signal) - { - thread->pass_through_signal = 0; - if(!ctrls->ignore_previous_exception) - { - sig_code = (void *)(uintptr_t)thread->pass_through_signo; - } - } - - // resume thread - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, thread->tid, 0, sig_code)) >= 0) - { - thread->state = DMN_LNX_ThreadState_Running; - thread->is_reg_block_dirty = 1; - dmn_lnx_thread_ptr_list_push(scratch.arena, &running_threads, thread); - } - else - { - if(errno == ESRCH) - { - // Race note: In multithreaded programs, ptrace(PTRACE_CONT) may fail if a running thread calls - // exit_group while we are resuming threads. The kernel begins tearing down the thread group so - // the target might not be in a ptrace-soppped state anymore. Handle this by ignoring, on waitpid - // we will get chance to report exit event. - // - // TODO: unfortunatley, kernel also sends us ESRCH whenever ptrace(PTRACE_CONT) is sent to a tracee - // that is not in a ptace-stop, so how can we tell these errors apart? - } - else - { - InvalidPath; - } - } - } - } - } - } - - // hash running threads tids - HashTable *running_threads_ht = hash_table_init(scratch.arena, running_threads.count * 2); - for EachNode(n, DMN_LNX_ThreadPtrNode, running_threads.first) - { - hash_table_push_u64_raw(scratch.arena, running_threads_ht, n->v->tid, n); - } - - B32 is_halt_done = 0; - DMN_LNX_ThreadPtrList stopped_threads = {0}; - do - { - // wait for a signal - int status = 0; - pid_t wait_id = 0; - { - mutex_drop(dmn_lnx_state->halter_mutex); - for(;;) - { - wait_id = OS_LNX_RETRY_ON_EINTR(waitpid(-1, &status, __WALL|__WNOTHREAD)); - if(wait_id == -1) { InvalidPath; } // TODO: graceful exit - break; - } - mutex_take(dmn_lnx_state->halter_mutex); - } - - // unpack status - int wifexited = WIFEXITED(status); - int wifsignaled = WIFSIGNALED(status); - int wifstopped = WIFSTOPPED(status); - int wstopsig = WSTOPSIG(status); - int event_code = (status >> 16); - - // intercept initing processes - { - DMN_LNX_Process *process = dmn_lnx_process_from_pid(wait_id); - - if(process && process->state != DMN_LNX_ProcessState_Normal) - { - switch(process->state) - { - case DMN_LNX_ProcessState_Null: - case DMN_LNX_ProcessState_Normal: - { - InvalidPath; - } break; - case DMN_LNX_ProcessState_Launch: - { - if(wstopsig == SIGSTOP) - { - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, process->pid, 0, 0)) >= 0) - { - process->state = DMN_LNX_ProcessState_WaitForExec; - goto wait_for_signal; - } - else { Assert(0 && "failed to resume tracee"); } - } - // else { Assert(0 && "unexpected signal"); } - } break; - case DMN_LNX_ProcessState_WaitForExec: - { - if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) - { - DMN_LNX_CreateProcessFlags create_flags = process->debug_subprocesses ? DMN_LNX_CreateProcessFlag_DebugSubprocesses : 0; - dmn_lnx_process_release(process); - dmn_lnx_event_create_process(arena, &events, wait_id, 0, create_flags); - goto wait_for_signal; - } - //else { Assert(0 && "unexpected signal"); } - } break; - case DMN_LNX_ProcessState_ExecFailedDoExit: - { - if(wifexited) - { - dmn_lnx_process_release(process); - goto wait_for_signal; - } - else { Assert(0 && "unexpected signal"); } - } break; - case DMN_LNX_ProcessState_Attach: - { - if(wstopsig == SIGSTOP) - { - DMN_LNX_CreateProcessFlags create_flags = process->debug_subprocesses ? DMN_LNX_CreateProcessFlag_DebugSubprocesses : 0; - dmn_lnx_process_release(process); - dmn_lnx_event_create_process(arena, &events, wait_id, 0, create_flags); - goto wait_for_signal; - } - else { Assert(0 && "unexpected signal"); } - } break; - } - - // shutdown process - { - if(OS_LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)) >= 0) - { - process->state = DMN_LNX_ProcessState_ExecFailedDoExit; - } - else - { - //Assert(0 && "failed to send kill signal to the tracee"); - dmn_lnx_process_release(process); - } - } - - wait_for_signal:; - continue; - } - } - - if(wifstopped || wifsignaled || wifexited) - { - DMN_LNX_ThreadPtrNode *thread_n = hash_table_search_u64_raw(running_threads_ht, wait_id); - if(thread_n) - { - DMN_LNX_Thread *thread = thread_n->v; - AssertAlways(thread->state == DMN_LNX_ThreadState_Running); - - // remove mapping - hash_table_purge_u64(running_threads_ht, thread->tid); - - // move thread to the stopped list - dmn_lnx_thread_ptr_list_remove(&running_threads, thread_n); - dmn_lnx_thread_ptr_list_push_node(&stopped_threads, thread_n); - - // update thread state - if(wifstopped && !wifsignaled && !wifexited) - { - thread->state = DMN_LNX_ThreadState_Stopped; - } - else if(!wifstopped && (wifsignaled || wifexited)) - { - thread->state = DMN_LNX_ThreadState_Exited; - } - else { InvalidPath; } - - // stop all other threads - if(stopped_threads.count == 1) - { - for EachNode(n, DMN_LNX_ThreadPtrNode, running_threads.first) - { - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, n->v->tid, 0, 0)) < 0) { Assert(0 && "failed to interrupt process"); } - } - } - } - } - - // normal child exit via _exit or exit() - if(wifexited) - { - dmn_lnx_event_exit_thread(arena, &events, wait_id, WEXITSTATUS(status)); - } - - // exit because child did not handle a signal - else if(wifsignaled) - { - dmn_lnx_event_exit_thread(arena, &events, wait_id, WTERMSIG(status)); - } - // thread or group stop - else if(wifstopped) - { - // read thread registers - { - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); - if(thread && thread->state != DMN_LNX_ThreadState_PendingCreation) - { - thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread); - Assert(!thread->is_reg_block_dirty); - } - } - - if(wstopsig == SIGTRAP) - { - switch(event_code) - { - case 0: - { - // translate signal code to DEMON event - siginfo_t siginfo = {0}; - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0) - { - switch(siginfo.si_code) - { - case SI_KERNEL: { dmn_lnx_event_breakpoint(arena, &events, active_trap_first, wait_id); } break; - case TRAP_BRKPT: { dmn_lnx_event_breakpoint(arena, &events, active_trap_first, wait_id); } break; - case TRAP_TRACE: { dmn_lnx_event_single_step(arena, &events, wait_id); } break; - case TRAP_HWBKPT: { dmn_lnx_event_data_breakpoint(arena, &events, wait_id); } break; - case TRAP_BRANCH: { NotImplemented; }break; - case TRAP_UNK: { NotImplemented; }break; - default: { InvalidPath; } break; - } - } else { Assert(0 && "failed to get signal info"); } - }break; - case PTRACE_EVENT_FORK: - { - NotImplemented; - }break; - case PTRACE_EVENT_VFORK: - { - NotImplemented; - }break; - case PTRACE_EVENT_VFORK_DONE: - { - NotImplemented; - }break; - case PTRACE_EVENT_CLONE: - { - // kernel stopped the parent just before scheduling the child to - // give us a chance to prepare to trace it; next event for the child - // will be a PTRACE_EVENT_STOP - - pid_t new_tid; - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid)) >= 0) - { - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); - - // create a new partially inited thread - dmn_lnx_thread_alloc(thread->process, DMN_LNX_ThreadState_PendingCreation, new_tid); - } - else { Assert(0 && "failed to get new tid"); } - }break; - case PTRACE_EVENT_EXEC: - { - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); - DMN_LNX_Process *process = thread->process; - if(process->debug_subprocesses) - { - dmn_lnx_event_exit_thread(arena, &events, wait_id, 0); - dmn_lnx_event_create_process(arena, &events, wait_id, 0, DMN_LNX_CreateProcessFlag_DebugSubprocesses); - } - else - { - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, wait_id, 0, 0)) >= 0) - { - dmn_lnx_event_exit_thread(arena, &events, wait_id, 0); - } - else { Assert(0 && "failed to detach"); } - } - }break; - case PTRACE_EVENT_EXIT: - { - AssertAlways(0 && "exit tracing is disabled, this event should not be delivered"); - }break; - case PTRACE_EVENT_SECCOMP: - { - NotImplemented; - }break; - case PTRACE_EVENT_STOP: - { - DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); - if(thread->state == DMN_LNX_ThreadState_PendingCreation) - { - DMN_LNX_Process *process = thread->process; - dmn_lnx_thread_release(thread); - thread = dmn_lnx_event_create_thread(arena, &events, process, wait_id); - } - else - { - AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); - } - }break; - default: { Assert(0 && "unexpected ptrace code"); } break; - } - } - else if(wstopsig == SIGSTOP) - { - siginfo_t siginfo = {0}; - if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0) - { - // finish halting if this is our SIGSTOP - if(siginfo.si_pid == dmn_lnx_state->halter_tid) - { - is_halt_done = 1; - } - else - { - // TODO: handle SIGSTOP - } - } - } - else - { - dmn_lnx_event_exception(arena, &events, wait_id, wstopsig); - } - } - else { Assert(0 && "unexpected stop code"); } - } while(running_threads.count > 0 || dmn_lnx_state->process_pending_creation > 0 || dmn_lnx_state->threads_pending_creation > 0); - - // finalize halter state - if(is_halt_done) - { - // push event - dmn_lnx_event_halt(arena, &events); - - // reset state - dmn_lnx_state->halter_tid = 0; - dmn_lnx_state->halt_code = 0; - dmn_lnx_state->halt_user_data = 0; - dmn_lnx_state->is_halting = 0; - } - - // restore original instruction bytes - for EachNode(active_trap, DMN_ActiveTrap, active_trap_first) - { - // skip process that exited during the wait - DMN_LNX_Process *process = dmn_lnx_process_from_handle(active_trap->trap->process); - if(!process) { continue; } - - if(!dmn_process_write(active_trap->trap->process, r1u64(active_trap->trap->vaddr, active_trap->trap->vaddr + active_trap->swap_bytes.size), active_trap->swap_bytes.str) && - active_trap->good) - { - // TODO(rjf): log an error here? - we wrote the trap successfully, but we did not remove it successfully, - // implying a change in address mapping. this is maybe not even a bug, since the address is simply now - // unmapped. - } - } - } - - if(events.count == 0 && dmn_lnx_state->process_count == 0) - { - dmn_lnx_push_event_not_attached(arena, &events); - } - - mutex_drop(dmn_lnx_state->halter_mutex); - scratch_end(scratch); - return events; -} - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS) - -internal void -dmn_halt(U64 code, U64 user_data) -{ - mutex_take(dmn_lnx_state->halter_mutex); - if(dmn_lnx_state->process_count) - { - dmn_lnx_state->halter_tid = gettid(); - dmn_lnx_state->halt_code = code; - dmn_lnx_state->halt_user_data = user_data; - for EachNode(process, DMN_LNX_Process, dmn_lnx_state->first_process) - { - if(OS_LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)) < 0) { Assert(0 && "failed to send SIGSTOP"); } - } - } - mutex_drop(dmn_lnx_state->halter_mutex); -} - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS) - -//- rjf: non-blocking-control-thread access barriers - -internal B32 -dmn_access_open(void) -{ - B32 result = 0; - if(dmn_lnx_ctrl_thread) - { - result = 1; - } - else - { - mutex_take(dmn_lnx_state->access_mutex); - result = !dmn_lnx_state->access_run_state; - } - return result; -} - -internal void -dmn_access_close(void) -{ - if(!dmn_lnx_ctrl_thread) - { - mutex_drop(dmn_lnx_state->access_mutex); - } -} - -//- rjf: processes - -internal U64 -dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) -{ - return 0; -} - -internal void -dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size) -{ -} - -internal void -dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size) -{ -} - -internal void -dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size) -{ -} - -internal void -dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, OS_AccessFlags flags) -{ -} - -internal U64 -dmn_process_read(DMN_Handle process_handle, Rng1U64 range, void *dst) -{ - DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); - U64 result = 0; - if(process) - { - result = dmn_lnx_read(process->fd, range, dst); - } - return result; -} - -internal B32 -dmn_process_write(DMN_Handle process_handle, Rng1U64 range, void *src) -{ - DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); - B32 result = 0; - if(process) - { - result = dmn_lnx_write(process->fd, range, src); - } - return result; -} - -//- rjf: threads - -internal Arch -dmn_arch_from_thread(DMN_Handle thread_handle) -{ - Arch arch = Arch_Null; - DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); - if(thread) - { - arch = thread->process->ctx->arch; - } - return arch; -} - -internal U64 -dmn_stack_base_vaddr_from_thread(DMN_Handle thread_handle) -{ - return 0; -} - -internal U64 -dmn_tls_root_vaddr_from_thread(DMN_Handle thread_handle) -{ - U64 tls_root_vaddr = max_U64; - DMN_AccessScope - { - DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); - if(thread) - { - switch(thread->process->ctx->arch) - { - case Arch_Null: {} break; - case Arch_x64: - { - X64_RegBlock *reg_block = thread->reg_block; - U64 dtv_pointer = 0; - if(dmn_lnx_read_struct(thread->process->fd, reg_block->fsbase.u64 + 8, &dtv_pointer)) - { - tls_root_vaddr = dtv_pointer; - } - } break; - case Arch_x86: - case Arch_arm32: - case Arch_arm64: - { - NotImplemented; - } break; - default: { InvalidPath; } break; - } - } - } - return tls_root_vaddr; -} - -internal B32 -dmn_thread_read_reg_block(DMN_Handle thread_handle, void *reg_block) -{ - B32 result = 0; - DMN_AccessScope - { - DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); - if(thread) - { - ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); - U64 reg_block_size = arch_info->reg_block_size; - MemoryCopy(reg_block, thread->reg_block, reg_block_size); - } - result = 1; - } - return result; -} - -internal B32 -dmn_thread_write_reg_block(DMN_Handle thread_handle, void *reg_block) -{ - B32 result = 0; - DMN_AccessScope - { - DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); - if(thread) - { - ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); - U64 reg_block_size = arch_info->reg_block_size; - MemoryCopy(thread->reg_block, reg_block, reg_block_size); - thread->is_reg_block_dirty = 1; - result = 1; - } - } - return result; -} - -//- rjf: system process listing - -internal void -dmn_process_iter_begin(DMN_ProcessIter *iter) -{ - DIR *dir = opendir("/proc"); - MemoryZeroStruct(iter); - iter->v[0] = IntFromPtr(dir); -} - -internal B32 -dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out) -{ - // rjf: scan for the next process ID in the directory - B32 got_pid = 0; - String8 pid_string = {0}; - { - DIR *dir = (DIR*)PtrFromInt(iter->v[0]); - if(dir != 0 && iter->v[1] == 0) - { - for(;;) - { - // rjf: get next entry - struct dirent *d = readdir(dir); - if(d == 0) - { - break; - } - - // rjf: check file name is integer - String8 file_name = str8_cstring((char*)d->d_name); - B32 is_integer = str8_is_integer(file_name, 10); - - // rjf: break on integers (which represent processes) - if(is_integer) - { - got_pid = 1; - pid_string = file_name; - break; - } - } - } - } - - // rjf: if we found a process id, map id => info - B32 result = 0; - if(got_pid) - { - pid_t pid = u64_from_str8(pid_string, 10); - String8 name = dmn_lnx_exe_path_from_pid(arena, pid); - if(name.size == 0) - { - name = str8_lit("(unknown process)"); - } - info_out->name = name; - info_out->pid = pid; - result = 1; - } - - return result; -} - -internal void -dmn_process_iter_end(DMN_ProcessIter *iter) -{ - DIR *dir = (DIR*)PtrFromInt(iter->v[0]); - if(dir != 0) - { - closedir(dir); - } - MemoryZeroStruct(iter); -} +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ Includes + +#include +#include +#include + +//////////////////////////////// + +internal U64 +dmn_lnx_size_from_fd(int memory_fd, U64 cap) +{ + U8 temp[4096]; + size_t cursor = 0; + while(cursor < cap) + { + ssize_t actual_read = OS_LNX_RETRY_ON_EINTR(pread(memory_fd, temp, sizeof(temp), cursor)); + if(actual_read <= 0) { break; } + cursor += (U64)actual_read; + } + return (U64)cursor; +} + +internal U64 +dmn_lnx_read(int memory_fd, Rng1U64 range, void *dst) +{ + size_t cursor = 0, size = dim_1u64(range); + while(cursor < size) + { + size_t to_read = size - cursor; + ssize_t actual_read = OS_LNX_RETRY_ON_EINTR(pread(memory_fd, (U8 *)dst + cursor, to_read, range.min + cursor)); + if(actual_read < 0) { break; } + if(actual_read == 0) { break; } + cursor += actual_read; + } + return cursor; +} + +internal B32 +dmn_lnx_write(int memory_fd, Rng1U64 range, void *src) +{ + size_t cursor = 0, size = dim_1u64(range); + while(cursor < size) + { + size_t to_write = size - cursor; + ssize_t actual_write = OS_LNX_RETRY_ON_EINTR(pwrite(memory_fd, (U8 *)src + cursor, to_write, range.min + cursor)); + if(actual_write <= 0) { break; } + cursor += actual_write; + } + B32 is_written = (cursor == size); + return is_written; +} + +internal String8 +dmn_lnx_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size) +{ + String8 result = {0}; + U64 string_size = 0; + for(U64 vaddr = base_vaddr; string_size < cap_size; vaddr += 1, string_size += 1) + { + char byte = 0; + if(OS_LNX_RETRY_ON_EINTR(pread(memory_fd, &byte, sizeof(byte), vaddr) <= 0)) + { + break; + } + if(byte == '\0' || byte == '\n') + { + break; + } + } + if(string_size != 0) + { + char *buf = push_array_no_zero(arena, char, string_size+1); + OS_LNX_RETRY_ON_EINTR(pread(memory_fd, buf, string_size, base_vaddr)); + buf[string_size] = '\0'; + result = str8((U8 *)buf, string_size); + } + return result; +} + +internal String8 +dmn_lnx_read_string(Arena *arena, int memory_fd, U64 vaddr) +{ + return dmn_lnx_read_string_capped(arena, memory_fd, vaddr, 4096); +} + +internal +MACHINE_OP_MEM_READ(dmn_lnx_machine_op_mem_read) +{ + U64 read_size = dmn_lnx_read(*(int *)ud, r1u64(addr, addr + buffer_size), buffer); + return read_size == buffer_size ? MachineOpResult_Ok : MachineOpResult_Fail; +} + +internal int +dmn_lnx_ptrace_seize(pid_t pid) +{ + // TODO: PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEFORK + return OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACECLONE)); +} + +internal String8 +dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid) +{ + Temp scratch = scratch_begin(&arena, 1); + + String8 exe_link_path = str8f(scratch.arena, "/proc/%d/exe", pid); + String8List parts = {0}; + int readlink_result = 0; + for(S64 r = 0, cap = PATH_MAX; r < 4; cap *= 2, r += 1) + { + U8 *buffer = push_array(arena, U8, cap); + readlink_result = readlink((char *)exe_link_path.str, (char *)buffer, cap); + + if(readlink_result < 0) + { + break; + } + + str8_list_push(scratch.arena, &parts, str8(buffer, readlink_result)); + + if(readlink_result < cap) + { + break; + } + } + + String8 result = str8_list_join(arena, &parts, 0); + scratch_end(scratch); + return result; +} + +internal String8 +dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base) +{ + Temp scratch = scratch_begin(&arena, 1); + + String8 dl_path = {0}; + + int maps_fd = OS_LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/maps", pid).str, O_RDONLY)); + if(maps_fd != -1) + { + // read entire /proc/pid/maps + U64 maps_size = dmn_lnx_size_from_fd(maps_fd, MB(1)); + U8 *maps_ptr = push_array(scratch.arena, U8, maps_size); + U64 read_size = dmn_lnx_read(maps_fd, r1u64(0, maps_size), maps_ptr); + + // split map file on lines + String8List lines = str8_split_by_string_chars(scratch.arena, str8(maps_ptr, maps_size), str8_lit("\n"), 0); + + // scan each line until a virtual mapping whose low part matches the DL base address is found + for EachNode(n, String8Node, lines.first) + { + String8 line = n->string; + + // split the string while respecting escape sequences + String8List parts = {0}; + for EachIndex(cursor, line.size) + { + if(parts.node_count > 5) { break; } + if(line.str[cursor] == ' ') { continue; } + + // scan forward to the closing delimiter + U64 token_start = cursor; + for(; cursor < line.size; cursor += 1) + { + if(line.str[cursor] == '\\') + { + cursor += 1; + continue; + } + if(line.str[cursor] == ' ') { break; } + } + + // push sub-string to the list + str8_list_push(scratch.arena, &parts, str8_substr(line, r1u64(token_start, cursor))); + } + + // was line parsed correctly? + if(parts.node_count < 5) { Assert(0 && "failed to parse map line"); continue; } + + // parse map virtual range + String8List vaddr_list = str8_split_by_string_chars(scratch.arena, parts.first->string, str8_lit("-"), 0); + if(vaddr_list.node_count != 2) { Assert(0 && "failed to parse virtual range portion of map line"); continue; } + + // does the low part match DL base address? + U64 lo_vaddr = u64_from_str8(vaddr_list.first->string, 16); + if(lo_vaddr == auxv_base) + { + dl_path = parts.node_count == 5 ? str8_zero() : parts.last->string; + dl_path = push_str8_copy(arena, dl_path); + break; + } + } + + OS_LNX_RETRY_ON_EINTR(close(maps_fd)); + } + else { Assert(0 && "failed to open DL fd"); } + + scratch_end(scratch); + Assert(dl_path.size); + return dl_path; +} + +internal ELF_Hdr64 +dmn_lnx_ehdr_from_pid(pid_t pid) +{ + Temp scratch = scratch_begin(0, 0); + + ELF_Hdr64 exe = {0}; + B32 is_read = 0; + + char *exe_path = (char *)str8f(scratch.arena, "/proc/%d/exe", pid).str; + int exe_fd = OS_LNX_RETRY_ON_EINTR(open(exe_path, O_RDONLY)); + + if(exe_fd >= 0) + { + is_read = elf_read_ehdr(dmn_lnx_machine_op_mem_read, &exe_fd, 0, &exe); + OS_LNX_RETRY_ON_EINTR(close(exe_fd)); + } + + Assert(is_read); + scratch_end(scratch); + return exe; +} + +internal DMN_LNX_Auxv +dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class) +{ + Temp scratch = scratch_begin(0, 0); + DMN_LNX_Auxv result = {0}; + + // rjf: open aux data + String8 auxv_path = str8f(scratch.arena, "/proc/%d/auxv", pid); + int auxv_fd = OS_LNX_RETRY_ON_EINTR(open((char *)auxv_path.str, O_RDONLY)); + + // rjf: scan aux data + if(auxv_fd >= 0) + { + for(;;) + { + // rjf: read next aux + ELF_Auxv64 auxv = {0}; + switch(elf_class) + { + case ELF_Class_None:{}break; + case ELF_Class_32: + { + ELF_Auxv32 auxv32 = {0}; + if(read(auxv_fd, &auxv32, sizeof(auxv32)) != sizeof(auxv32)) { goto brkloop; } + auxv = elf_auxv64_from_auxv32(auxv32); + }break; + case ELF_Class_64: + { + if(read(auxv_fd, &auxv, sizeof(auxv)) != sizeof(auxv)) { goto brkloop; } + }break; + default:{NotImplemented;}break; + } + + // rjf: fill result + switch(auxv.a_type) + { + default:{}break; + case ELF_AuxType_Null: goto brkloop; break; + case ELF_AuxType_Base: result.base = auxv.a_val; break; + case ELF_AuxType_Phnum: result.phnum = auxv.a_val; break; + case ELF_AuxType_Phent: result.phent = auxv.a_val; break; + case ELF_AuxType_Phdr: result.phdr = auxv.a_val; break; + case ELF_AuxType_ExecFn: result.execfn = auxv.a_val; break; + case ELF_AuxType_Pagesz: result.pagesz = auxv.a_val; break; + } + } + brkloop:; + OS_LNX_RETRY_ON_EINTR(close(auxv_fd)); + } + + scratch_end(scratch); + return result; +} + +internal DMN_LNX_Thread * +dmn_lnx_thread_from_pid(pid_t tid) +{ + return hash_table_search_u64_raw(dmn_lnx_state->tid_ht, tid); +} + +internal DMN_LNX_Process * +dmn_lnx_process_from_pid(pid_t pid) +{ + return hash_table_search_u64_raw(dmn_lnx_state->pid_ht, pid); +} + +internal Rng1U64 +dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) +{ + Rng1U64 result = { .min = max_U64 }; + + for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize) + { + ELF_Phdr64 phdr = {0}; + if(elf_read_phdr(dmn_lnx_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok) + { + Assert(0 && "unable to read a program header"); + } + + if(phdr.p_type == ELF_PType_Load) + { + U64 min = rebase + phdr.p_vaddr; + U64 max = rebase + phdr.p_vaddr + phdr.p_memsz; + result.min = Min(result.min, min); + result.max = Max(result.max, max); + } + } + + return result; +} + +internal DMN_LNX_DynamicInfo +dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_class, U64 rebase, U64 dynamic_vaddr) +{ + DMN_LNX_DynamicInfo dynamic_info = {0}; + for(U64 dynamic_cursor = dynamic_vaddr; ; dynamic_cursor += elf_dyn_size_from_class(elf_class)) + { + // rjf: read next dyn entry + ELF_Dyn64 dyn = {0}; + if(elf_read_dyn(dmn_lnx_machine_op_mem_read, &memory_fd, dynamic_cursor, elf_class, &dyn) != MachineOpResult_Ok) { Assert(0 && "unable to read dynamic"); } + + // rjf: break on zero + if(dyn.tag == ELF_DynTag_Null) { break; } + + // extract reuiqred values out of dynamic section + if(dyn.tag == ELF_DynTag_Strtab) + { + dynamic_info.strtab_vaddr = rebase + dyn.val; + } + else if(dyn.tag == ELF_DynTag_Strsz) + { + dynamic_info.strtab_size = dyn.val; + } + else if(dyn.tag == ELF_DynTag_Symtab) + { + dynamic_info.symtab_vaddr = rebase + dyn.val; + } + else if(dyn.tag == ELF_DynTag_Syment) + { + dynamic_info.symtab_entry_size = dyn.val; + } + else if(dyn.tag == ELF_DynTag_Hash) + { + dynamic_info.hash_vaddr = rebase + dyn.val; + } + else if(dyn.tag == ELF_DynTag_GNU_Hash) + { + dynamic_info.gnu_hash_vaddr = rebase + dyn.val; + } + } + return dynamic_info; +} + +internal U64 +dmn_lnx_find_dynamic_phdr(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum) +{ + U64 result = max_U64; + + for(U64 ph_cursor = e_phaddr, ph_opl = (e_phaddr + e_phentsize * e_phnum); ph_cursor < ph_opl; ph_cursor += e_phentsize) + { + ELF_Phdr64 phdr = {0}; + if(elf_read_phdr(dmn_lnx_machine_op_mem_read, &memory_fd, ph_cursor, elf_class, &phdr) != MachineOpResult_Ok) + { + Assert(0 && "unable to read a program header"); + } + + if(phdr.p_type == ELF_PType_Dynamic) + { + result = rebase + phdr.p_vaddr; + break; + } + } + + return result; +} + +internal U64 +dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vbase, B32 is_rebased) +{ + Temp scratch = scratch_begin(0, 0); + + U64 rdebug_vaddr = 0; + + // load DL's header + ELF_Hdr64 ehdr = {0}; + if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &memory_fd, loader_vbase, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); goto exit; } + + U64 rebase = ehdr.e_type == ELF_Type_Dyn ? loader_vbase : 0; + ELF_Class elf_class = ehdr.e_ident[ELF_Identifier_Class]; + + // find dynamic program header + U64 phdr_vaddr = loader_vbase + ehdr.e_phoff; + U64 dynamic_vaddr = dmn_lnx_find_dynamic_phdr(memory_fd, elf_class, rebase, phdr_vaddr, ehdr.e_phentsize, ehdr.e_phentsize); + + // extract necessary info out of dynamic program header + U64 dynamic_info_rebase = is_rebased ? 0 : rebase; + DMN_LNX_DynamicInfo dynamic_info = dmn_lnx_dynamic_info_from_memory(memory_fd, elf_class, dynamic_info_rebase, dynamic_vaddr); + + // extract symbol table count from available options + U64 symbol_count = 0; + if(dynamic_info.hash_vaddr) + { + U64 hash_entry_size = 4; + if(elf_class == ELF_Class_64 && (ehdr.e_machine == ELF_MachineKind_ALPHA || ehdr.e_machine == ELF_MachineKind_S390 || ehdr.e_machine == ELF_MachineKind_S390_OLD)) + { + hash_entry_size = 8; + } + + U64 chain_count = 0; + if(dmn_lnx_read(memory_fd, r1u64(dynamic_info.hash_vaddr, dynamic_info.hash_vaddr + hash_entry_size), &chain_count) == hash_entry_size) + { + symbol_count = chain_count; + } + else + { + Assert(0 && "failed to read hash table's chain count out of HASH"); + } + } + else + { + // TODO: extract count from GNU_HASH + NotImplemented; + } + + // scan symbol table for the rendezvous symbol + if(dynamic_info.symtab_vaddr && dynamic_info.symtab_entry_size && symbol_count) + { + for EachIndex(symbol_idx, symbol_count) + { + ELF_Sym64 symbol = {0}; + if(elf_read_symbol(dmn_lnx_machine_op_mem_read, &memory_fd, dynamic_info.symtab_vaddr + symbol_idx * dynamic_info.symtab_entry_size, elf_class, &symbol) != MachineOpResult_Ok) + { + Assert(0 && "failed to read symbol table"); + break; + } + + Temp temp = temp_begin(scratch.arena); + + String8 symbol_name = {0}; + if(symbol.st_name < dynamic_info.strtab_size) + { + U64 cap = dynamic_info.strtab_size - symbol.st_name; + symbol_name = dmn_lnx_read_string_capped(temp.arena, memory_fd, dynamic_info.strtab_vaddr + symbol.st_name, cap); + } + + if(str8_match(symbol_name, str8_lit("_r_debug"), 0)) + { + ELF_SymType symbol_type = ELF_ST_TYPE(symbol.st_info); + if(symbol_type == ELF_SymType_Object && symbol.st_size > 0) + { + rdebug_vaddr = rebase + symbol.st_value; + break; + } + } + + temp_end(temp); + } + } + + exit:; + scratch_end(scratch); + return rdebug_vaddr; +} + +internal DMN_LNX_ProbeList +dmn_lnx_read_probes(Arena *arena, int fd, U64 offset, U64 image_base) +{ + Temp scratch = scratch_begin(&arena, 1); + + DMN_LNX_ProbeList probes = {0}; + + ELF_Hdr64 ehdr = {0}; + if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &fd, offset, &ehdr) != MachineOpResult_Ok) { goto exit; } + + U64 strtab_shdr_offset = offset + ehdr.e_shoff + ehdr.e_shstrndx * ehdr.e_shentsize; + ELF_Shdr64 strtab_shdr = {0}; + if(elf_read_shdr(dmn_lnx_machine_op_mem_read, &fd, strtab_shdr_offset, ehdr.e_ident[ELF_Identifier_Class], &strtab_shdr) != MachineOpResult_Ok) { goto exit; } + + B32 found_probes = 0; + B32 found_probes_base = 0; + ELF_Shdr64 text_shdr = {0}; + ELF_Shdr64 stapsdt_base_shdr = {0}; + ELF_Shdr64 stapsdt_shdr = {0}; + for(U64 shdr_off = offset + ehdr.e_shoff, shdr_opl = shdr_off + ehdr.e_shentsize * ehdr.e_shnum; + shdr_off < shdr_opl; + shdr_off += ehdr.e_shentsize) { + ELF_Shdr64 shdr = {0}; + if(elf_read_shdr(dmn_lnx_machine_op_mem_read, &fd, shdr_off, ehdr.e_ident[ELF_Identifier_Class], &shdr) != MachineOpResult_Ok) { goto exit; } + + if(shdr.sh_type == ELF_ShType_Note) + { + U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; + U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; + String8 name = dmn_lnx_read_string_capped(scratch.arena, fd, name_offset, name_cap); + + if(str8_match(name, str8_lit(".note.stapsdt"), 0)) + { + stapsdt_shdr = shdr; + found_probes = 1; + } + } + else if(shdr.sh_type == ELF_ShType_ProgBits) + { + U64 name_offset = offset + strtab_shdr.sh_offset + shdr.sh_name; + U64 name_cap = offset + strtab_shdr.sh_offset + strtab_shdr.sh_size; + String8 name = dmn_lnx_read_string_capped(scratch.arena, fd, name_offset, name_cap); + + if(str8_match(name, str8_lit(".stapsdt.base"), 0)) + { + stapsdt_base_shdr = shdr; + found_probes_base = 1; + } else if(str8_match(name, str8_lit(".text"), 0)) + { + text_shdr = shdr; + } + } + + if(found_probes && found_probes_base) { break; } + } + + if(!found_probes || !found_probes_base) { goto exit; } + + U64 probes_base = stapsdt_base_shdr.sh_addr; + + Rng1U64 note_range = shift_1u64(r1u64(stapsdt_shdr.sh_offset, stapsdt_shdr.sh_offset + stapsdt_shdr.sh_size), offset); + void *raw_note = push_array(arena, U8, stapsdt_shdr.sh_size); + U64 note_read_size = dmn_lnx_read(fd, note_range, raw_note); + if(note_read_size != dim_1u64(note_range)) { goto exit; } + + Arch arch = arch_from_elf_machine(ehdr.e_machine); + ELF_NoteList note = elf_parse_note(scratch.arena, str8(raw_note, dim_1u64(note_range)), ehdr.e_ident[ELF_Identifier_Class], ehdr.e_machine); + + for EachNode(n, ELF_NoteNode, note.first) + { + ELF_Note *note = &n->v; + if(!str8_match(note->owner, str8_lit("stapsdt"), 0)) { continue; } + if(note->type != ELF_NoteType_STapSdt) { continue; } + + DMN_LNX_Probe probe = {0}; + { + U64 cursor = 0; + U64 addr_size = ehdr.e_ident[ELF_Identifier_Class] == ELF_Class_64 ? 8 : 4; + + U64 pc = 0; + U64 pc_size = str8_deserial_read(note->desc, cursor, &pc, addr_size, addr_size); + if (pc_size == 0) { goto exit; } + cursor += pc_size; + + U64 base_addr = 0; + U64 base_addr_size = str8_deserial_read(note->desc, cursor, &base_addr, addr_size, addr_size); + if (base_addr_size == 0) { goto exit; } + cursor += base_addr_size; + + U64 semaphore = 0; + U64 semaphore_size = str8_deserial_read(note->desc, cursor, &semaphore, addr_size, addr_size); + if (semaphore_size == 0) { goto exit; } + cursor += semaphore_size; + + String8 provider = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += provider.size + 1; + if (cursor > note->desc.size) { goto exit; } + + String8 name = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += name.size + 1; + if (cursor > note->desc.size) { goto exit; } + + String8 args = str8_cstring_capped(note->desc.str + cursor, note->desc.str + note->desc.size); + cursor += args.size + 1; + if (cursor > note->desc.size) { goto exit; } + + U64 probe_rebase = image_base + (base_addr - probes_base); + + probe.provider = provider; + probe.name = name; + probe.args = stap_arg_array_from_string(arena, arch, args); + probe.pc = pc + probe_rebase; + probe.semaphore = semaphore ? semaphore + probe_rebase : 0; + } + + DMN_LNX_ProbeNode *n = push_array(arena, DMN_LNX_ProbeNode, 1); + n->v = probe; + SLLQueuePush(probes.first, probes.last, n); + probes.count += 1; + } + + exit:; + scratch_end(scratch); + return probes; +} + +internal +STAP_MEMORY_READ(dmn_lnx_stap_memory_read) +{ + DMN_LNX_Process *process = raw_ctx; + U64 bytes_read = dmn_lnx_read(process->fd, r1u64(addr, addr + read_size), buffer); + return bytes_read == read_size; +} + +internal DMN_LNX_Entity * +dmn_lnx_entity_alloc(DMN_LNX_EntityKind kind) +{ + DMN_LNX_Entity *entity = dmn_lnx_state->free_entity; + if(entity != 0) + { + SLLStackPop(dmn_lnx_state->free_entity); + } + else + { + entity = push_array(dmn_lnx_state->entities_arena, DMN_LNX_Entity, 1); + dmn_lnx_state->entities_count += 1; + } + U32 gen = entity->gen; + entity->gen += 1; + entity->kind = kind; + return entity; +} + +internal DMN_LNX_Process * +dmn_lnx_process_alloc(pid_t pid, DMN_LNX_ProcessState state, DMN_LNX_Process *parent_process, B32 debug_subprocesses, B32 is_cow) +{ + Temp scratch = scratch_begin(0, 0); + + DMN_LNX_Process *process = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Process)->process; + process->pid = pid; + process->fd = OS_LNX_RETRY_ON_EINTR(open((char *)str8f(scratch.arena, "/proc/%d/mem", pid).str, O_RDWR)); + process->state = state; + process->debug_subprocesses = debug_subprocesses; + process->is_cow = is_cow; + process->parent_process = parent_process; + + // update pending process tracker + if(state != DMN_LNX_ProcessState_Normal) + { + dmn_lnx_state->process_pending_creation += 1; + } + + // add process to the list + DLLPushBack(dmn_lnx_state->first_process, dmn_lnx_state->last_process, process); + dmn_lnx_state->process_count += 1; + + // push pid -> DMN_LNX_Process mapping + hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->pid_ht, pid, process); + + scratch_end(scratch); + return process; +} + +internal DMN_LNX_ProcessCtx * +dmn_lnx_process_ctx_alloc(DMN_LNX_Process *process, B32 is_rebased) +{ + DMN_LNX_ProcessCtx *ctx = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; + + ELF_Hdr64 exe_ehdr = dmn_lnx_ehdr_from_pid(process->pid); + DMN_LNX_Auxv auxv = dmn_lnx_auxv_from_pid(process->pid, exe_ehdr.e_ident[ELF_Identifier_Class]); + Arch arch = arch_from_elf_machine(exe_ehdr.e_machine); + U64 rdebug_vaddr = dmn_lnx_rdebug_vaddr_from_memory(process->fd, auxv.base, is_rebased); + U64 base_vaddr = (auxv.phdr & ~(auxv.pagesz-1)); + U64 rebase = exe_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + Rng1U64 image_vrange = dmn_lnx_compute_image_vrange(process->fd, exe_ehdr.e_ident[ELF_Identifier_Class], rebase, auxv.phdr, auxv.phent, auxv.phnum); + Arena *ctx_arena = arena_alloc(); + + ELF_Class dl_class; + { + ELF_Hdr64 ehdr = {0}; + if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &process->fd, auxv.base, &ehdr) != MachineOpResult_Ok) { Assert(0 && "failed to read interp's header"); } + dl_class = ehdr.e_ident[ELF_Identifier_Class]; + } + + // query xsave layout + U64 xcr0 = 0; + U64 xsave_size = 0; + X64_XSaveLayout xsave_layout = {0}; + if(arch == Arch_x64) + { + X64_XSave xsave = {0}; + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, process->pid, (void *)NT_X86_XSTATE, &(struct iovec){.iov_base = &xsave, .iov_len = sizeof(xsave) }) >= 0)) + { + // Linux stores xcr0 bits in fxstate padding, + // see https://github.com/torvalds/linux/blob/6548d364a3e850326831799d7e3ea2d7bb97ba08/arch/x86/include/asm/user.h#L25 + xcr0 = *(U64 *)((U8 *)&xsave + 464); + xsave_size = x64_get_xsave_size(); + xsave_layout = x64_get_xsave_layout(xcr0); + } + else { Assert(0 && "failed to get xstate"); } + } + + // gather probes + DMN_LNX_Probe **known_probes = push_array(ctx_arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count); + { + Temp scratch = scratch_begin(0, 0); + + String8 dl_path = dmn_lnx_dl_path_from_pid(scratch.arena, process->pid, auxv.base); + int dl_fd = OS_LNX_RETRY_ON_EINTR(open((char *)dl_path.str, O_RDONLY)); + + DMN_LNX_ProbeList probes = {0}; + if(dl_fd >= 0) + { + probes = dmn_lnx_read_probes(ctx_arena, dl_fd, 0, auxv.base); + OS_LNX_RETRY_ON_EINTR(close(dl_fd)); + } + + for EachNode(n, DMN_LNX_ProbeNode, probes.first) + { + DMN_LNX_Probe *p = &n->v; + if(str8_match(p->provider, str8_lit("rtld"), 0)) + { +#define X(_N,_A,_S) if(str8_match(p->name, str8_lit(_S), 0)) { AssertAlways(p->args.count == _A); known_probes[DMN_LNX_ProbeType_##_N] = p; continue ; } + DMN_LNX_Probe_XList +#undef X + } + } + + scratch_end(scratch); + } + + ctx = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; + ctx->arena = arena_alloc(); + ctx->arch = arch; + ctx->rdebug_vaddr = rdebug_vaddr; + ctx->dl_class = dl_class; + ctx->loaded_modules_ht = hash_table_init(ctx_arena, 0x1000); + ctx->probes = known_probes; + ctx->xcr0 = xcr0; + ctx->xsave_size = Max(xsave_size, sizeof(X64_XSave)); + ctx->xsave_layout = xsave_layout; + + // create main module + DMN_LNX_Module *main_module = dmn_lnx_module_alloc(ctx, process->fd, base_vaddr, auxv.execfn, 1, 1); + + // glibc has a shortcut mapping for the main module + hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, 0, main_module); + + return ctx; +} + +internal DMN_LNX_Thread * +dmn_lnx_thread_alloc(DMN_LNX_Process *process, DMN_LNX_ThreadState thread_state, pid_t tid) +{ + void *reg_block; + if(process->ctx->free_reg_blocks.node_count) + { + String8Node *n = str8_list_pop_front(&process->ctx->free_reg_blocks); + reg_block = n->string.str; + str8_list_push_node(&process->ctx->free_reg_block_nodes, n); + } + else + { + ARCH_Info *arch_info = arch_info_from_arch(process->ctx->arch); + U64 reg_block_size = arch_info->reg_block_size; + reg_block = push_array(process->ctx->arena, U8, reg_block_size); + } + + DMN_LNX_Thread *thread = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Thread)->thread; + thread->tid = tid; + thread->state = thread_state; + thread->process = process; + thread->reg_block = reg_block; + if(thread_state == DMN_LNX_ThreadState_Stopped) + { + thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread); + } + + // add thread to the list + DLLPushBack(process->first_thread, process->last_thread, thread); + process->thread_count += 1; + + // push tid -> thread mapping + hash_table_push_u64_raw(dmn_lnx_state->arena, dmn_lnx_state->tid_ht, thread->tid, thread); + + // update global thread counter + if(thread_state == DMN_LNX_ThreadState_PendingCreation) + { + dmn_lnx_state->threads_pending_creation += 1; + } + + return thread; +} + +internal DMN_LNX_Module * +dmn_lnx_module_alloc(DMN_LNX_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main) +{ + DMN_LNX_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, base_vaddr); + if(module) { goto exit; } + + // parse out module's ELF header + ELF_Hdr64 module_ehdr = {0}; + if(elf_read_ehdr(dmn_lnx_machine_op_mem_read, &memory_fd, base_vaddr, &module_ehdr) != MachineOpResult_Ok) { goto exit; } + + // gather info about module + U64 module_rebase = module_ehdr.e_type == ELF_Type_Dyn ? base_vaddr : 0; + U64 module_phdr_vaddr = module_rebase + module_ehdr.e_phoff; + Rng1U64 module_vrange = dmn_lnx_compute_image_vrange(memory_fd, module_ehdr.e_ident[ELF_Identifier_Class], module_rebase, module_phdr_vaddr, module_ehdr.e_phentsize, module_ehdr.e_phnum); + + // read TLS index and TLS offset + U64 tls_index = max_U64; + U64 tls_offset = max_U64; + if(is_main) + { + tls_index = 1; + tls_offset = 0; + } + else + { + if(dmn_lnx_state->is_tls_detected) + { + Rng1U64 tls_modid_range = r1u64(dmn_lnx_state->tls_modid_desc.offset, dmn_lnx_state->tls_modid_desc.offset + dmn_lnx_state->tls_modid_desc.bit_size / 8); + Rng1U64 tls_offset_range = r1u64(dmn_lnx_state->tls_offset_desc.offset, dmn_lnx_state->tls_offset_desc.offset + dmn_lnx_state->tls_offset_desc.bit_size / 8); + tls_modid_range = shift_1u64(tls_modid_range, base_vaddr); + tls_offset_range = shift_1u64(tls_offset_range, base_vaddr); + if(!dmn_lnx_read(memory_fd, tls_modid_range, &tls_index)) { Assert(0 && "failed to read TLS index"); } + if(!dmn_lnx_read(memory_fd, tls_offset_range, &tls_offset)) { Assert(0 && "failed to read TLS offset"); } + } + } + + module = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Module)->module; + module->base_vaddr = base_vaddr; + module->name_vaddr = name_vaddr; + module->name_space_id = name_space_id; + module->size = dim_1u64(module_vrange); + module->phvaddr = base_vaddr + module_ehdr.e_phoff; + module->phcount = module_ehdr.e_phnum; + module->phentsize = module_ehdr.e_phentsize; + module->tls_index = tls_index; + module->tls_offset = tls_offset; + module->is_main = is_main; + + // add module to the list + DLLPushBack(ctx->first_module, ctx->last_module, module); + ctx->module_count += 1; + + // push base address -> module mapping + hash_table_push_u64_raw(ctx->arena, ctx->loaded_modules_ht, base_vaddr, module); + + exit:; + return module; +} + +internal void +dmn_lnx_entity_release(DMN_LNX_Entity *entity) +{ + U32 gen = entity->gen + 1; + MemoryZeroStruct(entity); + entity->gen = gen; + SLLStackPush(dmn_lnx_state->free_entity, entity); +} + +internal void +dmn_lnx_process_release(DMN_LNX_Process *process) +{ + // update global state + AssertAlways(dmn_lnx_state->process_count > 0); + DLLRemove(dmn_lnx_state->first_process, dmn_lnx_state->last_process, process); + dmn_lnx_state->process_count -= 1; + + // update pending process tracker + if(process->state != DMN_LNX_ProcessState_Normal) + { + Assert(dmn_lnx_state->process_pending_creation > 0); + dmn_lnx_state->process_pending_creation -= 1; + } + + // close memory handle + if(OS_LNX_RETRY_ON_EINTR(close(process->fd)) < 0) { Assert(0 && "failed to close memory descriptor"); } + + // remove pid mapping + hash_table_purge_u64(dmn_lnx_state->pid_ht, process->pid); + + // release the context + if(process->ctx) + { + dmn_lnx_process_ctx_release(process->ctx); + } + + // release process entity + dmn_lnx_entity_release((DMN_LNX_Entity *)process); +} + +internal void +dmn_lnx_process_ctx_release(DMN_LNX_ProcessCtx *ctx) +{ + Assert(ctx->ref_count > 0); + ctx->ref_count -= 1; + + if(ctx->ref_count == 0) + { + arena_release(ctx->arena); + dmn_lnx_entity_release((DMN_LNX_Entity *)ctx); + } +} + +internal void +dmn_lnx_thread_release(DMN_LNX_Thread *thread) +{ + DMN_LNX_Process *process = thread->process; + + // purge tid mapping + hash_table_purge_u64(dmn_lnx_state->tid_ht, thread->tid); + + // update global thread counter + if(thread->state == DMN_LNX_ThreadState_PendingCreation) + { + AssertAlways(dmn_lnx_state->threads_pending_creation > 0); + dmn_lnx_state->threads_pending_creation -= 1; + } + + // remove thread from the list + Assert(process->thread_count > 0); + DLLRemove(process->first_thread, process->last_thread, thread); + process->thread_count -= 1; + + // push reg block to the free list + String8Node *reg_block_node; + if(process->ctx->free_reg_block_nodes.node_count) + { + reg_block_node = str8_list_pop_front(&process->ctx->free_reg_block_nodes); + } + else + { + reg_block_node = push_array(process->ctx->arena, String8Node, 1); + } + reg_block_node->string = str8(thread->reg_block, 0); + str8_list_push_node(&process->ctx->free_reg_blocks, reg_block_node); + + dmn_lnx_entity_release((DMN_LNX_Entity *)thread); +} + +internal void +dmn_lnx_module_release(DMN_LNX_ProcessCtx *ctx, DMN_LNX_Module *module) +{ + // remove module from the list + Assert(ctx->module_count > 0); + DLLRemove(ctx->first_module, ctx->last_module, module); + ctx->module_count -= 1; + + // purge base addr -> module mapping + hash_table_purge_u64(ctx->loaded_modules_ht, module->base_vaddr); + + dmn_lnx_entity_release((DMN_LNX_Entity *)module); +} + +internal DMN_LNX_ProcessCtx * +dmn_lnx_process_ctx_clone(DMN_LNX_Process *new_owner, DMN_LNX_ProcessCtx *ctx) +{ + DMN_LNX_ProcessCtx *result = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_ProcessCtx)->process_ctx; + + result->arena = arena_alloc(); + result->arch = ctx->arch; + result->rdebug_vaddr = ctx->rdebug_vaddr; + result->dl_class = ctx->dl_class; + result->loaded_modules_ht = hash_table_init(result->arena, ctx->loaded_modules_ht->cap); + result->xcr0 = ctx->xcr0; + result->xsave_size = ctx->xsave_size; + result->xsave_layout = ctx->xsave_layout; + + // clone probes + result->probes = push_array(result->arena, DMN_LNX_Probe *, DMN_LNX_ProbeType_Count); + for EachIndex(probe_idx, DMN_LNX_ProbeType_Count) + { + DMN_LNX_Probe *dst = result->probes[probe_idx]; + DMN_LNX_Probe *src = ctx->probes[probe_idx]; + + dst->provider = str8_copy(result->arena, src->provider); + dst->name = str8_copy(result->arena, src->name); + dst->args_string = str8_copy(result->arena, src->args_string); + dst->args = stap_arg_array_copy(result->arena, src->args); + dst->pc = src->pc; + dst->semaphore = src->semaphore; + } + + // clone probe traps + for EachNode(src, DMN_ActiveTrap, ctx->first_probe_trap) + { + DMN_Trap *src_trap = src->trap; + DMN_Trap *dst_trap = push_array(result->arena, DMN_Trap, 1); + dst_trap->process = dmn_lnx_handle_from_process(new_owner); + dst_trap->vaddr = src_trap->vaddr; + dst_trap->id = src_trap->id; + dst_trap->flags = src_trap->flags; + dst_trap->size = src_trap->size; + + DMN_ActiveTrap *dst = push_array(result->arena, DMN_ActiveTrap, 1); + dst->trap = dst_trap; + dst->swap_bytes = str8_copy(result->arena, src->swap_bytes); + + SLLQueuePush(result->first_probe_trap, result->last_probe_trap, dst); + } + + // clone modules + for EachNode(module, DMN_LNX_Module, ctx->first_module) + { + dmn_lnx_module_clone(result, module); + } + + return result; +} + +internal DMN_LNX_Module * +dmn_lnx_module_clone(DMN_LNX_ProcessCtx *process_ctx, DMN_LNX_Module *module) +{ + DMN_LNX_Module *result = &dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Module)->module; + *result = *module; + result->next = result->prev = 0; + + // clone base addr mapping + hash_table_push_u64_raw(process_ctx->arena, process_ctx->loaded_modules_ht, result->base_vaddr, result); + + // push module to the list + DLLPushBack(process_ctx->first_module, process_ctx->last_module, module); + process_ctx->module_count += 1; + + return result; +} + +internal DMN_Handle +dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity) +{ + DMN_Handle handle = {0}; + U64 index = IntFromPtr(entity - dmn_lnx_state->entities_base); + if(index <= max_U32) + { + handle.u32[0] = index; + handle.u32[1] = entity->gen; + } + else + { + Assert(0 && "failed to make a handle for the entity"); + } + return handle; +} + +internal DMN_Handle +dmn_lnx_handle_from_process(DMN_LNX_Process *process) +{ + return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)process); +} + +internal DMN_Handle +dmn_lnx_handle_from_process_ctx(DMN_LNX_ProcessCtx *process_ctx) +{ + return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)process_ctx); +} + +internal DMN_Handle +dmn_lnx_handle_from_thread(DMN_LNX_Thread *thread) +{ + return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)thread); +} + +internal DMN_Handle +dmn_lnx_handle_from_module(DMN_LNX_Module *module) +{ + return dmn_lnx_handle_from_entity((DMN_LNX_Entity *)module); +} + +internal DMN_LNX_Entity * +dmn_lnx_entity_from_handle(DMN_Handle handle, DMN_LNX_EntityKind expected_kind) +{ + DMN_LNX_Entity *result = 0; + U32 index = handle.u32[0]; + U32 gen = handle.u32[1]; + if(index < dmn_lnx_state->entities_count && dmn_lnx_state->entities_base[index].gen == gen) + { + if(dmn_lnx_state->entities_base[index].kind == expected_kind) + { + result = &dmn_lnx_state->entities_base[index]; + } + } + return result; +} + +internal DMN_LNX_Process * +dmn_lnx_process_from_handle(DMN_Handle process_handle) +{ + return (DMN_LNX_Process *)dmn_lnx_entity_from_handle(process_handle, DMN_LNX_EntityKind_Process); +} + +internal DMN_LNX_ProcessCtx * +dmn_lnx_process_ctx_from_handle(DMN_Handle process_ctx_handle) +{ + return (DMN_LNX_ProcessCtx *)dmn_lnx_entity_from_handle(process_ctx_handle, DMN_LNX_EntityKind_ProcessCtx); +} + +internal DMN_LNX_Thread * +dmn_lnx_thread_from_handle(DMN_Handle thread_handle) +{ + return (DMN_LNX_Thread *)dmn_lnx_entity_from_handle(thread_handle, DMN_LNX_EntityKind_Thread); +} + +internal DMN_LNX_Module * +dmn_lnx_module_from_handle(DMN_Handle module_handle) +{ + return (DMN_LNX_Module *)dmn_lnx_entity_from_handle(module_handle, DMN_LNX_EntityKind_Module); +} + +internal void +dmn_lnx_process_trap_probes(DMN_LNX_Process *process) +{ + for EachIndex(i, DMN_LNX_ProbeType_Count) + { + if(process->ctx->probes[i] == 0) { continue; } + + DMN_Trap *trap = push_array(process->ctx->arena, DMN_Trap, 1); + trap->process = dmn_lnx_handle_from_process(process); + trap->vaddr = process->ctx->probes[i]->pc; + trap->id = i; + + DMN_ActiveTrap *active_trap = dmn_set_trap(process->ctx->arena, trap); + SLLQueuePush(process->ctx->first_probe_trap, process->ctx->last_probe_trap, active_trap); + + if(BUILD_DEBUG && process->ctx->arch == Arch_x64) + { + Assert(active_trap->swap_bytes.size == 1 && active_trap->swap_bytes.str[0] == 0x90); + } + } +} + +internal void +dmn_lnx_process_untrap_probes(DMN_LNX_Process *process) +{ + NotImplemented; +} + +internal U64 +dmn_lnx_thread_read_ip(DMN_LNX_Thread *thread) +{ + ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); + U64 result = arch_ip_from_reg_block(arch_info, thread->reg_block); + return result; +} + +internal U64 +dmn_lnx_thread_read_sp(DMN_LNX_Thread *thread) +{ + ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); + U64 result = arch_sp_from_reg_block(arch_info, thread->reg_block); + return result; +} + +internal void +dmn_lnx_thread_write_ip(DMN_LNX_Thread *thread, U64 ip) +{ + ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); + arch_reg_block_write_ip(arch_info, thread->reg_block, ip); + thread->is_reg_block_dirty = 1; +} + +internal void +dmn_lnx_thread_write_sp(DMN_LNX_Thread *thread, U64 sp) +{ + ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); + arch_reg_block_write_sp(arch_info, thread->reg_block, sp); + thread->is_reg_block_dirty = 1; +} + +internal B32 +dmn_lnx_thread_read_reg_block(DMN_LNX_Thread *thread) +{ + B32 is_reg_block_read = 0; + if(thread->state == DMN_LNX_ThreadState_Stopped) + { + switch(thread->process->ctx->arch) + { + default:{}break; + case Arch_x64: + { + DMN_LNX_ProcessCtx *process_ctx = thread->process->ctx; + X64_RegBlock *dst = thread->reg_block; + + // general purpose registers + { + OS_LNX_GprsX64 src; + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_len = sizeof(src), .iov_base = &src })); + if(ptrace_result < 0) { goto exit; } + + dst->r15 = src.r15; + dst->r14 = src.r14; + dst->r13 = src.r13; + dst->r12 = src.r12; + dst->rbp = src.rbp; + dst->rbx = src.rbx; + dst->r11 = src.r11; + dst->r10 = src.r10; + dst->r9 = src.r9; + dst->r8 = src.r8; + dst->rax = src.rax; + dst->rcx = src.rcx; + dst->rdx = src.rdx; + dst->rsi = src.rsi; + dst->rdi = src.rdi; + dst->rip = src.rip; + dst->cs = src.cs; + dst->rflags = src.rflags; + dst->rsp = src.rsp; + dst->ss = src.ss; + dst->fsbase = src.fsbase; + dst->gsbase = src.gsbase; + dst->ds = src.ds; + dst->es = src.es; + dst->fs = src.fs; + dst->gs = src.gs; + thread->orig_rax = src.orig_rax; + } + + // xsave + { + Temp scratch = scratch_begin(0, 0); + + X64_XSave *xsave = 0; + X64_FXSave *fxsave = 0; + + // get xsave + if(x64_is_xsave_supported()) + { + void *xsave_raw = push_array(scratch.arena, U8, process_ctx->xsave_size); + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_len = process_ctx->xsave_size, .iov_base = xsave_raw })); + if(ptrace_result < 0) { goto exit; } + xsave = xsave_raw; + fxsave = &xsave->fxsave; + } + + // get fxsave + if(fxsave == 0) + { + fxsave = push_array(scratch.arena, X64_FXSave, 1); + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETREGSET, thread->tid, (void *)NT_FPREGSET, &(struct iovec){ .iov_len = sizeof(*fxsave), .iov_base = fxsave })); + if(ptrace_result < 0) { goto exit; } + fxsave = 0; + } + + // copy fxsave registers + if(fxsave) + { + X64_FXSave *src = fxsave; + + // copy x87 registers + dst->fcw = src->fcw; + dst->fsw = src->fsw; + dst->ftw = src->ftw; + dst->fop = src->fop; + dst->fip = src->fip; + dst->fdp = src->fdp; + dst->mxcsr = src->mxcsr; + dst->mxcsr_mask = src->mxcsr_mask; + for EachIndex(i, 8) + { + MemoryCopy(&dst->st0 + i, src->st_space + i, sizeof(U80)); + } + + // SSE registers are always available in x64 + { + U128 *xmm_d = fxsave->xmm_space; + U512 *zmm_s = &dst->zmm0; + for EachIndex(i, 16) + { + MemoryCopy(&zmm_s[i], &xmm_d[i], sizeof(*xmm_d)); + } + } + } + + // copy xsave registers + // + // compact register layout is not supported (xsave->header.xcomp_bv == 0) + // + if(xsave && xsave->header.xcomp_bv == 0) + { + if(xsave->header.xstate_bv & X64_XStateComponentFlag_AVX && + process_ctx->xsave_layout.avx_offset + 16*sizeof(U128) <= process_ctx->xsave_size) + { + U128 *avx_s = (U128 *)((U8 *)xsave + process_ctx->xsave_layout.avx_offset); + U512 *zmm_d = &dst->zmm0; + for EachIndex(n, 16) + { + MemoryCopy(&zmm_d[n].u8[16], &avx_s[n], sizeof(U128)); + } + } + + if(xsave->header.xstate_bv & X64_XStateComponentFlag_OPMASK && + process_ctx->xsave_layout.opmask_offset + sizeof(U64) * 8 <= process_ctx->xsave_size) + { + U64 *kmask_s = (U64 *)((U8 *)xsave + process_ctx->xsave_layout.opmask_offset); + U64 *kmask_d = &dst->k0; + for EachIndex(n, 8) + { + MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(U64)); + } + } + + if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM_H && + process_ctx->xsave_layout.zmm_h_offset + sizeof(U256) * 16 <= process_ctx->xsave_size) + { + U256 *avx512h_s = (U256 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_h_offset); + U512 *zmmh_d = &dst->zmm0; + for EachIndex(n, 16) + { + MemoryCopy(&zmmh_d[n].u8[32], &avx512h_s[n], sizeof(U256)); + } + } + + if(xsave->header.xstate_bv & X64_XStateComponentFlag_ZMM && + process_ctx->xsave_layout.zmm_offset + sizeof(U512) * 16 <= process_ctx->xsave_size) + { + U512 *avx512_s = (U512 *)((U8 *)xsave + process_ctx->xsave_layout.zmm_offset); + U512 *zmm_d = &dst->zmm16; + for EachIndex(n, 16) + { + MemoryCopy(&zmm_d[n], &avx512_s[n], sizeof(U512)); + } + } + + if(xsave->header.xstate_bv & X64_XStateComponentFlag_CETU && + process_ctx->xsave_layout.cet_u_offset + sizeof(U64)*2 <= process_ctx->xsave_size) + { + U64 *cet_u = (U64 *)((U8 *)xsave + process_ctx->xsave_layout.cet_u_offset); + dst->cetmsr = cet_u[0]; + dst->cetssp = cet_u[1]; + } + } + + scratch_end(scratch); + } + + // debug registers + { + U64 *dr_d = &dst->dr0; + for EachIndex(n, 8) + { + if(n != 4 && n != 5) + { + U64 offset = OffsetOf(OS_LNX_UserX64, u_debugreg[n]); + errno = 0; + long peek_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_PEEKUSER, thread->tid, PtrFromInt(offset), 0)); + if(errno != 0) { goto exit; } + dr_d[n] = peek_result; + } + } + } + + is_reg_block_read = 1; + }break; + } + } + exit:; + return is_reg_block_read; +} + +internal B32 +dmn_lnx_thread_write_reg_block(DMN_LNX_Thread *thread) +{ + AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); + + B32 is_reg_block_written = 0; + + switch(thread->process->ctx->arch) + { + case Arch_Null: {} break; + case Arch_x64: + { + DMN_LNX_ProcessCtx *process_ctx = thread->process->ctx; + X64_RegBlock *src = thread->reg_block; + + // general purpose registers + { + OS_LNX_GprsX64 dst; + dst.r15 = src->r15; + dst.r14 = src->r14; + dst.r13 = src->r13; + dst.r12 = src->r12; + dst.rbp = src->rbp; + dst.rbx = src->rbx; + dst.r11 = src->r11; + dst.r10 = src->r10; + dst.r9 = src->r9; + dst.r8 = src->r8; + dst.rax = src->rax; + dst.rcx = src->rcx; + dst.rdx = src->rdx; + dst.rsi = src->rsi; + dst.rdi = src->rdi; + dst.orig_rax = thread->orig_rax; + dst.rip = src->rip; + dst.cs = src->cs; + dst.rflags = src->rflags; + dst.rsp = src->rsp; + dst.ss = src->ss; + dst.fsbase = src->fsbase; + dst.gsbase = src->gsbase; + dst.ds = src->ds; + dst.es = src->es; + dst.fs = src->fs; + dst.gs = src->gs; + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_PRSTATUS, &(struct iovec){ .iov_base = &dst, .iov_len = sizeof(dst) })); + if(ptrace_result < 0) { goto exit; } + } + + // xsave + { + Temp scratch = scratch_begin(0, 0); + + X64_FXSave dst_fxsave = {0}; + { + dst_fxsave.fcw = src->fcw; + dst_fxsave.fsw = src->fsw; + dst_fxsave.ftw = src->ftw; + dst_fxsave.fop = src->fop; + dst_fxsave.fip = src->fip; + dst_fxsave.fdp = src->fdp; + dst_fxsave.mxcsr = src->mxcsr; + dst_fxsave.mxcsr_mask = src->mxcsr_mask; + + U128 *st_d = (U128 *)dst_fxsave.st_space; + U80 *st_s = &src->st0; + for EachIndex(n, 8) + { + MemoryCopy(&st_d[n], &st_s[n], sizeof(U80)); + } + + U128 *xmm_d = (U128 *)dst_fxsave.xmm_space; + U512 *xmm_s = &src->zmm0; + for EachIndex(n, 16) + { + MemoryCopy(&xmm_d[n], &xmm_s[n], sizeof(U128)); + } + } + + if(x64_is_xsave_supported()) + { + U8 *xsave_raw = push_array(scratch.arena, U8, process_ctx->xsave_size); + X64_XSave *dst = (X64_XSave *)xsave_raw; + dst->fxsave = dst_fxsave; + dst->header.xstate_bv |= X64_XStateComponentFlag_FP; + dst->header.xstate_bv |= X64_XStateComponentFlag_SSE; + + if(process_ctx->xsave_layout.avx_offset) + { + if(process_ctx->xsave_layout.avx_offset + sizeof(U128) * 16 <= process_ctx->xsave_size) + { + U128 *avx_d = (U128 *)(xsave_raw + process_ctx->xsave_layout.avx_offset); + U512 *zmm_s = &src->zmm0; + for EachIndex(n, 16) + { + MemoryCopy(&avx_d[n], &zmm_s[n].u8[16], sizeof(U128)); + } + dst->header.xstate_bv |= X64_XStateComponentFlag_AVX; + } + } + + if(process_ctx->xsave_layout.opmask_offset) + { + if(process_ctx->xsave_layout.opmask_offset + sizeof(U64) * 8 <= process_ctx->xsave_size) + { + U64 *kmask_d = (U64 *)(xsave_raw + process_ctx->xsave_layout.opmask_offset); + U64 *kmask_s = &src->k0; + for EachIndex(n, 8) + { + MemoryCopy(&kmask_d[n], &kmask_s[n], sizeof(U64)); + } + dst->header.xstate_bv |= X64_XStateComponentFlag_OPMASK; + } + else { Assert(0 && "invalid xsave size"); goto exit; } + } + + if(process_ctx->xsave_layout.zmm_h_offset) + { + if(process_ctx->xsave_layout.zmm_h_offset + sizeof(U256) * 16 <= process_ctx->xsave_size) + { + U256 *avx512h_d = (U256 *)(xsave_raw + process_ctx->xsave_layout.zmm_h_offset); + U512 *zmmh_s = &src->zmm0; + for EachIndex(n, 16) + { + MemoryCopy(&avx512h_d[n], &zmmh_s[n].u8[32], sizeof(U256)); + } + dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM_H; + } + else { Assert(0 && "invalid xsave size"); goto exit; } + } + + if(process_ctx->xsave_layout.zmm_offset) + { + if(process_ctx->xsave_layout.zmm_offset + sizeof(U512) * 16 <= process_ctx->xsave_size) + { + U512 *avx512_d = (U512 *)(xsave_raw + process_ctx->xsave_layout.zmm_offset); + U512 *zmm_s = &src->zmm16; + for EachIndex(n, 16) + { + MemoryCopy(&avx512_d[n], &zmm_s[n], sizeof(U512)); + } + dst->header.xstate_bv |= X64_XStateComponentFlag_ZMM; + } + else { Assert(0 && "invalid xsave size"); goto exit; } + } + + if(process_ctx->xsave_layout.cet_u_offset) + { + if(process_ctx->xsave_layout.cet_u_offset + sizeof(U64) * 2 <= process_ctx->xsave_size) + { + U64 *cet_u = (U64 *)(xsave_raw + process_ctx->xsave_layout.cet_u_offset); + cet_u[0] = src->cetmsr; + cet_u[1] = src->cetssp; + dst->header.xstate_bv |= X64_XStateComponentFlag_CETU; + } + else { Assert(0 && "invalid xsave size"); goto exit; } + } + + // xsave + Assert(dst->header.xcomp_bv == 0); // must always be zero + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_SETREGSET, thread->tid, (void *)NT_X86_XSTATE, &(struct iovec){ .iov_base = dst, .iov_len = process_ctx->xsave_size })); + if(ptrace_result < 0) { goto exit; } + } + + scratch_end(scratch); + } + + // debug registers + { + src->dr7 |= (1 << 10); + + U64 *dr_s = &src->dr0; + for EachIndex(n, 8) + { + if(n != 4 && n != 5) + { + U64 offset = OffsetOf(OS_LNX_UserX64, u_debugreg[n]); + int ptrace_result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_POKEUSER, thread->tid, PtrFromInt(offset), (void*)(uintptr_t)dr_s[n])); + if(ptrace_result < 0) { goto exit; } + } + } + } + + is_reg_block_written = 1; + } break; + case Arch_arm64: + case Arch_arm32: + case Arch_x86: { NotImplemented; }break; + default: { InvalidPath; } break; + } + + exit:; + return is_reg_block_written; +} + +internal B32 +dmn_lnx_set_single_step_flag(DMN_LNX_Thread *thread, B32 is_on) +{ + B32 is_flag_set = 0; + switch(thread->process->ctx->arch) + { + case Arch_Null: {} break; + case Arch_x64: + { + X64_RegBlock *reg_block = thread->reg_block; + if(is_on) { reg_block->rflags |= X64_RFlag_Trap; } + else { reg_block->rflags &= ~X64_RFlag_Trap; } + thread->is_reg_block_dirty = 1; + is_flag_set = 1; + } break; + case Arch_x86: + case Arch_arm32: + case Arch_arm64: { NotImplemented; }break; + default: { InvalidPath; } break; + } + Assert(is_flag_set); + return is_flag_set; +} + +internal void +dmn_lnx_thread_ptr_list_push_node(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n) +{ + DLLPushBack(list->first, list->last, n); + list->count += 1; +} + +internal DMN_LNX_ThreadPtrNode * +dmn_lnx_thread_ptr_list_push(Arena *arena, DMN_LNX_ThreadPtrList *list, DMN_LNX_Thread *v) +{ + DMN_LNX_ThreadPtrNode *n = push_array(arena, DMN_LNX_ThreadPtrNode, 1); + n->v = v; + dmn_lnx_thread_ptr_list_push_node(list, n); + return n; +} + +internal void +dmn_lnx_thread_ptr_list_remove(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n) +{ + DLLRemove(list->first, list->last, n); + list->count -= 1; +} + +internal DMN_LNX_ModulePtrNode * +dmn_lnx_module_ptr_list_push(Arena *arena, DMN_LNX_ModulePtrList *list, DMN_LNX_Module *v) +{ + DMN_LNX_ModulePtrNode *n = push_array(arena, DMN_LNX_ModulePtrNode, 1); + n->v = v; + SLLQueuePush(list->first, list->last, n); + list->count += 1; + return n; +} + +internal DMN_LNX_ProcessPtrNode * +dmn_lnx_process_ptr_list_push(Arena *arena, DMN_LNX_ProcessPtrList *list, DMN_LNX_Process *v) +{ + DMN_LNX_ProcessPtrNode *n = push_array(arena, DMN_LNX_ProcessPtrNode, 1); + n->v = v; + SLLQueuePush(list->first, list->last, n); + list->count += 1; + return n; +} + +internal void +dmn_lnx_push_event_create_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process) +{ + // push create process event + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_CreateProcess; + e->process = dmn_lnx_handle_from_process(process); + e->arch = process->ctx->arch; + e->code = process->pid; + e->tls_model = DMN_TlsModel_Gnu; // TODO: use dynamic linker path to figure out correct enum here +} + +internal void +dmn_lnx_push_event_exit_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_ExitProcess; + e->process = dmn_lnx_handle_from_process(process); + e->code = process->main_thread_exit_code; +} + +internal void +dmn_lnx_push_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_CreateThread; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->arch = thread->process->ctx->arch; + e->code = thread->tid; +} + +internal void +dmn_lnx_push_event_exit_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 exit_code) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_ExitThread; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->code = exit_code; +} + +internal void +dmn_lnx_push_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, DMN_LNX_Module *module) +{ + // TODO: reporting this module breaks ctrl thread + String8 module_name = dmn_lnx_read_string(arena, thread->process->fd, module->name_vaddr); + if(!str8_match(module_name, str8_lit("linux-vdso.so.1"), 0)) + { + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_LoadModule; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->module = dmn_lnx_handle_from_module(module); + e->arch = thread->process->ctx->arch; + e->address = module->base_vaddr; + e->size = module->size; + e->string = module_name; + e->elf_phdr_vrange = r1u64(module->phvaddr, module->phvaddr + module->phentsize * module->phcount); + e->elf_phdr_entsize = module->phentsize; + e->tls_index = module->tls_index; + e->tls_offset = module->tls_offset; + } +} + +internal void +dmn_lnx_push_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, DMN_LNX_Module *module) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_UnloadModule; + e->process = dmn_lnx_handle_from_process(process); + e->module = dmn_lnx_handle_from_module(module); +} + +internal void +dmn_lnx_push_event_handshake_complete(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_HandshakeComplete; + e->process = dmn_lnx_handle_from_process(process); + e->thread = dmn_lnx_handle_from_thread(process->first_thread); + e->arch = process->ctx->arch; +} + +internal void +dmn_lnx_push_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 address) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_Breakpoint; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->instruction_pointer = address; +} + +internal void +dmn_lnx_push_event_single_step(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_SingleStep; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->instruction_pointer = dmn_lnx_thread_read_ip(thread); +} + +internal void +dmn_lnx_push_event_exception(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 signo) +{ + local_persist B8 is_repeatable[] = + { + 0, // null + 1, // 1 SIGHUP + 1, // 2 SIGINT + 1, // 3 SIGQUIT + 1, // 4 SIGTRAP + 1, // 5 SIGABRT + 1, // 6 SIGIOT + 1, // 7 SIGBUS + 1, // 8 SIGFPE + 1, // 9 SIGKILL + 1, // 10 SIGUSR1 + 1, // 11 SIGSEGV + 1, // 12 SIGUSR2 + 1, // 13 SIGPIPE + 1, // 14 SIGALRM + 1, // 15 SIGTERM + 1, // 16 SIGTKFLT + 0, // 17 SIGCHLD + 0, // 18 SIGCONT + 1, // 19 SIGSTOP + 1, // 20 SIGTSP + 1, // 21 SIGTTIN + 1, // 22 SIGTTOU + 0, // 23 SIGURG + 1, // 24 SIGXCPU + 1, // 25 SIGXFSZ + 1, // 26 SIGVTALRM + 1, // 27 SIGPROF + 0, // 28 SIGWINCH + 1, // 29 SIGIO + 1, // 30 SIGPWR + 1, // 31 SIGSYS + 1, // 32 SIGUNUSED + }; + + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_Exception; + e->process = dmn_lnx_handle_from_process(thread->process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->instruction_pointer = dmn_lnx_thread_read_ip(thread); + e->signo = signo; + e->exception_repeated = signo < ArrayCount(is_repeatable) ? is_repeatable[signo] : 0; + + if(signo == SIGSEGV) + { + siginfo_t si = {0}; + OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, thread->tid, 0, &si)); + e->address = (U64)si.si_addr; + } +} + +internal void +dmn_lnx_push_event_halt(Arena *arena, DMN_EventList *events) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_Halt; +} + +internal void +dmn_lnx_push_event_not_attached(Arena *arena, DMN_EventList *events) +{ + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_Error; + e->error_kind = DMN_ErrorKind_NotAttached; +} + +internal DMN_LNX_Thread * +dmn_lnx_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, pid_t tid) +{ + DMN_LNX_Thread *thread = dmn_lnx_thread_alloc(process, DMN_LNX_ThreadState_Stopped, tid); + dmn_lnx_push_event_create_thread(arena, events, thread); + return thread; +} + +internal void +dmn_lnx_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code) +{ + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); + DMN_LNX_Process *process = thread->process; + + // store main thread's exit code + if(thread->tid == thread->process->pid) + { + thread->process->main_thread_exit_code = exit_code; + } + + // push exit event + dmn_lnx_push_event_exit_thread(arena, events, thread, exit_code); + + // release entity + dmn_lnx_thread_release(thread); + + // auto exit process on last thread + if(process->thread_count == 0) + { + dmn_lnx_event_exit_process(arena, events, process->pid); + } +} + +internal DMN_LNX_Process * +dmn_lnx_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, DMN_LNX_Process *parent_process, DMN_LNX_CreateProcessFlags flags) +{ + DMN_LNX_Process *process = dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Normal, parent_process, !!(flags & DMN_LNX_CreateProcessFlag_DebugSubprocesses), !!(flags & DMN_LNX_CreateProcessFlag_Cow)); + + if(flags & DMN_LNX_CreateProcessFlag_ClonedMemory) + { + process->ctx = parent_process->ctx; + } + else + { + process->ctx = dmn_lnx_process_ctx_alloc(process, !!(flags & DMN_LNX_CreateProcessFlag_Rebased)); + } + process->ctx->ref_count += 1; + + // install probes in a process that does not have a cloned memory + if(!(flags & DMN_LNX_CreateProcessFlag_ClonedMemory)) + { + dmn_lnx_process_trap_probes(process); + } + + // create main thread + dmn_lnx_thread_alloc(process, DMN_LNX_ThreadState_Stopped, pid); + + // push events + dmn_lnx_push_event_create_process(arena, events, process); + for EachNode(thread, DMN_LNX_Thread, process->first_thread) + { + dmn_lnx_push_event_create_thread(arena, events, thread); + } + for EachNode(module, DMN_LNX_Module, process->ctx->first_module) + { + dmn_lnx_push_event_load_module(arena, events, process->first_thread, module); + } + dmn_lnx_push_event_handshake_complete(arena, events, process); + + return process; +} + +internal void +dmn_lnx_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid) +{ + DMN_LNX_Process *process = dmn_lnx_process_from_pid(pid); + AssertAlways(process->thread_count == 0); + + // push module events + for EachNode(module, DMN_LNX_Module, process->ctx->first_module) + { + dmn_lnx_push_event_unload_module(arena, events, process, module); + } + + // push process exit event + dmn_lnx_push_event_exit_process(arena, events, process); + + // release process + dmn_lnx_process_release(process); +} + +internal void +dmn_lnx_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr) +{ + DMN_LNX_Process *process = thread->process; + + GNU_LinkMap64 map = {0}; + for(U64 map_vaddr = new_link_map_vaddr; map_vaddr != 0; map_vaddr = map.next_vaddr) + { + // read out new link map item + if(gnu_read_link_map(dmn_lnx_machine_op_mem_read, &process->fd, map_vaddr, process->ctx->dl_class, &map) != MachineOpResult_Ok) { break; } + + // was module already loaded? + DMN_LNX_Module *module = hash_table_search_u64_raw(process->ctx->loaded_modules_ht, map.addr_vaddr); + if(module) { continue; } + + // clone process ctx + if(process->is_cow) + { + process->is_cow = 0; + process->ctx = dmn_lnx_process_ctx_clone(process, process->ctx); + } + + // alloc module + module = dmn_lnx_module_alloc(process->ctx, process->fd, map.addr_vaddr, map.name_vaddr, name_space_id, 0); + + // push load module event + dmn_lnx_push_event_load_module(arena, events, thread, module); + } +} + +internal void +dmn_lnx_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, U64 rdebug_vaddr) +{ + Temp scratch = scratch_begin(&arena, 1); + + DMN_LNX_ProcessCtx *ctx = process->ctx; + + // flag every module as inactive + for EachNode(module, DMN_LNX_Module, ctx->first_module) + { + module->is_live = 0; + } + + // mark live modules + B32 is_64bit = ctx->dl_class == ELF_Class_64; + GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd); + for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) + { + GNU_LinkMapList link_map_list = gnu_parse_link_map_list(scratch.arena, is_64bit, rdebug_n->v.r_map, dmn_lnx_machine_op_mem_read, &process->fd); + for EachNode(link_map_n, GNU_LinkMapNode, link_map_list.first) + { + DMN_LNX_Module *module = hash_table_search_u64_raw(ctx->loaded_modules_ht, link_map_n->v.addr_vaddr); + module->is_live = 1; + } + } + + // collect unloaded modules + DMN_LNX_ModulePtrList to_release = {0}; + for EachNode(module, DMN_LNX_Module, ctx->first_module) + { + if(!module->is_live) + { + dmn_lnx_module_ptr_list_push(scratch.arena, &to_release, module); + } + } + + // clone process context + if(to_release.count > 0) + { + if(process->is_cow) + { + process->is_cow = 0; + process->ctx = dmn_lnx_process_ctx_clone(process, process->ctx); + } + } + + // push events and clean up unloaded modules + for EachNode(module_n, DMN_LNX_ModulePtrNode, to_release.first) + { + dmn_lnx_push_event_unload_module(arena, events, process, module_n->v); + dmn_lnx_module_release(process->ctx, module_n->v); + } + + scratch_end(scratch); +} + +internal void +dmn_lnx_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid) +{ + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); + DMN_LNX_Process *process = thread->process; + U64 ip = dmn_lnx_thread_read_ip(thread); + + // is this user trap? + DMN_ActiveTrap *hit_user_trap = 0; + { + DMN_Handle process_handle = dmn_lnx_handle_from_process(process); + for EachNode(active_trap, DMN_ActiveTrap, user_traps) + { + if(MemoryCompare(&active_trap->trap->process, &process_handle, sizeof(DMN_Handle)) == 0) + { + if(active_trap->trap->vaddr == ip-1) + { + hit_user_trap = active_trap; + break; + } + } + } + } + + // is this a probe trap? + DMN_LNX_ProbeType probe_type = DMN_LNX_ProbeType_Null; + if(hit_user_trap == 0) + { + for EachNode(active_trap, DMN_ActiveTrap, process->ctx->first_probe_trap) + { + if(active_trap->trap->vaddr == ip-1) + { + probe_type = active_trap->trap->id; + break; + } + } + } + + if(probe_type == DMN_LNX_ProbeType_InitComplete) + { + B32 is_init_completed = 0; + + DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_InitComplete]; + U64 name_space_id = 0, rdebug_addr = 0; + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto init_complete_exit; } + if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_addr)) { goto init_complete_exit; } + + GNU_RDebugInfo64 rdebug = {0}; + if(gnu_read_r_debug(dmn_lnx_machine_op_mem_read, &process->fd, rdebug_addr, process->ctx->arch, &rdebug) != MachineOpResult_Ok) { goto init_complete_exit; } + if(rdebug.r_version < 1) { goto init_complete_exit; } + + dmn_lnx_event_load_module(arena, events, thread, name_space_id, rdebug.r_map); + + is_init_completed = 1; + init_complete_exit:; + AssertAlways(is_init_completed); + } + else if(probe_type == DMN_LNX_ProbeType_RelocComplete) + { + B32 is_reloc_completed = 0; + + DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_RelocComplete]; + U64 name_space_id = 0, new_link_map_addr = 0; + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto reloc_complete_exit; } + if(!stap_read_arg_u(probe->args.v[2], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &new_link_map_addr)) { goto reloc_complete_exit; } + + dmn_lnx_event_load_module(arena, events, thread, name_space_id, new_link_map_addr); + + is_reloc_completed = 1; + reloc_complete_exit:; + AssertAlways(is_reloc_completed); + } + else if(probe_type == DMN_LNX_ProbeType_UnmapComplete) + { + B32 is_unmap_completed = 0; + + DMN_LNX_Probe *probe = process->ctx->probes[DMN_LNX_ProbeType_UnmapComplete]; + U64 name_space_id = 0, rdebug_vaddr = 0; + if(!stap_read_arg_u(probe->args.v[0], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &name_space_id)) { goto unmap_complete_exit; } + if(!stap_read_arg_u(probe->args.v[1], process->ctx->arch, thread->reg_block, dmn_lnx_stap_memory_read, process, &rdebug_vaddr)) { goto unmap_complete_exit; } + + dmn_lnx_event_unload_module(arena, events, process, rdebug_vaddr); + + is_unmap_completed = 1; + unmap_complete_exit:; + AssertAlways(is_unmap_completed); + } + + if(probe_type == DMN_LNX_ProbeType_Null) + { + // rollback IP on user traps + if(hit_user_trap) + { + U64 ip = dmn_lnx_thread_read_ip(thread); + dmn_lnx_thread_write_ip(thread, ip - 1); + } + + DMN_Event *e = dmn_event_list_push(arena, events); + e->kind = DMN_EventKind_Breakpoint; + e->process = dmn_lnx_handle_from_process(process); + e->thread = dmn_lnx_handle_from_thread(thread); + e->instruction_pointer = dmn_lnx_thread_read_ip(thread); + } +} + +internal void +dmn_lnx_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid) +{ + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); + + B32 is_valid = 1; + U64 address = 0; + switch(thread->process->ctx->arch) + { + default:{}break; + case Arch_x64: + { + X64_RegBlock *regs_x64 = thread->reg_block; + if(regs_x64->dr6 & X64_DebugStatusFlag_B0) + { + address = regs_x64->dr0; + } + else if(regs_x64->dr6 & X64_DebugStatusFlag_B1) + { + address = regs_x64->dr1; + } + else if(regs_x64->dr6 & X64_DebugStatusFlag_B2) + { + address = regs_x64->dr2; + } + else if(regs_x64->dr6 & X64_DebugStatusFlag_B3) + { + address = regs_x64->dr3; + } + else + { + is_valid = 0; + } + }break; + } + + if(is_valid) + { + dmn_lnx_push_event_breakpoint(arena, events, thread, address); + } +} + +internal void +dmn_lnx_event_halt(Arena *arena, DMN_EventList *events) +{ + dmn_lnx_push_event_halt(arena, events); +} + +internal void +dmn_lnx_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid) +{ + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); + + // clear single step flag + dmn_lnx_set_single_step_flag(thread, 0); + + // push event + dmn_lnx_push_event_single_step(arena, events, thread); +} + +internal void +dmn_lnx_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo) +{ + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(tid); + + thread->pass_through_signal = 1; + thread->pass_through_signo = signo; + + dmn_lnx_push_event_exception(arena, events, thread, signo); +} + +internal DMN_LNX_Process * +dmn_lnx_event_attach(Arena *arena, DMN_EventList *events, pid_t pid) +{ + Temp scratch = scratch_begin(&arena, 1); + + // create process + DMN_LNX_Process *process = dmn_lnx_event_create_process(arena, events, pid, 0, DMN_LNX_CreateProcessFlag_DebugSubprocesses|DMN_LNX_CreateProcessFlag_Rebased); + + // extract threads from /proc/pid/task + { + String8 task_path = str8f(scratch.arena, "/proc/%d/task", pid); + DIR *task_dirp = opendir((char *)task_path.str); + if(task_dirp) + { + for(;;) + { + struct dirent *dirent = readdir(task_dirp); + if(dirent == 0) { break; } + + String8 tid_str = str8_cstring_capped(dirent->d_name, dirent->d_name + NAME_MAX); + if(str8_match(tid_str, str8_lit(".."), 0) || str8_match(tid_str, str8_lit("."), 0)) { continue; } + U64 tid_64 = u64_from_str8(tid_str, 10); + pid_t tid = (pid_t)tid_64; + AssertAlways(tid == tid_64); + + if(tid == pid) { continue; } // main thread was created during create process sequence + dmn_lnx_event_create_thread(arena, events, process, tid); + } + + OS_LNX_RETRY_ON_EINTR(closedir(task_dirp)); + } + } + + // extract modules from r_debug + { + B32 is_64bit = process->ctx->dl_class == ELF_Class_64; + GNU_RDebugInfoList rdebug_list = gnu_parse_rdebug(scratch.arena, is_64bit, process->ctx->rdebug_vaddr, dmn_lnx_machine_op_mem_read, &process->fd); + U64 name_space_id = 0; + for EachNode(rdebug_n, GNU_RDebugInfoNode, rdebug_list.first) + { + dmn_lnx_event_load_module(arena, events, process->first_thread, name_space_id, rdebug_n->v.r_map); + name_space_id += 1; + } + } + + // handshake complete + dmn_lnx_push_event_handshake_complete(arena, events, process); + + scratch_end(scratch); + return process; +} + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) + +internal void +dmn_init(void) +{ + if(dmn_lnx_state == 0) + { + local_persist DMN_LNX_State state; + dmn_lnx_state = &state; + + dmn_lnx_state->arena = arena_alloc(); + dmn_lnx_state->access_mutex = mutex_alloc(); + dmn_lnx_state->entities_arena = arena_alloc(.reserve_size = GB(32), .commit_size = KB(64), .flags = ArenaFlag_NoChain); + dmn_lnx_state->entities_base = push_array(dmn_lnx_state->entities_arena, DMN_LNX_Entity, 0); + dmn_lnx_state->tid_ht = hash_table_init(dmn_lnx_state->arena, 0x2000); + dmn_lnx_state->pid_ht = hash_table_init(dmn_lnx_state->arena, 0x400); + dmn_lnx_state->halter_mutex = mutex_alloc(); + dmn_lnx_entity_alloc(DMN_LNX_EntityKind_Null); + + // find offsets of TLS index and TLS offset in the link_map struct + // + // TODO: assuming that target is using same libc version as debugger + { + DMN_LNX_DbDesc *tls_modid_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_modid"); + DMN_LNX_DbDesc *tls_offset_desc = dlsym(RTLD_DEFAULT, "_thread_db_link_map_l_tls_offset"); + if(tls_modid_desc && tls_offset_desc) + { + if(tls_modid_desc->bit_size <= 64 && tls_offset_desc->bit_size <= 64) + { + dmn_lnx_state->tls_modid_desc = *tls_modid_desc; + dmn_lnx_state->tls_offset_desc = *tls_offset_desc; + dmn_lnx_state->is_tls_detected = 1; + } + else { Assert(0 && "invalid TLS desc"); } + } + } + } +} + +//////////////////////////////// +//~ 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_lnx_ctrl_thread = 1; + return ctx; +} + +internal void +dmn_ctrl_exclusive_access_begin(void) +{ + MutexScope(dmn_lnx_state->access_mutex) + { + dmn_lnx_state->access_run_state = 1; + } +} + +internal void +dmn_ctrl_exclusive_access_end(void) +{ + MutexScope(dmn_lnx_state->access_mutex) + { + dmn_lnx_state->access_run_state = 0; + } +} + +internal U32 +dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) +{ + Temp scratch = scratch_begin(0, 0); + + // setup target command line + U64 argc = params->cmd_line.node_count + 1; + char **argv = push_array(scratch.arena, char *, argc); + { + U64 idx = 0; + for EachNode(n, String8Node, params->cmd_line.first) + { + argv[idx] = (char *)str8_copy(scratch.arena, n->string).str; + idx += 1; + } + } + + // setup target environment + U64 envc = os_lnx_state.default_env_count + params->env.node_count + 1; + char **envp = push_array(scratch.arena, char *, envc); + { + // copy default environment + MemoryCopyTyped(envp, os_lnx_state.default_env, os_lnx_state.default_env_count); + + // copy user environment + U64 idx = os_lnx_state.default_env_count; + for EachNode(n, String8Node, params->env.first) + { + envp[idx] = (char *)str8_copy(scratch.arena, n->string).str; + idx += 1; + } + } + + // create zero-terminated work directory path + char *work_dir_path = (char *)str8_copy(scratch.arena, params->path).str; + + // fork process + pid_t pid = fork(); + + // child process + if(pid == 0) + { + // wait for seize + if(OS_LNX_RETRY_ON_EINTR(raise(SIGSTOP)) < 0) { goto child_exit; } + + // change work directory to tracee + if(OS_LNX_RETRY_ON_EINTR(chdir(work_dir_path)) < 0) { goto child_exit; } + + // replace process with target program + if(OS_LNX_RETRY_ON_EINTR(execve(argv[0], argv, envp)) < 0) { goto child_exit; } + + child_exit:; + exit(0); + } + // parent process + else if(pid > 0) + { + // try to seize child process + if(dmn_lnx_ptrace_seize(pid) >= 0) + { + // tracee was successfully seized, create process + dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Launch, 0, params->debug_subprocesses, 0); + } + else + { + Assert(0 && "failed to ptrace child process"); + OS_LNX_RETRY_ON_EINTR(kill(SIGKILL, pid)); + OS_LNX_RETRY_ON_EINTR(waitpid(pid, 0, WNOHANG)); + pid = 0; + } + } + + scratch_end(scratch); + return pid; +} + +internal B32 +dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) +{ + B32 is_attached = 0; + if(dmn_lnx_ptrace_seize(pid) >= 0) + { + if(OS_LNX_RETRY_ON_EINTR(kill(pid, SIGSTOP)) >= 0) + { + dmn_lnx_process_alloc(pid, DMN_LNX_ProcessState_Attach, 0, 1, 0); + is_attached = 1; + } + else + { + OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, pid, 0, 0)); + } + } + return is_attached; +} + +internal B32 +dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process_handle, U32 exit_code) +{ + B32 result = 0; + mutex_take(dmn_lnx_state->halter_mutex); + DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); + if(process) + { + result = OS_LNX_RETRY_ON_EINTR(kill(process->pid, SIGKILL)) >= 0; + } + mutex_drop(dmn_lnx_state->halter_mutex); + return result; +} + +internal B32 +dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process_handle) +{ + B32 result = 0; + mutex_take(dmn_lnx_state->halter_mutex); + DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); + if(process) + { + result = OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, process->pid, 0, 0)) >= 0; + } + mutex_drop(dmn_lnx_state->halter_mutex); + return result; +} + +internal DMN_EventList +dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) +{ + Temp scratch = scratch_begin(&arena, 1); + DMN_EventList events = {0}; + + mutex_take(dmn_lnx_state->halter_mutex); + + // wait for signals from the running threads + if(dmn_lnx_state->process_count > 0) + { + // write traps to memory + DMN_ActiveTrap *active_trap_first = 0, *active_trap_last = 0; + { + HashTable *process_ht = hash_table_init(scratch.arena, dmn_lnx_state->process_count); + for EachNode(n, DMN_TrapChunkNode, ctrls->traps.first) + { + for EachIndex(n_idx, n->count) + { + // skip hardware breakpoints + DMN_Trap *trap = n->v+n_idx; + if(trap->flags) { continue; } + + HashTable *active_trap_ht = hash_table_search_u64_raw(process_ht, trap->process.u64[0]); + if(active_trap_ht == 0) + { + active_trap_ht = hash_table_init(scratch.arena, ctrls->traps.trap_count); + hash_table_push_u64_raw(scratch.arena, process_ht, trap->process.u64[0], active_trap_ht); + } + + // TODO: ctrl sends down duplicate traps + DMN_ActiveTrap *is_set = hash_table_search_u64_raw(active_trap_ht, trap->vaddr); + if(is_set) { continue; } + + // TODO: ctrl sends down traps for exited process + DMN_LNX_Process *process = dmn_lnx_process_from_handle(trap->process); + if(!process) { continue; } + + // trap instruction + DMN_ActiveTrap *active_trap = dmn_set_trap(scratch.arena, trap); + + // add trap to the active list + SLLQueuePush(active_trap_first, active_trap_last, active_trap); + + // add (address -> trap) + hash_table_push_u64_raw(scratch.arena, active_trap_ht, trap->vaddr, active_trap); + } + } + } + + // enable single stepping + if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) + { + DMN_LNX_Thread *single_step_thread = dmn_lnx_thread_from_handle(ctrls->single_step_thread); + if(single_step_thread) + { + dmn_lnx_set_single_step_flag(single_step_thread, 1); + } + else + { + Assert(0 && "invalid single_step_thread handle"); + } + } + + // schedule threads to run + DMN_LNX_ThreadPtrList running_threads = {0}; + { + for EachNode(process, DMN_LNX_Process, dmn_lnx_state->first_process) + { + //- rjf: determine if this process is frozen + B32 process_is_frozen = 0; + if(ctrls->run_entities_are_processes) + { + for EachIndex(idx, ctrls->run_entity_count) + { + if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_process(process))) + { + process_is_frozen = 1; + break; + } + } + } + + for EachNode(thread, DMN_LNX_Thread, process->first_thread) + { + //- rjf: determine if this thread is frozen + B32 is_frozen = 0; + + // rjf: not single-stepping? determine based on run controls freezing info + if(dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread)) + { + if(ctrls->run_entities_are_processes) + { + is_frozen = process_is_frozen; + } + else + { + for EachIndex(idx, ctrls->run_entity_count) + { + if(dmn_handle_match(ctrls->run_entities[idx], dmn_lnx_handle_from_thread(thread))) + { + is_frozen = 1; + break; + } + } + } + if(ctrls->run_entities_are_unfrozen) + { + is_frozen ^= 1; + } + } + // rjf: single-step? freeze if not the single-step thread. + else + { + is_frozen = !dmn_handle_match(dmn_lnx_handle_from_thread(thread), ctrls->single_step_thread); + } + + // resume thread + if(!is_frozen) + { + AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); + + // write registers + if(thread->is_reg_block_dirty) + { + thread->is_reg_block_dirty = !dmn_lnx_thread_write_reg_block(thread); + } + + // pass signal to the child process + void *sig_code = 0; + if(thread->pass_through_signal) + { + thread->pass_through_signal = 0; + if(!ctrls->ignore_previous_exception) + { + sig_code = (void *)(uintptr_t)thread->pass_through_signo; + } + } + + // resume thread + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, thread->tid, 0, sig_code)) >= 0) + { + thread->state = DMN_LNX_ThreadState_Running; + thread->is_reg_block_dirty = 1; + dmn_lnx_thread_ptr_list_push(scratch.arena, &running_threads, thread); + } + else + { + if(errno == ESRCH) + { + // Race note: In multithreaded programs, ptrace(PTRACE_CONT) may fail if a running thread calls + // exit_group while we are resuming threads. The kernel begins tearing down the thread group so + // the target might not be in a ptrace-soppped state anymore. Handle this by ignoring, on waitpid + // we will get chance to report exit event. + // + // TODO: unfortunatley, kernel also sends us ESRCH whenever ptrace(PTRACE_CONT) is sent to a tracee + // that is not in a ptace-stop, so how can we tell these errors apart? + } + else + { + InvalidPath; + } + } + } + } + } + } + + // hash running threads tids + HashTable *running_threads_ht = hash_table_init(scratch.arena, running_threads.count * 2); + for EachNode(n, DMN_LNX_ThreadPtrNode, running_threads.first) + { + hash_table_push_u64_raw(scratch.arena, running_threads_ht, n->v->tid, n); + } + + B32 is_halt_done = 0; + DMN_LNX_ThreadPtrList stopped_threads = {0}; + do + { + // wait for a signal + int status = 0; + pid_t wait_id = 0; + { + mutex_drop(dmn_lnx_state->halter_mutex); + for(;;) + { + wait_id = OS_LNX_RETRY_ON_EINTR(waitpid(-1, &status, __WALL|__WNOTHREAD)); + if(wait_id == -1) { InvalidPath; } // TODO: graceful exit + break; + } + mutex_take(dmn_lnx_state->halter_mutex); + } + + // unpack status + int wifexited = WIFEXITED(status); + int wifsignaled = WIFSIGNALED(status); + int wifstopped = WIFSTOPPED(status); + int wstopsig = WSTOPSIG(status); + int event_code = (status >> 16); + + // intercept initing processes + { + DMN_LNX_Process *process = dmn_lnx_process_from_pid(wait_id); + + if(process && process->state != DMN_LNX_ProcessState_Normal) + { + switch(process->state) + { + case DMN_LNX_ProcessState_Null: + case DMN_LNX_ProcessState_Normal: + { + InvalidPath; + } break; + case DMN_LNX_ProcessState_Launch: + { + if(wstopsig == SIGSTOP) + { + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_CONT, process->pid, 0, 0)) >= 0) + { + process->state = DMN_LNX_ProcessState_WaitForExec; + goto wait_for_signal; + } + else { Assert(0 && "failed to resume tracee"); } + } + // else { Assert(0 && "unexpected signal"); } + } break; + case DMN_LNX_ProcessState_WaitForExec: + { + if(wstopsig == SIGTRAP && event_code == PTRACE_EVENT_EXEC) + { + DMN_LNX_CreateProcessFlags create_flags = process->debug_subprocesses ? DMN_LNX_CreateProcessFlag_DebugSubprocesses : 0; + dmn_lnx_process_release(process); + dmn_lnx_event_create_process(arena, &events, wait_id, 0, create_flags); + goto wait_for_signal; + } + //else { Assert(0 && "unexpected signal"); } + } break; + case DMN_LNX_ProcessState_ExecFailedDoExit: + { + if(wifexited) + { + dmn_lnx_process_release(process); + goto wait_for_signal; + } + else { Assert(0 && "unexpected signal"); } + } break; + case DMN_LNX_ProcessState_Attach: + { + if(wstopsig == SIGSTOP) + { + DMN_LNX_CreateProcessFlags create_flags = process->debug_subprocesses ? DMN_LNX_CreateProcessFlag_DebugSubprocesses : 0; + dmn_lnx_process_release(process); + dmn_lnx_event_create_process(arena, &events, wait_id, 0, create_flags); + goto wait_for_signal; + } + else { Assert(0 && "unexpected signal"); } + } break; + } + + // shutdown process + { + if(OS_LNX_RETRY_ON_EINTR(kill(wait_id, SIGKILL)) >= 0) + { + process->state = DMN_LNX_ProcessState_ExecFailedDoExit; + } + else + { + //Assert(0 && "failed to send kill signal to the tracee"); + dmn_lnx_process_release(process); + } + } + + wait_for_signal:; + continue; + } + } + + if(wifstopped || wifsignaled || wifexited) + { + DMN_LNX_ThreadPtrNode *thread_n = hash_table_search_u64_raw(running_threads_ht, wait_id); + if(thread_n) + { + DMN_LNX_Thread *thread = thread_n->v; + AssertAlways(thread->state == DMN_LNX_ThreadState_Running); + + // remove mapping + hash_table_purge_u64(running_threads_ht, thread->tid); + + // move thread to the stopped list + dmn_lnx_thread_ptr_list_remove(&running_threads, thread_n); + dmn_lnx_thread_ptr_list_push_node(&stopped_threads, thread_n); + + // update thread state + if(wifstopped && !wifsignaled && !wifexited) + { + thread->state = DMN_LNX_ThreadState_Stopped; + } + else if(!wifstopped && (wifsignaled || wifexited)) + { + thread->state = DMN_LNX_ThreadState_Exited; + } + else { InvalidPath; } + + // stop all other threads + if(stopped_threads.count == 1) + { + for EachNode(n, DMN_LNX_ThreadPtrNode, running_threads.first) + { + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_INTERRUPT, n->v->tid, 0, 0)) < 0) { Assert(0 && "failed to interrupt process"); } + } + } + } + } + + // normal child exit via _exit or exit() + if(wifexited) + { + dmn_lnx_event_exit_thread(arena, &events, wait_id, WEXITSTATUS(status)); + } + + // exit because child did not handle a signal + else if(wifsignaled) + { + dmn_lnx_event_exit_thread(arena, &events, wait_id, WTERMSIG(status)); + } + // thread or group stop + else if(wifstopped) + { + // read thread registers + { + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); + if(thread && thread->state != DMN_LNX_ThreadState_PendingCreation) + { + thread->is_reg_block_dirty = !dmn_lnx_thread_read_reg_block(thread); + Assert(!thread->is_reg_block_dirty); + } + } + + if(wstopsig == SIGTRAP) + { + switch(event_code) + { + case 0: + { + // translate signal code to DEMON event + siginfo_t siginfo = {0}; + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0) + { + switch(siginfo.si_code) + { + case SI_KERNEL: { dmn_lnx_event_breakpoint(arena, &events, active_trap_first, wait_id); } break; + case TRAP_BRKPT: { dmn_lnx_event_breakpoint(arena, &events, active_trap_first, wait_id); } break; + case TRAP_TRACE: { dmn_lnx_event_single_step(arena, &events, wait_id); } break; + case TRAP_HWBKPT: { dmn_lnx_event_data_breakpoint(arena, &events, wait_id); } break; + case TRAP_BRANCH: { NotImplemented; }break; + case TRAP_UNK: { NotImplemented; }break; + default: { InvalidPath; } break; + } + } else { Assert(0 && "failed to get signal info"); } + }break; + case PTRACE_EVENT_FORK: + { + NotImplemented; + }break; + case PTRACE_EVENT_VFORK: + { + NotImplemented; + }break; + case PTRACE_EVENT_VFORK_DONE: + { + NotImplemented; + }break; + case PTRACE_EVENT_CLONE: + { + // kernel stopped the parent just before scheduling the child to + // give us a chance to prepare to trace it; next event for the child + // will be a PTRACE_EVENT_STOP + + pid_t new_tid; + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid)) >= 0) + { + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); + + // create a new partially inited thread + dmn_lnx_thread_alloc(thread->process, DMN_LNX_ThreadState_PendingCreation, new_tid); + } + else { Assert(0 && "failed to get new tid"); } + }break; + case PTRACE_EVENT_EXEC: + { + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); + DMN_LNX_Process *process = thread->process; + if(process->debug_subprocesses) + { + dmn_lnx_event_exit_thread(arena, &events, wait_id, 0); + dmn_lnx_event_create_process(arena, &events, wait_id, 0, DMN_LNX_CreateProcessFlag_DebugSubprocesses); + } + else + { + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_DETACH, wait_id, 0, 0)) >= 0) + { + dmn_lnx_event_exit_thread(arena, &events, wait_id, 0); + } + else { Assert(0 && "failed to detach"); } + } + }break; + case PTRACE_EVENT_EXIT: + { + AssertAlways(0 && "exit tracing is disabled, this event should not be delivered"); + }break; + case PTRACE_EVENT_SECCOMP: + { + NotImplemented; + }break; + case PTRACE_EVENT_STOP: + { + DMN_LNX_Thread *thread = dmn_lnx_thread_from_pid(wait_id); + if(thread->state == DMN_LNX_ThreadState_PendingCreation) + { + DMN_LNX_Process *process = thread->process; + dmn_lnx_thread_release(thread); + thread = dmn_lnx_event_create_thread(arena, &events, process, wait_id); + } + else + { + AssertAlways(thread->state == DMN_LNX_ThreadState_Stopped); + } + }break; + default: { Assert(0 && "unexpected ptrace code"); } break; + } + } + else if(wstopsig == SIGSTOP) + { + siginfo_t siginfo = {0}; + if(OS_LNX_RETRY_ON_EINTR(ptrace(PTRACE_GETSIGINFO, wait_id, 0, &siginfo)) >= 0) + { + // finish halting if this is our SIGSTOP + if(siginfo.si_pid == dmn_lnx_state->halter_tid) + { + is_halt_done = 1; + } + else + { + // TODO: handle SIGSTOP + } + } + } + else + { + dmn_lnx_event_exception(arena, &events, wait_id, wstopsig); + } + } + else { Assert(0 && "unexpected stop code"); } + } while(running_threads.count > 0 || dmn_lnx_state->process_pending_creation > 0 || dmn_lnx_state->threads_pending_creation > 0); + + // finalize halter state + if(is_halt_done) + { + // push event + dmn_lnx_event_halt(arena, &events); + + // reset state + dmn_lnx_state->halter_tid = 0; + dmn_lnx_state->halt_code = 0; + dmn_lnx_state->halt_user_data = 0; + dmn_lnx_state->is_halting = 0; + } + + // restore original instruction bytes + for EachNode(active_trap, DMN_ActiveTrap, active_trap_first) + { + // skip process that exited during the wait + DMN_LNX_Process *process = dmn_lnx_process_from_handle(active_trap->trap->process); + if(!process) { continue; } + + if(!dmn_process_write(active_trap->trap->process, r1u64(active_trap->trap->vaddr, active_trap->trap->vaddr + active_trap->swap_bytes.size), active_trap->swap_bytes.str) && + active_trap->good) + { + // TODO(rjf): log an error here? - we wrote the trap successfully, but we did not remove it successfully, + // implying a change in address mapping. this is maybe not even a bug, since the address is simply now + // unmapped. + } + } + } + + if(events.count == 0 && dmn_lnx_state->process_count == 0) + { + dmn_lnx_push_event_not_attached(arena, &events); + } + + mutex_drop(dmn_lnx_state->halter_mutex); + scratch_end(scratch); + return events; +} + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS) + +internal void +dmn_halt(U64 code, U64 user_data) +{ + mutex_take(dmn_lnx_state->halter_mutex); + if(dmn_lnx_state->process_count) + { + dmn_lnx_state->halter_tid = gettid(); + dmn_lnx_state->halt_code = code; + dmn_lnx_state->halt_user_data = user_data; + for EachNode(process, DMN_LNX_Process, dmn_lnx_state->first_process) + { + if(OS_LNX_RETRY_ON_EINTR(kill(process->pid, SIGSTOP)) < 0) { Assert(0 && "failed to send SIGSTOP"); } + } + } + mutex_drop(dmn_lnx_state->halter_mutex); +} + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS) + +//- rjf: non-blocking-control-thread access barriers + +internal B32 +dmn_access_open(void) +{ + B32 result = 0; + if(dmn_lnx_ctrl_thread) + { + result = 1; + } + else + { + mutex_take(dmn_lnx_state->access_mutex); + result = !dmn_lnx_state->access_run_state; + } + return result; +} + +internal void +dmn_access_close(void) +{ + if(!dmn_lnx_ctrl_thread) + { + mutex_drop(dmn_lnx_state->access_mutex); + } +} + +//- rjf: processes + +internal U64 +dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) +{ + return 0; +} + +internal void +dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size) +{ +} + +internal void +dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size) +{ +} + +internal void +dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size) +{ +} + +internal void +dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, AccessFlags flags) +{ +} + +internal U64 +dmn_process_read(DMN_Handle process_handle, Rng1U64 range, void *dst) +{ + DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); + U64 result = 0; + if(process) + { + result = dmn_lnx_read(process->fd, range, dst); + } + return result; +} + +internal B32 +dmn_process_write(DMN_Handle process_handle, Rng1U64 range, void *src) +{ + DMN_LNX_Process *process = dmn_lnx_process_from_handle(process_handle); + B32 result = 0; + if(process) + { + result = dmn_lnx_write(process->fd, range, src); + } + return result; +} + +//- rjf: threads + +internal Arch +dmn_arch_from_thread(DMN_Handle thread_handle) +{ + Arch arch = Arch_Null; + DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); + if(thread) + { + arch = thread->process->ctx->arch; + } + return arch; +} + +internal U64 +dmn_stack_base_vaddr_from_thread(DMN_Handle thread_handle) +{ + return 0; +} + +internal U64 +dmn_tls_root_vaddr_from_thread(DMN_Handle thread_handle) +{ + U64 tls_root_vaddr = max_U64; + DMN_AccessScope + { + DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); + if(thread) + { + switch(thread->process->ctx->arch) + { + case Arch_Null: {} break; + case Arch_x64: + { + X64_RegBlock *reg_block = thread->reg_block; + U64 dtv_pointer = 0; + if(dmn_lnx_read_struct(thread->process->fd, reg_block->fsbase + 8, &dtv_pointer)) + { + tls_root_vaddr = dtv_pointer; + } + } break; + case Arch_x86: + case Arch_arm32: + case Arch_arm64: + { + NotImplemented; + } break; + default: { InvalidPath; } break; + } + } + } + return tls_root_vaddr; +} + +internal B32 +dmn_thread_read_reg_block(DMN_Handle thread_handle, void *reg_block) +{ + B32 result = 0; + DMN_AccessScope + { + DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); + if(thread) + { + ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); + U64 reg_block_size = arch_info->reg_block_size; + MemoryCopy(reg_block, thread->reg_block, reg_block_size); + } + result = 1; + } + return result; +} + +internal B32 +dmn_thread_write_reg_block(DMN_Handle thread_handle, void *reg_block) +{ + B32 result = 0; + DMN_AccessScope + { + DMN_LNX_Thread *thread = dmn_lnx_thread_from_handle(thread_handle); + if(thread) + { + ARCH_Info *arch_info = arch_info_from_arch(thread->process->ctx->arch); + U64 reg_block_size = arch_info->reg_block_size; + MemoryCopy(thread->reg_block, reg_block, reg_block_size); + thread->is_reg_block_dirty = 1; + result = 1; + } + } + return result; +} + +//- rjf: system process listing + +internal void +dmn_process_iter_begin(DMN_ProcessIter *iter) +{ + DIR *dir = opendir("/proc"); + MemoryZeroStruct(iter); + iter->v[0] = IntFromPtr(dir); +} + +internal B32 +dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out) +{ + // rjf: scan for the next process ID in the directory + B32 got_pid = 0; + String8 pid_string = {0}; + { + DIR *dir = (DIR*)PtrFromInt(iter->v[0]); + if(dir != 0 && iter->v[1] == 0) + { + for(;;) + { + // rjf: get next entry + struct dirent *d = readdir(dir); + if(d == 0) + { + break; + } + + // rjf: check file name is integer + String8 file_name = str8_cstring((char*)d->d_name); + B32 is_integer = str8_is_integer(file_name, 10); + + // rjf: break on integers (which represent processes) + if(is_integer) + { + got_pid = 1; + pid_string = file_name; + break; + } + } + } + } + + // rjf: if we found a process id, map id => info + B32 result = 0; + if(got_pid) + { + pid_t pid = u64_from_str8(pid_string, 10); + String8 name = dmn_lnx_exe_path_from_pid(arena, pid); + if(name.size == 0) + { + name = str8_lit("(unknown process)"); + } + info_out->name = name; + info_out->pid = pid; + result = 1; + } + + return result; +} + +internal void +dmn_process_iter_end(DMN_ProcessIter *iter) +{ + DIR *dir = (DIR*)PtrFromInt(iter->v[0]); + if(dir != 0) + { + closedir(dir); + } + MemoryZeroStruct(iter); +} diff --git a/src/demon/linux/demon_core_linux.h b/src/linux/demon/linux_demon.h similarity index 91% rename from src/demon/linux/demon_core_linux.h rename to src/linux/demon/linux_demon.h index 94a667c7..d4308d52 100644 --- a/src/demon/linux/demon_core_linux.h +++ b/src/linux/demon/linux_demon.h @@ -1,442 +1,502 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef DEMON_CORE_LINUX_H -#define DEMON_CORE_LINUX_H - -//////////////////////////////// -//~ TLS - -typedef struct DMN_LNX_DbDesc -{ - U32 bit_size; - U32 count; - U32 offset; -} DMN_LNX_DbDesc; - -//////////////////////////////// -//~ SDT Probes - -typedef struct DMN_LNX_Probe -{ - String8 provider; - String8 name; - String8 args_string; - STAP_ArgArray args; - U64 pc; - U64 semaphore; -} DMN_LNX_Probe; - -typedef struct DMN_LNX_ProbeNode -{ - DMN_LNX_Probe v; - struct DMN_LNX_ProbeNode *next; -} DMN_LNX_ProbeNode; - -typedef struct DMN_LNX_ProbeList -{ - U64 count; - DMN_LNX_ProbeNode *first; - DMN_LNX_ProbeNode *last; -} DMN_LNX_ProbeList; - -#define DMN_LNX_Probe_XList \ - X(InitStart, 2, "init_start") \ - X(InitComplete, 2, "init_complete") \ - X(RelocStart, 2, "reloc_start") \ - X(RelocComplete, 3, "reloc_complete") \ - X(MapStart, 2, "map_start") \ - X(MapComplete, 3, "map_complete") \ - X(UnmapStart, 2, "unmap_start") \ - X(UnmapComplete, 2, "unmap_complete") \ - X(LongJmp, 3, "longjmp") \ - X(LongJmpTarget, 3, "longjmp_target") \ - X(SetJmp, 3, "setjmp") - -typedef enum -{ - DMN_LNX_ProbeType_Null, -#define X(_N,...) DMN_LNX_ProbeType_##_N, - DMN_LNX_Probe_XList -#undef X - DMN_LNX_ProbeType_Count, -} DMN_LNX_ProbeType; - -//////////////////////////////// -//~ Process Info - -typedef struct DMN_LNX_Auxv -{ - U64 base; - U64 phnum; - U64 phent; - U64 phdr; - U64 execfn; - U64 pagesz; -} DMN_LNX_Auxv; - -typedef struct DMN_LNX_DynamicInfo -{ - U64 hash_vaddr; - U64 gnu_hash_vaddr; - U64 strtab_vaddr; - U64 strtab_size; - U64 symtab_vaddr; - U64 symtab_entry_size; -} DMN_LNX_DynamicInfo; - -//////////////////////////////// -//~ Entities - -typedef enum DMN_LNX_EntityKind -{ - DMN_LNX_EntityKind_Null, - DMN_LNX_EntityKind_Process, - DMN_LNX_EntityKind_ProcessCtx, - DMN_LNX_EntityKind_Thread, - DMN_LNX_EntityKind_Module, -} DMN_LNX_EntityKind; - -typedef enum DMN_LNX_ThreadState -{ - DMN_LNX_ThreadState_Null, - DMN_LNX_ThreadState_Running, - DMN_LNX_ThreadState_Stopped, - DMN_LNX_ThreadState_Exited, - DMN_LNX_ThreadState_PendingCreation, -} DMN_LNX_ThreadState; - -typedef struct DMN_LNX_Thread -{ - pid_t tid; - DMN_LNX_ThreadState state; - struct DMN_LNX_Process *process; - void *reg_block; - B32 is_reg_block_dirty; - B32 pass_through_signal; - U64 pass_through_signo; - U64 orig_rax; - - struct DMN_LNX_Thread *next; - struct DMN_LNX_Thread *prev; -} DMN_LNX_Thread; - -typedef struct DMN_LNX_ThreadPtrNode -{ - DMN_LNX_Thread *v; - struct DMN_LNX_ThreadPtrNode *next; - struct DMN_LNX_ThreadPtrNode *prev; -} DMN_LNX_ThreadPtrNode; - -typedef struct DMN_LNX_ThreadPtrList -{ - U64 count; - DMN_LNX_ThreadPtrNode *first; - DMN_LNX_ThreadPtrNode *last; -} DMN_LNX_ThreadPtrList; - -typedef struct DMN_LNX_Module -{ - U64 name_vaddr; - U64 base_vaddr; - U64 name_space_id; - U64 size; - U64 phvaddr; - U64 phentsize; - U64 phcount; - U64 tls_index; - U64 tls_offset; - B8 is_live; - B8 is_main; - - struct DMN_LNX_Module *next; - struct DMN_LNX_Module *prev; -} DMN_LNX_Module; - -typedef struct DMN_LNX_ModulePtrNode -{ - DMN_LNX_Module *v; - struct DMN_LNX_ModulePtrNode *next; -} DMN_LNX_ModulePtrNode; - -typedef struct DMN_LNX_ModulePtrList -{ - U64 count; - DMN_LNX_ModulePtrNode *first; - DMN_LNX_ModulePtrNode *last; -} DMN_LNX_ModulePtrList; - -typedef enum -{ - DMN_LNX_ProcessState_Null, - DMN_LNX_ProcessState_Launch, - DMN_LNX_ProcessState_Attach, - DMN_LNX_ProcessState_WaitForExec, - DMN_LNX_ProcessState_ExecFailedDoExit, - DMN_LNX_ProcessState_Normal, -} DMN_LNX_ProcessState; - -typedef enum -{ - DMN_LNX_CreateProcessFlag_DebugSubprocesses = (1 << 0), - DMN_LNX_CreateProcessFlag_Rebased = (1 << 1), - DMN_LNX_CreateProcessFlag_Cow = (1 << 2), - DMN_LNX_CreateProcessFlag_ClonedMemory = (1 << 3), -} DMN_LNX_CreateProcessFlags; - -typedef struct DMN_LNX_Process -{ - pid_t pid; - int fd; - DMN_LNX_ProcessState state; - B32 debug_subprocesses; - B32 is_cow; - B32 vfork_with_spoof; - U64 thread_count; - DMN_LNX_Thread *first_thread; - DMN_LNX_Thread *last_thread; - U64 main_thread_exit_code; - struct DMN_LNX_Process *parent_process; - struct DMN_LNX_ProcessCtx *ctx; - - - struct DMN_LNX_Process *next; - struct DMN_LNX_Process *prev; -} DMN_LNX_Process; - -typedef struct DMN_LNX_ProcessPtrNode -{ - DMN_LNX_Process *v; - struct DMN_LNX_ProcessPtrNode *next; -} DMN_LNX_ProcessPtrNode; - -typedef struct DMN_LNX_ProcessPtrList -{ - U64 count; - DMN_LNX_ProcessPtrNode *first; - DMN_LNX_ProcessPtrNode *last; -} DMN_LNX_ProcessPtrList; - -typedef struct DMN_LNX_ProcessCtx -{ - Arena *arena; - Arch arch; - U64 rdebug_vaddr; - ELF_Class dl_class; - HashTable *loaded_modules_ht; - DMN_LNX_Probe **probes; - DMN_ActiveTrap *first_probe_trap; - DMN_ActiveTrap *last_probe_trap; - DMN_LNX_Module *first_module; - DMN_LNX_Module *last_module; - U64 module_count; - U64 ref_count; - - String8List free_reg_blocks; - String8List free_reg_block_nodes; - - // x64 - U64 xcr0; - U64 xsave_size; - X64_XSaveLayout xsave_layout; -} DMN_LNX_ProcessCtx; - -typedef struct DMN_LNX_Entity -{ - union - { - DMN_LNX_Process process; - DMN_LNX_ProcessCtx process_ctx; - DMN_LNX_Thread thread; - DMN_LNX_Module module; - struct - { - struct DMN_LNX_Entity *next; - }; - }; - U32 gen; - DMN_LNX_EntityKind kind; -} DMN_LNX_Entity; - -typedef struct DMN_LNX_EntityNode -{ - DMN_LNX_Entity *v; - struct DMN_LNX_EntityNode *next; -} DMN_LNX_EntityNode; - -typedef struct DMN_LNX_EntityList -{ - U64 count; - DMN_LNX_EntityNode *first; - DMN_LNX_EntityNode *last; -} DMN_LNX_EntityList; - -//////////////////////////////// -//~ Global State - -typedef struct DMN_LNX_State -{ - Arena *arena; - - // rjf: access locking mechanism - Mutex access_mutex; - B32 access_run_state; - - // rjf: entity storage - Arena *entities_arena; - DMN_LNX_Entity *entities_base; - DMN_LNX_Entity *free_entity; - U64 entities_count; - - HashTable *tid_ht; // thread id -> thread entity - HashTable *pid_ht; // process id -> process entity - - // process tracking - U64 process_count; - DMN_LNX_Process *first_process; - DMN_LNX_Process *last_process; - - // process/thread creation tracking - U64 process_pending_creation; - U64 threads_pending_creation; - - // halter - Mutex halter_mutex; - pid_t halter_tid; - U64 halt_code; - U64 halt_user_data; - B32 is_halting; - - // TLS - B32 is_tls_detected; - DMN_LNX_DbDesc tls_modid_desc; - DMN_LNX_DbDesc tls_offset_desc; -} DMN_LNX_State; - -//////////////////////////////// -//~ Globals - -global B32 dmn_lnx_ctrl_thread; -global DMN_LNX_State *dmn_lnx_state; - -//////////////////////////////// -//~ Memory R/W - -internal U64 dmn_lnx_read(int memory_fd, Rng1U64 range, void *dst); -internal B32 dmn_lnx_write(int memory_fd, Rng1U64 range, void *src); -internal String8 dmn_lnx_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size); -internal String8 dmn_lnx_read_string(Arena *arena, int memory_fd, U64 base_vaddr); -#define dmn_lnx_read_struct(fd, vaddr, ptr) dmn_lnx_read((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) -#define dmn_lnx_write_struct(fd, vaddr, ptr) dmn_lnx_write((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) - -//////////////////////////////// -//~ ELF/GNU info - -internal Rng1U64 dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); -internal DMN_LNX_DynamicInfo dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_Class, U64 rebase, U64 dynamic_vaddr); -internal U64 dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vaddr, B32 is_rebased); - -//////////////////////////////// -//~ Process Info - -internal String8 dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid); -internal String8 dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base); -internal ELF_Hdr64 dmn_lnx_ehdr_from_pid(pid_t pid); -internal DMN_LNX_Auxv dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class); -internal DMN_LNX_Thread * dmn_lnx_thread_from_pid(pid_t pid); -internal DMN_LNX_Process * dmn_lnx_process_from_pid(pid_t pid); - -//////////////////////////////// -//~ Entity - -// alloc -internal DMN_LNX_Entity * dmn_lnx_entity_alloc(DMN_LNX_EntityKind kind); -internal DMN_LNX_Process * dmn_lnx_process_alloc(pid_t pid, DMN_LNX_ProcessState state, DMN_LNX_Process *parent_process, B32 debug_subprocess, B32 is_cow); -internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_alloc(DMN_LNX_Process *process, B32 is_rebased); -internal DMN_LNX_Thread * dmn_lnx_thread_alloc(DMN_LNX_Process *process, DMN_LNX_ThreadState thread_state, pid_t tid); -internal DMN_LNX_Module * dmn_lnx_module_alloc(DMN_LNX_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main); - -// release -internal void dmn_lnx_entity_release(DMN_LNX_Entity *entity); -internal void dmn_lnx_process_release(DMN_LNX_Process *process); -internal void dmn_lnx_process_ctx_release(DMN_LNX_ProcessCtx *process_ctx); -internal void dmn_lnx_thread_release(DMN_LNX_Thread *thread); -internal void dmn_lnx_module_release(DMN_LNX_ProcessCtx *ctx, DMN_LNX_Module *module); - -// clone -internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_clone(DMN_LNX_Process *process, DMN_LNX_ProcessCtx *ctx); -internal DMN_LNX_Module * dmn_lnx_module_clone(DMN_LNX_ProcessCtx *process_ctx, DMN_LNX_Module *module); - -// entity -> handle -internal DMN_Handle dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity); -internal DMN_Handle dmn_lnx_handle_from_process(DMN_LNX_Process *process); -internal DMN_Handle dmn_lnx_handle_from_process_ctx(DMN_LNX_ProcessCtx *process_ctx); -internal DMN_Handle dmn_lnx_handle_from_thread(DMN_LNX_Thread *thread); -internal DMN_Handle dmn_lnx_handle_from_module(DMN_LNX_Module *module); - -// handle -> entity -internal DMN_LNX_Entity * dmn_lnx_entity_from_handle(DMN_Handle handle, DMN_LNX_EntityKind expected_kind); -internal DMN_LNX_Process * dmn_lnx_process_from_handle(DMN_Handle process_handle); -internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_from_handle(DMN_Handle process_ctx_handle); -internal DMN_LNX_Thread * dmn_lnx_thread_from_handle(DMN_Handle thread_handle); -internal DMN_LNX_Module * dmn_lnx_module_from_handle(DMN_Handle module_handle); - -//////////////////////////////// -//~ Process Helpers - -internal void dmn_lnx_process_trap_probes(DMN_LNX_Process *process); -internal void dmn_lnx_process_untrap_probes(DMN_LNX_Process *process); - -//////////////////////////////// -//~ Thread Helpers - -internal U64 dmn_lnx_thread_read_ip(DMN_LNX_Thread *thread); -internal U64 dmn_lnx_thread_read_sp(DMN_LNX_Thread *thread); -internal void dmn_lnx_thread_write_ip(DMN_LNX_Thread *thread, U64 ip); -internal void dmn_lnx_thread_write_sp(DMN_LNX_Thread *thread, U64 sp); -internal B32 dmn_lnx_thread_read_reg_block(DMN_LNX_Thread *thread); -internal B32 dmn_lnx_thread_write_reg_block(DMN_LNX_Thread *thread); -internal B32 dmn_lnx_set_single_step_flag(DMN_LNX_Thread *thread, B32 is_on); - -//////////////////////////////// -//~ List Helpers - -internal void dmn_lnx_thread_ptr_list_push_node(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n); -internal DMN_LNX_ThreadPtrNode * dmn_lnx_thread_ptr_list_push(Arena *arena, DMN_LNX_ThreadPtrList *list, DMN_LNX_Thread *v); -internal DMN_LNX_ModulePtrNode * dmn_lnx_module_ptr_list_push(Arena *arena, DMN_LNX_ModulePtrList *list, DMN_LNX_Module *v); -internal DMN_LNX_ProcessPtrNode * dmn_lnx_process_ptr_list_push(Arena *arena, DMN_LNX_ProcessPtrList *list, DMN_LNX_Process *v); - -//////////////////////////////// -//~ Debug Event Pushers - -internal void dmn_lnx_push_event_create_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); -internal void dmn_lnx_push_event_exit_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); -internal void dmn_lnx_push_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread); -internal void dmn_lnx_push_event_exit_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 exit_code); -internal void dmn_lnx_push_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, DMN_LNX_Module *module); -internal void dmn_lnx_push_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, DMN_LNX_Module *module); -internal void dmn_lnx_push_event_handshake_complete(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); -internal void dmn_lnx_push_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 address); -internal void dmn_lnx_push_event_single_step(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread); -internal void dmn_lnx_push_event_exception(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 signo); -internal void dmn_lnx_push_event_halt(Arena *arena, DMN_EventList *events); -internal void dmn_lnx_push_event_not_attached(Arena *arena, DMN_EventList *events); - -//////////////////////////////// -//~ Debug Event - -internal DMN_LNX_Thread * dmn_lnx_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, pid_t tid); -internal void dmn_lnx_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code); -internal DMN_LNX_Process * dmn_lnx_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, DMN_LNX_Process *parent_process, DMN_LNX_CreateProcessFlags flags); -internal void dmn_lnx_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid); -internal void dmn_lnx_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr); -internal void dmn_lnx_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, U64 rdebug_vaddr); -internal void dmn_lnx_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid); -internal void dmn_lnx_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid); -internal void dmn_lnx_event_halt(Arena *arena, DMN_EventList *events); -internal void dmn_lnx_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid); -internal void dmn_lnx_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo); -internal DMN_LNX_Process * dmn_lnx_event_attach(Arena *arena, DMN_EventList *events, pid_t pid); - -#endif // DEMON_CORE_LINUX_H - +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DEMON_CORE_LINUX_H +#define DEMON_CORE_LINUX_H + +//////////////////////////////// +//~ rjf: Register Layouts +// +// These are defined in , but only for one architecture at a time + +typedef struct OS_LNX_GprsX64 OS_LNX_GprsX64; +struct OS_LNX_GprsX64 +{ + U64 r15; + U64 r14; + U64 r13; + U64 r12; + U64 rbp; + U64 rbx; + U64 r11; + U64 r10; + U64 r9; + U64 r8; + U64 rax; + U64 rcx; + U64 rdx; + U64 rsi; + U64 rdi; + U64 orig_rax; + U64 rip; + U64 cs; + U64 rflags; + U64 rsp; + U64 ss; + U64 fsbase; + U64 gsbase; + U64 ds; + U64 es; + U64 fs; + U64 gs; +}; + +typedef struct OS_LNX_UserX64 OS_LNX_UserX64; +struct OS_LNX_UserX64 +{ + OS_LNX_GprsX64 regs; + S32 u_fpvalid; + U32 _pad0; + X64_FXSave i387; + U64 u_tsize; + U64 u_dsize; + U64 u_ssize; + U64 start_code; + U64 start_stack; + U64 signal; + U32 reserved; + U32 _pad1; + U64 u_ar0; + U64 u_fpstate; + U64 magic; + U8 u_comm[32]; + U64 u_debugreg[8]; +}; +StaticAssert(sizeof(OS_LNX_UserX64) == 912, g_os_lnx_user_x64_size_check); + +//////////////////////////////// +//~ TLS + +typedef struct DMN_LNX_DbDesc +{ + U32 bit_size; + U32 count; + U32 offset; +} DMN_LNX_DbDesc; + +//////////////////////////////// +//~ SDT Probes + +typedef struct DMN_LNX_Probe +{ + String8 provider; + String8 name; + String8 args_string; + STAP_ArgArray args; + U64 pc; + U64 semaphore; +} DMN_LNX_Probe; + +typedef struct DMN_LNX_ProbeNode +{ + DMN_LNX_Probe v; + struct DMN_LNX_ProbeNode *next; +} DMN_LNX_ProbeNode; + +typedef struct DMN_LNX_ProbeList +{ + U64 count; + DMN_LNX_ProbeNode *first; + DMN_LNX_ProbeNode *last; +} DMN_LNX_ProbeList; + +#define DMN_LNX_Probe_XList \ +X(InitStart, 2, "init_start") \ +X(InitComplete, 2, "init_complete") \ +X(RelocStart, 2, "reloc_start") \ +X(RelocComplete, 3, "reloc_complete") \ +X(MapStart, 2, "map_start") \ +X(MapComplete, 3, "map_complete") \ +X(UnmapStart, 2, "unmap_start") \ +X(UnmapComplete, 2, "unmap_complete") \ +X(LongJmp, 3, "longjmp") \ +X(LongJmpTarget, 3, "longjmp_target") \ +X(SetJmp, 3, "setjmp") + +typedef enum +{ + DMN_LNX_ProbeType_Null, +#define X(_N,...) DMN_LNX_ProbeType_##_N, + DMN_LNX_Probe_XList +#undef X + DMN_LNX_ProbeType_Count, +} DMN_LNX_ProbeType; + +//////////////////////////////// +//~ Process Info + +typedef struct DMN_LNX_Auxv +{ + U64 base; + U64 phnum; + U64 phent; + U64 phdr; + U64 execfn; + U64 pagesz; +} DMN_LNX_Auxv; + +typedef struct DMN_LNX_DynamicInfo +{ + U64 hash_vaddr; + U64 gnu_hash_vaddr; + U64 strtab_vaddr; + U64 strtab_size; + U64 symtab_vaddr; + U64 symtab_entry_size; +} DMN_LNX_DynamicInfo; + +//////////////////////////////// +//~ Entities + +typedef enum DMN_LNX_EntityKind +{ + DMN_LNX_EntityKind_Null, + DMN_LNX_EntityKind_Process, + DMN_LNX_EntityKind_ProcessCtx, + DMN_LNX_EntityKind_Thread, + DMN_LNX_EntityKind_Module, +} DMN_LNX_EntityKind; + +typedef enum DMN_LNX_ThreadState +{ + DMN_LNX_ThreadState_Null, + DMN_LNX_ThreadState_Running, + DMN_LNX_ThreadState_Stopped, + DMN_LNX_ThreadState_Exited, + DMN_LNX_ThreadState_PendingCreation, +} DMN_LNX_ThreadState; + +typedef struct DMN_LNX_Thread +{ + pid_t tid; + DMN_LNX_ThreadState state; + struct DMN_LNX_Process *process; + void *reg_block; + B32 is_reg_block_dirty; + B32 pass_through_signal; + U64 pass_through_signo; + U64 orig_rax; + + struct DMN_LNX_Thread *next; + struct DMN_LNX_Thread *prev; +} DMN_LNX_Thread; + +typedef struct DMN_LNX_ThreadPtrNode +{ + DMN_LNX_Thread *v; + struct DMN_LNX_ThreadPtrNode *next; + struct DMN_LNX_ThreadPtrNode *prev; +} DMN_LNX_ThreadPtrNode; + +typedef struct DMN_LNX_ThreadPtrList +{ + U64 count; + DMN_LNX_ThreadPtrNode *first; + DMN_LNX_ThreadPtrNode *last; +} DMN_LNX_ThreadPtrList; + +typedef struct DMN_LNX_Module +{ + U64 name_vaddr; + U64 base_vaddr; + U64 name_space_id; + U64 size; + U64 phvaddr; + U64 phentsize; + U64 phcount; + U64 tls_index; + U64 tls_offset; + B8 is_live; + B8 is_main; + + struct DMN_LNX_Module *next; + struct DMN_LNX_Module *prev; +} DMN_LNX_Module; + +typedef struct DMN_LNX_ModulePtrNode +{ + DMN_LNX_Module *v; + struct DMN_LNX_ModulePtrNode *next; +} DMN_LNX_ModulePtrNode; + +typedef struct DMN_LNX_ModulePtrList +{ + U64 count; + DMN_LNX_ModulePtrNode *first; + DMN_LNX_ModulePtrNode *last; +} DMN_LNX_ModulePtrList; + +typedef enum +{ + DMN_LNX_ProcessState_Null, + DMN_LNX_ProcessState_Launch, + DMN_LNX_ProcessState_Attach, + DMN_LNX_ProcessState_WaitForExec, + DMN_LNX_ProcessState_ExecFailedDoExit, + DMN_LNX_ProcessState_Normal, +} DMN_LNX_ProcessState; + +typedef enum +{ + DMN_LNX_CreateProcessFlag_DebugSubprocesses = (1 << 0), + DMN_LNX_CreateProcessFlag_Rebased = (1 << 1), + DMN_LNX_CreateProcessFlag_Cow = (1 << 2), + DMN_LNX_CreateProcessFlag_ClonedMemory = (1 << 3), +} DMN_LNX_CreateProcessFlags; + +typedef struct DMN_LNX_Process +{ + pid_t pid; + int fd; + DMN_LNX_ProcessState state; + B32 debug_subprocesses; + B32 is_cow; + B32 vfork_with_spoof; + U64 thread_count; + DMN_LNX_Thread *first_thread; + DMN_LNX_Thread *last_thread; + U64 main_thread_exit_code; + struct DMN_LNX_Process *parent_process; + struct DMN_LNX_ProcessCtx *ctx; + + + struct DMN_LNX_Process *next; + struct DMN_LNX_Process *prev; +} DMN_LNX_Process; + +typedef struct DMN_LNX_ProcessPtrNode +{ + DMN_LNX_Process *v; + struct DMN_LNX_ProcessPtrNode *next; +} DMN_LNX_ProcessPtrNode; + +typedef struct DMN_LNX_ProcessPtrList +{ + U64 count; + DMN_LNX_ProcessPtrNode *first; + DMN_LNX_ProcessPtrNode *last; +} DMN_LNX_ProcessPtrList; + +typedef struct DMN_LNX_ProcessCtx +{ + Arena *arena; + Arch arch; + U64 rdebug_vaddr; + ELF_Class dl_class; + HashTable *loaded_modules_ht; + DMN_LNX_Probe **probes; + DMN_ActiveTrap *first_probe_trap; + DMN_ActiveTrap *last_probe_trap; + DMN_LNX_Module *first_module; + DMN_LNX_Module *last_module; + U64 module_count; + U64 ref_count; + + String8List free_reg_blocks; + String8List free_reg_block_nodes; + + // x64 + U64 xcr0; + U64 xsave_size; + X64_XSaveLayout xsave_layout; +} DMN_LNX_ProcessCtx; + +typedef struct DMN_LNX_Entity +{ + union + { + DMN_LNX_Process process; + DMN_LNX_ProcessCtx process_ctx; + DMN_LNX_Thread thread; + DMN_LNX_Module module; + struct + { + struct DMN_LNX_Entity *next; + }; + }; + U32 gen; + DMN_LNX_EntityKind kind; +} DMN_LNX_Entity; + +typedef struct DMN_LNX_EntityNode +{ + DMN_LNX_Entity *v; + struct DMN_LNX_EntityNode *next; +} DMN_LNX_EntityNode; + +typedef struct DMN_LNX_EntityList +{ + U64 count; + DMN_LNX_EntityNode *first; + DMN_LNX_EntityNode *last; +} DMN_LNX_EntityList; + +//////////////////////////////// +//~ Global State + +typedef struct DMN_LNX_State +{ + Arena *arena; + + // rjf: access locking mechanism + Mutex access_mutex; + B32 access_run_state; + + // rjf: entity storage + Arena *entities_arena; + DMN_LNX_Entity *entities_base; + DMN_LNX_Entity *free_entity; + U64 entities_count; + + HashTable *tid_ht; // thread id -> thread entity + HashTable *pid_ht; // process id -> process entity + + // process tracking + U64 process_count; + DMN_LNX_Process *first_process; + DMN_LNX_Process *last_process; + + // process/thread creation tracking + U64 process_pending_creation; + U64 threads_pending_creation; + + // halter + Mutex halter_mutex; + pid_t halter_tid; + U64 halt_code; + U64 halt_user_data; + B32 is_halting; + + // TLS + B32 is_tls_detected; + DMN_LNX_DbDesc tls_modid_desc; + DMN_LNX_DbDesc tls_offset_desc; +} DMN_LNX_State; + +//////////////////////////////// +//~ Globals + +global B32 dmn_lnx_ctrl_thread; +global DMN_LNX_State *dmn_lnx_state; + +//////////////////////////////// +//~ Memory R/W + +internal U64 dmn_lnx_read(int memory_fd, Rng1U64 range, void *dst); +internal B32 dmn_lnx_write(int memory_fd, Rng1U64 range, void *src); +internal String8 dmn_lnx_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size); +internal String8 dmn_lnx_read_string(Arena *arena, int memory_fd, U64 base_vaddr); +#define dmn_lnx_read_struct(fd, vaddr, ptr) dmn_lnx_read((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) +#define dmn_lnx_write_struct(fd, vaddr, ptr) dmn_lnx_write((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr)) + +//////////////////////////////// +//~ ELF/GNU info + +internal Rng1U64 dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum); +internal DMN_LNX_DynamicInfo dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_Class, U64 rebase, U64 dynamic_vaddr); +internal U64 dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vaddr, B32 is_rebased); + +//////////////////////////////// +//~ Process Info + +internal String8 dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid); +internal String8 dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base); +internal ELF_Hdr64 dmn_lnx_ehdr_from_pid(pid_t pid); +internal DMN_LNX_Auxv dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class); +internal DMN_LNX_Thread * dmn_lnx_thread_from_pid(pid_t pid); +internal DMN_LNX_Process * dmn_lnx_process_from_pid(pid_t pid); + +//////////////////////////////// +//~ Entity + +// alloc +internal DMN_LNX_Entity * dmn_lnx_entity_alloc(DMN_LNX_EntityKind kind); +internal DMN_LNX_Process * dmn_lnx_process_alloc(pid_t pid, DMN_LNX_ProcessState state, DMN_LNX_Process *parent_process, B32 debug_subprocess, B32 is_cow); +internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_alloc(DMN_LNX_Process *process, B32 is_rebased); +internal DMN_LNX_Thread * dmn_lnx_thread_alloc(DMN_LNX_Process *process, DMN_LNX_ThreadState thread_state, pid_t tid); +internal DMN_LNX_Module * dmn_lnx_module_alloc(DMN_LNX_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main); + +// release +internal void dmn_lnx_entity_release(DMN_LNX_Entity *entity); +internal void dmn_lnx_process_release(DMN_LNX_Process *process); +internal void dmn_lnx_process_ctx_release(DMN_LNX_ProcessCtx *process_ctx); +internal void dmn_lnx_thread_release(DMN_LNX_Thread *thread); +internal void dmn_lnx_module_release(DMN_LNX_ProcessCtx *ctx, DMN_LNX_Module *module); + +// clone +internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_clone(DMN_LNX_Process *process, DMN_LNX_ProcessCtx *ctx); +internal DMN_LNX_Module * dmn_lnx_module_clone(DMN_LNX_ProcessCtx *process_ctx, DMN_LNX_Module *module); + +// entity -> handle +internal DMN_Handle dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity); +internal DMN_Handle dmn_lnx_handle_from_process(DMN_LNX_Process *process); +internal DMN_Handle dmn_lnx_handle_from_process_ctx(DMN_LNX_ProcessCtx *process_ctx); +internal DMN_Handle dmn_lnx_handle_from_thread(DMN_LNX_Thread *thread); +internal DMN_Handle dmn_lnx_handle_from_module(DMN_LNX_Module *module); + +// handle -> entity +internal DMN_LNX_Entity * dmn_lnx_entity_from_handle(DMN_Handle handle, DMN_LNX_EntityKind expected_kind); +internal DMN_LNX_Process * dmn_lnx_process_from_handle(DMN_Handle process_handle); +internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_from_handle(DMN_Handle process_ctx_handle); +internal DMN_LNX_Thread * dmn_lnx_thread_from_handle(DMN_Handle thread_handle); +internal DMN_LNX_Module * dmn_lnx_module_from_handle(DMN_Handle module_handle); + +//////////////////////////////// +//~ Process Helpers + +internal void dmn_lnx_process_trap_probes(DMN_LNX_Process *process); +internal void dmn_lnx_process_untrap_probes(DMN_LNX_Process *process); + +//////////////////////////////// +//~ Thread Helpers + +internal U64 dmn_lnx_thread_read_ip(DMN_LNX_Thread *thread); +internal U64 dmn_lnx_thread_read_sp(DMN_LNX_Thread *thread); +internal void dmn_lnx_thread_write_ip(DMN_LNX_Thread *thread, U64 ip); +internal void dmn_lnx_thread_write_sp(DMN_LNX_Thread *thread, U64 sp); +internal B32 dmn_lnx_thread_read_reg_block(DMN_LNX_Thread *thread); +internal B32 dmn_lnx_thread_write_reg_block(DMN_LNX_Thread *thread); +internal B32 dmn_lnx_set_single_step_flag(DMN_LNX_Thread *thread, B32 is_on); + +//////////////////////////////// +//~ List Helpers + +internal void dmn_lnx_thread_ptr_list_push_node(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n); +internal DMN_LNX_ThreadPtrNode * dmn_lnx_thread_ptr_list_push(Arena *arena, DMN_LNX_ThreadPtrList *list, DMN_LNX_Thread *v); +internal DMN_LNX_ModulePtrNode * dmn_lnx_module_ptr_list_push(Arena *arena, DMN_LNX_ModulePtrList *list, DMN_LNX_Module *v); +internal DMN_LNX_ProcessPtrNode * dmn_lnx_process_ptr_list_push(Arena *arena, DMN_LNX_ProcessPtrList *list, DMN_LNX_Process *v); + +//////////////////////////////// +//~ Debug Event Pushers + +internal void dmn_lnx_push_event_create_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); +internal void dmn_lnx_push_event_exit_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); +internal void dmn_lnx_push_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread); +internal void dmn_lnx_push_event_exit_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 exit_code); +internal void dmn_lnx_push_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, DMN_LNX_Module *module); +internal void dmn_lnx_push_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, DMN_LNX_Module *module); +internal void dmn_lnx_push_event_handshake_complete(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process); +internal void dmn_lnx_push_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 address); +internal void dmn_lnx_push_event_single_step(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread); +internal void dmn_lnx_push_event_exception(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 signo); +internal void dmn_lnx_push_event_halt(Arena *arena, DMN_EventList *events); +internal void dmn_lnx_push_event_not_attached(Arena *arena, DMN_EventList *events); + +//////////////////////////////// +//~ Debug Event + +internal DMN_LNX_Thread * dmn_lnx_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, pid_t tid); +internal void dmn_lnx_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code); +internal DMN_LNX_Process * dmn_lnx_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, DMN_LNX_Process *parent_process, DMN_LNX_CreateProcessFlags flags); +internal void dmn_lnx_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid); +internal void dmn_lnx_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr); +internal void dmn_lnx_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, U64 rdebug_vaddr); +internal void dmn_lnx_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid); +internal void dmn_lnx_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid); +internal void dmn_lnx_event_halt(Arena *arena, DMN_EventList *events); +internal void dmn_lnx_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid); +internal void dmn_lnx_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo); +internal DMN_LNX_Process * dmn_lnx_event_attach(Arena *arena, DMN_EventList *events, pid_t pid); + +#endif // DEMON_CORE_LINUX_H + diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/linux/window_manager/linux_window_manager.c similarity index 85% rename from src/os/gfx/linux/os_gfx_linux.c rename to src/linux/window_manager/linux_window_manager.c index 9485225b..905ddef6 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/linux/window_manager/linux_window_manager.c @@ -1,719 +1,719 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Helpers - -internal OS_LNX_Window * -os_lnx_window_from_x11window(Window window) -{ - OS_LNX_Window *result = 0; - for(OS_LNX_Window *w = os_lnx_gfx_state->first_window; w != 0; w = w->next) - { - if(w->window == window) - { - result = w; - break; - } - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_gfx_init(void) -{ - //- rjf: initialize basics - Arena *arena = arena_alloc(); - os_lnx_gfx_state = push_array(arena, OS_LNX_GfxState, 1); - os_lnx_gfx_state->arena = arena; - os_lnx_gfx_state->display = XOpenDisplay(0); - - //- rjf: calculate atoms - os_lnx_gfx_state->wm_delete_window_atom = XInternAtom(os_lnx_gfx_state->display, "WM_DELETE_WINDOW", 0); - os_lnx_gfx_state->wm_sync_request_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST", 0); - os_lnx_gfx_state->wm_sync_request_counter_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST_COUNTER", 0); - - //- rjf: open im - os_lnx_gfx_state->xim = XOpenIM(os_lnx_gfx_state->display, 0, 0, 0); - - //- rjf: fill out gfx info - os_lnx_gfx_state->gfx_info.double_click_time = 0.5f; - os_lnx_gfx_state->gfx_info.caret_blink_time = 0.5f; - os_lnx_gfx_state->gfx_info.default_refresh_rate = 60.f; - - //- rjf: fill out cursors - { - struct - { - OS_Cursor cursor; - unsigned int id; - } - map[] = - { - {OS_Cursor_Pointer, XC_left_ptr}, - {OS_Cursor_IBar, XC_xterm}, - {OS_Cursor_LeftRight, XC_sb_h_double_arrow}, - {OS_Cursor_UpDown, XC_sb_v_double_arrow}, - {OS_Cursor_DownRight, XC_bottom_right_corner}, - {OS_Cursor_UpRight, XC_top_right_corner}, - {OS_Cursor_UpDownLeftRight, XC_fleur}, - {OS_Cursor_HandPoint, XC_hand1}, - {OS_Cursor_Disabled, XC_X_cursor}, - }; - for EachElement(idx, map) - { - os_lnx_gfx_state->cursors[map[idx].cursor] = XCreateFontCursor(os_lnx_gfx_state->display, map[idx].id); - } - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) - -internal OS_GfxInfo * -os_get_gfx_info(void) -{ - return &os_lnx_gfx_state->gfx_info; -} - -//////////////////////////////// -//~ rjf: @os_hooks Clipboards (Implemented Per-OS) - -internal void -os_set_clipboard_text(String8 string) -{ - -} - -internal String8 -os_get_clipboard_text(Arena *arena) -{ - String8 result = {0}; - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Windows (Implemented Per-OS) - -internal OS_Handle -os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) -{ - Vec2F32 resolution = dim_2f32(rect); - - //- rjf: allocate window - OS_LNX_Window *w = os_lnx_gfx_state->free_window; - if(w) - { - SLLStackPop(os_lnx_gfx_state->free_window); - } - else - { - w = push_array_no_zero(os_lnx_gfx_state->arena, OS_LNX_Window, 1); - } - MemoryZeroStruct(w); - DLLPushBack(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); - - //- rjf: create window & equip with x11 info - w->window = XCreateWindow(os_lnx_gfx_state->display, - XDefaultRootWindow(os_lnx_gfx_state->display), - 0, 0, resolution.x, resolution.y, - 0, - CopyFromParent, - InputOutput, - CopyFromParent, - 0, - 0); - XSelectInput(os_lnx_gfx_state->display, w->window, - ExposureMask| - PointerMotionMask| - ButtonPressMask| - ButtonReleaseMask| - KeyPressMask| - KeyReleaseMask| - FocusChangeMask); - Atom protocols[] = - { - os_lnx_gfx_state->wm_delete_window_atom, - os_lnx_gfx_state->wm_sync_request_atom, - }; - XSetWMProtocols(os_lnx_gfx_state->display, w->window, protocols, ArrayCount(protocols)); - { - XSyncValue initial_value; - XSyncIntToValue(&initial_value, 0); - w->counter_xid = XSyncCreateCounter(os_lnx_gfx_state->display, initial_value); - } - XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1); - - //- rjf: create xic - w->xic = XCreateIC(os_lnx_gfx_state->xim, - XNInputStyle, XIMPreeditNothing|XIMStatusNothing, - XNClientWindow, w->window, - XNFocusWindow, w->window, - NULL); - - //- rjf: attach name - Temp scratch = scratch_begin(0, 0); - String8 title_copy = push_str8_copy(scratch.arena, title); - XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str); - scratch_end(scratch); - - //- rjf: convert to handle & return - OS_Handle handle = {(U64)w}; - return handle; -} - -internal void -os_window_close(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - XDestroyWindow(os_lnx_gfx_state->display, w->window); -} - -internal void -os_window_set_title(OS_Handle handle, String8 title) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - Temp scratch = scratch_begin(0, 0); - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - String8 title_copy = push_str8_copy(scratch.arena, title); - XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str); - scratch_end(scratch); -} - -internal void -os_window_first_paint(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - XMapWindow(os_lnx_gfx_state->display, w->window); -} - -internal void -os_window_focus(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - XSetInputFocus(os_lnx_gfx_state->display, w->window, RevertToNone, CurrentTime); -} - -internal B32 -os_window_is_focused(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return 0;} - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - Window focused_window = 0; - int revert_to = 0; - XGetInputFocus(os_lnx_gfx_state->display, &focused_window, &revert_to); - B32 result = (w->window == focused_window); - return result; -} - -internal B32 -os_window_is_fullscreen(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return 0;} - // TODO(rjf) - return 0; -} - -internal void -os_window_set_fullscreen(OS_Handle handle, B32 fullscreen) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal B32 -os_window_is_maximized(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return 0;} - // TODO(rjf) - return 0; -} - -internal void -os_window_set_maximized(OS_Handle handle, B32 maximized) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal B32 -os_window_is_minimized(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return 0;} - // TODO(rjf) - return 0; -} - -internal void -os_window_set_minimized(OS_Handle handle, B32 minimized) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal void -os_window_bring_to_front(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal void -os_window_set_monitor(OS_Handle handle, OS_Handle monitor) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal void -os_window_clear_custom_border_data(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal void -os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal void -os_window_push_custom_edges(OS_Handle handle, F32 thickness) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal void -os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - // TODO(rjf) -} - -internal Rng2F32 -os_rect_from_window(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return r2f32p(0, 0, 0, 0);} - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - XWindowAttributes atts = {0}; - Status s = XGetWindowAttributes(os_lnx_gfx_state->display, w->window, &atts); - Rng2F32 result = r2f32p((F32)atts.x, (F32)atts.y, (F32)atts.x + (F32)atts.width, (F32)atts.y + (F32)atts.height); - return result; -} - -internal Rng2F32 -os_client_rect_from_window(OS_Handle handle) -{ - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - XWindowAttributes atts = {0}; - Status s = XGetWindowAttributes(os_lnx_gfx_state->display, w->window, &atts); - Rng2F32 result = r2f32p(0, 0, (F32)atts.width, (F32)atts.height); - return result; -} - -internal F32 -os_dpi_from_window(OS_Handle handle) -{ - // TODO(rjf) - return 96.f; -} - -//////////////////////////////// -//~ rjf: @os_hooks External Windows (Implemented Per-OS) - -internal OS_Handle -os_focused_external_window(void) -{ - OS_Handle result = {0}; - // TODO(rjf) - return result; -} - -internal void -os_focus_external_window(OS_Handle handle) -{ - // TODO(rjf) -} - -//////////////////////////////// -//~ rjf: @os_hooks Monitors (Implemented Per-OS) - -internal OS_HandleArray -os_push_monitors_array(Arena *arena) -{ - OS_HandleArray result = {0}; - // TODO(rjf) - return result; -} - -internal OS_Handle -os_primary_monitor(void) -{ - OS_Handle result = {0}; - // TODO(rjf) - return result; -} - -internal OS_Handle -os_monitor_from_window(OS_Handle window) -{ - OS_Handle result = {0}; - // TODO(rjf) - return result; -} - -internal String8 -os_name_from_monitor(Arena *arena, OS_Handle monitor) -{ - // TODO(rjf) - return str8_zero(); -} - -internal Vec2F32 -os_dim_from_monitor(OS_Handle monitor) -{ - // TODO(rjf) - return v2f32(0, 0); -} - -internal F32 -os_dpi_from_monitor(OS_Handle monitor) -{ - // TODO(rjf) - return 96.f; -} - -//////////////////////////////// -//~ rjf: @os_hooks Events (Implemented Per-OS) - -internal void -os_send_wakeup_event(void) -{ - // TODO(rjf) -} - -internal OS_EventList -os_get_events(Arena *arena, B32 wait) -{ - OS_EventList evts = {0}; - for(;XPending(os_lnx_gfx_state->display) > 0 || (wait && evts.count == 0);) - { - XEvent evt = {0}; - XNextEvent(os_lnx_gfx_state->display, &evt); - B32 set_mouse_cursor = 0; - switch(evt.type) - { - default:{}break; - - //- rjf: key presses/releases - case KeyPress: - case KeyRelease: - { - // rjf: determine flags - OS_Modifiers modifiers = 0; - if(evt.xkey.state & ShiftMask) { modifiers |= OS_Modifier_Shift; } - if(evt.xkey.state & ControlMask) { modifiers |= OS_Modifier_Ctrl; } - if(evt.xkey.state & Mod1Mask) { modifiers |= OS_Modifier_Alt; } - - // rjf: map keycode -> keysym & codepoint - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xkey.window); - KeySym keysym = 0; - U8 text[256] = {0}; - U64 text_size = Xutf8LookupString(window->xic, &evt.xkey, (char *)text, sizeof(text), &keysym, 0); - - // rjf: map keysym -> OS_Key - B32 is_right_sided = 0; - OS_Key key = OS_Key_Null; - switch(keysym) - { - default: - { - if(0){} - else if(XK_F1 <= keysym && keysym <= XK_F24) { key = (OS_Key)(OS_Key_F1 + (keysym - XK_F1)); } - else if('0' <= keysym && keysym <= '9') { key = OS_Key_0 + (keysym-'0'); } - }break; - case XK_Escape:{key = OS_Key_Esc;};break; - case XK_BackSpace:{key = OS_Key_Backspace;}break; - case XK_Delete:{key = OS_Key_Delete;}break; - case XK_Return:{key = OS_Key_Return;}break; - case XK_Pause:{key = OS_Key_Pause;}break; - case XK_Tab:{key = OS_Key_Tab;}break; - case XK_Left:{key = OS_Key_Left;}break; - case XK_Right:{key = OS_Key_Right;}break; - case XK_Up:{key = OS_Key_Up;}break; - case XK_Down:{key = OS_Key_Down;}break; - case XK_Home:{key = OS_Key_Home;}break; - case XK_End:{key = OS_Key_End;}break; - case XK_Page_Up:{key = OS_Key_PageUp;}break; - case XK_Page_Down:{key = OS_Key_PageDown;}break; - case XK_Alt_L:{ key = OS_Key_Alt; }break; - case XK_Alt_R:{ key = OS_Key_Alt; is_right_sided = 1;}break; - case XK_Shift_L:{ key = OS_Key_Shift; }break; - case XK_Shift_R:{ key = OS_Key_Shift; is_right_sided = 1;}break; - case XK_Control_L:{ key = OS_Key_Ctrl; }break; - case XK_Control_R:{ key = OS_Key_Ctrl; is_right_sided = 1;}break; - case '-':{key = OS_Key_Minus;}break; - case '=':{key = OS_Key_Equal;}break; - case '[':{key = OS_Key_LeftBracket;}break; - case ']':{key = OS_Key_RightBracket;}break; - case ';':{key = OS_Key_Semicolon;}break; - case '\'':{key = OS_Key_Quote;}break; - case '.':{key = OS_Key_Period;}break; - case ',':{key = OS_Key_Comma;}break; - case '/':{key = OS_Key_Slash;}break; - case '\\':{key = OS_Key_BackSlash;}break; - case '\t':{key = OS_Key_Tab;}break; - case 'a':case 'A':{key = OS_Key_A;}break; - case 'b':case 'B':{key = OS_Key_B;}break; - case 'c':case 'C':{key = OS_Key_C;}break; - case 'd':case 'D':{key = OS_Key_D;}break; - case 'e':case 'E':{key = OS_Key_E;}break; - case 'f':case 'F':{key = OS_Key_F;}break; - case 'g':case 'G':{key = OS_Key_G;}break; - case 'h':case 'H':{key = OS_Key_H;}break; - case 'i':case 'I':{key = OS_Key_I;}break; - case 'j':case 'J':{key = OS_Key_J;}break; - case 'k':case 'K':{key = OS_Key_K;}break; - case 'l':case 'L':{key = OS_Key_L;}break; - case 'm':case 'M':{key = OS_Key_M;}break; - case 'n':case 'N':{key = OS_Key_N;}break; - case 'o':case 'O':{key = OS_Key_O;}break; - case 'p':case 'P':{key = OS_Key_P;}break; - case 'q':case 'Q':{key = OS_Key_Q;}break; - case 'r':case 'R':{key = OS_Key_R;}break; - case 's':case 'S':{key = OS_Key_S;}break; - case 't':case 'T':{key = OS_Key_T;}break; - case 'u':case 'U':{key = OS_Key_U;}break; - case 'v':case 'V':{key = OS_Key_V;}break; - case 'w':case 'W':{key = OS_Key_W;}break; - case 'x':case 'X':{key = OS_Key_X;}break; - case 'y':case 'Y':{key = OS_Key_Y;}break; - case 'z':case 'Z':{key = OS_Key_Z;}break; - case ' ':{key = OS_Key_Space;}break; - } - - // rjf: push text event - if(evt.type == KeyPress && text_size != 0) - { - for(U64 off = 0; off < text_size;) - { - UnicodeDecode decode = utf8_decode(text+off, text_size-off); - if(decode.codepoint != 0 && (decode.codepoint >= 32 || decode.codepoint == '\t')) - { - OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_Text); - e->window.u64[0] = (U64)window; - e->character = decode.codepoint; - } - if(decode.inc == 0) - { - break; - } - off += decode.inc; - } - } - - // rjf: push key event - { - OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == KeyPress ? OS_EventKind_Press : OS_EventKind_Release); - e->window.u64[0] = (U64)window; - e->modifiers = modifiers; - e->key = key; - e->right_sided = is_right_sided; - } - }break; - - //- rjf: mouse button presses/releases - case ButtonPress: - case ButtonRelease: - { - // rjf: determine flags - OS_Modifiers modifiers = 0; - if(evt.xbutton.state & ShiftMask) { modifiers |= OS_Modifier_Shift; } - if(evt.xbutton.state & ControlMask) { modifiers |= OS_Modifier_Ctrl; } - if(evt.xbutton.state & Mod1Mask) { modifiers |= OS_Modifier_Alt; } - - // rjf: map button -> OS_Key - OS_Key key = OS_Key_Null; - switch(evt.xbutton.button) - { - default:{}break; - case Button1:{key = OS_Key_LeftMouseButton;}break; - case Button2:{key = OS_Key_MiddleMouseButton;}break; - case Button3:{key = OS_Key_RightMouseButton;}break; - } - - // rjf: push event - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xbutton.window); - if(key != OS_Key_Null) - { - OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == ButtonPress ? OS_EventKind_Press : OS_EventKind_Release); - e->window.u64[0] = (U64)window; - e->modifiers = modifiers; - e->key = key; - e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); - } - else if(evt.xbutton.button == Button4 || - evt.xbutton.button == Button5) - { - OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_Scroll); - e->window.u64[0] = (U64)window; - e->modifiers = modifiers; - e->delta = v2f32(0, evt.xbutton.button == Button4 ? -1.f : +1.f); - e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); - } - }break; - - //- rjf: mouse motion - case MotionNotify: - { - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); - OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_MouseMove); - e->window.u64[0] = (U64)window; - e->pos.x = (F32)evt.xmotion.x; - e->pos.y = (F32)evt.xmotion.y; - set_mouse_cursor = 1; - }break; - - //- rjf: window focus/unfocus - case FocusIn: - { - }break; - case FocusOut: - { - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xfocus.window); - OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowLoseFocus); - e->window.u64[0] = (U64)window; - }break; - - //- rjf: client messages - case ClientMessage: - { - if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_delete_window_atom) - { - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); - OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowClose); - e->window.u64[0] = (U64)window; - } - else if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_sync_request_atom) - { - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); - if(window != 0) - { - window->counter_value = 0; - window->counter_value |= evt.xclient.data.l[2]; - window->counter_value |= (evt.xclient.data.l[3] << 32); - XSyncValue value; - XSyncIntToValue(&value, window->counter_value); - XSyncSetCounter(os_lnx_gfx_state->display, window->counter_xid, value); - } - } - }break; - } - if(set_mouse_cursor) - { - Window root_window = 0; - Window child_window = 0; - int root_rel_x = 0; - int root_rel_y = 0; - int child_rel_x = 0; - int child_rel_y = 0; - unsigned int mask = 0; - if(XQueryPointer(os_lnx_gfx_state->display, XDefaultRootWindow(os_lnx_gfx_state->display), &root_window, &child_window, &root_rel_x, &root_rel_y, &child_rel_x, &child_rel_y, &mask)) - { - XDefineCursor(os_lnx_gfx_state->display, root_window, os_lnx_gfx_state->cursors[os_lnx_gfx_state->last_set_cursor]); - XFlush(os_lnx_gfx_state->display); - } - } - } - return evts; -} - -internal OS_Modifiers -os_get_modifiers(void) -{ - // TODO(rjf) - return 0; -} - -internal B32 -os_key_is_down(OS_Key key) -{ - // TODO(rjf) - return 0; -} - -internal Vec2F32 -os_mouse_from_window(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return v2f32(0, 0);} - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - Vec2F32 result = {0}; - { - Window root_window = 0; - Window child_window = 0; - int root_rel_x = 0; - int root_rel_y = 0; - int child_rel_x = 0; - int child_rel_y = 0; - unsigned int mask = 0; - if(XQueryPointer(os_lnx_gfx_state->display, w->window, &root_window, &child_window, &root_rel_x, &root_rel_y, &child_rel_x, &child_rel_y, &mask)) - { - result.x = child_rel_x; - result.y = child_rel_y; - } - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Cursors (Implemented Per-OS) - -internal void -os_set_cursor(OS_Cursor cursor) -{ - os_lnx_gfx_state->last_set_cursor = cursor; -} - -//////////////////////////////// -//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -os_graphical_message(B32 error, String8 title, String8 message) -{ - if(error) - { - fprintf(stderr, "[X] "); - } - fprintf(stderr, "%.*s\n", str8_varg(title)); - fprintf(stderr, "%.*s\n\n", str8_varg(message)); -} - -internal String8 -os_graphical_pick_file(Arena *arena, String8 initial_path) -{ - return str8_zero(); -} - -//////////////////////////////// -//~ rjf: @os_hooks Shell Operations - -internal void -os_show_in_filesystem_ui(String8 path) -{ - // TODO(rjf) -} - -internal void -os_open_in_browser(String8 url) -{ - // TODO(rjf) -} +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Helpers + +internal OS_LNX_Window * +os_lnx_window_from_x11window(Window window) +{ + OS_LNX_Window *result = 0; + for(OS_LNX_Window *w = os_lnx_gfx_state->first_window; w != 0; w = w->next) + { + if(w->window == window) + { + result = w; + break; + } + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void +os_gfx_init(void) +{ + //- rjf: initialize basics + Arena *arena = arena_alloc(); + os_lnx_gfx_state = push_array(arena, OS_LNX_GfxState, 1); + os_lnx_gfx_state->arena = arena; + os_lnx_gfx_state->display = XOpenDisplay(0); + + //- rjf: calculate atoms + os_lnx_gfx_state->wm_delete_window_atom = XInternAtom(os_lnx_gfx_state->display, "WM_DELETE_WINDOW", 0); + os_lnx_gfx_state->wm_sync_request_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST", 0); + os_lnx_gfx_state->wm_sync_request_counter_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST_COUNTER", 0); + + //- rjf: open im + os_lnx_gfx_state->xim = XOpenIM(os_lnx_gfx_state->display, 0, 0, 0); + + //- rjf: fill out gfx info + os_lnx_gfx_state->gfx_info.double_click_time = 0.5f; + os_lnx_gfx_state->gfx_info.caret_blink_time = 0.5f; + os_lnx_gfx_state->gfx_info.default_refresh_rate = 60.f; + + //- rjf: fill out cursors + { + struct + { + OS_Cursor cursor; + unsigned int id; + } + map[] = + { + {OS_Cursor_Pointer, XC_left_ptr}, + {OS_Cursor_IBar, XC_xterm}, + {OS_Cursor_LeftRight, XC_sb_h_double_arrow}, + {OS_Cursor_UpDown, XC_sb_v_double_arrow}, + {OS_Cursor_DownRight, XC_bottom_right_corner}, + {OS_Cursor_UpRight, XC_top_right_corner}, + {OS_Cursor_UpDownLeftRight, XC_fleur}, + {OS_Cursor_HandPoint, XC_hand1}, + {OS_Cursor_Disabled, XC_X_cursor}, + }; + for EachElement(idx, map) + { + os_lnx_gfx_state->cursors[map[idx].cursor] = XCreateFontCursor(os_lnx_gfx_state->display, map[idx].id); + } + } +} + +//////////////////////////////// +//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) + +internal OS_GfxInfo * +os_get_gfx_info(void) +{ + return &os_lnx_gfx_state->gfx_info; +} + +//////////////////////////////// +//~ rjf: @os_hooks Clipboards (Implemented Per-OS) + +internal void +os_set_clipboard_text(String8 string) +{ + +} + +internal String8 +os_get_clipboard_text(Arena *arena) +{ + String8 result = {0}; + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Windows (Implemented Per-OS) + +internal OS_Window +os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) +{ + Vec2F32 resolution = dim_2f32(rect); + + //- rjf: allocate window + OS_LNX_Window *w = os_lnx_gfx_state->free_window; + if(w) + { + SLLStackPop(os_lnx_gfx_state->free_window); + } + else + { + w = push_array_no_zero(os_lnx_gfx_state->arena, OS_LNX_Window, 1); + } + MemoryZeroStruct(w); + DLLPushBack(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + + //- rjf: create window & equip with x11 info + w->window = XCreateWindow(os_lnx_gfx_state->display, + XDefaultRootWindow(os_lnx_gfx_state->display), + 0, 0, resolution.x, resolution.y, + 0, + CopyFromParent, + InputOutput, + CopyFromParent, + 0, + 0); + XSelectInput(os_lnx_gfx_state->display, w->window, + ExposureMask| + PointerMotionMask| + ButtonPressMask| + ButtonReleaseMask| + KeyPressMask| + KeyReleaseMask| + FocusChangeMask); + Atom protocols[] = + { + os_lnx_gfx_state->wm_delete_window_atom, + os_lnx_gfx_state->wm_sync_request_atom, + }; + XSetWMProtocols(os_lnx_gfx_state->display, w->window, protocols, ArrayCount(protocols)); + { + XSyncValue initial_value; + XSyncIntToValue(&initial_value, 0); + w->counter_xid = XSyncCreateCounter(os_lnx_gfx_state->display, initial_value); + } + XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1); + + //- rjf: create xic + w->xic = XCreateIC(os_lnx_gfx_state->xim, + XNInputStyle, XIMPreeditNothing|XIMStatusNothing, + XNClientWindow, w->window, + XNFocusWindow, w->window, + NULL); + + //- rjf: attach name + Temp scratch = scratch_begin(0, 0); + String8 title_copy = push_str8_copy(scratch.arena, title); + XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str); + scratch_end(scratch); + + //- rjf: convert to handle & return + OS_Window handle = {(U64)w}; + return handle; +} + +internal void +os_window_close(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + XDestroyWindow(os_lnx_gfx_state->display, w->window); +} + +internal void +os_window_set_title(OS_Window handle, String8 title) +{ + if(os_window_match(handle, os_window_zero())) {return;} + Temp scratch = scratch_begin(0, 0); + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + String8 title_copy = push_str8_copy(scratch.arena, title); + XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str); + scratch_end(scratch); +} + +internal void +os_window_first_paint(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + XMapWindow(os_lnx_gfx_state->display, w->window); +} + +internal void +os_window_focus(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + XSetInputFocus(os_lnx_gfx_state->display, w->window, RevertToNone, CurrentTime); +} + +internal B32 +os_window_is_focused(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return 0;} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + Window focused_window = 0; + int revert_to = 0; + XGetInputFocus(os_lnx_gfx_state->display, &focused_window, &revert_to); + B32 result = (w->window == focused_window); + return result; +} + +internal B32 +os_window_is_fullscreen(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return 0;} + // TODO(rjf) + return 0; +} + +internal void +os_window_set_fullscreen(OS_Window handle, B32 fullscreen) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal B32 +os_window_is_maximized(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return 0;} + // TODO(rjf) + return 0; +} + +internal void +os_window_set_maximized(OS_Window handle, B32 maximized) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal B32 +os_window_is_minimized(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return 0;} + // TODO(rjf) + return 0; +} + +internal void +os_window_set_minimized(OS_Window handle, B32 minimized) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal void +os_window_bring_to_front(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal void +os_window_set_monitor(OS_Window handle, OS_Monitor monitor) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal void +os_window_clear_custom_border_data(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal void +os_window_push_custom_title_bar(OS_Window handle, F32 thickness) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal void +os_window_push_custom_edges(OS_Window handle, F32 thickness) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal void +os_window_push_custom_title_bar_client_area(OS_Window handle, Rng2F32 rect) +{ + if(os_window_match(handle, os_window_zero())) {return;} + // TODO(rjf) +} + +internal Rng2F32 +os_rect_from_window(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return r2f32p(0, 0, 0, 0);} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + XWindowAttributes atts = {0}; + Status s = XGetWindowAttributes(os_lnx_gfx_state->display, w->window, &atts); + Rng2F32 result = r2f32p((F32)atts.x, (F32)atts.y, (F32)atts.x + (F32)atts.width, (F32)atts.y + (F32)atts.height); + return result; +} + +internal Rng2F32 +os_client_rect_from_window(OS_Window handle) +{ + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + XWindowAttributes atts = {0}; + Status s = XGetWindowAttributes(os_lnx_gfx_state->display, w->window, &atts); + Rng2F32 result = r2f32p(0, 0, (F32)atts.width, (F32)atts.height); + return result; +} + +internal F32 +os_dpi_from_window(OS_Window handle) +{ + // TODO(rjf) + return 96.f; +} + +//////////////////////////////// +//~ rjf: @os_hooks External Windows (Implemented Per-OS) + +internal OS_ExternalWindow +os_focused_external_window(void) +{ + OS_ExternalWindow result = {0}; + // TODO(rjf) + return result; +} + +internal void +os_focus_external_window(OS_ExternalWindow handle) +{ + // TODO(rjf) +} + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_MonitorArray +os_push_monitors_array(Arena *arena) +{ + OS_MonitorArray result = {0}; + // TODO(rjf) + return result; +} + +internal OS_Monitor +os_primary_monitor(void) +{ + OS_Monitor result = {0}; + // TODO(rjf) + return result; +} + +internal OS_Monitor +os_monitor_from_window(OS_Window window) +{ + OS_Monitor result = {0}; + // TODO(rjf) + return result; +} + +internal String8 +os_name_from_monitor(Arena *arena, OS_Monitor monitor) +{ + // TODO(rjf) + return str8_zero(); +} + +internal Vec2F32 +os_dim_from_monitor(OS_Monitor monitor) +{ + // TODO(rjf) + return v2f32(0, 0); +} + +internal F32 +os_dpi_from_monitor(OS_Monitor monitor) +{ + // TODO(rjf) + return 96.f; +} + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +internal void +os_send_wakeup_event(void) +{ + // TODO(rjf) +} + +internal OS_EventList +os_get_events(Arena *arena, B32 wait) +{ + OS_EventList evts = {0}; + for(;XPending(os_lnx_gfx_state->display) > 0 || (wait && evts.count == 0);) + { + XEvent evt = {0}; + XNextEvent(os_lnx_gfx_state->display, &evt); + B32 set_mouse_cursor = 0; + switch(evt.type) + { + default:{}break; + + //- rjf: key presses/releases + case KeyPress: + case KeyRelease: + { + // rjf: determine flags + OS_Modifiers modifiers = 0; + if(evt.xkey.state & ShiftMask) { modifiers |= OS_Modifier_Shift; } + if(evt.xkey.state & ControlMask) { modifiers |= OS_Modifier_Ctrl; } + if(evt.xkey.state & Mod1Mask) { modifiers |= OS_Modifier_Alt; } + + // rjf: map keycode -> keysym & codepoint + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xkey.window); + KeySym keysym = 0; + U8 text[256] = {0}; + U64 text_size = Xutf8LookupString(window->xic, &evt.xkey, (char *)text, sizeof(text), &keysym, 0); + + // rjf: map keysym -> OS_Key + B32 is_right_sided = 0; + OS_Key key = OS_Key_Null; + switch(keysym) + { + default: + { + if(0){} + else if(XK_F1 <= keysym && keysym <= XK_F24) { key = (OS_Key)(OS_Key_F1 + (keysym - XK_F1)); } + else if('0' <= keysym && keysym <= '9') { key = OS_Key_0 + (keysym-'0'); } + }break; + case XK_Escape:{key = OS_Key_Esc;};break; + case XK_BackSpace:{key = OS_Key_Backspace;}break; + case XK_Delete:{key = OS_Key_Delete;}break; + case XK_Return:{key = OS_Key_Return;}break; + case XK_Pause:{key = OS_Key_Pause;}break; + case XK_Tab:{key = OS_Key_Tab;}break; + case XK_Left:{key = OS_Key_Left;}break; + case XK_Right:{key = OS_Key_Right;}break; + case XK_Up:{key = OS_Key_Up;}break; + case XK_Down:{key = OS_Key_Down;}break; + case XK_Home:{key = OS_Key_Home;}break; + case XK_End:{key = OS_Key_End;}break; + case XK_Page_Up:{key = OS_Key_PageUp;}break; + case XK_Page_Down:{key = OS_Key_PageDown;}break; + case XK_Alt_L:{ key = OS_Key_Alt; }break; + case XK_Alt_R:{ key = OS_Key_Alt; is_right_sided = 1;}break; + case XK_Shift_L:{ key = OS_Key_Shift; }break; + case XK_Shift_R:{ key = OS_Key_Shift; is_right_sided = 1;}break; + case XK_Control_L:{ key = OS_Key_Ctrl; }break; + case XK_Control_R:{ key = OS_Key_Ctrl; is_right_sided = 1;}break; + case '-':{key = OS_Key_Minus;}break; + case '=':{key = OS_Key_Equal;}break; + case '[':{key = OS_Key_LeftBracket;}break; + case ']':{key = OS_Key_RightBracket;}break; + case ';':{key = OS_Key_Semicolon;}break; + case '\'':{key = OS_Key_Quote;}break; + case '.':{key = OS_Key_Period;}break; + case ',':{key = OS_Key_Comma;}break; + case '/':{key = OS_Key_Slash;}break; + case '\\':{key = OS_Key_BackSlash;}break; + case '\t':{key = OS_Key_Tab;}break; + case 'a':case 'A':{key = OS_Key_A;}break; + case 'b':case 'B':{key = OS_Key_B;}break; + case 'c':case 'C':{key = OS_Key_C;}break; + case 'd':case 'D':{key = OS_Key_D;}break; + case 'e':case 'E':{key = OS_Key_E;}break; + case 'f':case 'F':{key = OS_Key_F;}break; + case 'g':case 'G':{key = OS_Key_G;}break; + case 'h':case 'H':{key = OS_Key_H;}break; + case 'i':case 'I':{key = OS_Key_I;}break; + case 'j':case 'J':{key = OS_Key_J;}break; + case 'k':case 'K':{key = OS_Key_K;}break; + case 'l':case 'L':{key = OS_Key_L;}break; + case 'm':case 'M':{key = OS_Key_M;}break; + case 'n':case 'N':{key = OS_Key_N;}break; + case 'o':case 'O':{key = OS_Key_O;}break; + case 'p':case 'P':{key = OS_Key_P;}break; + case 'q':case 'Q':{key = OS_Key_Q;}break; + case 'r':case 'R':{key = OS_Key_R;}break; + case 's':case 'S':{key = OS_Key_S;}break; + case 't':case 'T':{key = OS_Key_T;}break; + case 'u':case 'U':{key = OS_Key_U;}break; + case 'v':case 'V':{key = OS_Key_V;}break; + case 'w':case 'W':{key = OS_Key_W;}break; + case 'x':case 'X':{key = OS_Key_X;}break; + case 'y':case 'Y':{key = OS_Key_Y;}break; + case 'z':case 'Z':{key = OS_Key_Z;}break; + case ' ':{key = OS_Key_Space;}break; + } + + // rjf: push text event + if(evt.type == KeyPress && text_size != 0) + { + for(U64 off = 0; off < text_size;) + { + UnicodeDecode decode = utf8_decode(text+off, text_size-off); + if(decode.codepoint != 0 && (decode.codepoint >= 32 || decode.codepoint == '\t')) + { + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_Text); + e->window.u64[0] = (U64)window; + e->character = decode.codepoint; + } + if(decode.inc == 0) + { + break; + } + off += decode.inc; + } + } + + // rjf: push key event + { + OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == KeyPress ? OS_EventKind_Press : OS_EventKind_Release); + e->window.u64[0] = (U64)window; + e->modifiers = modifiers; + e->key = key; + e->right_sided = is_right_sided; + } + }break; + + //- rjf: mouse button presses/releases + case ButtonPress: + case ButtonRelease: + { + // rjf: determine flags + OS_Modifiers modifiers = 0; + if(evt.xbutton.state & ShiftMask) { modifiers |= OS_Modifier_Shift; } + if(evt.xbutton.state & ControlMask) { modifiers |= OS_Modifier_Ctrl; } + if(evt.xbutton.state & Mod1Mask) { modifiers |= OS_Modifier_Alt; } + + // rjf: map button -> OS_Key + OS_Key key = OS_Key_Null; + switch(evt.xbutton.button) + { + default:{}break; + case Button1:{key = OS_Key_LeftMouseButton;}break; + case Button2:{key = OS_Key_MiddleMouseButton;}break; + case Button3:{key = OS_Key_RightMouseButton;}break; + } + + // rjf: push event + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xbutton.window); + if(key != OS_Key_Null) + { + OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == ButtonPress ? OS_EventKind_Press : OS_EventKind_Release); + e->window.u64[0] = (U64)window; + e->modifiers = modifiers; + e->key = key; + e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); + } + else if(evt.xbutton.button == Button4 || + evt.xbutton.button == Button5) + { + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_Scroll); + e->window.u64[0] = (U64)window; + e->modifiers = modifiers; + e->delta = v2f32(0, evt.xbutton.button == Button4 ? -1.f : +1.f); + e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y); + } + }break; + + //- rjf: mouse motion + case MotionNotify: + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_MouseMove); + e->window.u64[0] = (U64)window; + e->pos.x = (F32)evt.xmotion.x; + e->pos.y = (F32)evt.xmotion.y; + set_mouse_cursor = 1; + }break; + + //- rjf: window focus/unfocus + case FocusIn: + { + }break; + case FocusOut: + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xfocus.window); + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowLoseFocus); + e->window.u64[0] = (U64)window; + }break; + + //- rjf: client messages + case ClientMessage: + { + if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_delete_window_atom) + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowClose); + e->window.u64[0] = (U64)window; + } + else if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_sync_request_atom) + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); + if(window != 0) + { + window->counter_value = 0; + window->counter_value |= evt.xclient.data.l[2]; + window->counter_value |= (evt.xclient.data.l[3] << 32); + XSyncValue value; + XSyncIntToValue(&value, window->counter_value); + XSyncSetCounter(os_lnx_gfx_state->display, window->counter_xid, value); + } + } + }break; + } + if(set_mouse_cursor) + { + Window root_window = 0; + Window child_window = 0; + int root_rel_x = 0; + int root_rel_y = 0; + int child_rel_x = 0; + int child_rel_y = 0; + unsigned int mask = 0; + if(XQueryPointer(os_lnx_gfx_state->display, XDefaultRootWindow(os_lnx_gfx_state->display), &root_window, &child_window, &root_rel_x, &root_rel_y, &child_rel_x, &child_rel_y, &mask)) + { + XDefineCursor(os_lnx_gfx_state->display, root_window, os_lnx_gfx_state->cursors[os_lnx_gfx_state->last_set_cursor]); + XFlush(os_lnx_gfx_state->display); + } + } + } + return evts; +} + +internal OS_Modifiers +os_get_modifiers(void) +{ + // TODO(rjf) + return 0; +} + +internal B32 +os_key_is_down(OS_Key key) +{ + // TODO(rjf) + return 0; +} + +internal Vec2F32 +os_mouse_from_window(OS_Window handle) +{ + if(os_window_match(handle, os_window_zero())) {return v2f32(0, 0);} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + Vec2F32 result = {0}; + { + Window root_window = 0; + Window child_window = 0; + int root_rel_x = 0; + int root_rel_y = 0; + int child_rel_x = 0; + int child_rel_y = 0; + unsigned int mask = 0; + if(XQueryPointer(os_lnx_gfx_state->display, w->window, &root_window, &child_window, &root_rel_x, &root_rel_y, &child_rel_x, &child_rel_y, &mask)) + { + result.x = child_rel_x; + result.y = child_rel_y; + } + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Cursors (Implemented Per-OS) + +internal void +os_set_cursor(OS_Cursor cursor) +{ + os_lnx_gfx_state->last_set_cursor = cursor; +} + +//////////////////////////////// +//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) + +internal void +os_graphical_message(B32 error, String8 title, String8 message) +{ + if(error) + { + fprintf(stderr, "[X] "); + } + fprintf(stderr, "%.*s\n", str8_varg(title)); + fprintf(stderr, "%.*s\n\n", str8_varg(message)); +} + +internal String8 +os_graphical_pick_file(Arena *arena, String8 initial_path) +{ + return str8_zero(); +} + +//////////////////////////////// +//~ rjf: @os_hooks Shell Operations + +internal void +os_show_in_filesystem_ui(String8 path) +{ + // TODO(rjf) +} + +internal void +os_open_in_browser(String8 url) +{ + // TODO(rjf) +} diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/linux/window_manager/linux_window_manager.h similarity index 95% rename from src/os/gfx/linux/os_gfx_linux.h rename to src/linux/window_manager/linux_window_manager.h index 58e51f27..0034adde 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/linux/window_manager/linux_window_manager.h @@ -1,61 +1,61 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_GFX_LINUX_H -#define OS_GFX_LINUX_H - -//////////////////////////////// -//~ rjf: Includes - -#include -#include -#include -#include -#include -#include - -//////////////////////////////// -//~ rjf: Window State - -typedef struct OS_LNX_Window OS_LNX_Window; -struct OS_LNX_Window -{ - OS_LNX_Window *next; - OS_LNX_Window *prev; - Window window; - XIC xic; - XID counter_xid; - U64 counter_value; -}; - -//////////////////////////////// -//~ rjf: State Bundle - -typedef struct OS_LNX_GfxState OS_LNX_GfxState; -struct OS_LNX_GfxState -{ - Arena *arena; - Display *display; - XIM xim; - OS_LNX_Window *first_window; - OS_LNX_Window *last_window; - OS_LNX_Window *free_window; - Atom wm_delete_window_atom; - Atom wm_sync_request_atom; - Atom wm_sync_request_counter_atom; - Cursor cursors[OS_Cursor_COUNT]; - OS_Cursor last_set_cursor; - OS_GfxInfo gfx_info; -}; - -//////////////////////////////// -//~ rjf: Globals - -global OS_LNX_GfxState *os_lnx_gfx_state = 0; - -//////////////////////////////// -//~ rjf: Helpers - -internal OS_LNX_Window *os_lnx_window_from_x11window(Window window); - -#endif // OS_GFX_LINUX_H +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_GFX_LINUX_H +#define OS_GFX_LINUX_H + +//////////////////////////////// +//~ rjf: Includes + +#include +#include +#include +#include +#include +#include + +//////////////////////////////// +//~ rjf: Window State + +typedef struct OS_LNX_Window OS_LNX_Window; +struct OS_LNX_Window +{ + OS_LNX_Window *next; + OS_LNX_Window *prev; + Window window; + XIC xic; + XID counter_xid; + U64 counter_value; +}; + +//////////////////////////////// +//~ rjf: State Bundle + +typedef struct OS_LNX_GfxState OS_LNX_GfxState; +struct OS_LNX_GfxState +{ + Arena *arena; + Display *display; + XIM xim; + OS_LNX_Window *first_window; + OS_LNX_Window *last_window; + OS_LNX_Window *free_window; + Atom wm_delete_window_atom; + Atom wm_sync_request_atom; + Atom wm_sync_request_counter_atom; + Cursor cursors[OS_Cursor_COUNT]; + OS_Cursor last_set_cursor; + OS_GfxInfo gfx_info; +}; + +//////////////////////////////// +//~ rjf: Globals + +global OS_LNX_GfxState *os_lnx_gfx_state = 0; + +//////////////////////////////// +//~ rjf: Helpers + +internal OS_LNX_Window *os_lnx_window_from_x11window(Window window); + +#endif // OS_GFX_LINUX_H diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c deleted file mode 100644 index 0e95df77..00000000 --- a/src/os/core/linux/os_core_linux.c +++ /dev/null @@ -1,1570 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Helpers - -internal DateTime -os_lnx_date_time_from_tm(tm in, U32 msec) -{ - DateTime dt = {0}; - dt.sec = in.tm_sec; - dt.min = in.tm_min; - dt.hour = in.tm_hour; - dt.day = in.tm_mday-1; - dt.mon = in.tm_mon; - dt.year = in.tm_year+1900; - dt.msec = msec; - return dt; -} - -internal tm -os_lnx_tm_from_date_time(DateTime dt) -{ - tm result = {0}; - result.tm_sec = dt.sec; - result.tm_min = dt.min; - result.tm_hour= dt.hour; - result.tm_mday= dt.day+1; - result.tm_mon = dt.mon; - result.tm_year= dt.year-1900; - return result; -} - -internal timespec -os_lnx_timespec_from_date_time(DateTime dt) -{ - tm tm_val = os_lnx_tm_from_date_time(dt); - time_t seconds = timegm(&tm_val); - timespec result = {0}; - result.tv_sec = seconds; - return result; -} - -internal DenseTime -os_lnx_dense_time_from_timespec(timespec in) -{ - DenseTime result = 0; - { - struct tm tm_time = {0}; - gmtime_r(&in.tv_sec, &tm_time); - DateTime date_time = os_lnx_date_time_from_tm(tm_time, in.tv_nsec/Million(1)); - result = dense_time_from_date_time(date_time); - } - return result; -} - -internal FileProperties -os_lnx_file_properties_from_stat(struct stat *s) -{ - FileProperties props = {0}; - props.size = s->st_size; - props.created = os_lnx_dense_time_from_timespec(s->st_ctim); - props.modified = os_lnx_dense_time_from_timespec(s->st_mtim); - if(s->st_mode & S_IFDIR) - { - props.flags |= FilePropertyFlag_IsFolder; - } - return props; -} - -internal void -os_lnx_safe_call_sig_handler(int x) -{ - OS_LNX_SafeCallChain *chain = os_lnx_safe_call_chain; - if(chain != 0 && chain->fail_handler != 0) - { - chain->fail_handler(chain->ptr); - } - abort(); -} - -//////////////////////////////// -//~ rjf: Entities - -internal OS_LNX_Entity * -os_lnx_entity_alloc(OS_LNX_EntityKind kind) -{ - OS_LNX_Entity *entity = 0; - DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), - pthread_mutex_unlock(&os_lnx_state.entity_mutex)) - { - entity = os_lnx_state.entity_free; - if(entity) - { - SLLStackPop(os_lnx_state.entity_free); - } - else - { - entity = push_array_no_zero(os_lnx_state.entity_arena, OS_LNX_Entity, 1); - } - } - MemoryZeroStruct(entity); - entity->kind = kind; - return entity; -} - -internal void -os_lnx_entity_release(OS_LNX_Entity *entity) -{ - DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), - pthread_mutex_unlock(&os_lnx_state.entity_mutex)) - { - SLLStackPush(os_lnx_state.entity_free, entity); - } -} - -//////////////////////////////// -//~ rjf: Thread Entry Point - -internal void * -os_lnx_thread_entry_point(void *ptr) -{ - OS_LNX_Entity *entity = (OS_LNX_Entity *)ptr; - ThreadEntryPointFunctionType *func = entity->thread.func; - void *thread_ptr = entity->thread.ptr; - supplement_thread_base_entry_point(func, thread_ptr); - return 0; -} - -//////////////////////////////// -//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) - -internal OS_SystemInfo * -os_get_system_info(void) -{ - return &os_lnx_state.system_info; -} - -internal OS_ProcessInfo * -os_get_process_info(void) -{ - return &os_lnx_state.process_info; -} - -internal String8 -os_get_current_path(Arena *arena) -{ - char *cwdir = getcwd(0, 0); - String8 string = push_str8_copy(arena, str8_cstring(cwdir)); - free(cwdir); - return string; -} - -internal U32 -os_get_process_start_time_unix(void) -{ - Temp scratch = scratch_begin(0,0); - U64 start_time = 0; - pid_t pid = getpid(); - String8 path = push_str8f(scratch.arena, "/proc/%u", pid); - struct stat st; - int err = stat((char *)path.str, &st); - if(err == 0) - { - start_time = st.st_mtime; - } - scratch_end(scratch); - return (U32)start_time; -} - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -//- rjf: basic - -internal void * -os_reserve(U64 size) -{ - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - if(result == MAP_FAILED) - { - result = 0; - } - return result; -} - -internal B32 -os_commit(void *ptr, U64 size) -{ - mprotect(ptr, size, PROT_READ|PROT_WRITE); - return 1; -} - -internal void -os_decommit(void *ptr, U64 size) -{ - madvise(ptr, size, MADV_DONTNEED); - mprotect(ptr, size, PROT_NONE); -} - -internal void -os_release(void *ptr, U64 size) -{ - munmap(ptr, size); -} - -//- rjf: large pages - -internal void * -os_reserve_large(U64 size) -{ - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0); - if(result == MAP_FAILED) - { - result = 0; - } - return result; -} - -internal B32 -os_commit_large(void *ptr, U64 size) -{ - mprotect(ptr, size, PROT_READ|PROT_WRITE); - return 1; -} - -//////////////////////////////// -//~ rjf: @os_hooks Thread Info (Implemented Per-OS) - -internal U32 -os_tid(void) -{ - U32 result = gettid(); - return result; -} - -internal void -os_set_thread_name(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String8 name_copy = push_str8_copy(scratch.arena, name); - pthread_t current_thread = pthread_self(); - pthread_setname_np(current_thread, (char *)name_copy.str); - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: @os_hooks Aborting (Implemented Per-OS) - -internal void -os_abort(S32 exit_code) -{ - exit(exit_code); -} - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files - -internal OS_Handle -os_file_open(OS_AccessFlags flags, String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = push_str8_copy(scratch.arena, path); - int lnx_flags = 0; - if(flags & OS_AccessFlag_Read && flags & OS_AccessFlag_Write) - { - lnx_flags = O_RDWR; - } - else if(flags & OS_AccessFlag_Write) - { - lnx_flags = O_WRONLY; - } - else if(flags & OS_AccessFlag_Read) - { - lnx_flags = O_RDONLY; - } - if(flags & OS_AccessFlag_Append) - { - lnx_flags |= O_APPEND; - } - if(flags & (OS_AccessFlag_Write|OS_AccessFlag_Append)) - { - lnx_flags |= O_CREAT; - } - lnx_flags |= O_CLOEXEC; - int fd = open((char *)path_copy.str, lnx_flags, 0755); - OS_Handle handle = {0}; - if(fd != -1) - { - handle.u64[0] = fd; - } - scratch_end(scratch); - return handle; -} - -internal void -os_file_close(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return; } - int fd = (int)file.u64[0]; - close(fd); -} - -internal U64 -os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - int fd = (int)file.u64[0]; - U64 total_num_bytes_to_read = dim_1u64(rng); - U64 total_num_bytes_read = 0; - U64 total_num_bytes_left_to_read = total_num_bytes_to_read; - for(;total_num_bytes_left_to_read > 0;) - { - int read_result = pread(fd, (U8 *)out_data + total_num_bytes_read, total_num_bytes_left_to_read, rng.min + total_num_bytes_read); - if(read_result >= 0) - { - total_num_bytes_read += read_result; - total_num_bytes_left_to_read -= read_result; - } - else if(errno != EINTR) - { - break; - } - } - return total_num_bytes_read; -} - -internal U64 -os_file_write(OS_Handle file, Rng1U64 rng, void *data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - int fd = (int)file.u64[0]; - U64 total_num_bytes_to_write = dim_1u64(rng); - U64 total_num_bytes_written = 0; - U64 total_num_bytes_left_to_write = total_num_bytes_to_write; - for(;total_num_bytes_left_to_write > 0;) - { - int write_result = pwrite(fd, (U8 *)data + total_num_bytes_written, total_num_bytes_left_to_write, rng.min + total_num_bytes_written); - if(write_result >= 0) - { - total_num_bytes_written += write_result; - total_num_bytes_left_to_write -= write_result; - } - else if(errno != EINTR) - { - break; - } - } - return total_num_bytes_written; -} - -internal B32 -os_file_set_times(OS_Handle file, DateTime date_time) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - int fd = (int)file.u64[0]; - timespec time = os_lnx_timespec_from_date_time(date_time); - timespec times[2] = {time, time}; - int futimens_result = futimens(fd, times); - B32 good = (futimens_result != -1); - return good; -} - -internal FileProperties -os_properties_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return (FileProperties){0}; } - int fd = (int)file.u64[0]; - struct stat fd_stat = {0}; - int fstat_result = fstat(fd, &fd_stat); - FileProperties props = {0}; - if(fstat_result != -1) - { - props = os_lnx_file_properties_from_stat(&fd_stat); - } - return props; -} - -internal OS_FileID -os_id_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return (OS_FileID){0}; } - int fd = (int)file.u64[0]; - struct stat fd_stat = {0}; - int fstat_result = fstat(fd, &fd_stat); - OS_FileID id = {0}; - if(fstat_result != -1) - { - id.v[0] = fd_stat.st_dev; - id.v[1] = fd_stat.st_ino; - } - return id; -} - -internal B32 -os_delete_file_at_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 result = 0; - String8 path_copy = push_str8_copy(scratch.arena, path); - if(remove((char *)path_copy.str) != -1) - { - result = 1; - } - scratch_end(scratch); - return result; -} - -internal B32 -os_copy_file_path(String8 dst, String8 src) -{ - B32 result = 0; - OS_Handle src_h = os_file_open(OS_AccessFlag_Read, src); - OS_Handle dst_h = os_file_open(OS_AccessFlag_Write, dst); - if(!os_handle_match(src_h, os_handle_zero()) && - !os_handle_match(dst_h, os_handle_zero())) - { - int src_fd = (int)src_h.u64[0]; - int dst_fd = (int)dst_h.u64[0]; - FileProperties src_props = os_properties_from_file(src_h); - U64 size = src_props.size; - U64 total_bytes_copied = 0; - U64 bytes_left_to_copy = size; - for(;bytes_left_to_copy > 0;) - { - off_t sendfile_off = total_bytes_copied; - int send_result = sendfile(dst_fd, src_fd, &sendfile_off, bytes_left_to_copy); - if(send_result <= 0) - { - break; - } - U64 bytes_copied = (U64)send_result; - bytes_left_to_copy -= bytes_copied; - total_bytes_copied += bytes_copied; - } - } - os_file_close(src_h); - os_file_close(dst_h); - return result; -} - -internal B32 -os_move_file_path(String8 dst, String8 src) -{ - B32 good = 0; - Temp scratch = scratch_begin(0, 0); - { - char *src_cstr = (char *)str8_copy(scratch.arena, src).str; - char *dst_cstr = (char *)str8_copy(scratch.arena, dst).str; - int rename_result = rename(src_cstr, dst_cstr); - good = (rename_result != -1); - } - scratch_end(scratch); - return good; -} - -internal String8 -os_full_path_from_path(Arena *arena, String8 path) -{ - Temp scratch = scratch_begin(&arena, 1); - String8 path_copy = str8_copy(scratch.arena, path); - char buffer[PATH_MAX] = {0}; - realpath((char *)path_copy.str, buffer); - String8 result = str8_copy(arena, str8_cstring(buffer)); - scratch_end(scratch); - return result; -} - -internal B32 -os_file_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = push_str8_copy(scratch.arena, path); - int access_result = access((char *)path_copy.str, F_OK); - B32 result = 0; - if(access_result == 0) - { - result = 1; - } - scratch_end(scratch); - return result; -} - -internal B32 -os_folder_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 exists = 0; - String8 path_copy = str8_copy(scratch.arena, path); - DIR *handle = opendir((char *)path_copy.str); - if(handle) - { - closedir(handle); - exists = 1; - } - scratch_end(scratch); - return exists; -} - -internal FileProperties -os_properties_from_file_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = str8_copy(scratch.arena, path); - struct stat f_stat = {0}; - int stat_result = stat((char *)path_copy.str, &f_stat); - FileProperties props = {0}; - if(stat_result != -1) - { - props = os_lnx_file_properties_from_stat(&f_stat); - } - scratch_end(scratch); - return props; -} - -//- rjf: file maps - -internal OS_Handle -os_file_map_open(OS_AccessFlags flags, OS_Handle file) -{ - OS_Handle map = file; - return map; -} - -internal void -os_file_map_close(OS_Handle map) -{ - // NOTE(rjf): nothing to do; `map` handles are the same as `file` handles in - // the linux implementation (on Windows they require separate handles) -} - -internal void * -os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) -{ - if(os_handle_match(map, os_handle_zero())) { return 0; } - int fd = (int)map.u64[0]; - int prot_flags = 0; - if(flags & OS_AccessFlag_Write) { prot_flags |= PROT_WRITE; } - if(flags & OS_AccessFlag_Read) { prot_flags |= PROT_READ; } - int map_flags = MAP_PRIVATE; - void *base = mmap(0, dim_1u64(range), prot_flags, map_flags, fd, range.min); - if(base == MAP_FAILED) - { - base = 0; - } - return base; -} - -internal void -os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) -{ - munmap(ptr, dim_1u64(range)); -} - -//- rjf: directory iteration - -internal OS_FileIter * -os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) -{ - OS_FileIter *base_iter = push_array(arena, OS_FileIter, 1); - base_iter->flags = flags; - OS_LNX_FileIter *iter = (OS_LNX_FileIter *)base_iter->memory; - { - String8 path_copy = push_str8_copy(arena, path); - iter->dir = opendir((char *)path_copy.str); - iter->path = path_copy; - } - return base_iter; -} - -internal B32 -os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) -{ - B32 good = 0; - OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; - for(;lnx_iter->dir != 0;) - { - // rjf: get next entry - lnx_iter->dp = readdir(lnx_iter->dir); - good = (lnx_iter->dp != 0); - - // rjf: unpack entry info - struct stat st = {0}; - int stat_result = 0; - if(good) - { - Temp scratch = scratch_begin(&arena, 1); - String8 full_path = push_str8f(scratch.arena, "%S/%s", lnx_iter->path, lnx_iter->dp->d_name); - stat_result = stat((char *)full_path.str, &st); - scratch_end(scratch); - } - - // rjf: determine if filtered - B32 filtered = 0; - if(good) - { - filtered = ((st.st_mode == S_IFDIR && iter->flags & OS_FileIterFlag_SkipFolders) || - (st.st_mode == S_IFREG && iter->flags & OS_FileIterFlag_SkipFiles) || - (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == 0) || - (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == '.' && lnx_iter->dp->d_name[2] == 0)); - } - - // rjf: output & exit, if good & unfiltered - if(good && !filtered) - { - info_out->name = push_str8_copy(arena, str8_cstring(lnx_iter->dp->d_name)); - if(stat_result != -1) - { - info_out->props = os_lnx_file_properties_from_stat(&st); - } - break; - } - - // rjf: exit if not good - if(!good) - { - break; - } - } - return good; -} - -internal void -os_file_iter_end(OS_FileIter *iter) -{ - OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; - closedir(lnx_iter->dir); -} - -//- rjf: directory creation - -internal B32 -os_make_directory(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 result = 0; - String8 path_copy = push_str8_copy(scratch.arena, path); - if(mkdir((char *)path_copy.str, 0755) != -1) - { - result = 1; - } - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle -os_shared_memory_alloc(U64 size, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String8 name_copy = push_str8_copy(scratch.arena, name); - int id = shm_open((char *)name_copy.str, O_RDWR|O_CREAT, 0666); - ftruncate(id, size); - OS_Handle result = {(U64)id}; - scratch_end(scratch); - return result; -} - -internal OS_Handle -os_shared_memory_open(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String8 name_copy = push_str8_copy(scratch.arena, name); - int id = shm_open((char *)name_copy.str, O_RDWR, 0); - OS_Handle result = {(U64)id}; - scratch_end(scratch); - return result; -} - -internal void -os_shared_memory_close(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())){return;} - int id = (int)handle.u64[0]; - close(id); -} - -internal void * -os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) -{ - if(os_handle_match(handle, os_handle_zero())){return 0;} - int id = (int)handle.u64[0]; - void *base = mmap(0, dim_1u64(range), PROT_READ|PROT_WRITE, MAP_SHARED, id, range.min); - if(base == MAP_FAILED) - { - base = 0; - } - return base; -} - -internal void -os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) -{ - if(os_handle_match(handle, os_handle_zero())){return;} - munmap(ptr, dim_1u64(range)); -} - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal U64 -os_now_microseconds(void) -{ - struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); - U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); - return result; -} - -internal U32 -os_now_unix(void) -{ - time_t t = time(0); - return (U32)t; -} - -internal DateTime -os_now_universal_time(void) -{ - time_t t = 0; - time(&t); - struct tm universal_tm = {0}; - gmtime_r(&t, &universal_tm); - DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); - return result; -} - -internal DateTime -os_universal_time_from_local(DateTime *date_time) -{ - // rjf: local DateTime -> universal time_t - tm local_tm = os_lnx_tm_from_date_time(*date_time); - local_tm.tm_isdst = -1; - time_t universal_t = mktime(&local_tm); - - // rjf: universal time_t -> DateTime - tm universal_tm = {0}; - gmtime_r(&universal_t, &universal_tm); - DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); - return result; -} - -internal DateTime -os_local_time_from_universal(DateTime *date_time) -{ - // rjf: universal DateTime -> local time_t - tm universal_tm = os_lnx_tm_from_date_time(*date_time); - universal_tm.tm_isdst = -1; - time_t universal_t = timegm(&universal_tm); - tm local_tm = {0}; - localtime_r(&universal_t, &local_tm); - - // rjf: local tm -> DateTime - DateTime result = os_lnx_date_time_from_tm(local_tm, 0); - return result; -} - -internal void -os_sleep_milliseconds(U32 msec) -{ - usleep(msec*Thousand(1)); -} - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal OS_Handle -os_process_launch(OS_ProcessLaunchParams *params) -{ - OS_Handle handle = {0}; - posix_spawn_file_actions_t file_actions = {0}; - int file_actions_init_code = posix_spawn_file_actions_init(&file_actions); - if(file_actions_init_code == 0) - { - int stdout_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stdout_file.u64[0], STDOUT_FILENO); - int stderr_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stderr_file.u64[0], STDERR_FILENO); - int stdin_code = posix_spawn_file_actions_adddup2(&file_actions, (int)params->stdin_file.u64[0], STDIN_FILENO); - posix_spawnattr_t attr = {0}; - int attr_init_code = posix_spawnattr_init(&attr); - if(attr_init_code == 0) - { - Temp scratch = scratch_begin(0, 0); - - // package argv - char **argv = push_array(scratch.arena, char *, params->cmd_line.node_count + 1); - { - String8List l = str8_split_path(scratch.arena, params->path); - str8_list_push(scratch.arena, &l, params->cmd_line.first->string); - String8 path_to_exe = str8_path_list_join_by_style(scratch.arena, &l, PathStyle_SystemAbsolute); - argv[0] = (char *)path_to_exe.str; - U64 arg_idx = 1; - for EachNode(n, String8Node, params->cmd_line.first->next) - { - argv[arg_idx] = (char *)n->string.str; - arg_idx += 1; - } - } - - // package envp - char **envp = 0; - if(params->inherit_env) - { - envp = os_lnx_state.default_env; - } - else - { - envp = push_array(scratch.arena, char *, params->env.node_count + 2); - U64 env_idx = 0; - for EachNode(n, String8Node, params->cmd_line.first) - { - envp[env_idx] = (char *)n->string.str; - env_idx += 1; - } - } - - // spawn process - pid_t pid = 0; - int spawn_code = posix_spawn(&pid, argv[0], &file_actions, &attr, argv, envp); - if(spawn_code == 0) - { - handle.u64[0] = (U64)pid; - } - - // clean up attributes - int attr_destroy_code = posix_spawnattr_destroy(&attr); - scratch_end(scratch); - } - - // clean up file actions - int file_actions_destroy_code = posix_spawn_file_actions_destroy(&file_actions); - } - return handle; -} - -internal B32 -os_process_join(OS_Handle handle, U64 endt_us, U64 *exit_code_out) -{ - pid_t pid = (pid_t)handle.u64[0]; - B32 result = 0; - if(endt_us == 0) - { - if(kill(pid, 0) >= 0) - { - result = (errno == ENOENT); - if(result) - { - int status; - waitpid(pid, &status, 0); - } - } - } - else if(endt_us == max_U64) - { - for(;;) - { - int status = 0; - int w = waitpid(pid, &status, 0); - if(w == -1) - { - break; - } - if(WIFEXITED(status) || WIFSTOPPED(status) || WIFSIGNALED(status)) - { - result = 1; - break; - } - } - } - else - { - NotImplemented; - } - return result; -} - -internal void -os_process_detach(OS_Handle handle) -{ - // no need to close pid -} - -internal B32 -os_process_kill(OS_Handle handle) -{ - int error_code = kill((pid_t)handle.u64[0], SIGKILL); - B32 is_killed = error_code == 0; - return is_killed; -} - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal Thread -os_thread_launch(ThreadEntryPointFunctionType *func, void *ptr) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Thread); - entity->thread.func = func; - entity->thread.ptr = ptr; - { - int pthread_result = pthread_create(&entity->thread.handle, 0, os_lnx_thread_entry_point, entity); - if(pthread_result == -1) - { - os_lnx_entity_release(entity); - entity = 0; - } - } - Thread handle = {(U64)entity}; - return handle; -} - -internal B32 -os_thread_join(Thread handle, U64 endt_us) -{ - if(MemoryIsZeroStruct(&handle)) { return 0; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; - int join_result = pthread_join(entity->thread.handle, 0); - B32 result = (join_result == 0); - os_lnx_entity_release(entity); - return result; -} - -internal void -os_thread_detach(Thread handle) -{ - if(MemoryIsZeroStruct(&handle)) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; - os_lnx_entity_release(entity); -} - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -//- rjf: mutexes - -internal Mutex -os_mutex_alloc(void) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Mutex); - int init_result = pthread_mutex_init(&entity->mutex_handle, 0); - if(init_result == -1) - { - os_lnx_entity_release(entity); - entity = 0; - } - Mutex handle = {(U64)entity}; - return handle; -} - -internal void -os_mutex_release(Mutex mutex) -{ - if(MemoryIsZeroStruct(&mutex)) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; - pthread_mutex_destroy(&entity->mutex_handle); - os_lnx_entity_release(entity); -} - -internal void -os_mutex_take(Mutex mutex) -{ - if(MemoryIsZeroStruct(&mutex)) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; - pthread_mutex_lock(&entity->mutex_handle); -} - -internal void -os_mutex_drop(Mutex mutex) -{ - if(MemoryIsZeroStruct(&mutex)) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; - pthread_mutex_unlock(&entity->mutex_handle); -} - -//- rjf: reader/writer mutexes - -internal RWMutex -os_rw_mutex_alloc(void) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_RWMutex); - int init_result = pthread_rwlock_init(&entity->rwmutex_handle, 0); - if(init_result == -1) - { - os_lnx_entity_release(entity); - entity = 0; - } - RWMutex handle = {(U64)entity}; - return handle; -} - -internal void -os_rw_mutex_release(RWMutex rw_mutex) -{ - if(MemoryIsZeroStruct(&rw_mutex)) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; - pthread_rwlock_destroy(&entity->rwmutex_handle); - os_lnx_entity_release(entity); -} - -internal void -os_rw_mutex_take(RWMutex rw_mutex, B32 write_mode) -{ - if(MemoryIsZeroStruct(&rw_mutex)) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; - if(write_mode) - { - pthread_rwlock_wrlock(&entity->rwmutex_handle); - } - else - { - pthread_rwlock_rdlock(&entity->rwmutex_handle); - } -} - -internal void -os_rw_mutex_drop(RWMutex rw_mutex, B32 write_mode) -{ - if(MemoryIsZeroStruct(&rw_mutex)) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; - pthread_rwlock_unlock(&entity->rwmutex_handle); -} - -//- rjf: condition variables - -internal CondVar -os_cond_var_alloc(void) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_ConditionVariable); - int init_result = pthread_cond_init(&entity->cv.cond_handle, 0); - if(init_result == -1) - { - os_lnx_entity_release(entity); - entity = 0; - } - int init2_result = 0; - if(entity) - { - init2_result = pthread_mutex_init(&entity->cv.rwlock_mutex_handle, 0); - } - if(init2_result == -1) - { - pthread_cond_destroy(&entity->cv.cond_handle); - os_lnx_entity_release(entity); - entity = 0; - } - CondVar handle = {(U64)entity}; - return handle; -} - -internal void -os_cond_var_release(CondVar cv) -{ - if(MemoryIsZeroStruct(&cv)) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)cv.u64[0]; - pthread_cond_destroy(&entity->cv.cond_handle); - pthread_mutex_destroy(&entity->cv.rwlock_mutex_handle); - os_lnx_entity_release(entity); -} - -internal B32 -os_cond_var_wait(CondVar cv, Mutex mutex, U64 endt_us) -{ - if(MemoryIsZeroStruct(&cv)) { return 0; } - if(MemoryIsZeroStruct(&mutex)) { return 0; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - OS_LNX_Entity *mutex_entity = (OS_LNX_Entity *)mutex.u64[0]; - struct timespec endt_timespec; - endt_timespec.tv_sec = endt_us/Million(1); - endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); - int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &mutex_entity->mutex_handle, &endt_timespec); - B32 result = (wait_result != ETIMEDOUT); - return result; -} - -internal B32 -os_cond_var_wait_rw(CondVar cv, RWMutex mutex_rw, B32 write_mode, U64 endt_us) -{ - // TODO(rjf): because pthread does not supply cv/rw natively, I had to hack - // this together, but this would probably just be a lot better if we just - // implemented the primitives ourselves with e.g. futexes - // - if(MemoryIsZeroStruct(&cv)) { return 0; } - if(MemoryIsZeroStruct(&mutex_rw)) { return 0; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - OS_LNX_Entity *rw_mutex_entity = (OS_LNX_Entity *)mutex_rw.u64[0]; - struct timespec endt_timespec; - endt_timespec.tv_sec = endt_us/Million(1); - endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); - B32 result = 0; - pthread_mutex_lock(&cv_entity->cv.rwlock_mutex_handle); - pthread_rwlock_unlock(&rw_mutex_entity->rwmutex_handle); - for(;;) - { - int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &cv_entity->cv.rwlock_mutex_handle, &endt_timespec); - if(wait_result != ETIMEDOUT) - { - if(write_mode) - { - pthread_rwlock_wrlock(&rw_mutex_entity->rwmutex_handle); - } - else - { - pthread_rwlock_rdlock(&rw_mutex_entity->rwmutex_handle); - } - result = 1; - break; - } - if(wait_result == ETIMEDOUT) - { - if(write_mode) - { - pthread_rwlock_wrlock(&rw_mutex_entity->rwmutex_handle); - } - else - { - pthread_rwlock_rdlock(&rw_mutex_entity->rwmutex_handle); - } - break; - } - } - pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); - return result; -} - -internal void -os_cond_var_signal(CondVar cv) -{ - if(MemoryIsZeroStruct(&cv)) { return; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - pthread_cond_signal(&cv_entity->cv.cond_handle); -} - -internal void -os_cond_var_broadcast(CondVar cv) -{ - if(MemoryIsZeroStruct(&cv)) { return; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - pthread_cond_broadcast(&cv_entity->cv.cond_handle); -} - -//- rjf: cross-process semaphores - -internal Semaphore -os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - Semaphore result = {0}; - if(name.size > 0) - { - for EachIndex(attempt_idx, 64) - { - String8 name_copy = str8_copy(scratch.arena, name); - sem_t *s = sem_open((char *)name_copy.str, O_CREAT | O_EXCL, 0666, initial_count); - if(s == SEM_FAILED) - { - s = sem_open((char *)name_copy.str, 0); - } - if(s != SEM_FAILED) - { - result.u64[0] = (U64)s; - break; - } - } - } - else - { - sem_t *s = mmap(0, sizeof(*s), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - AssertAlways(s != MAP_FAILED); - int err = sem_init(s, 0, initial_count); - if(err == 0) - { - result.u64[0] = (U64)s; - } - } - scratch_end(scratch); - return result; -} - -internal void -os_semaphore_release(Semaphore semaphore) -{ - int err = munmap((void*)semaphore.u64[0], sizeof(sem_t)); - AssertAlways(err == 0); -} - -internal Semaphore -os_semaphore_open(String8 name) -{ - Semaphore result = {0}; - { - Temp scratch = scratch_begin(0, 0); - String8 name_copy = str8_copy(scratch.arena, name); - sem_t *s = sem_open((char *)name_copy.str, 0); - if(s != SEM_FAILED) - { - result.u64[0] = (U64)s; - } - scratch_end(scratch); - } - return result; -} - -internal void -os_semaphore_close(Semaphore semaphore) -{ - sem_t *s = (sem_t *)semaphore.u64[0]; - sem_close(s); -} - -internal B32 -os_semaphore_take(Semaphore semaphore, U64 endt_us) -{ - // TODO(rjf): we need to use `sem_timedwait` here. - AssertAlways(endt_us == max_U64); - for(;;) - { - int err = sem_wait((sem_t*)semaphore.u64[0]); - if(err == 0) - { - break; - } - else if(errno == EAGAIN) - { - continue; - } - break; - } - return 1; -} - -internal void -os_semaphore_drop(Semaphore semaphore) -{ - for(;;) - { - int err = sem_post((sem_t*)semaphore.u64[0]); - if(err == 0) - { - break; - } - else - { - if(errno == EAGAIN) - { - continue; - } - } - break; - } -} - -//- rjf: barriers - -internal Barrier -os_barrier_alloc(U64 count) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Barrier); - if(entity != 0) - { - pthread_barrier_init(&entity->barrier, 0, count); - } - Barrier result = {IntFromPtr(entity)}; - return result; -} - -internal void -os_barrier_release(Barrier barrier) -{ - OS_LNX_Entity *entity = (OS_LNX_Entity*)PtrFromInt(barrier.u64[0]); - if(entity != 0) - { - pthread_barrier_destroy(&entity->barrier); - os_lnx_entity_release(entity); - } -} - -internal void -os_barrier_wait(Barrier barrier) -{ - OS_LNX_Entity *entity = (OS_LNX_Entity*)PtrFromInt(barrier.u64[0]); - if(entity != 0) - { - pthread_barrier_wait(&entity->barrier); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle -os_library_open(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - char *path_cstr = (char *)str8_copy(scratch.arena, path).str; - void *so = dlopen(path_cstr, RTLD_LAZY|RTLD_LOCAL); - OS_Handle lib = { (U64)so }; - scratch_end(scratch); - return lib; -} - -internal VoidProc* -os_library_load_proc(OS_Handle lib, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - void *so = (void *)lib.u64; - char *name_cstr = (char *)str8_copy(scratch.arena, name).str; - VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); - scratch_end(scratch); - return proc; -} - -internal void -os_library_close(OS_Handle lib) -{ - void *so = (void *)lib.u64; - dlclose(so); -} - -//////////////////////////////// -//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) - -internal void -os_safe_call(ThreadEntryPointFunctionType *func, ThreadEntryPointFunctionType *fail_handler, void *ptr) -{ - // rjf: push handler to chain - OS_LNX_SafeCallChain chain = {0}; - SLLStackPush(os_lnx_safe_call_chain, &chain); - chain.fail_handler = fail_handler; - chain.ptr = ptr; - - // rjf: set up sig handler info - struct sigaction new_act = {0}; - new_act.sa_handler = os_lnx_safe_call_sig_handler; - int signals_to_handle[] = - { - SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, - }; - struct sigaction og_act[ArrayCount(signals_to_handle)] = {0}; - - // rjf: attach handler info for all signals - for(U32 i = 0; i < ArrayCount(signals_to_handle); i += 1) - { - sigaction(signals_to_handle[i], &new_act, &og_act[i]); - } - - // rjf: call function - func(ptr); - - // rjf: reset handler info for all signals - for(U32 i = 0; i < ArrayCount(signals_to_handle); i += 1) - { - sigaction(signals_to_handle[i], &og_act[i], 0); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks GUIDs (Implemented Per-OS) - -internal Guid -os_make_guid(void) -{ - Guid guid = {0}; - getrandom(guid.v, sizeof(guid.v), 0); - guid.data3 &= 0x0fff; - guid.data3 |= (4 << 12); - guid.data4[0] &= 0x3f; - guid.data4[0] |= 0x80; - return guid; -} - -//////////////////////////////// -//~ rjf: @os_hooks Entry Points (Implemented Per-OS) - -internal void -lnx_signal_handler(int sig, siginfo_t *info, void *arg) -{ - local_persist volatile U32 first = 0; - if (ins_atomic_u32_eval_cond_assign(&first, 1, 0) != 0) - { - for(;;) - { - sleep(UINT32_MAX); - } - } - - local_persist void *ips[4096]; - int ips_count = backtrace(ips, ArrayCount(ips)); - - fprintf(stderr, "A fatal signal was received: %s (%d). The process is terminating.\n", strsignal(sig), sig); - fprintf(stderr, "Create a new issue with this report at %s.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); - fprintf(stderr, "Callstack:\n"); - for EachIndex(i, ips_count) - { - Dl_info info = {0}; - dladdr(ips[i], &info); - - char cmd[2048]; - snprintf(cmd, sizeof(cmd), "llvm-symbolizer --relative-address -f -e %s %lu", info.dli_fname, (unsigned long)ips[i] - (unsigned long)info.dli_fbase); - FILE *f = popen(cmd, "r"); - if(f) - { - char func_name[256], file_name[256]; - if(fgets(func_name, sizeof(func_name), f) && fgets(file_name, sizeof(file_name), f)) - { - String8 func = str8_cstring(func_name); - if(func.size > 0) func.size -= 1; - String8 module = str8_skip_last_slash(str8_cstring(info.dli_fname)); - String8 file = str8_skip_last_slash(str8_cstring_capped(file_name, file_name + sizeof(file_name))); - if(file.size > 0) file.size -= 1; - - B32 no_func = str8_match(func, str8_lit("??"), StringMatchFlag_RightSideSloppy); - B32 no_file = str8_match(file, str8_lit("??"), StringMatchFlag_RightSideSloppy); - if(no_func) { func = str8_zero(); } - if(no_file) { file = str8_zero(); } - - fprintf(stderr, "%ld. [0x%016lx] %.*s%s%.*s %.*s\n", i+1, (unsigned long)ips[i], (int)module.size, module.str, (!no_func || !no_file) ? ", " : "", (int)func.size, func.str, (int)file.size, file.str); - } - pclose(f); - } - else - { - fprintf(stderr, "%ld. [0x%016lx] %s\n", i+1, (unsigned long)ips[i], info.dli_fname); - } - } - fprintf(stderr, "\nVersion: %s%s\n\n", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); - - _exit(1); -} - -int -main(int argc, char **argv) -{ - // install signal handler for the crash call stacks - { - struct sigaction handler = { .sa_sigaction = lnx_signal_handler, .sa_flags = SA_SIGINFO, }; - sigfillset(&handler.sa_mask); - sigaction(SIGILL, &handler, NULL); - sigaction(SIGTRAP, &handler, NULL); - sigaction(SIGABRT, &handler, NULL); - sigaction(SIGFPE, &handler, NULL); - sigaction(SIGBUS, &handler, NULL); - sigaction(SIGSEGV, &handler, NULL); - sigaction(SIGQUIT, &handler, NULL); - } - - //- rjf: set up OS layer - { - //- rjf: get statically-allocated system/process info - { - OS_SystemInfo *info = &os_lnx_state.system_info; - info->logical_processor_count = (U32)get_nprocs(); - info->page_size = (U64)getpagesize(); - info->large_page_size = MB(2); - info->allocation_granularity = info->page_size; - } - { - OS_ProcessInfo *info = &os_lnx_state.process_info; - info->pid = (U32)getpid(); - } - - //- rjf: set up thread context - TCTX *tctx = tctx_alloc(); - tctx_select(tctx); - - //- rjf: set up dynamically allocated state - os_lnx_state.arena = arena_alloc(); - os_lnx_state.entity_arena = arena_alloc(); - pthread_mutex_init(&os_lnx_state.entity_mutex, 0); - - // cache default environment - { - U64 env_count = 0; - for(; __environ[env_count] != 0; env_count += 1) {} - char **default_env = push_array(os_lnx_state.arena, char *, env_count+1); - for EachIndex(idx, env_count) - { - default_env[idx] = (char *)str8_copy(os_lnx_state.arena, str8_cstring(__environ[idx])).str; - } - default_env[env_count] = 0; - os_lnx_state.default_env_count = env_count; - os_lnx_state.default_env = default_env; - } - - //- rjf: grab dynamically allocated system info - { - Temp scratch = scratch_begin(0, 0); - OS_SystemInfo *info = &os_lnx_state.system_info; - - // rjf: get machine name - B32 got_final_result = 0; - U8 *buffer = 0; - int size = 0; - for(S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1) - { - scratch_end(scratch); - buffer = push_array(scratch.arena, U8, cap); - int gethostname_result = gethostname((char*)buffer, cap); - size = cstring8_length(buffer); - if(gethostname_result == 0 && size < cap) - { - got_final_result = 1; - break; - } - } - - // rjf: save name to info - if(got_final_result && size > 0) - { - info->machine_name.size = size; - info->machine_name.str = push_array_no_zero(os_lnx_state.arena, U8, info->machine_name.size + 1); - MemoryCopy(info->machine_name.str, buffer, info->machine_name.size); - info->machine_name.str[info->machine_name.size] = 0; - } - - scratch_end(scratch); - } - - //- rjf: grab dynamically allocated process info - { - Temp scratch = scratch_begin(0, 0); - OS_ProcessInfo *info = &os_lnx_state.process_info; - - // rjf: grab binary path - { - // rjf: get self string - B32 got_final_result = 0; - U8 *buffer = 0; - int size = 0; - for(S64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1) - { - scratch_end(scratch); - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = readlink("/proc/self/exe", (char*)buffer, cap); - if(size < cap) - { - got_final_result = 1; - break; - } - } - - // rjf: save - if(got_final_result && size > 0) - { - String8 full_name = str8(buffer, size); - String8 name_chopped = str8_chop_last_slash(full_name); - info->binary_path = push_str8_copy(os_lnx_state.arena, name_chopped); - } - } - - // rjf: grab initial directory - { - info->initial_path = os_get_current_path(os_lnx_state.arena); - } - - // rjf: grab home directory - { - char *home = getenv("HOME"); - info->user_program_data_path = str8_cstring(home); - } - - scratch_end(scratch); - } - } - - //- rjf: call into "real" entry point - main_thread_base_entry_point(argc, argv); -} diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h deleted file mode 100644 index 1f3ab4a0..00000000 --- a/src/os/core/linux/os_core_linux.h +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_CORE_LINUX_H -#define OS_CORE_LINUX_H - -//////////////////////////////// -//~ rjf: Includes - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -pid_t gettid(void); -int pthread_setname_np(pthread_t thread, const char *name); -int pthread_getname_np(pthread_t thread, char *name, size_t size); - -typedef struct tm tm; -typedef struct timespec timespec; - -//////////////////////////////// - -#define OS_LNX_RETRY_ON_EINTR(expr) \ - (__extension__({ \ - __typeof__(expr) __ret; \ - do { \ - __ret = (expr); \ - } while ((__ret == -1) && errno == EINTR); \ - __ret; \ - })) - -//////////////////////////////// -//~ rjf: Register Layouts -// -// These are defined in , but only for one architecture at a time - -typedef struct OS_LNX_GprsX64 OS_LNX_GprsX64; -struct OS_LNX_GprsX64 -{ - U64 r15; - U64 r14; - U64 r13; - U64 r12; - U64 rbp; - U64 rbx; - U64 r11; - U64 r10; - U64 r9; - U64 r8; - U64 rax; - U64 rcx; - U64 rdx; - U64 rsi; - U64 rdi; - U64 orig_rax; - U64 rip; - U64 cs; - U64 rflags; - U64 rsp; - U64 ss; - U64 fsbase; - U64 gsbase; - U64 ds; - U64 es; - U64 fs; - U64 gs; -}; - -typedef struct OS_LNX_UserX64 OS_LNX_UserX64; -struct OS_LNX_UserX64 -{ - OS_LNX_GprsX64 regs; - S32 u_fpvalid; - U32 _pad0; - X64_FXSave i387; - U64 u_tsize; - U64 u_dsize; - U64 u_ssize; - U64 start_code; - U64 start_stack; - U64 signal; - U32 reserved; - U32 _pad1; - U64 u_ar0; - U64 u_fpstate; - U64 magic; - U8 u_comm[32]; - U64 u_debugreg[8]; -}; -StaticAssert(sizeof(OS_LNX_UserX64) == 912, g_os_lnx_user_x64_size_check); - -//////////////////////////////// -//~ rjf: File Iterator - -typedef struct OS_LNX_FileIter OS_LNX_FileIter; -struct OS_LNX_FileIter -{ - DIR *dir; - struct dirent *dp; - String8 path; -}; -StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(OS_LNX_FileIter), os_lnx_file_iter_size_check); - -//////////////////////////////// -//~ rjf: Safe Call Handler Chain - -typedef struct OS_LNX_SafeCallChain OS_LNX_SafeCallChain; -struct OS_LNX_SafeCallChain -{ - OS_LNX_SafeCallChain *next; - ThreadEntryPointFunctionType *fail_handler; - void *ptr; -}; - -//////////////////////////////// -//~ rjf: Entities - -typedef enum OS_LNX_EntityKind -{ - OS_LNX_EntityKind_Thread, - OS_LNX_EntityKind_Mutex, - OS_LNX_EntityKind_RWMutex, - OS_LNX_EntityKind_ConditionVariable, - OS_LNX_EntityKind_Barrier, -} -OS_LNX_EntityKind; - -typedef struct OS_LNX_Entity OS_LNX_Entity; -struct OS_LNX_Entity -{ - OS_LNX_Entity *next; - OS_LNX_EntityKind kind; - union - { - struct - { - pthread_t handle; - ThreadEntryPointFunctionType *func; - void *ptr; - } thread; - pthread_mutex_t mutex_handle; - pthread_rwlock_t rwmutex_handle; - struct - { - pthread_cond_t cond_handle; - pthread_mutex_t rwlock_mutex_handle; - } cv; - pthread_barrier_t barrier; - }; -}; - -//////////////////////////////// -//~ rjf: State - -typedef struct OS_LNX_State OS_LNX_State; -struct OS_LNX_State -{ - Arena *arena; - OS_SystemInfo system_info; - OS_ProcessInfo process_info; - pthread_mutex_t entity_mutex; - Arena *entity_arena; - OS_LNX_Entity *entity_free; - U64 default_env_count; - char **default_env; -}; - -//////////////////////////////// -//~ rjf: Globals - -global OS_LNX_State os_lnx_state = {0}; -thread_static OS_LNX_SafeCallChain *os_lnx_safe_call_chain = 0; - -//////////////////////////////// -//~ rjf: Helpers - -internal DateTime os_lnx_date_time_from_tm(tm in, U32 msec); -internal tm os_lnx_tm_from_date_time(DateTime dt); -internal timespec os_lnx_timespec_from_date_time(DateTime dt); -internal DenseTime os_lnx_dense_time_from_timespec(timespec in); -internal FileProperties os_lnx_file_properties_from_stat(struct stat *s); -internal void os_lnx_safe_call_sig_handler(int x); - -//////////////////////////////// -//~ rjf: Entities - -internal OS_LNX_Entity *os_lnx_entity_alloc(OS_LNX_EntityKind kind); -internal void os_lnx_entity_release(OS_LNX_Entity *entity); - -//////////////////////////////// -//~ rjf: Thread Entry Point - -internal void *os_lnx_thread_entry_point(void *ptr); - -#endif // OS_CORE_LINUX_H diff --git a/src/os/core/linux/os_core_linux_old.c b/src/os/core/linux/os_core_linux_old.c deleted file mode 100644 index 4d492f06..00000000 --- a/src/os/core/linux/os_core_linux_old.c +++ /dev/null @@ -1,1677 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#include - -//////////////////////////////// -//~ rjf: Globals - -global pthread_mutex_t lnx_mutex = {0}; -global Arena *lnx_perm_arena = 0; -global String8List lnx_cmd_line_args = {0}; -global LNX_Entity lnx_entity_buffer[1024]; -global LNX_Entity *lnx_entity_free = 0; -global String8 lnx_initial_path = {0}; -thread_static LNX_SafeCallChain *lnx_safe_call_chain = 0; - -//////////////////////////////// -//~ rjf: Helpers - -internal B32 -lnx_write_list_to_file_descriptor(int fd, String8List list){ - B32 success = true; - - String8Node *node = list.first; - if (node != 0){ - U8 *ptr = node->string.str;; - U8 *opl = ptr + node->string.size; - - U64 p = 0; - for (;p < list.total_size;){ - U64 amt64 = (U64)(opl - ptr); - U32 amt = u32_from_u64_saturate(amt64); - S64 written_amt = write(fd, ptr, amt); - if (written_amt < 0){ - break; - } - p += written_amt; - ptr += written_amt; - - Assert(ptr <= opl); - if (ptr == opl){ - node = node->next; - if (node == 0){ - if (p < list.total_size){ - success = false; - } - break; - } - ptr = node->string.str; - opl = ptr + node->string.size; - } - } - } - - return(success); -} - -internal void -lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec){ - out->msec = msec; - out->sec = in->tm_sec; - out->min = in->tm_min; - out->hour = in->tm_hour; - out->day = in->tm_mday - 1; - out->wday = in->tm_wday; - out->mon = in->tm_mon; - out->year = in->tm_year + 1900; -} - -internal void -lnx_tm_from_date_time(struct tm *out, DateTime *in){ - out->tm_sec = in->sec; - out->tm_min = in->min; - out->tm_hour = in->hour; - out->tm_mday = in->day + 1; - out->tm_mon = in->mon; - out->tm_year = in->year - 1900; -} - -internal void -lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in){ - struct tm tm_time = {0}; - gmtime_r(&in->tv_sec, &tm_time); - DateTime date_time = {0}; - lnx_date_time_from_tm(&date_time, &tm_time, in->tv_nsec/Million(1)); - *out = dense_time_from_date_time(date_time); -} - -internal void -lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ - MemoryZeroStruct(out); - out->size = in->st_size; - lnx_dense_time_from_timespec(&out->created, &in->st_ctim); - lnx_dense_time_from_timespec(&out->modified, &in->st_mtim); - if ((in->st_mode & S_IFDIR) != 0){ - out->flags |= FilePropertyFlag_IsFolder; - } -} - -internal String8 -lnx_string_from_signal(int signum){ - String8 result = str8_lit(""); - switch (signum){ - case SIGABRT: - { - result = str8_lit("SIGABRT"); - }break; - case SIGALRM: - { - result = str8_lit("SIGALRM"); - }break; - case SIGBUS: - { - result = str8_lit("SIGBUS"); - }break; - case SIGCHLD: - { - result = str8_lit("SIGCHLD"); - }break; - case SIGCONT: - { - result = str8_lit("SIGCONT"); - }break; - case SIGFPE: - { - result = str8_lit("SIGFPE"); - }break; - case SIGHUP: - { - result = str8_lit("SIGHUP"); - }break; - case SIGILL: - { - result = str8_lit("SIGILL"); - }break; - case SIGINT: - { - result = str8_lit("SIGINT"); - }break; - case SIGIO: - { - result = str8_lit("SIGIO"); - } - case SIGKILL: - { - result = str8_lit("SIGKILL"); - }break; - case SIGPIPE: - { - result = str8_lit("SIGPIPE"); - }break; - case SIGPROF: - { - result = str8_lit("SIGPROF"); - }break; - case SIGPWR: - { - result = str8_lit("SIGPWR"); - }break; - case SIGQUIT: - { - result = str8_lit("SIGQUIT"); - }break; - case SIGSEGV: - { - result = str8_lit("SIGSEGV"); - }break; - case SIGSTKFLT: - { - result = str8_lit("SIGSTKFLT"); - }break; - case SIGSTOP: - { - result = str8_lit("SIGSTOP"); - }break; - case SIGTSTP: - { - result = str8_lit("SIGTSTP"); - }break; - case SIGSYS: - { - result = str8_lit("SIGSYS"); - }break; - case SIGTERM: - { - result = str8_lit("SIGTERM"); - }break; - case SIGTRAP: - { - result = str8_lit("SIGTRAP"); - }break; - case SIGTTIN: - { - result = str8_lit("SIGTTIN"); - }break; - case SIGTTOU: - { - result = str8_lit("SIGTTOU"); - }break; - case SIGURG: - { - result = str8_lit("SIGURG"); - }break; - case SIGUSR1: - { - result = str8_lit("SIGUSR1"); - }break; - case SIGUSR2: - { - result = str8_lit("SIGUSR2"); - }break; - case SIGVTALRM: - { - result = str8_lit("SIGVTALRM"); - }break; - case SIGXCPU: - { - result = str8_lit("SIGXCPU"); - }break; - case SIGXFSZ: - { - result = str8_lit("SIGXFSZ"); - }break; - case SIGWINCH: - { - result = str8_lit("SIGWINCH"); - }break; - } - return(result); -} - -internal String8 -lnx_string_from_errno(int error_number){ - String8 result = str8_lit(""); - switch (error_number){ - case EPERM: - { - result = str8_lit("EPERM"); - }break; - case ENOENT: - { - result = str8_lit("ENOENT"); - }break; - case ESRCH: - { - result = str8_lit("ESRCH"); - }break; - case EINTR: - { - result = str8_lit("EINTR"); - }break; - case EIO: - { - result = str8_lit("EIO"); - }break; - case ENXIO: - { - result = str8_lit("ENXIO"); - }break; - case E2BIG: - { - result = str8_lit("E2BIG"); - }break; - case ENOEXEC: - { - result = str8_lit("ENOEXEC"); - }break; - case EBADF: - { - result = str8_lit("EBADF"); - }break; - case ECHILD: - { - result = str8_lit("ECHILD"); - }break; - case EAGAIN: - { - result = str8_lit("EAGAIN"); - }break; - case ENOMEM: - { - result = str8_lit("ENOMEM"); - }break; - case EACCES: - { - result = str8_lit("EACCES"); - }break; - case EFAULT: - { - result = str8_lit("EFAULT"); - }break; - case ENOTBLK: - { - result = str8_lit("ENOTBLK"); - }break; - case EBUSY: - { - result = str8_lit("EBUSY"); - }break; - case EEXIST: - { - result = str8_lit("EEXIST"); - }break; - case EXDEV: - { - result = str8_lit("EXDEV"); - }break; - case ENODEV: - { - result = str8_lit("ENODEV"); - }break; - case ENOTDIR: - { - result = str8_lit("ENOTDIR"); - }break; - case EISDIR: - { - result = str8_lit("EISDIR"); - }break; - case EINVAL: - { - result = str8_lit("EINVAL"); - }break; - case ENFILE: - { - result = str8_lit("ENFILE"); - }break; - case EMFILE: - { - result = str8_lit("EMFILE"); - }break; - case ENOTTY: - { - result = str8_lit("ENOTTY"); - }break; - case ETXTBSY: - { - result = str8_lit("ETXTBSY"); - }break; - case EFBIG: - { - result = str8_lit("EFBIG"); - }break; - case ENOSPC: - { - result = str8_lit("ENOSPC"); - }break; - case ESPIPE: - { - result = str8_lit("ESPIPE"); - }break; - case EROFS: - { - result = str8_lit("EROFS"); - }break; - case EMLINK: - { - result = str8_lit("EMLINK"); - }break; - case EPIPE: - { - result = str8_lit("EPIPE"); - }break; - case EDOM: - { - result = str8_lit("EDOM"); - }break; - case ERANGE: - { - result = str8_lit("ERANGE"); - }break; - case EDEADLK: - { - result = str8_lit("EDEADLK"); - }break; - case ENAMETOOLONG: - { - result = str8_lit("ENAMETOOLONG"); - }break; - case ENOLCK: - { - result = str8_lit("ENOLCK"); - }break; - case ENOSYS: - { - result = str8_lit("ENOSYS"); - }break; - case ENOTEMPTY: - { - result = str8_lit("ENOTEMPTY"); - }break; - case ELOOP: - { - result = str8_lit("ELOOP"); - }break; - case ENOMSG: - { - result = str8_lit("ENOMSG"); - }break; - case EIDRM: - { - result = str8_lit("EIDRM"); - }break; - case ECHRNG: - { - result = str8_lit("ECHRNG"); - }break; - case EL2NSYNC: - { - result = str8_lit("EL2NSYNC"); - }break; - case EL3HLT: - { - result = str8_lit("EL3HLT"); - }break; - case EL3RST: - { - result = str8_lit("EL3RST"); - }break; - case ELNRNG: - { - result = str8_lit("ELNRNG"); - }break; - case EUNATCH: - { - result = str8_lit("EUNATCH"); - }break; - case ENOCSI: - { - result = str8_lit("ENOCSI"); - }break; - case EL2HLT: - { - result = str8_lit("EL2HLT"); - }break; - case EBADE: - { - result = str8_lit("EBADE"); - }break; - case EBADR: - { - result = str8_lit("EBADR"); - }break; - case EXFULL: - { - result = str8_lit("EXFULL"); - }break; - case ENOANO: - { - result = str8_lit("ENOANO"); - }break; - case EBADRQC: - { - result = str8_lit("EBADRQC"); - }break; - case EBADSLT: - { - result = str8_lit("EBADSLT"); - }break; - case EBFONT: - { - result = str8_lit("EBFONT"); - }break; - case ENOSTR: - { - result = str8_lit("ENOSTR"); - }break; - case ENODATA: - { - result = str8_lit("ENODATA"); - }break; - case ETIME: - { - result = str8_lit("ETIME"); - }break; - case ENOSR: - { - result = str8_lit("ENOSR"); - }break; - case ENONET: - { - result = str8_lit("ENONET"); - }break; - case ENOPKG: - { - result = str8_lit("ENOPKG"); - }break; - case EREMOTE: - { - result = str8_lit("EREMOTE"); - }break; - case ENOLINK: - { - result = str8_lit("ENOLINK"); - }break; - case EADV: - { - result = str8_lit("EADV"); - }break; - case ESRMNT: - { - result = str8_lit("ESRMNT"); - }break; - case ECOMM: - { - result = str8_lit("ECOMM"); - }break; - case EPROTO: - { - result = str8_lit("EPROTO"); - }break; - case EMULTIHOP: - { - result = str8_lit("EMULTIHOP"); - }break; - case EDOTDOT: - { - result = str8_lit("EDOTDOT"); - }break; - case EBADMSG: - { - result = str8_lit("EBADMSG"); - }break; - case EOVERFLOW: - { - result = str8_lit("EOVERFLOW"); - }break; - case ENOTUNIQ: - { - result = str8_lit("ENOTUNIQ"); - }break; - case EBADFD: - { - result = str8_lit("EBADFD"); - }break; - case EREMCHG: - { - result = str8_lit("EREMCHG"); - }break; - case ELIBACC: - { - result = str8_lit("ELIBACC"); - }break; - case ELIBBAD: - { - result = str8_lit("ELIBBAD"); - }break; - case ELIBSCN: - { - result = str8_lit("ELIBSCN"); - }break; - case ELIBMAX: - { - result = str8_lit("ELIBMAX"); - }break; - case ELIBEXEC: - { - result = str8_lit("ELIBEXEC"); - }break; - case EILSEQ: - { - result = str8_lit("EILSEQ"); - }break; - case ERESTART: - { - result = str8_lit("ERESTART"); - }break; - case ESTRPIPE: - { - result = str8_lit("ESTRPIPE"); - }break; - case EUSERS: - { - result = str8_lit("EUSERS"); - }break; - case ENOTSOCK: - { - result = str8_lit("ENOTSOCK"); - }break; - case EDESTADDRREQ: - { - result = str8_lit("EDESTADDRREQ"); - }break; - case EMSGSIZE: - { - result = str8_lit("EMSGSIZE"); - }break; - case EPROTOTYPE: - { - result = str8_lit("EPROTOTYPE"); - }break; - case ENOPROTOOPT: - { - result = str8_lit("ENOPROTOOPT"); - }break; - case EPROTONOSUPPORT: - { - result = str8_lit("EPROTONOSUPPORT"); - }break; - case ESOCKTNOSUPPORT: - { - result = str8_lit("ESOCKTNOSUPPORT"); - }break; - case EOPNOTSUPP: - { - result = str8_lit("EOPNOTSUPP"); - }break; - case EPFNOSUPPORT: - { - result = str8_lit("EPFNOSUPPORT"); - }break; - case EAFNOSUPPORT: - { - result = str8_lit("EAFNOSUPPORT"); - }break; - case EADDRINUSE: - { - result = str8_lit("EADDRINUSE"); - }break; - case EADDRNOTAVAIL: - { - result = str8_lit("EADDRNOTAVAIL"); - }break; - case ENETDOWN: - { - result = str8_lit("ENETDOWN"); - }break; - case ENETUNREACH: - { - result = str8_lit("ENETUNREACH"); - }break; - case ENETRESET: - { - result = str8_lit("ENETRESET"); - }break; - case ECONNABORTED: - { - result = str8_lit("ECONNABORTED"); - }break; - case ECONNRESET: - { - result = str8_lit("ECONNRESET"); - }break; - case ENOBUFS: - { - result = str8_lit("ENOBUFS"); - }break; - case EISCONN: - { - result = str8_lit("EISCONN"); - }break; - case ENOTCONN: - { - result = str8_lit("ENOTCONN"); - }break; - case ESHUTDOWN: - { - result = str8_lit("ESHUTDOWN"); - }break; - case ETOOMANYREFS: - { - result = str8_lit("ETOOMANYREFS"); - }break; - case ETIMEDOUT: - { - result = str8_lit("ETIMEDOUT"); - }break; - case ECONNREFUSED: - { - result = str8_lit("ECONNREFUSED"); - }break; - case EHOSTDOWN: - { - result = str8_lit("EHOSTDOWN"); - }break; - case EHOSTUNREACH: - { - result = str8_lit("EHOSTUNREACH"); - }break; - case EALREADY: - { - result = str8_lit("EALREADY"); - }break; - case EINPROGRESS: - { - result = str8_lit("EINPROGRESS"); - }break; - case ESTALE: - { - result = str8_lit("ESTALE"); - }break; - case EUCLEAN: - { - result = str8_lit("EUCLEAN"); - }break; - case ENOTNAM: - { - result = str8_lit("ENOTNAM"); - }break; - case ENAVAIL: - { - result = str8_lit("ENAVAIL"); - }break; - case EISNAM: - { - result = str8_lit("EISNAM"); - }break; - case EREMOTEIO: - { - result = str8_lit("EREMOTEIO"); - }break; - case EDQUOT: - { - result = str8_lit("EDQUOT"); - }break; - case ENOMEDIUM: - { - result = str8_lit("ENOMEDIUM"); - }break; - case EMEDIUMTYPE: - { - result = str8_lit("EMEDIUMTYPE"); - }break; - case ECANCELED: - { - result = str8_lit("ECANCELED"); - }break; - case ENOKEY: - { - result = str8_lit("ENOKEY"); - }break; - case EKEYEXPIRED: - { - result = str8_lit("EKEYEXPIRED"); - }break; - case EKEYREVOKED: - { - result = str8_lit("EKEYREVOKED"); - }break; - case EKEYREJECTED: - { - result = str8_lit("EKEYREJECTED"); - }break; - case EOWNERDEAD: - { - result = str8_lit("EOWNERDEAD"); - }break; - case ENOTRECOVERABLE: - { - result = str8_lit("ENOTRECOVERABLE"); - }break; - case ERFKILL: - { - result = str8_lit("ERFKILL"); - }break; - case EHWPOISON: - { - result = str8_lit("EHWPOISON"); - }break; - } - return(result); -} - -internal LNX_Entity* -lnx_alloc_entity(LNX_EntityKind kind){ - pthread_mutex_lock(&lnx_mutex); - LNX_Entity *result = lnx_entity_free; - Assert(result != 0); - SLLStackPop(lnx_entity_free); - pthread_mutex_unlock(&lnx_mutex); - result->kind = kind; - return(result); -} - -internal void -lnx_free_entity(LNX_Entity *entity){ - entity->kind = LNX_EntityKind_Null; - pthread_mutex_lock(&lnx_mutex); - SLLStackPush(lnx_entity_free, entity); - pthread_mutex_unlock(&lnx_mutex); -} - -internal void* -lnx_thread_base(void *ptr){ - LNX_Entity *entity = (LNX_Entity*)ptr; - ThreadEntryPointFunctionType *func = entity->thread.func; - void *thread_ptr = entity->thread.ptr; - - TCTX tctx_; - tctx_init_and_equip(&tctx_); - func(thread_ptr); - tctx_release(); - - // remove my bit - U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x2); - // if the other bit is also gone, free entity - if ((result & 0x1) == 0){ - lnx_free_entity(entity); - } - return(0); -} - -internal void -lnx_safe_call_sig_handler(int){ - LNX_SafeCallChain *chain = lnx_safe_call_chain; - if (chain != 0 && chain->fail_handler != 0){ - chain->fail_handler(chain->ptr); - } - abort(); -} - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_init(void) -{ - // NOTE(allen): Initialize linux layer mutex - { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - int pthread_result = pthread_mutex_init(&lnx_mutex, &attr); - pthread_mutexattr_destroy(&attr); - if (pthread_result == -1){ - abort(); - } - } - MemoryZeroArray(lnx_entity_buffer); - { - LNX_Entity *ptr = lnx_entity_free = lnx_entity_buffer; - for (U64 i = 1; i < ArrayCount(lnx_entity_buffer); i += 1, ptr += 1){ - ptr->next = ptr + 1; - } - ptr->next = 0; - } - - // NOTE(allen): Permanent memory allocator for this layer - Arena *perm_arena = arena_alloc(); - lnx_perm_arena = perm_arena; - - // NOTE(allen): Initialize Paths - lnx_initial_path = os_get_path(lnx_perm_arena, OS_SystemPath_Current); - - // NOTE(rjf): Setup command line args - lnx_cmd_line_args = os_string_list_from_argcv(lnx_perm_arena, argc, argv); -} - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -internal void* -os_reserve(U64 size){ - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - return(result); -} - -internal B32 -os_commit(void *ptr, U64 size){ - mprotect(ptr, size, PROT_READ|PROT_WRITE); - // TODO(allen): can we test this? - return(true); -} - -internal void* -os_reserve_large(U64 size){ - NotImplemented; - return 0; -} - -internal B32 -os_commit_large(void *ptr, U64 size){ - NotImplemented; - return 0; -} - -internal void -os_decommit(void *ptr, U64 size){ - madvise(ptr, size, MADV_DONTNEED); - mprotect(ptr, size, PROT_NONE); -} - -internal void -os_release(void *ptr, U64 size){ - munmap(ptr, size); -} - -internal void -os_set_large_pages_enabled(B32 flag) -{ - NotImplemented; -} - -internal B32 -os_large_pages_enabled(void) -{ - NotImplemented; - return 0; -} - -internal U64 -os_large_page_size(void) -{ - NotImplemented; - return 0; -} - -//////////////////////////////// -//~ rjf: @os_hooks System Info (Implemented Per-OS) - -internal String8 -os_machine_name(void){ - local_persist B32 first = true; - local_persist String8 name = {0}; - - // TODO(allen): let's just pre-compute this at init and skip the complexity - pthread_mutex_lock(&lnx_mutex); - if (first){ - Temp scratch = scratch_begin(0, 0); - first = false; - - // get name - B32 got_final_result = false; - U8 *buffer = 0; - int size = 0; - for (S64 cap = 4096, r = 0; - r < 4; - cap *= 2, r += 1){ - scratch.restore(); - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = gethostname((char*)buffer, cap); - if (size < cap){ - got_final_result = true; - break; - } - } - - // save string - if (got_final_result && size > 0){ - name.size = size; - name.str = push_array_no_zero(lnx_perm_arena, U8, name.size + 1); - MemoryCopy(name.str, buffer, name.size); - name.str[name.size] = 0; - } - - scratch_end(scratch); - } - pthread_mutex_unlock(&lnx_mutex); - - return(name); -} - -internal U64 -os_page_size(void){ - int size = getpagesize(); - return((U64)size); -} - -internal U64 -os_allocation_granularity(void) -{ - // On linux there is no equivalent of "dwAllocationGranularity" - os_page_size(); -} - -internal U64 -os_logical_core_count(void) -{ - // TODO(rjf): check this - return get_nprocs(); -} - -//////////////////////////////// -//~ rjf: @os_hooks Process & Thread Info (Implemented Per-OS) - -internal String8List -os_get_command_line_arguments(void) -{ - return lnx_cmd_line_args; -} - -internal S32 -os_pid(void){ - S32 result = getpid(); - return(result); -} - -internal S32 -os_tid(void){ - S32 result = 0; -#ifdef SYS_gettid - result = syscall(SYS_gettid); -#else - result = gettid(); -#endif - return(result); -} - -internal String8List -os_environment(void) -{ - NotImplemented; - String8List result = {0}; - return result; -} - -internal U64 -os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *out){ - U64 result = 0; - - switch (path){ - case OS_SystemPath_Binary: - { - local_persist B32 first = true; - local_persist String8 name = {0}; - - // TODO(allen): let's just pre-compute this at init and skip the complexity - pthread_mutex_lock(&lnx_mutex); - if (first){ - Temp scratch = scratch_begin(&arena, 1); - first = false; - - // get self string - B32 got_final_result = false; - U8 *buffer = 0; - int size = 0; - for (S64 cap = PATH_MAX, r = 0; - r < 4; - cap *= 2, r += 1){ - scratch.restore(); - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = readlink("/proc/self/exe", (char*)buffer, cap); - if (size < cap){ - got_final_result = true; - break; - } - } - - // save string - if (got_final_result && size > 0){ - String8 full_name = str8(buffer, size); - String8 name_chopped = string_path_chop_last_slash(full_name); - name = push_str8_copy(lnx_perm_arena, name_chopped); - } - - scratch_end(scratch); - } - pthread_mutex_unlock(&lnx_mutex); - - result = 1; - str8_list_push(arena, out, name); - }break; - - case OS_SystemPath_Initial: - { - Assert(lnx_initial_path.str != 0); - result = 1; - str8_list_push(arena, out, lnx_initial_path); - }break; - - case OS_SystemPath_Current: - { - char *cwdir = getcwd(0, 0); - String8 string = push_str8_copy(arena, str8_cstring(cwdir)); - free(cwdir); - result = 1; - str8_list_push(arena, out, string); - }break; - - case OS_SystemPath_UserProgramData: - { - char *home = getenv("HOME"); - String8 string = str8_cstring(home); - result = 1; - str8_list_push(arena, out, string); - }break; - - case OS_SystemPath_ModuleLoad: - { - // TODO(allen): this one is big and complicated and only needed for making - // a debugger, skipping for now. - NotImplemented; - }break; - } - - return(result); -} - -//////////////////////////////// -//~ rjf: @os_hooks Process Control (Implemented Per-OS) - -internal void -os_abort(S32 exit_code){ - exit(exit_code); -} - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files - -internal OS_Handle -os_file_open(OS_AccessFlags flags, String8 path) -{ - OS_Handle file = {0}; - NotImplemented; - return file; -} - -internal void -os_file_close(OS_Handle file) -{ - NotImplemented; -} - -internal U64 -os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) -{ - NotImplemented; - return 0; -} - -internal U64 -os_file_write(OS_Handle file, Rng1U64 rng, void *data) -{ - NotImplemented; -} - -internal B32 -os_file_set_times(OS_Handle file, DateTime time) -{ - NotImplemented; -} - -internal FileProperties -os_properties_from_file(OS_Handle file) -{ - FileProperties props = {0}; - NotImplemented; - return props; -} - -internal OS_FileID -os_id_from_file(OS_Handle file) -{ - // TODO(nick): querry struct stat with fstat(2) and use st_dev and st_ino as ids - OS_FileID id = {0}; - NotImplemented; - return id; -} - -internal B32 -os_delete_file_at_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 result = false; - String8 name_copy = push_str8_copy(scratch.arena, name); - if (remove((char*)name_copy.str) != -1){ - result = true; - } - scratch_end(scratch); - return(result); -} - -internal B32 -os_copy_file_path(String8 dst, String8 src) -{ - NotImplemented; - return 0; -} - -internal String8 -os_full_path_from_path(Arena *arena, String8 path) -{ - // TODO: realpath can be used to resolve full path - String8 result = {0}; - NotImplemented; - return result; -} - -internal B32 -os_file_path_exists(String8 path) -{ - NotImplemented; - return 0; -} - -internal FileProperties -os_properties_from_file_path(String8 path) -{ - FileProperties props = {0}; - NotImplemented; - return props; -} - -//- rjf: file maps - -internal OS_Handle -os_file_map_open(OS_AccessFlags flags, OS_Handle file) -{ - NotImplemented; - OS_Handle handle = {0}; - return handle; -} - -internal void -os_file_map_close(OS_Handle map) -{ - NotImplemented; -} - -internal void * -os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) -{ - NotImplemented; - return 0; -} - -internal void -os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) -{ - NotImplemented; -} - -//- rjf: directory iteration - -internal OS_FileIter * -os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) -{ - NotImplemented; - return 0; -} - -internal B32 -os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) -{ - NotImplemented; - return 0; -} - -internal void -os_file_iter_end(OS_FileIter *iter) -{ - NotImplemented; -} - -//- rjf: directory creation - -internal B32 -os_make_directory(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 result = false; - String8 name_copy = push_str8_copy(scratch.arena, name); - if (mkdir((char*)name_copy.str, 0777) != -1){ - result = true; - } - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle -os_shared_memory_alloc(U64 size, String8 name) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal OS_Handle -os_shared_memory_open(String8 name) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal void -os_shared_memory_close(OS_Handle handle) -{ - NotImplemented; -} - -internal void * -os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) -{ - NotImplemented; - return 0; -} - -internal void -os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) -{ - NotImplemented; -} - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal U32 -os_now_unix(void) -{ - time_t t = time(0); - return (U32)t; -} - -internal DateTime -os_now_universal_time(void){ - time_t t = 0; - time(&t); - struct tm universal_tm = {0}; - gmtime_r(&t, &universal_tm); - DateTime result = {0}; - lnx_date_time_from_tm(&result, &universal_tm, 0); - return(result); -} - -internal DateTime -os_universal_time_from_local(DateTime *local_time){ - // local time -> universal time (using whatever types it takes) - struct tm local_tm = {0}; - lnx_tm_from_date_time(&local_tm, local_time); - local_tm.tm_isdst = -1; - time_t universal_t = mktime(&local_tm); - - // whatever type we ended up with -> DateTime (don't alter the space along the way) - struct tm universal_tm = {0}; - gmtime_r(&universal_t, &universal_tm); - DateTime result = {0}; - lnx_date_time_from_tm(&result, &universal_tm, 0); - return(result); -} - -internal DateTime -os_local_time_from_universal(DateTime *universal_time){ - // universal time -> local time (using whatever types it takes) - struct tm universal_tm = {0}; - lnx_tm_from_date_time(&universal_tm, universal_time); - universal_tm.tm_isdst = -1; - time_t universal_t = timegm(&universal_tm); - struct tm local_tm = {0}; - localtime_r(&universal_t, &local_tm); - - // whatever type we ended up with -> DateTime (don't alter the space along the way) - DateTime result = {0}; - lnx_date_time_from_tm(&result, &local_tm, 0); - return(result); -} - -internal U64 -os_now_microseconds(void){ - struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); - U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); - return(result); -} - -internal void -os_sleep_milliseconds(U32 msec){ - usleep(msec*Thousand(1)); -} - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal B32 -os_launch_process(OS_LaunchOptions *options){ - // TODO(allen): I want to redo this API before I bother implementing it here - NotImplemented; - return(false); -} - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal OS_Handle -os_thread_launch(ThreadEntryPointFunctionType *func, void *ptr, void *params){ - // entity - LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Thread); - entity->reference_mask = 0x3; - entity->thread.func = func; - entity->thread.ptr = ptr; - - // pthread - pthread_attr_t attr; - pthread_attr_init(&attr); - int pthread_result = pthread_create(&entity->thread.handle, &attr, lnx_thread_base, entity); - pthread_attr_destroy(&attr); - if (pthread_result == -1){ - lnx_free_entity(entity); - entity = 0; - } - - // cast to opaque handle - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_release_thread_handle(OS_Handle thread){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(thread.id); - // remove my bit - U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x1); - // if the other bit is also gone, free entity - if ((result & 0x2) == 0){ - lnx_free_entity(entity); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -// NOTE(allen): Mutexes are recursive - support counted acquire/release nesting -// on a single thread - -//- rjf: recursive mutexes - -internal OS_Handle -os_mutex_alloc(void){ - // entity - LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Mutex); - - // pthread - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - int pthread_result = pthread_mutex_init(&entity->mutex, &attr); - pthread_mutexattr_destroy(&attr); - if (pthread_result == -1){ - lnx_free_entity(entity); - entity = 0; - } - - // cast to opaque handle - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_mutex_release(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); - pthread_mutex_destroy(&entity->mutex); - lnx_free_entity(entity); -} - -internal void -os_mutex_take_(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); - pthread_mutex_lock(&entity->mutex); -} - -internal void -os_mutex_drop_(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); - pthread_mutex_unlock(&entity->mutex); -} - -//- rjf: reader/writer mutexes - -internal OS_Handle -os_rw_mutex_alloc(void) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal void -os_rw_mutex_release(OS_Handle rw_mutex) -{ - NotImplemented; -} - -internal void -os_rw_mutex_take_r_(OS_Handle mutex) -{ - NotImplemented; -} - -internal void -os_rw_mutex_drop_r_(OS_Handle mutex) -{ - NotImplemented; -} - -internal void -os_rw_mutex_take_w_(OS_Handle mutex) -{ - NotImplemented; -} - -internal void -os_rw_mutex_drop_w_(OS_Handle mutex) -{ - NotImplemented; -} - -//- rjf: condition variables - -internal OS_Handle -os_cond_var_alloc(void){ - // entity - LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_ConditionVariable); - - // pthread - pthread_condattr_t attr; - pthread_condattr_init(&attr); - int pthread_result = pthread_cond_init(&entity->cond, &attr); - pthread_condattr_destroy(&attr); - if (pthread_result == -1){ - lnx_free_entity(entity); - entity = 0; - } - - // cast to opaque handle - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_cond_var_release(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - pthread_cond_destroy(&entity->cond); - lnx_free_entity(entity); -} - -internal B32 -os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ - B32 result = false; - LNX_Entity *entity_cond = (LNX_Entity*)PtrFromInt(cv.id); - LNX_Entity *entity_mutex = (LNX_Entity*)PtrFromInt(mutex.id); - // TODO(allen): implement the time control - pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex); - return(result); -} - -internal B32 -os_condition_variable_wait_rw_r_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) -{ - NotImplemented; - return 0; -} - -internal B32 -os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) -{ - NotImplemented; - return 0; -} - -internal void -os_condition_variable_signal_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - pthread_cond_signal(&entity->cond); -} - -internal void -os_condition_variable_broadcast_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - DontCompile; -} - -//- rjf: cross-process semaphores - -internal OS_Handle -os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal void -os_semaphore_release(OS_Handle semaphore) -{ - NotImplemented; -} - -internal OS_Handle -os_semaphore_open(String8 name) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal void -os_semaphore_close(OS_Handle semaphore) -{ - NotImplemented; -} - -internal B32 -os_semaphore_take(OS_Handle semaphore, U64 endt_us) -{ - NotImplemented; - return 0; -} - -internal void -os_semaphore_drop(OS_Handle semaphore) -{ - NotImplemented; -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle -os_library_open(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - char *path_cstr = (char *)push_str8_copy(scratch.arena, path).str; - void *so = dlopen(path_cstr, RTLD_LAZY); - OS_Handle lib = { (U64)so }; - scratch_end(scratch); - return lib; -} - -internal VoidProc * -os_library_load_proc(OS_Handle lib, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - void *so = (void *)lib.id; - char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; - VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); - scratch_end(scratch); - return proc; -} - -internal void -os_library_close(OS_Handle lib) -{ - void *so = (void *)lib.id; - dlclose(so); -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal void -os_safe_call(ThreadEntryPointFunctionType *func, ThreadEntryPointFunctionType *fail_handler, void *ptr){ - LNX_SafeCallChain chain = {0}; - SLLStackPush(lnx_safe_call_chain, &chain); - chain.fail_handler = fail_handler; - chain.ptr = ptr; - - struct sigaction new_act = {0}; - new_act.sa_handler = lnx_safe_call_sig_handler; - - int signals_to_handle[] = { - SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, - }; - struct sigaction og_act[ArrayCount(signals_to_handle)] = {0}; - - for (U32 i = 0; i < ArrayCount(signals_to_handle); i += 1){ - sigaction(signals_to_handle[i], &new_act, &og_act[i]); - } - - func(ptr); - - for (U32 i = 0; i < ArrayCount(signals_to_handle); i += 1){ - sigaction(signals_to_handle[i], &og_act[i], 0); - } -} - -//////////////////////////////// - -internal OS_Guid -os_make_guid(void) -{ - NotImplemented; -} - diff --git a/src/os/core/linux/os_core_linux_old.h b/src/os/core/linux/os_core_linux_old.h deleted file mode 100644 index cf133b2a..00000000 --- a/src/os/core/linux/os_core_linux_old.h +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef LINUX_H -#define LINUX_H - -//////////////////////////////// -//~ NOTE(allen): Get all these linux includes - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//////////////////////////////// -//~ NOTE(allen): File Iterator - -struct LNX_FileIter{ - int fd; - DIR *dir; -}; -StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(LNX_FileIter), file_iter_memory_size); - -//////////////////////////////// -//~ NOTE(allen): Threading Entities - -enum LNX_EntityKind{ - LNX_EntityKind_Null, - LNX_EntityKind_Thread, - LNX_EntityKind_Mutex, - LNX_EntityKind_ConditionVariable, -}; - -struct LNX_Entity{ - LNX_Entity *next; - LNX_EntityKind kind; - volatile U32 reference_mask; - union{ - struct{ - ThreadEntryPointFunctionType *func; - void *ptr; - pthread_t handle; - } thread; - pthread_mutex_t mutex; - pthread_cond_t cond; - }; -}; - -//////////////////////////////// -//~ NOTE(allen): Safe Call Chain - -struct LNX_SafeCallChain{ - LNX_SafeCallChain *next; - ThreadEntryPointFunctionType *fail_handler; - void *ptr; -}; - -//////////////////////////////// -//~ NOTE(allen): Helpers - -internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list); - -internal void lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec); -internal void lnx_tm_from_date_time(struct tm *out, DateTime *in); -internal void lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in); -internal void lnx_file_properties_from_stat(FileProperties *out, struct stat *in); - -internal String8 lnx_string_from_signal(int signum); -internal String8 lnx_string_from_errno(int error_number); - -internal LNX_Entity* lnx_alloc_entity(LNX_EntityKind kind); -internal void lnx_free_entity(LNX_Entity *entity); -internal void* lnx_thread_base(void *ptr); - -internal void lnx_safe_call_sig_handler(int); - -#endif //LINUX_H diff --git a/src/os/core/os_core.c b/src/os/core/os_core.c deleted file mode 100644 index 670f8486..00000000 --- a/src/os/core/os_core.c +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Handle Type Functions (Helpers, Implemented Once) - -internal OS_Handle -os_handle_zero(void) -{ - OS_Handle handle = {0}; - return handle; -} - -internal B32 -os_handle_match(OS_Handle a, OS_Handle b) -{ - return a.u64[0] == b.u64[0]; -} - -internal void -os_handle_list_push(Arena *arena, OS_HandleList *handles, OS_Handle handle) -{ - OS_HandleNode *n = push_array(arena, OS_HandleNode, 1); - n->v = handle; - SLLQueuePush(handles->first, handles->last, n); - handles->count += 1; -} - -internal OS_HandleArray -os_handle_array_from_list(Arena *arena, OS_HandleList *list) -{ - OS_HandleArray result = {0}; - result.count = list->count; - result.v = push_array_no_zero(arena, OS_Handle, result.count); - U64 idx = 0; - for(OS_HandleNode *n = list->first; n != 0; n = n->next, idx += 1) - { - result.v[idx] = n->v; - } - return result; -} - -//////////////////////////////// -//~ rjf: Filesystem Helpers (Helpers, Implemented Once) - -internal String8 -os_data_from_file_path(Arena *arena, String8 path) -{ - OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, path); - FileProperties props = os_properties_from_file(file); - String8 data = os_string_from_file_range(arena, file, r1u64(0, props.size)); - os_file_close(file); - return data; -} - -internal B32 -os_write_data_to_file_path(String8 path, String8 data) -{ - B32 good = 0; - OS_Handle file = os_file_open(OS_AccessFlag_Write, path); - if(!os_handle_match(file, os_handle_zero())) - { - U64 bytes_written = os_file_write(file, r1u64(0, data.size), data.str); - good = (bytes_written == data.size); - os_file_close(file); - } - return good; -} - -internal B32 -os_write_data_list_to_file_path(String8 path, String8List list) -{ - B32 good = 0; - OS_Handle file = os_file_open(OS_AccessFlag_Write, path); - if(!os_handle_match(file, os_handle_zero())) - { - Temp scratch = scratch_begin(0, 0); - U64 write_buffer_size = KB(64); - U8 *write_buffer = push_array_no_zero(scratch.arena, U8, write_buffer_size); - U64 write_buffer_write_pos = 0; - U64 write_buffer_read_pos = 0; - U64 file_off = 0; - { - for(String8Node *n = list.first; n != 0; n = n->next) - { - for(U64 n_off = 0; n_off < n->string.size;) - { - U64 write_buffer_unconsumed_size = (write_buffer_write_pos - write_buffer_read_pos); - U64 write_buffer_available_size = (write_buffer_size - write_buffer_unconsumed_size); - if(write_buffer_available_size == 0) - { - U64 file_write_size = os_file_write(file, r1u64(file_off, file_off+write_buffer_size), write_buffer); - if(file_write_size != write_buffer_size) - { - goto dbl_break; - } - file_off += write_buffer_size; - write_buffer_read_pos += write_buffer_size; - } - else - { - U64 bytes_to_copy = Min(write_buffer_available_size, n->string.size - n_off); - write_buffer_write_pos += ring_write(write_buffer, write_buffer_size, write_buffer_write_pos, n->string.str + n_off, bytes_to_copy); - n_off += bytes_to_copy; - } - } - } - if(write_buffer_write_pos > write_buffer_read_pos) - { - U64 file_write_size = os_file_write(file, r1u64(file_off, file_off + (write_buffer_write_pos-write_buffer_read_pos)), write_buffer); - file_off += file_write_size; - } - } - dbl_break:; - good = (file_off == list.total_size); - os_file_close(file); - scratch_end(scratch); - } - return good; -} - -internal B32 -os_append_data_to_file_path(String8 path, String8 data) -{ - B32 good = 0; - if(data.size != 0) - { - OS_Handle file = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append, path); - if(!os_handle_match(file, os_handle_zero())) - { - U64 pos = os_properties_from_file(file).size; - U64 bytes_written = os_file_write(file, r1u64(pos, pos+data.size), data.str); - good = (bytes_written == data.size); - os_file_close(file); - } - } - return good; -} - -internal OS_FileID -os_id_from_file_path(String8 path) -{ - OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, path); - OS_FileID id = os_id_from_file(file); - os_file_close(file); - return id; -} - -internal S64 -os_file_id_compare(OS_FileID a, OS_FileID b) -{ - S64 cmp = MemoryCompare((void*)&a.v[0], (void*)&b.v[0], sizeof(a.v)); - return cmp; -} - -internal String8 -os_string_from_file_range(Arena *arena, OS_Handle file, Rng1U64 range) -{ - U64 pre_pos = arena_pos(arena); - String8 result; - result.size = dim_1u64(range); - result.str = push_array_no_zero(arena, U8, result.size); - U64 actual_read_size = os_file_read(file, range, result.str); - if(actual_read_size < result.size) - { - arena_pop_to(arena, pre_pos + actual_read_size); - result.size = actual_read_size; - } - return result; -} - -internal String8 -os_file_read_cstring(Arena *arena, OS_Handle file, U64 off) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List block_list = {0}; - for(U64 cursor = off, stride = 256;; cursor += stride) - { - U8 *raw_block = push_array_no_zero(scratch.arena, U8, stride); - U64 read_size = os_file_read(file, r1u64(cursor, cursor + stride), raw_block); - String8 block = str8_cstring_capped(raw_block, raw_block+read_size); - str8_list_push(scratch.arena, &block_list, block); - if(read_size != stride || (block.size+1 <= read_size && block.str[block.size] == 0)) - { - break; - } - } - String8 result = str8_list_join(arena, &block_list, 0); - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Process Launcher Helpers - -internal OS_Handle -os_cmd_line_launch(String8 string) -{ - Temp scratch = scratch_begin(0, 0); - U8 split_chars[] = {' '}; - String8List parts = str8_split(scratch.arena, string, split_chars, ArrayCount(split_chars), 0); - OS_Handle handle = {0}; - if(parts.node_count != 0) - { - // rjf: unpack exe part - String8 exe = parts.first->string; - String8 exe_folder = str8_chop_last_slash(exe); - if(exe_folder.size == 0) - { - exe_folder = os_get_current_path(scratch.arena); - } - - // rjf: find stdout delimiter - String8Node *stdout_delimiter_n = 0; - for(String8Node *n = parts.first; n != 0; n = n->next) - { - if(str8_match(n->string, str8_lit(">"), 0)) - { - stdout_delimiter_n = n; - break; - } - } - - // rjf: read stdout path - String8 stdout_path = {0}; - if(stdout_delimiter_n && stdout_delimiter_n->next) - { - stdout_path = stdout_delimiter_n->next->string; - } - - // rjf: open stdout handle - OS_Handle stdout_handle = {0}; - if(stdout_path.size != 0) - { - OS_Handle file = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Read, stdout_path); - os_file_close(file); - stdout_handle = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, stdout_path); - } - - // rjf: form command line - String8List cmdline = {0}; - for(String8Node *n = parts.first; n != stdout_delimiter_n && n != 0; n = n->next) - { - str8_list_push(scratch.arena, &cmdline, n->string); - } - - // rjf: launch - OS_ProcessLaunchParams params = {0}; - params.cmd_line = cmdline; - params.path = exe_folder; - params.inherit_env = 1; - params.stdout_file = stdout_handle; - handle = os_process_launch(¶ms); - - // rjf: close stdout handle - { - if(stdout_path.size != 0) - { - os_file_close(stdout_handle); - } - } - } - scratch_end(scratch); - return handle; -} - -internal OS_Handle -os_cmd_line_launchf(char *fmt, ...) -{ - Temp scratch = scratch_begin(0, 0); - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(scratch.arena, fmt, args); - OS_Handle result = os_cmd_line_launch(string); - va_end(args); - scratch_end(scratch); - return result; -} - diff --git a/src/os/core/os_core.h b/src/os/core/os_core.h deleted file mode 100644 index 2731228f..00000000 --- a/src/os/core/os_core.h +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_CORE_H -#define OS_CORE_H - -//////////////////////////////// -//~ rjf: System Info - -typedef struct OS_SystemInfo OS_SystemInfo; -struct OS_SystemInfo -{ - U32 logical_processor_count; - U64 page_size; - U64 large_page_size; - U64 allocation_granularity; - String8 machine_name; -}; - -//////////////////////////////// -//~ rjf: Process Info - -typedef struct OS_ProcessInfo OS_ProcessInfo; -struct OS_ProcessInfo -{ - U32 pid; - B32 large_pages_allowed; - String8 binary_path; - String8 initial_path; - String8 user_program_data_path; - String8List module_load_paths; - String8List environment; -}; - -//////////////////////////////// -//~ rjf: Access Flags - -typedef U32 OS_AccessFlags; -enum -{ - OS_AccessFlag_Read = (1<<0), - OS_AccessFlag_Write = (1<<1), - OS_AccessFlag_Execute = (1<<2), - OS_AccessFlag_Append = (1<<3), - OS_AccessFlag_ShareRead = (1<<4), - OS_AccessFlag_ShareWrite = (1<<5), - OS_AccessFlag_Inherited = (1<<6), -}; - -//////////////////////////////// -//~ rjf: Files - -typedef U32 OS_FileIterFlags; -enum -{ - OS_FileIterFlag_SkipFolders = (1 << 0), - OS_FileIterFlag_SkipFiles = (1 << 1), - OS_FileIterFlag_SkipHiddenFiles = (1 << 2), - OS_FileIterFlag_Done = (1 << 31), -}; - -typedef struct OS_FileIter OS_FileIter; -struct OS_FileIter -{ - OS_FileIterFlags flags; - U8 memory[800]; -}; - -typedef struct OS_FileInfo OS_FileInfo; -struct OS_FileInfo -{ - String8 name; - FileProperties props; -}; - -// nick: on-disk file identifier -typedef struct OS_FileID OS_FileID; -struct OS_FileID -{ - U64 v[3]; -}; - -//////////////////////////////// -//~ rjf: Handle Type - -typedef struct OS_Handle OS_Handle; -struct OS_Handle -{ - U64 u64[1]; -}; - -typedef struct OS_HandleNode OS_HandleNode; -struct OS_HandleNode -{ - OS_HandleNode *next; - OS_Handle v; -}; - -typedef struct OS_HandleList OS_HandleList; -struct OS_HandleList -{ - OS_HandleNode *first; - OS_HandleNode *last; - U64 count; -}; - -typedef struct OS_HandleArray OS_HandleArray; -struct OS_HandleArray -{ - OS_Handle *v; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Process Launch Parameters - -typedef struct OS_ProcessLaunchParams OS_ProcessLaunchParams; -struct OS_ProcessLaunchParams -{ - String8List cmd_line; - String8 path; - String8List env; - B32 inherit_env; - B32 debug_subprocesses; - B32 consoleless; - OS_Handle stdout_file; - OS_Handle stderr_file; - OS_Handle stdin_file; -}; - -//////////////////////////////// -//~ rjf: Handle Type Functions (Helpers, Implemented Once) - -internal OS_Handle os_handle_zero(void); -internal B32 os_handle_match(OS_Handle a, OS_Handle b); -internal void os_handle_list_push(Arena *arena, OS_HandleList *handles, OS_Handle handle); -internal OS_HandleArray os_handle_array_from_list(Arena *arena, OS_HandleList *list); - -//////////////////////////////// -//~ rjf: Filesystem Helpers (Helpers, Implemented Once) - -internal String8 os_data_from_file_path(Arena *arena, String8 path); -internal B32 os_write_data_to_file_path(String8 path, String8 data); -internal B32 os_write_data_list_to_file_path(String8 path, String8List list); -internal B32 os_append_data_to_file_path(String8 path, String8 data); -internal OS_FileID os_id_from_file_path(String8 path); -internal S64 os_file_id_compare(OS_FileID a, OS_FileID b); -internal String8 os_string_from_file_range(Arena *arena, OS_Handle file, Rng1U64 range); -internal String8 os_file_read_cstring(Arena *arena, OS_Handle file, U64 off); - -//////////////////////////////// -//~ rjf: Process Launcher Helpers - -internal OS_Handle os_cmd_line_launch(String8 string); -internal OS_Handle os_cmd_line_launchf(char *fmt, ...); - -//////////////////////////////// -//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) - -internal OS_SystemInfo *os_get_system_info(void); -internal OS_ProcessInfo *os_get_process_info(void); -internal String8 os_get_current_path(Arena *arena); -internal U32 os_get_process_start_time_unix(void); - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -//- rjf: basic -internal void *os_reserve(U64 size); -internal B32 os_commit(void *ptr, U64 size); -internal void os_decommit(void *ptr, U64 size); -internal void os_release(void *ptr, U64 size); - -//- rjf: large pages -internal void *os_reserve_large(U64 size); -internal B32 os_commit_large(void *ptr, U64 size); - -//////////////////////////////// -//~ rjf: @os_hooks Thread Info (Implemented Per-OS) - -internal U32 os_tid(void); -internal void os_set_thread_name(String8 string); - -//////////////////////////////// -//~ rjf: @os_hooks Aborting (Implemented Per-OS) - -internal void os_abort(S32 exit_code); - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files -internal OS_Handle os_file_open(OS_AccessFlags flags, String8 path); -internal void os_file_close(OS_Handle file); -internal U64 os_file_read(OS_Handle file, Rng1U64 rng, void *out_data); -#define os_file_read_struct(f, off, ptr) os_file_read((f), r1u64((off), (off)+sizeof(*(ptr))), (ptr)) -internal U64 os_file_write(OS_Handle file, Rng1U64 rng, void *data); -internal B32 os_file_set_times(OS_Handle file, DateTime time); -internal FileProperties os_properties_from_file(OS_Handle file); -internal OS_FileID os_id_from_file(OS_Handle file); -internal B32 os_file_reserve_size(OS_Handle file, U64 size); -internal B32 os_delete_file_at_path(String8 path); -internal B32 os_copy_file_path(String8 dst, String8 src); -internal B32 os_move_file_path(String8 dst, String8 src); -internal String8 os_full_path_from_path(Arena *arena, String8 path); -internal B32 os_file_path_exists(String8 path); -internal B32 os_folder_path_exists(String8 path); -internal FileProperties os_properties_from_file_path(String8 path); - -//- rjf: file maps -internal OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file); -internal void os_file_map_close(OS_Handle map); -internal void * os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range); -internal void os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range); - -//- rjf: directory iteration -internal OS_FileIter *os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags); -internal B32 os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out); -internal void os_file_iter_end(OS_FileIter *iter); - -//- rjf: directory creation -internal B32 os_make_directory(String8 path); - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle os_shared_memory_alloc(U64 size, String8 name); -internal OS_Handle os_shared_memory_open(String8 name); -internal void os_shared_memory_close(OS_Handle handle); -internal void * os_shared_memory_view_open(OS_Handle handle, Rng1U64 range); -internal void os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range); - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal U64 os_now_microseconds(void); -internal U32 os_now_unix(void); -internal DateTime os_now_universal_time(void); -internal DateTime os_universal_time_from_local(DateTime *local_time); -internal DateTime os_local_time_from_universal(DateTime *universal_time); -internal void os_sleep_milliseconds(U32 msec); - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal OS_Handle os_process_launch(OS_ProcessLaunchParams *params); -internal B32 os_process_join(OS_Handle handle, U64 endt_us, U64 *exit_code_out); -internal void os_process_detach(OS_Handle handle); -internal B32 os_process_kill(OS_Handle handle); - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal Thread os_thread_launch(ThreadEntryPointFunctionType *f, void *p); -internal B32 os_thread_join(Thread handle, U64 endt_us); -internal void os_thread_detach(Thread handle); - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -//- rjf: recursive mutexes -internal Mutex os_mutex_alloc(void); -internal void os_mutex_release(Mutex mutex); -internal void os_mutex_take(Mutex mutex); -internal void os_mutex_drop(Mutex mutex); - -//- rjf: reader/writer mutexes -internal RWMutex os_rw_mutex_alloc(void); -internal void os_rw_mutex_release(RWMutex mutex); -internal void os_rw_mutex_take(RWMutex mutex, B32 write_mode); -internal void os_rw_mutex_drop(RWMutex mutex, B32 write_mode); - -//- rjf: condition variables -internal CondVar os_cond_var_alloc(void); -internal void os_cond_var_release(CondVar cv); -// returns false on timeout, true on signal, (max_wait_ms = max_U64) -> no timeout -internal B32 os_cond_var_wait(CondVar cv, Mutex mutex, U64 endt_us); -internal B32 os_cond_var_wait_rw(CondVar cv, RWMutex mutex_rw, B32 write_mode, U64 endt_us); -internal void os_cond_var_signal(CondVar cv); -internal void os_cond_var_broadcast(CondVar cv); - -//- rjf: cross-process semaphores -internal Semaphore os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name); -internal void os_semaphore_release(Semaphore semaphore); -internal Semaphore os_semaphore_open(String8 name); -internal void os_semaphore_close(Semaphore semaphore); -internal B32 os_semaphore_take(Semaphore semaphore, U64 endt_us); -internal void os_semaphore_drop(Semaphore semaphore); - -//- rjf: barriers -internal Barrier os_barrier_alloc(U64 count); -internal void os_barrier_release(Barrier barrier); -internal void os_barrier_wait(Barrier barrier); - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle os_library_open(String8 path); -internal void os_library_close(OS_Handle lib); -internal VoidProc *os_library_load_proc(OS_Handle lib, String8 name); - -//////////////////////////////// -//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) - -internal void os_safe_call(ThreadEntryPointFunctionType *func, ThreadEntryPointFunctionType *fail_handler, void *ptr); - -//////////////////////////////// -//~ rjf: @os_hooks GUIDs (Implemented Per-OS) - -internal Guid os_make_guid(void); - -//////////////////////////////// -//~ rjf: @os_hooks Entry Points (Implemented Per-OS) - -// NOTE(rjf): The implementation of `os_core` will define low-level entry -// points if BUILD_ENTRY_DEFINING_UNIT is defined to 1. These will call -// into the standard codebase program entry points, named "entry_point". - -#if BUILD_ENTRY_DEFINING_UNIT -raddbg_entry_point(entry_point); -internal no_inline void entry_point(CmdLine *cmdline); -#endif - -#endif // OS_CORE_H diff --git a/src/os/core/win32/os_core_win32.c b/src/os/core/win32/os_core_win32.c deleted file mode 100644 index 6b216aa1..00000000 --- a/src/os/core/win32/os_core_win32.c +++ /dev/null @@ -1,1765 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) - -internal OS_SystemInfo * -os_get_system_info(void) -{ - return &os_w32_state.system_info; -} - -internal OS_ProcessInfo * -os_get_process_info(void) -{ - return &os_w32_state.process_info; -} - -internal String8 -os_get_current_path(Arena *arena) -{ - Temp scratch = scratch_begin(&arena, 1); - DWORD length = GetCurrentDirectoryW(0, 0); - U16 *memory = push_array_no_zero(scratch.arena, U16, length + 1); - length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory); - String8 name = str8_from_16(arena, str16(memory, length)); - scratch_end(scratch); - return name; -} - -internal U32 -os_get_process_start_time_unix(void) -{ - HANDLE handle = GetCurrentProcess(); - FILETIME start_time = {0}; - FILETIME exit_time; - FILETIME kernel_time; - FILETIME user_time; - if(GetProcessTimes(handle, &start_time, &exit_time, &kernel_time, &user_time)) - { - return os_w32_unix_time_from_file_time(start_time); - } - return 0; -} - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -//- rjf: basic - -internal void * -os_reserve(U64 size) -{ - void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); - return result; -} - -internal B32 -os_commit(void *ptr, U64 size) -{ - B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); - if (w32_rio_functions.RIORegisterBuffer) - { - // wine does not implement these functions - w32_rio_functions.RIODeregisterBuffer(w32_rio_functions.RIORegisterBuffer(ptr, size)); - } -#if PROFILE_TELEMETRY - tmAlloc(0, ptr, size / 1024, "Win32 Commit"); -#endif - return result; -} - -internal void -os_decommit(void *ptr, U64 size) -{ - VirtualFree(ptr, size, MEM_DECOMMIT); -#if PROFILE_TELEMETRY - tmFree(0, ptr); -#endif -} - -internal void -os_release(void *ptr, U64 size) -{ - // NOTE(rjf): size not used - not necessary on Windows, but necessary for other OSes. - VirtualFree(ptr, 0, MEM_RELEASE); -} - -//- rjf: large pages - -internal void * -os_reserve_large(U64 size) -{ - // we commit on reserve because windows - void *result = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_LARGE_PAGES, PAGE_READWRITE); - return result; -} - -internal B32 -os_commit_large(void *ptr, U64 size) -{ - return 1; -} - -//////////////////////////////// -//~ rjf: @os_hooks Thread Info (Implemented Per-OS) - -internal U32 -os_tid(void) -{ - DWORD id = GetCurrentThreadId(); - return (U32)id; -} - -internal void -os_set_thread_name(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - - // rjf: windows 10 style - if(w32_SetThreadDescription_func) - { - String16 name16 = str16_from_8(scratch.arena, name); - HRESULT hr = w32_SetThreadDescription_func(GetCurrentThread(), (WCHAR*)name16.str); - } - - // rjf: raise-exception style - { - String8 name_copy = push_str8_copy(scratch.arena, name); -#pragma pack(push,8) - typedef struct THREADNAME_INFO THREADNAME_INFO; - struct THREADNAME_INFO - { - U32 dwType; // Must be 0x1000. - char *szName; // Pointer to name (in user addr space). - U32 dwThreadID; // Thread ID (-1=caller thread). - U32 dwFlags; // Reserved for future use, must be zero. - }; -#pragma pack(pop) - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = (char *)name_copy.str; - info.dwThreadID = os_tid(); - info.dwFlags = 0; -#pragma warning(push) -#pragma warning(disable: 6320 6322) - __try - { - RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - } -#pragma warning(pop) - } - - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: @os_hooks Aborting (Implemented Per-OS) - -internal void -os_abort(S32 exit_code) -{ - ExitProcess(exit_code); -} - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files - -internal OS_Handle -os_file_open(OS_AccessFlags flags, String8 path) -{ - OS_Handle result = {0}; - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD access_flags = 0; - DWORD share_mode = 0; - DWORD creation_disposition = OPEN_EXISTING; - SECURITY_ATTRIBUTES security_attributes = {sizeof(security_attributes), 0, 0}; - if(flags & OS_AccessFlag_Read) {access_flags |= GENERIC_READ;} - if(flags & OS_AccessFlag_Write) {access_flags |= GENERIC_WRITE;} - if(flags & OS_AccessFlag_Execute) {access_flags |= GENERIC_EXECUTE;} - if(flags & OS_AccessFlag_ShareRead) {share_mode |= FILE_SHARE_READ;} - if(flags & OS_AccessFlag_ShareWrite) {share_mode |= FILE_SHARE_WRITE|FILE_SHARE_DELETE;} - if(flags & OS_AccessFlag_Write) {creation_disposition = CREATE_ALWAYS;} - if(flags & OS_AccessFlag_Append) {creation_disposition = OPEN_ALWAYS; access_flags |= FILE_APPEND_DATA; } - if(flags & OS_AccessFlag_Inherited) - { - security_attributes.bInheritHandle = 1; - } - HANDLE file = CreateFileW((WCHAR *)path16.str, access_flags, share_mode, &security_attributes, creation_disposition, FILE_ATTRIBUTE_NORMAL, 0); - if(file != INVALID_HANDLE_VALUE) - { - result.u64[0] = (U64)file; - } - else - { - DWORD err = GetLastError(); - (void)err; - } - scratch_end(scratch); - return result; -} - -internal void -os_file_close(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return; } - HANDLE handle = (HANDLE)file.u64[0]; - BOOL result = CloseHandle(handle); - (void)result; -} - -internal U64 -os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - - HANDLE handle = (HANDLE)file.u64[0]; - U8 *ptr = out_data; - U64 off = rng.min; - while(off != rng.max) - { - U64 amt64 = rng.max - off; - U32 amt32 = (U32)Min(MB(32), amt64); - DWORD read_size = 0; - OVERLAPPED overlapped = { .Offset = (U32)off, .OffsetHigh = (U32)(off >> 32) }; - if( ! ReadFile(handle, ptr, amt32, &read_size, &overlapped)) - { - break; - } - ptr += read_size; - off += read_size; - } - - U64 total_read_size = off - rng.min; - return total_read_size; -} - -internal U64 -os_file_write(OS_Handle file, Rng1U64 rng, void *data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - HANDLE win_handle = (HANDLE)file.u64[0]; - U64 src_off = 0; - U64 dst_off = rng.min; - U64 total_write_size = dim_1u64(rng); - for(;;) - { - void *bytes_src = (U8 *)data + src_off; - U64 bytes_left = total_write_size - src_off; - DWORD write_size = Min(MB(1), bytes_left); - DWORD bytes_written = 0; - OVERLAPPED overlapped = {0}; - overlapped.Offset = (dst_off&0x00000000ffffffffull); - overlapped.OffsetHigh = (dst_off&0xffffffff00000000ull) >> 32; - BOOL success = WriteFile(win_handle, bytes_src, write_size, &bytes_written, &overlapped); - if(success == 0) - { - break; - } - src_off += bytes_written; - dst_off += bytes_written; - if(bytes_left == 0) - { - break; - } - } - return src_off; -} - -internal B32 -os_file_set_time(OS_Handle file, DateTime time) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - B32 result = 0; - HANDLE handle = (HANDLE)file.u64[0]; - SYSTEMTIME system_time = {0}; - os_w32_system_time_from_date_time(&system_time, &time); - FILETIME file_time = {0}; - result = (SystemTimeToFileTime(&system_time, &file_time) && - SetFileTime(handle, &file_time, &file_time, &file_time)); - return result; -} - -internal FileProperties -os_properties_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { FileProperties r = {0}; return r; } - FileProperties props = {0}; - HANDLE handle = (HANDLE)file.u64[0]; - BY_HANDLE_FILE_INFORMATION info; - BOOL info_good = GetFileInformationByHandle(handle, &info); - if(info_good) - { - U32 size_lo = info.nFileSizeLow; - U32 size_hi = info.nFileSizeHigh; - props.size = (U64)size_lo | (((U64)size_hi)<<32); - os_w32_dense_time_from_file_time(&props.modified, &info.ftLastWriteTime); - os_w32_dense_time_from_file_time(&props.created, &info.ftCreationTime); - props.flags = os_w32_file_property_flags_from_dwFileAttributes(info.dwFileAttributes); - } - return props; -} - -internal OS_FileID -os_id_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { OS_FileID r = {0}; return r; } - OS_FileID result = {0}; - HANDLE handle = (HANDLE)file.u64[0]; - BY_HANDLE_FILE_INFORMATION info; - BOOL is_ok = GetFileInformationByHandle(handle, &info); - if(is_ok) - { - result.v[0] = info.dwVolumeSerialNumber; - result.v[1] = info.nFileIndexLow; - result.v[2] = info.nFileIndexHigh; - } - return result; -} - -internal B32 -os_file_reserve_size(OS_Handle file, U64 size) -{ - HANDLE handle = (HANDLE)file.u64[0]; - - FILE_ALLOCATION_INFO alloc_info = {0}; - alloc_info.AllocationSize.LowPart = size & max_U32; - alloc_info.AllocationSize.HighPart = (size >> 32) & max_U32; - - BOOL is_reserved = SetFileInformationByHandle(handle, FileAllocationInfo, &alloc_info, sizeof(alloc_info)); - return is_reserved; -} - -internal B32 -os_delete_file_at_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - B32 result = DeleteFileW((WCHAR*)path16.str); - scratch_end(scratch); - return result; -} - -internal B32 -os_copy_file_path(String8 dst, String8 src) -{ - Temp scratch = scratch_begin(0, 0); - String16 dst16 = str16_from_8(scratch.arena, dst); - String16 src16 = str16_from_8(scratch.arena, src); - B32 result = CopyFileW((WCHAR*)src16.str, (WCHAR*)dst16.str, 0); - scratch_end(scratch); - return result; -} - -internal B32 -os_move_file_path(String8 dst, String8 src) -{ - Temp scratch = scratch_begin(0, 0); - String16 dst16 = str16_from_8(scratch.arena, dst); - String16 src16 = str16_from_8(scratch.arena, src); - B32 result = MoveFileW((WCHAR*)src16.str, (WCHAR*)dst16.str); - scratch_end(scratch); - return result; -} - -internal String8 -os_full_path_from_path(Arena *arena, String8 path) -{ - Temp scratch = scratch_begin(&arena, 1); - DWORD buffer_size = Max(MAX_PATH, path.size * 2) + 1; - String16 path16 = str16_from_8(scratch.arena, path); - WCHAR *buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size); - DWORD path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL); - if(path16_size > buffer_size) - { - arena_pop(scratch.arena, buffer_size); - buffer_size = path16_size + 1; - buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size); - path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL); - } - String8 full_path = str8_from_16(arena, str16((U16*)buffer, path16_size)); - scratch_end(scratch); - return full_path; -} - -internal B32 -os_file_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0,0); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); - B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && !!(~attributes & FILE_ATTRIBUTE_DIRECTORY); - scratch_end(scratch); - return exists; -} - -internal B32 -os_folder_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0,0); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); - B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY); - scratch_end(scratch); - return exists; -} - -internal FileProperties -os_properties_from_file_path(String8 path) -{ - WIN32_FIND_DATAW find_data = {0}; - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - HANDLE handle = FindFirstFileW((WCHAR *)path16.str, &find_data); - FileProperties props = {0}; - if(handle != INVALID_HANDLE_VALUE) - { - props.size = Compose64Bit(find_data.nFileSizeHigh, find_data.nFileSizeLow); - os_w32_dense_time_from_file_time(&props.created, &find_data.ftCreationTime); - os_w32_dense_time_from_file_time(&props.modified, &find_data.ftLastWriteTime); - props.flags = os_w32_file_property_flags_from_dwFileAttributes(find_data.dwFileAttributes); - } - else - { - Temp scratch = scratch_begin(0, 0); - WCHAR buffer[512] = {0}; - DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer); - U64 last_slash_pos = 0; - for(;last_slash_pos < path.size; last_slash_pos = str8_find_needle(path, last_slash_pos+1, str8_lit("/"), StringMatchFlag_SlashInsensitive)); - String8 path_trimmed = str8_prefix(path, last_slash_pos); - for(U64 off = 0; off < (U64)length;) - { - String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off); - off += next_drive_string_16.size+1; - String8 next_drive_string = str8_from_16(scratch.arena, next_drive_string_16); - next_drive_string = str8_chop_last_slash(next_drive_string); - if(str8_match(path_trimmed, next_drive_string, StringMatchFlag_CaseInsensitive)) - { - props.flags |= FilePropertyFlag_IsFolder; - break; - } - } - scratch_end(scratch); - } - FindClose(handle); - scratch_end(scratch); - return props; -} - -//- rjf: file maps - -internal OS_Handle -os_file_map_open(OS_AccessFlags flags, OS_Handle file) -{ - OS_Handle map = {0}; - { - HANDLE file_handle = (HANDLE)file.u64[0]; - DWORD protect_flags = 0; - { - switch(flags) - { - default:{}break; - case OS_AccessFlag_Read: - {protect_flags = PAGE_READONLY;}break; - case OS_AccessFlag_Write: - case OS_AccessFlag_Read|OS_AccessFlag_Write: - {protect_flags = PAGE_READWRITE;}break; - case OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Execute: - {protect_flags = PAGE_EXECUTE_READ;}break; - case OS_AccessFlag_Execute|OS_AccessFlag_Write|OS_AccessFlag_Read: - case OS_AccessFlag_Execute|OS_AccessFlag_Write: - {protect_flags = PAGE_EXECUTE_READWRITE;}break; - } - } - HANDLE map_handle = CreateFileMappingA(file_handle, 0, protect_flags, 0, 0, 0); - map.u64[0] = (U64)map_handle; - } - return map; -} - -internal void -os_file_map_close(OS_Handle map) -{ - HANDLE handle = (HANDLE)map.u64[0]; - BOOL result = CloseHandle(handle); - (void)result; -} - -internal void * -os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) -{ - HANDLE handle = (HANDLE)map.u64[0]; - U32 off_lo = (U32)((range.min&0x00000000ffffffffull)>>0); - U32 off_hi = (U32)((range.min&0xffffffff00000000ull)>>32); - U64 size = dim_1u64(range); - DWORD access_flags = 0; - { - switch(flags) - { - default:{}break; - case OS_AccessFlag_Read: - { - access_flags = FILE_MAP_READ; - }break; - case OS_AccessFlag_Write: - { - access_flags = FILE_MAP_WRITE; - }break; - case OS_AccessFlag_Read|OS_AccessFlag_Write: - { - access_flags = FILE_MAP_ALL_ACCESS; - }break; - case OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Execute: - case OS_AccessFlag_Write|OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Write|OS_AccessFlag_Execute: - { - access_flags = FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE; - }break; - } - } - void *result = MapViewOfFile(handle, access_flags, off_hi, off_lo, size); - return result; -} - -internal void -os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) -{ - BOOL result = UnmapViewOfFile(ptr); - (void)result; -} - -//- rjf: directory iteration - -internal OS_FileIter * -os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) -{ - Temp scratch = scratch_begin(&arena, 1); - String8 path_with_wildcard = push_str8_cat(scratch.arena, path, str8_lit("\\*")); - String16 path16 = str16_from_8(scratch.arena, path_with_wildcard); - OS_FileIter *iter = push_array(arena, OS_FileIter, 1); - iter->flags = flags; - OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; - if(path.size == 0) - { - w32_iter->is_volume_iter = 1; - WCHAR buffer[512] = {0}; - DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer); - String8List drive_strings = {0}; - for(U64 off = 0; off < (U64)length;) - { - String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off); - off += next_drive_string_16.size+1; - String8 next_drive_string = str8_from_16(arena, next_drive_string_16); - next_drive_string = str8_chop_last_slash(next_drive_string); - str8_list_push(scratch.arena, &drive_strings, next_drive_string); - } - w32_iter->drive_strings = str8_array_from_list(arena, &drive_strings); - w32_iter->drive_strings_iter_idx = 0; - } - else - { - w32_iter->handle = FindFirstFileExW((WCHAR*)path16.str, FindExInfoBasic, &w32_iter->find_data, FindExSearchNameMatch, 0, FIND_FIRST_EX_LARGE_FETCH); - } - scratch_end(scratch); - return iter; -} - -internal B32 -os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) -{ - B32 result = 0; - OS_FileIterFlags flags = iter->flags; - OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; - switch(w32_iter->is_volume_iter) - { - //- rjf: file iteration - default: - case 0: - { - if (!(flags & OS_FileIterFlag_Done) && w32_iter->handle != INVALID_HANDLE_VALUE) - { - do - { - // check is usable - B32 usable_file = 1; - - WCHAR *file_name = w32_iter->find_data.cFileName; - DWORD attributes = w32_iter->find_data.dwFileAttributes; - if (file_name[0] == '.'){ - if (flags & OS_FileIterFlag_SkipHiddenFiles){ - usable_file = 0; - } - else if (file_name[1] == 0){ - usable_file = 0; - } - else if (file_name[1] == '.' && file_name[2] == 0){ - usable_file = 0; - } - } - if (attributes & FILE_ATTRIBUTE_DIRECTORY){ - if (flags & OS_FileIterFlag_SkipFolders){ - usable_file = 0; - } - } - else{ - if (flags & OS_FileIterFlag_SkipFiles){ - usable_file = 0; - } - } - - // emit if usable - if (usable_file){ - info_out->name = str8_from_16(arena, str16_cstring((U16*)file_name)); - info_out->props.size = (U64)w32_iter->find_data.nFileSizeLow | (((U64)w32_iter->find_data.nFileSizeHigh)<<32); - os_w32_dense_time_from_file_time(&info_out->props.created, &w32_iter->find_data.ftCreationTime); - os_w32_dense_time_from_file_time(&info_out->props.modified, &w32_iter->find_data.ftLastWriteTime); - info_out->props.flags = os_w32_file_property_flags_from_dwFileAttributes(attributes); - result = 1; - if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){ - iter->flags |= OS_FileIterFlag_Done; - } - break; - } - }while(FindNextFileW(w32_iter->handle, &w32_iter->find_data)); - } - }break; - - //- rjf: volume iteration - case 1: - { - result = w32_iter->drive_strings_iter_idx < w32_iter->drive_strings.count; - if(result != 0) - { - MemoryZeroStruct(info_out); - info_out->name = w32_iter->drive_strings.v[w32_iter->drive_strings_iter_idx]; - info_out->props.flags |= FilePropertyFlag_IsFolder; - w32_iter->drive_strings_iter_idx += 1; - } - }break; - } - if(!result) - { - iter->flags |= OS_FileIterFlag_Done; - } - return result; -} - -internal void -os_file_iter_end(OS_FileIter *iter) -{ - OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; - HANDLE zero_handle; - MemoryZeroStruct(&zero_handle); - if(!MemoryMatchStruct(&zero_handle, &w32_iter->handle)) - { - FindClose(w32_iter->handle); - } -} - -//- rjf: directory creation - -internal B32 -os_make_directory(String8 path) -{ - B32 result = 0; - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, path); - WIN32_FILE_ATTRIBUTE_DATA attributes = {0}; - GetFileAttributesExW((WCHAR*)name16.str, GetFileExInfoStandard, &attributes); - if(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - result = 1; - } - else if(CreateDirectoryW((WCHAR*)name16.str, 0)) - { - result = 1; - } - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle -os_shared_memory_alloc(U64 size, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE file = CreateFileMappingW(INVALID_HANDLE_VALUE, - 0, - PAGE_READWRITE, - (U32)((size & 0xffffffff00000000) >> 32), - (U32)((size & 0x00000000ffffffff)), - (WCHAR *)name16.str); - OS_Handle result = {(U64)file}; - scratch_end(scratch); - return result; -} - -internal OS_Handle -os_shared_memory_open(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE file = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, (WCHAR *)name16.str); - OS_Handle result = {(U64)file}; - scratch_end(scratch); - return result; -} - -internal void -os_shared_memory_close(OS_Handle handle) -{ - HANDLE file = (HANDLE)(handle.u64[0]); - CloseHandle(file); -} - -internal void * -os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) -{ - HANDLE file = (HANDLE)(handle.u64[0]); - U64 offset = range.min; - U64 size = range.max-range.min; - void *ptr = MapViewOfFile(file, FILE_MAP_ALL_ACCESS, - (U32)((offset & 0xffffffff00000000) >> 32), - (U32)((offset & 0x00000000ffffffff)), - size); - return ptr; -} - -internal void -os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) -{ - UnmapViewOfFile(ptr); -} - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal U64 -os_now_microseconds(void) -{ - U64 result = 0; - LARGE_INTEGER large_int_counter; - if(QueryPerformanceCounter(&large_int_counter)) - { - result = (large_int_counter.QuadPart*Million(1))/os_w32_state.microsecond_resolution; - } - return result; -} - -internal U32 -os_now_unix(void) -{ - FILETIME file_time; - GetSystemTimeAsFileTime(&file_time); - U32 unix_time = os_w32_unix_time_from_file_time(file_time); - return unix_time; -} - -internal DateTime -os_now_universal_time(void) -{ - SYSTEMTIME systime = {0}; - GetSystemTime(&systime); - DateTime result = {0}; - os_w32_date_time_from_system_time(&result, &systime); - return result; -} - -internal DateTime -os_universal_time_from_local(DateTime *date_time) -{ - SYSTEMTIME systime = {0}; - os_w32_system_time_from_date_time(&systime, date_time); - FILETIME ftime = {0}; - SystemTimeToFileTime(&systime, &ftime); - FILETIME ftime_local = {0}; - LocalFileTimeToFileTime(&ftime, &ftime_local); - FileTimeToSystemTime(&ftime_local, &systime); - DateTime result = {0}; - os_w32_date_time_from_system_time(&result, &systime); - return result; -} - -internal DateTime -os_local_time_from_universal(DateTime *date_time) -{ - SYSTEMTIME systime = {0}; - os_w32_system_time_from_date_time(&systime, date_time); - FILETIME ftime = {0}; - SystemTimeToFileTime(&systime, &ftime); - FILETIME ftime_local = {0}; - FileTimeToLocalFileTime(&ftime, &ftime_local); - FileTimeToSystemTime(&ftime_local, &systime); - DateTime result = {0}; - os_w32_date_time_from_system_time(&result, &systime); - return result; -} - -internal void -os_sleep_milliseconds(U32 msec) -{ - Sleep(msec); -} - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal OS_Handle -os_process_launch(OS_ProcessLaunchParams *params) -{ - OS_Handle result = {0}; - Temp scratch = scratch_begin(0, 0); - - //- rjf: form full command string - String8 cmd = {0}; - { - StringJoin join_params = {0}; - join_params.pre = str8_lit("\""); - join_params.sep = str8_lit("\" \""); - join_params.post = str8_lit("\""); - cmd = str8_list_join(scratch.arena, ¶ms->cmd_line, &join_params); - } - - //- rjf: form environment - B32 use_null_env_arg = 0; - String8 env = {0}; - { - StringJoin join_params2 = {0}; - join_params2.sep = str8_lit("\0"); - join_params2.post = str8_lit("\0"); - String8List all_opts = params->env; - if(params->inherit_env != 0) - { - if(all_opts.node_count != 0) - { - MemoryZeroStruct(&all_opts); - for(String8Node *n = params->env.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - for(String8Node *n = os_w32_state.process_info.environment.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - } - else - { - use_null_env_arg = 1; - } - } - if(use_null_env_arg == 0) - { - env = str8_list_join(scratch.arena, &all_opts, &join_params2); - } - } - - //- rjf: utf-8 -> utf-16 - String16 cmd16 = str16_from_8(scratch.arena, cmd); - String16 dir16 = str16_from_8(scratch.arena, params->path); - String16 env16 = {0}; - if(use_null_env_arg == 0) - { - env16 = str16_from_8(scratch.arena, env); - } - - //- rjf: determine creation flags - DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; - if(params->consoleless) - { - creation_flags |= CREATE_NO_WINDOW; - } - - //- rjf: launch - BOOL inherit_handles = 0; - STARTUPINFOW startup_info = {sizeof(startup_info)}; - if(!os_handle_match(params->stdout_file, os_handle_zero())) - { - HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0]; - startup_info.hStdOutput = stdout_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - if(!os_handle_match(params->stderr_file, os_handle_zero())) - { - HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0]; - startup_info.hStdError = stderr_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - if(!os_handle_match(params->stdin_file, os_handle_zero())) - { - HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0]; - startup_info.hStdInput = stdin_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - PROCESS_INFORMATION process_info = {0}; - if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, inherit_handles, creation_flags, use_null_env_arg ? 0 : (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) - { - result.u64[0] = (U64)process_info.hProcess; - CloseHandle(process_info.hThread); - } - - scratch_end(scratch); - return result; -} - -internal B32 -os_process_join(OS_Handle handle, U64 endt_us, U64 *exit_code_out) -{ - HANDLE process = (HANDLE)(handle.u64[0]); - DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - DWORD result = WaitForSingleObject(process, sleep_ms); - B32 process_joined = (result == WAIT_OBJECT_0); - if(process_joined && exit_code_out) - { - DWORD exit_code = 0; - if(GetExitCodeProcess(process, &exit_code)) - { - *exit_code_out = exit_code; - } - } - if(process_joined) - { - CloseHandle(process); - } - return process_joined; -} - -internal B32 -os_process_kill(OS_Handle handle) -{ - HANDLE process = (HANDLE)handle.u64[0]; - BOOL was_terminated = TerminateProcess(process, 999); - return was_terminated; -} - -internal void -os_process_detach(OS_Handle handle) -{ - HANDLE process = (HANDLE)(handle.u64[0]); - CloseHandle(process); -} - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal Thread -os_thread_launch(ThreadEntryPointFunctionType *f, void *p) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Thread); - entity->thread.func = f; - entity->thread.ptr = p; - entity->thread.handle = CreateThread(0, 0, os_w32_thread_entry_point, entity, 0, &entity->thread.tid); - Thread result = {IntFromPtr(entity)}; - return result; -} - -internal B32 -os_thread_join(Thread handle, U64 endt_us) -{ - DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - OS_W32_Entity *entity = (OS_W32_Entity *)PtrFromInt(handle.u64[0]); - DWORD wait_result = WAIT_OBJECT_0; - if(entity != 0) - { - wait_result = WaitForSingleObject(entity->thread.handle, sleep_ms); - CloseHandle(entity->thread.handle); - os_w32_entity_release(entity); - } - return (wait_result == WAIT_OBJECT_0); -} - -internal void -os_thread_detach(Thread thread) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(thread.u64[0]); - if(entity != 0) - { - CloseHandle(entity->thread.handle); - os_w32_entity_release(entity); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -//- rjf: mutexes - -internal Mutex -os_mutex_alloc(void) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Mutex); - InitializeCriticalSection(&entity->mutex); - Mutex result = {IntFromPtr(entity)}; - return result; -} - -internal void -os_mutex_release(Mutex mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); - DeleteCriticalSection(&entity->mutex); - os_w32_entity_release(entity); -} - -internal void -os_mutex_take(Mutex mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); - EnterCriticalSection(&entity->mutex); -} - -internal void -os_mutex_drop(Mutex mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); - LeaveCriticalSection(&entity->mutex); -} - -//- rjf: reader/writer mutexes - -internal RWMutex -os_rw_mutex_alloc(void) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_RWMutex); - InitializeSRWLock(&entity->rw_mutex); - RWMutex result = {IntFromPtr(entity)}; - return result; -} - -internal void -os_rw_mutex_release(RWMutex rw_mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - os_w32_entity_release(entity); -} - -internal void -os_rw_mutex_take(RWMutex rw_mutex, B32 write_mode) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - if(write_mode) - { - AcquireSRWLockExclusive(&entity->rw_mutex); - } - else - { - AcquireSRWLockShared(&entity->rw_mutex); - } -} - -internal void -os_rw_mutex_drop(RWMutex rw_mutex, B32 write_mode) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - if(write_mode) - { - ReleaseSRWLockExclusive(&entity->rw_mutex); - } - else - { - ReleaseSRWLockShared(&entity->rw_mutex); - } -} - -//- rjf: condition variables - -internal CondVar -os_cond_var_alloc(void) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_ConditionVariable); - InitializeConditionVariable(&entity->cv); - CondVar result = {IntFromPtr(entity)}; - return result; -} - -internal void -os_cond_var_release(CondVar cv) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - os_w32_entity_release(entity); -} - -internal B32 -os_cond_var_wait(CondVar cv, Mutex mutex, U64 endt_us) -{ - U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - BOOL result = 0; - if(sleep_ms > 0) - { - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); - result = SleepConditionVariableCS(&entity->cv, &mutex_entity->mutex, sleep_ms); - } - return result; -} - -internal B32 -os_cond_var_wait_rw(CondVar cv, RWMutex mutex_rw, B32 write_mode, U64 endt_us) -{ - U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - BOOL result = 0; - if(sleep_ms > 0) - { - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); - result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, - write_mode ? 0 : CONDITION_VARIABLE_LOCKMODE_SHARED); - } - return result; -} - -internal void -os_cond_var_signal(CondVar cv) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - WakeConditionVariable(&entity->cv); -} - -internal void -os_cond_var_broadcast(CondVar cv) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - WakeAllConditionVariable(&entity->cv); -} - -//- rjf: cross-process semaphores - -internal Semaphore -os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE handle = CreateSemaphoreW(0, initial_count, max_count, (WCHAR *)name16.str); - Semaphore result = {(U64)handle}; - scratch_end(scratch); - return result; -} - -internal void -os_semaphore_release(Semaphore semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - CloseHandle(handle); -} - -internal Semaphore -os_semaphore_open(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE handle = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS, 0, (WCHAR *)name16.str); - Semaphore result = {(U64)handle}; - scratch_end(scratch); - return result; -} - -internal void -os_semaphore_close(Semaphore semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - CloseHandle(handle); -} - -internal B32 -os_semaphore_take(Semaphore semaphore, U64 endt_us) -{ - U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - HANDLE handle = (HANDLE)semaphore.u64[0]; - DWORD wait_result = WaitForSingleObject(handle, sleep_ms); - B32 result = (wait_result == WAIT_OBJECT_0); - return result; -} - -internal void -os_semaphore_drop(Semaphore semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - ReleaseSemaphore(handle, 1, 0); -} - -//- rjf: barriers - -internal Barrier -os_barrier_alloc(U64 count) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Barrier); - if(entity != 0) - { - BOOL init_good = InitializeSynchronizationBarrier(&entity->sb, count, -1); - (void)init_good; - } - Barrier result = {IntFromPtr(entity)}; - return result; -} - -internal void -os_barrier_release(Barrier barrier) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(barrier.u64[0]); - if(entity != 0) - { - DeleteSynchronizationBarrier(&entity->sb); - os_w32_entity_release(entity); - } -} - -internal void -os_barrier_wait(Barrier barrier) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(barrier.u64[0]); - if(entity != 0) - { - EnterSynchronizationBarrier(&entity->sb, 0); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle -os_library_open(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - HMODULE mod = LoadLibraryW((LPCWSTR)path16.str); - OS_Handle result = { (U64)mod }; - scratch_end(scratch); - return result; -} - -internal VoidProc* -os_library_load_proc(OS_Handle lib, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - HMODULE mod = (HMODULE)lib.u64[0]; - name = push_str8_copy(scratch.arena, name); - VoidProc *result = (VoidProc*)GetProcAddress(mod, (LPCSTR)name.str); - scratch_end(scratch); - return result; -} - -internal void -os_library_close(OS_Handle lib) -{ - HMODULE mod = (HMODULE)lib.u64[0]; - FreeLibrary(mod); -} - -//////////////////////////////// -//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) - -internal void -os_safe_call(ThreadEntryPointFunctionType *func, ThreadEntryPointFunctionType *fail_handler, void *ptr) -{ - __try - { - func(ptr); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - if(fail_handler != 0) - { - fail_handler(ptr); - } - } -} - -//////////////////////////////// -//~ rjf: @os_hooks GUIDs (Implemented Per-OS) - -internal Guid -os_make_guid(void) -{ - Guid result; MemoryZeroStruct(&result); - UUID uuid; - RPC_STATUS rpc_status = UuidCreate(&uuid); - if(rpc_status == RPC_S_OK) - { - result.data1 = uuid.Data1; - result.data2 = uuid.Data2; - result.data3 = uuid.Data3; - MemoryCopyArray(result.data4, uuid.Data4); - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Entry Points (Implemented Per-OS) - -#include -#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside -#include - -internal B32 win32_g_is_quiet = 0; -internal B32 win32_g_gen_dump = 0; - -internal HRESULT WINAPI -win32_dialog_callback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LONG_PTR data) -{ - if(msg == TDN_HYPERLINK_CLICKED) - { - ShellExecuteW(NULL, L"open", (LPWSTR)lparam, NULL, NULL, SW_SHOWNORMAL); - } - return S_OK; -} - -internal LONG WINAPI -win32_exception_filter(EXCEPTION_POINTERS* exception_ptrs) -{ - if(win32_g_is_quiet) - { - ExitProcess(1); - } - - static volatile LONG first = 0; - if(InterlockedCompareExchange(&first, 1, 0) != 0) - { - // prevent failures in other threads to popup same message box - // this handler just shows first thread that crashes - // we are terminating afterwards anyway - for (;;) Sleep(1000); - } - - WCHAR buffer[4096] = {0}; - int buflen = 0; - - DWORD exception_code = exception_ptrs->ExceptionRecord->ExceptionCode; - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"A fatal exception (code 0x%x) occurred. The process is terminating.\n", exception_code); - - // load dbghelp dynamically just in case if it is missing - BOOL (WINAPI *dbg_MiniDumpWriteDump)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam) = 0; - HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); - if(dbghelp) - { - DWORD (WINAPI *dbg_SymSetOptions)(DWORD SymOptions); - BOOL (WINAPI *dbg_SymInitializeW)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess); - BOOL (WINAPI *dbg_StackWalk64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, - LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, - PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, - PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); - PVOID (WINAPI *dbg_SymFunctionTableAccess64)(HANDLE hProcess, DWORD64 AddrBase); - DWORD64 (WINAPI *dbg_SymGetModuleBase64)(HANDLE hProcess, DWORD64 qwAddr); - BOOL (WINAPI *dbg_SymFromAddrW)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFOW Symbol); - BOOL (WINAPI *dbg_SymGetLineFromAddrW64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINEW64 Line); - BOOL (WINAPI *dbg_SymGetModuleInfoW64)(HANDLE hProcess, DWORD64 qwAddr, PIMAGEHLP_MODULEW64 ModuleInfo); - - *(FARPROC*)&dbg_SymSetOptions = GetProcAddress(dbghelp, "SymSetOptions"); - *(FARPROC*)&dbg_SymInitializeW = GetProcAddress(dbghelp, "SymInitializeW"); - *(FARPROC*)&dbg_StackWalk64 = GetProcAddress(dbghelp, "StackWalk64"); - *(FARPROC*)&dbg_SymFunctionTableAccess64 = GetProcAddress(dbghelp, "SymFunctionTableAccess64"); - *(FARPROC*)&dbg_SymGetModuleBase64 = GetProcAddress(dbghelp, "SymGetModuleBase64"); - *(FARPROC*)&dbg_SymFromAddrW = GetProcAddress(dbghelp, "SymFromAddrW"); - *(FARPROC*)&dbg_SymGetLineFromAddrW64 = GetProcAddress(dbghelp, "SymGetLineFromAddrW64"); - *(FARPROC*)&dbg_SymGetModuleInfoW64 = GetProcAddress(dbghelp, "SymGetModuleInfoW64"); - *(FARPROC*)&dbg_MiniDumpWriteDump = GetProcAddress(dbghelp, "MiniDumpWriteDump"); - - if(dbg_SymSetOptions && dbg_SymInitializeW && dbg_StackWalk64 && dbg_SymFunctionTableAccess64 && dbg_SymGetModuleBase64 && dbg_SymFromAddrW && dbg_SymGetLineFromAddrW64 && dbg_SymGetModuleInfoW64) - { - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - CONTEXT context = *exception_ptrs->ContextRecord; - - WCHAR module_path[MAX_PATH]; - GetModuleFileNameW(NULL, module_path, ArrayCount(module_path)); - PathRemoveFileSpecW(module_path); - - dbg_SymSetOptions(SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); - if(dbg_SymInitializeW(process, module_path, TRUE)) - { - // check that raddbg.pdb file is good - B32 raddbg_pdb_valid = 0; - { - IMAGEHLP_MODULEW64 module = {0}; - module.SizeOfStruct = sizeof(module); - if(dbg_SymGetModuleInfoW64(process, (DWORD64)&win32_exception_filter, &module)) - { - raddbg_pdb_valid = (module.SymType == SymPdb); - } - } - - if(!raddbg_pdb_valid) - { - buflen += wnsprintfW(buffer + buflen, sizeof(buffer) - buflen, - L"\nThe PDB debug information file for this executable is not valid or was not found. Please rebuild binary to get the call stack.\n"); - } - else - { - STACKFRAME64 frame = {0}; - DWORD image_type; -#if defined(_M_AMD64) - image_type = IMAGE_FILE_MACHINE_AMD64; - frame.AddrPC.Offset = context.Rip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.Rbp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.Rsp; - frame.AddrStack.Mode = AddrModeFlat; -#elif defined(_M_ARM64) - image_type = IMAGE_FILE_MACHINE_ARM64; - frame.AddrPC.Offset = context.Pc; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.Fp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.Sp; - frame.AddrStack.Mode = AddrModeFlat; -#else -# error Arch not supported! -#endif - -#if BUILD_CONSOLE_INTERFACE - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nCreate a new issue with this report at %S.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); -#else - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, - L"\nPress Ctrl+C to copy this text to clipboard, then create a new issue at\n" - L"%S\n\n", BUILD_ISSUES_LINK_STRING_LITERAL, BUILD_ISSUES_LINK_STRING_LITERAL); -#endif - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"Call stack:\n"); - - U64 frame_offset = 0; - - if (frame.AddrPC.Offset == 0) - { - // if IP address is 0 then most likely we have called indirectly on NULL function pointer - // which means no useful stack unwinding will happen, because there's no unwind info for address 0 - // but we can try reading 8 bytes of return address from stack, and start unwinding there - - ULONG_PTR hi, lo; - GetCurrentThreadStackLimits(&lo, &hi); - if (frame.AddrStack.Offset >= lo && frame.AddrStack.Offset <= hi - sizeof(void*)) - { - frame.AddrPC.Offset = *(DWORD64*)frame.AddrStack.Offset - 1; - frame.AddrStack.Offset += sizeof(void*); -#if defined(_M_AMD64) - context.Rip = frame.AddrPC.Offset; - context.Rsp = frame.AddrStack.Offset; -#elif defined(_M_ARM64) - context.Pc = frame.AddrPC.Offset; - context.Sp = frame.AddrStack.Offset; -#endif - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"1. [NULL]\n"); - frame_offset = 1; - } - - for(U32 idx=0; ;idx++) - { - const U32 max_frames = 32; - if(idx == max_frames) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"..."); - break; - } - - if(!dbg_StackWalk64(image_type, process, thread, &frame, &context, 0, dbg_SymFunctionTableAccess64, dbg_SymGetModuleBase64, 0)) - { - break; - } - - U64 address = frame.AddrPC.Offset; - if(address == 0) - { - break; - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"%u. [0x%I64x]", frame_offset + idx + 1, address); - - struct { - SYMBOL_INFOW info; - WCHAR name[MAX_SYM_NAME]; - } symbol = {0}; - - symbol.info.SizeOfStruct = sizeof(symbol.info); - symbol.info.MaxNameLen = MAX_SYM_NAME; - - DWORD64 displacement = 0; - if(dbg_SymFromAddrW(process, address, &displacement, &symbol.info)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s +%u", symbol.info.Name, (DWORD)displacement); - - IMAGEHLP_LINEW64 line = {0}; - line.SizeOfStruct = sizeof(line); - - DWORD line_displacement = 0; - if(dbg_SymGetLineFromAddrW64(process, address, &line_displacement, &line)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L", %s line %u", PathFindFileNameW(line.FileName), line.LineNumber); - } - } - else - { - IMAGEHLP_MODULEW64 module = {0}; - module.SizeOfStruct = sizeof(module); - if(dbg_SymGetModuleInfoW64(process, address, &module)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s", module.ModuleName); - } - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\n"); - } - } - } - } - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nVersion: %S%S", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); - - B32 generate_crash_dump = win32_g_gen_dump; -#if BUILD_CONSOLE_INTERFACE - fwprintf(stderr, L"\n--- Fatal Exception ---\n"); - fwprintf(stderr, L"%s\n\n", buffer); -#else - int selected_button = 0; - TASKDIALOG_BUTTON generate_dump = {1, L"Generate Crash Dump File"}; - TASKDIALOGCONFIG dialog = {0}; - dialog.cbSize = sizeof(dialog); - dialog.dwFlags = TDF_SIZE_TO_CONTENT | TDF_ENABLE_HYPERLINKS | TDF_ALLOW_DIALOG_CANCELLATION; - dialog.pszMainIcon = TD_ERROR_ICON; - dialog.dwCommonButtons = TDCBF_CLOSE_BUTTON; - dialog.pszWindowTitle = L"Fatal Exception"; - dialog.pszContent = buffer; - dialog.pfCallback = &win32_dialog_callback; - dialog.cButtons = 1; - dialog.pButtons = &generate_dump; - TaskDialogIndirect(&dialog, &selected_button, 0, 0); - generate_crash_dump = (selected_button == generate_dump.nButtonID); -#endif - - if(dbg_MiniDumpWriteDump && generate_crash_dump) - { - WCHAR dump_file_path[MAX_PATH] = {0}; - SHGetFolderPathW(0, CSIDL_DESKTOP, 0, 0, dump_file_path); - PathAppendW(dump_file_path, L"raddbg_crash_dump.dmp"); - HANDLE file = CreateFileW(dump_file_path, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - if (file != INVALID_HANDLE_VALUE) - { - MINIDUMP_EXCEPTION_INFORMATION info = {0}; - info.ThreadId = GetCurrentThreadId(); - info.ExceptionPointers = exception_ptrs; - info.ClientPointers = FALSE; - BOOL dump_successful = dbg_MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal, &info, 0, 0); - CloseHandle(file); - - if (dump_successful) - { -#if !BUILD_CONSOLE_INTERFACE - // opens explorer and selects file - SFGAOF flags = 0; - PIDLIST_ABSOLUTE list = 0; - if (SUCCEEDED(SHParseDisplayName(dump_file_path, NULL, &list, 0, &flags))) - { - SHOpenFolderAndSelectItems(list, 0, NULL, 0); - CoTaskMemFree(list); - } -#endif - } - } - } - - ExitProcess(1); -} - -#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside -#define OS_WINDOWS 1 - -internal void -w32_entry_point_caller(int argc, WCHAR **wargv) -{ - SetUnhandledExceptionFilter(&win32_exception_filter); - - //- rjf: dynamically load windows functions which are not guaranteed - // in all SDKs - { - HMODULE module = LoadLibraryA("kernel32.dll"); - w32_SetThreadDescription_func = (W32_SetThreadDescription_Type *)GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); - } - - //- rjf: try to allow large pages if we can - B32 large_pages_allowed = 0; - { - HANDLE token; - if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) - { - LUID luid; - if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) - { - TOKEN_PRIVILEGES priv; - priv.PrivilegeCount = 1; - priv.Privileges[0].Luid = luid; - priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - large_pages_allowed = !!AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0); - } - CloseHandle(token); - } - } - - //- rjf: get RIO extension function table - { - // NOTE(mmozeiko): need to get function pointers to RIO functions, and that requires dummy socket - WSADATA WinSockData; - WSAStartup(MAKEWORD(2, 2), &WinSockData); - GUID guid = WSAID_MULTIPLE_RIO; - DWORD rio_byte = 0; - SOCKET Sock = socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP); - WSAIoctl(Sock, SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), (void**)&w32_rio_functions, sizeof(w32_rio_functions), &rio_byte, 0, 0); - closesocket(Sock); - } - - //- rjf: get system info - SYSTEM_INFO sysinfo = {0}; - GetSystemInfo(&sysinfo); - - //- rjf: set up non-dynamically-alloc'd state - // - // (we need to set up some basics before this layer can supply - // memory allocation primitives) - { - os_w32_state.microsecond_resolution = 1; - LARGE_INTEGER large_int_resolution; - if(QueryPerformanceFrequency(&large_int_resolution)) - { - os_w32_state.microsecond_resolution = large_int_resolution.QuadPart; - } - } - { - OS_SystemInfo *info = &os_w32_state.system_info; - info->logical_processor_count = (U64)sysinfo.dwNumberOfProcessors; - info->page_size = sysinfo.dwPageSize; - info->large_page_size = GetLargePageMinimum(); - info->allocation_granularity = sysinfo.dwAllocationGranularity; - } - { - OS_ProcessInfo *info = &os_w32_state.process_info; - info->large_pages_allowed = large_pages_allowed; - info->pid = GetCurrentProcessId(); - } - - //- rjf: extract arguments - Arena *args_arena = arena_alloc(.reserve_size = MB(1), .commit_size = KB(32)); - char **argv = push_array(args_arena, char *, argc); - for(int i = 0; i < argc; i += 1) - { - String16 arg16 = str16_cstring((U16 *)wargv[i]); - String8 arg8 = str8_from_16(args_arena, arg16); - if(str8_match(arg8, str8_lit("--quiet"), StringMatchFlag_CaseInsensitive) || - str8_match(arg8, str8_lit("-quiet"), StringMatchFlag_CaseInsensitive)) - { - win32_g_is_quiet = 1; - } - if(str8_match(arg8, str8_lit("--large_pages"), StringMatchFlag_CaseInsensitive) || - str8_match(arg8, str8_lit("-large_pages"), StringMatchFlag_CaseInsensitive)) - { - arena_default_flags = ArenaFlag_LargePages; - arena_default_reserve_size = Max(MB(64), os_w32_state.system_info.large_page_size); - arena_default_commit_size = arena_default_reserve_size; - } - if(str8_match(arg8, str8_lit("--gen_crash_dump"), StringMatchFlag_CaseInsensitive) || - str8_match(arg8, str8_lit("-gen_crash_dump"), StringMatchFlag_CaseInsensitive)) - { - win32_g_gen_dump = 1; - } - argv[i] = (char *)arg8.str; - } - - //- rjf: set up thread context - TCTX *tctx = tctx_alloc(); - tctx_select(tctx); - - //- rjf: set up dynamically-alloc'd state - Arena *arena = arena_alloc(); - { - os_w32_state.arena = arena; - { - OS_SystemInfo *info = &os_w32_state.system_info; - U8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0}; - DWORD size = MAX_COMPUTERNAME_LENGTH + 1; - if(GetComputerNameA((char*)buffer, &size)) - { - info->machine_name = push_str8_copy(arena, str8(buffer, size)); - } - } - } - { - OS_ProcessInfo *info = &os_w32_state.process_info; - { - Temp scratch = scratch_begin(0, 0); - DWORD size = KB(32); - U16 *buffer = push_array_no_zero(scratch.arena, U16, size); - DWORD length = GetModuleFileNameW(0, (WCHAR*)buffer, size); - String8 name8 = str8_from_16(scratch.arena, str16(buffer, length)); - String8 name_chopped = str8_chop_last_slash(name8); - info->binary_path = push_str8_copy(arena, name_chopped); - scratch_end(scratch); - } - info->initial_path = os_get_current_path(arena); - { - Temp scratch = scratch_begin(0, 0); - U64 size = KB(32); - U16 *buffer = push_array_no_zero(scratch.arena, U16, size); - if(SUCCEEDED(SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, (WCHAR*)buffer))) - { - info->user_program_data_path = str8_from_16(arena, str16_cstring(buffer)); - } - scratch_end(scratch); - } - { - 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(arena, string16); - str8_list_push(arena, &info->environment, string); - start_idx = idx+1; - } - } - } - } - } - - //- rjf: set up entity storage - InitializeCriticalSection(&os_w32_state.entity_mutex); - os_w32_state.entity_arena = arena_alloc(); - - //- rjf: call into "real" entry point - main_thread_base_entry_point(argc, argv); -} - -#if BUILD_CONSOLE_INTERFACE -int wmain(int argc, WCHAR **argv) -{ - w32_entry_point_caller(argc, argv); - return 0; -} -#else -int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) -{ - CoInitializeEx(0, COINIT_APARTMENTTHREADED); - w32_entry_point_caller(__argc, __wargv); - return 0; -} -#endif diff --git a/src/os/os_inc.c b/src/os/os_inc.c deleted file mode 100644 index 555089ca..00000000 --- a/src/os/os_inc.c +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -// #include "os/core/os_core.c" -#if OS_FEATURE_GRAPHICAL -# include "os/gfx/os_gfx.c" -#endif - -#if OS_WINDOWS -// # include "os/core/win32/os_core_win32.c" -#elif OS_LINUX -# include "os/core/linux/os_core_linux.c" -#else -# error OS core layer not implemented for this operating system. -#endif - -#if OS_FEATURE_GRAPHICAL -# if OS_GFX_STUB -# include "os/gfx/stub/os_gfx_stub.c" -# elif OS_WINDOWS -# include "os/gfx/win32/os_gfx_win32.c" -# elif OS_LINUX -# include "os/gfx/linux/os_gfx_linux.c" -# else -# error OS graphical layer not implemented for this operating system. -# endif -#endif diff --git a/src/os/os_inc.h b/src/os/os_inc.h deleted file mode 100644 index 233b55d3..00000000 --- a/src/os/os_inc.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_INC_H -#define OS_INC_H - -#if !defined(OS_FEATURE_GRAPHICAL) -# define OS_FEATURE_GRAPHICAL 0 -#endif - -#if !defined(OS_GFX_STUB) -# define OS_GFX_STUB 0 -#endif - -// #include "os/core/os_core.h" -#if OS_FEATURE_GRAPHICAL -# include "os/gfx/os_gfx.h" -#endif - -#if OS_WINDOWS -// # include "os/core/win32/os_core_win32.h" -#elif OS_LINUX -# include "os/core/linux/os_core_linux.h" -#else -# error OS core layer not implemented for this operating system. -#endif - -#if OS_FEATURE_GRAPHICAL -# if OS_GFX_STUB -# include "os/gfx/stub/os_gfx_stub.h" -# elif OS_WINDOWS -# include "os/gfx/win32/os_gfx_win32.h" -# elif OS_LINUX -# include "os/gfx/linux/os_gfx_linux.h" -# else -# error OS graphical layer not implemented for this operating system. -# endif -#endif - -#endif // OS_INC_H diff --git a/src/radbin/radbin_main.c b/src/radbin/radbin_main.c index ee14fe07..816544f6 100644 --- a/src/radbin/radbin_main.c +++ b/src/radbin/radbin_main.c @@ -16,7 +16,6 @@ #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" -#include "os/os_inc.h" #include "rdi/rdi_local.h" #include "rdi_make/rdi_make_local.h" #include "coff/coff_inc.h" @@ -47,7 +46,6 @@ #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" -#include "os/os_inc.c" #include "rdi/rdi_local.c" #include "rdi_make/rdi_make_local.c" #include "coff/coff_inc.c" diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 7db1c57c..a6c1d83f 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -265,11 +265,11 @@ #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" -#include "os/os_inc.h" #include "artifact_cache/artifact_cache.h" #include "rdi/rdi_local.h" #include "rdi_make/rdi_make_local.h" #include "mdesk/mdesk.h" +#include "window_manager/window_manager_inc.h" #include "config/config_inc.h" #include "content/content.h" #include "file_stream/file_stream.h" @@ -318,11 +318,11 @@ #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" -#include "os/os_inc.c" #include "artifact_cache/artifact_cache.c" #include "rdi/rdi_local.c" #include "rdi_make/rdi_make_local.c" #include "mdesk/mdesk.c" +#include "window_manager/window_manager_inc.c" #include "config/config_inc.c" #include "content/content.c" #include "file_stream/file_stream.c" diff --git a/src/render/opengl/linux/egl/render_opengl_linux_egl.c b/src/render/opengl/linux/egl/render_opengl_linux_egl.c index 0e8a6f1d..2df7024b 100644 --- a/src/render/opengl/linux/egl/render_opengl_linux_egl.c +++ b/src/render/opengl/linux/egl/render_opengl_linux_egl.c @@ -79,7 +79,7 @@ r_ogl_os_init(CmdLine *cmdln) } internal R_Handle -r_ogl_os_window_equip(OS_Handle window) +r_ogl_os_window_equip(OS_Window window) { OS_LNX_Window *window_os = (OS_LNX_Window *)window.u64[0]; R_OGL_LNX_Window *w = r_ogl_lnx_state->free_window; @@ -163,7 +163,7 @@ r_ogl_os_window_equip(OS_Handle window) } internal void -r_ogl_os_window_unequip(OS_Handle os, R_Handle r) +r_ogl_os_window_unequip(OS_Window os, R_Handle r) { R_OGL_LNX_Window *w = (R_OGL_LNX_Window *)r.u64[0]; { @@ -173,7 +173,7 @@ r_ogl_os_window_unequip(OS_Handle os, R_Handle r) } internal void -r_ogl_os_select_window(OS_Handle os, R_Handle r) +r_ogl_os_select_window(OS_Window os, R_Handle r) { OS_LNX_Window *w = (OS_LNX_Window *)os.u64[0]; R_OGL_LNX_Window *w_r = (R_OGL_LNX_Window *)r.u64[0]; @@ -181,7 +181,7 @@ r_ogl_os_select_window(OS_Handle os, R_Handle r) } internal void -r_ogl_os_window_swap(OS_Handle os, R_Handle r) +r_ogl_os_window_swap(OS_Window os, R_Handle r) { OS_LNX_Window *w = (OS_LNX_Window *)os.u64[0]; R_OGL_LNX_Window *w_r = (R_OGL_LNX_Window *)r.u64[0]; diff --git a/src/render/opengl/linux/glx/render_opengl_linux_glx.c b/src/render/opengl/linux/glx/render_opengl_linux_glx.c index 6f835515..665403ea 100644 --- a/src/render/opengl/linux/glx/render_opengl_linux_glx.c +++ b/src/render/opengl/linux/glx/render_opengl_linux_glx.c @@ -79,27 +79,27 @@ r_ogl_os_init(CmdLine *cmdln) } internal R_Handle -r_ogl_os_window_equip(OS_Handle window) +r_ogl_os_window_equip(OS_Window window) { R_Handle result = {0}; return result; } internal void -r_ogl_os_window_unequip(OS_Handle os, R_Handle r) +r_ogl_os_window_unequip(OS_Window os, R_Handle r) { } internal void -r_ogl_os_select_window(OS_Handle os, R_Handle r) +r_ogl_os_select_window(OS_Window os, R_Handle r) { OS_LNX_Window *w = (OS_LNX_Window *)os.u64[0]; glXMakeCurrent(os_lnx_gfx_state->display, w->window, r_ogl_lnx_ctx); } internal void -r_ogl_os_window_swap(OS_Handle os, R_Handle r) +r_ogl_os_window_swap(OS_Window os, R_Handle r) { OS_LNX_Window *w = (OS_LNX_Window *)os.u64[0]; glXSwapBuffers(os_lnx_gfx_state->display, w->window); diff --git a/src/render/opengl/render_opengl.c b/src/render/opengl/render_opengl.c index 7f646b36..f985ae0b 100644 --- a/src/render/opengl/render_opengl.c +++ b/src/render/opengl/render_opengl.c @@ -201,14 +201,14 @@ r_init(CmdLine *cmdln) //- rjf: window setup/teardown r_hook R_Handle -r_window_equip(OS_Handle window) +r_window_equip(OS_Window window) { R_Handle result = r_ogl_os_window_equip(window); return result; } r_hook void -r_window_unequip(OS_Handle window, R_Handle window_equip) +r_window_unequip(OS_Window window, R_Handle window_equip) { r_ogl_os_window_unequip(window, window_equip); } @@ -341,7 +341,7 @@ r_end_frame(void) } r_hook void -r_window_begin_frame(OS_Handle os, R_Handle r) +r_window_begin_frame(OS_Window os, R_Handle r) { r_ogl_os_select_window(os, r); @@ -358,7 +358,7 @@ r_window_begin_frame(OS_Handle os, R_Handle r) } r_hook void -r_window_end_frame(OS_Handle os, R_Handle r) +r_window_end_frame(OS_Window os, R_Handle r) { for(R_OGL_FlushBuffer *flush_buffer = r_ogl_state->first_buffer_to_flush; flush_buffer != 0; flush_buffer = flush_buffer->next) { @@ -372,7 +372,7 @@ r_window_end_frame(OS_Handle os, R_Handle r) //- rjf: render pass submission r_hook void -r_window_submit(OS_Handle window, R_Handle window_equip, R_PassList *passes) +r_window_submit(OS_Window window, R_Handle window_equip, R_PassList *passes) { Rng2F32 viewport_rect = os_client_rect_from_window(window); Vec2F32 viewport_dim = dim_2f32(viewport_rect); diff --git a/src/render/opengl/render_opengl.h b/src/render/opengl/render_opengl.h index bdbdf051..cd7c3f2f 100644 --- a/src/render/opengl/render_opengl.h +++ b/src/render/opengl/render_opengl.h @@ -224,9 +224,9 @@ internal void r_ogl_debug_message_callback(GLenum source, GLenum type, GLuint id internal VoidProc *r_ogl_os_load_procedure(char *name); internal void r_ogl_os_init(CmdLine *cmdln); -internal R_Handle r_ogl_os_window_equip(OS_Handle window); -internal void r_ogl_os_window_unequip(OS_Handle os, R_Handle r); -internal void r_ogl_os_select_window(OS_Handle os, R_Handle r); -internal void r_ogl_os_window_swap(OS_Handle os, R_Handle r); +internal R_Handle r_ogl_os_window_equip(OS_Window window); +internal void r_ogl_os_window_unequip(OS_Window os, R_Handle r); +internal void r_ogl_os_select_window(OS_Window os, R_Handle r); +internal void r_ogl_os_window_swap(OS_Window os, R_Handle r); #endif // RENDER_OPENGL_H diff --git a/src/render/opengl/win32/render_opengl_win32.c b/src/render/opengl/win32/render_opengl_win32.c index e0951e4c..53b4641b 100644 --- a/src/render/opengl/win32/render_opengl_win32.c +++ b/src/render/opengl/win32/render_opengl_win32.c @@ -101,7 +101,7 @@ r_ogl_os_init(CmdLine *cmdline) } internal R_Handle -r_ogl_os_window_equip(OS_Handle window) +r_ogl_os_window_equip(OS_Window window) { //- rjf: unpack window OS_W32_Window *w = os_w32_window_from_handle(window); @@ -155,12 +155,12 @@ r_ogl_os_window_equip(OS_Handle window) } internal void -r_ogl_os_window_unequip(OS_Handle os, R_Handle r) +r_ogl_os_window_unequip(OS_Window os, R_Handle r) { } internal void -r_ogl_os_select_window(OS_Handle os, R_Handle r) +r_ogl_os_select_window(OS_Window os, R_Handle r) { OS_W32_Window *w = os_w32_window_from_handle(os); if(w != 0) @@ -172,7 +172,7 @@ r_ogl_os_select_window(OS_Handle os, R_Handle r) } internal void -r_ogl_os_window_swap(OS_Handle os, R_Handle r) +r_ogl_os_window_swap(OS_Window os, R_Handle r) { OS_W32_Window *w = os_w32_window_from_handle(os); if(w != 0) diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index cd79bb71..41df35d1 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -12,7 +12,6 @@ //- rjf: [h] #include "base/base_inc.h" -#include "os/os_inc.h" #include "content/content.h" #include "artifact_cache/artifact_cache.h" #include "file_stream/file_stream.h" @@ -21,7 +20,6 @@ //- rjf: [c] #include "base/base_inc.c" -#include "os/os_inc.c" #include "content/content.c" #include "artifact_cache/artifact_cache.c" #include "file_stream/file_stream.c" diff --git a/src/scratch/textperf.c b/src/scratch/textperf.c index 3c6c963c..3821698a 100644 --- a/src/scratch/textperf.c +++ b/src/scratch/textperf.c @@ -12,7 +12,6 @@ //- rjf: [h] #include "base/base_inc.h" -#include "os/os_inc.h" #include "render/render_inc.h" #include "font_provider/font_provider_inc.h" #include "font_cache/font_cache.h" @@ -20,7 +19,6 @@ //- rjf: [c] #include "base/base_inc.c" -#include "os/os_inc.c" #include "render/render_inc.c" #include "font_provider/font_provider_inc.c" #include "font_cache/font_cache.c" diff --git a/src/stap/stap_parse.c b/src/stap/stap_parse.c index 25e696af..0dfd55a8 100644 --- a/src/stap/stap_parse.c +++ b/src/stap/stap_parse.c @@ -69,6 +69,7 @@ stap_list_from_string(Arena *arena, String8 string) internal String8 stap_parse_args_x64(String8 string, STAP_Arg *arg_out) { + ARCH_Info *arch_info = arch_info_from_arch(Arch_x64); String8 error = str8_lit("unknown parse error"); U64 sep_pos = str8_find_needle(string, 0, str8_lit("@"), 0); if (sep_pos < string.size) { @@ -200,14 +201,9 @@ stap_parse_args_x64(String8 string, STAP_Arg *arg_out) index_str = str8_skip(index_str, 1); // parse base - B8 base_is_alias = 0; U32 base_reg_code = 0; if (base_str.size) { - base_reg_code = regs_reg_code_from_name(Arch_x64, base_str); - if (base_reg_code == 0) { - base_reg_code = regs_alias_code_from_name(Arch_x64, base_str); - base_is_alias = base_reg_code != 0; - } + base_reg_code = arch_reg_code_from_name(arch_info, base_str); if (base_reg_code == 0) { error = str8_lit("unknown base register"); goto operand_parse_exit; @@ -215,14 +211,9 @@ stap_parse_args_x64(String8 string, STAP_Arg *arg_out) } // parse index - B8 index_is_alias = 0; U32 index_reg_code = 0; if (index_str.size) { - index_reg_code = regs_reg_code_from_name(Arch_x64, index_str); - if (index_reg_code == 0) { - index_reg_code = regs_alias_code_from_name(Arch_x64, index_str); - index_is_alias = index_reg_code != 0; - } + index_reg_code = arch_reg_code_from_name(arch_info, index_str); if (index_reg_code == 0) { error = str8_lit("unknown index register"); } @@ -247,9 +238,7 @@ stap_parse_args_x64(String8 string, STAP_Arg *arg_out) arg.type = STAP_ArgType_MemoryRef; arg.memory_ref.disp = disp; arg.memory_ref.base.reg_code = base_reg_code; - arg.memory_ref.base.is_alias = base_is_alias; arg.memory_ref.index.reg_code = index_reg_code; - arg.memory_ref.index.is_alias = index_is_alias; arg.memory_ref.scale = scale; } // $imm @@ -296,19 +285,13 @@ stap_parse_args_x64(String8 string, STAP_Arg *arg_out) goto operand_parse_exit; } - B8 is_reg_alias = 0; - U32 reg_code = regs_reg_code_from_name(Arch_x64, reg_str); - if (reg_code == 0) { - reg_code = regs_alias_code_from_name(Arch_x64, reg_str); - is_reg_alias = reg_code != 0; - } + U32 reg_code = arch_reg_code_from_name(arch_info, reg_str); if (reg_code == 0) { error = str8_lit("invalid register name"); goto operand_parse_exit; } arg.type = STAP_ArgType_Reg; - arg.reg.is_alias = is_reg_alias; arg.reg.reg_code = reg_code; } else { goto operand_parse_exit; diff --git a/src/stap/stap_parse.h b/src/stap/stap_parse.h index 8d5164ca..301e66e0 100644 --- a/src/stap/stap_parse.h +++ b/src/stap/stap_parse.h @@ -29,17 +29,14 @@ typedef struct STAP_Arg U64 imm; struct { U32 reg_code; - B8 is_alias; } reg; struct { S64 disp; struct { U32 reg_code; - B8 is_alias; } base; struct { U32 reg_code; - B8 is_alias; } index; U64 scale; } memory_ref; diff --git a/src/stap/stap_test.c b/src/stap/stap_test.c index 7996ae44..155a4562 100644 --- a/src/stap/stap_test.c +++ b/src/stap/stap_test.c @@ -5,12 +5,10 @@ #define BUILD_CONSOLE_INTERFACE 1 #include "base/base_inc.h" -#include "os/os_inc.h" #include "arch/arch_inc.h" #include "stap/stap_parse.h" #include "base/base_inc.c" -#include "os/os_inc.c" #include "arch/arch_inc.c" #include "stap/stap_parse.c" diff --git a/src/strip_lib_debug/strip_lib_debug.c b/src/strip_lib_debug/strip_lib_debug.c index 6bb5f1e5..4bc25e28 100644 --- a/src/strip_lib_debug/strip_lib_debug.c +++ b/src/strip_lib_debug/strip_lib_debug.c @@ -8,7 +8,6 @@ // Headers #include "base/base_inc.h" -#include "os/os_inc.h" #include "coff/coff.h" #include "coff/coff_parse.h" @@ -16,7 +15,6 @@ // Implementations #include "base/base_inc.c" -#include "os/os_inc.c" #include "coff/coff.c" #include "coff/coff_parse.c" diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index fe3e0819..71f530d8 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -24,11 +24,11 @@ #include "linker/hash_table.h" #include "linker/lf_hash_table.h" #include "linker/base_ext/base_bit_array.h" -#include "os/os_inc.h" #include "artifact_cache/artifact_cache.h" #include "rdi/rdi_local.h" #include "rdi_make/rdi_make_local.h" #include "mdesk/mdesk.h" +#include "window_manager/window_manager_inc.h" #include "config/config_inc.h" #include "content/content.h" #include "file_stream/file_stream.h" @@ -98,11 +98,11 @@ #include "linker/hash_table.c" #include "linker/lf_hash_table.c" #include "linker/base_ext/base_bit_array.c" -#include "os/os_inc.c" #include "artifact_cache/artifact_cache.c" #include "rdi/rdi_local.c" #include "rdi_make/rdi_make_local.c" #include "mdesk/mdesk.c" +#include "window_manager/window_manager_inc.c" #include "config/config_inc.c" #include "content/content.c" #include "file_stream/file_stream.c" diff --git a/src/demon/win32/demon_core_win32.c b/src/win32/demon/win32_demon.c similarity index 97% rename from src/demon/win32/demon_core_win32.c rename to src/win32/demon/win32_demon.c index 0084818d..528f01e5 100644 --- a/src/demon/win32/demon_core_win32.c +++ b/src/win32/demon/win32_demon.c @@ -1,3102 +1,3102 @@ -// Copyright (c) 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; - { - U32 gen = 0; - if(e != 0) - { - SLLStackPop(dmn_w32_shared->entities_first_free); - gen = e->gen; - } - else - { - e = push_array_no_zero(dmn_w32_shared->entities_arena, DMN_W32_Entity, 1); - dmn_w32_shared->entities_count += 1; - } - 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); - } - t->e->kind = DMN_W32_EntityKind_Null; - - // 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)) - { - DWORD error = GetLastError(); - log_infof("'Win32 ReadProcessMemory failure': { [0x%I64x, 0x%I64x), code: %i }\n", range.min, range.max, error); - // NOTE(rjf): I have discovered that `actual_read` is *NOT* guaranteed to have - // a usable value if `ReadProcessMemory` fails! - // bytes_read += actual_read; - (void)error; - 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; - } - 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_FileHeader 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); - got_coff_header = dmn_w32_process_read_struct(process, coff_header_off, &coff_header); - } - } - - DMN_W32_ImageInfo result = zero_struct; - if(got_coff_header) - { - U64 optional_off = coff_header_off + sizeof(COFF_FileHeader); - - Arch arch = arch_from_coff_machine(coff_header.machine); - U64 image_size = 0; - U64 data_dir_count = 0; - U64 data_dir_off = max_U64; - U64 tls_index = max_U64; - - // parse optional header - if(pe_has_plus_header(coff_header.machine)) - { - PE_OptionalHeader32Plus opt_header = {0}; - if(dmn_w32_process_read_struct(process, optional_off, &opt_header)) - { - image_size = opt_header.sizeof_image; - data_dir_count = opt_header.data_dir_count; - data_dir_off = optional_off + sizeof(opt_header); - } - else { Assert(0 && "failed to read optional header"); } - } - else - { - PE_OptionalHeader32 opt_header = {0}; - if(dmn_w32_process_read_struct(process, optional_off, &opt_header)) - { - image_size = opt_header.sizeof_image; - data_dir_count = opt_header.data_dir_count; - data_dir_off = optional_off + sizeof(opt_header); - } - else { Assert(0 && "failed to read optional header"); } - } - - // extract TLS index - if(PE_DataDirectoryIndex_TLS < data_dir_count) - { - U64 tls_dir_vaddr = data_dir_off + PE_DataDirectoryIndex_TLS * sizeof(PE_DataDirectory); - PE_DataDirectory tls_dir = {0}; - if(dmn_w32_process_read_struct(process, tls_dir_vaddr, &tls_dir)) - { - if(pe_has_plus_header(coff_header.machine)) - { - if(tls_dir.virt_size == sizeof(PE_TLSHeader64)) - { - PE_TLSHeader64 tls_header = {0}; - if(dmn_w32_process_read_struct(process, base_vaddr + tls_dir.virt_off, &tls_header)) - { - U64 tls_index64 = 0; - if(dmn_w32_process_read_struct(process, tls_header.index_address, &tls_index64)) - { - tls_index = tls_index64; - } - else { Assert(0 && "failed to read TLS Index 64"); } - } - else { Assert(0 && "failed to read TLS Header 64"); } - } - } - else - { - if(tls_dir.virt_size == sizeof(PE_TLSHeader32)) - { - PE_TLSHeader32 tls_header = {0}; - if(dmn_w32_process_read_struct(process, base_vaddr + tls_dir.virt_off, &tls_header)) - { - U32 tls_index32 = 0; - if(dmn_w32_process_read_struct(process, tls_header.index_address, &tls_index32)) - { - tls_index = tls_index32; - } - else { Assert(0 && "failed to read TLS Index 32"); } - } - else { Assert(0 && "failed to read TLS Header32"); } - } - } - } - else { Assert(0 && "failed to read TLS directory"); } - } - - // fill out result - result.arch = arch; - result.size = image_size; - result.tls_index = tls_index; - } - else { Assert(0 && "failed to find COFF file header"); } - - return result; -} - -//- rjf: threads - -internal B32 -dmn_w32_thread_read_reg_block(Arch arch, HANDLE thread, void *reg_block) -{ - B32 result = 0; - ProfBeginFunction(); - switch(arch) - { - //////////////////////////// - //- rjf: unimplemented win32/arch combos - // - case Arch_Null: - case Arch_COUNT: - {}break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: - {NotImplemented;}break; - - //////////////////////////// - //- rjf: x64 - // - case Arch_x64: - { - Temp scratch = scratch_begin(0, 0); - X64_RegBlock *dst = (X64_RegBlock *)reg_block; - - //- rjf: unpack info about available features - U32 feature_mask = GetEnabledXStateFeatures(); - B32 xstate_enabled = (feature_mask & (XSTATE_MASK_AVX | XSTATE_MASK_AVX512 | XSTATE_MASK_CET_U)) != 0; - - //- rjf: set up context - CONTEXT *ctx = 0; - U32 ctx_flags = DMN_W32_CTX_X64_ALL | (xstate_enabled ? DMN_W32_CTX_INTEL_XSTATE : 0); - 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 - if (xstate_enabled) - { - SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX | XSTATE_MASK_AVX512 | XSTATE_MASK_CET_U); - } - - //- rjf: get thread context - if(!GetThreadContext(thread, ctx)) - { - ctx = 0; - } - - //- rjf: bad context -> abort - if(ctx == 0) - { - break; - } - result = 1; - - DWORD64 xstate_mask = 0; - GetXStateFeaturesMask(ctx, &xstate_mask); - - //- rjf: convert context -> X64_RegBlock - XSAVE_FORMAT *xsave = &ctx->FltSave; - dst->rax = ctx->Rax; - dst->rcx = ctx->Rcx; - dst->rdx = ctx->Rdx; - dst->rbx = ctx->Rbx; - dst->rsp = ctx->Rsp; - dst->rbp = ctx->Rbp; - dst->rsi = ctx->Rsi; - dst->rdi = ctx->Rdi; - dst->r8 = ctx->R8; - dst->r9 = ctx->R9; - dst->r10 = ctx->R10; - dst->r11 = ctx->R11; - dst->r12 = ctx->R12; - dst->r13 = ctx->R13; - dst->r14 = ctx->R14; - dst->r15 = ctx->R15; - dst->rip = ctx->Rip; - dst->cs = ctx->SegCs; - dst->ds = ctx->SegDs; - dst->es = ctx->SegEs; - dst->fs = ctx->SegFs; - dst->gs = ctx->SegGs; - dst->ss = ctx->SegSs; - dst->dr0 = ctx->Dr0; - dst->dr1 = ctx->Dr1; - dst->dr2 = ctx->Dr2; - dst->dr3 = ctx->Dr3; - dst->dr6 = ctx->Dr6; - dst->dr7 = ctx->Dr7; - // NOTE(rjf): this bit is "supposed to always be 1", according to old info. - // may need to be investigated. - dst->rflags = ctx->EFlags | 0x2; - dst->fcw = xsave->ControlWord; - dst->fsw = xsave->StatusWord; - dst->ftw = xsave->TagWord; - dst->fop = xsave->ErrorOpcode; - MemoryCopy(&dst->fip, &xsave->ErrorOffset, sizeof(U64)); - MemoryCopy(&dst->fdp, &xsave->DataOffset, sizeof(U64)); - dst->mxcsr = xsave->MxCsr; - dst->mxcsr_mask = xsave->MxCsr_Mask; - { - M128A *float_s = xsave->FloatRegisters; - U80 *float_d = &dst->st0; - for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) - { - MemoryCopy(float_d, float_s, sizeof(*float_d)); - } - } - - // SSE registers are always available in x64 - { - M128A *xmm_s = xsave->XmmRegisters; - U512 *zmm_d = &dst->zmm0; - for(U32 n = 0; n < 16; n += 1, xmm_s += 1, zmm_d += 1) - { - MemoryCopy(zmm_d, xmm_s, sizeof(*xmm_s)); - } - } - - // AVX - if(xstate_mask & XSTATE_MASK_AVX) - { - DWORD avx_length = 0; - U8 *avx_s = (U8*)LocateXStateFeature(ctx, XSTATE_AVX, &avx_length); - if(avx_length == 16 * sizeof(U128)) - { - U512 *zmm_d = &dst->zmm0; - for(U32 n = 0; n < 16; n += 1, avx_s += sizeof(U128), zmm_d += 1) - { - MemoryCopy(&zmm_d->u8[16], avx_s, sizeof(U128)); - } - } - } - else - { - U512 *zmm_d = &dst->zmm0; - for(U32 n = 0; n < 16; n += 1, zmm_d += 1) - { - MemoryZero(&zmm_d->u8[16], sizeof(U128)); - } - } - - // AVX-512 - if(xstate_mask & XSTATE_MASK_AVX512) - { - // rjf: kmask - DWORD kmask_length = 0; - U64 *kmask_s = (U64*)LocateXStateFeature(ctx, XSTATE_AVX512_KMASK, &kmask_length); - if(kmask_length == 8 * sizeof(U64)) - { - U64 *kmask_d = &dst->k0; - for(U32 n = 0; n < 8; n += 1, kmask_s += 1, kmask_d += 1) - { - MemoryCopy(kmask_d, kmask_s, sizeof(*kmask_s)); - } - } - - // rjf: zmmh - DWORD avx512h_length = 0; - U8 *avx512h_s = (U8*)LocateXStateFeature(ctx, XSTATE_AVX512_ZMM_H, &avx512h_length); - if(avx512h_length == 16 * sizeof(U256)) - { - U512 *zmmh_d = &dst->zmm0; - for(U32 n = 0; n < 16; n += 1, avx512h_s += sizeof(U256), zmmh_d += 1) - { - MemoryCopy(&zmmh_d->u8[32], avx512h_s, sizeof(U256)); - } - } - - // rjf: zmm - DWORD avx512_length = 0; - U8 *avx512_s = (U8 *)LocateXStateFeature(ctx, XSTATE_AVX512_ZMM, &avx512_length); - if(avx512_length == 16 * sizeof(U512)) - { - U512 *zmm_d = &dst->zmm16; - for(U32 n = 0; n < 16; n += 1, avx512_s += sizeof(U512), zmm_d += 1) - { - MemoryCopy(zmm_d, avx512_s, sizeof(U512)); - } - } - } - else - { - U64 *kmask_d = &dst->k0; - for(U32 n = 0; n < 8; n += 1, kmask_d += 1) - { - MemoryZero(kmask_d, sizeof(*kmask_d)); - } - U512 *zmmh_d = &dst->zmm0; - for(U32 n = 0; n < 16; n += 1, zmmh_d += 1) - { - MemoryZero(&zmmh_d->u8[32], sizeof(U256)); - } - U512 *zmm_d = &dst->zmm16; - for(U32 n = 0; n < 16; n += 1, zmm_d += 1) - { - MemoryZero(zmm_d, sizeof(*zmm_d)); - } - } - - // CET / Shadow Stack - if(xstate_mask & XSTATE_MASK_CET_U) - { - DWORD cet_length = 0; - XSAVE_CET_U_FORMAT *cet = LocateXStateFeature(ctx, XSTATE_CET_U, &cet_length); - if (cet_length == sizeof(*cet)) - { - dst->cetmsr = cet->Ia32CetUMsr; - dst->cetssp = cet->Ia32Pl3SspMsr; - } - } - - scratch_end(scratch); - }break; - } - ProfEnd(); - return result; -} - -internal B32 -dmn_w32_thread_write_reg_block(Arch arch, HANDLE thread, void *reg_block) -{ - B32 result = 0; - ProfBeginFunction(); - switch(arch) - { - //////////////////////////// - //- rjf: unimplemented win32/arch combos - // - case Arch_Null: - case Arch_COUNT: - {}break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: - {NotImplemented;}break; - - //////////////////////////// - //- rjf: x64 - // - case Arch_x64: - { - Temp scratch = scratch_begin(0, 0); - X64_RegBlock *src = (X64_RegBlock *)reg_block; - - //- rjf: unpack info about available features - U32 feature_mask = GetEnabledXStateFeatures(); - B32 xstate_enabled = (feature_mask & (XSTATE_MASK_AVX | XSTATE_MASK_AVX512)) != 0; - - //- rjf: set up context - CONTEXT *ctx = 0; - U32 ctx_flags = DMN_W32_CTX_X64_ALL | (xstate_enabled ? DMN_W32_CTX_INTEL_XSTATE : 0); - 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 - if (xstate_enabled) - { - SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX | XSTATE_MASK_AVX512); - } - - //- rjf: bad context -> abort - if(ctx == 0) - { - break; - } - - //- rjf: convert X64_RegBlock -> CONTEXT - XSAVE_FORMAT *fxsave = &ctx->FltSave; - ctx->ContextFlags = ctx_flags; - ctx->MxCsr = src->mxcsr & src->mxcsr_mask; - ctx->Rax = src->rax; - ctx->Rcx = src->rcx; - ctx->Rdx = src->rdx; - ctx->Rbx = src->rbx; - ctx->Rsp = src->rsp; - ctx->Rbp = src->rbp; - ctx->Rsi = src->rsi; - ctx->Rdi = src->rdi; - ctx->R8 = src->r8; - ctx->R9 = src->r9; - ctx->R10 = src->r10; - ctx->R11 = src->r11; - ctx->R12 = src->r12; - ctx->R13 = src->r13; - ctx->R14 = src->r14; - ctx->R15 = src->r15; - ctx->Rip = src->rip; - ctx->SegCs = src->cs; - ctx->SegDs = src->ds; - ctx->SegEs = src->es; - ctx->SegFs = src->fs; - ctx->SegGs = src->gs; - ctx->SegSs = src->ss; - ctx->Dr0 = src->dr0; - ctx->Dr1 = src->dr1; - ctx->Dr2 = src->dr2; - ctx->Dr3 = src->dr3; - ctx->Dr6 = src->dr6; - ctx->Dr7 = src->dr7; - ctx->EFlags = src->rflags; - fxsave->ControlWord = src->fcw; - fxsave->StatusWord = src->fsw; - fxsave->TagWord = src->ftw; - fxsave->ErrorOpcode = src->fop; - MemoryCopy(&fxsave->ErrorOffset, &src->fip, sizeof(U64)); - MemoryCopy(&fxsave->DataOffset, &src->fdp, sizeof(U64)); - { - M128A *float_d = fxsave->FloatRegisters; - U80 *float_s = &src->st0; - for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) - { - MemoryCopy(float_d, float_s, 10); - } - } - - // SSE registers are always available in x64 - { - M128A *xmm_d = fxsave->XmmRegisters; - U512 *zmm_s = &src->zmm0; - for(U32 n = 0; n < 16; n += 1, xmm_d += 1, zmm_s += 1) - { - MemoryCopy(xmm_d, zmm_s, sizeof(*xmm_d)); - } - } - - // AVX - if(feature_mask & XSTATE_MASK_AVX) - { - DWORD avx_length = 0; - U8* avx_d = (U8*)LocateXStateFeature(ctx, XSTATE_AVX, &avx_length); - if(avx_length == 16 * sizeof(U128)) - { - U512 *zmm_s = &src->zmm0; - for(U32 n = 0; n < 16; n += 1, avx_d += sizeof(U128), zmm_s += 1) - { - MemoryCopy(avx_d, &zmm_s->u8[16], sizeof(U128)); - } - } - } - - // AVX-512 - if(feature_mask & XSTATE_MASK_AVX512) - { - DWORD kmask_length = 0; - U64* kmask_d = (U64*)LocateXStateFeature(ctx, XSTATE_AVX512_KMASK, &kmask_length); - if(kmask_length == 8 * sizeof(*kmask_d)) - { - U64 *kmask_s = &src->k0; - for(U32 n = 0; n < 8; n += 1, kmask_s += 1, kmask_d += 1) - { - MemoryCopy(kmask_d, kmask_s, sizeof(*kmask_d)); - } - } - - DWORD avx512h_length = 0; - U8* avx512h_d = (U8*)LocateXStateFeature(ctx, XSTATE_AVX512_ZMM_H, &avx512h_length); - if(avx512h_length == 16 * sizeof(U256)) - { - U512 *zmmh_s = &src->zmm0; - for(U32 n = 0; n < 16; n += 1, avx512h_d += sizeof(U256), zmmh_s += 1) - { - MemoryCopy(avx512h_d, &zmmh_s->u8[32], sizeof(U256)); - } - } - - DWORD avx512_length = 0; - U8* avx512_d = (U8*)LocateXStateFeature(ctx, XSTATE_AVX512_ZMM, &avx512_length); - if(avx512_length == 16 * sizeof(U512)) - { - U512 *zmm_s = &src->zmm16; - for(U32 n = 0; n < 16; n += 1, avx512_d += sizeof(U512), zmm_s += 1) - { - MemoryCopy(avx512_d, zmm_s, sizeof(U512)); - } - } - } - - //- rjf: set thread context - if(SetThreadContext(thread, ctx)) - { - result = 1; - } - scratch_end(scratch); - }break; - } - ProfEnd(); - 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 = mutex_alloc(); - dmn_w32_shared->detach_arena = arena_alloc(); - dmn_w32_shared->entities_arena = arena_alloc(.reserve_size = GB(8), .commit_size = 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) -{ - MutexScope(dmn_w32_shared->access_mutex) - { - dmn_w32_shared->access_run_state = 1; - } -} - -internal void -dmn_ctrl_exclusive_access_end(void) -{ - MutexScope(dmn_w32_shared->access_mutex) - { - dmn_w32_shared->access_run_state = 0; - } -} - -internal U32 -dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) -{ - Temp scratch = scratch_begin(0, 0); - U32 result = 0; - DMN_AccessScope - { - //- rjf: produce exe / arguments string - String8 cmd = {0}; - if(params->cmd_line.first != 0) - { - String8List args = {0}; - String8 exe_path = params->cmd_line.first->string; - String8List exe_path_parts = str8_split_path(scratch.arena, exe_path); - exe_path = str8_list_join(scratch.arena, &exe_path_parts, &(StringJoin){.sep = str8_lit("\\")}); - str8_list_pushf(scratch.arena, &args, "\"%S\"", exe_path); - for(String8Node *n = params->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 = params->env; - if(params->inherit_env != 0) - { - MemoryZeroStruct(&all_opts); - str8_list_push(scratch.arena, &all_opts, str8_lit("_NO_DEBUG_HEAP=1")); - for(String8Node *n = params->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, params->path); - String16 env16 = str16_from_8(scratch.arena, env); - - //- rjf: launch - DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; - if(params->debug_subprocesses) - { - creation_flags |= DEBUG_PROCESS; - } - else - { - creation_flags |= DEBUG_ONLY_THIS_PROCESS; - } - BOOL inherit_handles = 0; - STARTUPINFOW startup_info = {sizeof(startup_info)}; - if(!file_match(params->stdout_file, file_zero())) - { - HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0]; - startup_info.hStdOutput = stdout_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - if(!file_match(params->stderr_file, file_zero())) - { - HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0]; - startup_info.hStdError = stderr_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - if(!file_match(params->stdin_file, file_zero())) - { - HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0]; - startup_info.hStdInput = stdin_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - PROCESS_INFORMATION process_info = {0}; - if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 1, creation_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) - { - log_user_errorf("Only 64-bit applications can be debugged currently."); - 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 - { - DWORD error = GetLastError(); - LPWSTR message = 0; - FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, error, MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), (LPWSTR)&message, 0, 0); - String8 message8 = message ? str8_from_16(scratch.arena, str16_cstring(message)) : str8_lit("unknown error"); - LocalFree(message); - - log_user_errorf("There was an error starting %S: %S", params->cmd_line.first->string, message8); - } - } - 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; - -#if 0 - // TODO(rjf): JIT debugging info - { - typedef struct JIT_DEBUG_INFO JIT_DEBUG_INFO; - struct JIT_DEBUG_INFO - { - DWORD dwSize; - DWORD dwProcessorArchitecture; - DWORD dwThreadID; - DWORD dwReserved0; - ULONG64 lpExceptionAddress; - ULONG64 lpExceptionRecord; - ULONG64 lpContextRecord; - }; - } -#endif - } - 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: get single step thread's context (x64 single-step-set fast path) - // - CONTEXT *single_step_thread_ctx = 0; - if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); - Arch arch = thread->arch; - switch(arch) - { - default:{}break; - case Arch_x64: - { - U32 ctx_flags = DMN_W32_CTX_X64|DMN_W32_CTX_INTEL_CONTROL; - 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, &single_step_thread_ctx, &size)) - { - single_step_thread_ctx = 0; - } - } - }break; - } - } - - ////////////////////////// - //- rjf: set single step bit - // - if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) ProfScope("set single step bit") - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); - Arch arch = thread->arch; - switch(arch) - { - //- rjf: unimplemented win32/arch combos - case Arch_Null: - case Arch_COUNT: - {}break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: - {NotImplemented;}break; - - //- rjf: x64 - case Arch_x64: - { - if(!GetThreadContext(thread->handle, single_step_thread_ctx)) - { - single_step_thread_ctx = 0; - } - if(single_step_thread_ctx != 0) - { - U64 rflags = single_step_thread_ctx->EFlags|0x2; - U64 new_rflags = rflags | 0x100; - single_step_thread_ctx->EFlags = new_rflags; - SetThreadContext(thread->handle, single_step_thread_ctx); - } - }break; - } - } - - ////////////////////////// - //- rjf: write all traps into memory - // - U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, ctrls->traps.trap_count); - ProfScope("write all traps into memory") - { - 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; - if(trap->flags == 0) - { - 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: gather all flagged traps, bucketed by process - // - typedef struct DMN_FlaggedTrapTask DMN_FlaggedTrapTask; - struct DMN_FlaggedTrapTask - { - DMN_FlaggedTrapTask *next; - DMN_Handle process; - DMN_TrapChunkList traps; - }; - DMN_FlaggedTrapTask *first_flagged_trap_task = 0; - DMN_FlaggedTrapTask *last_flagged_trap_task= 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) - { - DMN_Trap *trap = n->v+n_idx; - if(trap->flags != 0) - { - DMN_FlaggedTrapTask *task = 0; - for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next) - { - if(dmn_handle_match(t->process, trap->process)) - { - task = t; - break; - } - } - if(task == 0) - { - task = push_array(scratch.arena, DMN_FlaggedTrapTask, 1); - SLLQueuePush(first_flagged_trap_task, last_flagged_trap_task, task); - task->process = trap->process; - } - B32 already_in_task = 0; - for(DMN_TrapChunkNode *n = task->traps.first; n != 0; n = n->next) - { - for(U64 n_idx = 0; n_idx < n->count; n_idx += 1) - { - if(n->v[n_idx].id == trap->id) - { - already_in_task = 1; - goto end_look_for_existing_trap_in_task; - } - } - } - end_look_for_existing_trap_in_task:; - if(!already_in_task) - { - dmn_trap_chunk_list_push(scratch.arena, &task->traps, 8, trap); - } - } - } - } - - ////////////////////////// - //- rjf: write all debug register states, for flagged-traps - // - ProfScope("write all debug register states, for flagged-traps") - { - //- rjf: for each flagged trap task, iterate all threads in the - // associated process, and prepare debug registers accordingly - for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next) - { - DMN_Handle process = t->process; - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - for(DMN_W32_Entity *child = process_entity->first; - child != &dmn_w32_entity_nil; - child = child->next) - { - if(child->kind == DMN_W32_EntityKind_Thread) - { - switch(child->arch) - { - default:{}break; - - //- rjf: x64 - case Arch_x64: - { - X64_RegBlock regs = {0}; - dmn_w32_thread_read_reg_block(child->arch, child->handle, ®s); - { - U64 trap_idx = 0; - for(DMN_TrapChunkNode *n = t->traps.first; n != 0; n = n->next) - { - for(U64 n_idx = 0; n_idx < n->count && trap_idx < 4; n_idx += 1, trap_idx += 1) - { - DMN_Trap *trap = &n->v[n_idx]; - U64 *addr_reg = ®s.dr0; - switch(trap_idx) - { - default:{}break; - case 0:{addr_reg = ®s.dr0;}break; - case 1:{addr_reg = ®s.dr1;}break; - case 2:{addr_reg = ®s.dr2;}break; - case 3:{addr_reg = ®s.dr3;}break; - } - addr_reg[0] = trap->vaddr; - regs.dr7 |= bit9|bit10|bit11; - regs.dr7 |= (1ull << (trap_idx*2)); - // NOTE(rjf): global-enable regs.dr7.u64 |= (1ull << (trap_idx*2+1)); - regs.dr7 &= ~((U64)(bit17|bit18|bit19|bit20) << (trap_idx*4)); - regs.dr7 &= ~((U64)(bit15|bit16)); - switch(trap->flags) - { - case DMN_TrapFlag_BreakOnExecute: - default:{}break; - case DMN_TrapFlag_BreakOnWrite: - case DMN_TrapFlag_BreakOnWrite|DMN_TrapFlag_BreakOnExecute: - { - regs.dr7 |= ((U64)bit17) << (trap_idx*4); - }break; - case DMN_TrapFlag_BreakOnRead|DMN_TrapFlag_BreakOnWrite|DMN_TrapFlag_BreakOnExecute: - case DMN_TrapFlag_BreakOnRead|DMN_TrapFlag_BreakOnWrite: - case DMN_TrapFlag_BreakOnRead: - { - regs.dr7 |= (((U64)bit17) << (trap_idx*4)); - regs.dr7 |= (((U64)bit18) << (trap_idx*4)); - }break; - } - switch(trap->size) - { - case 1: - default:{}break; - case 2: - { - regs.dr7 |= (((U64)bit19) << (trap_idx*4)); - }break; - case 4: - { - regs.dr7 |= (((U64)bit19) << (trap_idx*4)); - regs.dr7 |= (((U64)bit20) << (trap_idx*4)); - }break; - case 8: - { - regs.dr7 |= (((U64)bit20) << (trap_idx*4)); - }break; - } - } - } - } - dmn_w32_thread_write_reg_block(child->arch, child->handle, ®s); - }break; - } - } - } - } - } - - ////////////////////////// - //- rjf: produce list of threads which will run - // - DMN_W32_EntityNode *first_run_thread = 0; - DMN_W32_EntityNode *last_run_thread = 0; - ProfScope("produce list of threads which will run") - { - //- 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)) - { - is_frozen = !dmn_handle_match(dmn_w32_handle_from_entity(thread), ctrls->single_step_thread); - } - - // 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: loop, consume win32 debug events until we produce the relevant demon events - // - B32 priority_mode = !dmn_handle_match(dmn_handle_zero(), ctrls->priority_thread); - B32 did_priority_mode = priority_mode; - B32 do_threads_resume = 1; - DMN_W32_EntityNode *first_ran_thread = 0; - DMN_W32_EntityNode *last_ran_thread = 0; - U64 begin_time = now_time_us(); - String8List debug_strings = {0}; - DMN_Event *debug_strings_event = 0; - for(B32 keep_going = 1; keep_going;) - { - keep_going = 0; - - //////////////////////// - //- rjf: resume threads that we want to run - // - // if priority mode? => first, just resume priority thread - // if not? => resume all non-priority threads - // - if(do_threads_resume) ProfScope("resume threads that we want to run") - { - do_threads_resume = 0; - for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) - { - DMN_W32_Entity *thread = n->v; - B32 thread_is_priority = dmn_handle_match(dmn_w32_handle_from_entity(thread), ctrls->priority_thread); - if((priority_mode && !thread_is_priority) || - (!priority_mode && did_priority_mode && thread_is_priority)) - { - continue; - } - DWORD resume_result = ResumeThread(thread->handle); - DMN_W32_EntityNode *n = push_array(scratch.arena, DMN_W32_EntityNode, 1); - SLLQueuePush(first_ran_thread, last_ran_thread, n); - n->v = thread; - 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; - } - if(priority_mode && thread_is_priority) - { - break; - } - } - } - - //////////////////////// - //- rjf: choose win32 resume code - // - DWORD resume_code = DBG_CONTINUE; - { - if(dmn_w32_shared->exception_not_handled && !ctrls->ignore_previous_exception) - { - log_infof("using DBG_EXCEPTION_NOT_HANDLED\n"); - resume_code = DBG_EXCEPTION_NOT_HANDLED; - } - else - { - log_infof("using DBG_CONTINUE\n"); - } - dmn_w32_shared->exception_not_handled = 0; - } - - //////////////////////// - //- rjf: inform windows that we're resuming, run, & obtain next debug event - // - DEBUG_EVENT evt = {0}; - B32 evt_good = 0; - ProfScope("inform windows that we're resuming, run, & obtain next debug event") - { - 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); - DWORD error = GetLastError(); - dmn_w32_shared->resume_needed = 0; - dmn_w32_shared->resume_tid = 0; - dmn_w32_shared->resume_pid = 0; - } - if(resume_good) - { - DWORD wait_ms = 100; - if(priority_mode) - { - wait_ms = 30; - } - evt_good = !!WaitForDebugEvent(&evt, wait_ms); - if(evt_good) - { - dmn_w32_shared->resume_needed = 1; - dmn_w32_shared->resume_pid = evt.dwProcessId; - dmn_w32_shared->resume_tid = evt.dwThreadId; - } - else - { - DWORD err = GetLastError(); - (void)err; - keep_going = 1; - } - if(priority_mode) - { - priority_mode = 0; - do_threads_resume = 1; - } - } - } - - //////////////////////// - //- rjf: process the new event - // - if(evt_good) ProfScope("process the new event") - { - 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; - e->tls_model = DMN_TlsModel_WinodwsNt; - } - - // 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); - e->tls_index = image_info.tls_index; - } - } - }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: if this was the process we were going to resume, then we will - // just not resume, and wait for another debug event - if(evt.dwProcessId == dmn_w32_shared->resume_pid) - { - dmn_w32_shared->resume_needed = 0; - dmn_w32_shared->resume_tid = 0; - dmn_w32_shared->resume_pid = 0; - } - - // rjf: generate events for children - for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next) - { - switch(child->kind) - { - default:{}break; - 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; - keep_going = 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); - e->tls_index = image_info.tls_index; - } - }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): Further notes on MULTITHREADED STEPPING ACCESS VIOLATION - // EVENTS! @rjf @rjf @rjf - // (2024/05/29): - // - // Just adding another comment here to document that the above long - // comment went completely unnoticed by me during a pass over demon, - // and I had removed the proper rollback stuff here without reading - // the above comment. So this comment just serves to make that - // original comment even heftier. - - //- 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; - U64 user_trap_id = 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; - user_trap_id = n->v[idx].id; - 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 = (hit_user_trap || (is_trap && !hit_explicit_trap)); - - //- rjf: roll back thread's instruction pointer - if(should_do_rollback) ProfScope("roll back thread's instruction pointer") - { - switch(thread->arch) - { - //- rjf: default, general path - default: - { - Temp temp = temp_begin(scratch.arena); - ARCH_Info *arch_info = arch_info_from_arch(thread->arch); - U64 regs_block_size = arch_info->reg_block_size; - void *regs_block = push_array(scratch.arena, U8, regs_block_size); - if(dmn_w32_thread_read_reg_block(thread->arch, thread->handle, regs_block)) - { - arch_reg_block_write_ip(arch_info, regs_block, instruction_pointer); - dmn_w32_thread_write_reg_block(thread->arch, thread->handle, regs_block); - } - temp_end(temp); - }break; - - //- rjf: x64 (fastpath) - case Arch_x64: - { - CONTEXT *ctx = 0; - U32 ctx_flags = DMN_W32_CTX_X64|DMN_W32_CTX_INTEL_CONTROL; - 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; - } - } - if(!GetThreadContext(thread->handle, ctx)) - { - ctx = 0; - } - if(ctx != 0) - { - U64 rip = ctx->Rip; - U64 new_rip = instruction_pointer; - ctx->Rip = new_rip; - SetThreadContext(thread->handle, ctx); - } - }break; - } - } - - //- 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; - e->user_data = user_trap_id; - - //- 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; - if(exception->ExceptionInformation[0] == DMN_W32_FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS) - { - // TODO(rjf): this is a shadow stack violation - this can imply that the spoof was hit. - // need to handle this correctly in the ctrl layer when stepping w/ a spoof set. - // - // @shadow_stack_step - } - }break; - - //- rjf: fill single-step event info - case DMN_W32_EXCEPTION_SINGLE_STEP: - { - e->kind = DMN_EventKind_SingleStep; - - // NOTE(rjf): data breakpoints are reported via single-steps - // over the instructions which caused the breakpoint to be - // hit - so if we have data breakpoints set, we need to - // check this thread's debug registers, to determine if this - // is a regular single-step or a data breakpoint hit. - { - // rjf: first determine the flagged trap index - U64 flagged_trap_idx = 0; - switch(thread->arch) - { - default:{NotImplemented;}break; - case Arch_x64: - { - X64_RegBlock regs = {0}; - dmn_w32_thread_read_reg_block(thread->arch, thread->handle, ®s); - if(regs.dr6 & 0xF) - { - e->kind = DMN_EventKind_Breakpoint; - if(0){} - else if(regs.dr7 & (1ull<<0) && regs.dr6 & (1ull<<0)) { flagged_trap_idx = 0; e->address = regs.dr0; } - else if(regs.dr7 & (1ull<<2) && regs.dr6 & (1ull<<1)) { flagged_trap_idx = 1; e->address = regs.dr1; } - else if(regs.dr7 & (1ull<<4) && regs.dr6 & (1ull<<2)) { flagged_trap_idx = 2; e->address = regs.dr2; } - else if(regs.dr7 & (1ull<<8) && regs.dr6 & (1ull<<3)) { flagged_trap_idx = 3; e->address = regs.dr3; } - } - }break; - } - - // rjf: find the flagged trap task for this thread's process - DMN_W32_Entity *process = thread->parent; - DMN_FlaggedTrapTask *task = 0; - for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next) - { - if(dmn_handle_match(t->process, dmn_w32_handle_from_entity(process))) - { - task = t; - break; - } - } - - // rjf: find the trap - DMN_Trap *trap = 0; - if(task != 0) - { - U64 trap_idx = 0; - for(DMN_TrapChunkNode *n = task->traps.first; n != 0; n = n->next) - { - for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1) - { - if(trap_idx == flagged_trap_idx) - { - trap = &n->v[n_idx]; - goto break_search_for_flagged_trap; - } - } - } - break_search_for_flagged_trap:; - } - - // rjf: fill event based on trap - if(trap != 0) - { - e->user_data = trap->id; - } - } - }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: fill set-thread-color info - case DMN_W32_EXCEPTION_RADDBG_SET_THREAD_COLOR: - { - e->kind = DMN_EventKind_SetThreadColor; - e->code = exception->ExceptionInformation[0]; - e->user_data = exception->ExceptionInformation[1]; - }break; - - //- rjf: fill set-data-breakpoint info - case DMN_W32_EXCEPTION_RADDBG_SET_BREAKPOINT: - { - U64 vaddr = exception->ExceptionInformation[0]; - U64 size = exception->ExceptionInformation[1]; - U64 read = exception->ExceptionInformation[2]; - U64 write = exception->ExceptionInformation[3]; - U64 exec = exception->ExceptionInformation[4]; - U64 set = exception->ExceptionInformation[5]; - e->kind = set ? DMN_EventKind_SetBreakpoint : DMN_EventKind_UnsetBreakpoint; - e->address = vaddr; - e->size = size; - if(read) { e->flags |= DMN_TrapFlag_BreakOnRead; } - if(write) { e->flags |= DMN_TrapFlag_BreakOnWrite; } - if(exec) { e->flags |= DMN_TrapFlag_BreakOnExecute; } - }break; - - //- rjf: fill set-vaddr-range-note info - case DMN_W32_EXCEPTION_RADDBG_SET_VADDR_RANGE_NOTE: - { - U64 vaddr = exception->ExceptionInformation[0]; - U64 size = exception->ExceptionInformation[1]; - U64 name_vaddr = exception->ExceptionInformation[2]; - U64 name_size = exception->ExceptionInformation[3]; - U8 *name_buffer = push_array(arena, U8, name_size); - dmn_w32_process_read(process->handle, r1u64(name_vaddr, name_vaddr+name_size), name_buffer), - e->kind = DMN_EventKind_SetVAddrRangeNote; - e->address = vaddr; - e->size = size; - e->string = str8(name_buffer, name_size); - }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(scratch.arena, U8, string_size + 1); - dmn_w32_process_read(process->handle, r1u64(string_address, string_address+string_size), buffer); - buffer[string_size] = 0; - - // rjf: extract into string - String8 debug_string = str8(buffer, string_size); - if(debug_string.size != 0 && buffer[string_size-1] == 0) - { - debug_string.size -= 1; - } - - // rjf: make debug string event - debug_strings_event = dmn_event_list_push(arena, &events); - debug_strings_event->kind = DMN_EventKind_DebugString; - - // rjf: push into debug strings - str8_list_push(scratch.arena, &debug_strings, debug_string); - keep_going = 1; - - // rjf: exit loop, given sufficient amount of text - if(debug_strings.total_size >= KB(4)) - { - keep_going = 0; - } - }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: exit loop after a little while, so we keep pumping e.g. debug strings - // - if(now_time_us() >= begin_time+100000 && debug_strings.total_size != 0) - { - keep_going = 0; - } - } - - //////////////////////// - //- rjf: send out event for any remaining debug strings - // - if(debug_strings.total_size != 0 && debug_strings_event != 0) - { - String8 debug_strings_joined = str8_list_join(arena, &debug_strings, 0); - debug_strings_event->string = debug_strings_joined; - } - - //////////////////////// - //- rjf: suspend threads which ran - // - ProfScope("suspend threads which ran") - { - for(DMN_W32_EntityNode *n = first_ran_thread; n != 0; n = n->next) - { - DMN_W32_Entity *thread = n->v; - if(thread->kind != DMN_W32_EntityKind_Thread) - { - continue; - } - 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: gather new thread-names - // - ProfScope("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 <= now_time_us()) - { - 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 = now_time_us(); - thread->thread.last_name_hash = name_hash; - } - } - } - } - - ////////////////////////// - //- rjf: restore original memory at trap locations - // - ProfScope("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; - if(trap->flags == 0) - { - 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: clear all debug register states, for flagged-traps - // - ProfScope("clear all debug register states, for flagged-traps") - { - for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next) - { - DMN_Handle process = t->process; - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - for(DMN_W32_Entity *child = process_entity->first; - child != &dmn_w32_entity_nil; - child = child->next) - { - if(child->kind == DMN_W32_EntityKind_Thread) - { - switch(child->arch) - { - default:{}break; - - //- rjf: x64 - case Arch_x64: - { - X64_RegBlock regs = {0}; - dmn_w32_thread_read_reg_block(child->arch, child->handle, ®s); - regs.dr7 = 0; - dmn_w32_thread_write_reg_block(child->arch, child->handle, ®s); - }break; - } - } - } - } - } - - ////////////////////////// - //- rjf: unset single step bit - // - if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) ProfScope("unset single step bit") - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); - Arch arch = thread->arch; - switch(arch) - { - //- rjf: unimplemented win32/arch combos - case Arch_Null: - case Arch_COUNT: - {}break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: - {NotImplemented;}break; - - case Arch_x64: - { - if(!GetThreadContext(thread->handle, single_step_thread_ctx)) - { - single_step_thread_ctx = 0; - } - if(single_step_thread_ctx != 0) - { - U64 rflags = single_step_thread_ctx->EFlags|0x2; - U64 new_rflags = rflags & ~0x100; - single_step_thread_ctx->EFlags = new_rflags; - SetThreadContext(thread->handle, single_step_thread_ctx); - } - }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: non-blocking-control-thread access barriers - -internal B32 -dmn_access_open(void) -{ - B32 result = 0; - if(dmn_w32_ctrl_thread) - { - result = 1; - } - else - { - 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) - { - mutex_drop(dmn_w32_shared->access_mutex); - } -} - -//- rjf: processes - -internal U64 -dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) -{ - U64 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - result = (U64)VirtualAllocEx(process_entity->handle, (void *)vaddr, size, MEM_RESERVE, PAGE_READWRITE); - if(result == 0) - { - result = (U64)VirtualAllocEx(process_entity->handle, 0, size, MEM_RESERVE, PAGE_READWRITE); - } - } - return result; -} - -internal void -dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size) -{ - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - (U64)VirtualAllocEx(process_entity->handle, (void *)vaddr, size, MEM_COMMIT, PAGE_READWRITE); - } -} - -internal void -dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size) -{ - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - VirtualFreeEx(process_entity->handle, (void *)vaddr, size, MEM_DECOMMIT); - } -} - -internal void -dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size) -{ - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - VirtualFreeEx(process_entity->handle, (void *)vaddr, 0, MEM_RELEASE); - } -} - -internal void -dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, AccessFlags flags) -{ - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - DWORD old_flags = 0; - DWORD new_flags = PAGE_NOACCESS; - switch(flags) - { - default:{}break; - case AccessFlag_Execute:{new_flags = PAGE_EXECUTE;}break; - case AccessFlag_Execute|AccessFlag_Read:{new_flags = PAGE_EXECUTE_READ;}break; - case AccessFlag_Execute|AccessFlag_Read|AccessFlag_Write:{new_flags = PAGE_EXECUTE_READWRITE;}break; - case AccessFlag_Read:{new_flags = PAGE_READONLY;}break; - case AccessFlag_Read|AccessFlag_Write:{new_flags = PAGE_READWRITE;}break; - } - VirtualProtectEx(process_entity->handle, (void *)vaddr, size, new_flags, &old_flags); - } -} - -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 Arch -dmn_arch_from_thread(DMN_Handle handle) -{ - Arch arch = Arch_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 Arch_Null: - case Arch_COUNT: - {}break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: - {NotImplemented;}break; - case Arch_x64: - { - U64 stack_base_addr = tlb + 0x8; - dmn_w32_process_read(process->handle, r1u64(stack_base_addr, stack_base_addr+8), &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 Arch_Null: - case Arch_COUNT: - {}break; - case Arch_arm64: - case Arch_arm32: - case Arch_x86: - {NotImplemented;}break; - case Arch_x64: - { - result += 88; - }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); -} +// Copyright (c) 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; + { + U32 gen = 0; + if(e != 0) + { + SLLStackPop(dmn_w32_shared->entities_first_free); + gen = e->gen; + } + else + { + e = push_array_no_zero(dmn_w32_shared->entities_arena, DMN_W32_Entity, 1); + dmn_w32_shared->entities_count += 1; + } + 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); + } + t->e->kind = DMN_W32_EntityKind_Null; + + // 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)) + { + DWORD error = GetLastError(); + log_infof("'Win32 ReadProcessMemory failure': { [0x%I64x, 0x%I64x), code: %i }\n", range.min, range.max, error); + // NOTE(rjf): I have discovered that `actual_read` is *NOT* guaranteed to have + // a usable value if `ReadProcessMemory` fails! + // bytes_read += actual_read; + (void)error; + 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; + } + 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_FileHeader 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); + got_coff_header = dmn_w32_process_read_struct(process, coff_header_off, &coff_header); + } + } + + DMN_W32_ImageInfo result = zero_struct; + if(got_coff_header) + { + U64 optional_off = coff_header_off + sizeof(COFF_FileHeader); + + Arch arch = arch_from_coff_machine(coff_header.machine); + U64 image_size = 0; + U64 data_dir_count = 0; + U64 data_dir_off = max_U64; + U64 tls_index = max_U64; + + // parse optional header + if(pe_has_plus_header(coff_header.machine)) + { + PE_OptionalHeader32Plus opt_header = {0}; + if(dmn_w32_process_read_struct(process, optional_off, &opt_header)) + { + image_size = opt_header.sizeof_image; + data_dir_count = opt_header.data_dir_count; + data_dir_off = optional_off + sizeof(opt_header); + } + else { Assert(0 && "failed to read optional header"); } + } + else + { + PE_OptionalHeader32 opt_header = {0}; + if(dmn_w32_process_read_struct(process, optional_off, &opt_header)) + { + image_size = opt_header.sizeof_image; + data_dir_count = opt_header.data_dir_count; + data_dir_off = optional_off + sizeof(opt_header); + } + else { Assert(0 && "failed to read optional header"); } + } + + // extract TLS index + if(PE_DataDirectoryIndex_TLS < data_dir_count) + { + U64 tls_dir_vaddr = data_dir_off + PE_DataDirectoryIndex_TLS * sizeof(PE_DataDirectory); + PE_DataDirectory tls_dir = {0}; + if(dmn_w32_process_read_struct(process, tls_dir_vaddr, &tls_dir)) + { + if(pe_has_plus_header(coff_header.machine)) + { + if(tls_dir.virt_size == sizeof(PE_TLSHeader64)) + { + PE_TLSHeader64 tls_header = {0}; + if(dmn_w32_process_read_struct(process, base_vaddr + tls_dir.virt_off, &tls_header)) + { + U64 tls_index64 = 0; + if(dmn_w32_process_read_struct(process, tls_header.index_address, &tls_index64)) + { + tls_index = tls_index64; + } + else { Assert(0 && "failed to read TLS Index 64"); } + } + else { Assert(0 && "failed to read TLS Header 64"); } + } + } + else + { + if(tls_dir.virt_size == sizeof(PE_TLSHeader32)) + { + PE_TLSHeader32 tls_header = {0}; + if(dmn_w32_process_read_struct(process, base_vaddr + tls_dir.virt_off, &tls_header)) + { + U32 tls_index32 = 0; + if(dmn_w32_process_read_struct(process, tls_header.index_address, &tls_index32)) + { + tls_index = tls_index32; + } + else { Assert(0 && "failed to read TLS Index 32"); } + } + else { Assert(0 && "failed to read TLS Header32"); } + } + } + } + else { Assert(0 && "failed to read TLS directory"); } + } + + // fill out result + result.arch = arch; + result.size = image_size; + result.tls_index = tls_index; + } + else { Assert(0 && "failed to find COFF file header"); } + + return result; +} + +//- rjf: threads + +internal B32 +dmn_w32_thread_read_reg_block(Arch arch, HANDLE thread, void *reg_block) +{ + B32 result = 0; + ProfBeginFunction(); + switch(arch) + { + //////////////////////////// + //- rjf: unimplemented win32/arch combos + // + case Arch_Null: + case Arch_COUNT: + {}break; + case Arch_arm64: + case Arch_arm32: + case Arch_x86: + {NotImplemented;}break; + + //////////////////////////// + //- rjf: x64 + // + case Arch_x64: + { + Temp scratch = scratch_begin(0, 0); + X64_RegBlock *dst = (X64_RegBlock *)reg_block; + + //- rjf: unpack info about available features + U32 feature_mask = GetEnabledXStateFeatures(); + B32 xstate_enabled = (feature_mask & (XSTATE_MASK_AVX | XSTATE_MASK_AVX512 | XSTATE_MASK_CET_U)) != 0; + + //- rjf: set up context + CONTEXT *ctx = 0; + U32 ctx_flags = DMN_W32_CTX_X64_ALL | (xstate_enabled ? DMN_W32_CTX_INTEL_XSTATE : 0); + 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 + if (xstate_enabled) + { + SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX | XSTATE_MASK_AVX512 | XSTATE_MASK_CET_U); + } + + //- rjf: get thread context + if(!GetThreadContext(thread, ctx)) + { + ctx = 0; + } + + //- rjf: bad context -> abort + if(ctx == 0) + { + break; + } + result = 1; + + DWORD64 xstate_mask = 0; + GetXStateFeaturesMask(ctx, &xstate_mask); + + //- rjf: convert context -> X64_RegBlock + XSAVE_FORMAT *xsave = &ctx->FltSave; + dst->rax = ctx->Rax; + dst->rcx = ctx->Rcx; + dst->rdx = ctx->Rdx; + dst->rbx = ctx->Rbx; + dst->rsp = ctx->Rsp; + dst->rbp = ctx->Rbp; + dst->rsi = ctx->Rsi; + dst->rdi = ctx->Rdi; + dst->r8 = ctx->R8; + dst->r9 = ctx->R9; + dst->r10 = ctx->R10; + dst->r11 = ctx->R11; + dst->r12 = ctx->R12; + dst->r13 = ctx->R13; + dst->r14 = ctx->R14; + dst->r15 = ctx->R15; + dst->rip = ctx->Rip; + dst->cs = ctx->SegCs; + dst->ds = ctx->SegDs; + dst->es = ctx->SegEs; + dst->fs = ctx->SegFs; + dst->gs = ctx->SegGs; + dst->ss = ctx->SegSs; + dst->dr0 = ctx->Dr0; + dst->dr1 = ctx->Dr1; + dst->dr2 = ctx->Dr2; + dst->dr3 = ctx->Dr3; + dst->dr6 = ctx->Dr6; + dst->dr7 = ctx->Dr7; + // NOTE(rjf): this bit is "supposed to always be 1", according to old info. + // may need to be investigated. + dst->rflags = ctx->EFlags | 0x2; + dst->fcw = xsave->ControlWord; + dst->fsw = xsave->StatusWord; + dst->ftw = xsave->TagWord; + dst->fop = xsave->ErrorOpcode; + MemoryCopy(&dst->fip, &xsave->ErrorOffset, sizeof(U64)); + MemoryCopy(&dst->fdp, &xsave->DataOffset, sizeof(U64)); + dst->mxcsr = xsave->MxCsr; + dst->mxcsr_mask = xsave->MxCsr_Mask; + { + M128A *float_s = xsave->FloatRegisters; + U80 *float_d = &dst->st0; + for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) + { + MemoryCopy(float_d, float_s, sizeof(*float_d)); + } + } + + // SSE registers are always available in x64 + { + M128A *xmm_s = xsave->XmmRegisters; + U512 *zmm_d = &dst->zmm0; + for(U32 n = 0; n < 16; n += 1, xmm_s += 1, zmm_d += 1) + { + MemoryCopy(zmm_d, xmm_s, sizeof(*xmm_s)); + } + } + + // AVX + if(xstate_mask & XSTATE_MASK_AVX) + { + DWORD avx_length = 0; + U8 *avx_s = (U8*)LocateXStateFeature(ctx, XSTATE_AVX, &avx_length); + if(avx_length == 16 * sizeof(U128)) + { + U512 *zmm_d = &dst->zmm0; + for(U32 n = 0; n < 16; n += 1, avx_s += sizeof(U128), zmm_d += 1) + { + MemoryCopy(&zmm_d->u8[16], avx_s, sizeof(U128)); + } + } + } + else + { + U512 *zmm_d = &dst->zmm0; + for(U32 n = 0; n < 16; n += 1, zmm_d += 1) + { + MemoryZero(&zmm_d->u8[16], sizeof(U128)); + } + } + + // AVX-512 + if(xstate_mask & XSTATE_MASK_AVX512) + { + // rjf: kmask + DWORD kmask_length = 0; + U64 *kmask_s = (U64*)LocateXStateFeature(ctx, XSTATE_AVX512_KMASK, &kmask_length); + if(kmask_length == 8 * sizeof(U64)) + { + U64 *kmask_d = &dst->k0; + for(U32 n = 0; n < 8; n += 1, kmask_s += 1, kmask_d += 1) + { + MemoryCopy(kmask_d, kmask_s, sizeof(*kmask_s)); + } + } + + // rjf: zmmh + DWORD avx512h_length = 0; + U8 *avx512h_s = (U8*)LocateXStateFeature(ctx, XSTATE_AVX512_ZMM_H, &avx512h_length); + if(avx512h_length == 16 * sizeof(U256)) + { + U512 *zmmh_d = &dst->zmm0; + for(U32 n = 0; n < 16; n += 1, avx512h_s += sizeof(U256), zmmh_d += 1) + { + MemoryCopy(&zmmh_d->u8[32], avx512h_s, sizeof(U256)); + } + } + + // rjf: zmm + DWORD avx512_length = 0; + U8 *avx512_s = (U8 *)LocateXStateFeature(ctx, XSTATE_AVX512_ZMM, &avx512_length); + if(avx512_length == 16 * sizeof(U512)) + { + U512 *zmm_d = &dst->zmm16; + for(U32 n = 0; n < 16; n += 1, avx512_s += sizeof(U512), zmm_d += 1) + { + MemoryCopy(zmm_d, avx512_s, sizeof(U512)); + } + } + } + else + { + U64 *kmask_d = &dst->k0; + for(U32 n = 0; n < 8; n += 1, kmask_d += 1) + { + MemoryZero(kmask_d, sizeof(*kmask_d)); + } + U512 *zmmh_d = &dst->zmm0; + for(U32 n = 0; n < 16; n += 1, zmmh_d += 1) + { + MemoryZero(&zmmh_d->u8[32], sizeof(U256)); + } + U512 *zmm_d = &dst->zmm16; + for(U32 n = 0; n < 16; n += 1, zmm_d += 1) + { + MemoryZero(zmm_d, sizeof(*zmm_d)); + } + } + + // CET / Shadow Stack + if(xstate_mask & XSTATE_MASK_CET_U) + { + DWORD cet_length = 0; + XSAVE_CET_U_FORMAT *cet = LocateXStateFeature(ctx, XSTATE_CET_U, &cet_length); + if (cet_length == sizeof(*cet)) + { + dst->cetmsr = cet->Ia32CetUMsr; + dst->cetssp = cet->Ia32Pl3SspMsr; + } + } + + scratch_end(scratch); + }break; + } + ProfEnd(); + return result; +} + +internal B32 +dmn_w32_thread_write_reg_block(Arch arch, HANDLE thread, void *reg_block) +{ + B32 result = 0; + ProfBeginFunction(); + switch(arch) + { + //////////////////////////// + //- rjf: unimplemented win32/arch combos + // + case Arch_Null: + case Arch_COUNT: + {}break; + case Arch_arm64: + case Arch_arm32: + case Arch_x86: + {NotImplemented;}break; + + //////////////////////////// + //- rjf: x64 + // + case Arch_x64: + { + Temp scratch = scratch_begin(0, 0); + X64_RegBlock *src = (X64_RegBlock *)reg_block; + + //- rjf: unpack info about available features + U32 feature_mask = GetEnabledXStateFeatures(); + B32 xstate_enabled = (feature_mask & (XSTATE_MASK_AVX | XSTATE_MASK_AVX512)) != 0; + + //- rjf: set up context + CONTEXT *ctx = 0; + U32 ctx_flags = DMN_W32_CTX_X64_ALL | (xstate_enabled ? DMN_W32_CTX_INTEL_XSTATE : 0); + 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 + if (xstate_enabled) + { + SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX | XSTATE_MASK_AVX512); + } + + //- rjf: bad context -> abort + if(ctx == 0) + { + break; + } + + //- rjf: convert X64_RegBlock -> CONTEXT + XSAVE_FORMAT *fxsave = &ctx->FltSave; + ctx->ContextFlags = ctx_flags; + ctx->MxCsr = src->mxcsr & src->mxcsr_mask; + ctx->Rax = src->rax; + ctx->Rcx = src->rcx; + ctx->Rdx = src->rdx; + ctx->Rbx = src->rbx; + ctx->Rsp = src->rsp; + ctx->Rbp = src->rbp; + ctx->Rsi = src->rsi; + ctx->Rdi = src->rdi; + ctx->R8 = src->r8; + ctx->R9 = src->r9; + ctx->R10 = src->r10; + ctx->R11 = src->r11; + ctx->R12 = src->r12; + ctx->R13 = src->r13; + ctx->R14 = src->r14; + ctx->R15 = src->r15; + ctx->Rip = src->rip; + ctx->SegCs = src->cs; + ctx->SegDs = src->ds; + ctx->SegEs = src->es; + ctx->SegFs = src->fs; + ctx->SegGs = src->gs; + ctx->SegSs = src->ss; + ctx->Dr0 = src->dr0; + ctx->Dr1 = src->dr1; + ctx->Dr2 = src->dr2; + ctx->Dr3 = src->dr3; + ctx->Dr6 = src->dr6; + ctx->Dr7 = src->dr7; + ctx->EFlags = src->rflags; + fxsave->ControlWord = src->fcw; + fxsave->StatusWord = src->fsw; + fxsave->TagWord = src->ftw; + fxsave->ErrorOpcode = src->fop; + MemoryCopy(&fxsave->ErrorOffset, &src->fip, sizeof(U64)); + MemoryCopy(&fxsave->DataOffset, &src->fdp, sizeof(U64)); + { + M128A *float_d = fxsave->FloatRegisters; + U80 *float_s = &src->st0; + for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) + { + MemoryCopy(float_d, float_s, 10); + } + } + + // SSE registers are always available in x64 + { + M128A *xmm_d = fxsave->XmmRegisters; + U512 *zmm_s = &src->zmm0; + for(U32 n = 0; n < 16; n += 1, xmm_d += 1, zmm_s += 1) + { + MemoryCopy(xmm_d, zmm_s, sizeof(*xmm_d)); + } + } + + // AVX + if(feature_mask & XSTATE_MASK_AVX) + { + DWORD avx_length = 0; + U8* avx_d = (U8*)LocateXStateFeature(ctx, XSTATE_AVX, &avx_length); + if(avx_length == 16 * sizeof(U128)) + { + U512 *zmm_s = &src->zmm0; + for(U32 n = 0; n < 16; n += 1, avx_d += sizeof(U128), zmm_s += 1) + { + MemoryCopy(avx_d, &zmm_s->u8[16], sizeof(U128)); + } + } + } + + // AVX-512 + if(feature_mask & XSTATE_MASK_AVX512) + { + DWORD kmask_length = 0; + U64* kmask_d = (U64*)LocateXStateFeature(ctx, XSTATE_AVX512_KMASK, &kmask_length); + if(kmask_length == 8 * sizeof(*kmask_d)) + { + U64 *kmask_s = &src->k0; + for(U32 n = 0; n < 8; n += 1, kmask_s += 1, kmask_d += 1) + { + MemoryCopy(kmask_d, kmask_s, sizeof(*kmask_d)); + } + } + + DWORD avx512h_length = 0; + U8* avx512h_d = (U8*)LocateXStateFeature(ctx, XSTATE_AVX512_ZMM_H, &avx512h_length); + if(avx512h_length == 16 * sizeof(U256)) + { + U512 *zmmh_s = &src->zmm0; + for(U32 n = 0; n < 16; n += 1, avx512h_d += sizeof(U256), zmmh_s += 1) + { + MemoryCopy(avx512h_d, &zmmh_s->u8[32], sizeof(U256)); + } + } + + DWORD avx512_length = 0; + U8* avx512_d = (U8*)LocateXStateFeature(ctx, XSTATE_AVX512_ZMM, &avx512_length); + if(avx512_length == 16 * sizeof(U512)) + { + U512 *zmm_s = &src->zmm16; + for(U32 n = 0; n < 16; n += 1, avx512_d += sizeof(U512), zmm_s += 1) + { + MemoryCopy(avx512_d, zmm_s, sizeof(U512)); + } + } + } + + //- rjf: set thread context + if(SetThreadContext(thread, ctx)) + { + result = 1; + } + scratch_end(scratch); + }break; + } + ProfEnd(); + 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 = mutex_alloc(); + dmn_w32_shared->detach_arena = arena_alloc(); + dmn_w32_shared->entities_arena = arena_alloc(.reserve_size = GB(8), .commit_size = 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) +{ + MutexScope(dmn_w32_shared->access_mutex) + { + dmn_w32_shared->access_run_state = 1; + } +} + +internal void +dmn_ctrl_exclusive_access_end(void) +{ + MutexScope(dmn_w32_shared->access_mutex) + { + dmn_w32_shared->access_run_state = 0; + } +} + +internal U32 +dmn_ctrl_launch(DMN_CtrlCtx *ctx, ProcessLaunchParams *params) +{ + Temp scratch = scratch_begin(0, 0); + U32 result = 0; + DMN_AccessScope + { + //- rjf: produce exe / arguments string + String8 cmd = {0}; + if(params->cmd_line.first != 0) + { + String8List args = {0}; + String8 exe_path = params->cmd_line.first->string; + String8List exe_path_parts = str8_split_path(scratch.arena, exe_path); + exe_path = str8_list_join(scratch.arena, &exe_path_parts, &(StringJoin){.sep = str8_lit("\\")}); + str8_list_pushf(scratch.arena, &args, "\"%S\"", exe_path); + for(String8Node *n = params->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 = params->env; + if(params->inherit_env != 0) + { + MemoryZeroStruct(&all_opts); + str8_list_push(scratch.arena, &all_opts, str8_lit("_NO_DEBUG_HEAP=1")); + for(String8Node *n = params->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, params->path); + String16 env16 = str16_from_8(scratch.arena, env); + + //- rjf: launch + DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; + if(params->debug_subprocesses) + { + creation_flags |= DEBUG_PROCESS; + } + else + { + creation_flags |= DEBUG_ONLY_THIS_PROCESS; + } + BOOL inherit_handles = 0; + STARTUPINFOW startup_info = {sizeof(startup_info)}; + if(!file_match(params->stdout_file, file_zero())) + { + HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0]; + startup_info.hStdOutput = stdout_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + if(!file_match(params->stderr_file, file_zero())) + { + HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0]; + startup_info.hStdError = stderr_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + if(!file_match(params->stdin_file, file_zero())) + { + HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0]; + startup_info.hStdInput = stdin_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + PROCESS_INFORMATION process_info = {0}; + if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 1, creation_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) + { + log_user_errorf("Only 64-bit applications can be debugged currently."); + 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 + { + DWORD error = GetLastError(); + LPWSTR message = 0; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, error, MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), (LPWSTR)&message, 0, 0); + String8 message8 = message ? str8_from_16(scratch.arena, str16_cstring(message)) : str8_lit("unknown error"); + LocalFree(message); + + log_user_errorf("There was an error starting %S: %S", params->cmd_line.first->string, message8); + } + } + 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; + +#if 0 + // TODO(rjf): JIT debugging info + { + typedef struct JIT_DEBUG_INFO JIT_DEBUG_INFO; + struct JIT_DEBUG_INFO + { + DWORD dwSize; + DWORD dwProcessorArchitecture; + DWORD dwThreadID; + DWORD dwReserved0; + ULONG64 lpExceptionAddress; + ULONG64 lpExceptionRecord; + ULONG64 lpContextRecord; + }; + } +#endif + } + 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: get single step thread's context (x64 single-step-set fast path) + // + CONTEXT *single_step_thread_ctx = 0; + if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); + Arch arch = thread->arch; + switch(arch) + { + default:{}break; + case Arch_x64: + { + U32 ctx_flags = DMN_W32_CTX_X64|DMN_W32_CTX_INTEL_CONTROL; + 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, &single_step_thread_ctx, &size)) + { + single_step_thread_ctx = 0; + } + } + }break; + } + } + + ////////////////////////// + //- rjf: set single step bit + // + if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) ProfScope("set single step bit") + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); + Arch arch = thread->arch; + switch(arch) + { + //- rjf: unimplemented win32/arch combos + case Arch_Null: + case Arch_COUNT: + {}break; + case Arch_arm64: + case Arch_arm32: + case Arch_x86: + {NotImplemented;}break; + + //- rjf: x64 + case Arch_x64: + { + if(!GetThreadContext(thread->handle, single_step_thread_ctx)) + { + single_step_thread_ctx = 0; + } + if(single_step_thread_ctx != 0) + { + U64 rflags = single_step_thread_ctx->EFlags|0x2; + U64 new_rflags = rflags | 0x100; + single_step_thread_ctx->EFlags = new_rflags; + SetThreadContext(thread->handle, single_step_thread_ctx); + } + }break; + } + } + + ////////////////////////// + //- rjf: write all traps into memory + // + U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, ctrls->traps.trap_count); + ProfScope("write all traps into memory") + { + 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; + if(trap->flags == 0) + { + 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: gather all flagged traps, bucketed by process + // + typedef struct DMN_FlaggedTrapTask DMN_FlaggedTrapTask; + struct DMN_FlaggedTrapTask + { + DMN_FlaggedTrapTask *next; + DMN_Handle process; + DMN_TrapChunkList traps; + }; + DMN_FlaggedTrapTask *first_flagged_trap_task = 0; + DMN_FlaggedTrapTask *last_flagged_trap_task= 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) + { + DMN_Trap *trap = n->v+n_idx; + if(trap->flags != 0) + { + DMN_FlaggedTrapTask *task = 0; + for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next) + { + if(dmn_handle_match(t->process, trap->process)) + { + task = t; + break; + } + } + if(task == 0) + { + task = push_array(scratch.arena, DMN_FlaggedTrapTask, 1); + SLLQueuePush(first_flagged_trap_task, last_flagged_trap_task, task); + task->process = trap->process; + } + B32 already_in_task = 0; + for(DMN_TrapChunkNode *n = task->traps.first; n != 0; n = n->next) + { + for(U64 n_idx = 0; n_idx < n->count; n_idx += 1) + { + if(n->v[n_idx].id == trap->id) + { + already_in_task = 1; + goto end_look_for_existing_trap_in_task; + } + } + } + end_look_for_existing_trap_in_task:; + if(!already_in_task) + { + dmn_trap_chunk_list_push(scratch.arena, &task->traps, 8, trap); + } + } + } + } + + ////////////////////////// + //- rjf: write all debug register states, for flagged-traps + // + ProfScope("write all debug register states, for flagged-traps") + { + //- rjf: for each flagged trap task, iterate all threads in the + // associated process, and prepare debug registers accordingly + for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next) + { + DMN_Handle process = t->process; + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + for(DMN_W32_Entity *child = process_entity->first; + child != &dmn_w32_entity_nil; + child = child->next) + { + if(child->kind == DMN_W32_EntityKind_Thread) + { + switch(child->arch) + { + default:{}break; + + //- rjf: x64 + case Arch_x64: + { + X64_RegBlock regs = {0}; + dmn_w32_thread_read_reg_block(child->arch, child->handle, ®s); + { + U64 trap_idx = 0; + for(DMN_TrapChunkNode *n = t->traps.first; n != 0; n = n->next) + { + for(U64 n_idx = 0; n_idx < n->count && trap_idx < 4; n_idx += 1, trap_idx += 1) + { + DMN_Trap *trap = &n->v[n_idx]; + U64 *addr_reg = ®s.dr0; + switch(trap_idx) + { + default:{}break; + case 0:{addr_reg = ®s.dr0;}break; + case 1:{addr_reg = ®s.dr1;}break; + case 2:{addr_reg = ®s.dr2;}break; + case 3:{addr_reg = ®s.dr3;}break; + } + addr_reg[0] = trap->vaddr; + regs.dr7 |= bit9|bit10|bit11; + regs.dr7 |= (1ull << (trap_idx*2)); + // NOTE(rjf): global-enable regs.dr7.u64 |= (1ull << (trap_idx*2+1)); + regs.dr7 &= ~((U64)(bit17|bit18|bit19|bit20) << (trap_idx*4)); + regs.dr7 &= ~((U64)(bit15|bit16)); + switch(trap->flags) + { + case DMN_TrapFlag_BreakOnExecute: + default:{}break; + case DMN_TrapFlag_BreakOnWrite: + case DMN_TrapFlag_BreakOnWrite|DMN_TrapFlag_BreakOnExecute: + { + regs.dr7 |= ((U64)bit17) << (trap_idx*4); + }break; + case DMN_TrapFlag_BreakOnRead|DMN_TrapFlag_BreakOnWrite|DMN_TrapFlag_BreakOnExecute: + case DMN_TrapFlag_BreakOnRead|DMN_TrapFlag_BreakOnWrite: + case DMN_TrapFlag_BreakOnRead: + { + regs.dr7 |= (((U64)bit17) << (trap_idx*4)); + regs.dr7 |= (((U64)bit18) << (trap_idx*4)); + }break; + } + switch(trap->size) + { + case 1: + default:{}break; + case 2: + { + regs.dr7 |= (((U64)bit19) << (trap_idx*4)); + }break; + case 4: + { + regs.dr7 |= (((U64)bit19) << (trap_idx*4)); + regs.dr7 |= (((U64)bit20) << (trap_idx*4)); + }break; + case 8: + { + regs.dr7 |= (((U64)bit20) << (trap_idx*4)); + }break; + } + } + } + } + dmn_w32_thread_write_reg_block(child->arch, child->handle, ®s); + }break; + } + } + } + } + } + + ////////////////////////// + //- rjf: produce list of threads which will run + // + DMN_W32_EntityNode *first_run_thread = 0; + DMN_W32_EntityNode *last_run_thread = 0; + ProfScope("produce list of threads which will run") + { + //- 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)) + { + is_frozen = !dmn_handle_match(dmn_w32_handle_from_entity(thread), ctrls->single_step_thread); + } + + // 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: loop, consume win32 debug events until we produce the relevant demon events + // + B32 priority_mode = !dmn_handle_match(dmn_handle_zero(), ctrls->priority_thread); + B32 did_priority_mode = priority_mode; + B32 do_threads_resume = 1; + DMN_W32_EntityNode *first_ran_thread = 0; + DMN_W32_EntityNode *last_ran_thread = 0; + U64 begin_time = now_time_us(); + String8List debug_strings = {0}; + DMN_Event *debug_strings_event = 0; + for(B32 keep_going = 1; keep_going;) + { + keep_going = 0; + + //////////////////////// + //- rjf: resume threads that we want to run + // + // if priority mode? => first, just resume priority thread + // if not? => resume all non-priority threads + // + if(do_threads_resume) ProfScope("resume threads that we want to run") + { + do_threads_resume = 0; + for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) + { + DMN_W32_Entity *thread = n->v; + B32 thread_is_priority = dmn_handle_match(dmn_w32_handle_from_entity(thread), ctrls->priority_thread); + if((priority_mode && !thread_is_priority) || + (!priority_mode && did_priority_mode && thread_is_priority)) + { + continue; + } + DWORD resume_result = ResumeThread(thread->handle); + DMN_W32_EntityNode *n = push_array(scratch.arena, DMN_W32_EntityNode, 1); + SLLQueuePush(first_ran_thread, last_ran_thread, n); + n->v = thread; + 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; + } + if(priority_mode && thread_is_priority) + { + break; + } + } + } + + //////////////////////// + //- rjf: choose win32 resume code + // + DWORD resume_code = DBG_CONTINUE; + { + if(dmn_w32_shared->exception_not_handled && !ctrls->ignore_previous_exception) + { + log_infof("using DBG_EXCEPTION_NOT_HANDLED\n"); + resume_code = DBG_EXCEPTION_NOT_HANDLED; + } + else + { + log_infof("using DBG_CONTINUE\n"); + } + dmn_w32_shared->exception_not_handled = 0; + } + + //////////////////////// + //- rjf: inform windows that we're resuming, run, & obtain next debug event + // + DEBUG_EVENT evt = {0}; + B32 evt_good = 0; + ProfScope("inform windows that we're resuming, run, & obtain next debug event") + { + 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); + DWORD error = GetLastError(); + dmn_w32_shared->resume_needed = 0; + dmn_w32_shared->resume_tid = 0; + dmn_w32_shared->resume_pid = 0; + } + if(resume_good) + { + DWORD wait_ms = 100; + if(priority_mode) + { + wait_ms = 30; + } + evt_good = !!WaitForDebugEvent(&evt, wait_ms); + if(evt_good) + { + dmn_w32_shared->resume_needed = 1; + dmn_w32_shared->resume_pid = evt.dwProcessId; + dmn_w32_shared->resume_tid = evt.dwThreadId; + } + else + { + DWORD err = GetLastError(); + (void)err; + keep_going = 1; + } + if(priority_mode) + { + priority_mode = 0; + do_threads_resume = 1; + } + } + } + + //////////////////////// + //- rjf: process the new event + // + if(evt_good) ProfScope("process the new event") + { + 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; + e->tls_model = DMN_TlsModel_WinodwsNt; + } + + // 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); + e->tls_index = image_info.tls_index; + } + } + }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: if this was the process we were going to resume, then we will + // just not resume, and wait for another debug event + if(evt.dwProcessId == dmn_w32_shared->resume_pid) + { + dmn_w32_shared->resume_needed = 0; + dmn_w32_shared->resume_tid = 0; + dmn_w32_shared->resume_pid = 0; + } + + // rjf: generate events for children + for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next) + { + switch(child->kind) + { + default:{}break; + 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; + keep_going = 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); + e->tls_index = image_info.tls_index; + } + }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): Further notes on MULTITHREADED STEPPING ACCESS VIOLATION + // EVENTS! @rjf @rjf @rjf + // (2024/05/29): + // + // Just adding another comment here to document that the above long + // comment went completely unnoticed by me during a pass over demon, + // and I had removed the proper rollback stuff here without reading + // the above comment. So this comment just serves to make that + // original comment even heftier. + + //- 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; + U64 user_trap_id = 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; + user_trap_id = n->v[idx].id; + 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 = (hit_user_trap || (is_trap && !hit_explicit_trap)); + + //- rjf: roll back thread's instruction pointer + if(should_do_rollback) ProfScope("roll back thread's instruction pointer") + { + switch(thread->arch) + { + //- rjf: default, general path + default: + { + Temp temp = temp_begin(scratch.arena); + ARCH_Info *arch_info = arch_info_from_arch(thread->arch); + U64 regs_block_size = arch_info->reg_block_size; + void *regs_block = push_array(scratch.arena, U8, regs_block_size); + if(dmn_w32_thread_read_reg_block(thread->arch, thread->handle, regs_block)) + { + arch_reg_block_write_ip(arch_info, regs_block, instruction_pointer); + dmn_w32_thread_write_reg_block(thread->arch, thread->handle, regs_block); + } + temp_end(temp); + }break; + + //- rjf: x64 (fastpath) + case Arch_x64: + { + CONTEXT *ctx = 0; + U32 ctx_flags = DMN_W32_CTX_X64|DMN_W32_CTX_INTEL_CONTROL; + 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; + } + } + if(!GetThreadContext(thread->handle, ctx)) + { + ctx = 0; + } + if(ctx != 0) + { + U64 rip = ctx->Rip; + U64 new_rip = instruction_pointer; + ctx->Rip = new_rip; + SetThreadContext(thread->handle, ctx); + } + }break; + } + } + + //- 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; + e->user_data = user_trap_id; + + //- 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; + if(exception->ExceptionInformation[0] == DMN_W32_FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS) + { + // TODO(rjf): this is a shadow stack violation - this can imply that the spoof was hit. + // need to handle this correctly in the ctrl layer when stepping w/ a spoof set. + // + // @shadow_stack_step + } + }break; + + //- rjf: fill single-step event info + case DMN_W32_EXCEPTION_SINGLE_STEP: + { + e->kind = DMN_EventKind_SingleStep; + + // NOTE(rjf): data breakpoints are reported via single-steps + // over the instructions which caused the breakpoint to be + // hit - so if we have data breakpoints set, we need to + // check this thread's debug registers, to determine if this + // is a regular single-step or a data breakpoint hit. + { + // rjf: first determine the flagged trap index + U64 flagged_trap_idx = 0; + switch(thread->arch) + { + default:{NotImplemented;}break; + case Arch_x64: + { + X64_RegBlock regs = {0}; + dmn_w32_thread_read_reg_block(thread->arch, thread->handle, ®s); + if(regs.dr6 & 0xF) + { + e->kind = DMN_EventKind_Breakpoint; + if(0){} + else if(regs.dr7 & (1ull<<0) && regs.dr6 & (1ull<<0)) { flagged_trap_idx = 0; e->address = regs.dr0; } + else if(regs.dr7 & (1ull<<2) && regs.dr6 & (1ull<<1)) { flagged_trap_idx = 1; e->address = regs.dr1; } + else if(regs.dr7 & (1ull<<4) && regs.dr6 & (1ull<<2)) { flagged_trap_idx = 2; e->address = regs.dr2; } + else if(regs.dr7 & (1ull<<8) && regs.dr6 & (1ull<<3)) { flagged_trap_idx = 3; e->address = regs.dr3; } + } + }break; + } + + // rjf: find the flagged trap task for this thread's process + DMN_W32_Entity *process = thread->parent; + DMN_FlaggedTrapTask *task = 0; + for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next) + { + if(dmn_handle_match(t->process, dmn_w32_handle_from_entity(process))) + { + task = t; + break; + } + } + + // rjf: find the trap + DMN_Trap *trap = 0; + if(task != 0) + { + U64 trap_idx = 0; + for(DMN_TrapChunkNode *n = task->traps.first; n != 0; n = n->next) + { + for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1) + { + if(trap_idx == flagged_trap_idx) + { + trap = &n->v[n_idx]; + goto break_search_for_flagged_trap; + } + } + } + break_search_for_flagged_trap:; + } + + // rjf: fill event based on trap + if(trap != 0) + { + e->user_data = trap->id; + } + } + }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: fill set-thread-color info + case DMN_W32_EXCEPTION_RADDBG_SET_THREAD_COLOR: + { + e->kind = DMN_EventKind_SetThreadColor; + e->code = exception->ExceptionInformation[0]; + e->user_data = exception->ExceptionInformation[1]; + }break; + + //- rjf: fill set-data-breakpoint info + case DMN_W32_EXCEPTION_RADDBG_SET_BREAKPOINT: + { + U64 vaddr = exception->ExceptionInformation[0]; + U64 size = exception->ExceptionInformation[1]; + U64 read = exception->ExceptionInformation[2]; + U64 write = exception->ExceptionInformation[3]; + U64 exec = exception->ExceptionInformation[4]; + U64 set = exception->ExceptionInformation[5]; + e->kind = set ? DMN_EventKind_SetBreakpoint : DMN_EventKind_UnsetBreakpoint; + e->address = vaddr; + e->size = size; + if(read) { e->flags |= DMN_TrapFlag_BreakOnRead; } + if(write) { e->flags |= DMN_TrapFlag_BreakOnWrite; } + if(exec) { e->flags |= DMN_TrapFlag_BreakOnExecute; } + }break; + + //- rjf: fill set-vaddr-range-note info + case DMN_W32_EXCEPTION_RADDBG_SET_VADDR_RANGE_NOTE: + { + U64 vaddr = exception->ExceptionInformation[0]; + U64 size = exception->ExceptionInformation[1]; + U64 name_vaddr = exception->ExceptionInformation[2]; + U64 name_size = exception->ExceptionInformation[3]; + U8 *name_buffer = push_array(arena, U8, name_size); + dmn_w32_process_read(process->handle, r1u64(name_vaddr, name_vaddr+name_size), name_buffer), + e->kind = DMN_EventKind_SetVAddrRangeNote; + e->address = vaddr; + e->size = size; + e->string = str8(name_buffer, name_size); + }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(scratch.arena, U8, string_size + 1); + dmn_w32_process_read(process->handle, r1u64(string_address, string_address+string_size), buffer); + buffer[string_size] = 0; + + // rjf: extract into string + String8 debug_string = str8(buffer, string_size); + if(debug_string.size != 0 && buffer[string_size-1] == 0) + { + debug_string.size -= 1; + } + + // rjf: make debug string event + debug_strings_event = dmn_event_list_push(arena, &events); + debug_strings_event->kind = DMN_EventKind_DebugString; + + // rjf: push into debug strings + str8_list_push(scratch.arena, &debug_strings, debug_string); + keep_going = 1; + + // rjf: exit loop, given sufficient amount of text + if(debug_strings.total_size >= KB(4)) + { + keep_going = 0; + } + }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: exit loop after a little while, so we keep pumping e.g. debug strings + // + if(now_time_us() >= begin_time+100000 && debug_strings.total_size != 0) + { + keep_going = 0; + } + } + + //////////////////////// + //- rjf: send out event for any remaining debug strings + // + if(debug_strings.total_size != 0 && debug_strings_event != 0) + { + String8 debug_strings_joined = str8_list_join(arena, &debug_strings, 0); + debug_strings_event->string = debug_strings_joined; + } + + //////////////////////// + //- rjf: suspend threads which ran + // + ProfScope("suspend threads which ran") + { + for(DMN_W32_EntityNode *n = first_ran_thread; n != 0; n = n->next) + { + DMN_W32_Entity *thread = n->v; + if(thread->kind != DMN_W32_EntityKind_Thread) + { + continue; + } + 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: gather new thread-names + // + ProfScope("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 <= now_time_us()) + { + 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 = now_time_us(); + thread->thread.last_name_hash = name_hash; + } + } + } + } + + ////////////////////////// + //- rjf: restore original memory at trap locations + // + ProfScope("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; + if(trap->flags == 0) + { + 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: clear all debug register states, for flagged-traps + // + ProfScope("clear all debug register states, for flagged-traps") + { + for(DMN_FlaggedTrapTask *t = first_flagged_trap_task; t != 0; t = t->next) + { + DMN_Handle process = t->process; + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + for(DMN_W32_Entity *child = process_entity->first; + child != &dmn_w32_entity_nil; + child = child->next) + { + if(child->kind == DMN_W32_EntityKind_Thread) + { + switch(child->arch) + { + default:{}break; + + //- rjf: x64 + case Arch_x64: + { + X64_RegBlock regs = {0}; + dmn_w32_thread_read_reg_block(child->arch, child->handle, ®s); + regs.dr7 = 0; + dmn_w32_thread_write_reg_block(child->arch, child->handle, ®s); + }break; + } + } + } + } + } + + ////////////////////////// + //- rjf: unset single step bit + // + if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) ProfScope("unset single step bit") + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); + Arch arch = thread->arch; + switch(arch) + { + //- rjf: unimplemented win32/arch combos + case Arch_Null: + case Arch_COUNT: + {}break; + case Arch_arm64: + case Arch_arm32: + case Arch_x86: + {NotImplemented;}break; + + case Arch_x64: + { + if(!GetThreadContext(thread->handle, single_step_thread_ctx)) + { + single_step_thread_ctx = 0; + } + if(single_step_thread_ctx != 0) + { + U64 rflags = single_step_thread_ctx->EFlags|0x2; + U64 new_rflags = rflags & ~0x100; + single_step_thread_ctx->EFlags = new_rflags; + SetThreadContext(thread->handle, single_step_thread_ctx); + } + }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: non-blocking-control-thread access barriers + +internal B32 +dmn_access_open(void) +{ + B32 result = 0; + if(dmn_w32_ctrl_thread) + { + result = 1; + } + else + { + 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) + { + mutex_drop(dmn_w32_shared->access_mutex); + } +} + +//- rjf: processes + +internal U64 +dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) +{ + U64 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + result = (U64)VirtualAllocEx(process_entity->handle, (void *)vaddr, size, MEM_RESERVE, PAGE_READWRITE); + if(result == 0) + { + result = (U64)VirtualAllocEx(process_entity->handle, 0, size, MEM_RESERVE, PAGE_READWRITE); + } + } + return result; +} + +internal void +dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size) +{ + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + (U64)VirtualAllocEx(process_entity->handle, (void *)vaddr, size, MEM_COMMIT, PAGE_READWRITE); + } +} + +internal void +dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size) +{ + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + VirtualFreeEx(process_entity->handle, (void *)vaddr, size, MEM_DECOMMIT); + } +} + +internal void +dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size) +{ + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + VirtualFreeEx(process_entity->handle, (void *)vaddr, 0, MEM_RELEASE); + } +} + +internal void +dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, AccessFlags flags) +{ + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + DWORD old_flags = 0; + DWORD new_flags = PAGE_NOACCESS; + switch(flags) + { + default:{}break; + case AccessFlag_Execute:{new_flags = PAGE_EXECUTE;}break; + case AccessFlag_Execute|AccessFlag_Read:{new_flags = PAGE_EXECUTE_READ;}break; + case AccessFlag_Execute|AccessFlag_Read|AccessFlag_Write:{new_flags = PAGE_EXECUTE_READWRITE;}break; + case AccessFlag_Read:{new_flags = PAGE_READONLY;}break; + case AccessFlag_Read|AccessFlag_Write:{new_flags = PAGE_READWRITE;}break; + } + VirtualProtectEx(process_entity->handle, (void *)vaddr, size, new_flags, &old_flags); + } +} + +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 Arch +dmn_arch_from_thread(DMN_Handle handle) +{ + Arch arch = Arch_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 Arch_Null: + case Arch_COUNT: + {}break; + case Arch_arm64: + case Arch_arm32: + case Arch_x86: + {NotImplemented;}break; + case Arch_x64: + { + U64 stack_base_addr = tlb + 0x8; + dmn_w32_process_read(process->handle, r1u64(stack_base_addr, stack_base_addr+8), &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 Arch_Null: + case Arch_COUNT: + {}break; + case Arch_arm64: + case Arch_arm32: + case Arch_x86: + {NotImplemented;}break; + case Arch_x64: + { + result += 88; + }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); +} diff --git a/src/demon/win32/demon_core_win32.h b/src/win32/demon/win32_demon.h similarity index 97% rename from src/demon/win32/demon_core_win32.h rename to src/win32/demon/win32_demon.h index c19b3cd4..58e7dbc7 100644 --- a/src/demon/win32/demon_core_win32.h +++ b/src/win32/demon/win32_demon.h @@ -1,359 +1,359 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef DEMON_CORE_WIN32_H -#define DEMON_CORE_WIN32_H - -//////////////////////////////// -//~ rjf: Windows Includes - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -//////////////////////////////// -//~ rjf: Win32 Exception Codes - -#define DMN_W32_EXCEPTION_BREAKPOINT 0x80000003u -#define DMN_W32_EXCEPTION_SINGLE_STEP 0x80000004u -#define DMN_W32_EXCEPTION_LONG_JUMP 0x80000026u -#define DMN_W32_EXCEPTION_ACCESS_VIOLATION 0xC0000005u -#define DMN_W32_EXCEPTION_ARRAY_BOUNDS_EXCEEDED 0xC000008Cu -#define DMN_W32_EXCEPTION_DATA_TYPE_MISALIGNMENT 0x80000002u -#define DMN_W32_EXCEPTION_GUARD_PAGE_VIOLATION 0x80000001u -#define DMN_W32_EXCEPTION_FLT_DENORMAL_OPERAND 0xC000008Du -#define DMN_W32_EXCEPTION_FLT_DEVIDE_BY_ZERO 0xC000008Eu -#define DMN_W32_EXCEPTION_FLT_INEXACT_RESULT 0xC000008Fu -#define DMN_W32_EXCEPTION_FLT_INVALID_OPERATION 0xC0000090u -#define DMN_W32_EXCEPTION_FLT_OVERFLOW 0xC0000091u -#define DMN_W32_EXCEPTION_FLT_STACK_CHECK 0xC0000092u -#define DMN_W32_EXCEPTION_FLT_UNDERFLOW 0xC0000093u -#define DMN_W32_EXCEPTION_INT_DIVIDE_BY_ZERO 0xC0000094u -#define DMN_W32_EXCEPTION_INT_OVERFLOW 0xC0000095u -#define DMN_W32_EXCEPTION_PRIVILEGED_INSTRUCTION 0xC0000096u -#define DMN_W32_EXCEPTION_ILLEGAL_INSTRUCTION 0xC000001Du -#define DMN_W32_EXCEPTION_IN_PAGE_ERROR 0xC0000006u -#define DMN_W32_EXCEPTION_INVALID_DISPOSITION 0xC0000026u -#define DMN_W32_EXCEPTION_NONCONTINUABLE 0xC0000025u -#define DMN_W32_EXCEPTION_STACK_OVERFLOW 0xC00000FDu -#define DMN_W32_EXCEPTION_INVALID_HANDLE 0xC0000008u -#define DMN_W32_EXCEPTION_UNWIND_CONSOLIDATE 0x80000029u -#define DMN_W32_EXCEPTION_DLL_NOT_FOUND 0xC0000135u -#define DMN_W32_EXCEPTION_ORDINAL_NOT_FOUND 0xC0000138u -#define DMN_W32_EXCEPTION_ENTRY_POINT_NOT_FOUND 0xC0000139u -#define DMN_W32_EXCEPTION_DLL_INIT_FAILED 0xC0000142u -#define DMN_W32_EXCEPTION_CONTROL_C_EXIT 0xC000013Au -#define DMN_W32_EXCEPTION_FLT_MULTIPLE_FAULTS 0xC00002B4u -#define DMN_W32_EXCEPTION_FLT_MULTIPLE_TRAPS 0xC00002B5u -#define DMN_W32_EXCEPTION_NAT_CONSUMPTION 0xC00002C9u -#define DMN_W32_EXCEPTION_HEAP_CORRUPTION 0xC0000374u -#define DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN 0xC0000409u -#define DMN_W32_EXCEPTION_INVALID_CRUNTIME_PARAM 0xC0000417u -#define DMN_W32_EXCEPTION_ASSERT_FAILURE 0xC0000420u -#define DMN_W32_EXCEPTION_NO_MEMORY 0xC0000017u -#define DMN_W32_EXCEPTION_THROW 0xE06D7363u -#define DMN_W32_EXCEPTION_SET_THREAD_NAME 0x406d1388u -#define DMN_w32_EXCEPTION_CLRDBG_NOTIFICATION 0x04242420u -#define DMN_w32_EXCEPTION_CLR 0xE0434352u -#define DMN_W32_EXCEPTION_RADDBG_SET_THREAD_COLOR 0x00524144u -#define DMN_W32_EXCEPTION_RADDBG_SET_BREAKPOINT 0x00524145u -#define DMN_W32_EXCEPTION_RADDBG_SET_VADDR_RANGE_NOTE 0x00524156u - -//////////////////////////////// -//~ rjf: Win32 Exception ExceptionInformation Codes -// -// used as a subcode, apparently in all cases, for DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN. -// need to somehow pipe this through & interpret it correctly in outer layers... @fastfail - -#define DMN_W32_FAST_FAIL_LEGACY_GS_VIOLATION 0 -#define DMN_W32_FAST_FAIL_VTGUARD_CHECK_FAILURE 1 -#define DMN_W32_FAST_FAIL_STACK_COOKIE_CHECK_FAILURE 2 -#define DMN_W32_FAST_FAIL_CORRUPT_LIST_ENTRY 3 -#define DMN_W32_FAST_FAIL_INCORRECT_STACK 4 -#define DMN_W32_FAST_FAIL_INVALID_ARG 5 -#define DMN_W32_FAST_FAIL_GS_COOKIE_INIT 6 -#define DMN_W32_FAST_FAIL_FATAL_APP_EXIT 7 -#define DMN_W32_FAST_FAIL_RANGE_CHECK_FAILURE 8 -#define DMN_W32_FAST_FAIL_UNSAFE_REGISTRY_ACCESS 9 -#define DMN_W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE 10 -#define DMN_W32_FAST_FAIL_GUARD_WRITE_CHECK_FAILURE 11 -#define DMN_W32_FAST_FAIL_INVALID_FIBER_SWITCH 12 -#define DMN_W32_FAST_FAIL_INVALID_SET_OF_CONTEXT 13 -#define DMN_W32_FAST_FAIL_INVALID_REFERENCE_COUNT 14 -#define DMN_W32_FAST_FAIL_INVALID_JUMP_BUFFER 18 -#define DMN_W32_FAST_FAIL_MRDATA_MODIFIED 19 -#define DMN_W32_FAST_FAIL_CERTIFICATION_FAILURE 20 -#define DMN_W32_FAST_FAIL_INVALID_EXCEPTION_CHAIN 21 -#define DMN_W32_FAST_FAIL_CRYPTO_LIBRARY 22 -#define DMN_W32_FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT 23 -#define DMN_W32_FAST_FAIL_INVALID_IMAGE_BASE 24 -#define DMN_W32_FAST_FAIL_DLOAD_PROTECTION_FAILURE 25 -#define DMN_W32_FAST_FAIL_UNSAFE_EXTENSION_CALL 26 -#define DMN_W32_FAST_FAIL_DEPRECATED_SERVICE_INVOKED 27 -#define DMN_W32_FAST_FAIL_INVALID_BUFFER_ACCESS 28 -#define DMN_W32_FAST_FAIL_INVALID_BALANCED_TREE 29 -#define DMN_W32_FAST_FAIL_INVALID_NEXT_THREAD 30 -#define DMN_W32_FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED 31 // Telemetry, nonfatal -#define DMN_W32_FAST_FAIL_APCS_DISABLED 32 -#define DMN_W32_FAST_FAIL_INVALID_IDLE_STATE 33 -#define DMN_W32_FAST_FAIL_MRDATA_PROTECTION_FAILURE 34 -#define DMN_W32_FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION 35 -#define DMN_W32_FAST_FAIL_INVALID_LOCK_STATE 36 -#define DMN_W32_FAST_FAIL_GUARD_JUMPTABLE 37 // Known to compiler, must retain value 37 -#define DMN_W32_FAST_FAIL_INVALID_LONGJUMP_TARGET 38 -#define DMN_W32_FAST_FAIL_INVALID_DISPATCH_CONTEXT 39 -#define DMN_W32_FAST_FAIL_INVALID_THREAD 40 -#define DMN_W32_FAST_FAIL_INVALID_SYSCALL_NUMBER 41 // Telemetry, nonfatal -#define DMN_W32_FAST_FAIL_INVALID_FILE_OPERATION 42 // Telemetry, nonfatal -#define DMN_W32_FAST_FAIL_LPAC_ACCESS_DENIED 43 // Telemetry, nonfatal -#define DMN_W32_FAST_FAIL_GUARD_SS_FAILURE 44 -#define DMN_W32_FAST_FAIL_LOADER_CONTINUITY_FAILURE 45 // Telemetry, nonfatal -#define DMN_W32_FAST_FAIL_GUARD_EXPORT_SUPPRESSION_FAILURE 46 -#define DMN_W32_FAST_FAIL_INVALID_CONTROL_STACK 47 -#define DMN_W32_FAST_FAIL_SET_CONTEXT_DENIED 48 -#define DMN_W32_FAST_FAIL_INVALID_IAT 49 -#define DMN_W32_FAST_FAIL_HEAP_METADATA_CORRUPTION 50 -#define DMN_W32_FAST_FAIL_PAYLOAD_RESTRICTION_VIOLATION 51 -#define DMN_W32_FAST_FAIL_LOW_LABEL_ACCESS_DENIED 52 // Telemetry, nonfatal -#define DMN_W32_FAST_FAIL_ENCLAVE_CALL_FAILURE 53 -#define DMN_W32_FAST_FAIL_UNHANDLED_LSS_EXCEPTON 54 -#define DMN_W32_FAST_FAIL_ADMINLESS_ACCESS_DENIED 55 // Telemetry, nonfatal -#define DMN_W32_FAST_FAIL_UNEXPECTED_CALL 56 -#define DMN_W32_FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS 57 -#define DMN_W32_FAST_FAIL_UNEXPECTED_HOST_BEHAVIOR 58 -#define DMN_W32_FAST_FAIL_FLAGS_CORRUPTION 59 -#define DMN_W32_FAST_FAIL_VEH_CORRUPTION 60 -#define DMN_W32_FAST_FAIL_ETW_CORRUPTION 61 -#define DMN_W32_FAST_FAIL_RIO_ABORT 62 -#define DMN_W32_FAST_FAIL_INVALID_PFN 63 -#define DMN_W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE_XFG 64 -#define DMN_W32_FAST_FAIL_CAST_GUARD 65 // Known to compiler, must retain value 65 -#define DMN_W32_FAST_FAIL_HOST_VISIBILITY_CHANGE 66 -#define DMN_W32_FAST_FAIL_KERNEL_CET_SHADOW_STACK_ASSIST 67 -#define DMN_W32_FAST_FAIL_PATCH_CALLBACK_FAILED 68 -#define DMN_W32_FAST_FAIL_NTDLL_PATCH_FAILED 69 -#define DMN_W32_FAST_FAIL_INVALID_FLS_DATA 70 -#define DMN_W32_FAST_FAIL_INVALID_FAST_FAIL_CODE 0xFFFFFFFF - -//////////////////////////////// -//~ rjf: Win32 Register Codes - -#define DMN_W32_CTX_X86 0x00010000 -#define DMN_W32_CTX_X64 0x00100000 - -#define DMN_W32_CTX_INTEL_CONTROL 0x0001 // segss, rsp, segcs, rip, and rflags -#define DMN_W32_CTX_INTEL_INTEGER 0x0002 // rax, rcx, rdx, rbx, rbp, rsi, rdi, and r8-r15 -#define DMN_W32_CTX_INTEL_SEGMENTS 0x0004 // segds, seges, segfs, and seggs -#define DMN_W32_CTX_INTEL_FLOATS 0x0008 // xmm0-xmm15 -#define DMN_W32_CTX_INTEL_DEBUG 0x0010 // dr0-dr3 and dr6-dr7 -#define DMN_W32_CTX_INTEL_EXTENDED 0x0020 -#define DMN_W32_CTX_INTEL_XSTATE 0x0040 - -#define DMN_W32_CTX_X86_ALL (DMN_W32_CTX_X86 | \ -DMN_W32_CTX_INTEL_CONTROL | DMN_W32_CTX_INTEL_INTEGER | \ -DMN_W32_CTX_INTEL_SEGMENTS | DMN_W32_CTX_INTEL_DEBUG | \ -DMN_W32_CTX_INTEL_EXTENDED) -#define DMN_W32_CTX_X64_ALL (DMN_W32_CTX_X64 | \ -DMN_W32_CTX_INTEL_CONTROL | DMN_W32_CTX_INTEL_INTEGER | \ -DMN_W32_CTX_INTEL_SEGMENTS | DMN_W32_CTX_INTEL_FLOATS | \ -DMN_W32_CTX_INTEL_DEBUG) - -//////////////////////////////// -//~ rjf: Per-Entity State - -typedef enum DMN_W32_EntityKind -{ - DMN_W32_EntityKind_Null, - DMN_W32_EntityKind_Root, - DMN_W32_EntityKind_Process, - DMN_W32_EntityKind_Thread, - DMN_W32_EntityKind_Module, - DMN_W32_EntityKind_COUNT -} -DMN_W32_EntityKind; - -typedef struct DMN_W32_Entity DMN_W32_Entity; -struct DMN_W32_Entity -{ - DMN_W32_Entity *first; - DMN_W32_Entity *last; - DMN_W32_Entity *next; - DMN_W32_Entity *prev; - DMN_W32_Entity *parent; - DMN_W32_EntityKind kind; - U32 gen; - U64 id; - HANDLE handle; - Arch arch; - union - { - struct - { - U64 injection_address; - B32 did_first_bp; - } - proc; - struct - { - U64 thread_local_base; - U64 last_name_hash; - U64 name_gather_time_us; - } - thread; - struct - { - Rng1U64 vaddr_range; - U64 address_of_name_pointer; - B32 is_main; - B32 name_is_unicode; - } - module; - }; -}; - -typedef struct DMN_W32_EntityNode DMN_W32_EntityNode; -struct DMN_W32_EntityNode -{ - DMN_W32_EntityNode *next; - DMN_W32_Entity *v; -}; - -typedef struct DMN_W32_EntityIDHashNode DMN_W32_EntityIDHashNode; -struct DMN_W32_EntityIDHashNode -{ - DMN_W32_EntityIDHashNode *next; - DMN_W32_EntityIDHashNode *prev; - U64 id; - DMN_W32_Entity *entity; -}; - -typedef struct DMN_W32_EntityIDHashSlot DMN_W32_EntityIDHashSlot; -struct DMN_W32_EntityIDHashSlot -{ - DMN_W32_EntityIDHashNode *first; - DMN_W32_EntityIDHashNode *last; -}; - -//////////////////////////////// -//~ rjf: Injection Types - -typedef struct DMN_W32_InjectedBreak DMN_W32_InjectedBreak; -struct DMN_W32_InjectedBreak -{ - U64 code; - U64 user_data; -}; - -#define DMN_W32_INJECTED_CODE_SIZE 32 - -//////////////////////////////// -//~ rjf: Image Info Types - -typedef struct DMN_W32_ImageInfo DMN_W32_ImageInfo; -struct DMN_W32_ImageInfo -{ - Arch arch; - U32 size; - U64 tls_index; -}; - -//////////////////////////////// -//~ rjf: Dynamically-Loaded Win32 Function Types - -typedef HRESULT DMN_W32_GetThreadDescriptionFunctionType(HANDLE hThread, WCHAR **ppszThreadDescription); - -//////////////////////////////// -//~ rjf: Shared State Bundle - -typedef struct DMN_W32_Shared DMN_W32_Shared; -struct DMN_W32_Shared -{ - // rjf: top-level info - Arena *arena; - String8List env_strings; - - // rjf: access locking mechanism - Mutex access_mutex; - B32 access_run_state; - - // rjf: detaching info - Arena *detach_arena; - DMN_HandleList detach_processes; - - // rjf: entity state - Arena *entities_arena; - DMN_W32_Entity *entities_base; - DMN_W32_Entity *entities_first_free; - U64 entities_count; - DMN_W32_EntityIDHashSlot *entities_id_hash_slots; - U64 entities_id_hash_slots_count; - DMN_W32_EntityIDHashNode *entities_id_hash_node_free; - - // rjf: launch state - B32 new_process_pending; - - // rjf: run results - B32 resume_needed; - U32 resume_pid; - U32 resume_tid; - B32 exception_not_handled; - - // rjf: halting info - DMN_Handle halter_process; - U32 halter_tid; -}; - -//////////////////////////////// -//~ rjf: Globals - -global DMN_W32_Shared *dmn_w32_shared = 0; -global DMN_W32_Entity dmn_w32_entity_nil = {&dmn_w32_entity_nil, &dmn_w32_entity_nil, &dmn_w32_entity_nil, &dmn_w32_entity_nil, &dmn_w32_entity_nil}; -global DMN_W32_GetThreadDescriptionFunctionType *dmn_w32_GetThreadDescription = 0; -thread_static B32 dmn_w32_ctrl_thread = 0; - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal U64 dmn_w32_hash_from_string(String8 string); -internal U64 dmn_w32_hash_from_id(U64 id); - -//////////////////////////////// -//~ rjf: Entity Helpers - -//- rjf: entity <-> handle -internal DMN_Handle dmn_w32_handle_from_entity(DMN_W32_Entity *entity); -internal DMN_W32_Entity *dmn_w32_entity_from_handle(DMN_Handle handle); - -//- rjf: entity allocation/deallocation -internal DMN_W32_Entity *dmn_w32_entity_alloc(DMN_W32_Entity *parent, DMN_W32_EntityKind kind, U64 id); -internal void dmn_w32_entity_release(DMN_W32_Entity *entity); - -//- rjf: kind*id -> entity -internal DMN_W32_Entity *dmn_w32_entity_from_kind_id(DMN_W32_EntityKind kind, U64 id); - -//////////////////////////////// -//~ rjf: Module Info Extraction - -internal String8 dmn_w32_full_path_from_module(Arena *arena, DMN_W32_Entity *module); - -//////////////////////////////// -//~ rjf: Win32-Level Process/Thread Reads/Writes - -//- rjf: processes -internal U64 dmn_w32_process_read(HANDLE process, Rng1U64 range, void *dst); -internal B32 dmn_w32_process_write(HANDLE process, Rng1U64 range, void *src); -internal String8 dmn_w32_read_memory_str(Arena *arena, HANDLE process_handle, U64 address); -internal String16 dmn_w32_read_memory_str16(Arena *arena, HANDLE process_handle, U64 address); -#define dmn_w32_process_read_struct(process, vaddr, ptr) dmn_w32_process_read((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) -#define dmn_w32_process_write_struct(process, vaddr, ptr) dmn_w32_process_write((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) -internal DMN_W32_ImageInfo dmn_w32_image_info_from_process_base_vaddr(HANDLE process, U64 base_vaddr); - -//- rjf: threads -internal B32 dmn_w32_thread_read_reg_block(Arch arch, HANDLE thread, void *reg_block); -internal B32 dmn_w32_thread_write_reg_block(Arch arch, HANDLE thread, void *reg_block); - -//- rjf: remote thread injection -internal DWORD dmn_w32_inject_thread(HANDLE process, U64 start_address); - -#endif // DEMON_CORE_WIN32_H +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DEMON_CORE_WIN32_H +#define DEMON_CORE_WIN32_H + +//////////////////////////////// +//~ rjf: Windows Includes + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +//////////////////////////////// +//~ rjf: Win32 Exception Codes + +#define DMN_W32_EXCEPTION_BREAKPOINT 0x80000003u +#define DMN_W32_EXCEPTION_SINGLE_STEP 0x80000004u +#define DMN_W32_EXCEPTION_LONG_JUMP 0x80000026u +#define DMN_W32_EXCEPTION_ACCESS_VIOLATION 0xC0000005u +#define DMN_W32_EXCEPTION_ARRAY_BOUNDS_EXCEEDED 0xC000008Cu +#define DMN_W32_EXCEPTION_DATA_TYPE_MISALIGNMENT 0x80000002u +#define DMN_W32_EXCEPTION_GUARD_PAGE_VIOLATION 0x80000001u +#define DMN_W32_EXCEPTION_FLT_DENORMAL_OPERAND 0xC000008Du +#define DMN_W32_EXCEPTION_FLT_DEVIDE_BY_ZERO 0xC000008Eu +#define DMN_W32_EXCEPTION_FLT_INEXACT_RESULT 0xC000008Fu +#define DMN_W32_EXCEPTION_FLT_INVALID_OPERATION 0xC0000090u +#define DMN_W32_EXCEPTION_FLT_OVERFLOW 0xC0000091u +#define DMN_W32_EXCEPTION_FLT_STACK_CHECK 0xC0000092u +#define DMN_W32_EXCEPTION_FLT_UNDERFLOW 0xC0000093u +#define DMN_W32_EXCEPTION_INT_DIVIDE_BY_ZERO 0xC0000094u +#define DMN_W32_EXCEPTION_INT_OVERFLOW 0xC0000095u +#define DMN_W32_EXCEPTION_PRIVILEGED_INSTRUCTION 0xC0000096u +#define DMN_W32_EXCEPTION_ILLEGAL_INSTRUCTION 0xC000001Du +#define DMN_W32_EXCEPTION_IN_PAGE_ERROR 0xC0000006u +#define DMN_W32_EXCEPTION_INVALID_DISPOSITION 0xC0000026u +#define DMN_W32_EXCEPTION_NONCONTINUABLE 0xC0000025u +#define DMN_W32_EXCEPTION_STACK_OVERFLOW 0xC00000FDu +#define DMN_W32_EXCEPTION_INVALID_HANDLE 0xC0000008u +#define DMN_W32_EXCEPTION_UNWIND_CONSOLIDATE 0x80000029u +#define DMN_W32_EXCEPTION_DLL_NOT_FOUND 0xC0000135u +#define DMN_W32_EXCEPTION_ORDINAL_NOT_FOUND 0xC0000138u +#define DMN_W32_EXCEPTION_ENTRY_POINT_NOT_FOUND 0xC0000139u +#define DMN_W32_EXCEPTION_DLL_INIT_FAILED 0xC0000142u +#define DMN_W32_EXCEPTION_CONTROL_C_EXIT 0xC000013Au +#define DMN_W32_EXCEPTION_FLT_MULTIPLE_FAULTS 0xC00002B4u +#define DMN_W32_EXCEPTION_FLT_MULTIPLE_TRAPS 0xC00002B5u +#define DMN_W32_EXCEPTION_NAT_CONSUMPTION 0xC00002C9u +#define DMN_W32_EXCEPTION_HEAP_CORRUPTION 0xC0000374u +#define DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN 0xC0000409u +#define DMN_W32_EXCEPTION_INVALID_CRUNTIME_PARAM 0xC0000417u +#define DMN_W32_EXCEPTION_ASSERT_FAILURE 0xC0000420u +#define DMN_W32_EXCEPTION_NO_MEMORY 0xC0000017u +#define DMN_W32_EXCEPTION_THROW 0xE06D7363u +#define DMN_W32_EXCEPTION_SET_THREAD_NAME 0x406d1388u +#define DMN_w32_EXCEPTION_CLRDBG_NOTIFICATION 0x04242420u +#define DMN_w32_EXCEPTION_CLR 0xE0434352u +#define DMN_W32_EXCEPTION_RADDBG_SET_THREAD_COLOR 0x00524144u +#define DMN_W32_EXCEPTION_RADDBG_SET_BREAKPOINT 0x00524145u +#define DMN_W32_EXCEPTION_RADDBG_SET_VADDR_RANGE_NOTE 0x00524156u + +//////////////////////////////// +//~ rjf: Win32 Exception ExceptionInformation Codes +// +// used as a subcode, apparently in all cases, for DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN. +// need to somehow pipe this through & interpret it correctly in outer layers... @fastfail + +#define DMN_W32_FAST_FAIL_LEGACY_GS_VIOLATION 0 +#define DMN_W32_FAST_FAIL_VTGUARD_CHECK_FAILURE 1 +#define DMN_W32_FAST_FAIL_STACK_COOKIE_CHECK_FAILURE 2 +#define DMN_W32_FAST_FAIL_CORRUPT_LIST_ENTRY 3 +#define DMN_W32_FAST_FAIL_INCORRECT_STACK 4 +#define DMN_W32_FAST_FAIL_INVALID_ARG 5 +#define DMN_W32_FAST_FAIL_GS_COOKIE_INIT 6 +#define DMN_W32_FAST_FAIL_FATAL_APP_EXIT 7 +#define DMN_W32_FAST_FAIL_RANGE_CHECK_FAILURE 8 +#define DMN_W32_FAST_FAIL_UNSAFE_REGISTRY_ACCESS 9 +#define DMN_W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE 10 +#define DMN_W32_FAST_FAIL_GUARD_WRITE_CHECK_FAILURE 11 +#define DMN_W32_FAST_FAIL_INVALID_FIBER_SWITCH 12 +#define DMN_W32_FAST_FAIL_INVALID_SET_OF_CONTEXT 13 +#define DMN_W32_FAST_FAIL_INVALID_REFERENCE_COUNT 14 +#define DMN_W32_FAST_FAIL_INVALID_JUMP_BUFFER 18 +#define DMN_W32_FAST_FAIL_MRDATA_MODIFIED 19 +#define DMN_W32_FAST_FAIL_CERTIFICATION_FAILURE 20 +#define DMN_W32_FAST_FAIL_INVALID_EXCEPTION_CHAIN 21 +#define DMN_W32_FAST_FAIL_CRYPTO_LIBRARY 22 +#define DMN_W32_FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT 23 +#define DMN_W32_FAST_FAIL_INVALID_IMAGE_BASE 24 +#define DMN_W32_FAST_FAIL_DLOAD_PROTECTION_FAILURE 25 +#define DMN_W32_FAST_FAIL_UNSAFE_EXTENSION_CALL 26 +#define DMN_W32_FAST_FAIL_DEPRECATED_SERVICE_INVOKED 27 +#define DMN_W32_FAST_FAIL_INVALID_BUFFER_ACCESS 28 +#define DMN_W32_FAST_FAIL_INVALID_BALANCED_TREE 29 +#define DMN_W32_FAST_FAIL_INVALID_NEXT_THREAD 30 +#define DMN_W32_FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED 31 // Telemetry, nonfatal +#define DMN_W32_FAST_FAIL_APCS_DISABLED 32 +#define DMN_W32_FAST_FAIL_INVALID_IDLE_STATE 33 +#define DMN_W32_FAST_FAIL_MRDATA_PROTECTION_FAILURE 34 +#define DMN_W32_FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION 35 +#define DMN_W32_FAST_FAIL_INVALID_LOCK_STATE 36 +#define DMN_W32_FAST_FAIL_GUARD_JUMPTABLE 37 // Known to compiler, must retain value 37 +#define DMN_W32_FAST_FAIL_INVALID_LONGJUMP_TARGET 38 +#define DMN_W32_FAST_FAIL_INVALID_DISPATCH_CONTEXT 39 +#define DMN_W32_FAST_FAIL_INVALID_THREAD 40 +#define DMN_W32_FAST_FAIL_INVALID_SYSCALL_NUMBER 41 // Telemetry, nonfatal +#define DMN_W32_FAST_FAIL_INVALID_FILE_OPERATION 42 // Telemetry, nonfatal +#define DMN_W32_FAST_FAIL_LPAC_ACCESS_DENIED 43 // Telemetry, nonfatal +#define DMN_W32_FAST_FAIL_GUARD_SS_FAILURE 44 +#define DMN_W32_FAST_FAIL_LOADER_CONTINUITY_FAILURE 45 // Telemetry, nonfatal +#define DMN_W32_FAST_FAIL_GUARD_EXPORT_SUPPRESSION_FAILURE 46 +#define DMN_W32_FAST_FAIL_INVALID_CONTROL_STACK 47 +#define DMN_W32_FAST_FAIL_SET_CONTEXT_DENIED 48 +#define DMN_W32_FAST_FAIL_INVALID_IAT 49 +#define DMN_W32_FAST_FAIL_HEAP_METADATA_CORRUPTION 50 +#define DMN_W32_FAST_FAIL_PAYLOAD_RESTRICTION_VIOLATION 51 +#define DMN_W32_FAST_FAIL_LOW_LABEL_ACCESS_DENIED 52 // Telemetry, nonfatal +#define DMN_W32_FAST_FAIL_ENCLAVE_CALL_FAILURE 53 +#define DMN_W32_FAST_FAIL_UNHANDLED_LSS_EXCEPTON 54 +#define DMN_W32_FAST_FAIL_ADMINLESS_ACCESS_DENIED 55 // Telemetry, nonfatal +#define DMN_W32_FAST_FAIL_UNEXPECTED_CALL 56 +#define DMN_W32_FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS 57 +#define DMN_W32_FAST_FAIL_UNEXPECTED_HOST_BEHAVIOR 58 +#define DMN_W32_FAST_FAIL_FLAGS_CORRUPTION 59 +#define DMN_W32_FAST_FAIL_VEH_CORRUPTION 60 +#define DMN_W32_FAST_FAIL_ETW_CORRUPTION 61 +#define DMN_W32_FAST_FAIL_RIO_ABORT 62 +#define DMN_W32_FAST_FAIL_INVALID_PFN 63 +#define DMN_W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE_XFG 64 +#define DMN_W32_FAST_FAIL_CAST_GUARD 65 // Known to compiler, must retain value 65 +#define DMN_W32_FAST_FAIL_HOST_VISIBILITY_CHANGE 66 +#define DMN_W32_FAST_FAIL_KERNEL_CET_SHADOW_STACK_ASSIST 67 +#define DMN_W32_FAST_FAIL_PATCH_CALLBACK_FAILED 68 +#define DMN_W32_FAST_FAIL_NTDLL_PATCH_FAILED 69 +#define DMN_W32_FAST_FAIL_INVALID_FLS_DATA 70 +#define DMN_W32_FAST_FAIL_INVALID_FAST_FAIL_CODE 0xFFFFFFFF + +//////////////////////////////// +//~ rjf: Win32 Register Codes + +#define DMN_W32_CTX_X86 0x00010000 +#define DMN_W32_CTX_X64 0x00100000 + +#define DMN_W32_CTX_INTEL_CONTROL 0x0001 // segss, rsp, segcs, rip, and rflags +#define DMN_W32_CTX_INTEL_INTEGER 0x0002 // rax, rcx, rdx, rbx, rbp, rsi, rdi, and r8-r15 +#define DMN_W32_CTX_INTEL_SEGMENTS 0x0004 // segds, seges, segfs, and seggs +#define DMN_W32_CTX_INTEL_FLOATS 0x0008 // xmm0-xmm15 +#define DMN_W32_CTX_INTEL_DEBUG 0x0010 // dr0-dr3 and dr6-dr7 +#define DMN_W32_CTX_INTEL_EXTENDED 0x0020 +#define DMN_W32_CTX_INTEL_XSTATE 0x0040 + +#define DMN_W32_CTX_X86_ALL (DMN_W32_CTX_X86 | \ +DMN_W32_CTX_INTEL_CONTROL | DMN_W32_CTX_INTEL_INTEGER | \ +DMN_W32_CTX_INTEL_SEGMENTS | DMN_W32_CTX_INTEL_DEBUG | \ +DMN_W32_CTX_INTEL_EXTENDED) +#define DMN_W32_CTX_X64_ALL (DMN_W32_CTX_X64 | \ +DMN_W32_CTX_INTEL_CONTROL | DMN_W32_CTX_INTEL_INTEGER | \ +DMN_W32_CTX_INTEL_SEGMENTS | DMN_W32_CTX_INTEL_FLOATS | \ +DMN_W32_CTX_INTEL_DEBUG) + +//////////////////////////////// +//~ rjf: Per-Entity State + +typedef enum DMN_W32_EntityKind +{ + DMN_W32_EntityKind_Null, + DMN_W32_EntityKind_Root, + DMN_W32_EntityKind_Process, + DMN_W32_EntityKind_Thread, + DMN_W32_EntityKind_Module, + DMN_W32_EntityKind_COUNT +} +DMN_W32_EntityKind; + +typedef struct DMN_W32_Entity DMN_W32_Entity; +struct DMN_W32_Entity +{ + DMN_W32_Entity *first; + DMN_W32_Entity *last; + DMN_W32_Entity *next; + DMN_W32_Entity *prev; + DMN_W32_Entity *parent; + DMN_W32_EntityKind kind; + U32 gen; + U64 id; + HANDLE handle; + Arch arch; + union + { + struct + { + U64 injection_address; + B32 did_first_bp; + } + proc; + struct + { + U64 thread_local_base; + U64 last_name_hash; + U64 name_gather_time_us; + } + thread; + struct + { + Rng1U64 vaddr_range; + U64 address_of_name_pointer; + B32 is_main; + B32 name_is_unicode; + } + module; + }; +}; + +typedef struct DMN_W32_EntityNode DMN_W32_EntityNode; +struct DMN_W32_EntityNode +{ + DMN_W32_EntityNode *next; + DMN_W32_Entity *v; +}; + +typedef struct DMN_W32_EntityIDHashNode DMN_W32_EntityIDHashNode; +struct DMN_W32_EntityIDHashNode +{ + DMN_W32_EntityIDHashNode *next; + DMN_W32_EntityIDHashNode *prev; + U64 id; + DMN_W32_Entity *entity; +}; + +typedef struct DMN_W32_EntityIDHashSlot DMN_W32_EntityIDHashSlot; +struct DMN_W32_EntityIDHashSlot +{ + DMN_W32_EntityIDHashNode *first; + DMN_W32_EntityIDHashNode *last; +}; + +//////////////////////////////// +//~ rjf: Injection Types + +typedef struct DMN_W32_InjectedBreak DMN_W32_InjectedBreak; +struct DMN_W32_InjectedBreak +{ + U64 code; + U64 user_data; +}; + +#define DMN_W32_INJECTED_CODE_SIZE 32 + +//////////////////////////////// +//~ rjf: Image Info Types + +typedef struct DMN_W32_ImageInfo DMN_W32_ImageInfo; +struct DMN_W32_ImageInfo +{ + Arch arch; + U32 size; + U64 tls_index; +}; + +//////////////////////////////// +//~ rjf: Dynamically-Loaded Win32 Function Types + +typedef HRESULT DMN_W32_GetThreadDescriptionFunctionType(HANDLE hThread, WCHAR **ppszThreadDescription); + +//////////////////////////////// +//~ rjf: Shared State Bundle + +typedef struct DMN_W32_Shared DMN_W32_Shared; +struct DMN_W32_Shared +{ + // rjf: top-level info + Arena *arena; + String8List env_strings; + + // rjf: access locking mechanism + Mutex access_mutex; + B32 access_run_state; + + // rjf: detaching info + Arena *detach_arena; + DMN_HandleList detach_processes; + + // rjf: entity state + Arena *entities_arena; + DMN_W32_Entity *entities_base; + DMN_W32_Entity *entities_first_free; + U64 entities_count; + DMN_W32_EntityIDHashSlot *entities_id_hash_slots; + U64 entities_id_hash_slots_count; + DMN_W32_EntityIDHashNode *entities_id_hash_node_free; + + // rjf: launch state + B32 new_process_pending; + + // rjf: run results + B32 resume_needed; + U32 resume_pid; + U32 resume_tid; + B32 exception_not_handled; + + // rjf: halting info + DMN_Handle halter_process; + U32 halter_tid; +}; + +//////////////////////////////// +//~ rjf: Globals + +global DMN_W32_Shared *dmn_w32_shared = 0; +global DMN_W32_Entity dmn_w32_entity_nil = {&dmn_w32_entity_nil, &dmn_w32_entity_nil, &dmn_w32_entity_nil, &dmn_w32_entity_nil, &dmn_w32_entity_nil}; +global DMN_W32_GetThreadDescriptionFunctionType *dmn_w32_GetThreadDescription = 0; +thread_static B32 dmn_w32_ctrl_thread = 0; + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 dmn_w32_hash_from_string(String8 string); +internal U64 dmn_w32_hash_from_id(U64 id); + +//////////////////////////////// +//~ rjf: Entity Helpers + +//- rjf: entity <-> handle +internal DMN_Handle dmn_w32_handle_from_entity(DMN_W32_Entity *entity); +internal DMN_W32_Entity *dmn_w32_entity_from_handle(DMN_Handle handle); + +//- rjf: entity allocation/deallocation +internal DMN_W32_Entity *dmn_w32_entity_alloc(DMN_W32_Entity *parent, DMN_W32_EntityKind kind, U64 id); +internal void dmn_w32_entity_release(DMN_W32_Entity *entity); + +//- rjf: kind*id -> entity +internal DMN_W32_Entity *dmn_w32_entity_from_kind_id(DMN_W32_EntityKind kind, U64 id); + +//////////////////////////////// +//~ rjf: Module Info Extraction + +internal String8 dmn_w32_full_path_from_module(Arena *arena, DMN_W32_Entity *module); + +//////////////////////////////// +//~ rjf: Win32-Level Process/Thread Reads/Writes + +//- rjf: processes +internal U64 dmn_w32_process_read(HANDLE process, Rng1U64 range, void *dst); +internal B32 dmn_w32_process_write(HANDLE process, Rng1U64 range, void *src); +internal String8 dmn_w32_read_memory_str(Arena *arena, HANDLE process_handle, U64 address); +internal String16 dmn_w32_read_memory_str16(Arena *arena, HANDLE process_handle, U64 address); +#define dmn_w32_process_read_struct(process, vaddr, ptr) dmn_w32_process_read((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) +#define dmn_w32_process_write_struct(process, vaddr, ptr) dmn_w32_process_write((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) +internal DMN_W32_ImageInfo dmn_w32_image_info_from_process_base_vaddr(HANDLE process, U64 base_vaddr); + +//- rjf: threads +internal B32 dmn_w32_thread_read_reg_block(Arch arch, HANDLE thread, void *reg_block); +internal B32 dmn_w32_thread_write_reg_block(Arch arch, HANDLE thread, void *reg_block); + +//- rjf: remote thread injection +internal DWORD dmn_w32_inject_thread(HANDLE process, U64 start_address); + +#endif // DEMON_CORE_WIN32_H diff --git a/src/os/gfx/win32/os_gfx_win32.c b/src/win32/window_manager/win32_window_manager.c similarity index 96% rename from src/os/gfx/win32/os_gfx_win32.c rename to src/win32/window_manager/win32_window_manager.c index 8ffa577d..c7e8708f 100644 --- a/src/os/gfx/win32/os_gfx_win32.c +++ b/src/win32/window_manager/win32_window_manager.c @@ -1,1687 +1,1687 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Modern Windows SDK Functions -// -// (We must dynamically link to them, since they can be missing in older SDKs) - -typedef BOOL w32_SetProcessDpiAwarenessContext_Type(void* value); -typedef UINT w32_GetDpiForWindow_Type(HWND hwnd); -typedef HRESULT w32_GetDpiForMonitor_Type(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT *dpiX, UINT *dpiY); -typedef int w32_GetSystemMetricsForDpi_Type(int nIndex, UINT dpi); -#define w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((void*)-4) -global w32_GetDpiForWindow_Type *w32_GetDpiForWindow_func = 0; -global w32_GetDpiForMonitor_Type *w32_GetDpiForMonitor_func = 0; -global w32_GetSystemMetricsForDpi_Type *w32_GetSystemMetricsForDpi_func = 0; - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal Rng2F32 -os_w32_rng2f32_from_rect(RECT rect) -{ - Rng2F32 r = {0}; - r.x0 = (F32)rect.left; - r.x1 = (F32)rect.right; - r.y0 = (F32)rect.top; - r.y1 = (F32)rect.bottom; - return r; -} - -//////////////////////////////// -//~ rjf: Windows - -internal OS_Window -os_w32_handle_from_window(OS_W32_Window *window) -{ - OS_Window handle = {(U64)window}; - return handle; -} - -internal OS_W32_Window * -os_w32_window_from_handle(OS_Window handle) -{ - OS_W32_Window *window = (OS_W32_Window *)handle.u64[0]; - return window; -} - -internal OS_W32_Window * -os_w32_window_from_hwnd(HWND hwnd) -{ - OS_W32_Window *result = 0; - for(OS_W32_Window *w = os_w32_gfx_state->first_window; w; w = w->next) - { - if(w->hwnd == hwnd) - { - result = w; - break; - } - } - return result; -} - -internal HWND -os_w32_hwnd_from_window(OS_W32_Window *window) -{ - return window->hwnd; -} - -internal OS_W32_Window * -os_w32_window_alloc(void) -{ - OS_W32_Window *result = os_w32_gfx_state->free_window; - if(result) - { - SLLStackPop(os_w32_gfx_state->free_window); - } - else - { - result = push_array_no_zero(os_w32_gfx_state->arena, OS_W32_Window, 1); - } - MemoryZeroStruct(result); - if(result) - { - DLLPushBack(os_w32_gfx_state->first_window, os_w32_gfx_state->last_window, result); - } - result->last_window_placement.length = sizeof(WINDOWPLACEMENT); - return result; -} - -internal void -os_w32_window_release(OS_W32_Window *window) -{ - if(window->paint_arena != 0) - { - arena_release(window->paint_arena); - } - ReleaseDC(window->hwnd, window->hdc); - DestroyWindow(window->hwnd); - DLLRemove(os_w32_gfx_state->first_window, os_w32_gfx_state->last_window, window); - SLLStackPush(os_w32_gfx_state->free_window, window); -} - -internal OS_Event * -os_w32_push_event(OS_EventKind kind, OS_W32_Window *window) -{ - OS_Event *result = os_event_list_push_new(os_w32_event_arena, &os_w32_event_list, kind); - result->window = os_w32_handle_from_window(window); - result->modifiers = os_get_modifiers(); - return result; -} - -internal OS_Key -os_w32_os_key_from_vkey(WPARAM vkey) -{ - local_persist B32 first = 1; - local_persist OS_Key key_table[256]; - if (first){ - first = 0; - MemoryZeroArray(key_table); - - key_table[(unsigned int)'A'] = OS_Key_A; - key_table[(unsigned int)'B'] = OS_Key_B; - key_table[(unsigned int)'C'] = OS_Key_C; - key_table[(unsigned int)'D'] = OS_Key_D; - key_table[(unsigned int)'E'] = OS_Key_E; - key_table[(unsigned int)'F'] = OS_Key_F; - key_table[(unsigned int)'G'] = OS_Key_G; - key_table[(unsigned int)'H'] = OS_Key_H; - key_table[(unsigned int)'I'] = OS_Key_I; - key_table[(unsigned int)'J'] = OS_Key_J; - key_table[(unsigned int)'K'] = OS_Key_K; - key_table[(unsigned int)'L'] = OS_Key_L; - key_table[(unsigned int)'M'] = OS_Key_M; - key_table[(unsigned int)'N'] = OS_Key_N; - key_table[(unsigned int)'O'] = OS_Key_O; - key_table[(unsigned int)'P'] = OS_Key_P; - key_table[(unsigned int)'Q'] = OS_Key_Q; - key_table[(unsigned int)'R'] = OS_Key_R; - key_table[(unsigned int)'S'] = OS_Key_S; - key_table[(unsigned int)'T'] = OS_Key_T; - key_table[(unsigned int)'U'] = OS_Key_U; - key_table[(unsigned int)'V'] = OS_Key_V; - key_table[(unsigned int)'W'] = OS_Key_W; - key_table[(unsigned int)'X'] = OS_Key_X; - key_table[(unsigned int)'Y'] = OS_Key_Y; - key_table[(unsigned int)'Z'] = OS_Key_Z; - - for (U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1){ - key_table[i] = (OS_Key)j; - } - for (U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1){ - key_table[i] = (OS_Key)j; - } - for (U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1){ - key_table[i] = (OS_Key)j; - } - - key_table[VK_SPACE] = OS_Key_Space; - key_table[VK_OEM_3] = OS_Key_Tick; - key_table[VK_OEM_MINUS] = OS_Key_Minus; - key_table[VK_OEM_PLUS] = OS_Key_Equal; - key_table[VK_OEM_4] = OS_Key_LeftBracket; - key_table[VK_OEM_6] = OS_Key_RightBracket; - key_table[VK_OEM_1] = OS_Key_Semicolon; - key_table[VK_OEM_7] = OS_Key_Quote; - key_table[VK_OEM_COMMA] = OS_Key_Comma; - key_table[VK_OEM_PERIOD]= OS_Key_Period; - key_table[VK_OEM_2] = OS_Key_Slash; - key_table[VK_OEM_5] = OS_Key_BackSlash; - - key_table[VK_TAB] = OS_Key_Tab; - key_table[VK_PAUSE] = OS_Key_Pause; - key_table[VK_ESCAPE] = OS_Key_Esc; - - key_table[VK_UP] = OS_Key_Up; - key_table[VK_LEFT] = OS_Key_Left; - key_table[VK_DOWN] = OS_Key_Down; - key_table[VK_RIGHT] = OS_Key_Right; - - key_table[VK_BACK] = OS_Key_Backspace; - key_table[VK_RETURN] = OS_Key_Return; - - key_table[VK_DELETE] = OS_Key_Delete; - key_table[VK_INSERT] = OS_Key_Insert; - key_table[VK_PRIOR] = OS_Key_PageUp; - key_table[VK_NEXT] = OS_Key_PageDown; - key_table[VK_HOME] = OS_Key_Home; - key_table[VK_END] = OS_Key_End; - - key_table[VK_CAPITAL] = OS_Key_CapsLock; - key_table[VK_NUMLOCK] = OS_Key_NumLock; - key_table[VK_SCROLL] = OS_Key_ScrollLock; - key_table[VK_APPS] = OS_Key_Menu; - - key_table[VK_CONTROL] = OS_Key_Ctrl; - key_table[VK_LCONTROL] = OS_Key_Ctrl; - key_table[VK_RCONTROL] = OS_Key_Ctrl; - key_table[VK_SHIFT] = OS_Key_Shift; - key_table[VK_LSHIFT] = OS_Key_Shift; - key_table[VK_RSHIFT] = OS_Key_Shift; - key_table[VK_MENU] = OS_Key_Alt; - key_table[VK_LMENU] = OS_Key_Alt; - key_table[VK_RMENU] = OS_Key_Alt; - - key_table[VK_DIVIDE] = OS_Key_NumSlash; - key_table[VK_MULTIPLY] = OS_Key_NumStar; - key_table[VK_SUBTRACT] = OS_Key_NumMinus; - key_table[VK_ADD] = OS_Key_NumPlus; - key_table[VK_DECIMAL] = OS_Key_NumPeriod; - - for (U32 i = 0; i < 10; i += 1){ - key_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i); - } - - for (U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1){ - key_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j); - } - } - - OS_Key key = key_table[vkey&bitmask8]; - return key; -} - -internal WPARAM -os_w32_vkey_from_os_key(OS_Key key) -{ - WPARAM result = 0; - { - local_persist B32 initialized = 0; - local_persist WPARAM vkey_table[OS_Key_COUNT] = {0}; - if(initialized == 0) - { - initialized = 1; - vkey_table[OS_Key_Esc] = VK_ESCAPE; - for(OS_Key key = OS_Key_F1; key <= OS_Key_F24; key = (OS_Key)(key+1)) - { - vkey_table[key] = VK_F1+(key-OS_Key_F1); - } - vkey_table[OS_Key_Tick] = VK_OEM_3; - for(OS_Key key = OS_Key_0; key <= OS_Key_9; key = (OS_Key)(key+1)) - { - vkey_table[key] = '0'+(key-OS_Key_0); - } - vkey_table[OS_Key_Minus] = VK_OEM_MINUS; - vkey_table[OS_Key_Equal] = VK_OEM_PLUS; - vkey_table[OS_Key_Backspace] = VK_BACK; - vkey_table[OS_Key_Tab] = VK_TAB; - vkey_table[OS_Key_Q] = 'Q'; - vkey_table[OS_Key_W] = 'W'; - vkey_table[OS_Key_E] = 'E'; - vkey_table[OS_Key_R] = 'R'; - vkey_table[OS_Key_T] = 'T'; - vkey_table[OS_Key_Y] = 'Y'; - vkey_table[OS_Key_U] = 'U'; - vkey_table[OS_Key_I] = 'I'; - vkey_table[OS_Key_O] = 'O'; - vkey_table[OS_Key_P] = 'P'; - vkey_table[OS_Key_LeftBracket] = VK_OEM_4; - vkey_table[OS_Key_RightBracket] = VK_OEM_6; - vkey_table[OS_Key_BackSlash] = VK_OEM_5; - vkey_table[OS_Key_CapsLock] = VK_CAPITAL; - vkey_table[OS_Key_A] = 'A'; - vkey_table[OS_Key_S] = 'S'; - vkey_table[OS_Key_D] = 'D'; - vkey_table[OS_Key_F] = 'F'; - vkey_table[OS_Key_G] = 'G'; - vkey_table[OS_Key_H] = 'H'; - vkey_table[OS_Key_J] = 'J'; - vkey_table[OS_Key_K] = 'K'; - vkey_table[OS_Key_L] = 'L'; - vkey_table[OS_Key_Semicolon] = VK_OEM_1; - vkey_table[OS_Key_Quote] = VK_OEM_7; - vkey_table[OS_Key_Return] = VK_RETURN; - vkey_table[OS_Key_Shift] = VK_SHIFT; - vkey_table[OS_Key_Z] = 'Z'; - vkey_table[OS_Key_X] = 'X'; - vkey_table[OS_Key_C] = 'C'; - vkey_table[OS_Key_V] = 'V'; - vkey_table[OS_Key_B] = 'B'; - vkey_table[OS_Key_N] = 'N'; - vkey_table[OS_Key_M] = 'M'; - vkey_table[OS_Key_Comma] = VK_OEM_COMMA; - vkey_table[OS_Key_Period] = VK_OEM_PERIOD; - vkey_table[OS_Key_Slash] = VK_OEM_2; - vkey_table[OS_Key_Ctrl] = VK_CONTROL; - vkey_table[OS_Key_Alt] = VK_MENU; - vkey_table[OS_Key_Space] = VK_SPACE; - vkey_table[OS_Key_Menu] = VK_APPS; - vkey_table[OS_Key_ScrollLock] = VK_SCROLL; - vkey_table[OS_Key_Pause] = VK_PAUSE; - vkey_table[OS_Key_Insert] = VK_INSERT; - vkey_table[OS_Key_Home] = VK_HOME; - vkey_table[OS_Key_PageUp] = VK_PRIOR; - vkey_table[OS_Key_Delete] = VK_DELETE; - vkey_table[OS_Key_End] = VK_END; - vkey_table[OS_Key_PageDown] = VK_NEXT; - vkey_table[OS_Key_Up] = VK_UP; - vkey_table[OS_Key_Left] = VK_LEFT; - vkey_table[OS_Key_Down] = VK_DOWN; - vkey_table[OS_Key_Right] = VK_RIGHT; - for(OS_Key key = OS_Key_Ex0; key <= OS_Key_Ex29; key = (OS_Key)(key+1)) - { - vkey_table[key] = 0xDF + (key-OS_Key_Ex0); - } - vkey_table[OS_Key_NumLock] = VK_NUMLOCK; - vkey_table[OS_Key_NumSlash] = VK_DIVIDE; - vkey_table[OS_Key_NumStar] = VK_MULTIPLY; - vkey_table[OS_Key_NumMinus] = VK_SUBTRACT; - vkey_table[OS_Key_NumPlus] = VK_ADD; - vkey_table[OS_Key_NumPeriod] = VK_DECIMAL; - vkey_table[OS_Key_Num0] = VK_NUMPAD0; - vkey_table[OS_Key_Num1] = VK_NUMPAD1; - vkey_table[OS_Key_Num2] = VK_NUMPAD2; - vkey_table[OS_Key_Num3] = VK_NUMPAD3; - vkey_table[OS_Key_Num4] = VK_NUMPAD4; - vkey_table[OS_Key_Num5] = VK_NUMPAD5; - vkey_table[OS_Key_Num6] = VK_NUMPAD6; - vkey_table[OS_Key_Num7] = VK_NUMPAD7; - vkey_table[OS_Key_Num8] = VK_NUMPAD8; - vkey_table[OS_Key_Num9] = VK_NUMPAD9; - vkey_table[OS_Key_LeftMouseButton] = VK_LBUTTON; - vkey_table[OS_Key_MiddleMouseButton] = VK_MBUTTON; - vkey_table[OS_Key_RightMouseButton] = VK_RBUTTON; - } - result = vkey_table[key]; - } - return result; -} - -internal LRESULT -os_w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - ProfBeginFunction(); - LRESULT result = 0; - B32 good = 1; - if(os_w32_event_arena == 0) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - good = 0; - } - if(good) - { - OS_W32_Window *window = os_w32_window_from_hwnd(hwnd); - OS_Window window_handle = os_w32_handle_from_window(window); - B32 release = 0; - - switch(uMsg) - { - default: - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - }break; - - case WM_ENTERSIZEMOVE: - { - os_w32_resizing = 1; - }break; - - case WM_EXITSIZEMOVE: - { - os_w32_resizing = 0; - }break; - - case WM_SIZE: - case WM_PAINT: - { - PAINTSTRUCT ps = {0}; - BeginPaint(hwnd, &ps); - update(); - EndPaint(hwnd, &ps); - DwmFlush(); - }break; - - case WM_CLOSE: - { - os_w32_push_event(OS_EventKind_WindowClose, window); - }break; - - case WM_LBUTTONUP: - case WM_MBUTTONUP: - case WM_RBUTTONUP: - { - release = 1; - } // fallthrough; - case WM_LBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: - { - OS_Event *event = os_w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window); - switch (uMsg) - { - case WM_LBUTTONUP: case WM_LBUTTONDOWN: - { - event->key = OS_Key_LeftMouseButton; - }break; - case WM_MBUTTONUP: case WM_MBUTTONDOWN: - { - event->key = OS_Key_MiddleMouseButton; - }break; - case WM_RBUTTONUP: case WM_RBUTTONDOWN: - { - event->key = OS_Key_RightMouseButton; - }break; - } - event->pos.x = (F32)(S16)LOWORD(lParam); - event->pos.y = (F32)(S16)HIWORD(lParam); - if(release) - { - ReleaseCapture(); - } - else - { - SetCapture(hwnd); - } - }break; - - case WM_MOUSEMOVE: - { - OS_Event *event = os_w32_push_event(OS_EventKind_MouseMove, window); - event->pos.x = (F32)(S16)LOWORD(lParam); - event->pos.y = (F32)(S16)HIWORD(lParam); - }break; - - case WM_MOUSEWHEEL: - { - S16 wheel_delta = HIWORD(wParam); - OS_Event *event = os_w32_push_event(OS_EventKind_Scroll, window); - POINT p; - p.x = (S32)(S16)LOWORD(lParam); - p.y = (S32)(S16)HIWORD(lParam); - ScreenToClient(window->hwnd, &p); - event->pos.x = (F32)p.x; - event->pos.y = (F32)p.y; - event->delta = v2f32(0.f, -(F32)wheel_delta); - }break; - - case WM_MOUSEHWHEEL: - { - S16 wheel_delta = HIWORD(wParam); - OS_Event *event = os_w32_push_event(OS_EventKind_Scroll, window); - POINT p; - p.x = (S32)(S16)LOWORD(lParam); - p.y = (S32)(S16)HIWORD(lParam); - ScreenToClient(window->hwnd, &p); - event->pos.x = (F32)p.x; - event->pos.y = (F32)p.y; - event->delta = v2f32((F32)wheel_delta, 0.f); - }break; - - case WM_SYSKEYDOWN: case WM_SYSKEYUP: - { - if(wParam != VK_MENU && (wParam < VK_F1 || VK_F24 < wParam || wParam == VK_F4)) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - } // fallthrough; - case WM_KEYDOWN: case WM_KEYUP: - { - B32 was_down = (lParam & bit31); - B32 is_down = !(lParam & bit32); - - B32 is_repeat = 0; - if(!is_down) - { - release = 1; - } - else if(was_down) - { - is_repeat = 1; - } - - B32 right_sided = 0; - if ((lParam & bit25) && - (wParam == VK_CONTROL || wParam == VK_RCONTROL || - wParam == VK_MENU || wParam == VK_RMENU || - wParam == VK_SHIFT || wParam == VK_RSHIFT)) - { - right_sided = 1; - } - - OS_Event *event = os_w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window); - event->key = os_w32_os_key_from_vkey(wParam); - event->repeat_count = lParam & bitmask16; - event->is_repeat = is_repeat; - event->right_sided = right_sided; - if(event->key == OS_Key_Alt && event->modifiers & OS_Modifier_Alt) { event->modifiers &= ~OS_Modifier_Alt; } - if(event->key == OS_Key_Ctrl && event->modifiers & OS_Modifier_Ctrl) { event->modifiers &= ~OS_Modifier_Ctrl; } - if(event->key == OS_Key_Shift && event->modifiers & OS_Modifier_Shift) { event->modifiers &= ~OS_Modifier_Shift; } - }break; - - case WM_SYSCHAR: - { - WORD vk_code = LOWORD(wParam); - if(vk_code == VK_SPACE) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - else - { - result = 0; - } - }break; - - case WM_CHAR: - { - U32 character = wParam; - if(character >= 32 && character != 127) - { - OS_Event *event = os_w32_push_event(OS_EventKind_Text, window); - if(lParam & bit29) - { - event->modifiers |= OS_Modifier_Alt; - } - event->character = character; - } - }break; - - case WM_KILLFOCUS: - { - os_w32_push_event(OS_EventKind_WindowLoseFocus, window); - ReleaseCapture(); - }break; - - case WM_SETCURSOR: - { - Rng2F32 window_rect = os_client_rect_from_window(window_handle); - Vec2F32 mouse = os_mouse_from_window(window_handle); - B32 on_border = 0; - DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; - B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); - if(window != 0 && window->custom_border && !is_fullscreen) - { - B32 on_border_x = (mouse.x <= window->custom_border_edge_thickness || window_rect.x1-window->custom_border_edge_thickness <= mouse.x); - B32 on_border_y = (mouse.y <= window->custom_border_edge_thickness || window_rect.y1-window->custom_border_edge_thickness <= mouse.y); - on_border = on_border_x || on_border_y; - } - if(!os_w32_resizing && !on_border && contains_2f32(window_rect, mouse)) - { - SetCursor(os_w32_gfx_state->hCursor); - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - - case WM_DPICHANGED: - { - F32 new_dpi = (F32)(wParam & 0xffff); - RECT suggested_new_rect = *(RECT *)lParam; - window->dpi = new_dpi; - SetWindowPos(window->hwnd, 0, - suggested_new_rect.left, - suggested_new_rect.top, - suggested_new_rect.right - suggested_new_rect.left, - suggested_new_rect.bottom - suggested_new_rect.top, - 0); - }break; - - //- rjf: [file drop] - case WM_DROPFILES: - { - HDROP drop = (HDROP)wParam; - POINT drop_pt = {0}; - DragQueryPoint(drop, &drop_pt); - U64 num_files_dropped = DragQueryFile(drop, 0xffffffff, 0, 0); - OS_Event *event = os_w32_push_event(OS_EventKind_FileDrop, window); - event->pos = v2f32((F32)drop_pt.x, (F32)drop_pt.y); - for(U64 idx = 0; idx < num_files_dropped; idx += 1) - { - U64 name_size = DragQueryFile(drop, idx, 0, 0) + 1; - U8 *name_ptr = push_array(os_w32_event_arena, U8, name_size); - DragQueryFile(drop, idx, (char *)name_ptr, name_size); - String8 path_string = str8(name_ptr, name_size - 1); - String8 path_string__normalized = path_normalized_from_string(os_w32_event_arena, path_string); - str8_list_push(os_w32_event_arena, &event->strings, path_string__normalized); - } - DragFinish(drop); - }break; - - //- rjf: [custom border] - case WM_NCPAINT: - { - if(os_w32_new_window_custom_border || (window != 0 && window->custom_border && !window->custom_border_composition_enabled)) - { - result = 0; - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - case WM_DWMCOMPOSITIONCHANGED: - { - if(window != 0 && window->custom_border) - { - BOOL enabled = 0; - DwmIsCompositionEnabled(&enabled); - window->custom_border_composition_enabled = enabled; - if(enabled) - { - MARGINS m = { 0, 0, 1, 0 }; - DwmExtendFrameIntoClientArea(hwnd, &m); - DWORD dwmncrp_enabled = DWMNCRP_ENABLED; - DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(dwmncrp_enabled)); - } - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - case WM_WINDOWPOSCHANGED: - { - result = 0; - }break; - case WM_NCUAHDRAWCAPTION: - case WM_NCUAHDRAWFRAME: - { - // NOTE(rjf): undocumented messages for drawing themed window borders. - if(os_w32_new_window_custom_border || (window != 0 && window->custom_border)) - { - result = 0; - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - case WM_SETICON: - case WM_SETTEXT: - { - if(os_w32_new_window_custom_border || (window && window->custom_border && !window->custom_border_composition_enabled)) - { - // NOTE(rjf): - // https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/ - LONG_PTR old_style = GetWindowLongPtrW(hwnd, GWL_STYLE); - SetWindowLongPtrW(hwnd, GWL_STYLE, old_style & ~WS_VISIBLE); - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - SetWindowLongPtrW(hwnd, GWL_STYLE, old_style); - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - - //- rjf: [custom border] activation - without this `result`, stuff flickers. - case WM_NCACTIVATE: - { - if(!os_w32_new_window_custom_border && (window == 0 || window->custom_border == 0)) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, -1); - } - }break; - - //- rjf: [custom border] client/window size calculation - case WM_NCCALCSIZE: - { - if(os_w32_new_window_custom_border || (window && window->custom_border)) - { - F32 dpi = w32_GetDpiForWindow_func ? (F32)w32_GetDpiForWindow_func(hwnd) : 96.f; - S32 frame_x = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CXFRAME, dpi) : GetSystemMetrics(SM_CXFRAME); - S32 frame_y = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CYFRAME, dpi) : GetSystemMetrics(SM_CYFRAME); - S32 padding = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CXPADDEDBORDER, dpi) : GetSystemMetrics(SM_CXPADDEDBORDER); - DWORD window_style = GetWindowLong(hwnd, GWL_STYLE); - B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); - if(!is_fullscreen) - { - RECT* rect = wParam == 0 ? (RECT*)lParam : ((NCCALCSIZE_PARAMS*)lParam)->rgrc; - rect->right -= frame_x + padding; - rect->left += frame_x + padding; - rect->bottom -= frame_y + padding; - - if(IsMaximized(hwnd)) - { - rect->top += frame_y + padding; - // If we do not do this hidden taskbar can not be unhidden on mouse hover - // Unfortunately it can create an ugly bottom border when maximized... - rect->bottom -= 1; - } - } - } - else - { - result = DefWindowProc(hwnd, uMsg, wParam, lParam); - } - }break; - - //- rjf: [custom border] client/window hit testing (mapping mouse -> action) - case WM_NCHITTEST: - { - DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; - B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); - if(window == 0 || window->custom_border == 0 || is_fullscreen) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - else - { - B32 is_default_handled = 0; - - // Let the default procedure handle resizing areas - result = DefWindowProc(hwnd, uMsg, wParam, lParam); - switch (result) - { - case HTNOWHERE: - case HTRIGHT: - case HTLEFT: - case HTTOPLEFT: - case HTTOPRIGHT: - case HTBOTTOMRIGHT: - case HTBOTTOM: - case HTBOTTOMLEFT: - { - is_default_handled = 1; - } break; - } - - if (!is_default_handled) - { - POINT pos_monitor; - pos_monitor.x = GET_X_LPARAM(lParam); - pos_monitor.y = GET_Y_LPARAM(lParam); - POINT pos_client = pos_monitor; - ScreenToClient(hwnd, &pos_client); - - // Adjustments happening in NCCALCSIZE are messing with the detection - // of the top hit area so manually checking that. - F32 dpi = w32_GetDpiForWindow_func ? (F32)w32_GetDpiForWindow_func(hwnd) : 96.f; - S32 frame_y = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CYFRAME, dpi) : GetSystemMetrics(SM_CYFRAME); - // NOTE(rjf): it seems incorrect to apply this padding here... - // S32 padding = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CXPADDEDBORDER, dpi) : GetSystemMetrics(SM_CXPADDEDBORDER); - - B32 is_over_top_resize = pos_client.y >= 0 && pos_client.y < frame_y; // + padding; - B32 is_over_title_bar = pos_client.y >= 0 && pos_client.y < window->custom_border_title_thickness; - - //- rjf: check against title bar client areas - B32 is_over_title_bar_client_area = 0; - for(OS_W32_TitleBarClientArea *area = window->first_title_bar_client_area; - area != 0; - area = area->next) - { - Rng2F32 rect = area->rect; - if(rect.x0 <= pos_client.x && pos_client.x < rect.x1 && - rect.y0 <= pos_client.y && pos_client.y < rect.y1) - { - is_over_title_bar_client_area = 1; - break; - } - } - - if (IsMaximized(hwnd)) - { - if (is_over_title_bar_client_area) - { - result = HTCLIENT; - } - else if (is_over_title_bar) - { - result = HTCAPTION; - } - else - { - result = HTCLIENT; - } - } - else - { - //Swap the first two conditions to choose if hovering the top border - //should prioritize resize or title bar buttons. - if (is_over_title_bar_client_area) - { - result = HTCLIENT; - } - else if (is_over_top_resize) - { - result = HTTOP; - } - else if (is_over_title_bar) - { - result = HTCAPTION; - } - else { - result = HTCLIENT; - } - } - } - } - }break; - } - } - ProfEnd(); - return result; -} - -//////////////////////////////// -//~ rjf: Monitors - -internal BOOL -os_w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr) -{ - OS_W32_MonitorGatherBundle *bundle = (OS_W32_MonitorGatherBundle *)bundle_ptr; - OS_Monitor m = {(U64)monitor}; - OS_W32_MonitorGatherNode *n = push_array(bundle->arena, OS_W32_MonitorGatherNode, 1); - n->v = m; - SLLQueuePush(bundle->first_monitor, bundle->last_monitor, n); - bundle->monitor_count += 1; - return 1; -} - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_gfx_init(void) -{ - //- rjf: set up base shared state - Arena *arena = arena_alloc(); - os_w32_gfx_state = push_array(arena, OS_W32_GfxState, 1); - os_w32_gfx_state->arena = arena; - os_w32_gfx_state->gfx_thread_tid = (U32)GetCurrentThreadId(); - os_w32_gfx_state->hInstance = GetModuleHandle(0); - - //- rjf: set dpi awareness - w32_SetProcessDpiAwarenessContext_Type *SetProcessDpiAwarenessContext_func = 0; - HMODULE module = LoadLibraryA("user32.dll"); - if(module != 0) - { - SetProcessDpiAwarenessContext_func = - (w32_SetProcessDpiAwarenessContext_Type*)GetProcAddress(module, "SetProcessDpiAwarenessContext"); - w32_GetDpiForWindow_func = - (w32_GetDpiForWindow_Type*)GetProcAddress(module, "GetDpiForWindow"); - w32_GetDpiForMonitor_func = (w32_GetDpiForMonitor_Type *)GetProcAddress(module, "GetDpiForMonitor"); - w32_GetSystemMetricsForDpi_func = (w32_GetSystemMetricsForDpi_Type *)GetProcAddress(module, "GetSystemMetricsForDpi"); - FreeLibrary(module); - } - if(SetProcessDpiAwarenessContext_func != 0) - { - SetProcessDpiAwarenessContext_func(w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - } - else - { - HMODULE shcore = LoadLibraryA("shcore.dll"); - if(shcore) - { - typedef HRESULT (WINAPI* SetProcessDpiAwareness_t)(int); - SetProcessDpiAwareness_t SetProcessDpiAwareness = (void*)GetProcAddress(shcore, "SetProcessDpiAwareness"); - if(SetProcessDpiAwareness) - { - SetProcessDpiAwareness(2); - } - FreeLibrary(shcore); - } - SetProcessDPIAware(); - } - - //- rjf: register graphical-window class - { - WNDCLASSEXW wndclass = {sizeof(wndclass)}; - wndclass.lpfnWndProc = os_w32_wnd_proc; - wndclass.hInstance = os_w32_gfx_state->hInstance; - wndclass.lpszClassName = L"graphical-window"; - wndclass.hCursor = LoadCursorA(0, IDC_ARROW); - wndclass.hIcon = LoadIcon(os_w32_gfx_state->hInstance, MAKEINTRESOURCE(1)); - wndclass.style = CS_VREDRAW|CS_HREDRAW; - ATOM wndatom = RegisterClassExW(&wndclass); - (void)wndatom; - } - - //- rjf: grab graphics system info - { - os_w32_gfx_state->gfx_info.double_click_time = GetDoubleClickTime()/1000.f; - os_w32_gfx_state->gfx_info.caret_blink_time = GetCaretBlinkTime()/1000.f; - DEVMODEW devmodew = {0}; - if(EnumDisplaySettingsW(0, ENUM_CURRENT_SETTINGS, &devmodew)) - { - os_w32_gfx_state->gfx_info.default_refresh_rate = (F32)devmodew.dmDisplayFrequency; - } - } - - //- rjf: set initial cursor - os_set_cursor(OS_Cursor_Pointer); - - //- rjf: fill vkey -> OS_Key table - { - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'A'] = OS_Key_A; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'B'] = OS_Key_B; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'C'] = OS_Key_C; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'D'] = OS_Key_D; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'E'] = OS_Key_E; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'F'] = OS_Key_F; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'G'] = OS_Key_G; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'H'] = OS_Key_H; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'I'] = OS_Key_I; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'J'] = OS_Key_J; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'K'] = OS_Key_K; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'L'] = OS_Key_L; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'M'] = OS_Key_M; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'N'] = OS_Key_N; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'O'] = OS_Key_O; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'P'] = OS_Key_P; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Q'] = OS_Key_Q; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'R'] = OS_Key_R; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'S'] = OS_Key_S; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'T'] = OS_Key_T; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'U'] = OS_Key_U; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'V'] = OS_Key_V; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'W'] = OS_Key_W; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'X'] = OS_Key_X; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Y'] = OS_Key_Y; - os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Z'] = OS_Key_Z; - - for(U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1) - { - os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; - } - for(U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1) - { - os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; - } - for(U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1) - { - os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; - } - - os_w32_gfx_state->key_from_vkey_table[VK_SPACE] = OS_Key_Space; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_3] = OS_Key_Tick; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_MINUS] = OS_Key_Minus; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_PLUS] = OS_Key_Equal; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_4] = OS_Key_LeftBracket; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_6] = OS_Key_RightBracket; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_1] = OS_Key_Semicolon; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_7] = OS_Key_Quote; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_COMMA] = OS_Key_Comma; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_PERIOD]= OS_Key_Period; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_2] = OS_Key_Slash; - os_w32_gfx_state->key_from_vkey_table[VK_OEM_5] = OS_Key_BackSlash; - - os_w32_gfx_state->key_from_vkey_table[VK_TAB] = OS_Key_Tab; - os_w32_gfx_state->key_from_vkey_table[VK_PAUSE] = OS_Key_Pause; - os_w32_gfx_state->key_from_vkey_table[VK_ESCAPE] = OS_Key_Esc; - - os_w32_gfx_state->key_from_vkey_table[VK_UP] = OS_Key_Up; - os_w32_gfx_state->key_from_vkey_table[VK_LEFT] = OS_Key_Left; - os_w32_gfx_state->key_from_vkey_table[VK_DOWN] = OS_Key_Down; - os_w32_gfx_state->key_from_vkey_table[VK_RIGHT] = OS_Key_Right; - - os_w32_gfx_state->key_from_vkey_table[VK_BACK] = OS_Key_Backspace; - os_w32_gfx_state->key_from_vkey_table[VK_RETURN] = OS_Key_Return; - - os_w32_gfx_state->key_from_vkey_table[VK_DELETE] = OS_Key_Delete; - os_w32_gfx_state->key_from_vkey_table[VK_INSERT] = OS_Key_Insert; - os_w32_gfx_state->key_from_vkey_table[VK_PRIOR] = OS_Key_PageUp; - os_w32_gfx_state->key_from_vkey_table[VK_NEXT] = OS_Key_PageDown; - os_w32_gfx_state->key_from_vkey_table[VK_HOME] = OS_Key_Home; - os_w32_gfx_state->key_from_vkey_table[VK_END] = OS_Key_End; - - os_w32_gfx_state->key_from_vkey_table[VK_CAPITAL] = OS_Key_CapsLock; - os_w32_gfx_state->key_from_vkey_table[VK_NUMLOCK] = OS_Key_NumLock; - os_w32_gfx_state->key_from_vkey_table[VK_SCROLL] = OS_Key_ScrollLock; - os_w32_gfx_state->key_from_vkey_table[VK_APPS] = OS_Key_Menu; - - os_w32_gfx_state->key_from_vkey_table[VK_CONTROL] = OS_Key_Ctrl; - os_w32_gfx_state->key_from_vkey_table[VK_LCONTROL] = OS_Key_Ctrl; - os_w32_gfx_state->key_from_vkey_table[VK_RCONTROL] = OS_Key_Ctrl; - os_w32_gfx_state->key_from_vkey_table[VK_SHIFT] = OS_Key_Shift; - os_w32_gfx_state->key_from_vkey_table[VK_LSHIFT] = OS_Key_Shift; - os_w32_gfx_state->key_from_vkey_table[VK_RSHIFT] = OS_Key_Shift; - os_w32_gfx_state->key_from_vkey_table[VK_MENU] = OS_Key_Alt; - os_w32_gfx_state->key_from_vkey_table[VK_LMENU] = OS_Key_Alt; - os_w32_gfx_state->key_from_vkey_table[VK_RMENU] = OS_Key_Alt; - - os_w32_gfx_state->key_from_vkey_table[VK_DIVIDE] = OS_Key_NumSlash; - os_w32_gfx_state->key_from_vkey_table[VK_MULTIPLY] = OS_Key_NumStar; - os_w32_gfx_state->key_from_vkey_table[VK_SUBTRACT] = OS_Key_NumMinus; - os_w32_gfx_state->key_from_vkey_table[VK_ADD] = OS_Key_NumPlus; - os_w32_gfx_state->key_from_vkey_table[VK_DECIMAL] = OS_Key_NumPeriod; - - for(U32 i = 0; i < 10; i += 1) - { - os_w32_gfx_state->key_from_vkey_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i); - } - - for(U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1) - { - os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j); - } - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) - -internal OS_GfxInfo * -os_get_gfx_info(void) -{ - return &os_w32_gfx_state->gfx_info; -} - -//////////////////////////////// -//~ rjf: @os_hooks Clipboards (Implemented Per-OS) - -internal void -os_set_clipboard_text(String8 string) -{ - Temp scratch = scratch_begin(0, 0); - if(OpenClipboard(0)) - { - EmptyClipboard(); - String16 string16 = str16_from_8(scratch.arena, string); - HANDLE string16_copy_handle = GlobalAlloc(GMEM_MOVEABLE, (string16.size+1)*sizeof(string16.str[0])); - if(string16_copy_handle) - { - U16 *copy_buffer = (U16 *)GlobalLock(string16_copy_handle); - MemoryCopy(copy_buffer, string16.str, string16.size*sizeof(string16.str[0])); - copy_buffer[string16.size] = 0; - GlobalUnlock(string16_copy_handle); - SetClipboardData(CF_UNICODETEXT, string16_copy_handle); - } - CloseClipboard(); - } - scratch_end(scratch); -} - -internal String8 -os_get_clipboard_text(Arena *arena) -{ - String8 result = {0}; - if(IsClipboardFormatAvailable(CF_UNICODETEXT) && - OpenClipboard(0)) - { - HANDLE data_handle = GetClipboardData(CF_UNICODETEXT); - if(data_handle) - { - U16 *buffer = (U16 *)GlobalLock(data_handle); - if(buffer) - { - U64 size = cstring16_length(buffer); - String16 string16 = str16(buffer, size); - result = str8_from_16(arena, string16); - GlobalUnlock(data_handle); - } - } - CloseClipboard(); - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Windows (Implemented Per-OS) - -internal OS_Window -os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) -{ - B32 custom_border = !!(flags & OS_WindowFlag_CustomBorder); - B32 use_default_position = !!(flags & OS_WindowFlag_UseDefaultPosition); - Vec2F32 pos = rect.p0; - Vec2F32 dim = dim_2f32(rect); - - //- rjf: make hwnd - HWND hwnd = 0; - { - Temp scratch = scratch_begin(0, 0); - String16 title16 = str16_from_8(scratch.arena, title); - os_w32_new_window_custom_border = custom_border; - DWORD style_flags = WS_EX_APPWINDOW; -#if defined(R_BACKEND) && R_BACKEND != R_BACKEND_OPENGL - style_flags |= WS_EX_NOREDIRECTIONBITMAP; -#endif - hwnd = CreateWindowExW(style_flags, - L"graphical-window", - (WCHAR*)title16.str, - WS_OVERLAPPEDWINDOW | WS_SIZEBOX, - use_default_position ? CW_USEDEFAULT : (S32)pos.x, - use_default_position ? CW_USEDEFAULT : (S32)pos.y, - (S32)dim.x, - (S32)dim.y, - 0, 0, - os_w32_gfx_state->hInstance, - 0); - DragAcceptFiles(hwnd, 1); - os_w32_new_window_custom_border = 0; - scratch_end(scratch); - } - - //- rjf- make/fill window - OS_W32_Window *window = os_w32_window_alloc(); - { - window->hwnd = hwnd; - window->hdc = GetDC(hwnd); - if(w32_GetDpiForWindow_func != 0) - { - window->dpi = (F32)w32_GetDpiForWindow_func(hwnd); - } - else - { - window->dpi = 96.f; - } - } - - //- rjf: early detection of composition - { - BOOL enabled = 0; - DwmIsCompositionEnabled(&enabled); - window->custom_border_composition_enabled = enabled; - } - - //- rjf: custom border - if(flags & OS_WindowFlag_CustomBorder) - { - window->custom_border = 1; - window->paint_arena = arena_alloc(); - } - - //- rjf: convert to handle + return - OS_Window result = os_w32_handle_from_window(window); - return result; -} - -internal void -os_window_close(OS_Window handle) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - os_w32_window_release(window); -} - -internal void -os_window_set_title(OS_Window handle, String8 title) -{ - Temp scratch = scratch_begin(0, 0); - OS_W32_Window *window = os_w32_window_from_handle(handle); - String16 title16 = str16_from_8(scratch.arena, title); - SetWindowTextW(window->hwnd, (WCHAR *)title16.str); - scratch_end(scratch); -} - -internal void -os_window_first_paint(OS_Window window_handle) -{ - OS_W32_Window *window = os_w32_window_from_handle(window_handle); - window->first_paint_done = 1; - ShowWindow(window->hwnd, SW_SHOW); - if(window->maximized) - { - ShowWindow(window->hwnd, SW_MAXIMIZE); - } -} - -internal void -os_window_focus(OS_Window handle) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - SetForegroundWindow(window->hwnd); - SetFocus(window->hwnd); -} - -internal B32 -os_window_is_focused(OS_Window handle) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - HWND active_hwnd = GetActiveWindow(); - return active_hwnd == window->hwnd; -} - -internal B32 -os_window_is_fullscreen(OS_Window handle) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); - return !(window_style & WS_OVERLAPPEDWINDOW); -} - -internal void -os_window_set_fullscreen(OS_Window handle, B32 fullscreen) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); - B32 is_fullscreen_already = os_window_is_fullscreen(handle); - if(fullscreen) - { - if(!is_fullscreen_already) - { - GetWindowPlacement(window->hwnd, &window->last_window_placement); - } - MONITORINFO monitor_info = {sizeof(monitor_info)}; - if(GetMonitorInfo(MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info)) - { - SetWindowLong(window->hwnd, GWL_STYLE, window_style & ~WS_OVERLAPPEDWINDOW); - SetWindowPos(window->hwnd, HWND_TOP, - monitor_info.rcMonitor.left, - monitor_info.rcMonitor.top, - monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, - monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - } - } - else - { - SetWindowLong(window->hwnd, GWL_STYLE, window_style | WS_OVERLAPPEDWINDOW); - SetWindowPlacement(window->hwnd, &window->last_window_placement); - SetWindowPos(window->hwnd, 0, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - } -} - -internal B32 -os_window_is_maximized(OS_Window handle) -{ - B32 result = 0; - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window) - { - result = !!(IsZoomed(window->hwnd)); - } - return result; -} - -internal void -os_window_set_maximized(OS_Window handle, B32 maximized) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window != 0) - { - if(window->first_paint_done) - { - switch(maximized) - { - default: - case 0:{ShowWindow(window->hwnd, SW_RESTORE);}break; - case 1:{ShowWindow(window->hwnd, SW_MAXIMIZE);}break; - } - } - else - { - window->maximized = maximized; - } - } -} - -internal B32 -os_window_is_minimized(OS_Window handle) -{ - B32 result = 0; - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window) - { - result = !!(IsIconic(window->hwnd)); - } - return result; -} - -internal void -os_window_set_minimized(OS_Window handle, B32 minimized) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window != 0 && minimized != os_window_is_minimized(handle)) - { - switch(minimized) - { - default: - case 0:{ShowWindow(window->hwnd, SW_RESTORE);}break; - case 1:{ShowWindow(window->hwnd, SW_MINIMIZE);}break; - } - } -} - -internal void -os_window_bring_to_front(OS_Window handle) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window != 0) - { - BringWindowToTop(window->hwnd); - } -} - -internal void -os_window_set_monitor(OS_Window window_handle, OS_Monitor monitor) -{ - OS_W32_Window *window = os_w32_window_from_handle(window_handle); - HMONITOR hmonitor = (HMONITOR)monitor.u64[0]; - { - MONITORINFOEXW info; - info.cbSize = sizeof(MONITORINFOEXW); - if(GetMonitorInfoW(hmonitor, (MONITORINFO *)&info)) - { - Rng2F32 existing_rect = os_rect_from_window(window_handle); - Vec2F32 window_size = dim_2f32(existing_rect); - SetWindowPos(window->hwnd, HWND_TOP, - (info.rcWork.left + info.rcWork.right)/2 - window_size.x/2, - (info.rcWork.top + info.rcWork.bottom)/2 - window_size.y/2, - window_size.x, - window_size.y, 0); - } - } -} - -internal void -os_window_clear_custom_border_data(OS_Window handle) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window->custom_border) - { - arena_clear(window->paint_arena); - window->first_title_bar_client_area = window->last_title_bar_client_area = 0; - window->custom_border_title_thickness = 0; - window->custom_border_edge_thickness = 0; - } -} - -internal void -os_window_push_custom_title_bar(OS_Window handle, F32 thickness) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - window->custom_border_title_thickness = thickness; -} - -internal void -os_window_push_custom_edges(OS_Window handle, F32 thickness) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - window->custom_border_edge_thickness = thickness; -} - -internal void -os_window_push_custom_title_bar_client_area(OS_Window handle, Rng2F32 rect) -{ - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window->custom_border) - { - OS_W32_TitleBarClientArea *area = push_array(window->paint_arena, OS_W32_TitleBarClientArea, 1); - if(area != 0) - { - area->rect = rect; - SLLQueuePush(window->first_title_bar_client_area, window->last_title_bar_client_area, area); - } - } -} - -internal Rng2F32 -os_rect_from_window(OS_Window handle) -{ - Rng2F32 r = {0}; - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window) - { - RECT rect = {0}; - GetWindowRect(os_w32_hwnd_from_window(window), &rect); - r = os_w32_rng2f32_from_rect(rect); - } - return r; -} - -internal Rng2F32 -os_client_rect_from_window(OS_Window handle) -{ - Rng2F32 r = {0}; - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window) - { - RECT rect = {0}; - GetClientRect(os_w32_hwnd_from_window(window), &rect); - r = os_w32_rng2f32_from_rect(rect); - } - return r; -} - -internal F32 -os_dpi_from_window(OS_Window handle) -{ - F32 result = 96.f; - OS_W32_Window *window = os_w32_window_from_handle(handle); - if(window != 0) - { - result = window->dpi; - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks External Windows (Implemented Per-OS) - -internal OS_ExternalWindow -os_focused_external_window(void) -{ - HWND hwnd = GetForegroundWindow(); - OS_ExternalWindow result = {(U64)hwnd}; - return result; -} - -internal void -os_focus_external_window(OS_ExternalWindow handle) -{ - HWND hwnd = (HWND)handle.u64[0]; - if(hwnd != 0) - { - SetForegroundWindow(hwnd); - SetFocus(hwnd); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Monitors (Implemented Per-OS) - -internal OS_MonitorArray -os_push_monitors_array(Arena *arena) -{ - Temp scratch = scratch_begin(&arena, 1); - OS_W32_MonitorGatherBundle bundle = {arena}; - EnumDisplayMonitors(0, 0, os_w32_monitor_gather_enum_proc, (LPARAM)&bundle); - OS_MonitorArray array = {0}; - array.count = bundle.monitor_count; - array.v = push_array(arena, OS_Monitor, array.count); - { - U64 idx = 0; - for EachNode(n, OS_W32_MonitorGatherNode, bundle.first_monitor) - { - array.v[idx] = n->v; - idx += 1; - } - } - scratch_end(scratch); - return array; -} - -internal OS_Monitor -os_primary_monitor(void) -{ - POINT zero_pt = {0, 0}; - HMONITOR monitor = MonitorFromPoint(zero_pt, MONITOR_DEFAULTTOPRIMARY); - OS_Monitor result = {(U64)monitor}; - return result; -} - -internal OS_Monitor -os_monitor_from_window(OS_Window window) -{ - OS_W32_Window *w = os_w32_window_from_handle(window); - HMONITOR handle = MonitorFromWindow(w->hwnd, MONITOR_DEFAULTTOPRIMARY); - OS_Monitor result = {(U64)handle}; - return result; -} - -internal String8 -os_name_from_monitor(Arena *arena, OS_Monitor monitor) -{ - String8 result = {0}; - HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; - MONITORINFOEXW info; - info.cbSize = sizeof(MONITORINFOEXW); - if(GetMonitorInfoW(monitor_handle, (MONITORINFO *)&info)) - { - String16 result16 = str16_cstring((U16 *)info.szDevice); - result = str8_from_16(arena, result16); - } - return result; -} - -internal Vec2F32 -os_dim_from_monitor(OS_Monitor monitor) -{ - Vec2F32 result = {0}; - HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; - MONITORINFO info = {0}; - info.cbSize = sizeof(MONITORINFO); - if(GetMonitorInfoW(monitor_handle, &info)) - { - result.x = info.rcWork.right - info.rcWork.left; - result.y = info.rcWork.bottom - info.rcWork.top; - } - return result; -} - -internal F32 -os_dpi_from_monitor(OS_Monitor monitor) -{ - F32 result = 96.f; - HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; - if(w32_GetDpiForMonitor_func != 0) - { - UINT dpi_x = 0; - UINT dpi_y = 0; - HRESULT hr = w32_GetDpiForMonitor_func(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y); - result = (F32)dpi_x; - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Events (Implemented Per-OS) - -internal void -os_send_wakeup_event(void) -{ - PostThreadMessageA(os_w32_gfx_state->gfx_thread_tid, 0x401, 0, 0); -} - -internal OS_EventList -os_get_events(Arena *arena, B32 wait) -{ - os_w32_event_arena = arena; - MemoryZeroStruct(&os_w32_event_list); - MSG msg = {0}; - if(!wait || GetMessage(&msg, 0, 0, 0)) - { - B32 first_wait = wait; - for(;first_wait || PeekMessage(&msg, 0, 0, 0, PM_REMOVE); first_wait = 0) - { - DispatchMessage(&msg); - TranslateMessage(&msg); - if(msg.message == WM_QUIT) - { - os_w32_push_event(OS_EventKind_WindowClose, 0); - } - } - } - return os_w32_event_list; -} - -internal OS_Modifiers -os_get_modifiers(void) -{ - OS_Modifiers modifiers = 0; - if(GetKeyState(VK_CONTROL) & 0x8000) - { - modifiers |= OS_Modifier_Ctrl; - } - if(GetKeyState(VK_SHIFT) & 0x8000) - { - modifiers |= OS_Modifier_Shift; - } - if(GetKeyState(VK_MENU) & 0x8000) - { - modifiers |= OS_Modifier_Alt; - } - return modifiers; -} - -internal B32 -os_key_is_down(OS_Key key) -{ - B32 down = 0; - WPARAM vkey = os_w32_vkey_from_os_key(key); - if(GetKeyState(vkey) & 0x8000) - { - down = 1; - } - return down; -} - -internal Vec2F32 -os_mouse_from_window(OS_Window handle) -{ - ProfBeginFunction(); - Vec2F32 v = {0}; - POINT p; - if(GetCursorPos(&p)) - { - OS_W32_Window *window = os_w32_window_from_handle(handle); - ScreenToClient(window->hwnd, &p); - v.x = (F32)p.x; - v.y = (F32)p.y; - } - ProfEnd(); - return v; -} - -//////////////////////////////// -//~ rjf: @os_hooks Cursors (Implemented Per-OS) - -internal void -os_set_cursor(OS_Cursor cursor) -{ - B32 valid_cursor = 1; - HCURSOR hcursor = 0; - switch(cursor) - { - default: {valid_cursor = 0;}break; -#define Win32CursorXList(X) \ -X(Pointer, IDC_ARROW) \ -X(IBar, IDC_IBEAM) \ -X(LeftRight, IDC_SIZEWE) \ -X(UpDown, IDC_SIZENS) \ -X(DownRight, IDC_SIZENWSE) \ -X(UpRight, IDC_SIZENESW) \ -X(UpDownLeftRight, IDC_SIZEALL) \ -X(HandPoint, IDC_HAND)\ -X(Disabled, IDC_NO) -#define CursorCase(E,R) case OS_Cursor_##E:{ \ -local_persist HCURSOR curs = 0; \ -if (curs == 0){ curs = LoadCursor(NULL, R); } \ -hcursor = curs; }break; - Win32CursorXList(CursorCase) -#undef CursorCase -#undef Win32CursorXList - } - if(valid_cursor && !os_w32_resizing) - { - if(hcursor != os_w32_gfx_state->hCursor) - { - PostMessage(0, WM_SETCURSOR, 0, 0); - POINT p = {0}; - GetCursorPos(&p); - SetCursorPos(p.x, p.y); - } - os_w32_gfx_state->hCursor = hcursor; - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -os_graphical_message(B32 error, String8 title, String8 message) -{ - Temp scratch = scratch_begin(0, 0); - String16 title16 = str16_from_8(scratch.arena, title); - String16 message16 = str16_from_8(scratch.arena, message); - MessageBoxW(0, (WCHAR *)message16.str, (WCHAR *)title16.str, MB_OK|(!!error*MB_ICONERROR)); - scratch_end(scratch); -} - -internal String8 -os_graphical_pick_file(Arena *arena, String8 initial_path) -{ - String8 result = {0}; - { - Temp scratch = scratch_begin(&arena, 1); - U64 buffer_size = 4096; - U16 *buffer = push_array(scratch.arena, U16, buffer_size); - OPENFILENAMEW params = {sizeof(params)}; - { - params.lpstrFile = (WCHAR *)buffer; - params.nMaxFile = buffer_size; - params.lpstrInitialDir = (WCHAR *)str16_from_8(scratch.arena, initial_path).str; - } - if(GetOpenFileNameW(¶ms)) - { - result = str8_from_16(arena, str16_cstring((U16 *)buffer)); - } - scratch_end(scratch); - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Shell Operations - -internal void -os_show_in_filesystem_ui(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = push_str8_copy(scratch.arena, path); - for(U64 idx = 0; idx < path_copy.size; idx += 1) - { - if(path_copy.str[idx] == '/') - { - path_copy.str[idx] = '\\'; - } - } - String16 path16 = str16_from_8(scratch.arena, path_copy); - SFGAOF flags = 0; - PIDLIST_ABSOLUTE list = 0; - if(path16.size != 0 && SUCCEEDED(SHParseDisplayName(path16.str, 0, &list, 0, &flags))) - { - HRESULT hr = SHOpenFolderAndSelectItems(list, 0, 0, 0); - CoTaskMemFree(list); - (void)hr; - } - scratch_end(scratch); -} - -internal void -os_open_in_browser(String8 url) -{ - Temp scratch = scratch_begin(0, 0); - String16 url16 = str16_from_8(scratch.arena, url); - ShellExecuteW(0, L"open", (WCHAR *)url16.str, 0, 0, SW_SHOWNORMAL); - scratch_end(scratch); -} +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Modern Windows SDK Functions +// +// (We must dynamically link to them, since they can be missing in older SDKs) + +typedef BOOL w32_SetProcessDpiAwarenessContext_Type(void* value); +typedef UINT w32_GetDpiForWindow_Type(HWND hwnd); +typedef HRESULT w32_GetDpiForMonitor_Type(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT *dpiX, UINT *dpiY); +typedef int w32_GetSystemMetricsForDpi_Type(int nIndex, UINT dpi); +#define w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((void*)-4) +global w32_GetDpiForWindow_Type *w32_GetDpiForWindow_func = 0; +global w32_GetDpiForMonitor_Type *w32_GetDpiForMonitor_func = 0; +global w32_GetSystemMetricsForDpi_Type *w32_GetSystemMetricsForDpi_func = 0; + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal Rng2F32 +os_w32_rng2f32_from_rect(RECT rect) +{ + Rng2F32 r = {0}; + r.x0 = (F32)rect.left; + r.x1 = (F32)rect.right; + r.y0 = (F32)rect.top; + r.y1 = (F32)rect.bottom; + return r; +} + +//////////////////////////////// +//~ rjf: Windows + +internal OS_Window +os_w32_handle_from_window(OS_W32_Window *window) +{ + OS_Window handle = {(U64)window}; + return handle; +} + +internal OS_W32_Window * +os_w32_window_from_handle(OS_Window handle) +{ + OS_W32_Window *window = (OS_W32_Window *)handle.u64[0]; + return window; +} + +internal OS_W32_Window * +os_w32_window_from_hwnd(HWND hwnd) +{ + OS_W32_Window *result = 0; + for(OS_W32_Window *w = os_w32_gfx_state->first_window; w; w = w->next) + { + if(w->hwnd == hwnd) + { + result = w; + break; + } + } + return result; +} + +internal HWND +os_w32_hwnd_from_window(OS_W32_Window *window) +{ + return window->hwnd; +} + +internal OS_W32_Window * +os_w32_window_alloc(void) +{ + OS_W32_Window *result = os_w32_gfx_state->free_window; + if(result) + { + SLLStackPop(os_w32_gfx_state->free_window); + } + else + { + result = push_array_no_zero(os_w32_gfx_state->arena, OS_W32_Window, 1); + } + MemoryZeroStruct(result); + if(result) + { + DLLPushBack(os_w32_gfx_state->first_window, os_w32_gfx_state->last_window, result); + } + result->last_window_placement.length = sizeof(WINDOWPLACEMENT); + return result; +} + +internal void +os_w32_window_release(OS_W32_Window *window) +{ + if(window->paint_arena != 0) + { + arena_release(window->paint_arena); + } + ReleaseDC(window->hwnd, window->hdc); + DestroyWindow(window->hwnd); + DLLRemove(os_w32_gfx_state->first_window, os_w32_gfx_state->last_window, window); + SLLStackPush(os_w32_gfx_state->free_window, window); +} + +internal OS_Event * +os_w32_push_event(OS_EventKind kind, OS_W32_Window *window) +{ + OS_Event *result = os_event_list_push_new(os_w32_event_arena, &os_w32_event_list, kind); + result->window = os_w32_handle_from_window(window); + result->modifiers = os_get_modifiers(); + return result; +} + +internal OS_Key +os_w32_os_key_from_vkey(WPARAM vkey) +{ + local_persist B32 first = 1; + local_persist OS_Key key_table[256]; + if (first){ + first = 0; + MemoryZeroArray(key_table); + + key_table[(unsigned int)'A'] = OS_Key_A; + key_table[(unsigned int)'B'] = OS_Key_B; + key_table[(unsigned int)'C'] = OS_Key_C; + key_table[(unsigned int)'D'] = OS_Key_D; + key_table[(unsigned int)'E'] = OS_Key_E; + key_table[(unsigned int)'F'] = OS_Key_F; + key_table[(unsigned int)'G'] = OS_Key_G; + key_table[(unsigned int)'H'] = OS_Key_H; + key_table[(unsigned int)'I'] = OS_Key_I; + key_table[(unsigned int)'J'] = OS_Key_J; + key_table[(unsigned int)'K'] = OS_Key_K; + key_table[(unsigned int)'L'] = OS_Key_L; + key_table[(unsigned int)'M'] = OS_Key_M; + key_table[(unsigned int)'N'] = OS_Key_N; + key_table[(unsigned int)'O'] = OS_Key_O; + key_table[(unsigned int)'P'] = OS_Key_P; + key_table[(unsigned int)'Q'] = OS_Key_Q; + key_table[(unsigned int)'R'] = OS_Key_R; + key_table[(unsigned int)'S'] = OS_Key_S; + key_table[(unsigned int)'T'] = OS_Key_T; + key_table[(unsigned int)'U'] = OS_Key_U; + key_table[(unsigned int)'V'] = OS_Key_V; + key_table[(unsigned int)'W'] = OS_Key_W; + key_table[(unsigned int)'X'] = OS_Key_X; + key_table[(unsigned int)'Y'] = OS_Key_Y; + key_table[(unsigned int)'Z'] = OS_Key_Z; + + for (U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1){ + key_table[i] = (OS_Key)j; + } + for (U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1){ + key_table[i] = (OS_Key)j; + } + for (U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1){ + key_table[i] = (OS_Key)j; + } + + key_table[VK_SPACE] = OS_Key_Space; + key_table[VK_OEM_3] = OS_Key_Tick; + key_table[VK_OEM_MINUS] = OS_Key_Minus; + key_table[VK_OEM_PLUS] = OS_Key_Equal; + key_table[VK_OEM_4] = OS_Key_LeftBracket; + key_table[VK_OEM_6] = OS_Key_RightBracket; + key_table[VK_OEM_1] = OS_Key_Semicolon; + key_table[VK_OEM_7] = OS_Key_Quote; + key_table[VK_OEM_COMMA] = OS_Key_Comma; + key_table[VK_OEM_PERIOD]= OS_Key_Period; + key_table[VK_OEM_2] = OS_Key_Slash; + key_table[VK_OEM_5] = OS_Key_BackSlash; + + key_table[VK_TAB] = OS_Key_Tab; + key_table[VK_PAUSE] = OS_Key_Pause; + key_table[VK_ESCAPE] = OS_Key_Esc; + + key_table[VK_UP] = OS_Key_Up; + key_table[VK_LEFT] = OS_Key_Left; + key_table[VK_DOWN] = OS_Key_Down; + key_table[VK_RIGHT] = OS_Key_Right; + + key_table[VK_BACK] = OS_Key_Backspace; + key_table[VK_RETURN] = OS_Key_Return; + + key_table[VK_DELETE] = OS_Key_Delete; + key_table[VK_INSERT] = OS_Key_Insert; + key_table[VK_PRIOR] = OS_Key_PageUp; + key_table[VK_NEXT] = OS_Key_PageDown; + key_table[VK_HOME] = OS_Key_Home; + key_table[VK_END] = OS_Key_End; + + key_table[VK_CAPITAL] = OS_Key_CapsLock; + key_table[VK_NUMLOCK] = OS_Key_NumLock; + key_table[VK_SCROLL] = OS_Key_ScrollLock; + key_table[VK_APPS] = OS_Key_Menu; + + key_table[VK_CONTROL] = OS_Key_Ctrl; + key_table[VK_LCONTROL] = OS_Key_Ctrl; + key_table[VK_RCONTROL] = OS_Key_Ctrl; + key_table[VK_SHIFT] = OS_Key_Shift; + key_table[VK_LSHIFT] = OS_Key_Shift; + key_table[VK_RSHIFT] = OS_Key_Shift; + key_table[VK_MENU] = OS_Key_Alt; + key_table[VK_LMENU] = OS_Key_Alt; + key_table[VK_RMENU] = OS_Key_Alt; + + key_table[VK_DIVIDE] = OS_Key_NumSlash; + key_table[VK_MULTIPLY] = OS_Key_NumStar; + key_table[VK_SUBTRACT] = OS_Key_NumMinus; + key_table[VK_ADD] = OS_Key_NumPlus; + key_table[VK_DECIMAL] = OS_Key_NumPeriod; + + for (U32 i = 0; i < 10; i += 1){ + key_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i); + } + + for (U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1){ + key_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j); + } + } + + OS_Key key = key_table[vkey&bitmask8]; + return key; +} + +internal WPARAM +os_w32_vkey_from_os_key(OS_Key key) +{ + WPARAM result = 0; + { + local_persist B32 initialized = 0; + local_persist WPARAM vkey_table[OS_Key_COUNT] = {0}; + if(initialized == 0) + { + initialized = 1; + vkey_table[OS_Key_Esc] = VK_ESCAPE; + for(OS_Key key = OS_Key_F1; key <= OS_Key_F24; key = (OS_Key)(key+1)) + { + vkey_table[key] = VK_F1+(key-OS_Key_F1); + } + vkey_table[OS_Key_Tick] = VK_OEM_3; + for(OS_Key key = OS_Key_0; key <= OS_Key_9; key = (OS_Key)(key+1)) + { + vkey_table[key] = '0'+(key-OS_Key_0); + } + vkey_table[OS_Key_Minus] = VK_OEM_MINUS; + vkey_table[OS_Key_Equal] = VK_OEM_PLUS; + vkey_table[OS_Key_Backspace] = VK_BACK; + vkey_table[OS_Key_Tab] = VK_TAB; + vkey_table[OS_Key_Q] = 'Q'; + vkey_table[OS_Key_W] = 'W'; + vkey_table[OS_Key_E] = 'E'; + vkey_table[OS_Key_R] = 'R'; + vkey_table[OS_Key_T] = 'T'; + vkey_table[OS_Key_Y] = 'Y'; + vkey_table[OS_Key_U] = 'U'; + vkey_table[OS_Key_I] = 'I'; + vkey_table[OS_Key_O] = 'O'; + vkey_table[OS_Key_P] = 'P'; + vkey_table[OS_Key_LeftBracket] = VK_OEM_4; + vkey_table[OS_Key_RightBracket] = VK_OEM_6; + vkey_table[OS_Key_BackSlash] = VK_OEM_5; + vkey_table[OS_Key_CapsLock] = VK_CAPITAL; + vkey_table[OS_Key_A] = 'A'; + vkey_table[OS_Key_S] = 'S'; + vkey_table[OS_Key_D] = 'D'; + vkey_table[OS_Key_F] = 'F'; + vkey_table[OS_Key_G] = 'G'; + vkey_table[OS_Key_H] = 'H'; + vkey_table[OS_Key_J] = 'J'; + vkey_table[OS_Key_K] = 'K'; + vkey_table[OS_Key_L] = 'L'; + vkey_table[OS_Key_Semicolon] = VK_OEM_1; + vkey_table[OS_Key_Quote] = VK_OEM_7; + vkey_table[OS_Key_Return] = VK_RETURN; + vkey_table[OS_Key_Shift] = VK_SHIFT; + vkey_table[OS_Key_Z] = 'Z'; + vkey_table[OS_Key_X] = 'X'; + vkey_table[OS_Key_C] = 'C'; + vkey_table[OS_Key_V] = 'V'; + vkey_table[OS_Key_B] = 'B'; + vkey_table[OS_Key_N] = 'N'; + vkey_table[OS_Key_M] = 'M'; + vkey_table[OS_Key_Comma] = VK_OEM_COMMA; + vkey_table[OS_Key_Period] = VK_OEM_PERIOD; + vkey_table[OS_Key_Slash] = VK_OEM_2; + vkey_table[OS_Key_Ctrl] = VK_CONTROL; + vkey_table[OS_Key_Alt] = VK_MENU; + vkey_table[OS_Key_Space] = VK_SPACE; + vkey_table[OS_Key_Menu] = VK_APPS; + vkey_table[OS_Key_ScrollLock] = VK_SCROLL; + vkey_table[OS_Key_Pause] = VK_PAUSE; + vkey_table[OS_Key_Insert] = VK_INSERT; + vkey_table[OS_Key_Home] = VK_HOME; + vkey_table[OS_Key_PageUp] = VK_PRIOR; + vkey_table[OS_Key_Delete] = VK_DELETE; + vkey_table[OS_Key_End] = VK_END; + vkey_table[OS_Key_PageDown] = VK_NEXT; + vkey_table[OS_Key_Up] = VK_UP; + vkey_table[OS_Key_Left] = VK_LEFT; + vkey_table[OS_Key_Down] = VK_DOWN; + vkey_table[OS_Key_Right] = VK_RIGHT; + for(OS_Key key = OS_Key_Ex0; key <= OS_Key_Ex29; key = (OS_Key)(key+1)) + { + vkey_table[key] = 0xDF + (key-OS_Key_Ex0); + } + vkey_table[OS_Key_NumLock] = VK_NUMLOCK; + vkey_table[OS_Key_NumSlash] = VK_DIVIDE; + vkey_table[OS_Key_NumStar] = VK_MULTIPLY; + vkey_table[OS_Key_NumMinus] = VK_SUBTRACT; + vkey_table[OS_Key_NumPlus] = VK_ADD; + vkey_table[OS_Key_NumPeriod] = VK_DECIMAL; + vkey_table[OS_Key_Num0] = VK_NUMPAD0; + vkey_table[OS_Key_Num1] = VK_NUMPAD1; + vkey_table[OS_Key_Num2] = VK_NUMPAD2; + vkey_table[OS_Key_Num3] = VK_NUMPAD3; + vkey_table[OS_Key_Num4] = VK_NUMPAD4; + vkey_table[OS_Key_Num5] = VK_NUMPAD5; + vkey_table[OS_Key_Num6] = VK_NUMPAD6; + vkey_table[OS_Key_Num7] = VK_NUMPAD7; + vkey_table[OS_Key_Num8] = VK_NUMPAD8; + vkey_table[OS_Key_Num9] = VK_NUMPAD9; + vkey_table[OS_Key_LeftMouseButton] = VK_LBUTTON; + vkey_table[OS_Key_MiddleMouseButton] = VK_MBUTTON; + vkey_table[OS_Key_RightMouseButton] = VK_RBUTTON; + } + result = vkey_table[key]; + } + return result; +} + +internal LRESULT +os_w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + ProfBeginFunction(); + LRESULT result = 0; + B32 good = 1; + if(os_w32_event_arena == 0) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + good = 0; + } + if(good) + { + OS_W32_Window *window = os_w32_window_from_hwnd(hwnd); + OS_Window window_handle = os_w32_handle_from_window(window); + B32 release = 0; + + switch(uMsg) + { + default: + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + }break; + + case WM_ENTERSIZEMOVE: + { + os_w32_resizing = 1; + }break; + + case WM_EXITSIZEMOVE: + { + os_w32_resizing = 0; + }break; + + case WM_SIZE: + case WM_PAINT: + { + PAINTSTRUCT ps = {0}; + BeginPaint(hwnd, &ps); + update(); + EndPaint(hwnd, &ps); + DwmFlush(); + }break; + + case WM_CLOSE: + { + os_w32_push_event(OS_EventKind_WindowClose, window); + }break; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + { + release = 1; + } // fallthrough; + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + { + OS_Event *event = os_w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window); + switch (uMsg) + { + case WM_LBUTTONUP: case WM_LBUTTONDOWN: + { + event->key = OS_Key_LeftMouseButton; + }break; + case WM_MBUTTONUP: case WM_MBUTTONDOWN: + { + event->key = OS_Key_MiddleMouseButton; + }break; + case WM_RBUTTONUP: case WM_RBUTTONDOWN: + { + event->key = OS_Key_RightMouseButton; + }break; + } + event->pos.x = (F32)(S16)LOWORD(lParam); + event->pos.y = (F32)(S16)HIWORD(lParam); + if(release) + { + ReleaseCapture(); + } + else + { + SetCapture(hwnd); + } + }break; + + case WM_MOUSEMOVE: + { + OS_Event *event = os_w32_push_event(OS_EventKind_MouseMove, window); + event->pos.x = (F32)(S16)LOWORD(lParam); + event->pos.y = (F32)(S16)HIWORD(lParam); + }break; + + case WM_MOUSEWHEEL: + { + S16 wheel_delta = HIWORD(wParam); + OS_Event *event = os_w32_push_event(OS_EventKind_Scroll, window); + POINT p; + p.x = (S32)(S16)LOWORD(lParam); + p.y = (S32)(S16)HIWORD(lParam); + ScreenToClient(window->hwnd, &p); + event->pos.x = (F32)p.x; + event->pos.y = (F32)p.y; + event->delta = v2f32(0.f, -(F32)wheel_delta); + }break; + + case WM_MOUSEHWHEEL: + { + S16 wheel_delta = HIWORD(wParam); + OS_Event *event = os_w32_push_event(OS_EventKind_Scroll, window); + POINT p; + p.x = (S32)(S16)LOWORD(lParam); + p.y = (S32)(S16)HIWORD(lParam); + ScreenToClient(window->hwnd, &p); + event->pos.x = (F32)p.x; + event->pos.y = (F32)p.y; + event->delta = v2f32((F32)wheel_delta, 0.f); + }break; + + case WM_SYSKEYDOWN: case WM_SYSKEYUP: + { + if(wParam != VK_MENU && (wParam < VK_F1 || VK_F24 < wParam || wParam == VK_F4)) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + } // fallthrough; + case WM_KEYDOWN: case WM_KEYUP: + { + B32 was_down = (lParam & bit31); + B32 is_down = !(lParam & bit32); + + B32 is_repeat = 0; + if(!is_down) + { + release = 1; + } + else if(was_down) + { + is_repeat = 1; + } + + B32 right_sided = 0; + if ((lParam & bit25) && + (wParam == VK_CONTROL || wParam == VK_RCONTROL || + wParam == VK_MENU || wParam == VK_RMENU || + wParam == VK_SHIFT || wParam == VK_RSHIFT)) + { + right_sided = 1; + } + + OS_Event *event = os_w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window); + event->key = os_w32_os_key_from_vkey(wParam); + event->repeat_count = lParam & bitmask16; + event->is_repeat = is_repeat; + event->right_sided = right_sided; + if(event->key == OS_Key_Alt && event->modifiers & OS_Modifier_Alt) { event->modifiers &= ~OS_Modifier_Alt; } + if(event->key == OS_Key_Ctrl && event->modifiers & OS_Modifier_Ctrl) { event->modifiers &= ~OS_Modifier_Ctrl; } + if(event->key == OS_Key_Shift && event->modifiers & OS_Modifier_Shift) { event->modifiers &= ~OS_Modifier_Shift; } + }break; + + case WM_SYSCHAR: + { + WORD vk_code = LOWORD(wParam); + if(vk_code == VK_SPACE) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + else + { + result = 0; + } + }break; + + case WM_CHAR: + { + U32 character = wParam; + if(character >= 32 && character != 127) + { + OS_Event *event = os_w32_push_event(OS_EventKind_Text, window); + if(lParam & bit29) + { + event->modifiers |= OS_Modifier_Alt; + } + event->character = character; + } + }break; + + case WM_KILLFOCUS: + { + os_w32_push_event(OS_EventKind_WindowLoseFocus, window); + ReleaseCapture(); + }break; + + case WM_SETCURSOR: + { + Rng2F32 window_rect = os_client_rect_from_window(window_handle); + Vec2F32 mouse = os_mouse_from_window(window_handle); + B32 on_border = 0; + DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; + B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); + if(window != 0 && window->custom_border && !is_fullscreen) + { + B32 on_border_x = (mouse.x <= window->custom_border_edge_thickness || window_rect.x1-window->custom_border_edge_thickness <= mouse.x); + B32 on_border_y = (mouse.y <= window->custom_border_edge_thickness || window_rect.y1-window->custom_border_edge_thickness <= mouse.y); + on_border = on_border_x || on_border_y; + } + if(!os_w32_resizing && !on_border && contains_2f32(window_rect, mouse)) + { + SetCursor(os_w32_gfx_state->hCursor); + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + + case WM_DPICHANGED: + { + F32 new_dpi = (F32)(wParam & 0xffff); + RECT suggested_new_rect = *(RECT *)lParam; + window->dpi = new_dpi; + SetWindowPos(window->hwnd, 0, + suggested_new_rect.left, + suggested_new_rect.top, + suggested_new_rect.right - suggested_new_rect.left, + suggested_new_rect.bottom - suggested_new_rect.top, + 0); + }break; + + //- rjf: [file drop] + case WM_DROPFILES: + { + HDROP drop = (HDROP)wParam; + POINT drop_pt = {0}; + DragQueryPoint(drop, &drop_pt); + U64 num_files_dropped = DragQueryFile(drop, 0xffffffff, 0, 0); + OS_Event *event = os_w32_push_event(OS_EventKind_FileDrop, window); + event->pos = v2f32((F32)drop_pt.x, (F32)drop_pt.y); + for(U64 idx = 0; idx < num_files_dropped; idx += 1) + { + U64 name_size = DragQueryFile(drop, idx, 0, 0) + 1; + U8 *name_ptr = push_array(os_w32_event_arena, U8, name_size); + DragQueryFile(drop, idx, (char *)name_ptr, name_size); + String8 path_string = str8(name_ptr, name_size - 1); + String8 path_string__normalized = path_normalized_from_string(os_w32_event_arena, path_string); + str8_list_push(os_w32_event_arena, &event->strings, path_string__normalized); + } + DragFinish(drop); + }break; + + //- rjf: [custom border] + case WM_NCPAINT: + { + if(os_w32_new_window_custom_border || (window != 0 && window->custom_border && !window->custom_border_composition_enabled)) + { + result = 0; + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + case WM_DWMCOMPOSITIONCHANGED: + { + if(window != 0 && window->custom_border) + { + BOOL enabled = 0; + DwmIsCompositionEnabled(&enabled); + window->custom_border_composition_enabled = enabled; + if(enabled) + { + MARGINS m = { 0, 0, 1, 0 }; + DwmExtendFrameIntoClientArea(hwnd, &m); + DWORD dwmncrp_enabled = DWMNCRP_ENABLED; + DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(dwmncrp_enabled)); + } + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + case WM_WINDOWPOSCHANGED: + { + result = 0; + }break; + case WM_NCUAHDRAWCAPTION: + case WM_NCUAHDRAWFRAME: + { + // NOTE(rjf): undocumented messages for drawing themed window borders. + if(os_w32_new_window_custom_border || (window != 0 && window->custom_border)) + { + result = 0; + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + case WM_SETICON: + case WM_SETTEXT: + { + if(os_w32_new_window_custom_border || (window && window->custom_border && !window->custom_border_composition_enabled)) + { + // NOTE(rjf): + // https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/ + LONG_PTR old_style = GetWindowLongPtrW(hwnd, GWL_STYLE); + SetWindowLongPtrW(hwnd, GWL_STYLE, old_style & ~WS_VISIBLE); + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + SetWindowLongPtrW(hwnd, GWL_STYLE, old_style); + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + + //- rjf: [custom border] activation - without this `result`, stuff flickers. + case WM_NCACTIVATE: + { + if(!os_w32_new_window_custom_border && (window == 0 || window->custom_border == 0)) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, -1); + } + }break; + + //- rjf: [custom border] client/window size calculation + case WM_NCCALCSIZE: + { + if(os_w32_new_window_custom_border || (window && window->custom_border)) + { + F32 dpi = w32_GetDpiForWindow_func ? (F32)w32_GetDpiForWindow_func(hwnd) : 96.f; + S32 frame_x = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CXFRAME, dpi) : GetSystemMetrics(SM_CXFRAME); + S32 frame_y = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CYFRAME, dpi) : GetSystemMetrics(SM_CYFRAME); + S32 padding = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CXPADDEDBORDER, dpi) : GetSystemMetrics(SM_CXPADDEDBORDER); + DWORD window_style = GetWindowLong(hwnd, GWL_STYLE); + B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); + if(!is_fullscreen) + { + RECT* rect = wParam == 0 ? (RECT*)lParam : ((NCCALCSIZE_PARAMS*)lParam)->rgrc; + rect->right -= frame_x + padding; + rect->left += frame_x + padding; + rect->bottom -= frame_y + padding; + + if(IsMaximized(hwnd)) + { + rect->top += frame_y + padding; + // If we do not do this hidden taskbar can not be unhidden on mouse hover + // Unfortunately it can create an ugly bottom border when maximized... + rect->bottom -= 1; + } + } + } + else + { + result = DefWindowProc(hwnd, uMsg, wParam, lParam); + } + }break; + + //- rjf: [custom border] client/window hit testing (mapping mouse -> action) + case WM_NCHITTEST: + { + DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; + B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); + if(window == 0 || window->custom_border == 0 || is_fullscreen) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + else + { + B32 is_default_handled = 0; + + // Let the default procedure handle resizing areas + result = DefWindowProc(hwnd, uMsg, wParam, lParam); + switch (result) + { + case HTNOWHERE: + case HTRIGHT: + case HTLEFT: + case HTTOPLEFT: + case HTTOPRIGHT: + case HTBOTTOMRIGHT: + case HTBOTTOM: + case HTBOTTOMLEFT: + { + is_default_handled = 1; + } break; + } + + if (!is_default_handled) + { + POINT pos_monitor; + pos_monitor.x = GET_X_LPARAM(lParam); + pos_monitor.y = GET_Y_LPARAM(lParam); + POINT pos_client = pos_monitor; + ScreenToClient(hwnd, &pos_client); + + // Adjustments happening in NCCALCSIZE are messing with the detection + // of the top hit area so manually checking that. + F32 dpi = w32_GetDpiForWindow_func ? (F32)w32_GetDpiForWindow_func(hwnd) : 96.f; + S32 frame_y = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CYFRAME, dpi) : GetSystemMetrics(SM_CYFRAME); + // NOTE(rjf): it seems incorrect to apply this padding here... + // S32 padding = w32_GetSystemMetricsForDpi_func ? w32_GetSystemMetricsForDpi_func(SM_CXPADDEDBORDER, dpi) : GetSystemMetrics(SM_CXPADDEDBORDER); + + B32 is_over_top_resize = pos_client.y >= 0 && pos_client.y < frame_y; // + padding; + B32 is_over_title_bar = pos_client.y >= 0 && pos_client.y < window->custom_border_title_thickness; + + //- rjf: check against title bar client areas + B32 is_over_title_bar_client_area = 0; + for(OS_W32_TitleBarClientArea *area = window->first_title_bar_client_area; + area != 0; + area = area->next) + { + Rng2F32 rect = area->rect; + if(rect.x0 <= pos_client.x && pos_client.x < rect.x1 && + rect.y0 <= pos_client.y && pos_client.y < rect.y1) + { + is_over_title_bar_client_area = 1; + break; + } + } + + if (IsMaximized(hwnd)) + { + if (is_over_title_bar_client_area) + { + result = HTCLIENT; + } + else if (is_over_title_bar) + { + result = HTCAPTION; + } + else + { + result = HTCLIENT; + } + } + else + { + //Swap the first two conditions to choose if hovering the top border + //should prioritize resize or title bar buttons. + if (is_over_title_bar_client_area) + { + result = HTCLIENT; + } + else if (is_over_top_resize) + { + result = HTTOP; + } + else if (is_over_title_bar) + { + result = HTCAPTION; + } + else { + result = HTCLIENT; + } + } + } + } + }break; + } + } + ProfEnd(); + return result; +} + +//////////////////////////////// +//~ rjf: Monitors + +internal BOOL +os_w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr) +{ + OS_W32_MonitorGatherBundle *bundle = (OS_W32_MonitorGatherBundle *)bundle_ptr; + OS_Monitor m = {(U64)monitor}; + OS_W32_MonitorGatherNode *n = push_array(bundle->arena, OS_W32_MonitorGatherNode, 1); + n->v = m; + SLLQueuePush(bundle->first_monitor, bundle->last_monitor, n); + bundle->monitor_count += 1; + return 1; +} + +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void +os_gfx_init(void) +{ + //- rjf: set up base shared state + Arena *arena = arena_alloc(); + os_w32_gfx_state = push_array(arena, OS_W32_GfxState, 1); + os_w32_gfx_state->arena = arena; + os_w32_gfx_state->gfx_thread_tid = (U32)GetCurrentThreadId(); + os_w32_gfx_state->hInstance = GetModuleHandle(0); + + //- rjf: set dpi awareness + w32_SetProcessDpiAwarenessContext_Type *SetProcessDpiAwarenessContext_func = 0; + HMODULE module = LoadLibraryA("user32.dll"); + if(module != 0) + { + SetProcessDpiAwarenessContext_func = + (w32_SetProcessDpiAwarenessContext_Type*)GetProcAddress(module, "SetProcessDpiAwarenessContext"); + w32_GetDpiForWindow_func = + (w32_GetDpiForWindow_Type*)GetProcAddress(module, "GetDpiForWindow"); + w32_GetDpiForMonitor_func = (w32_GetDpiForMonitor_Type *)GetProcAddress(module, "GetDpiForMonitor"); + w32_GetSystemMetricsForDpi_func = (w32_GetSystemMetricsForDpi_Type *)GetProcAddress(module, "GetSystemMetricsForDpi"); + FreeLibrary(module); + } + if(SetProcessDpiAwarenessContext_func != 0) + { + SetProcessDpiAwarenessContext_func(w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + else + { + HMODULE shcore = LoadLibraryA("shcore.dll"); + if(shcore) + { + typedef HRESULT (WINAPI* SetProcessDpiAwareness_t)(int); + SetProcessDpiAwareness_t SetProcessDpiAwareness = (void*)GetProcAddress(shcore, "SetProcessDpiAwareness"); + if(SetProcessDpiAwareness) + { + SetProcessDpiAwareness(2); + } + FreeLibrary(shcore); + } + SetProcessDPIAware(); + } + + //- rjf: register graphical-window class + { + WNDCLASSEXW wndclass = {sizeof(wndclass)}; + wndclass.lpfnWndProc = os_w32_wnd_proc; + wndclass.hInstance = os_w32_gfx_state->hInstance; + wndclass.lpszClassName = L"graphical-window"; + wndclass.hCursor = LoadCursorA(0, IDC_ARROW); + wndclass.hIcon = LoadIcon(os_w32_gfx_state->hInstance, MAKEINTRESOURCE(1)); + wndclass.style = CS_VREDRAW|CS_HREDRAW; + ATOM wndatom = RegisterClassExW(&wndclass); + (void)wndatom; + } + + //- rjf: grab graphics system info + { + os_w32_gfx_state->gfx_info.double_click_time = GetDoubleClickTime()/1000.f; + os_w32_gfx_state->gfx_info.caret_blink_time = GetCaretBlinkTime()/1000.f; + DEVMODEW devmodew = {0}; + if(EnumDisplaySettingsW(0, ENUM_CURRENT_SETTINGS, &devmodew)) + { + os_w32_gfx_state->gfx_info.default_refresh_rate = (F32)devmodew.dmDisplayFrequency; + } + } + + //- rjf: set initial cursor + os_set_cursor(OS_Cursor_Pointer); + + //- rjf: fill vkey -> OS_Key table + { + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'A'] = OS_Key_A; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'B'] = OS_Key_B; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'C'] = OS_Key_C; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'D'] = OS_Key_D; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'E'] = OS_Key_E; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'F'] = OS_Key_F; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'G'] = OS_Key_G; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'H'] = OS_Key_H; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'I'] = OS_Key_I; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'J'] = OS_Key_J; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'K'] = OS_Key_K; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'L'] = OS_Key_L; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'M'] = OS_Key_M; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'N'] = OS_Key_N; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'O'] = OS_Key_O; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'P'] = OS_Key_P; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Q'] = OS_Key_Q; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'R'] = OS_Key_R; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'S'] = OS_Key_S; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'T'] = OS_Key_T; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'U'] = OS_Key_U; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'V'] = OS_Key_V; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'W'] = OS_Key_W; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'X'] = OS_Key_X; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Y'] = OS_Key_Y; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Z'] = OS_Key_Z; + + for(U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1) + { + os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; + } + for(U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1) + { + os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; + } + for(U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1) + { + os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; + } + + os_w32_gfx_state->key_from_vkey_table[VK_SPACE] = OS_Key_Space; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_3] = OS_Key_Tick; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_MINUS] = OS_Key_Minus; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_PLUS] = OS_Key_Equal; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_4] = OS_Key_LeftBracket; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_6] = OS_Key_RightBracket; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_1] = OS_Key_Semicolon; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_7] = OS_Key_Quote; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_COMMA] = OS_Key_Comma; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_PERIOD]= OS_Key_Period; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_2] = OS_Key_Slash; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_5] = OS_Key_BackSlash; + + os_w32_gfx_state->key_from_vkey_table[VK_TAB] = OS_Key_Tab; + os_w32_gfx_state->key_from_vkey_table[VK_PAUSE] = OS_Key_Pause; + os_w32_gfx_state->key_from_vkey_table[VK_ESCAPE] = OS_Key_Esc; + + os_w32_gfx_state->key_from_vkey_table[VK_UP] = OS_Key_Up; + os_w32_gfx_state->key_from_vkey_table[VK_LEFT] = OS_Key_Left; + os_w32_gfx_state->key_from_vkey_table[VK_DOWN] = OS_Key_Down; + os_w32_gfx_state->key_from_vkey_table[VK_RIGHT] = OS_Key_Right; + + os_w32_gfx_state->key_from_vkey_table[VK_BACK] = OS_Key_Backspace; + os_w32_gfx_state->key_from_vkey_table[VK_RETURN] = OS_Key_Return; + + os_w32_gfx_state->key_from_vkey_table[VK_DELETE] = OS_Key_Delete; + os_w32_gfx_state->key_from_vkey_table[VK_INSERT] = OS_Key_Insert; + os_w32_gfx_state->key_from_vkey_table[VK_PRIOR] = OS_Key_PageUp; + os_w32_gfx_state->key_from_vkey_table[VK_NEXT] = OS_Key_PageDown; + os_w32_gfx_state->key_from_vkey_table[VK_HOME] = OS_Key_Home; + os_w32_gfx_state->key_from_vkey_table[VK_END] = OS_Key_End; + + os_w32_gfx_state->key_from_vkey_table[VK_CAPITAL] = OS_Key_CapsLock; + os_w32_gfx_state->key_from_vkey_table[VK_NUMLOCK] = OS_Key_NumLock; + os_w32_gfx_state->key_from_vkey_table[VK_SCROLL] = OS_Key_ScrollLock; + os_w32_gfx_state->key_from_vkey_table[VK_APPS] = OS_Key_Menu; + + os_w32_gfx_state->key_from_vkey_table[VK_CONTROL] = OS_Key_Ctrl; + os_w32_gfx_state->key_from_vkey_table[VK_LCONTROL] = OS_Key_Ctrl; + os_w32_gfx_state->key_from_vkey_table[VK_RCONTROL] = OS_Key_Ctrl; + os_w32_gfx_state->key_from_vkey_table[VK_SHIFT] = OS_Key_Shift; + os_w32_gfx_state->key_from_vkey_table[VK_LSHIFT] = OS_Key_Shift; + os_w32_gfx_state->key_from_vkey_table[VK_RSHIFT] = OS_Key_Shift; + os_w32_gfx_state->key_from_vkey_table[VK_MENU] = OS_Key_Alt; + os_w32_gfx_state->key_from_vkey_table[VK_LMENU] = OS_Key_Alt; + os_w32_gfx_state->key_from_vkey_table[VK_RMENU] = OS_Key_Alt; + + os_w32_gfx_state->key_from_vkey_table[VK_DIVIDE] = OS_Key_NumSlash; + os_w32_gfx_state->key_from_vkey_table[VK_MULTIPLY] = OS_Key_NumStar; + os_w32_gfx_state->key_from_vkey_table[VK_SUBTRACT] = OS_Key_NumMinus; + os_w32_gfx_state->key_from_vkey_table[VK_ADD] = OS_Key_NumPlus; + os_w32_gfx_state->key_from_vkey_table[VK_DECIMAL] = OS_Key_NumPeriod; + + for(U32 i = 0; i < 10; i += 1) + { + os_w32_gfx_state->key_from_vkey_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i); + } + + for(U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1) + { + os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j); + } + } +} + +//////////////////////////////// +//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) + +internal OS_GfxInfo * +os_get_gfx_info(void) +{ + return &os_w32_gfx_state->gfx_info; +} + +//////////////////////////////// +//~ rjf: @os_hooks Clipboards (Implemented Per-OS) + +internal void +os_set_clipboard_text(String8 string) +{ + Temp scratch = scratch_begin(0, 0); + if(OpenClipboard(0)) + { + EmptyClipboard(); + String16 string16 = str16_from_8(scratch.arena, string); + HANDLE string16_copy_handle = GlobalAlloc(GMEM_MOVEABLE, (string16.size+1)*sizeof(string16.str[0])); + if(string16_copy_handle) + { + U16 *copy_buffer = (U16 *)GlobalLock(string16_copy_handle); + MemoryCopy(copy_buffer, string16.str, string16.size*sizeof(string16.str[0])); + copy_buffer[string16.size] = 0; + GlobalUnlock(string16_copy_handle); + SetClipboardData(CF_UNICODETEXT, string16_copy_handle); + } + CloseClipboard(); + } + scratch_end(scratch); +} + +internal String8 +os_get_clipboard_text(Arena *arena) +{ + String8 result = {0}; + if(IsClipboardFormatAvailable(CF_UNICODETEXT) && + OpenClipboard(0)) + { + HANDLE data_handle = GetClipboardData(CF_UNICODETEXT); + if(data_handle) + { + U16 *buffer = (U16 *)GlobalLock(data_handle); + if(buffer) + { + U64 size = cstring16_length(buffer); + String16 string16 = str16(buffer, size); + result = str8_from_16(arena, string16); + GlobalUnlock(data_handle); + } + } + CloseClipboard(); + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Windows (Implemented Per-OS) + +internal OS_Window +os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) +{ + B32 custom_border = !!(flags & OS_WindowFlag_CustomBorder); + B32 use_default_position = !!(flags & OS_WindowFlag_UseDefaultPosition); + Vec2F32 pos = rect.p0; + Vec2F32 dim = dim_2f32(rect); + + //- rjf: make hwnd + HWND hwnd = 0; + { + Temp scratch = scratch_begin(0, 0); + String16 title16 = str16_from_8(scratch.arena, title); + os_w32_new_window_custom_border = custom_border; + DWORD style_flags = WS_EX_APPWINDOW; +#if defined(R_BACKEND) && R_BACKEND != R_BACKEND_OPENGL + style_flags |= WS_EX_NOREDIRECTIONBITMAP; +#endif + hwnd = CreateWindowExW(style_flags, + L"graphical-window", + (WCHAR*)title16.str, + WS_OVERLAPPEDWINDOW | WS_SIZEBOX, + use_default_position ? CW_USEDEFAULT : (S32)pos.x, + use_default_position ? CW_USEDEFAULT : (S32)pos.y, + (S32)dim.x, + (S32)dim.y, + 0, 0, + os_w32_gfx_state->hInstance, + 0); + DragAcceptFiles(hwnd, 1); + os_w32_new_window_custom_border = 0; + scratch_end(scratch); + } + + //- rjf- make/fill window + OS_W32_Window *window = os_w32_window_alloc(); + { + window->hwnd = hwnd; + window->hdc = GetDC(hwnd); + if(w32_GetDpiForWindow_func != 0) + { + window->dpi = (F32)w32_GetDpiForWindow_func(hwnd); + } + else + { + window->dpi = 96.f; + } + } + + //- rjf: early detection of composition + { + BOOL enabled = 0; + DwmIsCompositionEnabled(&enabled); + window->custom_border_composition_enabled = enabled; + } + + //- rjf: custom border + if(flags & OS_WindowFlag_CustomBorder) + { + window->custom_border = 1; + window->paint_arena = arena_alloc(); + } + + //- rjf: convert to handle + return + OS_Window result = os_w32_handle_from_window(window); + return result; +} + +internal void +os_window_close(OS_Window handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + os_w32_window_release(window); +} + +internal void +os_window_set_title(OS_Window handle, String8 title) +{ + Temp scratch = scratch_begin(0, 0); + OS_W32_Window *window = os_w32_window_from_handle(handle); + String16 title16 = str16_from_8(scratch.arena, title); + SetWindowTextW(window->hwnd, (WCHAR *)title16.str); + scratch_end(scratch); +} + +internal void +os_window_first_paint(OS_Window window_handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(window_handle); + window->first_paint_done = 1; + ShowWindow(window->hwnd, SW_SHOW); + if(window->maximized) + { + ShowWindow(window->hwnd, SW_MAXIMIZE); + } +} + +internal void +os_window_focus(OS_Window handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + SetForegroundWindow(window->hwnd); + SetFocus(window->hwnd); +} + +internal B32 +os_window_is_focused(OS_Window handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + HWND active_hwnd = GetActiveWindow(); + return active_hwnd == window->hwnd; +} + +internal B32 +os_window_is_fullscreen(OS_Window handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); + return !(window_style & WS_OVERLAPPEDWINDOW); +} + +internal void +os_window_set_fullscreen(OS_Window handle, B32 fullscreen) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); + B32 is_fullscreen_already = os_window_is_fullscreen(handle); + if(fullscreen) + { + if(!is_fullscreen_already) + { + GetWindowPlacement(window->hwnd, &window->last_window_placement); + } + MONITORINFO monitor_info = {sizeof(monitor_info)}; + if(GetMonitorInfo(MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info)) + { + SetWindowLong(window->hwnd, GWL_STYLE, window_style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(window->hwnd, HWND_TOP, + monitor_info.rcMonitor.left, + monitor_info.rcMonitor.top, + monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, + monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLong(window->hwnd, GWL_STYLE, window_style | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(window->hwnd, &window->last_window_placement); + SetWindowPos(window->hwnd, 0, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } +} + +internal B32 +os_window_is_maximized(OS_Window handle) +{ + B32 result = 0; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window) + { + result = !!(IsZoomed(window->hwnd)); + } + return result; +} + +internal void +os_window_set_maximized(OS_Window handle, B32 maximized) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window != 0) + { + if(window->first_paint_done) + { + switch(maximized) + { + default: + case 0:{ShowWindow(window->hwnd, SW_RESTORE);}break; + case 1:{ShowWindow(window->hwnd, SW_MAXIMIZE);}break; + } + } + else + { + window->maximized = maximized; + } + } +} + +internal B32 +os_window_is_minimized(OS_Window handle) +{ + B32 result = 0; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window) + { + result = !!(IsIconic(window->hwnd)); + } + return result; +} + +internal void +os_window_set_minimized(OS_Window handle, B32 minimized) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window != 0 && minimized != os_window_is_minimized(handle)) + { + switch(minimized) + { + default: + case 0:{ShowWindow(window->hwnd, SW_RESTORE);}break; + case 1:{ShowWindow(window->hwnd, SW_MINIMIZE);}break; + } + } +} + +internal void +os_window_bring_to_front(OS_Window handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window != 0) + { + BringWindowToTop(window->hwnd); + } +} + +internal void +os_window_set_monitor(OS_Window window_handle, OS_Monitor monitor) +{ + OS_W32_Window *window = os_w32_window_from_handle(window_handle); + HMONITOR hmonitor = (HMONITOR)monitor.u64[0]; + { + MONITORINFOEXW info; + info.cbSize = sizeof(MONITORINFOEXW); + if(GetMonitorInfoW(hmonitor, (MONITORINFO *)&info)) + { + Rng2F32 existing_rect = os_rect_from_window(window_handle); + Vec2F32 window_size = dim_2f32(existing_rect); + SetWindowPos(window->hwnd, HWND_TOP, + (info.rcWork.left + info.rcWork.right)/2 - window_size.x/2, + (info.rcWork.top + info.rcWork.bottom)/2 - window_size.y/2, + window_size.x, + window_size.y, 0); + } + } +} + +internal void +os_window_clear_custom_border_data(OS_Window handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window->custom_border) + { + arena_clear(window->paint_arena); + window->first_title_bar_client_area = window->last_title_bar_client_area = 0; + window->custom_border_title_thickness = 0; + window->custom_border_edge_thickness = 0; + } +} + +internal void +os_window_push_custom_title_bar(OS_Window handle, F32 thickness) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + window->custom_border_title_thickness = thickness; +} + +internal void +os_window_push_custom_edges(OS_Window handle, F32 thickness) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + window->custom_border_edge_thickness = thickness; +} + +internal void +os_window_push_custom_title_bar_client_area(OS_Window handle, Rng2F32 rect) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window->custom_border) + { + OS_W32_TitleBarClientArea *area = push_array(window->paint_arena, OS_W32_TitleBarClientArea, 1); + if(area != 0) + { + area->rect = rect; + SLLQueuePush(window->first_title_bar_client_area, window->last_title_bar_client_area, area); + } + } +} + +internal Rng2F32 +os_rect_from_window(OS_Window handle) +{ + Rng2F32 r = {0}; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window) + { + RECT rect = {0}; + GetWindowRect(os_w32_hwnd_from_window(window), &rect); + r = os_w32_rng2f32_from_rect(rect); + } + return r; +} + +internal Rng2F32 +os_client_rect_from_window(OS_Window handle) +{ + Rng2F32 r = {0}; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window) + { + RECT rect = {0}; + GetClientRect(os_w32_hwnd_from_window(window), &rect); + r = os_w32_rng2f32_from_rect(rect); + } + return r; +} + +internal F32 +os_dpi_from_window(OS_Window handle) +{ + F32 result = 96.f; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window != 0) + { + result = window->dpi; + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks External Windows (Implemented Per-OS) + +internal OS_ExternalWindow +os_focused_external_window(void) +{ + HWND hwnd = GetForegroundWindow(); + OS_ExternalWindow result = {(U64)hwnd}; + return result; +} + +internal void +os_focus_external_window(OS_ExternalWindow handle) +{ + HWND hwnd = (HWND)handle.u64[0]; + if(hwnd != 0) + { + SetForegroundWindow(hwnd); + SetFocus(hwnd); + } +} + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_MonitorArray +os_push_monitors_array(Arena *arena) +{ + Temp scratch = scratch_begin(&arena, 1); + OS_W32_MonitorGatherBundle bundle = {arena}; + EnumDisplayMonitors(0, 0, os_w32_monitor_gather_enum_proc, (LPARAM)&bundle); + OS_MonitorArray array = {0}; + array.count = bundle.monitor_count; + array.v = push_array(arena, OS_Monitor, array.count); + { + U64 idx = 0; + for EachNode(n, OS_W32_MonitorGatherNode, bundle.first_monitor) + { + array.v[idx] = n->v; + idx += 1; + } + } + scratch_end(scratch); + return array; +} + +internal OS_Monitor +os_primary_monitor(void) +{ + POINT zero_pt = {0, 0}; + HMONITOR monitor = MonitorFromPoint(zero_pt, MONITOR_DEFAULTTOPRIMARY); + OS_Monitor result = {(U64)monitor}; + return result; +} + +internal OS_Monitor +os_monitor_from_window(OS_Window window) +{ + OS_W32_Window *w = os_w32_window_from_handle(window); + HMONITOR handle = MonitorFromWindow(w->hwnd, MONITOR_DEFAULTTOPRIMARY); + OS_Monitor result = {(U64)handle}; + return result; +} + +internal String8 +os_name_from_monitor(Arena *arena, OS_Monitor monitor) +{ + String8 result = {0}; + HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; + MONITORINFOEXW info; + info.cbSize = sizeof(MONITORINFOEXW); + if(GetMonitorInfoW(monitor_handle, (MONITORINFO *)&info)) + { + String16 result16 = str16_cstring((U16 *)info.szDevice); + result = str8_from_16(arena, result16); + } + return result; +} + +internal Vec2F32 +os_dim_from_monitor(OS_Monitor monitor) +{ + Vec2F32 result = {0}; + HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; + MONITORINFO info = {0}; + info.cbSize = sizeof(MONITORINFO); + if(GetMonitorInfoW(monitor_handle, &info)) + { + result.x = info.rcWork.right - info.rcWork.left; + result.y = info.rcWork.bottom - info.rcWork.top; + } + return result; +} + +internal F32 +os_dpi_from_monitor(OS_Monitor monitor) +{ + F32 result = 96.f; + HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; + if(w32_GetDpiForMonitor_func != 0) + { + UINT dpi_x = 0; + UINT dpi_y = 0; + HRESULT hr = w32_GetDpiForMonitor_func(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y); + result = (F32)dpi_x; + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +internal void +os_send_wakeup_event(void) +{ + PostThreadMessageA(os_w32_gfx_state->gfx_thread_tid, 0x401, 0, 0); +} + +internal OS_EventList +os_get_events(Arena *arena, B32 wait) +{ + os_w32_event_arena = arena; + MemoryZeroStruct(&os_w32_event_list); + MSG msg = {0}; + if(!wait || GetMessage(&msg, 0, 0, 0)) + { + B32 first_wait = wait; + for(;first_wait || PeekMessage(&msg, 0, 0, 0, PM_REMOVE); first_wait = 0) + { + DispatchMessage(&msg); + TranslateMessage(&msg); + if(msg.message == WM_QUIT) + { + os_w32_push_event(OS_EventKind_WindowClose, 0); + } + } + } + return os_w32_event_list; +} + +internal OS_Modifiers +os_get_modifiers(void) +{ + OS_Modifiers modifiers = 0; + if(GetKeyState(VK_CONTROL) & 0x8000) + { + modifiers |= OS_Modifier_Ctrl; + } + if(GetKeyState(VK_SHIFT) & 0x8000) + { + modifiers |= OS_Modifier_Shift; + } + if(GetKeyState(VK_MENU) & 0x8000) + { + modifiers |= OS_Modifier_Alt; + } + return modifiers; +} + +internal B32 +os_key_is_down(OS_Key key) +{ + B32 down = 0; + WPARAM vkey = os_w32_vkey_from_os_key(key); + if(GetKeyState(vkey) & 0x8000) + { + down = 1; + } + return down; +} + +internal Vec2F32 +os_mouse_from_window(OS_Window handle) +{ + ProfBeginFunction(); + Vec2F32 v = {0}; + POINT p; + if(GetCursorPos(&p)) + { + OS_W32_Window *window = os_w32_window_from_handle(handle); + ScreenToClient(window->hwnd, &p); + v.x = (F32)p.x; + v.y = (F32)p.y; + } + ProfEnd(); + return v; +} + +//////////////////////////////// +//~ rjf: @os_hooks Cursors (Implemented Per-OS) + +internal void +os_set_cursor(OS_Cursor cursor) +{ + B32 valid_cursor = 1; + HCURSOR hcursor = 0; + switch(cursor) + { + default: {valid_cursor = 0;}break; +#define Win32CursorXList(X) \ +X(Pointer, IDC_ARROW) \ +X(IBar, IDC_IBEAM) \ +X(LeftRight, IDC_SIZEWE) \ +X(UpDown, IDC_SIZENS) \ +X(DownRight, IDC_SIZENWSE) \ +X(UpRight, IDC_SIZENESW) \ +X(UpDownLeftRight, IDC_SIZEALL) \ +X(HandPoint, IDC_HAND)\ +X(Disabled, IDC_NO) +#define CursorCase(E,R) case OS_Cursor_##E:{ \ +local_persist HCURSOR curs = 0; \ +if (curs == 0){ curs = LoadCursor(NULL, R); } \ +hcursor = curs; }break; + Win32CursorXList(CursorCase) +#undef CursorCase +#undef Win32CursorXList + } + if(valid_cursor && !os_w32_resizing) + { + if(hcursor != os_w32_gfx_state->hCursor) + { + PostMessage(0, WM_SETCURSOR, 0, 0); + POINT p = {0}; + GetCursorPos(&p); + SetCursorPos(p.x, p.y); + } + os_w32_gfx_state->hCursor = hcursor; + } +} + +//////////////////////////////// +//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) + +internal void +os_graphical_message(B32 error, String8 title, String8 message) +{ + Temp scratch = scratch_begin(0, 0); + String16 title16 = str16_from_8(scratch.arena, title); + String16 message16 = str16_from_8(scratch.arena, message); + MessageBoxW(0, (WCHAR *)message16.str, (WCHAR *)title16.str, MB_OK|(!!error*MB_ICONERROR)); + scratch_end(scratch); +} + +internal String8 +os_graphical_pick_file(Arena *arena, String8 initial_path) +{ + String8 result = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + U64 buffer_size = 4096; + U16 *buffer = push_array(scratch.arena, U16, buffer_size); + OPENFILENAMEW params = {sizeof(params)}; + { + params.lpstrFile = (WCHAR *)buffer; + params.nMaxFile = buffer_size; + params.lpstrInitialDir = (WCHAR *)str16_from_8(scratch.arena, initial_path).str; + } + if(GetOpenFileNameW(¶ms)) + { + result = str8_from_16(arena, str16_cstring((U16 *)buffer)); + } + scratch_end(scratch); + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Shell Operations + +internal void +os_show_in_filesystem_ui(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + for(U64 idx = 0; idx < path_copy.size; idx += 1) + { + if(path_copy.str[idx] == '/') + { + path_copy.str[idx] = '\\'; + } + } + String16 path16 = str16_from_8(scratch.arena, path_copy); + SFGAOF flags = 0; + PIDLIST_ABSOLUTE list = 0; + if(path16.size != 0 && SUCCEEDED(SHParseDisplayName(path16.str, 0, &list, 0, &flags))) + { + HRESULT hr = SHOpenFolderAndSelectItems(list, 0, 0, 0); + CoTaskMemFree(list); + (void)hr; + } + scratch_end(scratch); +} + +internal void +os_open_in_browser(String8 url) +{ + Temp scratch = scratch_begin(0, 0); + String16 url16 = str16_from_8(scratch.arena, url); + ShellExecuteW(0, L"open", (WCHAR *)url16.str, 0, 0, SW_SHOWNORMAL); + scratch_end(scratch); +} diff --git a/src/os/gfx/win32/os_gfx_win32.h b/src/win32/window_manager/win32_window_manager.h similarity index 94% rename from src/os/gfx/win32/os_gfx_win32.h rename to src/win32/window_manager/win32_window_manager.h index 8dc95691..90548121 100644 --- a/src/os/gfx/win32/os_gfx_win32.h +++ b/src/win32/window_manager/win32_window_manager.h @@ -1,123 +1,123 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_GFX_WIN32_H -#define OS_GFX_WIN32_H - -//////////////////////////////// -//~ rjf: Includes / Libraries - -#include -#include -#include -#pragma comment(lib, "gdi32") -#pragma comment(lib, "dwmapi") -#pragma comment(lib, "UxTheme") -#pragma comment(lib, "comdlg32") -#ifndef WM_NCUAHDRAWCAPTION -#define WM_NCUAHDRAWCAPTION (0x00AE) -#endif -#ifndef WM_NCUAHDRAWFRAME -#define WM_NCUAHDRAWFRAME (0x00AF) -#endif - -//////////////////////////////// -//~ rjf: Windows - -typedef struct OS_W32_TitleBarClientArea OS_W32_TitleBarClientArea; -struct OS_W32_TitleBarClientArea -{ - OS_W32_TitleBarClientArea *next; - Rng2F32 rect; -}; - -typedef struct OS_W32_Window OS_W32_Window; -struct OS_W32_Window -{ - OS_W32_Window *next; - OS_W32_Window *prev; - HWND hwnd; - HDC hdc; - WINDOWPLACEMENT last_window_placement; - F32 dpi; - B32 first_paint_done; - B32 maximized; - B32 custom_border; - F32 custom_border_title_thickness; - F32 custom_border_edge_thickness; - B32 custom_border_composition_enabled; - Arena *paint_arena; - OS_W32_TitleBarClientArea *first_title_bar_client_area; - OS_W32_TitleBarClientArea *last_title_bar_client_area; -}; - -//////////////////////////////// -//~ rjf: Monitor Gathering Bundle - -typedef struct OS_W32_MonitorGatherNode OS_W32_MonitorGatherNode; -struct OS_W32_MonitorGatherNode -{ - OS_W32_MonitorGatherNode *next; - OS_Monitor v; -}; - -typedef struct OS_W32_MonitorGatherBundle OS_W32_MonitorGatherBundle; -struct OS_W32_MonitorGatherBundle -{ - Arena *arena; - OS_W32_MonitorGatherNode *first_monitor; - OS_W32_MonitorGatherNode *last_monitor; - U64 monitor_count; -}; - -//////////////////////////////// -//~ rjf: Global State - -typedef struct OS_W32_GfxState OS_W32_GfxState; -struct OS_W32_GfxState -{ - Arena *arena; - U32 gfx_thread_tid; - HINSTANCE hInstance; - HCURSOR hCursor; - OS_GfxInfo gfx_info; - OS_W32_Window *first_window; - OS_W32_Window *last_window; - OS_W32_Window *free_window; - OS_Key key_from_vkey_table[256]; -}; - -//////////////////////////////// -//~ rjf: Globals - -global OS_W32_GfxState *os_w32_gfx_state = 0; -global OS_EventList os_w32_event_list = {0}; -global Arena *os_w32_event_arena = 0; -global B32 os_w32_resizing = 0; -global B32 os_w32_new_window_custom_border = 0; - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal Rng2F32 os_w32_rng2f32_from_rect(RECT rect); - -//////////////////////////////// -//~ rjf: Windows - -internal OS_Window os_w32_handle_from_window(OS_W32_Window *window); -internal OS_W32_Window * os_w32_window_from_handle(OS_Window window); -internal OS_W32_Window * os_w32_window_from_hwnd(HWND hwnd); -internal HWND os_w32_hwnd_from_window(OS_W32_Window *window); -internal OS_W32_Window * os_w32_window_alloc(void); -internal void os_w32_window_release(OS_W32_Window *window); -internal OS_Event * os_w32_push_event(OS_EventKind kind, OS_W32_Window *window); -internal OS_Key os_w32_os_key_from_vkey(WPARAM vkey); -internal WPARAM os_w32_vkey_from_os_key(OS_Key key); -internal LRESULT os_w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - -//////////////////////////////// -//~ rjf: Monitors - -internal BOOL os_w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr); - -#endif // OS_GFX_WIN32_H +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef WIN32_WINDOW_MANAGER_H +#define WIN32_WINDOW_MANAGER_H + +//////////////////////////////// +//~ rjf: Includes / Libraries + +#include +#include +#include +#pragma comment(lib, "gdi32") +#pragma comment(lib, "dwmapi") +#pragma comment(lib, "UxTheme") +#pragma comment(lib, "comdlg32") +#ifndef WM_NCUAHDRAWCAPTION +#define WM_NCUAHDRAWCAPTION (0x00AE) +#endif +#ifndef WM_NCUAHDRAWFRAME +#define WM_NCUAHDRAWFRAME (0x00AF) +#endif + +//////////////////////////////// +//~ rjf: Windows + +typedef struct OS_W32_TitleBarClientArea OS_W32_TitleBarClientArea; +struct OS_W32_TitleBarClientArea +{ + OS_W32_TitleBarClientArea *next; + Rng2F32 rect; +}; + +typedef struct OS_W32_Window OS_W32_Window; +struct OS_W32_Window +{ + OS_W32_Window *next; + OS_W32_Window *prev; + HWND hwnd; + HDC hdc; + WINDOWPLACEMENT last_window_placement; + F32 dpi; + B32 first_paint_done; + B32 maximized; + B32 custom_border; + F32 custom_border_title_thickness; + F32 custom_border_edge_thickness; + B32 custom_border_composition_enabled; + Arena *paint_arena; + OS_W32_TitleBarClientArea *first_title_bar_client_area; + OS_W32_TitleBarClientArea *last_title_bar_client_area; +}; + +//////////////////////////////// +//~ rjf: Monitor Gathering Bundle + +typedef struct OS_W32_MonitorGatherNode OS_W32_MonitorGatherNode; +struct OS_W32_MonitorGatherNode +{ + OS_W32_MonitorGatherNode *next; + OS_Monitor v; +}; + +typedef struct OS_W32_MonitorGatherBundle OS_W32_MonitorGatherBundle; +struct OS_W32_MonitorGatherBundle +{ + Arena *arena; + OS_W32_MonitorGatherNode *first_monitor; + OS_W32_MonitorGatherNode *last_monitor; + U64 monitor_count; +}; + +//////////////////////////////// +//~ rjf: Global State + +typedef struct OS_W32_GfxState OS_W32_GfxState; +struct OS_W32_GfxState +{ + Arena *arena; + U32 gfx_thread_tid; + HINSTANCE hInstance; + HCURSOR hCursor; + OS_GfxInfo gfx_info; + OS_W32_Window *first_window; + OS_W32_Window *last_window; + OS_W32_Window *free_window; + OS_Key key_from_vkey_table[256]; +}; + +//////////////////////////////// +//~ rjf: Globals + +global OS_W32_GfxState *os_w32_gfx_state = 0; +global OS_EventList os_w32_event_list = {0}; +global Arena *os_w32_event_arena = 0; +global B32 os_w32_resizing = 0; +global B32 os_w32_new_window_custom_border = 0; + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal Rng2F32 os_w32_rng2f32_from_rect(RECT rect); + +//////////////////////////////// +//~ rjf: Windows + +internal OS_Window os_w32_handle_from_window(OS_W32_Window *window); +internal OS_W32_Window * os_w32_window_from_handle(OS_Window window); +internal OS_W32_Window * os_w32_window_from_hwnd(HWND hwnd); +internal HWND os_w32_hwnd_from_window(OS_W32_Window *window); +internal OS_W32_Window * os_w32_window_alloc(void); +internal void os_w32_window_release(OS_W32_Window *window); +internal OS_Event * os_w32_push_event(OS_EventKind kind, OS_W32_Window *window); +internal OS_Key os_w32_os_key_from_vkey(WPARAM vkey); +internal WPARAM os_w32_vkey_from_os_key(OS_Key key); +internal LRESULT os_w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +//////////////////////////////// +//~ rjf: Monitors + +internal BOOL os_w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr); + +#endif // WIN32_WINDOW_MANAGER_H diff --git a/src/os/gfx/generated/os_gfx.meta.c b/src/window_manager/generated/window_manager.meta.c similarity index 95% rename from src/os/gfx/generated/os_gfx.meta.c rename to src/window_manager/generated/window_manager.meta.c index b44fa317..a3f50334 100644 --- a/src/os/gfx/generated/os_gfx.meta.c +++ b/src/window_manager/generated/window_manager.meta.c @@ -1,302 +1,302 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//- GENERATED CODE - -C_LINKAGE_BEGIN -String8 os_g_key_display_string_table[143] = -{ -str8_lit_comp("Invalid Key"), -str8_lit_comp("Escape"), -str8_lit_comp("F1"), -str8_lit_comp("F2"), -str8_lit_comp("F3"), -str8_lit_comp("F4"), -str8_lit_comp("F5"), -str8_lit_comp("F6"), -str8_lit_comp("F7"), -str8_lit_comp("F8"), -str8_lit_comp("F9"), -str8_lit_comp("F10"), -str8_lit_comp("F11"), -str8_lit_comp("F12"), -str8_lit_comp("F13"), -str8_lit_comp("F14"), -str8_lit_comp("F15"), -str8_lit_comp("F16"), -str8_lit_comp("F17"), -str8_lit_comp("F18"), -str8_lit_comp("F19"), -str8_lit_comp("F20"), -str8_lit_comp("F21"), -str8_lit_comp("F22"), -str8_lit_comp("F23"), -str8_lit_comp("F24"), -str8_lit_comp("Tick"), -str8_lit_comp("0"), -str8_lit_comp("1"), -str8_lit_comp("2"), -str8_lit_comp("3"), -str8_lit_comp("4"), -str8_lit_comp("5"), -str8_lit_comp("6"), -str8_lit_comp("7"), -str8_lit_comp("8"), -str8_lit_comp("9"), -str8_lit_comp("Minus"), -str8_lit_comp("Equal"), -str8_lit_comp("Backspace"), -str8_lit_comp("Tab"), -str8_lit_comp("Q"), -str8_lit_comp("W"), -str8_lit_comp("E"), -str8_lit_comp("R"), -str8_lit_comp("T"), -str8_lit_comp("Y"), -str8_lit_comp("U"), -str8_lit_comp("I"), -str8_lit_comp("O"), -str8_lit_comp("P"), -str8_lit_comp("Left Bracket"), -str8_lit_comp("Right Bracket"), -str8_lit_comp("Back Slash"), -str8_lit_comp("Caps Lock"), -str8_lit_comp("A"), -str8_lit_comp("S"), -str8_lit_comp("D"), -str8_lit_comp("F"), -str8_lit_comp("G"), -str8_lit_comp("H"), -str8_lit_comp("J"), -str8_lit_comp("K"), -str8_lit_comp("L"), -str8_lit_comp("Semicolon"), -str8_lit_comp("Quote"), -str8_lit_comp("Return"), -str8_lit_comp("Shift"), -str8_lit_comp("Z"), -str8_lit_comp("X"), -str8_lit_comp("C"), -str8_lit_comp("V"), -str8_lit_comp("B"), -str8_lit_comp("N"), -str8_lit_comp("M"), -str8_lit_comp("Comma"), -str8_lit_comp("Period"), -str8_lit_comp("Slash"), -str8_lit_comp("Ctrl"), -str8_lit_comp("Alt"), -str8_lit_comp("Space"), -str8_lit_comp("Menu"), -str8_lit_comp("Scroll Lock"), -str8_lit_comp("Pause"), -str8_lit_comp("Insert"), -str8_lit_comp("Home"), -str8_lit_comp("Page Up"), -str8_lit_comp("Delete"), -str8_lit_comp("End"), -str8_lit_comp("Page Down"), -str8_lit_comp("Up"), -str8_lit_comp("Left"), -str8_lit_comp("Down"), -str8_lit_comp("Right"), -str8_lit_comp("Ex0"), -str8_lit_comp("Ex1"), -str8_lit_comp("Ex2"), -str8_lit_comp("Ex3"), -str8_lit_comp("Ex4"), -str8_lit_comp("Ex5"), -str8_lit_comp("Ex6"), -str8_lit_comp("Ex7"), -str8_lit_comp("Ex8"), -str8_lit_comp("Ex9"), -str8_lit_comp("Ex10"), -str8_lit_comp("Ex11"), -str8_lit_comp("Ex12"), -str8_lit_comp("Ex13"), -str8_lit_comp("Ex14"), -str8_lit_comp("Ex15"), -str8_lit_comp("Ex16"), -str8_lit_comp("Ex17"), -str8_lit_comp("Ex18"), -str8_lit_comp("Ex19"), -str8_lit_comp("Ex20"), -str8_lit_comp("Ex21"), -str8_lit_comp("Ex22"), -str8_lit_comp("Ex23"), -str8_lit_comp("Ex24"), -str8_lit_comp("Ex25"), -str8_lit_comp("Ex26"), -str8_lit_comp("Ex27"), -str8_lit_comp("Ex28"), -str8_lit_comp("Ex29"), -str8_lit_comp("Num Lock"), -str8_lit_comp("Numpad Slash"), -str8_lit_comp("Numpad Star"), -str8_lit_comp("Numpad Minus"), -str8_lit_comp("Numpad Plus"), -str8_lit_comp("Numpad Period"), -str8_lit_comp("Numpad 0"), -str8_lit_comp("Numpad 1"), -str8_lit_comp("Numpad 2"), -str8_lit_comp("Numpad 3"), -str8_lit_comp("Numpad 4"), -str8_lit_comp("Numpad 5"), -str8_lit_comp("Numpad 6"), -str8_lit_comp("Numpad 7"), -str8_lit_comp("Numpad 8"), -str8_lit_comp("Numpad 9"), -str8_lit_comp("Left Mouse Button"), -str8_lit_comp("Middle Mouse Button"), -str8_lit_comp("Right Mouse Button"), -}; - -String8 os_g_key_cfg_string_table[143] = -{ -str8_lit_comp("null"), -str8_lit_comp("esc"), -str8_lit_comp("f1"), -str8_lit_comp("f2"), -str8_lit_comp("f3"), -str8_lit_comp("f4"), -str8_lit_comp("f5"), -str8_lit_comp("f6"), -str8_lit_comp("f7"), -str8_lit_comp("f8"), -str8_lit_comp("f9"), -str8_lit_comp("f10"), -str8_lit_comp("f11"), -str8_lit_comp("f12"), -str8_lit_comp("f13"), -str8_lit_comp("f14"), -str8_lit_comp("f15"), -str8_lit_comp("f16"), -str8_lit_comp("f17"), -str8_lit_comp("f18"), -str8_lit_comp("f19"), -str8_lit_comp("f20"), -str8_lit_comp("f21"), -str8_lit_comp("f22"), -str8_lit_comp("f23"), -str8_lit_comp("f24"), -str8_lit_comp("tick"), -str8_lit_comp("0"), -str8_lit_comp("1"), -str8_lit_comp("2"), -str8_lit_comp("3"), -str8_lit_comp("4"), -str8_lit_comp("5"), -str8_lit_comp("6"), -str8_lit_comp("7"), -str8_lit_comp("8"), -str8_lit_comp("9"), -str8_lit_comp("minus"), -str8_lit_comp("equal"), -str8_lit_comp("backspace"), -str8_lit_comp("tab"), -str8_lit_comp("q"), -str8_lit_comp("w"), -str8_lit_comp("e"), -str8_lit_comp("r"), -str8_lit_comp("t"), -str8_lit_comp("y"), -str8_lit_comp("u"), -str8_lit_comp("i"), -str8_lit_comp("o"), -str8_lit_comp("p"), -str8_lit_comp("left_bracket"), -str8_lit_comp("right_bracket"), -str8_lit_comp("backslash"), -str8_lit_comp("caps_lock"), -str8_lit_comp("a"), -str8_lit_comp("s"), -str8_lit_comp("d"), -str8_lit_comp("f"), -str8_lit_comp("g"), -str8_lit_comp("h"), -str8_lit_comp("j"), -str8_lit_comp("k"), -str8_lit_comp("l"), -str8_lit_comp("semicolon"), -str8_lit_comp("quote"), -str8_lit_comp("return"), -str8_lit_comp("shift"), -str8_lit_comp("z"), -str8_lit_comp("x"), -str8_lit_comp("c"), -str8_lit_comp("v"), -str8_lit_comp("b"), -str8_lit_comp("n"), -str8_lit_comp("m"), -str8_lit_comp("comma"), -str8_lit_comp("period"), -str8_lit_comp("slash"), -str8_lit_comp("ctrl"), -str8_lit_comp("alt"), -str8_lit_comp("space"), -str8_lit_comp("menu"), -str8_lit_comp("scroll_lock"), -str8_lit_comp("pause"), -str8_lit_comp("insert"), -str8_lit_comp("home"), -str8_lit_comp("page_up"), -str8_lit_comp("delete"), -str8_lit_comp("end"), -str8_lit_comp("page_down"), -str8_lit_comp("up"), -str8_lit_comp("left"), -str8_lit_comp("down"), -str8_lit_comp("right"), -str8_lit_comp("ex0"), -str8_lit_comp("ex1"), -str8_lit_comp("ex2"), -str8_lit_comp("ex3"), -str8_lit_comp("ex4"), -str8_lit_comp("ex5"), -str8_lit_comp("ex6"), -str8_lit_comp("ex7"), -str8_lit_comp("ex8"), -str8_lit_comp("ex9"), -str8_lit_comp("ex10"), -str8_lit_comp("ex11"), -str8_lit_comp("ex12"), -str8_lit_comp("ex13"), -str8_lit_comp("ex14"), -str8_lit_comp("ex15"), -str8_lit_comp("ex16"), -str8_lit_comp("ex17"), -str8_lit_comp("ex18"), -str8_lit_comp("ex19"), -str8_lit_comp("ex20"), -str8_lit_comp("ex21"), -str8_lit_comp("ex22"), -str8_lit_comp("ex23"), -str8_lit_comp("ex24"), -str8_lit_comp("ex25"), -str8_lit_comp("ex26"), -str8_lit_comp("ex27"), -str8_lit_comp("ex28"), -str8_lit_comp("ex29"), -str8_lit_comp("num_lock"), -str8_lit_comp("numpad_slash"), -str8_lit_comp("numpad_star"), -str8_lit_comp("numpad_minus"), -str8_lit_comp("numpad_plus"), -str8_lit_comp("numpad_period"), -str8_lit_comp("numpad_0"), -str8_lit_comp("numpad_1"), -str8_lit_comp("numpad_2"), -str8_lit_comp("numpad_3"), -str8_lit_comp("numpad_4"), -str8_lit_comp("numpad_5"), -str8_lit_comp("numpad_6"), -str8_lit_comp("numpad_7"), -str8_lit_comp("numpad_8"), -str8_lit_comp("numpad_9"), -str8_lit_comp("left_mouse"), -str8_lit_comp("middle_mouse"), -str8_lit_comp("right_mouse"), -}; - -C_LINKAGE_END - +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +C_LINKAGE_BEGIN +String8 os_g_key_display_string_table[143] = +{ +str8_lit_comp("Invalid Key"), +str8_lit_comp("Escape"), +str8_lit_comp("F1"), +str8_lit_comp("F2"), +str8_lit_comp("F3"), +str8_lit_comp("F4"), +str8_lit_comp("F5"), +str8_lit_comp("F6"), +str8_lit_comp("F7"), +str8_lit_comp("F8"), +str8_lit_comp("F9"), +str8_lit_comp("F10"), +str8_lit_comp("F11"), +str8_lit_comp("F12"), +str8_lit_comp("F13"), +str8_lit_comp("F14"), +str8_lit_comp("F15"), +str8_lit_comp("F16"), +str8_lit_comp("F17"), +str8_lit_comp("F18"), +str8_lit_comp("F19"), +str8_lit_comp("F20"), +str8_lit_comp("F21"), +str8_lit_comp("F22"), +str8_lit_comp("F23"), +str8_lit_comp("F24"), +str8_lit_comp("Tick"), +str8_lit_comp("0"), +str8_lit_comp("1"), +str8_lit_comp("2"), +str8_lit_comp("3"), +str8_lit_comp("4"), +str8_lit_comp("5"), +str8_lit_comp("6"), +str8_lit_comp("7"), +str8_lit_comp("8"), +str8_lit_comp("9"), +str8_lit_comp("Minus"), +str8_lit_comp("Equal"), +str8_lit_comp("Backspace"), +str8_lit_comp("Tab"), +str8_lit_comp("Q"), +str8_lit_comp("W"), +str8_lit_comp("E"), +str8_lit_comp("R"), +str8_lit_comp("T"), +str8_lit_comp("Y"), +str8_lit_comp("U"), +str8_lit_comp("I"), +str8_lit_comp("O"), +str8_lit_comp("P"), +str8_lit_comp("Left Bracket"), +str8_lit_comp("Right Bracket"), +str8_lit_comp("Back Slash"), +str8_lit_comp("Caps Lock"), +str8_lit_comp("A"), +str8_lit_comp("S"), +str8_lit_comp("D"), +str8_lit_comp("F"), +str8_lit_comp("G"), +str8_lit_comp("H"), +str8_lit_comp("J"), +str8_lit_comp("K"), +str8_lit_comp("L"), +str8_lit_comp("Semicolon"), +str8_lit_comp("Quote"), +str8_lit_comp("Return"), +str8_lit_comp("Shift"), +str8_lit_comp("Z"), +str8_lit_comp("X"), +str8_lit_comp("C"), +str8_lit_comp("V"), +str8_lit_comp("B"), +str8_lit_comp("N"), +str8_lit_comp("M"), +str8_lit_comp("Comma"), +str8_lit_comp("Period"), +str8_lit_comp("Slash"), +str8_lit_comp("Ctrl"), +str8_lit_comp("Alt"), +str8_lit_comp("Space"), +str8_lit_comp("Menu"), +str8_lit_comp("Scroll Lock"), +str8_lit_comp("Pause"), +str8_lit_comp("Insert"), +str8_lit_comp("Home"), +str8_lit_comp("Page Up"), +str8_lit_comp("Delete"), +str8_lit_comp("End"), +str8_lit_comp("Page Down"), +str8_lit_comp("Up"), +str8_lit_comp("Left"), +str8_lit_comp("Down"), +str8_lit_comp("Right"), +str8_lit_comp("Ex0"), +str8_lit_comp("Ex1"), +str8_lit_comp("Ex2"), +str8_lit_comp("Ex3"), +str8_lit_comp("Ex4"), +str8_lit_comp("Ex5"), +str8_lit_comp("Ex6"), +str8_lit_comp("Ex7"), +str8_lit_comp("Ex8"), +str8_lit_comp("Ex9"), +str8_lit_comp("Ex10"), +str8_lit_comp("Ex11"), +str8_lit_comp("Ex12"), +str8_lit_comp("Ex13"), +str8_lit_comp("Ex14"), +str8_lit_comp("Ex15"), +str8_lit_comp("Ex16"), +str8_lit_comp("Ex17"), +str8_lit_comp("Ex18"), +str8_lit_comp("Ex19"), +str8_lit_comp("Ex20"), +str8_lit_comp("Ex21"), +str8_lit_comp("Ex22"), +str8_lit_comp("Ex23"), +str8_lit_comp("Ex24"), +str8_lit_comp("Ex25"), +str8_lit_comp("Ex26"), +str8_lit_comp("Ex27"), +str8_lit_comp("Ex28"), +str8_lit_comp("Ex29"), +str8_lit_comp("Num Lock"), +str8_lit_comp("Numpad Slash"), +str8_lit_comp("Numpad Star"), +str8_lit_comp("Numpad Minus"), +str8_lit_comp("Numpad Plus"), +str8_lit_comp("Numpad Period"), +str8_lit_comp("Numpad 0"), +str8_lit_comp("Numpad 1"), +str8_lit_comp("Numpad 2"), +str8_lit_comp("Numpad 3"), +str8_lit_comp("Numpad 4"), +str8_lit_comp("Numpad 5"), +str8_lit_comp("Numpad 6"), +str8_lit_comp("Numpad 7"), +str8_lit_comp("Numpad 8"), +str8_lit_comp("Numpad 9"), +str8_lit_comp("Left Mouse Button"), +str8_lit_comp("Middle Mouse Button"), +str8_lit_comp("Right Mouse Button"), +}; + +String8 os_g_key_cfg_string_table[143] = +{ +str8_lit_comp("null"), +str8_lit_comp("esc"), +str8_lit_comp("f1"), +str8_lit_comp("f2"), +str8_lit_comp("f3"), +str8_lit_comp("f4"), +str8_lit_comp("f5"), +str8_lit_comp("f6"), +str8_lit_comp("f7"), +str8_lit_comp("f8"), +str8_lit_comp("f9"), +str8_lit_comp("f10"), +str8_lit_comp("f11"), +str8_lit_comp("f12"), +str8_lit_comp("f13"), +str8_lit_comp("f14"), +str8_lit_comp("f15"), +str8_lit_comp("f16"), +str8_lit_comp("f17"), +str8_lit_comp("f18"), +str8_lit_comp("f19"), +str8_lit_comp("f20"), +str8_lit_comp("f21"), +str8_lit_comp("f22"), +str8_lit_comp("f23"), +str8_lit_comp("f24"), +str8_lit_comp("tick"), +str8_lit_comp("0"), +str8_lit_comp("1"), +str8_lit_comp("2"), +str8_lit_comp("3"), +str8_lit_comp("4"), +str8_lit_comp("5"), +str8_lit_comp("6"), +str8_lit_comp("7"), +str8_lit_comp("8"), +str8_lit_comp("9"), +str8_lit_comp("minus"), +str8_lit_comp("equal"), +str8_lit_comp("backspace"), +str8_lit_comp("tab"), +str8_lit_comp("q"), +str8_lit_comp("w"), +str8_lit_comp("e"), +str8_lit_comp("r"), +str8_lit_comp("t"), +str8_lit_comp("y"), +str8_lit_comp("u"), +str8_lit_comp("i"), +str8_lit_comp("o"), +str8_lit_comp("p"), +str8_lit_comp("left_bracket"), +str8_lit_comp("right_bracket"), +str8_lit_comp("backslash"), +str8_lit_comp("caps_lock"), +str8_lit_comp("a"), +str8_lit_comp("s"), +str8_lit_comp("d"), +str8_lit_comp("f"), +str8_lit_comp("g"), +str8_lit_comp("h"), +str8_lit_comp("j"), +str8_lit_comp("k"), +str8_lit_comp("l"), +str8_lit_comp("semicolon"), +str8_lit_comp("quote"), +str8_lit_comp("return"), +str8_lit_comp("shift"), +str8_lit_comp("z"), +str8_lit_comp("x"), +str8_lit_comp("c"), +str8_lit_comp("v"), +str8_lit_comp("b"), +str8_lit_comp("n"), +str8_lit_comp("m"), +str8_lit_comp("comma"), +str8_lit_comp("period"), +str8_lit_comp("slash"), +str8_lit_comp("ctrl"), +str8_lit_comp("alt"), +str8_lit_comp("space"), +str8_lit_comp("menu"), +str8_lit_comp("scroll_lock"), +str8_lit_comp("pause"), +str8_lit_comp("insert"), +str8_lit_comp("home"), +str8_lit_comp("page_up"), +str8_lit_comp("delete"), +str8_lit_comp("end"), +str8_lit_comp("page_down"), +str8_lit_comp("up"), +str8_lit_comp("left"), +str8_lit_comp("down"), +str8_lit_comp("right"), +str8_lit_comp("ex0"), +str8_lit_comp("ex1"), +str8_lit_comp("ex2"), +str8_lit_comp("ex3"), +str8_lit_comp("ex4"), +str8_lit_comp("ex5"), +str8_lit_comp("ex6"), +str8_lit_comp("ex7"), +str8_lit_comp("ex8"), +str8_lit_comp("ex9"), +str8_lit_comp("ex10"), +str8_lit_comp("ex11"), +str8_lit_comp("ex12"), +str8_lit_comp("ex13"), +str8_lit_comp("ex14"), +str8_lit_comp("ex15"), +str8_lit_comp("ex16"), +str8_lit_comp("ex17"), +str8_lit_comp("ex18"), +str8_lit_comp("ex19"), +str8_lit_comp("ex20"), +str8_lit_comp("ex21"), +str8_lit_comp("ex22"), +str8_lit_comp("ex23"), +str8_lit_comp("ex24"), +str8_lit_comp("ex25"), +str8_lit_comp("ex26"), +str8_lit_comp("ex27"), +str8_lit_comp("ex28"), +str8_lit_comp("ex29"), +str8_lit_comp("num_lock"), +str8_lit_comp("numpad_slash"), +str8_lit_comp("numpad_star"), +str8_lit_comp("numpad_minus"), +str8_lit_comp("numpad_plus"), +str8_lit_comp("numpad_period"), +str8_lit_comp("numpad_0"), +str8_lit_comp("numpad_1"), +str8_lit_comp("numpad_2"), +str8_lit_comp("numpad_3"), +str8_lit_comp("numpad_4"), +str8_lit_comp("numpad_5"), +str8_lit_comp("numpad_6"), +str8_lit_comp("numpad_7"), +str8_lit_comp("numpad_8"), +str8_lit_comp("numpad_9"), +str8_lit_comp("left_mouse"), +str8_lit_comp("middle_mouse"), +str8_lit_comp("right_mouse"), +}; + +C_LINKAGE_END + diff --git a/src/os/gfx/generated/os_gfx.meta.h b/src/window_manager/generated/window_manager.meta.h similarity index 90% rename from src/os/gfx/generated/os_gfx.meta.h rename to src/window_manager/generated/window_manager.meta.h index 72ccea0c..b81d453f 100644 --- a/src/os/gfx/generated/os_gfx.meta.h +++ b/src/window_manager/generated/window_manager.meta.h @@ -1,163 +1,163 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//- GENERATED CODE - -#ifndef OS_GFX_META_H -#define OS_GFX_META_H - -typedef enum OS_Key -{ -OS_Key_Null, -OS_Key_Esc, -OS_Key_F1, -OS_Key_F2, -OS_Key_F3, -OS_Key_F4, -OS_Key_F5, -OS_Key_F6, -OS_Key_F7, -OS_Key_F8, -OS_Key_F9, -OS_Key_F10, -OS_Key_F11, -OS_Key_F12, -OS_Key_F13, -OS_Key_F14, -OS_Key_F15, -OS_Key_F16, -OS_Key_F17, -OS_Key_F18, -OS_Key_F19, -OS_Key_F20, -OS_Key_F21, -OS_Key_F22, -OS_Key_F23, -OS_Key_F24, -OS_Key_Tick, -OS_Key_0, -OS_Key_1, -OS_Key_2, -OS_Key_3, -OS_Key_4, -OS_Key_5, -OS_Key_6, -OS_Key_7, -OS_Key_8, -OS_Key_9, -OS_Key_Minus, -OS_Key_Equal, -OS_Key_Backspace, -OS_Key_Tab, -OS_Key_Q, -OS_Key_W, -OS_Key_E, -OS_Key_R, -OS_Key_T, -OS_Key_Y, -OS_Key_U, -OS_Key_I, -OS_Key_O, -OS_Key_P, -OS_Key_LeftBracket, -OS_Key_RightBracket, -OS_Key_BackSlash, -OS_Key_CapsLock, -OS_Key_A, -OS_Key_S, -OS_Key_D, -OS_Key_F, -OS_Key_G, -OS_Key_H, -OS_Key_J, -OS_Key_K, -OS_Key_L, -OS_Key_Semicolon, -OS_Key_Quote, -OS_Key_Return, -OS_Key_Shift, -OS_Key_Z, -OS_Key_X, -OS_Key_C, -OS_Key_V, -OS_Key_B, -OS_Key_N, -OS_Key_M, -OS_Key_Comma, -OS_Key_Period, -OS_Key_Slash, -OS_Key_Ctrl, -OS_Key_Alt, -OS_Key_Space, -OS_Key_Menu, -OS_Key_ScrollLock, -OS_Key_Pause, -OS_Key_Insert, -OS_Key_Home, -OS_Key_PageUp, -OS_Key_Delete, -OS_Key_End, -OS_Key_PageDown, -OS_Key_Up, -OS_Key_Left, -OS_Key_Down, -OS_Key_Right, -OS_Key_Ex0, -OS_Key_Ex1, -OS_Key_Ex2, -OS_Key_Ex3, -OS_Key_Ex4, -OS_Key_Ex5, -OS_Key_Ex6, -OS_Key_Ex7, -OS_Key_Ex8, -OS_Key_Ex9, -OS_Key_Ex10, -OS_Key_Ex11, -OS_Key_Ex12, -OS_Key_Ex13, -OS_Key_Ex14, -OS_Key_Ex15, -OS_Key_Ex16, -OS_Key_Ex17, -OS_Key_Ex18, -OS_Key_Ex19, -OS_Key_Ex20, -OS_Key_Ex21, -OS_Key_Ex22, -OS_Key_Ex23, -OS_Key_Ex24, -OS_Key_Ex25, -OS_Key_Ex26, -OS_Key_Ex27, -OS_Key_Ex28, -OS_Key_Ex29, -OS_Key_NumLock, -OS_Key_NumSlash, -OS_Key_NumStar, -OS_Key_NumMinus, -OS_Key_NumPlus, -OS_Key_NumPeriod, -OS_Key_Num0, -OS_Key_Num1, -OS_Key_Num2, -OS_Key_Num3, -OS_Key_Num4, -OS_Key_Num5, -OS_Key_Num6, -OS_Key_Num7, -OS_Key_Num8, -OS_Key_Num9, -OS_Key_LeftMouseButton, -OS_Key_MiddleMouseButton, -OS_Key_RightMouseButton, -OS_Key_COUNT, -} OS_Key; - -C_LINKAGE_BEGIN -extern String8 os_g_key_display_string_table[143]; -extern String8 os_g_key_cfg_string_table[143]; - -C_LINKAGE_END - -#endif // OS_GFX_META_H +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +#ifndef WINDOW_MANAGER_META_H +#define WINDOW_MANAGER_META_H + +typedef enum OS_Key +{ +OS_Key_Null, +OS_Key_Esc, +OS_Key_F1, +OS_Key_F2, +OS_Key_F3, +OS_Key_F4, +OS_Key_F5, +OS_Key_F6, +OS_Key_F7, +OS_Key_F8, +OS_Key_F9, +OS_Key_F10, +OS_Key_F11, +OS_Key_F12, +OS_Key_F13, +OS_Key_F14, +OS_Key_F15, +OS_Key_F16, +OS_Key_F17, +OS_Key_F18, +OS_Key_F19, +OS_Key_F20, +OS_Key_F21, +OS_Key_F22, +OS_Key_F23, +OS_Key_F24, +OS_Key_Tick, +OS_Key_0, +OS_Key_1, +OS_Key_2, +OS_Key_3, +OS_Key_4, +OS_Key_5, +OS_Key_6, +OS_Key_7, +OS_Key_8, +OS_Key_9, +OS_Key_Minus, +OS_Key_Equal, +OS_Key_Backspace, +OS_Key_Tab, +OS_Key_Q, +OS_Key_W, +OS_Key_E, +OS_Key_R, +OS_Key_T, +OS_Key_Y, +OS_Key_U, +OS_Key_I, +OS_Key_O, +OS_Key_P, +OS_Key_LeftBracket, +OS_Key_RightBracket, +OS_Key_BackSlash, +OS_Key_CapsLock, +OS_Key_A, +OS_Key_S, +OS_Key_D, +OS_Key_F, +OS_Key_G, +OS_Key_H, +OS_Key_J, +OS_Key_K, +OS_Key_L, +OS_Key_Semicolon, +OS_Key_Quote, +OS_Key_Return, +OS_Key_Shift, +OS_Key_Z, +OS_Key_X, +OS_Key_C, +OS_Key_V, +OS_Key_B, +OS_Key_N, +OS_Key_M, +OS_Key_Comma, +OS_Key_Period, +OS_Key_Slash, +OS_Key_Ctrl, +OS_Key_Alt, +OS_Key_Space, +OS_Key_Menu, +OS_Key_ScrollLock, +OS_Key_Pause, +OS_Key_Insert, +OS_Key_Home, +OS_Key_PageUp, +OS_Key_Delete, +OS_Key_End, +OS_Key_PageDown, +OS_Key_Up, +OS_Key_Left, +OS_Key_Down, +OS_Key_Right, +OS_Key_Ex0, +OS_Key_Ex1, +OS_Key_Ex2, +OS_Key_Ex3, +OS_Key_Ex4, +OS_Key_Ex5, +OS_Key_Ex6, +OS_Key_Ex7, +OS_Key_Ex8, +OS_Key_Ex9, +OS_Key_Ex10, +OS_Key_Ex11, +OS_Key_Ex12, +OS_Key_Ex13, +OS_Key_Ex14, +OS_Key_Ex15, +OS_Key_Ex16, +OS_Key_Ex17, +OS_Key_Ex18, +OS_Key_Ex19, +OS_Key_Ex20, +OS_Key_Ex21, +OS_Key_Ex22, +OS_Key_Ex23, +OS_Key_Ex24, +OS_Key_Ex25, +OS_Key_Ex26, +OS_Key_Ex27, +OS_Key_Ex28, +OS_Key_Ex29, +OS_Key_NumLock, +OS_Key_NumSlash, +OS_Key_NumStar, +OS_Key_NumMinus, +OS_Key_NumPlus, +OS_Key_NumPeriod, +OS_Key_Num0, +OS_Key_Num1, +OS_Key_Num2, +OS_Key_Num3, +OS_Key_Num4, +OS_Key_Num5, +OS_Key_Num6, +OS_Key_Num7, +OS_Key_Num8, +OS_Key_Num9, +OS_Key_LeftMouseButton, +OS_Key_MiddleMouseButton, +OS_Key_RightMouseButton, +OS_Key_COUNT, +} OS_Key; + +C_LINKAGE_BEGIN +extern String8 os_g_key_display_string_table[143]; +extern String8 os_g_key_cfg_string_table[143]; + +C_LINKAGE_END + +#endif // WINDOW_MANAGER_META_H diff --git a/src/os/gfx/os_gfx.c b/src/window_manager/window_manager.c similarity index 96% rename from src/os/gfx/os_gfx.c rename to src/window_manager/window_manager.c index 29f8c544..1f59d2a5 100644 --- a/src/os/gfx/os_gfx.c +++ b/src/window_manager/window_manager.c @@ -1,300 +1,300 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Generated Code - -#include "generated/os_gfx.meta.c" - -//////////////////////////////// -//~ rjf: Handle Type Helpers - -internal OS_Window -os_window_zero(void) -{ - OS_Window result = {0}; - return result; -} - -internal B32 -os_window_match(OS_Window a, OS_Window b) -{ - B32 result = MemoryMatchStruct(&a, &b); - return result; -} - -internal B32 -os_monitor_match(OS_Monitor a, OS_Monitor b) -{ - B32 result = MemoryMatchStruct(&a, &b); - return result; -} - -//////////////////////////////// -//~ rjf: Event Functions (Helpers, Implemented Once) - -internal String8 -os_string_from_event_kind(OS_EventKind kind) -{ - String8 result = {0}; - switch(kind) - { - case OS_EventKind_Null: - case OS_EventKind_COUNT: - {}break; - case OS_EventKind_Press: {result = str8_lit("Press");}break; - case OS_EventKind_Release: {result = str8_lit("Release");}break; - case OS_EventKind_MouseMove: {result = str8_lit("MouseMove");}break; - case OS_EventKind_Text: {result = str8_lit("Text");}break; - case OS_EventKind_Scroll: {result = str8_lit("Scroll");}break; - case OS_EventKind_WindowLoseFocus: {result = str8_lit("WindowLoseFocus");}break; - case OS_EventKind_WindowClose: {result = str8_lit("WindowClose");}break; - case OS_EventKind_FileDrop: {result = str8_lit("FileDrop");}break; - case OS_EventKind_Wakeup: {result = str8_lit("Wakeup");}break; - } - return result; -} - -internal String8List -os_string_list_from_modifiers(Arena *arena, OS_Modifiers modifiers) -{ - String8List result = {0}; - if(modifiers & OS_Modifier_Ctrl) { str8_list_push(arena, &result, str8_lit("Ctrl")); } - if(modifiers & OS_Modifier_Shift) { str8_list_push(arena, &result, str8_lit("Shift")); } - if(modifiers & OS_Modifier_Alt) { str8_list_push(arena, &result, str8_lit("Alt")); } - return result; -} - -internal String8 -os_string_from_modifiers_key(Arena *arena, OS_Modifiers modifiers, OS_Key key) -{ - String8 result = {0}; - if(key != OS_Key_Null) - { - Temp scratch = scratch_begin(&arena, 1); - String8List mods = os_string_list_from_modifiers(scratch.arena, modifiers); - String8 key_string = os_g_key_display_string_table[key]; - str8_list_push(scratch.arena, &mods, key_string); - StringJoin join = {0}; - join.sep = str8_lit(" + "); - result = str8_list_join(arena, &mods, &join); - scratch_end(scratch); - } - return result; -} - -internal U32 -os_codepoint_from_modifiers_and_key(OS_Modifiers modifiers, OS_Key key) -{ - U32 result = 0; - - // rjf: special-case map - local_persist read_only struct {U32 character; OS_Key key; OS_Modifiers modifiers;} map[] = - { - {'!', OS_Key_1, OS_Modifier_Shift}, - {'@', OS_Key_2, OS_Modifier_Shift}, - {'#', OS_Key_3, OS_Modifier_Shift}, - {'$', OS_Key_4, OS_Modifier_Shift}, - {'%', OS_Key_5, OS_Modifier_Shift}, - {'^', OS_Key_6, OS_Modifier_Shift}, - {'&', OS_Key_7, OS_Modifier_Shift}, - {'*', OS_Key_8, OS_Modifier_Shift}, - {'(', OS_Key_9, OS_Modifier_Shift}, - {')', OS_Key_0, OS_Modifier_Shift}, - {'_', OS_Key_Minus, OS_Modifier_Shift}, - {'_', OS_Key_Minus, OS_Modifier_Shift}, - {'-', OS_Key_Minus, 0}, - {'=', OS_Key_Equal, 0}, - {'+', OS_Key_Equal, OS_Modifier_Shift}, - {'`', OS_Key_Tick, 0}, - {'~', OS_Key_Tick, OS_Modifier_Shift}, - {'[', OS_Key_LeftBracket, 0}, - {']', OS_Key_RightBracket, 0}, - {'{', OS_Key_LeftBracket, OS_Modifier_Shift}, - {'}', OS_Key_RightBracket, OS_Modifier_Shift}, - {'\\', OS_Key_BackSlash, 0}, - {'|', OS_Key_BackSlash, OS_Modifier_Shift}, - {';', OS_Key_Semicolon, 0}, - {':', OS_Key_Semicolon, OS_Modifier_Shift}, - {'\'', OS_Key_Quote, 0}, - {'"', OS_Key_Quote, OS_Modifier_Shift}, - {'.', OS_Key_Period, 0}, - {',', OS_Key_Comma, 0}, - {'<', OS_Key_Period, OS_Modifier_Shift}, - {'>', OS_Key_Comma, OS_Modifier_Shift}, - {'/', OS_Key_Slash, 0}, - {'?', OS_Key_Slash, OS_Modifier_Shift}, - {'a', OS_Key_A, 0}, - {'b', OS_Key_B, 0}, - {'c', OS_Key_C, 0}, - {'d', OS_Key_D, 0}, - {'e', OS_Key_E, 0}, - {'f', OS_Key_F, 0}, - {'g', OS_Key_G, 0}, - {'h', OS_Key_H, 0}, - {'i', OS_Key_I, 0}, - {'j', OS_Key_J, 0}, - {'k', OS_Key_K, 0}, - {'l', OS_Key_L, 0}, - {'m', OS_Key_M, 0}, - {'n', OS_Key_N, 0}, - {'o', OS_Key_O, 0}, - {'p', OS_Key_P, 0}, - {'q', OS_Key_Q, 0}, - {'r', OS_Key_R, 0}, - {'s', OS_Key_S, 0}, - {'t', OS_Key_T, 0}, - {'u', OS_Key_U, 0}, - {'v', OS_Key_V, 0}, - {'w', OS_Key_W, 0}, - {'x', OS_Key_X, 0}, - {'y', OS_Key_Y, 0}, - {'z', OS_Key_Z, 0}, - {'A', OS_Key_A, OS_Modifier_Shift}, - {'B', OS_Key_B, OS_Modifier_Shift}, - {'C', OS_Key_C, OS_Modifier_Shift}, - {'D', OS_Key_D, OS_Modifier_Shift}, - {'E', OS_Key_E, OS_Modifier_Shift}, - {'F', OS_Key_F, OS_Modifier_Shift}, - {'G', OS_Key_G, OS_Modifier_Shift}, - {'H', OS_Key_H, OS_Modifier_Shift}, - {'I', OS_Key_I, OS_Modifier_Shift}, - {'J', OS_Key_J, OS_Modifier_Shift}, - {'K', OS_Key_K, OS_Modifier_Shift}, - {'L', OS_Key_L, OS_Modifier_Shift}, - {'M', OS_Key_M, OS_Modifier_Shift}, - {'N', OS_Key_N, OS_Modifier_Shift}, - {'O', OS_Key_O, OS_Modifier_Shift}, - {'P', OS_Key_P, OS_Modifier_Shift}, - {'Q', OS_Key_Q, OS_Modifier_Shift}, - {'R', OS_Key_R, OS_Modifier_Shift}, - {'S', OS_Key_S, OS_Modifier_Shift}, - {'T', OS_Key_T, OS_Modifier_Shift}, - {'U', OS_Key_U, OS_Modifier_Shift}, - {'V', OS_Key_V, OS_Modifier_Shift}, - {'W', OS_Key_W, OS_Modifier_Shift}, - {'X', OS_Key_X, OS_Modifier_Shift}, - {'Y', OS_Key_Y, OS_Modifier_Shift}, - {'Z', OS_Key_Z, OS_Modifier_Shift}, - {' ', OS_Key_Space, 0}, - }; - - // rjf: check numeric - if(OS_Key_0 <= key && key <= OS_Key_9) - { - result = '0' + (key - OS_Key_0); - } - - // rjf: check special-case map - for(U64 idx = 0; idx < ArrayCount(map); idx += 1) - { - if(map[idx].key == key && map[idx].modifiers == modifiers) - { - result = map[idx].character; - break; - } - } - - return result; -} - -internal void -os_eat_event(OS_EventList *events, OS_Event *event) -{ - DLLRemove(events->first, events->last, event); - events->count -= 1; -} - -internal B32 -os_key_press(OS_EventList *events, OS_Window window, OS_Modifiers modifiers, OS_Key key) -{ - B32 result = 0; - for(OS_Event *event = events->first; event != 0; event = event->next) - { - if((os_window_match(event->window, window) || os_window_match(window, os_window_zero())) && - event->kind == OS_EventKind_Press && event->key == key && event->modifiers == modifiers) - { - result = 1; - os_eat_event(events, event); - break; - } - } - return result; -} - -internal B32 -os_key_release(OS_EventList *events, OS_Window window, OS_Modifiers modifiers, OS_Key key) -{ - B32 result = 0; - for(OS_Event *event = events->first; event != 0; event = event->next) - { - if((os_window_match(event->window, window) || os_window_match(window, os_window_zero())) && - event->kind == OS_EventKind_Release && event->key == key && event->modifiers == modifiers) - { - result = 1; - os_eat_event(events, event); - break; - } - } - return result; -} - -internal B32 -os_text(OS_EventList *events, OS_Window window, U32 character) -{ - B32 result = 0; - for(OS_Event *event = events->first; event != 0; event = event->next) - { - if((os_window_match(event->window, window) || os_window_match(window, os_window_zero())) && - event->kind == OS_EventKind_Text && event->character == character) - { - result = 1; - os_eat_event(events, event); - break; - } - } - return result; -} - -internal OS_EventList -os_event_list_copy(Arena *arena, OS_EventList *src) -{ - OS_EventList dst = {0}; - for(OS_Event *s = src->first; s != 0; s = s->next) - { - OS_Event *d = push_array(arena, OS_Event, 1); - MemoryCopyStruct(d, s); - d->strings = str8_list_copy(arena, &s->strings); - DLLPushBack(dst.first, dst.last, d); - dst.count += 1; - } - return dst; -} - -internal void -os_event_list_concat_in_place(OS_EventList *dst, OS_EventList *to_push) -{ - if(dst->last && to_push->first) - { - dst->last->next = to_push->first; - to_push->first->prev = dst->last; - dst->last = to_push->last; - dst->count += to_push->count; - } - else if(!dst->last && to_push->first) - { - MemoryCopyStruct(dst, to_push); - } - MemoryZeroStruct(to_push); -} - -internal OS_Event * -os_event_list_push_new(Arena *arena, OS_EventList *evts, OS_EventKind kind) -{ - OS_Event *evt = push_array(arena, OS_Event, 1); - DLLPushBack(evts->first, evts->last, evt); - evts->count += 1; - evt->timestamp_us = now_time_us(); - evt->kind = kind; - return evt; -} +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Generated Code + +#include "window_manager/generated/window_manager.meta.c" + +//////////////////////////////// +//~ rjf: Handle Type Helpers + +internal OS_Window +os_window_zero(void) +{ + OS_Window result = {0}; + return result; +} + +internal B32 +os_window_match(OS_Window a, OS_Window b) +{ + B32 result = MemoryMatchStruct(&a, &b); + return result; +} + +internal B32 +os_monitor_match(OS_Monitor a, OS_Monitor b) +{ + B32 result = MemoryMatchStruct(&a, &b); + return result; +} + +//////////////////////////////// +//~ rjf: Event Functions (Helpers, Implemented Once) + +internal String8 +os_string_from_event_kind(OS_EventKind kind) +{ + String8 result = {0}; + switch(kind) + { + case OS_EventKind_Null: + case OS_EventKind_COUNT: + {}break; + case OS_EventKind_Press: {result = str8_lit("Press");}break; + case OS_EventKind_Release: {result = str8_lit("Release");}break; + case OS_EventKind_MouseMove: {result = str8_lit("MouseMove");}break; + case OS_EventKind_Text: {result = str8_lit("Text");}break; + case OS_EventKind_Scroll: {result = str8_lit("Scroll");}break; + case OS_EventKind_WindowLoseFocus: {result = str8_lit("WindowLoseFocus");}break; + case OS_EventKind_WindowClose: {result = str8_lit("WindowClose");}break; + case OS_EventKind_FileDrop: {result = str8_lit("FileDrop");}break; + case OS_EventKind_Wakeup: {result = str8_lit("Wakeup");}break; + } + return result; +} + +internal String8List +os_string_list_from_modifiers(Arena *arena, OS_Modifiers modifiers) +{ + String8List result = {0}; + if(modifiers & OS_Modifier_Ctrl) { str8_list_push(arena, &result, str8_lit("Ctrl")); } + if(modifiers & OS_Modifier_Shift) { str8_list_push(arena, &result, str8_lit("Shift")); } + if(modifiers & OS_Modifier_Alt) { str8_list_push(arena, &result, str8_lit("Alt")); } + return result; +} + +internal String8 +os_string_from_modifiers_key(Arena *arena, OS_Modifiers modifiers, OS_Key key) +{ + String8 result = {0}; + if(key != OS_Key_Null) + { + Temp scratch = scratch_begin(&arena, 1); + String8List mods = os_string_list_from_modifiers(scratch.arena, modifiers); + String8 key_string = os_g_key_display_string_table[key]; + str8_list_push(scratch.arena, &mods, key_string); + StringJoin join = {0}; + join.sep = str8_lit(" + "); + result = str8_list_join(arena, &mods, &join); + scratch_end(scratch); + } + return result; +} + +internal U32 +os_codepoint_from_modifiers_and_key(OS_Modifiers modifiers, OS_Key key) +{ + U32 result = 0; + + // rjf: special-case map + local_persist read_only struct {U32 character; OS_Key key; OS_Modifiers modifiers;} map[] = + { + {'!', OS_Key_1, OS_Modifier_Shift}, + {'@', OS_Key_2, OS_Modifier_Shift}, + {'#', OS_Key_3, OS_Modifier_Shift}, + {'$', OS_Key_4, OS_Modifier_Shift}, + {'%', OS_Key_5, OS_Modifier_Shift}, + {'^', OS_Key_6, OS_Modifier_Shift}, + {'&', OS_Key_7, OS_Modifier_Shift}, + {'*', OS_Key_8, OS_Modifier_Shift}, + {'(', OS_Key_9, OS_Modifier_Shift}, + {')', OS_Key_0, OS_Modifier_Shift}, + {'_', OS_Key_Minus, OS_Modifier_Shift}, + {'_', OS_Key_Minus, OS_Modifier_Shift}, + {'-', OS_Key_Minus, 0}, + {'=', OS_Key_Equal, 0}, + {'+', OS_Key_Equal, OS_Modifier_Shift}, + {'`', OS_Key_Tick, 0}, + {'~', OS_Key_Tick, OS_Modifier_Shift}, + {'[', OS_Key_LeftBracket, 0}, + {']', OS_Key_RightBracket, 0}, + {'{', OS_Key_LeftBracket, OS_Modifier_Shift}, + {'}', OS_Key_RightBracket, OS_Modifier_Shift}, + {'\\', OS_Key_BackSlash, 0}, + {'|', OS_Key_BackSlash, OS_Modifier_Shift}, + {';', OS_Key_Semicolon, 0}, + {':', OS_Key_Semicolon, OS_Modifier_Shift}, + {'\'', OS_Key_Quote, 0}, + {'"', OS_Key_Quote, OS_Modifier_Shift}, + {'.', OS_Key_Period, 0}, + {',', OS_Key_Comma, 0}, + {'<', OS_Key_Period, OS_Modifier_Shift}, + {'>', OS_Key_Comma, OS_Modifier_Shift}, + {'/', OS_Key_Slash, 0}, + {'?', OS_Key_Slash, OS_Modifier_Shift}, + {'a', OS_Key_A, 0}, + {'b', OS_Key_B, 0}, + {'c', OS_Key_C, 0}, + {'d', OS_Key_D, 0}, + {'e', OS_Key_E, 0}, + {'f', OS_Key_F, 0}, + {'g', OS_Key_G, 0}, + {'h', OS_Key_H, 0}, + {'i', OS_Key_I, 0}, + {'j', OS_Key_J, 0}, + {'k', OS_Key_K, 0}, + {'l', OS_Key_L, 0}, + {'m', OS_Key_M, 0}, + {'n', OS_Key_N, 0}, + {'o', OS_Key_O, 0}, + {'p', OS_Key_P, 0}, + {'q', OS_Key_Q, 0}, + {'r', OS_Key_R, 0}, + {'s', OS_Key_S, 0}, + {'t', OS_Key_T, 0}, + {'u', OS_Key_U, 0}, + {'v', OS_Key_V, 0}, + {'w', OS_Key_W, 0}, + {'x', OS_Key_X, 0}, + {'y', OS_Key_Y, 0}, + {'z', OS_Key_Z, 0}, + {'A', OS_Key_A, OS_Modifier_Shift}, + {'B', OS_Key_B, OS_Modifier_Shift}, + {'C', OS_Key_C, OS_Modifier_Shift}, + {'D', OS_Key_D, OS_Modifier_Shift}, + {'E', OS_Key_E, OS_Modifier_Shift}, + {'F', OS_Key_F, OS_Modifier_Shift}, + {'G', OS_Key_G, OS_Modifier_Shift}, + {'H', OS_Key_H, OS_Modifier_Shift}, + {'I', OS_Key_I, OS_Modifier_Shift}, + {'J', OS_Key_J, OS_Modifier_Shift}, + {'K', OS_Key_K, OS_Modifier_Shift}, + {'L', OS_Key_L, OS_Modifier_Shift}, + {'M', OS_Key_M, OS_Modifier_Shift}, + {'N', OS_Key_N, OS_Modifier_Shift}, + {'O', OS_Key_O, OS_Modifier_Shift}, + {'P', OS_Key_P, OS_Modifier_Shift}, + {'Q', OS_Key_Q, OS_Modifier_Shift}, + {'R', OS_Key_R, OS_Modifier_Shift}, + {'S', OS_Key_S, OS_Modifier_Shift}, + {'T', OS_Key_T, OS_Modifier_Shift}, + {'U', OS_Key_U, OS_Modifier_Shift}, + {'V', OS_Key_V, OS_Modifier_Shift}, + {'W', OS_Key_W, OS_Modifier_Shift}, + {'X', OS_Key_X, OS_Modifier_Shift}, + {'Y', OS_Key_Y, OS_Modifier_Shift}, + {'Z', OS_Key_Z, OS_Modifier_Shift}, + {' ', OS_Key_Space, 0}, + }; + + // rjf: check numeric + if(OS_Key_0 <= key && key <= OS_Key_9) + { + result = '0' + (key - OS_Key_0); + } + + // rjf: check special-case map + for(U64 idx = 0; idx < ArrayCount(map); idx += 1) + { + if(map[idx].key == key && map[idx].modifiers == modifiers) + { + result = map[idx].character; + break; + } + } + + return result; +} + +internal void +os_eat_event(OS_EventList *events, OS_Event *event) +{ + DLLRemove(events->first, events->last, event); + events->count -= 1; +} + +internal B32 +os_key_press(OS_EventList *events, OS_Window window, OS_Modifiers modifiers, OS_Key key) +{ + B32 result = 0; + for(OS_Event *event = events->first; event != 0; event = event->next) + { + if((os_window_match(event->window, window) || os_window_match(window, os_window_zero())) && + event->kind == OS_EventKind_Press && event->key == key && event->modifiers == modifiers) + { + result = 1; + os_eat_event(events, event); + break; + } + } + return result; +} + +internal B32 +os_key_release(OS_EventList *events, OS_Window window, OS_Modifiers modifiers, OS_Key key) +{ + B32 result = 0; + for(OS_Event *event = events->first; event != 0; event = event->next) + { + if((os_window_match(event->window, window) || os_window_match(window, os_window_zero())) && + event->kind == OS_EventKind_Release && event->key == key && event->modifiers == modifiers) + { + result = 1; + os_eat_event(events, event); + break; + } + } + return result; +} + +internal B32 +os_text(OS_EventList *events, OS_Window window, U32 character) +{ + B32 result = 0; + for(OS_Event *event = events->first; event != 0; event = event->next) + { + if((os_window_match(event->window, window) || os_window_match(window, os_window_zero())) && + event->kind == OS_EventKind_Text && event->character == character) + { + result = 1; + os_eat_event(events, event); + break; + } + } + return result; +} + +internal OS_EventList +os_event_list_copy(Arena *arena, OS_EventList *src) +{ + OS_EventList dst = {0}; + for(OS_Event *s = src->first; s != 0; s = s->next) + { + OS_Event *d = push_array(arena, OS_Event, 1); + MemoryCopyStruct(d, s); + d->strings = str8_list_copy(arena, &s->strings); + DLLPushBack(dst.first, dst.last, d); + dst.count += 1; + } + return dst; +} + +internal void +os_event_list_concat_in_place(OS_EventList *dst, OS_EventList *to_push) +{ + if(dst->last && to_push->first) + { + dst->last->next = to_push->first; + to_push->first->prev = dst->last; + dst->last = to_push->last; + dst->count += to_push->count; + } + else if(!dst->last && to_push->first) + { + MemoryCopyStruct(dst, to_push); + } + MemoryZeroStruct(to_push); +} + +internal OS_Event * +os_event_list_push_new(Arena *arena, OS_EventList *evts, OS_EventKind kind) +{ + OS_Event *evt = push_array(arena, OS_Event, 1); + DLLPushBack(evts->first, evts->last, evt); + evts->count += 1; + evt->timestamp_us = now_time_us(); + evt->kind = kind; + return evt; +} diff --git a/src/os/gfx/os_gfx.h b/src/window_manager/window_manager.h similarity index 96% rename from src/os/gfx/os_gfx.h rename to src/window_manager/window_manager.h index bc4a7d16..a64ca87f 100644 --- a/src/os/gfx/os_gfx.h +++ b/src/window_manager/window_manager.h @@ -1,246 +1,246 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_GFX_H -#define OS_GFX_H - -//////////////////////////////// -//~ rjf: Graphics System Info - -typedef struct OS_GfxInfo OS_GfxInfo; -struct OS_GfxInfo -{ - F32 double_click_time; - F32 caret_blink_time; - F32 default_refresh_rate; -}; - -//////////////////////////////// -//~ rjf: Window Types - -typedef U32 OS_WindowFlags; -enum -{ - OS_WindowFlag_CustomBorder = (1<<0), - OS_WindowFlag_UseDefaultPosition = (1<<1), -}; - -typedef struct OS_Window OS_Window; -struct OS_Window -{ - U64 u64[1]; -}; - -//////////////////////////////// -//~ rjf: External Window Types - -typedef struct OS_ExternalWindow OS_ExternalWindow; -struct OS_ExternalWindow -{ - U64 u64[1]; -}; - -//////////////////////////////// -//~ rjf: Monitor Types - -typedef struct OS_Monitor OS_Monitor; -struct OS_Monitor -{ - U64 u64[1]; -}; - -typedef struct OS_MonitorArray OS_MonitorArray; -struct OS_MonitorArray -{ - OS_Monitor *v; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Cursor Types - -typedef enum OS_Cursor -{ - OS_Cursor_Pointer, - OS_Cursor_IBar, - OS_Cursor_LeftRight, - OS_Cursor_UpDown, - OS_Cursor_DownRight, - OS_Cursor_UpRight, - OS_Cursor_UpDownLeftRight, - OS_Cursor_HandPoint, - OS_Cursor_Disabled, - OS_Cursor_COUNT, -} -OS_Cursor; - -//////////////////////////////// -//~ rjf: Generated Code - -#include "os/gfx/generated/os_gfx.meta.h" - -//////////////////////////////// -//~ rjf: Event Types - -typedef enum OS_EventKind -{ - OS_EventKind_Null, - OS_EventKind_Press, - OS_EventKind_Release, - OS_EventKind_MouseMove, - OS_EventKind_Text, - OS_EventKind_Scroll, - OS_EventKind_WindowLoseFocus, - OS_EventKind_WindowClose, - OS_EventKind_FileDrop, - OS_EventKind_Wakeup, - OS_EventKind_COUNT -} -OS_EventKind; - -typedef U32 OS_Modifiers; -enum -{ - OS_Modifier_Ctrl = (1<<0), - OS_Modifier_Shift = (1<<1), - OS_Modifier_Alt = (1<<2), -}; - -typedef struct OS_Event OS_Event; -struct OS_Event -{ - OS_Event *next; - OS_Event *prev; - U64 timestamp_us; - OS_Window window; - OS_EventKind kind; - OS_Modifiers modifiers; - OS_Key key; - B32 is_repeat; - B32 right_sided; - U32 character; - U32 repeat_count; - Vec2F32 pos; - Vec2F32 delta; - String8List strings; -}; - -typedef struct OS_EventList OS_EventList; -struct OS_EventList -{ - U64 count; - OS_Event *first; - OS_Event *last; -}; - -//////////////////////////////// -//~ rjf: Application-Defined Frame Hook Forward Declaration - -internal B32 frame(void); - -//////////////////////////////// -//~ rjf: Handle Type Helpers - -internal OS_Window os_window_zero(void); -internal B32 os_window_match(OS_Window a, OS_Window b); -internal B32 os_monitor_match(OS_Monitor a, OS_Monitor b); - -//////////////////////////////// -//~ rjf: Event Functions (Helpers, Implemented Once) - -internal String8 os_string_from_event_kind(OS_EventKind kind); -internal String8List os_string_list_from_modifiers(Arena *arena, OS_Modifiers flags); -internal String8 os_string_from_modifiers_key(Arena *arena, OS_Modifiers modifiers, OS_Key key); -internal U32 os_codepoint_from_modifiers_and_key(OS_Modifiers flags, OS_Key key); -internal void os_eat_event(OS_EventList *events, OS_Event *event); -internal B32 os_key_press(OS_EventList *events, OS_Window window, OS_Modifiers modifiers, OS_Key key); -internal B32 os_key_release(OS_EventList *events, OS_Window window, OS_Modifiers modifiers, OS_Key key); -internal B32 os_text(OS_EventList *events, OS_Window window, U32 character); -internal OS_EventList os_event_list_copy(Arena *arena, OS_EventList *src); -internal void os_event_list_concat_in_place(OS_EventList *dst, OS_EventList *to_push); -internal OS_Event *os_event_list_push_new(Arena *arena, OS_EventList *evts, OS_EventKind kind); - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void os_gfx_init(void); - -//////////////////////////////// -//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) - -internal OS_GfxInfo *os_get_gfx_info(void); - -//////////////////////////////// -//~ rjf: @os_hooks Clipboards (Implemented Per-OS) - -internal void os_set_clipboard_text(String8 string); -internal String8 os_get_clipboard_text(Arena *arena); - -//////////////////////////////// -//~ rjf: @os_hooks Windows (Implemented Per-OS) - -internal OS_Window os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title); -internal void os_window_close(OS_Window window); -internal void os_window_set_title(OS_Window window, String8 title); -internal void os_window_first_paint(OS_Window window); -internal void os_window_focus(OS_Window window); -internal B32 os_window_is_focused(OS_Window window); -internal B32 os_window_is_fullscreen(OS_Window window); -internal void os_window_set_fullscreen(OS_Window window, B32 fullscreen); -internal B32 os_window_is_maximized(OS_Window window); -internal void os_window_set_maximized(OS_Window window, B32 maximized); -internal B32 os_window_is_minimized(OS_Window window); -internal void os_window_set_minimized(OS_Window window, B32 minimized); -internal void os_window_bring_to_front(OS_Window window); -internal void os_window_set_monitor(OS_Window window, OS_Monitor monitor); -internal void os_window_clear_custom_border_data(OS_Window handle); -internal void os_window_push_custom_title_bar(OS_Window handle, F32 thickness); -internal void os_window_push_custom_edges(OS_Window handle, F32 thickness); -internal void os_window_push_custom_title_bar_client_area(OS_Window handle, Rng2F32 rect); -internal Rng2F32 os_rect_from_window(OS_Window window); -internal Rng2F32 os_client_rect_from_window(OS_Window window); -internal F32 os_dpi_from_window(OS_Window window); - -//////////////////////////////// -//~ rjf: @os_hooks External Windows (Implemented Per-OS) - -internal OS_ExternalWindow os_focused_external_window(void); -internal void os_focus_external_window(OS_ExternalWindow ext_window); - -//////////////////////////////// -//~ rjf: @os_hooks Monitors (Implemented Per-OS) - -internal OS_MonitorArray os_push_monitors_array(Arena *arena); -internal OS_Monitor os_primary_monitor(void); -internal OS_Monitor os_monitor_from_window(OS_Window window); -internal String8 os_name_from_monitor(Arena *arena, OS_Monitor monitor); -internal Vec2F32 os_dim_from_monitor(OS_Monitor monitor); -internal F32 os_dpi_from_monitor(OS_Monitor monitor); - -//////////////////////////////// -//~ rjf: @os_hooks Events (Implemented Per-OS) - -internal void os_send_wakeup_event(void); -internal OS_EventList os_get_events(Arena *arena, B32 wait); -internal OS_Modifiers os_get_modifiers(void); -internal B32 os_key_is_down(OS_Key key); -internal Vec2F32 os_mouse_from_window(OS_Window window); - -//////////////////////////////// -//~ rjf: @os_hooks Cursors (Implemented Per-OS) - -internal void os_set_cursor(OS_Cursor cursor); - -//////////////////////////////// -//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void os_graphical_message(B32 error, String8 title, String8 message); -internal String8 os_graphical_pick_file(Arena *arena, String8 initial_path); - -//////////////////////////////// -//~ rjf: @os_hooks Shell Operations - -internal void os_show_in_filesystem_ui(String8 path); -internal void os_open_in_browser(String8 url); - -#endif // OS_GFX_H +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_GFX_H +#define OS_GFX_H + +//////////////////////////////// +//~ rjf: Graphics System Info + +typedef struct OS_GfxInfo OS_GfxInfo; +struct OS_GfxInfo +{ + F32 double_click_time; + F32 caret_blink_time; + F32 default_refresh_rate; +}; + +//////////////////////////////// +//~ rjf: Window Types + +typedef U32 OS_WindowFlags; +enum +{ + OS_WindowFlag_CustomBorder = (1<<0), + OS_WindowFlag_UseDefaultPosition = (1<<1), +}; + +typedef struct OS_Window OS_Window; +struct OS_Window +{ + U64 u64[1]; +}; + +//////////////////////////////// +//~ rjf: External Window Types + +typedef struct OS_ExternalWindow OS_ExternalWindow; +struct OS_ExternalWindow +{ + U64 u64[1]; +}; + +//////////////////////////////// +//~ rjf: Monitor Types + +typedef struct OS_Monitor OS_Monitor; +struct OS_Monitor +{ + U64 u64[1]; +}; + +typedef struct OS_MonitorArray OS_MonitorArray; +struct OS_MonitorArray +{ + OS_Monitor *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Cursor Types + +typedef enum OS_Cursor +{ + OS_Cursor_Pointer, + OS_Cursor_IBar, + OS_Cursor_LeftRight, + OS_Cursor_UpDown, + OS_Cursor_DownRight, + OS_Cursor_UpRight, + OS_Cursor_UpDownLeftRight, + OS_Cursor_HandPoint, + OS_Cursor_Disabled, + OS_Cursor_COUNT, +} +OS_Cursor; + +//////////////////////////////// +//~ rjf: Generated Code + +#include "window_manager/generated/window_manager.meta.h" + +//////////////////////////////// +//~ rjf: Event Types + +typedef enum OS_EventKind +{ + OS_EventKind_Null, + OS_EventKind_Press, + OS_EventKind_Release, + OS_EventKind_MouseMove, + OS_EventKind_Text, + OS_EventKind_Scroll, + OS_EventKind_WindowLoseFocus, + OS_EventKind_WindowClose, + OS_EventKind_FileDrop, + OS_EventKind_Wakeup, + OS_EventKind_COUNT +} +OS_EventKind; + +typedef U32 OS_Modifiers; +enum +{ + OS_Modifier_Ctrl = (1<<0), + OS_Modifier_Shift = (1<<1), + OS_Modifier_Alt = (1<<2), +}; + +typedef struct OS_Event OS_Event; +struct OS_Event +{ + OS_Event *next; + OS_Event *prev; + U64 timestamp_us; + OS_Window window; + OS_EventKind kind; + OS_Modifiers modifiers; + OS_Key key; + B32 is_repeat; + B32 right_sided; + U32 character; + U32 repeat_count; + Vec2F32 pos; + Vec2F32 delta; + String8List strings; +}; + +typedef struct OS_EventList OS_EventList; +struct OS_EventList +{ + U64 count; + OS_Event *first; + OS_Event *last; +}; + +//////////////////////////////// +//~ rjf: Application-Defined Frame Hook Forward Declaration + +internal B32 frame(void); + +//////////////////////////////// +//~ rjf: Handle Type Helpers + +internal OS_Window os_window_zero(void); +internal B32 os_window_match(OS_Window a, OS_Window b); +internal B32 os_monitor_match(OS_Monitor a, OS_Monitor b); + +//////////////////////////////// +//~ rjf: Event Functions (Helpers, Implemented Once) + +internal String8 os_string_from_event_kind(OS_EventKind kind); +internal String8List os_string_list_from_modifiers(Arena *arena, OS_Modifiers flags); +internal String8 os_string_from_modifiers_key(Arena *arena, OS_Modifiers modifiers, OS_Key key); +internal U32 os_codepoint_from_modifiers_and_key(OS_Modifiers flags, OS_Key key); +internal void os_eat_event(OS_EventList *events, OS_Event *event); +internal B32 os_key_press(OS_EventList *events, OS_Window window, OS_Modifiers modifiers, OS_Key key); +internal B32 os_key_release(OS_EventList *events, OS_Window window, OS_Modifiers modifiers, OS_Key key); +internal B32 os_text(OS_EventList *events, OS_Window window, U32 character); +internal OS_EventList os_event_list_copy(Arena *arena, OS_EventList *src); +internal void os_event_list_concat_in_place(OS_EventList *dst, OS_EventList *to_push); +internal OS_Event *os_event_list_push_new(Arena *arena, OS_EventList *evts, OS_EventKind kind); + +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void os_gfx_init(void); + +//////////////////////////////// +//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) + +internal OS_GfxInfo *os_get_gfx_info(void); + +//////////////////////////////// +//~ rjf: @os_hooks Clipboards (Implemented Per-OS) + +internal void os_set_clipboard_text(String8 string); +internal String8 os_get_clipboard_text(Arena *arena); + +//////////////////////////////// +//~ rjf: @os_hooks Windows (Implemented Per-OS) + +internal OS_Window os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title); +internal void os_window_close(OS_Window window); +internal void os_window_set_title(OS_Window window, String8 title); +internal void os_window_first_paint(OS_Window window); +internal void os_window_focus(OS_Window window); +internal B32 os_window_is_focused(OS_Window window); +internal B32 os_window_is_fullscreen(OS_Window window); +internal void os_window_set_fullscreen(OS_Window window, B32 fullscreen); +internal B32 os_window_is_maximized(OS_Window window); +internal void os_window_set_maximized(OS_Window window, B32 maximized); +internal B32 os_window_is_minimized(OS_Window window); +internal void os_window_set_minimized(OS_Window window, B32 minimized); +internal void os_window_bring_to_front(OS_Window window); +internal void os_window_set_monitor(OS_Window window, OS_Monitor monitor); +internal void os_window_clear_custom_border_data(OS_Window handle); +internal void os_window_push_custom_title_bar(OS_Window handle, F32 thickness); +internal void os_window_push_custom_edges(OS_Window handle, F32 thickness); +internal void os_window_push_custom_title_bar_client_area(OS_Window handle, Rng2F32 rect); +internal Rng2F32 os_rect_from_window(OS_Window window); +internal Rng2F32 os_client_rect_from_window(OS_Window window); +internal F32 os_dpi_from_window(OS_Window window); + +//////////////////////////////// +//~ rjf: @os_hooks External Windows (Implemented Per-OS) + +internal OS_ExternalWindow os_focused_external_window(void); +internal void os_focus_external_window(OS_ExternalWindow ext_window); + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_MonitorArray os_push_monitors_array(Arena *arena); +internal OS_Monitor os_primary_monitor(void); +internal OS_Monitor os_monitor_from_window(OS_Window window); +internal String8 os_name_from_monitor(Arena *arena, OS_Monitor monitor); +internal Vec2F32 os_dim_from_monitor(OS_Monitor monitor); +internal F32 os_dpi_from_monitor(OS_Monitor monitor); + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +internal void os_send_wakeup_event(void); +internal OS_EventList os_get_events(Arena *arena, B32 wait); +internal OS_Modifiers os_get_modifiers(void); +internal B32 os_key_is_down(OS_Key key); +internal Vec2F32 os_mouse_from_window(OS_Window window); + +//////////////////////////////// +//~ rjf: @os_hooks Cursors (Implemented Per-OS) + +internal void os_set_cursor(OS_Cursor cursor); + +//////////////////////////////// +//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) + +internal void os_graphical_message(B32 error, String8 title, String8 message); +internal String8 os_graphical_pick_file(Arena *arena, String8 initial_path); + +//////////////////////////////// +//~ rjf: @os_hooks Shell Operations + +internal void os_show_in_filesystem_ui(String8 path); +internal void os_open_in_browser(String8 url); + +#endif // OS_GFX_H diff --git a/src/os/gfx/os_gfx.mdesk b/src/window_manager/window_manager.mdesk similarity index 98% rename from src/os/gfx/os_gfx.mdesk rename to src/window_manager/window_manager.mdesk index fc15a5f2..053c5119 100644 --- a/src/os/gfx/os_gfx.mdesk +++ b/src/window_manager/window_manager.mdesk @@ -1,172 +1,172 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Tables - -@table(name, display_string, cfg_string) -OS_KeyTable: -{ - {Null "Invalid Key" "null" } - {Esc "Escape" "esc" } - {F1 "F1" "f1" } - {F2 "F2" "f2" } - {F3 "F3" "f3" } - {F4 "F4" "f4" } - {F5 "F5" "f5" } - {F6 "F6" "f6" } - {F7 "F7" "f7" } - {F8 "F8" "f8" } - {F9 "F9" "f9" } - {F10 "F10" "f10" } - {F11 "F11" "f11" } - {F12 "F12" "f12" } - {F13 "F13" "f13" } - {F14 "F14" "f14" } - {F15 "F15" "f15" } - {F16 "F16" "f16" } - {F17 "F17" "f17" } - {F18 "F18" "f18" } - {F19 "F19" "f19" } - {F20 "F20" "f20" } - {F21 "F21" "f21" } - {F22 "F22" "f22" } - {F23 "F23" "f23" } - {F24 "F24" "f24" } - {Tick "Tick" "tick" } - {0 "0" "0" } - {1 "1" "1" } - {2 "2" "2" } - {3 "3" "3" } - {4 "4" "4" } - {5 "5" "5" } - {6 "6" "6" } - {7 "7" "7" } - {8 "8" "8" } - {9 "9" "9" } - {Minus "Minus" "minus" } - {Equal "Equal" "equal" } - {Backspace "Backspace" "backspace" } - {Tab "Tab" "tab" } - {Q "Q" "q" } - {W "W" "w" } - {E "E" "e" } - {R "R" "r" } - {T "T" "t" } - {Y "Y" "y" } - {U "U" "u" } - {I "I" "i" } - {O "O" "o" } - {P "P" "p" } - {LeftBracket "Left Bracket" "left_bracket" } - {RightBracket "Right Bracket" "right_bracket" } - {BackSlash "Back Slash" "backslash" } - {CapsLock "Caps Lock" "caps_lock" } - {A "A" "a" } - {S "S" "s" } - {D "D" "d" } - {F "F" "f" } - {G "G" "g" } - {H "H" "h" } - {J "J" "j" } - {K "K" "k" } - {L "L" "l" } - {Semicolon "Semicolon" "semicolon" } - {Quote "Quote" "quote" } - {Return "Return" "return" } - {Shift "Shift" "shift" } - {Z "Z" "z" } - {X "X" "x" } - {C "C" "c" } - {V "V" "v" } - {B "B" "b" } - {N "N" "n" } - {M "M" "m" } - {Comma "Comma" "comma" } - {Period "Period" "period" } - {Slash "Slash" "slash" } - {Ctrl "Ctrl" "ctrl" } - {Alt "Alt" "alt" } - {Space "Space" "space" } - {Menu "Menu" "menu" } - {ScrollLock "Scroll Lock" "scroll_lock" } - {Pause "Pause" "pause" } - {Insert "Insert" "insert" } - {Home "Home" "home" } - {PageUp "Page Up" "page_up" } - {Delete "Delete" "delete" } - {End "End" "end" } - {PageDown "Page Down" "page_down" } - {Up "Up" "up" } - {Left "Left" "left" } - {Down "Down" "down" } - {Right "Right" "right" } - {Ex0 "Ex0" "ex0" } - {Ex1 "Ex1" "ex1" } - {Ex2 "Ex2" "ex2" } - {Ex3 "Ex3" "ex3" } - {Ex4 "Ex4" "ex4" } - {Ex5 "Ex5" "ex5" } - {Ex6 "Ex6" "ex6" } - {Ex7 "Ex7" "ex7" } - {Ex8 "Ex8" "ex8" } - {Ex9 "Ex9" "ex9" } - {Ex10 "Ex10" "ex10" } - {Ex11 "Ex11" "ex11" } - {Ex12 "Ex12" "ex12" } - {Ex13 "Ex13" "ex13" } - {Ex14 "Ex14" "ex14" } - {Ex15 "Ex15" "ex15" } - {Ex16 "Ex16" "ex16" } - {Ex17 "Ex17" "ex17" } - {Ex18 "Ex18" "ex18" } - {Ex19 "Ex19" "ex19" } - {Ex20 "Ex20" "ex20" } - {Ex21 "Ex21" "ex21" } - {Ex22 "Ex22" "ex22" } - {Ex23 "Ex23" "ex23" } - {Ex24 "Ex24" "ex24" } - {Ex25 "Ex25" "ex25" } - {Ex26 "Ex26" "ex26" } - {Ex27 "Ex27" "ex27" } - {Ex28 "Ex28" "ex28" } - {Ex29 "Ex29" "ex29" } - {NumLock "Num Lock" "num_lock" } - {NumSlash "Numpad Slash" "numpad_slash" } - {NumStar "Numpad Star" "numpad_star" } - {NumMinus "Numpad Minus" "numpad_minus" } - {NumPlus "Numpad Plus" "numpad_plus" } - {NumPeriod "Numpad Period" "numpad_period" } - {Num0 "Numpad 0" "numpad_0" } - {Num1 "Numpad 1" "numpad_1" } - {Num2 "Numpad 2" "numpad_2" } - {Num3 "Numpad 3" "numpad_3" } - {Num4 "Numpad 4" "numpad_4" } - {Num5 "Numpad 5" "numpad_5" } - {Num6 "Numpad 6" "numpad_6" } - {Num7 "Numpad 7" "numpad_7" } - {Num8 "Numpad 8" "numpad_8" } - {Num9 "Numpad 9" "numpad_9" } - {LeftMouseButton "Left Mouse Button" "left_mouse" } - {MiddleMouseButton "Middle Mouse Button" "middle_mouse" } - {RightMouseButton "Right Mouse Button" "right_mouse" } -} - -//////////////////////////////// -//~ rjf: Generators - -@enum OS_Key: -{ - @expand(OS_KeyTable a) `$(a.name)`, - COUNT, -} - -@data(String8) os_g_key_display_string_table: -{ - @expand(OS_KeyTable a) `str8_lit_comp("$(a.display_string)")`; -} - -@data(String8) os_g_key_cfg_string_table: -{ - @expand(OS_KeyTable a) `str8_lit_comp("$(a.cfg_string)")`; -} +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Tables + +@table(name, display_string, cfg_string) +OS_KeyTable: +{ + {Null "Invalid Key" "null" } + {Esc "Escape" "esc" } + {F1 "F1" "f1" } + {F2 "F2" "f2" } + {F3 "F3" "f3" } + {F4 "F4" "f4" } + {F5 "F5" "f5" } + {F6 "F6" "f6" } + {F7 "F7" "f7" } + {F8 "F8" "f8" } + {F9 "F9" "f9" } + {F10 "F10" "f10" } + {F11 "F11" "f11" } + {F12 "F12" "f12" } + {F13 "F13" "f13" } + {F14 "F14" "f14" } + {F15 "F15" "f15" } + {F16 "F16" "f16" } + {F17 "F17" "f17" } + {F18 "F18" "f18" } + {F19 "F19" "f19" } + {F20 "F20" "f20" } + {F21 "F21" "f21" } + {F22 "F22" "f22" } + {F23 "F23" "f23" } + {F24 "F24" "f24" } + {Tick "Tick" "tick" } + {0 "0" "0" } + {1 "1" "1" } + {2 "2" "2" } + {3 "3" "3" } + {4 "4" "4" } + {5 "5" "5" } + {6 "6" "6" } + {7 "7" "7" } + {8 "8" "8" } + {9 "9" "9" } + {Minus "Minus" "minus" } + {Equal "Equal" "equal" } + {Backspace "Backspace" "backspace" } + {Tab "Tab" "tab" } + {Q "Q" "q" } + {W "W" "w" } + {E "E" "e" } + {R "R" "r" } + {T "T" "t" } + {Y "Y" "y" } + {U "U" "u" } + {I "I" "i" } + {O "O" "o" } + {P "P" "p" } + {LeftBracket "Left Bracket" "left_bracket" } + {RightBracket "Right Bracket" "right_bracket" } + {BackSlash "Back Slash" "backslash" } + {CapsLock "Caps Lock" "caps_lock" } + {A "A" "a" } + {S "S" "s" } + {D "D" "d" } + {F "F" "f" } + {G "G" "g" } + {H "H" "h" } + {J "J" "j" } + {K "K" "k" } + {L "L" "l" } + {Semicolon "Semicolon" "semicolon" } + {Quote "Quote" "quote" } + {Return "Return" "return" } + {Shift "Shift" "shift" } + {Z "Z" "z" } + {X "X" "x" } + {C "C" "c" } + {V "V" "v" } + {B "B" "b" } + {N "N" "n" } + {M "M" "m" } + {Comma "Comma" "comma" } + {Period "Period" "period" } + {Slash "Slash" "slash" } + {Ctrl "Ctrl" "ctrl" } + {Alt "Alt" "alt" } + {Space "Space" "space" } + {Menu "Menu" "menu" } + {ScrollLock "Scroll Lock" "scroll_lock" } + {Pause "Pause" "pause" } + {Insert "Insert" "insert" } + {Home "Home" "home" } + {PageUp "Page Up" "page_up" } + {Delete "Delete" "delete" } + {End "End" "end" } + {PageDown "Page Down" "page_down" } + {Up "Up" "up" } + {Left "Left" "left" } + {Down "Down" "down" } + {Right "Right" "right" } + {Ex0 "Ex0" "ex0" } + {Ex1 "Ex1" "ex1" } + {Ex2 "Ex2" "ex2" } + {Ex3 "Ex3" "ex3" } + {Ex4 "Ex4" "ex4" } + {Ex5 "Ex5" "ex5" } + {Ex6 "Ex6" "ex6" } + {Ex7 "Ex7" "ex7" } + {Ex8 "Ex8" "ex8" } + {Ex9 "Ex9" "ex9" } + {Ex10 "Ex10" "ex10" } + {Ex11 "Ex11" "ex11" } + {Ex12 "Ex12" "ex12" } + {Ex13 "Ex13" "ex13" } + {Ex14 "Ex14" "ex14" } + {Ex15 "Ex15" "ex15" } + {Ex16 "Ex16" "ex16" } + {Ex17 "Ex17" "ex17" } + {Ex18 "Ex18" "ex18" } + {Ex19 "Ex19" "ex19" } + {Ex20 "Ex20" "ex20" } + {Ex21 "Ex21" "ex21" } + {Ex22 "Ex22" "ex22" } + {Ex23 "Ex23" "ex23" } + {Ex24 "Ex24" "ex24" } + {Ex25 "Ex25" "ex25" } + {Ex26 "Ex26" "ex26" } + {Ex27 "Ex27" "ex27" } + {Ex28 "Ex28" "ex28" } + {Ex29 "Ex29" "ex29" } + {NumLock "Num Lock" "num_lock" } + {NumSlash "Numpad Slash" "numpad_slash" } + {NumStar "Numpad Star" "numpad_star" } + {NumMinus "Numpad Minus" "numpad_minus" } + {NumPlus "Numpad Plus" "numpad_plus" } + {NumPeriod "Numpad Period" "numpad_period" } + {Num0 "Numpad 0" "numpad_0" } + {Num1 "Numpad 1" "numpad_1" } + {Num2 "Numpad 2" "numpad_2" } + {Num3 "Numpad 3" "numpad_3" } + {Num4 "Numpad 4" "numpad_4" } + {Num5 "Numpad 5" "numpad_5" } + {Num6 "Numpad 6" "numpad_6" } + {Num7 "Numpad 7" "numpad_7" } + {Num8 "Numpad 8" "numpad_8" } + {Num9 "Numpad 9" "numpad_9" } + {LeftMouseButton "Left Mouse Button" "left_mouse" } + {MiddleMouseButton "Middle Mouse Button" "middle_mouse" } + {RightMouseButton "Right Mouse Button" "right_mouse" } +} + +//////////////////////////////// +//~ rjf: Generators + +@enum OS_Key: +{ + @expand(OS_KeyTable a) `$(a.name)`, + COUNT, +} + +@data(String8) os_g_key_display_string_table: +{ + @expand(OS_KeyTable a) `str8_lit_comp("$(a.display_string)")`; +} + +@data(String8) os_g_key_cfg_string_table: +{ + @expand(OS_KeyTable a) `str8_lit_comp("$(a.cfg_string)")`; +} diff --git a/src/window_manager/window_manager_inc.c b/src/window_manager/window_manager_inc.c new file mode 100644 index 00000000..5e83758c --- /dev/null +++ b/src/window_manager/window_manager_inc.c @@ -0,0 +1,13 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "window_manager.c" +#if WM_STUB +# include "window_manager_stub.c" +#elif OS_WINDOWS +# include "win32/window_manager/win32_window_manager.c" +#elif OS_LINUX +# include "linux/window_manager/linux_window_manager.c" +#else +# error Window manager layer not implemented for this operating system. +#endif diff --git a/src/window_manager/window_manager_inc.h b/src/window_manager/window_manager_inc.h new file mode 100644 index 00000000..69463a64 --- /dev/null +++ b/src/window_manager/window_manager_inc.h @@ -0,0 +1,18 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef WINDOW_MANAGER_INC_H +#define WINDOW_MANAGER_INC_H + +#include "window_manager.h" +#if WM_STUB +# include "window_manager_stub.h" +#elif OS_WINDOWS +# include "win32/window_manager/win32_window_manager.h" +#elif OS_LINUX +# include "linux/window_manager/linux_window_manager.h" +#else +# error Window manager layer not implemented for this operating system. +#endif + +#endif // WINDOW_MANAGER_INC_H diff --git a/src/os/gfx/stub/os_gfx_stub.c b/src/window_manager/window_manager_stub.c similarity index 93% rename from src/os/gfx/stub/os_gfx_stub.c rename to src/window_manager/window_manager_stub.c index e1e9b805..bf49a3d0 100644 --- a/src/os/gfx/stub/os_gfx_stub.c +++ b/src/window_manager/window_manager_stub.c @@ -1,279 +1,279 @@ -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_gfx_init(void) -{ -} - -//////////////////////////////// -//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) - -internal OS_GfxInfo * -os_get_gfx_info(void) -{ - local_persist OS_GfxInfo g = {0}; - return &g; -} - -//////////////////////////////// -//~ rjf: @os_hooks Clipboards (Implemented Per-OS) - -internal void -os_set_clipboard_text(String8 string) -{ -} - -internal String8 -os_get_clipboard_text(Arena *arena) -{ - return str8_zero(); -} - -//////////////////////////////// -//~ rjf: @os_hooks Windows (Implemented Per-OS) - -internal OS_Handle -os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) -{ - OS_Handle handle = {1}; - return handle; -} - -internal void -os_window_close(OS_Handle window) -{ -} - -internal void -os_window_set_title(OS_Handle window, String8 title) -{ -} - -internal void -os_window_first_paint(OS_Handle window) -{ -} - -internal void -os_window_focus(OS_Handle window) -{ -} - -internal B32 -os_window_is_focused(OS_Handle window) -{ - return 0; -} - -internal B32 -os_window_is_fullscreen(OS_Handle window) -{ - return 0; -} - -internal void -os_window_set_fullscreen(OS_Handle window, B32 fullscreen) -{ -} - -internal B32 -os_window_is_maximized(OS_Handle window) -{ - return 0; -} - -internal void -os_window_set_maximized(OS_Handle window, B32 maximized) -{ -} - -internal B32 -os_window_is_minimized(OS_Handle window) -{ - return 0; -} - -internal void -os_window_set_minimized(OS_Handle window, B32 minimized) -{ -} - -internal void -os_window_bring_to_front(OS_Handle window) -{ -} - -internal void -os_window_set_monitor(OS_Handle window, OS_Handle monitor) -{ -} - -internal void -os_window_clear_custom_border_data(OS_Handle handle) -{ -} - -internal void -os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) -{ -} - -internal void -os_window_push_custom_edges(OS_Handle handle, F32 thickness) -{ -} - -internal void -os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) -{ -} - -internal Rng2F32 -os_rect_from_window(OS_Handle window) -{ - Rng2F32 rect = r2f32(v2f32(0, 0), v2f32(500, 500)); - return rect; -} - -internal Rng2F32 -os_client_rect_from_window(OS_Handle window) -{ - Rng2F32 rect = r2f32(v2f32(0, 0), v2f32(500, 500)); - return rect; -} - -internal F32 -os_dpi_from_window(OS_Handle window) -{ - return 96.f; -} - -//////////////////////////////// -//~ rjf: @os_hooks External Windows (Implemented Per-OS) - -internal OS_Handle -os_focused_external_window(void) -{ - OS_Handle result = {0}; - // TODO(rjf) - return result; -} - -internal void -os_focus_external_window(OS_Handle handle) -{ - // TODO(rjf) -} - -//////////////////////////////// -//~ rjf: @os_hooks Monitors (Implemented Per-OS) - -internal OS_HandleArray -os_push_monitors_array(Arena *arena) -{ - OS_HandleArray arr = {0}; - return arr; -} - -internal OS_Handle -os_primary_monitor(void) -{ - OS_Handle handle = {1}; - return handle; -} - -internal OS_Handle -os_monitor_from_window(OS_Handle window) -{ - OS_Handle handle = {1}; - return handle; -} - -internal String8 -os_name_from_monitor(Arena *arena, OS_Handle monitor) -{ - return str8_zero(); -} - -internal Vec2F32 -os_dim_from_monitor(OS_Handle monitor) -{ - Vec2F32 v = v2f32(1000, 1000); - return v; -} - -internal F32 -os_dpi_from_monitor(OS_Handle monitor) -{ - return 96.f; -} - -//////////////////////////////// -//~ rjf: @os_hooks Events (Implemented Per-OS) - -internal void -os_send_wakeup_event(void) -{ -} - -internal OS_EventList -os_get_events(Arena *arena, B32 wait) -{ - OS_EventList evts = {0}; - return evts; -} - -internal OS_Modifiers -os_get_modifiers(void) -{ - OS_Modifiers f = 0; - return f; -} - -internal B32 -os_key_is_down(OS_Key key) -{ - return 0; -} - -internal Vec2F32 -os_mouse_from_window(OS_Handle window) -{ - return v2f32(0, 0); -} - -//////////////////////////////// -//~ rjf: @os_hooks Cursors (Implemented Per-OS) - -internal void -os_set_cursor(OS_Cursor cursor) -{ -} - -//////////////////////////////// -//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -os_graphical_message(B32 error, String8 title, String8 message) -{ -} - -internal String8 -os_graphical_pick_file(Arena *arena, String8 initial_path) -{ - return str8_zero(); -} - -//////////////////////////////// -//~ rjf: @os_hooks Shell Operations - -internal void -os_show_in_filesystem_ui(String8 path) -{ -} - -internal void -os_open_in_browser(String8 url) -{ -} +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void +os_gfx_init(void) +{ +} + +//////////////////////////////// +//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) + +internal OS_GfxInfo * +os_get_gfx_info(void) +{ + local_persist OS_GfxInfo g = {0}; + return &g; +} + +//////////////////////////////// +//~ rjf: @os_hooks Clipboards (Implemented Per-OS) + +internal void +os_set_clipboard_text(String8 string) +{ +} + +internal String8 +os_get_clipboard_text(Arena *arena) +{ + return str8_zero(); +} + +//////////////////////////////// +//~ rjf: @os_hooks Windows (Implemented Per-OS) + +internal OS_Handle +os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title) +{ + OS_Handle handle = {1}; + return handle; +} + +internal void +os_window_close(OS_Handle window) +{ +} + +internal void +os_window_set_title(OS_Handle window, String8 title) +{ +} + +internal void +os_window_first_paint(OS_Handle window) +{ +} + +internal void +os_window_focus(OS_Handle window) +{ +} + +internal B32 +os_window_is_focused(OS_Handle window) +{ + return 0; +} + +internal B32 +os_window_is_fullscreen(OS_Handle window) +{ + return 0; +} + +internal void +os_window_set_fullscreen(OS_Handle window, B32 fullscreen) +{ +} + +internal B32 +os_window_is_maximized(OS_Handle window) +{ + return 0; +} + +internal void +os_window_set_maximized(OS_Handle window, B32 maximized) +{ +} + +internal B32 +os_window_is_minimized(OS_Handle window) +{ + return 0; +} + +internal void +os_window_set_minimized(OS_Handle window, B32 minimized) +{ +} + +internal void +os_window_bring_to_front(OS_Handle window) +{ +} + +internal void +os_window_set_monitor(OS_Handle window, OS_Handle monitor) +{ +} + +internal void +os_window_clear_custom_border_data(OS_Handle handle) +{ +} + +internal void +os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) +{ +} + +internal void +os_window_push_custom_edges(OS_Handle handle, F32 thickness) +{ +} + +internal void +os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) +{ +} + +internal Rng2F32 +os_rect_from_window(OS_Handle window) +{ + Rng2F32 rect = r2f32(v2f32(0, 0), v2f32(500, 500)); + return rect; +} + +internal Rng2F32 +os_client_rect_from_window(OS_Handle window) +{ + Rng2F32 rect = r2f32(v2f32(0, 0), v2f32(500, 500)); + return rect; +} + +internal F32 +os_dpi_from_window(OS_Handle window) +{ + return 96.f; +} + +//////////////////////////////// +//~ rjf: @os_hooks External Windows (Implemented Per-OS) + +internal OS_Handle +os_focused_external_window(void) +{ + OS_Handle result = {0}; + // TODO(rjf) + return result; +} + +internal void +os_focus_external_window(OS_Handle handle) +{ + // TODO(rjf) +} + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_HandleArray +os_push_monitors_array(Arena *arena) +{ + OS_HandleArray arr = {0}; + return arr; +} + +internal OS_Handle +os_primary_monitor(void) +{ + OS_Handle handle = {1}; + return handle; +} + +internal OS_Handle +os_monitor_from_window(OS_Handle window) +{ + OS_Handle handle = {1}; + return handle; +} + +internal String8 +os_name_from_monitor(Arena *arena, OS_Handle monitor) +{ + return str8_zero(); +} + +internal Vec2F32 +os_dim_from_monitor(OS_Handle monitor) +{ + Vec2F32 v = v2f32(1000, 1000); + return v; +} + +internal F32 +os_dpi_from_monitor(OS_Handle monitor) +{ + return 96.f; +} + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +internal void +os_send_wakeup_event(void) +{ +} + +internal OS_EventList +os_get_events(Arena *arena, B32 wait) +{ + OS_EventList evts = {0}; + return evts; +} + +internal OS_Modifiers +os_get_modifiers(void) +{ + OS_Modifiers f = 0; + return f; +} + +internal B32 +os_key_is_down(OS_Key key) +{ + return 0; +} + +internal Vec2F32 +os_mouse_from_window(OS_Handle window) +{ + return v2f32(0, 0); +} + +//////////////////////////////// +//~ rjf: @os_hooks Cursors (Implemented Per-OS) + +internal void +os_set_cursor(OS_Cursor cursor) +{ +} + +//////////////////////////////// +//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) + +internal void +os_graphical_message(B32 error, String8 title, String8 message) +{ +} + +internal String8 +os_graphical_pick_file(Arena *arena, String8 initial_path) +{ + return str8_zero(); +} + +//////////////////////////////// +//~ rjf: @os_hooks Shell Operations + +internal void +os_show_in_filesystem_ui(String8 path) +{ +} + +internal void +os_open_in_browser(String8 url) +{ +} diff --git a/src/os/gfx/stub/os_gfx_stub.h b/src/window_manager/window_manager_stub.h similarity index 96% rename from src/os/gfx/stub/os_gfx_stub.h rename to src/window_manager/window_manager_stub.h index 009f9802..afdfce32 100644 --- a/src/os/gfx/stub/os_gfx_stub.h +++ b/src/window_manager/window_manager_stub.h @@ -1,7 +1,7 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_GFX_STUB_H -#define OS_GFX_STUB_H - -#endif // OS_GFX_STUB_H +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_GFX_STUB_H +#define OS_GFX_STUB_H + +#endif // OS_GFX_STUB_H diff --git a/src/x64/x64.h b/src/x64/x64.h index db5edf11..db6ec7d2 100644 --- a/src/x64/x64.h +++ b/src/x64/x64.h @@ -34,7 +34,8 @@ enum //////////////////////////////// //~ fxsave/xsave -typedef struct AlignType(16) X64_FXSave +typedef struct X64_FXSave X64_FXSave; +struct AlignType(16) X64_FXSave { U16 fcw; U16 fsw; @@ -48,23 +49,25 @@ typedef struct AlignType(16) X64_FXSave U128 st_space[8]; U128 xmm_space[16]; U8 padding[96]; -} X64_FXSave; +}; StaticAssert(sizeof(X64_FXSave) == 512, g_x64_xsave_legacy_size_check); -typedef struct X64_XSaveHeader +typedef struct X64_XSaveHeader X64_XSaveHeader; +struct X64_XSaveHeader { U64 xstate_bv; U64 xcomp_bv; U8 reserved[48]; -} X64_XSaveHeader; +}; StaticAssert(sizeof(X64_XSaveHeader) == 64, g_x64_xsave_header_size_check); -typedef struct AlignType(64) X64_XSave +typedef struct X64_XSave X64_XSave; +struct AlignType(64) X64_XSave { X64_FXSave fxsave; X64_XSaveHeader header; // U8 ext_area[0]; -} X64_XSave; +}; StaticAssert(sizeof(X64_XSave) == 576, g_x64_xsave_size_check); typedef U32 X64_XStateComponentIdx; @@ -90,7 +93,6 @@ enum X64_XStateComponentIdx_TILECFG = 17, X64_XStateComponentIdx_TILEDATA = 18, }; - enum { X64_XStateComponentFlag_FP = (1 << X64_XStateComponentIdx_FP), @@ -114,7 +116,8 @@ enum X64_XStateComponentFlag_TILEDATA = (1 << X64_XStateComponentIdx_TILEDATA), }; -typedef struct +typedef struct X64_XSaveLayout X64_XSaveLayout; +struct X64_XSaveLayout { U64 x87_offset; U64 sse_offset; @@ -135,12 +138,12 @@ typedef struct U64 hwp_offset; U64 tile_cfg_offset; U64 tile_data_offset; -} X64_XSaveLayout; +}; //////////////////////////////// //~ Debug Status Register -typedef enum +typedef enum X64_DebugStatusFlags { X64_DebugStatusFlag_B0 = (1 << 0), X64_DebugStatusFlag_B1 = (1 << 1), @@ -151,16 +154,19 @@ typedef enum X64_DebugStatusFlag_SingleStep = (1 << 14), X64_DebugStatusFlag_TaskSwitch = (1 << 15), X64_DebugStatusFlag_RTM = (1 << 16), -} X64_DebugStatusFlags; +} +X64_DebugStatusFlags; //////////////////////////////// //~ Debug Control Register -typedef enum X64_BreakpointType { +typedef enum X64_BreakpointType +{ X64_BreakpointType_Null, X64_BreakpointType_Local, X64_BreakpointType_Global, -} X64_BreakpointType; +} +X64_BreakpointType; typedef enum X64_DebugBreakType { @@ -169,7 +175,8 @@ typedef enum X64_DebugBreakType X64_DebugBreakType_Write, X64_DebugBreakType_ReadWriteIO, X64_DebugBreakType_ReadWriteNoFetch, -} X64_DebugBreakType; +} +X64_DebugBreakType; typedef enum X64_DebugControlFlags { @@ -199,7 +206,8 @@ typedef enum X64_DebugControlFlags X64_DebugControlFlag_LEN1 = (3 << 22), X64_DebugControlFlag_LEN2 = (3 << 26), X64_DebugControlFlag_LEN3 = (3 << 30), -} X64_DebugControlFlags; +} +X64_DebugControlFlags; //////////////////////////////// //~ rjf: Generated