Browse Source

Merge pull request #15463 from neikeq/the-stack-frame-madness

Mono: Implement stack info for errors and exceptions
Rémi Verschelde 7 years ago
parent
commit
c037f6339f

+ 5 - 0
core/script_debugger_local.cpp

@@ -294,6 +294,11 @@ void ScriptDebuggerLocal::send_message(const String &p_message, const Array &p_a
 	print_line("MESSAGE: '" + p_message + "' - " + String(Variant(p_args)));
 }
 
+void ScriptDebuggerLocal::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info) {
+
+	print_line("ERROR: '" + (p_descr.empty() ? p_err : p_descr) + "'");
+}
+
 ScriptDebuggerLocal::ScriptDebuggerLocal() {
 
 	profiling = false;

+ 1 - 0
core/script_debugger_local.h

@@ -44,6 +44,7 @@ class ScriptDebuggerLocal : public ScriptDebugger {
 public:
 	void debug(ScriptLanguage *p_script, bool p_can_continue);
 	virtual void send_message(const String &p_message, const Array &p_args);
+	virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info);
 
 	virtual bool is_profiling() const { return profiling; }
 	virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data) {}

+ 41 - 42
core/script_debugger_remote.cpp

@@ -432,22 +432,6 @@ void ScriptDebuggerRemote::_err_handler(void *ud, const char *p_func, const char
 	if (p_type == ERR_HANDLER_SCRIPT)
 		return; //ignore script errors, those go through debugger
 
-	ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)ud;
-
-	OutputError oe;
-	oe.error = p_err;
-	oe.error_descr = p_descr;
-	oe.source_file = p_file;
-	oe.source_line = p_line;
-	oe.source_func = p_func;
-	oe.warning = p_type == ERR_HANDLER_WARNING;
-	uint64_t time = OS::get_singleton()->get_ticks_msec();
-	oe.hr = time / 3600000;
-	oe.min = (time / 60000) % 60;
-	oe.sec = (time / 1000) % 60;
-	oe.msec = time % 1000;
-	Array cstack;
-
 	Vector<ScriptLanguage::StackInfo> si;
 
 	for (int i = 0; i < ScriptServer::get_language_count(); i++) {
@@ -456,32 +440,8 @@ void ScriptDebuggerRemote::_err_handler(void *ud, const char *p_func, const char
 			break;
 	}
 
-	cstack.resize(si.size() * 2);
-	for (int i = 0; i < si.size(); i++) {
-		String path;
-		int line = 0;
-		if (si[i].script.is_valid()) {
-			path = si[i].script->get_path();
-			line = si[i].line;
-		}
-		cstack[i * 2 + 0] = path;
-		cstack[i * 2 + 1] = line;
-	}
-
-	oe.callstack = cstack;
-
-	sdr->mutex->lock();
-
-	if (!sdr->locking && sdr->tcp_client->is_connected_to_host()) {
-
-		if (sdr->errors.size() >= sdr->max_errors_per_frame) {
-			sdr->n_errors_dropped++;
-		} else {
-			sdr->errors.push_back(oe);
-		}
-	}
-
-	sdr->mutex->unlock();
+	ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)ud;
+	sdr->send_error(p_func, p_file, p_line, p_err, p_descr, p_type, si);
 }
 
 bool ScriptDebuggerRemote::_parse_live_edit(const Array &p_command) {
@@ -928,6 +888,45 @@ void ScriptDebuggerRemote::send_message(const String &p_message, const Array &p_
 	mutex->unlock();
 }
 
+void ScriptDebuggerRemote::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info) {
+
+	OutputError oe;
+	oe.error = p_err;
+	oe.error_descr = p_descr;
+	oe.source_file = p_file;
+	oe.source_line = p_line;
+	oe.source_func = p_func;
+	oe.warning = p_type == ERR_HANDLER_WARNING;
+	uint64_t time = OS::get_singleton()->get_ticks_msec();
+	oe.hr = time / 3600000;
+	oe.min = (time / 60000) % 60;
+	oe.sec = (time / 1000) % 60;
+	oe.msec = time % 1000;
+	Array cstack;
+
+	cstack.resize(p_stack_info.size() * 3);
+	for (int i = 0; i < p_stack_info.size(); i++) {
+		cstack[i * 3 + 0] = p_stack_info[i].file;
+		cstack[i * 3 + 1] = p_stack_info[i].func;
+		cstack[i * 3 + 2] = p_stack_info[i].line;
+	}
+
+	oe.callstack = cstack;
+
+	mutex->lock();
+
+	if (!locking && tcp_client->is_connected_to_host()) {
+
+		if (errors.size() >= max_errors_per_frame) {
+			n_errors_dropped++;
+		} else {
+			errors.push_back(oe);
+		}
+	}
+
+	mutex->unlock();
+}
+
 void ScriptDebuggerRemote::_print_handler(void *p_this, const String &p_string, bool p_error) {
 
 	ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)p_this;

+ 1 - 0
core/script_debugger_remote.h

@@ -157,6 +157,7 @@ public:
 	virtual void request_quit();
 
 	virtual void send_message(const String &p_message, const Array &p_args);
+	virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info);
 
 	virtual void set_request_scene_tree_message_func(RequestSceneTreeMessageFunc p_func, void *p_udata);
 	virtual void set_live_edit_funcs(LiveEditFuncs *p_funcs);

+ 3 - 1
core/script_language.h

@@ -254,7 +254,8 @@ public:
 	virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) = 0;
 
 	struct StackInfo {
-		Ref<Script> script;
+		String file;
+		String func;
 		int line;
 	};
 
@@ -391,6 +392,7 @@ public:
 	ScriptLanguage *get_break_language() const;
 
 	virtual void send_message(const String &p_message, const Array &p_args) = 0;
+	virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info) = 0;
 
 	virtual bool is_remote() const { return false; }
 	virtual void request_quit() {}

+ 13 - 15
editor/plugins/script_editor_plugin.cpp

@@ -313,24 +313,22 @@ void ScriptEditor::_goto_script_line2(int p_line) {
 
 void ScriptEditor::_goto_script_line(REF p_script, int p_line) {
 
-	editor->push_item(p_script.ptr());
+	Ref<Script> script = Object::cast_to<Script>(*p_script);
+	if (!script.is_null() && script->get_path().is_resource_file()) {
+		if (edit(p_script, p_line, 0)) {
+			editor->push_item(p_script.ptr());
 
-	if (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) {
-
-		Ref<Script> script = Object::cast_to<Script>(*p_script);
-		if (!script.is_null() && script->get_path().is_resource_file())
-			edit(p_script, p_line, 0);
-	}
-
-	int selected = tab_container->get_current_tab();
-	if (selected < 0 || selected >= tab_container->get_child_count())
-		return;
+			int selected = tab_container->get_current_tab();
+			if (selected < 0 || selected >= tab_container->get_child_count())
+				return;
 
-	ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
-	if (!current)
-		return;
+			ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
+			if (!current)
+				return;
 
-	current->goto_line(p_line, true);
+			current->goto_line(p_line, true);
+		}
+	}
 }
 
 void ScriptEditor::_update_history_arrows() {

+ 9 - 6
editor/script_editor_debugger.cpp

@@ -1616,30 +1616,33 @@ void ScriptEditorDebugger::_error_selected(int p_idx) {
 
 	error_stack->clear();
 	Array st = error_list->get_item_metadata(p_idx);
-	for (int i = 0; i < st.size(); i += 2) {
+	for (int i = 0; i < st.size(); i += 3) {
 
 		String script = st[i];
-		int line = st[i + 1];
+		String func = st[i + 1];
+		int line = st[i + 2];
 		Array md;
 		md.push_back(st[i]);
 		md.push_back(st[i + 1]);
+		md.push_back(st[i + 2]);
 
-		String str = script.get_file() + ":" + itos(line);
+		String str = func + " in " + script.get_file() + ":line " + itos(line);
 
 		error_stack->add_item(str);
 		error_stack->set_item_metadata(error_stack->get_item_count() - 1, md);
-		error_stack->set_item_tooltip(error_stack->get_item_count() - 1, TTR("File:") + " " + String(st[i]) + "\n" + TTR("Line:") + " " + itos(line));
+		error_stack->set_item_tooltip(error_stack->get_item_count() - 1,
+				TTR("File:") + " " + script + "\n" + TTR("Function:") + " " + func + "\n" + TTR("Line:") + " " + itos(line));
 	}
 }
 
 void ScriptEditorDebugger::_error_stack_selected(int p_idx) {
 
 	Array arr = error_stack->get_item_metadata(p_idx);
-	if (arr.size() != 2)
+	if (arr.size() != 3)
 		return;
 
 	Ref<Script> s = ResourceLoader::load(arr[0]);
-	emit_signal("goto_script_line", s, int(arr[1]) - 1);
+	emit_signal("goto_script_line", s, int(arr[2]) - 1);
 }
 
 void ScriptEditorDebugger::set_hide_on_stop(bool p_hide) {

+ 3 - 1
modules/gdscript/gdscript.h

@@ -349,7 +349,9 @@ public:
 		csi.resize(_debug_call_stack_pos);
 		for (int i = 0; i < _debug_call_stack_pos; i++) {
 			csi[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0;
-			csi[_debug_call_stack_pos - i - 1].script = Ref<GDScript>(_call_stack[i].function->get_script());
+			if (_call_stack[i].function)
+				csi[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name();
+			csi[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path();
 		}
 		return csi;
 	}

+ 69 - 3
modules/mono/csharp_script.cpp

@@ -445,6 +445,72 @@ String CSharpLanguage::_get_indentation() const {
 	return "\t";
 }
 
+Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() {
+
+	// Printing an error here will result in endless recursion, so we must be careful
+
+	if (!gdmono->is_runtime_initialized() && GDMono::get_singleton()->get_editor_tools_assembly())
+		return Vector<StackInfo>();
+
+	MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr());
+
+	MonoBoolean need_file_info = true;
+	void *ctor_args[1] = { &need_file_info };
+
+	CACHED_METHOD(System_Diagnostics_StackTrace, ctor_bool)->invoke_raw(stack_trace, ctor_args);
+
+	Vector<StackInfo> si;
+	si = stack_trace_get_info(stack_trace);
+
+	return si;
+}
+
+Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) {
+
+	// Printing an error here could result in endless recursion, so we must be careful
+
+	MonoObject *exc = NULL;
+
+	GDMonoUtils::StackTrace_GetFrames st_get_frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames);
+	MonoArray *frames = st_get_frames(p_stack_trace, &exc);
+
+	if (exc) {
+		GDMonoUtils::print_unhandled_exception(exc, true /* fail silently to avoid endless recursion */);
+		return Vector<StackInfo>();
+	}
+
+	int frame_count = mono_array_length(frames);
+
+	if (frame_count <= 0)
+		return Vector<StackInfo>();
+
+	GDMonoUtils::DebugUtils_StackFrameInfo get_sf_info = CACHED_METHOD_THUNK(DebuggingUtils, GetStackFrameInfo);
+
+	Vector<StackInfo> si;
+	si.resize(frame_count);
+
+	for (int i = 0; i < frame_count; i++) {
+		StackInfo &sif = si[i];
+		MonoObject *frame = mono_array_get(frames, MonoObject *, i);
+
+		MonoString *file_name;
+		int file_line_num;
+		MonoString *method_decl;
+		get_sf_info(frame, &file_name, &file_line_num, &method_decl, &exc);
+
+		if (exc) {
+			GDMonoUtils::print_unhandled_exception(exc, true /* fail silently to avoid endless recursion */);
+			return Vector<StackInfo>();
+		}
+
+		sif.file = GDMonoMarshal::mono_string_to_godot(file_name);
+		sif.line = file_line_num;
+		sif.func = GDMonoMarshal::mono_string_to_godot(method_decl);
+	}
+
+	return si;
+}
+
 void CSharpLanguage::frame() {
 
 	const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle;
@@ -1049,7 +1115,7 @@ bool CSharpInstance::has_method(const StringName &p_method) const {
 	GDMonoClass *top = script->script_class;
 
 	while (top && top != script->native) {
-		if (top->has_method(p_method)) {
+		if (top->has_fetched_method_unknown_params(p_method)) {
 			return true;
 		}
 
@@ -1227,7 +1293,7 @@ ScriptInstance::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method)
 	GDMonoClass *top = script->script_class;
 
 	while (top && top != script->native) {
-		GDMonoMethod *method = top->get_method(p_method);
+		GDMonoMethod *method = top->get_fetched_method_unknown_params(p_method);
 
 		if (method && !method->is_static())
 			return _member_get_rpc_mode(method);
@@ -1848,7 +1914,7 @@ void CSharpScript::set_source_code(const String &p_code) {
 
 bool CSharpScript::has_method(const StringName &p_method) const {
 
-	return script_class->has_method(p_method);
+	return script_class->has_fetched_method_unknown_params(p_method);
 }
 
 Error CSharpScript::reload(bool p_keep_state) {

+ 3 - 1
modules/mono/csharp_script.h

@@ -303,7 +303,7 @@ public:
 	/* TODO */ virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {}
 	/* TODO */ virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {}
 	/* TODO */ virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) { return ""; }
-	/* TODO */ virtual Vector<StackInfo> debug_get_current_stack_info() { return Vector<StackInfo>(); }
+	virtual Vector<StackInfo> debug_get_current_stack_info();
 
 	/* PROFILING FUNCTIONS */
 	/* TODO */ virtual void profiling_start() {}
@@ -335,6 +335,8 @@ public:
 	virtual void *alloc_instance_binding_data(Object *p_object);
 	virtual void free_instance_binding_data(void *p_data);
 
+	Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace);
+
 	CSharpLanguage();
 	~CSharpLanguage();
 };

+ 6 - 1
modules/mono/editor/godotsharp_editor.cpp

@@ -151,7 +151,7 @@ Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int
 
 			if (p_line >= 0) {
 				args.push_back("-g");
-				args.push_back(script_path + ":" + itos(p_line) + ":" + itos(p_col));
+				args.push_back(script_path + ":" + itos(p_line + 1) + ":" + itos(p_col));
 			} else {
 				args.push_back(script_path);
 			}
@@ -170,6 +170,11 @@ Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int
 				monodevel_instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path()));
 
 			String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path());
+
+			if (p_line >= 0) {
+				script_path += ";" + itos(p_line + 1) + ";" + itos(p_col);
+			}
+
 			monodevel_instance->execute(script_path);
 		} break;
 		default:

+ 77 - 0
modules/mono/glue/cs_files/DebuggingUtils.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text;
+
+namespace Godot
+{
+    internal static class DebuggingUtils
+    {
+        internal static void AppendTypeName(this StringBuilder sb, Type type)
+        {
+            if (type.IsPrimitive)
+                sb.Append(type.Name);
+            else if (type == typeof(void))
+                sb.Append("void");
+            else
+                sb.Append(type.ToString());
+
+            sb.Append(" ");
+        }
+
+        public static void GetStackFrameInfo(StackFrame frame, out string fileName, out int fileLineNumber, out string methodDecl)
+        {
+            fileName = frame.GetFileName();
+            fileLineNumber = frame.GetFileLineNumber();
+
+            MethodBase methodBase = frame.GetMethod();
+
+            StringBuilder sb = new StringBuilder();
+
+            if (methodBase is MethodInfo methodInfo)
+                sb.AppendTypeName(methodInfo.ReturnType);
+
+            sb.Append(methodBase.DeclaringType.FullName);
+            sb.Append(".");
+            sb.Append(methodBase.Name);
+
+            if (methodBase.IsGenericMethod)
+            {
+                Type[] genericParams = methodBase.GetGenericArguments();
+
+                sb.Append("<");
+
+                for (int j = 0; j < genericParams.Length; j++)
+                {
+                    if (j > 0)
+                        sb.Append(", ");
+
+                    sb.AppendTypeName(genericParams[j]);
+                }
+
+                sb.Append(">");
+            }
+
+            sb.Append("(");
+
+            bool varArgs = (methodBase.CallingConvention & CallingConventions.VarArgs) != 0;
+
+            ParameterInfo[] parameter = methodBase.GetParameters();
+
+            for (int i = 0; i < parameter.Length; i++)
+            {
+                if (i > 0)
+                    sb.Append(", ");
+
+                if (i == parameter.Length - 1 && varArgs)
+                    sb.Append("params ");
+
+                sb.AppendTypeName(parameter[i].ParameterType);
+            }
+
+            sb.Append(")");
+
+            methodDecl = sb.ToString();
+        }
+    }
+}

+ 1 - 1
modules/mono/mono_gd/gd_mono.cpp

@@ -226,7 +226,7 @@ void GDMono::initialize() {
 
 	mono_install_unhandled_exception_hook(gdmono_unhandled_exception_hook, NULL);
 
-	OS::get_singleton()->print("Mono: ALL IS GOOD\n");
+	OS::get_singleton()->print("Mono: INITIALIZED\n");
 }
 
 #ifndef MONO_GLUE_DISABLED

+ 8 - 6
modules/mono/mono_gd/gd_mono_class.cpp

@@ -89,11 +89,6 @@ Vector<MonoClassField *> GDMonoClass::get_enum_fields() {
 }
 #endif
 
-bool GDMonoClass::has_method(const StringName &p_name) {
-
-	return get_method(p_name) != NULL;
-}
-
 bool GDMonoClass::has_attribute(GDMonoClass *p_attr_class) {
 
 #ifdef DEBUG_ENABLED
@@ -225,7 +220,7 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
 	methods_fetched = true;
 }
 
-GDMonoMethod *GDMonoClass::get_method(const StringName &p_name) {
+GDMonoMethod *GDMonoClass::get_fetched_method_unknown_params(const StringName &p_name) {
 
 	ERR_FAIL_COND_V(!methods_fetched, NULL);
 
@@ -239,6 +234,11 @@ GDMonoMethod *GDMonoClass::get_method(const StringName &p_name) {
 	return NULL;
 }
 
+bool GDMonoClass::has_fetched_method_unknown_params(const StringName &p_name) {
+
+	return get_fetched_method_unknown_params(p_name) != NULL;
+}
+
 GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, int p_params_count) {
 
 	MethodKey key = MethodKey(p_name, p_params_count);
@@ -303,6 +303,8 @@ GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, boo
 	MonoMethod *method = mono_method_desc_search_in_class(desc, mono_class);
 	mono_method_desc_free(desc);
 
+	ERR_FAIL_COND_V(mono_method_get_class(method) != mono_class, NULL);
+
 	return get_method(method);
 }
 

+ 3 - 3
modules/mono/mono_gd/gd_mono_class.h

@@ -112,7 +112,8 @@ public:
 	Vector<MonoClassField *> get_enum_fields();
 #endif
 
-	bool has_method(const StringName &p_name);
+	GDMonoMethod *get_fetched_method_unknown_params(const StringName &p_name);
+	bool has_fetched_method_unknown_params(const StringName &p_name);
 
 	bool has_attribute(GDMonoClass *p_attr_class);
 	MonoObject *get_attribute(GDMonoClass *p_attr_class);
@@ -120,8 +121,7 @@ public:
 	void fetch_attributes();
 	void fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base);
 
-	GDMonoMethod *get_method(const StringName &p_name);
-	GDMonoMethod *get_method(const StringName &p_name, int p_params_count);
+	GDMonoMethod *get_method(const StringName &p_name, int p_params_count = 0);
 	GDMonoMethod *get_method(MonoMethod *p_raw_method);
 	GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name);
 	GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name, int p_params_count);

+ 70 - 3
modules/mono/mono_gd/gd_mono_utils.cpp

@@ -31,6 +31,7 @@
 #include "gd_mono_utils.h"
 
 #include "os/dir_access.h"
+#include "os/os.h"
 #include "project_settings.h"
 #include "reference.h"
 
@@ -53,6 +54,7 @@ MonoCache mono_cache;
 #define CACHE_NS_CLASS_AND_CHECK(m_ns, m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.class_##m_ns##_##m_class, m_val)
 #define CACHE_RAW_MONO_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.rawclass_##m_class, m_val)
 #define CACHE_FIELD_AND_CHECK(m_class, m_field, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.field_##m_class##_##m_field, m_val)
+#define CACHE_METHOD_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.method_##m_class##_##m_method, m_val)
 #define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method, m_val)
 
 void MonoCache::clear_members() {
@@ -72,6 +74,13 @@ void MonoCache::clear_members() {
 	class_String = NULL;
 	class_IntPtr = NULL;
 
+#ifdef DEBUG_ENABLED
+	class_System_Diagnostics_StackTrace = NULL;
+	methodthunk_System_Diagnostics_StackTrace_GetFrames = NULL;
+	method_System_Diagnostics_StackTrace_ctor_bool = NULL;
+	method_System_Diagnostics_StackTrace_ctor_Exception_bool = NULL;
+#endif
+
 	rawclass_Dictionary = NULL;
 
 	class_Vector2 = NULL;
@@ -94,6 +103,11 @@ void MonoCache::clear_members() {
 	class_WeakRef = NULL;
 	class_MarshalUtils = NULL;
 
+#ifdef DEBUG_ENABLED
+	class_DebuggingUtils = NULL;
+	methodthunk_DebuggingUtils_GetStackFrameInfo = NULL;
+#endif
+
 	class_ExportAttribute = NULL;
 	field_ExportAttribute_hint = NULL;
 	field_ExportAttribute_hintString = NULL;
@@ -137,6 +151,13 @@ void update_corlib_cache() {
 	CACHE_CLASS_AND_CHECK(double, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_double_class()));
 	CACHE_CLASS_AND_CHECK(String, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_string_class()));
 	CACHE_CLASS_AND_CHECK(IntPtr, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_intptr_class()));
+
+#ifdef DEBUG_ENABLED
+	CACHE_CLASS_AND_CHECK(System_Diagnostics_StackTrace, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Diagnostics", "StackTrace"));
+	CACHE_METHOD_THUNK_AND_CHECK(System_Diagnostics_StackTrace, GetFrames, (StackTrace_GetFrames)CACHED_CLASS(System_Diagnostics_StackTrace)->get_method("GetFrames")->get_thunk());
+	CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(bool)", true));
+	CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true));
+#endif
 }
 
 void update_godot_api_cache() {
@@ -161,6 +182,10 @@ void update_godot_api_cache() {
 	CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef));
 	CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils));
 
+#ifdef DEBUG_ENABLED
+	CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils));
+#endif
+
 	// Attributes
 	CACHE_CLASS_AND_CHECK(ExportAttribute, GODOT_API_CLASS(ExportAttribute));
 	CACHE_FIELD_AND_CHECK(ExportAttribute, hint, CACHED_CLASS(ExportAttribute)->get_field("hint"));
@@ -183,6 +208,10 @@ void update_godot_api_cache() {
 	CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method("FailureCallback", 0)->get_thunk());
 	CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0)->get_thunk());
 
+#ifdef DEBUG_ENABLED
+	CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method("GetStackFrameInfo", 4)->get_thunk());
+#endif
+
 	{
 		/*
 		 * TODO Right now we only support Dictionary<object, object>.
@@ -366,9 +395,47 @@ String get_exception_name_and_message(MonoObject *p_ex) {
 	return res;
 }
 
-void print_unhandled_exception(MonoObject *p_ex) {
-	ERR_PRINT(GDMonoUtils::get_exception_name_and_message(p_ex).utf8());
-	mono_print_unhandled_exception(p_ex);
+void print_unhandled_exception(MonoObject *p_exc) {
+	print_unhandled_exception(p_exc, false);
+}
+
+void print_unhandled_exception(MonoObject *p_exc, bool p_fail_silently) {
+	mono_print_unhandled_exception(p_exc);
+#ifdef DEBUG_ENABLED
+	GDMonoClass *st_klass = CACHED_CLASS(System_Diagnostics_StackTrace);
+	MonoObject *stack_trace = mono_object_new(mono_domain_get(), st_klass->get_mono_ptr());
+
+	MonoBoolean need_file_info = true;
+	void *ctor_args[2] = { p_exc, &need_file_info };
+
+	MonoObject *unexpected_exc = NULL;
+	CACHED_METHOD(System_Diagnostics_StackTrace, ctor_Exception_bool)->invoke_raw(stack_trace, ctor_args, &unexpected_exc);
+
+	if (unexpected_exc != NULL) {
+		mono_print_unhandled_exception(unexpected_exc);
+
+		if (p_fail_silently) {
+			// Called from CSharpLanguage::get_current_stack_info,
+			// so printing an error here could result in endless recursion
+			OS::get_singleton()->printerr("Mono: Method GDMonoUtils::print_unhandled_exception failed");
+			return;
+		} else {
+			ERR_FAIL();
+		}
+	}
+
+	Vector<ScriptLanguage::StackInfo> si;
+	if (stack_trace != NULL)
+		si = CSharpLanguage::get_singleton()->stack_trace_get_info(stack_trace);
+
+	String file = si.size() ? si[0].file : __FILE__;
+	String func = si.size() ? si[0].func : FUNCTION_STR;
+	int line = si.size() ? si[0].line : __LINE__;
+	String error_msg = "Unhandled exception";
+	String exc_msg = GDMonoUtils::get_exception_name_and_message(p_exc);
+
+	ScriptDebugger::get_singleton()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si);
+#endif
 }
 
 } // namespace GDMonoUtils

+ 17 - 7
modules/mono/mono_gd/gd_mono_utils.h

@@ -46,13 +46,10 @@ typedef MonoObject *(*MarshalUtils_ArraysToDict)(MonoArray *, MonoArray *, MonoO
 typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray **, MonoObject **);
 typedef MonoObject *(*SignalAwaiter_FailureCallback)(MonoObject *, MonoObject **);
 typedef MonoObject *(*GodotTaskScheduler_Activate)(MonoObject *, MonoObject **);
+typedef MonoArray *(*StackTrace_GetFrames)(MonoObject *, MonoObject **);
+typedef void (*DebugUtils_StackFrameInfo)(MonoObject *, MonoString **, int *, MonoString **, MonoObject **);
 
 struct MonoCache {
-	// Format for cached classes in the Godot namespace: class_<Class>
-	// Macro: CACHED_CLASS(<Class>)
-
-	// Format for cached classes in a different namespace: class_<Namespace>_<Class>
-	// Macro: CACHED_NS_CLASS(<Namespace>, <Class>)
 
 	// -----------------------------------------------
 	// corlib classes
@@ -73,6 +70,13 @@ struct MonoCache {
 	GDMonoClass *class_String;
 	GDMonoClass *class_IntPtr;
 
+#ifdef DEBUG_ENABLED
+	GDMonoClass *class_System_Diagnostics_StackTrace;
+	StackTrace_GetFrames methodthunk_System_Diagnostics_StackTrace_GetFrames;
+	GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_bool;
+	GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_Exception_bool;
+#endif
+
 	MonoClass *rawclass_Dictionary;
 	// -----------------------------------------------
 
@@ -96,6 +100,11 @@ struct MonoCache {
 	GDMonoClass *class_WeakRef;
 	GDMonoClass *class_MarshalUtils;
 
+#ifdef DEBUG_ENABLED
+	GDMonoClass *class_DebuggingUtils;
+	DebugUtils_StackFrameInfo methodthunk_DebuggingUtils_GetStackFrameInfo;
+#endif
+
 	GDMonoClass *class_ExportAttribute;
 	GDMonoField *field_ExportAttribute_hint;
 	GDMonoField *field_ExportAttribute_hintString;
@@ -167,7 +176,8 @@ MonoDomain *create_domain(const String &p_friendly_name);
 
 String get_exception_name_and_message(MonoObject *p_ex);
 
-void print_unhandled_exception(MonoObject *p_ex);
+void print_unhandled_exception(MonoObject *p_exc);
+void print_unhandled_exception(MonoObject *p_exc, bool p_fail_silently);
 
 } // namespace GDMonoUtils
 
@@ -175,9 +185,9 @@ void print_unhandled_exception(MonoObject *p_ex);
 
 #define CACHED_CLASS(m_class) (GDMonoUtils::mono_cache.class_##m_class)
 #define CACHED_CLASS_RAW(m_class) (GDMonoUtils::mono_cache.class_##m_class->get_mono_ptr())
-#define CACHED_NS_CLASS(m_ns, m_class) (GDMonoUtils::mono_cache.class_##m_ns##_##m_class)
 #define CACHED_RAW_MONO_CLASS(m_class) (GDMonoUtils::mono_cache.rawclass_##m_class)
 #define CACHED_FIELD(m_class, m_field) (GDMonoUtils::mono_cache.field_##m_class##_##m_field)
+#define CACHED_METHOD(m_class, m_method) (GDMonoUtils::mono_cache.method_##m_class##_##m_method)
 #define CACHED_METHOD_THUNK(m_class, m_method) (GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method)
 
 #ifdef REAL_T_IS_DOUBLE