mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-10-14 04:49:41 -07:00
simplify testing ipc driving stuff in raddbg layer; put in formal eval/line-from-vaddr commands
This commit is contained in:
1 parent
ec6f569181
commit
5f851ad1b0
17 files changed
+434
-694
No files matched your search
+251
-169
@@ -7,6 +7,88 @@
|
||||
|
||||
extern B32 g_stop_on_first_fail_or_crash;
|
||||
|
||||
////////////////////////////////
|
||||
// Debugger IPC Replies
|
||||
|
||||
typedef struct RD_IpcReply RD_IpcReply;
|
||||
struct RD_IpcReply
|
||||
{
|
||||
MD_ParseResult parse;
|
||||
MD_Node *root;
|
||||
MD_Node *msg;
|
||||
};
|
||||
|
||||
internal RD_IpcReply
|
||||
rd_ipc_mdesk_reply_from_string(Arena *arena, String8 string)
|
||||
{
|
||||
MD_ParseResult parse = md_parse_from_text(arena, str8_lit("ipc_reply"), string);
|
||||
return (RD_IpcReply){ .parse = parse, .root = parse.root, parse.root->first };
|
||||
}
|
||||
|
||||
internal B32
|
||||
rd_ipc_parse_string(MD_Node *node, String8 child_name, String8 *out)
|
||||
{
|
||||
MD_Node *child = md_child_from_string(node, child_name, 0);
|
||||
if (!md_node_is_nil(child) && !md_node_is_nil(child->first))
|
||||
{
|
||||
if (out) { *out = child->first->string; }
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal B32
|
||||
rd_ipc_parse_u32(MD_Node *node, String8 child_name, U32 *out)
|
||||
{
|
||||
String8 value = {0};
|
||||
if (rd_ipc_parse_string(node, child_name, &value))
|
||||
{
|
||||
U64 v64 = 0;
|
||||
if (try_u64_from_str8_c_rules(value, &v64))
|
||||
{
|
||||
if (out) { *out = safe_cast_u32(v64); }
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal B32
|
||||
rd_ipc_parse_int_(MD_Node *node, String8 child_name, U64 out_size, void *out)
|
||||
{
|
||||
String8 value = {0};
|
||||
if (rd_ipc_parse_string(node, child_name, &value))
|
||||
{
|
||||
U64 v64 = 0;
|
||||
if (try_u64_from_str8_c_rules(value, &v64))
|
||||
{
|
||||
if (out) { MemoryCopy(out, &v64, out_size); }
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal B32
|
||||
rd_ipc_parse_b32(MD_Node *node, String8 child_name, B32 *out)
|
||||
{
|
||||
B32 is_ok = 0;
|
||||
String8 s = {0};
|
||||
U64 value = 0;
|
||||
if (rd_ipc_parse_string(node, child_name, &s))
|
||||
{
|
||||
if (str8_matchi(s, str8_lit("true"))) { value = 1; is_ok = 1; }
|
||||
else if (str8_matchi(s, str8_lit("false"))) { value = 0; is_ok = 1; }
|
||||
else
|
||||
{
|
||||
is_ok = try_u64_from_str8_c_rules(s, &value);
|
||||
value = !!value;
|
||||
}
|
||||
}
|
||||
if (is_ok && out) { *out = value; }
|
||||
return is_ok;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// IPC Controller
|
||||
|
||||
@@ -17,14 +99,14 @@ t_dbg_send_cmd(String8 cmd, U64 timeout_us, Arena *reply_arena, RD_IpcReply *rep
|
||||
{
|
||||
Temp scratch = scratch_begin(&reply_arena, 1);
|
||||
B32 is_sent = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
// send command
|
||||
String8 cmdline = str8f(scratch.arena, "--gen_crash_dump --ipc --pid:%u %S", g_dbg_pid, cmd);
|
||||
if (t_invoke(t_raddbg_path(), cmdline, timeout_us) == 0) { goto exit; }
|
||||
|
||||
|
||||
B32 has_reply = 1;
|
||||
|
||||
|
||||
char *no_reply_cmds[] = {
|
||||
"add_breakpoint",
|
||||
"add_function_breakpoint",
|
||||
@@ -37,23 +119,23 @@ t_dbg_send_cmd(String8 cmd, U64 timeout_us, Arena *reply_arena, RD_IpcReply *rep
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (has_reply) {
|
||||
// parse reply
|
||||
Arena *a = reply_arena ? reply_arena : scratch.arena;
|
||||
String8 reply_text = str8_copy(a, g_output);
|
||||
RD_IpcReply reply = rd_ipc_mdesk_reply_from_string(a, reply_text);
|
||||
|
||||
|
||||
//fprintf(stderr, "Reply: %.*s\n", str8_varg(reply_text));
|
||||
|
||||
|
||||
// validate reply
|
||||
if (reply.parse.msgs.worst_message_kind >= MD_MsgKind_Error) { goto exit; }
|
||||
if (md_node_is_nil(reply.msg)) { goto exit; }
|
||||
//if ( ! str8_matchi(reply.parse.root->first->string, cmd)) { Assert(0); goto exit; }
|
||||
|
||||
|
||||
if (reply_arena && reply_out) { *reply_out = reply; }
|
||||
}
|
||||
|
||||
|
||||
is_sent = 1;
|
||||
exit:;
|
||||
scratch_end(scratch);
|
||||
@@ -77,21 +159,21 @@ internal B32
|
||||
t_dbg_status(T_DbgStatus *status_out, U64 timeout_us)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
|
||||
B32 is_ok = 0;
|
||||
T_DbgStatus status = {0};
|
||||
|
||||
|
||||
// send status request
|
||||
RD_IpcReply reply = {0};
|
||||
if ( ! t_dbg_send_cmd(str8_lit("status"), timeout_us, scratch.arena, &reply)) { goto exit; }
|
||||
|
||||
|
||||
// parse reply
|
||||
if ( ! rd_ipc_parse_b32(reply.msg, str8_lit("ok"), &is_ok)) { t_errorf("ERROR: failed to parse reply member: ok\n"); Assert(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_b32(reply.msg, str8_lit("running"), &status.running)) { t_errorf("ERROR: failed to parse reply member: running\n"); Assert(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_int(reply.msg, str8_lit("run_gen"), &status.run_gen)) { t_errorf("ERROR: failed to parse reply member: run_gen\n"); Assert(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_int(reply.msg, str8_lit("ip"), &status.ip)) { t_errorf("ERROR: failed to parse reply member: ip\n"); Assert(0); goto exit; }
|
||||
if (status_out != 0) { *status_out = status; }
|
||||
|
||||
|
||||
exit:;
|
||||
if ( ! is_ok && reply.parse.root) {
|
||||
t_errorf("\tReply: %S\n", reply.parse.root->raw_string);
|
||||
@@ -104,14 +186,14 @@ internal B32
|
||||
t_dbg_stop_event(Arena *arena, T_DbgStopEvent *out, U64 timeout_us)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
|
||||
|
||||
T_DbgStopEvent v = {0};
|
||||
B32 is_ok = 0;
|
||||
|
||||
|
||||
// send status request
|
||||
RD_IpcReply reply = {0};
|
||||
if ( ! t_dbg_send_cmd(str8_lit("stop_event"), timeout_us, arena ? arena : scratch.arena, &reply)) { goto exit; }
|
||||
|
||||
|
||||
// parse reply
|
||||
if ( ! rd_ipc_parse_b32 (reply.msg, str8_lit("ok"), &is_ok)) { AssertAlways(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_int (reply.msg, str8_lit("arch"), &v.arch)) { AssertAlways(0); goto exit; }
|
||||
@@ -129,9 +211,9 @@ t_dbg_stop_event(Arena *arena, T_DbgStopEvent *out, U64 timeout_us)
|
||||
if ( ! rd_ipc_parse_int (reply.msg, str8_lit("target_os"), &v.target_os)) { AssertAlways(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_int (reply.msg, str8_lit("tls_model"), &v.tls_model)) { AssertAlways(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_string (reply.msg, str8_lit("stop_cause"), &v.stop_cause)) { AssertAlways(0); goto exit; }
|
||||
|
||||
|
||||
if (arena && out != 0) { *out = v; }
|
||||
|
||||
|
||||
exit:;
|
||||
return is_ok;
|
||||
}
|
||||
@@ -140,14 +222,14 @@ internal B32
|
||||
t_dbg_src_line(Arena *arena, U64 vaddr, T_DbgSourceLocation *loc_out, U64 timeout_us)
|
||||
{
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
|
||||
|
||||
B32 is_ok = 0;
|
||||
|
||||
|
||||
RD_IpcReply reply = {0};
|
||||
if(!t_dbg_send_cmdf(timeout_us, arena, &reply, "source_location_from_address 0x%llx", vaddr)) { goto exit; }
|
||||
|
||||
|
||||
T_DbgSourceLocation loc = {0};
|
||||
|
||||
|
||||
if ( ! rd_ipc_parse_b32 (reply.msg, str8_lit("ok"), &is_ok)) { AssertAlways(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_int (reply.msg, str8_lit("vaddr"), &loc.vaddr)) { AssertAlways(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_int (reply.msg, str8_lit("voff"), &loc.voff)) { AssertAlways(0); goto exit; }
|
||||
@@ -156,9 +238,9 @@ t_dbg_src_line(Arena *arena, U64 vaddr, T_DbgSourceLocation *loc_out, U64 timeou
|
||||
if ( ! rd_ipc_parse_int (reply.msg, str8_lit("column"), &loc.pt.column)) { AssertAlways(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_int (reply.msg, str8_lit("voff_min"), &loc.voff_range.min)){ AssertAlways(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_int (reply.msg, str8_lit("voff_max"), &loc.voff_range.max)){ AssertAlways(0); goto exit; }
|
||||
|
||||
|
||||
if (loc_out != 0) { *loc_out = loc; }
|
||||
|
||||
|
||||
exit:;
|
||||
scratch_end(scratch);
|
||||
return is_ok;
|
||||
@@ -190,37 +272,37 @@ t_dbg_send_cmd_and_wait_stop(String8 cmd, U64 timeout_us)
|
||||
{
|
||||
Temp scratch = scratch_begin(0,0);
|
||||
B32 is_stopped = 0;
|
||||
|
||||
|
||||
// snapshot status
|
||||
T_DbgStatus status_before = {0};
|
||||
if (t_dbg_status(&status_before, max_U64) == 0) { Assert(0 && "failed to snapshot status"); goto exit; }
|
||||
|
||||
|
||||
// send command
|
||||
if (t_dbg_send_cmd(cmd, max_U64, 0, 0) == 0) { Assert(0 && "failed to the command"); goto exit; }
|
||||
|
||||
|
||||
// wait for debugger to stop
|
||||
U64 t = ENDT_US(timeout_us);
|
||||
for (;;) {
|
||||
// query debugger status
|
||||
T_DbgStatus status = {0};
|
||||
if (t_dbg_status(&status, t) == 0) { Assert(0 && "failed to acquire debugger status"); goto exit; }
|
||||
|
||||
|
||||
// did state change? -> break
|
||||
if (!status.running && status.run_gen != status_before.run_gen) {
|
||||
is_stopped = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// "solve" the wait problem
|
||||
if (now_time_us() >= t) { Assert(0 && "timeout"); goto exit; }
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
|
||||
//--- Status ---------------------
|
||||
if (0 && is_stopped) {
|
||||
T_DbgStatus status = {0};
|
||||
AssertAlways(t_dbg_status(&status, T_Dbg_DefaultTimeout));
|
||||
|
||||
|
||||
String8 process_id = str8_skip(str8_chop(t_dbg_value_from_exprf(scratch.arena, "query:current_process.id"), 2), 2);
|
||||
String8 process_label = str8_chop(str8_skip(t_dbg_value_from_exprf(scratch.arena, "query:current_process.label"), 2), 2);
|
||||
String8 process_active = t_dbg_value_from_exprf(scratch.arena, "query:current_process.active");
|
||||
@@ -229,13 +311,13 @@ t_dbg_send_cmd_and_wait_stop(String8 cmd, U64 timeout_us)
|
||||
String8 thread_label = str8_skip(str8_chop(t_dbg_value_from_exprf(scratch.arena, "query:current_thread.label"), 2), 2);
|
||||
String8 ip = t_dbg_value_from_exprf(scratch.arena, "hex(reg:rip)");
|
||||
String8 sp = t_dbg_value_from_exprf(scratch.arena, "hex(reg:rsp)");
|
||||
|
||||
|
||||
T_DbgStopEvent last_stop = {0};
|
||||
AssertAlways(t_dbg_stop_event(scratch.arena, &last_stop, T_Dbg_DefaultTimeout));
|
||||
|
||||
|
||||
T_DbgSourceLocation loc = {0};
|
||||
AssertAlways(t_dbg_src_line(scratch.arena, last_stop.ip_vaddr, &loc, T_Dbg_DefaultTimeout));
|
||||
|
||||
|
||||
printf("------------------------------------------------------------------------------------------------------------------------\n");
|
||||
printf(" Process: %.*s [%.*s] (Active: %.*s)\n", str8_varg(process_id), str8_varg(process_label), str8_varg(process_active));
|
||||
printf(" Thread: %.*s [%.*s] (Active: %.*s)\n", str8_varg(thread_id), str8_varg(thread_label), str8_varg(thread_active));
|
||||
@@ -249,7 +331,7 @@ t_dbg_send_cmd_and_wait_stop(String8 cmd, U64 timeout_us)
|
||||
fflush(stdout);
|
||||
}
|
||||
//--------------------------------
|
||||
|
||||
|
||||
exit:;
|
||||
scratch_end(scratch);
|
||||
return is_stopped;
|
||||
@@ -265,11 +347,11 @@ t_dbg_launch(String8 cmdline, U64 timeout_us)
|
||||
{
|
||||
Temp scratch = scratch_begin(0, 0);
|
||||
B32 dbg_ready = 0;
|
||||
|
||||
|
||||
String8 user_path = t_make_file_path(scratch.arena, str8_lit("test.raddbg_user"));
|
||||
String8 project_path = t_make_file_path(scratch.arena, str8_lit("test.raddbg_project"));
|
||||
cmdline = str8f(scratch.arena, "%S --gen_crash_dump --user:\"%S\" --project:\"%S\" --logs:\"%S\" %S", t_raddbg_path(), user_path, project_path, g_wdir, cmdline);
|
||||
|
||||
|
||||
// launch debugger
|
||||
ProcessLaunchParams launch_opts = {
|
||||
.path = g_wdir,
|
||||
@@ -279,7 +361,7 @@ t_dbg_launch(String8 cmdline, U64 timeout_us)
|
||||
};
|
||||
Process dbg_handle = process_launch(&launch_opts);
|
||||
if (process_match(dbg_handle, process_zero())) { AssertAlways(0 && "failed to launch debugger"); goto exit; }
|
||||
|
||||
|
||||
#if OS_WINDOWS
|
||||
// cache debugger PID
|
||||
g_dbg_pid = GetProcessId((HANDLE)dbg_handle.u64[0]);
|
||||
@@ -288,22 +370,22 @@ t_dbg_launch(String8 cmdline, U64 timeout_us)
|
||||
#else
|
||||
# error NotImplemented
|
||||
#endif
|
||||
|
||||
|
||||
// close debugger handle
|
||||
process_detach(dbg_handle);
|
||||
|
||||
|
||||
// now wait for debugger to init
|
||||
U64 t = ENDT_US(timeout_us);
|
||||
for (;;) {
|
||||
// time the ping
|
||||
dbg_ready = t_dbg_ping(t);
|
||||
if (dbg_ready) { break; }
|
||||
|
||||
|
||||
// "solve" the wait problem
|
||||
if (now_time_us() >= t) { Assert(0 && "timeout"); break; }
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
|
||||
exit:;
|
||||
scratch_end(scratch);
|
||||
return dbg_ready;
|
||||
@@ -313,18 +395,18 @@ internal B32
|
||||
t_dbg_eval(Arena *arena, String8 expr, T_Eval *eval_out)
|
||||
{
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
|
||||
|
||||
RD_IpcReply reply = {0};
|
||||
String8 cmd = str8f(scratch.arena, "eval %llu %S", /* value char cap: */ 10000, expr);
|
||||
B32 is_ok = t_dbg_send_cmd(cmd, T_Dbg_DefaultTimeout, arena, &reply);
|
||||
|
||||
|
||||
T_Eval e = {0};
|
||||
if ( ! rd_ipc_parse_string(reply.msg, str8_lit("expr"), &e.expr)) { t_errorf("ERROR: failed to parse reply member: expr\n"); Assert(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_string(reply.msg, str8_lit("value"), &e.value)) { t_errorf("ERROR: failed to parse reply member: value\n"); Assert(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_string(reply.msg, str8_lit("type"), &e.type)) { t_errorf("ERROR: failed to parse reply member: type\n"); Assert(0); goto exit; }
|
||||
if ( ! rd_ipc_parse_string(reply.msg, str8_lit("error"), &e.error)) { t_errorf("ERROR: failed to parse reply member: error\n"); Assert(0); goto exit; }
|
||||
if (eval_out) { *eval_out = e; }
|
||||
|
||||
|
||||
exit:;
|
||||
scratch_end(scratch);
|
||||
return is_ok;
|
||||
@@ -350,22 +432,22 @@ internal String8
|
||||
t_string_from_dbg_script_cmd_kind(T_DbgScriptCmdKind v)
|
||||
{
|
||||
switch (v) {
|
||||
case T_DbgScriptCmdKind_Null: return str8_zero();
|
||||
case T_DbgScriptCmdKind_Breakpoint: return str8_lit("bp");
|
||||
case T_DbgScriptCmdKind_ClearBreakpoints: return str8_lit("bp_clear");
|
||||
case T_DbgScriptCmdKind_Run: return str8_lit("run");
|
||||
case T_DbgScriptCmdKind_Halt: return str8_lit("halt");
|
||||
case T_DbgScriptCmdKind_StepOver: return str8_lit("step_over");
|
||||
case T_DbgScriptCmdKind_StepInto: return str8_lit("step_into");
|
||||
case T_DbgScriptCmdKind_StepOut: return str8_lit("step_out");
|
||||
case T_DbgScriptCmdKind_StepOverInst: return str8_lit("step_over_inst");
|
||||
case T_DbgScriptCmdKind_StepIntoInst: return str8_lit("step_into_inst");
|
||||
case T_DbgScriptCmdKind_StepOverLine: return str8_lit("step_over_line");
|
||||
case T_DbgScriptCmdKind_StepIntoLine: return str8_lit("step_into_line");
|
||||
case T_DbgScriptCmdKind_KillAll: return str8_lit("kill_all");
|
||||
case T_DbgScriptCmdKind_At: return str8_lit("at");
|
||||
case T_DbgScriptCmdKind_Eval: return str8_lit("eval");
|
||||
default: InvalidPath;
|
||||
case T_DbgScriptCmdKind_Null: return str8_zero();
|
||||
case T_DbgScriptCmdKind_Breakpoint: return str8_lit("bp");
|
||||
case T_DbgScriptCmdKind_ClearBreakpoints: return str8_lit("bp_clear");
|
||||
case T_DbgScriptCmdKind_Run: return str8_lit("run");
|
||||
case T_DbgScriptCmdKind_Halt: return str8_lit("halt");
|
||||
case T_DbgScriptCmdKind_StepOver: return str8_lit("step_over");
|
||||
case T_DbgScriptCmdKind_StepInto: return str8_lit("step_into");
|
||||
case T_DbgScriptCmdKind_StepOut: return str8_lit("step_out");
|
||||
case T_DbgScriptCmdKind_StepOverInst: return str8_lit("step_over_inst");
|
||||
case T_DbgScriptCmdKind_StepIntoInst: return str8_lit("step_into_inst");
|
||||
case T_DbgScriptCmdKind_StepOverLine: return str8_lit("step_over_line");
|
||||
case T_DbgScriptCmdKind_StepIntoLine: return str8_lit("step_into_line");
|
||||
case T_DbgScriptCmdKind_KillAll: return str8_lit("kill_all");
|
||||
case T_DbgScriptCmdKind_At: return str8_lit("at");
|
||||
case T_DbgScriptCmdKind_Eval: return str8_lit("eval");
|
||||
default: InvalidPath;
|
||||
}
|
||||
return str8_zero();
|
||||
}
|
||||
@@ -386,7 +468,7 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
B32 is_ok = 0;
|
||||
|
||||
T_DbgScript script = { .file_path = push_str8_copy(arena, file_path) };
|
||||
|
||||
|
||||
// scrape MD comment tokens out of source while preserving original source offsets
|
||||
MD_TokenArray script_tokens = {0};
|
||||
{
|
||||
@@ -415,11 +497,11 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
}
|
||||
script_tokens = md_token_array_from_chunk_list(scratch.arena, &script_token_chunks);
|
||||
}
|
||||
|
||||
|
||||
// script tokens -> mdesk tree
|
||||
MD_ParseResult script_parse = md_parse_from_text_tokens(scratch.arena, file_path, source, script_tokens);
|
||||
AssertAlways(script_parse.msgs.worst_message_kind < MD_MsgKind_Error);
|
||||
|
||||
|
||||
// test
|
||||
{
|
||||
MD_Node *test = script_parse.root->first;
|
||||
@@ -428,32 +510,32 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
for MD_EachNode(n, test->first) {
|
||||
OperatingSystem os = operating_system_from_string(n->string);
|
||||
AssertAlways(os != OperatingSystem_Null);
|
||||
|
||||
|
||||
for MD_EachNode(field, n->first) {
|
||||
T_DbgScriptDirectiveKind kind = T_DbgScriptDirectiveKind_Null;
|
||||
if (str8_matchi(field->string, str8_lit("compile"))) { kind = T_DbgScriptDirectiveKind_Compile; }
|
||||
else if (str8_matchi(field->string, str8_lit("link"))) { kind = T_DbgScriptDirectiveKind_Link; }
|
||||
else if (str8_matchi(field->string, str8_lit("launch"))) { kind = T_DbgScriptDirectiveKind_Launch; }
|
||||
AssertAlways(kind != T_DbgScriptDirectiveKind_Null);
|
||||
|
||||
|
||||
// syntax check
|
||||
AssertAlways( !md_node_is_nil(field->first));
|
||||
AssertAlways(md_node_is_nil(field->first->next));
|
||||
Assert(field->first->flags & MD_NodeFlag_StringLiteral);
|
||||
|
||||
|
||||
// src_offset -> line
|
||||
//
|
||||
// TODO: super silly!! mdesk should export line numbers
|
||||
U64 line = 1;
|
||||
String8 text_before_src = str8_prefix(source, field->src_offset);
|
||||
for EachIndex(idx, text_before_src.size) { line += (text_before_src.str[idx] == '\n'); }
|
||||
|
||||
|
||||
T_DbgScriptDirective *n = push_array(arena, T_DbgScriptDirective, 1);
|
||||
n->kind = kind;
|
||||
n->line = line;
|
||||
n->args = str8_copy(arena, field->first->string);
|
||||
// TODO: expand % in compile: and link: to current source file name and esacpe with %%
|
||||
|
||||
|
||||
T_DbgScriptDirectiveList *list = &script.directives[os][kind];
|
||||
SLLQueuePush(list->first, list->last, n);
|
||||
list->count += 1;
|
||||
@@ -464,7 +546,7 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// file
|
||||
{
|
||||
MD_Node *last_file = 0;
|
||||
@@ -475,19 +557,19 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
AssertAlways( ! md_node_is_nil(n->first));
|
||||
AssertAlways(md_node_is_nil(n->first->next));
|
||||
}
|
||||
|
||||
|
||||
if (last_file) {
|
||||
// src_offset -> base_line
|
||||
//
|
||||
// TODO: super silly!! mdesk should export line numbers
|
||||
U64 line = 1;
|
||||
for EachIndex(idx, last_file->src_offset) { line += (source.str[idx] == '\n'); }
|
||||
|
||||
|
||||
String8 sub_source = str8_substr(source, r1u64(last_file->src_offset, is_end ? source.size : n->src_offset));
|
||||
U64 file_dir_end = str8_find_needle(sub_source, 0, str8_lit("\n"), 0);
|
||||
U64 next_file_dir_begin = str8_find_needle_reverse(sub_source, 0, str8_lit("\n"), n->src_offset);
|
||||
sub_source = str8_substr(sub_source, r1u64(file_dir_end + 1, next_file_dir_begin));
|
||||
|
||||
|
||||
T_DbgScriptFile *file = push_array(arena, T_DbgScriptFile, 1);
|
||||
file->path = t_make_file_path(arena, last_file->first->string);
|
||||
file->source = sub_source;
|
||||
@@ -495,11 +577,11 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
SLLQueuePush(script.files.first, script.files.last, file);
|
||||
script.files.count += 1;
|
||||
}
|
||||
|
||||
|
||||
last_file = n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// no file directives? assume script file as main source file
|
||||
if (last_file == 0) {
|
||||
T_DbgScriptFile *file = push_array(arena, T_DbgScriptFile, 1);
|
||||
@@ -509,7 +591,7 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
script.files.count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// programs
|
||||
{
|
||||
HashTable *ht = hash_table_init(scratch.arena, 256); // <U64, T_DbgScriptProgram>
|
||||
@@ -523,7 +605,7 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
}
|
||||
}
|
||||
AssertAlways(file != 0);
|
||||
|
||||
|
||||
// src_offset -> line
|
||||
//
|
||||
// TODO: super silly!! mdesk should export line numbers
|
||||
@@ -541,7 +623,7 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
} else {
|
||||
t_errorf("ERROR: duplicate order number %llu found on line %llu\n", (unsigned long long)order, (unsigned long long)p->line);
|
||||
}
|
||||
|
||||
|
||||
for MD_EachNode(cmd_n, n->first) {
|
||||
// push new cmd
|
||||
T_DbgScriptCmd *cmd = push_array(arena, T_DbgScriptCmd, 1);
|
||||
@@ -550,7 +632,7 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
Assert(cmd->kind != T_DbgScriptCmdKind_Null);
|
||||
SLLQueuePush(p->first, p->last, cmd);
|
||||
p->count += 1;
|
||||
|
||||
|
||||
// parse cmd args
|
||||
MD_Node *cmd_arg = cmd_n->first;
|
||||
if ( ! md_node_is_nil(cmd_arg)) {
|
||||
@@ -565,12 +647,12 @@ t_dbg_script_from_source(Arena *arena, String8 file_path, String8 source, T_DbgS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
script.program_count = ht->count;
|
||||
script.programs = values_from_hash_table_raw(arena, ht);
|
||||
radsort(script.programs, script.program_count, t_dbg_script_program_is_before);
|
||||
}
|
||||
|
||||
|
||||
is_ok = 1;
|
||||
exit:;
|
||||
if (script_out) {
|
||||
@@ -584,80 +666,80 @@ internal B32
|
||||
t_dbg_script_invoke(T_DbgScript *script, U64 timeout_us)
|
||||
{
|
||||
Temp scratch = scratch_begin(0,0);
|
||||
|
||||
|
||||
B32 is_ok = 0;
|
||||
|
||||
|
||||
for EachIndex(i, script->program_count) {
|
||||
T_DbgScriptProgram *program = script->programs[i];
|
||||
|
||||
|
||||
if (program->os == OperatingSystem_CURRENT) {
|
||||
for EachNode(cmd, T_DbgScriptCmd, program->first) {
|
||||
t_infof("[%llu] Command: %S:%llu %S\n", program->order, script->file_path, (unsigned long long)cmd->line, t_string_from_dbg_script_cmd_kind(cmd->kind));
|
||||
|
||||
|
||||
switch (cmd->kind) {
|
||||
case T_DbgScriptCmdKind_Null: break;
|
||||
case T_DbgScriptCmdKind_Halt: t_dbg_send_cmd_and_wait_stop(str8_lit("halt"), timeout_us); break; // NOTE: does not auto-magically select main thread on stop
|
||||
case T_DbgScriptCmdKind_StepOver: t_dbg_send_cmd_and_wait_stop(str8_lit("step_over"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepInto: t_dbg_send_cmd_and_wait_stop(str8_lit("step_into"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepOut: t_dbg_send_cmd_and_wait_stop(str8_lit("step_out"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepOverInst: t_dbg_send_cmd_and_wait_stop(str8_lit("step_over_inst"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepIntoInst: t_dbg_send_cmd_and_wait_stop(str8_lit("step_into_inst"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepOverLine: t_dbg_send_cmd_and_wait_stop(str8_lit("step_over_line"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepIntoLine: t_dbg_send_cmd_and_wait_stop(str8_lit("step_into_line"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_KillAll: t_dbg_send_cmd_and_wait_stop(str8_lit("kill_all"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_Breakpoint: NotImplemented; break;
|
||||
case T_DbgScriptCmdKind_ClearBreakpoints: t_dbg_send_cmdf(0,0,0, "clear_breakpoints"); break;
|
||||
case T_DbgScriptCmdKind_Run: t_dbg_send_cmd(str8_lit("run"), timeout_us, 0, 0); break;
|
||||
case T_DbgScriptCmdKind_At: {
|
||||
// TODO: debugger does not populate eval cache with registers before first frame,
|
||||
// so this racy, for now use lower level option
|
||||
case T_DbgScriptCmdKind_Null: break;
|
||||
case T_DbgScriptCmdKind_Halt: t_dbg_send_cmd_and_wait_stop(str8_lit("halt"), timeout_us); break; // NOTE: does not auto-magically select main thread on stop
|
||||
case T_DbgScriptCmdKind_StepOver: t_dbg_send_cmd_and_wait_stop(str8_lit("step_over"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepInto: t_dbg_send_cmd_and_wait_stop(str8_lit("step_into"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepOut: t_dbg_send_cmd_and_wait_stop(str8_lit("step_out"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepOverInst: t_dbg_send_cmd_and_wait_stop(str8_lit("step_over_inst"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepIntoInst: t_dbg_send_cmd_and_wait_stop(str8_lit("step_into_inst"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepOverLine: t_dbg_send_cmd_and_wait_stop(str8_lit("step_over_line"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_StepIntoLine: t_dbg_send_cmd_and_wait_stop(str8_lit("step_into_line"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_KillAll: t_dbg_send_cmd_and_wait_stop(str8_lit("kill_all"), timeout_us); break;
|
||||
case T_DbgScriptCmdKind_Breakpoint: NotImplemented; break;
|
||||
case T_DbgScriptCmdKind_ClearBreakpoints: t_dbg_send_cmdf(0,0,0, "clear_breakpoints"); break;
|
||||
case T_DbgScriptCmdKind_Run: t_dbg_send_cmd(str8_lit("run"), timeout_us, 0, 0); break;
|
||||
case T_DbgScriptCmdKind_At: {
|
||||
// TODO: debugger does not populate eval cache with registers before first frame,
|
||||
// so this racy, for now use lower level option
|
||||
#if 0
|
||||
U64 ip = u64_from_str8(t_dbg_value_from_exprf(scratch.arena, "reg:rip"), 10);
|
||||
if (ip == 0) {
|
||||
fprintf(stderr, "ERROR: invalid IP address: 0x%llx\n", (unsigned long long)ip);
|
||||
goto exit;
|
||||
}
|
||||
U64 ip = u64_from_str8(t_dbg_value_from_exprf(scratch.arena, "reg:rip"), 10);
|
||||
if (ip == 0) {
|
||||
fprintf(stderr, "ERROR: invalid IP address: 0x%llx\n", (unsigned long long)ip);
|
||||
goto exit;
|
||||
}
|
||||
#else
|
||||
T_DbgStatus temp_status = {0};
|
||||
if ( ! t_dbg_status(&temp_status, T_Dbg_DefaultTimeout)) {
|
||||
t_errorf("ERROR: %S:%llu: failed to query IP\n", script->file_path, (unsigned long long)cmd->line);
|
||||
goto exit;
|
||||
}
|
||||
U64 ip = temp_status.ip;
|
||||
T_DbgStatus temp_status = {0};
|
||||
if ( ! t_dbg_status(&temp_status, T_Dbg_DefaultTimeout)) {
|
||||
t_errorf("ERROR: %S:%llu: failed to query IP\n", script->file_path, (unsigned long long)cmd->line);
|
||||
goto exit;
|
||||
}
|
||||
U64 ip = temp_status.ip;
|
||||
#endif
|
||||
|
||||
// map IP -> source location
|
||||
T_DbgSourceLocation loc = {0};
|
||||
if (t_dbg_src_line(scratch.arena, ip, &loc, T_Dbg_DefaultTimeout) == 0) {
|
||||
t_errorf("ERROR: %S:%llu: IP (0x%llx) does not map to a source line\n", script->file_path, (unsigned long long)cmd->line, (unsigned long long)ip);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// compute line where debugger must be
|
||||
S64 at_line_s64 = (S64)(program->line - program->file->line) + cmd->at.delta;
|
||||
U64 at_line_u64 = at_line_s64 >= 0 ? (U64)at_line_s64 : 0;
|
||||
AssertAlways(at_line_u64 > 0);
|
||||
|
||||
// match expected vs current debugger locations
|
||||
B32 mismatch = loc.pt.line != at_line_u64 ||
|
||||
!str8_match(loc.file_path, program->file->path, StringMatchFlag_CaseInsensitive|StringMatchFlag_SlashInsensitive);
|
||||
if (mismatch) {
|
||||
t_errorf("ERROR: %S:%llu: location check did not pass:\n", script->file_path, (unsigned long long)cmd->line);
|
||||
t_errorf(" Expected: %S:%llu\n", program->file->path, (unsigned long long)at_line_u64);
|
||||
t_errorf(" Got : %S:%llu\n", loc.file_path, (unsigned long long)loc.pt.line);
|
||||
t_errorf(" IP : 0x%llx\n", (unsigned long long)ip);
|
||||
goto exit;
|
||||
}
|
||||
} break;
|
||||
case T_DbgScriptCmdKind_Eval: NotImplemented; break;
|
||||
default: { InvalidPath; }
|
||||
|
||||
// map IP -> source location
|
||||
T_DbgSourceLocation loc = {0};
|
||||
if (t_dbg_src_line(scratch.arena, ip, &loc, T_Dbg_DefaultTimeout) == 0) {
|
||||
t_errorf("ERROR: %S:%llu: IP (0x%llx) does not map to a source line\n", script->file_path, (unsigned long long)cmd->line, (unsigned long long)ip);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// compute line where debugger must be
|
||||
S64 at_line_s64 = (S64)(program->line - program->file->line) + cmd->at.delta;
|
||||
U64 at_line_u64 = at_line_s64 >= 0 ? (U64)at_line_s64 : 0;
|
||||
AssertAlways(at_line_u64 > 0);
|
||||
|
||||
// match expected vs current debugger locations
|
||||
B32 mismatch = loc.pt.line != at_line_u64 ||
|
||||
!str8_match(loc.file_path, program->file->path, StringMatchFlag_CaseInsensitive|StringMatchFlag_SlashInsensitive);
|
||||
if (mismatch) {
|
||||
t_errorf("ERROR: %S:%llu: location check did not pass:\n", script->file_path, (unsigned long long)cmd->line);
|
||||
t_errorf(" Expected: %S:%llu\n", program->file->path, (unsigned long long)at_line_u64);
|
||||
t_errorf(" Got : %S:%llu\n", loc.file_path, (unsigned long long)loc.pt.line);
|
||||
t_errorf(" IP : 0x%llx\n", (unsigned long long)ip);
|
||||
goto exit;
|
||||
}
|
||||
} break;
|
||||
case T_DbgScriptCmdKind_Eval: NotImplemented; break;
|
||||
default: { InvalidPath; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
is_ok = (i+1 == script->program_count);
|
||||
}
|
||||
|
||||
|
||||
exit:;
|
||||
scratch_end(scratch);
|
||||
return is_ok;
|
||||
@@ -670,20 +752,20 @@ T_RunSig(dbg_script_runner)
|
||||
t_errorf("ERROR: failed to find debugger \"%S\"\n", t_raddbg_path());
|
||||
T_Ok(0);
|
||||
}
|
||||
|
||||
|
||||
// read source file
|
||||
String8 source = data_from_file_path(arena, user_data);
|
||||
if (source.size == 0) {
|
||||
t_errorf("ERROR: failed to read script: \"%S\"\n", user_data);
|
||||
T_Ok(0);
|
||||
}
|
||||
|
||||
|
||||
// source -> script
|
||||
T_DbgScript script = {0};
|
||||
if ( ! t_dbg_script_from_source(arena, user_data, source, &script)) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
||||
// write source files to test folder
|
||||
for EachNode(file, T_DbgScriptFile, script.files.first) {
|
||||
if (write_data_to_file_path(file->path, file->source) == 0) {
|
||||
@@ -691,32 +773,32 @@ T_RunSig(dbg_script_runner)
|
||||
T_Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// compiler vars
|
||||
HashTable *script_vars = hash_table_init(arena, 1000);
|
||||
hash_table_push_path_string(arena, script_vars, str8_lit("CWD"), g_wdir);
|
||||
|
||||
|
||||
// run compilers
|
||||
for EachNode(directive, T_DbgScriptDirective, script.directives[OperatingSystem_CURRENT][T_DbgScriptDirectiveKind_Compile].first) {
|
||||
T_Compiler compiler = directive->compile.compiler;
|
||||
|
||||
|
||||
// pick default compiler if none selected
|
||||
if (compiler == T_Compiler_Null) {
|
||||
switch (OperatingSystem_CURRENT) {
|
||||
case OperatingSystem_Windows: { compiler = T_Compiler_Cl; } break;
|
||||
case OperatingSystem_Linux: { compiler = T_Compiler_Clang; } break;
|
||||
case OperatingSystem_Windows: { compiler = T_Compiler_Cl; } break;
|
||||
case OperatingSystem_Linux: { compiler = T_Compiler_Clang; } break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get compiler path
|
||||
String8 compiler_path = {0};
|
||||
switch (compiler) {
|
||||
case T_Compiler_Null: break;
|
||||
case T_Compiler_Cl: compiler_path = t_cl_path(); break;
|
||||
case T_Compiler_Clang: compiler_path = t_clang_path(); break;
|
||||
case T_Compiler_Gcc: compiler_path = t_gcc_path(); break;
|
||||
case T_Compiler_Null: break;
|
||||
case T_Compiler_Cl: compiler_path = t_cl_path(); break;
|
||||
case T_Compiler_Clang: compiler_path = t_clang_path(); break;
|
||||
case T_Compiler_Gcc: compiler_path = t_gcc_path(); break;
|
||||
}
|
||||
|
||||
|
||||
// invoke compiler with arguments from directive
|
||||
String8 expanded_args = lnk_expand_env_vars_windows(arena, script_vars, directive->args);
|
||||
if (t_invoke(compiler_path, expanded_args, max_U64) == 0) {
|
||||
@@ -730,7 +812,7 @@ T_RunSig(dbg_script_runner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// run linkers
|
||||
String8 linker_path = t_radlink_path();
|
||||
for EachNode(directive, T_DbgScriptDirective, script.directives[OperatingSystem_CURRENT][T_DbgScriptDirectiveKind_Link].first) {
|
||||
@@ -744,7 +826,7 @@ T_RunSig(dbg_script_runner)
|
||||
T_Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// launch targets
|
||||
for EachNode(directive, T_DbgScriptDirective, script.directives[OperatingSystem_CURRENT][T_DbgScriptDirectiveKind_Launch].first) {
|
||||
String8 expanded_args = lnk_expand_env_vars_windows(arena, script_vars, directive->args);
|
||||
@@ -754,13 +836,13 @@ T_RunSig(dbg_script_runner)
|
||||
T_Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// debugger is ready -- now invoke script
|
||||
if (t_dbg_script_invoke(&script, T_Dbg_DefaultTimeout) == 0) {
|
||||
t_errorf("ERROR: %S: failed to run to completion\n", user_data);
|
||||
T_Ok(0);
|
||||
}
|
||||
|
||||
|
||||
// clean up
|
||||
#if OS_WINDOWS
|
||||
HANDLE dbg_handle = OpenProcess(PROCESS_ALL_ACCESS, 0, g_dbg_pid);
|
||||
@@ -771,7 +853,7 @@ T_RunSig(dbg_script_runner)
|
||||
#else
|
||||
kill(g_dbg_pid, SIGKILL);
|
||||
#endif
|
||||
|
||||
|
||||
exit:;
|
||||
g_dbg_pid = 0;
|
||||
}
|
||||
@@ -780,31 +862,31 @@ internal void
|
||||
t_dbg_register_script_tests(Arena *arena, String8 folder_path)
|
||||
{
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
|
||||
|
||||
if ( ! folder_path_exists(folder_path)) {
|
||||
t_errorf("ERROR: this folder does not exists: %S\n", folder_path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// gather file paths in a folder
|
||||
String8List paths = t_file_paths_from_dir(arena, folder_path);
|
||||
|
||||
|
||||
// register a test for each file
|
||||
for EachNode(n, String8Node, paths.first) {
|
||||
String8 file_path = n->string;
|
||||
|
||||
|
||||
// test files may contain dots for extensions we have to escape them when creating output folder for a test
|
||||
String8List file_name_parts = str8_split_by_string_chars(scratch.arena, str8_skip_last_slash(file_path), str8_lit("."), 0);
|
||||
String8 file_name_escaped = str8_list_join(arena, &file_name_parts, &(StringJoin){.sep=str8_lit("-"), .post = str8_lit("\0") });
|
||||
|
||||
|
||||
g_torture_tests_[g_torture_test_count++] = (T_Test){
|
||||
.group = T_Group,
|
||||
.label = (char*)file_name_escaped.str,
|
||||
.r = t_dbg_script_runner,
|
||||
.user_data = str8_copy(arena, file_path),
|
||||
.group = T_Group,
|
||||
.label = (char*)file_name_escaped.str,
|
||||
.r = t_dbg_script_runner,
|
||||
.user_data = str8_copy(arena, file_path),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user