mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-07-17 14:51:26 -07:00
initial upload
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
////////////////////////////////
|
||||
//- allen: Acceleration Layer Functions
|
||||
|
||||
//- accel helpers
|
||||
internal DEMON_AccelModule*
|
||||
demon_accel_module_alloc(void){
|
||||
DEMON_AccelModule *result = demon_free_module_accel;
|
||||
if (result != 0){
|
||||
SLLStackPop(demon_free_module_accel);
|
||||
}
|
||||
else{
|
||||
result = push_array_no_zero(demon_ent_arena, DEMON_AccelModule, 1);
|
||||
}
|
||||
MemoryZeroStruct(result);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_accel_module_free(DEMON_AccelModule *module){
|
||||
SLLStackPush(demon_free_module_accel, module);
|
||||
}
|
||||
|
||||
internal DEMON_AccelThread*
|
||||
demon_accel_thread_alloc(void){
|
||||
DEMON_AccelThread *result = demon_free_thread_accel;
|
||||
if (result != 0){
|
||||
SLLStackPop(demon_free_thread_accel);
|
||||
}
|
||||
else{
|
||||
result = push_array_no_zero(demon_ent_arena, DEMON_AccelThread, 1);
|
||||
}
|
||||
MemoryZeroStruct(result);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_accel_thread_free(DEMON_AccelThread *thread){
|
||||
SLLStackPush(demon_free_thread_accel, thread);
|
||||
}
|
||||
|
||||
internal DEMON_AccelThread*
|
||||
demon_accel_from_thread(DEMON_Entity *thread){
|
||||
DEMON_AccelThread *accel = (DEMON_AccelThread*)thread->accel;
|
||||
if (accel == 0){
|
||||
accel = demon_accel_thread_alloc();
|
||||
thread->accel = accel;
|
||||
}
|
||||
return(accel);
|
||||
}
|
||||
|
||||
//- operations on demon objects
|
||||
internal String8
|
||||
demon_accel_full_path_from_module(Arena *arena, DEMON_Entity *module){
|
||||
DEMON_AccelModule *accel = (DEMON_AccelModule*)module->accel;
|
||||
|
||||
String8 result = {0};
|
||||
|
||||
// first time
|
||||
if (accel == 0){
|
||||
result = demon_os_full_path_from_module(arena, module);
|
||||
|
||||
// build chain
|
||||
DEMON_AccelModule *last_accel = 0;
|
||||
|
||||
U8 *ptr = result.str;
|
||||
U8 *opl = result.str + result.size;
|
||||
for (;ptr < opl;){
|
||||
U64 size = (U64)(ptr - opl);
|
||||
U64 clamped_size = ClampTop(result.size, sizeof(Member(DEMON_AccelModule, buf)));
|
||||
|
||||
DEMON_AccelModule *node = demon_accel_module_alloc();
|
||||
SLLQueuePush(accel, last_accel, node);
|
||||
node->total_size = result.size;
|
||||
MemoryCopy(node->buf, ptr, clamped_size);
|
||||
|
||||
ptr += clamped_size;
|
||||
}
|
||||
|
||||
// store in module
|
||||
module->accel = accel;
|
||||
}
|
||||
|
||||
// read from accel
|
||||
else{
|
||||
U64 size = accel->total_size;
|
||||
U8 *str = push_array_no_zero(arena, U8, size + 1);
|
||||
|
||||
// copy chain contents to buffer
|
||||
U8 *ptr = str;
|
||||
for (DEMON_AccelModule *node = accel;
|
||||
node != 0;
|
||||
node = node->next){
|
||||
U64 total_size = node->total_size;
|
||||
U64 clamped_size = ClampTop(total_size, sizeof(node->buf));
|
||||
MemoryCopy(ptr, node->buf, clamped_size);
|
||||
ptr += clamped_size;
|
||||
}
|
||||
*ptr = 0;
|
||||
|
||||
// fill result
|
||||
result.str = str;
|
||||
result.size = size;
|
||||
}
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_accel_stack_base_vaddr_from_thread(DEMON_Entity *thread){
|
||||
// get accel data
|
||||
DEMON_AccelThread *accel = demon_accel_from_thread(thread);
|
||||
|
||||
// fill stack base
|
||||
if (!accel->has_stack_base){
|
||||
accel->has_stack_base = 1;
|
||||
accel->stack_base = demon_os_stack_base_vaddr_from_thread(thread);
|
||||
}
|
||||
|
||||
return(accel->stack_base);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_accel_tls_root_vaddr_from_thread(DEMON_Entity *thread){
|
||||
// get accel data
|
||||
DEMON_AccelThread *accel = demon_accel_from_thread(thread);
|
||||
|
||||
// fill tls root
|
||||
if (!accel->has_tls_root){
|
||||
accel->has_tls_root = 1;
|
||||
accel->tls_root = demon_os_tls_root_vaddr_from_thread(thread);
|
||||
}
|
||||
|
||||
return(accel->tls_root);
|
||||
}
|
||||
|
||||
internal void*
|
||||
demon_accel_read_regs(DEMON_Entity *thread){
|
||||
// get accel data
|
||||
DEMON_AccelThread *accel = demon_accel_from_thread(thread);
|
||||
|
||||
// update reg cache
|
||||
if (accel->reg_cache_time != demon_time){
|
||||
accel->reg_cache_time = demon_time;
|
||||
B32 success = 0;
|
||||
switch (thread->arch){
|
||||
case Architecture_x86:{success = demon_os_read_regs_x86(thread, &accel->regs.x86);}break;
|
||||
case Architecture_x64:{success = demon_os_read_regs_x64(thread, &accel->regs.x64);}break;
|
||||
}
|
||||
if (!success){
|
||||
MemoryZeroStruct(&accel->regs);
|
||||
}
|
||||
}
|
||||
|
||||
return(&accel->regs);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_accel_write_regs(DEMON_Entity *thread, void *data){
|
||||
// get accel data
|
||||
DEMON_AccelThread *accel = demon_accel_from_thread(thread);
|
||||
|
||||
// low level write
|
||||
B32 success = 0;
|
||||
U64 data_size = 0;
|
||||
switch (thread->arch){
|
||||
case Architecture_x86:
|
||||
{
|
||||
data_size = sizeof(REGS_RegBlockX86);
|
||||
success = demon_os_write_regs_x86(thread, (REGS_RegBlockX86*)data);
|
||||
}break;
|
||||
case Architecture_x64:
|
||||
{
|
||||
data_size = sizeof(REGS_RegBlockX64);
|
||||
success = demon_os_write_regs_x64(thread, (REGS_RegBlockX64*)data);
|
||||
}break;
|
||||
}
|
||||
|
||||
// update cache
|
||||
if (success){
|
||||
accel->reg_cache_time = demon_time;
|
||||
MemoryCopy(&accel->regs, data, data_size);
|
||||
}
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_accel_low_level_write_regs(DEMON_Entity *thread){
|
||||
// NOTE(allen): This is a tricky one. It's just a way to enable some internal
|
||||
// optimizations. Instead of forcing the user to pass in register data
|
||||
// to write out and copy to the cache, the "user" is other demon code that
|
||||
// knows what it's doing. So it grabs the cache memory (through a call to
|
||||
// `demon_accel_read_regs`) modifies it in place and then calls this.
|
||||
// So we just have to write the cache contents directly out to OS.
|
||||
|
||||
// get accel data
|
||||
DEMON_AccelThread *accel = demon_accel_from_thread(thread);
|
||||
switch (thread->arch){
|
||||
case Architecture_x86:
|
||||
{
|
||||
demon_os_write_regs_x86(thread, &accel->regs.x86);
|
||||
}break;
|
||||
case Architecture_x64:
|
||||
{
|
||||
demon_os_write_regs_x64(thread, &accel->regs.x64);
|
||||
}break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//- entity accel free
|
||||
internal void
|
||||
demon_accel_free(DEMON_Entity *entity){
|
||||
switch (entity->kind){
|
||||
case DEMON_EntityKind_Module:
|
||||
{
|
||||
if (entity->accel != 0){
|
||||
for (DEMON_AccelModule *node = (DEMON_AccelModule*)entity->accel, *next = 0;
|
||||
node != 0;
|
||||
node = next){
|
||||
next = node->next;
|
||||
demon_accel_module_free(node);
|
||||
}
|
||||
}
|
||||
}break;
|
||||
|
||||
case DEMON_EntityKind_Thread:
|
||||
{
|
||||
if (entity->accel != 0){
|
||||
demon_accel_thread_free((DEMON_AccelThread*)entity->accel);
|
||||
}
|
||||
}break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#ifndef DEMON_ACCEL_H
|
||||
#define DEMON_ACCEL_H
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Acceleration Data
|
||||
|
||||
typedef struct DEMON_AccelModule DEMON_AccelModule;
|
||||
struct DEMON_AccelModule
|
||||
{
|
||||
DEMON_AccelModule *next;
|
||||
U64 total_size;
|
||||
U8 buf[240];
|
||||
};
|
||||
|
||||
typedef union DEMON_AccelThread DEMON_AccelThread;
|
||||
union DEMON_AccelThread
|
||||
{
|
||||
DEMON_AccelThread *next;
|
||||
struct{
|
||||
B32 has_stack_base;
|
||||
B32 has_tls_root;
|
||||
U64 stack_base;
|
||||
U64 tls_root;
|
||||
|
||||
U64 reg_cache_time;
|
||||
union{
|
||||
REGS_RegBlockX64 x64;
|
||||
REGS_RegBlockX86 x86;
|
||||
} regs;
|
||||
};
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Acceleration Globals
|
||||
|
||||
global DEMON_AccelModule *demon_free_module_accel = 0;
|
||||
global DEMON_AccelThread *demon_free_thread_accel = 0;
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Acceleration Layer Functions
|
||||
|
||||
//- accel helpers
|
||||
internal DEMON_AccelModule *demon_accel_module_alloc(void);
|
||||
internal void demon_accel_module_free(DEMON_AccelModule *module);
|
||||
|
||||
internal DEMON_AccelThread *demon_accel_thread_alloc(void);
|
||||
internal void demon_accel_thread_free(DEMON_AccelThread *thread);
|
||||
internal DEMON_AccelThread *demon_accel_from_thread(DEMON_Entity *thread);
|
||||
|
||||
//- operations on demon objects
|
||||
internal String8 demon_accel_full_path_from_module(Arena *arena, DEMON_Entity *module);
|
||||
internal U64 demon_accel_stack_base_vaddr_from_thread(DEMON_Entity *thread);
|
||||
internal U64 demon_accel_tls_root_vaddr_from_thread(DEMON_Entity *thread);
|
||||
|
||||
internal void* demon_accel_read_regs(DEMON_Entity *thread);
|
||||
internal void demon_accel_write_regs(DEMON_Entity *thread, void *data);
|
||||
|
||||
internal void demon_accel_low_level_write_regs(DEMON_Entity *thread);
|
||||
|
||||
//- entity accel free
|
||||
internal void demon_accel_free(DEMON_Entity *entity);
|
||||
|
||||
#endif //DEMON_ACCEL_H
|
||||
@@ -0,0 +1,270 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
////////////////////////////////
|
||||
// NOTE(allen): State Safety Helper
|
||||
|
||||
internal B32
|
||||
demon_access_begin(void){
|
||||
B32 result = 0;
|
||||
if (demon_primary_thread){
|
||||
Assert(demon_run_state);
|
||||
result = 1;
|
||||
}
|
||||
else{
|
||||
os_mutex_take(demon_state_mutex);
|
||||
if (demon_run_state){
|
||||
os_mutex_drop(demon_state_mutex);
|
||||
}
|
||||
else{
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_access_end(void){
|
||||
if (!demon_primary_thread){
|
||||
os_mutex_drop(demon_state_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// NOTE(allen): Entity System
|
||||
|
||||
internal void
|
||||
demon_common_init(void){
|
||||
// access control mechanism
|
||||
demon_state_mutex = os_mutex_alloc();
|
||||
|
||||
// time
|
||||
demon_time = 1;
|
||||
|
||||
// setup arena
|
||||
demon_ent_arena = arena_alloc();
|
||||
|
||||
// setup map
|
||||
demon_ent_map = push_array(demon_ent_arena, DEMON_Map, 1);
|
||||
demon_ent_map->bucket_count = 4093;
|
||||
demon_ent_map->buckets = push_array(demon_ent_arena, DEMON_MapSlot*, demon_ent_map->bucket_count);
|
||||
|
||||
// setup entity memory
|
||||
U64 reserve_size_unaligned = (DEMON_ENTITY_CAP)*sizeof(DEMON_Entity);
|
||||
U64 reserve_size = AlignPow2(reserve_size_unaligned, DEMON_ENTITY_CMT_SIZE);
|
||||
demon_ent_cmt = demon_ent_pos = demon_ent_base = (DEMON_Entity*)os_reserve(reserve_size);
|
||||
demon_ent_opl = demon_ent_base + (reserve_size/sizeof(DEMON_Entity));
|
||||
|
||||
Assert(demon_ent_base != 0);
|
||||
|
||||
// setup root
|
||||
demon_ent_root = demon_ent_alloc();
|
||||
demon_ent_root->kind = DEMON_EntityKind_Root;
|
||||
}
|
||||
|
||||
internal DEMON_Entity*
|
||||
demon_ent_alloc(void){
|
||||
DEMON_Entity *result = demon_ent_free;
|
||||
if (result != 0){
|
||||
SLLStackPop(demon_ent_free);
|
||||
}
|
||||
else{
|
||||
if (demon_ent_pos < demon_ent_opl){
|
||||
if (ensure_commit(&demon_ent_cmt, demon_ent_pos + 1, DEMON_ENTITY_CMT_SIZE)){
|
||||
result = demon_ent_pos;
|
||||
demon_ent_pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result != 0){
|
||||
U32 gen = result->gen;
|
||||
MemoryZeroStruct(result);
|
||||
result->gen = gen;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
//- handle <-> entity pointer
|
||||
|
||||
internal DEMON_Entity*
|
||||
demon_ent_ptr_from_handle(DEMON_Handle handle){
|
||||
Assert(demon_ent_base != 0);
|
||||
DEMON_Entity *result = 0;
|
||||
U32 index = (U32)(handle & 0xFFFFFFFF);
|
||||
U64 count = (U64)(demon_ent_pos - demon_ent_base);
|
||||
if (0 < index && index < count){
|
||||
DEMON_Entity *entity = demon_ent_base + index;
|
||||
U32 gen = (U32)(handle >> 32);
|
||||
if (gen == entity->gen){
|
||||
result = entity;
|
||||
}
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal DEMON_Handle
|
||||
demon_ent_handle_from_ptr(DEMON_Entity *entity){
|
||||
Assert(demon_ent_base != 0);
|
||||
DEMON_Handle result = {0};
|
||||
if (demon_ent_base < entity && entity < demon_ent_pos){
|
||||
U32 index = (U32)(entity - demon_ent_base);
|
||||
U64 gen = entity->gen;
|
||||
result = (gen << 32) | index;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
//- high level entity alloc,init,release
|
||||
|
||||
internal DEMON_Entity*
|
||||
demon_ent_new(DEMON_Entity *parent, DEMON_EntityKind kind, U64 id){
|
||||
Assert(demon_ent_base != 0);
|
||||
DEMON_Entity *result = demon_ent_alloc();
|
||||
if (result != 0){
|
||||
result->kind = kind;
|
||||
result->id = id;
|
||||
result->arch = parent->arch;
|
||||
result->parent = parent;
|
||||
DLLPushBack(parent->first, parent->last, result);
|
||||
demon_ent_map_save(kind, id, result);
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_ent_release_single(DEMON_Entity *entity){
|
||||
switch (entity->kind){
|
||||
case DEMON_EntityKind_Process: demon_proc_count -= 1; break;
|
||||
case DEMON_EntityKind_Thread: demon_thread_count -= 1; break;
|
||||
case DEMON_EntityKind_Module: demon_module_count -= 1; break;
|
||||
}
|
||||
demon_accel_free(entity);
|
||||
demon_os_entity_cleanup(entity);
|
||||
DEMON_MapRef ref = demon_ent_map_find(entity->kind, entity->id);
|
||||
demon_ent_map_erase(ref);
|
||||
entity->gen += 1;
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_ent_release_children(DEMON_Entity *root){
|
||||
Assert(demon_ent_base != 0);
|
||||
if (root->first != 0){
|
||||
for (DEMON_Entity *node = root->first;
|
||||
node != 0;
|
||||
node = node->next){
|
||||
demon_ent_release_children(node);
|
||||
demon_ent_release_single(node);
|
||||
}
|
||||
root->last->next = demon_ent_free;
|
||||
demon_ent_free = root->first;
|
||||
root->first = 0;
|
||||
root->last = 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_ent_release_root_and_children(DEMON_Entity *root){
|
||||
Assert(demon_ent_base != 0);
|
||||
Assert(root->parent != 0);
|
||||
|
||||
// release children
|
||||
demon_ent_release_children(root);
|
||||
|
||||
// release root
|
||||
DEMON_Entity *parent = root->parent;
|
||||
demon_ent_release_single(root);
|
||||
DLLRemove(parent->first, parent->last, root);
|
||||
SLLStackPush(demon_ent_free, root);
|
||||
}
|
||||
|
||||
//- entity map
|
||||
|
||||
internal U64
|
||||
demon_ent_map_hash(U16 kind, U64 id){
|
||||
U64 result = ((U64)kind << 32) ^ id;
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_ent_map_save(U16 kind, U64 id, DEMON_Entity *entity){
|
||||
Assert(demon_ent_base != 0);
|
||||
DEMON_Map *map = demon_ent_map;
|
||||
|
||||
// allocate a new slot
|
||||
DEMON_MapSlot *slot = map->free_slots;
|
||||
if (slot != 0){
|
||||
SLLStackPop(map->free_slots);
|
||||
}
|
||||
else{
|
||||
slot = push_array_no_zero(demon_ent_arena, DEMON_MapSlot, 1);
|
||||
}
|
||||
|
||||
// fill slot
|
||||
slot->kind = kind;
|
||||
slot->id = id;
|
||||
slot->entity = entity;
|
||||
|
||||
// insert into bucket
|
||||
U64 hash = demon_ent_map_hash(kind, id);
|
||||
U64 bucket_index = hash%map->bucket_count;
|
||||
SLLStackPush(map->buckets[bucket_index], slot);
|
||||
}
|
||||
|
||||
internal DEMON_MapRef
|
||||
demon_ent_map_find(U16 kind, U64 id){
|
||||
Assert(demon_ent_base != 0);
|
||||
DEMON_Map *map = demon_ent_map;
|
||||
|
||||
// scan bucket
|
||||
DEMON_MapRef result = {0};
|
||||
U64 hash = demon_ent_map_hash(kind, id);
|
||||
U64 bucket_index = hash%map->bucket_count;
|
||||
for (DEMON_MapSlot **ptr = &map->buckets[bucket_index], *slot = 0;
|
||||
*ptr != 0;
|
||||
ptr = &slot->next){
|
||||
slot = *ptr;
|
||||
if (slot->kind == kind && slot->id == id){
|
||||
result.slot = slot;
|
||||
result.ptr_to_slot = ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal DEMON_Entity*
|
||||
demon_ent_map_entity_from_id(U16 kind, U64 id){
|
||||
DEMON_Entity *result = 0;
|
||||
DEMON_MapRef ref = demon_ent_map_find(kind, id);
|
||||
if (ref.slot != 0){
|
||||
result = ref.slot->entity;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_ent_map_erase(DEMON_MapRef ref){
|
||||
Assert(demon_ent_base != 0);
|
||||
DEMON_Map *map = demon_ent_map;
|
||||
|
||||
// move slot to free list
|
||||
if (ref.slot != 0){
|
||||
*ref.ptr_to_slot = ref.slot->next;
|
||||
SLLStackPush(map->free_slots, ref.slot);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// NOTE(allen): Event Helpers
|
||||
|
||||
internal DEMON_Event*
|
||||
demon_push_event(Arena *arena, DEMON_EventList *list, DEMON_EventKind kind){
|
||||
DEMON_EventNode *n = push_array(arena, DEMON_EventNode, 1);
|
||||
DEMON_Event *result = &n->v;
|
||||
SLLQueuePush(list->first, list->last, n);
|
||||
list->count += 1;
|
||||
result->kind = kind;
|
||||
return(result);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#ifndef DEMON_COMMON_H
|
||||
#define DEMON_COMMON_H
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: DEMON Entity System
|
||||
|
||||
typedef enum DEMON_EntityKind
|
||||
{
|
||||
DEMON_EntityKind_NULL,
|
||||
|
||||
DEMON_EntityKind_Root,
|
||||
DEMON_EntityKind_Process,
|
||||
DEMON_EntityKind_Thread,
|
||||
DEMON_EntityKind_Module,
|
||||
|
||||
DEMON_EntityKind_COUNT
|
||||
}
|
||||
DEMON_EntityKind;
|
||||
|
||||
typedef struct DEMON_Entity DEMON_Entity;
|
||||
struct DEMON_Entity
|
||||
{
|
||||
// TODO(allen): these could be U32s
|
||||
DEMON_Entity *next;
|
||||
DEMON_Entity *prev;
|
||||
DEMON_Entity *parent;
|
||||
DEMON_Entity *first;
|
||||
DEMON_Entity *last;
|
||||
|
||||
U16 kind;
|
||||
U16 arch;
|
||||
U32 gen;
|
||||
U64 id;
|
||||
U64 addr_range_dim;
|
||||
|
||||
// each OS backend decides how to use `ext` for each entity kind
|
||||
union{
|
||||
void *ext;
|
||||
U64 ext_u64;
|
||||
};
|
||||
|
||||
// the accel layer attaches some extra information to some entities
|
||||
void *accel;
|
||||
};
|
||||
|
||||
//- id -> entity map
|
||||
typedef struct DEMON_MapSlot DEMON_MapSlot;
|
||||
struct DEMON_MapSlot
|
||||
{
|
||||
DEMON_MapSlot *next;
|
||||
U16 kind;
|
||||
U64 id;
|
||||
DEMON_Entity *entity;
|
||||
};
|
||||
|
||||
typedef struct DEMON_Map DEMON_Map;
|
||||
struct DEMON_Map
|
||||
{
|
||||
DEMON_MapSlot **buckets;
|
||||
U64 bucket_count;
|
||||
DEMON_MapSlot *free_slots;
|
||||
};
|
||||
|
||||
typedef struct DEMON_MapRef DEMON_MapRef;
|
||||
struct DEMON_MapRef
|
||||
{
|
||||
DEMON_MapSlot *slot;
|
||||
DEMON_MapSlot **ptr_to_slot;
|
||||
};
|
||||
|
||||
//- rjf: entity extrusive list
|
||||
|
||||
typedef struct DEMON_EntityNode DEMON_EntityNode;
|
||||
struct DEMON_EntityNode
|
||||
{
|
||||
DEMON_EntityNode *next;
|
||||
DEMON_Entity *entity;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Demon Globals
|
||||
|
||||
thread_static B32 demon_primary_thread = 0;
|
||||
global B32 demon_run_state = 0;
|
||||
global OS_Handle demon_state_mutex = {0};
|
||||
|
||||
global U64 demon_time = 0;
|
||||
|
||||
global Arena *demon_ent_arena = 0;
|
||||
global DEMON_Map *demon_ent_map = 0;
|
||||
|
||||
global DEMON_Entity *demon_ent_free = 0;
|
||||
global DEMON_Entity *demon_ent_root = 0;
|
||||
|
||||
global DEMON_Entity *demon_ent_base = 0;
|
||||
global DEMON_Entity *demon_ent_pos = 0;
|
||||
global DEMON_Entity *demon_ent_opl = 0;
|
||||
global void *demon_ent_cmt = 0;
|
||||
|
||||
global U64 demon_proc_count = 0;
|
||||
global U64 demon_thread_count = 0;
|
||||
global U64 demon_module_count = 0;
|
||||
|
||||
#if !defined(DEMON_ENTITY_CMT_SIZE)
|
||||
# define DEMON_ENTITY_CMT_SIZE KB(64)
|
||||
#endif
|
||||
#if !defined(DEMON_ENTITY_CAP)
|
||||
# define DEMON_ENTITY_CAP 65536
|
||||
#endif
|
||||
|
||||
StaticAssert(IsPow2(DEMON_ENTITY_CMT_SIZE), check_demon_entity_cmt_size);
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: State Safety Helper
|
||||
|
||||
internal B32 demon_access_begin(void);
|
||||
internal void demon_access_end(void);
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Entity System
|
||||
|
||||
internal void demon_common_init(void);
|
||||
internal DEMON_Entity* demon_ent_alloc(void);
|
||||
|
||||
//- handle <-> entity pointer
|
||||
internal DEMON_Entity* demon_ent_ptr_from_handle(DEMON_Handle handle);
|
||||
internal DEMON_Handle demon_ent_handle_from_ptr(DEMON_Entity *entity);
|
||||
|
||||
//- high level entity alloc,init,release
|
||||
internal DEMON_Entity* demon_ent_new(DEMON_Entity *parent, DEMON_EntityKind kind, U64 id);
|
||||
internal void demon_ent_release_single(DEMON_Entity *entity);
|
||||
internal void demon_ent_release_children(DEMON_Entity *root);
|
||||
internal void demon_ent_release_root_and_children(DEMON_Entity *root);
|
||||
|
||||
//- entity map
|
||||
internal U64 demon_ent_map_hash(U16 kind, U64 id);
|
||||
internal void demon_ent_map_save(U16 kind, U64 id, DEMON_Entity *entity);
|
||||
internal DEMON_MapRef demon_ent_map_find(U16 kind, U64 id);
|
||||
internal DEMON_Entity* demon_ent_map_entity_from_id(U16 kind, U64 id);
|
||||
internal void demon_ent_map_erase(DEMON_MapRef map_ref);
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Event Helpers
|
||||
|
||||
internal DEMON_Event* demon_push_event(Arena *arena, DEMON_EventList *list, DEMON_EventKind kind);
|
||||
|
||||
#endif //DEMON_COMMON_H
|
||||
@@ -0,0 +1,871 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Main Layer Initialization
|
||||
|
||||
internal void
|
||||
demon_init(void){
|
||||
demon_common_init();
|
||||
demon_os_init();
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Basic Type Functions
|
||||
|
||||
//- rjf: stringizing
|
||||
|
||||
internal String8
|
||||
demon_string_from_event_kind(DEMON_EventKind kind){
|
||||
String8 result = str8_lit("unknown");
|
||||
switch (kind){
|
||||
default: break;
|
||||
case DEMON_EventKind_Error: result = str8_lit("Error"); break;
|
||||
case DEMON_EventKind_HandshakeComplete: result = str8_lit("HandshakeComplete"); break;
|
||||
case DEMON_EventKind_CreateProcess: result = str8_lit("CreateProcess"); break;
|
||||
case DEMON_EventKind_ExitProcess: result = str8_lit("ExitProcess"); break;
|
||||
case DEMON_EventKind_CreateThread: result = str8_lit("CreateThread"); break;
|
||||
case DEMON_EventKind_ExitThread: result = str8_lit("ExitThread"); break;
|
||||
case DEMON_EventKind_LoadModule: result = str8_lit("LoadModule"); break;
|
||||
case DEMON_EventKind_UnloadModule: result = str8_lit("UnloadModule"); break;
|
||||
case DEMON_EventKind_Breakpoint: result = str8_lit("Breakpoint"); break;
|
||||
case DEMON_EventKind_Trap: result = str8_lit("Trap"); break;
|
||||
case DEMON_EventKind_SingleStep: result = str8_lit("SingleStep"); break;
|
||||
case DEMON_EventKind_Exception: result = str8_lit("Exception"); break;
|
||||
case DEMON_EventKind_Halt: result = str8_lit("Halt"); break;
|
||||
case DEMON_EventKind_Memory: result = str8_lit("Memory"); break;
|
||||
case DEMON_EventKind_DebugString: result = str8_lit("DebugString"); break;
|
||||
case DEMON_EventKind_SetThreadName: result = str8_lit("SetThreadName"); break;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal String8
|
||||
demon_string_from_memory_event_kind(DEMON_MemoryEventKind kind){
|
||||
String8 result = str8_lit("unknown");
|
||||
switch (kind){
|
||||
default: break;
|
||||
case DEMON_MemoryEventKind_Commit: result = str8_lit("Commit"); break;
|
||||
case DEMON_MemoryEventKind_Reserve: result = str8_lit("Reserve"); break;
|
||||
case DEMON_MemoryEventKind_Decommit: result = str8_lit("Decommit"); break;
|
||||
case DEMON_MemoryEventKind_Release: result = str8_lit("Release"); break;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal String8
|
||||
demon_string_from_exception_kind(DEMON_ExceptionKind kind){
|
||||
String8 result = str8_lit("unknown");
|
||||
switch (kind){
|
||||
default: break;
|
||||
case DEMON_ExceptionKind_MemoryRead: result = str8_lit("MemoryRead"); break;
|
||||
case DEMON_ExceptionKind_MemoryWrite: result = str8_lit("MemoryWrite"); break;
|
||||
case DEMON_ExceptionKind_MemoryExecute: result = str8_lit("MemoryExecute"); break;
|
||||
case DEMON_ExceptionKind_CppThrow: result = str8_lit("CppThrow"); break;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_string_list_from_event(Arena *arena, String8List *out, DEMON_Event *event){
|
||||
B32 need_exception_info = (event->kind == DEMON_EventKind_Exception ||
|
||||
event->kind == DEMON_EventKind_Breakpoint ||
|
||||
event->kind == DEMON_EventKind_Halt ||
|
||||
event->kind == DEMON_EventKind_SingleStep);
|
||||
|
||||
// allen: kind
|
||||
String8 kind_string = demon_string_from_event_kind(event->kind);
|
||||
str8_list_pushf(arena, out, "%S: { (%i)", kind_string, event->kind);
|
||||
|
||||
// rjf: basics
|
||||
{
|
||||
str8_list_pushf(arena, out, " process: (%I64x)", event->process);
|
||||
str8_list_pushf(arena, out, " thread: (%I64x)", event->thread);
|
||||
str8_list_pushf(arena, out, " module: (%I64x)", event->module);
|
||||
str8_list_pushf(arena, out, " address: (%I64x)", event->address, event->address);
|
||||
str8_list_pushf(arena, out, " size: (0x%I64x, %I64u)", event->size, event->size);
|
||||
}
|
||||
|
||||
// rjf: string
|
||||
if (event->string.size != 0){
|
||||
str8_list_pushf(arena, out, " string: \"%S\"", event->string);
|
||||
}
|
||||
|
||||
// rjf: exception info
|
||||
if (need_exception_info){
|
||||
str8_list_pushf(arena, out, " code: (0x%x, %i)", event->code, event->code);
|
||||
str8_list_pushf(arena, out, " flags: (0x%x, %i)", event->flags, event->flags);
|
||||
str8_list_pushf(arena, out, " signo: (0x%x, %i)", event->signo, event->signo);
|
||||
str8_list_pushf(arena, out, " sigcode: (0x%x, %i)", event->sigcode, event->sigcode);
|
||||
}
|
||||
|
||||
// rjf: need error info
|
||||
if (event->kind == DEMON_EventKind_Error){
|
||||
str8_list_pushf(arena, out, " error_kind: (0x%x, %i)", event->error_kind, event->error_kind);
|
||||
}
|
||||
|
||||
// rjf: memory event kind info
|
||||
if (event->memory_kind != DEMON_MemoryEventKind_Null){
|
||||
String8 memory_kind_string = demon_string_from_memory_event_kind(event->memory_kind);
|
||||
str8_list_pushf(arena, out, " memory_kind: (%S, %i)",
|
||||
memory_kind_string, event->memory_kind);
|
||||
}
|
||||
|
||||
// rjf: exception kind
|
||||
if (need_exception_info){
|
||||
String8 exception_kind_string = demon_string_from_exception_kind(event->exception_kind);
|
||||
str8_list_pushf(arena, out, " exception_kind: (%S, %i)",
|
||||
exception_kind_string, event->exception_kind);
|
||||
}
|
||||
|
||||
// rjf: instruction ptr
|
||||
if (event->instruction_pointer != 0){
|
||||
str8_list_pushf(arena, out, " instruction_pointer: (%I64x)", event->instruction_pointer);
|
||||
}
|
||||
|
||||
// rjf: stack ptr
|
||||
if (event->stack_pointer != 0){
|
||||
str8_list_pushf(arena, out, " stack_pointer: (%I64x)", event->stack_pointer);
|
||||
}
|
||||
|
||||
str8_list_pushf(arena, out, " user_data: (0x%I64x, %I64u)",
|
||||
event->user_data, event->user_data);
|
||||
str8_list_pushf(arena, out, "}");
|
||||
}
|
||||
|
||||
//- rjf: trap chunk lists
|
||||
|
||||
internal void
|
||||
demon_trap_chunk_list_push(Arena *arena, DEMON_TrapChunkList *list, U64 cap, DEMON_Trap *trap)
|
||||
{
|
||||
DEMON_TrapChunkNode *node = list->last;
|
||||
if(node == 0 || node->count >= node->cap)
|
||||
{
|
||||
node = push_array(arena, DEMON_TrapChunkNode, 1);
|
||||
node->cap = cap;
|
||||
node->v = push_array_no_zero(arena, DEMON_Trap, node->cap);
|
||||
SLLQueuePush(list->first, list->last, node);
|
||||
list->node_count += 1;
|
||||
}
|
||||
MemoryCopyStruct(&node->v[node->count], trap);
|
||||
node->count += 1;
|
||||
list->trap_count += 1;
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_trap_chunk_list_concat_in_place(DEMON_TrapChunkList *dst, DEMON_TrapChunkList *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->node_count += to_push->node_count;
|
||||
dst->trap_count += to_push->trap_count;
|
||||
}
|
||||
MemoryZeroStruct(to_push);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_trap_chunk_list_concat_shallow_copy(Arena *arena, DEMON_TrapChunkList *dst, DEMON_TrapChunkList *to_push)
|
||||
{
|
||||
for(DEMON_TrapChunkNode *src_n = to_push->first; src_n != 0; src_n = src_n->next)
|
||||
{
|
||||
DEMON_TrapChunkNode *dst_n = push_array(arena, DEMON_TrapChunkNode, 1);
|
||||
dst_n->v = src_n->v;
|
||||
dst_n->cap = src_n->cap;
|
||||
dst_n->count = src_n->count;
|
||||
SLLQueuePush(dst->first, dst->last, dst_n);
|
||||
dst->node_count += 1;
|
||||
dst->trap_count += dst_n->count;
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: handle lists
|
||||
|
||||
internal void
|
||||
demon_handle_list_push(Arena *arena, DEMON_HandleList *list, DEMON_Handle handle)
|
||||
{
|
||||
DEMON_HandleNode *node = push_array(arena, DEMON_HandleNode, 1);
|
||||
SLLQueuePush(list->first, list->last, node);
|
||||
node->v = handle;
|
||||
list->count += 1;
|
||||
}
|
||||
|
||||
internal DEMON_HandleArray
|
||||
demon_handle_array_from_list(Arena *arena, DEMON_HandleList *list)
|
||||
{
|
||||
DEMON_HandleArray array = {0};
|
||||
array.count = list->count;
|
||||
array.handles = push_array_no_zero(arena, DEMON_Handle, array.count);
|
||||
U64 idx = 0;
|
||||
for(DEMON_HandleNode *n = list->first; n != 0; n = n->next, idx += 1)
|
||||
{
|
||||
array.handles[idx] = n->v;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
internal DEMON_HandleArray
|
||||
demon_handle_array_copy(Arena *arena, DEMON_HandleArray *src)
|
||||
{
|
||||
DEMON_HandleArray dst = {0};
|
||||
dst.count = src->count;
|
||||
dst.handles = push_array_no_zero(arena, DEMON_Handle, dst.count);
|
||||
MemoryCopy(dst.handles, src->handles, sizeof(DEMON_Handle)*dst.count);
|
||||
return dst;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Primary Thread & Exclusive Mode Controls
|
||||
|
||||
internal void
|
||||
demon_primary_thread_begin(void){
|
||||
demon_primary_thread = 1;
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_exclusive_mode_begin(void){
|
||||
Assert(demon_primary_thread);
|
||||
os_mutex_take(demon_state_mutex);
|
||||
demon_run_state = 1;
|
||||
os_mutex_drop(demon_state_mutex);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_exclusive_mode_end(void){
|
||||
Assert(demon_primary_thread);
|
||||
os_mutex_take(demon_state_mutex);
|
||||
demon_run_state = 0;
|
||||
os_mutex_drop(demon_state_mutex);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Running/Halting
|
||||
|
||||
internal DEMON_EventList
|
||||
demon_run(Arena *arena, DEMON_RunCtrls *ctrls)
|
||||
{
|
||||
Assert(demon_primary_thread);
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
|
||||
// convert controls to os controls
|
||||
B32 full_conversion = 1;
|
||||
DEMON_OS_RunCtrls os_ctrls = {0};
|
||||
{
|
||||
// convert single_step_thread
|
||||
if (ctrls->single_step_thread != 0){
|
||||
DEMON_Entity *sst_entity = demon_ent_ptr_from_handle(ctrls->single_step_thread);
|
||||
if (sst_entity != 0 &&
|
||||
sst_entity->kind == DEMON_EntityKind_Thread){
|
||||
os_ctrls.single_step_thread = sst_entity;
|
||||
}
|
||||
else{
|
||||
full_conversion = 0;
|
||||
goto finish_conversion;
|
||||
}
|
||||
}
|
||||
|
||||
// convert exception handling flag
|
||||
os_ctrls.ignore_previous_exception = ctrls->ignore_previous_exception;
|
||||
|
||||
// convert fronzen threads
|
||||
os_ctrls.run_entities_are_unfrozen = ctrls->run_entities_are_unfrozen;
|
||||
os_ctrls.run_entities_are_processes = ctrls->run_entities_are_processes;
|
||||
os_ctrls.run_entity_count = ctrls->run_entity_count;
|
||||
os_ctrls.run_entities = push_array_no_zero(scratch.arena, DEMON_Entity*, ctrls->run_entity_count);
|
||||
{
|
||||
DEMON_EntityKind expected_entity_kind = DEMON_EntityKind_Thread;
|
||||
if (os_ctrls.run_entities_are_processes){
|
||||
expected_entity_kind = DEMON_EntityKind_Process;
|
||||
}
|
||||
|
||||
DEMON_Handle *src = ctrls->run_entities;
|
||||
DEMON_Entity **dst = os_ctrls.run_entities;
|
||||
for (U64 i = 0; i < ctrls->run_entity_count; i += 1, src += 1, dst += 1){
|
||||
DEMON_Entity *frozen_thread = demon_ent_ptr_from_handle(*src);
|
||||
if (frozen_thread != 0 &&
|
||||
frozen_thread->kind == expected_entity_kind){
|
||||
*dst = frozen_thread;
|
||||
}
|
||||
else{
|
||||
full_conversion = 0;
|
||||
goto finish_conversion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert traps
|
||||
os_ctrls.traps = push_array_no_zero(scratch.arena, DEMON_OS_Trap, ctrls->traps.trap_count);
|
||||
{
|
||||
DEMON_OS_Trap *dst = os_ctrls.traps;
|
||||
|
||||
for (DEMON_TrapChunkNode *node = ctrls->traps.first;
|
||||
node != 0;
|
||||
node = node->next){
|
||||
DEMON_Trap *src = node->v;
|
||||
U64 node_trap_count = node->count;
|
||||
for (U64 i = 0; i < node_trap_count; i += 1, src += 1){
|
||||
if (src->process != 0){
|
||||
DEMON_Entity *trap_process = demon_ent_ptr_from_handle(src->process);
|
||||
if (trap_process != 0 &&
|
||||
trap_process->kind == DEMON_EntityKind_Process){
|
||||
dst->process = trap_process;
|
||||
dst->address = src->address;
|
||||
dst += 1;
|
||||
}
|
||||
else{
|
||||
full_conversion = 0;
|
||||
goto finish_conversion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os_ctrls.trap_count = (U64)(dst - os_ctrls.traps);
|
||||
}
|
||||
|
||||
finish_conversion:;
|
||||
}
|
||||
|
||||
// call the OS implementation of run
|
||||
DEMON_EventList result = {0};
|
||||
if (full_conversion){
|
||||
result = demon_os_run(arena, &os_ctrls);
|
||||
}
|
||||
else{
|
||||
DEMON_Event *event = demon_push_event(arena, &result, DEMON_EventKind_Error);
|
||||
event->error_kind = DEMON_ErrorKind_InvalidHandle;
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_halt(U64 code, U64 user_data){
|
||||
demon_os_halt(code, user_data);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_get_time_counter(void){
|
||||
return(demon_time);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Target Process Launching/Attaching/Killing/Detaching/Halting
|
||||
|
||||
internal U32
|
||||
demon_launch_process(OS_LaunchOptions *options){
|
||||
Assert(demon_primary_thread);
|
||||
U32 result = demon_os_launch_process(options);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
demon_attach_process(U32 pid){
|
||||
Assert(demon_primary_thread);
|
||||
B32 result = demon_os_attach_process(pid);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
demon_kill_process(DEMON_Handle process, U32 exit_code){
|
||||
Assert(demon_primary_thread);
|
||||
B32 result = 0;
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(process);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Process){
|
||||
result = demon_os_kill_process(entity, exit_code);
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
demon_detach_process(DEMON_Handle process){
|
||||
Assert(demon_primary_thread);
|
||||
B32 result = 0;
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(process);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Process){
|
||||
result = demon_os_detach_process(entity);
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Entity Functions
|
||||
|
||||
//- rjf: basics
|
||||
|
||||
internal B32
|
||||
demon_object_exists(DEMON_Handle object){
|
||||
B32 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(object);
|
||||
result = (entity != 0);
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
//- rjf: introspection
|
||||
|
||||
internal Architecture
|
||||
demon_arch_from_object(DEMON_Handle object){
|
||||
Architecture result = Architecture_Null;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(object);
|
||||
if (entity != 0){
|
||||
result = (Architecture)entity->arch;
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_base_vaddr_from_module(DEMON_Handle module){
|
||||
U64 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(module);
|
||||
if (entity != 0 && entity->kind == DEMON_EntityKind_Module){
|
||||
result = entity->id;
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal Rng1U64
|
||||
demon_vaddr_range_from_module(DEMON_Handle module)
|
||||
{
|
||||
Rng1U64 result = {0};
|
||||
if(demon_access_begin())
|
||||
{
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(module);
|
||||
if(entity != 0 && entity->kind == DEMON_EntityKind_Module)
|
||||
{
|
||||
result = r1u64(entity->id, entity->id+entity->addr_range_dim);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal String8
|
||||
demon_full_path_from_module(Arena *arena, DEMON_Handle module){
|
||||
String8 result = {0};
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(module);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Module){
|
||||
result = demon_accel_full_path_from_module(arena, entity);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_stack_base_vaddr_from_thread(DEMON_Handle thread){
|
||||
U64 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(thread);
|
||||
if (entity != 0 && entity->kind == DEMON_EntityKind_Thread){
|
||||
result = demon_accel_stack_base_vaddr_from_thread(entity);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_tls_root_vaddr_from_thread(DEMON_Handle handle){
|
||||
U64 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(handle);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Thread){
|
||||
result = demon_accel_tls_root_vaddr_from_thread(entity);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal DEMON_HandleArray
|
||||
demon_all_processes(Arena *arena){
|
||||
DEMON_HandleArray result = {0};
|
||||
|
||||
if (demon_access_begin()){
|
||||
DEMON_Handle *handles = push_array_no_zero(arena, DEMON_Handle, demon_proc_count);
|
||||
DEMON_Handle *handle_opl = handles + demon_proc_count;
|
||||
DEMON_Handle *handle_ptr = handles;
|
||||
|
||||
for (DEMON_Entity *process = demon_ent_root->first;
|
||||
process != 0 && handle_ptr < handle_opl;
|
||||
process = process->next){
|
||||
if (process->kind == DEMON_EntityKind_Process){
|
||||
*handle_ptr = demon_ent_handle_from_ptr(process);
|
||||
handle_ptr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
result.handles = handles;
|
||||
result.count = (U64)(handle_ptr - handles);
|
||||
|
||||
U64 unused_count = demon_proc_count - result.count;
|
||||
arena_put_back(arena, sizeof(DEMON_Handle)*unused_count);
|
||||
demon_access_end();
|
||||
}
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal DEMON_HandleArray
|
||||
demon_threads_from_process(Arena *arena, DEMON_Handle process){
|
||||
DEMON_HandleArray result = {0};
|
||||
|
||||
if (demon_access_begin()){
|
||||
DEMON_Handle *handles = push_array_no_zero(arena, DEMON_Handle, demon_thread_count);
|
||||
DEMON_Handle *handle_opl = handles + demon_thread_count;
|
||||
DEMON_Handle *handle_ptr = handles;
|
||||
|
||||
DEMON_Entity *process_ptr = demon_ent_ptr_from_handle(process);
|
||||
|
||||
if (process_ptr != 0 && process_ptr->kind == DEMON_EntityKind_Process){
|
||||
for (DEMON_Entity *thread = process_ptr->first;
|
||||
thread != 0 && handle_ptr < handle_opl;
|
||||
thread = thread->next){
|
||||
if (thread->kind == DEMON_EntityKind_Thread){
|
||||
*handle_ptr = demon_ent_handle_from_ptr(thread);
|
||||
handle_ptr += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.handles = handles;
|
||||
result.count = (U64)(handle_ptr - handles);
|
||||
|
||||
U64 unused_count = demon_thread_count - result.count;
|
||||
arena_put_back(arena, sizeof(DEMON_Handle)*unused_count);
|
||||
demon_access_end();
|
||||
}
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal DEMON_HandleArray
|
||||
demon_modules_from_process(Arena *arena, DEMON_Handle process){
|
||||
DEMON_HandleArray result = {0};
|
||||
|
||||
if (demon_access_begin()){
|
||||
DEMON_Handle *handles = push_array_no_zero(arena, DEMON_Handle, demon_module_count);
|
||||
DEMON_Handle *handle_opl = handles + demon_module_count;
|
||||
DEMON_Handle *handle_ptr = handles;
|
||||
|
||||
DEMON_Entity *process_ptr = demon_ent_ptr_from_handle(process);
|
||||
|
||||
if (process_ptr != 0 && process_ptr->kind == DEMON_EntityKind_Process){
|
||||
for (DEMON_Entity *module = process_ptr->first;
|
||||
module != 0 && handle_ptr < handle_opl;
|
||||
module = module->next){
|
||||
if (module->kind == DEMON_EntityKind_Module){
|
||||
*handle_ptr = demon_ent_handle_from_ptr(module);
|
||||
handle_ptr += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.handles = handles;
|
||||
result.count = (U64)(handle_ptr - handles);
|
||||
|
||||
U64 unused_count = demon_module_count - result.count;
|
||||
arena_put_back(arena, sizeof(DEMON_Handle)*unused_count);
|
||||
demon_access_end();
|
||||
}
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
//- rjf: target process memory allocation/protection
|
||||
|
||||
internal U64
|
||||
demon_reserve_memory(DEMON_Handle process, U64 size){
|
||||
U64 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(process);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Process){
|
||||
result = demon_os_reserve_memory(entity, size);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
demon_set_memory_protect_flags(DEMON_Handle process, U64 page_vaddr, U64 size, DEMON_MemoryProtectFlags flags){
|
||||
B32 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(process);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Process){
|
||||
demon_os_set_memory_protect_flags(entity, page_vaddr, size, flags);
|
||||
result = 1;
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
demon_release_memory(DEMON_Handle process, U64 vaddr, U64 size){
|
||||
B32 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(process);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Process){
|
||||
demon_os_release_memory(entity, vaddr, size);
|
||||
result = 1;
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
//- rjf: target process memory reading/writing
|
||||
|
||||
internal U64
|
||||
demon_read_memory(DEMON_Handle process, void *dst, U64 src_address, U64 size){
|
||||
U64 bytes_read = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(process);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Process){
|
||||
bytes_read = demon_os_read_memory(entity, dst, src_address, size);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(bytes_read);
|
||||
}
|
||||
|
||||
internal B32
|
||||
demon_write_memory(DEMON_Handle process, U64 dst_address, void *src, U64 size){
|
||||
B32 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(process);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Process){
|
||||
result = demon_os_write_memory(entity, dst_address, src, size);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
#define READ_BLOCK_SIZE 4096
|
||||
|
||||
internal U64
|
||||
demon_read_memory_amap_aligned(DEMON_Handle process, void *dst, U64 src_address, U64 size){
|
||||
// Algorithm:
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// ^ ^ ^
|
||||
// MIN MAX SMAX
|
||||
// [MIN,MAX) - range attempting to read
|
||||
// [MAX,SMAX) - range not yet proven to be impossible to read
|
||||
|
||||
Assert(src_address%READ_BLOCK_SIZE == 0);
|
||||
Assert(size%READ_BLOCK_SIZE == 0);
|
||||
|
||||
U64 read_size = 0;
|
||||
U64 min = 0;
|
||||
U64 max = size;
|
||||
U64 smax = max;
|
||||
|
||||
for (;;){
|
||||
if (max <= min){
|
||||
break;
|
||||
}
|
||||
|
||||
// attempt to read range
|
||||
U64 attempt_size = max - min;
|
||||
B32 success = demon_read_memory(process, (U8*)dst + min, src_address + min, attempt_size);
|
||||
|
||||
if (success){
|
||||
// increase successful read size
|
||||
read_size += attempt_size;
|
||||
// adjust range up
|
||||
min = max;
|
||||
max = smax;
|
||||
}
|
||||
else{
|
||||
// mark this point as too far
|
||||
smax = max - READ_BLOCK_SIZE;
|
||||
// bisect the range for the next read attempt
|
||||
U64 mid = (min + max)/2;
|
||||
U64 aligned_mid = AlignDownPow2(mid, READ_BLOCK_SIZE);
|
||||
max = aligned_mid;
|
||||
}
|
||||
}
|
||||
|
||||
U64 result = read_size;
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_read_memory_amap(DEMON_Handle process, void *dst, U64 src_address, U64 size){
|
||||
U64 read_size = 0;
|
||||
|
||||
if (demon_access_begin()){
|
||||
B32 done = 0;
|
||||
U64 read_opl = src_address + size;
|
||||
|
||||
// pre-aligned part -- [SRC,PRE_OPL)
|
||||
U64 src_block_opl = AlignPow2(src_address, READ_BLOCK_SIZE);
|
||||
U64 pre_opl = Min(src_block_opl, read_opl);
|
||||
if(src_address < pre_opl)
|
||||
{
|
||||
U64 attempt_size = pre_opl - src_address;
|
||||
if(!demon_read_memory(process, dst, src_address, attempt_size))
|
||||
{
|
||||
done = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
read_size += attempt_size;
|
||||
}
|
||||
}
|
||||
|
||||
// aligned part -- [PRE_OPL,POST_FIRST)
|
||||
U64 read_opl_block_base = AlignDownPow2(read_opl, READ_BLOCK_SIZE);
|
||||
U64 post_first = Max(read_opl_block_base, pre_opl);
|
||||
if (!done && pre_opl < post_first){
|
||||
U64 off = pre_opl - src_address;
|
||||
U64 attempt_size = post_first - pre_opl;
|
||||
U64 actual_size = demon_read_memory_amap_aligned(process, (U8*)dst + off,
|
||||
pre_opl, attempt_size);
|
||||
read_size += actual_size;
|
||||
if (actual_size < attempt_size){
|
||||
done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// post-aligned part -- [POST_FIRST,READ_OPL)
|
||||
if (!done && post_first < read_opl){
|
||||
U64 off = post_first - src_address;
|
||||
U64 attempt_size = read_opl - post_first;
|
||||
if (!demon_read_memory(process, (U8*)dst + off, post_first, attempt_size)){
|
||||
done = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
read_size += attempt_size;
|
||||
}
|
||||
}
|
||||
|
||||
demon_access_end();
|
||||
}
|
||||
|
||||
U64 result = read_size;
|
||||
return(result);
|
||||
}
|
||||
|
||||
#undef READ_BLOCK_SIZE
|
||||
|
||||
//- rjf: thread registers reading/writing
|
||||
|
||||
internal void*
|
||||
demon_read_regs(DEMON_Handle thread){
|
||||
void *result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(thread);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Thread){
|
||||
result = demon_accel_read_regs(entity);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
demon_write_regs(DEMON_Handle thread, void *data){
|
||||
B32 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(thread);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Thread){
|
||||
demon_accel_write_regs(entity, data);
|
||||
result = 1;
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_read_ip(DEMON_Handle thread){
|
||||
U64 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(thread);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Thread){
|
||||
void *regs = demon_accel_read_regs(entity);
|
||||
result = regs_rip_from_arch_block((Architecture)entity->arch, regs);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U64
|
||||
demon_read_sp(DEMON_Handle thread){
|
||||
U64 result = 0;
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(thread);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Thread){
|
||||
void *regs = demon_accel_read_regs(entity);
|
||||
result = regs_rsp_from_arch_block((Architecture)entity->arch, regs);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_write_ip(DEMON_Handle thread, U64 ip){
|
||||
if (demon_access_begin()){
|
||||
DEMON_Entity *entity = demon_ent_ptr_from_handle(thread);
|
||||
if (entity != 0 &&
|
||||
entity->kind == DEMON_EntityKind_Thread){
|
||||
void *regs = demon_accel_read_regs(entity);
|
||||
regs_arch_block_write_rip((Architecture)entity->arch, regs, ip);
|
||||
demon_accel_write_regs(entity, regs);
|
||||
}
|
||||
demon_access_end();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Process Listing
|
||||
|
||||
internal void
|
||||
demon_proc_iter_begin(DEMON_ProcessIter *iter){
|
||||
demon_os_proc_iter_begin(iter);
|
||||
}
|
||||
|
||||
internal B32
|
||||
demon_proc_iter_next(Arena *arena, DEMON_ProcessIter *iter, DEMON_ProcessInfo *info_out){
|
||||
return(demon_os_proc_iter_next(arena, iter, info_out));
|
||||
}
|
||||
|
||||
internal void
|
||||
demon_proc_iter_end(DEMON_ProcessIter *iter){
|
||||
demon_os_proc_iter_end(iter);
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#ifndef DEMON_CORE_H
|
||||
#define DEMON_CORE_H
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Demon Low Level Entities
|
||||
|
||||
typedef U64 DEMON_Handle;
|
||||
|
||||
typedef struct DEMON_HandleNode DEMON_HandleNode;
|
||||
struct DEMON_HandleNode
|
||||
{
|
||||
DEMON_HandleNode *next;
|
||||
DEMON_Handle v;
|
||||
};
|
||||
|
||||
typedef struct DEMON_HandleList DEMON_HandleList;
|
||||
struct DEMON_HandleList
|
||||
{
|
||||
DEMON_HandleNode *first;
|
||||
DEMON_HandleNode *last;
|
||||
U64 count;
|
||||
};
|
||||
|
||||
typedef struct DEMON_HandleArray DEMON_HandleArray;
|
||||
struct DEMON_HandleArray
|
||||
{
|
||||
DEMON_Handle *handles;
|
||||
U64 count;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Memory Protection Flags
|
||||
|
||||
typedef U32 DEMON_MemoryProtectFlags;
|
||||
enum{
|
||||
DEMON_MemoryProtectFlag_Read = (1<<0),
|
||||
DEMON_MemoryProtectFlag_Write = (1<<1),
|
||||
DEMON_MemoryProtectFlag_Execute = (1<<2),
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Demon Event Types
|
||||
|
||||
typedef enum DEMON_EventKind
|
||||
{
|
||||
DEMON_EventKind_Null,
|
||||
DEMON_EventKind_Error,
|
||||
DEMON_EventKind_HandshakeComplete,
|
||||
DEMON_EventKind_CreateProcess,
|
||||
DEMON_EventKind_ExitProcess,
|
||||
DEMON_EventKind_CreateThread,
|
||||
DEMON_EventKind_ExitThread,
|
||||
DEMON_EventKind_LoadModule,
|
||||
DEMON_EventKind_UnloadModule,
|
||||
DEMON_EventKind_Breakpoint,
|
||||
DEMON_EventKind_Trap,
|
||||
DEMON_EventKind_SingleStep,
|
||||
DEMON_EventKind_Exception,
|
||||
DEMON_EventKind_Halt,
|
||||
DEMON_EventKind_Memory,
|
||||
DEMON_EventKind_DebugString,
|
||||
DEMON_EventKind_SetThreadName,
|
||||
DEMON_EventKind_COUNT
|
||||
}
|
||||
DEMON_EventKind;
|
||||
|
||||
typedef enum DEMON_ErrorKind
|
||||
{
|
||||
DEMON_ErrorKind_Null,
|
||||
DEMON_ErrorKind_NotInitialized,
|
||||
DEMON_ErrorKind_NotAttached,
|
||||
DEMON_ErrorKind_UnexpectedFailure,
|
||||
DEMON_ErrorKind_InvalidHandle,
|
||||
}
|
||||
DEMON_ErrorKind;
|
||||
|
||||
typedef enum DEMON_MemoryEventKind
|
||||
{
|
||||
DEMON_MemoryEventKind_Null,
|
||||
DEMON_MemoryEventKind_Commit,
|
||||
DEMON_MemoryEventKind_Reserve,
|
||||
DEMON_MemoryEventKind_Decommit,
|
||||
DEMON_MemoryEventKind_Release,
|
||||
DEMON_MemoryEventKind_COUNT
|
||||
}
|
||||
DEMON_MemoryEventKind;
|
||||
|
||||
typedef enum DEMON_ExceptionKind
|
||||
{
|
||||
DEMON_ExceptionKind_Null,
|
||||
DEMON_ExceptionKind_MemoryRead,
|
||||
DEMON_ExceptionKind_MemoryWrite,
|
||||
DEMON_ExceptionKind_MemoryExecute,
|
||||
DEMON_ExceptionKind_CppThrow,
|
||||
DEMON_ExceptionKind_COUNT
|
||||
}
|
||||
DEMON_ExceptionKind;
|
||||
|
||||
typedef struct DEMON_Event DEMON_Event;
|
||||
struct DEMON_Event
|
||||
{
|
||||
// TODO(allen): condense
|
||||
DEMON_EventKind kind;
|
||||
DEMON_ErrorKind error_kind;
|
||||
DEMON_MemoryEventKind memory_kind;
|
||||
DEMON_ExceptionKind exception_kind;
|
||||
DEMON_Handle process;
|
||||
DEMON_Handle thread;
|
||||
DEMON_Handle module;
|
||||
U64 address;
|
||||
U64 size;
|
||||
String8 string;
|
||||
U32 code; // code gives pid & tid on CreateProcess and CreateThread (respectfully)
|
||||
U32 flags;
|
||||
S32 signo;
|
||||
S32 sigcode;
|
||||
U64 instruction_pointer;
|
||||
U64 stack_pointer;
|
||||
U64 user_data;
|
||||
B32 exception_repeated;
|
||||
};
|
||||
|
||||
typedef struct DEMON_EventNode DEMON_EventNode;
|
||||
struct DEMON_EventNode
|
||||
{
|
||||
DEMON_EventNode *next;
|
||||
DEMON_Event v;
|
||||
};
|
||||
|
||||
typedef struct DEMON_EventList DEMON_EventList;
|
||||
struct DEMON_EventList
|
||||
{
|
||||
DEMON_EventNode *first;
|
||||
DEMON_EventNode *last;
|
||||
U64 count;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Demon Run Control Types
|
||||
|
||||
typedef struct DEMON_Trap DEMON_Trap;
|
||||
struct DEMON_Trap
|
||||
{
|
||||
DEMON_Handle process;
|
||||
U64 address;
|
||||
U64 id;
|
||||
};
|
||||
|
||||
typedef struct DEMON_TrapChunkNode DEMON_TrapChunkNode;
|
||||
struct DEMON_TrapChunkNode
|
||||
{
|
||||
DEMON_TrapChunkNode *next;
|
||||
DEMON_Trap *v;
|
||||
U64 cap;
|
||||
U64 count;
|
||||
};
|
||||
|
||||
typedef struct DEMON_TrapChunkList DEMON_TrapChunkList;
|
||||
struct DEMON_TrapChunkList
|
||||
{
|
||||
DEMON_TrapChunkNode *first;
|
||||
DEMON_TrapChunkNode *last;
|
||||
U64 node_count;
|
||||
U64 trap_count;
|
||||
};
|
||||
|
||||
typedef struct DEMON_RunCtrls DEMON_RunCtrls;
|
||||
struct DEMON_RunCtrls
|
||||
{
|
||||
DEMON_Handle single_step_thread;
|
||||
B8 ignore_previous_exception;
|
||||
B8 run_entities_are_unfrozen;
|
||||
B8 run_entities_are_processes;
|
||||
DEMON_Handle *run_entities;
|
||||
U64 run_entity_count;
|
||||
DEMON_TrapChunkList traps;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ allen: Demon Process Listing
|
||||
|
||||
typedef struct DEMON_ProcessIter DEMON_ProcessIter;
|
||||
struct DEMON_ProcessIter
|
||||
{
|
||||
U64 v[2];
|
||||
};
|
||||
|
||||
typedef struct DEMON_ProcessInfo DEMON_ProcessInfo;
|
||||
struct DEMON_ProcessInfo
|
||||
{
|
||||
String8 name;
|
||||
U32 pid;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Main Layer Initialization
|
||||
|
||||
internal void demon_init(void);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Basic Type Functions
|
||||
|
||||
//- rjf: stringizing
|
||||
internal String8 demon_string_from_event_kind(DEMON_EventKind kind);
|
||||
internal String8 demon_string_from_memory_event_kind(DEMON_MemoryEventKind kind);
|
||||
internal String8 demon_string_from_exception_kind(DEMON_ExceptionKind kind);
|
||||
internal void demon_string_list_from_event(Arena *arena, String8List *out, DEMON_Event *event);
|
||||
|
||||
//- rjf: trap chunk lists
|
||||
internal void demon_trap_chunk_list_push(Arena *arena, DEMON_TrapChunkList *list, U64 cap, DEMON_Trap *trap);
|
||||
internal void demon_trap_chunk_list_concat_in_place(DEMON_TrapChunkList *dst, DEMON_TrapChunkList *to_push);
|
||||
internal void demon_trap_chunk_list_concat_shallow_copy(Arena *arena, DEMON_TrapChunkList *dst, DEMON_TrapChunkList *to_push);
|
||||
|
||||
//- rjf: handle lists
|
||||
internal void demon_handle_list_push(Arena *arena, DEMON_HandleList *list, DEMON_Handle handle);
|
||||
internal DEMON_HandleArray demon_handle_array_from_list(Arena *arena, DEMON_HandleList *list);
|
||||
internal DEMON_HandleArray demon_handle_array_copy(Arena *arena, DEMON_HandleArray *src);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Primary Thread & Exclusive Mode Controls
|
||||
|
||||
internal void demon_primary_thread_begin(void);
|
||||
internal void demon_exclusive_mode_begin(void);
|
||||
internal void demon_exclusive_mode_end(void);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Running/Halting
|
||||
|
||||
internal DEMON_EventList demon_run(Arena *arena, DEMON_RunCtrls *ctrls);
|
||||
internal void demon_halt(U64 code, U64 user_data);
|
||||
internal U64 demon_get_time_counter(void);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Target Process Launching/Attaching/Killing/Detaching/Halting
|
||||
|
||||
internal U32 demon_launch_process(OS_LaunchOptions *options);
|
||||
internal B32 demon_attach_process(U32 pid);
|
||||
internal B32 demon_kill_process(DEMON_Handle process, U32 exit_code);
|
||||
internal B32 demon_detach_process(DEMON_Handle process);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Entity Functions
|
||||
|
||||
//- rjf: basics
|
||||
internal B32 demon_object_exists(DEMON_Handle object);
|
||||
|
||||
//- rjf: introspection
|
||||
internal Architecture demon_arch_from_object(DEMON_Handle object);
|
||||
internal U64 demon_base_vaddr_from_module(DEMON_Handle module);
|
||||
internal Rng1U64 demon_vaddr_range_from_module(DEMON_Handle module);
|
||||
internal String8 demon_full_path_from_module(Arena *arena, DEMON_Handle module);
|
||||
internal U64 demon_stack_base_vaddr_from_thread(DEMON_Handle thread);
|
||||
internal U64 demon_tls_root_vaddr_from_thread(DEMON_Handle thread);
|
||||
internal DEMON_HandleArray demon_all_processes(Arena *arena);
|
||||
internal DEMON_HandleArray demon_threads_from_process(Arena *arena, DEMON_Handle process);
|
||||
internal DEMON_HandleArray demon_modules_from_process(Arena *arena, DEMON_Handle process);
|
||||
|
||||
//- rjf: target process memory allocation/protection
|
||||
internal U64 demon_reserve_memory(DEMON_Handle process, U64 size);
|
||||
internal B32 demon_set_memory_protect_flags(DEMON_Handle process, U64 page_vaddr, U64 size, DEMON_MemoryProtectFlags flags);
|
||||
internal B32 demon_release_memory(DEMON_Handle process, U64 vaddr, U64 size);
|
||||
|
||||
//- rjf: target process memory reading/writing
|
||||
internal U64 demon_read_memory(DEMON_Handle process, void *dst, U64 src_address, U64 size);
|
||||
internal B32 demon_write_memory(DEMON_Handle process, U64 dst_address, void *src, U64 size);
|
||||
internal U64 demon_read_memory_amap_aligned(DEMON_Handle process, void *dst, U64 src_address, U64 size);
|
||||
internal U64 demon_read_memory_amap(DEMON_Handle process, void *dst, U64 src_address, U64 size);
|
||||
|
||||
//- rjf: thread registers reading/writing
|
||||
// IMPORTANT(allen): This API is _trusting_ you. You should never modify the data pointed
|
||||
// at by that void pointer! It is pointing to the internal cache of the registers, so it
|
||||
// will become invalid after a call to demon_write_regs, or demon_run. Use it to read
|
||||
// what you need and be done ASAP and we can avoid an extra copy baked into the API.
|
||||
internal void *demon_read_regs(DEMON_Handle thread);
|
||||
internal B32 demon_write_regs(DEMON_Handle thread, void *data);
|
||||
// TODO(allen): These might be a bad idea when we try to extend to ARM
|
||||
// They make sense for x86/x64 abstraction, which often needs identical
|
||||
// code paths except for these parts. Revisit this when ARM is integrated.
|
||||
internal U64 demon_read_ip(DEMON_Handle thread);
|
||||
internal U64 demon_read_sp(DEMON_Handle thread);
|
||||
internal void demon_write_ip(DEMON_Handle thread, U64 ip);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Process Listing
|
||||
|
||||
internal void demon_proc_iter_begin(DEMON_ProcessIter *iter);
|
||||
internal B32 demon_proc_iter_next(Arena *arena, DEMON_ProcessIter *iter, DEMON_ProcessInfo *info_out);
|
||||
internal void demon_proc_iter_end(DEMON_ProcessIter *iter);
|
||||
|
||||
#endif //DEMON_CORE_H
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#include "demon_core.c"
|
||||
#include "demon_common.c"
|
||||
#include "demon_accel.c"
|
||||
|
||||
#if OS_WINDOWS
|
||||
# include "win32/demon_os_win32.c"
|
||||
#elif OS_LINUX
|
||||
# include "linux/demon_os_linux.c"
|
||||
#else
|
||||
# error No Demon Implementation for This OS
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#ifndef DEMON_INC_H
|
||||
#define DEMON_INC_H
|
||||
|
||||
#include "demon_core.h"
|
||||
#include "demon_common.h"
|
||||
#include "demon_accel.h"
|
||||
#include "demon_os.h"
|
||||
|
||||
#if OS_WINDOWS
|
||||
# include "win32/demon_os_win32.h"
|
||||
#elif OS_LINUX
|
||||
# include "linux/demon_os_linux.h"
|
||||
#else
|
||||
# error No Demon Implementation for This OS
|
||||
#endif
|
||||
|
||||
#endif //DEMON_INC_H
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#ifndef DEMON_OS_H
|
||||
#define DEMON_OS_H
|
||||
|
||||
// NOTE(allen):
|
||||
// These are the functions that the OS backends actually implement.
|
||||
// Demon objects go through a handle validation layer but it is a lot more
|
||||
// convenient in the OS backends to implement these versions which take the
|
||||
// already validated DEMON_Entity*. These are also more convenient to call from
|
||||
// the backend layer, which lets us avoid converting back and forth between
|
||||
// handles and pointers a lot.
|
||||
|
||||
////////////////////////////////
|
||||
//~ NOTE(allen): Demon OS Run Control Types
|
||||
|
||||
typedef struct DEMON_OS_Trap DEMON_OS_Trap;
|
||||
struct DEMON_OS_Trap
|
||||
{
|
||||
DEMON_Entity *process;
|
||||
U64 address;
|
||||
};
|
||||
|
||||
typedef struct DEMON_OS_RunCtrls DEMON_OS_RunCtrls;
|
||||
struct DEMON_OS_RunCtrls
|
||||
{
|
||||
DEMON_Entity *single_step_thread;
|
||||
B8 ignore_previous_exception;
|
||||
B8 run_entities_are_unfrozen;
|
||||
B8 run_entities_are_processes;
|
||||
DEMON_Entity **run_entities;
|
||||
U64 run_entity_count;
|
||||
DEMON_OS_Trap *traps;
|
||||
U64 trap_count;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: @demon_os_hooks Main Layer Initialization
|
||||
|
||||
internal void demon_os_init(void);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: @demon_os_hooks Running/Halting
|
||||
|
||||
internal DEMON_EventList demon_os_run(Arena *arena, DEMON_OS_RunCtrls *controls);
|
||||
internal void demon_os_halt(U64 code, U64 user_data);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: @demon_os_hooks Target Process Launching/Attaching/Killing/Detaching/Halting
|
||||
|
||||
internal U32 demon_os_launch_process(OS_LaunchOptions *options);
|
||||
internal B32 demon_os_attach_process(U32 pid);
|
||||
internal B32 demon_os_kill_process(DEMON_Entity *process, U32 exit_code);
|
||||
internal B32 demon_os_detach_process(DEMON_Entity *process);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: @demon_os_hooks Entity Functions
|
||||
|
||||
//- rjf: cleanup
|
||||
internal void demon_os_entity_cleanup(DEMON_Entity *entity);
|
||||
|
||||
//- rjf: introspection
|
||||
internal String8 demon_os_full_path_from_module(Arena *arena, DEMON_Entity *module);
|
||||
internal U64 demon_os_stack_base_vaddr_from_thread(DEMON_Entity *thread);
|
||||
internal U64 demon_os_tls_root_vaddr_from_thread(DEMON_Entity *thread);
|
||||
|
||||
//- rjf: target process memory allocation/protection
|
||||
internal U64 demon_os_reserve_memory(DEMON_Entity *process, U64 size);
|
||||
internal void demon_os_set_memory_protect_flags(DEMON_Entity *process, U64 page_vaddr, U64 size, DEMON_MemoryProtectFlags flags);
|
||||
internal void demon_os_release_memory(DEMON_Entity *process, U64 vaddr, U64 size);
|
||||
|
||||
//- rjf: target process memory reading/writing
|
||||
internal U64 demon_os_read_memory(DEMON_Entity *process, void *dst, U64 src_address, U64 size);
|
||||
internal B32 demon_os_write_memory(DEMON_Entity *process, U64 dst_address, void *src, U64 size);
|
||||
#define demon_os_read_struct(p,dst,src) demon_os_read_memory((p), (dst), (src), sizeof(*(dst)))
|
||||
#define demon_os_write_struct(p,dst,src) demon_os_write_memory((p), (dst), (src), sizeof(*(src)))
|
||||
|
||||
//- rjf: thread registers reading/writing
|
||||
internal B32 demon_os_read_regs_x86(DEMON_Entity *thread, REGS_RegBlockX86 *dst);
|
||||
internal B32 demon_os_write_regs_x86(DEMON_Entity *thread, REGS_RegBlockX86 *src);
|
||||
internal B32 demon_os_read_regs_x64(DEMON_Entity *thread, REGS_RegBlockX64 *dst);
|
||||
internal B32 demon_os_write_regs_x64(DEMON_Entity *thread, REGS_RegBlockX64 *src);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: @demon_os_hooks Process Listing
|
||||
|
||||
internal void demon_os_proc_iter_begin(DEMON_ProcessIter *iter);
|
||||
internal B32 demon_os_proc_iter_next(Arena *arena, DEMON_ProcessIter *iter, DEMON_ProcessInfo *info_out);
|
||||
internal void demon_os_proc_iter_end(DEMON_ProcessIter *iter);
|
||||
|
||||
#endif //DEMON_OS_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#ifndef DEMON_OS_LINUX_H
|
||||
#define DEMON_OS_LINUX_H
|
||||
|
||||
// TODO(allen): Potential Upgrades:
|
||||
//
|
||||
// memory fd upgrade - Right now for each process we hold open a file
|
||||
// descriptor for the process's memory (/proc/%d/mem) for the entire lifetime
|
||||
// of the process; it could be opened and closed with some kind of LRU cache
|
||||
// to put a finite cap on the number of handles the demon holds
|
||||
//
|
||||
|
||||
////////////////////////////////
|
||||
//~ NOTE(allen): Get The Linux Includes
|
||||
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <elf.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
|
||||
////////////////////////////////
|
||||
//~ NOTE(allen): Linux Demon Types
|
||||
|
||||
//- entities
|
||||
|
||||
// Demon Linux Entity Extensions
|
||||
// Process: ext_u64 set to memory file descriptor
|
||||
// Thread : ext_u64 cast to DEMON_LNX_ThreadExt
|
||||
// Module : ext_u64 set to U64 (address of name)
|
||||
|
||||
struct DEMON_LNX_ThreadExt{
|
||||
B32 expecting_dummy_sigstop;
|
||||
};
|
||||
StaticAssert(sizeof(DEMON_LNX_ThreadExt) <= sizeof(Member(DEMON_Entity, ext_u64)), check_demon_lnx_thread_ext);
|
||||
|
||||
//- helpers
|
||||
|
||||
struct DEMON_LNX_AttachNode{
|
||||
DEMON_LNX_AttachNode *next;
|
||||
pid_t pid;
|
||||
};
|
||||
|
||||
struct DEMON_LNX_ProcessAux{
|
||||
B32 filled;
|
||||
U64 phnum;
|
||||
U64 phent;
|
||||
U64 phdr;
|
||||
U64 execfn;
|
||||
};
|
||||
|
||||
struct DEMON_LNX_PhdrInfo{
|
||||
Rng1U64 range;
|
||||
U64 dynamic;
|
||||
};
|
||||
|
||||
struct DEMON_LNX_ModuleNode{
|
||||
DEMON_LNX_ModuleNode *next;
|
||||
U64 vaddr;
|
||||
U64 size;
|
||||
U64 name;
|
||||
U64 already_known;
|
||||
};
|
||||
|
||||
struct DEMON_LNX_EntityNode{
|
||||
DEMON_LNX_EntityNode *next;
|
||||
DEMON_Entity *entity;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ NOTE(allen): Linux Demon Register Layouts
|
||||
|
||||
// these are defined in <sys/user.h> but only for one architecture at a time
|
||||
// (and we can't really trick it into giving us both in any obvious way)
|
||||
// we define them here so that we have them all "at once"
|
||||
|
||||
struct DEMON_LNX_UserRegsX64{
|
||||
U64 r15;
|
||||
U64 r14;
|
||||
U64 r13;
|
||||
U64 r12;
|
||||
U64 rbp;
|
||||
U64 rbx;
|
||||
U64 r11;
|
||||
U64 r10;
|
||||
U64 r9;
|
||||
U64 r8;
|
||||
U64 rax;
|
||||
U64 rcx;
|
||||
U64 rdx;
|
||||
U64 rsi;
|
||||
U64 rdi;
|
||||
U64 orig_rax;
|
||||
U64 rip;
|
||||
U64 cs;
|
||||
U64 rflags;
|
||||
U64 rsp;
|
||||
U64 ss;
|
||||
U64 fsbase;
|
||||
U64 gsbase;
|
||||
U64 ds;
|
||||
U64 es;
|
||||
U64 fs;
|
||||
U64 gs;
|
||||
};
|
||||
|
||||
struct DEMON_LNX_UserX64{
|
||||
DEMON_LNX_UserRegsX64 regs;
|
||||
S32 u_fpvalid, _pad0;
|
||||
SYMS_XSaveLegacy i387;
|
||||
U64 u_tsize, u_dsize, u_ssize, start_code, start_stack;
|
||||
U64 signal;
|
||||
S32 reserved, _pad1;
|
||||
U64 u_ar0, u_fpstate;
|
||||
U64 magic;
|
||||
U8 u_comm[32];
|
||||
U64 u_debugreg[8];
|
||||
};
|
||||
|
||||
struct DEMON_LNX_UserRegsX86{
|
||||
U32 ebx;
|
||||
U32 ecx;
|
||||
U32 edx;
|
||||
U32 esi;
|
||||
U32 edi;
|
||||
U32 ebp;
|
||||
U32 eax;
|
||||
U32 ds;
|
||||
U32 es;
|
||||
U32 fs;
|
||||
U32 gs;
|
||||
U32 orig_eax;
|
||||
U32 eip;
|
||||
U32 cs;
|
||||
U32 eflags;
|
||||
U32 sp;
|
||||
U32 ss;
|
||||
};
|
||||
|
||||
struct DEMON_LNX_UserX86{
|
||||
DEMON_LNX_UserRegsX86 regs;
|
||||
S32 u_fpvalid;
|
||||
SYMS_FSave i387;
|
||||
U32 u_tsize, u_dsize, u_ssize, start_code, start_stack;
|
||||
S32 signal, reserved;
|
||||
U32 u_ar0, u_fpstate;
|
||||
U32 magic;
|
||||
U8 u_comm[32];
|
||||
U32 u_debugreg[8];
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
enum
|
||||
{
|
||||
DEMON_LNX_PermFlags_Read = (1 << 0),
|
||||
DEMON_LNX_PermFlags_Write = (1 << 1),
|
||||
DEMON_LNX_PermFlags_Exec = (1 << 2),
|
||||
DEMON_LNX_PermFlags_Private = (1 << 3)
|
||||
};
|
||||
typedef int DEMON_LNX_PermFlags;
|
||||
|
||||
enum
|
||||
{
|
||||
DEMON_LNX_MapsEntryType_Null,
|
||||
DEMON_LNX_MapsEntryType_Path,
|
||||
DEMON_LNX_MapsEntryType_Heap,
|
||||
DEMON_LNX_MapsEntryType_Stack,
|
||||
DEMON_LNX_MapsEntryType_VDSO,
|
||||
};
|
||||
typedef int DEMON_LNX_MapsEntryType;
|
||||
|
||||
struct DEMON_LNX_MapsEntry
|
||||
{
|
||||
U64 address_lo;
|
||||
U64 address_hi;
|
||||
DEMON_LNX_PermFlags perms;
|
||||
U64 offset;
|
||||
U32 dev_major;
|
||||
U32 dev_minor;
|
||||
U64 inode;
|
||||
String8 pathname;
|
||||
DEMON_LNX_MapsEntryType type;
|
||||
pid_t stack_tid;
|
||||
};
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Helpers
|
||||
|
||||
internal DEMON_LNX_ThreadExt* demon_lnx_thread_ext(DEMON_Entity *entity);
|
||||
|
||||
internal B32 demon_lnx_attach_pid(Arena *arena, pid_t pid, DEMON_LNX_AttachNode **new_node);
|
||||
|
||||
internal String8 demon_lnx_executable_path_from_pid(Arena *arena, pid_t pid);
|
||||
internal int demon_lnx_open_memory_fd_for_pid(pid_t pid);
|
||||
|
||||
internal Architecture demon_lnx_arch_from_pid(pid_t pid);
|
||||
internal DEMON_LNX_ProcessAux demon_lnx_aux_from_pid(pid_t pid, Architecture arch);
|
||||
internal DEMON_LNX_PhdrInfo demon_lnx_phdr_info_from_memory(int memory_fd, B32 is_32bit,
|
||||
U64 phvaddr, U64 phstride, U64 phcount);
|
||||
internal DEMON_LNX_ModuleNode* demon_lnx_module_list_from_process(Arena *arena, DEMON_Entity *process);
|
||||
|
||||
internal U64 demon_lnx_read_memory(int memory_fd, void *dst, U64 src, U64 size);
|
||||
internal B32 demon_lnx_write_memory(int memory_fd, U64 dst, void *src, U64 size);
|
||||
internal String8 demon_lnx_read_memory_str(Arena *arena, int memory_fd, U64 address);
|
||||
|
||||
internal void demon_lnx_regs_x64_from_usr_regs_x64(SYMS_RegX64 *dst, DEMON_LNX_UserRegsX64 *src);
|
||||
internal void demon_lnx_usr_regs_x64_from_regs_x64(DEMON_LNX_UserRegsX64 *dst, SYMS_RegX64 *src);
|
||||
|
||||
internal String8 demon_lnx_read_int_string(int fd);
|
||||
internal B32 demon_lnx_read_expect(int fd, char expect);
|
||||
internal int demon_lnx_read_whitespace(int fd);
|
||||
internal String8 demon_lnx_read_string(Arena *arena, int fd);
|
||||
|
||||
internal int demon_lnx_open_maps(pid_t pid);
|
||||
internal B32 demon_lnx_next_map(Arena *arena, int maps, DEMON_LNX_MapsEntry *entry_out);
|
||||
|
||||
#endif //DEMON_OS_LINUX_H
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
// exe //
|
||||
|
||||
#include "base/base_inc.h"
|
||||
#include "os/os_inc.h"
|
||||
#include "demon/demon_inc.h"
|
||||
#include "syms_helpers/syms_internal_overrides.h"
|
||||
#include "syms/syms_inc.h"
|
||||
#include "syms_helpers/syms_helpers.h"
|
||||
|
||||
#include "base/base_inc.c"
|
||||
#include "os/os_inc.c"
|
||||
#include "demon/demon_inc.c"
|
||||
#include "syms_helpers/syms_internal_overrides.c"
|
||||
#include "syms/syms_inc.c"
|
||||
#include "syms_helpers/syms_helpers.c"
|
||||
|
||||
int
|
||||
main(int argument_count, char **arguments)
|
||||
{
|
||||
os_init(argument_count, arguments);
|
||||
Arena *arena = arena_alloc();
|
||||
demon_init();
|
||||
|
||||
//- rjf: find PID of mule_loop.exe
|
||||
String8 attach_process_name = str8_lit("mule_loop.exe");
|
||||
U32 pid = 0;
|
||||
{
|
||||
DEMON_ProcessIter it = {0};
|
||||
demon_proc_iter_begin(&it);
|
||||
for(DEMON_ProcessInfo info = {0}; demon_proc_iter_next(arena, &it, &info);)
|
||||
{
|
||||
if(str8_match(info.name, attach_process_name, 0))
|
||||
{
|
||||
pid = info.pid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
demon_proc_iter_end(&it);
|
||||
}
|
||||
|
||||
//- rjf: attach
|
||||
B32 attach_good = demon_attach_process(pid);
|
||||
|
||||
//- rjf: get events
|
||||
DEMON_RunCtrls ctrls = {0};
|
||||
DEMON_EventList events = demon_run(arena, ctrls);
|
||||
for(DEMON_Event *event = events.first; event != 0; event = event->next)
|
||||
{
|
||||
int x = 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//- rjf: try to break in the loop
|
||||
DEMON_RunCtrls ctrls = {0};
|
||||
DEMON_Trap trap = {0};
|
||||
{
|
||||
U64 loop_bp = 0x0000000140001074;
|
||||
ctrls.trap_count = 1;
|
||||
ctrls.traps = &trap;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#include "base/base_inc.h"
|
||||
#include "os/os_inc.h"
|
||||
#include "demon/demon_inc.h"
|
||||
#include "syms_helpers/syms_internal_overrides.h"
|
||||
#include "syms/syms_inc.h"
|
||||
#include "syms_helpers/syms_helpers.h"
|
||||
|
||||
#include "base/base_inc.c"
|
||||
#include "os/os_inc.c"
|
||||
#include "demon/demon_inc.c"
|
||||
#include "syms_helpers/syms_internal_overrides.c"
|
||||
#include "syms/syms_inc.c"
|
||||
#include "syms_helpers/syms_helpers.c"
|
||||
|
||||
internal SYMS_String8
|
||||
file_load_func_for_syms(void *user, SYMS_Arena *arena, SYMS_String8 file_name){
|
||||
String8 data = os_read_file(arena, str8_from_syms(file_name));
|
||||
SYMS_String8 result = syms_from_str8(data);
|
||||
return(result);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argument_count, char **arguments)
|
||||
{
|
||||
os_init(argument_count, arguments);
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
// setup
|
||||
demon_init();
|
||||
|
||||
// parse arguments
|
||||
String8 executable_file_name = {0};
|
||||
U64 bp_address = 0;
|
||||
|
||||
{
|
||||
String8List command_line_arguments = os_get_command_line_arguments();
|
||||
CmdLine cmd_line = cmd_line_from_string_list(scratch.arena, command_line_arguments);
|
||||
if (cmd_line.inputs.first != 0){
|
||||
executable_file_name = cmd_line.inputs.first->string;
|
||||
}
|
||||
String8 bp_string = cmd_line_string(cmd_line, str8_lit("bp"));
|
||||
try_u64_from_str8_c_rules(bp_string, &bp_address);
|
||||
}
|
||||
|
||||
// check parameters
|
||||
if (bp_address == 0 || executable_file_name.size == 0){
|
||||
printf("bad parameters\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// demon launch
|
||||
OS_LaunchOptions launch_opts = {0};
|
||||
str8_list_push(scratch.arena, &launch_opts.cmd_line, executable_file_name);
|
||||
launch_opts.path = os_get_path(scratch.arena, OS_SystemPath_Current);
|
||||
U32 process_id = demon_launch_process(&launch_opts);
|
||||
if (process_id == 0){
|
||||
printf("could not launch: '%.*s'\n", str8_varg(executable_file_name));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// demon loop
|
||||
{
|
||||
DEMON_Handle process = 0;
|
||||
DEMON_Handle thread = 0;
|
||||
|
||||
B32 hit_bp = false;
|
||||
U64 single_step_counter = 0;
|
||||
|
||||
U64 counter = 0;
|
||||
for (;;){
|
||||
Temp temp = temp_begin(scratch.arena);
|
||||
|
||||
DEMON_RunCtrls run_controls = {0};
|
||||
DEMON_Trap traps[1];
|
||||
|
||||
if (!hit_bp){
|
||||
if (process != 0){
|
||||
run_controls.trap_count = 1;
|
||||
run_controls.traps = traps;
|
||||
run_controls.traps[0].process = process;
|
||||
run_controls.traps[0].address = bp_address;
|
||||
}
|
||||
}
|
||||
else{
|
||||
run_controls.single_step_thread = thread;
|
||||
}
|
||||
|
||||
DEMON_EventList events = demon_run(temp.arena, run_controls);
|
||||
|
||||
for (DEMON_Event *event = events.first;
|
||||
event != 0;
|
||||
event = event->next, counter += 1){
|
||||
// update tracking state
|
||||
switch (event->kind){
|
||||
case DEMON_EventKind_CreateProcess:
|
||||
{
|
||||
process = event->process;
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_ExitProcess:
|
||||
{
|
||||
if (event->process == process){
|
||||
process = 0;
|
||||
}
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_CreateThread:
|
||||
{
|
||||
thread = event->thread;
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_Breakpoint:
|
||||
{
|
||||
hit_bp = true;
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_SingleStep:
|
||||
{
|
||||
single_step_counter += 1;
|
||||
|
||||
SYMS_RegX64 regs1 = {0};
|
||||
demon_read_x64_regs(thread, ®s1);
|
||||
demon_write_x64_regs(thread, ®s1);
|
||||
SYMS_RegX64 regs2 = {0};
|
||||
demon_read_x64_regs(thread, ®s2);
|
||||
if (!MemoryMatchStruct(®s1, ®s2)){
|
||||
printf("mismatch at single_step_counter=%llu\n", single_step_counter);
|
||||
}
|
||||
|
||||
if (single_step_counter == 1000){
|
||||
goto end_loop;
|
||||
}
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_NotAttached:
|
||||
{
|
||||
fprintf(stderr, "not attached - exiting\n");
|
||||
goto end_loop;
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_NotInitialized:
|
||||
case DEMON_EventKind_UnexpectedFailure:
|
||||
{
|
||||
fprintf(stderr, "unexpected error - exiting\n");
|
||||
goto end_loop;
|
||||
}break;
|
||||
}
|
||||
}
|
||||
|
||||
goto end_it;
|
||||
end_loop:
|
||||
temp_end(temp);
|
||||
goto loop_exit;
|
||||
end_it:;
|
||||
}
|
||||
|
||||
loop_exit:;
|
||||
}
|
||||
|
||||
printf("[done]\n");
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#include "base/base_inc.h"
|
||||
#include "os/os_inc.h"
|
||||
#include "demon/demon_inc.h"
|
||||
#include "syms_helpers/syms_internal_overrides.h"
|
||||
#include "syms/syms_inc.h"
|
||||
#include "syms_helpers/syms_helpers.h"
|
||||
|
||||
#include "base/base_inc.c"
|
||||
#include "os/os_inc.c"
|
||||
#include "demon/demon_inc.c"
|
||||
#include "syms_helpers/syms_internal_overrides.c"
|
||||
#include "syms/syms_inc.c"
|
||||
#include "syms_helpers/syms_helpers.c"
|
||||
|
||||
internal SYMS_String8
|
||||
file_load_func_for_syms(void *user, SYMS_Arena *arena, SYMS_String8 file_name){
|
||||
String8 data = os_read_file(arena, str8_from_syms(file_name));
|
||||
SYMS_String8 result = syms_from_str8(data);
|
||||
return(result);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argument_count, char **arguments)
|
||||
{
|
||||
os_init(argument_count, arguments);
|
||||
demon_init();
|
||||
|
||||
String8Node node[2];
|
||||
|
||||
#define TARGET_EXE "C:\\devel\\projects\\debugger\\build\\mule_unwind_20210511_clang11_lldlink.exe"
|
||||
|
||||
OS_LaunchOptions options = {0};
|
||||
#if OS_WINDOWS
|
||||
str8_list_push(&options.cmd_line, &node[0], str8_lit(TARGET_EXE));
|
||||
options.path = str8_lit("C:\\devel\\projects\\debugger\\build\\");
|
||||
#else
|
||||
str8_list_push(&options.cmd_line, &node[0], str8_lit("/home/allenw/projects_copy/debugger/build/mule_main"));
|
||||
options.path = str8_lit("/home/allenw/projects_copy/debugger/build/");
|
||||
#endif
|
||||
U32 process_id = demon_launch_process(&options);
|
||||
if (process_id == 0){
|
||||
printf("Could not launch process\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#if OS_WINDOWS
|
||||
U64 bp_addr = 0x140001134;
|
||||
#else
|
||||
U64 bp_addr = 0x400918;
|
||||
#endif
|
||||
|
||||
DEMON_Handle process = 0;
|
||||
DEMON_Handle thread = 0;
|
||||
DEMON_Handle module = 0;
|
||||
U64 module_base = 0;
|
||||
SYMS_Group *group = 0;
|
||||
|
||||
B32 hit_bp = false;
|
||||
U64 counter = 0;
|
||||
|
||||
for (;;){
|
||||
DEMON_RunCtrls run_controls = {0};
|
||||
DEMON_Trap trap_memory[2];
|
||||
|
||||
DEMON_Trap *trap_ptr = trap_memory;
|
||||
if (process != 0 && !hit_bp){
|
||||
trap_ptr->process = process;
|
||||
trap_ptr->address = bp_addr;
|
||||
trap_ptr += 1;
|
||||
}
|
||||
run_controls.traps = trap_memory;
|
||||
run_controls.trap_count = (U64)(trap_ptr - trap_memory);
|
||||
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
DEMON_EventList events = demon_run(scratch.arena, run_controls);
|
||||
|
||||
for (DEMON_Event *event = events.first;
|
||||
event != 0;
|
||||
event = event->next){
|
||||
printf("STEP[%05llx] -- ", counter);
|
||||
counter += 1;
|
||||
|
||||
switch (event->kind){
|
||||
case DEMON_EventKind_NotInitialized:
|
||||
{
|
||||
printf("Not Initialized\n");
|
||||
exit(1);
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_NotAttached:
|
||||
{
|
||||
printf("Not Attached\n");
|
||||
exit(1);
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_UnexpectedFailure:
|
||||
{
|
||||
printf("Unexpected Failure\n");
|
||||
exit(1);
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_CreateProcess:
|
||||
{
|
||||
printf("Create Process\n");
|
||||
if (process == 0){
|
||||
process = event->process;
|
||||
}
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_CreateThread:
|
||||
{
|
||||
printf("Create Thread\n");
|
||||
if (thread == 0){
|
||||
thread = event->thread;
|
||||
}
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_LoadModule:
|
||||
{
|
||||
Temp temp = temp_begin(scratch.arena);
|
||||
String8 file_name = demon_full_path_from_module(scratch.arena, event->module);
|
||||
printf("Load Module: %.*s\n", str8_varg(file_name));
|
||||
if (module == 0 && str8_match(file_name, str8_lit(TARGET_EXE), 0)){
|
||||
module = event->module;
|
||||
module_base = event->address;
|
||||
|
||||
// setup syms group
|
||||
group = syms_group_alloc();
|
||||
|
||||
SYMS_FileLoadCtx ctx = {0};
|
||||
ctx.file_load_func = file_load_func_for_syms;
|
||||
|
||||
SYMS_String8List file_names = {0};
|
||||
syms_string_list_push(group->arena, &file_names, syms_from_str8(file_name));
|
||||
|
||||
SYMS_FileInfOptions opts = {0};
|
||||
|
||||
SYMS_FileInfResult inf_result = syms_file_inf_infer_from_file_list(group->arena, ctx, file_names, &opts);
|
||||
syms_group_init(group, &inf_result.data_parsed);
|
||||
}
|
||||
temp_end(temp);
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_ExitProcess:
|
||||
{
|
||||
printf("Exit Process\n");
|
||||
exit(0);
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_ExitThread:
|
||||
{
|
||||
printf("Exit Thread\n");
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_UnloadModule:
|
||||
{
|
||||
printf("Unload Module\n");
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_Breakpoint:
|
||||
{
|
||||
Architecture arch = demon_arch_from_object(event->process);
|
||||
U64 ip = event->instruction_pointer;
|
||||
printf("Breakpoint: %llx\n", ip);
|
||||
|
||||
hit_bp = true;
|
||||
|
||||
//- unwind
|
||||
|
||||
// setup bin
|
||||
SYMS_String8 bin_data = group->bin_data;
|
||||
SYMS_BinAccel *generic_bin = group->bin;
|
||||
SYMS_PeBinAccel *pe_bin = 0;
|
||||
if (generic_bin->format == SYMS_FileFormat_PE){
|
||||
pe_bin = (SYMS_PeBinAccel*)generic_bin;
|
||||
}
|
||||
|
||||
if (pe_bin != 0){
|
||||
// read regs
|
||||
SYMS_RegX64 regs = {0};
|
||||
demon_read_x64_regs(event->thread, ®s);
|
||||
|
||||
// read stack
|
||||
SYMS_U64 sp = regs.rsp.u64;
|
||||
SYMS_U64 sp_rounded_down = sp&~(KB(4) - 1);
|
||||
|
||||
SYMS_String8 stack_memory = {0};
|
||||
stack_memory.size = KB(8);
|
||||
stack_memory.str = push_array_no_zero(scratch.arena, U8, stack_memory.size);
|
||||
|
||||
SYMS_U64 stack_memory_addr = sp_rounded_down;
|
||||
stack_memory.size = demon_read_memory_amap(event->process, stack_memory.str,
|
||||
stack_memory_addr, stack_memory.size);
|
||||
|
||||
// unwind loop
|
||||
U64 counter = 1;
|
||||
for (;; counter += 1){
|
||||
printf("%02llu: ip=%llx; sp=%llx\n", counter, regs.rip.u64, regs.rsp.u64);
|
||||
SYMS_MemoryView memview = syms_memory_view_make(stack_memory, stack_memory_addr);
|
||||
SYMS_UnwindResult unwind_result = syms_unwind_pe_x64(bin_data, pe_bin, module_base, &memview, ®s);
|
||||
if (unwind_result.dead){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_Trap:
|
||||
{
|
||||
Architecture arch = demon_arch_from_object(event->process);
|
||||
U64 ip = event->instruction_pointer;
|
||||
printf("Trap: %llx\n", ip);
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_SingleStep:
|
||||
{
|
||||
printf("Single Step: %llx\n", event->instruction_pointer);
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_Exception:
|
||||
{
|
||||
printf("Exception: %llx\n", event->instruction_pointer);
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_Halt:
|
||||
{
|
||||
printf("Halt\n");
|
||||
}break;
|
||||
|
||||
case DEMON_EventKind_Memory:
|
||||
{
|
||||
printf("Memory\n");
|
||||
}break;
|
||||
|
||||
default:
|
||||
{
|
||||
printf("Unhandled Event\n");
|
||||
exit(1);
|
||||
}break;
|
||||
}
|
||||
}
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
printf("Done\n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,935 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
// NOTE(rjf): (18 October 2021) Notes on Win32 process halting via
|
||||
// DebugBreakProcess:
|
||||
//
|
||||
// Calling DebugBreakProcess seems to cause a few events to come back:
|
||||
// 1. Thread Creation Event
|
||||
// 2. Breakpoint Event (with the thread matching that of #1)
|
||||
// 3. Thread Exiting Event (matching #1)
|
||||
//
|
||||
// Having done this experiment on a single-threaded program (mule_loop.exe),
|
||||
// I can only infer that what is happening here is that when DebugBreakProcess
|
||||
// is called, it first injects a thread into the target process that runs
|
||||
// code with an int3. This is very similar to the old approach that Demon
|
||||
// took.
|
||||
//
|
||||
// It's going to be difficult to distinguish between these CreateThreads and
|
||||
// ExitThreads from others (not caused by halting), even though we can match
|
||||
// the hit breakpoint to the associated thread.
|
||||
//
|
||||
// What could be possible (in order to distinguish the hit breakpoint as a
|
||||
// halt event, instead of an arbitrary breakpoint) is looking at the breakpoint
|
||||
// address. This injected thread has a breakpoint that's different from the
|
||||
// initial breakpoint that the kernel automatically hits when a process is
|
||||
// first being debugged.
|
||||
//
|
||||
// With DebugBreakProcess:
|
||||
// - first breakpoint that is hit: 0x7ff8ad9806b0 in kernel code
|
||||
// - last breakpoint that is hit: 0x7ff8ad950860 in kernel code (halt)
|
||||
//
|
||||
// Without DebugBreakProcess:
|
||||
// - first breakpoint that is hit: 0x7ff8ad9806b0
|
||||
//
|
||||
|
||||
// NOTE(rjf): (18 October 2021) Notes on suspending processes via
|
||||
// NtSuspendProcess:
|
||||
//
|
||||
// NtSuspendProcess is an undocumented API that is exported by ntdll. It is
|
||||
// fairly simple but could be unstable. To call it, the main trick is that
|
||||
// you need a handle with certain privileges (PROCESS_SUSPEND_RESUME), so
|
||||
// you can't just take any handle and use it.
|
||||
//
|
||||
// To use it, we can manually load it from ntdll, grab an elevated handle
|
||||
// for a given process HANDLE, and then call it. We can resume, then, with
|
||||
// NtResumeProcess.
|
||||
//
|
||||
// Other than this, our options seem to more-or-less lie in individually
|
||||
// suspending all of the threads in the process-to-be-halted.
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/base_inc.h"
|
||||
#include "os/os_inc.h"
|
||||
#include "syms_helpers/syms_internal_overrides.h"
|
||||
#include "syms/syms_inc.h"
|
||||
#include "syms_helpers/syms_helpers.h"
|
||||
|
||||
#include "base/base_inc.c"
|
||||
#include "os/os_inc.c"
|
||||
#include "syms_helpers/syms_internal_overrides.c"
|
||||
#include "syms/syms_inc.c"
|
||||
#include "syms_helpers/syms_helpers.c"
|
||||
|
||||
typedef LONG NtSuspendProcessFunction(HANDLE ProcessHandle);
|
||||
global NtSuspendProcessFunction *NtSuspendProcess = 0;
|
||||
|
||||
////////////////////////////////
|
||||
// NOTE(allen): Win32 Demon Exceptions
|
||||
|
||||
#define DEMON_W32_EXCEPTION_BREAKPOINT 0x80000003u
|
||||
#define DEMON_W32_EXCEPTION_SINGLE_STEP 0x80000004u
|
||||
#define DEMON_W32_EXCEPTION_LONG_JUMP 0x80000026u
|
||||
#define DEMON_W32_EXCEPTION_ACCESS_VIOLATION 0xC0000005u
|
||||
#define DEMON_W32_EXCEPTION_ARRAY_BOUNDS_EXCEEDED 0xC000008Cu
|
||||
#define DEMON_W32_EXCEPTION_DATA_TYPE_MISALIGNMENT 0x80000002u
|
||||
#define DEMON_W32_EXCEPTION_GUARD_PAGE_VIOLATION 0x80000001u
|
||||
#define DEMON_W32_EXCEPTION_FLT_DENORMAL_OPERAND 0xC000008Du
|
||||
#define DEMON_W32_EXCEPTION_FLT_DEVIDE_BY_ZERO 0xC000008Eu
|
||||
#define DEMON_W32_EXCEPTION_FLT_INEXACT_RESULT 0xC000008Fu
|
||||
#define DEMON_W32_EXCEPTION_FLT_INVALID_OPERATION 0xC0000090u
|
||||
#define DEMON_W32_EXCEPTION_FLT_OVERFLOW 0xC0000091u
|
||||
#define DEMON_W32_EXCEPTION_FLT_STACK_CHECK 0xC0000092u
|
||||
#define DEMON_W32_EXCEPTION_FLT_UNDERFLOW 0xC0000093u
|
||||
#define DEMON_W32_EXCEPTION_INT_DIVIDE_BY_ZERO 0xC0000094u
|
||||
#define DEMON_W32_EXCEPTION_INT_OVERFLOW 0xC0000095u
|
||||
#define DEMON_W32_EXCEPTION_PRIVILEGED_INSTRUCTION 0xC0000096u
|
||||
#define DEMON_W32_EXCEPTION_ILLEGAL_INSTRUCTION 0xC000001Du
|
||||
#define DEMON_W32_EXCEPTION_IN_PAGE_ERROR 0xC0000006u
|
||||
#define DEMON_W32_EXCEPTION_INVALID_DISPOSITION 0xC0000026u
|
||||
#define DEMON_W32_EXCEPTION_NONCONTINUABLE 0xC0000025u
|
||||
#define DEMON_W32_EXCEPTION_STACK_OVERFLOW 0xC00000FDu
|
||||
#define DEMON_W32_EXCEPTION_INVALID_HANDLE 0xC0000008u
|
||||
#define DEMON_W32_EXCEPTION_UNWIND_CONSOLIDATE 0x80000029u
|
||||
#define DEMON_W32_EXCEPTION_DLL_NOT_FOUND 0xC0000135u
|
||||
#define DEMON_W32_EXCEPTION_ORDINAL_NOT_FOUND 0xC0000138u
|
||||
#define DEMON_W32_EXCEPTION_ENTRY_POINT_NOT_FOUND 0xC0000139u
|
||||
#define DEMON_W32_EXCEPTION_DLL_INIT_FAILED 0xC0000142u
|
||||
#define DEMON_W32_EXCEPTION_CONTROL_C_EXIT 0xC000013Au
|
||||
#define DEMON_W32_EXCEPTION_FLT_MULTIPLE_FAULTS 0xC00002B4u
|
||||
#define DEMON_W32_EXCEPTION_FLT_MULTIPLE_TRAPS 0xC00002B5u
|
||||
#define DEMON_W32_EXCEPTION_NAT_CONSUMPTION 0xC00002C9u
|
||||
#define DEMON_W32_EXCEPTION_HEAP_CORRUPTION 0xC0000374u
|
||||
#define DEMON_W32_EXCEPTION_STACK_BUFFER_OVERRUN 0xC0000409u
|
||||
#define DEMON_W32_EXCEPTION_INVALID_CRUNTIME_PARAM 0xC0000417u
|
||||
#define DEMON_W32_EXCEPTION_ASSERT_FAILURE 0xC0000420u
|
||||
#define DEMON_W32_EXCEPTION_NO_MEMORY 0xC0000017u
|
||||
#define DEMON_W32_EXCEPTION_THROW 0xE06D7363u
|
||||
|
||||
////////////////////////////////
|
||||
// NOTE(allen): Win32 Demon Register API Codes
|
||||
|
||||
#define DEMON_W32_CTX_X86 0x00010000
|
||||
#define DEMON_W32_CTX_X64 0x00100000
|
||||
|
||||
#define DEMON_W32_CTX_INTEL_CONTROL 0x0001
|
||||
#define DEMON_W32_CTX_INTEL_INTEGER 0x0002
|
||||
#define DEMON_W32_CTX_INTEL_SEGMENTS 0x0004
|
||||
#define DEMON_W32_CTX_INTEL_FLOATS 0x0008
|
||||
#define DEMON_W32_CTX_INTEL_DEBUG 0x0010
|
||||
#define DEMON_W32_CTX_INTEL_EXTENDED 0x0020
|
||||
#define DEMON_W32_CTX_INTEL_XSTATE 0x0040
|
||||
|
||||
#define DEMON_W32_CTX_X86_ALL (DEMON_W32_CTX_X86 | \
|
||||
DEMON_W32_CTX_INTEL_CONTROL | DEMON_W32_CTX_INTEL_INTEGER | \
|
||||
DEMON_W32_CTX_INTEL_SEGMENTS | DEMON_W32_CTX_INTEL_DEBUG | \
|
||||
DEMON_W32_CTX_INTEL_EXTENDED)
|
||||
#define DEMON_W32_CTX_X64_ALL (DEMON_W32_CTX_X64 | \
|
||||
DEMON_W32_CTX_INTEL_CONTROL | DEMON_W32_CTX_INTEL_INTEGER | \
|
||||
DEMON_W32_CTX_INTEL_SEGMENTS | DEMON_W32_CTX_INTEL_FLOATS | \
|
||||
DEMON_W32_CTX_INTEL_DEBUG)
|
||||
|
||||
struct TEST_DebugEvent
|
||||
{
|
||||
String8 name;
|
||||
U64 process_id;
|
||||
U64 thread_id;
|
||||
HANDLE process;
|
||||
HANDLE thread;
|
||||
U64 addr;
|
||||
DEBUG_EVENT evt;
|
||||
};
|
||||
|
||||
struct TEST_Trap
|
||||
{
|
||||
HANDLE process;
|
||||
U64 address;
|
||||
};
|
||||
|
||||
internal U16
|
||||
test_w32_real_tag_word_from_xsave(XSAVE_FORMAT *fxsave)
|
||||
{
|
||||
U16 result = 0;
|
||||
U32 top = (fxsave->StatusWord >> 11) & 7;
|
||||
for (U32 fpr = 0; fpr < 8; fpr += 1){
|
||||
U32 tag = 3;
|
||||
if (fxsave->TagWord & (1 << fpr)){
|
||||
U32 st = (fpr - top)&7;
|
||||
|
||||
SYMS_Reg80 *fp = (SYMS_Reg80*)&fxsave->FloatRegisters[st*16];
|
||||
U16 exponent = fp->sign1_exp15 & bitmask15;
|
||||
U64 integer_part = fp->int1_frac63 >> 63;
|
||||
U64 fraction_part = fp->int1_frac63 & bitmask63;
|
||||
|
||||
// tag: 0 - normal; 1 - zero; 2 - special
|
||||
tag = 2;
|
||||
if (exponent == 0){
|
||||
if (integer_part == 0 && fraction_part == 0){
|
||||
tag = 1;
|
||||
}
|
||||
}
|
||||
else if (exponent != bitmask15 && integer_part != 0){
|
||||
tag = 0;
|
||||
}
|
||||
}
|
||||
result |= tag << (2 * fpr);
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U16
|
||||
test_w32_xsave_tag_word_from_real_tag_word(U16 ftw)
|
||||
{
|
||||
U16 compact = 0;
|
||||
for (U32 fpr = 0; fpr < 8; fpr++){
|
||||
U32 tag = (ftw >> (fpr * 2)) & 3;
|
||||
if (tag != 3){
|
||||
compact |= (1 << fpr);
|
||||
}
|
||||
}
|
||||
return(compact);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_w32_read_x64_regs(HANDLE thread, SYMS_RegX64 *dst)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
// NOTE(allen): Check available features
|
||||
U32 feature_mask = GetEnabledXStateFeatures();
|
||||
B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX);
|
||||
|
||||
// NOTE(allen): Setup the context
|
||||
CONTEXT *ctx = 0;
|
||||
U32 ctx_flags = DEMON_W32_CTX_X64_ALL;
|
||||
if (avx_enabled){
|
||||
ctx_flags |= DEMON_W32_CTX_INTEL_XSTATE;
|
||||
}
|
||||
DWORD size = 0;
|
||||
InitializeContext(0, ctx_flags, 0, &size);
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER){
|
||||
void *ctx_memory = push_array(scratch.arena, U8, size);
|
||||
if (!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)){
|
||||
ctx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
B32 avx_available = false;
|
||||
|
||||
if (ctx != 0){
|
||||
// NOTE(allen): Finish Context Setup
|
||||
if (avx_enabled){
|
||||
SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX);
|
||||
}
|
||||
|
||||
// NOTE(allen): Determine what features are available on this particular ctx
|
||||
// TODO(allen): Experiment carefully with this nonsense.
|
||||
// Does avx_enabled = avx_available in all circumstances or not?
|
||||
DWORD64 xstate_flags = 0;
|
||||
if (GetXStateFeaturesMask(ctx, &xstate_flags)){
|
||||
if (xstate_flags & XSTATE_MASK_AVX){
|
||||
avx_available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get thread context
|
||||
HANDLE thread_handle = thread;
|
||||
if (!GetThreadContext(thread_handle, ctx)){
|
||||
ctx = 0;
|
||||
}
|
||||
|
||||
B32 result = false;
|
||||
if (ctx != 0){
|
||||
result = true;
|
||||
|
||||
// NOTE(allen): Convert CONTEXT -> SYMS_RegX64
|
||||
dst->rax.u64 = ctx->Rax;
|
||||
dst->rcx.u64 = ctx->Rcx;
|
||||
dst->rdx.u64 = ctx->Rdx;
|
||||
dst->rbx.u64 = ctx->Rbx;
|
||||
dst->rsp.u64 = ctx->Rsp;
|
||||
dst->rbp.u64 = ctx->Rbp;
|
||||
dst->rsi.u64 = ctx->Rsi;
|
||||
dst->rdi.u64 = ctx->Rdi;
|
||||
dst->r8.u64 = ctx->R8;
|
||||
dst->r9.u64 = ctx->R9;
|
||||
dst->r10.u64 = ctx->R10;
|
||||
dst->r11.u64 = ctx->R11;
|
||||
dst->r12.u64 = ctx->R12;
|
||||
dst->r13.u64 = ctx->R13;
|
||||
dst->r14.u64 = ctx->R14;
|
||||
dst->r15.u64 = ctx->R15;
|
||||
dst->rip.u64 = ctx->Rip;
|
||||
dst->cs.u16 = ctx->SegCs;
|
||||
dst->ds.u16 = ctx->SegDs;
|
||||
dst->es.u16 = ctx->SegEs;
|
||||
dst->fs.u16 = ctx->SegFs;
|
||||
dst->gs.u16 = ctx->SegGs;
|
||||
dst->ss.u16 = ctx->SegSs;
|
||||
dst->dr0.u32 = ctx->Dr0;
|
||||
dst->dr1.u32 = ctx->Dr1;
|
||||
dst->dr2.u32 = ctx->Dr2;
|
||||
dst->dr3.u32 = ctx->Dr3;
|
||||
dst->dr6.u32 = ctx->Dr6;
|
||||
dst->dr7.u32 = ctx->Dr7;
|
||||
|
||||
// NOTE(allen): This bit is "supposed to always be 1" I guess.
|
||||
// TODO(allen): Not sure what this is all about but I haven't investigated it yet.
|
||||
// This might be totally not necessary or something.
|
||||
dst->rflags.u64 = ctx->EFlags | 0x2;
|
||||
|
||||
XSAVE_FORMAT *xsave = &ctx->FltSave;
|
||||
dst->fcw.u16 = xsave->ControlWord;
|
||||
dst->fsw.u16 = xsave->StatusWord;
|
||||
dst->ftw.u16 = test_w32_real_tag_word_from_xsave(xsave);
|
||||
dst->fop.u16 = xsave->ErrorOpcode;
|
||||
dst->fcs.u16 = xsave->ErrorSelector;
|
||||
dst->fds.u16 = xsave->DataSelector;
|
||||
dst->fip.u32 = xsave->ErrorOffset;
|
||||
dst->fdp.u32 = xsave->DataOffset;
|
||||
dst->mxcsr.u32 = xsave->MxCsr;
|
||||
dst->mxcsr_mask.u32 = xsave->MxCsr_Mask;
|
||||
|
||||
M128A *float_s = xsave->FloatRegisters;
|
||||
SYMS_Reg80 *float_d = &dst->fpr0;
|
||||
for (U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1){
|
||||
MemoryCopy(float_d, float_s, sizeof(*float_d));
|
||||
}
|
||||
|
||||
if (!avx_available){
|
||||
M128A *xmm_s = xsave->XmmRegisters;
|
||||
SYMS_Reg256 *xmm_d = &dst->ymm0;
|
||||
for (U32 n = 0; n < 16; n += 1, xmm_s += 1, xmm_d += 1){
|
||||
MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_s));
|
||||
}
|
||||
}
|
||||
|
||||
if (avx_available){
|
||||
DWORD part0_length = 0;
|
||||
M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length);
|
||||
DWORD part1_length = 0;
|
||||
M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length);
|
||||
Assert(part0_length == part1_length);
|
||||
|
||||
DWORD count = part0_length/sizeof(part0[0]);
|
||||
count = ClampTop(count, 16);
|
||||
SYMS_Reg256 *ymm_d = &dst->ymm0;
|
||||
for (DWORD i = 0;
|
||||
i < count;
|
||||
i += 1, part0 += 1, part1 += 1, ymm_d += 1){
|
||||
// TODO(allen): Are we writing these out in the right order? Seems weird right?
|
||||
ymm_d->u64[3] = part0->Low;
|
||||
ymm_d->u64[2] = part0->High;
|
||||
ymm_d->u64[1] = part1->Low;
|
||||
ymm_d->u64[0] = part1->High;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_w32_write_x64_regs(HANDLE thread, SYMS_RegX64 *src)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
// NOTE(allen): Check available features
|
||||
U32 feature_mask = GetEnabledXStateFeatures();
|
||||
B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX);
|
||||
|
||||
// NOTE(allen): Setup the context
|
||||
CONTEXT *ctx = 0;
|
||||
U32 ctx_flags = DEMON_W32_CTX_X64_ALL;
|
||||
if (avx_enabled){
|
||||
ctx_flags |= DEMON_W32_CTX_INTEL_XSTATE;
|
||||
}
|
||||
DWORD size = 0;
|
||||
InitializeContext(0, ctx_flags, 0, &size);
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER){
|
||||
void *ctx_memory = push_array(scratch.arena, U8, size);
|
||||
if (!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)){
|
||||
ctx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
B32 avx_available = false;
|
||||
|
||||
if (ctx != 0){
|
||||
// NOTE(allen): Finish Context Setup
|
||||
if (avx_enabled){
|
||||
SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX);
|
||||
}
|
||||
|
||||
// NOTE(allen): Determine what features are available on this particular ctx
|
||||
// TODO(allen): Experiment carefully with this nonsense.
|
||||
// Does avx_enabled = avx_available in all circumstances or not?
|
||||
DWORD64 xstate_flags = 0;
|
||||
if (GetXStateFeaturesMask(ctx, &xstate_flags)){
|
||||
if (xstate_flags & XSTATE_MASK_AVX){
|
||||
avx_available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
B32 result = false;
|
||||
if (ctx != 0){
|
||||
// NOTE(allen): Convert SYMS_RegX64 -> CONTEXT
|
||||
ctx->ContextFlags = ctx_flags;
|
||||
|
||||
ctx->MxCsr = src->mxcsr.u32 & src->mxcsr_mask.u32;
|
||||
|
||||
ctx->Rax = src->rax.u64;
|
||||
ctx->Rcx = src->rcx.u64;
|
||||
ctx->Rdx = src->rdx.u64;
|
||||
ctx->Rbx = src->rbx.u64;
|
||||
ctx->Rsp = src->rsp.u64;
|
||||
ctx->Rbp = src->rbp.u64;
|
||||
ctx->Rsi = src->rsi.u64;
|
||||
ctx->Rdi = src->rdi.u64;
|
||||
ctx->R8 = src->r8.u64;
|
||||
ctx->R9 = src->r9.u64;
|
||||
ctx->R10 = src->r10.u64;
|
||||
ctx->R11 = src->r11.u64;
|
||||
ctx->R12 = src->r12.u64;
|
||||
ctx->R13 = src->r13.u64;
|
||||
ctx->R14 = src->r14.u64;
|
||||
ctx->R15 = src->r15.u64;
|
||||
ctx->Rip = src->rip.u64;
|
||||
ctx->SegCs = src->cs.u16;
|
||||
ctx->SegDs = src->ds.u16;
|
||||
ctx->SegEs = src->es.u16;
|
||||
ctx->SegFs = src->fs.u16;
|
||||
ctx->SegGs = src->gs.u16;
|
||||
ctx->SegSs = src->ss.u16;
|
||||
ctx->Dr0 = src->dr0.u32;
|
||||
ctx->Dr1 = src->dr1.u32;
|
||||
ctx->Dr2 = src->dr2.u32;
|
||||
ctx->Dr3 = src->dr3.u32;
|
||||
ctx->Dr6 = src->dr6.u32;
|
||||
ctx->Dr7 = src->dr7.u32;
|
||||
|
||||
ctx->EFlags = src->rflags.u64;
|
||||
|
||||
XSAVE_FORMAT *fxsave = &ctx->FltSave;
|
||||
fxsave->ControlWord = src->fcw.u16;
|
||||
fxsave->StatusWord = src->fsw.u16;
|
||||
fxsave->TagWord = test_w32_xsave_tag_word_from_real_tag_word(src->ftw.u16);
|
||||
fxsave->ErrorOpcode = src->fop.u16;
|
||||
fxsave->ErrorSelector = src->fcs.u16;
|
||||
fxsave->DataSelector = src->fds.u16;
|
||||
fxsave->ErrorOffset = src->fip.u32;
|
||||
fxsave->DataOffset = src->fdp.u32;
|
||||
|
||||
M128A *float_d = fxsave->FloatRegisters;
|
||||
SYMS_Reg80 *float_s = &src->fpr0;
|
||||
for (U32 n = 0;
|
||||
n < 8;
|
||||
n += 1, float_s += 1, float_d += 1){
|
||||
MemoryCopy(float_d, float_s, 10);
|
||||
}
|
||||
|
||||
if (!avx_available){
|
||||
M128A *xmm_d = fxsave->XmmRegisters;
|
||||
SYMS_Reg256 *xmm_s = &src->ymm0;
|
||||
for (U32 n = 0;
|
||||
n < 8;
|
||||
n += 1, xmm_d += 1, xmm_s += 1){
|
||||
MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_d));
|
||||
}
|
||||
}
|
||||
|
||||
if (avx_available){
|
||||
DWORD part0_length = 0;
|
||||
M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length);
|
||||
DWORD part1_length = 0;
|
||||
M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length);
|
||||
Assert(part0_length == part1_length);
|
||||
|
||||
DWORD count = part0_length/sizeof(part0[0]);
|
||||
count = ClampTop(count, 16);
|
||||
SYMS_Reg256 *ymm_d = &src->ymm0;
|
||||
for (DWORD i = 0;
|
||||
i < count;
|
||||
i += 1, part0 += 1, part1 += 1, ymm_d += 1){
|
||||
// TODO(allen): Are we writing these out in the right order? Seems weird right?
|
||||
part0->Low = ymm_d->u64[3];
|
||||
part0->High = ymm_d->u64[2];
|
||||
part1->Low = ymm_d->u64[1];
|
||||
part1->High = ymm_d->u64[0];
|
||||
}
|
||||
}
|
||||
|
||||
//- set thread context
|
||||
HANDLE thread_handle = thread;
|
||||
if (SetThreadContext(thread_handle, ctx)){
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_w32_read_memory(HANDLE process_handle, void *dst, U64 src_address, U64 size)
|
||||
{
|
||||
B32 result = true;
|
||||
U8 *ptr = (U8*)dst;
|
||||
U8 *opl = ptr + size;
|
||||
U64 cursor = src_address;
|
||||
for (;ptr < opl;){
|
||||
SIZE_T to_read = (SIZE_T)(opl - ptr);
|
||||
SIZE_T actual_read = 0;
|
||||
if (!ReadProcessMemory(process_handle, (LPCVOID)cursor, ptr, to_read, &actual_read)){
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
ptr += actual_read;
|
||||
cursor += actual_read;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_w32_write_memory(HANDLE process_handle, U64 dst_address, void *src, U64 size)
|
||||
{
|
||||
B32 result = true;
|
||||
U8 *ptr = (U8*)src;
|
||||
U8 *opl = ptr + size;
|
||||
U64 cursor = dst_address;
|
||||
for (;ptr < opl;){
|
||||
SIZE_T to_write = (SIZE_T)(opl - ptr);
|
||||
SIZE_T actual_write = 0;
|
||||
if (!WriteProcessMemory(process_handle, (LPVOID)cursor, ptr, to_write, &actual_write)){
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
ptr += actual_write;
|
||||
cursor += actual_write;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_launch_process(OS_LaunchOptions *options)
|
||||
{
|
||||
B32 result = false;
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
StringJoin join_params = {0};
|
||||
join_params.pre = str8_lit("\"");
|
||||
join_params.sep = str8_lit("\" \"");
|
||||
join_params.post = str8_lit("\"");
|
||||
String8 cmd = str8_list_join(scratch.arena, &options->cmd_line, &join_params);
|
||||
|
||||
StringJoin join_params2 = {0};
|
||||
join_params2.sep = str8_lit("\0");
|
||||
join_params2.post = str8_lit("\0");
|
||||
String8 env = str8_list_join(scratch.arena, &options->env, &join_params2);
|
||||
|
||||
String16 cmd16 = str16_from_8(scratch.arena, cmd);
|
||||
String16 dir16 = str16_from_8(scratch.arena, options->path);
|
||||
String16 env16 = str16_from_8(scratch.arena, env);
|
||||
|
||||
DWORD access_flags = PROCESS_QUERY_INFORMATION | DEBUG_PROCESS | PROCESS_VM_READ | PROCESS_VM_WRITE;
|
||||
STARTUPINFOW startup_info = {sizeof(startup_info)};
|
||||
PROCESS_INFORMATION process_info = {0};
|
||||
if (CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 0, access_flags, (WCHAR*)env16.str, (WCHAR*)dir16.str,
|
||||
&startup_info, &process_info))
|
||||
{
|
||||
CloseHandle(process_info.hProcess);
|
||||
CloseHandle(process_info.hThread);
|
||||
result = true;
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return(result);
|
||||
}
|
||||
|
||||
global HANDLE g_process_1 = 0;
|
||||
global DWORD g_process_id_1 = 0;
|
||||
global U64 g_process_injection_addr_1 = 0;
|
||||
global HANDLE g_process_2 = 0;
|
||||
global DWORD g_process_id_2 = 0;
|
||||
|
||||
internal B32
|
||||
test_w32_inject_thread(HANDLE process, U64 start_address)
|
||||
{
|
||||
B32 result = false;
|
||||
LPTHREAD_START_ROUTINE start = (LPTHREAD_START_ROUTINE)start_address;
|
||||
HANDLE thread = CreateRemoteThread(process, 0, 0, start, 0, 0, 0);
|
||||
if(thread != 0)
|
||||
{
|
||||
CloseHandle(thread);
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void
|
||||
test_halt(void)
|
||||
{
|
||||
test_w32_inject_thread(g_process_1, g_process_injection_addr_1);
|
||||
}
|
||||
|
||||
internal TEST_DebugEvent
|
||||
test_run_process(HANDLE step_thread, HANDLE suspend_thread, U64 traps_count, TEST_Trap *traps)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
TEST_DebugEvent result = {0};
|
||||
|
||||
//- rjf: freeze thread
|
||||
if(suspend_thread)
|
||||
{
|
||||
DWORD result = SuspendThread(suspend_thread);
|
||||
DWORD error = GetLastError();
|
||||
int x = 0;
|
||||
}
|
||||
|
||||
//- rjf: write traps
|
||||
U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, traps_count);
|
||||
{
|
||||
TEST_Trap *trap = traps;
|
||||
for(U64 i = 0; i < traps_count; i += 1, trap += 1)
|
||||
{
|
||||
if(test_w32_read_memory(trap->process, trap_swap_bytes + i, trap->address, 1))
|
||||
{
|
||||
U8 int3 = 0xCC;
|
||||
test_w32_write_memory(trap->process, trap->address, &int3, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
trap_swap_bytes[i] = 0xCC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: set single step bit
|
||||
if(step_thread != 0)
|
||||
{
|
||||
SYMS_RegX64 regs = {0};
|
||||
test_w32_read_x64_regs(step_thread, ®s);
|
||||
regs.rflags.u64 |= 0x100;
|
||||
test_w32_write_x64_regs(step_thread, ®s);
|
||||
}
|
||||
|
||||
//- rjf: continue
|
||||
local_persist B32 need_resume = 0;
|
||||
local_persist DWORD resume_pid = 0;
|
||||
local_persist DWORD resume_tid = 0;
|
||||
|
||||
if(need_resume)
|
||||
{
|
||||
need_resume = 0;
|
||||
ContinueDebugEvent(resume_pid, resume_tid, DBG_CONTINUE);
|
||||
}
|
||||
|
||||
//- rjf: get event
|
||||
DEBUG_EVENT evt = {0};
|
||||
if(WaitForDebugEvent(&evt, INFINITE))
|
||||
{
|
||||
need_resume = 1;
|
||||
resume_pid = evt.dwProcessId;
|
||||
resume_tid = evt.dwThreadId;
|
||||
result.evt = evt;
|
||||
|
||||
switch(evt.dwDebugEventCode)
|
||||
{
|
||||
default:break;
|
||||
|
||||
case CREATE_PROCESS_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("create process");
|
||||
result.process_id = evt.dwProcessId;
|
||||
result.process = evt.u.CreateProcessInfo.hProcess;
|
||||
result.thread_id = evt.dwThreadId;
|
||||
result.thread = evt.u.CreateProcessInfo.hThread;
|
||||
if(g_process_1 == 0)
|
||||
{
|
||||
g_process_1 = result.process;
|
||||
g_process_id_1 = result.process_id;
|
||||
|
||||
// injection memory
|
||||
{
|
||||
U8 injection_code[64];
|
||||
injection_code[0] = 0xCC;
|
||||
injection_code[1] = 0xC3;
|
||||
for (U64 i = 2; i < 64; i += 1){
|
||||
injection_code[i] = 0xCC;
|
||||
}
|
||||
|
||||
U64 injection_size = 64;
|
||||
U64 injection_address = (U64)VirtualAllocEx(g_process_1, 0, injection_size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE);
|
||||
test_w32_write_memory(g_process_1, injection_address, injection_code, sizeof(injection_code));
|
||||
g_process_injection_addr_1 = injection_address;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
g_process_2 = result.process;
|
||||
g_process_id_2 = result.process_id;
|
||||
}
|
||||
}break;
|
||||
|
||||
case EXIT_PROCESS_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("exit process");
|
||||
result.process_id = evt.dwProcessId;
|
||||
}break;
|
||||
|
||||
case CREATE_THREAD_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("create thread");
|
||||
result.thread_id = evt.dwThreadId;
|
||||
result.thread = evt.u.CreateThread.hThread;
|
||||
}break;
|
||||
|
||||
case EXIT_THREAD_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("exit thread");
|
||||
result.thread_id = evt.dwThreadId;
|
||||
}break;
|
||||
|
||||
case LOAD_DLL_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("load dll");
|
||||
}break;
|
||||
|
||||
case UNLOAD_DLL_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("unload dll");
|
||||
}break;
|
||||
|
||||
case EXCEPTION_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("exception");
|
||||
|
||||
EXCEPTION_DEBUG_INFO *edi = &evt.u.Exception;
|
||||
EXCEPTION_RECORD *exception = &edi->ExceptionRecord;
|
||||
|
||||
switch(exception->ExceptionCode)
|
||||
{
|
||||
case DEMON_W32_EXCEPTION_BREAKPOINT:
|
||||
{
|
||||
result.name = str8_lit("breakpoint");
|
||||
result.addr = (U64)exception->ExceptionAddress;
|
||||
}break;
|
||||
|
||||
case DEMON_W32_EXCEPTION_SINGLE_STEP:
|
||||
{
|
||||
result.name = str8_lit("single_step");
|
||||
}break;
|
||||
|
||||
case DEMON_W32_EXCEPTION_THROW:
|
||||
{
|
||||
result.name = str8_lit("exception throw");
|
||||
}break;
|
||||
|
||||
case DEMON_W32_EXCEPTION_ACCESS_VIOLATION:
|
||||
case DEMON_W32_EXCEPTION_IN_PAGE_ERROR:
|
||||
{
|
||||
result.name = str8_lit("exception access violation");
|
||||
}break;
|
||||
|
||||
default:
|
||||
{
|
||||
}break;
|
||||
}
|
||||
|
||||
}break;
|
||||
|
||||
case OUTPUT_DEBUG_STRING_EVENT:
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
result.name = str8_lit("output debug string");
|
||||
|
||||
U64 string_address = (U64)evt.u.DebugString.lpDebugStringData;
|
||||
U64 string_size = (U64)evt.u.DebugString.nDebugStringLength;
|
||||
|
||||
// TODO(allen): is the string in UTF-8 or UTF-16?
|
||||
|
||||
U8 *buffer = push_array_no_zero(scratch.arena, U8, string_size + 1);
|
||||
test_w32_read_memory(g_process_id_1 == evt.dwProcessId ? g_process_1 : g_process_2, buffer, string_address, string_size);
|
||||
buffer[string_size] = 0;
|
||||
|
||||
printf("%s\n", buffer);
|
||||
scratch_end(scratch);
|
||||
}break;
|
||||
|
||||
case RIP_EVENT:
|
||||
{
|
||||
result.name = str8_lit("rip event");
|
||||
}break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: set single step bit
|
||||
if(step_thread != 0)
|
||||
{
|
||||
SYMS_RegX64 regs = {0};
|
||||
test_w32_read_x64_regs(step_thread, ®s);
|
||||
regs.rflags.u64 &= ~0x100;
|
||||
test_w32_write_x64_regs(step_thread, ®s);
|
||||
}
|
||||
|
||||
//- rjf: unset traps
|
||||
{
|
||||
TEST_Trap *trap = traps;
|
||||
for(U64 i = 0; i < traps_count; i += 1, trap += 1)
|
||||
{
|
||||
U8 og_byte = trap_swap_bytes[i];
|
||||
if(og_byte != 0xCC)
|
||||
{
|
||||
test_w32_write_memory(trap->process, trap->address, &og_byte, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: resume thread
|
||||
if(suspend_thread)
|
||||
{
|
||||
ResumeThread(suspend_thread);
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal DWORD
|
||||
test_halter_thread(void *params)
|
||||
{
|
||||
HANDLE original_process_handle = params;
|
||||
Sleep(1500);
|
||||
test_halt();
|
||||
#if 0
|
||||
DWORD process_id = GetProcessId(original_process_handle);
|
||||
HANDLE elevated_process_handle = OpenProcess(PROCESS_SUSPEND_RESUME, 0, process_id);
|
||||
LONG result = NtSuspendProcess(elevated_process_handle);
|
||||
CloseHandle(elevated_process_handle);
|
||||
DebugBreakProcess(process);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argument_count, char **arguments)
|
||||
{
|
||||
os_init(argument_count, arguments);
|
||||
Arena *arena = arena_alloc();
|
||||
|
||||
NtSuspendProcess = (NtSuspendProcessFunction *)GetProcAddress(GetModuleHandle("ntdll"), "NtSuspendProcess");
|
||||
|
||||
// rjf: launch
|
||||
{
|
||||
OS_LaunchOptions opts = {0};
|
||||
opts.path = os_get_path(arena, OS_SystemPath_Current);
|
||||
str8_list_push(arena, &opts.cmd_line, str8_lit("R:\\projects\\debugger\\build\\mule_loop.exe"));
|
||||
B32 launch_good = test_launch_process(&opts);
|
||||
int x = 0;
|
||||
}
|
||||
|
||||
// rjf: get process/thread handles
|
||||
HANDLE process = 0;
|
||||
HANDLE thread1 = 0;
|
||||
U64 thread1_id = 0;
|
||||
{
|
||||
for(TEST_DebugEvent evt = {0};;)
|
||||
{
|
||||
evt = test_run_process(0, 0, 0, 0);
|
||||
if(evt.process)
|
||||
{
|
||||
process = evt.process;
|
||||
}
|
||||
if(evt.thread)
|
||||
{
|
||||
thread1 = evt.thread;
|
||||
thread1_id = evt.thread_id;
|
||||
}
|
||||
if(process != 0 && thread1 != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rjf: get first breakpoint
|
||||
{
|
||||
for(TEST_DebugEvent evt = {0};;)
|
||||
{
|
||||
evt = test_run_process(0, 0, 0, 0);
|
||||
if(evt.evt.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rjf: launch halter thread
|
||||
DWORD halter_id = 0;
|
||||
{
|
||||
CreateThread(0, 0, test_halter_thread, process, 0, &halter_id);
|
||||
}
|
||||
|
||||
// rjf: run + wait for event
|
||||
for(;;)
|
||||
{
|
||||
TEST_DebugEvent evt = test_run_process(0, 0, 0, 0);
|
||||
int x = 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//- rjf: run until 2nd thread starts up
|
||||
HANDLE thread2 = 0;
|
||||
U64 thread2_id = 0;
|
||||
{
|
||||
for(TEST_DebugEvent evt = {0};;)
|
||||
{
|
||||
evt = test_run_process(0, 0, 0, 0/*ArrayCount(traps), traps*/);
|
||||
if(evt.thread)
|
||||
{
|
||||
thread2 = evt.thread;
|
||||
thread2_id = evt.thread_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: wait for first output string
|
||||
{
|
||||
for(TEST_DebugEvent evt = {0};;)
|
||||
{
|
||||
evt = test_run_process(0, 0, 0, 0);
|
||||
if(evt.evt.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: wait for bps
|
||||
{
|
||||
// U64 thread1_stop_vaddr = 0x0000000140001119;
|
||||
// U64 thread2_stop_vaddr = 0x00000001400010C8;
|
||||
// TEST_Trap traps[] =
|
||||
{
|
||||
// {process, thread1_stop_vaddr},
|
||||
//{process, thread2_stop_vaddr},
|
||||
};
|
||||
TEST_DebugEvent evt = {0};
|
||||
//for(;;)
|
||||
{
|
||||
evt = test_run_process(0, thread2, 0, 0/*ArrayCount(traps), traps*/);
|
||||
int x = 0;
|
||||
}
|
||||
for(;;) {}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,874 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/base_inc.h"
|
||||
#include "os/os_inc.h"
|
||||
#include "syms_helpers/syms_internal_overrides.h"
|
||||
#include "syms/syms_inc.h"
|
||||
#include "syms_helpers/syms_helpers.h"
|
||||
|
||||
#include "base/base_inc.c"
|
||||
#include "os/os_inc.c"
|
||||
#include "syms_helpers/syms_internal_overrides.c"
|
||||
#include "syms/syms_inc.c"
|
||||
#include "syms_helpers/syms_helpers.c"
|
||||
|
||||
////////////////////////////////
|
||||
// NOTE(allen): Win32 Demon Exceptions
|
||||
|
||||
#define DEMON_W32_EXCEPTION_BREAKPOINT 0x80000003u
|
||||
#define DEMON_W32_EXCEPTION_SINGLE_STEP 0x80000004u
|
||||
#define DEMON_W32_EXCEPTION_LONG_JUMP 0x80000026u
|
||||
#define DEMON_W32_EXCEPTION_ACCESS_VIOLATION 0xC0000005u
|
||||
#define DEMON_W32_EXCEPTION_ARRAY_BOUNDS_EXCEEDED 0xC000008Cu
|
||||
#define DEMON_W32_EXCEPTION_DATA_TYPE_MISALIGNMENT 0x80000002u
|
||||
#define DEMON_W32_EXCEPTION_GUARD_PAGE_VIOLATION 0x80000001u
|
||||
#define DEMON_W32_EXCEPTION_FLT_DENORMAL_OPERAND 0xC000008Du
|
||||
#define DEMON_W32_EXCEPTION_FLT_DEVIDE_BY_ZERO 0xC000008Eu
|
||||
#define DEMON_W32_EXCEPTION_FLT_INEXACT_RESULT 0xC000008Fu
|
||||
#define DEMON_W32_EXCEPTION_FLT_INVALID_OPERATION 0xC0000090u
|
||||
#define DEMON_W32_EXCEPTION_FLT_OVERFLOW 0xC0000091u
|
||||
#define DEMON_W32_EXCEPTION_FLT_STACK_CHECK 0xC0000092u
|
||||
#define DEMON_W32_EXCEPTION_FLT_UNDERFLOW 0xC0000093u
|
||||
#define DEMON_W32_EXCEPTION_INT_DIVIDE_BY_ZERO 0xC0000094u
|
||||
#define DEMON_W32_EXCEPTION_INT_OVERFLOW 0xC0000095u
|
||||
#define DEMON_W32_EXCEPTION_PRIVILEGED_INSTRUCTION 0xC0000096u
|
||||
#define DEMON_W32_EXCEPTION_ILLEGAL_INSTRUCTION 0xC000001Du
|
||||
#define DEMON_W32_EXCEPTION_IN_PAGE_ERROR 0xC0000006u
|
||||
#define DEMON_W32_EXCEPTION_INVALID_DISPOSITION 0xC0000026u
|
||||
#define DEMON_W32_EXCEPTION_NONCONTINUABLE 0xC0000025u
|
||||
#define DEMON_W32_EXCEPTION_STACK_OVERFLOW 0xC00000FDu
|
||||
#define DEMON_W32_EXCEPTION_INVALID_HANDLE 0xC0000008u
|
||||
#define DEMON_W32_EXCEPTION_UNWIND_CONSOLIDATE 0x80000029u
|
||||
#define DEMON_W32_EXCEPTION_DLL_NOT_FOUND 0xC0000135u
|
||||
#define DEMON_W32_EXCEPTION_ORDINAL_NOT_FOUND 0xC0000138u
|
||||
#define DEMON_W32_EXCEPTION_ENTRY_POINT_NOT_FOUND 0xC0000139u
|
||||
#define DEMON_W32_EXCEPTION_DLL_INIT_FAILED 0xC0000142u
|
||||
#define DEMON_W32_EXCEPTION_CONTROL_C_EXIT 0xC000013Au
|
||||
#define DEMON_W32_EXCEPTION_FLT_MULTIPLE_FAULTS 0xC00002B4u
|
||||
#define DEMON_W32_EXCEPTION_FLT_MULTIPLE_TRAPS 0xC00002B5u
|
||||
#define DEMON_W32_EXCEPTION_NAT_CONSUMPTION 0xC00002C9u
|
||||
#define DEMON_W32_EXCEPTION_HEAP_CORRUPTION 0xC0000374u
|
||||
#define DEMON_W32_EXCEPTION_STACK_BUFFER_OVERRUN 0xC0000409u
|
||||
#define DEMON_W32_EXCEPTION_INVALID_CRUNTIME_PARAM 0xC0000417u
|
||||
#define DEMON_W32_EXCEPTION_ASSERT_FAILURE 0xC0000420u
|
||||
#define DEMON_W32_EXCEPTION_NO_MEMORY 0xC0000017u
|
||||
#define DEMON_W32_EXCEPTION_THROW 0xE06D7363u
|
||||
|
||||
////////////////////////////////
|
||||
// NOTE(allen): Win32 Demon Register API Codes
|
||||
|
||||
#define DEMON_W32_CTX_X86 0x00010000
|
||||
#define DEMON_W32_CTX_X64 0x00100000
|
||||
|
||||
#define DEMON_W32_CTX_INTEL_CONTROL 0x0001
|
||||
#define DEMON_W32_CTX_INTEL_INTEGER 0x0002
|
||||
#define DEMON_W32_CTX_INTEL_SEGMENTS 0x0004
|
||||
#define DEMON_W32_CTX_INTEL_FLOATS 0x0008
|
||||
#define DEMON_W32_CTX_INTEL_DEBUG 0x0010
|
||||
#define DEMON_W32_CTX_INTEL_EXTENDED 0x0020
|
||||
#define DEMON_W32_CTX_INTEL_XSTATE 0x0040
|
||||
|
||||
#define DEMON_W32_CTX_X86_ALL (DEMON_W32_CTX_X86 | \
|
||||
DEMON_W32_CTX_INTEL_CONTROL | DEMON_W32_CTX_INTEL_INTEGER | \
|
||||
DEMON_W32_CTX_INTEL_SEGMENTS | DEMON_W32_CTX_INTEL_DEBUG | \
|
||||
DEMON_W32_CTX_INTEL_EXTENDED)
|
||||
#define DEMON_W32_CTX_X64_ALL (DEMON_W32_CTX_X64 | \
|
||||
DEMON_W32_CTX_INTEL_CONTROL | DEMON_W32_CTX_INTEL_INTEGER | \
|
||||
DEMON_W32_CTX_INTEL_SEGMENTS | DEMON_W32_CTX_INTEL_FLOATS | \
|
||||
DEMON_W32_CTX_INTEL_DEBUG)
|
||||
|
||||
struct TEST_DebugEvent
|
||||
{
|
||||
String8 name;
|
||||
U64 process_id;
|
||||
U64 thread_id;
|
||||
HANDLE process;
|
||||
HANDLE thread;
|
||||
U64 addr;
|
||||
DEBUG_EVENT evt;
|
||||
};
|
||||
|
||||
struct TEST_Trap
|
||||
{
|
||||
HANDLE process;
|
||||
U64 address;
|
||||
};
|
||||
|
||||
internal U16
|
||||
test_w32_real_tag_word_from_xsave(XSAVE_FORMAT *fxsave)
|
||||
{
|
||||
U16 result = 0;
|
||||
U32 top = (fxsave->StatusWord >> 11) & 7;
|
||||
for (U32 fpr = 0; fpr < 8; fpr += 1){
|
||||
U32 tag = 3;
|
||||
if (fxsave->TagWord & (1 << fpr)){
|
||||
U32 st = (fpr - top)&7;
|
||||
|
||||
SYMS_Reg80 *fp = (SYMS_Reg80*)&fxsave->FloatRegisters[st*16];
|
||||
U16 exponent = fp->sign1_exp15 & bitmask15;
|
||||
U64 integer_part = fp->int1_frac63 >> 63;
|
||||
U64 fraction_part = fp->int1_frac63 & bitmask63;
|
||||
|
||||
// tag: 0 - normal; 1 - zero; 2 - special
|
||||
tag = 2;
|
||||
if (exponent == 0){
|
||||
if (integer_part == 0 && fraction_part == 0){
|
||||
tag = 1;
|
||||
}
|
||||
}
|
||||
else if (exponent != bitmask15 && integer_part != 0){
|
||||
tag = 0;
|
||||
}
|
||||
}
|
||||
result |= tag << (2 * fpr);
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal U16
|
||||
test_w32_xsave_tag_word_from_real_tag_word(U16 ftw)
|
||||
{
|
||||
U16 compact = 0;
|
||||
for (U32 fpr = 0; fpr < 8; fpr++){
|
||||
U32 tag = (ftw >> (fpr * 2)) & 3;
|
||||
if (tag != 3){
|
||||
compact |= (1 << fpr);
|
||||
}
|
||||
}
|
||||
return(compact);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_w32_read_x64_regs(HANDLE thread, SYMS_RegX64 *dst)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
// NOTE(allen): Check available features
|
||||
U32 feature_mask = GetEnabledXStateFeatures();
|
||||
B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX);
|
||||
|
||||
// NOTE(allen): Setup the context
|
||||
CONTEXT *ctx = 0;
|
||||
U32 ctx_flags = DEMON_W32_CTX_X64_ALL;
|
||||
if (avx_enabled){
|
||||
ctx_flags |= DEMON_W32_CTX_INTEL_XSTATE;
|
||||
}
|
||||
DWORD size = 0;
|
||||
InitializeContext(0, ctx_flags, 0, &size);
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER){
|
||||
void *ctx_memory = push_array(scratch.arena, U8, size);
|
||||
if (!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)){
|
||||
ctx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
B32 avx_available = false;
|
||||
|
||||
if (ctx != 0){
|
||||
// NOTE(allen): Finish Context Setup
|
||||
if (avx_enabled){
|
||||
SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX);
|
||||
}
|
||||
|
||||
// NOTE(allen): Determine what features are available on this particular ctx
|
||||
// TODO(allen): Experiment carefully with this nonsense.
|
||||
// Does avx_enabled = avx_available in all circumstances or not?
|
||||
DWORD64 xstate_flags = 0;
|
||||
if (GetXStateFeaturesMask(ctx, &xstate_flags)){
|
||||
if (xstate_flags & XSTATE_MASK_AVX){
|
||||
avx_available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get thread context
|
||||
HANDLE thread_handle = thread;
|
||||
if (!GetThreadContext(thread_handle, ctx)){
|
||||
ctx = 0;
|
||||
}
|
||||
|
||||
B32 result = false;
|
||||
if (ctx != 0){
|
||||
result = true;
|
||||
|
||||
// NOTE(allen): Convert CONTEXT -> SYMS_RegX64
|
||||
dst->rax.u64 = ctx->Rax;
|
||||
dst->rcx.u64 = ctx->Rcx;
|
||||
dst->rdx.u64 = ctx->Rdx;
|
||||
dst->rbx.u64 = ctx->Rbx;
|
||||
dst->rsp.u64 = ctx->Rsp;
|
||||
dst->rbp.u64 = ctx->Rbp;
|
||||
dst->rsi.u64 = ctx->Rsi;
|
||||
dst->rdi.u64 = ctx->Rdi;
|
||||
dst->r8.u64 = ctx->R8;
|
||||
dst->r9.u64 = ctx->R9;
|
||||
dst->r10.u64 = ctx->R10;
|
||||
dst->r11.u64 = ctx->R11;
|
||||
dst->r12.u64 = ctx->R12;
|
||||
dst->r13.u64 = ctx->R13;
|
||||
dst->r14.u64 = ctx->R14;
|
||||
dst->r15.u64 = ctx->R15;
|
||||
dst->rip.u64 = ctx->Rip;
|
||||
dst->cs.u16 = ctx->SegCs;
|
||||
dst->ds.u16 = ctx->SegDs;
|
||||
dst->es.u16 = ctx->SegEs;
|
||||
dst->fs.u16 = ctx->SegFs;
|
||||
dst->gs.u16 = ctx->SegGs;
|
||||
dst->ss.u16 = ctx->SegSs;
|
||||
dst->dr0.u32 = ctx->Dr0;
|
||||
dst->dr1.u32 = ctx->Dr1;
|
||||
dst->dr2.u32 = ctx->Dr2;
|
||||
dst->dr3.u32 = ctx->Dr3;
|
||||
dst->dr6.u32 = ctx->Dr6;
|
||||
dst->dr7.u32 = ctx->Dr7;
|
||||
|
||||
// NOTE(allen): This bit is "supposed to always be 1" I guess.
|
||||
// TODO(allen): Not sure what this is all about but I haven't investigated it yet.
|
||||
// This might be totally not necessary or something.
|
||||
dst->rflags.u64 = ctx->EFlags | 0x2;
|
||||
|
||||
XSAVE_FORMAT *xsave = &ctx->FltSave;
|
||||
dst->fcw.u16 = xsave->ControlWord;
|
||||
dst->fsw.u16 = xsave->StatusWord;
|
||||
dst->ftw.u16 = test_w32_real_tag_word_from_xsave(xsave);
|
||||
dst->fop.u16 = xsave->ErrorOpcode;
|
||||
dst->fcs.u16 = xsave->ErrorSelector;
|
||||
dst->fds.u16 = xsave->DataSelector;
|
||||
dst->fip.u32 = xsave->ErrorOffset;
|
||||
dst->fdp.u32 = xsave->DataOffset;
|
||||
dst->mxcsr.u32 = xsave->MxCsr;
|
||||
dst->mxcsr_mask.u32 = xsave->MxCsr_Mask;
|
||||
|
||||
M128A *float_s = xsave->FloatRegisters;
|
||||
SYMS_Reg80 *float_d = &dst->fpr0;
|
||||
for (U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1){
|
||||
MemoryCopy(float_d, float_s, sizeof(*float_d));
|
||||
}
|
||||
|
||||
if (!avx_available){
|
||||
M128A *xmm_s = xsave->XmmRegisters;
|
||||
SYMS_Reg256 *xmm_d = &dst->ymm0;
|
||||
for (U32 n = 0; n < 16; n += 1, xmm_s += 1, xmm_d += 1){
|
||||
MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_s));
|
||||
}
|
||||
}
|
||||
|
||||
if (avx_available){
|
||||
DWORD part0_length = 0;
|
||||
M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length);
|
||||
DWORD part1_length = 0;
|
||||
M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length);
|
||||
Assert(part0_length == part1_length);
|
||||
|
||||
DWORD count = part0_length/sizeof(part0[0]);
|
||||
count = ClampTop(count, 16);
|
||||
SYMS_Reg256 *ymm_d = &dst->ymm0;
|
||||
for (DWORD i = 0;
|
||||
i < count;
|
||||
i += 1, part0 += 1, part1 += 1, ymm_d += 1){
|
||||
// TODO(allen): Are we writing these out in the right order? Seems weird right?
|
||||
ymm_d->u64[3] = part0->Low;
|
||||
ymm_d->u64[2] = part0->High;
|
||||
ymm_d->u64[1] = part1->Low;
|
||||
ymm_d->u64[0] = part1->High;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_w32_write_x64_regs(HANDLE thread, SYMS_RegX64 *src)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
// NOTE(allen): Check available features
|
||||
U32 feature_mask = GetEnabledXStateFeatures();
|
||||
B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX);
|
||||
|
||||
// NOTE(allen): Setup the context
|
||||
CONTEXT *ctx = 0;
|
||||
U32 ctx_flags = DEMON_W32_CTX_X64_ALL;
|
||||
if (avx_enabled){
|
||||
ctx_flags |= DEMON_W32_CTX_INTEL_XSTATE;
|
||||
}
|
||||
DWORD size = 0;
|
||||
InitializeContext(0, ctx_flags, 0, &size);
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER){
|
||||
void *ctx_memory = push_array(scratch.arena, U8, size);
|
||||
if (!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)){
|
||||
ctx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
B32 avx_available = false;
|
||||
|
||||
if (ctx != 0){
|
||||
// NOTE(allen): Finish Context Setup
|
||||
if (avx_enabled){
|
||||
SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX);
|
||||
}
|
||||
|
||||
// NOTE(allen): Determine what features are available on this particular ctx
|
||||
// TODO(allen): Experiment carefully with this nonsense.
|
||||
// Does avx_enabled = avx_available in all circumstances or not?
|
||||
DWORD64 xstate_flags = 0;
|
||||
if (GetXStateFeaturesMask(ctx, &xstate_flags)){
|
||||
if (xstate_flags & XSTATE_MASK_AVX){
|
||||
avx_available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
B32 result = false;
|
||||
if (ctx != 0){
|
||||
// NOTE(allen): Convert SYMS_RegX64 -> CONTEXT
|
||||
ctx->ContextFlags = ctx_flags;
|
||||
|
||||
ctx->MxCsr = src->mxcsr.u32 & src->mxcsr_mask.u32;
|
||||
|
||||
ctx->Rax = src->rax.u64;
|
||||
ctx->Rcx = src->rcx.u64;
|
||||
ctx->Rdx = src->rdx.u64;
|
||||
ctx->Rbx = src->rbx.u64;
|
||||
ctx->Rsp = src->rsp.u64;
|
||||
ctx->Rbp = src->rbp.u64;
|
||||
ctx->Rsi = src->rsi.u64;
|
||||
ctx->Rdi = src->rdi.u64;
|
||||
ctx->R8 = src->r8.u64;
|
||||
ctx->R9 = src->r9.u64;
|
||||
ctx->R10 = src->r10.u64;
|
||||
ctx->R11 = src->r11.u64;
|
||||
ctx->R12 = src->r12.u64;
|
||||
ctx->R13 = src->r13.u64;
|
||||
ctx->R14 = src->r14.u64;
|
||||
ctx->R15 = src->r15.u64;
|
||||
ctx->Rip = src->rip.u64;
|
||||
ctx->SegCs = src->cs.u16;
|
||||
ctx->SegDs = src->ds.u16;
|
||||
ctx->SegEs = src->es.u16;
|
||||
ctx->SegFs = src->fs.u16;
|
||||
ctx->SegGs = src->gs.u16;
|
||||
ctx->SegSs = src->ss.u16;
|
||||
ctx->Dr0 = src->dr0.u32;
|
||||
ctx->Dr1 = src->dr1.u32;
|
||||
ctx->Dr2 = src->dr2.u32;
|
||||
ctx->Dr3 = src->dr3.u32;
|
||||
ctx->Dr6 = src->dr6.u32;
|
||||
ctx->Dr7 = src->dr7.u32;
|
||||
|
||||
ctx->EFlags = src->rflags.u64;
|
||||
|
||||
XSAVE_FORMAT *fxsave = &ctx->FltSave;
|
||||
fxsave->ControlWord = src->fcw.u16;
|
||||
fxsave->StatusWord = src->fsw.u16;
|
||||
fxsave->TagWord = test_w32_xsave_tag_word_from_real_tag_word(src->ftw.u16);
|
||||
fxsave->ErrorOpcode = src->fop.u16;
|
||||
fxsave->ErrorSelector = src->fcs.u16;
|
||||
fxsave->DataSelector = src->fds.u16;
|
||||
fxsave->ErrorOffset = src->fip.u32;
|
||||
fxsave->DataOffset = src->fdp.u32;
|
||||
|
||||
M128A *float_d = fxsave->FloatRegisters;
|
||||
SYMS_Reg80 *float_s = &src->fpr0;
|
||||
for (U32 n = 0;
|
||||
n < 8;
|
||||
n += 1, float_s += 1, float_d += 1){
|
||||
MemoryCopy(float_d, float_s, 10);
|
||||
}
|
||||
|
||||
if (!avx_available){
|
||||
M128A *xmm_d = fxsave->XmmRegisters;
|
||||
SYMS_Reg256 *xmm_s = &src->ymm0;
|
||||
for (U32 n = 0;
|
||||
n < 8;
|
||||
n += 1, xmm_d += 1, xmm_s += 1){
|
||||
MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_d));
|
||||
}
|
||||
}
|
||||
|
||||
if (avx_available){
|
||||
DWORD part0_length = 0;
|
||||
M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length);
|
||||
DWORD part1_length = 0;
|
||||
M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length);
|
||||
Assert(part0_length == part1_length);
|
||||
|
||||
DWORD count = part0_length/sizeof(part0[0]);
|
||||
count = ClampTop(count, 16);
|
||||
SYMS_Reg256 *ymm_d = &src->ymm0;
|
||||
for (DWORD i = 0;
|
||||
i < count;
|
||||
i += 1, part0 += 1, part1 += 1, ymm_d += 1){
|
||||
// TODO(allen): Are we writing these out in the right order? Seems weird right?
|
||||
part0->Low = ymm_d->u64[3];
|
||||
part0->High = ymm_d->u64[2];
|
||||
part1->Low = ymm_d->u64[1];
|
||||
part1->High = ymm_d->u64[0];
|
||||
}
|
||||
}
|
||||
|
||||
//- set thread context
|
||||
HANDLE thread_handle = thread;
|
||||
if (SetThreadContext(thread_handle, ctx)){
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_w32_read_memory(HANDLE process_handle, void *dst, U64 src_address, U64 size)
|
||||
{
|
||||
B32 result = true;
|
||||
U8 *ptr = (U8*)dst;
|
||||
U8 *opl = ptr + size;
|
||||
U64 cursor = src_address;
|
||||
for (;ptr < opl;){
|
||||
SIZE_T to_read = (SIZE_T)(opl - ptr);
|
||||
SIZE_T actual_read = 0;
|
||||
if (!ReadProcessMemory(process_handle, (LPCVOID)cursor, ptr, to_read, &actual_read)){
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
ptr += actual_read;
|
||||
cursor += actual_read;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_w32_write_memory(HANDLE process_handle, U64 dst_address, void *src, U64 size)
|
||||
{
|
||||
B32 result = true;
|
||||
U8 *ptr = (U8*)src;
|
||||
U8 *opl = ptr + size;
|
||||
U64 cursor = dst_address;
|
||||
for (;ptr < opl;){
|
||||
SIZE_T to_write = (SIZE_T)(opl - ptr);
|
||||
SIZE_T actual_write = 0;
|
||||
if (!WriteProcessMemory(process_handle, (LPVOID)cursor, ptr, to_write, &actual_write)){
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
ptr += actual_write;
|
||||
cursor += actual_write;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal B32
|
||||
test_launch_process(OS_LaunchOptions *options)
|
||||
{
|
||||
B32 result = false;
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
StringJoin join_params = {0};
|
||||
join_params.pre = str8_lit("\"");
|
||||
join_params.sep = str8_lit("\" \"");
|
||||
join_params.post = str8_lit("\"");
|
||||
String8 cmd = str8_list_join(scratch.arena, &options->cmd_line, &join_params);
|
||||
|
||||
StringJoin join_params2 = {0};
|
||||
join_params2.sep = str8_lit("\0");
|
||||
join_params2.post = str8_lit("\0");
|
||||
String8 env = str8_list_join(scratch.arena, &options->env, &join_params2);
|
||||
|
||||
String16 cmd16 = str16_from_8(scratch.arena, cmd);
|
||||
String16 dir16 = str16_from_8(scratch.arena, options->path);
|
||||
String16 env16 = str16_from_8(scratch.arena, env);
|
||||
|
||||
DWORD access_flags = PROCESS_QUERY_INFORMATION | DEBUG_PROCESS | PROCESS_VM_READ | PROCESS_VM_WRITE;
|
||||
STARTUPINFOW startup_info = {sizeof(startup_info)};
|
||||
PROCESS_INFORMATION process_info = {0};
|
||||
if (CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 0, access_flags, (WCHAR*)env16.str, (WCHAR*)dir16.str,
|
||||
&startup_info, &process_info))
|
||||
{
|
||||
CloseHandle(process_info.hProcess);
|
||||
CloseHandle(process_info.hThread);
|
||||
result = true;
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return(result);
|
||||
}
|
||||
|
||||
global HANDLE g_process = 0;
|
||||
global DWORD g_process_id = 0;
|
||||
global HANDLE g_thread1 = 0;
|
||||
global DWORD g_thread1_id = 0;
|
||||
global HANDLE g_thread2 = 0;
|
||||
global DWORD g_thread2_id = 0;
|
||||
|
||||
internal TEST_DebugEvent
|
||||
test_run_process(HANDLE step_thread, HANDLE suspend_thread, U64 traps_count, TEST_Trap *traps)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
TEST_DebugEvent result = {0};
|
||||
|
||||
//- rjf: freeze thread
|
||||
if(suspend_thread)
|
||||
{
|
||||
DWORD result = SuspendThread(suspend_thread);
|
||||
DWORD error = GetLastError();
|
||||
int x = 0;
|
||||
}
|
||||
|
||||
//- rjf: write traps
|
||||
U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, traps_count);
|
||||
{
|
||||
TEST_Trap *trap = traps;
|
||||
for(U64 i = 0; i < traps_count; i += 1, trap += 1)
|
||||
{
|
||||
if(test_w32_read_memory(trap->process, trap_swap_bytes + i, trap->address, 1))
|
||||
{
|
||||
U8 int3 = 0xCC;
|
||||
test_w32_write_memory(trap->process, trap->address, &int3, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
trap_swap_bytes[i] = 0xCC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: set single step bit
|
||||
if(step_thread != 0)
|
||||
{
|
||||
SYMS_RegX64 regs = {0};
|
||||
test_w32_read_x64_regs(step_thread, ®s);
|
||||
regs.rflags.u64 |= 0x100;
|
||||
test_w32_write_x64_regs(step_thread, ®s);
|
||||
}
|
||||
|
||||
//- rjf: continue
|
||||
local_persist B32 need_resume = 0;
|
||||
local_persist DWORD resume_pid = 0;
|
||||
local_persist DWORD resume_tid = 0;
|
||||
|
||||
if(need_resume)
|
||||
{
|
||||
need_resume = 0;
|
||||
ContinueDebugEvent(resume_pid, resume_tid, DBG_CONTINUE);
|
||||
}
|
||||
|
||||
//- rjf: get event
|
||||
DEBUG_EVENT evt = {0};
|
||||
if(WaitForDebugEvent(&evt, INFINITE))
|
||||
{
|
||||
need_resume = 1;
|
||||
resume_pid = evt.dwProcessId;
|
||||
resume_tid = evt.dwThreadId;
|
||||
result.evt = evt;
|
||||
|
||||
switch(evt.dwDebugEventCode)
|
||||
{
|
||||
default:break;
|
||||
|
||||
case CREATE_PROCESS_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("create process");
|
||||
result.process_id = evt.dwProcessId;
|
||||
result.process = evt.u.CreateProcessInfo.hProcess;
|
||||
result.thread_id = evt.dwThreadId;
|
||||
result.thread = evt.u.CreateProcessInfo.hThread;
|
||||
if(g_process == 0)
|
||||
{
|
||||
g_process = result.process;
|
||||
g_process_id = result.process_id;
|
||||
}
|
||||
if(g_thread1 == 0)
|
||||
{
|
||||
g_thread1 = result.thread;
|
||||
g_thread1_id = result.thread_id;
|
||||
}
|
||||
}break;
|
||||
|
||||
case EXIT_PROCESS_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("exit process");
|
||||
result.process_id = evt.dwProcessId;
|
||||
}break;
|
||||
|
||||
case CREATE_THREAD_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("create thread");
|
||||
result.thread_id = evt.dwThreadId;
|
||||
result.thread = evt.u.CreateThread.hThread;
|
||||
g_thread2 = result.thread;
|
||||
g_thread2_id = result.thread_id;
|
||||
}break;
|
||||
|
||||
case EXIT_THREAD_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("exit thread");
|
||||
result.thread_id = evt.dwThreadId;
|
||||
}break;
|
||||
|
||||
case LOAD_DLL_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("load dll");
|
||||
}break;
|
||||
|
||||
case UNLOAD_DLL_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("unload dll");
|
||||
}break;
|
||||
|
||||
case EXCEPTION_DEBUG_EVENT:
|
||||
{
|
||||
result.name = str8_lit("exception");
|
||||
|
||||
EXCEPTION_DEBUG_INFO *edi = &evt.u.Exception;
|
||||
EXCEPTION_RECORD *exception = &edi->ExceptionRecord;
|
||||
|
||||
switch(exception->ExceptionCode)
|
||||
{
|
||||
case DEMON_W32_EXCEPTION_BREAKPOINT:
|
||||
{
|
||||
result.name = str8_lit("breakpoint");
|
||||
result.addr = (U64)exception->ExceptionAddress;
|
||||
|
||||
local_persist B32 did_first_bp = 0;
|
||||
if(did_first_bp != 0)
|
||||
{
|
||||
HANDLE thread = evt.dwThreadId == g_thread1_id ? g_thread1 : g_thread2;
|
||||
SYMS_RegX64 regs = {0};
|
||||
test_w32_read_x64_regs(thread, ®s);
|
||||
regs.rip.u64 = result.addr;
|
||||
test_w32_write_x64_regs(thread, ®s);
|
||||
}
|
||||
did_first_bp = 1;
|
||||
|
||||
}break;
|
||||
|
||||
case DEMON_W32_EXCEPTION_SINGLE_STEP:
|
||||
{
|
||||
result.name = str8_lit("single_step");
|
||||
}break;
|
||||
|
||||
case DEMON_W32_EXCEPTION_THROW:
|
||||
{
|
||||
result.name = str8_lit("exception throw");
|
||||
}break;
|
||||
|
||||
case DEMON_W32_EXCEPTION_ACCESS_VIOLATION:
|
||||
case DEMON_W32_EXCEPTION_IN_PAGE_ERROR:
|
||||
{
|
||||
result.name = str8_lit("exception access violation");
|
||||
}break;
|
||||
|
||||
default:
|
||||
{
|
||||
}break;
|
||||
}
|
||||
|
||||
}break;
|
||||
|
||||
case OUTPUT_DEBUG_STRING_EVENT:
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
result.name = str8_lit("output debug string");
|
||||
|
||||
U64 string_address = (U64)evt.u.DebugString.lpDebugStringData;
|
||||
U64 string_size = (U64)evt.u.DebugString.nDebugStringLength;
|
||||
|
||||
// TODO(allen): is the string in UTF-8 or UTF-16?
|
||||
|
||||
U8 *buffer = push_array_no_zero(scratch.arena, U8, string_size + 1);
|
||||
test_w32_read_memory(g_process, buffer, string_address, string_size);
|
||||
buffer[string_size] = 0;
|
||||
|
||||
printf("%s\n", buffer);
|
||||
scratch_end(scratch);
|
||||
}break;
|
||||
|
||||
case RIP_EVENT:
|
||||
{
|
||||
result.name = str8_lit("rip event");
|
||||
}break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: set single step bit
|
||||
if(step_thread != 0)
|
||||
{
|
||||
SYMS_RegX64 regs = {0};
|
||||
test_w32_read_x64_regs(step_thread, ®s);
|
||||
regs.rflags.u64 &= ~0x100;
|
||||
test_w32_write_x64_regs(step_thread, ®s);
|
||||
}
|
||||
|
||||
//- rjf: unset traps
|
||||
{
|
||||
TEST_Trap *trap = traps;
|
||||
for(U64 i = 0; i < traps_count; i += 1, trap += 1)
|
||||
{
|
||||
U8 og_byte = trap_swap_bytes[i];
|
||||
if(og_byte != 0xCC)
|
||||
{
|
||||
test_w32_write_memory(trap->process, trap->address, &og_byte, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: check for more events
|
||||
for(int i = 0; i < 100; i += 1)
|
||||
{
|
||||
DEBUG_EVENT evt = {0};
|
||||
if(WaitForDebugEvent(&evt, 0))
|
||||
{
|
||||
int x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: resume thread
|
||||
if(suspend_thread)
|
||||
{
|
||||
ResumeThread(suspend_thread);
|
||||
}
|
||||
|
||||
scratch_end(scratch);
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argument_count, char **arguments)
|
||||
{
|
||||
os_init(argument_count, arguments);
|
||||
Arena *arena = arena_alloc();
|
||||
|
||||
U64 before_loop_stop_vaddr = 0x0000000140001089;
|
||||
U64 inner_loop_stop_vaddr = 0x0000000140001098;
|
||||
|
||||
// rjf: launch
|
||||
{
|
||||
OS_LaunchOptions opts = {0};
|
||||
opts.path = os_get_path(arena, OS_SystemPath_Current);
|
||||
str8_list_push(arena, &opts.cmd_line, str8_lit("R:\\projects\\debugger\\build\\mule_loop_threads_win32.exe"));
|
||||
B32 launch_good = test_launch_process(&opts);
|
||||
int x = 0;
|
||||
}
|
||||
|
||||
// rjf: get process/thread handles
|
||||
HANDLE process = 0;
|
||||
HANDLE thread1 = 0;
|
||||
U64 thread1_id = 0;
|
||||
{
|
||||
for(TEST_DebugEvent evt = {0};;)
|
||||
{
|
||||
evt = test_run_process(0, 0, 0, 0);
|
||||
if(evt.process)
|
||||
{
|
||||
process = evt.process;
|
||||
}
|
||||
if(evt.thread)
|
||||
{
|
||||
thread1 = evt.thread;
|
||||
thread1_id = evt.thread_id;
|
||||
}
|
||||
if(process != 0 && thread1 != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rjf: get first breakpoint
|
||||
{
|
||||
for(TEST_DebugEvent evt = {0};;)
|
||||
{
|
||||
evt = test_run_process(0, 0, 0, 0);
|
||||
if(evt.evt.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: run until 2nd thread starts up
|
||||
HANDLE thread2 = 0;
|
||||
U64 thread2_id = 0;
|
||||
{
|
||||
TEST_Trap traps[] =
|
||||
{
|
||||
{process, before_loop_stop_vaddr},
|
||||
};
|
||||
int trap_count = 0; //ArrayCount(traps);
|
||||
for(TEST_DebugEvent evt = {0};;)
|
||||
{
|
||||
evt = test_run_process(0, 0, trap_count, traps);
|
||||
if(str8_match(evt.name, str8_lit("breakpoint"), 0))
|
||||
{
|
||||
trap_count = 0;
|
||||
}
|
||||
if(evt.thread)
|
||||
{
|
||||
thread2 = evt.thread;
|
||||
thread2_id = evt.thread_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//- rjf: wait for bps
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
TEST_Trap traps[] =
|
||||
{
|
||||
{process, 0x0000000140001098},
|
||||
{process, 0x00000001400010fb},
|
||||
{process, 0x00000001400010bc},
|
||||
{process, 0x00000001400010d7},
|
||||
};
|
||||
|
||||
for(int i = 0;; i += 1)
|
||||
{
|
||||
TEST_DebugEvent evt = test_run_process(0, 0, 1, &traps[i % ArrayCount(traps)]);
|
||||
|
||||
// rjf: check regs
|
||||
{
|
||||
U64 rip = 0;
|
||||
SYMS_RegX64 *regs = push_array(scratch.arena, SYMS_RegX64, 1);
|
||||
if(evt.evt.dwThreadId == g_thread1_id && test_w32_read_x64_regs(thread1, regs))
|
||||
{
|
||||
rip = regs->rip.u64;
|
||||
}
|
||||
if(evt.evt.dwThreadId == g_thread2_id && test_w32_read_x64_regs(thread2, regs))
|
||||
{
|
||||
rip = regs->rip.u64;
|
||||
}
|
||||
|
||||
for(int i = 0; i < ArrayCount(traps); i += 1)
|
||||
{
|
||||
if(traps[i].address == rip)
|
||||
{
|
||||
printf("WRONG BP! 0x%I64x\n", rip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(str8_match(evt.name, str8_lit("breakpoint"), 0))
|
||||
{
|
||||
HANDLE step = 0;
|
||||
HANDLE suspend = 0;
|
||||
step = evt.evt.dwThreadId == thread2_id ? thread2 : thread1;
|
||||
suspend = step == thread2 ? thread1 : thread2;
|
||||
evt = test_run_process(step, suspend, 0, 0);
|
||||
}
|
||||
}
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,378 @@
|
||||
// Copyright (c) 2024 Epic Games Tools
|
||||
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
||||
|
||||
#ifndef DEMON_OS_WIN32_H
|
||||
#define DEMON_OS_WIN32_H
|
||||
|
||||
////////////////////////////////
|
||||
//~ NOTE(allen): Win32 Demon Headers Negotation
|
||||
|
||||
// windows headers
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <tlhelp32.h>
|
||||
|
||||
////////////////////////////////
|
||||
//~ NOTE(allen): Win32 Demon Types
|
||||
|
||||
//- entities
|
||||
|
||||
// Demon Win32 Entity Extensions
|
||||
// Process: ext points to independently allocated DEMON_W32_Ext
|
||||
// Thread : ext points to independently allocated DEMON_W32_Ext
|
||||
// Module : ext set to HANDLE
|
||||
|
||||
typedef union DEMON_W32_Ext DEMON_W32_Ext;
|
||||
union DEMON_W32_Ext
|
||||
{
|
||||
DEMON_W32_Ext *next;
|
||||
struct{
|
||||
HANDLE handle;
|
||||
U64 injection_address;
|
||||
B32 did_first_bp;
|
||||
} proc;
|
||||
struct{
|
||||
HANDLE handle;
|
||||
U64 thread_local_base;
|
||||
} thread;
|
||||
struct{
|
||||
HANDLE handle;
|
||||
U64 address_of_name_pointer;
|
||||
B32 is_main;
|
||||
B32 name_is_unicode;
|
||||
} module;
|
||||
};
|
||||
|
||||
//- helpers
|
||||
|
||||
typedef struct DEMON_W32_InjectedBreak DEMON_W32_InjectedBreak;
|
||||
struct DEMON_W32_InjectedBreak
|
||||
{
|
||||
U64 code;
|
||||
U64 user_data;
|
||||
};
|
||||
#define DEMON_W32_INJECTED_CODE_SIZE 32
|
||||
|
||||
typedef struct DEMON_W32_ImageInfo DEMON_W32_ImageInfo;
|
||||
struct DEMON_W32_ImageInfo
|
||||
{
|
||||
Architecture arch;
|
||||
U32 size;
|
||||
};
|
||||
|
||||
typedef struct DEMON_W32_EntityNode DEMON_W32_EntityNode;
|
||||
struct DEMON_W32_EntityNode
|
||||
{
|
||||
DEMON_W32_EntityNode *next;
|
||||
DEMON_Entity *entity;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////
|
||||
//~ NOTE(allen): Win32 Demon Exceptions
|
||||
|
||||
#define DEMON_W32_EXCEPTION_BREAKPOINT 0x80000003u
|
||||
#define DEMON_W32_EXCEPTION_SINGLE_STEP 0x80000004u
|
||||
#define DEMON_W32_EXCEPTION_LONG_JUMP 0x80000026u
|
||||
#define DEMON_W32_EXCEPTION_ACCESS_VIOLATION 0xC0000005u
|
||||
#define DEMON_W32_EXCEPTION_ARRAY_BOUNDS_EXCEEDED 0xC000008Cu
|
||||
#define DEMON_W32_EXCEPTION_DATA_TYPE_MISALIGNMENT 0x80000002u
|
||||
#define DEMON_W32_EXCEPTION_GUARD_PAGE_VIOLATION 0x80000001u
|
||||
#define DEMON_W32_EXCEPTION_FLT_DENORMAL_OPERAND 0xC000008Du
|
||||
#define DEMON_W32_EXCEPTION_FLT_DEVIDE_BY_ZERO 0xC000008Eu
|
||||
#define DEMON_W32_EXCEPTION_FLT_INEXACT_RESULT 0xC000008Fu
|
||||
#define DEMON_W32_EXCEPTION_FLT_INVALID_OPERATION 0xC0000090u
|
||||
#define DEMON_W32_EXCEPTION_FLT_OVERFLOW 0xC0000091u
|
||||
#define DEMON_W32_EXCEPTION_FLT_STACK_CHECK 0xC0000092u
|
||||
#define DEMON_W32_EXCEPTION_FLT_UNDERFLOW 0xC0000093u
|
||||
#define DEMON_W32_EXCEPTION_INT_DIVIDE_BY_ZERO 0xC0000094u
|
||||
#define DEMON_W32_EXCEPTION_INT_OVERFLOW 0xC0000095u
|
||||
#define DEMON_W32_EXCEPTION_PRIVILEGED_INSTRUCTION 0xC0000096u
|
||||
#define DEMON_W32_EXCEPTION_ILLEGAL_INSTRUCTION 0xC000001Du
|
||||
#define DEMON_W32_EXCEPTION_IN_PAGE_ERROR 0xC0000006u
|
||||
#define DEMON_W32_EXCEPTION_INVALID_DISPOSITION 0xC0000026u
|
||||
#define DEMON_W32_EXCEPTION_NONCONTINUABLE 0xC0000025u
|
||||
#define DEMON_W32_EXCEPTION_STACK_OVERFLOW 0xC00000FDu
|
||||
#define DEMON_W32_EXCEPTION_INVALID_HANDLE 0xC0000008u
|
||||
#define DEMON_W32_EXCEPTION_UNWIND_CONSOLIDATE 0x80000029u
|
||||
#define DEMON_W32_EXCEPTION_DLL_NOT_FOUND 0xC0000135u
|
||||
#define DEMON_W32_EXCEPTION_ORDINAL_NOT_FOUND 0xC0000138u
|
||||
#define DEMON_W32_EXCEPTION_ENTRY_POINT_NOT_FOUND 0xC0000139u
|
||||
#define DEMON_W32_EXCEPTION_DLL_INIT_FAILED 0xC0000142u
|
||||
#define DEMON_W32_EXCEPTION_CONTROL_C_EXIT 0xC000013Au
|
||||
#define DEMON_W32_EXCEPTION_FLT_MULTIPLE_FAULTS 0xC00002B4u
|
||||
#define DEMON_W32_EXCEPTION_FLT_MULTIPLE_TRAPS 0xC00002B5u
|
||||
#define DEMON_W32_EXCEPTION_NAT_CONSUMPTION 0xC00002C9u
|
||||
#define DEMON_W32_EXCEPTION_HEAP_CORRUPTION 0xC0000374u
|
||||
#define DEMON_W32_EXCEPTION_STACK_BUFFER_OVERRUN 0xC0000409u
|
||||
#define DEMON_W32_EXCEPTION_INVALID_CRUNTIME_PARAM 0xC0000417u
|
||||
#define DEMON_W32_EXCEPTION_ASSERT_FAILURE 0xC0000420u
|
||||
#define DEMON_W32_EXCEPTION_NO_MEMORY 0xC0000017u
|
||||
#define DEMON_W32_EXCEPTION_THROW 0xE06D7363u
|
||||
#define DEMON_W32_EXCEPTION_SET_THREAD_NAME 0x406d1388u
|
||||
|
||||
////////////////////////////////
|
||||
//~ NOTE(allen): Win32 Demon Register API Codes
|
||||
|
||||
#define DEMON_W32_CTX_X86 0x00010000
|
||||
#define DEMON_W32_CTX_X64 0x00100000
|
||||
|
||||
#define DEMON_W32_CTX_INTEL_CONTROL 0x0001
|
||||
#define DEMON_W32_CTX_INTEL_INTEGER 0x0002
|
||||
#define DEMON_W32_CTX_INTEL_SEGMENTS 0x0004
|
||||
#define DEMON_W32_CTX_INTEL_FLOATS 0x0008
|
||||
#define DEMON_W32_CTX_INTEL_DEBUG 0x0010
|
||||
#define DEMON_W32_CTX_INTEL_EXTENDED 0x0020
|
||||
#define DEMON_W32_CTX_INTEL_XSTATE 0x0040
|
||||
|
||||
#define DEMON_W32_CTX_X86_ALL (DEMON_W32_CTX_X86 | \
|
||||
DEMON_W32_CTX_INTEL_CONTROL | DEMON_W32_CTX_INTEL_INTEGER | \
|
||||
DEMON_W32_CTX_INTEL_SEGMENTS | DEMON_W32_CTX_INTEL_DEBUG | \
|
||||
DEMON_W32_CTX_INTEL_EXTENDED)
|
||||
#define DEMON_W32_CTX_X64_ALL (DEMON_W32_CTX_X64 | \
|
||||
DEMON_W32_CTX_INTEL_CONTROL | DEMON_W32_CTX_INTEL_INTEGER | \
|
||||
DEMON_W32_CTX_INTEL_SEGMENTS | DEMON_W32_CTX_INTEL_FLOATS | \
|
||||
DEMON_W32_CTX_INTEL_DEBUG)
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: DOS Header Types
|
||||
|
||||
// this is the "MZ" as a 16-bit short
|
||||
#define DEMON_DOS_MAGIC 0x5a4d
|
||||
|
||||
#pragma pack(push,1)
|
||||
typedef struct DEMON_DosHeader DEMON_DosHeader;
|
||||
struct DEMON_DosHeader
|
||||
{
|
||||
U16 magic;
|
||||
U16 last_page_size;
|
||||
U16 page_count;
|
||||
U16 reloc_count;
|
||||
U16 paragraph_header_size;
|
||||
U16 min_paragraph;
|
||||
U16 max_paragraph;
|
||||
U16 init_ss;
|
||||
U16 init_sp;
|
||||
U16 checksum;
|
||||
U16 init_ip;
|
||||
U16 init_cs;
|
||||
U16 reloc_table_file_off;
|
||||
U16 overlay_number;
|
||||
U16 reserved[4];
|
||||
U16 oem_id;
|
||||
U16 oem_info;
|
||||
U16 reserved2[10];
|
||||
U32 coff_file_offset;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Coff Header Types
|
||||
|
||||
#define DEMON_PE_MAGIC 0x00004550u
|
||||
|
||||
typedef U16 DEMON_CoffMachineType;
|
||||
enum{
|
||||
DEMON_CoffMachineType_UNKNOWN = 0x0,
|
||||
DEMON_CoffMachineType_X86 = 0x14c,
|
||||
DEMON_CoffMachineType_X64 = 0x8664,
|
||||
DEMON_CoffMachineType_ARM33 = 0x1d3,
|
||||
DEMON_CoffMachineType_ARM = 0x1c0,
|
||||
DEMON_CoffMachineType_ARM64 = 0xaa64,
|
||||
DEMON_CoffMachineType_ARMNT = 0x1c4,
|
||||
DEMON_CoffMachineType_EBC = 0xebc,
|
||||
DEMON_CoffMachineType_IA64 = 0x200,
|
||||
DEMON_CoffMachineType_M32R = 0x9041,
|
||||
DEMON_CoffMachineType_MIPS16 = 0x266,
|
||||
DEMON_CoffMachineType_MIPSFPU = 0x366,
|
||||
DEMON_CoffMachineType_MIPSFPU16 = 0x466,
|
||||
DEMON_CoffMachineType_POWERPC = 0x1f0,
|
||||
DEMON_CoffMachineType_POWERPCFP = 0x1f1,
|
||||
DEMON_CoffMachineType_R4000 = 0x166,
|
||||
DEMON_CoffMachineType_RISCV32 = 0x5032,
|
||||
DEMON_CoffMachineType_RISCV64 = 0x5064,
|
||||
DEMON_CoffMachineType_RISCV128 = 0x5128,
|
||||
DEMON_CoffMachineType_SH3 = 0x1a2,
|
||||
DEMON_CoffMachineType_SH3DSP = 0x1a3,
|
||||
DEMON_CoffMachineType_SH4 = 0x1a6,
|
||||
DEMON_CoffMachineType_SH5 = 0x1a8,
|
||||
DEMON_CoffMachineType_THUMB = 0x1c2,
|
||||
DEMON_CoffMachineType_WCEMIPSV2 = 0x169,
|
||||
DEMON_CoffMachineType_COUNT = 25
|
||||
};
|
||||
|
||||
typedef U16 DEMON_CoffFlags;
|
||||
enum{
|
||||
DEMON_CoffFlag_RELOC_STRIPPED = (1 << 0),
|
||||
DEMON_CoffFlag_EXECUTABLE_IMAGE = (1 << 1),
|
||||
DEMON_CoffFlag_LINE_NUMS_STRIPPED = (1 << 2),
|
||||
DEMON_CoffFlag_SYM_STRIPPED = (1 << 3),
|
||||
DEMON_CoffFlag_RESERVED_0 = (1 << 4),
|
||||
DEMON_CoffFlag_LARGE_ADDRESS_AWARE = (1 << 5),
|
||||
DEMON_CoffFlag_RESERVED_1 = (1 << 6),
|
||||
DEMON_CoffFlag_RESERVED_2 = (1 << 7),
|
||||
DEMON_CoffFlag_32BIT_MACHINE = (1 << 8),
|
||||
DEMON_CoffFlag_DEBUG_STRIPPED = (1 << 9),
|
||||
DEMON_CoffFlag_REMOVABLE_RUN_FROM_SWAP = (1 << 10),
|
||||
DEMON_CoffFlag_NET_RUN_FROM_SWAP = (1 << 11),
|
||||
DEMON_CoffFlag_SYSTEM = (1 << 12),
|
||||
DEMON_CoffFlag_DLL = (1 << 13),
|
||||
DEMON_CoffFlag_UP_SYSTEM_ONLY = (1 << 14),
|
||||
DEMON_CoffFlag_BYTES_RESERVED_HI = (1 << 15),
|
||||
};
|
||||
|
||||
#pragma pack(push,1)
|
||||
typedef struct DEMON_CoffHeader DEMON_CoffHeader;
|
||||
struct DEMON_CoffHeader
|
||||
{
|
||||
DEMON_CoffMachineType machine;
|
||||
U16 section_count;
|
||||
U32 time_date_stamp;
|
||||
// TODO: rename to "unix_timestamp"
|
||||
U32 pointer_to_symbol_table;
|
||||
U32 number_of_symbols;
|
||||
// TODO: rename to "symbol_count"
|
||||
U16 size_of_optional_header;
|
||||
// TODO: rename to "optional_header_size"
|
||||
DEMON_CoffFlags flags;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: PE Header Types
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef U16 DEMON_PeWindowsSubsystem;
|
||||
enum{
|
||||
DEMON_PeWindowsSubsystem_UNKNOWN = 0,
|
||||
DEMON_PeWindowsSubsystem_NATIVE = 1,
|
||||
DEMON_PeWindowsSubsystem_WINDOWS_GUI = 2,
|
||||
DEMON_PeWindowsSubsystem_WINDOWS_CUI = 3,
|
||||
DEMON_PeWindowsSubsystem_OS2_CUI = 5,
|
||||
DEMON_PeWindowsSubsystem_POSIX_CUI = 7,
|
||||
DEMON_PeWindowsSubsystem_NATIVE_WINDOWS = 8,
|
||||
DEMON_PeWindowsSubsystem_WINDOWS_CE_GUI = 9,
|
||||
DEMON_PeWindowsSubsystem_EFI_APPLICATION = 10,
|
||||
DEMON_PeWindowsSubsystem_EFI_BOOT_SERVICE_DRIVER = 11,
|
||||
DEMON_PeWindowsSubsystem_EFI_RUNTIME_DRIVER = 12,
|
||||
DEMON_PeWindowsSubsystem_EFI_ROM = 13,
|
||||
DEMON_PeWindowsSubsystem_XBOX = 14,
|
||||
DEMON_PeWindowsSubsystem_WINDOWS_BOOT_APPLICATION = 16,
|
||||
DEMON_PeWindowsSubsystem_COUNT = 14
|
||||
};
|
||||
|
||||
typedef U16 DEMON_DllCharacteristics;
|
||||
enum{
|
||||
DEMON_DllCharacteristic_HIGH_ENTROPY_VA = (1 << 5),
|
||||
DEMON_DllCharacteristic_DYNAMIC_BASE = (1 << 6),
|
||||
DEMON_DllCharacteristic_FORCE_INTEGRITY = (1 << 7),
|
||||
DEMON_DllCharacteristic_NX_COMPAT = (1 << 8),
|
||||
DEMON_DllCharacteristic_NO_ISOLATION = (1 << 9),
|
||||
DEMON_DllCharacteristic_NO_SEH = (1 << 10),
|
||||
DEMON_DllCharacteristic_NO_BIND = (1 << 11),
|
||||
DEMON_DllCharacteristic_APPCONTAINER = (1 << 12),
|
||||
DEMON_DllCharacteristic_WDM_DRIVER = (1 << 13),
|
||||
DEMON_DllCharacteristic_GUARD_CF = (1 << 14),
|
||||
DEMON_DllCharacteristic_TERMINAL_SERVER_AWARE = (1 << 15),
|
||||
};
|
||||
|
||||
typedef struct DEMON_PeOptionalHeader32 DEMON_PeOptionalHeader32;
|
||||
struct DEMON_PeOptionalHeader32
|
||||
{
|
||||
U16 magic;
|
||||
U8 major_linker_version;
|
||||
U8 minor_linker_version;
|
||||
U32 sizeof_code;
|
||||
U32 sizeof_inited_data;
|
||||
U32 sizeof_uninited_data;
|
||||
U32 entry_point_va;
|
||||
U32 code_base;
|
||||
U32 data_base;
|
||||
U32 image_base;
|
||||
U32 section_alignment;
|
||||
U32 file_alignment;
|
||||
U16 major_os_ver;
|
||||
U16 minor_os_ver;
|
||||
U16 major_img_ver;
|
||||
U16 minor_img_ver;
|
||||
U16 major_subsystem_ver;
|
||||
U16 minor_subsystem_ver;
|
||||
U32 win32_version_value;
|
||||
U32 sizeof_image;
|
||||
U32 sizeof_headers;
|
||||
U32 check_sum;
|
||||
DEMON_PeWindowsSubsystem subsystem;
|
||||
DEMON_DllCharacteristics dll_characteristics;
|
||||
U32 sizeof_stack_reserve;
|
||||
U32 sizeof_stack_commit;
|
||||
U32 sizeof_heap_reserve;
|
||||
U32 sizeof_heap_commit;
|
||||
U32 loader_flags;
|
||||
U32 data_dir_count;
|
||||
};
|
||||
|
||||
typedef struct DEMON_PeOptionalHeader32Plus DEMON_PeOptionalHeader32Plus;
|
||||
struct DEMON_PeOptionalHeader32Plus
|
||||
{
|
||||
U16 magic;
|
||||
U8 major_linker_version;
|
||||
U8 minor_linker_version;
|
||||
U32 sizeof_code;
|
||||
U32 sizeof_inited_data;
|
||||
U32 sizeof_uninited_data;
|
||||
U32 entry_point_va;
|
||||
U32 code_base;
|
||||
U64 image_base;
|
||||
U32 section_alignment;
|
||||
U32 file_alignment;
|
||||
U16 major_os_ver;
|
||||
U16 minor_os_ver;
|
||||
U16 major_img_ver;
|
||||
U16 minor_img_ver;
|
||||
U16 major_subsystem_ver;
|
||||
U16 minor_subsystem_ver;
|
||||
U32 win32_version_value;
|
||||
U32 sizeof_image;
|
||||
U32 sizeof_headers;
|
||||
U32 check_sum;
|
||||
DEMON_PeWindowsSubsystem subsystem;
|
||||
DEMON_DllCharacteristics dll_characteristics;
|
||||
U64 sizeof_stack_reserve;
|
||||
U64 sizeof_stack_commit;
|
||||
U64 sizeof_heap_reserve;
|
||||
U64 sizeof_heap_commit;
|
||||
U32 loader_flags;
|
||||
U32 data_dir_count;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Helpers
|
||||
|
||||
internal DEMON_W32_Ext* demon_w32_ext_alloc(void);
|
||||
internal DEMON_W32_Ext* demon_w32_ext(DEMON_Entity *entity);
|
||||
|
||||
internal U64 demon_w32_read_memory(HANDLE process_handle, void *dst, U64 src_address, U64 size);
|
||||
internal B32 demon_w32_write_memory(HANDLE process_handle, U64 dst_address, void *src, U64 size);
|
||||
internal String8 demon_w32_read_memory_str(Arena *arena, HANDLE process_handle, U64 address);
|
||||
internal String16 demon_w32_read_memory_str16(Arena *arena, HANDLE process_handle, U64 address);
|
||||
|
||||
#define demon_w32_read_struct(h,dst,src) demon_w32_read_memory((h), (dst), (src), sizeof(*(dst)))
|
||||
|
||||
internal DEMON_W32_ImageInfo demon_w32_image_info_from_base(HANDLE process_handle, U64 base);
|
||||
internal DWORD demon_w32_inject_thread(DEMON_Entity *process, U64 start_address);
|
||||
|
||||
internal U16 demon_w32_real_tag_word_from_xsave(XSAVE_FORMAT *fxsave);
|
||||
internal U16 demon_w32_xsave_tag_word_from_real_tag_word(U16 ftw);
|
||||
|
||||
internal DWORD demon_w32_win32_from_memory_protect_flags(DEMON_MemoryProtectFlags flags);
|
||||
|
||||
////////////////////////////////
|
||||
//~ rjf: Experiments
|
||||
|
||||
internal void demon_w32_peak_at_tls(DEMON_Handle handle);
|
||||
|
||||
#endif //DEMON_OS_WIN32_H
|
||||
Reference in New Issue
Block a user