diff --git a/core/os/os.h b/core/os/os.h index 943c0498f16..f6404468b13 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -320,6 +320,7 @@ public: virtual void disable_crash_handler() {} virtual bool is_disable_crash_handler() const { return false; } + virtual void initialize_debugging() {} enum CursorShape { CURSOR_ARROW, diff --git a/core/script_debugger_local.cpp b/core/script_debugger_local.cpp index c0e115e3005..55d7270473d 100644 --- a/core/script_debugger_local.cpp +++ b/core/script_debugger_local.cpp @@ -29,12 +29,23 @@ /*************************************************************************/ #include "script_debugger_local.h" +#include "scene/main/scene_tree.h" #include "os/os.h" void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue) { - print_line("Debugger Break, Reason: '" + p_script->debug_get_error() + "'"); + if (!target_function.empty()) { + String current_function = p_script->debug_get_stack_level_function(0); + if (current_function != target_function) { + set_depth(0); + set_lines_left(1); + return; + } + target_function = ""; + } + + print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'"); print_line("*Frame " + itos(0) + " - " + p_script->debug_get_stack_level_source(0) + ":" + itos(p_script->debug_get_stack_level_line(0)) + " in function '" + p_script->debug_get_stack_level_function(0) + "'"); print_line("Enter \"help\" for assistance."); int current_frame = 0; @@ -44,8 +55,11 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue) { OS::get_singleton()->print("debug> "); String line = OS::get_singleton()->get_stdin_string().strip_edges(); + // Cache options + String variable_prefix = options["variable_prefix"]; + if (line == "") { - print_line("Debugger Break, Reason: '" + p_script->debug_get_error() + "'"); + print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'"); print_line("*Frame " + itos(current_frame) + " - " + p_script->debug_get_stack_level_source(current_frame) + ":" + itos(p_script->debug_get_stack_level_line(current_frame)) + " in function '" + p_script->debug_get_stack_level_function(current_frame) + "'"); print_line("Enter \"help\" for assistance."); } else if (line == "c" || line == "continue") @@ -72,38 +86,56 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue) { } } + } else if (line.begins_with("set")) { + + if (line.get_slice_count(" ") == 1) { + + for (Map::Element *E = options.front(); E; E = E->next()) { + print_line("\t" + E->key() + "=" + E->value()); + } + + } else { + String key_value = line.get_slicec(' ', 1); + int value_pos = key_value.find("="); + + if (value_pos < 0) { + print_line("Error: Invalid set format. Use: set key=value"); + } else { + + String key = key_value.left(value_pos); + + if (!options.has(key)) { + print_line("Error: Unknown option " + key); + } else { + + // Allow explicit tab character + String value = key_value.right(value_pos + 1).replace("\\t", "\t"); + + options[key] = value; + } + } + } + } else if (line == "lv" || line == "locals") { List locals; List values; p_script->debug_get_stack_level_locals(current_frame, &locals, &values); - List::Element *V = values.front(); - for (List::Element *E = locals.front(); E; E = E->next()) { - print_line(E->get() + ": " + String(V->get())); - V = V->next(); - } + print_variables(locals, values, variable_prefix); } else if (line == "gv" || line == "globals") { - List locals; + List globals; List values; - p_script->debug_get_globals(&locals, &values); - List::Element *V = values.front(); - for (List::Element *E = locals.front(); E; E = E->next()) { - print_line(E->get() + ": " + String(V->get())); - V = V->next(); - } + p_script->debug_get_globals(&globals, &values); + print_variables(globals, values, variable_prefix); } else if (line == "mv" || line == "members") { - List locals; + List members; List values; - p_script->debug_get_stack_level_members(current_frame, &locals, &values); - List::Element *V = values.front(); - for (List::Element *E = locals.front(); E; E = E->next()) { - print_line(E->get() + ": " + String(V->get())); - V = V->next(); - } + p_script->debug_get_stack_level_members(current_frame, &members, &values); + print_variables(members, values, variable_prefix); } else if (line.begins_with("p") || line.begins_with("print")) { @@ -121,65 +153,149 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue) { set_depth(-1); set_lines_left(1); break; - } else if (line.begins_with("n") || line.begins_with("next")) { + } else if (line == "n" || line == "next") { set_depth(0); set_lines_left(1); break; + } else if (line == "fin" || line == "finish") { + + String current_function = p_script->debug_get_stack_level_function(0); + + for (int i = 0; i < total_frames; i++) { + target_function = p_script->debug_get_stack_level_function(i); + if (target_function != current_function) { + set_depth(0); + set_lines_left(1); + return; + } + } + + print_line("Error: Reached last frame."); + target_function = ""; + } else if (line.begins_with("br") || line.begins_with("break")) { if (line.get_slice_count(" ") <= 1) { - //show breakpoints + + const Map > &breakpoints = get_breakpoints(); + if (breakpoints.size() == 0) { + print_line("No Breakpoints."); + continue; + } + + print_line("Breakpoint(s): " + itos(breakpoints.size())); + for (Map >::Element *E = breakpoints.front(); E; E = E->next()) { + print_line("\t" + String(E->value().front()->get()) + ":" + itos(E->key())); + } + } else { - String bppos = line.get_slicec(' ', 1); - String source = bppos.get_slicec(':', 0).strip_edges(); - int line = bppos.get_slicec(':', 1).strip_edges().to_int(); + Pair breakpoint = to_breakpoint(line); - source = breakpoint_find_source(source); + String source = breakpoint.first; + int linenr = breakpoint.second; - insert_breakpoint(line, source); + if (source.empty()) + continue; - print_line("BreakPoint at " + source + ":" + itos(line)); + insert_breakpoint(linenr, source); + + print_line("Added breakpoint at " + source + ":" + itos(linenr)); } + } else if (line == "q" || line == "quit") { + + // Do not stop again on quit + clear_breakpoints(); + ScriptDebugger::get_singleton()->set_depth(-1); + ScriptDebugger::get_singleton()->set_lines_left(-1); + + SceneTree::get_singleton()->quit(); + break; } else if (line.begins_with("delete")) { if (line.get_slice_count(" ") <= 1) { clear_breakpoints(); } else { - String bppos = line.get_slicec(' ', 1); - String source = bppos.get_slicec(':', 0).strip_edges(); - int line = bppos.get_slicec(':', 1).strip_edges().to_int(); + Pair breakpoint = to_breakpoint(line); - source = breakpoint_find_source(source); + String source = breakpoint.first; + int linenr = breakpoint.second; - remove_breakpoint(line, source); + if (source.empty()) + continue; - print_line("Removed BreakPoint at " + source + ":" + itos(line)); + remove_breakpoint(linenr, source); + + print_line("Removed breakpoint at " + source + ":" + itos(linenr)); } } else if (line == "h" || line == "help") { print_line("Built-In Debugger command list:\n"); - print_line("\tc,continue :\t\t Continue execution."); - print_line("\tbt,backtrace :\t\t Show stack trace (frames)."); + print_line("\tc,continue\t\t Continue execution."); + print_line("\tbt,backtrace\t\t Show stack trace (frames)."); print_line("\tfr,frame :\t Change current frame."); - print_line("\tlv,locals :\t\t Show local variables for current frame."); - print_line("\tmv,members :\t\t Show member variables for \"this\" in frame."); - print_line("\tgv,globals :\t\t Show global variables."); - print_line("\tp,print :\t Execute and print variable in expression."); - print_line("\ts,step :\t\t Step to next line."); - print_line("\tn,next :\t\t Next line."); - print_line("\tbr,break source:line :\t Place a breakpoint."); - print_line("\tdelete [source:line]:\t\t Delete one/all breakpoints."); + print_line("\tlv,locals\t\t Show local variables for current frame."); + print_line("\tmv,members\t\t Show member variables for \"this\" in frame."); + print_line("\tgv,globals\t\t Show global variables."); + print_line("\tp,print \t\t Execute and print variable in expression."); + print_line("\ts,step\t\t\t Step to next line."); + print_line("\tn,next\t\t\t Next line."); + print_line("\tfin,finish\t\t Step out of current frame."); + print_line("\tbr,break [source:line]\t List all breakpoints or place a breakpoint."); + print_line("\tdelete [source:line]:\t Delete one/all breakpoints."); + print_line("\tset [key=value]:\t List all options, or set one."); + print_line("\tq,quit\t\t\t Quit application."); } else { print_line("Error: Invalid command, enter \"help\" for assistance."); } } } +void ScriptDebuggerLocal::print_variables(const List &names, const List &values, const String &variable_prefix) { + + String value; + Vector value_lines; + const List::Element *V = values.front(); + for (const List::Element *E = names.front(); E; E = E->next()) { + + value = String(V->get()); + + if (variable_prefix.empty()) { + print_line(E->get() + ": " + String(V->get())); + } else { + + print_line(E->get() + ":"); + value_lines = value.split("\n"); + for (int i = 0; i < value_lines.size(); ++i) { + print_line(variable_prefix + value_lines[i]); + } + } + + V = V->next(); + } +} + +Pair ScriptDebuggerLocal::to_breakpoint(const String &p_line) { + + String breakpoint_part = p_line.get_slicec(' ', 1); + Pair breakpoint; + + int last_colon = breakpoint_part.rfind(":"); + if (last_colon < 0) { + print_line("Error: Invalid breakpoint format. Expected [source:line]"); + return breakpoint; + } + + breakpoint.first = breakpoint_find_source(breakpoint_part.left(last_colon).strip_edges()); + breakpoint.second = breakpoint_part.right(last_colon).strip_edges().to_int(); + + return breakpoint; +} + struct _ScriptDebuggerLocalProfileInfoSort { bool operator()(const ScriptLanguage::ProfilingInfo &A, const ScriptLanguage::ProfilingInfo &B) const { @@ -304,4 +420,5 @@ ScriptDebuggerLocal::ScriptDebuggerLocal() { profiling = false; idle_accum = OS::get_singleton()->get_ticks_usec(); + options["variable_prefix"] = ""; } diff --git a/core/script_debugger_local.h b/core/script_debugger_local.h index c87bc90bb4d..7eea6ef2157 100644 --- a/core/script_debugger_local.h +++ b/core/script_debugger_local.h @@ -31,6 +31,7 @@ #ifndef SCRIPT_DEBUGGER_LOCAL_H #define SCRIPT_DEBUGGER_LOCAL_H +#include "list.h" #include "script_language.h" class ScriptDebuggerLocal : public ScriptDebugger { @@ -38,9 +39,14 @@ class ScriptDebuggerLocal : public ScriptDebugger { bool profiling; float frame_time, idle_time, physics_time, physics_frame_time; uint64_t idle_accum; + String target_function; + Map options; Vector pinfo; + Pair to_breakpoint(const String &p_line); + void print_variables(const List &names, const List &values, const String &variable_prefix); + public: void debug(ScriptLanguage *p_script, bool p_can_continue); virtual void send_message(const String &p_message, const Array &p_args); diff --git a/core/script_language.h b/core/script_language.h index 0c1f99cea69..55a20c74780 100644 --- a/core/script_language.h +++ b/core/script_language.h @@ -386,6 +386,7 @@ public: bool is_breakpoint(int p_line, const StringName &p_source) const; bool is_breakpoint_line(int p_line) const; void clear_breakpoints(); + const Map > &get_breakpoints() const { return breakpoints; } virtual void debug(ScriptLanguage *p_script, bool p_can_continue = true) = 0; virtual void idle_poll(); diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index eeb3b31fc2e..1e34d63b130 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -73,6 +73,23 @@ void OS_Unix::debug_break() { assert(false); }; +static void handle_interrupt(int sig) { + if (ScriptDebugger::get_singleton() == NULL) + return; + + ScriptDebugger::get_singleton()->set_depth(-1); + ScriptDebugger::get_singleton()->set_lines_left(1); +} + +void OS_Unix::initialize_debugging() { + + if (ScriptDebugger::get_singleton() != NULL) { + struct sigaction action; + action.sa_handler = handle_interrupt; + sigaction(SIGINT, &action, NULL); + } +} + int OS_Unix::unix_initialize_audio(int p_audio_driver) { return 0; diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index db0fe1e00bd..95b74d23ff2 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -101,6 +101,7 @@ public: virtual int get_processor_count() const; virtual void debug_break(); + virtual void initialize_debugging(); virtual String get_executable_path() const; virtual String get_user_data_dir() const; diff --git a/main/main.cpp b/main/main.cpp index fa60bd4e964..5959deb4955 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -730,6 +730,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } else if (debug_mode == "local") { script_debugger = memnew(ScriptDebuggerLocal); + OS::get_singleton()->initialize_debugging(); } FileAccessNetwork::configure(); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index bed5781ae4f..7b46608c55c 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -152,6 +152,25 @@ void RedirectIOToConsole() { // point to console as well } +BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) { + if (ScriptDebugger::get_singleton() == NULL) + return FALSE; + + switch (dwCtrlType) { + case CTRL_C_EVENT: + ScriptDebugger::get_singleton()->set_depth(-1); + ScriptDebugger::get_singleton()->set_lines_left(1); + return TRUE; + default: + return FALSE; + } +} + +void OS_Windows::initialize_debugging() { + + SetConsoleCtrlHandler(HandlerRoutine, TRUE); +} + void OS_Windows::initialize_core() { crash_handler.initialize(); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 3d13627bfaa..584f6fb334d 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -294,6 +294,7 @@ public: void disable_crash_handler(); bool is_disable_crash_handler() const; + virtual void initialize_debugging(); void force_process_input();