diff --git a/src/ctrl/ctrl.mdesk b/src/ctrl/ctrl.mdesk deleted file mode 100644 index 9f5d3fa6..00000000 --- a/src/ctrl/ctrl.mdesk +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Entity Kinds - -@table(name code_name display_string) -CTRL_EntityKindTable: -{ - {Root root "Root" } - {Machine machine "Machine" } - {Process process "Process" } - {Thread thread "Thread" } - {Module module "Module" } - {EntryPoint entry_point "Entry Point" } - {DebugInfoPath debug_info_path "Debug Info Path" } - {PendingThreadName pending_thread_name "Pending Thread Name" } - {PendingThreadColor pending_thread_color "Pending Thread Color" } - {Breakpoint breakpoint "Breakpoint" } - {AddressRangeAnnotation address_range_annotation "Address Range Annotation" } -} - -@enum CTRL_EntityKind: -{ - Null, - @expand(CTRL_EntityKindTable a) `$(a.name)`, - COUNT, -} - -@data(String8) ctrl_entity_kind_code_name_table: -{ - `{0}`, - @expand(CTRL_EntityKindTable a) `str8_lit_comp("$(a.code_name)")` -} - -@data(String8) ctrl_entity_kind_display_string_table: -{ - `{0}`, - @expand(CTRL_EntityKindTable a) `str8_lit_comp("$(a.display_name)")` -} - -//////////////////////////////// -//~ rjf: Exception Codes - -@table(name lower_name code default display_string) -CTRL_ExceptionCodeKindTable: -{ - {Win32CtrlC win32_ctrl_c 0x40010005 1 "(Win32) Control-C" } - {Win32CtrlBreak win32_ctrl_break 0x40010008 1 "(Win32) Control-Break" } - {Win32WinRTOriginateError win32_win_rt_originate_error 0x40080201 0 "(Win32) WinRT Originate Error" } - {Win32WinRTTransformError win32_win_rt_transform_error 0x40080202 0 "(Win32) WinRT Transform Error" } - {Win32RPCCallCancelled win32_rpc_call_cancelled 0x0000071a 0 "(Win32) RPC Call Cancelled" } - {Win32DatatypeMisalignment win32_datatype_misalignment 0x80000002 0 "(Win32) Data Type Misalignment" } - {Win32AccessViolation win32_access_violation 0xc0000005 1 "(Win32) Access Violation" } - {Win32InPageError win32_in_page_error 0xc0000006 0 "(Win32) In Page Error" } - {Win32InvalidHandle win32_invalid_handle 0xc0000008 1 "(Win32) Invalid Handle Specified" } - {Win32NotEnoughQuota win32_not_enough_quota 0xc0000017 0 "(Win32) Not Enough Quota" } - {Win32IllegalInstruction win32_illegal_instruction 0xc000001d 0 "(Win32) Illegal Instruction" } - {Win32CannotContinueException win32_cannot_continue_exception 0xc0000025 0 "(Win32) Cannot Continue From Exception" } - {Win32InvalidExceptionDisposition win32_invalid_exception_disposition 0xc0000026 0 "(Win32) Invalid Exception Disposition Returned By Handler" } - {Win32ArrayBoundsExceeded win32_array_bounds_exceeded 0xc000008c 0 "(Win32) Array Bounds Exceeded" } - {Win32FloatingPointDenormalOperand win32_floating_point_denormal_operand 0xc000008d 0 "(Win32) Floating-Point Denormal Operand" } - {Win32FloatingPointDivisionByZero win32_floating_point_division_by_zero 0xc000008e 0 "(Win32) Floating-Point Division By Zero" } - {Win32FloatingPointInexactResult win32_floating_point_inexact_result 0xc000008f 0 "(Win32) Floating-Point Inexact Result" } - {Win32FloatingPointInvalidOperation win32_floating_point_invalid_operation 0xc0000090 0 "(Win32) Floating-Point Invalid Operation" } - {Win32FloatingPointOverflow win32_floating_point_overflow 0xc0000091 0 "(Win32) Floating-Point Overflow" } - {Win32FloatingPointStackCheck win32_floating_point_stack_check 0xc0000092 0 "(Win32) Floating-Point Stack Check" } - {Win32FloatingPointUnderflow win32_floating_point_underflow 0xc0000093 0 "(Win32) Floating-Point Underflow" } - {Win32IntegerDivisionByZero win32_integer_division_by_zero 0xc0000094 0 "(Win32) Integer Division By Zero" } - {Win32IntegerOverflow win32_integer_overflow 0xc0000095 0 "(Win32) Integer Overflow" } - {Win32PrivilegedInstruction win32_privileged_instruction 0xc0000096 0 "(Win32) Privileged Instruction" } - {Win32StackOverflow win32_stack_overflow 0xc00000fd 0 "(Win32) Stack Overflow" } - {Win32UnableToLocateDLL win32_unable_to_locate_dll 0xc0000135 0 "(Win32) Unable To Locate DLL" } - {Win32OrdinalNotFound win32_ordinal_not_found 0xc0000138 0 "(Win32) Ordinal Not Found" } - {Win32EntryPointNotFound win32_entry_point_not_found 0xc0000139 0 "(Win32) Entry Point Not Found" } - {Win32DLLInitializationFailed win32_dll_initialization_failed 0xc0000142 0 "(Win32) DLL Initialization Failed" } - {Win32FloatingPointSSEMultipleFaults win32_floating_point_sse_multiple_faults 0xc00002b4 0 "(Win32) Floating Point SSE Multiple Faults" } - {Win32FloatingPointSSEMultipleTraps win32_floating_point_sse_multiple_traps 0xc00002b5 0 "(Win32) Floating Point SSE Multiple Traps" } - {Win32AssertionFailed win32_assertion_failed 0xc0000420 1 "(Win32) Assertion Failed" } - {Win32ModuleNotFound win32_module_not_found 0xc06d007e 0 "(Win32) Module Not Found" } - {Win32ProcedureNotFound win32_procedure_not_found 0xc06d007f 0 "(Win32) Procedure Not Found" } - {Win32SanitizerErrorDetected win32_sanitizer_error_detected 0xe073616e 1 "(Win32) Sanitizer Error Detected" } - {Win32SanitizerRawAccessViolation win32_sanitizer_raw_access_violation 0xe0736171 0 "(Win32) Sanitizer Raw Access Violation" } - {Win32DirectXDebugLayer win32_directx_debug_layer 0x0000087a 1 "(Win32) DirectX Debug Layer" } -} - -@enum CTRL_ExceptionCodeKind: -{ - Null, - @expand(CTRL_ExceptionCodeKindTable a) `$(a.name)`, - COUNT, -} - -@data(U32) ctrl_exception_code_kind_code_table: -{ - `0`; - @expand(CTRL_ExceptionCodeKindTable a) `$(a.code)`; -} - -@data(String8) ctrl_exception_code_kind_display_string_table: -{ - `{0}`; - @expand(CTRL_ExceptionCodeKindTable a) `str8_lit_comp("$(a.display_string)")`; -} - -@data(String8) ctrl_exception_code_kind_lowercase_code_string_table: -{ - `{0}`; - @expand(CTRL_ExceptionCodeKindTable a) `str8_lit_comp("$(a.lower_name)")`; -} - -@data(B8) ctrl_exception_code_kind_default_enable_table: -{ - `0`; - @expand(CTRL_ExceptionCodeKindTable a) `$(a.default)`; -} - -//////////////////////////////// -//~ rjf: Exception Sub-Codes - -@table(name, value) -CTRL_ExceptionSubCodeKindTable: -{ - { W32_FAST_FAIL_LEGACY_GS_VIOLATION 0 } - { W32_FAST_FAIL_VTGUARD_CHECK_FAILURE 1 } - { W32_FAST_FAIL_STACK_COOKIE_CHECK_FAILURE 2 } - { W32_FAST_FAIL_CORRUPT_LIST_ENTRY 3 } - { W32_FAST_FAIL_INCORRECT_STACK 4 } - { W32_FAST_FAIL_INVALID_ARG 5 } - { W32_FAST_FAIL_GS_COOKIE_INIT 6 } - { W32_FAST_FAIL_FATAL_APP_EXIT 7 } - { W32_FAST_FAIL_RANGE_CHECK_FAILURE 8 } - { W32_FAST_FAIL_UNSAFE_REGISTRY_ACCESS 9 } - { W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE 10 } - { W32_FAST_FAIL_GUARD_WRITE_CHECK_FAILURE 11 } - { W32_FAST_FAIL_INVALID_FIBER_SWITCH 12 } - { W32_FAST_FAIL_INVALID_SET_OF_CONTEXT 13 } - { W32_FAST_FAIL_INVALID_REFERENCE_COUNT 14 } - { W32_FAST_FAIL_INVALID_JUMP_BUFFER 18 } - { W32_FAST_FAIL_MRDATA_MODIFIED 19 } - { W32_FAST_FAIL_CERTIFICATION_FAILURE 20 } - { W32_FAST_FAIL_INVALID_EXCEPTION_CHAIN 21 } - { W32_FAST_FAIL_CRYPTO_LIBRARY 22 } - { W32_FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT 23 } - { W32_FAST_FAIL_INVALID_IMAGE_BASE 24 } - { W32_FAST_FAIL_DLOAD_PROTECTION_FAILURE 25 } - { W32_FAST_FAIL_UNSAFE_EXTENSION_CALL 26 } - { W32_FAST_FAIL_DEPRECATED_SERVICE_INVOKED 27 } - { W32_FAST_FAIL_INVALID_BUFFER_ACCESS 28 } - { W32_FAST_FAIL_INVALID_BALANCED_TREE 29 } - { W32_FAST_FAIL_INVALID_NEXT_THREAD 30 } - { W32_FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED 31 } - { W32_FAST_FAIL_APCS_DISABLED 32 } - { W32_FAST_FAIL_INVALID_IDLE_STATE 33 } - { W32_FAST_FAIL_MRDATA_PROTECTION_FAILURE 34 } - { W32_FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION 35 } - { W32_FAST_FAIL_INVALID_LOCK_STATE 36 } - { W32_FAST_FAIL_GUARD_JUMPTABLE 37 } - { W32_FAST_FAIL_INVALID_LONGJUMP_TARGET 38 } - { W32_FAST_FAIL_INVALID_DISPATCH_CONTEXT 39 } - { W32_FAST_FAIL_INVALID_THREAD 40 } - { W32_FAST_FAIL_INVALID_SYSCALL_NUMBER 41 } - { W32_FAST_FAIL_INVALID_FILE_OPERATION 42 } - { W32_FAST_FAIL_LPAC_ACCESS_DENIED 43 } - { W32_FAST_FAIL_GUARD_SS_FAILURE 44 } - { W32_FAST_FAIL_LOADER_CONTINUITY_FAILURE 45 } - { W32_FAST_FAIL_GUARD_EXPORT_SUPPRESSION_FAILURE 46 } - { W32_FAST_FAIL_INVALID_CONTROL_STACK 47 } - { W32_FAST_FAIL_SET_CONTEXT_DENIED 48 } - { W32_FAST_FAIL_INVALID_IAT 49 } - { W32_FAST_FAIL_HEAP_METADATA_CORRUPTION 50 } - { W32_FAST_FAIL_PAYLOAD_RESTRICTION_VIOLATION 51 } - { W32_FAST_FAIL_LOW_LABEL_ACCESS_DENIED 52 } - { W32_FAST_FAIL_ENCLAVE_CALL_FAILURE 53 } - { W32_FAST_FAIL_UNHANDLED_LSS_EXCEPTON 54 } - { W32_FAST_FAIL_ADMINLESS_ACCESS_DENIED 55 } - { W32_FAST_FAIL_UNEXPECTED_CALL 56 } - { W32_FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS 57 } - { W32_FAST_FAIL_UNEXPECTED_HOST_BEHAVIOR 58 } - { W32_FAST_FAIL_FLAGS_CORRUPTION 59 } - { W32_FAST_FAIL_VEH_CORRUPTION 60 } - { W32_FAST_FAIL_ETW_CORRUPTION 61 } - { W32_FAST_FAIL_RIO_ABORT 62 } - { W32_FAST_FAIL_INVALID_PFN 63 } - { W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE_XFG 64 } - { W32_FAST_FAIL_CAST_GUARD 65 } - { W32_FAST_FAIL_HOST_VISIBILITY_CHANGE 66 } - { W32_FAST_FAIL_KERNEL_CET_SHADOW_STACK_ASSIST 67 } - { W32_FAST_FAIL_PATCH_CALLBACK_FAILED 68 } - { W32_FAST_FAIL_NTDLL_PATCH_FAILED 69 } - { W32_FAST_FAIL_INVALID_FLS_DATA 70 } -} diff --git a/src/ctrl/ctrl_inc.c b/src/ctrl/ctrl_inc.c deleted file mode 100644 index 45213b22..00000000 --- a/src/ctrl/ctrl_inc.c +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#undef LAYER_COLOR -#define LAYER_COLOR 0x969696ff - -#include "ctrl_core.c" diff --git a/src/ctrl/ctrl_inc.h b/src/ctrl/ctrl_inc.h deleted file mode 100644 index 3226e1b2..00000000 --- a/src/ctrl/ctrl_inc.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef CTRL_INC_H -#define CTRL_INC_H - -//////////////////////////////// -//~ NOTE(rjf): Control Layer Overview (2023/8/29) -// -// This layer's purpose is to provide access to the asynchronously-running, low -// level parts of a debugger, running on the debugger client. This primarily -// consists of process control, using the Demon layer (the lower level -// abstraction layer for process control, across multiple OSes), but including -// higher-level concepts, like stepping, breakpoint resolution, conditional -// breakpoint evaluation, and so on. Right now, this just includes process -// control *local to the debugger client machine*. But in the future, this can -// also include communication to multiple target machines, all running their -// own process controller, using the Demon layer. -// -// This part of a debugger must run asynchronously to prevent blocking the UI - -// ideally our debugger is designed such that, if targets are running, the -// debugger frontend is still usable for a variety of purposes. So, in short, -// the asynchronously-running "control thread", implemented by this layer, is -// tasked with communicating with a separately executing "user thread". This -// communication happens in two directions - `user -> ctrl`, and the reverse, -// `ctrl -> user`. -// -// In the case of `user -> ctrl` communication, this is done with a ring buffer -// of "messages" (`CTRL_Msg`), pushed via `ctrl_u2c_push_msgs`. These messages -// include commands like: launching targets, attaching to targets, killing -// targets, detaching from targets, stepping/running, or single stepping. -// -// In the case of `ctrl -> user` communication, this is done with a ring buffer -// of "events" (`CTRL_Event`), popped via `ctrl_c2u_pop_events`. These events -// include information about what happened during the execution of targets - -// including: process/module/thread creation, process/module/thread deletion, -// debug strings, thread name events, memory allocation events, and stop events -// (where stops can be caused by: user breakpoints, traps set for stepping, -// exceptions, halts, or errors). -// -// The various stepping algorithms are implemented with two concepts: (a) the -// "trap net", and (b) "spoofs". -// -// A "trap net" is a term which refers to a set of addresses paired with a set -// of behavioral flags. Before targets run, trap instructions are written to -// these addresses. After targets stop, these addresses are reset to their -// original bytes. These trap instructions cause the debugger's targets to -// stop executing, and based on which behavioral flags are associated with -// the instruction causing the stop, the control thread may adjust parameters -// used for running, then continue execution, or it will not resume target -// execution, and will report stopped events. These behavioral flags can -// include: single-stepping the stopped thread to execute the instruction at -// the trap location, saving a stack pointer "check value" (where this check -// value is compared against when making decisions about whether to continue -// running or not), and so on. It's complicated to unpack why exactly these -// behaviors are useful, but the TL;DR of it is that they are used for a -// variety of stepping behaviors. For example, when doing a "step into" step, -// a `call` instruction can have a trap set at it, and will be marked with -// a "single-step-after" trap flag, as well as the "end stepping" trap flag, -// such that the step operation will complete after the `call` has executed. -// -// A "spoof" is a feature the control layer uses to detect when some thread -// returns from a particular sub-callstack. This is useful when implementing -// "step over" in functions that may be recursive. In short, unlike a trap, -// which writes a trap instruction (like `int3`) into an instruction stream, -// a spoof overwrites a *return address* on some thread's *stack*. This return -// address is not a valid address for executing code -- it is simply a value -// that the debugger can recognize, such that it is notified when the thread -// returns from some level in a callstack. When the thread exits some function, -// it will return to the "spoofed" address, and it will immediately hit an -// exception, because the spoofed address will not be a valid address for -// code execution. At that point, the debugger can move the thread back to -// the pre-spoof return address, and resume execution. - -#include "ctrl_core.h" - -#endif // CTRL_INC_H diff --git a/src/ctrl/generated/ctrl.meta.c b/src/ctrl/generated/ctrl.meta.c deleted file mode 100644 index 09d20976..00000000 --- a/src/ctrl/generated/ctrl.meta.c +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//- GENERATED CODE - -C_LINKAGE_BEGIN -String8 ctrl_entity_kind_code_name_table[12] = -{ -{0}, -str8_lit_comp("root"), -str8_lit_comp("machine"), -str8_lit_comp("process"), -str8_lit_comp("thread"), -str8_lit_comp("module"), -str8_lit_comp("entry_point"), -str8_lit_comp("debug_info_path"), -str8_lit_comp("pending_thread_name"), -str8_lit_comp("pending_thread_color"), -str8_lit_comp("breakpoint"), -str8_lit_comp("address_range_annotation"), -}; - -String8 ctrl_entity_kind_display_string_table[12] = -{ -{0}, -str8_lit_comp("Root"), -str8_lit_comp("Machine"), -str8_lit_comp("Process"), -str8_lit_comp("Thread"), -str8_lit_comp("Module"), -str8_lit_comp("EntryPoint"), -str8_lit_comp("DebugInfoPath"), -str8_lit_comp("PendingThreadName"), -str8_lit_comp("PendingThreadColor"), -str8_lit_comp("Breakpoint"), -str8_lit_comp("AddressRangeAnnotation"), -}; - -U32 ctrl_exception_code_kind_code_table[38] = -{ -0, -0x40010005, -0x40010008, -0x40080201, -0x40080202, -0x0000071a, -0x80000002, -0xc0000005, -0xc0000006, -0xc0000008, -0xc0000017, -0xc000001d, -0xc0000025, -0xc0000026, -0xc000008c, -0xc000008d, -0xc000008e, -0xc000008f, -0xc0000090, -0xc0000091, -0xc0000092, -0xc0000093, -0xc0000094, -0xc0000095, -0xc0000096, -0xc00000fd, -0xc0000135, -0xc0000138, -0xc0000139, -0xc0000142, -0xc00002b4, -0xc00002b5, -0xc0000420, -0xc06d007e, -0xc06d007f, -0xe073616e, -0xe0736171, -0x0000087a, -}; - -String8 ctrl_exception_code_kind_display_string_table[38] = -{ -{0}, -str8_lit_comp("(Win32) Control-C"), -str8_lit_comp("(Win32) Control-Break"), -str8_lit_comp("(Win32) WinRT Originate Error"), -str8_lit_comp("(Win32) WinRT Transform Error"), -str8_lit_comp("(Win32) RPC Call Cancelled"), -str8_lit_comp("(Win32) Data Type Misalignment"), -str8_lit_comp("(Win32) Access Violation"), -str8_lit_comp("(Win32) In Page Error"), -str8_lit_comp("(Win32) Invalid Handle Specified"), -str8_lit_comp("(Win32) Not Enough Quota"), -str8_lit_comp("(Win32) Illegal Instruction"), -str8_lit_comp("(Win32) Cannot Continue From Exception"), -str8_lit_comp("(Win32) Invalid Exception Disposition Returned By Handler"), -str8_lit_comp("(Win32) Array Bounds Exceeded"), -str8_lit_comp("(Win32) Floating-Point Denormal Operand"), -str8_lit_comp("(Win32) Floating-Point Division By Zero"), -str8_lit_comp("(Win32) Floating-Point Inexact Result"), -str8_lit_comp("(Win32) Floating-Point Invalid Operation"), -str8_lit_comp("(Win32) Floating-Point Overflow"), -str8_lit_comp("(Win32) Floating-Point Stack Check"), -str8_lit_comp("(Win32) Floating-Point Underflow"), -str8_lit_comp("(Win32) Integer Division By Zero"), -str8_lit_comp("(Win32) Integer Overflow"), -str8_lit_comp("(Win32) Privileged Instruction"), -str8_lit_comp("(Win32) Stack Overflow"), -str8_lit_comp("(Win32) Unable To Locate DLL"), -str8_lit_comp("(Win32) Ordinal Not Found"), -str8_lit_comp("(Win32) Entry Point Not Found"), -str8_lit_comp("(Win32) DLL Initialization Failed"), -str8_lit_comp("(Win32) Floating Point SSE Multiple Faults"), -str8_lit_comp("(Win32) Floating Point SSE Multiple Traps"), -str8_lit_comp("(Win32) Assertion Failed"), -str8_lit_comp("(Win32) Module Not Found"), -str8_lit_comp("(Win32) Procedure Not Found"), -str8_lit_comp("(Win32) Sanitizer Error Detected"), -str8_lit_comp("(Win32) Sanitizer Raw Access Violation"), -str8_lit_comp("(Win32) DirectX Debug Layer"), -}; - -String8 ctrl_exception_code_kind_lowercase_code_string_table[38] = -{ -{0}, -str8_lit_comp("win32_ctrl_c"), -str8_lit_comp("win32_ctrl_break"), -str8_lit_comp("win32_win_rt_originate_error"), -str8_lit_comp("win32_win_rt_transform_error"), -str8_lit_comp("win32_rpc_call_cancelled"), -str8_lit_comp("win32_datatype_misalignment"), -str8_lit_comp("win32_access_violation"), -str8_lit_comp("win32_in_page_error"), -str8_lit_comp("win32_invalid_handle"), -str8_lit_comp("win32_not_enough_quota"), -str8_lit_comp("win32_illegal_instruction"), -str8_lit_comp("win32_cannot_continue_exception"), -str8_lit_comp("win32_invalid_exception_disposition"), -str8_lit_comp("win32_array_bounds_exceeded"), -str8_lit_comp("win32_floating_point_denormal_operand"), -str8_lit_comp("win32_floating_point_division_by_zero"), -str8_lit_comp("win32_floating_point_inexact_result"), -str8_lit_comp("win32_floating_point_invalid_operation"), -str8_lit_comp("win32_floating_point_overflow"), -str8_lit_comp("win32_floating_point_stack_check"), -str8_lit_comp("win32_floating_point_underflow"), -str8_lit_comp("win32_integer_division_by_zero"), -str8_lit_comp("win32_integer_overflow"), -str8_lit_comp("win32_privileged_instruction"), -str8_lit_comp("win32_stack_overflow"), -str8_lit_comp("win32_unable_to_locate_dll"), -str8_lit_comp("win32_ordinal_not_found"), -str8_lit_comp("win32_entry_point_not_found"), -str8_lit_comp("win32_dll_initialization_failed"), -str8_lit_comp("win32_floating_point_sse_multiple_faults"), -str8_lit_comp("win32_floating_point_sse_multiple_traps"), -str8_lit_comp("win32_assertion_failed"), -str8_lit_comp("win32_module_not_found"), -str8_lit_comp("win32_procedure_not_found"), -str8_lit_comp("win32_sanitizer_error_detected"), -str8_lit_comp("win32_sanitizer_raw_access_violation"), -str8_lit_comp("win32_directx_debug_layer"), -}; - -B8 ctrl_exception_code_kind_default_enable_table[38] = -{ -0, -1, -1, -0, -0, -0, -0, -1, -0, -1, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -0, -1, -0, -0, -1, -0, -1, -}; - -C_LINKAGE_END - diff --git a/src/ctrl/generated/ctrl.meta.h b/src/ctrl/generated/ctrl.meta.h deleted file mode 100644 index 5495f320..00000000 --- a/src/ctrl/generated/ctrl.meta.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//- GENERATED CODE - -#ifndef CTRL_META_H -#define CTRL_META_H - -typedef enum CTRL_EntityKind -{ -CTRL_EntityKind_Null, -CTRL_EntityKind_Root, -CTRL_EntityKind_Machine, -CTRL_EntityKind_Process, -CTRL_EntityKind_Thread, -CTRL_EntityKind_Module, -CTRL_EntityKind_EntryPoint, -CTRL_EntityKind_DebugInfoPath, -CTRL_EntityKind_PendingThreadName, -CTRL_EntityKind_PendingThreadColor, -CTRL_EntityKind_Breakpoint, -CTRL_EntityKind_AddressRangeAnnotation, -CTRL_EntityKind_COUNT, -} CTRL_EntityKind; - -typedef enum CTRL_ExceptionCodeKind -{ -CTRL_ExceptionCodeKind_Null, -CTRL_ExceptionCodeKind_Win32CtrlC, -CTRL_ExceptionCodeKind_Win32CtrlBreak, -CTRL_ExceptionCodeKind_Win32WinRTOriginateError, -CTRL_ExceptionCodeKind_Win32WinRTTransformError, -CTRL_ExceptionCodeKind_Win32RPCCallCancelled, -CTRL_ExceptionCodeKind_Win32DatatypeMisalignment, -CTRL_ExceptionCodeKind_Win32AccessViolation, -CTRL_ExceptionCodeKind_Win32InPageError, -CTRL_ExceptionCodeKind_Win32InvalidHandle, -CTRL_ExceptionCodeKind_Win32NotEnoughQuota, -CTRL_ExceptionCodeKind_Win32IllegalInstruction, -CTRL_ExceptionCodeKind_Win32CannotContinueException, -CTRL_ExceptionCodeKind_Win32InvalidExceptionDisposition, -CTRL_ExceptionCodeKind_Win32ArrayBoundsExceeded, -CTRL_ExceptionCodeKind_Win32FloatingPointDenormalOperand, -CTRL_ExceptionCodeKind_Win32FloatingPointDivisionByZero, -CTRL_ExceptionCodeKind_Win32FloatingPointInexactResult, -CTRL_ExceptionCodeKind_Win32FloatingPointInvalidOperation, -CTRL_ExceptionCodeKind_Win32FloatingPointOverflow, -CTRL_ExceptionCodeKind_Win32FloatingPointStackCheck, -CTRL_ExceptionCodeKind_Win32FloatingPointUnderflow, -CTRL_ExceptionCodeKind_Win32IntegerDivisionByZero, -CTRL_ExceptionCodeKind_Win32IntegerOverflow, -CTRL_ExceptionCodeKind_Win32PrivilegedInstruction, -CTRL_ExceptionCodeKind_Win32StackOverflow, -CTRL_ExceptionCodeKind_Win32UnableToLocateDLL, -CTRL_ExceptionCodeKind_Win32OrdinalNotFound, -CTRL_ExceptionCodeKind_Win32EntryPointNotFound, -CTRL_ExceptionCodeKind_Win32DLLInitializationFailed, -CTRL_ExceptionCodeKind_Win32FloatingPointSSEMultipleFaults, -CTRL_ExceptionCodeKind_Win32FloatingPointSSEMultipleTraps, -CTRL_ExceptionCodeKind_Win32AssertionFailed, -CTRL_ExceptionCodeKind_Win32ModuleNotFound, -CTRL_ExceptionCodeKind_Win32ProcedureNotFound, -CTRL_ExceptionCodeKind_Win32SanitizerErrorDetected, -CTRL_ExceptionCodeKind_Win32SanitizerRawAccessViolation, -CTRL_ExceptionCodeKind_Win32DirectXDebugLayer, -CTRL_ExceptionCodeKind_COUNT, -} CTRL_ExceptionCodeKind; - -C_LINKAGE_BEGIN -extern String8 ctrl_entity_kind_code_name_table[12]; -extern String8 ctrl_entity_kind_display_string_table[12]; -extern U32 ctrl_exception_code_kind_code_table[38]; -extern String8 ctrl_exception_code_kind_display_string_table[38]; -extern String8 ctrl_exception_code_kind_lowercase_code_string_table[38]; -extern B8 ctrl_exception_code_kind_default_enable_table[38]; - -C_LINKAGE_END - -#endif // CTRL_META_H diff --git a/src/dbg_engine/dbg_engine.mdesk b/src/dbg_engine/dbg_engine.mdesk index 7ab14cad..225af11b 100644 --- a/src/dbg_engine/dbg_engine.mdesk +++ b/src/dbg_engine/dbg_engine.mdesk @@ -88,3 +88,193 @@ D_DevToggleTable: @expand(D_DevToggleTable a) `{&DEV_$(a.name), str8_lit_comp("$(a.name)")},` `};`; } + +//////////////////////////////// +//~ rjf: Entity Kinds + +@table(name code_name display_string) +CTRL_EntityKindTable: +{ + {Root root "Root" } + {Machine machine "Machine" } + {Process process "Process" } + {Thread thread "Thread" } + {Module module "Module" } + {EntryPoint entry_point "Entry Point" } + {DebugInfoPath debug_info_path "Debug Info Path" } + {PendingThreadName pending_thread_name "Pending Thread Name" } + {PendingThreadColor pending_thread_color "Pending Thread Color" } + {Breakpoint breakpoint "Breakpoint" } + {AddressRangeAnnotation address_range_annotation "Address Range Annotation" } +} + +@enum CTRL_EntityKind: +{ + Null, + @expand(CTRL_EntityKindTable a) `$(a.name)`, + COUNT, +} + +@data(String8) ctrl_entity_kind_code_name_table: +{ + `{0}`, + @expand(CTRL_EntityKindTable a) `str8_lit_comp("$(a.code_name)")` +} + +@data(String8) ctrl_entity_kind_display_string_table: +{ + `{0}`, + @expand(CTRL_EntityKindTable a) `str8_lit_comp("$(a.display_name)")` +} + +//////////////////////////////// +//~ rjf: Exception Codes + +@table(name lower_name code default display_string) +CTRL_ExceptionCodeKindTable: +{ + {Win32CtrlC win32_ctrl_c 0x40010005 1 "(Win32) Control-C" } + {Win32CtrlBreak win32_ctrl_break 0x40010008 1 "(Win32) Control-Break" } + {Win32WinRTOriginateError win32_win_rt_originate_error 0x40080201 0 "(Win32) WinRT Originate Error" } + {Win32WinRTTransformError win32_win_rt_transform_error 0x40080202 0 "(Win32) WinRT Transform Error" } + {Win32RPCCallCancelled win32_rpc_call_cancelled 0x0000071a 0 "(Win32) RPC Call Cancelled" } + {Win32DatatypeMisalignment win32_datatype_misalignment 0x80000002 0 "(Win32) Data Type Misalignment" } + {Win32AccessViolation win32_access_violation 0xc0000005 1 "(Win32) Access Violation" } + {Win32InPageError win32_in_page_error 0xc0000006 0 "(Win32) In Page Error" } + {Win32InvalidHandle win32_invalid_handle 0xc0000008 1 "(Win32) Invalid Handle Specified" } + {Win32NotEnoughQuota win32_not_enough_quota 0xc0000017 0 "(Win32) Not Enough Quota" } + {Win32IllegalInstruction win32_illegal_instruction 0xc000001d 0 "(Win32) Illegal Instruction" } + {Win32CannotContinueException win32_cannot_continue_exception 0xc0000025 0 "(Win32) Cannot Continue From Exception" } + {Win32InvalidExceptionDisposition win32_invalid_exception_disposition 0xc0000026 0 "(Win32) Invalid Exception Disposition Returned By Handler" } + {Win32ArrayBoundsExceeded win32_array_bounds_exceeded 0xc000008c 0 "(Win32) Array Bounds Exceeded" } + {Win32FloatingPointDenormalOperand win32_floating_point_denormal_operand 0xc000008d 0 "(Win32) Floating-Point Denormal Operand" } + {Win32FloatingPointDivisionByZero win32_floating_point_division_by_zero 0xc000008e 0 "(Win32) Floating-Point Division By Zero" } + {Win32FloatingPointInexactResult win32_floating_point_inexact_result 0xc000008f 0 "(Win32) Floating-Point Inexact Result" } + {Win32FloatingPointInvalidOperation win32_floating_point_invalid_operation 0xc0000090 0 "(Win32) Floating-Point Invalid Operation" } + {Win32FloatingPointOverflow win32_floating_point_overflow 0xc0000091 0 "(Win32) Floating-Point Overflow" } + {Win32FloatingPointStackCheck win32_floating_point_stack_check 0xc0000092 0 "(Win32) Floating-Point Stack Check" } + {Win32FloatingPointUnderflow win32_floating_point_underflow 0xc0000093 0 "(Win32) Floating-Point Underflow" } + {Win32IntegerDivisionByZero win32_integer_division_by_zero 0xc0000094 0 "(Win32) Integer Division By Zero" } + {Win32IntegerOverflow win32_integer_overflow 0xc0000095 0 "(Win32) Integer Overflow" } + {Win32PrivilegedInstruction win32_privileged_instruction 0xc0000096 0 "(Win32) Privileged Instruction" } + {Win32StackOverflow win32_stack_overflow 0xc00000fd 0 "(Win32) Stack Overflow" } + {Win32UnableToLocateDLL win32_unable_to_locate_dll 0xc0000135 0 "(Win32) Unable To Locate DLL" } + {Win32OrdinalNotFound win32_ordinal_not_found 0xc0000138 0 "(Win32) Ordinal Not Found" } + {Win32EntryPointNotFound win32_entry_point_not_found 0xc0000139 0 "(Win32) Entry Point Not Found" } + {Win32DLLInitializationFailed win32_dll_initialization_failed 0xc0000142 0 "(Win32) DLL Initialization Failed" } + {Win32FloatingPointSSEMultipleFaults win32_floating_point_sse_multiple_faults 0xc00002b4 0 "(Win32) Floating Point SSE Multiple Faults" } + {Win32FloatingPointSSEMultipleTraps win32_floating_point_sse_multiple_traps 0xc00002b5 0 "(Win32) Floating Point SSE Multiple Traps" } + {Win32AssertionFailed win32_assertion_failed 0xc0000420 1 "(Win32) Assertion Failed" } + {Win32ModuleNotFound win32_module_not_found 0xc06d007e 0 "(Win32) Module Not Found" } + {Win32ProcedureNotFound win32_procedure_not_found 0xc06d007f 0 "(Win32) Procedure Not Found" } + {Win32SanitizerErrorDetected win32_sanitizer_error_detected 0xe073616e 1 "(Win32) Sanitizer Error Detected" } + {Win32SanitizerRawAccessViolation win32_sanitizer_raw_access_violation 0xe0736171 0 "(Win32) Sanitizer Raw Access Violation" } + {Win32DirectXDebugLayer win32_directx_debug_layer 0x0000087a 1 "(Win32) DirectX Debug Layer" } +} + +@enum CTRL_ExceptionCodeKind: +{ + Null, + @expand(CTRL_ExceptionCodeKindTable a) `$(a.name)`, + COUNT, +} + +@data(U32) ctrl_exception_code_kind_code_table: +{ + `0`; + @expand(CTRL_ExceptionCodeKindTable a) `$(a.code)`; +} + +@data(String8) ctrl_exception_code_kind_display_string_table: +{ + `{0}`; + @expand(CTRL_ExceptionCodeKindTable a) `str8_lit_comp("$(a.display_string)")`; +} + +@data(String8) ctrl_exception_code_kind_lowercase_code_string_table: +{ + `{0}`; + @expand(CTRL_ExceptionCodeKindTable a) `str8_lit_comp("$(a.lower_name)")`; +} + +@data(B8) ctrl_exception_code_kind_default_enable_table: +{ + `0`; + @expand(CTRL_ExceptionCodeKindTable a) `$(a.default)`; +} + +//////////////////////////////// +//~ rjf: Exception Sub-Codes + +@table(name, value) +CTRL_ExceptionSubCodeKindTable: +{ + { W32_FAST_FAIL_LEGACY_GS_VIOLATION 0 } + { W32_FAST_FAIL_VTGUARD_CHECK_FAILURE 1 } + { W32_FAST_FAIL_STACK_COOKIE_CHECK_FAILURE 2 } + { W32_FAST_FAIL_CORRUPT_LIST_ENTRY 3 } + { W32_FAST_FAIL_INCORRECT_STACK 4 } + { W32_FAST_FAIL_INVALID_ARG 5 } + { W32_FAST_FAIL_GS_COOKIE_INIT 6 } + { W32_FAST_FAIL_FATAL_APP_EXIT 7 } + { W32_FAST_FAIL_RANGE_CHECK_FAILURE 8 } + { W32_FAST_FAIL_UNSAFE_REGISTRY_ACCESS 9 } + { W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE 10 } + { W32_FAST_FAIL_GUARD_WRITE_CHECK_FAILURE 11 } + { W32_FAST_FAIL_INVALID_FIBER_SWITCH 12 } + { W32_FAST_FAIL_INVALID_SET_OF_CONTEXT 13 } + { W32_FAST_FAIL_INVALID_REFERENCE_COUNT 14 } + { W32_FAST_FAIL_INVALID_JUMP_BUFFER 18 } + { W32_FAST_FAIL_MRDATA_MODIFIED 19 } + { W32_FAST_FAIL_CERTIFICATION_FAILURE 20 } + { W32_FAST_FAIL_INVALID_EXCEPTION_CHAIN 21 } + { W32_FAST_FAIL_CRYPTO_LIBRARY 22 } + { W32_FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT 23 } + { W32_FAST_FAIL_INVALID_IMAGE_BASE 24 } + { W32_FAST_FAIL_DLOAD_PROTECTION_FAILURE 25 } + { W32_FAST_FAIL_UNSAFE_EXTENSION_CALL 26 } + { W32_FAST_FAIL_DEPRECATED_SERVICE_INVOKED 27 } + { W32_FAST_FAIL_INVALID_BUFFER_ACCESS 28 } + { W32_FAST_FAIL_INVALID_BALANCED_TREE 29 } + { W32_FAST_FAIL_INVALID_NEXT_THREAD 30 } + { W32_FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED 31 } + { W32_FAST_FAIL_APCS_DISABLED 32 } + { W32_FAST_FAIL_INVALID_IDLE_STATE 33 } + { W32_FAST_FAIL_MRDATA_PROTECTION_FAILURE 34 } + { W32_FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION 35 } + { W32_FAST_FAIL_INVALID_LOCK_STATE 36 } + { W32_FAST_FAIL_GUARD_JUMPTABLE 37 } + { W32_FAST_FAIL_INVALID_LONGJUMP_TARGET 38 } + { W32_FAST_FAIL_INVALID_DISPATCH_CONTEXT 39 } + { W32_FAST_FAIL_INVALID_THREAD 40 } + { W32_FAST_FAIL_INVALID_SYSCALL_NUMBER 41 } + { W32_FAST_FAIL_INVALID_FILE_OPERATION 42 } + { W32_FAST_FAIL_LPAC_ACCESS_DENIED 43 } + { W32_FAST_FAIL_GUARD_SS_FAILURE 44 } + { W32_FAST_FAIL_LOADER_CONTINUITY_FAILURE 45 } + { W32_FAST_FAIL_GUARD_EXPORT_SUPPRESSION_FAILURE 46 } + { W32_FAST_FAIL_INVALID_CONTROL_STACK 47 } + { W32_FAST_FAIL_SET_CONTEXT_DENIED 48 } + { W32_FAST_FAIL_INVALID_IAT 49 } + { W32_FAST_FAIL_HEAP_METADATA_CORRUPTION 50 } + { W32_FAST_FAIL_PAYLOAD_RESTRICTION_VIOLATION 51 } + { W32_FAST_FAIL_LOW_LABEL_ACCESS_DENIED 52 } + { W32_FAST_FAIL_ENCLAVE_CALL_FAILURE 53 } + { W32_FAST_FAIL_UNHANDLED_LSS_EXCEPTON 54 } + { W32_FAST_FAIL_ADMINLESS_ACCESS_DENIED 55 } + { W32_FAST_FAIL_UNEXPECTED_CALL 56 } + { W32_FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS 57 } + { W32_FAST_FAIL_UNEXPECTED_HOST_BEHAVIOR 58 } + { W32_FAST_FAIL_FLAGS_CORRUPTION 59 } + { W32_FAST_FAIL_VEH_CORRUPTION 60 } + { W32_FAST_FAIL_ETW_CORRUPTION 61 } + { W32_FAST_FAIL_RIO_ABORT 62 } + { W32_FAST_FAIL_INVALID_PFN 63 } + { W32_FAST_FAIL_GUARD_ICALL_CHECK_FAILURE_XFG 64 } + { W32_FAST_FAIL_CAST_GUARD 65 } + { W32_FAST_FAIL_HOST_VISIBILITY_CHANGE 66 } + { W32_FAST_FAIL_KERNEL_CET_SHADOW_STACK_ASSIST 67 } + { W32_FAST_FAIL_PATCH_CALLBACK_FAILED 68 } + { W32_FAST_FAIL_NTDLL_PATCH_FAILED 69 } + { W32_FAST_FAIL_INVALID_FLS_DATA 70 } +} diff --git a/src/dbg_engine/dbg_engine_core.c b/src/dbg_engine/dbg_engine_core.c index 8baa5c00..d2143055 100644 --- a/src/dbg_engine/dbg_engine_core.c +++ b/src/dbg_engine/dbg_engine_core.c @@ -1,2260 +1,7 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#undef LAYER_COLOR -#define LAYER_COLOR 0x4d9ae3ff - //////////////////////////////// //~ rjf: Generated Code -#include "dbg_engine/generated/dbg_engine.meta.c" - -//////////////////////////////// -//~ rjf: Basic Helpers - -#if !defined(XXH_IMPLEMENTATION) -# define XXH_IMPLEMENTATION -# define XXH_STATIC_LINKING_ONLY -# include "third_party/xxHash/xxhash.h" -#endif - -internal U64 -d_hash_from_seed_string(U64 seed, String8 string) -{ - U64 result = XXH3_64bits_withSeed(string.str, string.size, seed); - return result; -} - -internal U64 -d_hash_from_string(String8 string) -{ - return d_hash_from_seed_string(5381, string); -} - -internal U64 -d_hash_from_seed_string__case_insensitive(U64 seed, String8 string) -{ - U64 result = seed; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + lower_from_char(string.str[i]); - } - return result; -} - -internal U64 -d_hash_from_string__case_insensitive(String8 string) -{ - return d_hash_from_seed_string__case_insensitive(5381, string); -} - -//////////////////////////////// -//~ rjf: Breakpoints - -internal D_BreakpointArray -d_breakpoint_array_copy(Arena *arena, D_BreakpointArray *src) -{ - D_BreakpointArray dst = {0}; - dst.count = src->count; - dst.v = push_array(arena, D_Breakpoint, dst.count); - MemoryCopy(dst.v, src->v, sizeof(dst.v[0])*dst.count); - for(U64 idx = 0; idx < dst.count; idx += 1) - { - dst.v[idx].file_path = push_str8_copy(arena, dst.v[idx].file_path); - dst.v[idx].vaddr_expr = push_str8_copy(arena, dst.v[idx].vaddr_expr); - dst.v[idx].condition = push_str8_copy(arena, dst.v[idx].condition); - } - return dst; -} - -//////////////////////////////// -//~ rjf: Path Map Application - -internal String8List -d_possible_path_overrides_from_maps_path(Arena *arena, D_PathMapArray *path_maps, String8 file_path) -{ - // NOTE(rjf): This path, given some target file path, scans all file path map - // overrides, and collects the set of file paths which could've redirected - // to the target file path given the set of file path maps. - // - // For example, if I have a rule saying D:/devel/ maps to C:/devel/, and I - // feed in C:/devel/foo/bar.txt, then this path will construct - // D:/devel/foo/bar.txt, as a possible option. - // - // It will also preserve C:/devel/foo/bar.txt in the resultant list, so that - // overrideless files still work through this path, and both redirected - // files and non-redirected files can go through the same path. - // - String8List result = {0}; - str8_list_push(arena, &result, file_path); - Temp scratch = scratch_begin(&arena, 1); - PathStyle pth_style = PathStyle_Relative; - String8List pth_parts = path_normalized_list_from_string(scratch.arena, file_path, &pth_style); - { - for(U64 idx = 0; idx < path_maps->count; idx += 1) - { - //- rjf: unpack link - D_PathMap *map = &path_maps->v[idx]; - PathStyle src_style = PathStyle_Relative; - PathStyle dst_style = PathStyle_Relative; - String8List src_parts = path_normalized_list_from_string(scratch.arena, map->src, &src_style); - String8List dst_parts = path_normalized_list_from_string(scratch.arena, map->dst, &dst_style); - - //- rjf: determine if this link can possibly redirect to the target file path - B32 dst_redirects_to_pth = 0; - String8Node *non_redirected_pth_first = 0; - if(dst_style == pth_style && dst_parts.first != 0 && pth_parts.first != 0) - { - dst_redirects_to_pth = 1; - String8Node *dst_n = dst_parts.first; - String8Node *pth_n = pth_parts.first; - for(;dst_n != 0 && pth_n != 0; dst_n = dst_n->next, pth_n = pth_n->next) - { - if(!str8_match(dst_n->string, pth_n->string, StringMatchFlag_CaseInsensitive)) - { - dst_redirects_to_pth = 0; - break; - } - non_redirected_pth_first = pth_n->next; - } - } - - //- rjf: if this link can redirect to this path via `src` -> `dst`, compute - // possible full source path, by taking `src` and appending non-redirected - // suffix (which did not show up in `dst`) - if(dst_redirects_to_pth) - { - String8List candidate_parts = src_parts; - for(String8Node *p = non_redirected_pth_first; p != 0; p = p->next) - { - str8_list_push(scratch.arena, &candidate_parts, p->string); - } - StringJoin join = {0}; - join.sep = str8_lit("/"); - String8 candidate_path = str8_list_join(arena, &candidate_parts, &join); - str8_list_push(arena, &result, candidate_path); - } - } - } - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Debug Info Extraction Type Pure Functions - -internal D_LineList -d_line_list_copy(Arena *arena, D_LineList *list) -{ - D_LineList dst = {0}; - for(D_LineNode *src_n = list->first; src_n != 0; src_n = src_n->next) - { - D_LineNode *dst_n = push_array(arena, D_LineNode, 1); - MemoryCopyStruct(dst_n, src_n); - dst_n->v.file_path = push_str8_copy(arena, dst_n->v.file_path); - SLLQueuePush(dst.first, dst.last, dst_n); - dst.count += 1; - } - return dst; -} - -//////////////////////////////// -//~ rjf: Command Type Functions - -//- rjf: command parameters - -internal D_CmdParams -d_cmd_params_copy(Arena *arena, D_CmdParams *src) -{ - D_CmdParams dst = {0}; - MemoryCopyStruct(&dst, src); - dst.file_path = push_str8_copy(arena, dst.file_path); - dst.targets.v = push_array(arena, D_Target, dst.targets.count); - MemoryCopy(dst.targets.v, src->targets.v, sizeof(D_Target)*dst.targets.count); - for(U64 idx = 0; idx < dst.targets.count; idx += 1) - { - D_Target *target = &dst.targets.v[idx]; - target->exe = push_str8_copy(arena, target->exe); - target->args = push_str8_copy(arena, target->args); - target->working_directory = push_str8_copy(arena, target->working_directory); - target->custom_entry_point_name = push_str8_copy(arena, target->custom_entry_point_name); - target->env = str8_list_copy(arena, &target->env); - } - return dst; -} - -//- rjf: command lists - -internal void -d_cmd_list_push_new(Arena *arena, D_CmdList *cmds, D_CmdKind kind, D_CmdParams *params) -{ - D_CmdNode *n = push_array(arena, D_CmdNode, 1); - n->cmd.kind = kind; - n->cmd.params = d_cmd_params_copy(arena, params); - DLLPushBack(cmds->first, cmds->last, n); - cmds->count += 1; -} - -//////////////////////////////// -//~ rjf: Stepping "Trap Net" Builders - -// NOTE(rjf): Stepping Algorithm Overview (2024/01/17) -// -// The basic idea behind all stepping algorithms in the debugger are setting up -// a "trap net". A "trap net" is just a collection of high-level traps that are -// meant to "catch" a thread after letting it run. This trap net is submitted -// when the debugger frontend sends a "run" command (it is just empty if doing -// an actual 'run' or 'continue'). The debugger control thread then uses this -// trap net to program a state machine, to appropriately respond to a variety -// of debug events which it is passed from the OS. -// -// These are "high-level traps" because they can have specific behavioral info -// attached to them. These are encoded via the `CTRL_TrapFlags` type, which -// allow expression of the following behaviors: -// -// - end-stepping: when this trap is hit, it will end the stepping operation, -// and the target will not continue. -// - ignore-stack-pointer-check: when a trap in the trap net is hit, it will -// by-default be ignored if the thread's stack pointer has changed. this -// flag disables that behavior, for when the stack pointer is expected to -// change (e.g. step-out). -// - single-step-after-hit: when a trap with this flag is hit, the debugger -// will immediately single-step the thread which hit it. -// - save-stack-pointer: when a trap with this flag is hit, it will rewrite -// the stack pointer which is used to compare against, when deciding -// whether or not to filter a trap (based on stack pointer changes). -// - begin-spoof-mode: this enables "spoof mode". "spoof mode" is a special -// mode that disables the trap net entirely, and lets the thread run -// freely - but it catches the thread not with a trap, but a false return -// address. the debugger will overwrite a specific return address on the -// stack. this address will be overwritten with an address which does NOT -// point to a valid page, such that when the thread returns out of a -// particular call frame, the debugger will receive a debug event, at -// which point it can move the thread back to the correct return address, -// and resume with the trap net enabled. this is used in "step over" -// operations, because it avoids target <-> debugger "roundtrips" (e.g. -// target being stopped, debugger being called with debug events, then -// target resumes when debugger's control thread is done running) for -// recursions. (it doesn't make a difference with non-recursive calls, -// but the debugger can't detect the difference). -// -// Each stepping command prepares its trap net differently. -// -// --- Instruction Step Into -------------------------------------------------- -// In this case, no trap net is prepared, and only a low-level single-step is -// performed. -// -// --- Instruction Step Over -------------------------------------------------- -// To build a trap net for an instruction-level step-over, the next instruction -// at the thread's current instruction pointer is decoded. If it is a call -// instruction, or if it is a repeating instruction, then a trap with the -// 'end-stepping' behavior is placed at the instruction immediately following -// the 'call' instruction. -// -// --- Line Step Into --------------------------------------------------------- -// For a source-line step-into, the thread's instruction pointer is first used -// to look up into the debug info's line info, to find the machine code in the -// thread's current source line. Every instruction in this range is decoded. -// Traps are then built in the following way: -// -// - 'call' instruction -> if can decode call destination address, place -// "end-stepping | ignore-stack-pointer-check" trap at destination. if -// can't, "end-stepping | single-step-after | ignore-stack-pointer-check" -// trap at call. -// - 'jmp' (both unconditional & conditional) -> if can decode jump destination -// address, AND if jump leaves the line, place "end-stepping | ignore- -// stack-pointer-check" trap at destination. if can't, "end-stepping | -// single-step-after | ignore-stack-pointer-check" trap at jmp. if jump -// stays within the line, do nothing. -// - 'return' -> place "end-stepping | single-step-after" trap at return inst. -// - "end-stepping" trap is placed at the first address after the line, to -// catch all steps which simply proceed linearly through the instruction -// stream. -// -// --- Line Step Over --------------------------------------------------------- -// For a source-line step-over, the thread's instruction pointer is first used -// to look up into the debug info's line info, to find the machine code in the -// thread's current source line. Every instruction in this range is decoded. -// Traps are then built in the following way: -// -// - 'call' instruction -> place "single-step-after | begin-spoof-mode" trap at -// call instruction. -// - 'jmp' (both unconditional & conditional) -> if can decode jump destination -// address, AND if jump leaves the line, place "end-stepping" trap at -// destination. if can't, "end-stepping | single-step-after" trap at jmp. -// if jump stays within the line, do nothing. -// - 'return' -> place "end-stepping | single-step-after" trap at return inst. -// - "end-stepping" trap is placed at the first address after the line, to -// catch all steps which simply proceed linearly through the instruction -// stream. -// - for any instructions which may change the stack pointer, traps are placed -// at them with the "save-stack-pointer | single-step-after" behaviors. - -internal D_TrapNet -d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread) -{ - Temp scratch = scratch_begin(&arena, 1); - D_TrapNet result = {0}; - - // rjf: thread => unpacked info - CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process); - Arch arch = thread->arch; - U64 ip_vaddr = ctrl_rip_from_thread(&d_state->ctrl_entity_store->ctx, thread->handle); - - // rjf: ip => machine code - String8 machine_code = {0}; - { - Rng1U64 rng = r1u64(ip_vaddr, ip_vaddr+max_instruction_size_from_arch(arch)); - CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, rng, 0, os_now_microseconds()+5000); - machine_code = machine_code_slice.data; - } - - // rjf: build traps if machine code was read successfully - if(machine_code.size != 0) - { - result.good_read = 1; - - // rjf: decode instruction - DASM_Inst inst = dasm_inst_from_code(scratch.arena, arch, ip_vaddr, machine_code, DASM_Syntax_Intel); - - // rjf: call => run until call returns - if(inst.flags & DASM_InstFlag_Call || inst.flags & DASM_InstFlag_Repeats) - { - CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, ip_vaddr+inst.size}; - ctrl_trap_list_push(arena, &result.traps, &trap); - } - } - - scratch_end(scratch); - return result; -} - -internal D_TrapNet -d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread) -{ - Temp scratch = scratch_begin(&arena, 1); - log_infof("step_over_line:\n{\n"); - D_TrapNet result = {0}; - - // rjf: thread => info - Arch arch = thread->arch; - U64 ip_vaddr = ctrl_rip_from_thread(&d_state->ctrl_entity_store->ctx, thread->handle); - CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process); - CTRL_Entity *module = ctrl_module_from_process_vaddr(process, ip_vaddr); - DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); - log_infof("ip_vaddr: 0x%I64x\n", ip_vaddr); - log_infof("dbgi_key: {0x%I64x, 0x%I64x}\n", dbgi_key.u64[0], dbgi_key.u64[1]); - - // rjf: ip => line info - String8 file_path = {0}; - S64 line_num = 0; - Rng1U64 line_vaddr_rng = {0}; - { - U64 ip_voff = ctrl_voff_from_vaddr(module, ip_vaddr); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, ip_voff); - Rng1U64 line_voff_rng = {0}; - if(lines.first != 0) - { - line_voff_rng = lines.first->v.voff_range; - file_path = lines.first->v.file_path; - line_num = lines.first->v.pt.line; - line_vaddr_rng = ctrl_vaddr_range_from_voff_range(module, line_voff_rng); - log_infof("line: {%S:%I64i}\n", lines.first->v.file_path, lines.first->v.pt.line); - } - log_infof("voff_range: {0x%I64x, 0x%I64x}\n", line_voff_rng.min, line_voff_rng.max); - log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max); - } - - // rjf: gather other ranges on this same textual line, which we don't want to return to - Rng1U64List all_vaddr_ranges_on_same_line = {0}; - { - D_LineList lines = d_lines_from_dbgi_key_file_path_line_num(scratch.arena, dbgi_key, file_path, line_num); - for EachNode(n, D_LineNode, lines.first) - { - Rng1U64 voff_range = n->v.voff_range; - Rng1U64 vaddr_range = ctrl_vaddr_range_from_voff_range(module, voff_range); - rng1u64_list_push(scratch.arena, &all_vaddr_ranges_on_same_line, vaddr_range); - } - } - - // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range - // - // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging - // is enabled. This is enabled by default normally. - { - U64 opl_line_voff_rng = ctrl_voff_from_vaddr(module, line_vaddr_rng.max); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, opl_line_voff_rng); - if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) - { - line_vaddr_rng.max = ctrl_vaddr_from_voff(module, lines.first->v.voff_range.max); - } - } - - // rjf: line vaddr range => did we find anything successfully? - B32 good_line_info = (line_vaddr_rng.max != 0); - - // rjf: line vaddr range => line's machine code - String8 machine_code = {0}; - B32 good_machine_code = 0; - if(good_line_info) - { - CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, line_vaddr_rng, 0, os_now_microseconds()+50000); - machine_code = machine_code_slice.data; - good_machine_code = (machine_code.size >= dim_1u64(line_vaddr_rng) && !machine_code_slice.any_byte_bad); - LogInfoNamedBlockF("machine_code_slice") - { - log_infof("stale: %i\n", machine_code_slice.stale); - log_infof("any_byte_bad: %i\n", machine_code_slice.any_byte_bad); - log_infof("any_byte_changed: %i\n", machine_code_slice.any_byte_changed); - log_infof("bytes:\n[\n"); - for(U64 idx = 0; idx < machine_code_slice.data.size; idx += 1) - { - log_infof("0x%x,", machine_code_slice.data.str[idx]); - if(idx%16 == 15 || idx+1 == machine_code_slice.data.size) - { - log_infof("\n"); - } - } - log_infof("]\n"); - } - } - - // rjf: machine code => ctrl flow analysis - DASM_CtrlFlowInfo ctrl_flow_info = {0}; - if(good_machine_code) - { - ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, - DASM_InstFlag_Call| - DASM_InstFlag_Branch| - DASM_InstFlag_UnconditionalJump| - DASM_InstFlag_ChangesStackPointer| - DASM_InstFlag_Return, - arch, - line_vaddr_rng.min, - machine_code); - LogInfoNamedBlockF("ctrl_flow_info") - { - LogInfoNamedBlockF("exit_points") for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) - { - log_infof("{vaddr:0x%I64x, jump_dest_vaddr:0x%I64x, inst_flags:%x}\n", n->v.vaddr, n->v.jump_dest_vaddr, n->v.inst_flags); - } - } - } - - // rjf: push traps for all exit points - if(good_machine_code) for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) - { - DASM_CtrlFlowPoint *point = &n->v; - CTRL_TrapFlags flags = 0; - B32 add = 1; - U64 trap_addr = point->vaddr; - - // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. - if(point->inst_flags & (DASM_InstFlag_Branch| - DASM_InstFlag_UnconditionalJump| - DASM_InstFlag_Return)) - { - flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping); - - // rjf: omit if this jump stays inside of this line - if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) - { - add = 0; - } - - // rjf: omit if this jump stays inside of any range on this same textual line - for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first) - { - if(contains_1u64(n->v, point->jump_dest_vaddr)) - { - add = 0; - break; - } - } - - // rjf: trap @ destination, if we can - we can avoid a single-step this way. - if(point->jump_dest_vaddr != 0) - { - trap_addr = point->jump_dest_vaddr; - flags &= ~CTRL_TrapFlag_SingleStepAfterHit; - } - - } - - // rjf: call => place spoof at return spot in stack, single-step after hitting - else if(point->inst_flags & DASM_InstFlag_Call) - { - flags |= (CTRL_TrapFlag_BeginSpoofMode|CTRL_TrapFlag_SingleStepAfterHit); - } - - // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping - else if(point->inst_flags & DASM_InstFlag_ChangesStackPointer) - { - flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); - } - - // rjf: add if appropriate - if(add) - { - CTRL_Trap trap = {flags, trap_addr}; - ctrl_trap_list_push(arena, &result.traps, &trap); - } - } - - // rjf: push traps for natural linear flow - if(good_line_info && good_machine_code) - { - U64 line_opl_vaddr = line_vaddr_rng.max; - for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first) - { - U64 opl = n->v.max; - B32 opl_in_line = 0; - for EachNode(n2, Rng1U64Node, all_vaddr_ranges_on_same_line.first) - { - if(n != n2 && opl == n2->v.min) - { - opl_in_line = 1; - break; - } - } - if(!opl_in_line) - { - CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, opl}; - ctrl_trap_list_push(arena, &result.traps, &trap); - } - } - } - - // rjf: store goodness - if(good_machine_code) - { - result.good_line_info = good_line_info; - result.good_read = good_machine_code; - } - - // rjf: log - LogInfoNamedBlockF("traps") for(CTRL_TrapNode *n = result.traps.first; n != 0; n = n->next) - { - log_infof("{flags:0x%x, vaddr:0x%I64x}\n", n->v.flags, n->v.vaddr); - } - - scratch_end(scratch); - log_infof("}\n\n"); - return result; -} - -internal D_TrapNet -d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread) -{ - Temp scratch = scratch_begin(&arena, 1); - D_TrapNet result = {0}; - - // rjf: thread => info - Arch arch = thread->arch; - U64 ip_vaddr = ctrl_rip_from_thread(&d_state->ctrl_entity_store->ctx, thread->handle); - CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process); - CTRL_Entity *module = ctrl_module_from_process_vaddr(process, ip_vaddr); - DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); - - // rjf: ip => line info - String8 file_path = {0}; - S64 line_num = 0; - Rng1U64 line_vaddr_rng = {0}; - { - U64 ip_voff = ctrl_voff_from_vaddr(module, ip_vaddr); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, ip_voff); - Rng1U64 line_voff_rng = {0}; - if(lines.first != 0) - { - line_voff_rng = lines.first->v.voff_range; - file_path = lines.first->v.file_path; - line_num = lines.first->v.pt.line; - line_vaddr_rng = ctrl_vaddr_range_from_voff_range(module, line_voff_rng); - log_infof("line: {%S:%I64i}\n", lines.first->v.file_path, lines.first->v.pt.line); - } - log_infof("voff_range: {0x%I64x, 0x%I64x}\n", line_voff_rng.min, line_voff_rng.max); - log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max); - } - - // rjf: gather other ranges on this same textual line, which we don't want to return to - Rng1U64List all_vaddr_ranges_on_same_line = {0}; - { - D_LineList lines = d_lines_from_dbgi_key_file_path_line_num(scratch.arena, dbgi_key, file_path, line_num); - for EachNode(n, D_LineNode, lines.first) - { - Rng1U64 voff_range = n->v.voff_range; - Rng1U64 vaddr_range = ctrl_vaddr_range_from_voff_range(module, voff_range); - rng1u64_list_push(scratch.arena, &all_vaddr_ranges_on_same_line, vaddr_range); - } - } - - // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range - // - // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging - // is enabled. This is enabled by default normally. - { - U64 opl_line_voff_rng = ctrl_voff_from_vaddr(module, line_vaddr_rng.max); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, opl_line_voff_rng); - if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) - { - line_vaddr_rng.max = ctrl_vaddr_from_voff(module, lines.first->v.voff_range.max); - } - } - - // rjf: line vaddr range => did we find anything successfully? - B32 good_line_info = (line_vaddr_rng.max != 0); - - // rjf: line vaddr range => line's machine code - String8 machine_code = {0}; - B32 good_machine_code = 0; - if(good_line_info) - { - CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, line_vaddr_rng, 0, os_now_microseconds()+5000); - machine_code = machine_code_slice.data; - good_machine_code = (machine_code.size >= dim_1u64(line_vaddr_rng) && !machine_code_slice.any_byte_bad); - } - - // rjf: machine code => ctrl flow analysis - DASM_CtrlFlowInfo ctrl_flow_info = {0}; - if(good_machine_code) - { - ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, - DASM_InstFlag_Call| - DASM_InstFlag_Branch| - DASM_InstFlag_UnconditionalJump| - DASM_InstFlag_ChangesStackPointer| - DASM_InstFlag_Return, - arch, - line_vaddr_rng.min, - machine_code); - } - - // rjf: determine last - DASM_CtrlFlowPoint *last_call_point = 0; - if(good_machine_code) for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) - { - if(n->v.inst_flags & DASM_InstFlag_Call) - { - last_call_point = &n->v; - } - } - - // rjf: push traps for all exit points - if(good_machine_code) for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) - { - DASM_CtrlFlowPoint *point = &n->v; - CTRL_TrapFlags flags = 0; - B32 add = 1; - U64 trap_addr = point->vaddr; - - // rjf: if this is not the last call instruction in the control flow, - // and if we have no line info for this address, then do not add. - if(point != last_call_point && - point->inst_flags & DASM_InstFlag_Call && - point->jump_dest_vaddr != 0) - { - U64 jump_dest_vaddr = point->jump_dest_vaddr; - CTRL_Entity *jump_dest_module = ctrl_module_from_process_vaddr(process, jump_dest_vaddr); - U64 jump_dest_voff = ctrl_voff_from_vaddr(jump_dest_module, jump_dest_vaddr); - DI_Key jump_dest_dbgi_key = ctrl_dbgi_key_from_module(jump_dest_module); - D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, jump_dest_dbgi_key, jump_dest_voff); - if(lines.count == 0) - { - add = 0; - } - } - - // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. - if(point->inst_flags & (DASM_InstFlag_Call| - DASM_InstFlag_Branch| - DASM_InstFlag_UnconditionalJump| - DASM_InstFlag_Return)) - { - flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck); - - // rjf: omit if this jump stays inside of this line - if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) - { - add = 0; - } - - // rjf: omit if this jump stays inside of any range on this same textual line - for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first) - { - if(contains_1u64(n->v, point->jump_dest_vaddr)) - { - add = 0; - break; - } - } - - // rjf: trap @ destination, if we can - we can avoid a single-step this way. - if(point->jump_dest_vaddr != 0) - { - trap_addr = point->jump_dest_vaddr; - flags &= ~CTRL_TrapFlag_SingleStepAfterHit; - } - } - - // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping - else if(point->inst_flags & DASM_InstFlag_ChangesStackPointer) - { - flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); - } - - // rjf: add if appropriate - if(add) - { - CTRL_Trap trap = {flags, trap_addr}; - ctrl_trap_list_push(arena, &result.traps, &trap); - } - } - - // rjf: push traps for natural linear flow - if(good_line_info && good_machine_code) - { - U64 line_opl_vaddr = line_vaddr_rng.max; - for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first) - { - U64 opl = n->v.max; - B32 opl_in_line = 0; - for EachNode(n2, Rng1U64Node, all_vaddr_ranges_on_same_line.first) - { - if(n != n2 && opl == n2->v.min) - { - opl_in_line = 1; - break; - } - } - if(!opl_in_line) - { - CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, opl}; - ctrl_trap_list_push(arena, &result.traps, &trap); - } - } - } - - // rjf: store goodness - { - result.good_line_info = good_line_info; - result.good_read = good_machine_code; - } - - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Debug Info Lookups - -//- rjf: voff -> line info - -internal D_LineList -d_lines_from_dbgi_key_voff(Arena *arena, DI_Key dbgi_key, U64 voff) -{ - Temp scratch = scratch_begin(&arena, 1); - Access *access = access_open(); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, 0); - D_LineList result = {0}; - { - //- rjf: gather line tables - typedef struct LineTableNode LineTableNode; - struct LineTableNode - { - LineTableNode *next; - RDI_ParsedLineTable parsed_line_table; - }; - LineTableNode start_line_table = {0}; - RDI_Unit *unit = rdi_unit_from_voff(rdi, voff); - RDI_LineTable *unit_line_table = rdi_line_table_from_unit(rdi, unit); - rdi_parsed_from_line_table(rdi, unit_line_table, &start_line_table.parsed_line_table); - LineTableNode *top_line_table = 0; - RDI_Scope *scope = rdi_scope_from_voff(rdi, voff); - { - for(RDI_Scope *s = scope; - s->inline_site_idx != 0; - s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) - { - RDI_InlineSite *inline_site = rdi_element_from_name_idx(rdi, InlineSites, s->inline_site_idx); - if(inline_site->line_table_idx != 0) - { - LineTableNode *n = push_array(scratch.arena, LineTableNode, 1); - SLLStackPush(top_line_table, n); - RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, inline_site->line_table_idx); - rdi_parsed_from_line_table(rdi, line_table, &n->parsed_line_table); - } - } - } - SLLStackPush(top_line_table, &start_line_table); - - //- rjf: gather lines in each line table - Rng1U64 shallowest_voff_range = {0}; - for(LineTableNode *line_table_n = top_line_table; line_table_n != 0; line_table_n = line_table_n->next) - { - RDI_ParsedLineTable parsed_line_table = line_table_n->parsed_line_table; - U64 line_info_idx = rdi_line_info_idx_from_voff(&parsed_line_table, voff); - if(line_info_idx < parsed_line_table.count) - { - RDI_Line *line = &parsed_line_table.lines[line_info_idx]; - RDI_Column *column = (line_info_idx < parsed_line_table.col_count) ? &parsed_line_table.cols[line_info_idx] : 0; - RDI_SourceFile *file = rdi_element_from_name_idx(rdi, SourceFiles, line->file_idx); - String8List path_parts = {0}; - for(RDI_FilePathNode *fpn = rdi_element_from_name_idx(rdi, FilePathNodes, file->file_path_node_idx); - fpn != rdi_element_from_name_idx(rdi, FilePathNodes, 0); - fpn = rdi_element_from_name_idx(rdi, FilePathNodes, fpn->parent_path_node)) - { - String8 path_part = {0}; - path_part.str = rdi_string_from_idx(rdi, fpn->name_string_idx, &path_part.size); - str8_list_push_front(scratch.arena, &path_parts, path_part); - } - StringJoin join = {0}; - join.sep = str8_lit("/"); - String8 file_normalized_full_path = str8_list_join(arena, &path_parts, &join); - D_LineNode *n = push_array(arena, D_LineNode, 1); - SLLQueuePush(result.first, result.last, n); - result.count += 1; - if(line->file_idx != 0 && file_normalized_full_path.size != 0) - { - n->v.file_path = file_normalized_full_path; - } - n->v.pt = txt_pt(line->line_num, column ? column->col_first : 1); - n->v.voff_range = r1u64(parsed_line_table.voffs[line_info_idx], parsed_line_table.voffs[line_info_idx+1]); - n->v.dbgi_key = dbgi_key; - if(line_table_n == top_line_table) - { - shallowest_voff_range = n->v.voff_range; - } - } - } - - //- rjf: clamp all lines from all tables by shallowest (most unwound) range - for(D_LineNode *n = result.first; n != 0; n = n->next) - { - n->v.voff_range = intersect_1u64(n->v.voff_range, shallowest_voff_range); - } - } - access_close(access); - scratch_end(scratch); - return result; -} - -//- rjf: file:line -> line info - -// TODO(rjf): this depends on file path maps, needs to move - -internal D_LineListArray -d_lines_array_from_dbgi_key_file_path_line_range(Arena *arena, DI_Key dbgi_key, String8 file_path, Rng1S64 line_num_range) -{ - D_LineListArray array = {0}; - { - array.count = dim_1s64(line_num_range)+1; - array.v = push_array(arena, D_LineList, array.count); - di_key_list_push(arena, &array.dbgi_keys, dbgi_key); - } - Temp scratch = scratch_begin(&arena, 1); - U64 *lines_num_voffs = push_array(scratch.arena, U64, array.count); - Access *access = access_open(); - String8List overrides = rd_possible_overrides_from_file_path(scratch.arena, file_path); - for(String8Node *override_n = overrides.first; - override_n != 0; - override_n = override_n->next) - { - String8 file_path = override_n->string; - String8 file_path_normalized = rdim_normalize_path_str8(scratch.arena, file_path); - - // rjf: binary -> rdi - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); - - // rjf: file_path_normalized * rdi -> src_id - B32 good_src_id = 0; - U32 src_id = 0; - if(rdi != &rdi_parsed_nil) ProfScope("file_path_normalized * rdi -> src_id") - { - RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - good_src_id = 1; - src_id = ids[0]; - } - } - } - - // rjf: good src-id -> look up line info for visible range - if(good_src_id) ProfScope("good src-id -> look up line info for visible range") - { - RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); - RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); - RDI_ParsedSourceLineMap line_map = {0}; - rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); - U64 line_idx = 0; - for(S64 line_num = line_num_range.min; - line_num <= line_num_range.max; - line_num += 1, line_idx += 1) - { - D_LineList *list = &array.v[line_idx]; - U32 voff_count = 0; - U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); - if(lines_num_voffs[line_idx] < 8) ProfScope("iterate voffs (%i)", voff_count) for(U64 idx = 0; idx < voff_count; idx += 1) - { - U64 base_voff = voffs[idx]; - U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); - RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); - RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); - RDI_ParsedLineTable unit_line_info = {0}; - rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); - U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); - if(unit_line_info.voffs != 0) - { - Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); - S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; - D_LineNode *n = push_array(arena, D_LineNode, 1); - n->v.voff_range = range; - n->v.pt.line = (S64)actual_line; - n->v.pt.column = 1; - n->v.dbgi_key = dbgi_key; - SLLQueuePush(list->first, list->last, n); - list->count += 1; - lines_num_voffs[line_idx] += 1; - if(lines_num_voffs[line_idx] >= 8) - { - break; - } - } - } - } - } - } - access_close(access); - scratch_end(scratch); - return array; -} - -internal D_LineListArray -d_lines_array_from_file_path_line_range(Arena *arena, String8 file_path, Rng1S64 line_num_range) -{ - D_LineListArray array = {0}; - { - array.count = dim_1s64(line_num_range)+1; - array.v = push_array(arena, D_LineList, array.count); - } - Temp scratch = scratch_begin(&arena, 1); - U64 *lines_num_voffs = push_array(scratch.arena, U64, array.count); - DI_KeyArray dbgi_keys = di_push_all_loaded_keys(scratch.arena); - String8List overrides = rd_possible_overrides_from_file_path(scratch.arena, file_path); - for(String8Node *override_n = overrides.first; - override_n != 0; - override_n = override_n->next) - { - String8 file_path = override_n->string; - String8 file_path_normalized = rdim_normalize_path_str8(scratch.arena, file_path); - for EachIndex(idx, dbgi_keys.count) - { - Access *access = access_open(); - - // rjf: binary -> rdi - DI_Key key = dbgi_keys.v[idx]; - RDI_Parsed *rdi = di_rdi_from_key(access, key, 1, 0); - - // rjf: file_path_normalized * rdi -> src_id - B32 good_src_id = 0; - U32 src_id = 0; - if(rdi != &rdi_parsed_nil) ProfScope("file_path_normalized * rdi -> src_id") - { - RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - good_src_id = 1; - src_id = ids[0]; - } - } - } - - // rjf: good src-id -> look up line info for visible range - if(good_src_id) ProfScope("good src-id -> look up line info for visible range") - { - RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); - RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); - RDI_ParsedSourceLineMap line_map = {0}; - rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); - U64 line_idx = 0; - for(S64 line_num = line_num_range.min; - line_num <= line_num_range.max; - line_num += 1, line_idx += 1) - { - D_LineList *list = &array.v[line_idx]; - U32 voff_count = 0; - U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); - if(lines_num_voffs[line_idx] < 8) ProfScope("iterate voffs (%i)", voff_count) for(U64 idx = 0; idx < voff_count; idx += 1) - { - U64 base_voff = voffs[idx]; - U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); - RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); - RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); - RDI_ParsedLineTable unit_line_info = {0}; - rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); - U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); - if(unit_line_info.voffs != 0) - { - Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); - S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; - D_LineNode *n = push_array(arena, D_LineNode, 1); - n->v.voff_range = range; - n->v.pt.line = (S64)actual_line; - n->v.pt.column = 1; - n->v.dbgi_key = key; - SLLQueuePush(list->first, list->last, n); - list->count += 1; - lines_num_voffs[line_idx] += 1; - if(lines_num_voffs[line_idx] >= 8) - { - break; - } - } - } - } - } - - // rjf: good src id -> push to relevant dbgi keys - if(good_src_id) - { - di_key_list_push(arena, &array.dbgi_keys, key); - } - - access_close(access); - } - } - scratch_end(scratch); - return array; -} - -internal D_LineList -d_lines_from_dbgi_key_file_path_line_num(Arena *arena, DI_Key dbgi_key, String8 file_path, S64 line_num) -{ - D_LineListArray array = d_lines_array_from_dbgi_key_file_path_line_range(arena, dbgi_key, file_path, r1s64(line_num, line_num+1)); - D_LineList list = {0}; - if(array.count != 0) - { - list = array.v[0]; - } - return list; -} - -internal D_LineList -d_lines_from_file_path_line_num(Arena *arena, String8 file_path, S64 line_num) -{ - D_LineListArray array = d_lines_array_from_file_path_line_range(arena, file_path, r1s64(line_num, line_num+1)); - D_LineList list = {0}; - if(array.count != 0) - { - list = array.v[0]; - } - return list; -} - -//////////////////////////////// -//~ rjf: Process/Thread/Module Info Lookups - -internal U64 -d_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64 rip_vaddr) -{ - ProfBeginFunction(); - U64 base_vaddr = 0; - if(!d_ctrl_targets_running()) - { - Temp scratch = scratch_begin(0, 0); - - //- rjf: unpack module info - CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr); - U64 addr_size = byte_size_from_arch(process->arch); - - switch(process->tls_model) - { - case CTRL_TlsModel_Null: {}break; - case CTRL_TlsModel_WinodwsNt: - { - // read thread local base pointer out of TEB - U64 thread_local_base = root_vaddr; - U64 tls_addr_array = 0; - CTRL_ProcessMemorySlice tls_addr_array_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(thread_local_base, thread_local_base + addr_size), 0, 0); - String8 tls_array_vaddr_data = tls_addr_array_slice.data; - if(tls_array_vaddr_data.size == addr_size) - { - U64 tls_array_vaddr = 0; - MemoryCopyStr8(&tls_array_vaddr, tls_array_vaddr_data); - - // read thread local storage pointer (array of TLS pointers, one per module) - U64 tls_ptr_vaddr = tls_array_vaddr + module->tls_index * addr_size; - CTRL_ProcessMemorySlice result_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(tls_ptr_vaddr, tls_ptr_vaddr + addr_size), 0, 0); - String8 result_data = result_slice.data; - if(result_data.size == addr_size) - { - MemoryCopyStr8(&base_vaddr, result_data); - } - } - }break; - case CTRL_TlsModel_Gnu: - { - if(module->tls_index > 0) // zero is reserved for the generation counter - { - // read dynamic thread vector pointer (one per dynamic module) - U64 dtv_base = root_vaddr; - U64 dtv_size = addr_size * 2; // union dtv { size_t counter; struct dtv_pointer { void *val, *to_free; }; }; - U64 dtv_pointer_vaddr = dtv_base + module->tls_index * dtv_size; - CTRL_ProcessMemorySlice dtv_pointer_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(dtv_pointer_vaddr, dtv_pointer_vaddr + dtv_size), 0, 0); - String8 dtv_pointer_data = dtv_pointer_slice.data; - if(dtv_pointer_data.size == dtv_size) - { - U64 dtv_pointer = 0; - MemoryCopyStr8(&dtv_pointer, dtv_pointer_data); - - // verify that TLS block was allocated - U64 tls_dtv_unallocated = addr_size == 4 ? max_U32 : max_U64; - if(dtv_pointer != tls_dtv_unallocated) - { - base_vaddr = dtv_pointer + module->tls_offset; // add tls_offset because DT_NEEDED modules share TLS block with the main exe - } - } - } - }break; - default: {InvalidPath;}break; - } - - scratch_end(scratch); - } - ProfEnd(); - return base_vaddr; -} - -//////////////////////////////// -//~ rjf: Target Controls - -//- rjf: stopped info from the control thread - -internal CTRL_Event -d_ctrl_last_stop_event(void) -{ - return d_state->ctrl_last_stop_event; -} - -//////////////////////////////// -//~ rjf: Main State Accessors/Mutators - -//- rjf: frame data - -internal U64 -d_frame_index(void) -{ - return d_state->frame_index; -} - -//- rjf: control state - -internal D_RunKind -d_ctrl_last_run_kind(void) -{ - return d_state->ctrl_last_run_kind; -} - -internal U64 -d_ctrl_last_run_frame_idx(void) -{ - return d_state->ctrl_last_run_frame_idx; -} - -internal B32 -d_ctrl_targets_running(void) -{ - return d_state->ctrl_is_running; -} - -//- rjf: active entity based queries - -internal DI_KeyList -d_push_active_dbgi_key_list(Arena *arena) -{ - DI_KeyList dbgis = {0}; - CTRL_EntityArray modules = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Module); - for EachIndex(idx, modules.count) - { - CTRL_Entity *module = modules.v[idx]; - DI_Key key = ctrl_dbgi_key_from_module(module); - di_key_list_push(arena, &dbgis, key); - } - return dbgis; -} - -//- rjf: per-run caches - -internal U64 -d_query_cached_rip_from_thread(CTRL_Entity *thread) -{ - U64 result = d_query_cached_rip_from_thread_unwind(thread, 0); - return result; -} - -internal U64 -d_query_cached_rip_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count) -{ - U64 result = 0; - if(unwind_count == 0) - { - result = ctrl_rip_from_thread(&d_state->ctrl_entity_store->ctx, thread->handle); - } - else - { - Access *access = access_open(); - CTRL_CallStack callstack = ctrl_call_stack_from_thread(access, thread->handle, 1, 0); - if(callstack.concrete_frames_count != 0) - { - result = regs_rip_from_arch_block(thread->arch, callstack.concrete_frames[unwind_count%callstack.concrete_frames_count]->regs); - } - access_close(access); - } - return result; -} - -internal U64 -d_query_cached_cfa_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count) -{ - U64 cfa = 0; - Access *access = access_open(); - CTRL_CallStack callstack = ctrl_call_stack_from_thread(access, thread->handle, 1, 0); - if(callstack.concrete_frames_count != 0) - { - cfa = callstack.concrete_frames[unwind_count%callstack.concrete_frames_count]->cfa; - } - access_close(access); - return cfa; -} - -internal U64 -d_query_cached_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64 rip_vaddr) -{ - U64 result = 0; - for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->tls_base_caches); cache_idx += 1) - { - D_RunTLSBaseCache *cache = &d_state->tls_base_caches[(d_state->tls_base_cache_gen+cache_idx)%ArrayCount(d_state->tls_base_caches)]; - if(cache_idx == 0 && cache->slots_count == 0) - { - cache->slots_count = 256; - cache->slots = push_array(cache->arena, D_RunTLSBaseCacheSlot, cache->slots_count); - } - else if(cache->slots_count == 0) - { - break; - } - CTRL_Handle handle = process->handle; - U64 hash = d_hash_from_seed_string(d_hash_from_string(str8_struct(&handle)), str8_struct(&rip_vaddr)); - U64 slot_idx = hash%cache->slots_count; - D_RunTLSBaseCacheSlot *slot = &cache->slots[slot_idx]; - D_RunTLSBaseCacheNode *node = 0; - for(D_RunTLSBaseCacheNode *n = slot->first; n != 0; n = n->hash_next) - { - if(ctrl_handle_match(n->process, handle) && n->root_vaddr == root_vaddr && n->rip_vaddr == rip_vaddr) - { - node = n; - break; - } - } - if(node == 0) - { - U64 tls_base_vaddr = d_tls_base_vaddr_from_process_root_rip(process, root_vaddr, rip_vaddr); - if(tls_base_vaddr != 0) - { - node = push_array(cache->arena, D_RunTLSBaseCacheNode, 1); - SLLQueuePush_N(slot->first, slot->last, node, hash_next); - node->process = handle; - node->root_vaddr = root_vaddr; - node->rip_vaddr = rip_vaddr; - node->tls_base_vaddr = tls_base_vaddr; - } - } - if(node != 0 && node->tls_base_vaddr != 0) - { - result = node->tls_base_vaddr; - break; - } - } - return result; -} - -internal E_String2NumMap * -d_query_cached_locals_map_from_dbgi_key_voff(DI_Key dbgi_key, U64 voff) -{ - ProfBeginFunction(); - E_String2NumMap *map = &e_string2num_map_nil; - for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->locals_caches); cache_idx += 1) - { - D_RunLocalsCache *cache = &d_state->locals_caches[(d_state->locals_cache_gen+cache_idx)%ArrayCount(d_state->locals_caches)]; - if(cache_idx == 0 && cache->table_size == 0) - { - cache->table_size = 256; - cache->table = push_array(cache->arena, D_RunLocalsCacheSlot, cache->table_size); - } - else if(cache->table_size == 0) - { - break; - } - U64 hash = u64_hash_from_str8(str8_struct(&dbgi_key)); - U64 slot_idx = hash % cache->table_size; - D_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; - D_RunLocalsCacheNode *node = 0; - for(D_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) - { - if(di_key_match(n->dbgi_key, dbgi_key) && n->voff == voff) - { - node = n; - break; - } - } - if(node == 0) - { - Access *access = access_open(); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, 0); - E_String2NumMap *map = e_push_locals_map_from_rdi_voff(cache->arena, rdi, voff); - if(map->slots_count != 0) - { - node = push_array(cache->arena, D_RunLocalsCacheNode, 1); - node->dbgi_key = dbgi_key; - node->voff = voff; - node->locals_map = map; - SLLQueuePush_N(slot->first, slot->last, node, hash_next); - } - access_close(access); - } - if(node != 0 && node->locals_map->slots_count != 0) - { - map = node->locals_map; - break; - } - } - ProfEnd(); - return map; -} - -internal E_String2NumMap * -d_query_cached_member_map_from_dbgi_key_voff(DI_Key dbgi_key, U64 voff) -{ - ProfBeginFunction(); - E_String2NumMap *map = &e_string2num_map_nil; - for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->member_caches); cache_idx += 1) - { - D_RunLocalsCache *cache = &d_state->member_caches[(d_state->member_cache_gen+cache_idx)%ArrayCount(d_state->member_caches)]; - if(cache_idx == 0 && cache->table_size == 0) - { - cache->table_size = 256; - cache->table = push_array(cache->arena, D_RunLocalsCacheSlot, cache->table_size); - } - else if(cache->table_size == 0) - { - break; - } - U64 hash = u64_hash_from_str8(str8_struct(&dbgi_key)); - U64 slot_idx = hash % cache->table_size; - D_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; - D_RunLocalsCacheNode *node = 0; - for(D_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) - { - if(di_key_match(n->dbgi_key, dbgi_key) && n->voff == voff) - { - node = n; - break; - } - } - if(node == 0) - { - Access *access = access_open(); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, 0); - E_String2NumMap *map = e_push_member_map_from_rdi_voff(cache->arena, rdi, voff); - if(map->slots_count != 0) - { - node = push_array(cache->arena, D_RunLocalsCacheNode, 1); - node->dbgi_key = dbgi_key; - node->voff = voff; - node->locals_map = map; - SLLQueuePush_N(slot->first, slot->last, node, hash_next); - } - access_close(access); - } - if(node != 0 && node->locals_map->slots_count != 0) - { - map = node->locals_map; - break; - } - } - ProfEnd(); - return map; -} - -//- rjf: top-level command dispatch - -internal void -d_push_cmd(D_CmdKind kind, D_CmdParams *params) -{ - d_cmd_list_push_new(d_state->cmds_arena, &d_state->cmds, kind, params); -} - -//- rjf: command iteration - -internal B32 -d_next_cmd(D_Cmd **cmd) -{ - D_CmdNode *start_node = d_state->cmds.first; - if(cmd[0] != 0) - { - start_node = CastFromMember(D_CmdNode, cmd, cmd[0]); - start_node = start_node->next; - } - cmd[0] = 0; - if(start_node != 0) - { - cmd[0] = &start_node->cmd; - } - return !!cmd[0]; -} - -//////////////////////////////// -//~ rjf: Main Layer Top-Level Calls - -#if !defined(XXH_IMPLEMENTATION) -# define XXH_IMPLEMENTATION -# define XXH_STATIC_LINKING_ONLY -# include "third_party/xxHash/xxhash.h" -#endif - -internal void -d_init(void) -{ - Arena *arena = arena_alloc(); - d_state = push_array(arena, D_State, 1); - d_state->arena = arena; - d_state->cmds_arena = arena_alloc(); - d_state->output_log_key = c_key_make(c_root_alloc(), c_id_make(0, 0)); - c_submit_data(d_state->output_log_key, 0, str8_zero()); - d_state->ctrl_entity_store = ctrl_entity_ctx_rw_store_alloc(); - d_state->ctrl_stop_arena = arena_alloc(); - d_state->ctrl_msg_arena = arena_alloc(); - - // rjf: set up caches - for(U64 idx = 0; idx < ArrayCount(d_state->tls_base_caches); idx += 1) - { - d_state->tls_base_caches[idx].arena = arena_alloc(); - } - for(U64 idx = 0; idx < ArrayCount(d_state->locals_caches); idx += 1) - { - d_state->locals_caches[idx].arena = arena_alloc(); - } - for(U64 idx = 0; idx < ArrayCount(d_state->member_caches); idx += 1) - { - d_state->member_caches[idx].arena = arena_alloc(); - } - - // rjf: set up run state - d_state->ctrl_last_run_arena = arena_alloc(); -} - -internal D_EventList -d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_PathMapArray *path_maps, U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - D_EventList result = {0}; - d_state->frame_index += 1; - - ////////////////////////////// - //- rjf: sync with ctrl thread - // - ProfScope("sync with ctrl thread") - { - //- rjf: grab next reggen/memgen - U64 new_mem_gen = ctrl_mem_gen(); - U64 new_reg_gen = ctrl_reg_gen(); - - //- rjf: consume & process events - CTRL_EventList events = ctrl_c2u_pop_events(scratch.arena); - ctrl_entity_store_apply_events(d_state->ctrl_entity_store, &events); - for(CTRL_EventNode *event_n = events.first; - event_n != 0; - event_n = event_n->next) - { - CTRL_Event *event = &event_n->v; - log_infof("ctrl_event:\n{\n"); - log_infof("kind: \"%S\"\n", ctrl_string_from_event_kind(event->kind)); - log_infof("entity_id: %u\n", event->entity_id); - switch(event->kind) - { - default:{}break; - - //- rjf: errors - - case CTRL_EventKind_Error: - { - log_user_error(event->string); - }break; - - //- rjf: starts/stops - - case CTRL_EventKind_Started: - { - d_state->ctrl_is_running = 1; - d_state->ctrl_thread_run_state = 1; - }break; - - case CTRL_EventKind_Stopped: - { - B32 should_snap = !(d_state->ctrl_soft_halt_issued); - d_state->ctrl_is_running = 0; - d_state->ctrl_thread_run_state = 0; - d_state->ctrl_soft_halt_issued = 0; - - // rjf: exception or unexpected trap -> push error - if(event->cause == CTRL_EventCause_InterruptedByException || - event->cause == CTRL_EventCause_InterruptedByTrap) - { - log_user_error(str8_zero()); - } - - // rjf: gather stop info - { - arena_clear(d_state->ctrl_stop_arena); - MemoryCopyStruct(&d_state->ctrl_last_stop_event, event); - d_state->ctrl_last_stop_event.string = push_str8_copy(d_state->ctrl_stop_arena, d_state->ctrl_last_stop_event.string); - } - - // rjf: push stop event to caller, if this is not a soft-halt - if(should_snap) - { - CTRL_Entity *thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, event->entity); - D_EventCause cause = D_EventCause_Null; - switch(event->cause) - { - default:{}break; - case CTRL_EventCause_InterruptedByHalt: - { - if(should_snap) - { - cause = D_EventCause_Halt; - } - else - { - cause = D_EventCause_SoftHalt; - } - }break; - case CTRL_EventCause_UserBreakpoint: {cause = D_EventCause_UserBreakpoint;}break; - } - D_EventNode *n = push_array(arena, D_EventNode, 1); - SLLQueuePush(result.first, result.last, n); - result.count += 1; - D_Event *evt = &n->v; - evt->kind = D_EventKind_Stop; - evt->cause = cause; - evt->thread = thread->kind == CTRL_EntityKind_Thread ? thread->handle : ctrl_handle_zero(); - evt->vaddr = event->rip_vaddr; - evt->id = event->u64_code; - } - }break; - - //- rjf: entity creation/deletion - - case CTRL_EventKind_NewProc: - { - // rjf: the first process? -> clear session output - CTRL_EntityArray existing_processes = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Process); - if(existing_processes.count == 1) - { - MTX_Op op = {r1u64(0, 0xffffffffffffffffull), str8_lit("[new session]\n")}; - mtx_push_op(d_state->output_log_key, op); - } - }break; - - case CTRL_EventKind_EndProc: - { - D_EventNode *n = push_array(arena, D_EventNode, 1); - SLLQueuePush(result.first, result.last, n); - result.count += 1; - D_Event *evt = &n->v; - evt->kind = D_EventKind_ProcessEnd; - evt->code = event->u64_code; - }break; - - case CTRL_EventKind_NewModule: - { - D_EventNode *n = push_array(arena, D_EventNode, 1); - SLLQueuePush(result.first, result.last, n); - result.count += 1; - D_Event *evt = &n->v; - evt->kind = D_EventKind_ModuleLoad; - evt->module = event->entity; - }break; - - //- rjf: debug strings - - case CTRL_EventKind_DebugString: - { - MTX_Op op = {r1u64(max_U64, max_U64), event->string}; - mtx_push_op(d_state->output_log_key, op); - }break; - - //- rjf: memory - - case CTRL_EventKind_MemReserve:{}break; - case CTRL_EventKind_MemCommit:{}break; - case CTRL_EventKind_MemDecommit:{}break; - case CTRL_EventKind_MemRelease:{}break; - } - log_infof("}\n\n"); - } - - //- rjf: clear tls base cache - if((d_state->tls_base_cache_reggen_idx != new_reg_gen || - d_state->tls_base_cache_memgen_idx != new_mem_gen) && - !d_ctrl_targets_running()) - { - d_state->tls_base_cache_gen += 1; - D_RunTLSBaseCache *cache = &d_state->tls_base_caches[d_state->tls_base_cache_gen%ArrayCount(d_state->tls_base_caches)]; - arena_clear(cache->arena); - cache->slots_count = 0; - cache->slots = 0; - d_state->tls_base_cache_reggen_idx = new_reg_gen; - d_state->tls_base_cache_memgen_idx = new_mem_gen; - } - - //- rjf: clear locals cache - if(d_state->locals_cache_reggen_idx != new_reg_gen && - !d_ctrl_targets_running()) - { - d_state->locals_cache_gen += 1; - D_RunLocalsCache *cache = &d_state->locals_caches[d_state->locals_cache_gen%ArrayCount(d_state->locals_caches)]; - arena_clear(cache->arena); - cache->table_size = 0; - cache->table = 0; - d_state->locals_cache_reggen_idx = new_reg_gen; - } - - //- rjf: clear members cache - if(d_state->member_cache_reggen_idx != new_reg_gen && - !d_ctrl_targets_running()) - { - d_state->member_cache_gen += 1; - D_RunLocalsCache *cache = &d_state->member_caches[d_state->member_cache_gen%ArrayCount(d_state->member_caches)]; - arena_clear(cache->arena); - cache->table_size = 0; - cache->table = 0; - d_state->member_cache_reggen_idx = new_reg_gen; - } - } - - ////////////////////////////// - //- rjf: hash ctrl parameterization state - // - U128 ctrl_param_state_hash = {0}; - { - // rjf: build data strings of all param data - String8List strings = {0}; - { - CTRL_EntityArray threads = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Thread); - for EachIndex(idx, threads.count) - { - CTRL_Entity *thread = threads.v[idx]; - if(thread->is_frozen) - { - str8_list_push(scratch.arena, &strings, str8_struct(&thread->id)); - str8_list_push(scratch.arena, &strings, str8_struct(&thread->is_frozen)); - } - } - for(U64 idx = 0; idx < breakpoints->count; idx += 1) - { - D_Breakpoint *bp = &breakpoints->v[idx]; - str8_list_push(scratch.arena, &strings, bp->file_path); - str8_list_push(scratch.arena, &strings, str8_struct(&bp->pt)); - str8_list_push(scratch.arena, &strings, bp->vaddr_expr); - str8_list_push(scratch.arena, &strings, bp->condition); - } - } - - // rjf: join & hash to produce result - String8 string = str8_list_join(scratch.arena, &strings, 0); - XXH128_hash_t hash = XXH3_128bits(string.str, string.size); - MemoryCopy(&ctrl_param_state_hash, &hash, sizeof(ctrl_param_state_hash)); - } - - ////////////////////////////// - //- rjf: if ctrl thread is running, and our ctrl parameterization - // state hash has changed since the last run, we should soft- - // halt-refresh to inform the ctrl thread about the updated - // state - // - if(d_ctrl_targets_running() && !u128_match(ctrl_param_state_hash, d_state->ctrl_last_run_param_state_hash)) - { - d_cmd(D_CmdKind_SoftHaltRefresh); - } - - ////////////////////////////// - //- rjf: process top-level commands - // - D_CmdList deferred_cmds = {0}; - CTRL_MsgList ctrl_msgs = {0}; - ProfScope("process top-level commands") - { - D_BreakpointArray run_extra_bps = {0}; - for(D_Cmd *cmd = 0; d_next_cmd(&cmd);) - { - // rjf: unpack command - D_CmdParams *params = &cmd->params; - - // rjf: prep ctrl running arguments - B32 need_run = 0; - D_RunKind run_kind = D_RunKind_Run; - CTRL_Entity *run_thread = &ctrl_entity_nil; - CTRL_RunFlags run_flags = 0; - CTRL_TrapList run_traps = {0}; - - // rjf: process command - switch(cmd->kind) - { - default:{}break; - - //- rjf: low-level target control operations - case D_CmdKind_LaunchAndRun: - case D_CmdKind_LaunchAndStepInto: - { - // rjf: get list of targets to launch - D_TargetArray *targets_to_launch = ¶ms->targets; - - // rjf: no targets => assume all active targets - if(targets_to_launch->count == 0) - { - targets_to_launch = targets; - } - - // rjf: launch - if(targets_to_launch->count != 0) - { - for(U64 idx = 0; idx < targets_to_launch->count; idx += 1) - { - // rjf: unpack target - D_Target *target = &targets_to_launch->v[idx]; - String8 exe = str8_skip_chop_whitespace(target->exe); - String8 args = str8_skip_chop_whitespace(target->args); - String8 working_directory = str8_skip_chop_whitespace(target->working_directory); - String8 custom_entry_point_name = str8_skip_chop_whitespace(target->custom_entry_point_name); - String8 stdout_path = str8_skip_chop_whitespace(target->stdout_path); - String8 stderr_path = str8_skip_chop_whitespace(target->stderr_path); - String8 stdin_path = str8_skip_chop_whitespace(target->stdin_path); - String8List env = target->env; - if(working_directory.size == 0) - { - working_directory = os_get_current_path(scratch.arena); - } - - // rjf: build launch options - String8List cmdln_strings = {0}; - { - str8_list_push(scratch.arena, &cmdln_strings, exe); - { - U64 start_split_idx = 0; - B32 quoted = 0; - for(U64 idx = 0; idx <= args.size; idx += 1) - { - U8 byte = idx < args.size ? args.str[idx] : 0; - if(byte == '"') - { - quoted ^= 1; - } - B32 splitter_found = (!quoted && (byte == 0 || char_is_space(byte))); - if(splitter_found) - { - String8 string = str8_substr(args, r1u64(start_split_idx, idx)); - if(string.size > 0) - { - str8_list_push(scratch.arena, &cmdln_strings, string); - } - start_split_idx = idx+1; - } - } - } - } - - // rjf: push message to launch - { - CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); - msg->kind = CTRL_MsgKind_Launch; - msg->path = working_directory; - msg->cmd_line_string_list = cmdln_strings; - msg->stdout_path = stdout_path; - msg->stderr_path = stderr_path; - msg->stdin_path = stdin_path; - msg->debug_subprocesses = target->debug_subprocesses; - msg->env_inherit = 1; - MemoryCopyArray(msg->exception_code_filters, exception_code_filters); - str8_list_push(scratch.arena, &msg->entry_points, custom_entry_point_name); - msg->env_string_list = env; - } - } - - // rjf: run - need_run = 1; - run_kind = D_RunKind_Run; - run_thread = &ctrl_entity_nil; - run_flags = (cmd->kind == D_CmdKind_LaunchAndStepInto) ? CTRL_RunFlag_StopOnEntryPoint : 0; - } - - // rjf: no targets -> error - if(targets_to_launch->count == 0) - { - log_user_error(str8_lit("No active targets exist; cannot launch. You must select a target first.")); - } - }break; - case D_CmdKind_Kill: - { - CTRL_Entity *process = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->process); - if(process == &ctrl_entity_nil) - { - log_user_error(str8_lit("Cannot kill; no process was specified.")); - } - else - { - CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); - msg->kind = CTRL_MsgKind_Kill; - msg->exit_code = 1; - msg->entity = process->handle; - MemoryCopyArray(msg->exception_code_filters, exception_code_filters); - } - }break; - case D_CmdKind_KillAll: - { - CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); - msg->kind = CTRL_MsgKind_KillAll; - msg->exit_code = 1; - MemoryCopyArray(msg->exception_code_filters, exception_code_filters); - }break; - case D_CmdKind_Detach: - { - CTRL_Entity *process = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->process); - if(process == &ctrl_entity_nil) - { - log_user_error(str8_lit("Cannot detach; no process specified.")); - } - else - { - CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); - msg->kind = CTRL_MsgKind_Detach; - msg->entity = process->handle; - MemoryCopyArray(msg->exception_code_filters, exception_code_filters); - } - }break; - case D_CmdKind_Continue: - { - B32 good_to_run = 0; - CTRL_EntityArray threads = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Thread); - if(threads.count > 0) - { - for EachIndex(idx, threads.count) - { - CTRL_Entity *thread = threads.v[idx]; - if(!thread->is_frozen) - { - good_to_run = 1; - break; - } - } - if(good_to_run) - { - need_run = 1; - run_kind = D_RunKind_Run; - run_thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->thread); - } - else - { - log_user_error(str8_lit("Cannot run with all threads frozen.")); - } - } - }break; - case D_CmdKind_StepIntoInst: - case D_CmdKind_StepOverInst: - case D_CmdKind_StepIntoLine: - case D_CmdKind_StepOverLine: - case D_CmdKind_StepOut: - { - CTRL_Entity *thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->thread); - if(thread == &ctrl_entity_nil) - { - log_user_error(str8_lit("Must have a selected thread to step.")); - } - else if(d_ctrl_targets_running()) - { - if(d_ctrl_last_run_kind() == D_RunKind_Run) - { - log_user_error(str8_lit("Must halt before stepping.")); - } - } - else if(thread->is_frozen) - { - log_user_error(str8_lit("Must thaw selected thread before stepping.")); - } - else - { - D_TrapNet trap_net = {0}; - switch(cmd->kind) - { - default: break; - case D_CmdKind_StepIntoInst: {trap_net.good_read = 1;}break; - case D_CmdKind_StepOverInst: {trap_net = d_trap_net_from_thread__step_over_inst(scratch.arena, thread);}break; - case D_CmdKind_StepIntoLine: {trap_net = d_trap_net_from_thread__step_into_line(scratch.arena, thread);}break; - case D_CmdKind_StepOverLine: {trap_net = d_trap_net_from_thread__step_over_line(scratch.arena, thread);}break; - case D_CmdKind_StepOut: - { - Access *access = access_open(); - - // rjf: thread => call stack - CTRL_CallStack callstack = ctrl_call_stack_from_thread(access, thread->handle, 1, os_now_microseconds()+10000); - - // rjf: use first unwind frame to generate trap - if(callstack.concrete_frames_count > 1) - { - U64 vaddr = regs_rip_from_arch_block(thread->arch, callstack.concrete_frames[1]->regs); - CTRL_Trap trap = {CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck, vaddr}; - ctrl_trap_list_push(scratch.arena, &trap_net.traps, &trap); - trap_net.good_read = 1; - } - else - { - log_user_error(str8_lit("Could not find the return address of the current callstack frame successfully.")); - } - - access_close(access); - }break; - } - B32 good_trap_net = (trap_net.good_read || !trap_net.good_line_info); - if(good_trap_net && trap_net.traps.count != 0) - { - need_run = 1; - run_kind = D_RunKind_Step; - run_thread = thread; - run_flags = 0; - run_traps = trap_net.traps; - } - else if(good_trap_net && trap_net.traps.count == 0) - { - need_run = 1; - run_kind = D_RunKind_SingleStep; - run_thread = thread; - run_flags = 0; - run_traps = trap_net.traps; - } - else if(!good_trap_net && params->retry_idx < 100) - { - D_CmdParams params_copy = *params; - params_copy.retry_idx += 1; - d_cmd_list_push_new(scratch.arena, &deferred_cmds, cmd->kind, ¶ms_copy); - } - else if(!good_trap_net) - { - log_user_error(str8_lit("Could not successfully step.")); - } - } - }break; - case D_CmdKind_Halt: - if(d_ctrl_targets_running()) - { - ctrl_halt(); - }break; - case D_CmdKind_SoftHaltRefresh: - if(d_ctrl_targets_running()) - { - need_run = 1; - run_kind = d_state->ctrl_last_run_kind; - run_thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, d_state->ctrl_last_run_thread_handle); - run_flags = d_state->ctrl_last_run_flags; - run_traps = d_state->ctrl_last_run_traps; - }break; - case D_CmdKind_SetThreadIP: - { - CTRL_Entity *thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->thread); - U64 vaddr = params->vaddr; - void *block = ctrl_reg_block_from_thread(scratch.arena, &d_state->ctrl_entity_store->ctx, thread->handle); - regs_arch_block_write_rip(thread->arch, block, vaddr); - B32 result = ctrl_thread_write_reg_block(thread->handle, block); - (void)result; - }break; - - //- rjf: high-level composite target control operations - case D_CmdKind_RunToLine: - { - run_extra_bps.count = 1; - run_extra_bps.v = push_array(scratch.arena, D_Breakpoint, 1); - if(params->file_path.size != 0) - { - run_extra_bps.v[0].file_path = params->file_path; - run_extra_bps.v[0].pt = params->cursor; - } - else if(params->vaddr != 0) - { - run_extra_bps.v[0].vaddr_expr = push_str8f(scratch.arena, "0x%I64x", params->vaddr); - } - d_cmd(D_CmdKind_Run); - }break; - case D_CmdKind_Run: - { - CTRL_EntityArray processes = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Process); - if(processes.count != 0) - { - d_cmd(D_CmdKind_Continue, .machine = params->machine, .process = params->process, .thread = params->thread); - } - else if(!d_ctrl_targets_running()) - { - d_cmd(D_CmdKind_LaunchAndRun); - } - }break; - case D_CmdKind_Restart: - { - CTRL_EntityArray processes = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Process); - if(processes.count != 0) - { - d_cmd(D_CmdKind_KillAll); - } - d_cmd(D_CmdKind_LaunchAndRun); - }break; - case D_CmdKind_StepInto: - case D_CmdKind_StepOver: - { - CTRL_EntityArray processes = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Process); - if(processes.count != 0) - { - D_CmdKind step_cmd_kind = (cmd->kind == D_CmdKind_StepInto - ? D_CmdKind_StepIntoLine - : D_CmdKind_StepOverLine); - B32 prefer_disasm = params->prefer_disasm; - if(prefer_disasm) - { - step_cmd_kind = (cmd->kind == D_CmdKind_StepInto - ? D_CmdKind_StepIntoInst - : D_CmdKind_StepOverInst); - } - d_cmd(step_cmd_kind, .thread = params->thread); - } - else if(!d_ctrl_targets_running()) - { - d_cmd(D_CmdKind_LaunchAndStepInto, .targets = *targets); - } - }break; - - //- rjf: debug control context management operations - case D_CmdKind_FreezeThread: - case D_CmdKind_ThawThread: - case D_CmdKind_FreezeProcess: - case D_CmdKind_ThawProcess: - case D_CmdKind_FreezeMachine: - case D_CmdKind_ThawMachine: - { - D_CmdKind disptch_kind = ((cmd->kind == D_CmdKind_FreezeThread || - cmd->kind == D_CmdKind_FreezeProcess || - cmd->kind == D_CmdKind_FreezeMachine) - ? D_CmdKind_FreezeEntity - : D_CmdKind_ThawEntity); - d_push_cmd(disptch_kind, params); - }break; - case D_CmdKind_FreezeLocalMachine: - { - CTRL_MachineID machine_id = CTRL_MachineID_Local; - d_cmd(D_CmdKind_FreezeMachine, .entity = ctrl_handle_make(machine_id, dmn_handle_zero())); - }break; - case D_CmdKind_ThawLocalMachine: - { - CTRL_MachineID machine_id = CTRL_MachineID_Local; - d_cmd(D_CmdKind_ThawMachine, .entity = ctrl_handle_make(machine_id, dmn_handle_zero())); - }break; - case D_CmdKind_FreezeEntity: - case D_CmdKind_ThawEntity: - { - B32 should_freeze = (cmd->kind == D_CmdKind_FreezeEntity); - CTRL_Entity *root = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->entity); - for(CTRL_Entity *e = root; e != &ctrl_entity_nil; e = ctrl_entity_rec_depth_first_pre(e, root).next) - { - if(e->kind == CTRL_EntityKind_Thread) - { - e->is_frozen = should_freeze; - CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); - msg->kind = (should_freeze ? CTRL_MsgKind_FreezeThread : CTRL_MsgKind_ThawThread); - msg->entity = e->handle; - } - } - if(d_ctrl_targets_running()) - { - need_run = 1; - run_kind = d_state->ctrl_last_run_kind; - run_thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, d_state->ctrl_last_run_thread_handle); - run_flags = d_state->ctrl_last_run_flags; - run_traps = d_state->ctrl_last_run_traps; - } - }break; - - //- rjf: entity decoration - case D_CmdKind_SetEntityColor: - { - CTRL_Entity *entity = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->entity); - entity->rgba = params->rgba; - }break; - case D_CmdKind_SetEntityName: - { - CTRL_Entity *entity = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->entity); - ctrl_entity_equip_string(d_state->ctrl_entity_store, entity, params->string); - }break; - - //- rjf: attaching - case D_CmdKind_Attach: - { - U32 pid = params->pid; - if(pid != 0) - { - CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); - msg->kind = CTRL_MsgKind_Attach; - msg->entity_id = pid; - MemoryCopyArray(msg->exception_code_filters, exception_code_filters); - } - }break; - } - - // rjf: do run if needed - if(need_run) - { - // rjf: compute hash of all run-parameterization entities, store - { - d_state->ctrl_last_run_param_state_hash = ctrl_param_state_hash; - } - - // rjf: push & fill run message - CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); - { - CTRL_Entity *process = ctrl_entity_ancestor_from_kind(run_thread, CTRL_EntityKind_Process); - msg->kind = (run_kind == D_RunKind_Run || run_kind == D_RunKind_Step) ? CTRL_MsgKind_Run : CTRL_MsgKind_SingleStep; - msg->run_flags = run_flags; - msg->entity = run_thread->handle; - msg->parent = process->handle; - MemoryCopyArray(msg->exception_code_filters, exception_code_filters); - MemoryCopyStruct(&msg->traps, &run_traps); - D_BreakpointArray *bp_batches[] = - { - breakpoints, - &run_extra_bps, - }; - for(U64 batch_idx = 0; batch_idx < ArrayCount(bp_batches); batch_idx += 1) - { - D_BreakpointArray *batch_breakpoints = bp_batches[batch_idx]; - for(U64 idx = 0; idx < batch_breakpoints->count; idx += 1) - { - // rjf: unpack user breakpoint entity - D_Breakpoint *bp = &batch_breakpoints->v[idx]; - - // rjf: d -> ctrl flags - CTRL_UserBreakpointFlags ctrl_bp_flags = 0; - if(bp->flags & D_BreakpointFlag_BreakOnWrite) { ctrl_bp_flags |= CTRL_UserBreakpointFlag_BreakOnWrite; } - if(bp->flags & D_BreakpointFlag_BreakOnRead) { ctrl_bp_flags |= CTRL_UserBreakpointFlag_BreakOnRead; } - if(bp->flags & D_BreakpointFlag_BreakOnExecute) { ctrl_bp_flags |= CTRL_UserBreakpointFlag_BreakOnExecute; } - - // rjf: textual location -> add breakpoints for all possible override locations - if(bp->file_path.size != 0 && bp->pt.line != 0) - { - String8List overrides = d_possible_path_overrides_from_maps_path(scratch.arena, path_maps, bp->file_path); - for(String8Node *n = overrides.first; n != 0; n = n->next) - { - CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_FileNameAndLineColNumber}; - ctrl_user_bp.flags = ctrl_bp_flags; - ctrl_user_bp.id = bp->id; - ctrl_user_bp.string = n->string; - ctrl_user_bp.pt = bp->pt; - ctrl_user_bp.condition = bp->condition; - ctrl_user_bp.size = bp->size; - ctrl_user_breakpoint_list_push(scratch.arena, &msg->user_bps, &ctrl_user_bp); - } - } - - // rjf: virtual address expression -> add expression breakpoint - else if(bp->vaddr_expr.size != 0) - { - CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_Expression}; - ctrl_user_bp.flags = ctrl_bp_flags; - ctrl_user_bp.id = bp->id; - ctrl_user_bp.string = bp->vaddr_expr; - ctrl_user_bp.condition = bp->condition; - ctrl_user_bp.size = bp->size; - ctrl_user_breakpoint_list_push(scratch.arena, &msg->user_bps, &ctrl_user_bp); - } - } - } - } - - // rjf: copy run traps to scratch (needed, if run_traps can be `d_state->ctrl_last_run_traps`) - CTRL_TrapList run_traps_copy = ctrl_trap_list_copy(scratch.arena, &run_traps); - D_BreakpointArray run_extra_bps_copy = d_breakpoint_array_copy(scratch.arena, &run_extra_bps); - - // rjf: store last run info - arena_clear(d_state->ctrl_last_run_arena); - d_state->ctrl_last_run_kind = run_kind; - d_state->ctrl_last_run_frame_idx = d_frame_index(); - d_state->ctrl_last_run_thread_handle = run_thread->handle; - d_state->ctrl_last_run_flags = run_flags; - d_state->ctrl_last_run_traps = ctrl_trap_list_copy(d_state->ctrl_last_run_arena, &run_traps_copy); - d_state->ctrl_last_run_extra_bps = d_breakpoint_array_copy(d_state->ctrl_last_run_arena, &run_extra_bps_copy); - d_state->ctrl_is_running = 1; - } - } - } - - ////////////////////////////// - //- rjf: clear command batch - // - { - arena_clear(d_state->cmds_arena); - MemoryZeroStruct(&d_state->cmds); - } - - ////////////////////////////// - //- rjf: push deferred commands - // - for EachNode(n, D_CmdNode, deferred_cmds.first) - { - d_cmd_list_push_new(d_state->cmds_arena, &d_state->cmds, n->cmd.kind, &n->cmd.params); - } - - ////////////////////////////// - //- rjf: push new control messages to queue - try to send queue to control, - // clear queue if successful (if not, we'll just keep them around until - // the next tick) - // - { - CTRL_MsgList msgs_copy = ctrl_msg_list_deep_copy(d_state->ctrl_msg_arena, &ctrl_msgs); - ctrl_msg_list_concat_in_place(&d_state->ctrl_msgs, &msgs_copy); - if(d_state->ctrl_msgs.count != 0) - { - if(!d_state->ctrl_soft_halt_issued && d_state->ctrl_thread_run_state) - { - d_state->ctrl_soft_halt_issued = 1; - ctrl_halt(); - } - if(ctrl_u2c_push_msgs(&d_state->ctrl_msgs, os_now_microseconds()+100)) - { - MemoryZeroStruct(&d_state->ctrl_msgs); - arena_clear(d_state->ctrl_msg_arena); - } - } - } - - ProfEnd(); - scratch_end(scratch); - return result; -} +#include "generated/dbg_engine.meta.c" diff --git a/src/dbg_engine/dbg_engine_core.h b/src/dbg_engine/dbg_engine_core.h index 03b4a8f8..6fe61679 100644 --- a/src/dbg_engine/dbg_engine_core.h +++ b/src/dbg_engine/dbg_engine_core.h @@ -4,6 +4,231 @@ #ifndef DBG_ENGINE_CORE_H #define DBG_ENGINE_CORE_H +//////////////////////////////// +//~ rjf: ID Types + +typedef U64 CTRL_MsgID; +typedef U64 CTRL_MachineID; + +#define CTRL_MachineID_Local (1) + +//////////////////////////////// +//~ rjf: Entity Handle Types + +typedef struct CTRL_Handle CTRL_Handle; +struct CTRL_Handle +{ + CTRL_MachineID machine_id; + DMN_Handle dmn_handle; +}; + +typedef struct CTRL_HandleNode CTRL_HandleNode; +struct CTRL_HandleNode +{ + CTRL_HandleNode *next; + CTRL_Handle v; +}; + +typedef struct CTRL_HandleList CTRL_HandleList; +struct CTRL_HandleList +{ + CTRL_HandleNode *first; + CTRL_HandleNode *last; + U64 count; +}; + +typedef struct CTRL_HandleArray CTRL_HandleArray; +struct CTRL_HandleArray +{ + CTRL_Handle *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/dbg_engine.meta.h" + +//////////////////////////////// +//~ rjf: User Breakpoint Types + +typedef U32 CTRL_UserBreakpointFlags; +enum +{ + CTRL_UserBreakpointFlag_BreakOnWrite = (1<<0), + CTRL_UserBreakpointFlag_BreakOnRead = (1<<1), + CTRL_UserBreakpointFlag_BreakOnExecute = (1<<2), +}; + +typedef enum CTRL_UserBreakpointKind +{ + CTRL_UserBreakpointKind_Null, + CTRL_UserBreakpointKind_FileNameAndLineColNumber, + CTRL_UserBreakpointKind_Expression, + CTRL_UserBreakpointKind_COUNT +} +CTRL_UserBreakpointKind; + +typedef struct CTRL_UserBreakpoint CTRL_UserBreakpoint; +struct CTRL_UserBreakpoint +{ + CTRL_UserBreakpointKind kind; + CTRL_UserBreakpointFlags flags; + U64 id; + String8 string; + TxtPt pt; + U64 size; + String8 condition; +}; + +typedef struct CTRL_UserBreakpointNode CTRL_UserBreakpointNode; +struct CTRL_UserBreakpointNode +{ + CTRL_UserBreakpointNode *next; + CTRL_UserBreakpoint v; +}; + +typedef struct CTRL_UserBreakpointList CTRL_UserBreakpointList; +struct CTRL_UserBreakpointList +{ + CTRL_UserBreakpointNode *first; + CTRL_UserBreakpointNode *last; + U64 count; +}; + +//////////////////////////////// +//~ Dynamic Linker Types + +typedef U32 CTRL_TlsModel; +enum +{ + CTRL_TlsModel_Null, + CTRL_TlsModel_WinodwsNt, + CTRL_TlsModel_Gnu +}; + +//////////////////////////////// +//~ rjf: Entity Types + +typedef struct CTRL_Entity CTRL_Entity; +struct CTRL_Entity +{ + CTRL_Entity *first; + CTRL_Entity *last; + CTRL_Entity *next; + CTRL_Entity *prev; + CTRL_Entity *parent; + CTRL_EntityKind kind; + Arch arch; + B32 is_frozen; + B32 is_soloed; + U32 rgba; + CTRL_Handle handle; + U64 id; + Rng1U64 vaddr_range; + U64 stack_base; + U64 timestamp; + CTRL_UserBreakpointFlags bp_flags; + String8 string; + CTRL_TlsModel tls_model; + U64 tls_index; + U64 tls_offset; + OperatingSystem target_os; +}; + +typedef struct CTRL_EntityNode CTRL_EntityNode; +struct CTRL_EntityNode +{ + CTRL_EntityNode *next; + CTRL_Entity *v; +}; + +typedef struct CTRL_EntityList CTRL_EntityList; +struct CTRL_EntityList +{ + CTRL_EntityNode *first; + CTRL_EntityNode *last; + U64 count; +}; + +typedef struct CTRL_EntityArray CTRL_EntityArray; +struct CTRL_EntityArray +{ + CTRL_Entity **v; + U64 count; +}; + +typedef struct CTRL_EntityRec CTRL_EntityRec; +struct CTRL_EntityRec +{ + CTRL_Entity *next; + S32 push_count; + S64 pop_count; +}; + +typedef struct CTRL_EntityHashNode CTRL_EntityHashNode; +struct CTRL_EntityHashNode +{ + CTRL_EntityHashNode *next; + CTRL_EntityHashNode *prev; + CTRL_Entity *entity; +}; + +typedef struct CTRL_EntityHashSlot CTRL_EntityHashSlot; +struct CTRL_EntityHashSlot +{ + CTRL_EntityHashNode *first; + CTRL_EntityHashNode *last; +}; + +typedef struct CTRL_EntityStringChunkNode CTRL_EntityStringChunkNode; +struct CTRL_EntityStringChunkNode +{ + CTRL_EntityStringChunkNode *next; + U64 size; +}; + +read_only global U64 ctrl_entity_string_bucket_chunk_sizes[] = +{ + 16, + 64, + 256, + 1024, + 4096, + 16384, + 65536, + 0xffffffffffffffffull, +}; + +typedef struct CTRL_EntityCtx CTRL_EntityCtx; +struct CTRL_EntityCtx +{ + CTRL_Entity *root; + U64 hash_slots_count; + CTRL_EntityHashSlot *hash_slots; + U64 entity_kind_counts[CTRL_EntityKind_COUNT]; + U64 entity_kind_alloc_gens[CTRL_EntityKind_COUNT]; +}; + +typedef struct CTRL_EntityCtxRWStore CTRL_EntityCtxRWStore; +struct CTRL_EntityCtxRWStore +{ + Arena *arena; + CTRL_EntityCtx ctx; + CTRL_Entity *free; + CTRL_EntityHashNode *hash_node_free; + CTRL_EntityStringChunkNode *free_string_chunks[ArrayCount(ctrl_entity_string_bucket_chunk_sizes)]; +}; + +typedef struct CTRL_EntityCtxLookupAccel CTRL_EntityCtxLookupAccel; +struct CTRL_EntityCtxLookupAccel +{ + Arena *arena; + Arena *entity_kind_arrays_arenas[CTRL_EntityKind_COUNT]; + CTRL_EntityArray entity_kind_arrays[CTRL_EntityKind_COUNT]; + U64 entity_kind_arrays_gens[CTRL_EntityKind_COUNT]; +}; + //////////////////////////////// //~ rjf: Tick Input Types @@ -69,17 +294,6 @@ struct D_PathMapArray U64 count; }; -//////////////////////////////// -//~ rjf: Trap Nets - -typedef struct D_TrapNet D_TrapNet; -struct D_TrapNet -{ - CTRL_TrapList traps; - B32 good_line_info; - B32 good_read; -}; - //////////////////////////////// //~ rjf: Tick Output Types @@ -131,309 +345,58 @@ struct D_EventList }; //////////////////////////////// -//~ rjf: Line Info Types +//~ rjf: Trap Types -typedef struct D_Line D_Line; -struct D_Line +typedef U32 CTRL_TrapFlags; +enum { - String8 file_path; - TxtPt pt; - Rng1U64 voff_range; - DI_Key dbgi_key; + CTRL_TrapFlag_IgnoreStackPointerCheck = (1<<0), + CTRL_TrapFlag_SingleStepAfterHit = (1<<1), + CTRL_TrapFlag_SaveStackPointer = (1<<2), + CTRL_TrapFlag_BeginSpoofMode = (1<<3), + CTRL_TrapFlag_EndStepping = (1<<4), }; -typedef struct D_LineNode D_LineNode; -struct D_LineNode +typedef struct CTRL_Trap CTRL_Trap; +struct CTRL_Trap { - D_LineNode *next; - D_Line v; -}; - -typedef struct D_LineList D_LineList; -struct D_LineList -{ - D_LineNode *first; - D_LineNode *last; - U64 count; -}; - -typedef struct D_LineListArray D_LineListArray; -struct D_LineListArray -{ - D_LineList *v; - U64 count; - DI_KeyList dbgi_keys; -}; - -//////////////////////////////// -//~ rjf: Debug Engine Control Communication Types - -typedef enum D_RunKind -{ - D_RunKind_Run, - D_RunKind_SingleStep, - D_RunKind_Step, - D_RunKind_COUNT -} -D_RunKind; - -//////////////////////////////// -//~ rjf: Generated Code - -#include "dbg_engine/generated/dbg_engine.meta.h" - -//////////////////////////////// -//~ rjf: Command Types - -typedef struct D_CmdParams D_CmdParams; -struct D_CmdParams -{ - CTRL_Handle machine; - CTRL_Handle process; - CTRL_Handle thread; - CTRL_Handle entity; - String8 string; - String8 file_path; - TxtPt cursor; + CTRL_TrapFlags flags; U64 vaddr; - B32 prefer_disasm; - U32 pid; - U32 rgba; - D_TargetArray targets; - U64 retry_idx; }; -typedef struct D_Cmd D_Cmd; -struct D_Cmd +typedef struct CTRL_TrapNode CTRL_TrapNode; +struct CTRL_TrapNode { - D_CmdKind kind; - D_CmdParams params; + CTRL_TrapNode *next; + CTRL_Trap v; }; -typedef struct D_CmdNode D_CmdNode; -struct D_CmdNode +typedef struct CTRL_TrapList CTRL_TrapList; +struct CTRL_TrapList { - D_CmdNode *next; - D_CmdNode *prev; - D_Cmd cmd; -}; - -typedef struct D_CmdList D_CmdList; -struct D_CmdList -{ - D_CmdNode *first; - D_CmdNode *last; + CTRL_TrapNode *first; + CTRL_TrapNode *last; U64 count; }; -//////////////////////////////// -//~ rjf: Main State Caches - -//- rjf: per-run tls-base-vaddr cache - -typedef struct D_RunTLSBaseCacheNode D_RunTLSBaseCacheNode; -struct D_RunTLSBaseCacheNode +typedef struct CTRL_Spoof CTRL_Spoof; +struct CTRL_Spoof { - D_RunTLSBaseCacheNode *hash_next; - CTRL_Handle process; - U64 root_vaddr; - U64 rip_vaddr; - U64 tls_base_vaddr; -}; - -typedef struct D_RunTLSBaseCacheSlot D_RunTLSBaseCacheSlot; -struct D_RunTLSBaseCacheSlot -{ - D_RunTLSBaseCacheNode *first; - D_RunTLSBaseCacheNode *last; -}; - -typedef struct D_RunTLSBaseCache D_RunTLSBaseCache; -struct D_RunTLSBaseCache -{ - Arena *arena; - U64 slots_count; - D_RunTLSBaseCacheSlot *slots; -}; - -//- rjf: per-run locals cache - -typedef struct D_RunLocalsCacheNode D_RunLocalsCacheNode; -struct D_RunLocalsCacheNode -{ - D_RunLocalsCacheNode *hash_next; - DI_Key dbgi_key; - U64 voff; - E_String2NumMap *locals_map; -}; - -typedef struct D_RunLocalsCacheSlot D_RunLocalsCacheSlot; -struct D_RunLocalsCacheSlot -{ - D_RunLocalsCacheNode *first; - D_RunLocalsCacheNode *last; -}; - -typedef struct D_RunLocalsCache D_RunLocalsCache; -struct D_RunLocalsCache -{ - Arena *arena; - U64 table_size; - D_RunLocalsCacheSlot *table; + DMN_Handle process; + DMN_Handle thread; + U64 vaddr; + U64 new_ip_value; }; //////////////////////////////// -//~ rjf: Main State Types +//~ rjf: Trap Nets -typedef struct D_State D_State; -struct D_State +typedef struct D_TrapNet D_TrapNet; +struct D_TrapNet { - // rjf: top-level state - Arena *arena; - U64 frame_index; - - // rjf: commands - Arena *cmds_arena; - D_CmdList cmds; - - // rjf: output log key - C_Key output_log_key; - - // rjf: per-run caches - U64 tls_base_cache_reggen_idx; - U64 tls_base_cache_memgen_idx; - D_RunTLSBaseCache tls_base_caches[2]; - U64 tls_base_cache_gen; - U64 locals_cache_reggen_idx; - D_RunLocalsCache locals_caches[2]; - U64 locals_cache_gen; - U64 member_cache_reggen_idx; - D_RunLocalsCache member_caches[2]; - U64 member_cache_gen; - - // rjf: user -> ctrl driving state - Arena *ctrl_last_run_arena; - D_RunKind ctrl_last_run_kind; - U64 ctrl_last_run_frame_idx; - CTRL_Handle ctrl_last_run_thread_handle; - CTRL_RunFlags ctrl_last_run_flags; - CTRL_TrapList ctrl_last_run_traps; - D_BreakpointArray ctrl_last_run_extra_bps; - U128 ctrl_last_run_param_state_hash; - B32 ctrl_is_running; - B32 ctrl_thread_run_state; - B32 ctrl_soft_halt_issued; - Arena *ctrl_msg_arena; - CTRL_MsgList ctrl_msgs; - - // rjf: ctrl -> user reading state - CTRL_EntityCtxRWStore *ctrl_entity_store; - Arena *ctrl_stop_arena; - CTRL_Event ctrl_last_stop_event; + CTRL_TrapList traps; + B32 good_line_info; + B32 good_read; }; -//////////////////////////////// -//~ rjf: Globals - -global D_State *d_state = 0; - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal U64 d_hash_from_seed_string(U64 seed, String8 string); -internal U64 d_hash_from_string(String8 string); -internal U64 d_hash_from_seed_string__case_insensitive(U64 seed, String8 string); -internal U64 d_hash_from_string__case_insensitive(String8 string); - -//////////////////////////////// -//~ rjf: Breakpoints - -internal D_BreakpointArray d_breakpoint_array_copy(Arena *arena, D_BreakpointArray *src); - -//////////////////////////////// -//~ rjf: Path Map Application - -internal String8List d_possible_path_overrides_from_maps_path(Arena *arena, D_PathMapArray *path_maps, String8 file_path); - -//////////////////////////////// -//~ rjf: Debug Info Extraction Type Pure Functions - -internal D_LineList d_line_list_copy(Arena *arena, D_LineList *list); - -//////////////////////////////// -//~ rjf: Command Type Functions - -//- rjf: command parameters -internal D_CmdParams d_cmd_params_copy(Arena *arena, D_CmdParams *src); - -//- rjf: command lists -internal void d_cmd_list_push_new(Arena *arena, D_CmdList *cmds, D_CmdKind kind, D_CmdParams *params); - -//////////////////////////////// -//~ rjf: Stepping "Trap Net" Builders - -internal D_TrapNet d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread); -internal D_TrapNet d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread); -internal D_TrapNet d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread); - -//////////////////////////////// -//~ rjf: Debug Info Lookups - -//- rjf: voff -> line info -internal D_LineList d_lines_from_dbgi_key_voff(Arena *arena, DI_Key dbgi_key, U64 voff); - -//- rjf: file:line -> line info -// TODO(rjf): this depends on file path maps, needs to move -// TODO(rjf): need to clean this up & dedup -internal D_LineListArray d_lines_array_from_dbgi_key_file_path_line_range(Arena *arena, DI_Key dbgi_key, String8 file_path, Rng1S64 line_num_range); -internal D_LineListArray d_lines_array_from_file_path_line_range(Arena *arena, String8 file_path, Rng1S64 line_num_range); -internal D_LineList d_lines_from_dbgi_key_file_path_line_num(Arena *arena, DI_Key dbgi_key, String8 file_path, S64 line_num); -internal D_LineList d_lines_from_file_path_line_num(Arena *arena, String8 file_path, S64 line_num); - -//////////////////////////////// -//~ rjf: Process/Thread/Module Info Lookups - -internal U64 d_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64 rip_vaddr); - -//////////////////////////////// -//~ rjf: Target Controls - -//- rjf: stopped info from the control thread -internal CTRL_Event d_ctrl_last_stop_event(void); - -//////////////////////////////// -//~ rjf: Main State Accessors/Mutators - -//- rjf: frame data -internal U64 d_frame_index(void); - -//- rjf: control state -internal D_RunKind d_ctrl_last_run_kind(void); -internal U64 d_ctrl_last_run_frame_idx(void); -internal B32 d_ctrl_targets_running(void); - -//- rjf: active entity based queries -internal DI_KeyList d_push_active_dbgi_key_list(Arena *arena); - -//- rjf: per-run caches -internal U64 d_query_cached_rip_from_thread(CTRL_Entity *thread); -internal U64 d_query_cached_rip_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count); -internal U64 d_query_cached_cfa_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count); -internal U64 d_query_cached_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64 rip_vaddr); -internal E_String2NumMap *d_query_cached_locals_map_from_dbgi_key_voff(DI_Key dbgi_key, U64 voff); -internal E_String2NumMap *d_query_cached_member_map_from_dbgi_key_voff(DI_Key dbgi_key, U64 voff); - -//- rjf: top-level command dispatch -internal void d_push_cmd(D_CmdKind kind, D_CmdParams *params); -#define d_cmd(kind, ...) d_push_cmd((kind), &(D_CmdParams){.thread = {0}, __VA_ARGS__}) - -//- rjf: command iteration -internal B32 d_next_cmd(D_Cmd **cmd); - -//////////////////////////////// -//~ rjf: Main Layer Top-Level Calls - -internal void d_init(void); -internal D_EventList d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_PathMapArray *path_maps, U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]); - #endif // DBG_ENGINE_CORE_H diff --git a/src/ctrl/ctrl_core.c b/src/dbg_engine/dbg_engine_ctrl.c similarity index 97% rename from src/ctrl/ctrl_core.c rename to src/dbg_engine/dbg_engine_ctrl.c index 2ecfd9ad..32357e0e 100644 --- a/src/ctrl/ctrl_core.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1,7192 +1,7187 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Generated Code - -#include "generated/ctrl.meta.c" - -//////////////////////////////// -//~ rjf: Basic Type Functions - -#if !defined(XXH_IMPLEMENTATION) -# define XXH_IMPLEMENTATION -# define XXH_STATIC_LINKING_ONLY -# include "third_party/xxHash/xxhash.h" -#endif - -internal U64 -ctrl_hash_from_string(String8 string) -{ - U64 result = XXH3_64bits_withSeed(string.str, string.size, 5381); - return result; -} - -internal U64 -ctrl_hash_from_handle(CTRL_Handle handle) -{ - U64 buf[] = {handle.machine_id, handle.dmn_handle.u64[0]}; - U64 hash = ctrl_hash_from_string(str8((U8 *)buf, sizeof(buf))); - return hash; -} - -internal CTRL_EventCause -ctrl_event_cause_from_dmn_event_kind(DMN_EventKind event_kind) -{ - CTRL_EventCause cause = CTRL_EventCause_Null; - switch(event_kind) - { - default:{}break; - case DMN_EventKind_Error: {cause = CTRL_EventCause_Error;}break; - case DMN_EventKind_Exception:{cause = CTRL_EventCause_InterruptedByException;}break; - case DMN_EventKind_Trap: {cause = CTRL_EventCause_InterruptedByTrap;}break; - case DMN_EventKind_Halt: {cause = CTRL_EventCause_InterruptedByHalt;}break; - } - return cause; -} - -internal CTRL_ExceptionKind -ctrl_exception_kind_from_dmn(DMN_ExceptionKind kind) -{ - CTRL_ExceptionKind result = CTRL_ExceptionKind_Null; - switch(kind) - { - default:{}break; - case DMN_ExceptionKind_MemoryRead: {result = CTRL_ExceptionKind_MemoryRead;}break; - case DMN_ExceptionKind_MemoryWrite: {result = CTRL_ExceptionKind_MemoryWrite;}break; - case DMN_ExceptionKind_MemoryExecute: {result = CTRL_ExceptionKind_MemoryExecute;}break; - case DMN_ExceptionKind_CppThrow: {result = CTRL_ExceptionKind_CppThrow;}break; - } - return result; -} - -internal CTRL_TlsModel -ctrl_dynamic_linker_type_from_dmn(DMN_TlsModel type) -{ - CTRL_TlsModel result = CTRL_TlsModel_Null; - switch(type) - { - default:{}break; - case DMN_TlsModel_Null: {result = CTRL_TlsModel_Null;}break; - case DMN_TlsModel_WinodwsNt: {result = CTRL_TlsModel_WinodwsNt;}break; - case DMN_TlsModel_Gnu: {result = CTRL_TlsModel_Gnu;}break; - } - return result; -} - -internal String8 -ctrl_string_from_event_kind(CTRL_EventKind kind) -{ - String8 result = {0}; - switch(kind) - { - default:{}break; - case CTRL_EventKind_Null: { result = str8_lit("Null");}break; - case CTRL_EventKind_Error: { result = str8_lit("Error");}break; - case CTRL_EventKind_Started: { result = str8_lit("Started");}break; - case CTRL_EventKind_Stopped: { result = str8_lit("Stopped");}break; - case CTRL_EventKind_NewProc: { result = str8_lit("NewProc");}break; - case CTRL_EventKind_NewThread: { result = str8_lit("NewThread");}break; - case CTRL_EventKind_NewModule: { result = str8_lit("NewModule");}break; - case CTRL_EventKind_EndProc: { result = str8_lit("EndProc");}break; - case CTRL_EventKind_EndThread: { result = str8_lit("EndThread");}break; - case CTRL_EventKind_EndModule: { result = str8_lit("EndModule");}break; - case CTRL_EventKind_ModuleDebugInfoPathChange: { result = str8_lit("ModuleDebugInfoPathChange");}break; - case CTRL_EventKind_DebugString: { result = str8_lit("DebugString");}break; - case CTRL_EventKind_ThreadName: { result = str8_lit("ThreadName");}break; - case CTRL_EventKind_MemReserve: { result = str8_lit("MemReserve");}break; - case CTRL_EventKind_MemCommit: { result = str8_lit("MemCommit");}break; - case CTRL_EventKind_MemDecommit: { result = str8_lit("MemDecommit");}break; - case CTRL_EventKind_MemRelease: { result = str8_lit("MemRelease");}break; - } - return result; -} - -internal String8 -ctrl_string_from_msg_kind(CTRL_MsgKind kind) -{ - String8 result = {0}; - switch(kind) - { - default:{}break; - case CTRL_MsgKind_Launch: {result = str8_lit("Launch");}break; - case CTRL_MsgKind_Attach: {result = str8_lit("Attach");}break; - case CTRL_MsgKind_Kill: {result = str8_lit("Kill");}break; - case CTRL_MsgKind_KillAll: {result = str8_lit("KillAll");}break; - case CTRL_MsgKind_Detach: {result = str8_lit("Detach");}break; - case CTRL_MsgKind_Run: {result = str8_lit("Run");}break; - case CTRL_MsgKind_SingleStep: {result = str8_lit("SingleStep");}break; - case CTRL_MsgKind_SetUserEntryPoints: {result = str8_lit("SetUserEntryPoints");}break; - case CTRL_MsgKind_SetModuleDebugInfoPath: {result = str8_lit("SetModuleDebugInfoPath");}break; - } - return result; -} - -internal CTRL_EntityKind -ctrl_entity_kind_from_string(String8 string) -{ - CTRL_EntityKind result = CTRL_EntityKind_Null; - for EachNonZeroEnumVal(CTRL_EntityKind, k) - { - if(str8_match(ctrl_entity_kind_code_name_table[k], string, 0)) - { - result = k; - break; - } - } - return result; -} - -internal DMN_TrapFlags -ctrl_dmn_trap_flags_from_user_breakpoint_flags(CTRL_UserBreakpointFlags flags) -{ - DMN_TrapFlags result = 0; - if(flags & CTRL_UserBreakpointFlag_BreakOnWrite) { result |= DMN_TrapFlag_BreakOnWrite; } - if(flags & CTRL_UserBreakpointFlag_BreakOnRead) { result |= DMN_TrapFlag_BreakOnRead; } - if(flags & CTRL_UserBreakpointFlag_BreakOnExecute) { result |= DMN_TrapFlag_BreakOnExecute; } - return result; -} - -internal CTRL_UserBreakpointFlags -ctrl_user_breakpoint_flags_from_dmn_trap_flags(DMN_TrapFlags flags) -{ - CTRL_UserBreakpointFlags result = 0; - if(flags & DMN_TrapFlag_BreakOnWrite) { result |= CTRL_UserBreakpointFlag_BreakOnWrite; } - if(flags & DMN_TrapFlag_BreakOnRead) { result |= CTRL_UserBreakpointFlag_BreakOnRead; } - if(flags & DMN_TrapFlag_BreakOnExecute) { result |= CTRL_UserBreakpointFlag_BreakOnExecute; } - return result; -} - -//////////////////////////////// -//~ rjf: Machine/Handle Pair Type Functions - -internal CTRL_Handle -ctrl_handle_zero(void) -{ - CTRL_Handle handle = {0}; - return handle; -} - -internal CTRL_Handle -ctrl_handle_make(CTRL_MachineID machine_id, DMN_Handle dmn_handle) -{ - CTRL_Handle handle = {machine_id, dmn_handle}; - return handle; -} - -internal B32 -ctrl_handle_match(CTRL_Handle a, CTRL_Handle b) -{ - B32 result = (a.machine_id == b.machine_id && - dmn_handle_match(a.dmn_handle, b.dmn_handle)); - return result; -} - -internal void -ctrl_handle_list_push(Arena *arena, CTRL_HandleList *list, CTRL_Handle *pair) -{ - CTRL_HandleNode *n = push_array(arena, CTRL_HandleNode, 1); - MemoryCopyStruct(&n->v, pair); - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -internal CTRL_HandleList -ctrl_handle_list_copy(Arena *arena, CTRL_HandleList *src) -{ - CTRL_HandleList dst = {0}; - for(CTRL_HandleNode *n = src->first; n != 0; n = n->next) - { - ctrl_handle_list_push(arena, &dst, &n->v); - } - return dst; -} - -internal CTRL_HandleArray -ctrl_handle_array_from_list(Arena *arena, CTRL_HandleList *src) -{ - CTRL_HandleArray array = {0}; - array.count = src->count; - array.v = push_array_no_zero(arena, CTRL_Handle, array.count); - { - U64 idx = 0; - for(CTRL_HandleNode *n = src->first; n != 0; n = n->next, idx += 1) - { - array.v[idx] = n->v; - } - } - return array; -} - -internal String8 -ctrl_string_from_handle(Arena *arena, CTRL_Handle handle) -{ - String8 result = push_str8f(arena, "$%I64x$%I64x", handle.machine_id, handle.dmn_handle.u64[0]); - return result; -} - -internal CTRL_Handle -ctrl_handle_from_string(String8 string) -{ - CTRL_Handle handle = {0}; - { - Temp scratch = scratch_begin(0, 0); - U8 split = '$'; - String8List parts = str8_split(scratch.arena, string, &split, 1, 0); - if(parts.first && parts.first->next) - { - CTRL_MachineID machine_id = u64_from_str8(parts.first->string, 16); - DMN_Handle dmn_handle = {0}; - dmn_handle.u64[0] = u64_from_str8(parts.first->next->string, 16); - handle.machine_id = machine_id; - handle.dmn_handle = dmn_handle; - } - scratch_end(scratch); - } - return handle; -} - -//////////////////////////////// -//~ rjf: Trap Type Functions - -internal void -ctrl_trap_list_push(Arena *arena, CTRL_TrapList *list, CTRL_Trap *trap) -{ - CTRL_TrapNode *node = push_array(arena, CTRL_TrapNode, 1); - MemoryCopyStruct(&node->v, trap); - SLLQueuePush(list->first, list->last, node); - list->count += 1; -} - -internal CTRL_TrapList -ctrl_trap_list_copy(Arena *arena, CTRL_TrapList *src) -{ - CTRL_TrapList dst = {0}; - for(CTRL_TrapNode *src_n = src->first; src_n != 0; src_n = src_n->next) - { - ctrl_trap_list_push(arena, &dst, &src_n->v); - } - return dst; -} - -//////////////////////////////// -//~ rjf: User Breakpoint Type Functions - -internal void -ctrl_user_breakpoint_list_push(Arena *arena, CTRL_UserBreakpointList *list, CTRL_UserBreakpoint *bp) -{ - CTRL_UserBreakpointNode *n = push_array(arena, CTRL_UserBreakpointNode, 1); - MemoryCopyStruct(&n->v, bp); - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -internal CTRL_UserBreakpointList -ctrl_user_breakpoint_list_copy(Arena *arena, CTRL_UserBreakpointList *src) -{ - CTRL_UserBreakpointList dst = {0}; - for(CTRL_UserBreakpointNode *src_n = src->first; src_n != 0; src_n = src_n->next) - { - CTRL_UserBreakpoint dst_bp = zero_struct; - MemoryCopyStruct(&dst_bp, &src_n->v); - dst_bp.string = push_str8_copy(arena, src_n->v.string); - dst_bp.condition = push_str8_copy(arena, src_n->v.condition); - ctrl_user_breakpoint_list_push(arena, &dst, &dst_bp); - } - return dst; -} - -//////////////////////////////// -//~ rjf: Message Type Functions - -//- rjf: deep copying - -internal void -ctrl_msg_deep_copy(Arena *arena, CTRL_Msg *dst, CTRL_Msg *src) -{ - MemoryCopyStruct(dst, src); - dst->path = push_str8_copy(arena, src->path); - dst->entry_points = str8_list_copy(arena, &src->entry_points); - dst->cmd_line_string_list = str8_list_copy(arena, &src->cmd_line_string_list); - dst->env_string_list = str8_list_copy(arena, &src->env_string_list); - dst->traps = ctrl_trap_list_copy(arena, &src->traps); - dst->user_bps = ctrl_user_breakpoint_list_copy(arena, &src->user_bps); -} - -//- rjf: list building - -internal CTRL_Msg * -ctrl_msg_list_push(Arena *arena, CTRL_MsgList *list) -{ - CTRL_MsgNode *n = push_array(arena, CTRL_MsgNode, 1); - SLLQueuePush(list->first, list->last, n); - list->count += 1; - CTRL_Msg *msg = &n->v; - return msg; -} - -internal CTRL_MsgList -ctrl_msg_list_deep_copy(Arena *arena, CTRL_MsgList *src) -{ - CTRL_MsgList dst = {0}; - for(CTRL_MsgNode *n = src->first; n != 0; n = n->next) - { - CTRL_Msg *src_msg = &n->v; - CTRL_Msg *dst_msg = ctrl_msg_list_push(arena, &dst); - ctrl_msg_deep_copy(arena, dst_msg, src_msg); - } - return dst; -} - -internal void -ctrl_msg_list_concat_in_place(CTRL_MsgList *dst, CTRL_MsgList *src) -{ - if(dst->last && src->first) - { - dst->last->next = src->first; - dst->last = src->last; - dst->count += src->count; - } - else if(src->first) - { - MemoryCopyStruct(dst, src); - } - MemoryZeroStruct(src); -} - -//- rjf: serialization - -internal String8 -ctrl_serialized_string_from_msg_list(Arena *arena, CTRL_MsgList *msgs) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List msgs_srlzed = {0}; - str8_serial_begin(scratch.arena, &msgs_srlzed); - { - // rjf: write message count - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msgs->count); - - // rjf: write all message data - for(CTRL_MsgNode *msg_n = msgs->first; msg_n != 0; msg_n = msg_n->next) - { - CTRL_Msg *msg = &msg_n->v; - - // rjf: write flat parts - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->kind); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->run_flags); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->msg_id); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entity); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->parent); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entity_id); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->exit_code); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_inherit); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->debug_subprocesses); - str8_serial_push_array (scratch.arena, &msgs_srlzed, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); - - // rjf: write path string - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->path.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, msg->path.str, msg->path.size); - - // rjf: write entry point string list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entry_points.node_count); - for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) - { - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); - } - - // rjf: write command line string list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->cmd_line_string_list.node_count); - for(String8Node *n = msg->cmd_line_string_list.first; n != 0; n = n->next) - { - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); - } - - // rjf: write environment string list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_string_list.node_count); - for(String8Node *n = msg->env_string_list.first; n != 0; n = n->next) - { - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); - } - - // rjf: write stdout/stderr/stdin paths - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->stdout_path.size); - str8_serial_push_string(scratch.arena, &msgs_srlzed, msg->stdout_path); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->stderr_path.size); - str8_serial_push_string(scratch.arena, &msgs_srlzed, msg->stderr_path); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->stdin_path.size); - str8_serial_push_string(scratch.arena, &msgs_srlzed, msg->stdin_path); - - // rjf: write trap list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->traps.count); - for(CTRL_TrapNode *n = msg->traps.first; n != 0; n = n->next) - { - CTRL_Trap *trap = &n->v; - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &trap->flags); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &trap->vaddr); - } - - // rjf: write user breakpoint list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->user_bps.count); - for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) - { - CTRL_UserBreakpoint *bp = &n->v; - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->kind); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->flags); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->id); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->string.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->string.str, bp->string.size); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->pt); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->size); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->condition.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->condition.str, bp->condition.size); - } - } - } - String8 string = str8_serial_end(arena, &msgs_srlzed); - scratch_end(scratch); - return string; -} - -internal CTRL_MsgList -ctrl_msg_list_from_serialized_string(Arena *arena, String8 string) -{ - CTRL_MsgList msgs = {0}; - { - U64 read_off = 0; - - // rjf: read message count - U64 msg_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &msg_count); - - // rjf: read data for all messages - for(U64 msg_idx = 0; msg_idx < msg_count; msg_idx += 1) - { - // rjf: construct message - CTRL_MsgNode *msg_node = push_array(arena, CTRL_MsgNode, 1); - SLLQueuePush(msgs.first, msgs.last, msg_node); - msgs.count += 1; - CTRL_Msg *msg = &msg_node->v; - - // rjf: read flat data - read_off += str8_deserial_read_struct(string, read_off, &msg->kind); - read_off += str8_deserial_read_struct(string, read_off, &msg->run_flags); - read_off += str8_deserial_read_struct(string, read_off, &msg->msg_id); - read_off += str8_deserial_read_struct(string, read_off, &msg->entity); - read_off += str8_deserial_read_struct(string, read_off, &msg->parent); - read_off += str8_deserial_read_struct(string, read_off, &msg->entity_id); - read_off += str8_deserial_read_struct(string, read_off, &msg->exit_code); - read_off += str8_deserial_read_struct(string, read_off, &msg->env_inherit); - read_off += str8_deserial_read_struct(string, read_off, &msg->debug_subprocesses); - read_off += str8_deserial_read_array (string, read_off, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); - - // rjf: read path string - read_off += str8_deserial_read_struct(string, read_off, &msg->path.size); - msg->path.str = push_array_no_zero(arena, U8, msg->path.size); - read_off += str8_deserial_read(string, read_off, msg->path.str, msg->path.size, 1); - - // rjf: read entry point string list - U64 entry_point_list_string_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &entry_point_list_string_count); - for(U64 idx = 0; idx < entry_point_list_string_count; idx += 1) - { - String8 str = {0}; - read_off += str8_deserial_read_struct(string, read_off, &str.size); - str.str = push_array_no_zero(arena, U8, str.size); - read_off += str8_deserial_read(string, read_off, str.str, str.size, 1); - str8_list_push(arena, &msg->entry_points, str); - } - - // rjf: read command line string list - U64 cmd_line_string_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &cmd_line_string_count); - for(U64 idx = 0; idx < cmd_line_string_count; idx += 1) - { - String8 cmd_line_str = {0}; - read_off += str8_deserial_read_struct(string, read_off, &cmd_line_str.size); - cmd_line_str.str = push_array_no_zero(arena, U8, cmd_line_str.size); - read_off += str8_deserial_read(string, read_off, cmd_line_str.str, cmd_line_str.size, 1); - str8_list_push(arena, &msg->cmd_line_string_list, cmd_line_str); - } - - // rjf: read environment string list - U64 env_string_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &env_string_count); - for(U64 idx = 0; idx < env_string_count; idx += 1) - { - String8 env_str = {0}; - read_off += str8_deserial_read_struct(string, read_off, &env_str.size); - env_str.str = push_array_no_zero(arena, U8, env_str.size); - read_off += str8_deserial_read(string, read_off, env_str.str, env_str.size, 1); - str8_list_push(arena, &msg->env_string_list, env_str); - } - - // rjf: read stdout/stderr/stdin paths - read_off += str8_deserial_read_struct(string, read_off, &msg->stdout_path.size); - msg->stdout_path.str = push_array(arena, U8, msg->stdout_path.size); - read_off += str8_deserial_read(string, read_off, msg->stdout_path.str, msg->stdout_path.size, 1); - read_off += str8_deserial_read_struct(string, read_off, &msg->stderr_path.size); - msg->stderr_path.str = push_array(arena, U8, msg->stderr_path.size); - read_off += str8_deserial_read(string, read_off, msg->stderr_path.str, msg->stderr_path.size, 1); - read_off += str8_deserial_read_struct(string, read_off, &msg->stdin_path.size); - msg->stdin_path.str = push_array(arena, U8, msg->stdin_path.size); - read_off += str8_deserial_read(string, read_off, msg->stdin_path.str, msg->stdin_path.size, 1); - - // rjf: read trap list - U64 trap_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &trap_count); - for(U64 idx = 0; idx < trap_count; idx += 1) - { - CTRL_TrapNode *n = push_array(arena, CTRL_TrapNode, 1); - SLLQueuePush(msg->traps.first, msg->traps.last, n); - msg->traps.count += 1; - CTRL_Trap *trap = &n->v; - read_off += str8_deserial_read_struct(string, read_off, &trap->flags); - read_off += str8_deserial_read_struct(string, read_off, &trap->vaddr); - } - - // rjf: read user breakpoint list - U64 user_bp_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &user_bp_count); - for(U64 idx = 0; idx < user_bp_count; idx += 1) - { - CTRL_UserBreakpointNode *n = push_array(arena, CTRL_UserBreakpointNode, 1); - SLLQueuePush(msg->user_bps.first, msg->user_bps.last, n); - msg->user_bps.count += 1; - CTRL_UserBreakpoint *bp = &n->v; - read_off += str8_deserial_read_struct(string, read_off, &bp->kind); - read_off += str8_deserial_read_struct(string, read_off, &bp->flags); - read_off += str8_deserial_read_struct(string, read_off, &bp->id); - read_off += str8_deserial_read_struct(string, read_off, &bp->string.size); - bp->string.str = push_array_no_zero(arena, U8, bp->string.size); - read_off += str8_deserial_read(string, read_off, bp->string.str, bp->string.size, 1); - read_off += str8_deserial_read_struct(string, read_off, &bp->pt); - read_off += str8_deserial_read_struct(string, read_off, &bp->size); - read_off += str8_deserial_read_struct(string, read_off, &bp->condition.size); - bp->condition.str = push_array_no_zero(arena, U8, bp->condition.size); - read_off += str8_deserial_read(string, read_off, bp->condition.str, bp->condition.size, 1); - } - } - } - return msgs; -} - -//////////////////////////////// -//~ rjf: Event Type Functions - -//- rjf: list building - -internal CTRL_Event * -ctrl_event_list_push(Arena *arena, CTRL_EventList *list) -{ - CTRL_EventNode *n = push_array(arena, CTRL_EventNode, 1); - SLLQueuePush(list->first, list->last, n); - list->count += 1; - CTRL_Event *event = &n->v; - return event; -} - -internal void -ctrl_event_list_concat_in_place(CTRL_EventList *dst, CTRL_EventList *to_push) -{ - if(dst->last == 0) - { - MemoryCopyStruct(dst, to_push); - } - else if(to_push->first != 0) - { - dst->last->next = to_push->first; - dst->last = to_push->last; - dst->count += to_push->count; - } - MemoryZeroStruct(to_push); -} - -//- rjf: serialization - -internal String8 -ctrl_serialized_string_from_event(Arena *arena, CTRL_Event *event, U64 max) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List srl = {0}; - str8_serial_begin(scratch.arena, &srl); - { - str8_serial_push_struct(scratch.arena, &srl, &event->kind); - str8_serial_push_struct(scratch.arena, &srl, &event->cause); - str8_serial_push_struct(scratch.arena, &srl, &event->exception_kind); - str8_serial_push_struct(scratch.arena, &srl, &event->msg_id); - str8_serial_push_struct(scratch.arena, &srl, &event->entity); - str8_serial_push_struct(scratch.arena, &srl, &event->parent); - str8_serial_push_struct(scratch.arena, &srl, &event->arch); - str8_serial_push_struct(scratch.arena, &srl, &event->u64_code); - str8_serial_push_struct(scratch.arena, &srl, &event->entity_id); - str8_serial_push_struct(scratch.arena, &srl, &event->vaddr_rng); - str8_serial_push_struct(scratch.arena, &srl, &event->rip_vaddr); - str8_serial_push_struct(scratch.arena, &srl, &event->stack_base); - str8_serial_push_struct(scratch.arena, &srl, &event->tls_root); - str8_serial_push_struct(scratch.arena, &srl, &event->timestamp); - str8_serial_push_struct(scratch.arena, &srl, &event->exception_code); - str8_serial_push_struct(scratch.arena, &srl, &event->rgba); - str8_serial_push_struct(scratch.arena, &srl, &event->bp_flags); - str8_serial_push_struct(scratch.arena, &srl, &event->target_os); - str8_serial_push_struct(scratch.arena, &srl, &event->tls_model); - str8_serial_push_struct(scratch.arena, &srl, &event->tls_index); - str8_serial_push_struct(scratch.arena, &srl, &event->tls_offset); - String8 string = event->string; - string.size = Min(string.size, max-srl.total_size); - str8_serial_push_struct(scratch.arena, &srl, &string.size); - str8_serial_push_data(scratch.arena, &srl, string.str, string.size); - } - String8 string = str8_serial_end(arena, &srl); - scratch_end(scratch); - return string; -} - -internal CTRL_Event -ctrl_event_from_serialized_string(Arena *arena, String8 string) -{ - CTRL_Event event = zero_struct; - { - U64 read_off = 0; - read_off += str8_deserial_read_struct(string, read_off, &event.kind); - read_off += str8_deserial_read_struct(string, read_off, &event.cause); - read_off += str8_deserial_read_struct(string, read_off, &event.exception_kind); - read_off += str8_deserial_read_struct(string, read_off, &event.msg_id); - read_off += str8_deserial_read_struct(string, read_off, &event.entity); - read_off += str8_deserial_read_struct(string, read_off, &event.parent); - read_off += str8_deserial_read_struct(string, read_off, &event.arch); - read_off += str8_deserial_read_struct(string, read_off, &event.u64_code); - read_off += str8_deserial_read_struct(string, read_off, &event.entity_id); - read_off += str8_deserial_read_struct(string, read_off, &event.vaddr_rng); - read_off += str8_deserial_read_struct(string, read_off, &event.rip_vaddr); - read_off += str8_deserial_read_struct(string, read_off, &event.stack_base); - read_off += str8_deserial_read_struct(string, read_off, &event.tls_root); - read_off += str8_deserial_read_struct(string, read_off, &event.timestamp); - read_off += str8_deserial_read_struct(string, read_off, &event.exception_code); - read_off += str8_deserial_read_struct(string, read_off, &event.rgba); - read_off += str8_deserial_read_struct(string, read_off, &event.bp_flags); - read_off += str8_deserial_read_struct(string, read_off, &event.target_os); - read_off += str8_deserial_read_struct(string, read_off, &event.tls_model); - read_off += str8_deserial_read_struct(string, read_off, &event.tls_index); - read_off += str8_deserial_read_struct(string, read_off, &event.tls_offset); - read_off += str8_deserial_read_struct(string, read_off, &event.string.size); - event.string.str = push_array_no_zero(arena, U8, event.string.size); - read_off += str8_deserial_read(string, read_off, event.string.str, event.string.size, 1); - } - return event; -} - -//////////////////////////////// -//~ rjf: Entity Type Functions - -//- rjf: entity list data structures - -internal void -ctrl_entity_list_push(Arena *arena, CTRL_EntityList *list, CTRL_Entity *entity) -{ - CTRL_EntityNode *n = push_array(arena, CTRL_EntityNode, 1); - n->v = entity; - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -internal CTRL_EntityList -ctrl_entity_list_from_handle_list(Arena *arena, CTRL_EntityCtx *ctx, CTRL_HandleList *list) -{ - CTRL_EntityList result = {0}; - for(CTRL_HandleNode *n = list->first; n != 0; n = n->next) - { - CTRL_Entity *entity = ctrl_entity_from_handle(ctx, n->v); - ctrl_entity_list_push(arena, &result, entity); - } - return result; -} - -//- rjf: entity array data structure - -internal CTRL_EntityArray -ctrl_entity_array_from_list(Arena *arena, CTRL_EntityList *list) -{ - CTRL_EntityArray result = {0}; - result.count = list->count; - result.v = push_array_no_zero(arena, CTRL_Entity *, result.count); - U64 idx = 0; - for(CTRL_EntityNode *n = list->first; n != 0; n = n->next, idx += 1) - { - result.v[idx] = n->v; - } - return result; -} - -//- rjf: entity context (entity group read-only) functions - -internal CTRL_Entity * -ctrl_entity_from_handle(CTRL_EntityCtx *ctx, CTRL_Handle handle) -{ - CTRL_Entity *entity = &ctrl_entity_nil; - if(!ctrl_handle_match(handle, ctrl_handle_zero())) - { - U64 hash = ctrl_hash_from_handle(handle); - U64 slot_idx = hash%ctx->hash_slots_count; - CTRL_EntityHashSlot *slot = &ctx->hash_slots[slot_idx]; - CTRL_EntityHashNode *node = 0; - for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->entity->handle, handle)) - { - entity = n->entity; - break; - } - } - } - return entity; -} - -internal CTRL_Entity * -ctrl_entity_child_from_kind(CTRL_Entity *parent, CTRL_EntityKind kind) -{ - CTRL_Entity *result = &ctrl_entity_nil; - for(CTRL_Entity *child = parent->first; - child != &ctrl_entity_nil; - child = child->next) - { - if(child->kind == kind) - { - result = child; - break; - } - } - return result; -} - -internal CTRL_Entity * -ctrl_entity_ancestor_from_kind(CTRL_Entity *entity, CTRL_EntityKind kind) -{ - CTRL_Entity *result = &ctrl_entity_nil; - for(CTRL_Entity *p = entity->parent; p != &ctrl_entity_nil; p = p->parent) - { - if(p->kind == kind) - { - result = p; - break; - } - } - return result; -} - -internal CTRL_Entity * -ctrl_process_from_entity(CTRL_Entity *entity) -{ - CTRL_Entity *result = &ctrl_entity_nil; - if(entity->kind == CTRL_EntityKind_Process) - { - result = entity; - } - else - { - result = ctrl_entity_ancestor_from_kind(entity, CTRL_EntityKind_Process); - } - return result; -} - -internal CTRL_Entity * -ctrl_module_from_process_vaddr(CTRL_Entity *process, U64 vaddr) -{ - CTRL_Entity *result = &ctrl_entity_nil; - for(CTRL_Entity *child = process->first; - child != &ctrl_entity_nil; - child = child->next) - { - if(child->kind == CTRL_EntityKind_Module && contains_1u64(child->vaddr_range, vaddr)) - { - result = child; - break; - } - } - return result; -} - -internal DI_Key -ctrl_dbgi_key_from_module(CTRL_Entity *module) -{ - CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); - DI_Key dbgi_key = di_key_from_path_timestamp(debug_info_path->string, debug_info_path->timestamp); - return dbgi_key; -} - -internal CTRL_Entity * -ctrl_module_from_thread_candidates(CTRL_EntityCtx *ctx, CTRL_Entity *thread, CTRL_EntityList *candidates) -{ - CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process); - U64 thread_rip_vaddr = ctrl_rip_from_thread(ctx, thread->handle); - CTRL_Entity *src_module = ctrl_module_from_process_vaddr(process, thread_rip_vaddr); - CTRL_Entity *module = &ctrl_entity_nil; - for(CTRL_EntityNode *n = candidates->first; n != 0; n = n->next) - { - CTRL_Entity *candidate_module = n->v; - CTRL_Entity *candidate_process = ctrl_entity_ancestor_from_kind(candidate_module, CTRL_EntityKind_Process); - if(candidate_process == process) - { - module = candidate_module; - } - if(candidate_module == src_module) - { - break; - } - } - return module; -} - -internal U64 -ctrl_vaddr_from_voff(CTRL_Entity *module, U64 voff) -{ - U64 result = voff + module->vaddr_range.min; - return result; -} - -internal U64 -ctrl_voff_from_vaddr(CTRL_Entity *module, U64 vaddr) -{ - U64 result = vaddr - module->vaddr_range.min; - return result; -} - -internal Rng1U64 -ctrl_vaddr_range_from_voff_range(CTRL_Entity *module, Rng1U64 voff_range) -{ - U64 dim = dim_1u64(voff_range); - U64 min = ctrl_vaddr_from_voff(module, voff_range.min); - Rng1U64 result = {min, min+dim}; - return result; -} - -internal Rng1U64 -ctrl_voff_range_from_vaddr_range(CTRL_Entity *module, Rng1U64 vaddr_range) -{ - U64 dim = dim_1u64(vaddr_range); - U64 min = ctrl_voff_from_vaddr(module, vaddr_range.min); - Rng1U64 result = {min, min+dim}; - return result; -} - -internal B32 -ctrl_entity_tree_is_frozen(CTRL_Entity *root) -{ - B32 is_frozen = 1; - for(CTRL_Entity *e = root; e != &ctrl_entity_nil; e = ctrl_entity_rec_depth_first_pre(e, root).next) - { - if(e->kind == CTRL_EntityKind_Thread && !e->is_frozen) - { - is_frozen = 0; - break; - } - } - return is_frozen; -} - -//- rjf: entity ctx r/w store state functions - -internal CTRL_EntityCtxRWStore * -ctrl_entity_ctx_rw_store_alloc(void) -{ - Arena *arena = arena_alloc(); - CTRL_EntityCtxRWStore *store = push_array(arena, CTRL_EntityCtxRWStore, 1); - store->arena = arena; - store->ctx.hash_slots_count = 1024; - store->ctx.hash_slots = push_array(arena, CTRL_EntityHashSlot, store->ctx.hash_slots_count); - CTRL_Entity *root = store->ctx.root = ctrl_entity_alloc(store, &ctrl_entity_nil, CTRL_EntityKind_Root, Arch_Null, ctrl_handle_zero(), 0); - CTRL_Entity *local_machine = ctrl_entity_alloc(store, root, CTRL_EntityKind_Machine, Arch_CURRENT, ctrl_handle_make(CTRL_MachineID_Local, dmn_handle_zero()), 0); - Temp scratch = scratch_begin(0, 0); - String8 local_machine_name = push_str8f(scratch.arena, "This PC (%S)", os_get_system_info()->machine_name); - ctrl_entity_equip_string(store, local_machine, local_machine_name); - scratch_end(scratch); - return store; -} - -internal void -ctrl_entity_ctx_rw_store_release(CTRL_EntityCtxRWStore *store) -{ - arena_release(store->arena); -} - -//- rjf: string allocation/deletion - -internal U64 -ctrl_name_bucket_num_from_string_size(U64 size) -{ - U64 bucket_num = 0; - if(size > 0) - { - for EachElement(idx, ctrl_entity_string_bucket_chunk_sizes) - { - if(size <= ctrl_entity_string_bucket_chunk_sizes[idx]) - { - bucket_num = idx+1; - break; - } - } - } - return bucket_num; -} - -internal String8 -ctrl_entity_string_alloc(CTRL_EntityCtxRWStore *store, String8 string) -{ - //- rjf: allocate node - CTRL_EntityStringChunkNode *node = 0; - { - U64 bucket_num = ctrl_name_bucket_num_from_string_size(string.size); - if(bucket_num == ArrayCount(ctrl_entity_string_bucket_chunk_sizes)) - { - CTRL_EntityStringChunkNode *best_node = 0; - CTRL_EntityStringChunkNode *best_node_prev = 0; - U64 best_node_size = max_U64; - { - for(CTRL_EntityStringChunkNode *n = store->free_string_chunks[bucket_num-1], *prev = 0; n != 0; (prev = n, n = n->next)) - { - if(n->size >= string.size && n->size < best_node_size) - { - best_node = n; - best_node_prev = prev; - best_node_size = n->size; - } - } - } - if(best_node != 0) - { - node = best_node; - if(best_node_prev) - { - best_node_prev->next = best_node->next; - } - else - { - store->free_string_chunks[bucket_num-1] = best_node->next; - } - } - else - { - U64 chunk_size = u64_up_to_pow2(string.size); - node = (CTRL_EntityStringChunkNode *)push_array(store->arena, U8, chunk_size); - } - } - else if(bucket_num != 0) - { - node = store->free_string_chunks[bucket_num-1]; - if(node != 0) - { - SLLStackPop(store->free_string_chunks[bucket_num-1]); - } - else - { - node = (CTRL_EntityStringChunkNode *)push_array(store->arena, U8, ctrl_entity_string_bucket_chunk_sizes[bucket_num-1]); - } - } - } - - //- rjf: fill node - String8 result = {0}; - if(node != 0) - { - result.str = (U8 *)node; - result.size = string.size; - MemoryCopy(result.str, string.str, result.size); - } - return result; -} - -internal void -ctrl_entity_string_release(CTRL_EntityCtxRWStore *store, String8 string) -{ - U64 bucket_num = ctrl_name_bucket_num_from_string_size(string.size); - if(1 <= bucket_num && bucket_num <= ArrayCount(ctrl_entity_string_bucket_chunk_sizes)) - { - U64 bucket_idx = bucket_num-1; - CTRL_EntityStringChunkNode *node = (CTRL_EntityStringChunkNode *)string.str; - SLLStackPush(store->free_string_chunks[bucket_idx], node); - node->size = u64_up_to_pow2(string.size); - } -} - -//- rjf: entity construction/deletion - -internal CTRL_Entity * -ctrl_entity_alloc(CTRL_EntityCtxRWStore *store, CTRL_Entity *parent, CTRL_EntityKind kind, Arch arch, CTRL_Handle handle, U64 id) -{ - CTRL_Entity *entity = &ctrl_entity_nil; - { - // rjf: allocate - entity = store->free; - { - if(entity != 0) - { - SLLStackPop(store->free); - } - else - { - entity = push_array_no_zero(store->arena, CTRL_Entity, 1); - } - MemoryZeroStruct(entity); - } - - // rjf: fill - { - entity->kind = kind; - entity->arch = arch; - entity->handle = handle; - entity->id = id; - entity->parent = parent; - entity->next = entity->prev = entity->first = entity->last = &ctrl_entity_nil; - if(parent != &ctrl_entity_nil) - { - DLLPushBack_NPZ(&ctrl_entity_nil, parent->first, parent->last, entity, next, prev); - } - } - - // rjf: insert into hash map - { - U64 hash = ctrl_hash_from_handle(handle); - U64 slot_idx = hash%store->ctx.hash_slots_count; - CTRL_EntityHashSlot *slot = &store->ctx.hash_slots[slot_idx]; - CTRL_EntityHashNode *node = 0; - for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->entity->handle, handle)) - { - node = n; - break; - } - } - if(node == 0) - { - node = store->hash_node_free; - if(node != 0) - { - SLLStackPop(store->hash_node_free); - } - else - { - node = push_array_no_zero(store->arena, CTRL_EntityHashNode, 1); - } - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - node->entity = entity; - } - } - - // rjf: bump counters - store->ctx.entity_kind_counts[kind] += 1; - store->ctx.entity_kind_alloc_gens[kind] += 1; - } - return entity; -} - -internal void -ctrl_entity_release(CTRL_EntityCtxRWStore *store, CTRL_Entity *entity) -{ - // rjf: unhook root - if(entity->parent != &ctrl_entity_nil) - { - DLLRemove_NPZ(&ctrl_entity_nil, entity->parent->first, entity->parent->last, entity, next, prev); - } - - // rjf: walk every entity in this tree, free each - if(entity != &ctrl_entity_nil) - { - Temp scratch = scratch_begin(0, 0); - typedef struct Task Task; - struct Task - { - Task *next; - CTRL_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(CTRL_Entity *child = t->e->first; child != &ctrl_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(store->free, t->e); - - // rjf: remove from hash map - { - U64 hash = ctrl_hash_from_handle(t->e->handle); - U64 slot_idx = hash%store->ctx.hash_slots_count; - CTRL_EntityHashSlot *slot = &store->ctx.hash_slots[slot_idx]; - CTRL_EntityHashNode *node = 0; - for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->entity->handle, t->e->handle)) - { - DLLRemove(slot->first, slot->last, n); - SLLStackPush(store->hash_node_free, n); - break; - } - } - } - - // rjf: dec counter - store->ctx.entity_kind_counts[t->e->kind] -= 1; - store->ctx.entity_kind_alloc_gens[t->e->kind] += 1; - } - scratch_end(scratch); - } -} - -//- rjf: entity equipment - -internal void -ctrl_entity_equip_string(CTRL_EntityCtxRWStore *store, CTRL_Entity *entity, String8 string) -{ - if(entity->string.size != 0) - { - ctrl_entity_string_release(store, entity->string); - } - entity->string = ctrl_entity_string_alloc(store, string); -} - -//- rjf: accelerated entity context lookups - -internal CTRL_EntityCtxLookupAccel * -ctrl_thread_entity_ctx_lookup_accel(void) -{ - if(ctrl_entity_ctx_lookup_accel == 0) - { - Arena *arena = arena_alloc(); - ctrl_entity_ctx_lookup_accel = push_array(arena, CTRL_EntityCtxLookupAccel, 1); - ctrl_entity_ctx_lookup_accel->arena = arena; - for EachEnumVal(CTRL_EntityKind, k) - { - ctrl_entity_ctx_lookup_accel->entity_kind_arrays_arenas[k] = arena_alloc(); - } - } - return ctrl_entity_ctx_lookup_accel; -} - -internal CTRL_EntityArray -ctrl_entity_array_from_kind(CTRL_EntityCtx *ctx, CTRL_EntityKind kind) -{ - CTRL_EntityCtxLookupAccel *accel = ctrl_thread_entity_ctx_lookup_accel(); - if(accel->entity_kind_arrays_gens[kind] != ctx->entity_kind_alloc_gens[kind]) - { - Temp scratch = scratch_begin(0, 0); - CTRL_EntityList entities = {0}; - for(CTRL_Entity *e = ctx->root; - e != &ctrl_entity_nil; - e = ctrl_entity_rec_depth_first_pre(e, ctx->root).next) - { - if(e->kind == kind) - { - ctrl_entity_list_push(scratch.arena, &entities, e); - } - } - accel->entity_kind_arrays_gens[kind] = ctx->entity_kind_alloc_gens[kind]; - arena_clear(accel->entity_kind_arrays_arenas[kind]); - accel->entity_kind_arrays[kind] = ctrl_entity_array_from_list(accel->entity_kind_arrays_arenas[kind], &entities); - scratch_end(scratch); - } - return accel->entity_kind_arrays[kind]; -} - -internal CTRL_EntityList -ctrl_modules_from_dbgi_key(Arena *arena, CTRL_EntityCtx *ctx, DI_Key dbgi_key) -{ - CTRL_EntityList list = {0}; - CTRL_EntityArray all_modules = ctrl_entity_array_from_kind(ctx, CTRL_EntityKind_Module); - for EachIndex(idx, all_modules.count) - { - CTRL_Entity *module = all_modules.v[idx]; - DI_Key module_dbgi_key = ctrl_dbgi_key_from_module(module); - if(di_key_match(module_dbgi_key, dbgi_key)) - { - ctrl_entity_list_push(arena, &list, module); - } - } - return list; -} - -internal CTRL_Entity * -ctrl_thread_from_id(CTRL_EntityCtx *ctx, U64 id) -{ - CTRL_Entity *thread = &ctrl_entity_nil; - CTRL_EntityArray threads = ctrl_entity_array_from_kind(ctx, CTRL_EntityKind_Thread); - for EachIndex(idx, threads.count) - { - if(threads.v[idx]->id == id) - { - thread = threads.v[idx]; - } - } - return thread; -} - -//- rjf: entity tree iteration - -internal CTRL_EntityRec -ctrl_entity_rec_depth_first(CTRL_Entity *entity, CTRL_Entity *subtree_root, U64 sib_off, U64 child_off) -{ - CTRL_EntityRec result = {0}; - result.next = &ctrl_entity_nil; - if((*MemberFromOffset(CTRL_Entity **, entity, child_off)) != &ctrl_entity_nil) - { - result.next = *MemberFromOffset(CTRL_Entity **, entity, child_off); - result.push_count = 1; - } - else for(CTRL_Entity *parent = entity; parent != subtree_root && parent != &ctrl_entity_nil; parent = parent->parent) - { - if(parent != subtree_root && (*MemberFromOffset(CTRL_Entity **, parent, sib_off)) != &ctrl_entity_nil) - { - result.next = *MemberFromOffset(CTRL_Entity **, parent, sib_off); - break; - } - result.pop_count += 1; - } - return result; -} - -//- rjf: applying events to entity caches - -internal void -ctrl_entity_store_apply_events(CTRL_EntityCtxRWStore *store, CTRL_EventList *list) -{ - //- rjf: scan events & construct entities - for(CTRL_EventNode *n = list->first; n != 0; n = n->next) - { - CTRL_Event *event = &n->v; - switch(event->kind) - { - default:{}break; - - //- rjf: processes - case CTRL_EventKind_NewProc: - { - CTRL_Entity *machine = ctrl_entity_from_handle(&store->ctx, ctrl_handle_make(event->entity.machine_id, dmn_handle_zero())); - if(machine != &ctrl_entity_nil) - { - CTRL_Entity *process = ctrl_entity_alloc(store, machine, CTRL_EntityKind_Process, event->arch, event->entity, (U64)event->entity_id); - process->tls_model = event->tls_model; - process->target_os = event->target_os; - } - }break; - case CTRL_EventKind_EndProc: - { - CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->entity); - ctrl_entity_release(store, process); - for(CTRL_Entity *entry = store->ctx.root->first, *next = &ctrl_entity_nil; - entry != &ctrl_entity_nil; - entry = next) - { - next = entry->next; - if(entry->kind == CTRL_EntityKind_EntryPoint && entry->id == process->id) - { - ctrl_entity_release(store, entry); - } - } - }break; - - //- rjf: threads - case CTRL_EventKind_NewThread: - { - CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); - if(process != &ctrl_entity_nil) - { - CTRL_Entity *thread = ctrl_entity_alloc(store, process, CTRL_EntityKind_Thread, event->arch, event->entity, (U64)event->entity_id); - CTRL_Entity *first_thread = ctrl_entity_child_from_kind(process, CTRL_EntityKind_Thread); - if(first_thread == thread) - { - ctrl_entity_equip_string(store, thread, str8_lit("main_thread")); - } - CTRL_EntityArray pending_thread_names = ctrl_entity_array_from_kind(&store->ctx, CTRL_EntityKind_PendingThreadName); - for EachIndex(idx, pending_thread_names.count) - { - CTRL_Entity *entity = pending_thread_names.v[idx]; - if(entity->id == event->entity_id) - { - ctrl_entity_equip_string(store, thread, entity->string); - ctrl_entity_release(store, entity); - break; - } - } - CTRL_EntityArray pending_thread_colors = ctrl_entity_array_from_kind(&store->ctx, CTRL_EntityKind_PendingThreadColor); - for EachIndex(idx, pending_thread_colors.count) - { - CTRL_Entity *entity = pending_thread_colors.v[idx]; - if(entity->id == event->entity_id) - { - thread->rgba = entity->rgba; - ctrl_entity_release(store, entity); - break; - } - } - thread->stack_base = event->stack_base; - } - //ctrl_rip_from_thread(&store->ctx, event->entity); - }break; - case CTRL_EventKind_EndThread: - { - CTRL_Entity *thread = ctrl_entity_from_handle(&store->ctx, event->entity); - ctrl_entity_release(store, thread); - }break; - case CTRL_EventKind_ThreadName: - { - CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); - CTRL_Entity *thread = &ctrl_entity_nil; - if(event->entity_id == 0) - { - thread = ctrl_entity_from_handle(&store->ctx, event->entity); - } - else - { - thread = ctrl_thread_from_id(&store->ctx, event->entity_id); - } - if(thread != &ctrl_entity_nil) - { - ctrl_entity_equip_string(store, thread, event->string); - } - else if(process != &ctrl_entity_nil) - { - CTRL_Entity *pending_name = ctrl_entity_alloc(store, process, CTRL_EntityKind_PendingThreadName, Arch_Null, ctrl_handle_zero(), event->entity_id); - ctrl_entity_equip_string(store, pending_name, event->string); - } - }break; - case CTRL_EventKind_ThreadColor: - { - CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); - CTRL_Entity *thread = &ctrl_entity_nil; - if(event->entity_id == 0) - { - thread = ctrl_entity_from_handle(&store->ctx, event->entity); - } - else - { - thread = ctrl_thread_from_id(&store->ctx, event->entity_id); - } - if(thread != &ctrl_entity_nil) - { - thread->rgba = event->rgba; - } - else if(process != &ctrl_entity_nil) - { - CTRL_Entity *pending = ctrl_entity_alloc(store, process, CTRL_EntityKind_PendingThreadColor, Arch_Null, ctrl_handle_zero(), event->entity_id); - pending->rgba = event->rgba; - } - }break; - case CTRL_EventKind_ThreadFrozen: - { - CTRL_Entity *thread = ctrl_entity_from_handle(&store->ctx, event->entity); - if(thread != &ctrl_entity_nil) - { - thread->is_frozen = 1; - } - }break; - case CTRL_EventKind_ThreadThawed: - { - CTRL_Entity *thread = ctrl_entity_from_handle(&store->ctx, event->entity); - if(thread != &ctrl_entity_nil) - { - thread->is_frozen = 0; - } - }break; - - //- rjf: modules - case CTRL_EventKind_NewModule: - { - Temp scratch = scratch_begin(0, 0); - CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); - CTRL_Entity *module = ctrl_entity_alloc(store, process, CTRL_EntityKind_Module, event->arch, event->entity, event->vaddr_rng.min); - ctrl_entity_equip_string(store, module, event->string); - module->timestamp = event->timestamp; - module->vaddr_range = event->vaddr_rng; - module->tls_index = event->tls_index; - module->tls_offset = event->tls_offset; - CTRL_Entity *first_module = ctrl_entity_child_from_kind(process, CTRL_EntityKind_Module); - if(first_module == module) - { - ctrl_entity_equip_string(store, process, str8_skip_last_slash(event->string)); - } - scratch_end(scratch); - }break; - case CTRL_EventKind_EndModule: - { - CTRL_Entity *module = ctrl_entity_from_handle(&store->ctx, event->entity); - ctrl_entity_release(store, module); - }break; - case CTRL_EventKind_ModuleDebugInfoPathChange: - { - Temp scratch = scratch_begin(0, 0); - CTRL_Entity *module = ctrl_entity_from_handle(&store->ctx, event->entity); - CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); - if(debug_info_path == &ctrl_entity_nil) - { - debug_info_path = ctrl_entity_alloc(store, module, CTRL_EntityKind_DebugInfoPath, Arch_Null, ctrl_handle_zero(), 0); - } - ctrl_entity_equip_string(store, debug_info_path, path_normalized_from_string(scratch.arena, event->string)); - debug_info_path->timestamp = event->timestamp; - scratch_end(scratch); - }break; - - //- rjf: dynamic, program-created breakpoints - case CTRL_EventKind_SetBreakpoint: - { - CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); - CTRL_Entity *bp = ctrl_entity_alloc(store, process, CTRL_EntityKind_Breakpoint, Arch_Null, ctrl_handle_zero(), 0); - bp->vaddr_range = event->vaddr_rng; - bp->bp_flags = event->bp_flags; - }break; - case CTRL_EventKind_UnsetBreakpoint: - { - CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); - for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) - { - if(child->kind == CTRL_EntityKind_Breakpoint && - child->vaddr_range.min == event->vaddr_rng.min && - child->vaddr_range.max == event->vaddr_rng.max && - child->bp_flags == event->bp_flags) - { - ctrl_entity_release(store, child); - break; - } - } - }break; - - //- rjf: address range annotations - case CTRL_EventKind_SetVAddrRangeNote: - { - CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); - CTRL_Entity *annotation = ctrl_entity_alloc(store, process, CTRL_EntityKind_AddressRangeAnnotation, Arch_Null, ctrl_handle_zero(), 0); - annotation->vaddr_range = event->vaddr_rng; - ctrl_entity_equip_string(store, annotation, event->string); - }break; - } - } -} - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -ctrl_init(void) -{ - Arena *arena = arena_alloc(); - ctrl_state = push_array(arena, CTRL_State, 1); - ctrl_state->arena = arena; - for(Arch arch = (Arch)0; arch < Arch_COUNT; arch = (Arch)(arch+1)) - { - String8 *reg_names = regs_reg_code_string_table_from_arch(arch); - U64 reg_count = regs_reg_code_count_from_arch(arch); - String8 *alias_names = regs_alias_code_string_table_from_arch(arch); - U64 alias_count = regs_alias_code_count_from_arch(arch); - ctrl_state->arch_string2reg_tables[arch] = e_string2num_map_make(ctrl_state->arena, 256); - ctrl_state->arch_string2alias_tables[arch] = e_string2num_map_make(ctrl_state->arena, 256); - for(U64 idx = 1; idx < reg_count; idx += 1) - { - e_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2reg_tables[arch], reg_names[idx], idx); - } - for(U64 idx = 1; idx < alias_count; idx += 1) - { - e_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2alias_tables[arch], alias_names[idx], idx); - } - } - ctrl_state->thread_reg_cache.slots_count = 1024; - ctrl_state->thread_reg_cache.slots = push_array(arena, CTRL_ThreadRegCacheSlot, ctrl_state->thread_reg_cache.slots_count); - ctrl_state->thread_reg_cache.stripes_count = os_get_system_info()->logical_processor_count; - ctrl_state->thread_reg_cache.stripes = push_array(arena, CTRL_ThreadRegCacheStripe, ctrl_state->thread_reg_cache.stripes_count); - for(U64 idx = 0; idx < ctrl_state->thread_reg_cache.stripes_count; idx += 1) - { - ctrl_state->thread_reg_cache.stripes[idx].arena = arena_alloc(); - ctrl_state->thread_reg_cache.stripes[idx].rw_mutex = rw_mutex_alloc(); - } - ctrl_state->module_image_info_cache.slots_count = 1024; - ctrl_state->module_image_info_cache.slots = push_array(arena, CTRL_ModuleImageInfoCacheSlot, ctrl_state->module_image_info_cache.slots_count); - ctrl_state->module_image_info_cache.stripes_count = os_get_system_info()->logical_processor_count; - ctrl_state->module_image_info_cache.stripes = push_array(arena, CTRL_ModuleImageInfoCacheStripe, ctrl_state->module_image_info_cache.stripes_count); - for(U64 idx = 0; idx < ctrl_state->module_image_info_cache.stripes_count; idx += 1) - { - ctrl_state->module_image_info_cache.stripes[idx].arena = arena_alloc(); - ctrl_state->module_image_info_cache.stripes[idx].rw_mutex = rw_mutex_alloc(); - } - ctrl_state->u2c_ring_size = KB(64); - ctrl_state->u2c_ring_base = push_array_no_zero(arena, U8, ctrl_state->u2c_ring_size); - ctrl_state->u2c_ring_mutex = mutex_alloc(); - ctrl_state->u2c_ring_cv = cond_var_alloc(); - ctrl_state->c2u_ring_size = KB(64); - ctrl_state->c2u_ring_max_string_size = ctrl_state->c2u_ring_size/2; - ctrl_state->c2u_ring_base = push_array_no_zero(arena, U8, ctrl_state->c2u_ring_size); - ctrl_state->c2u_ring_mutex = mutex_alloc(); - ctrl_state->c2u_ring_cv = cond_var_alloc(); - { - Temp scratch = scratch_begin(0, 0); - String8 user_program_data_path = os_get_process_info()->user_program_data_path; - String8 user_data_folder = push_str8f(scratch.arena, "%S/raddbg/logs", user_program_data_path); - os_make_directory(user_data_folder); - ctrl_state->ctrl_thread_log_path = push_str8f(ctrl_state->arena, "%S/ctrl_thread.raddbg_log", user_data_folder); - os_write_data_to_file_path(ctrl_state->ctrl_thread_log_path, str8_zero()); - scratch_end(scratch); - } - ctrl_state->ctrl_thread_entity_ctx_rw_mutex = rw_mutex_alloc(); - ctrl_state->ctrl_thread_entity_store = ctrl_entity_ctx_rw_store_alloc(); - ctrl_state->ctrl_thread_eval_cache = e_cache_alloc(); - ctrl_state->ctrl_thread_msg_process_arena = arena_alloc(); - ctrl_state->dmn_event_arena = arena_alloc(); - ctrl_state->user_entry_point_arena = arena_alloc(); - ctrl_state->dbg_dir_arena = arena_alloc(); - for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) - { - if(ctrl_exception_code_kind_default_enable_table[k]) - { - ctrl_state->exception_code_filters[k/64] |= 1ull<<(k%64); - } - } - ctrl_state->ctrl_thread_log = log_alloc(); - ctrl_state->ctrl_thread = thread_launch(ctrl_thread__entry_point, 0); -} - -//////////////////////////////// -//~ rjf: Wakeup Callback Registration - -internal void -ctrl_set_wakeup_hook(CTRL_WakeupFunctionType *wakeup_hook) -{ - ctrl_state->wakeup_hook = wakeup_hook; -} - -//////////////////////////////// -//~ rjf: Thread Register Functions - -//- rjf: thread register cache reading - -internal void * -ctrl_reg_block_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle handle) -{ - CTRL_ThreadRegCache *cache = &ctrl_state->thread_reg_cache; - CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, handle); - Arch arch = thread_entity->arch; - U64 reg_block_size = regs_block_size_from_arch(arch); - U64 hash = ctrl_hash_from_handle(handle); - U64 slot_idx = hash%cache->slots_count; - U64 stripe_idx = slot_idx%cache->stripes_count; - CTRL_ThreadRegCacheSlot *slot = &cache->slots[slot_idx]; - CTRL_ThreadRegCacheStripe *stripe = &cache->stripes[stripe_idx]; - void *result = push_array(arena, U8, reg_block_size); - MutexScopeW(stripe->rw_mutex) - { - // rjf: find existing node - CTRL_ThreadRegCacheNode *node = 0; - for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->handle, handle)) - { - node = n; - break; - } - } - - // rjf: allocate existing node - if(!node) - { - node = push_array(stripe->arena, CTRL_ThreadRegCacheNode, 1); - DLLPushBack(slot->first, slot->last, node); - node->handle = handle; - node->block_size = reg_block_size; - node->block = push_array(stripe->arena, U8, reg_block_size); - } - - // rjf: copy from node - if(node) - { - U64 current_reg_gen = ctrl_reg_gen(); - B32 need_stale = 1; - if(node->reg_gen != current_reg_gen && - dmn_thread_read_reg_block(handle.dmn_handle, result)) - { - if(node != 0) - { - need_stale = 0; - node->reg_gen = current_reg_gen; - MemoryCopy(node->block, result, reg_block_size); - } - } - if(need_stale) - { - MemoryCopy(result, node->block, reg_block_size); - } - } - } - return result; -} - -internal U64 -ctrl_tls_root_vaddr_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle) -{ - U64 result = dmn_tls_root_vaddr_from_thread(handle.dmn_handle); - return result; -} - -internal U64 -ctrl_rip_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle) -{ - Temp scratch = scratch_begin(0, 0); - CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, handle); - Arch arch = thread_entity->arch; - void *block = ctrl_reg_block_from_thread(scratch.arena, ctx, handle); - U64 result = regs_rip_from_arch_block(arch, block); - scratch_end(scratch); - return result; -} - -internal U64 -ctrl_rsp_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle) -{ - Temp scratch = scratch_begin(0, 0); - CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, handle); - Arch arch = thread_entity->arch; - void *block = ctrl_reg_block_from_thread(scratch.arena, ctx, handle); - U64 result = regs_rsp_from_arch_block(arch, block); - scratch_end(scratch); - return result; -} - -//- rjf: thread register writing - -internal B32 -ctrl_thread_write_reg_block(CTRL_Handle thread, void *block) -{ - // TODO(rjf): @callstacks immediately reflect this in the call stack cache - B32 good = dmn_thread_write_reg_block(thread.dmn_handle, block); - if(good) - { - ins_atomic_u64_inc_eval(&ctrl_state->reg_gen); - } - return good; -} - -//////////////////////////////// -//~ rjf: Module Image Info Functions - -//- rjf: cache lookups - -internal PE_IntelPdata * -ctrl_intel_pdata_from_module_voff(Arena *arena, CTRL_Handle module_handle, U64 voff) -{ - PE_IntelPdata *first_pdata = 0; - { - U64 hash = ctrl_hash_from_handle(module_handle); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->module, module_handle)) - { - PE_IntelPdata *pdatas = n->pdatas; - U64 pdatas_count = n->pdatas_count; - if(n->pdatas_count != 0 && voff >= n->pdatas[0].voff_first) - { - // NOTE(rjf): - // - // binary search: - // find max index s.t. pdata_array[index].voff_first <= voff - // we assume (i < j) -> (pdata_array[i].voff_first < pdata_array[j].voff_first) - U64 index = pdatas_count; - U64 min = 0; - U64 opl = pdatas_count; - for(;;) - { - U64 mid = (min + opl)/2; - PE_IntelPdata *pdata = pdatas + mid; - if(voff < pdata->voff_first) - { - opl = mid; - } - else if(pdata->voff_first < voff) - { - min = mid; - } - else - { - index = mid; - break; - } - if(min + 1 >= opl) - { - index = min; - break; - } - } - - // rjf: if we are in range fill result - { - PE_IntelPdata *pdata = pdatas + index; - if(pdata->voff_first <= voff && voff < pdata->voff_one_past_last) - { - first_pdata = push_array(arena, PE_IntelPdata, 1); - MemoryCopyStruct(first_pdata, pdata); - } - } - } - break; - } - } - } - return first_pdata; -} - -internal U64 -ctrl_entry_point_voff_from_module(CTRL_Handle module_handle) -{ - U64 result = 0; - U64 hash = ctrl_hash_from_handle(module_handle); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->module, module_handle)) - { - result = n->entry_point_voff; - break; - } - } - return result; -} - -internal String8 -ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_Handle module_handle) -{ - String8 result = {0}; - U64 hash = ctrl_hash_from_handle(module_handle); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->module, module_handle)) - { - result = push_str8_copy(arena, n->initial_debug_info_path); - break; - } - } - return result; -} - -internal String8 -ctrl_raddbg_data_from_module(Arena *arena, CTRL_Handle module_handle) -{ - String8 result = {0}; - U64 hash = ctrl_hash_from_handle(module_handle); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->module, module_handle)) - { - result = push_str8_copy(arena, n->raddbg_data); - break; - } - } - return result; -} - -//////////////////////////////// -//~ Process Info Functions - -internal Arch -ctrl_arch_from_process_handle(CTRL_Handle process_handle) -{ - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - CTRL_Entity *process_entity = ctrl_entity_from_handle(entity_ctx, process_handle); - return process_entity->arch; -} - -//////////////////////////////// -//~ rjf: Unwinding Functions - -//- rjf: unwind deep copier - -internal CTRL_Unwind -ctrl_unwind_deep_copy(Arena *arena, Arch arch, CTRL_Unwind *src) -{ - CTRL_Unwind dst = {0}; - { - dst.flags = src->flags; - dst.frames.count = src->frames.count; - dst.frames.v = push_array(arena, CTRL_UnwindFrame, dst.frames.count); - MemoryCopy(dst.frames.v, src->frames.v, sizeof(dst.frames.v[0])*dst.frames.count); - U64 block_size = regs_block_size_from_arch(arch); - for(U64 idx = 0; idx < dst.frames.count; idx += 1) - { - dst.frames.v[idx].regs = push_array_no_zero(arena, U8, block_size); - MemoryCopy(dst.frames.v[idx].regs, src->frames.v[idx].regs, block_size); - } - } - return dst; -} - -//- DWARF - -typedef struct -{ - CTRL_Handle process_handle; - U64 endt_us; -} CTRL_MemoryReadContextDwarfX64; - -internal -MACHINE_OP_MEM_READ(ctrl_machine_mem_read) -{ - CTRL_MemoryReadContextDwarfX64 *ctx = ud; - - B32 is_stale = 0; - B32 is_read = ctrl_process_memory_read(ctx->process_handle, r1u64(addr, addr + buffer_size), &is_stale, buffer, ctx->endt_us); - - MachineOpResult status = MachineOpResult_Fail; - if(is_stale) - { - status = MachineOpResult_Maybe; - } - else if(is_read) - { - status = MachineOpResult_Ok; - } - - return status; -} - -internal CTRL_UnwindStepResult -ctrl_unwind_step_result_from_machine_op_result(MachineOpResult s) -{ - CTRL_UnwindStepResult result = {0}; - switch(s) - { - case MachineOpResult_Null: {} break; - case MachineOpResult_Ok: - { - result.flags &= ~(CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale); - }break; - case MachineOpResult_Fail: - { - result.flags |= CTRL_UnwindFlag_Error; - }break; - case MachineOpResult_Maybe: - { - result.flags &= ~CTRL_UnwindFlag_Error; - result.flags |= CTRL_UnwindFlag_Stale; - }break; - default: { InvalidPath; } break; - } - return result; -} - -internal CTRL_UnwindStepResult -ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us, CTRL_FrameUnwindContext *ctx_out) -{ - Temp scratch = scratch_begin(&arena, 1); - CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error }; - - // gather context for virtual stack unwinder - U64 cfi_rebase = 0; - B32 is_unwind_eh = 0; - String8 unwind_data = {0}; - EH_FrameHdr eh_frame_hdr = {0}; - EH_PtrCtx eh_ptr_ctx = {0}; - { - U64 hash = ctrl_hash_from_handle(module_handle); - U64 slot_idx = hash % ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx % ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - MutexScopeR(stripe->rw_mutex) for EachNode(n, CTRL_ModuleImageInfoCacheNode, slot->first) - { - if(ctrl_handle_match(n->module, module_handle)) - { - cfi_rebase = n->cfi_rebase; - is_unwind_eh = n->is_unwind_eh; - unwind_data = n->dwarf_unwind_data; - eh_frame_hdr = n->eh_frame_hdr; - eh_ptr_ctx = n->eh_ptr_ctx; - break; - } - } - } - - // grab IP - U64 ip = regs_rip_from_arch_block(arch, regs); - - // use .eh_frame_hdr to quickly locate nearest FDE - U64 fde_addr = eh_find_nearest_fde(eh_frame_hdr, &eh_ptr_ctx, ip); - - if(fde_addr != max_U64) - { - // parse call frame info - DW_CIE cie = {0}; - DW_FDE fde = {0}; - B32 is_cfi_parsed = 0; - if(is_unwind_eh) - { - B32 is_stale = 0; - - // extract FDE info - Rng1U64 fde_vrange = {0}; - DW_Format fde_format = DW_Format_Null; - String8 fde_data = {0}; - U64 cie_addr = 0; - { - // parse FDE length - U32 first_four_bytes = 0; - if(!ctrl_process_memory_read_struct(process_handle, fde_addr, &is_stale, &first_four_bytes, endt_us)) { goto eh_parse_exit; } - if(first_four_bytes == max_U32) - { - U64 length = 0; - if(!ctrl_process_memory_read_struct(process_handle, fde_addr + sizeof(first_four_bytes), &is_stale, &length, endt_us)) { goto eh_parse_exit; } - fde_vrange = r1u64(fde_addr, fde_addr + sizeof(first_four_bytes) + length); - fde_format = DW_Format_64Bit; - } - else - { - fde_vrange = r1u64(fde_addr, fde_addr + sizeof(first_four_bytes) + first_four_bytes); - fde_format = DW_Format_32Bit; - } - - // read out whole FDE - void *fde_raw = push_array(scratch.arena, U8, dim_1u64(fde_vrange)); - if(!ctrl_process_memory_read(process_handle, fde_vrange, &is_stale, fde_raw, endt_us)) { goto eh_parse_exit; } - fde_data = str8(fde_raw, dim_1u64(fde_vrange)); - - // compute CIE address - U64 cie_delta_off = fde_format == DW_Format_32Bit ? 4 : 12; - U64 cie_delta = 0; - U64 cie_delta_size = str8_deserial_read_dwarf_uint(fde_data, cie_delta_off, fde_format, &cie_delta); - if (cie_delta_size == 0) { goto eh_parse_exit; } - cie_addr = (fde_addr + cie_delta_off) - cie_delta; - } - - // extract CIE info - Rng1U64 cie_vrange = {0}; - DW_Format cie_format = DW_Format_Null; - String8 cie_data = {0}; - { - // parse CIE length - U32 first_four_bytes = 0; - if(!ctrl_process_memory_read_struct(process_handle, cie_addr, &is_stale, &first_four_bytes, endt_us)) { goto eh_parse_exit; } - if(first_four_bytes == max_U32) - { - U64 length = 0; - if(!ctrl_process_memory_read_struct(process_handle, cie_addr + sizeof(first_four_bytes), &is_stale, &length, endt_us)) { goto eh_parse_exit; } - cie_vrange = r1u64(cie_addr, cie_addr + sizeof(first_four_bytes) + length); - cie_format = DW_Format_64Bit; - } - else - { - cie_vrange = r1u64(cie_addr, cie_addr + sizeof(first_four_bytes) + first_four_bytes); - cie_format = DW_Format_32Bit; - } - - // read out whole CIE - void *cie_raw = push_array(scratch.arena, U8, dim_1u64(cie_vrange)); - if(!ctrl_process_memory_read(process_handle, cie_vrange, &is_stale, cie_raw, endt_us)) { goto eh_parse_exit; } - cie_data = str8(cie_raw, dim_1u64(cie_vrange)); - } - - // parse CIE and FDE - if(eh_parse_cie(cie_data, cie_format, arch, cie_vrange.min, &eh_ptr_ctx, &cie)) - { - is_cfi_parsed = eh_parse_fde(fde_data, fde_format, fde_vrange.min, &cie, &eh_ptr_ctx, &fde); - } - - eh_parse_exit:; - if(is_stale) - { - result.flags = CTRL_UnwindFlag_Stale; - } - } - else - { - is_cfi_parsed = dw_parse_cfi(unwind_data, fde_addr, arch, &cie, &fde); - } - - if(is_cfi_parsed && contains_1u64(fde.pc_range, ip)) - { - // setup pointer decoder ops - DW_DecodePtr *decode_ptr_func = 0; - void *decode_ptr_ctx = 0; - if(is_unwind_eh) - { - EH_DecodePtrCtx *decode_ptr_ctx_eh = push_array(scratch.arena, EH_DecodePtrCtx, 1); - decode_ptr_ctx_eh->ptr_ctx = &eh_ptr_ctx; - decode_ptr_ctx_eh->addr_enc = cie.ext[EH_CIE_Ext_AddrEnc]; - - decode_ptr_func = eh_decode_ptr; - decode_ptr_ctx = decode_ptr_ctx_eh; - } - else - { - decode_ptr_func = dw_decode_ptr_debug_frame; - decode_ptr_ctx = &cie; - } - - // find register rules for IP - DW_CFI_Row *cfi_row = dw_cfi_row_from_pc(arena, arch, &cie, &fde, decode_ptr_func, decode_ptr_ctx, ip); - if(cfi_row) - { - // setup machine ops - void *mem_read_ctx = 0; - void *reg_read_ctx = 0; - MachineOp_MemRead *mem_read_func = 0; - MachineOp_RegRead *reg_read_func = 0; - switch(arch) - { - case Arch_Null: break; - case Arch_x64: - { - CTRL_MemoryReadContextDwarfX64 *mem_read_ctx_x64 = push_array(scratch.arena, CTRL_MemoryReadContextDwarfX64, 1); - mem_read_ctx_x64->process_handle = process_handle; - mem_read_ctx_x64->endt_us = endt_us; - - mem_read_ctx = mem_read_ctx_x64; - reg_read_ctx = regs; - - mem_read_func = ctrl_machine_mem_read; - reg_read_func = regs_read_dwarf_x64; - }break; - case Arch_x86: - case Arch_arm64: - case Arch_arm32: - { - NotImplemented; - }break; - default: { InvalidPath; }break; - } - - // compute CFA for the row - U64 cfa = 0; - MachineOpResult unwind_status = dw_compute_cfa(arch, cfi_row, mem_read_func, mem_read_ctx, reg_read_func, reg_read_ctx, &cfa); - - // on success fill out output - if(unwind_status == MachineOpResult_Ok) - { - ctx_out->cfa = cfa; - ctx_out->cfi_row = cfi_row; - ctx_out->ret_addr_reg = cie.ret_addr_reg; - } - - // translate unwind status code - result = ctrl_unwind_step_result_from_machine_op_result(unwind_status); - } - } - } - - scratch_end(scratch); - return result; -} - -internal CTRL_UnwindStepResult -ctrl_unwind_step__dwarf(CTRL_Handle process_handle, Arch arch, void *regs, CTRL_FrameUnwindContext *frame_ctx, U64 endt_us) -{ - Temp scratch = scratch_begin(0, 0); - - CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error }; - - // setup machine ops - void *mem_read_ctx = 0; - void *reg_read_ctx = 0; - void *reg_write_ctx = 0; - MachineOp_MemRead *mem_read_func = 0; - MachineOp_RegRead *reg_read_func = 0; - MachineOp_RegWrite *reg_write_func = 0; - switch(arch) - { - case Arch_Null: break; - case Arch_x64: - { - CTRL_MemoryReadContextDwarfX64 *mem_read_ctx_x64 = push_array(scratch.arena, CTRL_MemoryReadContextDwarfX64, 1); - mem_read_ctx_x64->process_handle = process_handle; - mem_read_ctx_x64->endt_us = endt_us; - - mem_read_ctx = mem_read_ctx_x64; - reg_read_ctx = regs; - reg_write_ctx = regs; - - mem_read_func = ctrl_machine_mem_read; - reg_read_func = regs_read_dwarf_x64; - reg_write_func = regs_write_dwarf_x64; - }break; - case Arch_x86: - case Arch_arm64: - case Arch_arm32: - { - NotImplemented; - }break; - default: { InvalidPath; }break; - } - - // apply register rules to the context - MachineOpResult unwind_status = dw_cfi_apply_register_rules(arch, - frame_ctx->cfa, - frame_ctx->cfi_row, - mem_read_func, - mem_read_ctx, - reg_read_func, - reg_read_ctx, - reg_write_func, - reg_write_ctx); - - // last frame typically has undefined rule for IP - if(frame_ctx->cfi_row->regs[frame_ctx->ret_addr_reg].rule == DW_CFI_RegisterRule_Undefined) - { - regs_arch_block_write_rip(arch, regs, 0); - } - - // translate unwind status code - result = ctrl_unwind_step_result_from_machine_op_result(unwind_status); - - scratch_end(scratch); - return result; -} - -//- rjf: [x64] - -internal REGS_Reg64 * -ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(REGS_RegBlockX64 *regs, PE_UnwindGprRegX64 gpr_reg) -{ - local_persist REGS_Reg64 dummy = {0}; - REGS_Reg64 *result = &dummy; - switch(gpr_reg) - { - case PE_UnwindGprRegX64_RAX:{result = ®s->rax;}break; - case PE_UnwindGprRegX64_RCX:{result = ®s->rcx;}break; - case PE_UnwindGprRegX64_RDX:{result = ®s->rdx;}break; - case PE_UnwindGprRegX64_RBX:{result = ®s->rbx;}break; - case PE_UnwindGprRegX64_RSP:{result = ®s->rsp;}break; - case PE_UnwindGprRegX64_RBP:{result = ®s->rbp;}break; - case PE_UnwindGprRegX64_RSI:{result = ®s->rsi;}break; - case PE_UnwindGprRegX64_RDI:{result = ®s->rdi;}break; - case PE_UnwindGprRegX64_R8 :{result = ®s->r8 ;}break; - case PE_UnwindGprRegX64_R9 :{result = ®s->r9 ;}break; - case PE_UnwindGprRegX64_R10:{result = ®s->r10;}break; - case PE_UnwindGprRegX64_R11:{result = ®s->r11;}break; - case PE_UnwindGprRegX64_R12:{result = ®s->r12;}break; - case PE_UnwindGprRegX64_R13:{result = ®s->r13;}break; - case PE_UnwindGprRegX64_R14:{result = ®s->r14;}break; - case PE_UnwindGprRegX64_R15:{result = ®s->r15;}break; - } - return result; -} - -internal CTRL_UnwindStepResult -ctrl_unwind_step__pe_x64(CTRL_Handle process_handle, CTRL_Handle module_handle, U64 module_base_vaddr, REGS_RegBlockX64 *regs, U64 endt_us) -{ - B32 is_stale = 0; - B32 is_good = 1; - Temp scratch = scratch_begin(0, 0); - - ////////////////////////////// - //- rjf: unpack parameters - // - U64 rip_voff = regs->rip.u64 - module_base_vaddr; - - ////////////////////////////// - //- rjf: rip_voff -> first pdata - // - PE_IntelPdata *first_pdata = ctrl_intel_pdata_from_module_voff(scratch.arena, module_handle, rip_voff); - - ////////////////////////////// - //- rjf: pdata -> detect if in epilog - // - B32 has_pdata_and_in_epilog = 0; - if(first_pdata) ProfScope("pdata -> detect if in epilog") - { - // NOTE(allen): There are restrictions placed on how an epilog is allowed - // to be formed (https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=msvc-160) - // Here we interpret machine code directly according to the rules - // given there to determine if the code we're looking at looks like an epilog. - - //- rjf: set up parsing state - B32 is_epilog = 0; - B32 keep_parsing = 1; - U64 read_vaddr = regs->rip.u64; - U64 read_vaddr_opl = read_vaddr + 256; - - //- rjf: check first instruction - { - B32 inst_good = 0; - U8 inst[4] = {0}; - if(read_vaddr + sizeof(inst) <= read_vaddr_opl) - { - inst_good = ctrl_process_memory_read(process_handle, r1u64(read_vaddr, read_vaddr+sizeof(inst)), &is_stale, inst, endt_us); - inst_good = inst_good && !is_stale; - } - if(!inst_good) - { - keep_parsing = 0; - } - else if((inst[0] & 0xF8) == 0x48) - { - switch(inst[1]) - { - // rjf: add $nnnn,%rsp - case 0x81: - { - if(inst[0] == 0x48 && inst[2] == 0xC4) - { - read_vaddr += 7; - } - else - { - keep_parsing = 0; - } - }break; - - // rjf: add $n,%rsp - case 0x83: - { - if(inst[0] == 0x48 && inst[2] == 0xC4) - { - read_vaddr += 4; - } - else - { - keep_parsing = 0; - } - }break; - - // rjf: lea n(reg),%rsp - case 0x8D: - { - if((inst[0] & 0x06) == 0 && - ((inst[2] >> 3) & 0x07) == 0x04 && - (inst[2] & 0x07) != 0x04) - { - U8 imm_size = (inst[2] >> 6); - - // rjf: 1-byte immediate - if(imm_size == 1) - { - read_vaddr += 4; - } - - // rjf: 4-byte immediate - else if(imm_size == 2) - { - read_vaddr += 7; - } - - // rjf: other case - else - { - keep_parsing = 0; - } - } - else - { - keep_parsing = 0; - } - }break; - } - } - } - - //- rjf: continue parsing instructions - for(;keep_parsing;) - { - // rjf: read next instruction byte - B32 inst_byte_good = 0; - U8 inst_byte = 0; - if(read_vaddr + sizeof(inst_byte) <= read_vaddr_opl) - { - inst_byte_good = ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &inst_byte, endt_us); - } - if(!inst_byte_good || is_stale) - { - keep_parsing = 0; - } - - // rjf: when (... I don't know ...) rely on the next byte - B32 check_inst_byte_good = inst_byte_good; - U64 check_vaddr = read_vaddr; - U8 check_inst_byte = inst_byte; - if(inst_byte_good && (inst_byte & 0xF0) == 0x40) - { - check_vaddr = read_vaddr + 1; - if(read_vaddr + sizeof(check_inst_byte) <= read_vaddr_opl) - { - check_inst_byte_good = ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &check_inst_byte, endt_us); - } - if(!check_inst_byte_good || is_stale) - { - keep_parsing = 0; - } - } - - // rjf: check instruction byte - if(check_inst_byte_good) - { - switch(check_inst_byte) - { - // rjf: pop - case 0x58:case 0x59:case 0x5A:case 0x5B: - case 0x5C:case 0x5D:case 0x5E:case 0x5F: - { - read_vaddr = check_vaddr + 1; - }break; - - // rjf: ret - case 0xC2: - case 0xC3: - { - is_epilog = 1; - keep_parsing = 0; - }break; - - // rjf: jmp nnnn - case 0xE9: - { - U64 imm_vaddr = check_vaddr + 1; - S32 imm = 0; - B32 imm_good = 0; - if(read_vaddr + sizeof(imm) <= read_vaddr_opl) - { - imm_good = ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us); - } - if(!imm_good || is_stale) - { - keep_parsing = 0; - } - if(imm_good) - { - U64 next_vaddr = (U64)(imm_vaddr + sizeof(imm) + imm); - U64 next_voff = next_vaddr - module_base_vaddr; // TODO(rjf): verify that this offset is from module base vaddr, not section - if(!(first_pdata->voff_first <= next_voff && next_voff < first_pdata->voff_one_past_last)) - { - keep_parsing = 0; - } - else - { - read_vaddr = next_vaddr; - } - } - // TODO(allen): why isn't this just the end of the epilog? - }break; - - // rjf: rep; ret (for amd64 prediction bug) - case 0xF3: - { - U8 next_inst_byte = 0; - B32 next_inst_byte_good = 0; - if(read_vaddr + sizeof(next_inst_byte) <= read_vaddr_opl) - { - next_inst_byte_good = ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &next_inst_byte, endt_us); - } - if(next_inst_byte_good) - { - is_epilog = (next_inst_byte == 0xC3); - } - keep_parsing = 0; - }break; - - default:{keep_parsing = 0;}break; - } - } - } - has_pdata_and_in_epilog = is_epilog; - } - - ////////////////////////////// - //- rjf: pdata & in epilog -> epilog unwind - // - if(first_pdata && has_pdata_and_in_epilog) ProfScope("pdata & in epilog -> epilog unwind") - { - U64 read_vaddr = regs->rip.u64; - for(B32 keep_parsing = 1;keep_parsing != 0;) - { - //- rjf: assume no more parsing after this instruction - keep_parsing = 0; - - //- rjf: read next instruction byte - U8 inst_byte = 0; - is_good = is_good && ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &inst_byte, endt_us); - is_good = is_good && !is_stale; - read_vaddr += 1; - - //- rjf: extract rex from instruction byte - U8 rex = 0; - if((inst_byte & 0xF0) == 0x40) - { - rex = inst_byte & 0xF; // rex prefix - is_good = is_good && ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &inst_byte, endt_us); - is_good = is_good && !is_stale; - read_vaddr += 1; - } - - //- rjf: parse remainder of instruction - switch(inst_byte) - { - // rjf: pop - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5C: - case 0x5D: - case 0x5E: - case 0x5F: - { - // rjf: read value at rsp - U64 sp = regs->rsp.u64; - U64 value = 0; - if(!ctrl_process_memory_read_struct(process_handle, sp, &is_stale, &value, endt_us) || - is_stale) - { - is_good = 0; - break; - } - - // rjf: modify registers - PE_UnwindGprRegX64 gpr_reg = (inst_byte - 0x58) + (rex & 1)*8; - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, gpr_reg); - reg->u64 = value; - regs->rsp.u64 = sp + 8; - - // rjf: not a final instruction, so keep mparsing - keep_parsing = 1; - }break; - - // rjf: add $nnnn,%rsp - case 0x81: - { - // rjf: skip one byte (we already know what it is in this scenario) - read_vaddr += 1; - - // rjf: read the 4-byte immediate - S32 imm = 0; - if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 4; - - // rjf: update stack pointer - regs->rsp.u64 = (U64)(regs->rsp.u64 + imm); - - // rjf: not a final instruction; keep parsing - keep_parsing = 1; - }break; - - // rjf: add $n,%rsp - case 0x83: - { - // rjf: skip one byte (we already know what it is in this scenario) - read_vaddr += 1; - - // rjf: read the 4-byte immediate - S8 imm = 0; - if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 1; - - // rjf: update stack pointer - regs->rsp.u64 = (U64)(regs->rsp.u64 + imm); - - // rjf: not a final instruction; keep parsing - keep_parsing = 1; - }break; - - // rjf: lea imm8/imm32,$rsp - case 0x8D: - { - // rjf: read source register - U8 modrm = 0; - if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &modrm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 1; - PE_UnwindGprRegX64 gpr_reg = (modrm & 7) + (rex & 1)*8; - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, gpr_reg); - U64 reg_value = reg->u64; - - // rjf: read immediate - S32 imm = 0; - { - // rjf: read 1-byte immediate - if((modrm >> 6) == 1) - { - S8 imm8 = 0; - if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm8, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 1; - imm = (S32)imm8; - } - - // rjf: read 4-byte immediate - else - { - if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 4; - } - } - - // rjf: update stack pointer - regs->rsp.u64 = (U64)(reg_value + imm); - - // rjf: not a final instruction; keep parsing - keep_parsing = 1; - }break; - - // rjf: ret $nn - case 0xC2: - { - // rjf: read new ip - U64 sp = regs->rsp.u64; - U64 new_ip = 0; - if(!ctrl_process_memory_read_struct(process_handle, sp, &is_stale, &new_ip, endt_us) || - is_stale) - { - is_good = 0; - break; - } - - // rjf: read 2-byte immediate & advance stack pointer - U16 imm = 0; - if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - U64 new_sp = sp + 8 + imm; - - // rjf: commit registers - regs->rip.u64 = new_ip; - regs->rsp.u64 = new_sp; - }break; - - // rjf: ret / rep; ret - case 0xF3: - { - // Assert(!"Hit me!"); - }break; - case 0xC3: - { - // rjf: read new ip - U64 sp = regs->rsp.u64; - U64 new_ip = 0; - if(!ctrl_process_memory_read_struct(process_handle, sp, &is_stale, &new_ip, endt_us) || - is_stale) - { - is_good = 0; - break; - } - - // rjf: advance stack pointer - U64 new_sp = sp + 8; - - // rjf: commit registers - regs->rip.u64 = new_ip; - regs->rsp.u64 = new_sp; - }break; - - // rjf: jmp nnnn - case 0xE9: - { - // Assert(!"Hit Me"); - // TODO(allen): general idea: read the immediate, move the ip, leave the sp, done - // we don't have any cases to exercise this right now. no guess implementation! - }break; - - // rjf: Sjmp n - case 0xEB: - { - // Assert(!"Hit Me"); - // TODO(allen): general idea: read the immediate, move the ip, leave the sp, done - // we don't have any cases to exercise this right now. no guess implementation! - }break; - } - } - } - - ////////////////////////////// - //- rjf: pdata & not in epilog -> xdata unwind - // - B32 xdata_unwind_did_machframe = 0; - if(first_pdata && !has_pdata_and_in_epilog) ProfScope("pdata & not in epilog -> xdata unwind") - { - //- rjf: get frame reg - B32 bad_frame_reg_info = 0; - REGS_Reg64 *frame_reg = 0; - U64 frame_off = 0; - { - U64 unwind_info_off = first_pdata->voff_unwind_info; - PE_UnwindInfo unwind_info = {0}; - if(!ctrl_process_memory_read_struct(process_handle, module_base_vaddr+unwind_info_off, &is_stale, &unwind_info, endt_us) || - is_stale) - { - is_good = 0; - } - U32 frame_reg_id = PE_UNWIND_INFO_REG_FROM_FRAME(unwind_info.frame); - U64 frame_off_val = PE_UNWIND_INFO_OFF_FROM_FRAME(unwind_info.frame); - if(frame_reg_id != 0) - { - frame_reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, frame_reg_id); - bad_frame_reg_info = (frame_reg == 0); // NOTE(rjf): frame_reg should never be 0 at this point, in valid exe - } - frame_off = frame_off_val; - } - - //- rjf: iterate pdatas, apply opcodes - PE_IntelPdata *last_pdata = 0; - PE_IntelPdata *pdata = first_pdata; - if(!bad_frame_reg_info) for(B32 keep_parsing = 1; keep_parsing && pdata != last_pdata;) - { - //- rjf: unpack unwind info & codes - B32 good_unwind_info = 1; - U64 unwind_info_off = pdata->voff_unwind_info; - PE_UnwindInfo unwind_info = {0}; - good_unwind_info = good_unwind_info && ctrl_process_memory_read_struct(process_handle, module_base_vaddr+unwind_info_off, &is_stale, &unwind_info, endt_us); - PE_UnwindCode *unwind_codes = push_array(scratch.arena, PE_UnwindCode, unwind_info.codes_num); - good_unwind_info = good_unwind_info && ctrl_process_memory_read(process_handle, r1u64(module_base_vaddr+unwind_info_off+sizeof(unwind_info), - module_base_vaddr+unwind_info_off+sizeof(unwind_info)+sizeof(PE_UnwindCode)*unwind_info.codes_num), - &is_stale, unwind_codes, endt_us); - good_unwind_info = good_unwind_info && !is_stale; - - //- rjf: bad unwind info -> abort - if(!good_unwind_info) - { - is_good = 0; - break; - } - - //- rjf: unpack frame base - U64 frame_base = regs->rsp.u64; - if(frame_reg != 0) - { - U64 raw_frame_base = frame_reg->u64; - U64 adjusted_frame_base = raw_frame_base - frame_off*16; - frame_base = adjusted_frame_base; - } - - //- rjf: apply opcodes - PE_UnwindCode *code_ptr = unwind_codes; - PE_UnwindCode *code_opl = unwind_codes + unwind_info.codes_num; - for(PE_UnwindCode *next_code_ptr = 0; code_ptr < code_opl; code_ptr = next_code_ptr) - { - // rjf: unpack opcode info - U32 op_code = PE_UNWIND_OPCODE_FROM_FLAGS(code_ptr->flags); - U32 op_info = PE_UNWIND_INFO_FROM_FLAGS(code_ptr->flags); - U32 slot_count = pe_slot_count_from_unwind_op_code(op_code); - if(op_code == PE_UnwindOpCode_ALLOC_LARGE && op_info == 1) - { - slot_count += 1; - } - - // rjf: detect bad slot counts - if(slot_count == 0 || code_ptr+slot_count > code_opl) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: set next op code pointer - next_code_ptr = code_ptr + slot_count; - - // rjf: interpret this op code - U64 code_voff = pdata->voff_first + code_ptr->off_in_prolog; - if(code_voff <= rip_voff) - { - switch(op_code) - { - case PE_UnwindOpCode_PUSH_NONVOL: - { - // rjf: read value from stack pointer - U64 rsp = regs->rsp.u64; - U64 value = 0; - if(!ctrl_process_memory_read_struct(process_handle, rsp, &is_stale, &value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: advance stack ptr - U64 new_rsp = rsp + 8; - - // rjf: commit registers - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); - reg->u64 = value; - regs->rsp.u64 = new_rsp; - }break; - - case PE_UnwindOpCode_ALLOC_LARGE: - { - // rjf: read alloc size - U64 size = 0; - if(op_info == 0) - { - size = code_ptr[1].u16*8; - } - else if(op_info == 1) - { - size = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); - } - else - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: advance stack pointer - U64 rsp = regs->rsp.u64; - U64 new_rsp = rsp + size; - - // rjf: advance stack pointer - regs->rsp.u64 = new_rsp; - }break; - - case PE_UnwindOpCode_ALLOC_SMALL: - { - // rjf: advance stack pointer - regs->rsp.u64 += op_info*8 + 8; - }break; - - case PE_UnwindOpCode_SET_FPREG: - { - // rjf: put stack pointer back to the frame base - regs->rsp.u64 = frame_base; - }break; - - case PE_UnwindOpCode_SAVE_NONVOL: - { - // rjf: read value from frame base - U64 off = code_ptr[1].u16*8; - U64 addr = frame_base + off; - U64 value = 0; - if(!ctrl_process_memory_read_struct(process_handle, addr, &is_stale, &value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit to register - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); - reg->u64 = value; - }break; - - case PE_UnwindOpCode_SAVE_NONVOL_FAR: - { - // rjf: read value from frame base - U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); - U64 addr = frame_base + off; - U64 value = 0; - if(!ctrl_process_memory_read_struct(process_handle, addr, &is_stale, &value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit to register - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); - reg->u64 = value; - }break; - - case PE_UnwindOpCode_EPILOG: - { - keep_parsing = 1; - }break; - - case PE_UnwindOpCode_SPARE_CODE: - { - // TODO(rjf): ??? - keep_parsing = 0; - is_good = 0; - }break; - - case PE_UnwindOpCode_SAVE_XMM128: - { - // rjf: read new register values - U8 buf[16]; - U64 off = code_ptr[1].u16*16; - U64 addr = frame_base + off; - if(!ctrl_process_memory_read(process_handle, r1u64(addr, addr+sizeof(buf)), &is_stale, buf, endt_us)) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit to register - void *xmm_reg = (®s->zmm0) + op_info; - MemoryCopy(xmm_reg, buf, sizeof(buf)); - }break; - - case PE_UnwindOpCode_SAVE_XMM128_FAR: - { - // rjf: read new register values - U8 buf[16]; - U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); - U64 addr = frame_base + off; - if(!ctrl_process_memory_read(process_handle, r1u64(addr, addr+16), &is_stale, buf, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit to register - void *xmm_reg = (®s->zmm0) + op_info; - MemoryCopy(xmm_reg, buf, sizeof(buf)); - }break; - - case PE_UnwindOpCode_PUSH_MACHFRAME: - { - // NOTE(rjf): this was found by stepping through kernel code after an exception was - // thrown, encountered in the exception_stepping_tests (after the throw) in mule_main - if(op_info > 1) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: read values - U64 sp_og = regs->rsp.u64; - U64 sp_adj = sp_og; - if(op_info == 1) - { - sp_adj += 8; - } - U64 ip_value = 0; - if(!ctrl_process_memory_read_struct(process_handle, sp_adj, &is_stale, &ip_value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - U64 sp_after_ip = sp_adj + 8; - U16 ss_value = 0; - if(!ctrl_process_memory_read_struct(process_handle, sp_after_ip, &is_stale, &ss_value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - U64 sp_after_ss = sp_after_ip + 8; - U64 rflags_value = 0; - if(!ctrl_process_memory_read_struct(process_handle, sp_after_ss, &is_stale, &rflags_value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - U64 sp_after_rflags = sp_after_ss + 8; - U64 sp_value = 0; - if(!ctrl_process_memory_read_struct(process_handle, sp_after_rflags, &is_stale, &sp_value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit registers - regs->rip.u64 = ip_value; - regs->ss.u16 = ss_value; - regs->rflags.u64 = rflags_value; - regs->rsp.u64 = sp_value; - - // rjf: mark machine frame - xdata_unwind_did_machframe = 1; - }break; - } - } - } - - //- rjf: iterate to next pdata - if(keep_parsing) - { - U32 flags = PE_UNWIND_INFO_FLAGS_FROM_HDR(unwind_info.header); - if(!(flags & PE_UnwindInfoFlag_CHAINED)) - { - break; - } - U64 code_count_rounded = AlignPow2(unwind_info.codes_num, sizeof(PE_UnwindCode)); - U64 code_size = code_count_rounded*sizeof(PE_UnwindCode); - U64 chained_pdata_off = unwind_info_off + sizeof(PE_UnwindInfo) + code_size; - last_pdata = pdata; - pdata = push_array(scratch.arena, PE_IntelPdata, 1); - if(!ctrl_process_memory_read_struct(process_handle, module_base_vaddr+chained_pdata_off, &is_stale, pdata, endt_us) || - is_stale) - { - is_good = 0; - break; - } - } - } - } - - ////////////////////////////// - //- rjf: no pdata, or didn't do machframe in xdata unwind -> unwind by reading stack pointer - // - if(!first_pdata || (!has_pdata_and_in_epilog && !xdata_unwind_did_machframe)) ProfScope("no pdata, or didn't do machframe in xdata unwind -> unwind by reading stack pointer") - { - // rjf: read rip from stack pointer - U64 rsp = regs->rsp.u64; - U64 new_rip = 0; - if(!ctrl_process_memory_read_struct(process_handle, rsp, &is_stale, &new_rip, endt_us) || - is_stale) - { - is_good = 0; - } - - // rjf: commit registers - if(is_good) - { - U64 new_rsp = rsp + 8; - regs->rip.u64 = new_rip; - regs->rsp.u64 = new_rsp; - } - } - - ////////////////////////////// - //- rjf: fill & return - // - scratch_end(scratch); - CTRL_UnwindStepResult result = {0}; - if(!is_good) {result.flags |= CTRL_UnwindFlag_Error;} - if(is_stale) {result.flags |= CTRL_UnwindFlag_Stale;} - return result; -} - -//- rjf: abstracted full unwind - -internal CTRL_Unwind -ctrl_unwind_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle thread, U64 endt_us) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - CTRL_Unwind unwind = { .flags = CTRL_UnwindFlag_Error }; - - //- rjf: unpack args - CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, thread); - CTRL_Entity *process_entity = thread_entity->parent; - Arch arch = thread_entity->arch; - U64 arch_reg_block_size = regs_block_size_from_arch(arch); - - //- rjf: grab initial register block - void *regs_block = ctrl_reg_block_from_thread(scratch.arena, ctx, thread); - B32 regs_block_good = (arch != Arch_Null && regs_block != 0); - - //- rjf: loop & unwind - CTRL_UnwindFrameNode *first_frame_node = 0; - CTRL_UnwindFrameNode *last_frame_node = 0; - U64 frame_node_count = 0; - if(regs_block_good) - { - for(unwind.flags = 0;;) - { - U64 rip = regs_rip_from_arch_block(arch, regs_block); - U64 rsp = regs_rsp_from_arch_block(arch, regs_block); - - // rjf: cancel on zero rip (except the top frame) - if(rip == 0 && frame_node_count > 0) - { - break; - } - - // rip -> module - CTRL_Entity *module_entity = ctrl_module_from_process_vaddr(process_entity, rip); - - // establish frame context - CTRL_FrameUnwindContext frame_ctx = {0}; - CTRL_UnwindStepResult frame_ctx_result = {0}; - switch(process_entity->target_os) - { - case OperatingSystem_Null:{}break; - case OperatingSystem_Windows: - { - // no concept of frame unwind context - }break; - case OperatingSystem_Linux: - { - frame_ctx_result = ctrl_establish_frame_unwind_context__dwarf(arena, process_entity->handle, module_entity->handle, arch, regs_block, endt_us, &frame_ctx); - }break; - case OperatingSystem_Mac: - { - NotImplemented; - }break; - default: { InvalidPath; }break; - } - unwind.flags |= frame_ctx_result.flags; - if(unwind.flags & (CTRL_UnwindFlag_Stale|CTRL_UnwindFlag_Error)) - { - break; - } - - // rjf: valid step -> push frame - CTRL_UnwindFrameNode *frame_node = push_array(scratch.arena, CTRL_UnwindFrameNode, 1); - CTRL_UnwindFrame *frame = &frame_node->v; - frame->cfa = frame_ctx.cfa; - frame->regs = push_array_no_zero(arena, U8, arch_reg_block_size); - MemoryCopy(frame->regs, regs_block, arch_reg_block_size); - DLLPushBack(first_frame_node, last_frame_node, frame_node); - frame_node_count += 1; - - // rjf: unwind one step - CTRL_UnwindStepResult step_result = {0}; - switch(process_entity->target_os) - { - default:{}break; - case OperatingSystem_Windows: - { - switch(arch) - { - default:{}break; - case Arch_x64: - { - step_result = ctrl_unwind_step__pe_x64(process_entity->handle, module_entity->handle, module_entity->vaddr_range.min, regs_block, endt_us); - }break; - } - }break; - case OperatingSystem_Linux: - { - step_result = ctrl_unwind_step__dwarf(process_entity->handle, arch, regs_block, &frame_ctx, endt_us); - }break; - } - - // stop unwinding on errors or stale data - unwind.flags |= step_result.flags; - if(unwind.flags & (CTRL_UnwindFlag_Stale|CTRL_UnwindFlag_Error) || - (regs_rsp_from_arch_block(arch, regs_block) == rsp && regs_rip_from_arch_block(arch, regs_block) == rip)) - { - break; - } - } - } - - //- rjf: bake frames list into result array - { - unwind.frames.count = frame_node_count; - unwind.frames.v = push_array(arena, CTRL_UnwindFrame, unwind.frames.count); - U64 idx = 0; - for(CTRL_UnwindFrameNode *n = first_frame_node; n != 0; n = n->next, idx += 1) - { - unwind.frames.v[idx] = n->v; - } - } - - scratch_end(scratch); - ProfEnd(); - return unwind; -} - -//////////////////////////////// -//~ rjf: Call Stack Building Functions - -internal CTRL_CallStack -ctrl_call_stack_from_unwind(Arena *arena, CTRL_Entity *process, CTRL_Unwind *base_unwind) -{ - Temp scratch = scratch_begin(&arena, 1); - Access *access = access_open(); - Arch arch = process->arch; - CTRL_CallStack result = {0}; - { - typedef struct FrameNode FrameNode; - struct FrameNode - { - FrameNode *next; - CTRL_CallStackFrame v; - }; - - //- rjf: gather all frames - FrameNode *first_frame = 0; - FrameNode *last_frame = 0; - U64 frame_count = 0; - for(U64 base_frame_idx = 0; base_frame_idx < base_unwind->frames.count; base_frame_idx += 1) - { - // rjf: unpack - CTRL_UnwindFrame *src = &base_unwind->frames.v[base_frame_idx]; - U64 rip_vaddr = regs_rip_from_arch_block(arch, src->regs); - CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr); - U64 rip_voff = ctrl_voff_from_vaddr(module, rip_vaddr); - DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); - RDI_Scope *scope = rdi_scope_from_voff(rdi, rip_voff); - - // rjf: build inline frames (minus parent & inline depth) - FrameNode *first_inline_frame = 0; - FrameNode *last_inline_frame = 0; - U64 inline_frame_count = 0; - for(RDI_Scope *s = scope; - s->inline_site_idx != 0; - s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) - { - FrameNode *dst_inline = push_array(scratch.arena, FrameNode, 1); - if(first_inline_frame == 0) - { - first_inline_frame = dst_inline; - } - last_inline_frame = dst_inline; - SLLQueuePush(first_frame, last_frame, dst_inline); - dst_inline->v.unwind_count = base_frame_idx; - dst_inline->v.regs = src->regs; - dst_inline->v.cfa = src->cfa; - frame_count += 1; - inline_frame_count += 1; - } - - // rjf: build concrete frame - FrameNode *dst_base = push_array(scratch.arena, FrameNode, 1); - SLLQueuePush(first_frame, last_frame, dst_base); - dst_base->v.unwind_count = base_frame_idx; - dst_base->v.regs = src->regs; - dst_base->v.cfa = src->cfa; - frame_count += 1; - - // rjf: hook up inline frames to point to concrete frame, and to account for inline depth - U64 inline_frame_idx = 0; - for(FrameNode *inline_frame = first_inline_frame; inline_frame != 0; inline_frame = inline_frame->next, inline_frame_idx += 1) - { - inline_frame->v.inline_depth = inline_frame_count - inline_frame_idx; - if(inline_frame == last_inline_frame) - { - break; - } - } - } - - //- rjf: package - result.frames_count = frame_count; - result.frames = push_array(arena, CTRL_CallStackFrame, result.frames_count); - result.concrete_frames_count = base_unwind->frames.count; - result.concrete_frames = push_array(arena, CTRL_CallStackFrame *, result.concrete_frames_count); - { - U64 idx = 0; - U64 concrete_idx = 0; - for(FrameNode *n = first_frame; n != 0; n = n->next, idx += 1) - { - MemoryCopyStruct(&result.frames[idx], &n->v); - if(n->v.inline_depth == 0 && concrete_idx < result.concrete_frames_count) - { - result.concrete_frames[concrete_idx] = &result.frames[idx]; - concrete_idx += 1; - } - } - } - } - access_close(access); - scratch_end(scratch); - return result; -} - -internal CTRL_CallStackFrame * -ctrl_call_stack_frame_from_unwind_and_inline_depth(CTRL_CallStack *call_stack, U64 unwind_count, U64 inline_depth) -{ - CTRL_CallStackFrame *f = 0; - { - U64 base_frame_idx = 0; - for(U64 idx = 0; idx < call_stack->frames_count; idx += 1) - { - if(call_stack->frames[idx].inline_depth == 0) - { - if(base_frame_idx == unwind_count) - { - f = &call_stack->frames[idx]; - break; - } - base_frame_idx += 1; - } - } - if(f != 0 && call_stack->frames + inline_depth < f) - { - f -= inline_depth; - } - } - return f; -} - -//////////////////////////////// -//~ rjf: Halting All Attached Processes - -internal void -ctrl_halt(void) -{ - dmn_halt(0, 0); -} - -//////////////////////////////// -//~ rjf: Shared Accessor Functions - -//- rjf: generation counters - -internal U64 -ctrl_run_gen(void) -{ - U64 result = ins_atomic_u64_eval(&ctrl_state->run_gen); - return result; -} - -internal U64 -ctrl_mem_gen(void) -{ - U64 result = ins_atomic_u64_eval(&ctrl_state->mem_gen); - return result; -} - -internal U64 -ctrl_reg_gen(void) -{ - U64 result = ins_atomic_u64_eval(&ctrl_state->reg_gen); - return result; -} - -//- rjf: name -> register/alias hash tables, for eval - -internal E_String2NumMap * -ctrl_string2reg_from_arch(Arch arch) -{ - return &ctrl_state->arch_string2reg_tables[arch]; -} - -internal E_String2NumMap * -ctrl_string2alias_from_arch(Arch arch) -{ - return &ctrl_state->arch_string2alias_tables[arch]; -} - -//////////////////////////////// -//~ rjf: Control-Thread Functions - -//- rjf: user -> control thread communication - -internal B32 -ctrl_u2c_push_msgs(CTRL_MsgList *msgs, U64 endt_us) -{ - Temp scratch = scratch_begin(0, 0); - String8 msgs_srlzed_baked = ctrl_serialized_string_from_msg_list(scratch.arena, msgs); - B32 good = 0; - MutexScope(ctrl_state->u2c_ring_mutex) for(;;) - { - U64 unconsumed_size = (ctrl_state->u2c_ring_write_pos-ctrl_state->u2c_ring_read_pos); - U64 available_size = ctrl_state->u2c_ring_size-unconsumed_size; - U64 needed_size = sizeof(msgs_srlzed_baked.size) + msgs_srlzed_baked.size; - if(available_size >= needed_size) - { - ctrl_state->u2c_ring_write_pos += ring_write_struct(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_write_pos, &msgs_srlzed_baked.size); - ctrl_state->u2c_ring_write_pos += ring_write(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_write_pos, msgs_srlzed_baked.str, msgs_srlzed_baked.size); - good = 1; - break; - } - if(os_now_microseconds() >= endt_us) - { - break; - } - cond_var_wait(ctrl_state->u2c_ring_cv, ctrl_state->u2c_ring_mutex, endt_us); - } - if(good) - { - cond_var_broadcast(ctrl_state->u2c_ring_cv); - } - scratch_end(scratch); - return good; -} - -internal CTRL_MsgList -ctrl_u2c_pop_msgs(Arena *arena) -{ - Temp scratch = scratch_begin(&arena, 1); - String8 msgs_srlzed_baked = {0}; - MutexScope(ctrl_state->u2c_ring_mutex) for(;;) - { - U64 unconsumed_size = (ctrl_state->u2c_ring_write_pos-ctrl_state->u2c_ring_read_pos); - if(unconsumed_size >= sizeof(U64)) - { - U64 size_to_decode = 0; - ctrl_state->u2c_ring_read_pos += ring_read_struct(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_read_pos, &size_to_decode); - msgs_srlzed_baked.size = size_to_decode; - msgs_srlzed_baked.str = push_array_no_zero(scratch.arena, U8, msgs_srlzed_baked.size); - ctrl_state->u2c_ring_read_pos += ring_read(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_read_pos, msgs_srlzed_baked.str, size_to_decode); - break; - } - cond_var_wait(ctrl_state->u2c_ring_cv, ctrl_state->u2c_ring_mutex, max_U64); - } - cond_var_broadcast(ctrl_state->u2c_ring_cv); - CTRL_MsgList msgs = ctrl_msg_list_from_serialized_string(arena, msgs_srlzed_baked); - scratch_end(scratch); - return msgs; -} - -//- rjf: control -> user thread communication - -internal void -ctrl_c2u_push_events(CTRL_EventList *events) -{ - if(events->count != 0) ProfScope("ctrl_c2u_push_events") - { - MutexScopeW(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) - { - ctrl_entity_store_apply_events(ctrl_state->ctrl_thread_entity_store, events); - } - for(CTRL_EventNode *n = events->first; n != 0; n = n ->next) - { - Temp scratch = scratch_begin(0, 0); - String8 event_srlzed = ctrl_serialized_string_from_event(scratch.arena, &n->v, ctrl_state->c2u_ring_size-sizeof(U64)); - MutexScope(ctrl_state->c2u_ring_mutex) for(;;) - { - U64 unconsumed_size = (ctrl_state->c2u_ring_write_pos-ctrl_state->c2u_ring_read_pos); - U64 available_size = ctrl_state->c2u_ring_size-unconsumed_size; - U64 needed_size = sizeof(event_srlzed.size) + event_srlzed.size; - if(available_size >= needed_size) - { - ctrl_state->c2u_ring_write_pos += ring_write_struct(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_write_pos, &event_srlzed.size); - ctrl_state->c2u_ring_write_pos += ring_write(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_write_pos, event_srlzed.str, event_srlzed.size); - break; - } - cond_var_wait(ctrl_state->c2u_ring_cv, ctrl_state->c2u_ring_mutex, os_now_microseconds()+100); - } - cond_var_broadcast(ctrl_state->c2u_ring_cv); - if(ctrl_state->wakeup_hook != 0) - { - ctrl_state->wakeup_hook(); - } - scratch_end(scratch); - } - } -} - -internal CTRL_EventList -ctrl_c2u_pop_events(Arena *arena) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - CTRL_EventList events = {0}; - MutexScope(ctrl_state->c2u_ring_mutex) for(;;) - { - U64 unconsumed_size = (ctrl_state->c2u_ring_write_pos-ctrl_state->c2u_ring_read_pos); - if(unconsumed_size >= sizeof(U64)) - { - U64 size_to_decode = 0; - ctrl_state->c2u_ring_read_pos += ring_read_struct(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_read_pos, &size_to_decode); - String8 event_srlzed = {0}; - event_srlzed.size = size_to_decode; - event_srlzed.str = push_array_no_zero(scratch.arena, U8, event_srlzed.size); - ctrl_state->c2u_ring_read_pos += ring_read(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_read_pos, event_srlzed.str, event_srlzed.size); - CTRL_Event *new_event = ctrl_event_list_push(arena, &events); - *new_event = ctrl_event_from_serialized_string(arena, event_srlzed); - } - else - { - break; - } - } - cond_var_broadcast(ctrl_state->c2u_ring_cv); - scratch_end(scratch); - ProfEnd(); - return events; -} - -//- rjf: entry point - -internal void -ctrl_thread__entry_point(void *p) -{ - ThreadNameF("ctrl_thread"); - ProfBeginFunction(); - DMN_CtrlCtx *ctrl_ctx = dmn_ctrl_begin(); - log_select(ctrl_state->ctrl_thread_log); - - //- rjf: loop - Temp scratch = scratch_begin(0, 0); - for(;;) - { - temp_end(scratch); - log_scope_begin(); - - //- rjf: get next messages - CTRL_MsgList msgs = ctrl_u2c_pop_msgs(scratch.arena); - - //- rjf: process messages - DMN_CtrlExclusiveAccessScope - { - ins_atomic_u64_eval_assign(&ctrl_state->ctrl_thread_run_state, 1); - for(CTRL_MsgNode *msg_n = msgs.first; msg_n != 0; msg_n = msg_n->next) - { - CTRL_Msg *msg = &msg_n->v; - { - log_infof("user2ctrl_msg:{kind:\"%S\"}\n", ctrl_string_from_msg_kind(msg->kind)); - } - - //- rjf: reset per-message state - ProfScope("reset per-message state") - { - arena_clear(ctrl_state->ctrl_thread_msg_process_arena); - ctrl_state->module_req_cache_slots_count = 4096; - ctrl_state->module_req_cache_slots = push_array(ctrl_state->ctrl_thread_msg_process_arena, CTRL_ModuleReqCacheNode *, ctrl_state->module_req_cache_slots_count); - MemoryZeroStruct(&ctrl_state->msg_user_bp_touched_files); - MemoryZeroStruct(&ctrl_state->msg_user_bp_touched_symbols); - MemoryCopyArray(ctrl_state->exception_code_filters, msg->exception_code_filters); - } - - //- rjf: gather all touched symbols by user breakpoints - { - Temp scratch = scratch_begin(0, 0); - for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) - { - if(n->v.kind != CTRL_UserBreakpointKind_Expression) - { - continue; - } - E_Parse addr_parse = e_push_parse_from_string(scratch.arena, n->v.string); - E_Parse cnd_parse = e_push_parse_from_string(scratch.arena, n->v.condition); - E_Expr *exprs[] = {addr_parse.expr, cnd_parse.expr}; - for EachElement(idx, exprs) - { - typedef struct ExprWalkTask ExprWalkTask; - struct ExprWalkTask - { - ExprWalkTask *next; - E_Expr *expr; - }; - ExprWalkTask start_task = {0, exprs[idx]}; - ExprWalkTask *first_task = &start_task; - for(ExprWalkTask *t = first_task; t != 0; t = t->next) - { - E_Expr *expr = t->expr; - if(expr->ref != &e_expr_nil) - { - expr = expr->ref; - } - if(expr->kind == E_ExprKind_LeafIdentifier) - { - str8_list_push(ctrl_state->ctrl_thread_msg_process_arena, &ctrl_state->msg_user_bp_touched_symbols, expr->string); - } - if(expr->next != &e_expr_nil) - { - ExprWalkTask *task = push_array(scratch.arena, ExprWalkTask, 1); - task->expr = expr->next; - task->next = t->next; - t->next = task; - } - if(expr->first != &e_expr_nil) - { - ExprWalkTask *task = push_array(scratch.arena, ExprWalkTask, 1); - task->expr = expr->first; - task->next = t->next; - t->next = task; - } - } - } - } - scratch_end(scratch); - } - - - - - - //- rjf: gather all touched files by user breakpoints - for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) - { - if(n->v.kind != CTRL_UserBreakpointKind_FileNameAndLineColNumber) - { - continue; - } - str8_list_push(ctrl_state->ctrl_thread_msg_process_arena, &ctrl_state->msg_user_bp_touched_files, n->v.string); - } - - //- rjf: process message - switch(msg->kind) - { - case CTRL_MsgKind_Null: - case CTRL_MsgKind_COUNT:{}break; - - //- rjf: target operations - case CTRL_MsgKind_Launch: {ctrl_thread__launch (ctrl_ctx, msg);}break; - case CTRL_MsgKind_Attach: {ctrl_thread__attach (ctrl_ctx, msg);}break; - case CTRL_MsgKind_Kill: {ctrl_thread__kill (ctrl_ctx, msg);}break; - case CTRL_MsgKind_KillAll: {ctrl_thread__kill_all (ctrl_ctx, msg);}break; - case CTRL_MsgKind_Detach: {ctrl_thread__detach (ctrl_ctx, msg);}break; - case CTRL_MsgKind_Run: {ctrl_thread__run (ctrl_ctx, msg);}break; - case CTRL_MsgKind_SingleStep: {ctrl_thread__single_step (ctrl_ctx, msg);}break; - - //- rjf: configuration - case CTRL_MsgKind_SetUserEntryPoints: - { - arena_clear(ctrl_state->user_entry_point_arena); - MemoryZeroStruct(&ctrl_state->user_entry_points); - for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) - { - str8_list_push(ctrl_state->user_entry_point_arena, &ctrl_state->user_entry_points, n->string); - } - }break; - case CTRL_MsgKind_SetModuleDebugInfoPath: - { - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - String8 path = msg->path; - CTRL_Entity *module = ctrl_entity_from_handle(entity_ctx, msg->entity); - CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); - DI_Key old_dbgi_key = di_key_from_path_timestamp(debug_info_path->string, debug_info_path->timestamp); - di_close(old_dbgi_key, 0); - MutexScopeW(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) - { - ctrl_entity_equip_string(ctrl_state->ctrl_thread_entity_store, debug_info_path, path_normalized_from_string(scratch.arena, path)); - } - U64 new_dbgi_timestamp = os_properties_from_file_path(path).modified; - debug_info_path->timestamp = new_dbgi_timestamp; - DI_Key new_dbgi_key = di_key_from_path_timestamp(debug_info_path->string, new_dbgi_timestamp); - di_open(new_dbgi_key); - CTRL_EventList evts = {0}; - CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); - evt->kind = CTRL_EventKind_ModuleDebugInfoPathChange; - evt->entity = msg->entity; - evt->string = path; - evt->timestamp = new_dbgi_timestamp; - ctrl_c2u_push_events(&evts); - }break; - case CTRL_MsgKind_FreezeThread: - { - CTRL_EventList evts = {0}; - CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); - evt->kind = CTRL_EventKind_ThreadFrozen; - evt->entity = msg->entity; - ctrl_c2u_push_events(&evts); - }break; - case CTRL_MsgKind_ThawThread: - { - CTRL_EventList evts = {0}; - CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); - evt->kind = CTRL_EventKind_ThreadThawed; - evt->entity = msg->entity; - ctrl_c2u_push_events(&evts); - }break; - } - } - ins_atomic_u64_eval_assign(&ctrl_state->ctrl_thread_run_state, 0); - } - ins_atomic_u64_inc_eval(&ctrl_state->run_gen); - ins_atomic_u64_inc_eval(&ctrl_state->mem_gen); - ins_atomic_u64_inc_eval(&ctrl_state->reg_gen); - - //- rjf: update thread register cache - ProfScope("update thread register cache") - { - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - CTRL_EntityArray threads = ctrl_entity_array_from_kind(entity_ctx, CTRL_EntityKind_Thread); - REGS_RegBlockX64 *blocks = push_array(scratch.arena, REGS_RegBlockX64, threads.count); - { - for EachIndex(idx, threads.count) - { - Temp scratch = scratch_begin(0, 0); - ctrl_reg_block_from_thread(scratch.arena, entity_ctx, threads.v[idx]->handle); - scratch_end(scratch); - } - } - } - - //- rjf: gather & output logs - ctrl_thread__end_and_flush_log(); - } - - scratch_end(scratch); - ProfEnd(); -} - -//- rjf: breakpoint resolution - -internal void -ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, CTRL_EvalScope *eval_scope, CTRL_Handle process, CTRL_Handle module, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out) -{ - if(user_bps->first == 0) { return; } - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - Access *access = eval_scope->access; - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - CTRL_Entity *module_entity = ctrl_entity_from_handle(entity_ctx, module); - CTRL_Entity *debug_info_path_entity = ctrl_entity_child_from_kind(module_entity, CTRL_EntityKind_DebugInfoPath); - DI_Key dbgi_key = ctrl_dbgi_key_from_module(module_entity); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); - U64 base_vaddr = module_entity->vaddr_range.min; - for(CTRL_UserBreakpointNode *n = user_bps->first; n != 0; n = n->next) - { - CTRL_UserBreakpoint *bp = &n->v; - switch(bp->kind) - { - default:{}break; - - //- rjf: file:line-based breakpoints - case CTRL_UserBreakpointKind_FileNameAndLineColNumber: - { - // rjf: unpack & normalize - TxtPt pt = bp->pt; - String8 filename = bp->string; - String8 filename_normalized = push_str8_copy(scratch.arena, filename); - for(U64 idx = 0; idx < filename_normalized.size; idx += 1) - { - filename_normalized.str[idx] = lower_from_char(filename_normalized.str[idx]); - filename_normalized.str[idx] = correct_slash_from_char(filename_normalized.str[idx]); - } - - // rjf: filename -> src_id - U32 src_id = 0; - { - RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); - if(mapptr != 0) - { - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, filename_normalized.str, filename_normalized.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - src_id = ids[0]; - } - } - } - } - - // rjf: src_id * pt -> push - if(src_id != 0) - { - RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); - RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); - RDI_ParsedSourceLineMap line_map = {0}; - rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); - U32 voff_count = 0; - U64 *voffs = rdi_line_voffs_from_num(&line_map, pt.line, &voff_count); - for(U32 i = 0; i < voff_count; i += 1) - { - U64 vaddr = voffs[i] + base_vaddr; - DMN_Trap trap = {process.dmn_handle, vaddr, (U64)bp}; - dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); - } - } - }break; - - //- rjf: expression-based breakpoints - case CTRL_UserBreakpointKind_Expression: - { - String8 expr = bp->string; - E_Value value = e_value_from_string(expr); - if(value.u64 != 0 || bp->flags != 0) - { - DMN_Trap trap = {process.dmn_handle, value.u64, (U64)bp}; - trap.flags = ctrl_dmn_trap_flags_from_user_breakpoint_flags(bp->flags); - trap.size = bp->size; - dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); - } - }break; - } - } - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__append_resolved_process_user_bp_traps(Arena *arena, CTRL_EvalScope *eval_scope, CTRL_Handle process, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out) -{ - for(CTRL_UserBreakpointNode *n = user_bps->first; n != 0; n = n->next) - { - CTRL_UserBreakpoint *bp = &n->v; - if(bp->kind == CTRL_UserBreakpointKind_Expression) - { - String8 expr = bp->string; - E_Value value = e_value_from_string(expr); - if(value.u64 != 0 || bp->flags != 0) - { - DMN_Trap trap = {process.dmn_handle, value.u64, (U64)bp}; - trap.flags = ctrl_dmn_trap_flags_from_user_breakpoint_flags(bp->flags); - trap.size = bp->size; - dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); - } - } - } -} - -internal void -ctrl_thread__append_program_defined_bp_traps(Arena *arena, CTRL_Entity *bp, DMN_TrapChunkList *traps_out) -{ - CTRL_Entity *process = bp->parent; - DMN_Trap trap = - { - .process = process->handle.dmn_handle, - .vaddr = bp->vaddr_range.min, - .id = ((U64)bp|bit64), - .flags = ctrl_dmn_trap_flags_from_user_breakpoint_flags(bp->bp_flags), - .size = (U32)dim_1u64(bp->vaddr_range), - }; - dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); -} - -//- rjf: module lifetime open/close work - -internal -MACHINE_OP_MEM_READ(ctrl_dmn_mem_read) -{ - U64 read = dmn_process_read(((CTRL_Handle *)ud)->dmn_handle, r1u64(addr, addr + buffer_size), buffer); - return read == buffer_size ? MachineOpResult_Ok : MachineOpResult_Fail; -} - -internal void -ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range, String8 path, Rng1U64 elf_phdr_vrange, U64 elf_phdr_entsize) -{ - Temp scratch = scratch_begin(0,0); - - //- rjf: set up per-module info - Arena *arena = arena_alloc(); - U64 entry_point_voff = 0; - U32 rdi_dbg_time = 0; - Guid rdi_dbg_guid = {0}; - String8 exe_dbg_path = {0}; - String8 rdi_dbg_path = {0}; - String8 raddbg_data = {0}; - Rng1U64 raddbg_section_voff_range = {0}; - Rng1U64 raddbg_is_attached_section_voff_range = {0}; - PE_IntelPdata *pdatas = 0; - U64 pdatas_count = 0; - U32 pdb_dbg_time = 0; - U32 pdb_dbg_age = 0; - Guid pdb_dbg_guid = {0}; - String8 pdb_dbg_path = {0}; - U64 cfi_rebase = 0; - B32 is_unwind_eh = 0; - EH_FrameHdr eh_frame_hdr = {0}; - EH_PtrCtx eh_ptr_ctx = { .pc_vaddr = max_U64, .text_vaddr = max_U64, .data_vaddr = max_U64, .func_vaddr = max_U64, .ptr_align = 0 }; - - //- read module's signature bytes - U64 module_sig_size = Max(elf_magic_string.size, sizeof(PE_DosMagic)); - U8 *module_sig_bytes = push_array(scratch.arena, U8, module_sig_size); - dmn_process_read(process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + module_sig_size), module_sig_bytes); - - ////////////////////////////// - //- parse PE module - // - PE_DosMagic dos_magic = *(PE_DosMagic *)module_sig_bytes; - if(dos_magic == PE_DOS_MAGIC) - { - ProfScope("unpack relevant PE info") - { - B32 is_valid = 1; - - //- rjf: read DOS header - PE_DosHeader dos_header = {0}; - if(is_valid) - { - if(!dmn_process_read_struct(process.dmn_handle, vaddr_range.min, &dos_header) || - dos_header.magic != PE_DOS_MAGIC) - { - is_valid = 0; - } - } - - //- rjf: read PE magic - U32 pe_magic = 0; - if(is_valid) - { - if(!dmn_process_read_struct(process.dmn_handle, vaddr_range.min + dos_header.coff_file_offset, &pe_magic) || - pe_magic != PE_MAGIC) - { - is_valid = 0; - } - } - - //- rjf: read COFF header - U64 file_header_off = dos_header.coff_file_offset + sizeof(pe_magic); - COFF_FileHeader file_header = {0}; - if(is_valid) - { - if(!dmn_process_read_struct(process.dmn_handle, vaddr_range.min + file_header_off, &file_header)) - { - is_valid = 0; - } - } - - //- rjf: unpack range of optional extension header - U32 opt_ext_size = file_header.optional_header_size; - Rng1U64 opt_ext_off_range = r1u64(file_header_off + sizeof(COFF_FileHeader), - file_header_off + sizeof(COFF_FileHeader) + opt_ext_size); - - //- rjf: read optional header - U64 entry_point = 0; - U32 data_dir_count = 0; - if(opt_ext_size > 0) - { - // rjf: read magic number - U16 opt_ext_magic = 0; - dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &opt_ext_magic); - - // rjf: read info - U32 reported_data_dir_offset = 0; - U32 reported_data_dir_count = 0; - switch(opt_ext_magic) - { - case PE_PE32_MAGIC: - { - PE_OptionalHeader32 pe_optional = {0}; - dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &pe_optional); - entry_point = pe_optional.entry_point_va; - reported_data_dir_offset = sizeof(pe_optional); - reported_data_dir_count = pe_optional.data_dir_count; - }break; - case PE_PE32PLUS_MAGIC: - { - PE_OptionalHeader32Plus pe_optional = {0}; - dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &pe_optional); - entry_point = pe_optional.entry_point_va; - reported_data_dir_offset = sizeof(pe_optional); - reported_data_dir_count = pe_optional.data_dir_count; - }break; - } - - // rjf: find number of data directories - U32 data_dir_max = (opt_ext_size - reported_data_dir_offset) / sizeof(PE_DataDirectory); - data_dir_count = ClampTop(reported_data_dir_count, data_dir_max); - - // rjf: grab pdatas from exceptions section - if(data_dir_count > PE_DataDirectoryIndex_EXCEPTIONS) - { - PE_DataDirectory dir = {0}; - dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_EXCEPTIONS, &dir); - Rng1U64 pdatas_voff_range = r1u64((U64)dir.virt_off, (U64)dir.virt_off + (U64)dir.virt_size); - pdatas_count = dim_1u64(pdatas_voff_range)/sizeof(PE_IntelPdata); - pdatas = push_array(arena, PE_IntelPdata, pdatas_count); - dmn_process_read(process.dmn_handle, r1u64(vaddr_range.min + pdatas_voff_range.min, vaddr_range.min + pdatas_voff_range.max), pdatas); - } - - // rjf: extract sections - U64 sec_array_off = opt_ext_off_range.max; - U64 sec_count = file_header.section_count; - COFF_SectionHeader *sec = push_array(scratch.arena, COFF_SectionHeader, sec_count); - dmn_process_read(process.dmn_handle, r1u64(vaddr_range.min + sec_array_off, vaddr_range.min + sec_array_off + sec_count*sizeof(COFF_SectionHeader)), sec); - - // rjf: grab entry point vaddr - entry_point_voff = entry_point; - - // rjf: grab data about debug info - if(data_dir_count > PE_DataDirectoryIndex_DEBUG) - { - // rjf: read data dir - PE_DataDirectory dir = {0}; - dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_DEBUG, &dir); - - U64 dbg_dir_count = dir.virt_size / sizeof(PE_DebugDirectory); - for(U64 dbg_dir_idx = 0; dbg_dir_idx < dbg_dir_count; dbg_dir_idx += 1) - { - // rjf: read debug directory - U64 dir_addr = vaddr_range.min + dir.virt_off + dbg_dir_idx * sizeof(PE_DebugDirectory); - PE_DebugDirectory dbg_data = {0}; - dmn_process_read_struct(process.dmn_handle, dir_addr, &dbg_data); - - // rjf: extract external file info from codeview header - if(dbg_data.type == PE_DebugDirectoryType_CODEVIEW) - { - U32 cv_magic = 0; - dmn_process_read_struct(process.dmn_handle, vaddr_range.min + dbg_data.voff, &cv_magic); - switch(cv_magic) - { - default:break; - case PE_CODEVIEW_PDB20_MAGIC: - { - PE_CvHeaderPDB20 cv; - U64 read_size = dmn_process_read_struct(process.dmn_handle, vaddr_range.min+dbg_data.voff, &cv); - if(read_size == sizeof(cv)) - { - pdb_dbg_time = cv.time_stamp; - pdb_dbg_age = cv.age; - pdb_dbg_path = dmn_process_read_cstring(arena, process.dmn_handle, vaddr_range.min + dbg_data.voff + sizeof(cv)); - } - }break; - case PE_CODEVIEW_PDB70_MAGIC: - { - PE_CvHeaderPDB70 cv; - U64 read_size = dmn_process_read_struct(process.dmn_handle, vaddr_range.min + dbg_data.voff, &cv); - if(read_size == sizeof(cv)) - { - pdb_dbg_guid = cv.guid; - pdb_dbg_age = cv.age; - pdb_dbg_path = dmn_process_read_cstring(arena, process.dmn_handle, vaddr_range.min + dbg_data.voff + sizeof(cv)); - } - }break; - case PE_CODEVIEW_RDI_MAGIC: - { - PE_CvHeaderRDI cv; - U64 read_size = dmn_process_read_struct(process.dmn_handle, vaddr_range.min + dbg_data.voff, &cv); - if(read_size == sizeof(cv)) - { - rdi_dbg_guid = cv.guid; - rdi_dbg_path = dmn_process_read_cstring(arena, process.dmn_handle, vaddr_range.min + dbg_data.voff + sizeof(cv)); - } - }break; - } - } - } - } - - // rjf: look for DWARF debug info - { - U64 symbol_array_off = file_header.symbol_table_foff; - U64 symbol_count = file_header.symbol_count; - if(symbol_array_off != 0) - { - exe_dbg_path = path; - } - } - - // rjf: extract copy of module's raddbg data - { - for EachIndex(idx, sec_count) - { - String8 section_name = str8_cstring((char *)sec[idx].name); - if(str8_match(section_name, str8_lit(".raddbg"), 0)) - { - raddbg_section_voff_range.min = sec[idx].voff; - raddbg_section_voff_range.max = sec[idx].voff + sec[idx].vsize; - } - else if(str8_match(section_name, str8_lit(".rdbgia"), 0)) - { - raddbg_is_attached_section_voff_range.min = sec[idx].voff; - raddbg_is_attached_section_voff_range.max = sec[idx].voff + sec[idx].vsize; - } - } - raddbg_data.size = dim_1u64(raddbg_section_voff_range); - raddbg_data.str = push_array(arena, U8, raddbg_data.size); - dmn_process_read(process.dmn_handle, r1u64(vaddr_range.min + raddbg_section_voff_range.min, - vaddr_range.min + raddbg_section_voff_range.max), raddbg_data.str); - } - - // rjf: if we have a "raddbg is attached" section, mark the first byte as 1, to signify attachment - if(raddbg_is_attached_section_voff_range.max != raddbg_is_attached_section_voff_range.min) - { - U8 new_value = 1; - dmn_process_write_struct(process.dmn_handle, vaddr_range.min + raddbg_is_attached_section_voff_range.min, &new_value); - } - } - } - } - - ////////////////////////////// - //- parse ELF module - // - else if(str8_match(str8(module_sig_bytes, elf_magic_string.size), elf_magic_string, 0)) - { - U64 e_entry = 0; - ELF_Type e_type = ELF_Type_None; - { - U8 *elf_sig = dmn_process_read_raw(scratch.arena, process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + sizeof(elf_sig[0]) * ELF_Identifier_Max)); - if(elf_sig == 0) { goto elf_exit; } - switch(elf_sig[ELF_Identifier_Class]) - { - default: - case ELF_Class_None:{}break; - case ELF_Class_32: - { - NotImplemented; - }break; - case ELF_Class_64: - { - ELF_Hdr64 *ehdr = dmn_process_read_raw(scratch.arena, process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + sizeof(*ehdr))); - if(ehdr == 0) { goto elf_exit; } - e_entry = ehdr->e_entry; - e_type = ehdr->e_type; - }break; - } - } - - if(e_type == ELF_Type_Dyn) - { - cfi_rebase = vaddr_range.min; - } - - // find and parse .eh_frame_hdr - Rng1U64 eh_frame_hdr_vrange = {0}; - String8 eh_frame_hdr_data = {0}; - { - void *phdrs_raw = dmn_process_read_raw(scratch.arena, process.dmn_handle, elf_phdr_vrange); - if(phdrs_raw == 0) { goto elf_exit; } - - if(elf_phdr_entsize == sizeof(ELF_Phdr32)) - { - NotImplemented; - } - else if(elf_phdr_entsize == sizeof(ELF_Phdr64)) - { - U64 elf_phcount = dim_1u64(elf_phdr_vrange) / elf_phdr_entsize; - ELF_Phdr64 *phdrs64 = phdrs_raw; - for EachIndex(phdr_idx, elf_phcount) - { - ELF_Phdr64 *phdr = phdrs64 + phdr_idx; - if(phdr->p_type == ELF_PType_GnuEHFrame) - { - eh_frame_hdr_vrange = r1u64(cfi_rebase + phdr->p_vaddr, cfi_rebase + phdr->p_vaddr + phdr->p_memsz); - eh_frame_hdr_data = dmn_process_read_block(arena, process.dmn_handle, eh_frame_hdr_vrange); - is_unwind_eh = 1; - break; - } - } - } - } - - // parse .eh_frame_hdr - Arch arch = ctrl_arch_from_process_handle(process); - eh_ptr_ctx.pc_vaddr = eh_frame_hdr_vrange.min; - eh_ptr_ctx.data_vaddr = eh_frame_hdr_vrange.min; - eh_frame_hdr = eh_parse_frame_hdr(eh_frame_hdr_data, byte_size_from_arch(arch), &eh_ptr_ctx); - - // set entry point - if (e_entry != 0) - { - entry_point_voff = e_entry - vaddr_range.min; - } - - // TODO: is there a way to detect DWARF in runtime ELF? - if(1) - { - exe_dbg_path = path; - } - elf_exit:; - } - - ////////////////////////////// - //- rjf: pick default initial debug info path - // - String8 initial_debug_info_path = str8_zero(); - { - String8 exe_folder = str8_chop_last_slash(path); - String8List dbg_path_candidates = {0}; - // - //~ TODO(rjf): @linux_port PLEASE READ RYAN vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - // - // TODO(rjf): trying "exe_folder/embedded_path" as the first option is only a valid - // heuristic on Windows, because we know that two absolute paths concatted together - // are necessarily invalid. however, on Linux, this is not the case - you could stitch - // two paths together and get a third path that is completely valid. so, in that case, - // we will need to infer if the path is relative, and then use either the embedded - // path as-is, or the exe-relative-path accordingly, depending on that. - // - if(rdi_dbg_path.size != 0) - { - str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S/%S", exe_folder, rdi_dbg_path); - str8_list_push(scratch.arena, &dbg_path_candidates, rdi_dbg_path); - } - if(exe_dbg_path.size != 0) - { - str8_list_push(scratch.arena, &dbg_path_candidates, path); - } - if(pdb_dbg_path.size != 0) - { - str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S/%S", exe_folder, pdb_dbg_path); - str8_list_push(scratch.arena, &dbg_path_candidates, pdb_dbg_path); - } - str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S.pdb", str8_chop_last_dot(path)); - str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S.pdb", path); - str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S.rdi", str8_chop_last_dot(path)); - str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S.rdi", path); - for(String8Node *n = dbg_path_candidates.first; n != 0; n = n->next) - { - String8 candidate_path = n->string; - FileProperties props = os_properties_from_file_path(candidate_path); - if(props.modified != 0 && props.size != 0) - { - initial_debug_info_path = push_str8_copy(arena, path_normalized_from_string(scratch.arena, candidate_path)); - break; - } - } - } - - ////////////////////////////// - //- rjf: insert info into cache - // - { - U64 hash = ctrl_hash_from_handle(module); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - MutexScopeW(stripe->rw_mutex) - { - CTRL_ModuleImageInfoCacheNode *node = 0; - for EachNode(n, CTRL_ModuleImageInfoCacheNode, slot->first) - { - if(ctrl_handle_match(n->module, module)) - { - node = n; - break; - } - } - if(!node) - { - node = push_array(arena, CTRL_ModuleImageInfoCacheNode, 1); - DLLPushBack(slot->first, slot->last, node); - node->module = module; - node->arena = arena; - node->pdatas = pdatas; - node->pdatas_count = pdatas_count; - node->cfi_rebase = cfi_rebase; - node->is_unwind_eh = is_unwind_eh; - node->eh_frame_hdr = eh_frame_hdr; - node->eh_ptr_ctx = eh_ptr_ctx; - node->entry_point_voff = entry_point_voff; - node->initial_debug_info_path = initial_debug_info_path; - node->raddbg_attached_marker_voff = raddbg_is_attached_section_voff_range.min; - node->raddbg_data = str8_copy(arena, raddbg_data); - } - } - } - - scratch_end(scratch); -} - -internal void -ctrl_thread__module_close(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range) -{ - ////////////////////////////// - //- rjf: evict module image info from cache - // - U64 raddbg_attached_marker_voff = 0; - { - U64 hash = ctrl_hash_from_handle(module); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - MutexScopeW(stripe->rw_mutex) - { - CTRL_ModuleImageInfoCacheNode *node = 0; - for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->module, module)) - { - node = n; - break; - } - } - if(node) - { - raddbg_attached_marker_voff = node->raddbg_attached_marker_voff; - DLLRemove(slot->first, slot->last, node); - arena_release(node->arena); - } - } - } - - ////////////////////////////// - //- rjf: write 0 at attachment market, to signify detachment - // - if(raddbg_attached_marker_voff != 0) - { - U8 new_value = 0; - dmn_process_write_struct(process.dmn_handle, vaddr_range.min + raddbg_attached_marker_voff, &new_value); - } -} - -//- rjf: attached process running/event gathering - -internal DMN_Event * -ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg, DMN_RunCtrls *run_ctrls, CTRL_Spoof *spoof) -{ - ProfBeginFunction(); - DMN_Event *event = push_array(arena, DMN_Event, 1); - Temp scratch = scratch_begin(&arena, 1); - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - - //- rjf: loop -> try to get event, run, repeat - U64 spoof_old_ip_value = 0; - ProfScope("loop -> try to get event, run, repeat") for(B32 got_event = 0; got_event == 0;) - { - //- rjf: get next event - ProfScope("get next event") - { - // rjf: grab first event - DMN_EventNode *next_event_node = ctrl_state->first_dmn_event_node; - - // rjf: log event - if(next_event_node != 0) - { - DMN_Event *ev = &next_event_node->v; - LogInfoNamedBlockF("dmn_event") - { - log_infof("kind: %S\n", dmn_event_kind_string_table[ev->kind]); - log_infof("exception_kind: %S\n", dmn_exception_kind_string_table[ev->exception_kind]); - log_infof("process: [%I64u]\n", ev->process.u64[0]); - log_infof("thread: [%I64u]\n", ev->thread.u64[0]); - log_infof("module: [%I64u]\n", ev->module.u64[0]); - log_infof("arch: %S\n", string_from_arch(ev->arch)); - log_infof("address: 0x%I64x\n", ev->address); - log_infof("string: \"%S\"\n", ev->string); - log_infof("ip_vaddr: 0x%I64x\n", ev->instruction_pointer); - } - } - - // rjf: determine if we should filter - B32 should_filter_event = 0; - if(next_event_node != 0) - { - DMN_Event *ev = &next_event_node->v; - switch(ev->kind) - { - default:{}break; - case DMN_EventKind_Exception: - { - // NOTE(rjf): first chance exceptions -> try ignoring - should_filter_event = (ev->exception_repeated == 0 && (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)); - - // rjf: exception code -> kind - CTRL_ExceptionCodeKind code_kind = CTRL_ExceptionCodeKind_Null; - if(should_filter_event) - { - for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) - { - if(ctrl_exception_code_kind_code_table[k] == ev->code) - { - code_kind = k; - break; - } - } - } - - // rjf: exception code kind -> shouldn't stop? if so, do not filter - if(should_filter_event) - { - B32 shouldnt_filter = !!(ctrl_state->exception_code_filters[code_kind/64] & (1ull<<(code_kind%64))); - if(should_filter_event && shouldnt_filter) - { - should_filter_event = 0; - } - } - - // rjf: special case: be gracious with ASan modules or symbols if - // they do their cute little 0xc0000005 exception trick... - if(!should_filter_event && ev->code == 0xc0000005 && - (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)) - { - Access *access = access_open(); - CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, ev->process)); - CTRL_Entity *module = &ctrl_entity_nil; - for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) - { - if(child->kind == CTRL_EntityKind_Module) - { - module = child; - break; - } - } - if(module != &ctrl_entity_nil) - { - // rjf: determine base address of asan shadow space - U64 asan_shadow_base_vaddr = 0; - B32 asan_shadow_variable_exists_but_is_zero = 0; - DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, max_U64); - RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_GlobalVariables); - { - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, unparsed_map, &map); - String8 name = str8_lit("__asan_shadow_memory_dynamic_address"); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - RDI_Symbol *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, ids[0]); - U64 global_var_voff = rdi_voff_from_location(global_var->location); - U64 global_var_vaddr = global_var_voff + module->vaddr_range.min; - Arch arch = process->arch; - U64 addr_size = bit_size_from_arch(arch)/8; - dmn_process_read(ev->process, r1u64(global_var_vaddr, global_var_vaddr+addr_size), &asan_shadow_base_vaddr); - asan_shadow_variable_exists_but_is_zero = (asan_shadow_base_vaddr == 0); - } - } - } - - // rjf: determine if this was a read/write to the shadow space - B32 violation_in_shadow_space = 0; - if(asan_shadow_base_vaddr != 0) - { - U64 asan_shadow_space_size = TB(128)/8; - if(asan_shadow_base_vaddr <= ev->address && ev->address < asan_shadow_base_vaddr+asan_shadow_space_size) - { - violation_in_shadow_space = 1; - } - } - - // rjf: filter event if this violation occurred in asan's shadow space - if(violation_in_shadow_space || asan_shadow_variable_exists_but_is_zero) - { - should_filter_event = 1; - } - } - - access_close(access); - } - }break; - } - } - - // rjf: good event & unfiltered? -> pop from queue & grab as result - if(next_event_node != 0 && !should_filter_event) - { - got_event = 1; - SLLQueuePop(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node); - MemoryCopyStruct(event, &next_event_node->v); - event->string = push_str8_copy(arena, event->string); - run_ctrls->ignore_previous_exception = 1; - } - - // rjf: good event but filtered? pop from queue - if(next_event_node != 0 && should_filter_event) - { - SLLQueuePop(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node); - run_ctrls->ignore_previous_exception = 0; - } - } - - //- rjf: no event -> dmn_ctrl_run for a new one - if(got_event == 0) ProfScope("no event -> dmn_ctrl_run for a new one") - { - // rjf: prep spoof - B32 do_spoof = (spoof != 0 && dmn_handle_match(run_ctrls->single_step_thread, dmn_handle_zero())); - U64 size_of_spoof = 0; - if(do_spoof) ProfScope("prep spoof") - { - CTRL_Entity *spoof_process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, spoof->process)); - Arch arch = spoof_process->arch; - size_of_spoof = bit_size_from_arch(arch)/8; - dmn_process_read(spoof_process->handle.dmn_handle, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); - } - - // rjf: set spoof - if(do_spoof) ProfScope("set spoof") - { - dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof->new_ip_value); - } - - // rjf: run for new events - ProfScope("run for new events") - { - LogInfoNamedBlockF("dmn_ctrl_run") - { - log_infof("single_step_thread: [0x%I64x]\n", run_ctrls->single_step_thread); - log_infof("ignore_previous_exception: %i\n", !!run_ctrls->ignore_previous_exception); - log_infof("run_entities_are_unfrozen: %i\n", !!run_ctrls->run_entities_are_unfrozen); - log_infof("run_entities_are_processes: %i\n", !!run_ctrls->run_entities_are_processes); - log_infof("run_entity_count: %I64u\n", run_ctrls->run_entity_count); - LogInfoNamedBlockF("run_entities") for(U64 idx = 0; idx < run_ctrls->run_entity_count; idx += 1) - { - log_infof("[0x%I64x]\n", run_ctrls->run_entities[idx]); - } - log_infof("trap_count: %I64u\n", run_ctrls->traps.trap_count); - LogInfoNamedBlockF("traps") for(DMN_TrapChunkNode *n = run_ctrls->traps.first; n != 0; n = n->next) - { - for(U64 idx = 0; idx < n->count; idx += 1) - { - log_infof("{process:[0x%I64x], vaddr:0x%I64x, id:0x%I64x}\n", n->v[idx].process.u64[0], n->v[idx].vaddr, n->v[idx].id); - } - } - } - - DMN_EventList events = dmn_ctrl_run(scratch.arena, ctrl_ctx, run_ctrls); - ins_atomic_u64_inc_eval(&ctrl_state->mem_gen); - ins_atomic_u64_inc_eval(&ctrl_state->reg_gen); - ins_atomic_u64_inc_eval(&ctrl_state->run_gen); - for(DMN_EventNode *src_n = events.first; src_n != 0; src_n = src_n->next) - { - DMN_EventNode *dst_n = ctrl_state->free_dmn_event_node; - if(dst_n != 0) - { - SLLStackPop(ctrl_state->free_dmn_event_node); - } - else - { - dst_n = push_array(ctrl_state->dmn_event_arena, DMN_EventNode, 1); - } - MemoryCopyStruct(&dst_n->v, &src_n->v); - dst_n->v.string = push_str8_copy(ctrl_state->dmn_event_arena, dst_n->v.string); - SLLQueuePush(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node, dst_n); - } - } - - // rjf: unset spoof - if(do_spoof) ProfScope("unset spoof") - { - dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); - } - } - } - - //- rjf: irrespective of what event came back, we should ALWAYS check the - // spoof's thread and see if it hit the spoof address, because we may have - // simply been sent other debug events first - if(spoof != 0) - { - CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, spoof->thread)); - Arch arch = thread->arch; - void *regs_block = push_array(scratch.arena, U8, regs_block_size_from_arch(arch)); - dmn_thread_read_reg_block(spoof->thread, regs_block); - U64 spoof_thread_rip = regs_rip_from_arch_block(arch, regs_block); - if(spoof_thread_rip == spoof->new_ip_value) - { - regs_arch_block_write_rip(arch, regs_block, spoof_old_ip_value); - ctrl_thread_write_reg_block(ctrl_handle_make(CTRL_MachineID_Local, spoof->thread), regs_block); - } - } - - //- rjf: push ctrl events associated with this demon event - CTRL_EventList evts = {0}; - ProfScope("push ctrl events associated with this demon event") switch(event->kind) - { - default:{}break; - case DMN_EventKind_CreateProcess: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_NewProc; - out_evt->msg_id = msg->msg_id; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->arch = event->arch; - out_evt->entity_id = event->code; - out_evt->tls_model = ctrl_dynamic_linker_type_from_dmn(event->tls_model); - out_evt->target_os = OperatingSystem_CURRENT; // TODO: operating system of the remote target machine - ctrl_state->process_counter += 1; - }break; - case DMN_EventKind_CreateThread: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_NewThread; - out_evt->msg_id = msg->msg_id; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); - out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->arch = event->arch; - out_evt->entity_id = event->code; - out_evt->stack_base = dmn_stack_base_vaddr_from_thread(event->thread); - out_evt->tls_root = dmn_tls_root_vaddr_from_thread(event->thread); - out_evt->rip_vaddr = event->instruction_pointer; - out_evt->string = event->string; - }break; - case DMN_EventKind_LoadModule: - { - CTRL_Handle process_handle = ctrl_handle_make(CTRL_MachineID_Local, event->process); - CTRL_Handle module_handle = ctrl_handle_make(CTRL_MachineID_Local, event->module); - CTRL_Event *out_evt1 = ctrl_event_list_push(scratch.arena, &evts); - String8 module_path = path_normalized_from_string(scratch.arena, event->string); - U64 exe_timestamp = os_properties_from_file_path(module_path).modified; - ctrl_thread__module_open(process_handle, module_handle, r1u64(event->address, event->address+event->size), module_path, event->elf_phdr_vrange, event->elf_phdr_entsize); - out_evt1->kind = CTRL_EventKind_NewModule; - out_evt1->msg_id = msg->msg_id; - out_evt1->entity = module_handle; - out_evt1->parent = process_handle; - out_evt1->arch = event->arch; - out_evt1->entity_id = event->code; - out_evt1->vaddr_rng = r1u64(event->address, event->address+event->size); - out_evt1->rip_vaddr = event->address; - out_evt1->timestamp = exe_timestamp; - out_evt1->string = module_path; - out_evt1->tls_index = event->tls_index; - out_evt1->tls_offset = event->tls_offset; - CTRL_Event *out_evt2 = ctrl_event_list_push(scratch.arena, &evts); - String8 initial_debug_info_path = ctrl_initial_debug_info_path_from_module(scratch.arena, module_handle); - U64 debug_info_timestamp = os_properties_from_file_path(initial_debug_info_path).modified; - out_evt2->kind = CTRL_EventKind_ModuleDebugInfoPathChange; - out_evt2->msg_id = msg->msg_id; - out_evt2->entity = module_handle; - out_evt2->parent = process_handle; - out_evt2->timestamp = debug_info_timestamp; - out_evt2->string = initial_debug_info_path; - DI_Key initial_dbgi_key = di_key_from_path_timestamp(initial_debug_info_path, debug_info_timestamp); - di_open(initial_dbgi_key); - }break; - case DMN_EventKind_ExitProcess: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_EndProc; - out_evt->msg_id = msg->msg_id; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->u64_code = event->code; - ctrl_state->process_counter -= 1; - }break; - case DMN_EventKind_ExitThread: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_EndThread; - out_evt->msg_id = msg->msg_id; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); - out_evt->entity_id = event->code; - }break; - case DMN_EventKind_UnloadModule: - ProfScope("unload module %.*s", str8_varg(event->string)) - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - CTRL_Handle module_handle = ctrl_handle_make(CTRL_MachineID_Local, event->module); - CTRL_Entity *module_ent = ctrl_entity_from_handle(entity_ctx, module_handle); - CTRL_Entity *process_ent = ctrl_process_from_entity(module_ent); - String8 module_path = event->string; - ctrl_thread__module_close(process_ent->handle, module_handle, module_ent->vaddr_range); - out_evt->kind = CTRL_EventKind_EndModule; - out_evt->msg_id = msg->msg_id; - out_evt->entity = module_handle; - out_evt->string = module_path; - DI_Key dbgi_key = ctrl_dbgi_key_from_module(module_ent); - di_close(dbgi_key, 0); - }break; - case DMN_EventKind_DebugString: - { - U64 num_strings = (event->string.size + ctrl_state->c2u_ring_max_string_size-1) / ctrl_state->c2u_ring_max_string_size; - for(U64 string_idx = 0; string_idx < num_strings; string_idx += 1) - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_DebugString; - out_evt->msg_id = msg->msg_id; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); - out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->string = str8_substr(event->string, r1u64(string_idx*ctrl_state->c2u_ring_max_string_size, (string_idx+1)*ctrl_state->c2u_ring_max_string_size)); - } - }break; - case DMN_EventKind_SetThreadName: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_ThreadName; - out_evt->msg_id = msg->msg_id; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); - out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->string = event->string; - out_evt->entity_id = event->code; - }break; - case DMN_EventKind_SetThreadColor: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_ThreadColor; - out_evt->msg_id = msg->msg_id; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); - out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->entity_id = event->code; - out_evt->rgba = event->user_data; - }break; - case DMN_EventKind_SetVAddrRangeNote: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_SetVAddrRangeNote; - out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->msg_id = msg->msg_id; - out_evt->vaddr_rng = r1u64(event->address, event->address + event->size); - out_evt->string = event->string; - }break; - case DMN_EventKind_SetBreakpoint: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_SetBreakpoint; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); - out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); - out_evt->bp_flags = ctrl_user_breakpoint_flags_from_dmn_trap_flags(event->flags); - }break; - case DMN_EventKind_UnsetBreakpoint: - { - // TODO(rjf): this needs to be reflected in the resolved trap list too!!!!!!!! - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_UnsetBreakpoint; - out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); - out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); - out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); - out_evt->bp_flags = ctrl_user_breakpoint_flags_from_dmn_trap_flags(event->flags); - }break; - } - ctrl_c2u_push_events(&evts); - - //- rjf: if this is the first process in a session, clear the debug directory - // cache state - if(ctrl_state->process_counter == 1 && event->kind == DMN_EventKind_CreateProcess) - { - arena_clear(ctrl_state->dbg_dir_arena); - ctrl_state->dbg_dir_root = push_array(ctrl_state->dbg_dir_arena, CTRL_DbgDirNode, 1); - } - - //- rjf: out of queued up demon events -> clear event arena - if(ctrl_state->first_dmn_event_node == 0) - { - ctrl_state->free_dmn_event_node = 0; - arena_clear(ctrl_state->dmn_event_arena); - } - - scratch_end(scratch); - ProfEnd(); - return(event); -} - -//- rjf: eval helpers - -internal U64 -ctrl_eval_space_gen(E_Space space) -{ - U64 result = 0; - switch(space.kind) - { - default:{}break; - case CTRL_EvalSpaceKind_Entity: - { - result = ctrl_mem_gen(); - }break; - } - return result; -} - -internal B32 -ctrl_eval_space_read(E_Space space, void *out, Rng1U64 range) -{ - B32 result = 0; - switch(space.kind) - { - default:{}break; - - //- rjf: intra-entity reads (process memory or thread registers) - case CTRL_EvalSpaceKind_Entity: - { - CTRL_Entity *entity = (CTRL_Entity *)space.u64_0; - switch(entity->kind) - { - default:{}break; - case CTRL_EntityKind_Process: - { - U64 read_size = dmn_process_read(entity->handle.dmn_handle, range, out); - result = (read_size == dim_1u64(range)); - }break; - case CTRL_EntityKind_Thread: - { - Temp scratch = scratch_begin(0, 0); - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - U64 regs_size = regs_block_size_from_arch(entity->arch); - void *regs = ctrl_reg_block_from_thread(scratch.arena, entity_ctx, entity->handle); - Rng1U64 legal_range = r1u64(0, regs_size); - Rng1U64 read_range = intersect_1u64(legal_range, range); - U64 read_size = dim_1u64(read_range); - MemoryCopy(out, (U8 *)regs + read_range.min, read_size); - result = (read_size == dim_1u64(range)); - scratch_end(scratch); - }break; - } - }break; - } - return result; -} - -//- rjf: control thread eval scopes - -internal CTRL_EvalScope * -ctrl_thread__eval_scope_begin(Arena *arena, CTRL_UserBreakpointList *user_bps, CTRL_Entity *thread) -{ - ProfBeginFunction(); - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - CTRL_EvalScope *scope = push_array(arena, CTRL_EvalScope, 1); - scope->access = access_open(); - - ////////////////////////////// - //- rjf: unpack thread - // - Arch arch = thread->arch; - U64 thread_rip_vaddr = dmn_rip_from_thread(thread->handle.dmn_handle); - CTRL_Entity *process = ctrl_process_from_entity(thread); - CTRL_Entity *module = ctrl_module_from_process_vaddr(process, thread_rip_vaddr); - U64 thread_rip_voff = ctrl_voff_from_vaddr(module, thread_rip_vaddr); - - ////////////////////////////// - //- rjf: gather evaluation debug infos & modules - // - U64 eval_modules_count = Max(1, entity_ctx->entity_kind_counts[CTRL_EntityKind_Module]); - E_Module *eval_modules = push_array(arena, E_Module, eval_modules_count); - E_Module *eval_modules_primary = &eval_modules[0]; - eval_modules_primary->vaddr_range = r1u64(0, max_U64); - U64 eval_dbg_infos_count = Max(1, entity_ctx->entity_kind_counts[CTRL_EntityKind_Module]); - E_DbgInfo *eval_dbg_infos = push_array(arena, E_DbgInfo, eval_dbg_infos_count); - E_DbgInfo *eval_dbg_infos_primary = &eval_dbg_infos[0]; - MemoryCopyStruct(eval_dbg_infos_primary, &e_dbg_info_nil); - { - U64 eval_module_idx = 0; - U64 eval_dbg_info_idx = 0; - for(CTRL_Entity *machine = entity_ctx->root->first; - machine != &ctrl_entity_nil; - machine = machine->next) - { - if(machine->kind != CTRL_EntityKind_Machine) { continue; } - for(CTRL_Entity *process = machine->first; - process != &ctrl_entity_nil; - process = process->next) - { - if(process->kind != CTRL_EntityKind_Process) { continue; } - for(CTRL_Entity *mod = process->first; - mod != &ctrl_entity_nil; - mod = mod->next) - { - if(mod->kind != CTRL_EntityKind_Module) { continue; } - DI_Key dbgi_key = ctrl_dbgi_key_from_module(mod); - - //- rjf: try to obtain this module's RDI - RDI_Parsed *rdi = di_rdi_from_key(scope->access, dbgi_key, 0, 0); - - //- rjf: if this RDI is not yet ready => determine if we need to wait for it - // - // (we *always* wait for the initial module) - // - B32 rdi_is_necessary = 1; - if(user_bps->count == 0) - { - rdi_is_necessary = 0; - } - else if(rdi == &rdi_parsed_nil) ProfScope("determine if RDI is necessary") - { - // rjf: find cached result - U64 hash = ctrl_hash_from_handle(mod->handle); - U64 slot_idx = hash%ctrl_state->module_req_cache_slots_count; - CTRL_ModuleReqCacheNode *slot = ctrl_state->module_req_cache_slots[slot_idx]; - CTRL_ModuleReqCacheNode *node = 0; - for(CTRL_ModuleReqCacheNode *n = slot; n != 0; n = n->next) - { - if(ctrl_handle_match(n->module, mod->handle)) - { - node = n; - break; - } - } - - // rjf: cached? -> take cached result - if(node != 0) - { - rdi_is_necessary = node->required; - } - - // rjf: not cached -> compute & store - else ProfScope("cache miss") - { - CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(mod, CTRL_EntityKind_DebugInfoPath); - OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, debug_info_path->string); - ProfScope("determine if %.*s is necessary", str8_varg(debug_info_path->string)) - { - //- rjf: determine if file is PDB - B32 file_is_pdb = 0; - if(!file_is_pdb) - { - U8 msf70_magic_maybe[sizeof(msf_msf70_magic)] = {0}; - os_file_read(file, r1u64(0, sizeof(msf70_magic_maybe)), msf70_magic_maybe); - if(MemoryMatch(msf70_magic_maybe, msf_msf70_magic, sizeof(msf70_magic_maybe))) - { - file_is_pdb = 1; - } - } - if(!file_is_pdb) - { - U8 msf20_magic_maybe[sizeof(msf_msf20_magic)] = {0}; - os_file_read(file, r1u64(0, sizeof(msf20_magic_maybe)), msf20_magic_maybe); - if(MemoryMatch(msf20_magic_maybe, msf_msf20_magic, sizeof(msf20_magic_maybe))) - { - file_is_pdb = 1; - } - } - - //- rjf: file is PDB -> do thin parse & lookup of all breakpoint files/symbols. - // if any are found in the PDB, then this RDI is necessary. - if(file_is_pdb) - { - FileProperties props = os_properties_from_file(file); - OS_Handle map = os_file_map_open(OS_AccessFlag_Read, file); - void *file_base = os_file_map_view_open(map, OS_AccessFlag_Read, r1u64(0, props.size)); - String8 file_data = str8(file_base, props.size); - { - rdi_is_necessary = pdb_has_symbol_or_file_ref(file_data, ctrl_state->msg_user_bp_touched_symbols, ctrl_state->msg_user_bp_touched_files); - } - os_file_map_view_close(map, file_base, r1u64(0, props.size)); - os_file_map_close(map); - } - } - os_file_close(file); - node = push_array(ctrl_state->ctrl_thread_msg_process_arena, CTRL_ModuleReqCacheNode, 1); - node->next = slot; - ctrl_state->module_req_cache_slots[slot_idx] = node; - node->module = mod->handle; - node->required = rdi_is_necessary; - } - } - - //- rjf: if this RDI is necessary, but we do not have it => wait for it forever - if(rdi == &rdi_parsed_nil && rdi_is_necessary) ProfScope("RDI is necessary -> wait") - { - rdi = di_rdi_from_key(scope->access, dbgi_key, 1, max_U64); - } - - //- rjf: fill debug info - eval_dbg_infos[eval_dbg_info_idx].dbgi_key = dbgi_key; - eval_dbg_infos[eval_dbg_info_idx].rdi = rdi; - if(mod == module) - { - eval_dbg_infos_primary = &eval_dbg_infos[eval_dbg_info_idx]; - } - eval_dbg_info_idx += 1; - - //- rjf: fill evaluation module info - eval_modules[eval_module_idx].arch = arch; - eval_modules[eval_module_idx].dbg_info_num= (U32)eval_dbg_info_idx; - eval_modules[eval_module_idx].vaddr_range = mod->vaddr_range; - eval_modules[eval_module_idx].space = e_space_make(CTRL_EvalSpaceKind_Entity); - eval_modules[eval_module_idx].space.u64_0 = (U64)process; - if(mod == module) - { - eval_modules_primary = &eval_modules[eval_module_idx]; - } - eval_module_idx += 1; - } - } - } - } - - ////////////////////////////// - //- rjf: select evaluation cache - // - e_select_cache(ctrl_state->ctrl_thread_eval_cache); - - ////////////////////////////// - //- rjf: build base evaluation context - // - { - E_BaseCtx *ctx = &scope->base_ctx; - - //- rjf: fill instruction pointer info - ctx->thread_ip_vaddr = thread_rip_vaddr; - ctx->thread_ip_voff = thread_rip_voff; - ctx->thread_arch = thread->arch; - ctx->thread_reg_space = e_space_make(CTRL_EvalSpaceKind_Entity); - ctx->thread_reg_space.u64_0 = (U64)thread; - ctx->thread_process_space = e_space_make(CTRL_EvalSpaceKind_Entity); - ctx->thread_process_space.u64_0 = (U64)process; - - //- rjf: fill debug infos - ctx->dbg_infos = eval_dbg_infos; - ctx->dbg_infos_count = eval_dbg_infos_count; - ctx->primary_dbg_info = eval_dbg_infos_primary; - - //- rjf: fill modules - ctx->modules = eval_modules; - ctx->modules_count = eval_modules_count; - ctx->primary_module = eval_modules_primary; - - //- rjf: fill space hooks - ctx->space_gen = ctrl_eval_space_gen; - ctx->space_read = ctrl_eval_space_read; - } - e_select_base_ctx(&scope->base_ctx); - - ////////////////////////////// - //- rjf: build IR evaluation context - // - { - E_IRCtx *ctx = &scope->ir_ctx; - ctx->regs_map = ctrl_string2reg_from_arch(arch); - ctx->reg_alias_map = ctrl_string2alias_from_arch(arch); - ctx->locals_map = e_push_locals_map_from_rdi_voff(arena, eval_dbg_infos_primary->rdi, thread_rip_voff); - ctx->member_map = e_push_member_map_from_rdi_voff(arena, eval_dbg_infos_primary->rdi, thread_rip_voff); - ctx->macro_map = push_array(arena, E_String2ExprMap, 1); - ctx->macro_map[0] = e_string2expr_map_make(arena, 512); - ctx->auto_hook_map = push_array(arena, E_AutoHookMap, 1); - ctx->auto_hook_map[0] = e_auto_hook_map_make(arena, 512); - } - e_select_ir_ctx(&scope->ir_ctx); - - ////////////////////////////// - //- rjf: build eval interpretation context - // - { - E_InterpretCtx *ctx = &scope->interpret_ctx; - ctx->primary_space = eval_modules_primary->space; - ctx->reg_arch = eval_modules_primary->arch; - ctx->reg_space = e_space_make(CTRL_EvalSpaceKind_Entity); - ctx->reg_space.u64_0 = (U64)thread; - ctx->module_base = push_array(arena, U64, 1); - ctx->module_base[0]= module->vaddr_range.min; - ctx->frame_base = push_array(arena, U64, 1); - // TODO(rjf): need to compute this out here somehow... ctx->frame_base[0] = ; - ctx->tls_base = push_array(arena, U64, 1); - // TODO: compute CFA - ctx->cfa = 0; - } - e_select_interpret_ctx(&scope->interpret_ctx, eval_dbg_infos_primary->rdi, thread_rip_voff); - - ProfEnd(); - return scope; -} - -internal void -ctrl_thread__eval_scope_end(CTRL_EvalScope *scope) -{ - access_close(scope->access); -} - -//- rjf: log flusher - -internal void -ctrl_thread__end_and_flush_log(void) -{ - Temp scratch = scratch_begin(0, 0); - LogScopeResult log = log_scope_end(scratch.arena); - os_append_data_to_file_path(ctrl_state->ctrl_thread_log_path, log.strings[LogMsgKind_Info]); - if(log.strings[LogMsgKind_UserError].size != 0) - { - CTRL_EventList evts = {0}; - CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); - evt->kind = CTRL_EventKind_Error; - evt->string = log.strings[LogMsgKind_UserError]; - ctrl_c2u_push_events(&evts); - } - scratch_end(scratch); -} - -//- rjf: msg kind implementations - -internal void -ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - Temp scratch = scratch_begin(0, 0); - - //- rjf: produce full stdout/stderr/stdin paths - String8 stdout_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stdout_path, msg->path); - String8 stdin_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stdin_path, msg->path); - String8 stderr_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stderr_path, msg->path); - - //- rjf: obtain stdout/stderr/stdin handles - OS_Handle stdout_handle = {0}; - OS_Handle stderr_handle = {0}; - OS_Handle stdin_handle = {0}; - if(stdout_path.size != 0) - { - OS_Handle f = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Read, stdout_path); - os_file_close(f); - stdout_handle = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, msg->stdout_path); - } - if(stderr_path.size != 0) - { - OS_Handle f = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Read, stderr_path); - os_file_close(f); - stderr_handle = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, msg->stderr_path); - } - if(stdin_path.size != 0) - { - stdin_handle = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, stdin_path); - } - - //- rjf: launch - OS_ProcessLaunchParams params = {0}; - { - params.cmd_line = msg->cmd_line_string_list; - params.path = msg->path; - params.env = msg->env_string_list; - params.inherit_env = msg->env_inherit; - params.debug_subprocesses = msg->debug_subprocesses; - params.stdout_file = stdout_handle; - params.stderr_file = stderr_handle; - params.stdin_file = stdin_handle; - } - U32 id = dmn_ctrl_launch(ctrl_ctx, ¶ms); - - //- rjf: close stdout/stderr/stdin files - os_file_close(stdout_handle); - os_file_close(stderr_handle); - os_file_close(stdin_handle); - - //- rjf: record (id -> entry points), so that we know custom entry points for this PID - CTRL_EntityCtxRWStore *entity_ctx_rw_store = ctrl_state->ctrl_thread_entity_store; - MutexScopeW(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) - { - for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) - { - String8 string = n->string; - CTRL_Entity *entry = ctrl_entity_alloc(entity_ctx_rw_store, entity_ctx_rw_store->ctx.root, CTRL_EntityKind_EntryPoint, Arch_Null, ctrl_handle_zero(), (U64)id); - ctrl_entity_equip_string(entity_ctx_rw_store, entry, string); - } - } - - scratch_end(scratch); -} - -internal void -ctrl_thread__attach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - //- rjf: attach - B32 attach_successful = dmn_ctrl_attach(ctrl_ctx, msg->entity_id); - - //- rjf: run to handshake - if(attach_successful) - { - DMN_Handle unfrozen_process = {0}; - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities_are_processes = 1; - for(B32 done = 0; done == 0;) - { - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_CreateProcess: - { - unfrozen_process = event->process; - run_ctrls.run_entities = &unfrozen_process; - run_ctrls.run_entity_count = 1; - }break; - case DMN_EventKind_Halt: - case DMN_EventKind_Exception: - case DMN_EventKind_Error: - case DMN_EventKind_HandshakeComplete: - { - done = 1; - }break; - } - } - } - - //- rjf: record stop - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = CTRL_EventCause_Finished; - event->msg_id = msg->msg_id; - event->entity_id = !!attach_successful * msg->entity_id; - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__kill(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DMN_Handle process = msg->entity.dmn_handle; - U32 exit_code = msg->exit_code; - - //- rjf: send kill - B32 kill_worked = dmn_ctrl_kill(ctrl_ctx, process, exit_code); - - //- rjf: wait for process to be dead - CTRL_EventCause cause = CTRL_EventCause_Finished; - if(kill_worked) - { - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities_are_processes = 1; - run_ctrls.run_entities = &process; - run_ctrls.run_entity_count = 1; - for(B32 done = 0; done == 0;) - { - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_ExitProcess: - if(dmn_handle_match(event->process, process)) - { - done = 1; - }break; - case DMN_EventKind_Error:{done = 1; cause = CTRL_EventCause_Error;}break; - case DMN_EventKind_Halt: {done = 1; cause = CTRL_EventCause_InterruptedByHalt;}break; - } - } - } - - //- rjf: record stop - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = cause; - event->msg_id = msg->msg_id; - if(kill_worked) - { - event->entity = msg->entity; - } - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__kill_all(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - U32 exit_code = msg->exit_code; - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - - //- rjf: gather all currently existing processes - CTRL_EntityArray initial_processes = ctrl_entity_array_from_kind(entity_ctx, CTRL_EntityKind_Process); - typedef struct Task Task; - struct Task - { - Task *next; - Task *prev; - CTRL_Entity *process; - }; - Task *first_task = 0; - Task *last_task = 0; - for EachIndex(idx, initial_processes.count) - { - CTRL_Entity *entity = initial_processes.v[idx]; - Task *t = push_array(scratch.arena, Task, 1); - t->process = entity; - DLLPushBack(first_task, last_task, t); - } - - //- rjf: kill processes as needed, wait for all processes to be dead - CTRL_EventCause cause = CTRL_EventCause_Finished; - if(first_task != 0) - { - DMN_RunCtrls run_ctrls = {0}; - for(B32 done = 0; !done;) - { - // rjf: kill remaining processes - for(Task *t = first_task, *next = 0; t != 0; t = next) - { - next = t->next; - B32 kill_worked = dmn_ctrl_kill(ctrl_ctx, t->process->handle.dmn_handle, exit_code); - if(kill_worked) - { - DLLRemove(first_task, last_task, t); - } - } - - // rjf: get next event - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - - // rjf: process event - switch(event->kind) - { - default:{}break; - case DMN_EventKind_CreateProcess: - { - CTRL_Entity *new_process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process)); - Task *t = push_array(scratch.arena, Task, 1); - t->process = new_process; - DLLPushBack(first_task, last_task, t); - }break; - case DMN_EventKind_Error:{done = 1; cause = CTRL_EventCause_Error;}break; - case DMN_EventKind_Halt: {done = 1; cause = CTRL_EventCause_InterruptedByHalt;}break; - } - - // rjf: end if all processes are gone - CTRL_EntityArray processes = ctrl_entity_array_from_kind(entity_ctx, CTRL_EntityKind_Process); - if(processes.count == 0) - { - done = 1; - } - } - } - - //- rjf: record stop - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = cause; - event->msg_id = msg->msg_id; - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__detach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DMN_Handle process = msg->entity.dmn_handle; - - //- rjf: detach - B32 detach_worked = dmn_ctrl_detach(ctrl_ctx, process); - - //- rjf: wait for process to be dead - if(detach_worked) - { - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities_are_processes = 1; - run_ctrls.run_entities = &process; - run_ctrls.run_entity_count = 1; - for(B32 done = 0; done == 0;) - { - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - if(event->kind == DMN_EventKind_ExitProcess && dmn_handle_match(event->process, process)) - { - done = 1; - } - if(event->kind == DMN_EventKind_Halt) - { - done = 1; - } - } - } - - //- rjf: record stop - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = CTRL_EventCause_Finished; - event->msg_id = msg->msg_id; - if(detach_worked) - { - event->entity = msg->entity; - } - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DMN_Event *stop_event = 0; - CTRL_EventCause stop_cause = CTRL_EventCause_Null; - CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - CTRL_Handle target_thread = msg->entity; - CTRL_Handle target_process = msg->parent; - CTRL_Entity *target_process_entity = ctrl_entity_from_handle(entity_ctx, target_process); - U64 spoof_ip_vaddr = 911; - log_infof("ctrl_thread__run:\n{\n"); - - ////////////////////////////// - //- rjf: gather all initial breakpoints - // - DMN_TrapChunkList user_traps = {0}; - { - CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, target_thread); - CTRL_EvalScope *eval_scope = ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, thread); - for(CTRL_Entity *machine = entity_ctx->root->first; - machine != &ctrl_entity_nil; - machine = machine->next) - { - if(machine->kind != CTRL_EntityKind_Machine) { continue; } - for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) - { - if(process->kind != CTRL_EntityKind_Process) { continue; } - - // rjf: resolve module-dependent user bps - for(CTRL_Entity *module = process->first; module != &ctrl_entity_nil; module = module->next) - { - if(module->kind != CTRL_EntityKind_Module) { continue; } - ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, eval_scope, process->handle, module->handle, &msg->user_bps, &user_traps); - } - - // rjf: push process-declared breakpoins - for(CTRL_Entity *bp = process->first; bp != &ctrl_entity_nil; bp = bp->next) - { - if(bp->kind != CTRL_EntityKind_Breakpoint) { continue; } - ctrl_thread__append_program_defined_bp_traps(scratch.arena, bp, &user_traps); - } - - // rjf: push virtual-address user breakpoints per-process - ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, eval_scope, process->handle, &msg->user_bps, &user_traps); - } - } - ctrl_thread__eval_scope_end(eval_scope); - } - - ////////////////////////////// - //- rjf: read initial stack-pointer-check value - // - // This MUST happen before any threads move, including single-stepping stuck - // threads, because otherwise, their stack pointer may change, if single-stepping - // causes e.g. entrance into a function via a call instruction. - // - U64 sp_check_value = dmn_rsp_from_thread(target_thread.dmn_handle); - log_infof("sp_check_value := 0x%I64x\n", sp_check_value); - - ////////////////////////////// - //- rjf: single step "stuck threads" - // - // "Stuck threads" are threads that are already on a User BP and would hit - // it immediately if resumed with all User BPs enabled. To get them "unstuck" - // we just need to single step them to get them off their current instruction. - // - // This only applies to threads OTHER THAN the target thread. If the target - // thread is on a user breakpoint, then we need to let trap net logic run, - // which may include features put on a trap net trap at the same address as - // the user breakpoint. - // - B32 target_thread_is_on_user_bp_and_trap_net_trap = 0; - if(stop_event == 0) - { - // rjf: gather stuck threads - DMN_HandleList stuck_threads = {0}; - for(CTRL_Entity *machine = entity_ctx->root->first; - machine != &ctrl_entity_nil; - machine = machine->next) - { - if(machine->kind != CTRL_EntityKind_Machine) { continue; } - for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) - { - if(process->kind != CTRL_EntityKind_Process) { continue; } - for(CTRL_Entity *thread = process->first; thread != &ctrl_entity_nil; thread = thread->next) - { - if(thread->kind != CTRL_EntityKind_Thread) { continue; } - U64 rip = dmn_rip_from_thread(thread->handle.dmn_handle); - - // rjf: determine if thread is frozen - B32 thread_is_frozen = thread->is_frozen; - - // rjf: not frozen? -> check if stuck & gather if so - if(!thread_is_frozen) - { - for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next) - { - B32 is_on_user_bp = 0; - for(DMN_Trap *trap_ptr = n->v; trap_ptr < n->v+n->count; trap_ptr += 1) - { - if(dmn_handle_match(trap_ptr->process, process->handle.dmn_handle) && trap_ptr->vaddr == rip) - { - is_on_user_bp = 1; - } - } - - B32 is_on_net_trap = 0; - for(CTRL_TrapNode *n = msg->traps.first; n != 0; n = n->next) - { - if(n->v.vaddr == rip) - { - is_on_net_trap = 1; - } - } - - if(is_on_user_bp && (!is_on_net_trap || !dmn_handle_match(thread->handle.dmn_handle, target_thread.dmn_handle))) - { - dmn_handle_list_push(scratch.arena, &stuck_threads, thread->handle.dmn_handle); - } - - if(is_on_user_bp && is_on_net_trap && dmn_handle_match(thread->handle.dmn_handle, target_thread.dmn_handle)) - { - target_thread_is_on_user_bp_and_trap_net_trap = 1; - } - } - } - } - } - } - - // rjf: actually step stuck threads - for(DMN_HandleNode *node = stuck_threads.first; - node != 0; - node = node->next) - { - DMN_Handle thread = node->v; - U64 thread_pre_rip = dmn_rip_from_thread(thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 done = 0; !done;) - { - log_infof("single_step_stuck_thread([0x%I64x])\n", thread.u64[0]); - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities = &thread; - run_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - run_ctrls.single_step_thread = thread; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(thread); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_ExitThread: - if(dmn_handle_match(event->thread, thread)) - { - stop_cause = CTRL_EventCause_Error; - goto stop; - }break; - case DMN_EventKind_Error: stop_cause = CTRL_EventCause_Error; goto stop; - case DMN_EventKind_Exception: stop_cause = CTRL_EventCause_InterruptedByException; goto stop; - case DMN_EventKind_Trap: stop_cause = CTRL_EventCause_InterruptedByTrap; goto stop; - case DMN_EventKind_Halt: stop_cause = CTRL_EventCause_InterruptedByHalt; goto stop; - stop:; - { - stop_event = event; - done = 1; - }break; - case DMN_EventKind_SingleStep: - { - done = dmn_handle_match(node->v, event->thread); - }break; - } - } - } - } - - ////////////////////////////// - //- rjf: gather frozen threads - // - CTRL_EntityList frozen_threads = {0}; - for(CTRL_Entity *machine = entity_ctx->root->first; - machine != &ctrl_entity_nil; - machine = machine->next) - { - if(machine->kind != CTRL_EntityKind_Machine) { continue; } - for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) - { - if(process->kind != CTRL_EntityKind_Process) { continue; } - for(CTRL_Entity *thread = process->first; thread != &ctrl_entity_nil; thread = thread->next) - { - if(thread->is_frozen) - { - ctrl_entity_list_push(scratch.arena, &frozen_threads, thread); - } - } - } - } - - ////////////////////////////// - //- rjf: resolve trap net - // - DMN_TrapChunkList trap_net_traps = {0}; - for(CTRL_TrapNode *node = msg->traps.first; - node != 0; - node = node->next) - { - DMN_Trap trap = {target_process.dmn_handle, node->v.vaddr}; - dmn_trap_chunk_list_push(scratch.arena, &trap_net_traps, 256, &trap); - } - - ////////////////////////////// - //- rjf: join user breakpoints and trap net traps - // - DMN_TrapChunkList joined_traps = {0}; - { - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &user_traps); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &trap_net_traps); - } - - ////////////////////////////// - //- rjf: record start - // - if(stop_event == 0) - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Started; - ctrl_c2u_push_events(&evts); - } - - ////////////////////////////// - //- rjf: run loop - // - if(stop_event == 0) - { - B32 spoof_mode = 0; - CTRL_Spoof spoof = {0}; - DMN_TrapChunkList entry_traps = {0}; - for(U64 run_loop_idx = 0;; run_loop_idx += 1) - { - ////////////////////////// - //- rjf: choose low level traps - // - DMN_TrapChunkList *trap_list = &joined_traps; - if(spoof_mode) - { - trap_list = &user_traps; - } - - ////////////////////////// - //- rjf: choose spoof - // - CTRL_Spoof *run_spoof = 0; - if(spoof_mode) - { - run_spoof = &spoof; - } - - ////////////////////////// - //- rjf: setup run controls - // - DMN_RunCtrls run_ctrls = {0}; - if(run_loop_idx == 0) - { - run_ctrls.priority_thread = target_thread.dmn_handle; - } - run_ctrls.ignore_previous_exception = 1; - run_ctrls.run_entity_count = frozen_threads.count; - run_ctrls.run_entities = push_array(scratch.arena, DMN_Handle, run_ctrls.run_entity_count); - run_ctrls.run_entities_are_unfrozen = 0; - { - U64 idx = 0; - for(CTRL_EntityNode *n = frozen_threads.first; n != 0; n = n->next) - { - run_ctrls.run_entities[idx] = n->v->handle.dmn_handle; - idx += 1; - } - } - run_ctrls.traps = *trap_list; - - ////////////////////////// - //- rjf: get next run-related event - // - log_infof("get_next_event:\n{\n"); - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, run_spoof); - log_infof("}\n\n"); - - ////////////////////////// - //- rjf: determine event handling - // - B32 launch_done_first_module = 0; - B32 hard_stop = 0; - CTRL_EventCause hard_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - B32 use_stepping_logic = 0; - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: - case DMN_EventKind_Halt: - case DMN_EventKind_SingleStep: - case DMN_EventKind_Trap: - { - hard_stop = 1; - log_infof("step_rule: unexpected -> hard_stop\n"); - }break; - case DMN_EventKind_Exception: - case DMN_EventKind_Breakpoint: - { - use_stepping_logic = 1; - log_infof("step_rule: exception/breakpoint -> stepping_logic\n"); - }break; - case DMN_EventKind_CreateProcess: - { - CTRL_EvalScope *eval_scope = ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, &ctrl_entity_nil); - { - DMN_TrapChunkList new_traps = {0}; - ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, eval_scope, ctrl_handle_make(CTRL_MachineID_Local, event->process), &msg->user_bps, &new_traps); - log_infof("step_rule: create_process -> resolve traps\n"); - log_infof("new_traps:\n{\n"); - for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) - { - for(U64 idx = 0; idx < n->count; idx += 1) - { - DMN_Trap *trap = &n->v[idx]; - log_infof("{process:[0x%I64x], vaddr:0x%I64x}\n", trap->process.u64[0], trap->vaddr); - } - } - log_infof("}\n\n"); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); - } - ctrl_thread__eval_scope_end(eval_scope); - }break; - case DMN_EventKind_LoadModule: - { - CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->thread)); - CTRL_EvalScope *eval_scope = ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, thread); - { - DMN_TrapChunkList new_traps = {0}; - ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, eval_scope, ctrl_handle_make(CTRL_MachineID_Local, event->process), ctrl_handle_make(CTRL_MachineID_Local, event->module), &msg->user_bps, &new_traps); - log_infof("step_rule: load_module -> resolve traps\n"); - log_infof("new_traps:\n{\n"); - for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) - { - for(U64 idx = 0; idx < n->count; idx += 1) - { - DMN_Trap *trap = &n->v[idx]; - log_infof("{process:[0x%I64x], vaddr:0x%I64x}\n", trap->process.u64[0], trap->vaddr); - } - } - log_infof("}\n\n"); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); - } - ctrl_thread__eval_scope_end(eval_scope); - }break; - case DMN_EventKind_SetBreakpoint: - { - CTRL_Entity *bp = &ctrl_entity_nil; - { - CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process)); - for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) - { - if(child->kind == CTRL_EntityKind_Breakpoint && - child->vaddr_range.min == event->address && - child->vaddr_range.max == event->address + event->size && - child->bp_flags == ctrl_user_breakpoint_flags_from_dmn_trap_flags(event->flags)) - { - bp = child; - break; - } - } - } - if(bp != &ctrl_entity_nil) - { - DMN_TrapChunkList new_traps = {0}; - ctrl_thread__append_program_defined_bp_traps(scratch.arena, bp, &new_traps); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); - } - }break; - } - - ////////////////////////// - //- rjf: on launches, detect entry points, place traps - // - if(msg->run_flags & CTRL_RunFlag_StopOnEntryPoint && !launch_done_first_module && event->kind == DMN_EventKind_HandshakeComplete) - { - launch_done_first_module = 1; - Access *access = access_open(); - - //- rjf: unpack process/module info - CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process)); - CTRL_Entity *module = ctrl_entity_child_from_kind(process, CTRL_EntityKind_Module); - U64 module_base_vaddr = module->vaddr_range.min; - DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); - RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, max_U64); - RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Procedures); - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, unparsed_map, &map); - - //- rjf: add traps for user-specified entry points on this message, if specified - B32 entries_found = 0; - if(!entries_found) - { - for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) - { - U32 procedure_id = 0; - { - String8 name = n->string; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - - //- rjf: add traps for module-baked entry points, if specified - if(!entries_found) - { - String8 raddbg_data = ctrl_raddbg_data_from_module(scratch.arena, module->handle); - U8 split_char = 0; - String8List raddbg_data_text_parts = str8_split(scratch.arena, raddbg_data, &split_char, 1, 0); - for(String8Node *text_n = raddbg_data_text_parts.first; text_n != 0; text_n = text_n->next) - { - String8 text = text_n->string; - MD_Node *root = md_tree_from_string(scratch.arena, text); - if(str8_match(root->first->string, str8_lit("entry_point"), 0)) - { - String8 name = root->first->first->string; - U32 procedure_id = 0; - { - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - } - - //- rjf: add traps for PID-correllated entry points - if(!entries_found) - { - for(CTRL_Entity *e = entity_ctx->root->first; e != &ctrl_entity_nil; e = e->next) - { - if(e->id == process->id) - { - U32 procedure_id = 0; - { - String8 name = e->string; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - } - - //- rjf: add traps for all custom user entry points - if(!entries_found) - { - for(String8Node *n = ctrl_state->user_entry_points.first; n != 0; n = n->next) - { - U32 procedure_id = 0; - { - String8 name = n->string; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - break; - } - } - } - - //- rjf: add traps for all high-level entry points - if(!entries_found) - { - String8 hi_entry_points[] = - { - str8_lit("WinMain"), - str8_lit("wWinMain"), - str8_lit("main"), - str8_lit("wmain"), - }; - for(U64 idx = 0; idx < ArrayCount(hi_entry_points); idx += 1) - { - U32 procedure_id = 0; - { - String8 name = hi_entry_points[idx]; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - - //- rjf: add trap for PE header entry - if(!entries_found) - { - U64 voff = ctrl_entry_point_voff_from_module(module->handle); - if(voff != 0) - { - DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - - //- rjf: add traps for all low-level entry points - if(!entries_found) - { - String8 lo_entry_points[] = - { - str8_lit("WinMainCRTStartup"), - str8_lit("wWinMainCRTStartup"), - str8_lit("mainCRTStartup"), - str8_lit("wmainCRTStartup"), - }; - for(U64 idx = 0; idx < ArrayCount(lo_entry_points); idx += 1) - { - U32 procedure_id = 0; - { - String8 name = lo_entry_points[idx]; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - - //- rjf: no entry point found -> done - if(entry_traps.trap_count == 0) - { - hard_stop = 1; - } - - //- rjf: found entry points -> add to joined traps - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &entry_traps); - - access_close(access); - } - - ////////////////////////// - //- rjf: unpack info about thread attached to event - // - CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->thread)); - CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process)); - Arch arch = thread->arch; - U64 thread_rip_vaddr = dmn_rip_from_thread(event->thread); - CTRL_Entity *module = ctrl_module_from_process_vaddr(process, thread_rip_vaddr); - - ////////////////////////// - //- rjf: extract module-dependent info - // - U64 thread_rip_voff = thread_rip_vaddr - module->vaddr_range.min; - - ////////////////////////// - //- rjf: stepping logic - // - //{ - - ////////////////////////// - //- rjf: handle if hitting a spoof - // - B32 exception_stop = 0; - B32 hit_spoof = 0; - if(!hard_stop && use_stepping_logic && event->kind == DMN_EventKind_Exception) - { - if(spoof_mode && - dmn_handle_match(target_process.dmn_handle, event->process) && - dmn_handle_match(target_thread.dmn_handle, event->thread) && - spoof.new_ip_value == event->address) - { - hit_spoof = 1; - log_infof("hit_spoof\n"); - } - else - { - exception_stop = 1; - use_stepping_logic = 0; - } - } - - //- rjf: handle spoof hit - if(hit_spoof) - { - log_infof("exit_spoof_mode\n"); - - // rjf: clear spoof mode - spoof_mode = 0; - MemoryZeroStruct(&spoof); - - // rjf: skip remainder of handling - use_stepping_logic = 0; - } - - //- rjf: for breakpoint events, gather bp info - B32 hit_entry = 0; - B32 hit_user_bp = 0; - B32 hit_trap_net_bp = 0; - B32 hit_conditional_bp_but_filtered = 0; - CTRL_TrapFlags hit_trap_flags = 0; - if(!hard_stop && use_stepping_logic && event->kind == DMN_EventKind_Breakpoint) - ProfScope("for breakpoint events, gather bp info") - { - Temp temp = temp_begin(scratch.arena); - String8List conditions = {0}; - - // rjf: entry breakpoints - for(DMN_TrapChunkNode *n = entry_traps.first; n != 0; n = n->next) - { - DMN_Trap *trap = n->v; - DMN_Trap *opl = n->v + n->count; - for(;trap < opl; trap += 1) - { - if(dmn_handle_match(trap->process, event->process) && trap->vaddr == event->instruction_pointer) - { - hit_entry = 1; - } - } - } - - // rjf: user breakpoints - { - if(event->user_data != 0) - { - hit_user_bp = 1; - } - for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next) - { - DMN_Trap *trap = n->v; - DMN_Trap *opl = n->v + n->count; - for(;trap < opl; trap += 1) - { - if(dmn_handle_match(trap->process, event->process) && - trap->vaddr == event->instruction_pointer && - (!dmn_handle_match(event->thread, target_thread.dmn_handle) || !target_thread_is_on_user_bp_and_trap_net_trap)) - { - CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)trap->id; - hit_user_bp = 1; - if(user_bp != 0 && !(trap->id & bit64) && user_bp->condition.size != 0) - { - str8_list_push(temp.arena, &conditions, user_bp->condition); - } - } - } - } - } - - // rjf: programmatic user breakpoints (we do not have state for it, - // but the target program(s) did something breakpoint-like, and we - // want to treat it as if we did) - if(event->address != 0) - { - hit_user_bp = 1; - } - - // rjf: evaluate hit stop conditions - if(conditions.node_count != 0) ProfScope("evaluate hit stop conditions") - { - CTRL_EvalScope *eval_scope = ctrl_thread__eval_scope_begin(temp.arena, &msg->user_bps, thread); - for(String8Node *condition_n = conditions.first; condition_n != 0; condition_n = condition_n->next) - { - // rjf: evaluate - E_Eval eval = zero_struct; - ProfScope("evaluate expression") - { - eval = e_eval_from_string(condition_n->string); - } - - // rjf: interpret evaluation - if(eval.code == E_InterpretationCode_Good && eval.value.u64 == 0) - { - hit_user_bp = 0; - hit_conditional_bp_but_filtered = 1; - log_infof("conditional_breakpoint_hit: 'condition eval'd to 0, and so filtered'\n"); - } - else - { - hit_user_bp = 1; - hit_conditional_bp_but_filtered = 0; - log_infof("conditional_breakpoint_hit: 'conditional eval'd to nonzero, hit'\n"); - break; - } - } - ctrl_thread__eval_scope_end(eval_scope); - } - - // rjf: gather trap net hits - ProfScope("gather trap net hits") - { - if(!hit_user_bp && dmn_handle_match(event->process, target_process.dmn_handle)) - { - for(CTRL_TrapNode *node = msg->traps.first; - node != 0; - node = node->next) - { - if(node->v.vaddr == event->instruction_pointer) - { - hit_trap_net_bp = 1; - hit_trap_flags |= node->v.flags; - } - } - } - } - - log_infof("user_breakpoint_hit: %i\n", hit_user_bp); - log_infof("entry_point_hit: %i\n", hit_entry); - temp_end(temp); - } - - //- rjf: hit conditional user bp but filtered -> single step - B32 cond_bp_single_step_stop = 0; - CTRL_EventCause cond_bp_single_step_stop_cause = CTRL_EventCause_Null; - if(hit_conditional_bp_but_filtered) LogInfoNamedBlockF("conditional_bp_hit_single_step") - { - DMN_Handle thread = event->thread; - U64 thread_pre_rip = dmn_rip_from_thread(thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 single_step_done = 0; !single_step_done;) - { - DMN_RunCtrls single_step_ctrls = {0}; - single_step_ctrls.run_entities_are_unfrozen = 1; - single_step_ctrls.run_entities = &thread; - single_step_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - single_step_ctrls.single_step_thread = thread; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(thread); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: - case DMN_EventKind_Exception: - case DMN_EventKind_Halt: - case DMN_EventKind_Trap: - { - cond_bp_single_step_stop = 1; - single_step_done = 1; - use_stepping_logic = 0; - cond_bp_single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - case DMN_EventKind_SingleStep: - { - single_step_done = dmn_handle_match(event->thread, thread); - cond_bp_single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - } - } - } - - //- rjf: hit entry points on *any thread* cause a stop, if this msg says as such - B32 entry_stop = 0; - if(msg->run_flags & CTRL_RunFlag_StopOnEntryPoint && hit_entry) - { - entry_stop = 1; - use_stepping_logic = 0; - } - - //- rjf: user breakpoints on *any thread* cause a stop - B32 user_bp_stop = 0; - if(!hard_stop && use_stepping_logic && hit_user_bp) - { - user_bp_stop = 1; - use_stepping_logic = 0; - } - - //- rjf: trap net on off-target threads are ignored - B32 step_past_trap_net = 0; - if(!hard_stop && use_stepping_logic && hit_trap_net_bp) - { - if(!dmn_handle_match(event->thread, target_thread.dmn_handle)) - { - step_past_trap_net = 1; - use_stepping_logic = 0; - } - } - - //- rjf: trap net on on-target threads trigger trap net logic - B32 use_trap_net_logic = 0; - if(!hard_stop && use_stepping_logic && hit_trap_net_bp) - { - if(dmn_handle_match(event->thread, target_thread.dmn_handle)) - { - use_trap_net_logic = 1; - } - } - - //- rjf: trap net logic: stack pointer check - B32 stack_pointer_matches = 0; - if(use_trap_net_logic) - { - U64 sp = dmn_rsp_from_thread(target_thread.dmn_handle); - stack_pointer_matches = (sp == sp_check_value); - } - - //- rjf: trap net logic: single step after hit - B32 single_step_stop = 0; - CTRL_EventCause single_step_stop_cause = CTRL_EventCause_Null; - if(!hard_stop && use_trap_net_logic) - { - if(hit_trap_flags & CTRL_TrapFlag_SingleStepAfterHit) LogInfoNamedBlockF("trap_net__single_step_after_hit") - { - U64 thread_pre_rip = dmn_rip_from_thread(target_thread.dmn_handle); - U64 thread_post_rip = thread_pre_rip; - for(B32 single_step_done = 0; single_step_done == 0;) - { - DMN_RunCtrls single_step_ctrls = {0}; - single_step_ctrls.run_entities_are_unfrozen = 1; - single_step_ctrls.run_entities = &target_thread.dmn_handle; - single_step_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - single_step_ctrls.single_step_thread = target_thread.dmn_handle; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(target_thread.dmn_handle); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: - case DMN_EventKind_Exception: - case DMN_EventKind_Halt: - case DMN_EventKind_Trap: - { - single_step_stop = 1; - single_step_done = 1; - use_stepping_logic = 0; - use_trap_net_logic = 0; - single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - case DMN_EventKind_SingleStep: - { - single_step_done = dmn_handle_match(event->thread, target_thread.dmn_handle); - single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - } - } - } - } - - //- rjf: trap net logic: begin spoof mode - B32 begin_spoof_mode = 0; - if(!hard_stop && use_trap_net_logic) - { - if(hit_trap_flags & CTRL_TrapFlag_BeginSpoofMode) LogInfoNamedBlockF("trap_net__begin_spoof_mode") - { - // rjf: setup spoof mode - begin_spoof_mode = 1; - U64 spoof_sp = dmn_rsp_from_thread(target_thread.dmn_handle); - spoof_mode = 1; - spoof.process = target_process.dmn_handle; - spoof.thread = target_thread.dmn_handle; - spoof.vaddr = spoof_sp; - spoof.new_ip_value = spoof_ip_vaddr; - log_infof("spoof:{process:[0x%I64x], thread:[0x%I64x], vaddr:0x%I64x, new_ip_value:0x%I64x}\n", spoof.process.u64[0], spoof.thread.u64[0], spoof.vaddr, spoof.new_ip_value); - } - } - - //- rjf: trap net logic: save stack pointer - B32 save_stack_pointer = 0; - if(!hard_stop && use_trap_net_logic) - { - if(hit_trap_flags & CTRL_TrapFlag_SaveStackPointer) - { - if(stack_pointer_matches) LogInfoNamedBlockF("trap_net__save_sp") - { - save_stack_pointer = 1; - sp_check_value = dmn_rsp_from_thread(target_thread.dmn_handle); - log_infof("sp_check_value = 0x%I64x\n", sp_check_value); - } - } - } - - //- rjf: trap net logic: end stepping - B32 trap_net_stop = 0; - if(!hard_stop && use_trap_net_logic) - { - if(hit_trap_flags & CTRL_TrapFlag_EndStepping) LogInfoNamedBlockF("trap_net__end_step") - { - if((hit_trap_flags & CTRL_TrapFlag_IgnoreStackPointerCheck) || - stack_pointer_matches) - { - trap_net_stop = 1; - use_trap_net_logic = 0; - } - } - } - - //} - // - //- rjf: stepping logic - //////////////////////////////// - - //- rjf: handle step past trap net - B32 step_past_trap_net_stop = 0; - CTRL_EventCause step_past_trap_net_stop_cause = CTRL_EventCause_Null; - if(step_past_trap_net) LogInfoNamedBlockF("trap_net__single_step_past_trap_net") - { - DMN_Handle thread = event->thread; - U64 thread_pre_rip = dmn_rip_from_thread(thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 single_step_done = 0; single_step_done == 0;) - { - DMN_RunCtrls single_step_ctrls = {0}; - single_step_ctrls.run_entities_are_unfrozen = 1; - single_step_ctrls.run_entities = &thread; - single_step_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - single_step_ctrls.single_step_thread = thread; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(thread); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: - case DMN_EventKind_Exception: - case DMN_EventKind_Halt: - case DMN_EventKind_Trap: - { - step_past_trap_net_stop = 1; - single_step_done = 1; - step_past_trap_net_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - case DMN_EventKind_SingleStep: - { - single_step_done = dmn_handle_match(event->thread, thread); - step_past_trap_net_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - } - } - } - - //- rjf: loop exit condition - CTRL_EventCause stage_stop_cause = CTRL_EventCause_Null; - if(hard_stop) - { - stage_stop_cause = hard_stop_cause; - } - else if(cond_bp_single_step_stop) - { - stage_stop_cause = cond_bp_single_step_stop_cause; - } - else if(single_step_stop) - { - stage_stop_cause = single_step_stop_cause; - } - else if(step_past_trap_net_stop) - { - stage_stop_cause = step_past_trap_net_stop_cause; - } - else if(exception_stop) - { - stage_stop_cause = CTRL_EventCause_InterruptedByException; - } - else if(user_bp_stop) - { - stage_stop_cause = CTRL_EventCause_UserBreakpoint; - } - else if(entry_stop) - { - stage_stop_cause = CTRL_EventCause_EntryPoint; - } - else if(trap_net_stop) - { - stage_stop_cause = CTRL_EventCause_Finished; - } - log_infof("stop_cause: %i\n", stage_stop_cause); - if(stage_stop_cause != CTRL_EventCause_Null) - { - stop_event = event; - stop_cause = stage_stop_cause; - break; - } - } - } - - ////////////////////////////// - //- rjf: record stop - // - if(stop_event != 0) - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = stop_cause; - event->entity = ctrl_handle_make(CTRL_MachineID_Local, stop_event->thread); - event->parent = ctrl_handle_make(CTRL_MachineID_Local, stop_event->process); - event->exception_code = stop_event->code; - event->exception_kind = ctrl_exception_kind_from_dmn(stop_event->exception_kind); - event->vaddr_rng = r1u64(stop_event->address, stop_event->address); - event->rip_vaddr = stop_event->instruction_pointer; - if(stop_cause == CTRL_EventCause_UserBreakpoint && stop_event->user_data != 0) - { - if(!(stop_event->user_data & bit64)) - { - CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)stop_event->user_data; - event->u64_code = user_bp->id; - } - } - ctrl_c2u_push_events(&evts); - } - - log_infof("}\n\n"); - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - //- rjf: record start - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Started; - ctrl_c2u_push_events(&evts); - } - - //- rjf: single step - DMN_Handle thread = msg->entity.dmn_handle; - B32 thread_is_valid = !dmn_handle_match(thread, dmn_handle_zero()); - DMN_Event *stop_event = 0; - CTRL_EventCause stop_cause = CTRL_EventCause_Null; - if(thread_is_valid) - { - U64 thread_pre_rip = dmn_rip_from_thread(thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 done = 0; done == 0;) - { - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities = &thread; - run_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - run_ctrls.single_step_thread = msg->entity.dmn_handle; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(msg->entity.dmn_handle); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: {stop_cause = CTRL_EventCause_Error;}goto end_single_step; - case DMN_EventKind_Exception: {stop_cause = CTRL_EventCause_InterruptedByException;}goto end_single_step; - case DMN_EventKind_Halt: {stop_cause = CTRL_EventCause_InterruptedByHalt;}goto end_single_step; - case DMN_EventKind_Trap: {stop_cause = CTRL_EventCause_InterruptedByTrap;}goto end_single_step; - case DMN_EventKind_Breakpoint: {stop_cause = CTRL_EventCause_UserBreakpoint;}goto end_single_step; - case DMN_EventKind_SingleStep: {stop_cause = CTRL_EventCause_Finished;}goto end_single_step; - end_single_step: - { - stop_event = event; - done = 1; - }break; - } - } - } - - //- rjf: record stop - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = stop_cause; - if(stop_event != 0) - { - event->entity = ctrl_handle_make(CTRL_MachineID_Local, stop_event->thread); - event->parent = ctrl_handle_make(CTRL_MachineID_Local, stop_event->process); - event->exception_code = stop_event->code; - event->exception_kind = ctrl_exception_kind_from_dmn(stop_event->exception_kind); - event->vaddr_rng = r1u64(stop_event->address, stop_event->address); - event->rip_vaddr = stop_event->instruction_pointer; - } - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Process Memory Artifact Cache Hooks / Lookups - -internal AC_Artifact -ctrl_memory_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) -{ - AC_Artifact artifact = {0}; - { - //- rjf: unpack key - CTRL_Handle process = {0}; - Rng1U64 vaddr_range = {0}; - B32 zero_terminated = 0; - { - U64 key_read_off = 0; - key_read_off += str8_deserial_read_struct(key, key_read_off, &process); - key_read_off += str8_deserial_read_struct(key, key_read_off, &vaddr_range); - key_read_off += str8_deserial_read_struct(key, key_read_off, &zero_terminated); - } - - //- rjf: clamp vaddr range - Rng1U64 vaddr_range_clamped = vaddr_range; - { - vaddr_range_clamped.max = Max(vaddr_range_clamped.max, vaddr_range_clamped.min); - U64 max_size_cap = Min(max_U64-vaddr_range_clamped.min, GB(1)); - vaddr_range_clamped.max = Min(vaddr_range_clamped.max, vaddr_range_clamped.min+max_size_cap); - } - - //- rjf: do read - U64 range_size = dim_1u64(vaddr_range_clamped); - Arena *range_arena = 0; - void *range_base = 0; - U64 zero_terminated_size = 0; - U64 pre_read_mem_gen = ctrl_mem_gen(); - B32 pre_run_state = ins_atomic_u64_eval(&ctrl_state->ctrl_thread_run_state); - if(range_size != 0) - { - // rjf: set up arena - U64 page_size = os_get_system_info()->page_size; // TODO(rjf): @page_size_from_process - U64 arena_size = AlignPow2(range_size + ARENA_HEADER_SIZE, page_size); - range_arena = arena_alloc(.reserve_size = range_size+ARENA_HEADER_SIZE, .commit_size = range_size+ARENA_HEADER_SIZE); - - // rjf: if we got an arena -> push buffer & read - if(range_arena != 0) - { - // rjf: read as much as possible - range_base = push_array_no_zero(range_arena, U8, range_size); - U64 bytes_read = 0; - U64 retry_count = 0; - U64 retry_limit = range_size > page_size ? 64 : 0; - for(Rng1U64 vaddr_range_clamped_retry = vaddr_range_clamped; - retry_count <= retry_limit; - retry_count += 1) - { - bytes_read = dmn_process_read(process.dmn_handle, vaddr_range_clamped_retry, range_base); - if(bytes_read == 0 && vaddr_range_clamped_retry.max > vaddr_range_clamped_retry.min) - { - U64 diff = (vaddr_range_clamped_retry.max-vaddr_range_clamped_retry.min)/2; - vaddr_range_clamped_retry.max -= diff; - vaddr_range_clamped_retry.max = AlignDownPow2(vaddr_range_clamped_retry.max, page_size); - if(diff == 0) - { - break; - } - } - else - { - break; - } - } - - // rjf: if we read nothing, release arena - if(bytes_read == 0) - { - arena_release(range_arena); - range_base = 0; - range_arena = 0; - } - - // rjf: if we only got a partial read, zero the rest - else if(bytes_read < range_size) - { - MemoryZero((U8 *)range_base + bytes_read, range_size-bytes_read); - } - - // rjf: determine final size; zero terminate if needed; pop any unneeded bytes if zero-terminating - zero_terminated_size = bytes_read; - if(zero_terminated && range_base != 0) - { - for(U64 idx = 0; idx < bytes_read; idx += 1) - { - if(((U8 *)range_base)[idx] == 0) - { - zero_terminated_size = idx; - break; - } - } - U64 bytes_overkill = (bytes_read - zero_terminated_size); - arena_pop(range_arena, bytes_overkill); - } - } - } - U64 post_read_mem_gen = ctrl_mem_gen(); - B32 post_run_state = ins_atomic_u64_eval(&ctrl_state->ctrl_thread_run_state); - - //- rjf: form content key - C_Key content_key = {0}; - { - content_key.id.u128[0] = u128_hash_from_str8(key); - } - - //- rjf: determine if we have any history for this key - B32 key_has_history = 0; - { - U128 last_hash = c_hash_from_key(content_key, 0); - if(!u128_match(last_hash, u128_zero())) - { - key_has_history = 1; - } - } - - //- rjf: read successful, OR we have no history -> submit to hash store - U128 hash = {0}; - if((zero_terminated_size > 0 || !key_has_history) && range_base != 0 && range_size != 0 && pre_read_mem_gen == post_read_mem_gen) - { - hash = c_submit_data(content_key, &range_arena, str8((U8 *)range_base, zero_terminated_size)); - gen_out[0] = pre_read_mem_gen; - } - - //- rjf: wakeup on new submissions - if(!u128_match(u128_zero(), hash)) - { - if(ctrl_state->wakeup_hook != 0) - { - ctrl_state->wakeup_hook(); - } - } - - //- rjf: always release leftover arenas - if(range_arena != 0) - { - arena_release(range_arena); - } - - //- rjf: retry on mem gen "tearing", and if the range is non-empty - if(pre_read_mem_gen != post_read_mem_gen && range_size != 0) - { - retry_out[0] = 1; - } - - //- rjf: bundle content key as artifact - StaticAssert(sizeof(content_key) == sizeof(artifact), artifact_key_size_check); - MemoryCopyStruct(&artifact, &content_key); - } - return artifact; -} - -internal void -ctrl_memory_artifact_destroy(AC_Artifact artifact) -{ - C_Key key = {0}; - MemoryCopyStruct(&key, &artifact); - c_close_key(key); -} - -internal C_Key -ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, B32 wait_for_fresh, U64 endt_us, B32 *out_is_stale) -{ - ProfBeginFunction(); -#pragma pack(push, 1) - struct - { - CTRL_Handle process; - Rng1U64 vaddr_range; - B32 zero_terminated; - } key_data = {process, vaddr_range, zero_terminated}; -#pragma pack(pop) - String8 key = str8_struct(&key_data); - Access *access = access_open(); - AC_Artifact artifact = ac_artifact_from_key(access, key, ctrl_memory_artifact_create, ctrl_memory_artifact_destroy, endt_us, - .flags = AC_Flag_HighPriority | (wait_for_fresh ? AC_Flag_WaitForFresh : 0), - .gen = ctrl_mem_gen(), - .slots_count = 2048, - .stale_out = out_is_stale, - .evict_threshold_us = 10000000); - C_Key content_key = {0}; - MemoryCopyStruct(&content_key, &artifact); - access_close(access); - ProfEnd(); - return content_key; -} - -//- rjf: process memory reading helpers - -internal CTRL_ProcessMemorySlice -ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, B32 wait_for_fresh, U64 endt_us) -{ - ProfBeginFunction(); - CTRL_ProcessMemorySlice result = {0}; - if(range.max > range.min && - dim_1u64(range) <= MB(256) && - range.min <= 0x000FFFFFFFFFFFFFull && - range.max <= 0x000FFFFFFFFFFFFFull) - { - Temp scratch = scratch_begin(&arena, 1); - Access *access = access_open(); - - //- rjf: unpack address range, prepare per-touched-page info - U64 page_size = KB(4); - Rng1U64 page_range = r1u64(AlignDownPow2(range.min, page_size), AlignPow2(range.max, page_size)); - U64 page_count = dim_1u64(page_range)/page_size; - U128 *page_hashes = push_array(scratch.arena, U128, page_count); - U128 *page_last_hashes = push_array(scratch.arena, U128, page_count); - - //- rjf: gather hashes & last-hashes for each page - ProfScope("gather hashes & last-hashes for each page") - { - for(U64 page_idx = 0; page_idx < page_count; page_idx += 1) - { - U64 page_base_vaddr = page_range.min + page_idx*page_size; - B32 page_is_stale = 0; - C_Key page_key = ctrl_key_from_process_vaddr_range(process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0, wait_for_fresh, endt_us, &page_is_stale); - U128 page_hash = c_hash_from_key(page_key, 0); - U128 page_last_hash = c_hash_from_key(page_key, 1); - result.stale = (result.stale || page_is_stale); - page_hashes[page_idx] = page_hash; - page_last_hashes[page_idx] = page_last_hash; - } - } - - //- rjf: setup output buffers - void *read_out = push_array(arena, U8, dim_1u64(range)); - U64 *byte_bad_flags = push_array(arena, U64, (dim_1u64(range)+63)/64); - U64 *byte_changed_flags = push_array(arena, U64, (dim_1u64(range)+63)/64); - - //- rjf: iterate pages, fill output - ProfScope("iterate pages, fill output") - { - U64 write_off = 0; - for(U64 page_idx = 0; page_idx < page_count; page_idx += 1) - { - // rjf: read data for this page - String8 data = c_data_from_hash(access, page_hashes[page_idx]); - Rng1U64 data_vaddr_range = r1u64(page_range.min + page_idx*page_size, page_range.min + page_idx*page_size+data.size); - - // rjf: skip/chop bytes which are irrelevant for the actual requested read - String8 in_range_data = data; - if(page_idx == page_count-1 && data_vaddr_range.max > range.max) - { - in_range_data = str8_chop(in_range_data, data_vaddr_range.max-range.max); - } - if(page_idx == 0 && range.min > data_vaddr_range.min) - { - in_range_data = str8_skip(in_range_data, range.min-data_vaddr_range.min); - } - - // rjf: write this chunk - MemoryCopy((U8*)read_out+write_off, in_range_data.str, in_range_data.size); - - // rjf; if this page's data doesn't fill the entire range, mark - // missing bytes as bad - if(data.size < page_size) ProfScope("mark missing bytes as bad") - { - Rng1U64 invalid_range = r1u64(data_vaddr_range.min+data.size, data_vaddr_range.min + page_size); - Rng1U64 in_range_invalid_range = intersect_1u64(invalid_range, range); - for(U64 invalid_vaddr = in_range_invalid_range.min; - invalid_vaddr < in_range_invalid_range.max; - invalid_vaddr += 1) - { - U64 idx_in_range = invalid_vaddr - range.min; - byte_bad_flags[idx_in_range/64] |= (1ull<<(idx_in_range%64)); - } - } - - // rjf: if this page's hash & last_hash don't match, diff each byte & - // fill out changed flags - if(!u128_match(page_hashes[page_idx], page_last_hashes[page_idx])) ProfScope("hashes don't match; diff each byte") - { - String8 last_data = c_data_from_hash(access, page_last_hashes[page_idx]); - String8 in_range_last_data = last_data; - if(page_idx == page_count-1 && data_vaddr_range.max > range.max) - { - in_range_last_data = str8_chop(in_range_last_data, data_vaddr_range.max-range.max); - } - if(page_idx == 0 && range.min > data_vaddr_range.min) - { - in_range_last_data = str8_skip(in_range_last_data, range.min-data_vaddr_range.min); - } - for(U64 idx = 0; idx < in_range_data.size; idx += 1) - { - U8 last_byte = idx < in_range_last_data.size ? in_range_last_data.str[idx] : 0; - U8 now_byte = idx < in_range_data.size ? in_range_data.str[idx] : 0; - if(last_byte != now_byte) - { - U64 idx_in_read_out = write_off+idx; - byte_changed_flags[idx_in_read_out/64] |= (1ull<<(idx_in_read_out%64)); - } - } - } - - // rjf: increment past this chunk - U64 bytes_to_skip = page_size; - if(page_idx == 0 && range.min > data_vaddr_range.min) - { - bytes_to_skip -= (range.min-data_vaddr_range.min); - } - write_off += bytes_to_skip; - } - } - - //- rjf: fill result - result.data.str = (U8*)read_out; - result.data.size = dim_1u64(range); - result.byte_bad_flags = byte_bad_flags; - result.byte_changed_flags = byte_changed_flags; - if(byte_bad_flags != 0) - { - for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1) - { - result.any_byte_bad = result.any_byte_bad || !!result.byte_bad_flags[idx]; - } - } - if(byte_changed_flags != 0) - { - for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1) - { - result.any_byte_changed = result.any_byte_changed || !!result.byte_changed_flags[idx]; - } - } - - access_close(access); - scratch_end(scratch); - } - ProfEnd(); - return result; -} - -internal B32 -ctrl_process_memory_read(CTRL_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us) -{ - Temp scratch = scratch_begin(0, 0); - U64 needed_size = dim_1u64(range); - CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process, range, 0, endt_us); - B32 good = (slice.data.size >= needed_size && !slice.any_byte_bad); - if(good) - { - MemoryCopy(out, slice.data.str, needed_size); - } - if(slice.stale && is_stale_out) - { - *is_stale_out = 1; - } - scratch_end(scratch); - return good; -} - -//- rjf: process memory writing - -internal B32 -ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src) -{ - ProfBeginFunction(); - B32 result = dmn_process_write(process.dmn_handle, range, src); - - //- rjf: success -> bump generation - if(result) - { - ins_atomic_u64_inc_eval(&ctrl_state->mem_gen); - } - - //- rjf: success -> wait for cache updates, for small regions - prefer relatively seamless - // writes within calling frame's "view" of the memory, at the expense of a small amount of - // time. - if(result) - { - U64 endt_us = os_now_microseconds()+10000; - U64 page_size = os_get_system_info()->page_size; // TODO(rjf): @page_size_from_process - Rng1U64 page_range = r1u64(range.min/page_size, (range.max+page_size-1)/page_size); - for EachInRange(page_idx, page_range) - { - Temp scratch = scratch_begin(0, 0); - CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process, r1u64(page_idx*page_size, (page_idx+1)*page_size), 1, endt_us); - scratch_end(scratch); - if(!slice.stale || os_now_microseconds() >= endt_us) - { - break; - } - } - } - - ProfEnd(); - return result; -} - -//////////////////////////////// -//~ rjf: Call Stack Artifact Cache Hooks / Lookups - -internal AC_Artifact -ctrl_call_stack_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) -{ - AC_Artifact artifact = {0}; - { - Temp scratch = scratch_begin(0, 0); - - //- rjf: unpack key - CTRL_Handle thread_handle = {0}; - str8_deserial_read_struct(key, 0, &thread_handle); - - //- rjf: produce mini entity context for just this call stack build - CTRL_EntityCtx *entity_ctx = push_array(scratch.arena, CTRL_EntityCtx, 1); - MutexScopeR(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) - { - CTRL_EntityCtx *src_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - CTRL_EntityCtx *dst_ctx = entity_ctx; - { - dst_ctx->root = &ctrl_entity_nil; - dst_ctx->hash_slots_count = 1024; - dst_ctx->hash_slots = push_array(scratch.arena, CTRL_EntityHashSlot, dst_ctx->hash_slots_count); - MemoryCopyArray(dst_ctx->entity_kind_counts, src_ctx->entity_kind_counts); - MemoryCopyArray(dst_ctx->entity_kind_alloc_gens, src_ctx->entity_kind_alloc_gens); - } - CTRL_Entity *src_thread = ctrl_entity_from_handle(src_ctx, thread_handle); - CTRL_Entity *src_process = ctrl_process_from_entity(src_thread); - { - CTRL_EntityRec rec = {0}; - CTRL_Entity *dst_parent = &ctrl_entity_nil; - for(CTRL_Entity *src_e = src_process; src_e != &ctrl_entity_nil; src_e = rec.next) - { - rec = ctrl_entity_rec_depth_first_pre(src_e, src_process); - - // rjf: determine if we need this entity - B32 need_this_entity = (ctrl_handle_match(thread_handle, src_e->handle) || src_e->kind == CTRL_EntityKind_Module || src_e->kind == CTRL_EntityKind_Process || src_e->kind == CTRL_EntityKind_DebugInfoPath); - - // rjf: copy this entity - CTRL_Entity *dst_e = &ctrl_entity_nil; - if(need_this_entity) - { - dst_e = push_array(scratch.arena, CTRL_Entity, 1); - { - dst_e->first = dst_e->last = dst_e->next = dst_e->prev = &ctrl_entity_nil; - dst_e->parent = dst_parent; - dst_e->kind = src_e->kind; - dst_e->arch = src_e->arch; - dst_e->is_frozen = src_e->is_frozen; - dst_e->is_soloed = src_e->is_soloed; - dst_e->rgba = src_e->rgba; - dst_e->handle = src_e->handle; - dst_e->id = src_e->id; - dst_e->vaddr_range = src_e->vaddr_range; - dst_e->stack_base = src_e->stack_base; - dst_e->timestamp = src_e->timestamp; - dst_e->bp_flags = src_e->bp_flags; - dst_e->string = push_str8_copy(scratch.arena, src_e->string); - dst_e->tls_index = src_e->tls_index; - dst_e->tls_offset = src_e->tls_offset; - dst_e->target_os = src_e->target_os; - dst_e->tls_model = src_e->tls_model; - } - if(dst_parent == &ctrl_entity_nil) - { - dst_ctx->root = dst_e; - } - else - { - DLLPushBack_NPZ(&ctrl_entity_nil, dst_parent->first, dst_parent->last, dst_e, next, prev); - } - } - - // rjf: insert into hash map - if(dst_e != &ctrl_entity_nil) - { - U64 hash = ctrl_hash_from_handle(dst_e->handle); - U64 slot_idx = hash%dst_ctx->hash_slots_count; - CTRL_EntityHashSlot *slot = &dst_ctx->hash_slots[slot_idx]; - CTRL_EntityHashNode *node = 0; - for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) - { - if(ctrl_handle_match(n->entity->handle, dst_e->handle)) - { - node = n; - break; - } - } - if(node == 0) - { - node = push_array(scratch.arena, CTRL_EntityHashNode, 1); - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - node->entity = dst_e; - } - } - - // rjf: push/pop - if(rec.push_count) - { - dst_parent = dst_e; - } - else for(S32 pop_idx = 0; pop_idx < rec.pop_count; pop_idx += 1) - { - dst_parent = dst_parent->parent; - } - } - } - } - - //- rjf: compute call stack - CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, thread_handle); - B32 good = 1; - B32 retry = 0; - Arena *arena = 0; - CTRL_CallStack *call_stack = 0; - if(thread != &ctrl_entity_nil) - { - good = 0; - arena = arena_alloc(); - call_stack = push_array(arena, CTRL_CallStack, 1); - CTRL_Entity *process = ctrl_process_from_entity(thread); - U64 pre_reg_gen = 0; - U64 post_reg_gen = 0; - U64 pre_mem_gen = 0; - U64 post_mem_gen = 0; - CTRL_Unwind unwind = {0}; - { - pre_reg_gen = ctrl_reg_gen(); - pre_mem_gen = ctrl_mem_gen(); - unwind = ctrl_unwind_from_thread(arena, entity_ctx, thread_handle, os_now_microseconds()+5000); - if(!(unwind.flags & CTRL_UnwindFlag_Stale)) - { - good = 1; - call_stack[0] = ctrl_call_stack_from_unwind(arena, process, &unwind); - } - if(unwind.flags & CTRL_UnwindFlag_Stale) - { - retry = 1; - } - post_reg_gen = ctrl_reg_gen(); - post_mem_gen = ctrl_mem_gen(); - } - if(pre_reg_gen != post_reg_gen || pre_mem_gen != post_mem_gen) - { - good = 0; - retry = 1; - } - if(!good) - { - arena_release(arena); - } - } - - //- rjf: broadcast update - if(good && ctrl_state->wakeup_hook != 0) - { - ctrl_state->wakeup_hook(); - } - - //- rjf: bundle call stack as artifact - if(good) - { - artifact.u64[0] = (U64)arena; - artifact.u64[1] = (U64)call_stack; - } - - //- rjf: mark retry - retry_out[0] = retry; - - scratch_end(scratch); - } - return artifact; -} - -internal void -ctrl_call_stack_artifact_destroy(AC_Artifact artifact) -{ - Arena *arena = (Arena *)artifact.u64[0]; - if(arena != 0) - { - arena_release(arena); - } -} - -internal CTRL_CallStack -ctrl_call_stack_from_thread(Access *access, CTRL_Handle thread_handle, B32 high_priority, U64 endt_us) -{ - CTRL_CallStack result = {0}; - { - AC_Artifact artifact = ac_artifact_from_key(access, str8_struct(&thread_handle), ctrl_call_stack_artifact_create, ctrl_call_stack_artifact_destroy, endt_us, - .gen = ctrl_mem_gen() + ctrl_reg_gen(), - .evict_threshold_us = 10000000, - .flags = high_priority ? AC_Flag_HighPriority : 0); - if(artifact.u64[1] != 0) - { - MemoryCopyStruct(&result, (CTRL_CallStack *)artifact.u64[1]); - } - } - return result; -} - -//////////////////////////////// -//~ rjf: Call Stack Tree Artifact Cache Hooks / Lookups - -internal AC_Artifact -ctrl_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) -{ - Temp scratch = scratch_begin(0, 0); - Access *access = access_open(); - - //- rjf: gather list of all thread handles - U64 threads_count = 0; - CTRL_Handle *threads = 0; - CTRL_Handle *threads_processes = 0; - Arch *threads_arches = 0; - MutexScopeR(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) - { - CTRL_EntityCtx *ctx = &ctrl_state->ctrl_thread_entity_store->ctx; - CTRL_EntityArray thread_entities = ctrl_entity_array_from_kind(ctx, CTRL_EntityKind_Thread); - threads_count = thread_entities.count; - threads = push_array(scratch.arena, CTRL_Handle, threads_count); - threads_processes = push_array(scratch.arena, CTRL_Handle, threads_count); - threads_arches = push_array(scratch.arena, Arch, threads_count); - for EachIndex(idx, threads_count) - { - threads[idx] = thread_entities.v[idx]->handle; - threads_processes[idx] = thread_entities.v[idx]->parent->handle; - threads_arches[idx] = thread_entities.v[idx]->arch; - } - } - - //- rjf: gather all callstacks - B32 stale = 0; - U64 pre_mem_gen = ctrl_mem_gen(); - U64 pre_reg_gen = ctrl_reg_gen(); - CTRL_CallStack *call_stacks = push_array(scratch.arena, CTRL_CallStack, threads_count); - { - for EachIndex(idx, threads_count) - { - call_stacks[idx] = ctrl_call_stack_from_thread(access, threads[idx], 0, 0); - if(call_stacks[idx].concrete_frames_count == 0) - { - stale = 1; - break; - } - } - } - U64 post_mem_gen = ctrl_mem_gen(); - U64 post_reg_gen = ctrl_reg_gen(); - stale = (stale || pre_mem_gen != post_mem_gen || pre_reg_gen != post_reg_gen); - - //- rjf: build call stack tree - Arena *arena = 0; - CTRL_CallStackTree *tree = 0; - if(!stale) - { - U64 id_gen = 0; - arena = arena_alloc(); - tree = push_array(arena, CTRL_CallStackTree, 1); - tree->root = push_array(arena, CTRL_CallStackTreeNode, 1); - MemoryCopyStruct(tree->root, &ctrl_call_stack_tree_node_nil); - tree->root->id = id_gen; - tree->slots_count = Max(1, threads_count); - tree->slots = push_array(arena, CTRL_CallStackTreeNode *, tree->slots_count); - id_gen += 1; - for EachIndex(thread_idx, threads_count) - { - CTRL_Handle thread = threads[thread_idx]; - CTRL_Handle process = threads_processes[thread_idx]; - Arch arch = threads_arches[thread_idx]; - CTRL_CallStack call_stack = call_stacks[thread_idx]; - CTRL_CallStackTreeNode *thread_node = tree->root; - for EachIndex(frame_idx, call_stack.frames_count) - { - U64 vaddr = regs_rip_from_arch_block(arch, call_stack.frames[frame_idx].regs); - U64 depth = call_stack.frames[frame_idx].inline_depth; - CTRL_CallStackTreeNode *next_node = &ctrl_call_stack_tree_node_nil; - for(CTRL_CallStackTreeNode *child = thread_node->first; child != &ctrl_call_stack_tree_node_nil; child = child->next) - { - if(ctrl_handle_match(child->process, process) && child->vaddr == vaddr && child->depth == depth) - { - next_node = child; - break; - } - } - if(next_node == &ctrl_call_stack_tree_node_nil) - { - next_node = push_array(arena, CTRL_CallStackTreeNode, 1); - MemoryCopyStruct(next_node, &ctrl_call_stack_tree_node_nil); - next_node->id = id_gen; - SLLStackPush_N(tree->slots[next_node->id%tree->slots_count], next_node, hash_next); - id_gen += 1; - SLLQueuePush_NZ(&ctrl_call_stack_tree_node_nil, thread_node->first, thread_node->last, next_node, next); - next_node->parent = thread_node; - thread_node->child_count += 1; - } - thread_node = next_node; - } - ctrl_handle_list_push(arena, &thread_node->threads, &thread); - for(CTRL_CallStackTreeNode *n = thread_node; n != &ctrl_call_stack_tree_node_nil; n = n->parent) - { - n->all_descendant_threads_count += 1; - } - } - } - - //- rjf: produce artifact - AC_Artifact artifact = {0}; - { - artifact.u64[0] = (U64)arena; - artifact.u64[1] = (U64)tree; - } - - //- rjf: retry on stale - if(stale) - { - retry_out[0] = 1; - } - - access_close(access); - scratch_end(scratch); - return artifact; -} - -internal void -ctrl_call_stack_tree_artifact_destroy(AC_Artifact artifact) -{ - Arena *arena = (Arena *)artifact.u64[0]; - if(arena != 0) - { - arena_release(arena); - } -} - -internal CTRL_CallStackTree -ctrl_call_stack_tree(Access *access, U64 endt_us) -{ - CTRL_CallStackTree result = {&ctrl_call_stack_tree_node_nil}; - { - AC_Artifact artifact = ac_artifact_from_key(access, str8_zero(), ctrl_call_stack_tree_artifact_create, ctrl_call_stack_tree_artifact_destroy, endt_us); - if(artifact.u64[1] != 0) - { - MemoryCopyStruct(&result, (CTRL_CallStackTree *)artifact.u64[1]); - } - } - return result; -} +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Basic Type Functions + +#if !defined(XXH_IMPLEMENTATION) +# define XXH_IMPLEMENTATION +# define XXH_STATIC_LINKING_ONLY +# include "third_party/xxHash/xxhash.h" +#endif + +internal U64 +ctrl_hash_from_string(String8 string) +{ + U64 result = XXH3_64bits_withSeed(string.str, string.size, 5381); + return result; +} + +internal U64 +ctrl_hash_from_handle(CTRL_Handle handle) +{ + U64 buf[] = {handle.machine_id, handle.dmn_handle.u64[0]}; + U64 hash = ctrl_hash_from_string(str8((U8 *)buf, sizeof(buf))); + return hash; +} + +internal CTRL_EventCause +ctrl_event_cause_from_dmn_event_kind(DMN_EventKind event_kind) +{ + CTRL_EventCause cause = CTRL_EventCause_Null; + switch(event_kind) + { + default:{}break; + case DMN_EventKind_Error: {cause = CTRL_EventCause_Error;}break; + case DMN_EventKind_Exception:{cause = CTRL_EventCause_InterruptedByException;}break; + case DMN_EventKind_Trap: {cause = CTRL_EventCause_InterruptedByTrap;}break; + case DMN_EventKind_Halt: {cause = CTRL_EventCause_InterruptedByHalt;}break; + } + return cause; +} + +internal CTRL_ExceptionKind +ctrl_exception_kind_from_dmn(DMN_ExceptionKind kind) +{ + CTRL_ExceptionKind result = CTRL_ExceptionKind_Null; + switch(kind) + { + default:{}break; + case DMN_ExceptionKind_MemoryRead: {result = CTRL_ExceptionKind_MemoryRead;}break; + case DMN_ExceptionKind_MemoryWrite: {result = CTRL_ExceptionKind_MemoryWrite;}break; + case DMN_ExceptionKind_MemoryExecute: {result = CTRL_ExceptionKind_MemoryExecute;}break; + case DMN_ExceptionKind_CppThrow: {result = CTRL_ExceptionKind_CppThrow;}break; + } + return result; +} + +internal CTRL_TlsModel +ctrl_dynamic_linker_type_from_dmn(DMN_TlsModel type) +{ + CTRL_TlsModel result = CTRL_TlsModel_Null; + switch(type) + { + default:{}break; + case DMN_TlsModel_Null: {result = CTRL_TlsModel_Null;}break; + case DMN_TlsModel_WinodwsNt: {result = CTRL_TlsModel_WinodwsNt;}break; + case DMN_TlsModel_Gnu: {result = CTRL_TlsModel_Gnu;}break; + } + return result; +} + +internal String8 +ctrl_string_from_event_kind(CTRL_EventKind kind) +{ + String8 result = {0}; + switch(kind) + { + default:{}break; + case CTRL_EventKind_Null: { result = str8_lit("Null");}break; + case CTRL_EventKind_Error: { result = str8_lit("Error");}break; + case CTRL_EventKind_Started: { result = str8_lit("Started");}break; + case CTRL_EventKind_Stopped: { result = str8_lit("Stopped");}break; + case CTRL_EventKind_NewProc: { result = str8_lit("NewProc");}break; + case CTRL_EventKind_NewThread: { result = str8_lit("NewThread");}break; + case CTRL_EventKind_NewModule: { result = str8_lit("NewModule");}break; + case CTRL_EventKind_EndProc: { result = str8_lit("EndProc");}break; + case CTRL_EventKind_EndThread: { result = str8_lit("EndThread");}break; + case CTRL_EventKind_EndModule: { result = str8_lit("EndModule");}break; + case CTRL_EventKind_ModuleDebugInfoPathChange: { result = str8_lit("ModuleDebugInfoPathChange");}break; + case CTRL_EventKind_DebugString: { result = str8_lit("DebugString");}break; + case CTRL_EventKind_ThreadName: { result = str8_lit("ThreadName");}break; + case CTRL_EventKind_MemReserve: { result = str8_lit("MemReserve");}break; + case CTRL_EventKind_MemCommit: { result = str8_lit("MemCommit");}break; + case CTRL_EventKind_MemDecommit: { result = str8_lit("MemDecommit");}break; + case CTRL_EventKind_MemRelease: { result = str8_lit("MemRelease");}break; + } + return result; +} + +internal String8 +ctrl_string_from_msg_kind(CTRL_MsgKind kind) +{ + String8 result = {0}; + switch(kind) + { + default:{}break; + case CTRL_MsgKind_Launch: {result = str8_lit("Launch");}break; + case CTRL_MsgKind_Attach: {result = str8_lit("Attach");}break; + case CTRL_MsgKind_Kill: {result = str8_lit("Kill");}break; + case CTRL_MsgKind_KillAll: {result = str8_lit("KillAll");}break; + case CTRL_MsgKind_Detach: {result = str8_lit("Detach");}break; + case CTRL_MsgKind_Run: {result = str8_lit("Run");}break; + case CTRL_MsgKind_SingleStep: {result = str8_lit("SingleStep");}break; + case CTRL_MsgKind_SetUserEntryPoints: {result = str8_lit("SetUserEntryPoints");}break; + case CTRL_MsgKind_SetModuleDebugInfoPath: {result = str8_lit("SetModuleDebugInfoPath");}break; + } + return result; +} + +internal CTRL_EntityKind +ctrl_entity_kind_from_string(String8 string) +{ + CTRL_EntityKind result = CTRL_EntityKind_Null; + for EachNonZeroEnumVal(CTRL_EntityKind, k) + { + if(str8_match(ctrl_entity_kind_code_name_table[k], string, 0)) + { + result = k; + break; + } + } + return result; +} + +internal DMN_TrapFlags +ctrl_dmn_trap_flags_from_user_breakpoint_flags(CTRL_UserBreakpointFlags flags) +{ + DMN_TrapFlags result = 0; + if(flags & CTRL_UserBreakpointFlag_BreakOnWrite) { result |= DMN_TrapFlag_BreakOnWrite; } + if(flags & CTRL_UserBreakpointFlag_BreakOnRead) { result |= DMN_TrapFlag_BreakOnRead; } + if(flags & CTRL_UserBreakpointFlag_BreakOnExecute) { result |= DMN_TrapFlag_BreakOnExecute; } + return result; +} + +internal CTRL_UserBreakpointFlags +ctrl_user_breakpoint_flags_from_dmn_trap_flags(DMN_TrapFlags flags) +{ + CTRL_UserBreakpointFlags result = 0; + if(flags & DMN_TrapFlag_BreakOnWrite) { result |= CTRL_UserBreakpointFlag_BreakOnWrite; } + if(flags & DMN_TrapFlag_BreakOnRead) { result |= CTRL_UserBreakpointFlag_BreakOnRead; } + if(flags & DMN_TrapFlag_BreakOnExecute) { result |= CTRL_UserBreakpointFlag_BreakOnExecute; } + return result; +} + +//////////////////////////////// +//~ rjf: Machine/Handle Pair Type Functions + +internal CTRL_Handle +ctrl_handle_zero(void) +{ + CTRL_Handle handle = {0}; + return handle; +} + +internal CTRL_Handle +ctrl_handle_make(CTRL_MachineID machine_id, DMN_Handle dmn_handle) +{ + CTRL_Handle handle = {machine_id, dmn_handle}; + return handle; +} + +internal B32 +ctrl_handle_match(CTRL_Handle a, CTRL_Handle b) +{ + B32 result = (a.machine_id == b.machine_id && + dmn_handle_match(a.dmn_handle, b.dmn_handle)); + return result; +} + +internal void +ctrl_handle_list_push(Arena *arena, CTRL_HandleList *list, CTRL_Handle *pair) +{ + CTRL_HandleNode *n = push_array(arena, CTRL_HandleNode, 1); + MemoryCopyStruct(&n->v, pair); + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal CTRL_HandleList +ctrl_handle_list_copy(Arena *arena, CTRL_HandleList *src) +{ + CTRL_HandleList dst = {0}; + for(CTRL_HandleNode *n = src->first; n != 0; n = n->next) + { + ctrl_handle_list_push(arena, &dst, &n->v); + } + return dst; +} + +internal CTRL_HandleArray +ctrl_handle_array_from_list(Arena *arena, CTRL_HandleList *src) +{ + CTRL_HandleArray array = {0}; + array.count = src->count; + array.v = push_array_no_zero(arena, CTRL_Handle, array.count); + { + U64 idx = 0; + for(CTRL_HandleNode *n = src->first; n != 0; n = n->next, idx += 1) + { + array.v[idx] = n->v; + } + } + return array; +} + +internal String8 +ctrl_string_from_handle(Arena *arena, CTRL_Handle handle) +{ + String8 result = push_str8f(arena, "$%I64x$%I64x", handle.machine_id, handle.dmn_handle.u64[0]); + return result; +} + +internal CTRL_Handle +ctrl_handle_from_string(String8 string) +{ + CTRL_Handle handle = {0}; + { + Temp scratch = scratch_begin(0, 0); + U8 split = '$'; + String8List parts = str8_split(scratch.arena, string, &split, 1, 0); + if(parts.first && parts.first->next) + { + CTRL_MachineID machine_id = u64_from_str8(parts.first->string, 16); + DMN_Handle dmn_handle = {0}; + dmn_handle.u64[0] = u64_from_str8(parts.first->next->string, 16); + handle.machine_id = machine_id; + handle.dmn_handle = dmn_handle; + } + scratch_end(scratch); + } + return handle; +} + +//////////////////////////////// +//~ rjf: Trap Type Functions + +internal void +ctrl_trap_list_push(Arena *arena, CTRL_TrapList *list, CTRL_Trap *trap) +{ + CTRL_TrapNode *node = push_array(arena, CTRL_TrapNode, 1); + MemoryCopyStruct(&node->v, trap); + SLLQueuePush(list->first, list->last, node); + list->count += 1; +} + +internal CTRL_TrapList +ctrl_trap_list_copy(Arena *arena, CTRL_TrapList *src) +{ + CTRL_TrapList dst = {0}; + for(CTRL_TrapNode *src_n = src->first; src_n != 0; src_n = src_n->next) + { + ctrl_trap_list_push(arena, &dst, &src_n->v); + } + return dst; +} + +//////////////////////////////// +//~ rjf: User Breakpoint Type Functions + +internal void +ctrl_user_breakpoint_list_push(Arena *arena, CTRL_UserBreakpointList *list, CTRL_UserBreakpoint *bp) +{ + CTRL_UserBreakpointNode *n = push_array(arena, CTRL_UserBreakpointNode, 1); + MemoryCopyStruct(&n->v, bp); + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal CTRL_UserBreakpointList +ctrl_user_breakpoint_list_copy(Arena *arena, CTRL_UserBreakpointList *src) +{ + CTRL_UserBreakpointList dst = {0}; + for(CTRL_UserBreakpointNode *src_n = src->first; src_n != 0; src_n = src_n->next) + { + CTRL_UserBreakpoint dst_bp = zero_struct; + MemoryCopyStruct(&dst_bp, &src_n->v); + dst_bp.string = push_str8_copy(arena, src_n->v.string); + dst_bp.condition = push_str8_copy(arena, src_n->v.condition); + ctrl_user_breakpoint_list_push(arena, &dst, &dst_bp); + } + return dst; +} + +//////////////////////////////// +//~ rjf: Message Type Functions + +//- rjf: deep copying + +internal void +ctrl_msg_deep_copy(Arena *arena, CTRL_Msg *dst, CTRL_Msg *src) +{ + MemoryCopyStruct(dst, src); + dst->path = push_str8_copy(arena, src->path); + dst->entry_points = str8_list_copy(arena, &src->entry_points); + dst->cmd_line_string_list = str8_list_copy(arena, &src->cmd_line_string_list); + dst->env_string_list = str8_list_copy(arena, &src->env_string_list); + dst->traps = ctrl_trap_list_copy(arena, &src->traps); + dst->user_bps = ctrl_user_breakpoint_list_copy(arena, &src->user_bps); +} + +//- rjf: list building + +internal CTRL_Msg * +ctrl_msg_list_push(Arena *arena, CTRL_MsgList *list) +{ + CTRL_MsgNode *n = push_array(arena, CTRL_MsgNode, 1); + SLLQueuePush(list->first, list->last, n); + list->count += 1; + CTRL_Msg *msg = &n->v; + return msg; +} + +internal CTRL_MsgList +ctrl_msg_list_deep_copy(Arena *arena, CTRL_MsgList *src) +{ + CTRL_MsgList dst = {0}; + for(CTRL_MsgNode *n = src->first; n != 0; n = n->next) + { + CTRL_Msg *src_msg = &n->v; + CTRL_Msg *dst_msg = ctrl_msg_list_push(arena, &dst); + ctrl_msg_deep_copy(arena, dst_msg, src_msg); + } + return dst; +} + +internal void +ctrl_msg_list_concat_in_place(CTRL_MsgList *dst, CTRL_MsgList *src) +{ + if(dst->last && src->first) + { + dst->last->next = src->first; + dst->last = src->last; + dst->count += src->count; + } + else if(src->first) + { + MemoryCopyStruct(dst, src); + } + MemoryZeroStruct(src); +} + +//- rjf: serialization + +internal String8 +ctrl_serialized_string_from_msg_list(Arena *arena, CTRL_MsgList *msgs) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List msgs_srlzed = {0}; + str8_serial_begin(scratch.arena, &msgs_srlzed); + { + // rjf: write message count + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msgs->count); + + // rjf: write all message data + for(CTRL_MsgNode *msg_n = msgs->first; msg_n != 0; msg_n = msg_n->next) + { + CTRL_Msg *msg = &msg_n->v; + + // rjf: write flat parts + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->kind); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->run_flags); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->msg_id); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entity); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->parent); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entity_id); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->exit_code); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_inherit); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->debug_subprocesses); + str8_serial_push_array (scratch.arena, &msgs_srlzed, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); + + // rjf: write path string + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->path.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, msg->path.str, msg->path.size); + + // rjf: write entry point string list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entry_points.node_count); + for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) + { + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); + } + + // rjf: write command line string list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->cmd_line_string_list.node_count); + for(String8Node *n = msg->cmd_line_string_list.first; n != 0; n = n->next) + { + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); + } + + // rjf: write environment string list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_string_list.node_count); + for(String8Node *n = msg->env_string_list.first; n != 0; n = n->next) + { + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); + } + + // rjf: write stdout/stderr/stdin paths + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->stdout_path.size); + str8_serial_push_string(scratch.arena, &msgs_srlzed, msg->stdout_path); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->stderr_path.size); + str8_serial_push_string(scratch.arena, &msgs_srlzed, msg->stderr_path); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->stdin_path.size); + str8_serial_push_string(scratch.arena, &msgs_srlzed, msg->stdin_path); + + // rjf: write trap list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->traps.count); + for(CTRL_TrapNode *n = msg->traps.first; n != 0; n = n->next) + { + CTRL_Trap *trap = &n->v; + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &trap->flags); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &trap->vaddr); + } + + // rjf: write user breakpoint list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->user_bps.count); + for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) + { + CTRL_UserBreakpoint *bp = &n->v; + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->kind); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->flags); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->id); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->string.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->string.str, bp->string.size); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->pt); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->size); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->condition.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->condition.str, bp->condition.size); + } + } + } + String8 string = str8_serial_end(arena, &msgs_srlzed); + scratch_end(scratch); + return string; +} + +internal CTRL_MsgList +ctrl_msg_list_from_serialized_string(Arena *arena, String8 string) +{ + CTRL_MsgList msgs = {0}; + { + U64 read_off = 0; + + // rjf: read message count + U64 msg_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &msg_count); + + // rjf: read data for all messages + for(U64 msg_idx = 0; msg_idx < msg_count; msg_idx += 1) + { + // rjf: construct message + CTRL_MsgNode *msg_node = push_array(arena, CTRL_MsgNode, 1); + SLLQueuePush(msgs.first, msgs.last, msg_node); + msgs.count += 1; + CTRL_Msg *msg = &msg_node->v; + + // rjf: read flat data + read_off += str8_deserial_read_struct(string, read_off, &msg->kind); + read_off += str8_deserial_read_struct(string, read_off, &msg->run_flags); + read_off += str8_deserial_read_struct(string, read_off, &msg->msg_id); + read_off += str8_deserial_read_struct(string, read_off, &msg->entity); + read_off += str8_deserial_read_struct(string, read_off, &msg->parent); + read_off += str8_deserial_read_struct(string, read_off, &msg->entity_id); + read_off += str8_deserial_read_struct(string, read_off, &msg->exit_code); + read_off += str8_deserial_read_struct(string, read_off, &msg->env_inherit); + read_off += str8_deserial_read_struct(string, read_off, &msg->debug_subprocesses); + read_off += str8_deserial_read_array (string, read_off, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); + + // rjf: read path string + read_off += str8_deserial_read_struct(string, read_off, &msg->path.size); + msg->path.str = push_array_no_zero(arena, U8, msg->path.size); + read_off += str8_deserial_read(string, read_off, msg->path.str, msg->path.size, 1); + + // rjf: read entry point string list + U64 entry_point_list_string_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &entry_point_list_string_count); + for(U64 idx = 0; idx < entry_point_list_string_count; idx += 1) + { + String8 str = {0}; + read_off += str8_deserial_read_struct(string, read_off, &str.size); + str.str = push_array_no_zero(arena, U8, str.size); + read_off += str8_deserial_read(string, read_off, str.str, str.size, 1); + str8_list_push(arena, &msg->entry_points, str); + } + + // rjf: read command line string list + U64 cmd_line_string_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &cmd_line_string_count); + for(U64 idx = 0; idx < cmd_line_string_count; idx += 1) + { + String8 cmd_line_str = {0}; + read_off += str8_deserial_read_struct(string, read_off, &cmd_line_str.size); + cmd_line_str.str = push_array_no_zero(arena, U8, cmd_line_str.size); + read_off += str8_deserial_read(string, read_off, cmd_line_str.str, cmd_line_str.size, 1); + str8_list_push(arena, &msg->cmd_line_string_list, cmd_line_str); + } + + // rjf: read environment string list + U64 env_string_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &env_string_count); + for(U64 idx = 0; idx < env_string_count; idx += 1) + { + String8 env_str = {0}; + read_off += str8_deserial_read_struct(string, read_off, &env_str.size); + env_str.str = push_array_no_zero(arena, U8, env_str.size); + read_off += str8_deserial_read(string, read_off, env_str.str, env_str.size, 1); + str8_list_push(arena, &msg->env_string_list, env_str); + } + + // rjf: read stdout/stderr/stdin paths + read_off += str8_deserial_read_struct(string, read_off, &msg->stdout_path.size); + msg->stdout_path.str = push_array(arena, U8, msg->stdout_path.size); + read_off += str8_deserial_read(string, read_off, msg->stdout_path.str, msg->stdout_path.size, 1); + read_off += str8_deserial_read_struct(string, read_off, &msg->stderr_path.size); + msg->stderr_path.str = push_array(arena, U8, msg->stderr_path.size); + read_off += str8_deserial_read(string, read_off, msg->stderr_path.str, msg->stderr_path.size, 1); + read_off += str8_deserial_read_struct(string, read_off, &msg->stdin_path.size); + msg->stdin_path.str = push_array(arena, U8, msg->stdin_path.size); + read_off += str8_deserial_read(string, read_off, msg->stdin_path.str, msg->stdin_path.size, 1); + + // rjf: read trap list + U64 trap_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &trap_count); + for(U64 idx = 0; idx < trap_count; idx += 1) + { + CTRL_TrapNode *n = push_array(arena, CTRL_TrapNode, 1); + SLLQueuePush(msg->traps.first, msg->traps.last, n); + msg->traps.count += 1; + CTRL_Trap *trap = &n->v; + read_off += str8_deserial_read_struct(string, read_off, &trap->flags); + read_off += str8_deserial_read_struct(string, read_off, &trap->vaddr); + } + + // rjf: read user breakpoint list + U64 user_bp_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &user_bp_count); + for(U64 idx = 0; idx < user_bp_count; idx += 1) + { + CTRL_UserBreakpointNode *n = push_array(arena, CTRL_UserBreakpointNode, 1); + SLLQueuePush(msg->user_bps.first, msg->user_bps.last, n); + msg->user_bps.count += 1; + CTRL_UserBreakpoint *bp = &n->v; + read_off += str8_deserial_read_struct(string, read_off, &bp->kind); + read_off += str8_deserial_read_struct(string, read_off, &bp->flags); + read_off += str8_deserial_read_struct(string, read_off, &bp->id); + read_off += str8_deserial_read_struct(string, read_off, &bp->string.size); + bp->string.str = push_array_no_zero(arena, U8, bp->string.size); + read_off += str8_deserial_read(string, read_off, bp->string.str, bp->string.size, 1); + read_off += str8_deserial_read_struct(string, read_off, &bp->pt); + read_off += str8_deserial_read_struct(string, read_off, &bp->size); + read_off += str8_deserial_read_struct(string, read_off, &bp->condition.size); + bp->condition.str = push_array_no_zero(arena, U8, bp->condition.size); + read_off += str8_deserial_read(string, read_off, bp->condition.str, bp->condition.size, 1); + } + } + } + return msgs; +} + +//////////////////////////////// +//~ rjf: Event Type Functions + +//- rjf: list building + +internal CTRL_Event * +ctrl_event_list_push(Arena *arena, CTRL_EventList *list) +{ + CTRL_EventNode *n = push_array(arena, CTRL_EventNode, 1); + SLLQueuePush(list->first, list->last, n); + list->count += 1; + CTRL_Event *event = &n->v; + return event; +} + +internal void +ctrl_event_list_concat_in_place(CTRL_EventList *dst, CTRL_EventList *to_push) +{ + if(dst->last == 0) + { + MemoryCopyStruct(dst, to_push); + } + else if(to_push->first != 0) + { + dst->last->next = to_push->first; + dst->last = to_push->last; + dst->count += to_push->count; + } + MemoryZeroStruct(to_push); +} + +//- rjf: serialization + +internal String8 +ctrl_serialized_string_from_event(Arena *arena, CTRL_Event *event, U64 max) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List srl = {0}; + str8_serial_begin(scratch.arena, &srl); + { + str8_serial_push_struct(scratch.arena, &srl, &event->kind); + str8_serial_push_struct(scratch.arena, &srl, &event->cause); + str8_serial_push_struct(scratch.arena, &srl, &event->exception_kind); + str8_serial_push_struct(scratch.arena, &srl, &event->msg_id); + str8_serial_push_struct(scratch.arena, &srl, &event->entity); + str8_serial_push_struct(scratch.arena, &srl, &event->parent); + str8_serial_push_struct(scratch.arena, &srl, &event->arch); + str8_serial_push_struct(scratch.arena, &srl, &event->u64_code); + str8_serial_push_struct(scratch.arena, &srl, &event->entity_id); + str8_serial_push_struct(scratch.arena, &srl, &event->vaddr_rng); + str8_serial_push_struct(scratch.arena, &srl, &event->rip_vaddr); + str8_serial_push_struct(scratch.arena, &srl, &event->stack_base); + str8_serial_push_struct(scratch.arena, &srl, &event->tls_root); + str8_serial_push_struct(scratch.arena, &srl, &event->timestamp); + str8_serial_push_struct(scratch.arena, &srl, &event->exception_code); + str8_serial_push_struct(scratch.arena, &srl, &event->rgba); + str8_serial_push_struct(scratch.arena, &srl, &event->bp_flags); + str8_serial_push_struct(scratch.arena, &srl, &event->target_os); + str8_serial_push_struct(scratch.arena, &srl, &event->tls_model); + str8_serial_push_struct(scratch.arena, &srl, &event->tls_index); + str8_serial_push_struct(scratch.arena, &srl, &event->tls_offset); + String8 string = event->string; + string.size = Min(string.size, max-srl.total_size); + str8_serial_push_struct(scratch.arena, &srl, &string.size); + str8_serial_push_data(scratch.arena, &srl, string.str, string.size); + } + String8 string = str8_serial_end(arena, &srl); + scratch_end(scratch); + return string; +} + +internal CTRL_Event +ctrl_event_from_serialized_string(Arena *arena, String8 string) +{ + CTRL_Event event = zero_struct; + { + U64 read_off = 0; + read_off += str8_deserial_read_struct(string, read_off, &event.kind); + read_off += str8_deserial_read_struct(string, read_off, &event.cause); + read_off += str8_deserial_read_struct(string, read_off, &event.exception_kind); + read_off += str8_deserial_read_struct(string, read_off, &event.msg_id); + read_off += str8_deserial_read_struct(string, read_off, &event.entity); + read_off += str8_deserial_read_struct(string, read_off, &event.parent); + read_off += str8_deserial_read_struct(string, read_off, &event.arch); + read_off += str8_deserial_read_struct(string, read_off, &event.u64_code); + read_off += str8_deserial_read_struct(string, read_off, &event.entity_id); + read_off += str8_deserial_read_struct(string, read_off, &event.vaddr_rng); + read_off += str8_deserial_read_struct(string, read_off, &event.rip_vaddr); + read_off += str8_deserial_read_struct(string, read_off, &event.stack_base); + read_off += str8_deserial_read_struct(string, read_off, &event.tls_root); + read_off += str8_deserial_read_struct(string, read_off, &event.timestamp); + read_off += str8_deserial_read_struct(string, read_off, &event.exception_code); + read_off += str8_deserial_read_struct(string, read_off, &event.rgba); + read_off += str8_deserial_read_struct(string, read_off, &event.bp_flags); + read_off += str8_deserial_read_struct(string, read_off, &event.target_os); + read_off += str8_deserial_read_struct(string, read_off, &event.tls_model); + read_off += str8_deserial_read_struct(string, read_off, &event.tls_index); + read_off += str8_deserial_read_struct(string, read_off, &event.tls_offset); + read_off += str8_deserial_read_struct(string, read_off, &event.string.size); + event.string.str = push_array_no_zero(arena, U8, event.string.size); + read_off += str8_deserial_read(string, read_off, event.string.str, event.string.size, 1); + } + return event; +} + +//////////////////////////////// +//~ rjf: Entity Type Functions + +//- rjf: entity list data structures + +internal void +ctrl_entity_list_push(Arena *arena, CTRL_EntityList *list, CTRL_Entity *entity) +{ + CTRL_EntityNode *n = push_array(arena, CTRL_EntityNode, 1); + n->v = entity; + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal CTRL_EntityList +ctrl_entity_list_from_handle_list(Arena *arena, CTRL_EntityCtx *ctx, CTRL_HandleList *list) +{ + CTRL_EntityList result = {0}; + for(CTRL_HandleNode *n = list->first; n != 0; n = n->next) + { + CTRL_Entity *entity = ctrl_entity_from_handle(ctx, n->v); + ctrl_entity_list_push(arena, &result, entity); + } + return result; +} + +//- rjf: entity array data structure + +internal CTRL_EntityArray +ctrl_entity_array_from_list(Arena *arena, CTRL_EntityList *list) +{ + CTRL_EntityArray result = {0}; + result.count = list->count; + result.v = push_array_no_zero(arena, CTRL_Entity *, result.count); + U64 idx = 0; + for(CTRL_EntityNode *n = list->first; n != 0; n = n->next, idx += 1) + { + result.v[idx] = n->v; + } + return result; +} + +//- rjf: entity context (entity group read-only) functions + +internal CTRL_Entity * +ctrl_entity_from_handle(CTRL_EntityCtx *ctx, CTRL_Handle handle) +{ + CTRL_Entity *entity = &ctrl_entity_nil; + if(!ctrl_handle_match(handle, ctrl_handle_zero())) + { + U64 hash = ctrl_hash_from_handle(handle); + U64 slot_idx = hash%ctx->hash_slots_count; + CTRL_EntityHashSlot *slot = &ctx->hash_slots[slot_idx]; + CTRL_EntityHashNode *node = 0; + for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->entity->handle, handle)) + { + entity = n->entity; + break; + } + } + } + return entity; +} + +internal CTRL_Entity * +ctrl_entity_child_from_kind(CTRL_Entity *parent, CTRL_EntityKind kind) +{ + CTRL_Entity *result = &ctrl_entity_nil; + for(CTRL_Entity *child = parent->first; + child != &ctrl_entity_nil; + child = child->next) + { + if(child->kind == kind) + { + result = child; + break; + } + } + return result; +} + +internal CTRL_Entity * +ctrl_entity_ancestor_from_kind(CTRL_Entity *entity, CTRL_EntityKind kind) +{ + CTRL_Entity *result = &ctrl_entity_nil; + for(CTRL_Entity *p = entity->parent; p != &ctrl_entity_nil; p = p->parent) + { + if(p->kind == kind) + { + result = p; + break; + } + } + return result; +} + +internal CTRL_Entity * +ctrl_process_from_entity(CTRL_Entity *entity) +{ + CTRL_Entity *result = &ctrl_entity_nil; + if(entity->kind == CTRL_EntityKind_Process) + { + result = entity; + } + else + { + result = ctrl_entity_ancestor_from_kind(entity, CTRL_EntityKind_Process); + } + return result; +} + +internal CTRL_Entity * +ctrl_module_from_process_vaddr(CTRL_Entity *process, U64 vaddr) +{ + CTRL_Entity *result = &ctrl_entity_nil; + for(CTRL_Entity *child = process->first; + child != &ctrl_entity_nil; + child = child->next) + { + if(child->kind == CTRL_EntityKind_Module && contains_1u64(child->vaddr_range, vaddr)) + { + result = child; + break; + } + } + return result; +} + +internal DI_Key +ctrl_dbgi_key_from_module(CTRL_Entity *module) +{ + CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); + DI_Key dbgi_key = di_key_from_path_timestamp(debug_info_path->string, debug_info_path->timestamp); + return dbgi_key; +} + +internal CTRL_Entity * +ctrl_module_from_thread_candidates(CTRL_EntityCtx *ctx, CTRL_Entity *thread, CTRL_EntityList *candidates) +{ + CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process); + U64 thread_rip_vaddr = ctrl_rip_from_thread(ctx, thread->handle); + CTRL_Entity *src_module = ctrl_module_from_process_vaddr(process, thread_rip_vaddr); + CTRL_Entity *module = &ctrl_entity_nil; + for(CTRL_EntityNode *n = candidates->first; n != 0; n = n->next) + { + CTRL_Entity *candidate_module = n->v; + CTRL_Entity *candidate_process = ctrl_entity_ancestor_from_kind(candidate_module, CTRL_EntityKind_Process); + if(candidate_process == process) + { + module = candidate_module; + } + if(candidate_module == src_module) + { + break; + } + } + return module; +} + +internal U64 +ctrl_vaddr_from_voff(CTRL_Entity *module, U64 voff) +{ + U64 result = voff + module->vaddr_range.min; + return result; +} + +internal U64 +ctrl_voff_from_vaddr(CTRL_Entity *module, U64 vaddr) +{ + U64 result = vaddr - module->vaddr_range.min; + return result; +} + +internal Rng1U64 +ctrl_vaddr_range_from_voff_range(CTRL_Entity *module, Rng1U64 voff_range) +{ + U64 dim = dim_1u64(voff_range); + U64 min = ctrl_vaddr_from_voff(module, voff_range.min); + Rng1U64 result = {min, min+dim}; + return result; +} + +internal Rng1U64 +ctrl_voff_range_from_vaddr_range(CTRL_Entity *module, Rng1U64 vaddr_range) +{ + U64 dim = dim_1u64(vaddr_range); + U64 min = ctrl_voff_from_vaddr(module, vaddr_range.min); + Rng1U64 result = {min, min+dim}; + return result; +} + +internal B32 +ctrl_entity_tree_is_frozen(CTRL_Entity *root) +{ + B32 is_frozen = 1; + for(CTRL_Entity *e = root; e != &ctrl_entity_nil; e = ctrl_entity_rec_depth_first_pre(e, root).next) + { + if(e->kind == CTRL_EntityKind_Thread && !e->is_frozen) + { + is_frozen = 0; + break; + } + } + return is_frozen; +} + +//- rjf: entity ctx r/w store state functions + +internal CTRL_EntityCtxRWStore * +ctrl_entity_ctx_rw_store_alloc(void) +{ + Arena *arena = arena_alloc(); + CTRL_EntityCtxRWStore *store = push_array(arena, CTRL_EntityCtxRWStore, 1); + store->arena = arena; + store->ctx.hash_slots_count = 1024; + store->ctx.hash_slots = push_array(arena, CTRL_EntityHashSlot, store->ctx.hash_slots_count); + CTRL_Entity *root = store->ctx.root = ctrl_entity_alloc(store, &ctrl_entity_nil, CTRL_EntityKind_Root, Arch_Null, ctrl_handle_zero(), 0); + CTRL_Entity *local_machine = ctrl_entity_alloc(store, root, CTRL_EntityKind_Machine, Arch_CURRENT, ctrl_handle_make(CTRL_MachineID_Local, dmn_handle_zero()), 0); + Temp scratch = scratch_begin(0, 0); + String8 local_machine_name = push_str8f(scratch.arena, "This PC (%S)", os_get_system_info()->machine_name); + ctrl_entity_equip_string(store, local_machine, local_machine_name); + scratch_end(scratch); + return store; +} + +internal void +ctrl_entity_ctx_rw_store_release(CTRL_EntityCtxRWStore *store) +{ + arena_release(store->arena); +} + +//- rjf: string allocation/deletion + +internal U64 +ctrl_name_bucket_num_from_string_size(U64 size) +{ + U64 bucket_num = 0; + if(size > 0) + { + for EachElement(idx, ctrl_entity_string_bucket_chunk_sizes) + { + if(size <= ctrl_entity_string_bucket_chunk_sizes[idx]) + { + bucket_num = idx+1; + break; + } + } + } + return bucket_num; +} + +internal String8 +ctrl_entity_string_alloc(CTRL_EntityCtxRWStore *store, String8 string) +{ + //- rjf: allocate node + CTRL_EntityStringChunkNode *node = 0; + { + U64 bucket_num = ctrl_name_bucket_num_from_string_size(string.size); + if(bucket_num == ArrayCount(ctrl_entity_string_bucket_chunk_sizes)) + { + CTRL_EntityStringChunkNode *best_node = 0; + CTRL_EntityStringChunkNode *best_node_prev = 0; + U64 best_node_size = max_U64; + { + for(CTRL_EntityStringChunkNode *n = store->free_string_chunks[bucket_num-1], *prev = 0; n != 0; (prev = n, n = n->next)) + { + if(n->size >= string.size && n->size < best_node_size) + { + best_node = n; + best_node_prev = prev; + best_node_size = n->size; + } + } + } + if(best_node != 0) + { + node = best_node; + if(best_node_prev) + { + best_node_prev->next = best_node->next; + } + else + { + store->free_string_chunks[bucket_num-1] = best_node->next; + } + } + else + { + U64 chunk_size = u64_up_to_pow2(string.size); + node = (CTRL_EntityStringChunkNode *)push_array(store->arena, U8, chunk_size); + } + } + else if(bucket_num != 0) + { + node = store->free_string_chunks[bucket_num-1]; + if(node != 0) + { + SLLStackPop(store->free_string_chunks[bucket_num-1]); + } + else + { + node = (CTRL_EntityStringChunkNode *)push_array(store->arena, U8, ctrl_entity_string_bucket_chunk_sizes[bucket_num-1]); + } + } + } + + //- rjf: fill node + String8 result = {0}; + if(node != 0) + { + result.str = (U8 *)node; + result.size = string.size; + MemoryCopy(result.str, string.str, result.size); + } + return result; +} + +internal void +ctrl_entity_string_release(CTRL_EntityCtxRWStore *store, String8 string) +{ + U64 bucket_num = ctrl_name_bucket_num_from_string_size(string.size); + if(1 <= bucket_num && bucket_num <= ArrayCount(ctrl_entity_string_bucket_chunk_sizes)) + { + U64 bucket_idx = bucket_num-1; + CTRL_EntityStringChunkNode *node = (CTRL_EntityStringChunkNode *)string.str; + SLLStackPush(store->free_string_chunks[bucket_idx], node); + node->size = u64_up_to_pow2(string.size); + } +} + +//- rjf: entity construction/deletion + +internal CTRL_Entity * +ctrl_entity_alloc(CTRL_EntityCtxRWStore *store, CTRL_Entity *parent, CTRL_EntityKind kind, Arch arch, CTRL_Handle handle, U64 id) +{ + CTRL_Entity *entity = &ctrl_entity_nil; + { + // rjf: allocate + entity = store->free; + { + if(entity != 0) + { + SLLStackPop(store->free); + } + else + { + entity = push_array_no_zero(store->arena, CTRL_Entity, 1); + } + MemoryZeroStruct(entity); + } + + // rjf: fill + { + entity->kind = kind; + entity->arch = arch; + entity->handle = handle; + entity->id = id; + entity->parent = parent; + entity->next = entity->prev = entity->first = entity->last = &ctrl_entity_nil; + if(parent != &ctrl_entity_nil) + { + DLLPushBack_NPZ(&ctrl_entity_nil, parent->first, parent->last, entity, next, prev); + } + } + + // rjf: insert into hash map + { + U64 hash = ctrl_hash_from_handle(handle); + U64 slot_idx = hash%store->ctx.hash_slots_count; + CTRL_EntityHashSlot *slot = &store->ctx.hash_slots[slot_idx]; + CTRL_EntityHashNode *node = 0; + for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->entity->handle, handle)) + { + node = n; + break; + } + } + if(node == 0) + { + node = store->hash_node_free; + if(node != 0) + { + SLLStackPop(store->hash_node_free); + } + else + { + node = push_array_no_zero(store->arena, CTRL_EntityHashNode, 1); + } + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + node->entity = entity; + } + } + + // rjf: bump counters + store->ctx.entity_kind_counts[kind] += 1; + store->ctx.entity_kind_alloc_gens[kind] += 1; + } + return entity; +} + +internal void +ctrl_entity_release(CTRL_EntityCtxRWStore *store, CTRL_Entity *entity) +{ + // rjf: unhook root + if(entity->parent != &ctrl_entity_nil) + { + DLLRemove_NPZ(&ctrl_entity_nil, entity->parent->first, entity->parent->last, entity, next, prev); + } + + // rjf: walk every entity in this tree, free each + if(entity != &ctrl_entity_nil) + { + Temp scratch = scratch_begin(0, 0); + typedef struct Task Task; + struct Task + { + Task *next; + CTRL_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(CTRL_Entity *child = t->e->first; child != &ctrl_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(store->free, t->e); + + // rjf: remove from hash map + { + U64 hash = ctrl_hash_from_handle(t->e->handle); + U64 slot_idx = hash%store->ctx.hash_slots_count; + CTRL_EntityHashSlot *slot = &store->ctx.hash_slots[slot_idx]; + CTRL_EntityHashNode *node = 0; + for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->entity->handle, t->e->handle)) + { + DLLRemove(slot->first, slot->last, n); + SLLStackPush(store->hash_node_free, n); + break; + } + } + } + + // rjf: dec counter + store->ctx.entity_kind_counts[t->e->kind] -= 1; + store->ctx.entity_kind_alloc_gens[t->e->kind] += 1; + } + scratch_end(scratch); + } +} + +//- rjf: entity equipment + +internal void +ctrl_entity_equip_string(CTRL_EntityCtxRWStore *store, CTRL_Entity *entity, String8 string) +{ + if(entity->string.size != 0) + { + ctrl_entity_string_release(store, entity->string); + } + entity->string = ctrl_entity_string_alloc(store, string); +} + +//- rjf: accelerated entity context lookups + +internal CTRL_EntityCtxLookupAccel * +ctrl_thread_entity_ctx_lookup_accel(void) +{ + if(ctrl_entity_ctx_lookup_accel == 0) + { + Arena *arena = arena_alloc(); + ctrl_entity_ctx_lookup_accel = push_array(arena, CTRL_EntityCtxLookupAccel, 1); + ctrl_entity_ctx_lookup_accel->arena = arena; + for EachEnumVal(CTRL_EntityKind, k) + { + ctrl_entity_ctx_lookup_accel->entity_kind_arrays_arenas[k] = arena_alloc(); + } + } + return ctrl_entity_ctx_lookup_accel; +} + +internal CTRL_EntityArray +ctrl_entity_array_from_kind(CTRL_EntityCtx *ctx, CTRL_EntityKind kind) +{ + CTRL_EntityCtxLookupAccel *accel = ctrl_thread_entity_ctx_lookup_accel(); + if(accel->entity_kind_arrays_gens[kind] != ctx->entity_kind_alloc_gens[kind]) + { + Temp scratch = scratch_begin(0, 0); + CTRL_EntityList entities = {0}; + for(CTRL_Entity *e = ctx->root; + e != &ctrl_entity_nil; + e = ctrl_entity_rec_depth_first_pre(e, ctx->root).next) + { + if(e->kind == kind) + { + ctrl_entity_list_push(scratch.arena, &entities, e); + } + } + accel->entity_kind_arrays_gens[kind] = ctx->entity_kind_alloc_gens[kind]; + arena_clear(accel->entity_kind_arrays_arenas[kind]); + accel->entity_kind_arrays[kind] = ctrl_entity_array_from_list(accel->entity_kind_arrays_arenas[kind], &entities); + scratch_end(scratch); + } + return accel->entity_kind_arrays[kind]; +} + +internal CTRL_EntityList +ctrl_modules_from_dbgi_key(Arena *arena, CTRL_EntityCtx *ctx, DI_Key dbgi_key) +{ + CTRL_EntityList list = {0}; + CTRL_EntityArray all_modules = ctrl_entity_array_from_kind(ctx, CTRL_EntityKind_Module); + for EachIndex(idx, all_modules.count) + { + CTRL_Entity *module = all_modules.v[idx]; + DI_Key module_dbgi_key = ctrl_dbgi_key_from_module(module); + if(di_key_match(module_dbgi_key, dbgi_key)) + { + ctrl_entity_list_push(arena, &list, module); + } + } + return list; +} + +internal CTRL_Entity * +ctrl_thread_from_id(CTRL_EntityCtx *ctx, U64 id) +{ + CTRL_Entity *thread = &ctrl_entity_nil; + CTRL_EntityArray threads = ctrl_entity_array_from_kind(ctx, CTRL_EntityKind_Thread); + for EachIndex(idx, threads.count) + { + if(threads.v[idx]->id == id) + { + thread = threads.v[idx]; + } + } + return thread; +} + +//- rjf: entity tree iteration + +internal CTRL_EntityRec +ctrl_entity_rec_depth_first(CTRL_Entity *entity, CTRL_Entity *subtree_root, U64 sib_off, U64 child_off) +{ + CTRL_EntityRec result = {0}; + result.next = &ctrl_entity_nil; + if((*MemberFromOffset(CTRL_Entity **, entity, child_off)) != &ctrl_entity_nil) + { + result.next = *MemberFromOffset(CTRL_Entity **, entity, child_off); + result.push_count = 1; + } + else for(CTRL_Entity *parent = entity; parent != subtree_root && parent != &ctrl_entity_nil; parent = parent->parent) + { + if(parent != subtree_root && (*MemberFromOffset(CTRL_Entity **, parent, sib_off)) != &ctrl_entity_nil) + { + result.next = *MemberFromOffset(CTRL_Entity **, parent, sib_off); + break; + } + result.pop_count += 1; + } + return result; +} + +//- rjf: applying events to entity caches + +internal void +ctrl_entity_store_apply_events(CTRL_EntityCtxRWStore *store, CTRL_EventList *list) +{ + //- rjf: scan events & construct entities + for(CTRL_EventNode *n = list->first; n != 0; n = n->next) + { + CTRL_Event *event = &n->v; + switch(event->kind) + { + default:{}break; + + //- rjf: processes + case CTRL_EventKind_NewProc: + { + CTRL_Entity *machine = ctrl_entity_from_handle(&store->ctx, ctrl_handle_make(event->entity.machine_id, dmn_handle_zero())); + if(machine != &ctrl_entity_nil) + { + CTRL_Entity *process = ctrl_entity_alloc(store, machine, CTRL_EntityKind_Process, event->arch, event->entity, (U64)event->entity_id); + process->tls_model = event->tls_model; + process->target_os = event->target_os; + } + }break; + case CTRL_EventKind_EndProc: + { + CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->entity); + ctrl_entity_release(store, process); + for(CTRL_Entity *entry = store->ctx.root->first, *next = &ctrl_entity_nil; + entry != &ctrl_entity_nil; + entry = next) + { + next = entry->next; + if(entry->kind == CTRL_EntityKind_EntryPoint && entry->id == process->id) + { + ctrl_entity_release(store, entry); + } + } + }break; + + //- rjf: threads + case CTRL_EventKind_NewThread: + { + CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); + if(process != &ctrl_entity_nil) + { + CTRL_Entity *thread = ctrl_entity_alloc(store, process, CTRL_EntityKind_Thread, event->arch, event->entity, (U64)event->entity_id); + CTRL_Entity *first_thread = ctrl_entity_child_from_kind(process, CTRL_EntityKind_Thread); + if(first_thread == thread) + { + ctrl_entity_equip_string(store, thread, str8_lit("main_thread")); + } + CTRL_EntityArray pending_thread_names = ctrl_entity_array_from_kind(&store->ctx, CTRL_EntityKind_PendingThreadName); + for EachIndex(idx, pending_thread_names.count) + { + CTRL_Entity *entity = pending_thread_names.v[idx]; + if(entity->id == event->entity_id) + { + ctrl_entity_equip_string(store, thread, entity->string); + ctrl_entity_release(store, entity); + break; + } + } + CTRL_EntityArray pending_thread_colors = ctrl_entity_array_from_kind(&store->ctx, CTRL_EntityKind_PendingThreadColor); + for EachIndex(idx, pending_thread_colors.count) + { + CTRL_Entity *entity = pending_thread_colors.v[idx]; + if(entity->id == event->entity_id) + { + thread->rgba = entity->rgba; + ctrl_entity_release(store, entity); + break; + } + } + thread->stack_base = event->stack_base; + } + //ctrl_rip_from_thread(&store->ctx, event->entity); + }break; + case CTRL_EventKind_EndThread: + { + CTRL_Entity *thread = ctrl_entity_from_handle(&store->ctx, event->entity); + ctrl_entity_release(store, thread); + }break; + case CTRL_EventKind_ThreadName: + { + CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); + CTRL_Entity *thread = &ctrl_entity_nil; + if(event->entity_id == 0) + { + thread = ctrl_entity_from_handle(&store->ctx, event->entity); + } + else + { + thread = ctrl_thread_from_id(&store->ctx, event->entity_id); + } + if(thread != &ctrl_entity_nil) + { + ctrl_entity_equip_string(store, thread, event->string); + } + else if(process != &ctrl_entity_nil) + { + CTRL_Entity *pending_name = ctrl_entity_alloc(store, process, CTRL_EntityKind_PendingThreadName, Arch_Null, ctrl_handle_zero(), event->entity_id); + ctrl_entity_equip_string(store, pending_name, event->string); + } + }break; + case CTRL_EventKind_ThreadColor: + { + CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); + CTRL_Entity *thread = &ctrl_entity_nil; + if(event->entity_id == 0) + { + thread = ctrl_entity_from_handle(&store->ctx, event->entity); + } + else + { + thread = ctrl_thread_from_id(&store->ctx, event->entity_id); + } + if(thread != &ctrl_entity_nil) + { + thread->rgba = event->rgba; + } + else if(process != &ctrl_entity_nil) + { + CTRL_Entity *pending = ctrl_entity_alloc(store, process, CTRL_EntityKind_PendingThreadColor, Arch_Null, ctrl_handle_zero(), event->entity_id); + pending->rgba = event->rgba; + } + }break; + case CTRL_EventKind_ThreadFrozen: + { + CTRL_Entity *thread = ctrl_entity_from_handle(&store->ctx, event->entity); + if(thread != &ctrl_entity_nil) + { + thread->is_frozen = 1; + } + }break; + case CTRL_EventKind_ThreadThawed: + { + CTRL_Entity *thread = ctrl_entity_from_handle(&store->ctx, event->entity); + if(thread != &ctrl_entity_nil) + { + thread->is_frozen = 0; + } + }break; + + //- rjf: modules + case CTRL_EventKind_NewModule: + { + Temp scratch = scratch_begin(0, 0); + CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); + CTRL_Entity *module = ctrl_entity_alloc(store, process, CTRL_EntityKind_Module, event->arch, event->entity, event->vaddr_rng.min); + ctrl_entity_equip_string(store, module, event->string); + module->timestamp = event->timestamp; + module->vaddr_range = event->vaddr_rng; + module->tls_index = event->tls_index; + module->tls_offset = event->tls_offset; + CTRL_Entity *first_module = ctrl_entity_child_from_kind(process, CTRL_EntityKind_Module); + if(first_module == module) + { + ctrl_entity_equip_string(store, process, str8_skip_last_slash(event->string)); + } + scratch_end(scratch); + }break; + case CTRL_EventKind_EndModule: + { + CTRL_Entity *module = ctrl_entity_from_handle(&store->ctx, event->entity); + ctrl_entity_release(store, module); + }break; + case CTRL_EventKind_ModuleDebugInfoPathChange: + { + Temp scratch = scratch_begin(0, 0); + CTRL_Entity *module = ctrl_entity_from_handle(&store->ctx, event->entity); + CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); + if(debug_info_path == &ctrl_entity_nil) + { + debug_info_path = ctrl_entity_alloc(store, module, CTRL_EntityKind_DebugInfoPath, Arch_Null, ctrl_handle_zero(), 0); + } + ctrl_entity_equip_string(store, debug_info_path, path_normalized_from_string(scratch.arena, event->string)); + debug_info_path->timestamp = event->timestamp; + scratch_end(scratch); + }break; + + //- rjf: dynamic, program-created breakpoints + case CTRL_EventKind_SetBreakpoint: + { + CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); + CTRL_Entity *bp = ctrl_entity_alloc(store, process, CTRL_EntityKind_Breakpoint, Arch_Null, ctrl_handle_zero(), 0); + bp->vaddr_range = event->vaddr_rng; + bp->bp_flags = event->bp_flags; + }break; + case CTRL_EventKind_UnsetBreakpoint: + { + CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); + for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) + { + if(child->kind == CTRL_EntityKind_Breakpoint && + child->vaddr_range.min == event->vaddr_rng.min && + child->vaddr_range.max == event->vaddr_rng.max && + child->bp_flags == event->bp_flags) + { + ctrl_entity_release(store, child); + break; + } + } + }break; + + //- rjf: address range annotations + case CTRL_EventKind_SetVAddrRangeNote: + { + CTRL_Entity *process = ctrl_entity_from_handle(&store->ctx, event->parent); + CTRL_Entity *annotation = ctrl_entity_alloc(store, process, CTRL_EntityKind_AddressRangeAnnotation, Arch_Null, ctrl_handle_zero(), 0); + annotation->vaddr_range = event->vaddr_rng; + ctrl_entity_equip_string(store, annotation, event->string); + }break; + } + } +} + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +ctrl_init(void) +{ + Arena *arena = arena_alloc(); + ctrl_state = push_array(arena, CTRL_State, 1); + ctrl_state->arena = arena; + for(Arch arch = (Arch)0; arch < Arch_COUNT; arch = (Arch)(arch+1)) + { + String8 *reg_names = regs_reg_code_string_table_from_arch(arch); + U64 reg_count = regs_reg_code_count_from_arch(arch); + String8 *alias_names = regs_alias_code_string_table_from_arch(arch); + U64 alias_count = regs_alias_code_count_from_arch(arch); + ctrl_state->arch_string2reg_tables[arch] = e_string2num_map_make(ctrl_state->arena, 256); + ctrl_state->arch_string2alias_tables[arch] = e_string2num_map_make(ctrl_state->arena, 256); + for(U64 idx = 1; idx < reg_count; idx += 1) + { + e_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2reg_tables[arch], reg_names[idx], idx); + } + for(U64 idx = 1; idx < alias_count; idx += 1) + { + e_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2alias_tables[arch], alias_names[idx], idx); + } + } + ctrl_state->thread_reg_cache.slots_count = 1024; + ctrl_state->thread_reg_cache.slots = push_array(arena, CTRL_ThreadRegCacheSlot, ctrl_state->thread_reg_cache.slots_count); + ctrl_state->thread_reg_cache.stripes_count = os_get_system_info()->logical_processor_count; + ctrl_state->thread_reg_cache.stripes = push_array(arena, CTRL_ThreadRegCacheStripe, ctrl_state->thread_reg_cache.stripes_count); + for(U64 idx = 0; idx < ctrl_state->thread_reg_cache.stripes_count; idx += 1) + { + ctrl_state->thread_reg_cache.stripes[idx].arena = arena_alloc(); + ctrl_state->thread_reg_cache.stripes[idx].rw_mutex = rw_mutex_alloc(); + } + ctrl_state->module_image_info_cache.slots_count = 1024; + ctrl_state->module_image_info_cache.slots = push_array(arena, CTRL_ModuleImageInfoCacheSlot, ctrl_state->module_image_info_cache.slots_count); + ctrl_state->module_image_info_cache.stripes_count = os_get_system_info()->logical_processor_count; + ctrl_state->module_image_info_cache.stripes = push_array(arena, CTRL_ModuleImageInfoCacheStripe, ctrl_state->module_image_info_cache.stripes_count); + for(U64 idx = 0; idx < ctrl_state->module_image_info_cache.stripes_count; idx += 1) + { + ctrl_state->module_image_info_cache.stripes[idx].arena = arena_alloc(); + ctrl_state->module_image_info_cache.stripes[idx].rw_mutex = rw_mutex_alloc(); + } + ctrl_state->u2c_ring_size = KB(64); + ctrl_state->u2c_ring_base = push_array_no_zero(arena, U8, ctrl_state->u2c_ring_size); + ctrl_state->u2c_ring_mutex = mutex_alloc(); + ctrl_state->u2c_ring_cv = cond_var_alloc(); + ctrl_state->c2u_ring_size = KB(64); + ctrl_state->c2u_ring_max_string_size = ctrl_state->c2u_ring_size/2; + ctrl_state->c2u_ring_base = push_array_no_zero(arena, U8, ctrl_state->c2u_ring_size); + ctrl_state->c2u_ring_mutex = mutex_alloc(); + ctrl_state->c2u_ring_cv = cond_var_alloc(); + { + Temp scratch = scratch_begin(0, 0); + String8 user_program_data_path = os_get_process_info()->user_program_data_path; + String8 user_data_folder = push_str8f(scratch.arena, "%S/raddbg/logs", user_program_data_path); + os_make_directory(user_data_folder); + ctrl_state->ctrl_thread_log_path = push_str8f(ctrl_state->arena, "%S/ctrl_thread.raddbg_log", user_data_folder); + os_write_data_to_file_path(ctrl_state->ctrl_thread_log_path, str8_zero()); + scratch_end(scratch); + } + ctrl_state->ctrl_thread_entity_ctx_rw_mutex = rw_mutex_alloc(); + ctrl_state->ctrl_thread_entity_store = ctrl_entity_ctx_rw_store_alloc(); + ctrl_state->ctrl_thread_eval_cache = e_cache_alloc(); + ctrl_state->ctrl_thread_msg_process_arena = arena_alloc(); + ctrl_state->dmn_event_arena = arena_alloc(); + ctrl_state->user_entry_point_arena = arena_alloc(); + ctrl_state->dbg_dir_arena = arena_alloc(); + for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) + { + if(ctrl_exception_code_kind_default_enable_table[k]) + { + ctrl_state->exception_code_filters[k/64] |= 1ull<<(k%64); + } + } + ctrl_state->ctrl_thread_log = log_alloc(); + ctrl_state->ctrl_thread = thread_launch(ctrl_thread__entry_point, 0); +} + +//////////////////////////////// +//~ rjf: Wakeup Callback Registration + +internal void +ctrl_set_wakeup_hook(CTRL_WakeupFunctionType *wakeup_hook) +{ + ctrl_state->wakeup_hook = wakeup_hook; +} + +//////////////////////////////// +//~ rjf: Thread Register Functions + +//- rjf: thread register cache reading + +internal void * +ctrl_reg_block_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle handle) +{ + CTRL_ThreadRegCache *cache = &ctrl_state->thread_reg_cache; + CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, handle); + Arch arch = thread_entity->arch; + U64 reg_block_size = regs_block_size_from_arch(arch); + U64 hash = ctrl_hash_from_handle(handle); + U64 slot_idx = hash%cache->slots_count; + U64 stripe_idx = slot_idx%cache->stripes_count; + CTRL_ThreadRegCacheSlot *slot = &cache->slots[slot_idx]; + CTRL_ThreadRegCacheStripe *stripe = &cache->stripes[stripe_idx]; + void *result = push_array(arena, U8, reg_block_size); + MutexScopeW(stripe->rw_mutex) + { + // rjf: find existing node + CTRL_ThreadRegCacheNode *node = 0; + for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->handle, handle)) + { + node = n; + break; + } + } + + // rjf: allocate existing node + if(!node) + { + node = push_array(stripe->arena, CTRL_ThreadRegCacheNode, 1); + DLLPushBack(slot->first, slot->last, node); + node->handle = handle; + node->block_size = reg_block_size; + node->block = push_array(stripe->arena, U8, reg_block_size); + } + + // rjf: copy from node + if(node) + { + U64 current_reg_gen = ctrl_reg_gen(); + B32 need_stale = 1; + if(node->reg_gen != current_reg_gen && + dmn_thread_read_reg_block(handle.dmn_handle, result)) + { + if(node != 0) + { + need_stale = 0; + node->reg_gen = current_reg_gen; + MemoryCopy(node->block, result, reg_block_size); + } + } + if(need_stale) + { + MemoryCopy(result, node->block, reg_block_size); + } + } + } + return result; +} + +internal U64 +ctrl_tls_root_vaddr_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle) +{ + U64 result = dmn_tls_root_vaddr_from_thread(handle.dmn_handle); + return result; +} + +internal U64 +ctrl_rip_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle) +{ + Temp scratch = scratch_begin(0, 0); + CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, handle); + Arch arch = thread_entity->arch; + void *block = ctrl_reg_block_from_thread(scratch.arena, ctx, handle); + U64 result = regs_rip_from_arch_block(arch, block); + scratch_end(scratch); + return result; +} + +internal U64 +ctrl_rsp_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle) +{ + Temp scratch = scratch_begin(0, 0); + CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, handle); + Arch arch = thread_entity->arch; + void *block = ctrl_reg_block_from_thread(scratch.arena, ctx, handle); + U64 result = regs_rsp_from_arch_block(arch, block); + scratch_end(scratch); + return result; +} + +//- rjf: thread register writing + +internal B32 +ctrl_thread_write_reg_block(CTRL_Handle thread, void *block) +{ + // TODO(rjf): @callstacks immediately reflect this in the call stack cache + B32 good = dmn_thread_write_reg_block(thread.dmn_handle, block); + if(good) + { + ins_atomic_u64_inc_eval(&ctrl_state->reg_gen); + } + return good; +} + +//////////////////////////////// +//~ rjf: Module Image Info Functions + +//- rjf: cache lookups + +internal PE_IntelPdata * +ctrl_intel_pdata_from_module_voff(Arena *arena, CTRL_Handle module_handle, U64 voff) +{ + PE_IntelPdata *first_pdata = 0; + { + U64 hash = ctrl_hash_from_handle(module_handle); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->module, module_handle)) + { + PE_IntelPdata *pdatas = n->pdatas; + U64 pdatas_count = n->pdatas_count; + if(n->pdatas_count != 0 && voff >= n->pdatas[0].voff_first) + { + // NOTE(rjf): + // + // binary search: + // find max index s.t. pdata_array[index].voff_first <= voff + // we assume (i < j) -> (pdata_array[i].voff_first < pdata_array[j].voff_first) + U64 index = pdatas_count; + U64 min = 0; + U64 opl = pdatas_count; + for(;;) + { + U64 mid = (min + opl)/2; + PE_IntelPdata *pdata = pdatas + mid; + if(voff < pdata->voff_first) + { + opl = mid; + } + else if(pdata->voff_first < voff) + { + min = mid; + } + else + { + index = mid; + break; + } + if(min + 1 >= opl) + { + index = min; + break; + } + } + + // rjf: if we are in range fill result + { + PE_IntelPdata *pdata = pdatas + index; + if(pdata->voff_first <= voff && voff < pdata->voff_one_past_last) + { + first_pdata = push_array(arena, PE_IntelPdata, 1); + MemoryCopyStruct(first_pdata, pdata); + } + } + } + break; + } + } + } + return first_pdata; +} + +internal U64 +ctrl_entry_point_voff_from_module(CTRL_Handle module_handle) +{ + U64 result = 0; + U64 hash = ctrl_hash_from_handle(module_handle); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->module, module_handle)) + { + result = n->entry_point_voff; + break; + } + } + return result; +} + +internal String8 +ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_Handle module_handle) +{ + String8 result = {0}; + U64 hash = ctrl_hash_from_handle(module_handle); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->module, module_handle)) + { + result = push_str8_copy(arena, n->initial_debug_info_path); + break; + } + } + return result; +} + +internal String8 +ctrl_raddbg_data_from_module(Arena *arena, CTRL_Handle module_handle) +{ + String8 result = {0}; + U64 hash = ctrl_hash_from_handle(module_handle); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->module, module_handle)) + { + result = push_str8_copy(arena, n->raddbg_data); + break; + } + } + return result; +} + +//////////////////////////////// +//~ Process Info Functions + +internal Arch +ctrl_arch_from_process_handle(CTRL_Handle process_handle) +{ + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + CTRL_Entity *process_entity = ctrl_entity_from_handle(entity_ctx, process_handle); + return process_entity->arch; +} + +//////////////////////////////// +//~ rjf: Unwinding Functions + +//- rjf: unwind deep copier + +internal CTRL_Unwind +ctrl_unwind_deep_copy(Arena *arena, Arch arch, CTRL_Unwind *src) +{ + CTRL_Unwind dst = {0}; + { + dst.flags = src->flags; + dst.frames.count = src->frames.count; + dst.frames.v = push_array(arena, CTRL_UnwindFrame, dst.frames.count); + MemoryCopy(dst.frames.v, src->frames.v, sizeof(dst.frames.v[0])*dst.frames.count); + U64 block_size = regs_block_size_from_arch(arch); + for(U64 idx = 0; idx < dst.frames.count; idx += 1) + { + dst.frames.v[idx].regs = push_array_no_zero(arena, U8, block_size); + MemoryCopy(dst.frames.v[idx].regs, src->frames.v[idx].regs, block_size); + } + } + return dst; +} + +//- DWARF + +typedef struct +{ + CTRL_Handle process_handle; + U64 endt_us; +} CTRL_MemoryReadContextDwarfX64; + +internal +MACHINE_OP_MEM_READ(ctrl_machine_mem_read) +{ + CTRL_MemoryReadContextDwarfX64 *ctx = ud; + + B32 is_stale = 0; + B32 is_read = ctrl_process_memory_read(ctx->process_handle, r1u64(addr, addr + buffer_size), &is_stale, buffer, ctx->endt_us); + + MachineOpResult status = MachineOpResult_Fail; + if(is_stale) + { + status = MachineOpResult_Maybe; + } + else if(is_read) + { + status = MachineOpResult_Ok; + } + + return status; +} + +internal CTRL_UnwindStepResult +ctrl_unwind_step_result_from_machine_op_result(MachineOpResult s) +{ + CTRL_UnwindStepResult result = {0}; + switch(s) + { + case MachineOpResult_Null: {} break; + case MachineOpResult_Ok: + { + result.flags &= ~(CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale); + }break; + case MachineOpResult_Fail: + { + result.flags |= CTRL_UnwindFlag_Error; + }break; + case MachineOpResult_Maybe: + { + result.flags &= ~CTRL_UnwindFlag_Error; + result.flags |= CTRL_UnwindFlag_Stale; + }break; + default: { InvalidPath; } break; + } + return result; +} + +internal CTRL_UnwindStepResult +ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us, CTRL_FrameUnwindContext *ctx_out) +{ + Temp scratch = scratch_begin(&arena, 1); + CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error }; + + // gather context for virtual stack unwinder + U64 cfi_rebase = 0; + B32 is_unwind_eh = 0; + String8 unwind_data = {0}; + EH_FrameHdr eh_frame_hdr = {0}; + EH_PtrCtx eh_ptr_ctx = {0}; + { + U64 hash = ctrl_hash_from_handle(module_handle); + U64 slot_idx = hash % ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx % ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + MutexScopeR(stripe->rw_mutex) for EachNode(n, CTRL_ModuleImageInfoCacheNode, slot->first) + { + if(ctrl_handle_match(n->module, module_handle)) + { + cfi_rebase = n->cfi_rebase; + is_unwind_eh = n->is_unwind_eh; + unwind_data = n->dwarf_unwind_data; + eh_frame_hdr = n->eh_frame_hdr; + eh_ptr_ctx = n->eh_ptr_ctx; + break; + } + } + } + + // grab IP + U64 ip = regs_rip_from_arch_block(arch, regs); + + // use .eh_frame_hdr to quickly locate nearest FDE + U64 fde_addr = eh_find_nearest_fde(eh_frame_hdr, &eh_ptr_ctx, ip); + + if(fde_addr != max_U64) + { + // parse call frame info + DW_CIE cie = {0}; + DW_FDE fde = {0}; + B32 is_cfi_parsed = 0; + if(is_unwind_eh) + { + B32 is_stale = 0; + + // extract FDE info + Rng1U64 fde_vrange = {0}; + DW_Format fde_format = DW_Format_Null; + String8 fde_data = {0}; + U64 cie_addr = 0; + { + // parse FDE length + U32 first_four_bytes = 0; + if(!ctrl_process_memory_read_struct(process_handle, fde_addr, &is_stale, &first_four_bytes, endt_us)) { goto eh_parse_exit; } + if(first_four_bytes == max_U32) + { + U64 length = 0; + if(!ctrl_process_memory_read_struct(process_handle, fde_addr + sizeof(first_four_bytes), &is_stale, &length, endt_us)) { goto eh_parse_exit; } + fde_vrange = r1u64(fde_addr, fde_addr + sizeof(first_four_bytes) + length); + fde_format = DW_Format_64Bit; + } + else + { + fde_vrange = r1u64(fde_addr, fde_addr + sizeof(first_four_bytes) + first_four_bytes); + fde_format = DW_Format_32Bit; + } + + // read out whole FDE + void *fde_raw = push_array(scratch.arena, U8, dim_1u64(fde_vrange)); + if(!ctrl_process_memory_read(process_handle, fde_vrange, &is_stale, fde_raw, endt_us)) { goto eh_parse_exit; } + fde_data = str8(fde_raw, dim_1u64(fde_vrange)); + + // compute CIE address + U64 cie_delta_off = fde_format == DW_Format_32Bit ? 4 : 12; + U64 cie_delta = 0; + U64 cie_delta_size = str8_deserial_read_dwarf_uint(fde_data, cie_delta_off, fde_format, &cie_delta); + if (cie_delta_size == 0) { goto eh_parse_exit; } + cie_addr = (fde_addr + cie_delta_off) - cie_delta; + } + + // extract CIE info + Rng1U64 cie_vrange = {0}; + DW_Format cie_format = DW_Format_Null; + String8 cie_data = {0}; + { + // parse CIE length + U32 first_four_bytes = 0; + if(!ctrl_process_memory_read_struct(process_handle, cie_addr, &is_stale, &first_four_bytes, endt_us)) { goto eh_parse_exit; } + if(first_four_bytes == max_U32) + { + U64 length = 0; + if(!ctrl_process_memory_read_struct(process_handle, cie_addr + sizeof(first_four_bytes), &is_stale, &length, endt_us)) { goto eh_parse_exit; } + cie_vrange = r1u64(cie_addr, cie_addr + sizeof(first_four_bytes) + length); + cie_format = DW_Format_64Bit; + } + else + { + cie_vrange = r1u64(cie_addr, cie_addr + sizeof(first_four_bytes) + first_four_bytes); + cie_format = DW_Format_32Bit; + } + + // read out whole CIE + void *cie_raw = push_array(scratch.arena, U8, dim_1u64(cie_vrange)); + if(!ctrl_process_memory_read(process_handle, cie_vrange, &is_stale, cie_raw, endt_us)) { goto eh_parse_exit; } + cie_data = str8(cie_raw, dim_1u64(cie_vrange)); + } + + // parse CIE and FDE + if(eh_parse_cie(cie_data, cie_format, arch, cie_vrange.min, &eh_ptr_ctx, &cie)) + { + is_cfi_parsed = eh_parse_fde(fde_data, fde_format, fde_vrange.min, &cie, &eh_ptr_ctx, &fde); + } + + eh_parse_exit:; + if(is_stale) + { + result.flags = CTRL_UnwindFlag_Stale; + } + } + else + { + is_cfi_parsed = dw_parse_cfi(unwind_data, fde_addr, arch, &cie, &fde); + } + + if(is_cfi_parsed && contains_1u64(fde.pc_range, ip)) + { + // setup pointer decoder ops + DW_DecodePtr *decode_ptr_func = 0; + void *decode_ptr_ctx = 0; + if(is_unwind_eh) + { + EH_DecodePtrCtx *decode_ptr_ctx_eh = push_array(scratch.arena, EH_DecodePtrCtx, 1); + decode_ptr_ctx_eh->ptr_ctx = &eh_ptr_ctx; + decode_ptr_ctx_eh->addr_enc = cie.ext[EH_CIE_Ext_AddrEnc]; + + decode_ptr_func = eh_decode_ptr; + decode_ptr_ctx = decode_ptr_ctx_eh; + } + else + { + decode_ptr_func = dw_decode_ptr_debug_frame; + decode_ptr_ctx = &cie; + } + + // find register rules for IP + DW_CFI_Row *cfi_row = dw_cfi_row_from_pc(arena, arch, &cie, &fde, decode_ptr_func, decode_ptr_ctx, ip); + if(cfi_row) + { + // setup machine ops + void *mem_read_ctx = 0; + void *reg_read_ctx = 0; + MachineOp_MemRead *mem_read_func = 0; + MachineOp_RegRead *reg_read_func = 0; + switch(arch) + { + case Arch_Null: break; + case Arch_x64: + { + CTRL_MemoryReadContextDwarfX64 *mem_read_ctx_x64 = push_array(scratch.arena, CTRL_MemoryReadContextDwarfX64, 1); + mem_read_ctx_x64->process_handle = process_handle; + mem_read_ctx_x64->endt_us = endt_us; + + mem_read_ctx = mem_read_ctx_x64; + reg_read_ctx = regs; + + mem_read_func = ctrl_machine_mem_read; + reg_read_func = regs_read_dwarf_x64; + }break; + case Arch_x86: + case Arch_arm64: + case Arch_arm32: + { + NotImplemented; + }break; + default: { InvalidPath; }break; + } + + // compute CFA for the row + U64 cfa = 0; + MachineOpResult unwind_status = dw_compute_cfa(arch, cfi_row, mem_read_func, mem_read_ctx, reg_read_func, reg_read_ctx, &cfa); + + // on success fill out output + if(unwind_status == MachineOpResult_Ok) + { + ctx_out->cfa = cfa; + ctx_out->cfi_row = cfi_row; + ctx_out->ret_addr_reg = cie.ret_addr_reg; + } + + // translate unwind status code + result = ctrl_unwind_step_result_from_machine_op_result(unwind_status); + } + } + } + + scratch_end(scratch); + return result; +} + +internal CTRL_UnwindStepResult +ctrl_unwind_step__dwarf(CTRL_Handle process_handle, Arch arch, void *regs, CTRL_FrameUnwindContext *frame_ctx, U64 endt_us) +{ + Temp scratch = scratch_begin(0, 0); + + CTRL_UnwindStepResult result = { .flags = CTRL_UnwindFlag_Error }; + + // setup machine ops + void *mem_read_ctx = 0; + void *reg_read_ctx = 0; + void *reg_write_ctx = 0; + MachineOp_MemRead *mem_read_func = 0; + MachineOp_RegRead *reg_read_func = 0; + MachineOp_RegWrite *reg_write_func = 0; + switch(arch) + { + case Arch_Null: break; + case Arch_x64: + { + CTRL_MemoryReadContextDwarfX64 *mem_read_ctx_x64 = push_array(scratch.arena, CTRL_MemoryReadContextDwarfX64, 1); + mem_read_ctx_x64->process_handle = process_handle; + mem_read_ctx_x64->endt_us = endt_us; + + mem_read_ctx = mem_read_ctx_x64; + reg_read_ctx = regs; + reg_write_ctx = regs; + + mem_read_func = ctrl_machine_mem_read; + reg_read_func = regs_read_dwarf_x64; + reg_write_func = regs_write_dwarf_x64; + }break; + case Arch_x86: + case Arch_arm64: + case Arch_arm32: + { + NotImplemented; + }break; + default: { InvalidPath; }break; + } + + // apply register rules to the context + MachineOpResult unwind_status = dw_cfi_apply_register_rules(arch, + frame_ctx->cfa, + frame_ctx->cfi_row, + mem_read_func, + mem_read_ctx, + reg_read_func, + reg_read_ctx, + reg_write_func, + reg_write_ctx); + + // last frame typically has undefined rule for IP + if(frame_ctx->cfi_row->regs[frame_ctx->ret_addr_reg].rule == DW_CFI_RegisterRule_Undefined) + { + regs_arch_block_write_rip(arch, regs, 0); + } + + // translate unwind status code + result = ctrl_unwind_step_result_from_machine_op_result(unwind_status); + + scratch_end(scratch); + return result; +} + +//- rjf: [x64] + +internal REGS_Reg64 * +ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(REGS_RegBlockX64 *regs, PE_UnwindGprRegX64 gpr_reg) +{ + local_persist REGS_Reg64 dummy = {0}; + REGS_Reg64 *result = &dummy; + switch(gpr_reg) + { + case PE_UnwindGprRegX64_RAX:{result = ®s->rax;}break; + case PE_UnwindGprRegX64_RCX:{result = ®s->rcx;}break; + case PE_UnwindGprRegX64_RDX:{result = ®s->rdx;}break; + case PE_UnwindGprRegX64_RBX:{result = ®s->rbx;}break; + case PE_UnwindGprRegX64_RSP:{result = ®s->rsp;}break; + case PE_UnwindGprRegX64_RBP:{result = ®s->rbp;}break; + case PE_UnwindGprRegX64_RSI:{result = ®s->rsi;}break; + case PE_UnwindGprRegX64_RDI:{result = ®s->rdi;}break; + case PE_UnwindGprRegX64_R8 :{result = ®s->r8 ;}break; + case PE_UnwindGprRegX64_R9 :{result = ®s->r9 ;}break; + case PE_UnwindGprRegX64_R10:{result = ®s->r10;}break; + case PE_UnwindGprRegX64_R11:{result = ®s->r11;}break; + case PE_UnwindGprRegX64_R12:{result = ®s->r12;}break; + case PE_UnwindGprRegX64_R13:{result = ®s->r13;}break; + case PE_UnwindGprRegX64_R14:{result = ®s->r14;}break; + case PE_UnwindGprRegX64_R15:{result = ®s->r15;}break; + } + return result; +} + +internal CTRL_UnwindStepResult +ctrl_unwind_step__pe_x64(CTRL_Handle process_handle, CTRL_Handle module_handle, U64 module_base_vaddr, REGS_RegBlockX64 *regs, U64 endt_us) +{ + B32 is_stale = 0; + B32 is_good = 1; + Temp scratch = scratch_begin(0, 0); + + ////////////////////////////// + //- rjf: unpack parameters + // + U64 rip_voff = regs->rip.u64 - module_base_vaddr; + + ////////////////////////////// + //- rjf: rip_voff -> first pdata + // + PE_IntelPdata *first_pdata = ctrl_intel_pdata_from_module_voff(scratch.arena, module_handle, rip_voff); + + ////////////////////////////// + //- rjf: pdata -> detect if in epilog + // + B32 has_pdata_and_in_epilog = 0; + if(first_pdata) ProfScope("pdata -> detect if in epilog") + { + // NOTE(allen): There are restrictions placed on how an epilog is allowed + // to be formed (https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=msvc-160) + // Here we interpret machine code directly according to the rules + // given there to determine if the code we're looking at looks like an epilog. + + //- rjf: set up parsing state + B32 is_epilog = 0; + B32 keep_parsing = 1; + U64 read_vaddr = regs->rip.u64; + U64 read_vaddr_opl = read_vaddr + 256; + + //- rjf: check first instruction + { + B32 inst_good = 0; + U8 inst[4] = {0}; + if(read_vaddr + sizeof(inst) <= read_vaddr_opl) + { + inst_good = ctrl_process_memory_read(process_handle, r1u64(read_vaddr, read_vaddr+sizeof(inst)), &is_stale, inst, endt_us); + inst_good = inst_good && !is_stale; + } + if(!inst_good) + { + keep_parsing = 0; + } + else if((inst[0] & 0xF8) == 0x48) + { + switch(inst[1]) + { + // rjf: add $nnnn,%rsp + case 0x81: + { + if(inst[0] == 0x48 && inst[2] == 0xC4) + { + read_vaddr += 7; + } + else + { + keep_parsing = 0; + } + }break; + + // rjf: add $n,%rsp + case 0x83: + { + if(inst[0] == 0x48 && inst[2] == 0xC4) + { + read_vaddr += 4; + } + else + { + keep_parsing = 0; + } + }break; + + // rjf: lea n(reg),%rsp + case 0x8D: + { + if((inst[0] & 0x06) == 0 && + ((inst[2] >> 3) & 0x07) == 0x04 && + (inst[2] & 0x07) != 0x04) + { + U8 imm_size = (inst[2] >> 6); + + // rjf: 1-byte immediate + if(imm_size == 1) + { + read_vaddr += 4; + } + + // rjf: 4-byte immediate + else if(imm_size == 2) + { + read_vaddr += 7; + } + + // rjf: other case + else + { + keep_parsing = 0; + } + } + else + { + keep_parsing = 0; + } + }break; + } + } + } + + //- rjf: continue parsing instructions + for(;keep_parsing;) + { + // rjf: read next instruction byte + B32 inst_byte_good = 0; + U8 inst_byte = 0; + if(read_vaddr + sizeof(inst_byte) <= read_vaddr_opl) + { + inst_byte_good = ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &inst_byte, endt_us); + } + if(!inst_byte_good || is_stale) + { + keep_parsing = 0; + } + + // rjf: when (... I don't know ...) rely on the next byte + B32 check_inst_byte_good = inst_byte_good; + U64 check_vaddr = read_vaddr; + U8 check_inst_byte = inst_byte; + if(inst_byte_good && (inst_byte & 0xF0) == 0x40) + { + check_vaddr = read_vaddr + 1; + if(read_vaddr + sizeof(check_inst_byte) <= read_vaddr_opl) + { + check_inst_byte_good = ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &check_inst_byte, endt_us); + } + if(!check_inst_byte_good || is_stale) + { + keep_parsing = 0; + } + } + + // rjf: check instruction byte + if(check_inst_byte_good) + { + switch(check_inst_byte) + { + // rjf: pop + case 0x58:case 0x59:case 0x5A:case 0x5B: + case 0x5C:case 0x5D:case 0x5E:case 0x5F: + { + read_vaddr = check_vaddr + 1; + }break; + + // rjf: ret + case 0xC2: + case 0xC3: + { + is_epilog = 1; + keep_parsing = 0; + }break; + + // rjf: jmp nnnn + case 0xE9: + { + U64 imm_vaddr = check_vaddr + 1; + S32 imm = 0; + B32 imm_good = 0; + if(read_vaddr + sizeof(imm) <= read_vaddr_opl) + { + imm_good = ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us); + } + if(!imm_good || is_stale) + { + keep_parsing = 0; + } + if(imm_good) + { + U64 next_vaddr = (U64)(imm_vaddr + sizeof(imm) + imm); + U64 next_voff = next_vaddr - module_base_vaddr; // TODO(rjf): verify that this offset is from module base vaddr, not section + if(!(first_pdata->voff_first <= next_voff && next_voff < first_pdata->voff_one_past_last)) + { + keep_parsing = 0; + } + else + { + read_vaddr = next_vaddr; + } + } + // TODO(allen): why isn't this just the end of the epilog? + }break; + + // rjf: rep; ret (for amd64 prediction bug) + case 0xF3: + { + U8 next_inst_byte = 0; + B32 next_inst_byte_good = 0; + if(read_vaddr + sizeof(next_inst_byte) <= read_vaddr_opl) + { + next_inst_byte_good = ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &next_inst_byte, endt_us); + } + if(next_inst_byte_good) + { + is_epilog = (next_inst_byte == 0xC3); + } + keep_parsing = 0; + }break; + + default:{keep_parsing = 0;}break; + } + } + } + has_pdata_and_in_epilog = is_epilog; + } + + ////////////////////////////// + //- rjf: pdata & in epilog -> epilog unwind + // + if(first_pdata && has_pdata_and_in_epilog) ProfScope("pdata & in epilog -> epilog unwind") + { + U64 read_vaddr = regs->rip.u64; + for(B32 keep_parsing = 1;keep_parsing != 0;) + { + //- rjf: assume no more parsing after this instruction + keep_parsing = 0; + + //- rjf: read next instruction byte + U8 inst_byte = 0; + is_good = is_good && ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &inst_byte, endt_us); + is_good = is_good && !is_stale; + read_vaddr += 1; + + //- rjf: extract rex from instruction byte + U8 rex = 0; + if((inst_byte & 0xF0) == 0x40) + { + rex = inst_byte & 0xF; // rex prefix + is_good = is_good && ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &inst_byte, endt_us); + is_good = is_good && !is_stale; + read_vaddr += 1; + } + + //- rjf: parse remainder of instruction + switch(inst_byte) + { + // rjf: pop + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + { + // rjf: read value at rsp + U64 sp = regs->rsp.u64; + U64 value = 0; + if(!ctrl_process_memory_read_struct(process_handle, sp, &is_stale, &value, endt_us) || + is_stale) + { + is_good = 0; + break; + } + + // rjf: modify registers + PE_UnwindGprRegX64 gpr_reg = (inst_byte - 0x58) + (rex & 1)*8; + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, gpr_reg); + reg->u64 = value; + regs->rsp.u64 = sp + 8; + + // rjf: not a final instruction, so keep mparsing + keep_parsing = 1; + }break; + + // rjf: add $nnnn,%rsp + case 0x81: + { + // rjf: skip one byte (we already know what it is in this scenario) + read_vaddr += 1; + + // rjf: read the 4-byte immediate + S32 imm = 0; + if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 4; + + // rjf: update stack pointer + regs->rsp.u64 = (U64)(regs->rsp.u64 + imm); + + // rjf: not a final instruction; keep parsing + keep_parsing = 1; + }break; + + // rjf: add $n,%rsp + case 0x83: + { + // rjf: skip one byte (we already know what it is in this scenario) + read_vaddr += 1; + + // rjf: read the 4-byte immediate + S8 imm = 0; + if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 1; + + // rjf: update stack pointer + regs->rsp.u64 = (U64)(regs->rsp.u64 + imm); + + // rjf: not a final instruction; keep parsing + keep_parsing = 1; + }break; + + // rjf: lea imm8/imm32,$rsp + case 0x8D: + { + // rjf: read source register + U8 modrm = 0; + if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &modrm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 1; + PE_UnwindGprRegX64 gpr_reg = (modrm & 7) + (rex & 1)*8; + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, gpr_reg); + U64 reg_value = reg->u64; + + // rjf: read immediate + S32 imm = 0; + { + // rjf: read 1-byte immediate + if((modrm >> 6) == 1) + { + S8 imm8 = 0; + if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm8, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 1; + imm = (S32)imm8; + } + + // rjf: read 4-byte immediate + else + { + if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 4; + } + } + + // rjf: update stack pointer + regs->rsp.u64 = (U64)(reg_value + imm); + + // rjf: not a final instruction; keep parsing + keep_parsing = 1; + }break; + + // rjf: ret $nn + case 0xC2: + { + // rjf: read new ip + U64 sp = regs->rsp.u64; + U64 new_ip = 0; + if(!ctrl_process_memory_read_struct(process_handle, sp, &is_stale, &new_ip, endt_us) || + is_stale) + { + is_good = 0; + break; + } + + // rjf: read 2-byte immediate & advance stack pointer + U16 imm = 0; + if(!ctrl_process_memory_read_struct(process_handle, read_vaddr, &is_stale, &imm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + U64 new_sp = sp + 8 + imm; + + // rjf: commit registers + regs->rip.u64 = new_ip; + regs->rsp.u64 = new_sp; + }break; + + // rjf: ret / rep; ret + case 0xF3: + { + // Assert(!"Hit me!"); + }break; + case 0xC3: + { + // rjf: read new ip + U64 sp = regs->rsp.u64; + U64 new_ip = 0; + if(!ctrl_process_memory_read_struct(process_handle, sp, &is_stale, &new_ip, endt_us) || + is_stale) + { + is_good = 0; + break; + } + + // rjf: advance stack pointer + U64 new_sp = sp + 8; + + // rjf: commit registers + regs->rip.u64 = new_ip; + regs->rsp.u64 = new_sp; + }break; + + // rjf: jmp nnnn + case 0xE9: + { + // Assert(!"Hit Me"); + // TODO(allen): general idea: read the immediate, move the ip, leave the sp, done + // we don't have any cases to exercise this right now. no guess implementation! + }break; + + // rjf: Sjmp n + case 0xEB: + { + // Assert(!"Hit Me"); + // TODO(allen): general idea: read the immediate, move the ip, leave the sp, done + // we don't have any cases to exercise this right now. no guess implementation! + }break; + } + } + } + + ////////////////////////////// + //- rjf: pdata & not in epilog -> xdata unwind + // + B32 xdata_unwind_did_machframe = 0; + if(first_pdata && !has_pdata_and_in_epilog) ProfScope("pdata & not in epilog -> xdata unwind") + { + //- rjf: get frame reg + B32 bad_frame_reg_info = 0; + REGS_Reg64 *frame_reg = 0; + U64 frame_off = 0; + { + U64 unwind_info_off = first_pdata->voff_unwind_info; + PE_UnwindInfo unwind_info = {0}; + if(!ctrl_process_memory_read_struct(process_handle, module_base_vaddr+unwind_info_off, &is_stale, &unwind_info, endt_us) || + is_stale) + { + is_good = 0; + } + U32 frame_reg_id = PE_UNWIND_INFO_REG_FROM_FRAME(unwind_info.frame); + U64 frame_off_val = PE_UNWIND_INFO_OFF_FROM_FRAME(unwind_info.frame); + if(frame_reg_id != 0) + { + frame_reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, frame_reg_id); + bad_frame_reg_info = (frame_reg == 0); // NOTE(rjf): frame_reg should never be 0 at this point, in valid exe + } + frame_off = frame_off_val; + } + + //- rjf: iterate pdatas, apply opcodes + PE_IntelPdata *last_pdata = 0; + PE_IntelPdata *pdata = first_pdata; + if(!bad_frame_reg_info) for(B32 keep_parsing = 1; keep_parsing && pdata != last_pdata;) + { + //- rjf: unpack unwind info & codes + B32 good_unwind_info = 1; + U64 unwind_info_off = pdata->voff_unwind_info; + PE_UnwindInfo unwind_info = {0}; + good_unwind_info = good_unwind_info && ctrl_process_memory_read_struct(process_handle, module_base_vaddr+unwind_info_off, &is_stale, &unwind_info, endt_us); + PE_UnwindCode *unwind_codes = push_array(scratch.arena, PE_UnwindCode, unwind_info.codes_num); + good_unwind_info = good_unwind_info && ctrl_process_memory_read(process_handle, r1u64(module_base_vaddr+unwind_info_off+sizeof(unwind_info), + module_base_vaddr+unwind_info_off+sizeof(unwind_info)+sizeof(PE_UnwindCode)*unwind_info.codes_num), + &is_stale, unwind_codes, endt_us); + good_unwind_info = good_unwind_info && !is_stale; + + //- rjf: bad unwind info -> abort + if(!good_unwind_info) + { + is_good = 0; + break; + } + + //- rjf: unpack frame base + U64 frame_base = regs->rsp.u64; + if(frame_reg != 0) + { + U64 raw_frame_base = frame_reg->u64; + U64 adjusted_frame_base = raw_frame_base - frame_off*16; + frame_base = adjusted_frame_base; + } + + //- rjf: apply opcodes + PE_UnwindCode *code_ptr = unwind_codes; + PE_UnwindCode *code_opl = unwind_codes + unwind_info.codes_num; + for(PE_UnwindCode *next_code_ptr = 0; code_ptr < code_opl; code_ptr = next_code_ptr) + { + // rjf: unpack opcode info + U32 op_code = PE_UNWIND_OPCODE_FROM_FLAGS(code_ptr->flags); + U32 op_info = PE_UNWIND_INFO_FROM_FLAGS(code_ptr->flags); + U32 slot_count = pe_slot_count_from_unwind_op_code(op_code); + if(op_code == PE_UnwindOpCode_ALLOC_LARGE && op_info == 1) + { + slot_count += 1; + } + + // rjf: detect bad slot counts + if(slot_count == 0 || code_ptr+slot_count > code_opl) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: set next op code pointer + next_code_ptr = code_ptr + slot_count; + + // rjf: interpret this op code + U64 code_voff = pdata->voff_first + code_ptr->off_in_prolog; + if(code_voff <= rip_voff) + { + switch(op_code) + { + case PE_UnwindOpCode_PUSH_NONVOL: + { + // rjf: read value from stack pointer + U64 rsp = regs->rsp.u64; + U64 value = 0; + if(!ctrl_process_memory_read_struct(process_handle, rsp, &is_stale, &value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: advance stack ptr + U64 new_rsp = rsp + 8; + + // rjf: commit registers + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); + reg->u64 = value; + regs->rsp.u64 = new_rsp; + }break; + + case PE_UnwindOpCode_ALLOC_LARGE: + { + // rjf: read alloc size + U64 size = 0; + if(op_info == 0) + { + size = code_ptr[1].u16*8; + } + else if(op_info == 1) + { + size = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); + } + else + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: advance stack pointer + U64 rsp = regs->rsp.u64; + U64 new_rsp = rsp + size; + + // rjf: advance stack pointer + regs->rsp.u64 = new_rsp; + }break; + + case PE_UnwindOpCode_ALLOC_SMALL: + { + // rjf: advance stack pointer + regs->rsp.u64 += op_info*8 + 8; + }break; + + case PE_UnwindOpCode_SET_FPREG: + { + // rjf: put stack pointer back to the frame base + regs->rsp.u64 = frame_base; + }break; + + case PE_UnwindOpCode_SAVE_NONVOL: + { + // rjf: read value from frame base + U64 off = code_ptr[1].u16*8; + U64 addr = frame_base + off; + U64 value = 0; + if(!ctrl_process_memory_read_struct(process_handle, addr, &is_stale, &value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit to register + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); + reg->u64 = value; + }break; + + case PE_UnwindOpCode_SAVE_NONVOL_FAR: + { + // rjf: read value from frame base + U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); + U64 addr = frame_base + off; + U64 value = 0; + if(!ctrl_process_memory_read_struct(process_handle, addr, &is_stale, &value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit to register + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); + reg->u64 = value; + }break; + + case PE_UnwindOpCode_EPILOG: + { + keep_parsing = 1; + }break; + + case PE_UnwindOpCode_SPARE_CODE: + { + // TODO(rjf): ??? + keep_parsing = 0; + is_good = 0; + }break; + + case PE_UnwindOpCode_SAVE_XMM128: + { + // rjf: read new register values + U8 buf[16]; + U64 off = code_ptr[1].u16*16; + U64 addr = frame_base + off; + if(!ctrl_process_memory_read(process_handle, r1u64(addr, addr+sizeof(buf)), &is_stale, buf, endt_us)) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit to register + void *xmm_reg = (®s->zmm0) + op_info; + MemoryCopy(xmm_reg, buf, sizeof(buf)); + }break; + + case PE_UnwindOpCode_SAVE_XMM128_FAR: + { + // rjf: read new register values + U8 buf[16]; + U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); + U64 addr = frame_base + off; + if(!ctrl_process_memory_read(process_handle, r1u64(addr, addr+16), &is_stale, buf, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit to register + void *xmm_reg = (®s->zmm0) + op_info; + MemoryCopy(xmm_reg, buf, sizeof(buf)); + }break; + + case PE_UnwindOpCode_PUSH_MACHFRAME: + { + // NOTE(rjf): this was found by stepping through kernel code after an exception was + // thrown, encountered in the exception_stepping_tests (after the throw) in mule_main + if(op_info > 1) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: read values + U64 sp_og = regs->rsp.u64; + U64 sp_adj = sp_og; + if(op_info == 1) + { + sp_adj += 8; + } + U64 ip_value = 0; + if(!ctrl_process_memory_read_struct(process_handle, sp_adj, &is_stale, &ip_value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + U64 sp_after_ip = sp_adj + 8; + U16 ss_value = 0; + if(!ctrl_process_memory_read_struct(process_handle, sp_after_ip, &is_stale, &ss_value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + U64 sp_after_ss = sp_after_ip + 8; + U64 rflags_value = 0; + if(!ctrl_process_memory_read_struct(process_handle, sp_after_ss, &is_stale, &rflags_value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + U64 sp_after_rflags = sp_after_ss + 8; + U64 sp_value = 0; + if(!ctrl_process_memory_read_struct(process_handle, sp_after_rflags, &is_stale, &sp_value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit registers + regs->rip.u64 = ip_value; + regs->ss.u16 = ss_value; + regs->rflags.u64 = rflags_value; + regs->rsp.u64 = sp_value; + + // rjf: mark machine frame + xdata_unwind_did_machframe = 1; + }break; + } + } + } + + //- rjf: iterate to next pdata + if(keep_parsing) + { + U32 flags = PE_UNWIND_INFO_FLAGS_FROM_HDR(unwind_info.header); + if(!(flags & PE_UnwindInfoFlag_CHAINED)) + { + break; + } + U64 code_count_rounded = AlignPow2(unwind_info.codes_num, sizeof(PE_UnwindCode)); + U64 code_size = code_count_rounded*sizeof(PE_UnwindCode); + U64 chained_pdata_off = unwind_info_off + sizeof(PE_UnwindInfo) + code_size; + last_pdata = pdata; + pdata = push_array(scratch.arena, PE_IntelPdata, 1); + if(!ctrl_process_memory_read_struct(process_handle, module_base_vaddr+chained_pdata_off, &is_stale, pdata, endt_us) || + is_stale) + { + is_good = 0; + break; + } + } + } + } + + ////////////////////////////// + //- rjf: no pdata, or didn't do machframe in xdata unwind -> unwind by reading stack pointer + // + if(!first_pdata || (!has_pdata_and_in_epilog && !xdata_unwind_did_machframe)) ProfScope("no pdata, or didn't do machframe in xdata unwind -> unwind by reading stack pointer") + { + // rjf: read rip from stack pointer + U64 rsp = regs->rsp.u64; + U64 new_rip = 0; + if(!ctrl_process_memory_read_struct(process_handle, rsp, &is_stale, &new_rip, endt_us) || + is_stale) + { + is_good = 0; + } + + // rjf: commit registers + if(is_good) + { + U64 new_rsp = rsp + 8; + regs->rip.u64 = new_rip; + regs->rsp.u64 = new_rsp; + } + } + + ////////////////////////////// + //- rjf: fill & return + // + scratch_end(scratch); + CTRL_UnwindStepResult result = {0}; + if(!is_good) {result.flags |= CTRL_UnwindFlag_Error;} + if(is_stale) {result.flags |= CTRL_UnwindFlag_Stale;} + return result; +} + +//- rjf: abstracted full unwind + +internal CTRL_Unwind +ctrl_unwind_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle thread, U64 endt_us) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + CTRL_Unwind unwind = { .flags = CTRL_UnwindFlag_Error }; + + //- rjf: unpack args + CTRL_Entity *thread_entity = ctrl_entity_from_handle(ctx, thread); + CTRL_Entity *process_entity = thread_entity->parent; + Arch arch = thread_entity->arch; + U64 arch_reg_block_size = regs_block_size_from_arch(arch); + + //- rjf: grab initial register block + void *regs_block = ctrl_reg_block_from_thread(scratch.arena, ctx, thread); + B32 regs_block_good = (arch != Arch_Null && regs_block != 0); + + //- rjf: loop & unwind + CTRL_UnwindFrameNode *first_frame_node = 0; + CTRL_UnwindFrameNode *last_frame_node = 0; + U64 frame_node_count = 0; + if(regs_block_good) + { + for(unwind.flags = 0;;) + { + U64 rip = regs_rip_from_arch_block(arch, regs_block); + U64 rsp = regs_rsp_from_arch_block(arch, regs_block); + + // rjf: cancel on zero rip (except the top frame) + if(rip == 0 && frame_node_count > 0) + { + break; + } + + // rip -> module + CTRL_Entity *module_entity = ctrl_module_from_process_vaddr(process_entity, rip); + + // establish frame context + CTRL_FrameUnwindContext frame_ctx = {0}; + CTRL_UnwindStepResult frame_ctx_result = {0}; + switch(process_entity->target_os) + { + case OperatingSystem_Null:{}break; + case OperatingSystem_Windows: + { + // no concept of frame unwind context + }break; + case OperatingSystem_Linux: + { + frame_ctx_result = ctrl_establish_frame_unwind_context__dwarf(arena, process_entity->handle, module_entity->handle, arch, regs_block, endt_us, &frame_ctx); + }break; + case OperatingSystem_Mac: + { + NotImplemented; + }break; + default: { InvalidPath; }break; + } + unwind.flags |= frame_ctx_result.flags; + if(unwind.flags & (CTRL_UnwindFlag_Stale|CTRL_UnwindFlag_Error)) + { + break; + } + + // rjf: valid step -> push frame + CTRL_UnwindFrameNode *frame_node = push_array(scratch.arena, CTRL_UnwindFrameNode, 1); + CTRL_UnwindFrame *frame = &frame_node->v; + frame->cfa = frame_ctx.cfa; + frame->regs = push_array_no_zero(arena, U8, arch_reg_block_size); + MemoryCopy(frame->regs, regs_block, arch_reg_block_size); + DLLPushBack(first_frame_node, last_frame_node, frame_node); + frame_node_count += 1; + + // rjf: unwind one step + CTRL_UnwindStepResult step_result = {0}; + switch(process_entity->target_os) + { + default:{}break; + case OperatingSystem_Windows: + { + switch(arch) + { + default:{}break; + case Arch_x64: + { + step_result = ctrl_unwind_step__pe_x64(process_entity->handle, module_entity->handle, module_entity->vaddr_range.min, regs_block, endt_us); + }break; + } + }break; + case OperatingSystem_Linux: + { + step_result = ctrl_unwind_step__dwarf(process_entity->handle, arch, regs_block, &frame_ctx, endt_us); + }break; + } + + // stop unwinding on errors or stale data + unwind.flags |= step_result.flags; + if(unwind.flags & (CTRL_UnwindFlag_Stale|CTRL_UnwindFlag_Error) || + (regs_rsp_from_arch_block(arch, regs_block) == rsp && regs_rip_from_arch_block(arch, regs_block) == rip)) + { + break; + } + } + } + + //- rjf: bake frames list into result array + { + unwind.frames.count = frame_node_count; + unwind.frames.v = push_array(arena, CTRL_UnwindFrame, unwind.frames.count); + U64 idx = 0; + for(CTRL_UnwindFrameNode *n = first_frame_node; n != 0; n = n->next, idx += 1) + { + unwind.frames.v[idx] = n->v; + } + } + + scratch_end(scratch); + ProfEnd(); + return unwind; +} + +//////////////////////////////// +//~ rjf: Call Stack Building Functions + +internal CTRL_CallStack +ctrl_call_stack_from_unwind(Arena *arena, CTRL_Entity *process, CTRL_Unwind *base_unwind) +{ + Temp scratch = scratch_begin(&arena, 1); + Access *access = access_open(); + Arch arch = process->arch; + CTRL_CallStack result = {0}; + { + typedef struct FrameNode FrameNode; + struct FrameNode + { + FrameNode *next; + CTRL_CallStackFrame v; + }; + + //- rjf: gather all frames + FrameNode *first_frame = 0; + FrameNode *last_frame = 0; + U64 frame_count = 0; + for(U64 base_frame_idx = 0; base_frame_idx < base_unwind->frames.count; base_frame_idx += 1) + { + // rjf: unpack + CTRL_UnwindFrame *src = &base_unwind->frames.v[base_frame_idx]; + U64 rip_vaddr = regs_rip_from_arch_block(arch, src->regs); + CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr); + U64 rip_voff = ctrl_voff_from_vaddr(module, rip_vaddr); + DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); + RDI_Scope *scope = rdi_scope_from_voff(rdi, rip_voff); + + // rjf: build inline frames (minus parent & inline depth) + FrameNode *first_inline_frame = 0; + FrameNode *last_inline_frame = 0; + U64 inline_frame_count = 0; + for(RDI_Scope *s = scope; + s->inline_site_idx != 0; + s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) + { + FrameNode *dst_inline = push_array(scratch.arena, FrameNode, 1); + if(first_inline_frame == 0) + { + first_inline_frame = dst_inline; + } + last_inline_frame = dst_inline; + SLLQueuePush(first_frame, last_frame, dst_inline); + dst_inline->v.unwind_count = base_frame_idx; + dst_inline->v.regs = src->regs; + dst_inline->v.cfa = src->cfa; + frame_count += 1; + inline_frame_count += 1; + } + + // rjf: build concrete frame + FrameNode *dst_base = push_array(scratch.arena, FrameNode, 1); + SLLQueuePush(first_frame, last_frame, dst_base); + dst_base->v.unwind_count = base_frame_idx; + dst_base->v.regs = src->regs; + dst_base->v.cfa = src->cfa; + frame_count += 1; + + // rjf: hook up inline frames to point to concrete frame, and to account for inline depth + U64 inline_frame_idx = 0; + for(FrameNode *inline_frame = first_inline_frame; inline_frame != 0; inline_frame = inline_frame->next, inline_frame_idx += 1) + { + inline_frame->v.inline_depth = inline_frame_count - inline_frame_idx; + if(inline_frame == last_inline_frame) + { + break; + } + } + } + + //- rjf: package + result.frames_count = frame_count; + result.frames = push_array(arena, CTRL_CallStackFrame, result.frames_count); + result.concrete_frames_count = base_unwind->frames.count; + result.concrete_frames = push_array(arena, CTRL_CallStackFrame *, result.concrete_frames_count); + { + U64 idx = 0; + U64 concrete_idx = 0; + for(FrameNode *n = first_frame; n != 0; n = n->next, idx += 1) + { + MemoryCopyStruct(&result.frames[idx], &n->v); + if(n->v.inline_depth == 0 && concrete_idx < result.concrete_frames_count) + { + result.concrete_frames[concrete_idx] = &result.frames[idx]; + concrete_idx += 1; + } + } + } + } + access_close(access); + scratch_end(scratch); + return result; +} + +internal CTRL_CallStackFrame * +ctrl_call_stack_frame_from_unwind_and_inline_depth(CTRL_CallStack *call_stack, U64 unwind_count, U64 inline_depth) +{ + CTRL_CallStackFrame *f = 0; + { + U64 base_frame_idx = 0; + for(U64 idx = 0; idx < call_stack->frames_count; idx += 1) + { + if(call_stack->frames[idx].inline_depth == 0) + { + if(base_frame_idx == unwind_count) + { + f = &call_stack->frames[idx]; + break; + } + base_frame_idx += 1; + } + } + if(f != 0 && call_stack->frames + inline_depth < f) + { + f -= inline_depth; + } + } + return f; +} + +//////////////////////////////// +//~ rjf: Halting All Attached Processes + +internal void +ctrl_halt(void) +{ + dmn_halt(0, 0); +} + +//////////////////////////////// +//~ rjf: Shared Accessor Functions + +//- rjf: generation counters + +internal U64 +ctrl_run_gen(void) +{ + U64 result = ins_atomic_u64_eval(&ctrl_state->run_gen); + return result; +} + +internal U64 +ctrl_mem_gen(void) +{ + U64 result = ins_atomic_u64_eval(&ctrl_state->mem_gen); + return result; +} + +internal U64 +ctrl_reg_gen(void) +{ + U64 result = ins_atomic_u64_eval(&ctrl_state->reg_gen); + return result; +} + +//- rjf: name -> register/alias hash tables, for eval + +internal E_String2NumMap * +ctrl_string2reg_from_arch(Arch arch) +{ + return &ctrl_state->arch_string2reg_tables[arch]; +} + +internal E_String2NumMap * +ctrl_string2alias_from_arch(Arch arch) +{ + return &ctrl_state->arch_string2alias_tables[arch]; +} + +//////////////////////////////// +//~ rjf: Control-Thread Functions + +//- rjf: user -> control thread communication + +internal B32 +ctrl_u2c_push_msgs(CTRL_MsgList *msgs, U64 endt_us) +{ + Temp scratch = scratch_begin(0, 0); + String8 msgs_srlzed_baked = ctrl_serialized_string_from_msg_list(scratch.arena, msgs); + B32 good = 0; + MutexScope(ctrl_state->u2c_ring_mutex) for(;;) + { + U64 unconsumed_size = (ctrl_state->u2c_ring_write_pos-ctrl_state->u2c_ring_read_pos); + U64 available_size = ctrl_state->u2c_ring_size-unconsumed_size; + U64 needed_size = sizeof(msgs_srlzed_baked.size) + msgs_srlzed_baked.size; + if(available_size >= needed_size) + { + ctrl_state->u2c_ring_write_pos += ring_write_struct(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_write_pos, &msgs_srlzed_baked.size); + ctrl_state->u2c_ring_write_pos += ring_write(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_write_pos, msgs_srlzed_baked.str, msgs_srlzed_baked.size); + good = 1; + break; + } + if(os_now_microseconds() >= endt_us) + { + break; + } + cond_var_wait(ctrl_state->u2c_ring_cv, ctrl_state->u2c_ring_mutex, endt_us); + } + if(good) + { + cond_var_broadcast(ctrl_state->u2c_ring_cv); + } + scratch_end(scratch); + return good; +} + +internal CTRL_MsgList +ctrl_u2c_pop_msgs(Arena *arena) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 msgs_srlzed_baked = {0}; + MutexScope(ctrl_state->u2c_ring_mutex) for(;;) + { + U64 unconsumed_size = (ctrl_state->u2c_ring_write_pos-ctrl_state->u2c_ring_read_pos); + if(unconsumed_size >= sizeof(U64)) + { + U64 size_to_decode = 0; + ctrl_state->u2c_ring_read_pos += ring_read_struct(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_read_pos, &size_to_decode); + msgs_srlzed_baked.size = size_to_decode; + msgs_srlzed_baked.str = push_array_no_zero(scratch.arena, U8, msgs_srlzed_baked.size); + ctrl_state->u2c_ring_read_pos += ring_read(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_read_pos, msgs_srlzed_baked.str, size_to_decode); + break; + } + cond_var_wait(ctrl_state->u2c_ring_cv, ctrl_state->u2c_ring_mutex, max_U64); + } + cond_var_broadcast(ctrl_state->u2c_ring_cv); + CTRL_MsgList msgs = ctrl_msg_list_from_serialized_string(arena, msgs_srlzed_baked); + scratch_end(scratch); + return msgs; +} + +//- rjf: control -> user thread communication + +internal void +ctrl_c2u_push_events(CTRL_EventList *events) +{ + if(events->count != 0) ProfScope("ctrl_c2u_push_events") + { + MutexScopeW(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) + { + ctrl_entity_store_apply_events(ctrl_state->ctrl_thread_entity_store, events); + } + for(CTRL_EventNode *n = events->first; n != 0; n = n ->next) + { + Temp scratch = scratch_begin(0, 0); + String8 event_srlzed = ctrl_serialized_string_from_event(scratch.arena, &n->v, ctrl_state->c2u_ring_size-sizeof(U64)); + MutexScope(ctrl_state->c2u_ring_mutex) for(;;) + { + U64 unconsumed_size = (ctrl_state->c2u_ring_write_pos-ctrl_state->c2u_ring_read_pos); + U64 available_size = ctrl_state->c2u_ring_size-unconsumed_size; + U64 needed_size = sizeof(event_srlzed.size) + event_srlzed.size; + if(available_size >= needed_size) + { + ctrl_state->c2u_ring_write_pos += ring_write_struct(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_write_pos, &event_srlzed.size); + ctrl_state->c2u_ring_write_pos += ring_write(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_write_pos, event_srlzed.str, event_srlzed.size); + break; + } + cond_var_wait(ctrl_state->c2u_ring_cv, ctrl_state->c2u_ring_mutex, os_now_microseconds()+100); + } + cond_var_broadcast(ctrl_state->c2u_ring_cv); + if(ctrl_state->wakeup_hook != 0) + { + ctrl_state->wakeup_hook(); + } + scratch_end(scratch); + } + } +} + +internal CTRL_EventList +ctrl_c2u_pop_events(Arena *arena) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + CTRL_EventList events = {0}; + MutexScope(ctrl_state->c2u_ring_mutex) for(;;) + { + U64 unconsumed_size = (ctrl_state->c2u_ring_write_pos-ctrl_state->c2u_ring_read_pos); + if(unconsumed_size >= sizeof(U64)) + { + U64 size_to_decode = 0; + ctrl_state->c2u_ring_read_pos += ring_read_struct(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_read_pos, &size_to_decode); + String8 event_srlzed = {0}; + event_srlzed.size = size_to_decode; + event_srlzed.str = push_array_no_zero(scratch.arena, U8, event_srlzed.size); + ctrl_state->c2u_ring_read_pos += ring_read(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_read_pos, event_srlzed.str, event_srlzed.size); + CTRL_Event *new_event = ctrl_event_list_push(arena, &events); + *new_event = ctrl_event_from_serialized_string(arena, event_srlzed); + } + else + { + break; + } + } + cond_var_broadcast(ctrl_state->c2u_ring_cv); + scratch_end(scratch); + ProfEnd(); + return events; +} + +//- rjf: entry point + +internal void +ctrl_thread__entry_point(void *p) +{ + ThreadNameF("ctrl_thread"); + ProfBeginFunction(); + DMN_CtrlCtx *ctrl_ctx = dmn_ctrl_begin(); + log_select(ctrl_state->ctrl_thread_log); + + //- rjf: loop + Temp scratch = scratch_begin(0, 0); + for(;;) + { + temp_end(scratch); + log_scope_begin(); + + //- rjf: get next messages + CTRL_MsgList msgs = ctrl_u2c_pop_msgs(scratch.arena); + + //- rjf: process messages + DMN_CtrlExclusiveAccessScope + { + ins_atomic_u64_eval_assign(&ctrl_state->ctrl_thread_run_state, 1); + for(CTRL_MsgNode *msg_n = msgs.first; msg_n != 0; msg_n = msg_n->next) + { + CTRL_Msg *msg = &msg_n->v; + { + log_infof("user2ctrl_msg:{kind:\"%S\"}\n", ctrl_string_from_msg_kind(msg->kind)); + } + + //- rjf: reset per-message state + ProfScope("reset per-message state") + { + arena_clear(ctrl_state->ctrl_thread_msg_process_arena); + ctrl_state->module_req_cache_slots_count = 4096; + ctrl_state->module_req_cache_slots = push_array(ctrl_state->ctrl_thread_msg_process_arena, CTRL_ModuleReqCacheNode *, ctrl_state->module_req_cache_slots_count); + MemoryZeroStruct(&ctrl_state->msg_user_bp_touched_files); + MemoryZeroStruct(&ctrl_state->msg_user_bp_touched_symbols); + MemoryCopyArray(ctrl_state->exception_code_filters, msg->exception_code_filters); + } + + //- rjf: gather all touched symbols by user breakpoints + { + Temp scratch = scratch_begin(0, 0); + for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) + { + if(n->v.kind != CTRL_UserBreakpointKind_Expression) + { + continue; + } + E_Parse addr_parse = e_push_parse_from_string(scratch.arena, n->v.string); + E_Parse cnd_parse = e_push_parse_from_string(scratch.arena, n->v.condition); + E_Expr *exprs[] = {addr_parse.expr, cnd_parse.expr}; + for EachElement(idx, exprs) + { + typedef struct ExprWalkTask ExprWalkTask; + struct ExprWalkTask + { + ExprWalkTask *next; + E_Expr *expr; + }; + ExprWalkTask start_task = {0, exprs[idx]}; + ExprWalkTask *first_task = &start_task; + for(ExprWalkTask *t = first_task; t != 0; t = t->next) + { + E_Expr *expr = t->expr; + if(expr->ref != &e_expr_nil) + { + expr = expr->ref; + } + if(expr->kind == E_ExprKind_LeafIdentifier) + { + str8_list_push(ctrl_state->ctrl_thread_msg_process_arena, &ctrl_state->msg_user_bp_touched_symbols, expr->string); + } + if(expr->next != &e_expr_nil) + { + ExprWalkTask *task = push_array(scratch.arena, ExprWalkTask, 1); + task->expr = expr->next; + task->next = t->next; + t->next = task; + } + if(expr->first != &e_expr_nil) + { + ExprWalkTask *task = push_array(scratch.arena, ExprWalkTask, 1); + task->expr = expr->first; + task->next = t->next; + t->next = task; + } + } + } + } + scratch_end(scratch); + } + + + + + + //- rjf: gather all touched files by user breakpoints + for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) + { + if(n->v.kind != CTRL_UserBreakpointKind_FileNameAndLineColNumber) + { + continue; + } + str8_list_push(ctrl_state->ctrl_thread_msg_process_arena, &ctrl_state->msg_user_bp_touched_files, n->v.string); + } + + //- rjf: process message + switch(msg->kind) + { + case CTRL_MsgKind_Null: + case CTRL_MsgKind_COUNT:{}break; + + //- rjf: target operations + case CTRL_MsgKind_Launch: {ctrl_thread__launch (ctrl_ctx, msg);}break; + case CTRL_MsgKind_Attach: {ctrl_thread__attach (ctrl_ctx, msg);}break; + case CTRL_MsgKind_Kill: {ctrl_thread__kill (ctrl_ctx, msg);}break; + case CTRL_MsgKind_KillAll: {ctrl_thread__kill_all (ctrl_ctx, msg);}break; + case CTRL_MsgKind_Detach: {ctrl_thread__detach (ctrl_ctx, msg);}break; + case CTRL_MsgKind_Run: {ctrl_thread__run (ctrl_ctx, msg);}break; + case CTRL_MsgKind_SingleStep: {ctrl_thread__single_step (ctrl_ctx, msg);}break; + + //- rjf: configuration + case CTRL_MsgKind_SetUserEntryPoints: + { + arena_clear(ctrl_state->user_entry_point_arena); + MemoryZeroStruct(&ctrl_state->user_entry_points); + for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) + { + str8_list_push(ctrl_state->user_entry_point_arena, &ctrl_state->user_entry_points, n->string); + } + }break; + case CTRL_MsgKind_SetModuleDebugInfoPath: + { + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + String8 path = msg->path; + CTRL_Entity *module = ctrl_entity_from_handle(entity_ctx, msg->entity); + CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); + DI_Key old_dbgi_key = di_key_from_path_timestamp(debug_info_path->string, debug_info_path->timestamp); + di_close(old_dbgi_key, 0); + MutexScopeW(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) + { + ctrl_entity_equip_string(ctrl_state->ctrl_thread_entity_store, debug_info_path, path_normalized_from_string(scratch.arena, path)); + } + U64 new_dbgi_timestamp = os_properties_from_file_path(path).modified; + debug_info_path->timestamp = new_dbgi_timestamp; + DI_Key new_dbgi_key = di_key_from_path_timestamp(debug_info_path->string, new_dbgi_timestamp); + di_open(new_dbgi_key); + CTRL_EventList evts = {0}; + CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); + evt->kind = CTRL_EventKind_ModuleDebugInfoPathChange; + evt->entity = msg->entity; + evt->string = path; + evt->timestamp = new_dbgi_timestamp; + ctrl_c2u_push_events(&evts); + }break; + case CTRL_MsgKind_FreezeThread: + { + CTRL_EventList evts = {0}; + CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); + evt->kind = CTRL_EventKind_ThreadFrozen; + evt->entity = msg->entity; + ctrl_c2u_push_events(&evts); + }break; + case CTRL_MsgKind_ThawThread: + { + CTRL_EventList evts = {0}; + CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); + evt->kind = CTRL_EventKind_ThreadThawed; + evt->entity = msg->entity; + ctrl_c2u_push_events(&evts); + }break; + } + } + ins_atomic_u64_eval_assign(&ctrl_state->ctrl_thread_run_state, 0); + } + ins_atomic_u64_inc_eval(&ctrl_state->run_gen); + ins_atomic_u64_inc_eval(&ctrl_state->mem_gen); + ins_atomic_u64_inc_eval(&ctrl_state->reg_gen); + + //- rjf: update thread register cache + ProfScope("update thread register cache") + { + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + CTRL_EntityArray threads = ctrl_entity_array_from_kind(entity_ctx, CTRL_EntityKind_Thread); + REGS_RegBlockX64 *blocks = push_array(scratch.arena, REGS_RegBlockX64, threads.count); + { + for EachIndex(idx, threads.count) + { + Temp scratch = scratch_begin(0, 0); + ctrl_reg_block_from_thread(scratch.arena, entity_ctx, threads.v[idx]->handle); + scratch_end(scratch); + } + } + } + + //- rjf: gather & output logs + ctrl_thread__end_and_flush_log(); + } + + scratch_end(scratch); + ProfEnd(); +} + +//- rjf: breakpoint resolution + +internal void +ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, CTRL_EvalScope *eval_scope, CTRL_Handle process, CTRL_Handle module, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out) +{ + if(user_bps->first == 0) { return; } + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + Access *access = eval_scope->access; + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + CTRL_Entity *module_entity = ctrl_entity_from_handle(entity_ctx, module); + CTRL_Entity *debug_info_path_entity = ctrl_entity_child_from_kind(module_entity, CTRL_EntityKind_DebugInfoPath); + DI_Key dbgi_key = ctrl_dbgi_key_from_module(module_entity); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); + U64 base_vaddr = module_entity->vaddr_range.min; + for(CTRL_UserBreakpointNode *n = user_bps->first; n != 0; n = n->next) + { + CTRL_UserBreakpoint *bp = &n->v; + switch(bp->kind) + { + default:{}break; + + //- rjf: file:line-based breakpoints + case CTRL_UserBreakpointKind_FileNameAndLineColNumber: + { + // rjf: unpack & normalize + TxtPt pt = bp->pt; + String8 filename = bp->string; + String8 filename_normalized = push_str8_copy(scratch.arena, filename); + for(U64 idx = 0; idx < filename_normalized.size; idx += 1) + { + filename_normalized.str[idx] = lower_from_char(filename_normalized.str[idx]); + filename_normalized.str[idx] = correct_slash_from_char(filename_normalized.str[idx]); + } + + // rjf: filename -> src_id + U32 src_id = 0; + { + RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); + if(mapptr != 0) + { + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, mapptr, &map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, filename_normalized.str, filename_normalized.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + src_id = ids[0]; + } + } + } + } + + // rjf: src_id * pt -> push + if(src_id != 0) + { + RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); + RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); + RDI_ParsedSourceLineMap line_map = {0}; + rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); + U32 voff_count = 0; + U64 *voffs = rdi_line_voffs_from_num(&line_map, pt.line, &voff_count); + for(U32 i = 0; i < voff_count; i += 1) + { + U64 vaddr = voffs[i] + base_vaddr; + DMN_Trap trap = {process.dmn_handle, vaddr, (U64)bp}; + dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); + } + } + }break; + + //- rjf: expression-based breakpoints + case CTRL_UserBreakpointKind_Expression: + { + String8 expr = bp->string; + E_Value value = e_value_from_string(expr); + if(value.u64 != 0 || bp->flags != 0) + { + DMN_Trap trap = {process.dmn_handle, value.u64, (U64)bp}; + trap.flags = ctrl_dmn_trap_flags_from_user_breakpoint_flags(bp->flags); + trap.size = bp->size; + dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); + } + }break; + } + } + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__append_resolved_process_user_bp_traps(Arena *arena, CTRL_EvalScope *eval_scope, CTRL_Handle process, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out) +{ + for(CTRL_UserBreakpointNode *n = user_bps->first; n != 0; n = n->next) + { + CTRL_UserBreakpoint *bp = &n->v; + if(bp->kind == CTRL_UserBreakpointKind_Expression) + { + String8 expr = bp->string; + E_Value value = e_value_from_string(expr); + if(value.u64 != 0 || bp->flags != 0) + { + DMN_Trap trap = {process.dmn_handle, value.u64, (U64)bp}; + trap.flags = ctrl_dmn_trap_flags_from_user_breakpoint_flags(bp->flags); + trap.size = bp->size; + dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); + } + } + } +} + +internal void +ctrl_thread__append_program_defined_bp_traps(Arena *arena, CTRL_Entity *bp, DMN_TrapChunkList *traps_out) +{ + CTRL_Entity *process = bp->parent; + DMN_Trap trap = + { + .process = process->handle.dmn_handle, + .vaddr = bp->vaddr_range.min, + .id = ((U64)bp|bit64), + .flags = ctrl_dmn_trap_flags_from_user_breakpoint_flags(bp->bp_flags), + .size = (U32)dim_1u64(bp->vaddr_range), + }; + dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); +} + +//- rjf: module lifetime open/close work + +internal +MACHINE_OP_MEM_READ(ctrl_dmn_mem_read) +{ + U64 read = dmn_process_read(((CTRL_Handle *)ud)->dmn_handle, r1u64(addr, addr + buffer_size), buffer); + return read == buffer_size ? MachineOpResult_Ok : MachineOpResult_Fail; +} + +internal void +ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range, String8 path, Rng1U64 elf_phdr_vrange, U64 elf_phdr_entsize) +{ + Temp scratch = scratch_begin(0,0); + + //- rjf: set up per-module info + Arena *arena = arena_alloc(); + U64 entry_point_voff = 0; + U32 rdi_dbg_time = 0; + Guid rdi_dbg_guid = {0}; + String8 exe_dbg_path = {0}; + String8 rdi_dbg_path = {0}; + String8 raddbg_data = {0}; + Rng1U64 raddbg_section_voff_range = {0}; + Rng1U64 raddbg_is_attached_section_voff_range = {0}; + PE_IntelPdata *pdatas = 0; + U64 pdatas_count = 0; + U32 pdb_dbg_time = 0; + U32 pdb_dbg_age = 0; + Guid pdb_dbg_guid = {0}; + String8 pdb_dbg_path = {0}; + U64 cfi_rebase = 0; + B32 is_unwind_eh = 0; + EH_FrameHdr eh_frame_hdr = {0}; + EH_PtrCtx eh_ptr_ctx = { .pc_vaddr = max_U64, .text_vaddr = max_U64, .data_vaddr = max_U64, .func_vaddr = max_U64, .ptr_align = 0 }; + + //- read module's signature bytes + U64 module_sig_size = Max(elf_magic_string.size, sizeof(PE_DosMagic)); + U8 *module_sig_bytes = push_array(scratch.arena, U8, module_sig_size); + dmn_process_read(process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + module_sig_size), module_sig_bytes); + + ////////////////////////////// + //- parse PE module + // + PE_DosMagic dos_magic = *(PE_DosMagic *)module_sig_bytes; + if(dos_magic == PE_DOS_MAGIC) + { + ProfScope("unpack relevant PE info") + { + B32 is_valid = 1; + + //- rjf: read DOS header + PE_DosHeader dos_header = {0}; + if(is_valid) + { + if(!dmn_process_read_struct(process.dmn_handle, vaddr_range.min, &dos_header) || + dos_header.magic != PE_DOS_MAGIC) + { + is_valid = 0; + } + } + + //- rjf: read PE magic + U32 pe_magic = 0; + if(is_valid) + { + if(!dmn_process_read_struct(process.dmn_handle, vaddr_range.min + dos_header.coff_file_offset, &pe_magic) || + pe_magic != PE_MAGIC) + { + is_valid = 0; + } + } + + //- rjf: read COFF header + U64 file_header_off = dos_header.coff_file_offset + sizeof(pe_magic); + COFF_FileHeader file_header = {0}; + if(is_valid) + { + if(!dmn_process_read_struct(process.dmn_handle, vaddr_range.min + file_header_off, &file_header)) + { + is_valid = 0; + } + } + + //- rjf: unpack range of optional extension header + U32 opt_ext_size = file_header.optional_header_size; + Rng1U64 opt_ext_off_range = r1u64(file_header_off + sizeof(COFF_FileHeader), + file_header_off + sizeof(COFF_FileHeader) + opt_ext_size); + + //- rjf: read optional header + U64 entry_point = 0; + U32 data_dir_count = 0; + if(opt_ext_size > 0) + { + // rjf: read magic number + U16 opt_ext_magic = 0; + dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &opt_ext_magic); + + // rjf: read info + U32 reported_data_dir_offset = 0; + U32 reported_data_dir_count = 0; + switch(opt_ext_magic) + { + case PE_PE32_MAGIC: + { + PE_OptionalHeader32 pe_optional = {0}; + dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &pe_optional); + entry_point = pe_optional.entry_point_va; + reported_data_dir_offset = sizeof(pe_optional); + reported_data_dir_count = pe_optional.data_dir_count; + }break; + case PE_PE32PLUS_MAGIC: + { + PE_OptionalHeader32Plus pe_optional = {0}; + dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min, &pe_optional); + entry_point = pe_optional.entry_point_va; + reported_data_dir_offset = sizeof(pe_optional); + reported_data_dir_count = pe_optional.data_dir_count; + }break; + } + + // rjf: find number of data directories + U32 data_dir_max = (opt_ext_size - reported_data_dir_offset) / sizeof(PE_DataDirectory); + data_dir_count = ClampTop(reported_data_dir_count, data_dir_max); + + // rjf: grab pdatas from exceptions section + if(data_dir_count > PE_DataDirectoryIndex_EXCEPTIONS) + { + PE_DataDirectory dir = {0}; + dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_EXCEPTIONS, &dir); + Rng1U64 pdatas_voff_range = r1u64((U64)dir.virt_off, (U64)dir.virt_off + (U64)dir.virt_size); + pdatas_count = dim_1u64(pdatas_voff_range)/sizeof(PE_IntelPdata); + pdatas = push_array(arena, PE_IntelPdata, pdatas_count); + dmn_process_read(process.dmn_handle, r1u64(vaddr_range.min + pdatas_voff_range.min, vaddr_range.min + pdatas_voff_range.max), pdatas); + } + + // rjf: extract sections + U64 sec_array_off = opt_ext_off_range.max; + U64 sec_count = file_header.section_count; + COFF_SectionHeader *sec = push_array(scratch.arena, COFF_SectionHeader, sec_count); + dmn_process_read(process.dmn_handle, r1u64(vaddr_range.min + sec_array_off, vaddr_range.min + sec_array_off + sec_count*sizeof(COFF_SectionHeader)), sec); + + // rjf: grab entry point vaddr + entry_point_voff = entry_point; + + // rjf: grab data about debug info + if(data_dir_count > PE_DataDirectoryIndex_DEBUG) + { + // rjf: read data dir + PE_DataDirectory dir = {0}; + dmn_process_read_struct(process.dmn_handle, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_DEBUG, &dir); + + U64 dbg_dir_count = dir.virt_size / sizeof(PE_DebugDirectory); + for(U64 dbg_dir_idx = 0; dbg_dir_idx < dbg_dir_count; dbg_dir_idx += 1) + { + // rjf: read debug directory + U64 dir_addr = vaddr_range.min + dir.virt_off + dbg_dir_idx * sizeof(PE_DebugDirectory); + PE_DebugDirectory dbg_data = {0}; + dmn_process_read_struct(process.dmn_handle, dir_addr, &dbg_data); + + // rjf: extract external file info from codeview header + if(dbg_data.type == PE_DebugDirectoryType_CODEVIEW) + { + U32 cv_magic = 0; + dmn_process_read_struct(process.dmn_handle, vaddr_range.min + dbg_data.voff, &cv_magic); + switch(cv_magic) + { + default:break; + case PE_CODEVIEW_PDB20_MAGIC: + { + PE_CvHeaderPDB20 cv; + U64 read_size = dmn_process_read_struct(process.dmn_handle, vaddr_range.min+dbg_data.voff, &cv); + if(read_size == sizeof(cv)) + { + pdb_dbg_time = cv.time_stamp; + pdb_dbg_age = cv.age; + pdb_dbg_path = dmn_process_read_cstring(arena, process.dmn_handle, vaddr_range.min + dbg_data.voff + sizeof(cv)); + } + }break; + case PE_CODEVIEW_PDB70_MAGIC: + { + PE_CvHeaderPDB70 cv; + U64 read_size = dmn_process_read_struct(process.dmn_handle, vaddr_range.min + dbg_data.voff, &cv); + if(read_size == sizeof(cv)) + { + pdb_dbg_guid = cv.guid; + pdb_dbg_age = cv.age; + pdb_dbg_path = dmn_process_read_cstring(arena, process.dmn_handle, vaddr_range.min + dbg_data.voff + sizeof(cv)); + } + }break; + case PE_CODEVIEW_RDI_MAGIC: + { + PE_CvHeaderRDI cv; + U64 read_size = dmn_process_read_struct(process.dmn_handle, vaddr_range.min + dbg_data.voff, &cv); + if(read_size == sizeof(cv)) + { + rdi_dbg_guid = cv.guid; + rdi_dbg_path = dmn_process_read_cstring(arena, process.dmn_handle, vaddr_range.min + dbg_data.voff + sizeof(cv)); + } + }break; + } + } + } + } + + // rjf: look for DWARF debug info + { + U64 symbol_array_off = file_header.symbol_table_foff; + U64 symbol_count = file_header.symbol_count; + if(symbol_array_off != 0) + { + exe_dbg_path = path; + } + } + + // rjf: extract copy of module's raddbg data + { + for EachIndex(idx, sec_count) + { + String8 section_name = str8_cstring((char *)sec[idx].name); + if(str8_match(section_name, str8_lit(".raddbg"), 0)) + { + raddbg_section_voff_range.min = sec[idx].voff; + raddbg_section_voff_range.max = sec[idx].voff + sec[idx].vsize; + } + else if(str8_match(section_name, str8_lit(".rdbgia"), 0)) + { + raddbg_is_attached_section_voff_range.min = sec[idx].voff; + raddbg_is_attached_section_voff_range.max = sec[idx].voff + sec[idx].vsize; + } + } + raddbg_data.size = dim_1u64(raddbg_section_voff_range); + raddbg_data.str = push_array(arena, U8, raddbg_data.size); + dmn_process_read(process.dmn_handle, r1u64(vaddr_range.min + raddbg_section_voff_range.min, + vaddr_range.min + raddbg_section_voff_range.max), raddbg_data.str); + } + + // rjf: if we have a "raddbg is attached" section, mark the first byte as 1, to signify attachment + if(raddbg_is_attached_section_voff_range.max != raddbg_is_attached_section_voff_range.min) + { + U8 new_value = 1; + dmn_process_write_struct(process.dmn_handle, vaddr_range.min + raddbg_is_attached_section_voff_range.min, &new_value); + } + } + } + } + + ////////////////////////////// + //- parse ELF module + // + else if(str8_match(str8(module_sig_bytes, elf_magic_string.size), elf_magic_string, 0)) + { + U64 e_entry = 0; + ELF_Type e_type = ELF_Type_None; + { + U8 *elf_sig = dmn_process_read_raw(scratch.arena, process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + sizeof(elf_sig[0]) * ELF_Identifier_Max)); + if(elf_sig == 0) { goto elf_exit; } + switch(elf_sig[ELF_Identifier_Class]) + { + default: + case ELF_Class_None:{}break; + case ELF_Class_32: + { + NotImplemented; + }break; + case ELF_Class_64: + { + ELF_Hdr64 *ehdr = dmn_process_read_raw(scratch.arena, process.dmn_handle, rng_1u64(vaddr_range.min, vaddr_range.min + sizeof(*ehdr))); + if(ehdr == 0) { goto elf_exit; } + e_entry = ehdr->e_entry; + e_type = ehdr->e_type; + }break; + } + } + + if(e_type == ELF_Type_Dyn) + { + cfi_rebase = vaddr_range.min; + } + + // find and parse .eh_frame_hdr + Rng1U64 eh_frame_hdr_vrange = {0}; + String8 eh_frame_hdr_data = {0}; + { + void *phdrs_raw = dmn_process_read_raw(scratch.arena, process.dmn_handle, elf_phdr_vrange); + if(phdrs_raw == 0) { goto elf_exit; } + + if(elf_phdr_entsize == sizeof(ELF_Phdr32)) + { + NotImplemented; + } + else if(elf_phdr_entsize == sizeof(ELF_Phdr64)) + { + U64 elf_phcount = dim_1u64(elf_phdr_vrange) / elf_phdr_entsize; + ELF_Phdr64 *phdrs64 = phdrs_raw; + for EachIndex(phdr_idx, elf_phcount) + { + ELF_Phdr64 *phdr = phdrs64 + phdr_idx; + if(phdr->p_type == ELF_PType_GnuEHFrame) + { + eh_frame_hdr_vrange = r1u64(cfi_rebase + phdr->p_vaddr, cfi_rebase + phdr->p_vaddr + phdr->p_memsz); + eh_frame_hdr_data = dmn_process_read_block(arena, process.dmn_handle, eh_frame_hdr_vrange); + is_unwind_eh = 1; + break; + } + } + } + } + + // parse .eh_frame_hdr + Arch arch = ctrl_arch_from_process_handle(process); + eh_ptr_ctx.pc_vaddr = eh_frame_hdr_vrange.min; + eh_ptr_ctx.data_vaddr = eh_frame_hdr_vrange.min; + eh_frame_hdr = eh_parse_frame_hdr(eh_frame_hdr_data, byte_size_from_arch(arch), &eh_ptr_ctx); + + // set entry point + if (e_entry != 0) + { + entry_point_voff = e_entry - vaddr_range.min; + } + + // TODO: is there a way to detect DWARF in runtime ELF? + if(1) + { + exe_dbg_path = path; + } + elf_exit:; + } + + ////////////////////////////// + //- rjf: pick default initial debug info path + // + String8 initial_debug_info_path = str8_zero(); + { + String8 exe_folder = str8_chop_last_slash(path); + String8List dbg_path_candidates = {0}; + // + //~ TODO(rjf): @linux_port PLEASE READ RYAN vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + // + // TODO(rjf): trying "exe_folder/embedded_path" as the first option is only a valid + // heuristic on Windows, because we know that two absolute paths concatted together + // are necessarily invalid. however, on Linux, this is not the case - you could stitch + // two paths together and get a third path that is completely valid. so, in that case, + // we will need to infer if the path is relative, and then use either the embedded + // path as-is, or the exe-relative-path accordingly, depending on that. + // + if(rdi_dbg_path.size != 0) + { + str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S/%S", exe_folder, rdi_dbg_path); + str8_list_push(scratch.arena, &dbg_path_candidates, rdi_dbg_path); + } + if(exe_dbg_path.size != 0) + { + str8_list_push(scratch.arena, &dbg_path_candidates, path); + } + if(pdb_dbg_path.size != 0) + { + str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S/%S", exe_folder, pdb_dbg_path); + str8_list_push(scratch.arena, &dbg_path_candidates, pdb_dbg_path); + } + str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S.pdb", str8_chop_last_dot(path)); + str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S.pdb", path); + str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S.rdi", str8_chop_last_dot(path)); + str8_list_pushf(scratch.arena, &dbg_path_candidates, "%S.rdi", path); + for(String8Node *n = dbg_path_candidates.first; n != 0; n = n->next) + { + String8 candidate_path = n->string; + FileProperties props = os_properties_from_file_path(candidate_path); + if(props.modified != 0 && props.size != 0) + { + initial_debug_info_path = push_str8_copy(arena, path_normalized_from_string(scratch.arena, candidate_path)); + break; + } + } + } + + ////////////////////////////// + //- rjf: insert info into cache + // + { + U64 hash = ctrl_hash_from_handle(module); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + MutexScopeW(stripe->rw_mutex) + { + CTRL_ModuleImageInfoCacheNode *node = 0; + for EachNode(n, CTRL_ModuleImageInfoCacheNode, slot->first) + { + if(ctrl_handle_match(n->module, module)) + { + node = n; + break; + } + } + if(!node) + { + node = push_array(arena, CTRL_ModuleImageInfoCacheNode, 1); + DLLPushBack(slot->first, slot->last, node); + node->module = module; + node->arena = arena; + node->pdatas = pdatas; + node->pdatas_count = pdatas_count; + node->cfi_rebase = cfi_rebase; + node->is_unwind_eh = is_unwind_eh; + node->eh_frame_hdr = eh_frame_hdr; + node->eh_ptr_ctx = eh_ptr_ctx; + node->entry_point_voff = entry_point_voff; + node->initial_debug_info_path = initial_debug_info_path; + node->raddbg_attached_marker_voff = raddbg_is_attached_section_voff_range.min; + node->raddbg_data = str8_copy(arena, raddbg_data); + } + } + } + + scratch_end(scratch); +} + +internal void +ctrl_thread__module_close(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range) +{ + ////////////////////////////// + //- rjf: evict module image info from cache + // + U64 raddbg_attached_marker_voff = 0; + { + U64 hash = ctrl_hash_from_handle(module); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + MutexScopeW(stripe->rw_mutex) + { + CTRL_ModuleImageInfoCacheNode *node = 0; + for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->module, module)) + { + node = n; + break; + } + } + if(node) + { + raddbg_attached_marker_voff = node->raddbg_attached_marker_voff; + DLLRemove(slot->first, slot->last, node); + arena_release(node->arena); + } + } + } + + ////////////////////////////// + //- rjf: write 0 at attachment market, to signify detachment + // + if(raddbg_attached_marker_voff != 0) + { + U8 new_value = 0; + dmn_process_write_struct(process.dmn_handle, vaddr_range.min + raddbg_attached_marker_voff, &new_value); + } +} + +//- rjf: attached process running/event gathering + +internal DMN_Event * +ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg, DMN_RunCtrls *run_ctrls, CTRL_Spoof *spoof) +{ + ProfBeginFunction(); + DMN_Event *event = push_array(arena, DMN_Event, 1); + Temp scratch = scratch_begin(&arena, 1); + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + + //- rjf: loop -> try to get event, run, repeat + U64 spoof_old_ip_value = 0; + ProfScope("loop -> try to get event, run, repeat") for(B32 got_event = 0; got_event == 0;) + { + //- rjf: get next event + ProfScope("get next event") + { + // rjf: grab first event + DMN_EventNode *next_event_node = ctrl_state->first_dmn_event_node; + + // rjf: log event + if(next_event_node != 0) + { + DMN_Event *ev = &next_event_node->v; + LogInfoNamedBlockF("dmn_event") + { + log_infof("kind: %S\n", dmn_event_kind_string_table[ev->kind]); + log_infof("exception_kind: %S\n", dmn_exception_kind_string_table[ev->exception_kind]); + log_infof("process: [%I64u]\n", ev->process.u64[0]); + log_infof("thread: [%I64u]\n", ev->thread.u64[0]); + log_infof("module: [%I64u]\n", ev->module.u64[0]); + log_infof("arch: %S\n", string_from_arch(ev->arch)); + log_infof("address: 0x%I64x\n", ev->address); + log_infof("string: \"%S\"\n", ev->string); + log_infof("ip_vaddr: 0x%I64x\n", ev->instruction_pointer); + } + } + + // rjf: determine if we should filter + B32 should_filter_event = 0; + if(next_event_node != 0) + { + DMN_Event *ev = &next_event_node->v; + switch(ev->kind) + { + default:{}break; + case DMN_EventKind_Exception: + { + // NOTE(rjf): first chance exceptions -> try ignoring + should_filter_event = (ev->exception_repeated == 0 && (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)); + + // rjf: exception code -> kind + CTRL_ExceptionCodeKind code_kind = CTRL_ExceptionCodeKind_Null; + if(should_filter_event) + { + for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) + { + if(ctrl_exception_code_kind_code_table[k] == ev->code) + { + code_kind = k; + break; + } + } + } + + // rjf: exception code kind -> shouldn't stop? if so, do not filter + if(should_filter_event) + { + B32 shouldnt_filter = !!(ctrl_state->exception_code_filters[code_kind/64] & (1ull<<(code_kind%64))); + if(should_filter_event && shouldnt_filter) + { + should_filter_event = 0; + } + } + + // rjf: special case: be gracious with ASan modules or symbols if + // they do their cute little 0xc0000005 exception trick... + if(!should_filter_event && ev->code == 0xc0000005 && + (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)) + { + Access *access = access_open(); + CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, ev->process)); + CTRL_Entity *module = &ctrl_entity_nil; + for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) + { + if(child->kind == CTRL_EntityKind_Module) + { + module = child; + break; + } + } + if(module != &ctrl_entity_nil) + { + // rjf: determine base address of asan shadow space + U64 asan_shadow_base_vaddr = 0; + B32 asan_shadow_variable_exists_but_is_zero = 0; + DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, max_U64); + RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_GlobalVariables); + { + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, unparsed_map, &map); + String8 name = str8_lit("__asan_shadow_memory_dynamic_address"); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + RDI_Symbol *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, ids[0]); + U64 global_var_voff = rdi_voff_from_location(global_var->location); + U64 global_var_vaddr = global_var_voff + module->vaddr_range.min; + Arch arch = process->arch; + U64 addr_size = bit_size_from_arch(arch)/8; + dmn_process_read(ev->process, r1u64(global_var_vaddr, global_var_vaddr+addr_size), &asan_shadow_base_vaddr); + asan_shadow_variable_exists_but_is_zero = (asan_shadow_base_vaddr == 0); + } + } + } + + // rjf: determine if this was a read/write to the shadow space + B32 violation_in_shadow_space = 0; + if(asan_shadow_base_vaddr != 0) + { + U64 asan_shadow_space_size = TB(128)/8; + if(asan_shadow_base_vaddr <= ev->address && ev->address < asan_shadow_base_vaddr+asan_shadow_space_size) + { + violation_in_shadow_space = 1; + } + } + + // rjf: filter event if this violation occurred in asan's shadow space + if(violation_in_shadow_space || asan_shadow_variable_exists_but_is_zero) + { + should_filter_event = 1; + } + } + + access_close(access); + } + }break; + } + } + + // rjf: good event & unfiltered? -> pop from queue & grab as result + if(next_event_node != 0 && !should_filter_event) + { + got_event = 1; + SLLQueuePop(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node); + MemoryCopyStruct(event, &next_event_node->v); + event->string = push_str8_copy(arena, event->string); + run_ctrls->ignore_previous_exception = 1; + } + + // rjf: good event but filtered? pop from queue + if(next_event_node != 0 && should_filter_event) + { + SLLQueuePop(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node); + run_ctrls->ignore_previous_exception = 0; + } + } + + //- rjf: no event -> dmn_ctrl_run for a new one + if(got_event == 0) ProfScope("no event -> dmn_ctrl_run for a new one") + { + // rjf: prep spoof + B32 do_spoof = (spoof != 0 && dmn_handle_match(run_ctrls->single_step_thread, dmn_handle_zero())); + U64 size_of_spoof = 0; + if(do_spoof) ProfScope("prep spoof") + { + CTRL_Entity *spoof_process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, spoof->process)); + Arch arch = spoof_process->arch; + size_of_spoof = bit_size_from_arch(arch)/8; + dmn_process_read(spoof_process->handle.dmn_handle, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); + } + + // rjf: set spoof + if(do_spoof) ProfScope("set spoof") + { + dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof->new_ip_value); + } + + // rjf: run for new events + ProfScope("run for new events") + { + LogInfoNamedBlockF("dmn_ctrl_run") + { + log_infof("single_step_thread: [0x%I64x]\n", run_ctrls->single_step_thread); + log_infof("ignore_previous_exception: %i\n", !!run_ctrls->ignore_previous_exception); + log_infof("run_entities_are_unfrozen: %i\n", !!run_ctrls->run_entities_are_unfrozen); + log_infof("run_entities_are_processes: %i\n", !!run_ctrls->run_entities_are_processes); + log_infof("run_entity_count: %I64u\n", run_ctrls->run_entity_count); + LogInfoNamedBlockF("run_entities") for(U64 idx = 0; idx < run_ctrls->run_entity_count; idx += 1) + { + log_infof("[0x%I64x]\n", run_ctrls->run_entities[idx]); + } + log_infof("trap_count: %I64u\n", run_ctrls->traps.trap_count); + LogInfoNamedBlockF("traps") for(DMN_TrapChunkNode *n = run_ctrls->traps.first; n != 0; n = n->next) + { + for(U64 idx = 0; idx < n->count; idx += 1) + { + log_infof("{process:[0x%I64x], vaddr:0x%I64x, id:0x%I64x}\n", n->v[idx].process.u64[0], n->v[idx].vaddr, n->v[idx].id); + } + } + } + + DMN_EventList events = dmn_ctrl_run(scratch.arena, ctrl_ctx, run_ctrls); + ins_atomic_u64_inc_eval(&ctrl_state->mem_gen); + ins_atomic_u64_inc_eval(&ctrl_state->reg_gen); + ins_atomic_u64_inc_eval(&ctrl_state->run_gen); + for(DMN_EventNode *src_n = events.first; src_n != 0; src_n = src_n->next) + { + DMN_EventNode *dst_n = ctrl_state->free_dmn_event_node; + if(dst_n != 0) + { + SLLStackPop(ctrl_state->free_dmn_event_node); + } + else + { + dst_n = push_array(ctrl_state->dmn_event_arena, DMN_EventNode, 1); + } + MemoryCopyStruct(&dst_n->v, &src_n->v); + dst_n->v.string = push_str8_copy(ctrl_state->dmn_event_arena, dst_n->v.string); + SLLQueuePush(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node, dst_n); + } + } + + // rjf: unset spoof + if(do_spoof) ProfScope("unset spoof") + { + dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); + } + } + } + + //- rjf: irrespective of what event came back, we should ALWAYS check the + // spoof's thread and see if it hit the spoof address, because we may have + // simply been sent other debug events first + if(spoof != 0) + { + CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, spoof->thread)); + Arch arch = thread->arch; + void *regs_block = push_array(scratch.arena, U8, regs_block_size_from_arch(arch)); + dmn_thread_read_reg_block(spoof->thread, regs_block); + U64 spoof_thread_rip = regs_rip_from_arch_block(arch, regs_block); + if(spoof_thread_rip == spoof->new_ip_value) + { + regs_arch_block_write_rip(arch, regs_block, spoof_old_ip_value); + ctrl_thread_write_reg_block(ctrl_handle_make(CTRL_MachineID_Local, spoof->thread), regs_block); + } + } + + //- rjf: push ctrl events associated with this demon event + CTRL_EventList evts = {0}; + ProfScope("push ctrl events associated with this demon event") switch(event->kind) + { + default:{}break; + case DMN_EventKind_CreateProcess: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_NewProc; + out_evt->msg_id = msg->msg_id; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->arch = event->arch; + out_evt->entity_id = event->code; + out_evt->tls_model = ctrl_dynamic_linker_type_from_dmn(event->tls_model); + out_evt->target_os = OperatingSystem_CURRENT; // TODO: operating system of the remote target machine + ctrl_state->process_counter += 1; + }break; + case DMN_EventKind_CreateThread: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_NewThread; + out_evt->msg_id = msg->msg_id; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); + out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->arch = event->arch; + out_evt->entity_id = event->code; + out_evt->stack_base = dmn_stack_base_vaddr_from_thread(event->thread); + out_evt->tls_root = dmn_tls_root_vaddr_from_thread(event->thread); + out_evt->rip_vaddr = event->instruction_pointer; + out_evt->string = event->string; + }break; + case DMN_EventKind_LoadModule: + { + CTRL_Handle process_handle = ctrl_handle_make(CTRL_MachineID_Local, event->process); + CTRL_Handle module_handle = ctrl_handle_make(CTRL_MachineID_Local, event->module); + CTRL_Event *out_evt1 = ctrl_event_list_push(scratch.arena, &evts); + String8 module_path = path_normalized_from_string(scratch.arena, event->string); + U64 exe_timestamp = os_properties_from_file_path(module_path).modified; + ctrl_thread__module_open(process_handle, module_handle, r1u64(event->address, event->address+event->size), module_path, event->elf_phdr_vrange, event->elf_phdr_entsize); + out_evt1->kind = CTRL_EventKind_NewModule; + out_evt1->msg_id = msg->msg_id; + out_evt1->entity = module_handle; + out_evt1->parent = process_handle; + out_evt1->arch = event->arch; + out_evt1->entity_id = event->code; + out_evt1->vaddr_rng = r1u64(event->address, event->address+event->size); + out_evt1->rip_vaddr = event->address; + out_evt1->timestamp = exe_timestamp; + out_evt1->string = module_path; + out_evt1->tls_index = event->tls_index; + out_evt1->tls_offset = event->tls_offset; + CTRL_Event *out_evt2 = ctrl_event_list_push(scratch.arena, &evts); + String8 initial_debug_info_path = ctrl_initial_debug_info_path_from_module(scratch.arena, module_handle); + U64 debug_info_timestamp = os_properties_from_file_path(initial_debug_info_path).modified; + out_evt2->kind = CTRL_EventKind_ModuleDebugInfoPathChange; + out_evt2->msg_id = msg->msg_id; + out_evt2->entity = module_handle; + out_evt2->parent = process_handle; + out_evt2->timestamp = debug_info_timestamp; + out_evt2->string = initial_debug_info_path; + DI_Key initial_dbgi_key = di_key_from_path_timestamp(initial_debug_info_path, debug_info_timestamp); + di_open(initial_dbgi_key); + }break; + case DMN_EventKind_ExitProcess: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_EndProc; + out_evt->msg_id = msg->msg_id; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->u64_code = event->code; + ctrl_state->process_counter -= 1; + }break; + case DMN_EventKind_ExitThread: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_EndThread; + out_evt->msg_id = msg->msg_id; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); + out_evt->entity_id = event->code; + }break; + case DMN_EventKind_UnloadModule: + ProfScope("unload module %.*s", str8_varg(event->string)) + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + CTRL_Handle module_handle = ctrl_handle_make(CTRL_MachineID_Local, event->module); + CTRL_Entity *module_ent = ctrl_entity_from_handle(entity_ctx, module_handle); + CTRL_Entity *process_ent = ctrl_process_from_entity(module_ent); + String8 module_path = event->string; + ctrl_thread__module_close(process_ent->handle, module_handle, module_ent->vaddr_range); + out_evt->kind = CTRL_EventKind_EndModule; + out_evt->msg_id = msg->msg_id; + out_evt->entity = module_handle; + out_evt->string = module_path; + DI_Key dbgi_key = ctrl_dbgi_key_from_module(module_ent); + di_close(dbgi_key, 0); + }break; + case DMN_EventKind_DebugString: + { + U64 num_strings = (event->string.size + ctrl_state->c2u_ring_max_string_size-1) / ctrl_state->c2u_ring_max_string_size; + for(U64 string_idx = 0; string_idx < num_strings; string_idx += 1) + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_DebugString; + out_evt->msg_id = msg->msg_id; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); + out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->string = str8_substr(event->string, r1u64(string_idx*ctrl_state->c2u_ring_max_string_size, (string_idx+1)*ctrl_state->c2u_ring_max_string_size)); + } + }break; + case DMN_EventKind_SetThreadName: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_ThreadName; + out_evt->msg_id = msg->msg_id; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); + out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->string = event->string; + out_evt->entity_id = event->code; + }break; + case DMN_EventKind_SetThreadColor: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_ThreadColor; + out_evt->msg_id = msg->msg_id; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); + out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->entity_id = event->code; + out_evt->rgba = event->user_data; + }break; + case DMN_EventKind_SetVAddrRangeNote: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_SetVAddrRangeNote; + out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->msg_id = msg->msg_id; + out_evt->vaddr_rng = r1u64(event->address, event->address + event->size); + out_evt->string = event->string; + }break; + case DMN_EventKind_SetBreakpoint: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_SetBreakpoint; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); + out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); + out_evt->bp_flags = ctrl_user_breakpoint_flags_from_dmn_trap_flags(event->flags); + }break; + case DMN_EventKind_UnsetBreakpoint: + { + // TODO(rjf): this needs to be reflected in the resolved trap list too!!!!!!!! + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_UnsetBreakpoint; + out_evt->entity = ctrl_handle_make(CTRL_MachineID_Local, event->thread); + out_evt->parent = ctrl_handle_make(CTRL_MachineID_Local, event->process); + out_evt->vaddr_rng = r1u64(event->address, event->address+event->size); + out_evt->bp_flags = ctrl_user_breakpoint_flags_from_dmn_trap_flags(event->flags); + }break; + } + ctrl_c2u_push_events(&evts); + + //- rjf: if this is the first process in a session, clear the debug directory + // cache state + if(ctrl_state->process_counter == 1 && event->kind == DMN_EventKind_CreateProcess) + { + arena_clear(ctrl_state->dbg_dir_arena); + ctrl_state->dbg_dir_root = push_array(ctrl_state->dbg_dir_arena, CTRL_DbgDirNode, 1); + } + + //- rjf: out of queued up demon events -> clear event arena + if(ctrl_state->first_dmn_event_node == 0) + { + ctrl_state->free_dmn_event_node = 0; + arena_clear(ctrl_state->dmn_event_arena); + } + + scratch_end(scratch); + ProfEnd(); + return(event); +} + +//- rjf: eval helpers + +internal U64 +ctrl_eval_space_gen(E_Space space) +{ + U64 result = 0; + switch(space.kind) + { + default:{}break; + case CTRL_EvalSpaceKind_Entity: + { + result = ctrl_mem_gen(); + }break; + } + return result; +} + +internal B32 +ctrl_eval_space_read(E_Space space, void *out, Rng1U64 range) +{ + B32 result = 0; + switch(space.kind) + { + default:{}break; + + //- rjf: intra-entity reads (process memory or thread registers) + case CTRL_EvalSpaceKind_Entity: + { + CTRL_Entity *entity = (CTRL_Entity *)space.u64_0; + switch(entity->kind) + { + default:{}break; + case CTRL_EntityKind_Process: + { + U64 read_size = dmn_process_read(entity->handle.dmn_handle, range, out); + result = (read_size == dim_1u64(range)); + }break; + case CTRL_EntityKind_Thread: + { + Temp scratch = scratch_begin(0, 0); + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + U64 regs_size = regs_block_size_from_arch(entity->arch); + void *regs = ctrl_reg_block_from_thread(scratch.arena, entity_ctx, entity->handle); + Rng1U64 legal_range = r1u64(0, regs_size); + Rng1U64 read_range = intersect_1u64(legal_range, range); + U64 read_size = dim_1u64(read_range); + MemoryCopy(out, (U8 *)regs + read_range.min, read_size); + result = (read_size == dim_1u64(range)); + scratch_end(scratch); + }break; + } + }break; + } + return result; +} + +//- rjf: control thread eval scopes + +internal CTRL_EvalScope * +ctrl_thread__eval_scope_begin(Arena *arena, CTRL_UserBreakpointList *user_bps, CTRL_Entity *thread) +{ + ProfBeginFunction(); + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + CTRL_EvalScope *scope = push_array(arena, CTRL_EvalScope, 1); + scope->access = access_open(); + + ////////////////////////////// + //- rjf: unpack thread + // + Arch arch = thread->arch; + U64 thread_rip_vaddr = dmn_rip_from_thread(thread->handle.dmn_handle); + CTRL_Entity *process = ctrl_process_from_entity(thread); + CTRL_Entity *module = ctrl_module_from_process_vaddr(process, thread_rip_vaddr); + U64 thread_rip_voff = ctrl_voff_from_vaddr(module, thread_rip_vaddr); + + ////////////////////////////// + //- rjf: gather evaluation debug infos & modules + // + U64 eval_modules_count = Max(1, entity_ctx->entity_kind_counts[CTRL_EntityKind_Module]); + E_Module *eval_modules = push_array(arena, E_Module, eval_modules_count); + E_Module *eval_modules_primary = &eval_modules[0]; + eval_modules_primary->vaddr_range = r1u64(0, max_U64); + U64 eval_dbg_infos_count = Max(1, entity_ctx->entity_kind_counts[CTRL_EntityKind_Module]); + E_DbgInfo *eval_dbg_infos = push_array(arena, E_DbgInfo, eval_dbg_infos_count); + E_DbgInfo *eval_dbg_infos_primary = &eval_dbg_infos[0]; + MemoryCopyStruct(eval_dbg_infos_primary, &e_dbg_info_nil); + { + U64 eval_module_idx = 0; + U64 eval_dbg_info_idx = 0; + for(CTRL_Entity *machine = entity_ctx->root->first; + machine != &ctrl_entity_nil; + machine = machine->next) + { + if(machine->kind != CTRL_EntityKind_Machine) { continue; } + for(CTRL_Entity *process = machine->first; + process != &ctrl_entity_nil; + process = process->next) + { + if(process->kind != CTRL_EntityKind_Process) { continue; } + for(CTRL_Entity *mod = process->first; + mod != &ctrl_entity_nil; + mod = mod->next) + { + if(mod->kind != CTRL_EntityKind_Module) { continue; } + DI_Key dbgi_key = ctrl_dbgi_key_from_module(mod); + + //- rjf: try to obtain this module's RDI + RDI_Parsed *rdi = di_rdi_from_key(scope->access, dbgi_key, 0, 0); + + //- rjf: if this RDI is not yet ready => determine if we need to wait for it + // + // (we *always* wait for the initial module) + // + B32 rdi_is_necessary = 1; + if(user_bps->count == 0) + { + rdi_is_necessary = 0; + } + else if(rdi == &rdi_parsed_nil) ProfScope("determine if RDI is necessary") + { + // rjf: find cached result + U64 hash = ctrl_hash_from_handle(mod->handle); + U64 slot_idx = hash%ctrl_state->module_req_cache_slots_count; + CTRL_ModuleReqCacheNode *slot = ctrl_state->module_req_cache_slots[slot_idx]; + CTRL_ModuleReqCacheNode *node = 0; + for(CTRL_ModuleReqCacheNode *n = slot; n != 0; n = n->next) + { + if(ctrl_handle_match(n->module, mod->handle)) + { + node = n; + break; + } + } + + // rjf: cached? -> take cached result + if(node != 0) + { + rdi_is_necessary = node->required; + } + + // rjf: not cached -> compute & store + else ProfScope("cache miss") + { + CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(mod, CTRL_EntityKind_DebugInfoPath); + OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, debug_info_path->string); + ProfScope("determine if %.*s is necessary", str8_varg(debug_info_path->string)) + { + //- rjf: determine if file is PDB + B32 file_is_pdb = 0; + if(!file_is_pdb) + { + U8 msf70_magic_maybe[sizeof(msf_msf70_magic)] = {0}; + os_file_read(file, r1u64(0, sizeof(msf70_magic_maybe)), msf70_magic_maybe); + if(MemoryMatch(msf70_magic_maybe, msf_msf70_magic, sizeof(msf70_magic_maybe))) + { + file_is_pdb = 1; + } + } + if(!file_is_pdb) + { + U8 msf20_magic_maybe[sizeof(msf_msf20_magic)] = {0}; + os_file_read(file, r1u64(0, sizeof(msf20_magic_maybe)), msf20_magic_maybe); + if(MemoryMatch(msf20_magic_maybe, msf_msf20_magic, sizeof(msf20_magic_maybe))) + { + file_is_pdb = 1; + } + } + + //- rjf: file is PDB -> do thin parse & lookup of all breakpoint files/symbols. + // if any are found in the PDB, then this RDI is necessary. + if(file_is_pdb) + { + FileProperties props = os_properties_from_file(file); + OS_Handle map = os_file_map_open(OS_AccessFlag_Read, file); + void *file_base = os_file_map_view_open(map, OS_AccessFlag_Read, r1u64(0, props.size)); + String8 file_data = str8(file_base, props.size); + { + rdi_is_necessary = pdb_has_symbol_or_file_ref(file_data, ctrl_state->msg_user_bp_touched_symbols, ctrl_state->msg_user_bp_touched_files); + } + os_file_map_view_close(map, file_base, r1u64(0, props.size)); + os_file_map_close(map); + } + } + os_file_close(file); + node = push_array(ctrl_state->ctrl_thread_msg_process_arena, CTRL_ModuleReqCacheNode, 1); + node->next = slot; + ctrl_state->module_req_cache_slots[slot_idx] = node; + node->module = mod->handle; + node->required = rdi_is_necessary; + } + } + + //- rjf: if this RDI is necessary, but we do not have it => wait for it forever + if(rdi == &rdi_parsed_nil && rdi_is_necessary) ProfScope("RDI is necessary -> wait") + { + rdi = di_rdi_from_key(scope->access, dbgi_key, 1, max_U64); + } + + //- rjf: fill debug info + eval_dbg_infos[eval_dbg_info_idx].dbgi_key = dbgi_key; + eval_dbg_infos[eval_dbg_info_idx].rdi = rdi; + if(mod == module) + { + eval_dbg_infos_primary = &eval_dbg_infos[eval_dbg_info_idx]; + } + eval_dbg_info_idx += 1; + + //- rjf: fill evaluation module info + eval_modules[eval_module_idx].arch = arch; + eval_modules[eval_module_idx].dbg_info_num= (U32)eval_dbg_info_idx; + eval_modules[eval_module_idx].vaddr_range = mod->vaddr_range; + eval_modules[eval_module_idx].space = e_space_make(CTRL_EvalSpaceKind_Entity); + eval_modules[eval_module_idx].space.u64_0 = (U64)process; + if(mod == module) + { + eval_modules_primary = &eval_modules[eval_module_idx]; + } + eval_module_idx += 1; + } + } + } + } + + ////////////////////////////// + //- rjf: select evaluation cache + // + e_select_cache(ctrl_state->ctrl_thread_eval_cache); + + ////////////////////////////// + //- rjf: build base evaluation context + // + { + E_BaseCtx *ctx = &scope->base_ctx; + + //- rjf: fill instruction pointer info + ctx->thread_ip_vaddr = thread_rip_vaddr; + ctx->thread_ip_voff = thread_rip_voff; + ctx->thread_arch = thread->arch; + ctx->thread_reg_space = e_space_make(CTRL_EvalSpaceKind_Entity); + ctx->thread_reg_space.u64_0 = (U64)thread; + ctx->thread_process_space = e_space_make(CTRL_EvalSpaceKind_Entity); + ctx->thread_process_space.u64_0 = (U64)process; + + //- rjf: fill debug infos + ctx->dbg_infos = eval_dbg_infos; + ctx->dbg_infos_count = eval_dbg_infos_count; + ctx->primary_dbg_info = eval_dbg_infos_primary; + + //- rjf: fill modules + ctx->modules = eval_modules; + ctx->modules_count = eval_modules_count; + ctx->primary_module = eval_modules_primary; + + //- rjf: fill space hooks + ctx->space_gen = ctrl_eval_space_gen; + ctx->space_read = ctrl_eval_space_read; + } + e_select_base_ctx(&scope->base_ctx); + + ////////////////////////////// + //- rjf: build IR evaluation context + // + { + E_IRCtx *ctx = &scope->ir_ctx; + ctx->regs_map = ctrl_string2reg_from_arch(arch); + ctx->reg_alias_map = ctrl_string2alias_from_arch(arch); + ctx->locals_map = e_push_locals_map_from_rdi_voff(arena, eval_dbg_infos_primary->rdi, thread_rip_voff); + ctx->member_map = e_push_member_map_from_rdi_voff(arena, eval_dbg_infos_primary->rdi, thread_rip_voff); + ctx->macro_map = push_array(arena, E_String2ExprMap, 1); + ctx->macro_map[0] = e_string2expr_map_make(arena, 512); + ctx->auto_hook_map = push_array(arena, E_AutoHookMap, 1); + ctx->auto_hook_map[0] = e_auto_hook_map_make(arena, 512); + } + e_select_ir_ctx(&scope->ir_ctx); + + ////////////////////////////// + //- rjf: build eval interpretation context + // + { + E_InterpretCtx *ctx = &scope->interpret_ctx; + ctx->primary_space = eval_modules_primary->space; + ctx->reg_arch = eval_modules_primary->arch; + ctx->reg_space = e_space_make(CTRL_EvalSpaceKind_Entity); + ctx->reg_space.u64_0 = (U64)thread; + ctx->module_base = push_array(arena, U64, 1); + ctx->module_base[0]= module->vaddr_range.min; + ctx->frame_base = push_array(arena, U64, 1); + // TODO(rjf): need to compute this out here somehow... ctx->frame_base[0] = ; + ctx->tls_base = push_array(arena, U64, 1); + // TODO: compute CFA + ctx->cfa = 0; + } + e_select_interpret_ctx(&scope->interpret_ctx, eval_dbg_infos_primary->rdi, thread_rip_voff); + + ProfEnd(); + return scope; +} + +internal void +ctrl_thread__eval_scope_end(CTRL_EvalScope *scope) +{ + access_close(scope->access); +} + +//- rjf: log flusher + +internal void +ctrl_thread__end_and_flush_log(void) +{ + Temp scratch = scratch_begin(0, 0); + LogScopeResult log = log_scope_end(scratch.arena); + os_append_data_to_file_path(ctrl_state->ctrl_thread_log_path, log.strings[LogMsgKind_Info]); + if(log.strings[LogMsgKind_UserError].size != 0) + { + CTRL_EventList evts = {0}; + CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); + evt->kind = CTRL_EventKind_Error; + evt->string = log.strings[LogMsgKind_UserError]; + ctrl_c2u_push_events(&evts); + } + scratch_end(scratch); +} + +//- rjf: msg kind implementations + +internal void +ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + Temp scratch = scratch_begin(0, 0); + + //- rjf: produce full stdout/stderr/stdin paths + String8 stdout_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stdout_path, msg->path); + String8 stdin_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stdin_path, msg->path); + String8 stderr_path = path_absolute_dst_from_relative_dst_src(scratch.arena, msg->stderr_path, msg->path); + + //- rjf: obtain stdout/stderr/stdin handles + OS_Handle stdout_handle = {0}; + OS_Handle stderr_handle = {0}; + OS_Handle stdin_handle = {0}; + if(stdout_path.size != 0) + { + OS_Handle f = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Read, stdout_path); + os_file_close(f); + stdout_handle = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, msg->stdout_path); + } + if(stderr_path.size != 0) + { + OS_Handle f = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Read, stderr_path); + os_file_close(f); + stderr_handle = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, msg->stderr_path); + } + if(stdin_path.size != 0) + { + stdin_handle = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, stdin_path); + } + + //- rjf: launch + OS_ProcessLaunchParams params = {0}; + { + params.cmd_line = msg->cmd_line_string_list; + params.path = msg->path; + params.env = msg->env_string_list; + params.inherit_env = msg->env_inherit; + params.debug_subprocesses = msg->debug_subprocesses; + params.stdout_file = stdout_handle; + params.stderr_file = stderr_handle; + params.stdin_file = stdin_handle; + } + U32 id = dmn_ctrl_launch(ctrl_ctx, ¶ms); + + //- rjf: close stdout/stderr/stdin files + os_file_close(stdout_handle); + os_file_close(stderr_handle); + os_file_close(stdin_handle); + + //- rjf: record (id -> entry points), so that we know custom entry points for this PID + CTRL_EntityCtxRWStore *entity_ctx_rw_store = ctrl_state->ctrl_thread_entity_store; + MutexScopeW(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) + { + for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) + { + String8 string = n->string; + CTRL_Entity *entry = ctrl_entity_alloc(entity_ctx_rw_store, entity_ctx_rw_store->ctx.root, CTRL_EntityKind_EntryPoint, Arch_Null, ctrl_handle_zero(), (U64)id); + ctrl_entity_equip_string(entity_ctx_rw_store, entry, string); + } + } + + scratch_end(scratch); +} + +internal void +ctrl_thread__attach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + + //- rjf: attach + B32 attach_successful = dmn_ctrl_attach(ctrl_ctx, msg->entity_id); + + //- rjf: run to handshake + if(attach_successful) + { + DMN_Handle unfrozen_process = {0}; + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities_are_processes = 1; + for(B32 done = 0; done == 0;) + { + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_CreateProcess: + { + unfrozen_process = event->process; + run_ctrls.run_entities = &unfrozen_process; + run_ctrls.run_entity_count = 1; + }break; + case DMN_EventKind_Halt: + case DMN_EventKind_Exception: + case DMN_EventKind_Error: + case DMN_EventKind_HandshakeComplete: + { + done = 1; + }break; + } + } + } + + //- rjf: record stop + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = CTRL_EventCause_Finished; + event->msg_id = msg->msg_id; + event->entity_id = !!attach_successful * msg->entity_id; + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__kill(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DMN_Handle process = msg->entity.dmn_handle; + U32 exit_code = msg->exit_code; + + //- rjf: send kill + B32 kill_worked = dmn_ctrl_kill(ctrl_ctx, process, exit_code); + + //- rjf: wait for process to be dead + CTRL_EventCause cause = CTRL_EventCause_Finished; + if(kill_worked) + { + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities_are_processes = 1; + run_ctrls.run_entities = &process; + run_ctrls.run_entity_count = 1; + for(B32 done = 0; done == 0;) + { + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_ExitProcess: + if(dmn_handle_match(event->process, process)) + { + done = 1; + }break; + case DMN_EventKind_Error:{done = 1; cause = CTRL_EventCause_Error;}break; + case DMN_EventKind_Halt: {done = 1; cause = CTRL_EventCause_InterruptedByHalt;}break; + } + } + } + + //- rjf: record stop + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = cause; + event->msg_id = msg->msg_id; + if(kill_worked) + { + event->entity = msg->entity; + } + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__kill_all(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + U32 exit_code = msg->exit_code; + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + + //- rjf: gather all currently existing processes + CTRL_EntityArray initial_processes = ctrl_entity_array_from_kind(entity_ctx, CTRL_EntityKind_Process); + typedef struct Task Task; + struct Task + { + Task *next; + Task *prev; + CTRL_Entity *process; + }; + Task *first_task = 0; + Task *last_task = 0; + for EachIndex(idx, initial_processes.count) + { + CTRL_Entity *entity = initial_processes.v[idx]; + Task *t = push_array(scratch.arena, Task, 1); + t->process = entity; + DLLPushBack(first_task, last_task, t); + } + + //- rjf: kill processes as needed, wait for all processes to be dead + CTRL_EventCause cause = CTRL_EventCause_Finished; + if(first_task != 0) + { + DMN_RunCtrls run_ctrls = {0}; + for(B32 done = 0; !done;) + { + // rjf: kill remaining processes + for(Task *t = first_task, *next = 0; t != 0; t = next) + { + next = t->next; + B32 kill_worked = dmn_ctrl_kill(ctrl_ctx, t->process->handle.dmn_handle, exit_code); + if(kill_worked) + { + DLLRemove(first_task, last_task, t); + } + } + + // rjf: get next event + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + + // rjf: process event + switch(event->kind) + { + default:{}break; + case DMN_EventKind_CreateProcess: + { + CTRL_Entity *new_process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process)); + Task *t = push_array(scratch.arena, Task, 1); + t->process = new_process; + DLLPushBack(first_task, last_task, t); + }break; + case DMN_EventKind_Error:{done = 1; cause = CTRL_EventCause_Error;}break; + case DMN_EventKind_Halt: {done = 1; cause = CTRL_EventCause_InterruptedByHalt;}break; + } + + // rjf: end if all processes are gone + CTRL_EntityArray processes = ctrl_entity_array_from_kind(entity_ctx, CTRL_EntityKind_Process); + if(processes.count == 0) + { + done = 1; + } + } + } + + //- rjf: record stop + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = cause; + event->msg_id = msg->msg_id; + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__detach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DMN_Handle process = msg->entity.dmn_handle; + + //- rjf: detach + B32 detach_worked = dmn_ctrl_detach(ctrl_ctx, process); + + //- rjf: wait for process to be dead + if(detach_worked) + { + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities_are_processes = 1; + run_ctrls.run_entities = &process; + run_ctrls.run_entity_count = 1; + for(B32 done = 0; done == 0;) + { + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + if(event->kind == DMN_EventKind_ExitProcess && dmn_handle_match(event->process, process)) + { + done = 1; + } + if(event->kind == DMN_EventKind_Halt) + { + done = 1; + } + } + } + + //- rjf: record stop + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = CTRL_EventCause_Finished; + event->msg_id = msg->msg_id; + if(detach_worked) + { + event->entity = msg->entity; + } + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DMN_Event *stop_event = 0; + CTRL_EventCause stop_cause = CTRL_EventCause_Null; + CTRL_EntityCtx *entity_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + CTRL_Handle target_thread = msg->entity; + CTRL_Handle target_process = msg->parent; + CTRL_Entity *target_process_entity = ctrl_entity_from_handle(entity_ctx, target_process); + U64 spoof_ip_vaddr = 911; + log_infof("ctrl_thread__run:\n{\n"); + + ////////////////////////////// + //- rjf: gather all initial breakpoints + // + DMN_TrapChunkList user_traps = {0}; + { + CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, target_thread); + CTRL_EvalScope *eval_scope = ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, thread); + for(CTRL_Entity *machine = entity_ctx->root->first; + machine != &ctrl_entity_nil; + machine = machine->next) + { + if(machine->kind != CTRL_EntityKind_Machine) { continue; } + for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) + { + if(process->kind != CTRL_EntityKind_Process) { continue; } + + // rjf: resolve module-dependent user bps + for(CTRL_Entity *module = process->first; module != &ctrl_entity_nil; module = module->next) + { + if(module->kind != CTRL_EntityKind_Module) { continue; } + ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, eval_scope, process->handle, module->handle, &msg->user_bps, &user_traps); + } + + // rjf: push process-declared breakpoins + for(CTRL_Entity *bp = process->first; bp != &ctrl_entity_nil; bp = bp->next) + { + if(bp->kind != CTRL_EntityKind_Breakpoint) { continue; } + ctrl_thread__append_program_defined_bp_traps(scratch.arena, bp, &user_traps); + } + + // rjf: push virtual-address user breakpoints per-process + ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, eval_scope, process->handle, &msg->user_bps, &user_traps); + } + } + ctrl_thread__eval_scope_end(eval_scope); + } + + ////////////////////////////// + //- rjf: read initial stack-pointer-check value + // + // This MUST happen before any threads move, including single-stepping stuck + // threads, because otherwise, their stack pointer may change, if single-stepping + // causes e.g. entrance into a function via a call instruction. + // + U64 sp_check_value = dmn_rsp_from_thread(target_thread.dmn_handle); + log_infof("sp_check_value := 0x%I64x\n", sp_check_value); + + ////////////////////////////// + //- rjf: single step "stuck threads" + // + // "Stuck threads" are threads that are already on a User BP and would hit + // it immediately if resumed with all User BPs enabled. To get them "unstuck" + // we just need to single step them to get them off their current instruction. + // + // This only applies to threads OTHER THAN the target thread. If the target + // thread is on a user breakpoint, then we need to let trap net logic run, + // which may include features put on a trap net trap at the same address as + // the user breakpoint. + // + B32 target_thread_is_on_user_bp_and_trap_net_trap = 0; + if(stop_event == 0) + { + // rjf: gather stuck threads + DMN_HandleList stuck_threads = {0}; + for(CTRL_Entity *machine = entity_ctx->root->first; + machine != &ctrl_entity_nil; + machine = machine->next) + { + if(machine->kind != CTRL_EntityKind_Machine) { continue; } + for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) + { + if(process->kind != CTRL_EntityKind_Process) { continue; } + for(CTRL_Entity *thread = process->first; thread != &ctrl_entity_nil; thread = thread->next) + { + if(thread->kind != CTRL_EntityKind_Thread) { continue; } + U64 rip = dmn_rip_from_thread(thread->handle.dmn_handle); + + // rjf: determine if thread is frozen + B32 thread_is_frozen = thread->is_frozen; + + // rjf: not frozen? -> check if stuck & gather if so + if(!thread_is_frozen) + { + for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next) + { + B32 is_on_user_bp = 0; + for(DMN_Trap *trap_ptr = n->v; trap_ptr < n->v+n->count; trap_ptr += 1) + { + if(dmn_handle_match(trap_ptr->process, process->handle.dmn_handle) && trap_ptr->vaddr == rip) + { + is_on_user_bp = 1; + } + } + + B32 is_on_net_trap = 0; + for(CTRL_TrapNode *n = msg->traps.first; n != 0; n = n->next) + { + if(n->v.vaddr == rip) + { + is_on_net_trap = 1; + } + } + + if(is_on_user_bp && (!is_on_net_trap || !dmn_handle_match(thread->handle.dmn_handle, target_thread.dmn_handle))) + { + dmn_handle_list_push(scratch.arena, &stuck_threads, thread->handle.dmn_handle); + } + + if(is_on_user_bp && is_on_net_trap && dmn_handle_match(thread->handle.dmn_handle, target_thread.dmn_handle)) + { + target_thread_is_on_user_bp_and_trap_net_trap = 1; + } + } + } + } + } + } + + // rjf: actually step stuck threads + for(DMN_HandleNode *node = stuck_threads.first; + node != 0; + node = node->next) + { + DMN_Handle thread = node->v; + U64 thread_pre_rip = dmn_rip_from_thread(thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 done = 0; !done;) + { + log_infof("single_step_stuck_thread([0x%I64x])\n", thread.u64[0]); + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities = &thread; + run_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + run_ctrls.single_step_thread = thread; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(thread); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_ExitThread: + if(dmn_handle_match(event->thread, thread)) + { + stop_cause = CTRL_EventCause_Error; + goto stop; + }break; + case DMN_EventKind_Error: stop_cause = CTRL_EventCause_Error; goto stop; + case DMN_EventKind_Exception: stop_cause = CTRL_EventCause_InterruptedByException; goto stop; + case DMN_EventKind_Trap: stop_cause = CTRL_EventCause_InterruptedByTrap; goto stop; + case DMN_EventKind_Halt: stop_cause = CTRL_EventCause_InterruptedByHalt; goto stop; + stop:; + { + stop_event = event; + done = 1; + }break; + case DMN_EventKind_SingleStep: + { + done = dmn_handle_match(node->v, event->thread); + }break; + } + } + } + } + + ////////////////////////////// + //- rjf: gather frozen threads + // + CTRL_EntityList frozen_threads = {0}; + for(CTRL_Entity *machine = entity_ctx->root->first; + machine != &ctrl_entity_nil; + machine = machine->next) + { + if(machine->kind != CTRL_EntityKind_Machine) { continue; } + for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) + { + if(process->kind != CTRL_EntityKind_Process) { continue; } + for(CTRL_Entity *thread = process->first; thread != &ctrl_entity_nil; thread = thread->next) + { + if(thread->is_frozen) + { + ctrl_entity_list_push(scratch.arena, &frozen_threads, thread); + } + } + } + } + + ////////////////////////////// + //- rjf: resolve trap net + // + DMN_TrapChunkList trap_net_traps = {0}; + for(CTRL_TrapNode *node = msg->traps.first; + node != 0; + node = node->next) + { + DMN_Trap trap = {target_process.dmn_handle, node->v.vaddr}; + dmn_trap_chunk_list_push(scratch.arena, &trap_net_traps, 256, &trap); + } + + ////////////////////////////// + //- rjf: join user breakpoints and trap net traps + // + DMN_TrapChunkList joined_traps = {0}; + { + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &user_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &trap_net_traps); + } + + ////////////////////////////// + //- rjf: record start + // + if(stop_event == 0) + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Started; + ctrl_c2u_push_events(&evts); + } + + ////////////////////////////// + //- rjf: run loop + // + if(stop_event == 0) + { + B32 spoof_mode = 0; + CTRL_Spoof spoof = {0}; + DMN_TrapChunkList entry_traps = {0}; + for(U64 run_loop_idx = 0;; run_loop_idx += 1) + { + ////////////////////////// + //- rjf: choose low level traps + // + DMN_TrapChunkList *trap_list = &joined_traps; + if(spoof_mode) + { + trap_list = &user_traps; + } + + ////////////////////////// + //- rjf: choose spoof + // + CTRL_Spoof *run_spoof = 0; + if(spoof_mode) + { + run_spoof = &spoof; + } + + ////////////////////////// + //- rjf: setup run controls + // + DMN_RunCtrls run_ctrls = {0}; + if(run_loop_idx == 0) + { + run_ctrls.priority_thread = target_thread.dmn_handle; + } + run_ctrls.ignore_previous_exception = 1; + run_ctrls.run_entity_count = frozen_threads.count; + run_ctrls.run_entities = push_array(scratch.arena, DMN_Handle, run_ctrls.run_entity_count); + run_ctrls.run_entities_are_unfrozen = 0; + { + U64 idx = 0; + for(CTRL_EntityNode *n = frozen_threads.first; n != 0; n = n->next) + { + run_ctrls.run_entities[idx] = n->v->handle.dmn_handle; + idx += 1; + } + } + run_ctrls.traps = *trap_list; + + ////////////////////////// + //- rjf: get next run-related event + // + log_infof("get_next_event:\n{\n"); + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, run_spoof); + log_infof("}\n\n"); + + ////////////////////////// + //- rjf: determine event handling + // + B32 launch_done_first_module = 0; + B32 hard_stop = 0; + CTRL_EventCause hard_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + B32 use_stepping_logic = 0; + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: + case DMN_EventKind_Halt: + case DMN_EventKind_SingleStep: + case DMN_EventKind_Trap: + { + hard_stop = 1; + log_infof("step_rule: unexpected -> hard_stop\n"); + }break; + case DMN_EventKind_Exception: + case DMN_EventKind_Breakpoint: + { + use_stepping_logic = 1; + log_infof("step_rule: exception/breakpoint -> stepping_logic\n"); + }break; + case DMN_EventKind_CreateProcess: + { + CTRL_EvalScope *eval_scope = ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, &ctrl_entity_nil); + { + DMN_TrapChunkList new_traps = {0}; + ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, eval_scope, ctrl_handle_make(CTRL_MachineID_Local, event->process), &msg->user_bps, &new_traps); + log_infof("step_rule: create_process -> resolve traps\n"); + log_infof("new_traps:\n{\n"); + for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) + { + for(U64 idx = 0; idx < n->count; idx += 1) + { + DMN_Trap *trap = &n->v[idx]; + log_infof("{process:[0x%I64x], vaddr:0x%I64x}\n", trap->process.u64[0], trap->vaddr); + } + } + log_infof("}\n\n"); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); + } + ctrl_thread__eval_scope_end(eval_scope); + }break; + case DMN_EventKind_LoadModule: + { + CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->thread)); + CTRL_EvalScope *eval_scope = ctrl_thread__eval_scope_begin(scratch.arena, &msg->user_bps, thread); + { + DMN_TrapChunkList new_traps = {0}; + ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, eval_scope, ctrl_handle_make(CTRL_MachineID_Local, event->process), ctrl_handle_make(CTRL_MachineID_Local, event->module), &msg->user_bps, &new_traps); + log_infof("step_rule: load_module -> resolve traps\n"); + log_infof("new_traps:\n{\n"); + for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) + { + for(U64 idx = 0; idx < n->count; idx += 1) + { + DMN_Trap *trap = &n->v[idx]; + log_infof("{process:[0x%I64x], vaddr:0x%I64x}\n", trap->process.u64[0], trap->vaddr); + } + } + log_infof("}\n\n"); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); + } + ctrl_thread__eval_scope_end(eval_scope); + }break; + case DMN_EventKind_SetBreakpoint: + { + CTRL_Entity *bp = &ctrl_entity_nil; + { + CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process)); + for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) + { + if(child->kind == CTRL_EntityKind_Breakpoint && + child->vaddr_range.min == event->address && + child->vaddr_range.max == event->address + event->size && + child->bp_flags == ctrl_user_breakpoint_flags_from_dmn_trap_flags(event->flags)) + { + bp = child; + break; + } + } + } + if(bp != &ctrl_entity_nil) + { + DMN_TrapChunkList new_traps = {0}; + ctrl_thread__append_program_defined_bp_traps(scratch.arena, bp, &new_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); + } + }break; + } + + ////////////////////////// + //- rjf: on launches, detect entry points, place traps + // + if(msg->run_flags & CTRL_RunFlag_StopOnEntryPoint && !launch_done_first_module && event->kind == DMN_EventKind_HandshakeComplete) + { + launch_done_first_module = 1; + Access *access = access_open(); + + //- rjf: unpack process/module info + CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process)); + CTRL_Entity *module = ctrl_entity_child_from_kind(process, CTRL_EntityKind_Module); + U64 module_base_vaddr = module->vaddr_range.min; + DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, max_U64); + RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Procedures); + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, unparsed_map, &map); + + //- rjf: add traps for user-specified entry points on this message, if specified + B32 entries_found = 0; + if(!entries_found) + { + for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) + { + U32 procedure_id = 0; + { + String8 name = n->string; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + + //- rjf: add traps for module-baked entry points, if specified + if(!entries_found) + { + String8 raddbg_data = ctrl_raddbg_data_from_module(scratch.arena, module->handle); + U8 split_char = 0; + String8List raddbg_data_text_parts = str8_split(scratch.arena, raddbg_data, &split_char, 1, 0); + for(String8Node *text_n = raddbg_data_text_parts.first; text_n != 0; text_n = text_n->next) + { + String8 text = text_n->string; + MD_Node *root = md_tree_from_string(scratch.arena, text); + if(str8_match(root->first->string, str8_lit("entry_point"), 0)) + { + String8 name = root->first->first->string; + U32 procedure_id = 0; + { + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + } + + //- rjf: add traps for PID-correllated entry points + if(!entries_found) + { + for(CTRL_Entity *e = entity_ctx->root->first; e != &ctrl_entity_nil; e = e->next) + { + if(e->id == process->id) + { + U32 procedure_id = 0; + { + String8 name = e->string; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + } + + //- rjf: add traps for all custom user entry points + if(!entries_found) + { + for(String8Node *n = ctrl_state->user_entry_points.first; n != 0; n = n->next) + { + U32 procedure_id = 0; + { + String8 name = n->string; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + break; + } + } + } + + //- rjf: add traps for all high-level entry points + if(!entries_found) + { + String8 hi_entry_points[] = + { + str8_lit("WinMain"), + str8_lit("wWinMain"), + str8_lit("main"), + str8_lit("wmain"), + }; + for(U64 idx = 0; idx < ArrayCount(hi_entry_points); idx += 1) + { + U32 procedure_id = 0; + { + String8 name = hi_entry_points[idx]; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + + //- rjf: add trap for PE header entry + if(!entries_found) + { + U64 voff = ctrl_entry_point_voff_from_module(module->handle); + if(voff != 0) + { + DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + + //- rjf: add traps for all low-level entry points + if(!entries_found) + { + String8 lo_entry_points[] = + { + str8_lit("WinMainCRTStartup"), + str8_lit("wWinMainCRTStartup"), + str8_lit("mainCRTStartup"), + str8_lit("wmainCRTStartup"), + }; + for(U64 idx = 0; idx < ArrayCount(lo_entry_points); idx += 1) + { + U32 procedure_id = 0; + { + String8 name = lo_entry_points[idx]; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Symbol *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle.dmn_handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + + //- rjf: no entry point found -> done + if(entry_traps.trap_count == 0) + { + hard_stop = 1; + } + + //- rjf: found entry points -> add to joined traps + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &entry_traps); + + access_close(access); + } + + ////////////////////////// + //- rjf: unpack info about thread attached to event + // + CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->thread)); + CTRL_Entity *process = ctrl_entity_from_handle(entity_ctx, ctrl_handle_make(CTRL_MachineID_Local, event->process)); + Arch arch = thread->arch; + U64 thread_rip_vaddr = dmn_rip_from_thread(event->thread); + CTRL_Entity *module = ctrl_module_from_process_vaddr(process, thread_rip_vaddr); + + ////////////////////////// + //- rjf: extract module-dependent info + // + U64 thread_rip_voff = thread_rip_vaddr - module->vaddr_range.min; + + ////////////////////////// + //- rjf: stepping logic + // + //{ + + ////////////////////////// + //- rjf: handle if hitting a spoof + // + B32 exception_stop = 0; + B32 hit_spoof = 0; + if(!hard_stop && use_stepping_logic && event->kind == DMN_EventKind_Exception) + { + if(spoof_mode && + dmn_handle_match(target_process.dmn_handle, event->process) && + dmn_handle_match(target_thread.dmn_handle, event->thread) && + spoof.new_ip_value == event->address) + { + hit_spoof = 1; + log_infof("hit_spoof\n"); + } + else + { + exception_stop = 1; + use_stepping_logic = 0; + } + } + + //- rjf: handle spoof hit + if(hit_spoof) + { + log_infof("exit_spoof_mode\n"); + + // rjf: clear spoof mode + spoof_mode = 0; + MemoryZeroStruct(&spoof); + + // rjf: skip remainder of handling + use_stepping_logic = 0; + } + + //- rjf: for breakpoint events, gather bp info + B32 hit_entry = 0; + B32 hit_user_bp = 0; + B32 hit_trap_net_bp = 0; + B32 hit_conditional_bp_but_filtered = 0; + CTRL_TrapFlags hit_trap_flags = 0; + if(!hard_stop && use_stepping_logic && event->kind == DMN_EventKind_Breakpoint) + ProfScope("for breakpoint events, gather bp info") + { + Temp temp = temp_begin(scratch.arena); + String8List conditions = {0}; + + // rjf: entry breakpoints + for(DMN_TrapChunkNode *n = entry_traps.first; n != 0; n = n->next) + { + DMN_Trap *trap = n->v; + DMN_Trap *opl = n->v + n->count; + for(;trap < opl; trap += 1) + { + if(dmn_handle_match(trap->process, event->process) && trap->vaddr == event->instruction_pointer) + { + hit_entry = 1; + } + } + } + + // rjf: user breakpoints + { + if(event->user_data != 0) + { + hit_user_bp = 1; + } + for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next) + { + DMN_Trap *trap = n->v; + DMN_Trap *opl = n->v + n->count; + for(;trap < opl; trap += 1) + { + if(dmn_handle_match(trap->process, event->process) && + trap->vaddr == event->instruction_pointer && + (!dmn_handle_match(event->thread, target_thread.dmn_handle) || !target_thread_is_on_user_bp_and_trap_net_trap)) + { + CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)trap->id; + hit_user_bp = 1; + if(user_bp != 0 && !(trap->id & bit64) && user_bp->condition.size != 0) + { + str8_list_push(temp.arena, &conditions, user_bp->condition); + } + } + } + } + } + + // rjf: programmatic user breakpoints (we do not have state for it, + // but the target program(s) did something breakpoint-like, and we + // want to treat it as if we did) + if(event->address != 0) + { + hit_user_bp = 1; + } + + // rjf: evaluate hit stop conditions + if(conditions.node_count != 0) ProfScope("evaluate hit stop conditions") + { + CTRL_EvalScope *eval_scope = ctrl_thread__eval_scope_begin(temp.arena, &msg->user_bps, thread); + for(String8Node *condition_n = conditions.first; condition_n != 0; condition_n = condition_n->next) + { + // rjf: evaluate + E_Eval eval = zero_struct; + ProfScope("evaluate expression") + { + eval = e_eval_from_string(condition_n->string); + } + + // rjf: interpret evaluation + if(eval.code == E_InterpretationCode_Good && eval.value.u64 == 0) + { + hit_user_bp = 0; + hit_conditional_bp_but_filtered = 1; + log_infof("conditional_breakpoint_hit: 'condition eval'd to 0, and so filtered'\n"); + } + else + { + hit_user_bp = 1; + hit_conditional_bp_but_filtered = 0; + log_infof("conditional_breakpoint_hit: 'conditional eval'd to nonzero, hit'\n"); + break; + } + } + ctrl_thread__eval_scope_end(eval_scope); + } + + // rjf: gather trap net hits + ProfScope("gather trap net hits") + { + if(!hit_user_bp && dmn_handle_match(event->process, target_process.dmn_handle)) + { + for(CTRL_TrapNode *node = msg->traps.first; + node != 0; + node = node->next) + { + if(node->v.vaddr == event->instruction_pointer) + { + hit_trap_net_bp = 1; + hit_trap_flags |= node->v.flags; + } + } + } + } + + log_infof("user_breakpoint_hit: %i\n", hit_user_bp); + log_infof("entry_point_hit: %i\n", hit_entry); + temp_end(temp); + } + + //- rjf: hit conditional user bp but filtered -> single step + B32 cond_bp_single_step_stop = 0; + CTRL_EventCause cond_bp_single_step_stop_cause = CTRL_EventCause_Null; + if(hit_conditional_bp_but_filtered) LogInfoNamedBlockF("conditional_bp_hit_single_step") + { + DMN_Handle thread = event->thread; + U64 thread_pre_rip = dmn_rip_from_thread(thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 single_step_done = 0; !single_step_done;) + { + DMN_RunCtrls single_step_ctrls = {0}; + single_step_ctrls.run_entities_are_unfrozen = 1; + single_step_ctrls.run_entities = &thread; + single_step_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + single_step_ctrls.single_step_thread = thread; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(thread); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: + case DMN_EventKind_Exception: + case DMN_EventKind_Halt: + case DMN_EventKind_Trap: + { + cond_bp_single_step_stop = 1; + single_step_done = 1; + use_stepping_logic = 0; + cond_bp_single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + case DMN_EventKind_SingleStep: + { + single_step_done = dmn_handle_match(event->thread, thread); + cond_bp_single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + } + } + } + + //- rjf: hit entry points on *any thread* cause a stop, if this msg says as such + B32 entry_stop = 0; + if(msg->run_flags & CTRL_RunFlag_StopOnEntryPoint && hit_entry) + { + entry_stop = 1; + use_stepping_logic = 0; + } + + //- rjf: user breakpoints on *any thread* cause a stop + B32 user_bp_stop = 0; + if(!hard_stop && use_stepping_logic && hit_user_bp) + { + user_bp_stop = 1; + use_stepping_logic = 0; + } + + //- rjf: trap net on off-target threads are ignored + B32 step_past_trap_net = 0; + if(!hard_stop && use_stepping_logic && hit_trap_net_bp) + { + if(!dmn_handle_match(event->thread, target_thread.dmn_handle)) + { + step_past_trap_net = 1; + use_stepping_logic = 0; + } + } + + //- rjf: trap net on on-target threads trigger trap net logic + B32 use_trap_net_logic = 0; + if(!hard_stop && use_stepping_logic && hit_trap_net_bp) + { + if(dmn_handle_match(event->thread, target_thread.dmn_handle)) + { + use_trap_net_logic = 1; + } + } + + //- rjf: trap net logic: stack pointer check + B32 stack_pointer_matches = 0; + if(use_trap_net_logic) + { + U64 sp = dmn_rsp_from_thread(target_thread.dmn_handle); + stack_pointer_matches = (sp == sp_check_value); + } + + //- rjf: trap net logic: single step after hit + B32 single_step_stop = 0; + CTRL_EventCause single_step_stop_cause = CTRL_EventCause_Null; + if(!hard_stop && use_trap_net_logic) + { + if(hit_trap_flags & CTRL_TrapFlag_SingleStepAfterHit) LogInfoNamedBlockF("trap_net__single_step_after_hit") + { + U64 thread_pre_rip = dmn_rip_from_thread(target_thread.dmn_handle); + U64 thread_post_rip = thread_pre_rip; + for(B32 single_step_done = 0; single_step_done == 0;) + { + DMN_RunCtrls single_step_ctrls = {0}; + single_step_ctrls.run_entities_are_unfrozen = 1; + single_step_ctrls.run_entities = &target_thread.dmn_handle; + single_step_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + single_step_ctrls.single_step_thread = target_thread.dmn_handle; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(target_thread.dmn_handle); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: + case DMN_EventKind_Exception: + case DMN_EventKind_Halt: + case DMN_EventKind_Trap: + { + single_step_stop = 1; + single_step_done = 1; + use_stepping_logic = 0; + use_trap_net_logic = 0; + single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + case DMN_EventKind_SingleStep: + { + single_step_done = dmn_handle_match(event->thread, target_thread.dmn_handle); + single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + } + } + } + } + + //- rjf: trap net logic: begin spoof mode + B32 begin_spoof_mode = 0; + if(!hard_stop && use_trap_net_logic) + { + if(hit_trap_flags & CTRL_TrapFlag_BeginSpoofMode) LogInfoNamedBlockF("trap_net__begin_spoof_mode") + { + // rjf: setup spoof mode + begin_spoof_mode = 1; + U64 spoof_sp = dmn_rsp_from_thread(target_thread.dmn_handle); + spoof_mode = 1; + spoof.process = target_process.dmn_handle; + spoof.thread = target_thread.dmn_handle; + spoof.vaddr = spoof_sp; + spoof.new_ip_value = spoof_ip_vaddr; + log_infof("spoof:{process:[0x%I64x], thread:[0x%I64x], vaddr:0x%I64x, new_ip_value:0x%I64x}\n", spoof.process.u64[0], spoof.thread.u64[0], spoof.vaddr, spoof.new_ip_value); + } + } + + //- rjf: trap net logic: save stack pointer + B32 save_stack_pointer = 0; + if(!hard_stop && use_trap_net_logic) + { + if(hit_trap_flags & CTRL_TrapFlag_SaveStackPointer) + { + if(stack_pointer_matches) LogInfoNamedBlockF("trap_net__save_sp") + { + save_stack_pointer = 1; + sp_check_value = dmn_rsp_from_thread(target_thread.dmn_handle); + log_infof("sp_check_value = 0x%I64x\n", sp_check_value); + } + } + } + + //- rjf: trap net logic: end stepping + B32 trap_net_stop = 0; + if(!hard_stop && use_trap_net_logic) + { + if(hit_trap_flags & CTRL_TrapFlag_EndStepping) LogInfoNamedBlockF("trap_net__end_step") + { + if((hit_trap_flags & CTRL_TrapFlag_IgnoreStackPointerCheck) || + stack_pointer_matches) + { + trap_net_stop = 1; + use_trap_net_logic = 0; + } + } + } + + //} + // + //- rjf: stepping logic + //////////////////////////////// + + //- rjf: handle step past trap net + B32 step_past_trap_net_stop = 0; + CTRL_EventCause step_past_trap_net_stop_cause = CTRL_EventCause_Null; + if(step_past_trap_net) LogInfoNamedBlockF("trap_net__single_step_past_trap_net") + { + DMN_Handle thread = event->thread; + U64 thread_pre_rip = dmn_rip_from_thread(thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 single_step_done = 0; single_step_done == 0;) + { + DMN_RunCtrls single_step_ctrls = {0}; + single_step_ctrls.run_entities_are_unfrozen = 1; + single_step_ctrls.run_entities = &thread; + single_step_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + single_step_ctrls.single_step_thread = thread; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(thread); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: + case DMN_EventKind_Exception: + case DMN_EventKind_Halt: + case DMN_EventKind_Trap: + { + step_past_trap_net_stop = 1; + single_step_done = 1; + step_past_trap_net_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + case DMN_EventKind_SingleStep: + { + single_step_done = dmn_handle_match(event->thread, thread); + step_past_trap_net_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + } + } + } + + //- rjf: loop exit condition + CTRL_EventCause stage_stop_cause = CTRL_EventCause_Null; + if(hard_stop) + { + stage_stop_cause = hard_stop_cause; + } + else if(cond_bp_single_step_stop) + { + stage_stop_cause = cond_bp_single_step_stop_cause; + } + else if(single_step_stop) + { + stage_stop_cause = single_step_stop_cause; + } + else if(step_past_trap_net_stop) + { + stage_stop_cause = step_past_trap_net_stop_cause; + } + else if(exception_stop) + { + stage_stop_cause = CTRL_EventCause_InterruptedByException; + } + else if(user_bp_stop) + { + stage_stop_cause = CTRL_EventCause_UserBreakpoint; + } + else if(entry_stop) + { + stage_stop_cause = CTRL_EventCause_EntryPoint; + } + else if(trap_net_stop) + { + stage_stop_cause = CTRL_EventCause_Finished; + } + log_infof("stop_cause: %i\n", stage_stop_cause); + if(stage_stop_cause != CTRL_EventCause_Null) + { + stop_event = event; + stop_cause = stage_stop_cause; + break; + } + } + } + + ////////////////////////////// + //- rjf: record stop + // + if(stop_event != 0) + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = stop_cause; + event->entity = ctrl_handle_make(CTRL_MachineID_Local, stop_event->thread); + event->parent = ctrl_handle_make(CTRL_MachineID_Local, stop_event->process); + event->exception_code = stop_event->code; + event->exception_kind = ctrl_exception_kind_from_dmn(stop_event->exception_kind); + event->vaddr_rng = r1u64(stop_event->address, stop_event->address); + event->rip_vaddr = stop_event->instruction_pointer; + if(stop_cause == CTRL_EventCause_UserBreakpoint && stop_event->user_data != 0) + { + if(!(stop_event->user_data & bit64)) + { + CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)stop_event->user_data; + event->u64_code = user_bp->id; + } + } + ctrl_c2u_push_events(&evts); + } + + log_infof("}\n\n"); + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + + //- rjf: record start + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Started; + ctrl_c2u_push_events(&evts); + } + + //- rjf: single step + DMN_Handle thread = msg->entity.dmn_handle; + B32 thread_is_valid = !dmn_handle_match(thread, dmn_handle_zero()); + DMN_Event *stop_event = 0; + CTRL_EventCause stop_cause = CTRL_EventCause_Null; + if(thread_is_valid) + { + U64 thread_pre_rip = dmn_rip_from_thread(thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 done = 0; done == 0;) + { + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities = &thread; + run_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + run_ctrls.single_step_thread = msg->entity.dmn_handle; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(msg->entity.dmn_handle); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: {stop_cause = CTRL_EventCause_Error;}goto end_single_step; + case DMN_EventKind_Exception: {stop_cause = CTRL_EventCause_InterruptedByException;}goto end_single_step; + case DMN_EventKind_Halt: {stop_cause = CTRL_EventCause_InterruptedByHalt;}goto end_single_step; + case DMN_EventKind_Trap: {stop_cause = CTRL_EventCause_InterruptedByTrap;}goto end_single_step; + case DMN_EventKind_Breakpoint: {stop_cause = CTRL_EventCause_UserBreakpoint;}goto end_single_step; + case DMN_EventKind_SingleStep: {stop_cause = CTRL_EventCause_Finished;}goto end_single_step; + end_single_step: + { + stop_event = event; + done = 1; + }break; + } + } + } + + //- rjf: record stop + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = stop_cause; + if(stop_event != 0) + { + event->entity = ctrl_handle_make(CTRL_MachineID_Local, stop_event->thread); + event->parent = ctrl_handle_make(CTRL_MachineID_Local, stop_event->process); + event->exception_code = stop_event->code; + event->exception_kind = ctrl_exception_kind_from_dmn(stop_event->exception_kind); + event->vaddr_rng = r1u64(stop_event->address, stop_event->address); + event->rip_vaddr = stop_event->instruction_pointer; + } + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Process Memory Artifact Cache Hooks / Lookups + +internal AC_Artifact +ctrl_memory_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +{ + AC_Artifact artifact = {0}; + { + //- rjf: unpack key + CTRL_Handle process = {0}; + Rng1U64 vaddr_range = {0}; + B32 zero_terminated = 0; + { + U64 key_read_off = 0; + key_read_off += str8_deserial_read_struct(key, key_read_off, &process); + key_read_off += str8_deserial_read_struct(key, key_read_off, &vaddr_range); + key_read_off += str8_deserial_read_struct(key, key_read_off, &zero_terminated); + } + + //- rjf: clamp vaddr range + Rng1U64 vaddr_range_clamped = vaddr_range; + { + vaddr_range_clamped.max = Max(vaddr_range_clamped.max, vaddr_range_clamped.min); + U64 max_size_cap = Min(max_U64-vaddr_range_clamped.min, GB(1)); + vaddr_range_clamped.max = Min(vaddr_range_clamped.max, vaddr_range_clamped.min+max_size_cap); + } + + //- rjf: do read + U64 range_size = dim_1u64(vaddr_range_clamped); + Arena *range_arena = 0; + void *range_base = 0; + U64 zero_terminated_size = 0; + U64 pre_read_mem_gen = ctrl_mem_gen(); + B32 pre_run_state = ins_atomic_u64_eval(&ctrl_state->ctrl_thread_run_state); + if(range_size != 0) + { + // rjf: set up arena + U64 page_size = os_get_system_info()->page_size; // TODO(rjf): @page_size_from_process + U64 arena_size = AlignPow2(range_size + ARENA_HEADER_SIZE, page_size); + range_arena = arena_alloc(.reserve_size = range_size+ARENA_HEADER_SIZE, .commit_size = range_size+ARENA_HEADER_SIZE); + + // rjf: if we got an arena -> push buffer & read + if(range_arena != 0) + { + // rjf: read as much as possible + range_base = push_array_no_zero(range_arena, U8, range_size); + U64 bytes_read = 0; + U64 retry_count = 0; + U64 retry_limit = range_size > page_size ? 64 : 0; + for(Rng1U64 vaddr_range_clamped_retry = vaddr_range_clamped; + retry_count <= retry_limit; + retry_count += 1) + { + bytes_read = dmn_process_read(process.dmn_handle, vaddr_range_clamped_retry, range_base); + if(bytes_read == 0 && vaddr_range_clamped_retry.max > vaddr_range_clamped_retry.min) + { + U64 diff = (vaddr_range_clamped_retry.max-vaddr_range_clamped_retry.min)/2; + vaddr_range_clamped_retry.max -= diff; + vaddr_range_clamped_retry.max = AlignDownPow2(vaddr_range_clamped_retry.max, page_size); + if(diff == 0) + { + break; + } + } + else + { + break; + } + } + + // rjf: if we read nothing, release arena + if(bytes_read == 0) + { + arena_release(range_arena); + range_base = 0; + range_arena = 0; + } + + // rjf: if we only got a partial read, zero the rest + else if(bytes_read < range_size) + { + MemoryZero((U8 *)range_base + bytes_read, range_size-bytes_read); + } + + // rjf: determine final size; zero terminate if needed; pop any unneeded bytes if zero-terminating + zero_terminated_size = bytes_read; + if(zero_terminated && range_base != 0) + { + for(U64 idx = 0; idx < bytes_read; idx += 1) + { + if(((U8 *)range_base)[idx] == 0) + { + zero_terminated_size = idx; + break; + } + } + U64 bytes_overkill = (bytes_read - zero_terminated_size); + arena_pop(range_arena, bytes_overkill); + } + } + } + U64 post_read_mem_gen = ctrl_mem_gen(); + B32 post_run_state = ins_atomic_u64_eval(&ctrl_state->ctrl_thread_run_state); + + //- rjf: form content key + C_Key content_key = {0}; + { + content_key.id.u128[0] = u128_hash_from_str8(key); + } + + //- rjf: determine if we have any history for this key + B32 key_has_history = 0; + { + U128 last_hash = c_hash_from_key(content_key, 0); + if(!u128_match(last_hash, u128_zero())) + { + key_has_history = 1; + } + } + + //- rjf: read successful, OR we have no history -> submit to hash store + U128 hash = {0}; + if((zero_terminated_size > 0 || !key_has_history) && range_base != 0 && range_size != 0 && pre_read_mem_gen == post_read_mem_gen) + { + hash = c_submit_data(content_key, &range_arena, str8((U8 *)range_base, zero_terminated_size)); + gen_out[0] = pre_read_mem_gen; + } + + //- rjf: wakeup on new submissions + if(!u128_match(u128_zero(), hash)) + { + if(ctrl_state->wakeup_hook != 0) + { + ctrl_state->wakeup_hook(); + } + } + + //- rjf: always release leftover arenas + if(range_arena != 0) + { + arena_release(range_arena); + } + + //- rjf: retry on mem gen "tearing", and if the range is non-empty + if(pre_read_mem_gen != post_read_mem_gen && range_size != 0) + { + retry_out[0] = 1; + } + + //- rjf: bundle content key as artifact + StaticAssert(sizeof(content_key) == sizeof(artifact), artifact_key_size_check); + MemoryCopyStruct(&artifact, &content_key); + } + return artifact; +} + +internal void +ctrl_memory_artifact_destroy(AC_Artifact artifact) +{ + C_Key key = {0}; + MemoryCopyStruct(&key, &artifact); + c_close_key(key); +} + +internal C_Key +ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, B32 wait_for_fresh, U64 endt_us, B32 *out_is_stale) +{ + ProfBeginFunction(); +#pragma pack(push, 1) + struct + { + CTRL_Handle process; + Rng1U64 vaddr_range; + B32 zero_terminated; + } key_data = {process, vaddr_range, zero_terminated}; +#pragma pack(pop) + String8 key = str8_struct(&key_data); + Access *access = access_open(); + AC_Artifact artifact = ac_artifact_from_key(access, key, ctrl_memory_artifact_create, ctrl_memory_artifact_destroy, endt_us, + .flags = AC_Flag_HighPriority | (wait_for_fresh ? AC_Flag_WaitForFresh : 0), + .gen = ctrl_mem_gen(), + .slots_count = 2048, + .stale_out = out_is_stale, + .evict_threshold_us = 10000000); + C_Key content_key = {0}; + MemoryCopyStruct(&content_key, &artifact); + access_close(access); + ProfEnd(); + return content_key; +} + +//- rjf: process memory reading helpers + +internal CTRL_ProcessMemorySlice +ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, B32 wait_for_fresh, U64 endt_us) +{ + ProfBeginFunction(); + CTRL_ProcessMemorySlice result = {0}; + if(range.max > range.min && + dim_1u64(range) <= MB(256) && + range.min <= 0x000FFFFFFFFFFFFFull && + range.max <= 0x000FFFFFFFFFFFFFull) + { + Temp scratch = scratch_begin(&arena, 1); + Access *access = access_open(); + + //- rjf: unpack address range, prepare per-touched-page info + U64 page_size = KB(4); + Rng1U64 page_range = r1u64(AlignDownPow2(range.min, page_size), AlignPow2(range.max, page_size)); + U64 page_count = dim_1u64(page_range)/page_size; + U128 *page_hashes = push_array(scratch.arena, U128, page_count); + U128 *page_last_hashes = push_array(scratch.arena, U128, page_count); + + //- rjf: gather hashes & last-hashes for each page + ProfScope("gather hashes & last-hashes for each page") + { + for(U64 page_idx = 0; page_idx < page_count; page_idx += 1) + { + U64 page_base_vaddr = page_range.min + page_idx*page_size; + B32 page_is_stale = 0; + C_Key page_key = ctrl_key_from_process_vaddr_range(process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0, wait_for_fresh, endt_us, &page_is_stale); + U128 page_hash = c_hash_from_key(page_key, 0); + U128 page_last_hash = c_hash_from_key(page_key, 1); + result.stale = (result.stale || page_is_stale); + page_hashes[page_idx] = page_hash; + page_last_hashes[page_idx] = page_last_hash; + } + } + + //- rjf: setup output buffers + void *read_out = push_array(arena, U8, dim_1u64(range)); + U64 *byte_bad_flags = push_array(arena, U64, (dim_1u64(range)+63)/64); + U64 *byte_changed_flags = push_array(arena, U64, (dim_1u64(range)+63)/64); + + //- rjf: iterate pages, fill output + ProfScope("iterate pages, fill output") + { + U64 write_off = 0; + for(U64 page_idx = 0; page_idx < page_count; page_idx += 1) + { + // rjf: read data for this page + String8 data = c_data_from_hash(access, page_hashes[page_idx]); + Rng1U64 data_vaddr_range = r1u64(page_range.min + page_idx*page_size, page_range.min + page_idx*page_size+data.size); + + // rjf: skip/chop bytes which are irrelevant for the actual requested read + String8 in_range_data = data; + if(page_idx == page_count-1 && data_vaddr_range.max > range.max) + { + in_range_data = str8_chop(in_range_data, data_vaddr_range.max-range.max); + } + if(page_idx == 0 && range.min > data_vaddr_range.min) + { + in_range_data = str8_skip(in_range_data, range.min-data_vaddr_range.min); + } + + // rjf: write this chunk + MemoryCopy((U8*)read_out+write_off, in_range_data.str, in_range_data.size); + + // rjf; if this page's data doesn't fill the entire range, mark + // missing bytes as bad + if(data.size < page_size) ProfScope("mark missing bytes as bad") + { + Rng1U64 invalid_range = r1u64(data_vaddr_range.min+data.size, data_vaddr_range.min + page_size); + Rng1U64 in_range_invalid_range = intersect_1u64(invalid_range, range); + for(U64 invalid_vaddr = in_range_invalid_range.min; + invalid_vaddr < in_range_invalid_range.max; + invalid_vaddr += 1) + { + U64 idx_in_range = invalid_vaddr - range.min; + byte_bad_flags[idx_in_range/64] |= (1ull<<(idx_in_range%64)); + } + } + + // rjf: if this page's hash & last_hash don't match, diff each byte & + // fill out changed flags + if(!u128_match(page_hashes[page_idx], page_last_hashes[page_idx])) ProfScope("hashes don't match; diff each byte") + { + String8 last_data = c_data_from_hash(access, page_last_hashes[page_idx]); + String8 in_range_last_data = last_data; + if(page_idx == page_count-1 && data_vaddr_range.max > range.max) + { + in_range_last_data = str8_chop(in_range_last_data, data_vaddr_range.max-range.max); + } + if(page_idx == 0 && range.min > data_vaddr_range.min) + { + in_range_last_data = str8_skip(in_range_last_data, range.min-data_vaddr_range.min); + } + for(U64 idx = 0; idx < in_range_data.size; idx += 1) + { + U8 last_byte = idx < in_range_last_data.size ? in_range_last_data.str[idx] : 0; + U8 now_byte = idx < in_range_data.size ? in_range_data.str[idx] : 0; + if(last_byte != now_byte) + { + U64 idx_in_read_out = write_off+idx; + byte_changed_flags[idx_in_read_out/64] |= (1ull<<(idx_in_read_out%64)); + } + } + } + + // rjf: increment past this chunk + U64 bytes_to_skip = page_size; + if(page_idx == 0 && range.min > data_vaddr_range.min) + { + bytes_to_skip -= (range.min-data_vaddr_range.min); + } + write_off += bytes_to_skip; + } + } + + //- rjf: fill result + result.data.str = (U8*)read_out; + result.data.size = dim_1u64(range); + result.byte_bad_flags = byte_bad_flags; + result.byte_changed_flags = byte_changed_flags; + if(byte_bad_flags != 0) + { + for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1) + { + result.any_byte_bad = result.any_byte_bad || !!result.byte_bad_flags[idx]; + } + } + if(byte_changed_flags != 0) + { + for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1) + { + result.any_byte_changed = result.any_byte_changed || !!result.byte_changed_flags[idx]; + } + } + + access_close(access); + scratch_end(scratch); + } + ProfEnd(); + return result; +} + +internal B32 +ctrl_process_memory_read(CTRL_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us) +{ + Temp scratch = scratch_begin(0, 0); + U64 needed_size = dim_1u64(range); + CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process, range, 0, endt_us); + B32 good = (slice.data.size >= needed_size && !slice.any_byte_bad); + if(good) + { + MemoryCopy(out, slice.data.str, needed_size); + } + if(slice.stale && is_stale_out) + { + *is_stale_out = 1; + } + scratch_end(scratch); + return good; +} + +//- rjf: process memory writing + +internal B32 +ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src) +{ + ProfBeginFunction(); + B32 result = dmn_process_write(process.dmn_handle, range, src); + + //- rjf: success -> bump generation + if(result) + { + ins_atomic_u64_inc_eval(&ctrl_state->mem_gen); + } + + //- rjf: success -> wait for cache updates, for small regions - prefer relatively seamless + // writes within calling frame's "view" of the memory, at the expense of a small amount of + // time. + if(result) + { + U64 endt_us = os_now_microseconds()+10000; + U64 page_size = os_get_system_info()->page_size; // TODO(rjf): @page_size_from_process + Rng1U64 page_range = r1u64(range.min/page_size, (range.max+page_size-1)/page_size); + for EachInRange(page_idx, page_range) + { + Temp scratch = scratch_begin(0, 0); + CTRL_ProcessMemorySlice slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process, r1u64(page_idx*page_size, (page_idx+1)*page_size), 1, endt_us); + scratch_end(scratch); + if(!slice.stale || os_now_microseconds() >= endt_us) + { + break; + } + } + } + + ProfEnd(); + return result; +} + +//////////////////////////////// +//~ rjf: Call Stack Artifact Cache Hooks / Lookups + +internal AC_Artifact +ctrl_call_stack_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +{ + AC_Artifact artifact = {0}; + { + Temp scratch = scratch_begin(0, 0); + + //- rjf: unpack key + CTRL_Handle thread_handle = {0}; + str8_deserial_read_struct(key, 0, &thread_handle); + + //- rjf: produce mini entity context for just this call stack build + CTRL_EntityCtx *entity_ctx = push_array(scratch.arena, CTRL_EntityCtx, 1); + MutexScopeR(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) + { + CTRL_EntityCtx *src_ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + CTRL_EntityCtx *dst_ctx = entity_ctx; + { + dst_ctx->root = &ctrl_entity_nil; + dst_ctx->hash_slots_count = 1024; + dst_ctx->hash_slots = push_array(scratch.arena, CTRL_EntityHashSlot, dst_ctx->hash_slots_count); + MemoryCopyArray(dst_ctx->entity_kind_counts, src_ctx->entity_kind_counts); + MemoryCopyArray(dst_ctx->entity_kind_alloc_gens, src_ctx->entity_kind_alloc_gens); + } + CTRL_Entity *src_thread = ctrl_entity_from_handle(src_ctx, thread_handle); + CTRL_Entity *src_process = ctrl_process_from_entity(src_thread); + { + CTRL_EntityRec rec = {0}; + CTRL_Entity *dst_parent = &ctrl_entity_nil; + for(CTRL_Entity *src_e = src_process; src_e != &ctrl_entity_nil; src_e = rec.next) + { + rec = ctrl_entity_rec_depth_first_pre(src_e, src_process); + + // rjf: determine if we need this entity + B32 need_this_entity = (ctrl_handle_match(thread_handle, src_e->handle) || src_e->kind == CTRL_EntityKind_Module || src_e->kind == CTRL_EntityKind_Process || src_e->kind == CTRL_EntityKind_DebugInfoPath); + + // rjf: copy this entity + CTRL_Entity *dst_e = &ctrl_entity_nil; + if(need_this_entity) + { + dst_e = push_array(scratch.arena, CTRL_Entity, 1); + { + dst_e->first = dst_e->last = dst_e->next = dst_e->prev = &ctrl_entity_nil; + dst_e->parent = dst_parent; + dst_e->kind = src_e->kind; + dst_e->arch = src_e->arch; + dst_e->is_frozen = src_e->is_frozen; + dst_e->is_soloed = src_e->is_soloed; + dst_e->rgba = src_e->rgba; + dst_e->handle = src_e->handle; + dst_e->id = src_e->id; + dst_e->vaddr_range = src_e->vaddr_range; + dst_e->stack_base = src_e->stack_base; + dst_e->timestamp = src_e->timestamp; + dst_e->bp_flags = src_e->bp_flags; + dst_e->string = push_str8_copy(scratch.arena, src_e->string); + dst_e->tls_index = src_e->tls_index; + dst_e->tls_offset = src_e->tls_offset; + dst_e->target_os = src_e->target_os; + dst_e->tls_model = src_e->tls_model; + } + if(dst_parent == &ctrl_entity_nil) + { + dst_ctx->root = dst_e; + } + else + { + DLLPushBack_NPZ(&ctrl_entity_nil, dst_parent->first, dst_parent->last, dst_e, next, prev); + } + } + + // rjf: insert into hash map + if(dst_e != &ctrl_entity_nil) + { + U64 hash = ctrl_hash_from_handle(dst_e->handle); + U64 slot_idx = hash%dst_ctx->hash_slots_count; + CTRL_EntityHashSlot *slot = &dst_ctx->hash_slots[slot_idx]; + CTRL_EntityHashNode *node = 0; + for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) + { + if(ctrl_handle_match(n->entity->handle, dst_e->handle)) + { + node = n; + break; + } + } + if(node == 0) + { + node = push_array(scratch.arena, CTRL_EntityHashNode, 1); + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + node->entity = dst_e; + } + } + + // rjf: push/pop + if(rec.push_count) + { + dst_parent = dst_e; + } + else for(S32 pop_idx = 0; pop_idx < rec.pop_count; pop_idx += 1) + { + dst_parent = dst_parent->parent; + } + } + } + } + + //- rjf: compute call stack + CTRL_Entity *thread = ctrl_entity_from_handle(entity_ctx, thread_handle); + B32 good = 1; + B32 retry = 0; + Arena *arena = 0; + CTRL_CallStack *call_stack = 0; + if(thread != &ctrl_entity_nil) + { + good = 0; + arena = arena_alloc(); + call_stack = push_array(arena, CTRL_CallStack, 1); + CTRL_Entity *process = ctrl_process_from_entity(thread); + U64 pre_reg_gen = 0; + U64 post_reg_gen = 0; + U64 pre_mem_gen = 0; + U64 post_mem_gen = 0; + CTRL_Unwind unwind = {0}; + { + pre_reg_gen = ctrl_reg_gen(); + pre_mem_gen = ctrl_mem_gen(); + unwind = ctrl_unwind_from_thread(arena, entity_ctx, thread_handle, os_now_microseconds()+5000); + if(!(unwind.flags & CTRL_UnwindFlag_Stale)) + { + good = 1; + call_stack[0] = ctrl_call_stack_from_unwind(arena, process, &unwind); + } + if(unwind.flags & CTRL_UnwindFlag_Stale) + { + retry = 1; + } + post_reg_gen = ctrl_reg_gen(); + post_mem_gen = ctrl_mem_gen(); + } + if(pre_reg_gen != post_reg_gen || pre_mem_gen != post_mem_gen) + { + good = 0; + retry = 1; + } + if(!good) + { + arena_release(arena); + } + } + + //- rjf: broadcast update + if(good && ctrl_state->wakeup_hook != 0) + { + ctrl_state->wakeup_hook(); + } + + //- rjf: bundle call stack as artifact + if(good) + { + artifact.u64[0] = (U64)arena; + artifact.u64[1] = (U64)call_stack; + } + + //- rjf: mark retry + retry_out[0] = retry; + + scratch_end(scratch); + } + return artifact; +} + +internal void +ctrl_call_stack_artifact_destroy(AC_Artifact artifact) +{ + Arena *arena = (Arena *)artifact.u64[0]; + if(arena != 0) + { + arena_release(arena); + } +} + +internal CTRL_CallStack +ctrl_call_stack_from_thread(Access *access, CTRL_Handle thread_handle, B32 high_priority, U64 endt_us) +{ + CTRL_CallStack result = {0}; + { + AC_Artifact artifact = ac_artifact_from_key(access, str8_struct(&thread_handle), ctrl_call_stack_artifact_create, ctrl_call_stack_artifact_destroy, endt_us, + .gen = ctrl_mem_gen() + ctrl_reg_gen(), + .evict_threshold_us = 10000000, + .flags = high_priority ? AC_Flag_HighPriority : 0); + if(artifact.u64[1] != 0) + { + MemoryCopyStruct(&result, (CTRL_CallStack *)artifact.u64[1]); + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Call Stack Tree Artifact Cache Hooks / Lookups + +internal AC_Artifact +ctrl_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out) +{ + Temp scratch = scratch_begin(0, 0); + Access *access = access_open(); + + //- rjf: gather list of all thread handles + U64 threads_count = 0; + CTRL_Handle *threads = 0; + CTRL_Handle *threads_processes = 0; + Arch *threads_arches = 0; + MutexScopeR(ctrl_state->ctrl_thread_entity_ctx_rw_mutex) + { + CTRL_EntityCtx *ctx = &ctrl_state->ctrl_thread_entity_store->ctx; + CTRL_EntityArray thread_entities = ctrl_entity_array_from_kind(ctx, CTRL_EntityKind_Thread); + threads_count = thread_entities.count; + threads = push_array(scratch.arena, CTRL_Handle, threads_count); + threads_processes = push_array(scratch.arena, CTRL_Handle, threads_count); + threads_arches = push_array(scratch.arena, Arch, threads_count); + for EachIndex(idx, threads_count) + { + threads[idx] = thread_entities.v[idx]->handle; + threads_processes[idx] = thread_entities.v[idx]->parent->handle; + threads_arches[idx] = thread_entities.v[idx]->arch; + } + } + + //- rjf: gather all callstacks + B32 stale = 0; + U64 pre_mem_gen = ctrl_mem_gen(); + U64 pre_reg_gen = ctrl_reg_gen(); + CTRL_CallStack *call_stacks = push_array(scratch.arena, CTRL_CallStack, threads_count); + { + for EachIndex(idx, threads_count) + { + call_stacks[idx] = ctrl_call_stack_from_thread(access, threads[idx], 0, 0); + if(call_stacks[idx].concrete_frames_count == 0) + { + stale = 1; + break; + } + } + } + U64 post_mem_gen = ctrl_mem_gen(); + U64 post_reg_gen = ctrl_reg_gen(); + stale = (stale || pre_mem_gen != post_mem_gen || pre_reg_gen != post_reg_gen); + + //- rjf: build call stack tree + Arena *arena = 0; + CTRL_CallStackTree *tree = 0; + if(!stale) + { + U64 id_gen = 0; + arena = arena_alloc(); + tree = push_array(arena, CTRL_CallStackTree, 1); + tree->root = push_array(arena, CTRL_CallStackTreeNode, 1); + MemoryCopyStruct(tree->root, &ctrl_call_stack_tree_node_nil); + tree->root->id = id_gen; + tree->slots_count = Max(1, threads_count); + tree->slots = push_array(arena, CTRL_CallStackTreeNode *, tree->slots_count); + id_gen += 1; + for EachIndex(thread_idx, threads_count) + { + CTRL_Handle thread = threads[thread_idx]; + CTRL_Handle process = threads_processes[thread_idx]; + Arch arch = threads_arches[thread_idx]; + CTRL_CallStack call_stack = call_stacks[thread_idx]; + CTRL_CallStackTreeNode *thread_node = tree->root; + for EachIndex(frame_idx, call_stack.frames_count) + { + U64 vaddr = regs_rip_from_arch_block(arch, call_stack.frames[frame_idx].regs); + U64 depth = call_stack.frames[frame_idx].inline_depth; + CTRL_CallStackTreeNode *next_node = &ctrl_call_stack_tree_node_nil; + for(CTRL_CallStackTreeNode *child = thread_node->first; child != &ctrl_call_stack_tree_node_nil; child = child->next) + { + if(ctrl_handle_match(child->process, process) && child->vaddr == vaddr && child->depth == depth) + { + next_node = child; + break; + } + } + if(next_node == &ctrl_call_stack_tree_node_nil) + { + next_node = push_array(arena, CTRL_CallStackTreeNode, 1); + MemoryCopyStruct(next_node, &ctrl_call_stack_tree_node_nil); + next_node->id = id_gen; + SLLStackPush_N(tree->slots[next_node->id%tree->slots_count], next_node, hash_next); + id_gen += 1; + SLLQueuePush_NZ(&ctrl_call_stack_tree_node_nil, thread_node->first, thread_node->last, next_node, next); + next_node->parent = thread_node; + thread_node->child_count += 1; + } + thread_node = next_node; + } + ctrl_handle_list_push(arena, &thread_node->threads, &thread); + for(CTRL_CallStackTreeNode *n = thread_node; n != &ctrl_call_stack_tree_node_nil; n = n->parent) + { + n->all_descendant_threads_count += 1; + } + } + } + + //- rjf: produce artifact + AC_Artifact artifact = {0}; + { + artifact.u64[0] = (U64)arena; + artifact.u64[1] = (U64)tree; + } + + //- rjf: retry on stale + if(stale) + { + retry_out[0] = 1; + } + + access_close(access); + scratch_end(scratch); + return artifact; +} + +internal void +ctrl_call_stack_tree_artifact_destroy(AC_Artifact artifact) +{ + Arena *arena = (Arena *)artifact.u64[0]; + if(arena != 0) + { + arena_release(arena); + } +} + +internal CTRL_CallStackTree +ctrl_call_stack_tree(Access *access, U64 endt_us) +{ + CTRL_CallStackTree result = {&ctrl_call_stack_tree_node_nil}; + { + AC_Artifact artifact = ac_artifact_from_key(access, str8_zero(), ctrl_call_stack_tree_artifact_create, ctrl_call_stack_tree_artifact_destroy, endt_us); + if(artifact.u64[1] != 0) + { + MemoryCopyStruct(&result, (CTRL_CallStackTree *)artifact.u64[1]); + } + } + return result; +} diff --git a/src/ctrl/ctrl_core.h b/src/dbg_engine/dbg_engine_ctrl.h similarity index 69% rename from src/ctrl/ctrl_core.h rename to src/dbg_engine/dbg_engine_ctrl.h index fa9b1571..895357f9 100644 --- a/src/ctrl/ctrl_core.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -1,1072 +1,625 @@ -// Copyright (c) Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef CTRL_CORE_H -#define CTRL_CORE_H - -//////////////////////////////// -//~ rjf: ID Types - -typedef U64 CTRL_MsgID; -typedef U64 CTRL_MachineID; - -#define CTRL_MachineID_Local (1) - -//////////////////////////////// -//~ rjf: User Breakpoint Types - -typedef U32 CTRL_UserBreakpointFlags; -enum -{ - CTRL_UserBreakpointFlag_BreakOnWrite = (1<<0), - CTRL_UserBreakpointFlag_BreakOnRead = (1<<1), - CTRL_UserBreakpointFlag_BreakOnExecute = (1<<2), -}; - -typedef enum CTRL_UserBreakpointKind -{ - CTRL_UserBreakpointKind_Null, - CTRL_UserBreakpointKind_FileNameAndLineColNumber, - CTRL_UserBreakpointKind_Expression, - CTRL_UserBreakpointKind_COUNT -} -CTRL_UserBreakpointKind; - -typedef struct CTRL_UserBreakpoint CTRL_UserBreakpoint; -struct CTRL_UserBreakpoint -{ - CTRL_UserBreakpointKind kind; - CTRL_UserBreakpointFlags flags; - U64 id; - String8 string; - TxtPt pt; - U64 size; - String8 condition; -}; - -typedef struct CTRL_UserBreakpointNode CTRL_UserBreakpointNode; -struct CTRL_UserBreakpointNode -{ - CTRL_UserBreakpointNode *next; - CTRL_UserBreakpoint v; -}; - -typedef struct CTRL_UserBreakpointList CTRL_UserBreakpointList; -struct CTRL_UserBreakpointList -{ - CTRL_UserBreakpointNode *first; - CTRL_UserBreakpointNode *last; - U64 count; -}; - -//////////////////////////////// -//~ Dynamic Linker Types - -typedef U32 CTRL_TlsModel; -enum -{ - CTRL_TlsModel_Null, - CTRL_TlsModel_WinodwsNt, - CTRL_TlsModel_Gnu -}; - -//////////////////////////////// -//~ rjf: Entity Handle Types - -typedef struct CTRL_Handle CTRL_Handle; -struct CTRL_Handle -{ - CTRL_MachineID machine_id; - DMN_Handle dmn_handle; -}; - -typedef struct CTRL_HandleNode CTRL_HandleNode; -struct CTRL_HandleNode -{ - CTRL_HandleNode *next; - CTRL_Handle v; -}; - -typedef struct CTRL_HandleList CTRL_HandleList; -struct CTRL_HandleList -{ - CTRL_HandleNode *first; - CTRL_HandleNode *last; - U64 count; -}; - -typedef struct CTRL_HandleArray CTRL_HandleArray; -struct CTRL_HandleArray -{ - CTRL_Handle *v; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Generated Code - -#include "generated/ctrl.meta.h" - -//////////////////////////////// -//~ rjf: Entity Types - -typedef struct CTRL_Entity CTRL_Entity; -struct CTRL_Entity -{ - CTRL_Entity *first; - CTRL_Entity *last; - CTRL_Entity *next; - CTRL_Entity *prev; - CTRL_Entity *parent; - CTRL_EntityKind kind; - Arch arch; - B32 is_frozen; - B32 is_soloed; - U32 rgba; - CTRL_Handle handle; - U64 id; - Rng1U64 vaddr_range; - U64 stack_base; - U64 timestamp; - CTRL_UserBreakpointFlags bp_flags; - String8 string; - CTRL_TlsModel tls_model; - U64 tls_index; - U64 tls_offset; - OperatingSystem target_os; -}; - -typedef struct CTRL_EntityNode CTRL_EntityNode; -struct CTRL_EntityNode -{ - CTRL_EntityNode *next; - CTRL_Entity *v; -}; - -typedef struct CTRL_EntityList CTRL_EntityList; -struct CTRL_EntityList -{ - CTRL_EntityNode *first; - CTRL_EntityNode *last; - U64 count; -}; - -typedef struct CTRL_EntityArray CTRL_EntityArray; -struct CTRL_EntityArray -{ - CTRL_Entity **v; - U64 count; -}; - -typedef struct CTRL_EntityRec CTRL_EntityRec; -struct CTRL_EntityRec -{ - CTRL_Entity *next; - S32 push_count; - S64 pop_count; -}; - -typedef struct CTRL_EntityHashNode CTRL_EntityHashNode; -struct CTRL_EntityHashNode -{ - CTRL_EntityHashNode *next; - CTRL_EntityHashNode *prev; - CTRL_Entity *entity; -}; - -typedef struct CTRL_EntityHashSlot CTRL_EntityHashSlot; -struct CTRL_EntityHashSlot -{ - CTRL_EntityHashNode *first; - CTRL_EntityHashNode *last; -}; - -typedef struct CTRL_EntityStringChunkNode CTRL_EntityStringChunkNode; -struct CTRL_EntityStringChunkNode -{ - CTRL_EntityStringChunkNode *next; - U64 size; -}; - -read_only global U64 ctrl_entity_string_bucket_chunk_sizes[] = -{ - 16, - 64, - 256, - 1024, - 4096, - 16384, - 65536, - 0xffffffffffffffffull, -}; - -typedef struct CTRL_EntityCtx CTRL_EntityCtx; -struct CTRL_EntityCtx -{ - CTRL_Entity *root; - U64 hash_slots_count; - CTRL_EntityHashSlot *hash_slots; - U64 entity_kind_counts[CTRL_EntityKind_COUNT]; - U64 entity_kind_alloc_gens[CTRL_EntityKind_COUNT]; -}; - -typedef struct CTRL_EntityCtxRWStore CTRL_EntityCtxRWStore; -struct CTRL_EntityCtxRWStore -{ - Arena *arena; - CTRL_EntityCtx ctx; - CTRL_Entity *free; - CTRL_EntityHashNode *hash_node_free; - CTRL_EntityStringChunkNode *free_string_chunks[ArrayCount(ctrl_entity_string_bucket_chunk_sizes)]; -}; - -typedef struct CTRL_EntityCtxLookupAccel CTRL_EntityCtxLookupAccel; -struct CTRL_EntityCtxLookupAccel -{ - Arena *arena; - Arena *entity_kind_arrays_arenas[CTRL_EntityKind_COUNT]; - CTRL_EntityArray entity_kind_arrays[CTRL_EntityKind_COUNT]; - U64 entity_kind_arrays_gens[CTRL_EntityKind_COUNT]; -}; - -//////////////////////////////// -//~ rjf: Unwind Types - -typedef U32 CTRL_UnwindFlags; -enum -{ - CTRL_UnwindFlag_Error = (1<<0), - CTRL_UnwindFlag_Stale = (1<<1), -}; - -typedef struct CTRL_UnwindStepResult CTRL_UnwindStepResult; -struct CTRL_UnwindStepResult -{ - CTRL_UnwindFlags flags; -}; - -typedef struct CTRL_UnwindFrame CTRL_UnwindFrame; -struct CTRL_UnwindFrame -{ - void *regs; - U64 cfa; -}; - -typedef struct CTRL_UnwindFrameNode CTRL_UnwindFrameNode; -struct CTRL_UnwindFrameNode -{ - CTRL_UnwindFrameNode *next; - CTRL_UnwindFrameNode *prev; - CTRL_UnwindFrame v; -}; - -typedef struct CTRL_UnwindFrameArray CTRL_UnwindFrameArray; -struct CTRL_UnwindFrameArray -{ - CTRL_UnwindFrame *v; - U64 count; -}; - -typedef struct CTRL_Unwind CTRL_Unwind; -struct CTRL_Unwind -{ - CTRL_UnwindFrameArray frames; - CTRL_UnwindFlags flags; -}; - -typedef struct CTRL_FrameUnwindContext CTRL_FrameUnwindContext; -struct CTRL_FrameUnwindContext -{ - // DWARF - U64 cfa; - DW_CFI_Row *cfi_row; - U64 ret_addr_reg; -}; - -//////////////////////////////// -//~ rjf: Call Stack Types - -typedef struct CTRL_CallStackFrame CTRL_CallStackFrame; -struct CTRL_CallStackFrame -{ - U64 unwind_count; - U64 inline_depth; - void *regs; - U64 cfa; -}; - -typedef struct CTRL_CallStack CTRL_CallStack; -struct CTRL_CallStack -{ - CTRL_CallStackFrame *frames; - U64 frames_count; - CTRL_CallStackFrame **concrete_frames; - U64 concrete_frames_count; -}; - -//////////////////////////////// -//~ rjf: Call Stack Tree Types - -typedef struct CTRL_CallStackTreeNode CTRL_CallStackTreeNode; -struct CTRL_CallStackTreeNode -{ - CTRL_CallStackTreeNode *hash_next; - CTRL_CallStackTreeNode *first; - CTRL_CallStackTreeNode *last; - CTRL_CallStackTreeNode *next; - CTRL_CallStackTreeNode *parent; - U64 child_count; - U64 id; - CTRL_Handle process; - U64 vaddr; - U64 depth; - CTRL_HandleList threads; - U64 all_descendant_threads_count; -}; - -typedef struct CTRL_CallStackTree CTRL_CallStackTree; -struct CTRL_CallStackTree -{ - CTRL_CallStackTreeNode *root; - U64 slots_count; - CTRL_CallStackTreeNode **slots; -}; - -//////////////////////////////// -//~ rjf: Trap Types - -typedef U32 CTRL_TrapFlags; -enum -{ - CTRL_TrapFlag_IgnoreStackPointerCheck = (1<<0), - CTRL_TrapFlag_SingleStepAfterHit = (1<<1), - CTRL_TrapFlag_SaveStackPointer = (1<<2), - CTRL_TrapFlag_BeginSpoofMode = (1<<3), - CTRL_TrapFlag_EndStepping = (1<<4), -}; - -typedef struct CTRL_Trap CTRL_Trap; -struct CTRL_Trap -{ - CTRL_TrapFlags flags; - U64 vaddr; -}; - -typedef struct CTRL_TrapNode CTRL_TrapNode; -struct CTRL_TrapNode -{ - CTRL_TrapNode *next; - CTRL_Trap v; -}; - -typedef struct CTRL_TrapList CTRL_TrapList; -struct CTRL_TrapList -{ - CTRL_TrapNode *first; - CTRL_TrapNode *last; - U64 count; -}; - -typedef struct CTRL_Spoof CTRL_Spoof; -struct CTRL_Spoof -{ - DMN_Handle process; - DMN_Handle thread; - U64 vaddr; - U64 new_ip_value; -}; - -//////////////////////////////// -//~ rjf: Evaluation Spaces - -typedef U64 CTRL_EvalSpaceKind; -enum -{ - CTRL_EvalSpaceKind_Entity = E_SpaceKind_FirstUserDefined, - CTRL_EvalSpaceKind_FirstUserDefined, -}; - -//////////////////////////////// -//~ rjf: Message Types - -typedef enum CTRL_MsgKind -{ - CTRL_MsgKind_Null, - CTRL_MsgKind_Launch, - CTRL_MsgKind_Attach, - CTRL_MsgKind_Kill, - CTRL_MsgKind_KillAll, - CTRL_MsgKind_Detach, - CTRL_MsgKind_Run, - CTRL_MsgKind_SingleStep, - CTRL_MsgKind_SetUserEntryPoints, - CTRL_MsgKind_SetModuleDebugInfoPath, - CTRL_MsgKind_FreezeThread, - CTRL_MsgKind_ThawThread, - CTRL_MsgKind_COUNT, -} -CTRL_MsgKind; - -typedef U32 CTRL_RunFlags; -enum -{ - CTRL_RunFlag_StopOnEntryPoint = (1<<0), -}; - -typedef struct CTRL_Msg CTRL_Msg; -struct CTRL_Msg -{ - CTRL_MsgKind kind; - CTRL_RunFlags run_flags; - CTRL_MsgID msg_id; - CTRL_Handle entity; - CTRL_Handle parent; - U32 entity_id; - U32 exit_code; - B32 env_inherit; - B32 debug_subprocesses; - U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]; - String8 path; - String8List entry_points; - String8List cmd_line_string_list; - String8List env_string_list; - String8 stdout_path; - String8 stderr_path; - String8 stdin_path; - CTRL_TrapList traps; - CTRL_UserBreakpointList user_bps; -}; - -typedef struct CTRL_MsgNode CTRL_MsgNode; -struct CTRL_MsgNode -{ - CTRL_MsgNode *next; - CTRL_Msg v; -}; - -typedef struct CTRL_MsgList CTRL_MsgList; -struct CTRL_MsgList -{ - CTRL_MsgNode *first; - CTRL_MsgNode *last; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Event Types - -typedef enum CTRL_EventKind -{ - CTRL_EventKind_Null, - CTRL_EventKind_Error, - - //- rjf: starts/stops - CTRL_EventKind_Started, - CTRL_EventKind_Stopped, - - //- rjf: entity creation/deletion - CTRL_EventKind_NewProc, - CTRL_EventKind_NewThread, - CTRL_EventKind_NewModule, - CTRL_EventKind_EndProc, - CTRL_EventKind_EndThread, - CTRL_EventKind_EndModule, - - //- rjf: thread freeze state changes - CTRL_EventKind_ThreadFrozen, - CTRL_EventKind_ThreadThawed, - - //- rjf: debug info changes - CTRL_EventKind_ModuleDebugInfoPathChange, - - //- rjf: debug strings / decorations / markup - CTRL_EventKind_DebugString, - CTRL_EventKind_ThreadName, - CTRL_EventKind_ThreadColor, - CTRL_EventKind_SetBreakpoint, - CTRL_EventKind_UnsetBreakpoint, - CTRL_EventKind_SetVAddrRangeNote, - - //- rjf: memory - CTRL_EventKind_MemReserve, - CTRL_EventKind_MemCommit, - CTRL_EventKind_MemDecommit, - CTRL_EventKind_MemRelease, - - CTRL_EventKind_COUNT -} -CTRL_EventKind; - -typedef enum CTRL_EventCause -{ - CTRL_EventCause_Null, - CTRL_EventCause_Error, - CTRL_EventCause_Finished, - CTRL_EventCause_EntryPoint, - CTRL_EventCause_UserBreakpoint, - CTRL_EventCause_InterruptedByTrap, - CTRL_EventCause_InterruptedByException, - CTRL_EventCause_InterruptedByHalt, - CTRL_EventCause_COUNT -} -CTRL_EventCause; - -typedef enum CTRL_ExceptionKind -{ - CTRL_ExceptionKind_Null, - CTRL_ExceptionKind_MemoryRead, - CTRL_ExceptionKind_MemoryWrite, - CTRL_ExceptionKind_MemoryExecute, - CTRL_ExceptionKind_CppThrow, - CTRL_ExceptionKind_COUNT -} -CTRL_ExceptionKind; - -typedef struct CTRL_Event CTRL_Event; -struct CTRL_Event -{ - CTRL_EventKind kind; - CTRL_EventCause cause; - CTRL_ExceptionKind exception_kind; - CTRL_MsgID msg_id; - CTRL_Handle entity; - CTRL_Handle parent; - Arch arch; - U64 u64_code; - U32 entity_id; - Rng1U64 vaddr_rng; - U64 rip_vaddr; - U64 stack_base; - U64 tls_root; - U64 tls_index; - U64 tls_offset; - U64 timestamp; - U32 exception_code; - U32 rgba; - CTRL_UserBreakpointFlags bp_flags; - String8 string; - OperatingSystem target_os; - CTRL_TlsModel tls_model; -}; - -typedef struct CTRL_EventNode CTRL_EventNode; -struct CTRL_EventNode -{ - CTRL_EventNode *next; - CTRL_Event v; -}; - -typedef struct CTRL_EventList CTRL_EventList; -struct CTRL_EventList -{ - CTRL_EventNode *first; - CTRL_EventNode *last; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Process Memory Types - -typedef struct CTRL_ProcessMemorySlice CTRL_ProcessMemorySlice; -struct CTRL_ProcessMemorySlice -{ - String8 data; - U64 *byte_bad_flags; - U64 *byte_changed_flags; - B32 stale; - B32 any_byte_bad; - B32 any_byte_changed; -}; - -//////////////////////////////// -//~ rjf: Thread Register Cache Types - -typedef struct CTRL_ThreadRegCacheNode CTRL_ThreadRegCacheNode; -struct CTRL_ThreadRegCacheNode -{ - CTRL_ThreadRegCacheNode *next; - CTRL_ThreadRegCacheNode *prev; - CTRL_Handle handle; - U64 block_size; - void *block; - U64 reg_gen; -}; - -typedef struct CTRL_ThreadRegCacheSlot CTRL_ThreadRegCacheSlot; -struct CTRL_ThreadRegCacheSlot -{ - CTRL_ThreadRegCacheNode *first; - CTRL_ThreadRegCacheNode *last; -}; - -typedef struct CTRL_ThreadRegCacheStripe CTRL_ThreadRegCacheStripe; -struct CTRL_ThreadRegCacheStripe -{ - Arena *arena; - RWMutex rw_mutex; -}; - -typedef struct CTRL_ThreadRegCache CTRL_ThreadRegCache; -struct CTRL_ThreadRegCache -{ - U64 slots_count; - CTRL_ThreadRegCacheSlot *slots; - U64 stripes_count; - CTRL_ThreadRegCacheStripe *stripes; -}; - -//////////////////////////////// -//~ rjf: Module Image Info Cache Types - -typedef struct CTRL_ModuleImageInfoCacheNode CTRL_ModuleImageInfoCacheNode; -struct CTRL_ModuleImageInfoCacheNode -{ - CTRL_ModuleImageInfoCacheNode *next; - CTRL_ModuleImageInfoCacheNode *prev; - CTRL_Handle module; - Arena *arena; - PE_IntelPdata *pdatas; - U64 pdatas_count; - U64 cfi_rebase; - B32 is_unwind_eh; - String8 dwarf_unwind_data; - EH_FrameHdr eh_frame_hdr; - EH_PtrCtx eh_ptr_ctx; - U64 entry_point_voff; - String8 initial_debug_info_path; - U64 raddbg_attached_marker_voff; - String8 raddbg_data; -}; - -typedef struct CTRL_ModuleImageInfoCacheSlot CTRL_ModuleImageInfoCacheSlot; -struct CTRL_ModuleImageInfoCacheSlot -{ - CTRL_ModuleImageInfoCacheNode *first; - CTRL_ModuleImageInfoCacheNode *last; -}; - -typedef struct CTRL_ModuleImageInfoCacheStripe CTRL_ModuleImageInfoCacheStripe; -struct CTRL_ModuleImageInfoCacheStripe -{ - Arena *arena; - RWMutex rw_mutex; -}; - -typedef struct CTRL_ModuleImageInfoCache CTRL_ModuleImageInfoCache; -struct CTRL_ModuleImageInfoCache -{ - U64 slots_count; - CTRL_ModuleImageInfoCacheSlot *slots; - U64 stripes_count; - CTRL_ModuleImageInfoCacheStripe *stripes; -}; - -//////////////////////////////// -//~ rjf: Touched Debug Info Directory Cache - -typedef struct CTRL_DbgDirNode CTRL_DbgDirNode; -struct CTRL_DbgDirNode -{ - CTRL_DbgDirNode *first; - CTRL_DbgDirNode *last; - CTRL_DbgDirNode *next; - CTRL_DbgDirNode *prev; - CTRL_DbgDirNode *parent; - String8 name; - U64 search_count; - U64 child_count; - U64 module_direct_count; -}; - -//////////////////////////////// -//~ rjf: Control Thread Evaluation Scopes - -typedef struct CTRL_EvalScope CTRL_EvalScope; -struct CTRL_EvalScope -{ - Access *access; - E_BaseCtx base_ctx; - E_IRCtx ir_ctx; - E_InterpretCtx interpret_ctx; -}; - -//////////////////////////////// -//~ rjf: Module Requirement Cache Types - -typedef struct CTRL_ModuleReqCacheNode CTRL_ModuleReqCacheNode; -struct CTRL_ModuleReqCacheNode -{ - CTRL_ModuleReqCacheNode *next; - CTRL_Handle module; - B32 required; -}; - -//////////////////////////////// -//~ rjf: Wakeup Hook Function Types - -#define CTRL_WAKEUP_FUNCTION_DEF(name) void name(void) -typedef CTRL_WAKEUP_FUNCTION_DEF(CTRL_WakeupFunctionType); - -//////////////////////////////// -//~ rjf: Main State Types - -typedef struct CTRL_State CTRL_State; -struct CTRL_State -{ - Arena *arena; - CTRL_WakeupFunctionType *wakeup_hook; - - // rjf: name -> register/alias hash tables for eval - E_String2NumMap arch_string2reg_tables[Arch_COUNT]; - E_String2NumMap arch_string2alias_tables[Arch_COUNT]; - - // rjf: caches - CTRL_ThreadRegCache thread_reg_cache; - CTRL_ModuleImageInfoCache module_image_info_cache; - - // rjf: generations - U64 run_gen; - U64 mem_gen; - U64 reg_gen; - - // rjf: user -> ctrl msg ring buffer - U64 u2c_ring_size; - U8 *u2c_ring_base; - U64 u2c_ring_write_pos; - U64 u2c_ring_read_pos; - Mutex u2c_ring_mutex; - CondVar u2c_ring_cv; - - // rjf: ctrl -> user event ring buffer - U64 c2u_ring_size; - U64 c2u_ring_max_string_size; - U8 *c2u_ring_base; - U64 c2u_ring_write_pos; - U64 c2u_ring_read_pos; - Mutex c2u_ring_mutex; - CondVar c2u_ring_cv; - - // rjf: ctrl thread state - U64 ctrl_thread_run_state; - String8 ctrl_thread_log_path; - Thread ctrl_thread; - Log *ctrl_thread_log; - RWMutex ctrl_thread_entity_ctx_rw_mutex; - CTRL_EntityCtxRWStore *ctrl_thread_entity_store; - E_Cache *ctrl_thread_eval_cache; - Arena *ctrl_thread_msg_process_arena; - Arena *dmn_event_arena; - DMN_EventNode *first_dmn_event_node; - DMN_EventNode *last_dmn_event_node; - DMN_EventNode *free_dmn_event_node; - Arena *user_entry_point_arena; - String8List user_entry_points; - U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]; - U64 process_counter; - Arena *dbg_dir_arena; - CTRL_DbgDirNode *dbg_dir_root; - U64 module_req_cache_slots_count; - CTRL_ModuleReqCacheNode **module_req_cache_slots; - String8List msg_user_bp_touched_files; - String8List msg_user_bp_touched_symbols; -}; - -//////////////////////////////// -//~ rjf: Globals - -global CTRL_State *ctrl_state = 0; -read_only global CTRL_Entity ctrl_entity_nil = -{ - &ctrl_entity_nil, - &ctrl_entity_nil, - &ctrl_entity_nil, - &ctrl_entity_nil, - &ctrl_entity_nil, -}; -read_only global CTRL_CallStackTreeNode ctrl_call_stack_tree_node_nil = -{ - 0, - &ctrl_call_stack_tree_node_nil, - &ctrl_call_stack_tree_node_nil, - &ctrl_call_stack_tree_node_nil, - &ctrl_call_stack_tree_node_nil, -}; -thread_static CTRL_EntityCtxLookupAccel *ctrl_entity_ctx_lookup_accel = 0; - -//////////////////////////////// -//~ rjf: Logging Markup - -#define CTRL_CtrlThreadLogScope DeferLoop(log_scope_begin(), ctrl_thread__end_and_flush_log()) - -//////////////////////////////// -//~ rjf: Basic Type Functions - -internal U64 ctrl_hash_from_string(String8 string); -internal U64 ctrl_hash_from_handle(CTRL_Handle handle); -internal CTRL_EventCause ctrl_event_cause_from_dmn_event_kind(DMN_EventKind event_kind); -internal CTRL_ExceptionKind ctrl_exception_kind_from_dmn(DMN_ExceptionKind kind); -internal CTRL_TlsModel ctrl_dynamic_linker_type_from_dmn(DMN_TlsModel type); -internal String8 ctrl_string_from_event_kind(CTRL_EventKind kind); -internal String8 ctrl_string_from_msg_kind(CTRL_MsgKind kind); -internal CTRL_EntityKind ctrl_entity_kind_from_string(String8 string); -internal DMN_TrapFlags ctrl_dmn_trap_flags_from_user_breakpoint_flags(CTRL_UserBreakpointFlags flags); -internal CTRL_UserBreakpointFlags ctrl_user_breakpoint_flags_from_dmn_trap_flags(DMN_TrapFlags flags); - -//////////////////////////////// -//~ rjf: Handle Type Functions - -internal CTRL_Handle ctrl_handle_zero(void); -internal CTRL_Handle ctrl_handle_make(CTRL_MachineID machine_id, DMN_Handle dmn_handle); -internal B32 ctrl_handle_match(CTRL_Handle a, CTRL_Handle b); -internal void ctrl_handle_list_push(Arena *arena, CTRL_HandleList *list, CTRL_Handle *pair); -internal CTRL_HandleList ctrl_handle_list_copy(Arena *arena, CTRL_HandleList *src); -internal CTRL_HandleArray ctrl_handle_array_from_list(Arena *arena, CTRL_HandleList *src); -internal String8 ctrl_string_from_handle(Arena *arena, CTRL_Handle handle); -internal CTRL_Handle ctrl_handle_from_string(String8 string); - -//////////////////////////////// -//~ rjf: Trap Type Functions - -internal void ctrl_trap_list_push(Arena *arena, CTRL_TrapList *list, CTRL_Trap *trap); -internal CTRL_TrapList ctrl_trap_list_copy(Arena *arena, CTRL_TrapList *src); - -//////////////////////////////// -//~ rjf: User Breakpoint Type Functions - -internal void ctrl_user_breakpoint_list_push(Arena *arena, CTRL_UserBreakpointList *list, CTRL_UserBreakpoint *bp); -internal CTRL_UserBreakpointList ctrl_user_breakpoint_list_copy(Arena *arena, CTRL_UserBreakpointList *src); - -//////////////////////////////// -//~ rjf: Message Type Functions - -//- rjf: deep copying -internal void ctrl_msg_deep_copy(Arena *arena, CTRL_Msg *dst, CTRL_Msg *src); - -//- rjf: list building -internal CTRL_Msg *ctrl_msg_list_push(Arena *arena, CTRL_MsgList *list); -internal CTRL_MsgList ctrl_msg_list_deep_copy(Arena *arena, CTRL_MsgList *src); -internal void ctrl_msg_list_concat_in_place(CTRL_MsgList *dst, CTRL_MsgList *src); - -//- rjf: serialization -internal String8 ctrl_serialized_string_from_msg_list(Arena *arena, CTRL_MsgList *msgs); -internal CTRL_MsgList ctrl_msg_list_from_serialized_string(Arena *arena, String8 string); - -//////////////////////////////// -//~ rjf: Event Type Functions - -//- rjf: list building -internal CTRL_Event *ctrl_event_list_push(Arena *arena, CTRL_EventList *list); -internal void ctrl_event_list_concat_in_place(CTRL_EventList *dst, CTRL_EventList *to_push); - -//- rjf: serialization -internal String8 ctrl_serialized_string_from_event(Arena *arena, CTRL_Event *event, U64 max); -internal CTRL_Event ctrl_event_from_serialized_string(Arena *arena, String8 string); - -//////////////////////////////// -//~ rjf: Entity Type Functions - -//- rjf: entity list data structures -internal void ctrl_entity_list_push(Arena *arena, CTRL_EntityList *list, CTRL_Entity *entity); -internal CTRL_EntityList ctrl_entity_list_from_handle_list(Arena *arena, CTRL_EntityCtx *ctx, CTRL_HandleList *list); -#define ctrl_entity_list_first(list) ((list)->first ? (list)->first->v : &ctrl_entity_nil) - -//- rjf: entity array data structure -internal CTRL_EntityArray ctrl_entity_array_from_list(Arena *arena, CTRL_EntityList *list); -#define ctrl_entity_array_first(array) ((array)->count != 0 ? (array)->v[0] : &ctrl_entity_nil) - -//- rjf: entity context (entity group read-only) functions -internal CTRL_Entity *ctrl_entity_from_handle(CTRL_EntityCtx *ctx, CTRL_Handle handle); -internal CTRL_Entity *ctrl_entity_child_from_kind(CTRL_Entity *parent, CTRL_EntityKind kind); -internal CTRL_Entity *ctrl_entity_ancestor_from_kind(CTRL_Entity *entity, CTRL_EntityKind kind); -internal CTRL_Entity *ctrl_process_from_entity(CTRL_Entity *entity); -internal CTRL_Entity *ctrl_module_from_process_vaddr(CTRL_Entity *process, U64 vaddr); -internal DI_Key ctrl_dbgi_key_from_module(CTRL_Entity *module); -internal CTRL_Entity *ctrl_module_from_thread_candidates(CTRL_EntityCtx *ctx, CTRL_Entity *thread, CTRL_EntityList *candidates); -internal U64 ctrl_vaddr_from_voff(CTRL_Entity *module, U64 voff); -internal U64 ctrl_voff_from_vaddr(CTRL_Entity *module, U64 vaddr); -internal Rng1U64 ctrl_vaddr_range_from_voff_range(CTRL_Entity *module, Rng1U64 voff_range); -internal Rng1U64 ctrl_voff_range_from_vaddr_range(CTRL_Entity *module, Rng1U64 vaddr_range); -internal B32 ctrl_entity_tree_is_frozen(CTRL_Entity *root); - -//- rjf: entity tree iteration -internal CTRL_EntityRec ctrl_entity_rec_depth_first(CTRL_Entity *entity, CTRL_Entity *subtree_root, U64 sib_off, U64 child_off); -#define ctrl_entity_rec_depth_first_pre(entity, subtree_root) ctrl_entity_rec_depth_first((entity), (subtree_root), OffsetOf(CTRL_Entity, next), OffsetOf(CTRL_Entity, first)) -#define ctrl_entity_rec_depth_first_post(entity, subtree_root) ctrl_entity_rec_depth_first((entity), (subtree_root), OffsetOf(CTRL_Entity, prev), OffsetOf(CTRL_Entity, last)) - -//- rjf: entity ctx r/w store state functions -internal CTRL_EntityCtxRWStore *ctrl_entity_ctx_rw_store_alloc(void); -internal void ctrl_entity_ctx_rw_store_release(CTRL_EntityCtxRWStore *store); - -//- rjf: string allocation/deletion -internal U64 ctrl_name_bucket_num_from_string_size(U64 size); -internal String8 ctrl_entity_string_alloc(CTRL_EntityCtxRWStore *store, String8 string); -internal void ctrl_entity_string_release(CTRL_EntityCtxRWStore *store, String8 string); - -//- rjf: entity construction/deletion -internal CTRL_Entity *ctrl_entity_alloc(CTRL_EntityCtxRWStore *store, CTRL_Entity *parent, CTRL_EntityKind kind, Arch arch, CTRL_Handle handle, U64 id); -internal void ctrl_entity_release(CTRL_EntityCtxRWStore *store, CTRL_Entity *entity); - -//- rjf: entity equipment -internal void ctrl_entity_equip_string(CTRL_EntityCtxRWStore *store, CTRL_Entity *entity, String8 string); - -//- rjf: accelerated entity context lookups -internal CTRL_EntityCtxLookupAccel *ctrl_thread_entity_ctx_lookup_accel(void); -internal CTRL_EntityArray ctrl_entity_array_from_kind(CTRL_EntityCtx *ctx, CTRL_EntityKind kind); -internal CTRL_EntityList ctrl_modules_from_dbgi_key(Arena *arena, CTRL_EntityCtx *ctx, DI_Key dbgi_key); -internal CTRL_Entity *ctrl_thread_from_id(CTRL_EntityCtx *ctx, U64 id); - -//- rjf: applying events to entity caches -internal void ctrl_entity_store_apply_events(CTRL_EntityCtxRWStore *store, CTRL_EventList *list); - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void ctrl_init(void); - -//////////////////////////////// -//~ rjf: Wakeup Callback Registration - -internal void ctrl_set_wakeup_hook(CTRL_WakeupFunctionType *wakeup_hook); - -//////////////////////////////// -//~ rjf: Thread Register Functions - -//- rjf: thread register cache reading -internal void *ctrl_reg_block_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle handle); -internal U64 ctrl_tls_root_vaddr_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle); -internal U64 ctrl_rip_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle); -internal U64 ctrl_rsp_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle); - -//- rjf: thread register writing -internal B32 ctrl_thread_write_reg_block(CTRL_Handle thread, void *block); - -//////////////////////////////// -//~ rjf: Module Image Info Functions - -//- rjf: cache lookups -internal PE_IntelPdata *ctrl_intel_pdata_from_module_voff(Arena *arena, CTRL_Handle module_handle, U64 voff); -internal U64 ctrl_entry_point_voff_from_module(CTRL_Handle module_handle); -internal String8 ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_Handle module_handle); -internal String8 ctrl_raddbg_data_from_module(Arena *arena, CTRL_Handle module_handle); - -//////////////////////////////// -//~ Process Info Functions - -internal Arch ctrl_arch_from_process_handle(CTRL_Handle process_handle); - -//////////////////////////////// -//~ rjf: Unwinding Functions - -//- rjf: unwind deep copier -internal CTRL_Unwind ctrl_unwind_deep_copy(Arena *arena, Arch arch, CTRL_Unwind *src); - -//- DWARF -internal CTRL_UnwindStepResult ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us, CTRL_FrameUnwindContext *ctx_out); -internal CTRL_UnwindStepResult ctrl_unwind_step__dwarf(CTRL_Handle process_handle, Arch arch, void *regs, CTRL_FrameUnwindContext *frame_ctx, U64 endt_us); - -//- rjf: [x64] -internal REGS_Reg64 *ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(REGS_RegBlockX64 *regs, PE_UnwindGprRegX64 gpr_reg); -internal CTRL_UnwindStepResult ctrl_unwind_step__pe_x64(CTRL_Handle process_handle, CTRL_Handle module_handle, U64 module_base_vaddr, REGS_RegBlockX64 *regs, U64 endt_us); - -//- rjf: abstracted full unwind -internal CTRL_Unwind ctrl_unwind_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle thread, U64 endt_us); - -//////////////////////////////// -//~ rjf: Call Stack Building Functions - -internal CTRL_CallStack ctrl_call_stack_from_unwind(Arena *arena, CTRL_Entity *process, CTRL_Unwind *base_unwind); -internal CTRL_CallStackFrame *ctrl_call_stack_frame_from_unwind_and_inline_depth(CTRL_CallStack *call_stack, U64 unwind_count, U64 inline_depth); - -//////////////////////////////// -//~ rjf: Halting All Attached Processes - -internal void ctrl_halt(void); - -//////////////////////////////// -//~ rjf: Shared Accessor Functions - -//- rjf: generation counters -internal U64 ctrl_run_gen(void); -internal U64 ctrl_mem_gen(void); -internal U64 ctrl_reg_gen(void); - -//- rjf: name -> register/alias hash tables, for eval -internal E_String2NumMap *ctrl_string2reg_from_arch(Arch arch); -internal E_String2NumMap *ctrl_string2alias_from_arch(Arch arch); - -//////////////////////////////// -//~ rjf: Control-Thread Functions - -//- rjf: user -> control thread communication -internal B32 ctrl_u2c_push_msgs(CTRL_MsgList *msgs, U64 endt_us); -internal CTRL_MsgList ctrl_u2c_pop_msgs(Arena *arena); - -//- rjf: control -> user thread communication -internal void ctrl_c2u_push_events(CTRL_EventList *events); -internal CTRL_EventList ctrl_c2u_pop_events(Arena *arena); - -//- rjf: entry point -internal void ctrl_thread__entry_point(void *p); - -//- rjf: breakpoint resolution -internal void ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, CTRL_EvalScope *eval_scope, CTRL_Handle process, CTRL_Handle module, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out); -internal void ctrl_thread__append_resolved_process_user_bp_traps(Arena *arena, CTRL_EvalScope *eval_scope, CTRL_Handle process, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out); -internal void ctrl_thread__append_program_defined_bp_traps(Arena *arena, CTRL_Entity *bp, DMN_TrapChunkList *traps_out); - -//- rjf: module lifetime open/close work -internal void ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range, String8 path, Rng1U64 elf_phdr_vrange, U64 elf_phdr_entsize); -internal void ctrl_thread__module_close(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range); - -//- rjf: attached process running/event gathering -internal DMN_Event *ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg, DMN_RunCtrls *run_ctrls, CTRL_Spoof *spoof); - -//- rjf: eval helpers -internal U64 ctrl_eval_space_gen(E_Space space); -internal B32 ctrl_eval_space_read(E_Space space, void *out, Rng1U64 vaddr_range); - -//- rjf: control thread eval scopes -internal CTRL_EvalScope *ctrl_thread__eval_scope_begin(Arena *arena, CTRL_UserBreakpointList *user_bps, CTRL_Entity *thread); -internal void ctrl_thread__eval_scope_end(CTRL_EvalScope *scope); - -//- rjf: log flusher -internal void ctrl_thread__end_and_flush_log(void); - -//- rjf: msg kind implementations -internal void ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); -internal void ctrl_thread__attach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); -internal void ctrl_thread__kill(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); -internal void ctrl_thread__kill_all(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); -internal void ctrl_thread__detach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); -internal void ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); -internal void ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); - -//////////////////////////////// -//~ rjf: Process Memory Artifact Cache Hooks / Lookups - -internal AC_Artifact ctrl_memory_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); -internal void ctrl_memory_artifact_destroy(AC_Artifact artifact); -internal C_Key ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, B32 wait_for_fresh, U64 endt_us, B32 *out_is_stale); - -//- rjf: process memory reading helpers -internal CTRL_ProcessMemorySlice ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, B32 wait_for_fresh, U64 endt_us); -internal B32 ctrl_process_memory_read(CTRL_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us); -#define ctrl_process_memory_read_struct(process, vaddr, is_stale_out, ptr, endt_us) ctrl_process_memory_read((process), r1u64((vaddr), (vaddr)+(sizeof(*(ptr)))), (is_stale_out), (ptr), (endt_us)) - -//- rjf: process memory writing -internal B32 ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src); - -//////////////////////////////// -//~ rjf: Call Stack Artifact Cache Hooks / Lookups - -internal AC_Artifact ctrl_call_stack_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); -internal void ctrl_call_stack_artifact_destroy(AC_Artifact artifact); -internal CTRL_CallStack ctrl_call_stack_from_thread(Access *access, CTRL_Handle thread_handle, B32 high_priority, U64 endt_us); - -//////////////////////////////// -//~ rjf: Call Stack Tree Artifact Cache Hooks / Lookups - -internal AC_Artifact ctrl_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); -internal void ctrl_call_stack_tree_artifact_destroy(AC_Artifact artifact); -internal CTRL_CallStackTree ctrl_call_stack_tree(Access *access, U64 endt_us); - -#endif // CTRL_CORE_H +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef CTRL_CORE_H +#define CTRL_CORE_H + +//////////////////////////////// +//~ rjf: Unwind Types + +typedef U32 CTRL_UnwindFlags; +enum +{ + CTRL_UnwindFlag_Error = (1<<0), + CTRL_UnwindFlag_Stale = (1<<1), +}; + +typedef struct CTRL_UnwindStepResult CTRL_UnwindStepResult; +struct CTRL_UnwindStepResult +{ + CTRL_UnwindFlags flags; +}; + +typedef struct CTRL_UnwindFrame CTRL_UnwindFrame; +struct CTRL_UnwindFrame +{ + void *regs; + U64 cfa; +}; + +typedef struct CTRL_UnwindFrameNode CTRL_UnwindFrameNode; +struct CTRL_UnwindFrameNode +{ + CTRL_UnwindFrameNode *next; + CTRL_UnwindFrameNode *prev; + CTRL_UnwindFrame v; +}; + +typedef struct CTRL_UnwindFrameArray CTRL_UnwindFrameArray; +struct CTRL_UnwindFrameArray +{ + CTRL_UnwindFrame *v; + U64 count; +}; + +typedef struct CTRL_Unwind CTRL_Unwind; +struct CTRL_Unwind +{ + CTRL_UnwindFrameArray frames; + CTRL_UnwindFlags flags; +}; + +typedef struct CTRL_FrameUnwindContext CTRL_FrameUnwindContext; +struct CTRL_FrameUnwindContext +{ + // DWARF + U64 cfa; + DW_CFI_Row *cfi_row; + U64 ret_addr_reg; +}; + +//////////////////////////////// +//~ rjf: Call Stack Types + +typedef struct CTRL_CallStackFrame CTRL_CallStackFrame; +struct CTRL_CallStackFrame +{ + U64 unwind_count; + U64 inline_depth; + void *regs; + U64 cfa; +}; + +typedef struct CTRL_CallStack CTRL_CallStack; +struct CTRL_CallStack +{ + CTRL_CallStackFrame *frames; + U64 frames_count; + CTRL_CallStackFrame **concrete_frames; + U64 concrete_frames_count; +}; + +//////////////////////////////// +//~ rjf: Call Stack Tree Types + +typedef struct CTRL_CallStackTreeNode CTRL_CallStackTreeNode; +struct CTRL_CallStackTreeNode +{ + CTRL_CallStackTreeNode *hash_next; + CTRL_CallStackTreeNode *first; + CTRL_CallStackTreeNode *last; + CTRL_CallStackTreeNode *next; + CTRL_CallStackTreeNode *parent; + U64 child_count; + U64 id; + CTRL_Handle process; + U64 vaddr; + U64 depth; + CTRL_HandleList threads; + U64 all_descendant_threads_count; +}; + +typedef struct CTRL_CallStackTree CTRL_CallStackTree; +struct CTRL_CallStackTree +{ + CTRL_CallStackTreeNode *root; + U64 slots_count; + CTRL_CallStackTreeNode **slots; +}; + +//////////////////////////////// +//~ rjf: Evaluation Spaces + +typedef U64 CTRL_EvalSpaceKind; +enum +{ + CTRL_EvalSpaceKind_Entity = E_SpaceKind_FirstUserDefined, + CTRL_EvalSpaceKind_FirstUserDefined, +}; + +//////////////////////////////// +//~ rjf: Process Memory Types + +typedef struct CTRL_ProcessMemorySlice CTRL_ProcessMemorySlice; +struct CTRL_ProcessMemorySlice +{ + String8 data; + U64 *byte_bad_flags; + U64 *byte_changed_flags; + B32 stale; + B32 any_byte_bad; + B32 any_byte_changed; +}; + +//////////////////////////////// +//~ rjf: Thread Register Cache Types + +typedef struct CTRL_ThreadRegCacheNode CTRL_ThreadRegCacheNode; +struct CTRL_ThreadRegCacheNode +{ + CTRL_ThreadRegCacheNode *next; + CTRL_ThreadRegCacheNode *prev; + CTRL_Handle handle; + U64 block_size; + void *block; + U64 reg_gen; +}; + +typedef struct CTRL_ThreadRegCacheSlot CTRL_ThreadRegCacheSlot; +struct CTRL_ThreadRegCacheSlot +{ + CTRL_ThreadRegCacheNode *first; + CTRL_ThreadRegCacheNode *last; +}; + +typedef struct CTRL_ThreadRegCacheStripe CTRL_ThreadRegCacheStripe; +struct CTRL_ThreadRegCacheStripe +{ + Arena *arena; + RWMutex rw_mutex; +}; + +typedef struct CTRL_ThreadRegCache CTRL_ThreadRegCache; +struct CTRL_ThreadRegCache +{ + U64 slots_count; + CTRL_ThreadRegCacheSlot *slots; + U64 stripes_count; + CTRL_ThreadRegCacheStripe *stripes; +}; + +//////////////////////////////// +//~ rjf: Module Image Info Cache Types + +typedef struct CTRL_ModuleImageInfoCacheNode CTRL_ModuleImageInfoCacheNode; +struct CTRL_ModuleImageInfoCacheNode +{ + CTRL_ModuleImageInfoCacheNode *next; + CTRL_ModuleImageInfoCacheNode *prev; + CTRL_Handle module; + Arena *arena; + PE_IntelPdata *pdatas; + U64 pdatas_count; + U64 cfi_rebase; + B32 is_unwind_eh; + String8 dwarf_unwind_data; + EH_FrameHdr eh_frame_hdr; + EH_PtrCtx eh_ptr_ctx; + U64 entry_point_voff; + String8 initial_debug_info_path; + U64 raddbg_attached_marker_voff; + String8 raddbg_data; +}; + +typedef struct CTRL_ModuleImageInfoCacheSlot CTRL_ModuleImageInfoCacheSlot; +struct CTRL_ModuleImageInfoCacheSlot +{ + CTRL_ModuleImageInfoCacheNode *first; + CTRL_ModuleImageInfoCacheNode *last; +}; + +typedef struct CTRL_ModuleImageInfoCacheStripe CTRL_ModuleImageInfoCacheStripe; +struct CTRL_ModuleImageInfoCacheStripe +{ + Arena *arena; + RWMutex rw_mutex; +}; + +typedef struct CTRL_ModuleImageInfoCache CTRL_ModuleImageInfoCache; +struct CTRL_ModuleImageInfoCache +{ + U64 slots_count; + CTRL_ModuleImageInfoCacheSlot *slots; + U64 stripes_count; + CTRL_ModuleImageInfoCacheStripe *stripes; +}; + +//////////////////////////////// +//~ rjf: Touched Debug Info Directory Cache + +typedef struct CTRL_DbgDirNode CTRL_DbgDirNode; +struct CTRL_DbgDirNode +{ + CTRL_DbgDirNode *first; + CTRL_DbgDirNode *last; + CTRL_DbgDirNode *next; + CTRL_DbgDirNode *prev; + CTRL_DbgDirNode *parent; + String8 name; + U64 search_count; + U64 child_count; + U64 module_direct_count; +}; + +//////////////////////////////// +//~ rjf: Control Thread Evaluation Scopes + +typedef struct CTRL_EvalScope CTRL_EvalScope; +struct CTRL_EvalScope +{ + Access *access; + E_BaseCtx base_ctx; + E_IRCtx ir_ctx; + E_InterpretCtx interpret_ctx; +}; + +//////////////////////////////// +//~ rjf: Module Requirement Cache Types + +typedef struct CTRL_ModuleReqCacheNode CTRL_ModuleReqCacheNode; +struct CTRL_ModuleReqCacheNode +{ + CTRL_ModuleReqCacheNode *next; + CTRL_Handle module; + B32 required; +}; + +//////////////////////////////// +//~ rjf: Wakeup Hook Function Types + +#define CTRL_WAKEUP_FUNCTION_DEF(name) void name(void) +typedef CTRL_WAKEUP_FUNCTION_DEF(CTRL_WakeupFunctionType); + +//////////////////////////////// +//~ rjf: Main State Types + +typedef struct CTRL_State CTRL_State; +struct CTRL_State +{ + Arena *arena; + CTRL_WakeupFunctionType *wakeup_hook; + + // rjf: name -> register/alias hash tables for eval + E_String2NumMap arch_string2reg_tables[Arch_COUNT]; + E_String2NumMap arch_string2alias_tables[Arch_COUNT]; + + // rjf: caches + CTRL_ThreadRegCache thread_reg_cache; + CTRL_ModuleImageInfoCache module_image_info_cache; + + // rjf: generations + U64 run_gen; + U64 mem_gen; + U64 reg_gen; + + // rjf: user -> ctrl msg ring buffer + U64 u2c_ring_size; + U8 *u2c_ring_base; + U64 u2c_ring_write_pos; + U64 u2c_ring_read_pos; + Mutex u2c_ring_mutex; + CondVar u2c_ring_cv; + + // rjf: ctrl -> user event ring buffer + U64 c2u_ring_size; + U64 c2u_ring_max_string_size; + U8 *c2u_ring_base; + U64 c2u_ring_write_pos; + U64 c2u_ring_read_pos; + Mutex c2u_ring_mutex; + CondVar c2u_ring_cv; + + // rjf: ctrl thread state + U64 ctrl_thread_run_state; + String8 ctrl_thread_log_path; + Thread ctrl_thread; + Log *ctrl_thread_log; + RWMutex ctrl_thread_entity_ctx_rw_mutex; + CTRL_EntityCtxRWStore *ctrl_thread_entity_store; + E_Cache *ctrl_thread_eval_cache; + Arena *ctrl_thread_msg_process_arena; + Arena *dmn_event_arena; + DMN_EventNode *first_dmn_event_node; + DMN_EventNode *last_dmn_event_node; + DMN_EventNode *free_dmn_event_node; + Arena *user_entry_point_arena; + String8List user_entry_points; + U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]; + U64 process_counter; + Arena *dbg_dir_arena; + CTRL_DbgDirNode *dbg_dir_root; + U64 module_req_cache_slots_count; + CTRL_ModuleReqCacheNode **module_req_cache_slots; + String8List msg_user_bp_touched_files; + String8List msg_user_bp_touched_symbols; +}; + +//////////////////////////////// +//~ rjf: Globals + +global CTRL_State *ctrl_state = 0; +read_only global CTRL_Entity ctrl_entity_nil = +{ + &ctrl_entity_nil, + &ctrl_entity_nil, + &ctrl_entity_nil, + &ctrl_entity_nil, + &ctrl_entity_nil, +}; +read_only global CTRL_CallStackTreeNode ctrl_call_stack_tree_node_nil = +{ + 0, + &ctrl_call_stack_tree_node_nil, + &ctrl_call_stack_tree_node_nil, + &ctrl_call_stack_tree_node_nil, + &ctrl_call_stack_tree_node_nil, +}; +thread_static CTRL_EntityCtxLookupAccel *ctrl_entity_ctx_lookup_accel = 0; + +//////////////////////////////// +//~ rjf: Logging Markup + +#define CTRL_CtrlThreadLogScope DeferLoop(log_scope_begin(), ctrl_thread__end_and_flush_log()) + +//////////////////////////////// +//~ rjf: Basic Type Functions + +internal U64 ctrl_hash_from_string(String8 string); +internal U64 ctrl_hash_from_handle(CTRL_Handle handle); +internal CTRL_EventCause ctrl_event_cause_from_dmn_event_kind(DMN_EventKind event_kind); +internal CTRL_ExceptionKind ctrl_exception_kind_from_dmn(DMN_ExceptionKind kind); +internal CTRL_TlsModel ctrl_dynamic_linker_type_from_dmn(DMN_TlsModel type); +internal String8 ctrl_string_from_event_kind(CTRL_EventKind kind); +internal String8 ctrl_string_from_msg_kind(CTRL_MsgKind kind); +internal CTRL_EntityKind ctrl_entity_kind_from_string(String8 string); +internal DMN_TrapFlags ctrl_dmn_trap_flags_from_user_breakpoint_flags(CTRL_UserBreakpointFlags flags); +internal CTRL_UserBreakpointFlags ctrl_user_breakpoint_flags_from_dmn_trap_flags(DMN_TrapFlags flags); + +//////////////////////////////// +//~ rjf: Handle Type Functions + +internal CTRL_Handle ctrl_handle_zero(void); +internal CTRL_Handle ctrl_handle_make(CTRL_MachineID machine_id, DMN_Handle dmn_handle); +internal B32 ctrl_handle_match(CTRL_Handle a, CTRL_Handle b); +internal void ctrl_handle_list_push(Arena *arena, CTRL_HandleList *list, CTRL_Handle *pair); +internal CTRL_HandleList ctrl_handle_list_copy(Arena *arena, CTRL_HandleList *src); +internal CTRL_HandleArray ctrl_handle_array_from_list(Arena *arena, CTRL_HandleList *src); +internal String8 ctrl_string_from_handle(Arena *arena, CTRL_Handle handle); +internal CTRL_Handle ctrl_handle_from_string(String8 string); + +//////////////////////////////// +//~ rjf: Trap Type Functions + +internal void ctrl_trap_list_push(Arena *arena, CTRL_TrapList *list, CTRL_Trap *trap); +internal CTRL_TrapList ctrl_trap_list_copy(Arena *arena, CTRL_TrapList *src); + +//////////////////////////////// +//~ rjf: User Breakpoint Type Functions + +internal void ctrl_user_breakpoint_list_push(Arena *arena, CTRL_UserBreakpointList *list, CTRL_UserBreakpoint *bp); +internal CTRL_UserBreakpointList ctrl_user_breakpoint_list_copy(Arena *arena, CTRL_UserBreakpointList *src); + +//////////////////////////////// +//~ rjf: Message Type Functions + +//- rjf: deep copying +internal void ctrl_msg_deep_copy(Arena *arena, CTRL_Msg *dst, CTRL_Msg *src); + +//- rjf: list building +internal CTRL_Msg *ctrl_msg_list_push(Arena *arena, CTRL_MsgList *list); +internal CTRL_MsgList ctrl_msg_list_deep_copy(Arena *arena, CTRL_MsgList *src); +internal void ctrl_msg_list_concat_in_place(CTRL_MsgList *dst, CTRL_MsgList *src); + +//- rjf: serialization +internal String8 ctrl_serialized_string_from_msg_list(Arena *arena, CTRL_MsgList *msgs); +internal CTRL_MsgList ctrl_msg_list_from_serialized_string(Arena *arena, String8 string); + +//////////////////////////////// +//~ rjf: Event Type Functions + +//- rjf: list building +internal CTRL_Event *ctrl_event_list_push(Arena *arena, CTRL_EventList *list); +internal void ctrl_event_list_concat_in_place(CTRL_EventList *dst, CTRL_EventList *to_push); + +//- rjf: serialization +internal String8 ctrl_serialized_string_from_event(Arena *arena, CTRL_Event *event, U64 max); +internal CTRL_Event ctrl_event_from_serialized_string(Arena *arena, String8 string); + +//////////////////////////////// +//~ rjf: Entity Type Functions + +//- rjf: entity list data structures +internal void ctrl_entity_list_push(Arena *arena, CTRL_EntityList *list, CTRL_Entity *entity); +internal CTRL_EntityList ctrl_entity_list_from_handle_list(Arena *arena, CTRL_EntityCtx *ctx, CTRL_HandleList *list); +#define ctrl_entity_list_first(list) ((list)->first ? (list)->first->v : &ctrl_entity_nil) + +//- rjf: entity array data structure +internal CTRL_EntityArray ctrl_entity_array_from_list(Arena *arena, CTRL_EntityList *list); +#define ctrl_entity_array_first(array) ((array)->count != 0 ? (array)->v[0] : &ctrl_entity_nil) + +//- rjf: entity context (entity group read-only) functions +internal CTRL_Entity *ctrl_entity_from_handle(CTRL_EntityCtx *ctx, CTRL_Handle handle); +internal CTRL_Entity *ctrl_entity_child_from_kind(CTRL_Entity *parent, CTRL_EntityKind kind); +internal CTRL_Entity *ctrl_entity_ancestor_from_kind(CTRL_Entity *entity, CTRL_EntityKind kind); +internal CTRL_Entity *ctrl_process_from_entity(CTRL_Entity *entity); +internal CTRL_Entity *ctrl_module_from_process_vaddr(CTRL_Entity *process, U64 vaddr); +internal DI_Key ctrl_dbgi_key_from_module(CTRL_Entity *module); +internal CTRL_Entity *ctrl_module_from_thread_candidates(CTRL_EntityCtx *ctx, CTRL_Entity *thread, CTRL_EntityList *candidates); +internal U64 ctrl_vaddr_from_voff(CTRL_Entity *module, U64 voff); +internal U64 ctrl_voff_from_vaddr(CTRL_Entity *module, U64 vaddr); +internal Rng1U64 ctrl_vaddr_range_from_voff_range(CTRL_Entity *module, Rng1U64 voff_range); +internal Rng1U64 ctrl_voff_range_from_vaddr_range(CTRL_Entity *module, Rng1U64 vaddr_range); +internal B32 ctrl_entity_tree_is_frozen(CTRL_Entity *root); + +//- rjf: entity tree iteration +internal CTRL_EntityRec ctrl_entity_rec_depth_first(CTRL_Entity *entity, CTRL_Entity *subtree_root, U64 sib_off, U64 child_off); +#define ctrl_entity_rec_depth_first_pre(entity, subtree_root) ctrl_entity_rec_depth_first((entity), (subtree_root), OffsetOf(CTRL_Entity, next), OffsetOf(CTRL_Entity, first)) +#define ctrl_entity_rec_depth_first_post(entity, subtree_root) ctrl_entity_rec_depth_first((entity), (subtree_root), OffsetOf(CTRL_Entity, prev), OffsetOf(CTRL_Entity, last)) + +//- rjf: entity ctx r/w store state functions +internal CTRL_EntityCtxRWStore *ctrl_entity_ctx_rw_store_alloc(void); +internal void ctrl_entity_ctx_rw_store_release(CTRL_EntityCtxRWStore *store); + +//- rjf: string allocation/deletion +internal U64 ctrl_name_bucket_num_from_string_size(U64 size); +internal String8 ctrl_entity_string_alloc(CTRL_EntityCtxRWStore *store, String8 string); +internal void ctrl_entity_string_release(CTRL_EntityCtxRWStore *store, String8 string); + +//- rjf: entity construction/deletion +internal CTRL_Entity *ctrl_entity_alloc(CTRL_EntityCtxRWStore *store, CTRL_Entity *parent, CTRL_EntityKind kind, Arch arch, CTRL_Handle handle, U64 id); +internal void ctrl_entity_release(CTRL_EntityCtxRWStore *store, CTRL_Entity *entity); + +//- rjf: entity equipment +internal void ctrl_entity_equip_string(CTRL_EntityCtxRWStore *store, CTRL_Entity *entity, String8 string); + +//- rjf: accelerated entity context lookups +internal CTRL_EntityCtxLookupAccel *ctrl_thread_entity_ctx_lookup_accel(void); +internal CTRL_EntityArray ctrl_entity_array_from_kind(CTRL_EntityCtx *ctx, CTRL_EntityKind kind); +internal CTRL_EntityList ctrl_modules_from_dbgi_key(Arena *arena, CTRL_EntityCtx *ctx, DI_Key dbgi_key); +internal CTRL_Entity *ctrl_thread_from_id(CTRL_EntityCtx *ctx, U64 id); + +//- rjf: applying events to entity caches +internal void ctrl_entity_store_apply_events(CTRL_EntityCtxRWStore *store, CTRL_EventList *list); + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void ctrl_init(void); + +//////////////////////////////// +//~ rjf: Wakeup Callback Registration + +internal void ctrl_set_wakeup_hook(CTRL_WakeupFunctionType *wakeup_hook); + +//////////////////////////////// +//~ rjf: Thread Register Functions + +//- rjf: thread register cache reading +internal void *ctrl_reg_block_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle handle); +internal U64 ctrl_tls_root_vaddr_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle); +internal U64 ctrl_rip_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle); +internal U64 ctrl_rsp_from_thread(CTRL_EntityCtx *ctx, CTRL_Handle handle); + +//- rjf: thread register writing +internal B32 ctrl_thread_write_reg_block(CTRL_Handle thread, void *block); + +//////////////////////////////// +//~ rjf: Module Image Info Functions + +//- rjf: cache lookups +internal PE_IntelPdata *ctrl_intel_pdata_from_module_voff(Arena *arena, CTRL_Handle module_handle, U64 voff); +internal U64 ctrl_entry_point_voff_from_module(CTRL_Handle module_handle); +internal String8 ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_Handle module_handle); +internal String8 ctrl_raddbg_data_from_module(Arena *arena, CTRL_Handle module_handle); + +//////////////////////////////// +//~ Process Info Functions + +internal Arch ctrl_arch_from_process_handle(CTRL_Handle process_handle); + +//////////////////////////////// +//~ rjf: Unwinding Functions + +//- rjf: unwind deep copier +internal CTRL_Unwind ctrl_unwind_deep_copy(Arena *arena, Arch arch, CTRL_Unwind *src); + +//- DWARF +internal CTRL_UnwindStepResult ctrl_establish_frame_unwind_context__dwarf(Arena *arena, CTRL_Handle process_handle, CTRL_Handle module_handle, Arch arch, void *regs, U64 endt_us, CTRL_FrameUnwindContext *ctx_out); +internal CTRL_UnwindStepResult ctrl_unwind_step__dwarf(CTRL_Handle process_handle, Arch arch, void *regs, CTRL_FrameUnwindContext *frame_ctx, U64 endt_us); + +//- rjf: [x64] +internal REGS_Reg64 *ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(REGS_RegBlockX64 *regs, PE_UnwindGprRegX64 gpr_reg); +internal CTRL_UnwindStepResult ctrl_unwind_step__pe_x64(CTRL_Handle process_handle, CTRL_Handle module_handle, U64 module_base_vaddr, REGS_RegBlockX64 *regs, U64 endt_us); + +//- rjf: abstracted full unwind +internal CTRL_Unwind ctrl_unwind_from_thread(Arena *arena, CTRL_EntityCtx *ctx, CTRL_Handle thread, U64 endt_us); + +//////////////////////////////// +//~ rjf: Call Stack Building Functions + +internal CTRL_CallStack ctrl_call_stack_from_unwind(Arena *arena, CTRL_Entity *process, CTRL_Unwind *base_unwind); +internal CTRL_CallStackFrame *ctrl_call_stack_frame_from_unwind_and_inline_depth(CTRL_CallStack *call_stack, U64 unwind_count, U64 inline_depth); + +//////////////////////////////// +//~ rjf: Halting All Attached Processes + +internal void ctrl_halt(void); + +//////////////////////////////// +//~ rjf: Shared Accessor Functions + +//- rjf: generation counters +internal U64 ctrl_run_gen(void); +internal U64 ctrl_mem_gen(void); +internal U64 ctrl_reg_gen(void); + +//- rjf: name -> register/alias hash tables, for eval +internal E_String2NumMap *ctrl_string2reg_from_arch(Arch arch); +internal E_String2NumMap *ctrl_string2alias_from_arch(Arch arch); + +//////////////////////////////// +//~ rjf: Control-Thread Functions + +//- rjf: user -> control thread communication +internal B32 ctrl_u2c_push_msgs(CTRL_MsgList *msgs, U64 endt_us); +internal CTRL_MsgList ctrl_u2c_pop_msgs(Arena *arena); + +//- rjf: control -> user thread communication +internal void ctrl_c2u_push_events(CTRL_EventList *events); +internal CTRL_EventList ctrl_c2u_pop_events(Arena *arena); + +//- rjf: entry point +internal void ctrl_thread__entry_point(void *p); + +//- rjf: breakpoint resolution +internal void ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, CTRL_EvalScope *eval_scope, CTRL_Handle process, CTRL_Handle module, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out); +internal void ctrl_thread__append_resolved_process_user_bp_traps(Arena *arena, CTRL_EvalScope *eval_scope, CTRL_Handle process, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out); +internal void ctrl_thread__append_program_defined_bp_traps(Arena *arena, CTRL_Entity *bp, DMN_TrapChunkList *traps_out); + +//- rjf: module lifetime open/close work +internal void ctrl_thread__module_open(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range, String8 path, Rng1U64 elf_phdr_vrange, U64 elf_phdr_entsize); +internal void ctrl_thread__module_close(CTRL_Handle process, CTRL_Handle module, Rng1U64 vaddr_range); + +//- rjf: attached process running/event gathering +internal DMN_Event *ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg, DMN_RunCtrls *run_ctrls, CTRL_Spoof *spoof); + +//- rjf: eval helpers +internal U64 ctrl_eval_space_gen(E_Space space); +internal B32 ctrl_eval_space_read(E_Space space, void *out, Rng1U64 vaddr_range); + +//- rjf: control thread eval scopes +internal CTRL_EvalScope *ctrl_thread__eval_scope_begin(Arena *arena, CTRL_UserBreakpointList *user_bps, CTRL_Entity *thread); +internal void ctrl_thread__eval_scope_end(CTRL_EvalScope *scope); + +//- rjf: log flusher +internal void ctrl_thread__end_and_flush_log(void); + +//- rjf: msg kind implementations +internal void ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); +internal void ctrl_thread__attach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); +internal void ctrl_thread__kill(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); +internal void ctrl_thread__kill_all(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); +internal void ctrl_thread__detach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); +internal void ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); +internal void ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg); + +//////////////////////////////// +//~ rjf: Process Memory Artifact Cache Hooks / Lookups + +internal AC_Artifact ctrl_memory_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal void ctrl_memory_artifact_destroy(AC_Artifact artifact); +internal C_Key ctrl_key_from_process_vaddr_range(CTRL_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, B32 wait_for_fresh, U64 endt_us, B32 *out_is_stale); + +//- rjf: process memory reading helpers +internal CTRL_ProcessMemorySlice ctrl_process_memory_slice_from_vaddr_range(Arena *arena, CTRL_Handle process, Rng1U64 range, B32 wait_for_fresh, U64 endt_us); +internal B32 ctrl_process_memory_read(CTRL_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us); +#define ctrl_process_memory_read_struct(process, vaddr, is_stale_out, ptr, endt_us) ctrl_process_memory_read((process), r1u64((vaddr), (vaddr)+(sizeof(*(ptr)))), (is_stale_out), (ptr), (endt_us)) + +//- rjf: process memory writing +internal B32 ctrl_process_write(CTRL_Handle process, Rng1U64 range, void *src); + +//////////////////////////////// +//~ rjf: Call Stack Artifact Cache Hooks / Lookups + +internal AC_Artifact ctrl_call_stack_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal void ctrl_call_stack_artifact_destroy(AC_Artifact artifact); +internal CTRL_CallStack ctrl_call_stack_from_thread(Access *access, CTRL_Handle thread_handle, B32 high_priority, U64 endt_us); + +//////////////////////////////// +//~ rjf: Call Stack Tree Artifact Cache Hooks / Lookups + +internal AC_Artifact ctrl_call_stack_tree_artifact_create(String8 key, B32 *cancel_signal, B32 *retry_out, U64 *gen_out); +internal void ctrl_call_stack_tree_artifact_destroy(AC_Artifact artifact); +internal CTRL_CallStackTree ctrl_call_stack_tree(Access *access, U64 endt_us); + +#endif // CTRL_CORE_H diff --git a/src/dbg_engine/dbg_engine_inc.c b/src/dbg_engine/dbg_engine_inc.c index b3d11c5b..04b84edd 100644 --- a/src/dbg_engine/dbg_engine_inc.c +++ b/src/dbg_engine/dbg_engine_inc.c @@ -2,3 +2,5 @@ // Licensed under the MIT license (https://opensource.org/license/mit/) #include "dbg_engine_core.c" +#include "dbg_engine_user.c" +#include "dbg_engine_ctrl.c" diff --git a/src/dbg_engine/dbg_engine_inc.h b/src/dbg_engine/dbg_engine_inc.h index 5dcf2fb4..df2d596d 100644 --- a/src/dbg_engine/dbg_engine_inc.h +++ b/src/dbg_engine/dbg_engine_inc.h @@ -5,5 +5,7 @@ #define DBG_ENGINE_INC_H #include "dbg_engine_core.h" +#include "dbg_engine_user.h" +#include "dbg_engine_ctrl.h" #endif // DBG_ENGINE_INC_H diff --git a/src/dbg_engine/dbg_engine_user.c b/src/dbg_engine/dbg_engine_user.c new file mode 100644 index 00000000..84ee8b33 --- /dev/null +++ b/src/dbg_engine/dbg_engine_user.c @@ -0,0 +1,2255 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#undef LAYER_COLOR +#define LAYER_COLOR 0x4d9ae3ff + +//////////////////////////////// +//~ rjf: Basic Helpers + +#if !defined(XXH_IMPLEMENTATION) +# define XXH_IMPLEMENTATION +# define XXH_STATIC_LINKING_ONLY +# include "third_party/xxHash/xxhash.h" +#endif + +internal U64 +d_hash_from_seed_string(U64 seed, String8 string) +{ + U64 result = XXH3_64bits_withSeed(string.str, string.size, seed); + return result; +} + +internal U64 +d_hash_from_string(String8 string) +{ + return d_hash_from_seed_string(5381, string); +} + +internal U64 +d_hash_from_seed_string__case_insensitive(U64 seed, String8 string) +{ + U64 result = seed; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + lower_from_char(string.str[i]); + } + return result; +} + +internal U64 +d_hash_from_string__case_insensitive(String8 string) +{ + return d_hash_from_seed_string__case_insensitive(5381, string); +} + +//////////////////////////////// +//~ rjf: Breakpoints + +internal D_BreakpointArray +d_breakpoint_array_copy(Arena *arena, D_BreakpointArray *src) +{ + D_BreakpointArray dst = {0}; + dst.count = src->count; + dst.v = push_array(arena, D_Breakpoint, dst.count); + MemoryCopy(dst.v, src->v, sizeof(dst.v[0])*dst.count); + for(U64 idx = 0; idx < dst.count; idx += 1) + { + dst.v[idx].file_path = push_str8_copy(arena, dst.v[idx].file_path); + dst.v[idx].vaddr_expr = push_str8_copy(arena, dst.v[idx].vaddr_expr); + dst.v[idx].condition = push_str8_copy(arena, dst.v[idx].condition); + } + return dst; +} + +//////////////////////////////// +//~ rjf: Path Map Application + +internal String8List +d_possible_path_overrides_from_maps_path(Arena *arena, D_PathMapArray *path_maps, String8 file_path) +{ + // NOTE(rjf): This path, given some target file path, scans all file path map + // overrides, and collects the set of file paths which could've redirected + // to the target file path given the set of file path maps. + // + // For example, if I have a rule saying D:/devel/ maps to C:/devel/, and I + // feed in C:/devel/foo/bar.txt, then this path will construct + // D:/devel/foo/bar.txt, as a possible option. + // + // It will also preserve C:/devel/foo/bar.txt in the resultant list, so that + // overrideless files still work through this path, and both redirected + // files and non-redirected files can go through the same path. + // + String8List result = {0}; + str8_list_push(arena, &result, file_path); + Temp scratch = scratch_begin(&arena, 1); + PathStyle pth_style = PathStyle_Relative; + String8List pth_parts = path_normalized_list_from_string(scratch.arena, file_path, &pth_style); + { + for(U64 idx = 0; idx < path_maps->count; idx += 1) + { + //- rjf: unpack link + D_PathMap *map = &path_maps->v[idx]; + PathStyle src_style = PathStyle_Relative; + PathStyle dst_style = PathStyle_Relative; + String8List src_parts = path_normalized_list_from_string(scratch.arena, map->src, &src_style); + String8List dst_parts = path_normalized_list_from_string(scratch.arena, map->dst, &dst_style); + + //- rjf: determine if this link can possibly redirect to the target file path + B32 dst_redirects_to_pth = 0; + String8Node *non_redirected_pth_first = 0; + if(dst_style == pth_style && dst_parts.first != 0 && pth_parts.first != 0) + { + dst_redirects_to_pth = 1; + String8Node *dst_n = dst_parts.first; + String8Node *pth_n = pth_parts.first; + for(;dst_n != 0 && pth_n != 0; dst_n = dst_n->next, pth_n = pth_n->next) + { + if(!str8_match(dst_n->string, pth_n->string, StringMatchFlag_CaseInsensitive)) + { + dst_redirects_to_pth = 0; + break; + } + non_redirected_pth_first = pth_n->next; + } + } + + //- rjf: if this link can redirect to this path via `src` -> `dst`, compute + // possible full source path, by taking `src` and appending non-redirected + // suffix (which did not show up in `dst`) + if(dst_redirects_to_pth) + { + String8List candidate_parts = src_parts; + for(String8Node *p = non_redirected_pth_first; p != 0; p = p->next) + { + str8_list_push(scratch.arena, &candidate_parts, p->string); + } + StringJoin join = {0}; + join.sep = str8_lit("/"); + String8 candidate_path = str8_list_join(arena, &candidate_parts, &join); + str8_list_push(arena, &result, candidate_path); + } + } + } + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Debug Info Extraction Type Pure Functions + +internal D_LineList +d_line_list_copy(Arena *arena, D_LineList *list) +{ + D_LineList dst = {0}; + for(D_LineNode *src_n = list->first; src_n != 0; src_n = src_n->next) + { + D_LineNode *dst_n = push_array(arena, D_LineNode, 1); + MemoryCopyStruct(dst_n, src_n); + dst_n->v.file_path = push_str8_copy(arena, dst_n->v.file_path); + SLLQueuePush(dst.first, dst.last, dst_n); + dst.count += 1; + } + return dst; +} + +//////////////////////////////// +//~ rjf: Command Type Functions + +//- rjf: command parameters + +internal D_CmdParams +d_cmd_params_copy(Arena *arena, D_CmdParams *src) +{ + D_CmdParams dst = {0}; + MemoryCopyStruct(&dst, src); + dst.file_path = push_str8_copy(arena, dst.file_path); + dst.targets.v = push_array(arena, D_Target, dst.targets.count); + MemoryCopy(dst.targets.v, src->targets.v, sizeof(D_Target)*dst.targets.count); + for(U64 idx = 0; idx < dst.targets.count; idx += 1) + { + D_Target *target = &dst.targets.v[idx]; + target->exe = push_str8_copy(arena, target->exe); + target->args = push_str8_copy(arena, target->args); + target->working_directory = push_str8_copy(arena, target->working_directory); + target->custom_entry_point_name = push_str8_copy(arena, target->custom_entry_point_name); + target->env = str8_list_copy(arena, &target->env); + } + return dst; +} + +//- rjf: command lists + +internal void +d_cmd_list_push_new(Arena *arena, D_CmdList *cmds, D_CmdKind kind, D_CmdParams *params) +{ + D_CmdNode *n = push_array(arena, D_CmdNode, 1); + n->cmd.kind = kind; + n->cmd.params = d_cmd_params_copy(arena, params); + DLLPushBack(cmds->first, cmds->last, n); + cmds->count += 1; +} + +//////////////////////////////// +//~ rjf: Stepping "Trap Net" Builders + +// NOTE(rjf): Stepping Algorithm Overview (2024/01/17) +// +// The basic idea behind all stepping algorithms in the debugger are setting up +// a "trap net". A "trap net" is just a collection of high-level traps that are +// meant to "catch" a thread after letting it run. This trap net is submitted +// when the debugger frontend sends a "run" command (it is just empty if doing +// an actual 'run' or 'continue'). The debugger control thread then uses this +// trap net to program a state machine, to appropriately respond to a variety +// of debug events which it is passed from the OS. +// +// These are "high-level traps" because they can have specific behavioral info +// attached to them. These are encoded via the `CTRL_TrapFlags` type, which +// allow expression of the following behaviors: +// +// - end-stepping: when this trap is hit, it will end the stepping operation, +// and the target will not continue. +// - ignore-stack-pointer-check: when a trap in the trap net is hit, it will +// by-default be ignored if the thread's stack pointer has changed. this +// flag disables that behavior, for when the stack pointer is expected to +// change (e.g. step-out). +// - single-step-after-hit: when a trap with this flag is hit, the debugger +// will immediately single-step the thread which hit it. +// - save-stack-pointer: when a trap with this flag is hit, it will rewrite +// the stack pointer which is used to compare against, when deciding +// whether or not to filter a trap (based on stack pointer changes). +// - begin-spoof-mode: this enables "spoof mode". "spoof mode" is a special +// mode that disables the trap net entirely, and lets the thread run +// freely - but it catches the thread not with a trap, but a false return +// address. the debugger will overwrite a specific return address on the +// stack. this address will be overwritten with an address which does NOT +// point to a valid page, such that when the thread returns out of a +// particular call frame, the debugger will receive a debug event, at +// which point it can move the thread back to the correct return address, +// and resume with the trap net enabled. this is used in "step over" +// operations, because it avoids target <-> debugger "roundtrips" (e.g. +// target being stopped, debugger being called with debug events, then +// target resumes when debugger's control thread is done running) for +// recursions. (it doesn't make a difference with non-recursive calls, +// but the debugger can't detect the difference). +// +// Each stepping command prepares its trap net differently. +// +// --- Instruction Step Into -------------------------------------------------- +// In this case, no trap net is prepared, and only a low-level single-step is +// performed. +// +// --- Instruction Step Over -------------------------------------------------- +// To build a trap net for an instruction-level step-over, the next instruction +// at the thread's current instruction pointer is decoded. If it is a call +// instruction, or if it is a repeating instruction, then a trap with the +// 'end-stepping' behavior is placed at the instruction immediately following +// the 'call' instruction. +// +// --- Line Step Into --------------------------------------------------------- +// For a source-line step-into, the thread's instruction pointer is first used +// to look up into the debug info's line info, to find the machine code in the +// thread's current source line. Every instruction in this range is decoded. +// Traps are then built in the following way: +// +// - 'call' instruction -> if can decode call destination address, place +// "end-stepping | ignore-stack-pointer-check" trap at destination. if +// can't, "end-stepping | single-step-after | ignore-stack-pointer-check" +// trap at call. +// - 'jmp' (both unconditional & conditional) -> if can decode jump destination +// address, AND if jump leaves the line, place "end-stepping | ignore- +// stack-pointer-check" trap at destination. if can't, "end-stepping | +// single-step-after | ignore-stack-pointer-check" trap at jmp. if jump +// stays within the line, do nothing. +// - 'return' -> place "end-stepping | single-step-after" trap at return inst. +// - "end-stepping" trap is placed at the first address after the line, to +// catch all steps which simply proceed linearly through the instruction +// stream. +// +// --- Line Step Over --------------------------------------------------------- +// For a source-line step-over, the thread's instruction pointer is first used +// to look up into the debug info's line info, to find the machine code in the +// thread's current source line. Every instruction in this range is decoded. +// Traps are then built in the following way: +// +// - 'call' instruction -> place "single-step-after | begin-spoof-mode" trap at +// call instruction. +// - 'jmp' (both unconditional & conditional) -> if can decode jump destination +// address, AND if jump leaves the line, place "end-stepping" trap at +// destination. if can't, "end-stepping | single-step-after" trap at jmp. +// if jump stays within the line, do nothing. +// - 'return' -> place "end-stepping | single-step-after" trap at return inst. +// - "end-stepping" trap is placed at the first address after the line, to +// catch all steps which simply proceed linearly through the instruction +// stream. +// - for any instructions which may change the stack pointer, traps are placed +// at them with the "save-stack-pointer | single-step-after" behaviors. + +internal D_TrapNet +d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread) +{ + Temp scratch = scratch_begin(&arena, 1); + D_TrapNet result = {0}; + + // rjf: thread => unpacked info + CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process); + Arch arch = thread->arch; + U64 ip_vaddr = ctrl_rip_from_thread(&d_state->ctrl_entity_store->ctx, thread->handle); + + // rjf: ip => machine code + String8 machine_code = {0}; + { + Rng1U64 rng = r1u64(ip_vaddr, ip_vaddr+max_instruction_size_from_arch(arch)); + CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, rng, 0, os_now_microseconds()+5000); + machine_code = machine_code_slice.data; + } + + // rjf: build traps if machine code was read successfully + if(machine_code.size != 0) + { + result.good_read = 1; + + // rjf: decode instruction + DASM_Inst inst = dasm_inst_from_code(scratch.arena, arch, ip_vaddr, machine_code, DASM_Syntax_Intel); + + // rjf: call => run until call returns + if(inst.flags & DASM_InstFlag_Call || inst.flags & DASM_InstFlag_Repeats) + { + CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, ip_vaddr+inst.size}; + ctrl_trap_list_push(arena, &result.traps, &trap); + } + } + + scratch_end(scratch); + return result; +} + +internal D_TrapNet +d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread) +{ + Temp scratch = scratch_begin(&arena, 1); + log_infof("step_over_line:\n{\n"); + D_TrapNet result = {0}; + + // rjf: thread => info + Arch arch = thread->arch; + U64 ip_vaddr = ctrl_rip_from_thread(&d_state->ctrl_entity_store->ctx, thread->handle); + CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process); + CTRL_Entity *module = ctrl_module_from_process_vaddr(process, ip_vaddr); + DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); + log_infof("ip_vaddr: 0x%I64x\n", ip_vaddr); + log_infof("dbgi_key: {0x%I64x, 0x%I64x}\n", dbgi_key.u64[0], dbgi_key.u64[1]); + + // rjf: ip => line info + String8 file_path = {0}; + S64 line_num = 0; + Rng1U64 line_vaddr_rng = {0}; + { + U64 ip_voff = ctrl_voff_from_vaddr(module, ip_vaddr); + D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, ip_voff); + Rng1U64 line_voff_rng = {0}; + if(lines.first != 0) + { + line_voff_rng = lines.first->v.voff_range; + file_path = lines.first->v.file_path; + line_num = lines.first->v.pt.line; + line_vaddr_rng = ctrl_vaddr_range_from_voff_range(module, line_voff_rng); + log_infof("line: {%S:%I64i}\n", lines.first->v.file_path, lines.first->v.pt.line); + } + log_infof("voff_range: {0x%I64x, 0x%I64x}\n", line_voff_rng.min, line_voff_rng.max); + log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max); + } + + // rjf: gather other ranges on this same textual line, which we don't want to return to + Rng1U64List all_vaddr_ranges_on_same_line = {0}; + { + D_LineList lines = d_lines_from_dbgi_key_file_path_line_num(scratch.arena, dbgi_key, file_path, line_num); + for EachNode(n, D_LineNode, lines.first) + { + Rng1U64 voff_range = n->v.voff_range; + Rng1U64 vaddr_range = ctrl_vaddr_range_from_voff_range(module, voff_range); + rng1u64_list_push(scratch.arena, &all_vaddr_ranges_on_same_line, vaddr_range); + } + } + + // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range + // + // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging + // is enabled. This is enabled by default normally. + { + U64 opl_line_voff_rng = ctrl_voff_from_vaddr(module, line_vaddr_rng.max); + D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, opl_line_voff_rng); + if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) + { + line_vaddr_rng.max = ctrl_vaddr_from_voff(module, lines.first->v.voff_range.max); + } + } + + // rjf: line vaddr range => did we find anything successfully? + B32 good_line_info = (line_vaddr_rng.max != 0); + + // rjf: line vaddr range => line's machine code + String8 machine_code = {0}; + B32 good_machine_code = 0; + if(good_line_info) + { + CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, line_vaddr_rng, 0, os_now_microseconds()+50000); + machine_code = machine_code_slice.data; + good_machine_code = (machine_code.size >= dim_1u64(line_vaddr_rng) && !machine_code_slice.any_byte_bad); + LogInfoNamedBlockF("machine_code_slice") + { + log_infof("stale: %i\n", machine_code_slice.stale); + log_infof("any_byte_bad: %i\n", machine_code_slice.any_byte_bad); + log_infof("any_byte_changed: %i\n", machine_code_slice.any_byte_changed); + log_infof("bytes:\n[\n"); + for(U64 idx = 0; idx < machine_code_slice.data.size; idx += 1) + { + log_infof("0x%x,", machine_code_slice.data.str[idx]); + if(idx%16 == 15 || idx+1 == machine_code_slice.data.size) + { + log_infof("\n"); + } + } + log_infof("]\n"); + } + } + + // rjf: machine code => ctrl flow analysis + DASM_CtrlFlowInfo ctrl_flow_info = {0}; + if(good_machine_code) + { + ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, + DASM_InstFlag_Call| + DASM_InstFlag_Branch| + DASM_InstFlag_UnconditionalJump| + DASM_InstFlag_ChangesStackPointer| + DASM_InstFlag_Return, + arch, + line_vaddr_rng.min, + machine_code); + LogInfoNamedBlockF("ctrl_flow_info") + { + LogInfoNamedBlockF("exit_points") for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) + { + log_infof("{vaddr:0x%I64x, jump_dest_vaddr:0x%I64x, inst_flags:%x}\n", n->v.vaddr, n->v.jump_dest_vaddr, n->v.inst_flags); + } + } + } + + // rjf: push traps for all exit points + if(good_machine_code) for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) + { + DASM_CtrlFlowPoint *point = &n->v; + CTRL_TrapFlags flags = 0; + B32 add = 1; + U64 trap_addr = point->vaddr; + + // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. + if(point->inst_flags & (DASM_InstFlag_Branch| + DASM_InstFlag_UnconditionalJump| + DASM_InstFlag_Return)) + { + flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping); + + // rjf: omit if this jump stays inside of this line + if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) + { + add = 0; + } + + // rjf: omit if this jump stays inside of any range on this same textual line + for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first) + { + if(contains_1u64(n->v, point->jump_dest_vaddr)) + { + add = 0; + break; + } + } + + // rjf: trap @ destination, if we can - we can avoid a single-step this way. + if(point->jump_dest_vaddr != 0) + { + trap_addr = point->jump_dest_vaddr; + flags &= ~CTRL_TrapFlag_SingleStepAfterHit; + } + + } + + // rjf: call => place spoof at return spot in stack, single-step after hitting + else if(point->inst_flags & DASM_InstFlag_Call) + { + flags |= (CTRL_TrapFlag_BeginSpoofMode|CTRL_TrapFlag_SingleStepAfterHit); + } + + // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping + else if(point->inst_flags & DASM_InstFlag_ChangesStackPointer) + { + flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); + } + + // rjf: add if appropriate + if(add) + { + CTRL_Trap trap = {flags, trap_addr}; + ctrl_trap_list_push(arena, &result.traps, &trap); + } + } + + // rjf: push traps for natural linear flow + if(good_line_info && good_machine_code) + { + U64 line_opl_vaddr = line_vaddr_rng.max; + for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first) + { + U64 opl = n->v.max; + B32 opl_in_line = 0; + for EachNode(n2, Rng1U64Node, all_vaddr_ranges_on_same_line.first) + { + if(n != n2 && opl == n2->v.min) + { + opl_in_line = 1; + break; + } + } + if(!opl_in_line) + { + CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, opl}; + ctrl_trap_list_push(arena, &result.traps, &trap); + } + } + } + + // rjf: store goodness + if(good_machine_code) + { + result.good_line_info = good_line_info; + result.good_read = good_machine_code; + } + + // rjf: log + LogInfoNamedBlockF("traps") for(CTRL_TrapNode *n = result.traps.first; n != 0; n = n->next) + { + log_infof("{flags:0x%x, vaddr:0x%I64x}\n", n->v.flags, n->v.vaddr); + } + + scratch_end(scratch); + log_infof("}\n\n"); + return result; +} + +internal D_TrapNet +d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread) +{ + Temp scratch = scratch_begin(&arena, 1); + D_TrapNet result = {0}; + + // rjf: thread => info + Arch arch = thread->arch; + U64 ip_vaddr = ctrl_rip_from_thread(&d_state->ctrl_entity_store->ctx, thread->handle); + CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process); + CTRL_Entity *module = ctrl_module_from_process_vaddr(process, ip_vaddr); + DI_Key dbgi_key = ctrl_dbgi_key_from_module(module); + + // rjf: ip => line info + String8 file_path = {0}; + S64 line_num = 0; + Rng1U64 line_vaddr_rng = {0}; + { + U64 ip_voff = ctrl_voff_from_vaddr(module, ip_vaddr); + D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, ip_voff); + Rng1U64 line_voff_rng = {0}; + if(lines.first != 0) + { + line_voff_rng = lines.first->v.voff_range; + file_path = lines.first->v.file_path; + line_num = lines.first->v.pt.line; + line_vaddr_rng = ctrl_vaddr_range_from_voff_range(module, line_voff_rng); + log_infof("line: {%S:%I64i}\n", lines.first->v.file_path, lines.first->v.pt.line); + } + log_infof("voff_range: {0x%I64x, 0x%I64x}\n", line_voff_rng.min, line_voff_rng.max); + log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max); + } + + // rjf: gather other ranges on this same textual line, which we don't want to return to + Rng1U64List all_vaddr_ranges_on_same_line = {0}; + { + D_LineList lines = d_lines_from_dbgi_key_file_path_line_num(scratch.arena, dbgi_key, file_path, line_num); + for EachNode(n, D_LineNode, lines.first) + { + Rng1U64 voff_range = n->v.voff_range; + Rng1U64 vaddr_range = ctrl_vaddr_range_from_voff_range(module, voff_range); + rng1u64_list_push(scratch.arena, &all_vaddr_ranges_on_same_line, vaddr_range); + } + } + + // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range + // + // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging + // is enabled. This is enabled by default normally. + { + U64 opl_line_voff_rng = ctrl_voff_from_vaddr(module, line_vaddr_rng.max); + D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, dbgi_key, opl_line_voff_rng); + if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) + { + line_vaddr_rng.max = ctrl_vaddr_from_voff(module, lines.first->v.voff_range.max); + } + } + + // rjf: line vaddr range => did we find anything successfully? + B32 good_line_info = (line_vaddr_rng.max != 0); + + // rjf: line vaddr range => line's machine code + String8 machine_code = {0}; + B32 good_machine_code = 0; + if(good_line_info) + { + CTRL_ProcessMemorySlice machine_code_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, line_vaddr_rng, 0, os_now_microseconds()+5000); + machine_code = machine_code_slice.data; + good_machine_code = (machine_code.size >= dim_1u64(line_vaddr_rng) && !machine_code_slice.any_byte_bad); + } + + // rjf: machine code => ctrl flow analysis + DASM_CtrlFlowInfo ctrl_flow_info = {0}; + if(good_machine_code) + { + ctrl_flow_info = dasm_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, + DASM_InstFlag_Call| + DASM_InstFlag_Branch| + DASM_InstFlag_UnconditionalJump| + DASM_InstFlag_ChangesStackPointer| + DASM_InstFlag_Return, + arch, + line_vaddr_rng.min, + machine_code); + } + + // rjf: determine last + DASM_CtrlFlowPoint *last_call_point = 0; + if(good_machine_code) for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) + { + if(n->v.inst_flags & DASM_InstFlag_Call) + { + last_call_point = &n->v; + } + } + + // rjf: push traps for all exit points + if(good_machine_code) for(DASM_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) + { + DASM_CtrlFlowPoint *point = &n->v; + CTRL_TrapFlags flags = 0; + B32 add = 1; + U64 trap_addr = point->vaddr; + + // rjf: if this is not the last call instruction in the control flow, + // and if we have no line info for this address, then do not add. + if(point != last_call_point && + point->inst_flags & DASM_InstFlag_Call && + point->jump_dest_vaddr != 0) + { + U64 jump_dest_vaddr = point->jump_dest_vaddr; + CTRL_Entity *jump_dest_module = ctrl_module_from_process_vaddr(process, jump_dest_vaddr); + U64 jump_dest_voff = ctrl_voff_from_vaddr(jump_dest_module, jump_dest_vaddr); + DI_Key jump_dest_dbgi_key = ctrl_dbgi_key_from_module(jump_dest_module); + D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, jump_dest_dbgi_key, jump_dest_voff); + if(lines.count == 0) + { + add = 0; + } + } + + // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. + if(point->inst_flags & (DASM_InstFlag_Call| + DASM_InstFlag_Branch| + DASM_InstFlag_UnconditionalJump| + DASM_InstFlag_Return)) + { + flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck); + + // rjf: omit if this jump stays inside of this line + if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) + { + add = 0; + } + + // rjf: omit if this jump stays inside of any range on this same textual line + for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first) + { + if(contains_1u64(n->v, point->jump_dest_vaddr)) + { + add = 0; + break; + } + } + + // rjf: trap @ destination, if we can - we can avoid a single-step this way. + if(point->jump_dest_vaddr != 0) + { + trap_addr = point->jump_dest_vaddr; + flags &= ~CTRL_TrapFlag_SingleStepAfterHit; + } + } + + // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping + else if(point->inst_flags & DASM_InstFlag_ChangesStackPointer) + { + flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); + } + + // rjf: add if appropriate + if(add) + { + CTRL_Trap trap = {flags, trap_addr}; + ctrl_trap_list_push(arena, &result.traps, &trap); + } + } + + // rjf: push traps for natural linear flow + if(good_line_info && good_machine_code) + { + U64 line_opl_vaddr = line_vaddr_rng.max; + for EachNode(n, Rng1U64Node, all_vaddr_ranges_on_same_line.first) + { + U64 opl = n->v.max; + B32 opl_in_line = 0; + for EachNode(n2, Rng1U64Node, all_vaddr_ranges_on_same_line.first) + { + if(n != n2 && opl == n2->v.min) + { + opl_in_line = 1; + break; + } + } + if(!opl_in_line) + { + CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, opl}; + ctrl_trap_list_push(arena, &result.traps, &trap); + } + } + } + + // rjf: store goodness + { + result.good_line_info = good_line_info; + result.good_read = good_machine_code; + } + + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Debug Info Lookups + +//- rjf: voff -> line info + +internal D_LineList +d_lines_from_dbgi_key_voff(Arena *arena, DI_Key dbgi_key, U64 voff) +{ + Temp scratch = scratch_begin(&arena, 1); + Access *access = access_open(); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, 0); + D_LineList result = {0}; + { + //- rjf: gather line tables + typedef struct LineTableNode LineTableNode; + struct LineTableNode + { + LineTableNode *next; + RDI_ParsedLineTable parsed_line_table; + }; + LineTableNode start_line_table = {0}; + RDI_Unit *unit = rdi_unit_from_voff(rdi, voff); + RDI_LineTable *unit_line_table = rdi_line_table_from_unit(rdi, unit); + rdi_parsed_from_line_table(rdi, unit_line_table, &start_line_table.parsed_line_table); + LineTableNode *top_line_table = 0; + RDI_Scope *scope = rdi_scope_from_voff(rdi, voff); + { + for(RDI_Scope *s = scope; + s->inline_site_idx != 0; + s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) + { + RDI_InlineSite *inline_site = rdi_element_from_name_idx(rdi, InlineSites, s->inline_site_idx); + if(inline_site->line_table_idx != 0) + { + LineTableNode *n = push_array(scratch.arena, LineTableNode, 1); + SLLStackPush(top_line_table, n); + RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, inline_site->line_table_idx); + rdi_parsed_from_line_table(rdi, line_table, &n->parsed_line_table); + } + } + } + SLLStackPush(top_line_table, &start_line_table); + + //- rjf: gather lines in each line table + Rng1U64 shallowest_voff_range = {0}; + for(LineTableNode *line_table_n = top_line_table; line_table_n != 0; line_table_n = line_table_n->next) + { + RDI_ParsedLineTable parsed_line_table = line_table_n->parsed_line_table; + U64 line_info_idx = rdi_line_info_idx_from_voff(&parsed_line_table, voff); + if(line_info_idx < parsed_line_table.count) + { + RDI_Line *line = &parsed_line_table.lines[line_info_idx]; + RDI_Column *column = (line_info_idx < parsed_line_table.col_count) ? &parsed_line_table.cols[line_info_idx] : 0; + RDI_SourceFile *file = rdi_element_from_name_idx(rdi, SourceFiles, line->file_idx); + String8List path_parts = {0}; + for(RDI_FilePathNode *fpn = rdi_element_from_name_idx(rdi, FilePathNodes, file->file_path_node_idx); + fpn != rdi_element_from_name_idx(rdi, FilePathNodes, 0); + fpn = rdi_element_from_name_idx(rdi, FilePathNodes, fpn->parent_path_node)) + { + String8 path_part = {0}; + path_part.str = rdi_string_from_idx(rdi, fpn->name_string_idx, &path_part.size); + str8_list_push_front(scratch.arena, &path_parts, path_part); + } + StringJoin join = {0}; + join.sep = str8_lit("/"); + String8 file_normalized_full_path = str8_list_join(arena, &path_parts, &join); + D_LineNode *n = push_array(arena, D_LineNode, 1); + SLLQueuePush(result.first, result.last, n); + result.count += 1; + if(line->file_idx != 0 && file_normalized_full_path.size != 0) + { + n->v.file_path = file_normalized_full_path; + } + n->v.pt = txt_pt(line->line_num, column ? column->col_first : 1); + n->v.voff_range = r1u64(parsed_line_table.voffs[line_info_idx], parsed_line_table.voffs[line_info_idx+1]); + n->v.dbgi_key = dbgi_key; + if(line_table_n == top_line_table) + { + shallowest_voff_range = n->v.voff_range; + } + } + } + + //- rjf: clamp all lines from all tables by shallowest (most unwound) range + for(D_LineNode *n = result.first; n != 0; n = n->next) + { + n->v.voff_range = intersect_1u64(n->v.voff_range, shallowest_voff_range); + } + } + access_close(access); + scratch_end(scratch); + return result; +} + +//- rjf: file:line -> line info + +// TODO(rjf): this depends on file path maps, needs to move + +internal D_LineListArray +d_lines_array_from_dbgi_key_file_path_line_range(Arena *arena, DI_Key dbgi_key, String8 file_path, Rng1S64 line_num_range) +{ + D_LineListArray array = {0}; + { + array.count = dim_1s64(line_num_range)+1; + array.v = push_array(arena, D_LineList, array.count); + di_key_list_push(arena, &array.dbgi_keys, dbgi_key); + } + Temp scratch = scratch_begin(&arena, 1); + U64 *lines_num_voffs = push_array(scratch.arena, U64, array.count); + Access *access = access_open(); + String8List overrides = rd_possible_overrides_from_file_path(scratch.arena, file_path); + for(String8Node *override_n = overrides.first; + override_n != 0; + override_n = override_n->next) + { + String8 file_path = override_n->string; + String8 file_path_normalized = rdim_normalize_path_str8(scratch.arena, file_path); + + // rjf: binary -> rdi + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 0, 0); + + // rjf: file_path_normalized * rdi -> src_id + B32 good_src_id = 0; + U32 src_id = 0; + if(rdi != &rdi_parsed_nil) ProfScope("file_path_normalized * rdi -> src_id") + { + RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, mapptr, &map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + good_src_id = 1; + src_id = ids[0]; + } + } + } + + // rjf: good src-id -> look up line info for visible range + if(good_src_id) ProfScope("good src-id -> look up line info for visible range") + { + RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); + RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); + RDI_ParsedSourceLineMap line_map = {0}; + rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); + U64 line_idx = 0; + for(S64 line_num = line_num_range.min; + line_num <= line_num_range.max; + line_num += 1, line_idx += 1) + { + D_LineList *list = &array.v[line_idx]; + U32 voff_count = 0; + U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); + if(lines_num_voffs[line_idx] < 8) ProfScope("iterate voffs (%i)", voff_count) for(U64 idx = 0; idx < voff_count; idx += 1) + { + U64 base_voff = voffs[idx]; + U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); + RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); + RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); + RDI_ParsedLineTable unit_line_info = {0}; + rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); + U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); + if(unit_line_info.voffs != 0) + { + Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); + S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; + D_LineNode *n = push_array(arena, D_LineNode, 1); + n->v.voff_range = range; + n->v.pt.line = (S64)actual_line; + n->v.pt.column = 1; + n->v.dbgi_key = dbgi_key; + SLLQueuePush(list->first, list->last, n); + list->count += 1; + lines_num_voffs[line_idx] += 1; + if(lines_num_voffs[line_idx] >= 8) + { + break; + } + } + } + } + } + } + access_close(access); + scratch_end(scratch); + return array; +} + +internal D_LineListArray +d_lines_array_from_file_path_line_range(Arena *arena, String8 file_path, Rng1S64 line_num_range) +{ + D_LineListArray array = {0}; + { + array.count = dim_1s64(line_num_range)+1; + array.v = push_array(arena, D_LineList, array.count); + } + Temp scratch = scratch_begin(&arena, 1); + U64 *lines_num_voffs = push_array(scratch.arena, U64, array.count); + DI_KeyArray dbgi_keys = di_push_all_loaded_keys(scratch.arena); + String8List overrides = rd_possible_overrides_from_file_path(scratch.arena, file_path); + for(String8Node *override_n = overrides.first; + override_n != 0; + override_n = override_n->next) + { + String8 file_path = override_n->string; + String8 file_path_normalized = rdim_normalize_path_str8(scratch.arena, file_path); + for EachIndex(idx, dbgi_keys.count) + { + Access *access = access_open(); + + // rjf: binary -> rdi + DI_Key key = dbgi_keys.v[idx]; + RDI_Parsed *rdi = di_rdi_from_key(access, key, 1, 0); + + // rjf: file_path_normalized * rdi -> src_id + B32 good_src_id = 0; + U32 src_id = 0; + if(rdi != &rdi_parsed_nil) ProfScope("file_path_normalized * rdi -> src_id") + { + RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, mapptr, &map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + good_src_id = 1; + src_id = ids[0]; + } + } + } + + // rjf: good src-id -> look up line info for visible range + if(good_src_id) ProfScope("good src-id -> look up line info for visible range") + { + RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); + RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); + RDI_ParsedSourceLineMap line_map = {0}; + rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); + U64 line_idx = 0; + for(S64 line_num = line_num_range.min; + line_num <= line_num_range.max; + line_num += 1, line_idx += 1) + { + D_LineList *list = &array.v[line_idx]; + U32 voff_count = 0; + U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); + if(lines_num_voffs[line_idx] < 8) ProfScope("iterate voffs (%i)", voff_count) for(U64 idx = 0; idx < voff_count; idx += 1) + { + U64 base_voff = voffs[idx]; + U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); + RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); + RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); + RDI_ParsedLineTable unit_line_info = {0}; + rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); + U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); + if(unit_line_info.voffs != 0) + { + Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); + S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; + D_LineNode *n = push_array(arena, D_LineNode, 1); + n->v.voff_range = range; + n->v.pt.line = (S64)actual_line; + n->v.pt.column = 1; + n->v.dbgi_key = key; + SLLQueuePush(list->first, list->last, n); + list->count += 1; + lines_num_voffs[line_idx] += 1; + if(lines_num_voffs[line_idx] >= 8) + { + break; + } + } + } + } + } + + // rjf: good src id -> push to relevant dbgi keys + if(good_src_id) + { + di_key_list_push(arena, &array.dbgi_keys, key); + } + + access_close(access); + } + } + scratch_end(scratch); + return array; +} + +internal D_LineList +d_lines_from_dbgi_key_file_path_line_num(Arena *arena, DI_Key dbgi_key, String8 file_path, S64 line_num) +{ + D_LineListArray array = d_lines_array_from_dbgi_key_file_path_line_range(arena, dbgi_key, file_path, r1s64(line_num, line_num+1)); + D_LineList list = {0}; + if(array.count != 0) + { + list = array.v[0]; + } + return list; +} + +internal D_LineList +d_lines_from_file_path_line_num(Arena *arena, String8 file_path, S64 line_num) +{ + D_LineListArray array = d_lines_array_from_file_path_line_range(arena, file_path, r1s64(line_num, line_num+1)); + D_LineList list = {0}; + if(array.count != 0) + { + list = array.v[0]; + } + return list; +} + +//////////////////////////////// +//~ rjf: Process/Thread/Module Info Lookups + +internal U64 +d_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64 rip_vaddr) +{ + ProfBeginFunction(); + U64 base_vaddr = 0; + if(!d_ctrl_targets_running()) + { + Temp scratch = scratch_begin(0, 0); + + //- rjf: unpack module info + CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr); + U64 addr_size = byte_size_from_arch(process->arch); + + switch(process->tls_model) + { + case CTRL_TlsModel_Null: {}break; + case CTRL_TlsModel_WinodwsNt: + { + // read thread local base pointer out of TEB + U64 thread_local_base = root_vaddr; + U64 tls_addr_array = 0; + CTRL_ProcessMemorySlice tls_addr_array_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(thread_local_base, thread_local_base + addr_size), 0, 0); + String8 tls_array_vaddr_data = tls_addr_array_slice.data; + if(tls_array_vaddr_data.size == addr_size) + { + U64 tls_array_vaddr = 0; + MemoryCopyStr8(&tls_array_vaddr, tls_array_vaddr_data); + + // read thread local storage pointer (array of TLS pointers, one per module) + U64 tls_ptr_vaddr = tls_array_vaddr + module->tls_index * addr_size; + CTRL_ProcessMemorySlice result_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(tls_ptr_vaddr, tls_ptr_vaddr + addr_size), 0, 0); + String8 result_data = result_slice.data; + if(result_data.size == addr_size) + { + MemoryCopyStr8(&base_vaddr, result_data); + } + } + }break; + case CTRL_TlsModel_Gnu: + { + if(module->tls_index > 0) // zero is reserved for the generation counter + { + // read dynamic thread vector pointer (one per dynamic module) + U64 dtv_base = root_vaddr; + U64 dtv_size = addr_size * 2; // union dtv { size_t counter; struct dtv_pointer { void *val, *to_free; }; }; + U64 dtv_pointer_vaddr = dtv_base + module->tls_index * dtv_size; + CTRL_ProcessMemorySlice dtv_pointer_slice = ctrl_process_memory_slice_from_vaddr_range(scratch.arena, process->handle, r1u64(dtv_pointer_vaddr, dtv_pointer_vaddr + dtv_size), 0, 0); + String8 dtv_pointer_data = dtv_pointer_slice.data; + if(dtv_pointer_data.size == dtv_size) + { + U64 dtv_pointer = 0; + MemoryCopyStr8(&dtv_pointer, dtv_pointer_data); + + // verify that TLS block was allocated + U64 tls_dtv_unallocated = addr_size == 4 ? max_U32 : max_U64; + if(dtv_pointer != tls_dtv_unallocated) + { + base_vaddr = dtv_pointer + module->tls_offset; // add tls_offset because DT_NEEDED modules share TLS block with the main exe + } + } + } + }break; + default: {InvalidPath;}break; + } + + scratch_end(scratch); + } + ProfEnd(); + return base_vaddr; +} + +//////////////////////////////// +//~ rjf: Target Controls + +//- rjf: stopped info from the control thread + +internal CTRL_Event +d_ctrl_last_stop_event(void) +{ + return d_state->ctrl_last_stop_event; +} + +//////////////////////////////// +//~ rjf: Main State Accessors/Mutators + +//- rjf: frame data + +internal U64 +d_frame_index(void) +{ + return d_state->frame_index; +} + +//- rjf: control state + +internal D_RunKind +d_ctrl_last_run_kind(void) +{ + return d_state->ctrl_last_run_kind; +} + +internal U64 +d_ctrl_last_run_frame_idx(void) +{ + return d_state->ctrl_last_run_frame_idx; +} + +internal B32 +d_ctrl_targets_running(void) +{ + return d_state->ctrl_is_running; +} + +//- rjf: active entity based queries + +internal DI_KeyList +d_push_active_dbgi_key_list(Arena *arena) +{ + DI_KeyList dbgis = {0}; + CTRL_EntityArray modules = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Module); + for EachIndex(idx, modules.count) + { + CTRL_Entity *module = modules.v[idx]; + DI_Key key = ctrl_dbgi_key_from_module(module); + di_key_list_push(arena, &dbgis, key); + } + return dbgis; +} + +//- rjf: per-run caches + +internal U64 +d_query_cached_rip_from_thread(CTRL_Entity *thread) +{ + U64 result = d_query_cached_rip_from_thread_unwind(thread, 0); + return result; +} + +internal U64 +d_query_cached_rip_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count) +{ + U64 result = 0; + if(unwind_count == 0) + { + result = ctrl_rip_from_thread(&d_state->ctrl_entity_store->ctx, thread->handle); + } + else + { + Access *access = access_open(); + CTRL_CallStack callstack = ctrl_call_stack_from_thread(access, thread->handle, 1, 0); + if(callstack.concrete_frames_count != 0) + { + result = regs_rip_from_arch_block(thread->arch, callstack.concrete_frames[unwind_count%callstack.concrete_frames_count]->regs); + } + access_close(access); + } + return result; +} + +internal U64 +d_query_cached_cfa_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count) +{ + U64 cfa = 0; + Access *access = access_open(); + CTRL_CallStack callstack = ctrl_call_stack_from_thread(access, thread->handle, 1, 0); + if(callstack.concrete_frames_count != 0) + { + cfa = callstack.concrete_frames[unwind_count%callstack.concrete_frames_count]->cfa; + } + access_close(access); + return cfa; +} + +internal U64 +d_query_cached_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64 rip_vaddr) +{ + U64 result = 0; + for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->tls_base_caches); cache_idx += 1) + { + D_RunTLSBaseCache *cache = &d_state->tls_base_caches[(d_state->tls_base_cache_gen+cache_idx)%ArrayCount(d_state->tls_base_caches)]; + if(cache_idx == 0 && cache->slots_count == 0) + { + cache->slots_count = 256; + cache->slots = push_array(cache->arena, D_RunTLSBaseCacheSlot, cache->slots_count); + } + else if(cache->slots_count == 0) + { + break; + } + CTRL_Handle handle = process->handle; + U64 hash = d_hash_from_seed_string(d_hash_from_string(str8_struct(&handle)), str8_struct(&rip_vaddr)); + U64 slot_idx = hash%cache->slots_count; + D_RunTLSBaseCacheSlot *slot = &cache->slots[slot_idx]; + D_RunTLSBaseCacheNode *node = 0; + for(D_RunTLSBaseCacheNode *n = slot->first; n != 0; n = n->hash_next) + { + if(ctrl_handle_match(n->process, handle) && n->root_vaddr == root_vaddr && n->rip_vaddr == rip_vaddr) + { + node = n; + break; + } + } + if(node == 0) + { + U64 tls_base_vaddr = d_tls_base_vaddr_from_process_root_rip(process, root_vaddr, rip_vaddr); + if(tls_base_vaddr != 0) + { + node = push_array(cache->arena, D_RunTLSBaseCacheNode, 1); + SLLQueuePush_N(slot->first, slot->last, node, hash_next); + node->process = handle; + node->root_vaddr = root_vaddr; + node->rip_vaddr = rip_vaddr; + node->tls_base_vaddr = tls_base_vaddr; + } + } + if(node != 0 && node->tls_base_vaddr != 0) + { + result = node->tls_base_vaddr; + break; + } + } + return result; +} + +internal E_String2NumMap * +d_query_cached_locals_map_from_dbgi_key_voff(DI_Key dbgi_key, U64 voff) +{ + ProfBeginFunction(); + E_String2NumMap *map = &e_string2num_map_nil; + for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->locals_caches); cache_idx += 1) + { + D_RunLocalsCache *cache = &d_state->locals_caches[(d_state->locals_cache_gen+cache_idx)%ArrayCount(d_state->locals_caches)]; + if(cache_idx == 0 && cache->table_size == 0) + { + cache->table_size = 256; + cache->table = push_array(cache->arena, D_RunLocalsCacheSlot, cache->table_size); + } + else if(cache->table_size == 0) + { + break; + } + U64 hash = u64_hash_from_str8(str8_struct(&dbgi_key)); + U64 slot_idx = hash % cache->table_size; + D_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; + D_RunLocalsCacheNode *node = 0; + for(D_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) + { + if(di_key_match(n->dbgi_key, dbgi_key) && n->voff == voff) + { + node = n; + break; + } + } + if(node == 0) + { + Access *access = access_open(); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, 0); + E_String2NumMap *map = e_push_locals_map_from_rdi_voff(cache->arena, rdi, voff); + if(map->slots_count != 0) + { + node = push_array(cache->arena, D_RunLocalsCacheNode, 1); + node->dbgi_key = dbgi_key; + node->voff = voff; + node->locals_map = map; + SLLQueuePush_N(slot->first, slot->last, node, hash_next); + } + access_close(access); + } + if(node != 0 && node->locals_map->slots_count != 0) + { + map = node->locals_map; + break; + } + } + ProfEnd(); + return map; +} + +internal E_String2NumMap * +d_query_cached_member_map_from_dbgi_key_voff(DI_Key dbgi_key, U64 voff) +{ + ProfBeginFunction(); + E_String2NumMap *map = &e_string2num_map_nil; + for(U64 cache_idx = 0; cache_idx < ArrayCount(d_state->member_caches); cache_idx += 1) + { + D_RunLocalsCache *cache = &d_state->member_caches[(d_state->member_cache_gen+cache_idx)%ArrayCount(d_state->member_caches)]; + if(cache_idx == 0 && cache->table_size == 0) + { + cache->table_size = 256; + cache->table = push_array(cache->arena, D_RunLocalsCacheSlot, cache->table_size); + } + else if(cache->table_size == 0) + { + break; + } + U64 hash = u64_hash_from_str8(str8_struct(&dbgi_key)); + U64 slot_idx = hash % cache->table_size; + D_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; + D_RunLocalsCacheNode *node = 0; + for(D_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) + { + if(di_key_match(n->dbgi_key, dbgi_key) && n->voff == voff) + { + node = n; + break; + } + } + if(node == 0) + { + Access *access = access_open(); + RDI_Parsed *rdi = di_rdi_from_key(access, dbgi_key, 1, 0); + E_String2NumMap *map = e_push_member_map_from_rdi_voff(cache->arena, rdi, voff); + if(map->slots_count != 0) + { + node = push_array(cache->arena, D_RunLocalsCacheNode, 1); + node->dbgi_key = dbgi_key; + node->voff = voff; + node->locals_map = map; + SLLQueuePush_N(slot->first, slot->last, node, hash_next); + } + access_close(access); + } + if(node != 0 && node->locals_map->slots_count != 0) + { + map = node->locals_map; + break; + } + } + ProfEnd(); + return map; +} + +//- rjf: top-level command dispatch + +internal void +d_push_cmd(D_CmdKind kind, D_CmdParams *params) +{ + d_cmd_list_push_new(d_state->cmds_arena, &d_state->cmds, kind, params); +} + +//- rjf: command iteration + +internal B32 +d_next_cmd(D_Cmd **cmd) +{ + D_CmdNode *start_node = d_state->cmds.first; + if(cmd[0] != 0) + { + start_node = CastFromMember(D_CmdNode, cmd, cmd[0]); + start_node = start_node->next; + } + cmd[0] = 0; + if(start_node != 0) + { + cmd[0] = &start_node->cmd; + } + return !!cmd[0]; +} + +//////////////////////////////// +//~ rjf: Main Layer Top-Level Calls + +#if !defined(XXH_IMPLEMENTATION) +# define XXH_IMPLEMENTATION +# define XXH_STATIC_LINKING_ONLY +# include "third_party/xxHash/xxhash.h" +#endif + +internal void +d_init(void) +{ + Arena *arena = arena_alloc(); + d_state = push_array(arena, D_State, 1); + d_state->arena = arena; + d_state->cmds_arena = arena_alloc(); + d_state->output_log_key = c_key_make(c_root_alloc(), c_id_make(0, 0)); + c_submit_data(d_state->output_log_key, 0, str8_zero()); + d_state->ctrl_entity_store = ctrl_entity_ctx_rw_store_alloc(); + d_state->ctrl_stop_arena = arena_alloc(); + d_state->ctrl_msg_arena = arena_alloc(); + + // rjf: set up caches + for(U64 idx = 0; idx < ArrayCount(d_state->tls_base_caches); idx += 1) + { + d_state->tls_base_caches[idx].arena = arena_alloc(); + } + for(U64 idx = 0; idx < ArrayCount(d_state->locals_caches); idx += 1) + { + d_state->locals_caches[idx].arena = arena_alloc(); + } + for(U64 idx = 0; idx < ArrayCount(d_state->member_caches); idx += 1) + { + d_state->member_caches[idx].arena = arena_alloc(); + } + + // rjf: set up run state + d_state->ctrl_last_run_arena = arena_alloc(); +} + +internal D_EventList +d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_PathMapArray *path_maps, U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + D_EventList result = {0}; + d_state->frame_index += 1; + + ////////////////////////////// + //- rjf: sync with ctrl thread + // + ProfScope("sync with ctrl thread") + { + //- rjf: grab next reggen/memgen + U64 new_mem_gen = ctrl_mem_gen(); + U64 new_reg_gen = ctrl_reg_gen(); + + //- rjf: consume & process events + CTRL_EventList events = ctrl_c2u_pop_events(scratch.arena); + ctrl_entity_store_apply_events(d_state->ctrl_entity_store, &events); + for(CTRL_EventNode *event_n = events.first; + event_n != 0; + event_n = event_n->next) + { + CTRL_Event *event = &event_n->v; + log_infof("ctrl_event:\n{\n"); + log_infof("kind: \"%S\"\n", ctrl_string_from_event_kind(event->kind)); + log_infof("entity_id: %u\n", event->entity_id); + switch(event->kind) + { + default:{}break; + + //- rjf: errors + + case CTRL_EventKind_Error: + { + log_user_error(event->string); + }break; + + //- rjf: starts/stops + + case CTRL_EventKind_Started: + { + d_state->ctrl_is_running = 1; + d_state->ctrl_thread_run_state = 1; + }break; + + case CTRL_EventKind_Stopped: + { + B32 should_snap = !(d_state->ctrl_soft_halt_issued); + d_state->ctrl_is_running = 0; + d_state->ctrl_thread_run_state = 0; + d_state->ctrl_soft_halt_issued = 0; + + // rjf: exception or unexpected trap -> push error + if(event->cause == CTRL_EventCause_InterruptedByException || + event->cause == CTRL_EventCause_InterruptedByTrap) + { + log_user_error(str8_zero()); + } + + // rjf: gather stop info + { + arena_clear(d_state->ctrl_stop_arena); + MemoryCopyStruct(&d_state->ctrl_last_stop_event, event); + d_state->ctrl_last_stop_event.string = push_str8_copy(d_state->ctrl_stop_arena, d_state->ctrl_last_stop_event.string); + } + + // rjf: push stop event to caller, if this is not a soft-halt + if(should_snap) + { + CTRL_Entity *thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, event->entity); + D_EventCause cause = D_EventCause_Null; + switch(event->cause) + { + default:{}break; + case CTRL_EventCause_InterruptedByHalt: + { + if(should_snap) + { + cause = D_EventCause_Halt; + } + else + { + cause = D_EventCause_SoftHalt; + } + }break; + case CTRL_EventCause_UserBreakpoint: {cause = D_EventCause_UserBreakpoint;}break; + } + D_EventNode *n = push_array(arena, D_EventNode, 1); + SLLQueuePush(result.first, result.last, n); + result.count += 1; + D_Event *evt = &n->v; + evt->kind = D_EventKind_Stop; + evt->cause = cause; + evt->thread = thread->kind == CTRL_EntityKind_Thread ? thread->handle : ctrl_handle_zero(); + evt->vaddr = event->rip_vaddr; + evt->id = event->u64_code; + } + }break; + + //- rjf: entity creation/deletion + + case CTRL_EventKind_NewProc: + { + // rjf: the first process? -> clear session output + CTRL_EntityArray existing_processes = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Process); + if(existing_processes.count == 1) + { + MTX_Op op = {r1u64(0, 0xffffffffffffffffull), str8_lit("[new session]\n")}; + mtx_push_op(d_state->output_log_key, op); + } + }break; + + case CTRL_EventKind_EndProc: + { + D_EventNode *n = push_array(arena, D_EventNode, 1); + SLLQueuePush(result.first, result.last, n); + result.count += 1; + D_Event *evt = &n->v; + evt->kind = D_EventKind_ProcessEnd; + evt->code = event->u64_code; + }break; + + case CTRL_EventKind_NewModule: + { + D_EventNode *n = push_array(arena, D_EventNode, 1); + SLLQueuePush(result.first, result.last, n); + result.count += 1; + D_Event *evt = &n->v; + evt->kind = D_EventKind_ModuleLoad; + evt->module = event->entity; + }break; + + //- rjf: debug strings + + case CTRL_EventKind_DebugString: + { + MTX_Op op = {r1u64(max_U64, max_U64), event->string}; + mtx_push_op(d_state->output_log_key, op); + }break; + + //- rjf: memory + + case CTRL_EventKind_MemReserve:{}break; + case CTRL_EventKind_MemCommit:{}break; + case CTRL_EventKind_MemDecommit:{}break; + case CTRL_EventKind_MemRelease:{}break; + } + log_infof("}\n\n"); + } + + //- rjf: clear tls base cache + if((d_state->tls_base_cache_reggen_idx != new_reg_gen || + d_state->tls_base_cache_memgen_idx != new_mem_gen) && + !d_ctrl_targets_running()) + { + d_state->tls_base_cache_gen += 1; + D_RunTLSBaseCache *cache = &d_state->tls_base_caches[d_state->tls_base_cache_gen%ArrayCount(d_state->tls_base_caches)]; + arena_clear(cache->arena); + cache->slots_count = 0; + cache->slots = 0; + d_state->tls_base_cache_reggen_idx = new_reg_gen; + d_state->tls_base_cache_memgen_idx = new_mem_gen; + } + + //- rjf: clear locals cache + if(d_state->locals_cache_reggen_idx != new_reg_gen && + !d_ctrl_targets_running()) + { + d_state->locals_cache_gen += 1; + D_RunLocalsCache *cache = &d_state->locals_caches[d_state->locals_cache_gen%ArrayCount(d_state->locals_caches)]; + arena_clear(cache->arena); + cache->table_size = 0; + cache->table = 0; + d_state->locals_cache_reggen_idx = new_reg_gen; + } + + //- rjf: clear members cache + if(d_state->member_cache_reggen_idx != new_reg_gen && + !d_ctrl_targets_running()) + { + d_state->member_cache_gen += 1; + D_RunLocalsCache *cache = &d_state->member_caches[d_state->member_cache_gen%ArrayCount(d_state->member_caches)]; + arena_clear(cache->arena); + cache->table_size = 0; + cache->table = 0; + d_state->member_cache_reggen_idx = new_reg_gen; + } + } + + ////////////////////////////// + //- rjf: hash ctrl parameterization state + // + U128 ctrl_param_state_hash = {0}; + { + // rjf: build data strings of all param data + String8List strings = {0}; + { + CTRL_EntityArray threads = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Thread); + for EachIndex(idx, threads.count) + { + CTRL_Entity *thread = threads.v[idx]; + if(thread->is_frozen) + { + str8_list_push(scratch.arena, &strings, str8_struct(&thread->id)); + str8_list_push(scratch.arena, &strings, str8_struct(&thread->is_frozen)); + } + } + for(U64 idx = 0; idx < breakpoints->count; idx += 1) + { + D_Breakpoint *bp = &breakpoints->v[idx]; + str8_list_push(scratch.arena, &strings, bp->file_path); + str8_list_push(scratch.arena, &strings, str8_struct(&bp->pt)); + str8_list_push(scratch.arena, &strings, bp->vaddr_expr); + str8_list_push(scratch.arena, &strings, bp->condition); + } + } + + // rjf: join & hash to produce result + String8 string = str8_list_join(scratch.arena, &strings, 0); + XXH128_hash_t hash = XXH3_128bits(string.str, string.size); + MemoryCopy(&ctrl_param_state_hash, &hash, sizeof(ctrl_param_state_hash)); + } + + ////////////////////////////// + //- rjf: if ctrl thread is running, and our ctrl parameterization + // state hash has changed since the last run, we should soft- + // halt-refresh to inform the ctrl thread about the updated + // state + // + if(d_ctrl_targets_running() && !u128_match(ctrl_param_state_hash, d_state->ctrl_last_run_param_state_hash)) + { + d_cmd(D_CmdKind_SoftHaltRefresh); + } + + ////////////////////////////// + //- rjf: process top-level commands + // + D_CmdList deferred_cmds = {0}; + CTRL_MsgList ctrl_msgs = {0}; + ProfScope("process top-level commands") + { + D_BreakpointArray run_extra_bps = {0}; + for(D_Cmd *cmd = 0; d_next_cmd(&cmd);) + { + // rjf: unpack command + D_CmdParams *params = &cmd->params; + + // rjf: prep ctrl running arguments + B32 need_run = 0; + D_RunKind run_kind = D_RunKind_Run; + CTRL_Entity *run_thread = &ctrl_entity_nil; + CTRL_RunFlags run_flags = 0; + CTRL_TrapList run_traps = {0}; + + // rjf: process command + switch(cmd->kind) + { + default:{}break; + + //- rjf: low-level target control operations + case D_CmdKind_LaunchAndRun: + case D_CmdKind_LaunchAndStepInto: + { + // rjf: get list of targets to launch + D_TargetArray *targets_to_launch = ¶ms->targets; + + // rjf: no targets => assume all active targets + if(targets_to_launch->count == 0) + { + targets_to_launch = targets; + } + + // rjf: launch + if(targets_to_launch->count != 0) + { + for(U64 idx = 0; idx < targets_to_launch->count; idx += 1) + { + // rjf: unpack target + D_Target *target = &targets_to_launch->v[idx]; + String8 exe = str8_skip_chop_whitespace(target->exe); + String8 args = str8_skip_chop_whitespace(target->args); + String8 working_directory = str8_skip_chop_whitespace(target->working_directory); + String8 custom_entry_point_name = str8_skip_chop_whitespace(target->custom_entry_point_name); + String8 stdout_path = str8_skip_chop_whitespace(target->stdout_path); + String8 stderr_path = str8_skip_chop_whitespace(target->stderr_path); + String8 stdin_path = str8_skip_chop_whitespace(target->stdin_path); + String8List env = target->env; + if(working_directory.size == 0) + { + working_directory = os_get_current_path(scratch.arena); + } + + // rjf: build launch options + String8List cmdln_strings = {0}; + { + str8_list_push(scratch.arena, &cmdln_strings, exe); + { + U64 start_split_idx = 0; + B32 quoted = 0; + for(U64 idx = 0; idx <= args.size; idx += 1) + { + U8 byte = idx < args.size ? args.str[idx] : 0; + if(byte == '"') + { + quoted ^= 1; + } + B32 splitter_found = (!quoted && (byte == 0 || char_is_space(byte))); + if(splitter_found) + { + String8 string = str8_substr(args, r1u64(start_split_idx, idx)); + if(string.size > 0) + { + str8_list_push(scratch.arena, &cmdln_strings, string); + } + start_split_idx = idx+1; + } + } + } + } + + // rjf: push message to launch + { + CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); + msg->kind = CTRL_MsgKind_Launch; + msg->path = working_directory; + msg->cmd_line_string_list = cmdln_strings; + msg->stdout_path = stdout_path; + msg->stderr_path = stderr_path; + msg->stdin_path = stdin_path; + msg->debug_subprocesses = target->debug_subprocesses; + msg->env_inherit = 1; + MemoryCopyArray(msg->exception_code_filters, exception_code_filters); + str8_list_push(scratch.arena, &msg->entry_points, custom_entry_point_name); + msg->env_string_list = env; + } + } + + // rjf: run + need_run = 1; + run_kind = D_RunKind_Run; + run_thread = &ctrl_entity_nil; + run_flags = (cmd->kind == D_CmdKind_LaunchAndStepInto) ? CTRL_RunFlag_StopOnEntryPoint : 0; + } + + // rjf: no targets -> error + if(targets_to_launch->count == 0) + { + log_user_error(str8_lit("No active targets exist; cannot launch. You must select a target first.")); + } + }break; + case D_CmdKind_Kill: + { + CTRL_Entity *process = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->process); + if(process == &ctrl_entity_nil) + { + log_user_error(str8_lit("Cannot kill; no process was specified.")); + } + else + { + CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); + msg->kind = CTRL_MsgKind_Kill; + msg->exit_code = 1; + msg->entity = process->handle; + MemoryCopyArray(msg->exception_code_filters, exception_code_filters); + } + }break; + case D_CmdKind_KillAll: + { + CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); + msg->kind = CTRL_MsgKind_KillAll; + msg->exit_code = 1; + MemoryCopyArray(msg->exception_code_filters, exception_code_filters); + }break; + case D_CmdKind_Detach: + { + CTRL_Entity *process = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->process); + if(process == &ctrl_entity_nil) + { + log_user_error(str8_lit("Cannot detach; no process specified.")); + } + else + { + CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); + msg->kind = CTRL_MsgKind_Detach; + msg->entity = process->handle; + MemoryCopyArray(msg->exception_code_filters, exception_code_filters); + } + }break; + case D_CmdKind_Continue: + { + B32 good_to_run = 0; + CTRL_EntityArray threads = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Thread); + if(threads.count > 0) + { + for EachIndex(idx, threads.count) + { + CTRL_Entity *thread = threads.v[idx]; + if(!thread->is_frozen) + { + good_to_run = 1; + break; + } + } + if(good_to_run) + { + need_run = 1; + run_kind = D_RunKind_Run; + run_thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->thread); + } + else + { + log_user_error(str8_lit("Cannot run with all threads frozen.")); + } + } + }break; + case D_CmdKind_StepIntoInst: + case D_CmdKind_StepOverInst: + case D_CmdKind_StepIntoLine: + case D_CmdKind_StepOverLine: + case D_CmdKind_StepOut: + { + CTRL_Entity *thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->thread); + if(thread == &ctrl_entity_nil) + { + log_user_error(str8_lit("Must have a selected thread to step.")); + } + else if(d_ctrl_targets_running()) + { + if(d_ctrl_last_run_kind() == D_RunKind_Run) + { + log_user_error(str8_lit("Must halt before stepping.")); + } + } + else if(thread->is_frozen) + { + log_user_error(str8_lit("Must thaw selected thread before stepping.")); + } + else + { + D_TrapNet trap_net = {0}; + switch(cmd->kind) + { + default: break; + case D_CmdKind_StepIntoInst: {trap_net.good_read = 1;}break; + case D_CmdKind_StepOverInst: {trap_net = d_trap_net_from_thread__step_over_inst(scratch.arena, thread);}break; + case D_CmdKind_StepIntoLine: {trap_net = d_trap_net_from_thread__step_into_line(scratch.arena, thread);}break; + case D_CmdKind_StepOverLine: {trap_net = d_trap_net_from_thread__step_over_line(scratch.arena, thread);}break; + case D_CmdKind_StepOut: + { + Access *access = access_open(); + + // rjf: thread => call stack + CTRL_CallStack callstack = ctrl_call_stack_from_thread(access, thread->handle, 1, os_now_microseconds()+10000); + + // rjf: use first unwind frame to generate trap + if(callstack.concrete_frames_count > 1) + { + U64 vaddr = regs_rip_from_arch_block(thread->arch, callstack.concrete_frames[1]->regs); + CTRL_Trap trap = {CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck, vaddr}; + ctrl_trap_list_push(scratch.arena, &trap_net.traps, &trap); + trap_net.good_read = 1; + } + else + { + log_user_error(str8_lit("Could not find the return address of the current callstack frame successfully.")); + } + + access_close(access); + }break; + } + B32 good_trap_net = (trap_net.good_read || !trap_net.good_line_info); + if(good_trap_net && trap_net.traps.count != 0) + { + need_run = 1; + run_kind = D_RunKind_Step; + run_thread = thread; + run_flags = 0; + run_traps = trap_net.traps; + } + else if(good_trap_net && trap_net.traps.count == 0) + { + need_run = 1; + run_kind = D_RunKind_SingleStep; + run_thread = thread; + run_flags = 0; + run_traps = trap_net.traps; + } + else if(!good_trap_net && params->retry_idx < 100) + { + D_CmdParams params_copy = *params; + params_copy.retry_idx += 1; + d_cmd_list_push_new(scratch.arena, &deferred_cmds, cmd->kind, ¶ms_copy); + } + else if(!good_trap_net) + { + log_user_error(str8_lit("Could not successfully step.")); + } + } + }break; + case D_CmdKind_Halt: + if(d_ctrl_targets_running()) + { + ctrl_halt(); + }break; + case D_CmdKind_SoftHaltRefresh: + if(d_ctrl_targets_running()) + { + need_run = 1; + run_kind = d_state->ctrl_last_run_kind; + run_thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, d_state->ctrl_last_run_thread_handle); + run_flags = d_state->ctrl_last_run_flags; + run_traps = d_state->ctrl_last_run_traps; + }break; + case D_CmdKind_SetThreadIP: + { + CTRL_Entity *thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->thread); + U64 vaddr = params->vaddr; + void *block = ctrl_reg_block_from_thread(scratch.arena, &d_state->ctrl_entity_store->ctx, thread->handle); + regs_arch_block_write_rip(thread->arch, block, vaddr); + B32 result = ctrl_thread_write_reg_block(thread->handle, block); + (void)result; + }break; + + //- rjf: high-level composite target control operations + case D_CmdKind_RunToLine: + { + run_extra_bps.count = 1; + run_extra_bps.v = push_array(scratch.arena, D_Breakpoint, 1); + if(params->file_path.size != 0) + { + run_extra_bps.v[0].file_path = params->file_path; + run_extra_bps.v[0].pt = params->cursor; + } + else if(params->vaddr != 0) + { + run_extra_bps.v[0].vaddr_expr = push_str8f(scratch.arena, "0x%I64x", params->vaddr); + } + d_cmd(D_CmdKind_Run); + }break; + case D_CmdKind_Run: + { + CTRL_EntityArray processes = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Process); + if(processes.count != 0) + { + d_cmd(D_CmdKind_Continue, .machine = params->machine, .process = params->process, .thread = params->thread); + } + else if(!d_ctrl_targets_running()) + { + d_cmd(D_CmdKind_LaunchAndRun); + } + }break; + case D_CmdKind_Restart: + { + CTRL_EntityArray processes = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Process); + if(processes.count != 0) + { + d_cmd(D_CmdKind_KillAll); + } + d_cmd(D_CmdKind_LaunchAndRun); + }break; + case D_CmdKind_StepInto: + case D_CmdKind_StepOver: + { + CTRL_EntityArray processes = ctrl_entity_array_from_kind(&d_state->ctrl_entity_store->ctx, CTRL_EntityKind_Process); + if(processes.count != 0) + { + D_CmdKind step_cmd_kind = (cmd->kind == D_CmdKind_StepInto + ? D_CmdKind_StepIntoLine + : D_CmdKind_StepOverLine); + B32 prefer_disasm = params->prefer_disasm; + if(prefer_disasm) + { + step_cmd_kind = (cmd->kind == D_CmdKind_StepInto + ? D_CmdKind_StepIntoInst + : D_CmdKind_StepOverInst); + } + d_cmd(step_cmd_kind, .thread = params->thread); + } + else if(!d_ctrl_targets_running()) + { + d_cmd(D_CmdKind_LaunchAndStepInto, .targets = *targets); + } + }break; + + //- rjf: debug control context management operations + case D_CmdKind_FreezeThread: + case D_CmdKind_ThawThread: + case D_CmdKind_FreezeProcess: + case D_CmdKind_ThawProcess: + case D_CmdKind_FreezeMachine: + case D_CmdKind_ThawMachine: + { + D_CmdKind disptch_kind = ((cmd->kind == D_CmdKind_FreezeThread || + cmd->kind == D_CmdKind_FreezeProcess || + cmd->kind == D_CmdKind_FreezeMachine) + ? D_CmdKind_FreezeEntity + : D_CmdKind_ThawEntity); + d_push_cmd(disptch_kind, params); + }break; + case D_CmdKind_FreezeLocalMachine: + { + CTRL_MachineID machine_id = CTRL_MachineID_Local; + d_cmd(D_CmdKind_FreezeMachine, .entity = ctrl_handle_make(machine_id, dmn_handle_zero())); + }break; + case D_CmdKind_ThawLocalMachine: + { + CTRL_MachineID machine_id = CTRL_MachineID_Local; + d_cmd(D_CmdKind_ThawMachine, .entity = ctrl_handle_make(machine_id, dmn_handle_zero())); + }break; + case D_CmdKind_FreezeEntity: + case D_CmdKind_ThawEntity: + { + B32 should_freeze = (cmd->kind == D_CmdKind_FreezeEntity); + CTRL_Entity *root = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->entity); + for(CTRL_Entity *e = root; e != &ctrl_entity_nil; e = ctrl_entity_rec_depth_first_pre(e, root).next) + { + if(e->kind == CTRL_EntityKind_Thread) + { + e->is_frozen = should_freeze; + CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); + msg->kind = (should_freeze ? CTRL_MsgKind_FreezeThread : CTRL_MsgKind_ThawThread); + msg->entity = e->handle; + } + } + if(d_ctrl_targets_running()) + { + need_run = 1; + run_kind = d_state->ctrl_last_run_kind; + run_thread = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, d_state->ctrl_last_run_thread_handle); + run_flags = d_state->ctrl_last_run_flags; + run_traps = d_state->ctrl_last_run_traps; + } + }break; + + //- rjf: entity decoration + case D_CmdKind_SetEntityColor: + { + CTRL_Entity *entity = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->entity); + entity->rgba = params->rgba; + }break; + case D_CmdKind_SetEntityName: + { + CTRL_Entity *entity = ctrl_entity_from_handle(&d_state->ctrl_entity_store->ctx, params->entity); + ctrl_entity_equip_string(d_state->ctrl_entity_store, entity, params->string); + }break; + + //- rjf: attaching + case D_CmdKind_Attach: + { + U32 pid = params->pid; + if(pid != 0) + { + CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); + msg->kind = CTRL_MsgKind_Attach; + msg->entity_id = pid; + MemoryCopyArray(msg->exception_code_filters, exception_code_filters); + } + }break; + } + + // rjf: do run if needed + if(need_run) + { + // rjf: compute hash of all run-parameterization entities, store + { + d_state->ctrl_last_run_param_state_hash = ctrl_param_state_hash; + } + + // rjf: push & fill run message + CTRL_Msg *msg = ctrl_msg_list_push(scratch.arena, &ctrl_msgs); + { + CTRL_Entity *process = ctrl_entity_ancestor_from_kind(run_thread, CTRL_EntityKind_Process); + msg->kind = (run_kind == D_RunKind_Run || run_kind == D_RunKind_Step) ? CTRL_MsgKind_Run : CTRL_MsgKind_SingleStep; + msg->run_flags = run_flags; + msg->entity = run_thread->handle; + msg->parent = process->handle; + MemoryCopyArray(msg->exception_code_filters, exception_code_filters); + MemoryCopyStruct(&msg->traps, &run_traps); + D_BreakpointArray *bp_batches[] = + { + breakpoints, + &run_extra_bps, + }; + for(U64 batch_idx = 0; batch_idx < ArrayCount(bp_batches); batch_idx += 1) + { + D_BreakpointArray *batch_breakpoints = bp_batches[batch_idx]; + for(U64 idx = 0; idx < batch_breakpoints->count; idx += 1) + { + // rjf: unpack user breakpoint entity + D_Breakpoint *bp = &batch_breakpoints->v[idx]; + + // rjf: d -> ctrl flags + CTRL_UserBreakpointFlags ctrl_bp_flags = 0; + if(bp->flags & D_BreakpointFlag_BreakOnWrite) { ctrl_bp_flags |= CTRL_UserBreakpointFlag_BreakOnWrite; } + if(bp->flags & D_BreakpointFlag_BreakOnRead) { ctrl_bp_flags |= CTRL_UserBreakpointFlag_BreakOnRead; } + if(bp->flags & D_BreakpointFlag_BreakOnExecute) { ctrl_bp_flags |= CTRL_UserBreakpointFlag_BreakOnExecute; } + + // rjf: textual location -> add breakpoints for all possible override locations + if(bp->file_path.size != 0 && bp->pt.line != 0) + { + String8List overrides = d_possible_path_overrides_from_maps_path(scratch.arena, path_maps, bp->file_path); + for(String8Node *n = overrides.first; n != 0; n = n->next) + { + CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_FileNameAndLineColNumber}; + ctrl_user_bp.flags = ctrl_bp_flags; + ctrl_user_bp.id = bp->id; + ctrl_user_bp.string = n->string; + ctrl_user_bp.pt = bp->pt; + ctrl_user_bp.condition = bp->condition; + ctrl_user_bp.size = bp->size; + ctrl_user_breakpoint_list_push(scratch.arena, &msg->user_bps, &ctrl_user_bp); + } + } + + // rjf: virtual address expression -> add expression breakpoint + else if(bp->vaddr_expr.size != 0) + { + CTRL_UserBreakpoint ctrl_user_bp = {CTRL_UserBreakpointKind_Expression}; + ctrl_user_bp.flags = ctrl_bp_flags; + ctrl_user_bp.id = bp->id; + ctrl_user_bp.string = bp->vaddr_expr; + ctrl_user_bp.condition = bp->condition; + ctrl_user_bp.size = bp->size; + ctrl_user_breakpoint_list_push(scratch.arena, &msg->user_bps, &ctrl_user_bp); + } + } + } + } + + // rjf: copy run traps to scratch (needed, if run_traps can be `d_state->ctrl_last_run_traps`) + CTRL_TrapList run_traps_copy = ctrl_trap_list_copy(scratch.arena, &run_traps); + D_BreakpointArray run_extra_bps_copy = d_breakpoint_array_copy(scratch.arena, &run_extra_bps); + + // rjf: store last run info + arena_clear(d_state->ctrl_last_run_arena); + d_state->ctrl_last_run_kind = run_kind; + d_state->ctrl_last_run_frame_idx = d_frame_index(); + d_state->ctrl_last_run_thread_handle = run_thread->handle; + d_state->ctrl_last_run_flags = run_flags; + d_state->ctrl_last_run_traps = ctrl_trap_list_copy(d_state->ctrl_last_run_arena, &run_traps_copy); + d_state->ctrl_last_run_extra_bps = d_breakpoint_array_copy(d_state->ctrl_last_run_arena, &run_extra_bps_copy); + d_state->ctrl_is_running = 1; + } + } + } + + ////////////////////////////// + //- rjf: clear command batch + // + { + arena_clear(d_state->cmds_arena); + MemoryZeroStruct(&d_state->cmds); + } + + ////////////////////////////// + //- rjf: push deferred commands + // + for EachNode(n, D_CmdNode, deferred_cmds.first) + { + d_cmd_list_push_new(d_state->cmds_arena, &d_state->cmds, n->cmd.kind, &n->cmd.params); + } + + ////////////////////////////// + //- rjf: push new control messages to queue - try to send queue to control, + // clear queue if successful (if not, we'll just keep them around until + // the next tick) + // + { + CTRL_MsgList msgs_copy = ctrl_msg_list_deep_copy(d_state->ctrl_msg_arena, &ctrl_msgs); + ctrl_msg_list_concat_in_place(&d_state->ctrl_msgs, &msgs_copy); + if(d_state->ctrl_msgs.count != 0) + { + if(!d_state->ctrl_soft_halt_issued && d_state->ctrl_thread_run_state) + { + d_state->ctrl_soft_halt_issued = 1; + ctrl_halt(); + } + if(ctrl_u2c_push_msgs(&d_state->ctrl_msgs, os_now_microseconds()+100)) + { + MemoryZeroStruct(&d_state->ctrl_msgs); + arena_clear(d_state->ctrl_msg_arena); + } + } + } + + ProfEnd(); + scratch_end(scratch); + return result; +} diff --git a/src/dbg_engine/dbg_engine_user.h b/src/dbg_engine/dbg_engine_user.h new file mode 100644 index 00000000..9f92e12a --- /dev/null +++ b/src/dbg_engine/dbg_engine_user.h @@ -0,0 +1,486 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DBG_ENGINE_USER_H +#define DBG_ENGINE_USER_H + +//////////////////////////////// +//~ rjf: Line Info Types + +typedef struct D_Line D_Line; +struct D_Line +{ + String8 file_path; + TxtPt pt; + Rng1U64 voff_range; + DI_Key dbgi_key; +}; + +typedef struct D_LineNode D_LineNode; +struct D_LineNode +{ + D_LineNode *next; + D_Line v; +}; + +typedef struct D_LineList D_LineList; +struct D_LineList +{ + D_LineNode *first; + D_LineNode *last; + U64 count; +}; + +typedef struct D_LineListArray D_LineListArray; +struct D_LineListArray +{ + D_LineList *v; + U64 count; + DI_KeyList dbgi_keys; +}; + +//////////////////////////////// +//~ rjf: Debug Engine Control Communication Types + +typedef enum D_RunKind +{ + D_RunKind_Run, + D_RunKind_SingleStep, + D_RunKind_Step, + D_RunKind_COUNT +} +D_RunKind; + +//////////////////////////////// +//~ rjf: Command Types + +typedef struct D_CmdParams D_CmdParams; +struct D_CmdParams +{ + CTRL_Handle machine; + CTRL_Handle process; + CTRL_Handle thread; + CTRL_Handle entity; + String8 string; + String8 file_path; + TxtPt cursor; + U64 vaddr; + B32 prefer_disasm; + U32 pid; + U32 rgba; + D_TargetArray targets; + U64 retry_idx; +}; + +typedef struct D_Cmd D_Cmd; +struct D_Cmd +{ + D_CmdKind kind; + D_CmdParams params; +}; + +typedef struct D_CmdNode D_CmdNode; +struct D_CmdNode +{ + D_CmdNode *next; + D_CmdNode *prev; + D_Cmd cmd; +}; + +typedef struct D_CmdList D_CmdList; +struct D_CmdList +{ + D_CmdNode *first; + D_CmdNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Main State Caches + +//- rjf: per-run tls-base-vaddr cache + +typedef struct D_RunTLSBaseCacheNode D_RunTLSBaseCacheNode; +struct D_RunTLSBaseCacheNode +{ + D_RunTLSBaseCacheNode *hash_next; + CTRL_Handle process; + U64 root_vaddr; + U64 rip_vaddr; + U64 tls_base_vaddr; +}; + +typedef struct D_RunTLSBaseCacheSlot D_RunTLSBaseCacheSlot; +struct D_RunTLSBaseCacheSlot +{ + D_RunTLSBaseCacheNode *first; + D_RunTLSBaseCacheNode *last; +}; + +typedef struct D_RunTLSBaseCache D_RunTLSBaseCache; +struct D_RunTLSBaseCache +{ + Arena *arena; + U64 slots_count; + D_RunTLSBaseCacheSlot *slots; +}; + +//- rjf: per-run locals cache + +typedef struct D_RunLocalsCacheNode D_RunLocalsCacheNode; +struct D_RunLocalsCacheNode +{ + D_RunLocalsCacheNode *hash_next; + DI_Key dbgi_key; + U64 voff; + E_String2NumMap *locals_map; +}; + +typedef struct D_RunLocalsCacheSlot D_RunLocalsCacheSlot; +struct D_RunLocalsCacheSlot +{ + D_RunLocalsCacheNode *first; + D_RunLocalsCacheNode *last; +}; + +typedef struct D_RunLocalsCache D_RunLocalsCache; +struct D_RunLocalsCache +{ + Arena *arena; + U64 table_size; + D_RunLocalsCacheSlot *table; +}; + +//////////////////////////////// +//~ rjf: User -> Ctrl Message Types + +typedef enum CTRL_MsgKind +{ + CTRL_MsgKind_Null, + CTRL_MsgKind_Launch, + CTRL_MsgKind_Attach, + CTRL_MsgKind_Kill, + CTRL_MsgKind_KillAll, + CTRL_MsgKind_Detach, + CTRL_MsgKind_Run, + CTRL_MsgKind_SingleStep, + CTRL_MsgKind_SetUserEntryPoints, + CTRL_MsgKind_SetModuleDebugInfoPath, + CTRL_MsgKind_FreezeThread, + CTRL_MsgKind_ThawThread, + CTRL_MsgKind_COUNT, +} +CTRL_MsgKind; + +typedef U32 CTRL_RunFlags; +enum +{ + CTRL_RunFlag_StopOnEntryPoint = (1<<0), +}; + +typedef struct CTRL_Msg CTRL_Msg; +struct CTRL_Msg +{ + CTRL_MsgKind kind; + CTRL_RunFlags run_flags; + CTRL_MsgID msg_id; + CTRL_Handle entity; + CTRL_Handle parent; + U32 entity_id; + U32 exit_code; + B32 env_inherit; + B32 debug_subprocesses; + U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]; + String8 path; + String8List entry_points; + String8List cmd_line_string_list; + String8List env_string_list; + String8 stdout_path; + String8 stderr_path; + String8 stdin_path; + CTRL_TrapList traps; + CTRL_UserBreakpointList user_bps; +}; + +typedef struct CTRL_MsgNode CTRL_MsgNode; +struct CTRL_MsgNode +{ + CTRL_MsgNode *next; + CTRL_Msg v; +}; + +typedef struct CTRL_MsgList CTRL_MsgList; +struct CTRL_MsgList +{ + CTRL_MsgNode *first; + CTRL_MsgNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Ctrl -> User Event Types + +typedef enum CTRL_EventKind +{ + CTRL_EventKind_Null, + CTRL_EventKind_Error, + + //- rjf: starts/stops + CTRL_EventKind_Started, + CTRL_EventKind_Stopped, + + //- rjf: entity creation/deletion + CTRL_EventKind_NewProc, + CTRL_EventKind_NewThread, + CTRL_EventKind_NewModule, + CTRL_EventKind_EndProc, + CTRL_EventKind_EndThread, + CTRL_EventKind_EndModule, + + //- rjf: thread freeze state changes + CTRL_EventKind_ThreadFrozen, + CTRL_EventKind_ThreadThawed, + + //- rjf: debug info changes + CTRL_EventKind_ModuleDebugInfoPathChange, + + //- rjf: debug strings / decorations / markup + CTRL_EventKind_DebugString, + CTRL_EventKind_ThreadName, + CTRL_EventKind_ThreadColor, + CTRL_EventKind_SetBreakpoint, + CTRL_EventKind_UnsetBreakpoint, + CTRL_EventKind_SetVAddrRangeNote, + + //- rjf: memory + CTRL_EventKind_MemReserve, + CTRL_EventKind_MemCommit, + CTRL_EventKind_MemDecommit, + CTRL_EventKind_MemRelease, + + CTRL_EventKind_COUNT +} +CTRL_EventKind; + +typedef enum CTRL_EventCause +{ + CTRL_EventCause_Null, + CTRL_EventCause_Error, + CTRL_EventCause_Finished, + CTRL_EventCause_EntryPoint, + CTRL_EventCause_UserBreakpoint, + CTRL_EventCause_InterruptedByTrap, + CTRL_EventCause_InterruptedByException, + CTRL_EventCause_InterruptedByHalt, + CTRL_EventCause_COUNT +} +CTRL_EventCause; + +typedef enum CTRL_ExceptionKind +{ + CTRL_ExceptionKind_Null, + CTRL_ExceptionKind_MemoryRead, + CTRL_ExceptionKind_MemoryWrite, + CTRL_ExceptionKind_MemoryExecute, + CTRL_ExceptionKind_CppThrow, + CTRL_ExceptionKind_COUNT +} +CTRL_ExceptionKind; + +typedef struct CTRL_Event CTRL_Event; +struct CTRL_Event +{ + CTRL_EventKind kind; + CTRL_EventCause cause; + CTRL_ExceptionKind exception_kind; + CTRL_MsgID msg_id; + CTRL_Handle entity; + CTRL_Handle parent; + Arch arch; + U64 u64_code; + U32 entity_id; + Rng1U64 vaddr_rng; + U64 rip_vaddr; + U64 stack_base; + U64 tls_root; + U64 tls_index; + U64 tls_offset; + U64 timestamp; + U32 exception_code; + U32 rgba; + CTRL_UserBreakpointFlags bp_flags; + String8 string; + OperatingSystem target_os; + CTRL_TlsModel tls_model; +}; + +typedef struct CTRL_EventNode CTRL_EventNode; +struct CTRL_EventNode +{ + CTRL_EventNode *next; + CTRL_Event v; +}; + +typedef struct CTRL_EventList CTRL_EventList; +struct CTRL_EventList +{ + CTRL_EventNode *first; + CTRL_EventNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Main State Types + +typedef struct D_State D_State; +struct D_State +{ + // rjf: top-level state + Arena *arena; + U64 frame_index; + + // rjf: commands + Arena *cmds_arena; + D_CmdList cmds; + + // rjf: output log key + C_Key output_log_key; + + // rjf: per-run caches + U64 tls_base_cache_reggen_idx; + U64 tls_base_cache_memgen_idx; + D_RunTLSBaseCache tls_base_caches[2]; + U64 tls_base_cache_gen; + U64 locals_cache_reggen_idx; + D_RunLocalsCache locals_caches[2]; + U64 locals_cache_gen; + U64 member_cache_reggen_idx; + D_RunLocalsCache member_caches[2]; + U64 member_cache_gen; + + // rjf: user -> ctrl driving state + Arena *ctrl_last_run_arena; + D_RunKind ctrl_last_run_kind; + U64 ctrl_last_run_frame_idx; + CTRL_Handle ctrl_last_run_thread_handle; + CTRL_RunFlags ctrl_last_run_flags; + CTRL_TrapList ctrl_last_run_traps; + D_BreakpointArray ctrl_last_run_extra_bps; + U128 ctrl_last_run_param_state_hash; + B32 ctrl_is_running; + B32 ctrl_thread_run_state; + B32 ctrl_soft_halt_issued; + Arena *ctrl_msg_arena; + CTRL_MsgList ctrl_msgs; + + // rjf: ctrl -> user reading state + CTRL_EntityCtxRWStore *ctrl_entity_store; + Arena *ctrl_stop_arena; + CTRL_Event ctrl_last_stop_event; +}; + +//////////////////////////////// +//~ rjf: Globals + +global D_State *d_state = 0; + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 d_hash_from_seed_string(U64 seed, String8 string); +internal U64 d_hash_from_string(String8 string); +internal U64 d_hash_from_seed_string__case_insensitive(U64 seed, String8 string); +internal U64 d_hash_from_string__case_insensitive(String8 string); + +//////////////////////////////// +//~ rjf: Breakpoints + +internal D_BreakpointArray d_breakpoint_array_copy(Arena *arena, D_BreakpointArray *src); + +//////////////////////////////// +//~ rjf: Path Map Application + +internal String8List d_possible_path_overrides_from_maps_path(Arena *arena, D_PathMapArray *path_maps, String8 file_path); + +//////////////////////////////// +//~ rjf: Debug Info Extraction Type Pure Functions + +internal D_LineList d_line_list_copy(Arena *arena, D_LineList *list); + +//////////////////////////////// +//~ rjf: Command Type Functions + +//- rjf: command parameters +internal D_CmdParams d_cmd_params_copy(Arena *arena, D_CmdParams *src); + +//- rjf: command lists +internal void d_cmd_list_push_new(Arena *arena, D_CmdList *cmds, D_CmdKind kind, D_CmdParams *params); + +//////////////////////////////// +//~ rjf: Stepping "Trap Net" Builders + +internal D_TrapNet d_trap_net_from_thread__step_over_inst(Arena *arena, CTRL_Entity *thread); +internal D_TrapNet d_trap_net_from_thread__step_over_line(Arena *arena, CTRL_Entity *thread); +internal D_TrapNet d_trap_net_from_thread__step_into_line(Arena *arena, CTRL_Entity *thread); + +//////////////////////////////// +//~ rjf: Debug Info Lookups + +//- rjf: voff -> line info +internal D_LineList d_lines_from_dbgi_key_voff(Arena *arena, DI_Key dbgi_key, U64 voff); + +//- rjf: file:line -> line info +// TODO(rjf): this depends on file path maps, needs to move +// TODO(rjf): need to clean this up & dedup +internal D_LineListArray d_lines_array_from_dbgi_key_file_path_line_range(Arena *arena, DI_Key dbgi_key, String8 file_path, Rng1S64 line_num_range); +internal D_LineListArray d_lines_array_from_file_path_line_range(Arena *arena, String8 file_path, Rng1S64 line_num_range); +internal D_LineList d_lines_from_dbgi_key_file_path_line_num(Arena *arena, DI_Key dbgi_key, String8 file_path, S64 line_num); +internal D_LineList d_lines_from_file_path_line_num(Arena *arena, String8 file_path, S64 line_num); + +//////////////////////////////// +//~ rjf: Process/Thread/Module Info Lookups + +internal U64 d_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64 rip_vaddr); + +//////////////////////////////// +//~ rjf: Target Controls + +//- rjf: stopped info from the control thread +internal CTRL_Event d_ctrl_last_stop_event(void); + +//////////////////////////////// +//~ rjf: Main State Accessors/Mutators + +//- rjf: frame data +internal U64 d_frame_index(void); + +//- rjf: control state +internal D_RunKind d_ctrl_last_run_kind(void); +internal U64 d_ctrl_last_run_frame_idx(void); +internal B32 d_ctrl_targets_running(void); + +//- rjf: active entity based queries +internal DI_KeyList d_push_active_dbgi_key_list(Arena *arena); + +//- rjf: per-run caches +internal U64 d_query_cached_rip_from_thread(CTRL_Entity *thread); +internal U64 d_query_cached_rip_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count); +internal U64 d_query_cached_cfa_from_thread_unwind(CTRL_Entity *thread, U64 unwind_count); +internal U64 d_query_cached_tls_base_vaddr_from_process_root_rip(CTRL_Entity *process, U64 root_vaddr, U64 rip_vaddr); +internal E_String2NumMap *d_query_cached_locals_map_from_dbgi_key_voff(DI_Key dbgi_key, U64 voff); +internal E_String2NumMap *d_query_cached_member_map_from_dbgi_key_voff(DI_Key dbgi_key, U64 voff); + +//- rjf: top-level command dispatch +internal void d_push_cmd(D_CmdKind kind, D_CmdParams *params); +#define d_cmd(kind, ...) d_push_cmd((kind), &(D_CmdParams){.thread = {0}, __VA_ARGS__}) + +//- rjf: command iteration +internal B32 d_next_cmd(D_Cmd **cmd); + +//////////////////////////////// +//~ rjf: Main Layer Top-Level Calls + +internal void d_init(void); +internal D_EventList d_tick(Arena *arena, D_TargetArray *targets, D_BreakpointArray *breakpoints, D_PathMapArray *path_maps, U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]); + +#endif // DBG_ENGINE_USER_H diff --git a/src/dbg_engine/generated/dbg_engine.meta.c b/src/dbg_engine/generated/dbg_engine.meta.c index 437ae10e..09d20976 100644 --- a/src/dbg_engine/generated/dbg_engine.meta.c +++ b/src/dbg_engine/generated/dbg_engine.meta.c @@ -3,3 +3,206 @@ //- GENERATED CODE +C_LINKAGE_BEGIN +String8 ctrl_entity_kind_code_name_table[12] = +{ +{0}, +str8_lit_comp("root"), +str8_lit_comp("machine"), +str8_lit_comp("process"), +str8_lit_comp("thread"), +str8_lit_comp("module"), +str8_lit_comp("entry_point"), +str8_lit_comp("debug_info_path"), +str8_lit_comp("pending_thread_name"), +str8_lit_comp("pending_thread_color"), +str8_lit_comp("breakpoint"), +str8_lit_comp("address_range_annotation"), +}; + +String8 ctrl_entity_kind_display_string_table[12] = +{ +{0}, +str8_lit_comp("Root"), +str8_lit_comp("Machine"), +str8_lit_comp("Process"), +str8_lit_comp("Thread"), +str8_lit_comp("Module"), +str8_lit_comp("EntryPoint"), +str8_lit_comp("DebugInfoPath"), +str8_lit_comp("PendingThreadName"), +str8_lit_comp("PendingThreadColor"), +str8_lit_comp("Breakpoint"), +str8_lit_comp("AddressRangeAnnotation"), +}; + +U32 ctrl_exception_code_kind_code_table[38] = +{ +0, +0x40010005, +0x40010008, +0x40080201, +0x40080202, +0x0000071a, +0x80000002, +0xc0000005, +0xc0000006, +0xc0000008, +0xc0000017, +0xc000001d, +0xc0000025, +0xc0000026, +0xc000008c, +0xc000008d, +0xc000008e, +0xc000008f, +0xc0000090, +0xc0000091, +0xc0000092, +0xc0000093, +0xc0000094, +0xc0000095, +0xc0000096, +0xc00000fd, +0xc0000135, +0xc0000138, +0xc0000139, +0xc0000142, +0xc00002b4, +0xc00002b5, +0xc0000420, +0xc06d007e, +0xc06d007f, +0xe073616e, +0xe0736171, +0x0000087a, +}; + +String8 ctrl_exception_code_kind_display_string_table[38] = +{ +{0}, +str8_lit_comp("(Win32) Control-C"), +str8_lit_comp("(Win32) Control-Break"), +str8_lit_comp("(Win32) WinRT Originate Error"), +str8_lit_comp("(Win32) WinRT Transform Error"), +str8_lit_comp("(Win32) RPC Call Cancelled"), +str8_lit_comp("(Win32) Data Type Misalignment"), +str8_lit_comp("(Win32) Access Violation"), +str8_lit_comp("(Win32) In Page Error"), +str8_lit_comp("(Win32) Invalid Handle Specified"), +str8_lit_comp("(Win32) Not Enough Quota"), +str8_lit_comp("(Win32) Illegal Instruction"), +str8_lit_comp("(Win32) Cannot Continue From Exception"), +str8_lit_comp("(Win32) Invalid Exception Disposition Returned By Handler"), +str8_lit_comp("(Win32) Array Bounds Exceeded"), +str8_lit_comp("(Win32) Floating-Point Denormal Operand"), +str8_lit_comp("(Win32) Floating-Point Division By Zero"), +str8_lit_comp("(Win32) Floating-Point Inexact Result"), +str8_lit_comp("(Win32) Floating-Point Invalid Operation"), +str8_lit_comp("(Win32) Floating-Point Overflow"), +str8_lit_comp("(Win32) Floating-Point Stack Check"), +str8_lit_comp("(Win32) Floating-Point Underflow"), +str8_lit_comp("(Win32) Integer Division By Zero"), +str8_lit_comp("(Win32) Integer Overflow"), +str8_lit_comp("(Win32) Privileged Instruction"), +str8_lit_comp("(Win32) Stack Overflow"), +str8_lit_comp("(Win32) Unable To Locate DLL"), +str8_lit_comp("(Win32) Ordinal Not Found"), +str8_lit_comp("(Win32) Entry Point Not Found"), +str8_lit_comp("(Win32) DLL Initialization Failed"), +str8_lit_comp("(Win32) Floating Point SSE Multiple Faults"), +str8_lit_comp("(Win32) Floating Point SSE Multiple Traps"), +str8_lit_comp("(Win32) Assertion Failed"), +str8_lit_comp("(Win32) Module Not Found"), +str8_lit_comp("(Win32) Procedure Not Found"), +str8_lit_comp("(Win32) Sanitizer Error Detected"), +str8_lit_comp("(Win32) Sanitizer Raw Access Violation"), +str8_lit_comp("(Win32) DirectX Debug Layer"), +}; + +String8 ctrl_exception_code_kind_lowercase_code_string_table[38] = +{ +{0}, +str8_lit_comp("win32_ctrl_c"), +str8_lit_comp("win32_ctrl_break"), +str8_lit_comp("win32_win_rt_originate_error"), +str8_lit_comp("win32_win_rt_transform_error"), +str8_lit_comp("win32_rpc_call_cancelled"), +str8_lit_comp("win32_datatype_misalignment"), +str8_lit_comp("win32_access_violation"), +str8_lit_comp("win32_in_page_error"), +str8_lit_comp("win32_invalid_handle"), +str8_lit_comp("win32_not_enough_quota"), +str8_lit_comp("win32_illegal_instruction"), +str8_lit_comp("win32_cannot_continue_exception"), +str8_lit_comp("win32_invalid_exception_disposition"), +str8_lit_comp("win32_array_bounds_exceeded"), +str8_lit_comp("win32_floating_point_denormal_operand"), +str8_lit_comp("win32_floating_point_division_by_zero"), +str8_lit_comp("win32_floating_point_inexact_result"), +str8_lit_comp("win32_floating_point_invalid_operation"), +str8_lit_comp("win32_floating_point_overflow"), +str8_lit_comp("win32_floating_point_stack_check"), +str8_lit_comp("win32_floating_point_underflow"), +str8_lit_comp("win32_integer_division_by_zero"), +str8_lit_comp("win32_integer_overflow"), +str8_lit_comp("win32_privileged_instruction"), +str8_lit_comp("win32_stack_overflow"), +str8_lit_comp("win32_unable_to_locate_dll"), +str8_lit_comp("win32_ordinal_not_found"), +str8_lit_comp("win32_entry_point_not_found"), +str8_lit_comp("win32_dll_initialization_failed"), +str8_lit_comp("win32_floating_point_sse_multiple_faults"), +str8_lit_comp("win32_floating_point_sse_multiple_traps"), +str8_lit_comp("win32_assertion_failed"), +str8_lit_comp("win32_module_not_found"), +str8_lit_comp("win32_procedure_not_found"), +str8_lit_comp("win32_sanitizer_error_detected"), +str8_lit_comp("win32_sanitizer_raw_access_violation"), +str8_lit_comp("win32_directx_debug_layer"), +}; + +B8 ctrl_exception_code_kind_default_enable_table[38] = +{ +0, +1, +1, +0, +0, +0, +0, +1, +0, +1, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +1, +0, +0, +1, +0, +1, +}; + +C_LINKAGE_END + diff --git a/src/dbg_engine/generated/dbg_engine.meta.h b/src/dbg_engine/generated/dbg_engine.meta.h index 4c8d2908..f7397b0e 100644 --- a/src/dbg_engine/generated/dbg_engine.meta.h +++ b/src/dbg_engine/generated/dbg_engine.meta.h @@ -44,6 +44,66 @@ D_CmdKind_Attach, D_CmdKind_COUNT, } D_CmdKind; +typedef enum CTRL_EntityKind +{ +CTRL_EntityKind_Null, +CTRL_EntityKind_Root, +CTRL_EntityKind_Machine, +CTRL_EntityKind_Process, +CTRL_EntityKind_Thread, +CTRL_EntityKind_Module, +CTRL_EntityKind_EntryPoint, +CTRL_EntityKind_DebugInfoPath, +CTRL_EntityKind_PendingThreadName, +CTRL_EntityKind_PendingThreadColor, +CTRL_EntityKind_Breakpoint, +CTRL_EntityKind_AddressRangeAnnotation, +CTRL_EntityKind_COUNT, +} CTRL_EntityKind; + +typedef enum CTRL_ExceptionCodeKind +{ +CTRL_ExceptionCodeKind_Null, +CTRL_ExceptionCodeKind_Win32CtrlC, +CTRL_ExceptionCodeKind_Win32CtrlBreak, +CTRL_ExceptionCodeKind_Win32WinRTOriginateError, +CTRL_ExceptionCodeKind_Win32WinRTTransformError, +CTRL_ExceptionCodeKind_Win32RPCCallCancelled, +CTRL_ExceptionCodeKind_Win32DatatypeMisalignment, +CTRL_ExceptionCodeKind_Win32AccessViolation, +CTRL_ExceptionCodeKind_Win32InPageError, +CTRL_ExceptionCodeKind_Win32InvalidHandle, +CTRL_ExceptionCodeKind_Win32NotEnoughQuota, +CTRL_ExceptionCodeKind_Win32IllegalInstruction, +CTRL_ExceptionCodeKind_Win32CannotContinueException, +CTRL_ExceptionCodeKind_Win32InvalidExceptionDisposition, +CTRL_ExceptionCodeKind_Win32ArrayBoundsExceeded, +CTRL_ExceptionCodeKind_Win32FloatingPointDenormalOperand, +CTRL_ExceptionCodeKind_Win32FloatingPointDivisionByZero, +CTRL_ExceptionCodeKind_Win32FloatingPointInexactResult, +CTRL_ExceptionCodeKind_Win32FloatingPointInvalidOperation, +CTRL_ExceptionCodeKind_Win32FloatingPointOverflow, +CTRL_ExceptionCodeKind_Win32FloatingPointStackCheck, +CTRL_ExceptionCodeKind_Win32FloatingPointUnderflow, +CTRL_ExceptionCodeKind_Win32IntegerDivisionByZero, +CTRL_ExceptionCodeKind_Win32IntegerOverflow, +CTRL_ExceptionCodeKind_Win32PrivilegedInstruction, +CTRL_ExceptionCodeKind_Win32StackOverflow, +CTRL_ExceptionCodeKind_Win32UnableToLocateDLL, +CTRL_ExceptionCodeKind_Win32OrdinalNotFound, +CTRL_ExceptionCodeKind_Win32EntryPointNotFound, +CTRL_ExceptionCodeKind_Win32DLLInitializationFailed, +CTRL_ExceptionCodeKind_Win32FloatingPointSSEMultipleFaults, +CTRL_ExceptionCodeKind_Win32FloatingPointSSEMultipleTraps, +CTRL_ExceptionCodeKind_Win32AssertionFailed, +CTRL_ExceptionCodeKind_Win32ModuleNotFound, +CTRL_ExceptionCodeKind_Win32ProcedureNotFound, +CTRL_ExceptionCodeKind_Win32SanitizerErrorDetected, +CTRL_ExceptionCodeKind_Win32SanitizerRawAccessViolation, +CTRL_ExceptionCodeKind_Win32DirectXDebugLayer, +CTRL_ExceptionCodeKind_COUNT, +} CTRL_ExceptionCodeKind; + global B32 DEV_always_refresh = 0; global B32 DEV_simulate_lag = 0; global B32 DEV_draw_ui_text_pos = 0; @@ -65,4 +125,14 @@ struct {B32 *value_ptr; String8 name;} DEV_toggle_table[] = {&DEV_cmd_context_tooltips, str8_lit_comp("cmd_context_tooltips")}, {&DEV_updating_indicator, str8_lit_comp("updating_indicator")}, }; +C_LINKAGE_BEGIN +extern String8 ctrl_entity_kind_code_name_table[12]; +extern String8 ctrl_entity_kind_display_string_table[12]; +extern U32 ctrl_exception_code_kind_code_table[38]; +extern String8 ctrl_exception_code_kind_display_string_table[38]; +extern String8 ctrl_exception_code_kind_lowercase_code_string_table[38]; +extern B8 ctrl_exception_code_kind_default_enable_table[38]; + +C_LINKAGE_END + #endif // DBG_ENGINE_META_H diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 6982e501..3b054c55 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -299,14 +299,13 @@ #include "stap/stap_parse.h" #include "demon/demon_inc.h" #include "eval/eval_inc.h" +#include "dbg_engine/dbg_engine_inc.h" #include "eval_visualization/eval_visualization_inc.h" -#include "ctrl/ctrl_inc.h" #include "font_provider/font_provider_inc.h" #include "render/render_inc.h" #include "font_cache/font_cache.h" #include "draw/draw.h" #include "ui/ui_inc.h" -#include "dbg_engine/dbg_engine_inc.h" #include "raddbg/raddbg_inc.h" //- rjf: [c] @@ -354,14 +353,13 @@ #include "stap/stap_parse.c" #include "demon/demon_inc.c" #include "eval/eval_inc.c" +#include "dbg_engine/dbg_engine_inc.c" #include "eval_visualization/eval_visualization_inc.c" -#include "ctrl/ctrl_inc.c" #include "font_provider/font_provider_inc.c" #include "render/render_inc.c" #include "font_cache/font_cache.c" #include "draw/draw.c" #include "ui/ui_inc.c" -#include "dbg_engine/dbg_engine_inc.c" #include "raddbg/raddbg_inc.c" ////////////////////////////////