14 changed files with 294 additions and 28 deletions
+70
View File
@@ -99,6 +99,76 @@ raddbg.cpp
If everything worked correctly, there will be a `build` folder in the root
level of the codebase, and it will contain a freshly-built `raddbg.exe`.
## Short-To-Medium-Term Roadmap
### The Initial Alpha Battle-Testing Phase
The first priority for the project is to ensure that the most crucial debugger
components are functioning extremely reliably for local, x64, Windows
debugging. This would include parts like debug info conversion, debug info
loading, process control, stepping, evaluation (correct usage of both location
info and type info), and a robust frontend which ensures the lower level parts
are usable.
We feel that the debugger has already come a long way in all of these respects,
but given the massive set of possible combinations of languages, build
settings, toolchains, used language features, and patterns of generated code,
there are still cases where the debugger has not been tested, and so there are
still issues. So, we feel that the top priority is eliminating these issues,
such that the debugging experience is rock solid.
Additionally, the debug info conversion process is not fast (nor wide) enough
to support extremely large projects. This is for two reasons: (a) the
PDB-to-RADDBG converter is an unoptimized reference implementation, and (b) the
debugger learns of new modules (and thus which PDBs to load) in a
serially-dependent way (this is necessarily the case for correct debugging
results). We expect that the conversion process' performance can be massively
improved, and also that some heuristics can be used to begin converting PDBs
to RADDBGs before the debugger knows those PDBs are needed, thus ensuring the
associated RADDBG files are ready instantaneously when the associated modules
are finally loaded by the debugger. Improving this situation is a major part of
this phase, as it will make the debugger much more usable for large projects.
### Local x64 Linux Debugging Phase
The next priority for the project is to take the rock solid x64 Windows
debugging experience, and port all of the relevant pieces to support local x64
Linux debugging also.
The debugger has been written to abstract over the parts that need to differ on
either Linux or Windows, and this is mainly going to be a task in building out
different backends for those abstraction layers.
The major parts of this phase are:
- Porting the `src/demon` layer to implement the Demon local process control
abstraction API.
- Porting the `src/unwind` layer to support x64 ELF unwinding (currently, there
is only an x64 PE unwinding implementation).
- Creating a DWARF-to-RADDBG converter (in the same way that we've built a PDB-
to-RADDBG converter). A partial implementation of this is in
`src/raddbg_convert/dwarf`.
- Porting the `src/render` layer to implement all of the rendering features the
frontend needs on a Linux-compatible API (the backend used on Windows is D3D11).
- Porting the `src/font_provider` layer to a Linux-compatible font
rasterization backend, like FreeType (the backend used on Windows is
DirectWrite).
- Porting the `src/os` layers to Linux. This includes core operating system
abstraction (virtual memory allocation, threading and synchronization
primitives, and so on), and graphical operating system abstraction (windows,
input events, and so on).
Once the above list is complete, and once every part is rock solid, the Windows
debugging experience we'll have worked diligently to create will also be
available natively on Linux machines.
### And Beyond!
There are several directions we might take after these two major phases,
like remote debugging, porting to different architectures, further improving
the debugger's features (like improving the visualization engine), and so on.
But for now, we're mostly focused on those first two phases.
## Top-Level Directory Descriptions
- `data`: Small binary files which are used when building, either to embed
+9
View File
@@ -81,6 +81,15 @@ commands =
.save_dirty_files = true,
.cursor_at_end = false,
},
.build_raddbg_dump =
{
.win = "build raddbg_dump",
.linux = "",
.out = "*compilation*",
.footer_panel = true,
.save_dirty_files = true,
.cursor_at_end = false,
},
.build_mule_main =
{
.win = "build mule_main",
+10 -2
View File
@@ -957,7 +957,9 @@ demon_os_run(Arena *arena, DEMON_OS_RunCtrls *ctrls){
}
// rjf: check if trap
B32 is_trap = (!first_bp && exception->ExceptionCode == DEMON_W32_EXCEPTION_BREAKPOINT);
B32 is_trap = (!first_bp &&
(exception->ExceptionCode == DEMON_W32_EXCEPTION_BREAKPOINT ||
exception->ExceptionCode == DEMON_W32_EXCEPTION_STACK_BUFFER_OVERRUN));
// rjf: check if this trap is currently registered
B32 hit_user_trap = 0;
@@ -979,7 +981,8 @@ demon_os_run(Arena *arena, DEMON_OS_RunCtrls *ctrls){
// TODO(rjf): x86/x64 specific check
// TODO(rjf): do we need to check to make sure the instruction
// pointer has not changed?
hit_explicit_trap = (instruction_byte == 0xCC);
hit_explicit_trap = (instruction_byte == 0xCC ||
instruction_byte == 0xCD);
}
}
@@ -1038,6 +1041,11 @@ demon_os_run(Arena *arena, DEMON_OS_RunCtrls *ctrls){
e->kind = report_event_kind;
}break;
case DEMON_W32_EXCEPTION_STACK_BUFFER_OVERRUN:
{
e->kind = DEMON_EventKind_Trap;
}break;
case DEMON_W32_EXCEPTION_SINGLE_STEP:
{
e->kind = DEMON_EventKind_SingleStep;
+17
View File
@@ -2869,6 +2869,23 @@ df_trap_net_from_thread__step_into_line(Arena *arena, DF_Entity *thread)
{
trap_addr = point->jump_dest_vaddr;
flags &= ~CTRL_TrapFlag_SingleStepAfterHit;
// rjf: read instruction one layer deep after a jump and determine if
// it is just jumping to an unconditional jump (e.g. a function
// dispatch table) - if so, then just follow one more layer
String8 dst_machine_code = {0};
dst_machine_code.str = push_array_no_zero(scratch.arena, U8, max_instruction_size_from_arch(arch));
dst_machine_code.size = ctrl_process_read(process->ctrl_machine_id, process->ctrl_handle, r1u64(trap_addr, trap_addr+max_instruction_size_from_arch(arch)), dst_machine_code.str);
if(dst_machine_code.size != 0)
{
DF_Inst inst = df_single_inst_from_machine_code(scratch.arena, arch, 0, dst_machine_code);
if((inst.flags & DF_InstFlag_UnconditionalJump ||
inst.flags & DF_InstFlag_Call) &&
inst.rel_voff != 0)
{
trap_addr = (U64)(trap_addr + (S64)((S32)inst.rel_voff));
}
}
}
}
+8 -4
View File
@@ -4990,7 +4990,8 @@ df_window_update_and_render(Arena *arena, OS_EventList *events, DF_Window *ws, D
}break;
case CTRL_EventCause_InterruptedByTrap:
{
icon = DF_IconKind_WarningBig;
explanation = push_str8f(scratch.arena, "%S interrupted by trap - 0x%x", thread_display_string, stop_event.exception_code);
}break;
case CTRL_EventCause_InterruptedByHalt:
{
@@ -11534,7 +11535,10 @@ df_gfx_begin_frame(Arena *arena, DF_CmdList *cmds)
}
//- rjf: apply keybindings
df_clear_bindings();
if(src == DF_CfgSrc_User)
{
df_clear_bindings();
}
DF_CfgVal *keybindings = df_cfg_val_from_string(table, str8_lit("keybindings"));
for(DF_CfgNode *keybinding_set = keybindings->first;
keybinding_set != &df_g_nil_cfg_node;
@@ -11649,7 +11653,7 @@ df_gfx_begin_frame(Arena *arena, DF_CmdList *cmds)
}
//- rjf: if config opened 0 windows, we need to do some sensible default
if(windows->first == &df_g_nil_cfg_node)
if(src == DF_CfgSrc_User && windows->first == &df_g_nil_cfg_node)
{
OS_Handle preferred_monitor = os_primary_monitor();
Vec2F32 monitor_dim = os_dim_from_monitor(preferred_monitor);
@@ -11660,7 +11664,7 @@ df_gfx_begin_frame(Arena *arena, DF_CmdList *cmds)
}
//- rjf: if config bound 0 keys, we need to do some sensible default
if(df_gfx_state->key_map_total_count == 0)
if(src == DF_CfgSrc_User && df_gfx_state->key_map_total_count == 0)
{
for(U64 idx = 0; idx < ArrayCount(df_g_default_binding_table); idx += 1)
{
+4 -4
View File
@@ -5153,7 +5153,7 @@ DF_VIEW_UI_FUNCTION_DEF(Code)
if(txti_buffer_is_ready) UI_Parent(container_box)
{
//- rjf: build fractional space
container_box->view_off.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
container_box->view_off.y = container_box->view_off_target.y = code_line_height*mod_f32(view->scroll_pos.y.off, 1.f) + code_line_height*(view->scroll_pos.y.off < 0) - code_line_height*(view->scroll_pos.y.off == -1.f && view->scroll_pos.y.idx == 1);
//- rjf: build code slice
@@ -6033,7 +6033,7 @@ DF_VIEW_UI_FUNCTION_DEF(Disassembly)
if(has_disasm) UI_Parent(container_box)
{
//- rjf: build fractional space
container_box->view_off.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
container_box->view_off.y = container_box->view_off_target.y = code_line_height*mod_f32(view->scroll_pos.y.off, 1.f) + code_line_height*(view->scroll_pos.y.off < 0) - code_line_height*(view->scroll_pos.y.off == -1.f && view->scroll_pos.y.idx == 1);
//- rjf: build code slice
@@ -6967,7 +6967,7 @@ DF_VIEW_UI_FUNCTION_DEF(Output)
if(txti_buffer_is_ready) UI_Parent(container_box)
{
//- rjf: build fractional space
container_box->view_off.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
container_box->view_off.y = container_box->view_off_target.y = code_line_height*mod_f32(view->scroll_pos.y.off, 1.f) + code_line_height*(view->scroll_pos.y.off < 0) - code_line_height*(view->scroll_pos.y.off == -1.f && view->scroll_pos.y.idx == 1);
//- rjf: build code slice
@@ -7723,7 +7723,7 @@ DF_VIEW_UI_FUNCTION_DEF(Memory)
UI_BoxFlag_AllowOverflowX|
UI_BoxFlag_AllowOverflowY,
"scrollable_box");
scrollable_box->view_off.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
scrollable_box->view_off.y = scrollable_box->view_off_target.y = floor_f32(row_height_px*mod_f32(view->scroll_pos.y.off, 1.f) + row_height_px*(view->scroll_pos.y.off < 0));
}
+1 -1
View File
@@ -470,7 +470,7 @@ eval_leaf_type_from_name(RADDBG_Parsed *rdbg, String8 name)
U32 *matches = raddbg_matches_from_map_node(rdbg, node, &match_count);
if(match_count != 0)
{
U32 type_node_idx = matches[match_count-1];
U32 type_node_idx = matches[0];
if(type_node_idx < rdbg->type_node_count)
{
RADDBG_TypeNode *type_node = &rdbg->type_nodes[type_node_idx];
+49 -1
View File
@@ -1317,7 +1317,53 @@ extended_type_coverage_eval_tests(void){
}
////////////////////////////////
// NOTE(allen): C Type Coverage
//~ rjf: Templated Function Eval Tests
typedef struct TemplateArg TemplateArg;
struct TemplateArg
{
int x;
int y;
int z;
float a;
float b;
float c;
char *name;
};
template<typename T> static T
templated_factorial(T t)
{
T result = t;
if(t > 1)
{
result *= templated_factorial<T>(t-1);
}
return result;
}
template<typename T> static T
compute_template_arg_info(T t)
{
int sum = t.x + t.y + t.z;
int size = sizeof(t);
float sum_f = t.a + t.b + t.c;
OutputDebugStringA(t.name);
return t;
}
static void
templated_function_eval_tests(void)
{
int int_factorial = templated_factorial<int>(10);
float float_factorial = templated_factorial<float>(10);
TemplateArg arg = {1, 2, 3, 4.f, 5.f, 6.f, "my template arg"};
compute_template_arg_info(arg);
int x = 0;
}
////////////////////////////////
//~ NOTE(allen): C Type Coverage
extern "C"{
#include "mule_c.h"
@@ -2343,6 +2389,8 @@ mule_main(int argc, char** argv){
extended_type_coverage_eval_tests();
templated_function_eval_tests();
c_type_coverage_eval_tests();
c_type_with_bitfield_usage();
+1 -1
View File
@@ -422,7 +422,7 @@
#define RADDBG_VERSION_MAJOR 0
#define RADDBG_VERSION_MINOR 9
#define RADDBG_VERSION_PATCH 1
#define RADDBG_VERSION_PATCH 3
#define RADDBG_VERSION_STRING_LITERAL Stringify(RADDBG_VERSION_MAJOR) "." Stringify(RADDBG_VERSION_MINOR) "." Stringify(RADDBG_VERSION_PATCH)
#if defined(NDEBUG)
# define RADDBG_TITLE_STRING_LITERAL "The RAD Debugger (" RADDBG_VERSION_STRING_LITERAL " ALPHA) - " __DATE__ ""
+2 -2
View File
@@ -2935,7 +2935,7 @@ str8_list_pushf(arena, &out->errors, fmt, __VA_ARGS__);\
if (tpi != 0) ProfScope("parse tpi hash"){
String8 hash_data = msf_data_from_stream(msf, tpi->hash_sn);
String8 aux_data = msf_data_from_stream(msf, tpi->hash_sn_aux);
tpi_hash = pdb_tpi_hash_from_data(arena, tpi, hash_data, aux_data);
tpi_hash = pdb_tpi_hash_from_data(arena, strtbl, tpi, hash_data, aux_data);
PARSE_CHECK_ERROR(tpi_hash, "TPI hash table");
}
@@ -2954,7 +2954,7 @@ str8_list_pushf(arena, &out->errors, fmt, __VA_ARGS__);\
if (ipi != 0) ProfScope("parse ipi hash"){
String8 hash_data = msf_data_from_stream(msf, ipi->hash_sn);
String8 aux_data = msf_data_from_stream(msf, ipi->hash_sn_aux);
ipi_hash = pdb_tpi_hash_from_data(arena, ipi, hash_data, aux_data);
ipi_hash = pdb_tpi_hash_from_data(arena, strtbl, ipi, hash_data, aux_data);
PARSE_CHECK_ERROR(tpi_hash, "IPI hash table");
}
+79 -5
View File
@@ -303,7 +303,7 @@ pdb_tpi_from_data(Arena *arena, String8 data){
}
static PDB_TpiHashParsed*
pdb_tpi_hash_from_data(Arena *arena, PDB_TpiParsed *tpi, String8 data, String8 aux_data){
pdb_tpi_hash_from_data(Arena *arena, PDB_Strtbl *strtbl, PDB_TpiParsed *tpi, String8 data, String8 aux_data){
ProfBegin("pdb_tpi_hash_from_data");
PDB_TpiHashParsed *result = 0;
@@ -335,7 +335,11 @@ pdb_tpi_hash_from_data(Arena *arena, PDB_TpiParsed *tpi, String8 data, String8 a
block = push_array(arena, PDB_TpiHashBlock, 1);
SLLStackPush(buckets[bucket_idx], block);
}
block->itypes[block->local_count] = itype;
if(block->local_count != 0)
{
MemoryCopy(block->itypes+1, block->itypes, sizeof(CV_TypeId)*block->local_count);
}
block->itypes[0] = itype;
block->local_count += 1;
}
@@ -344,15 +348,85 @@ pdb_tpi_hash_from_data(Arena *arena, PDB_TpiParsed *tpi, String8 data, String8 a
itype += 1;
}
//- rjf: compute bucket mask
U32 bucket_mask = 0;
if(IsPow2OrZero(bucket_count))
{
bucket_mask = bucket_count-1;
}
//- rjf: apply hash adjustments, to pull correct type IDs to the front of
// the chains
if(tpi->hash_adj_size != 0)
{
// NOTE(rjf): this table is laid out in the following format:
//
// pair_count: U32 -> # of name_index/type_index pairs
// slot_count: U32 -> # of slots in this hash table
// present_bit_array_count: U32 -> count for next array
// present_bit_array: U32[present_bit_array_count] -> 1 bit per slot, "is present"
// deleted_bit_array_count: U32 -> count for next array
// deleted_bit_array: U32[deleted_bit_array_count] -> 1 bit per slot, "is deleted"
// (U32, U32)[pair_count] -> array of name_index/type_index pairs
//
U8 *adjs = data.str + tpi->hash_adj_off;
U8 *adjs_opl = adjs + tpi->hash_adj_size;
U8 *adjs_cursor = adjs;
U32 pair_count = *(U32 *)adjs_cursor;
adjs_cursor += sizeof(U32);
U32 slot_count = *(U32 *)adjs_cursor;
adjs_cursor += sizeof(U32);
U32 present_bit_array_count = *(U32 *)adjs_cursor; // skip present_bit_array
adjs_cursor += sizeof(U32);
adjs_cursor += present_bit_array_count*sizeof(U32);
U32 deleted_bit_array_count = *(U32 *)adjs_cursor; // skip deleted_bit_array
adjs_cursor += sizeof(U32);
adjs_cursor += deleted_bit_array_count*sizeof(U32);
U32 adjs_stride = sizeof(U32)*2;
U32 pair_idx = 0;
for(;adjs_cursor < adjs_opl && pair_idx < pair_count;
adjs_cursor += adjs_stride, pair_idx += 1)
{
U32 name_off = ((U32 *)adjs_cursor)[0];
CV_TypeId type_id = ((CV_TypeId *)adjs_cursor)[1];
String8 string = pdb_strtbl_string_from_off(strtbl, name_off);
U32 hash = pdb_string_hash1(string);
U32 bucket_idx = ((bucket_mask != 0) ? hash&bucket_mask : hash%bucket_count);
PDB_TpiHashBlock *prev_block = 0;
for(PDB_TpiHashBlock *block = buckets[bucket_idx];
block != 0;
prev_block = block, block = block->next)
{
for(U32 local_idx = 0;
local_idx < block->local_count && local_idx < ArrayCount(block->itypes);
local_idx += 1)
{
if(block->itypes[local_idx] == type_id)
{
if(prev_block != 0)
{
prev_block->next = block->next;
block->next = buckets[bucket_idx];
buckets[bucket_idx] = block;
}
if(local_idx != 0)
{
Swap(CV_TypeId, block->itypes[0], block->itypes[local_idx]);
}
break;
}
}
}
}
}
// fill result
result = push_array(arena, PDB_TpiHashParsed, 1);
result->data = data;
result->aux_data = aux_data;
result->buckets = buckets;
result->bucket_count = bucket_count;
if (IsPow2OrZero(bucket_count)){
result->bucket_mask = bucket_count - 1;
}
result->bucket_mask = bucket_mask;
}
ProfEnd();
+1
View File
@@ -407,6 +407,7 @@ static PDB_Strtbl* pdb_strtbl_from_data(Arena *arena, String8 strtbl_da
static PDB_DbiParsed* pdb_dbi_from_data(Arena *arena, String8 dbi_data);
static PDB_TpiParsed* pdb_tpi_from_data(Arena *arena, String8 tpi_data);
static PDB_TpiHashParsed* pdb_tpi_hash_from_data(Arena *arena,
PDB_Strtbl *strtbl,
PDB_TpiParsed *tpi,
String8 tpi_hash_data,
String8 tpi_hash_aux_data);
+39 -4
View File
@@ -1,14 +1,49 @@
// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
#include <stdlib.h>
// build with:
// cl /Zi /nologo look_at_raddbg.c
#include <windows.h>
#include <stdint.h>
#include "raddbg_format/raddbg_format.h"
#include "raddbg_format/raddbg_format_parse.h"
#include "raddbg_format/raddbg_format.c"
#include "raddbg_format/raddbg_format_parse.c"
typedef struct Foo Foo;
struct Foo
{
int x;
int y;
int z;
};
int main(int argument_count, char **arguments)
{
int *arr = malloc(sizeof(int)*1000);
for(int i = 0; i < 1001; i += 1)
Foo foo = {123, 456, 789};
HANDLE file = CreateFileA(arguments[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
DWORD size_hi32 = 0;
DWORD size_lo32 = GetFileSize(file, &size_hi32);
HANDLE map = CreateFileMappingA(file, 0, PAGE_READONLY, 0, 0, 0);
uint64_t data_size = (size_lo32 | ((uint64_t)size_hi32 << 32));
uint8_t *data = (uint8_t *)MapViewOfFile(map, FILE_MAP_READ, 0, 0, data_size);
RADDBG_Parsed rdbg = {0};
RADDBG_ParseStatus parse_status = raddbg_parse(data, data_size, &rdbg);
uint64_t foo_count = 0;
for(uint64_t idx = 0; idx < rdbg.type_node_count; idx += 1)
{
arr[i] = i;
RADDBG_TypeNode *type_node = &rdbg.type_nodes[idx];
if(RADDBG_TypeKind_FirstUserDefined <= type_node->kind && type_node->kind <= RADDBG_TypeKind_LastUserDefined)
{
uint64_t name_size = 0;
uint8_t *name = raddbg_string_from_idx(&rdbg, type_node->user_defined.name_string_idx, &name_size);
if(name_size == 3 && name[0] == 'f' && name[1] == 'o' && name[2] == 'o')
{
foo_count += 1;
}
}
}
printf("%s -> %I64u foos\n", arguments[1], foo_count);
return 0;
}
+4 -4
View File
@@ -118,7 +118,7 @@ ui_nav_eat_action_node(UI_NavActionList *list, UI_NavActionNode *node)
internal B32
ui_nav_char_is_code_symbol(U8 c)
{
return (char_is_alpha(c) || char_is_digit(c, 10) || c == '_');
return (char_is_alpha(c) || char_is_digit(c, 10) || c == '_');
}
internal S64
@@ -134,7 +134,7 @@ ui_nav_scanned_column_from_column(String8 string, S64 start_column, Side side)
U8 byte = (col <= string.size) ? string.str[col-1] : 0;
B32 is_non_space = !char_is_space(byte);
B32 is_name = ui_nav_char_is_code_symbol(byte);
if (((side == Side_Min) && (col == 1)) ||
((side == Side_Max) && (col == string.size+1)) ||
(found_non_space && !is_non_space) ||
@@ -143,9 +143,9 @@ ui_nav_scanned_column_from_column(String8 string, S64 start_column, Side side)
new_column = col + (!side && col != 1);
break;
} else if (!found_text && is_name) {
found_text = 1;
found_text = 1;
} else if (!found_non_space && is_non_space ) {
found_non_space = 1;
found_non_space = 1;
}
}
return new_column;