如何获取Xamarin Android应用的托管及非托管调用栈?
Hey there! Let's work through how to capture both managed and native call stacks for your Xamarin diagnostic tool—this is totally achievable, but we need to handle each platform and runtime separately since Xamarin bridges .NET with native Android/iOS ecosystems. Let's break this down step by step.
This is the straightforward part. For C# exceptions, you can leverage the built-in System.Diagnostics.StackTrace class to get detailed stack frames, even with file names and line numbers if you have debugging symbols enabled.
try { // Your application code that might throw } catch (Exception ex) { var managedStackTrace = new System.Diagnostics.StackTrace(ex, fNeedFileInfo: true); foreach (var frame in managedStackTrace.GetFrames()) { var method = frame.GetMethod(); var stackLine = $"{method.DeclaringType?.FullName ?? "Unknown"}.{method.Name}"; if (frame.GetFileName() != null) { stackLine += $" at {frame.GetFileName()}:{frame.GetFileLineNumber()}"; } // Log or store stackLine } }
Pro tip: In Release builds, make sure to enable "Debug Information" in your project settings (set to pdb-only or full) to retain line number and file name data—otherwise, the stack trace will only show method names.
Android has two native layers you need to handle: Java (like Google Maps APIs) and C++ native code.
Java Call Stacks (e.g., Google Maps)
When Google Maps or other Java libraries throw exceptions, they'll surface as Java.Lang.Exception derivatives in Xamarin.Android. To get the full Java stack trace, use the PrintStackTrace() method to write the stack to a stream, then convert it to a string:
try { // Call Google Maps Java APIs, e.g., mapFragment.GetMapAsync(...) } catch (Java.Lang.Exception javaEx) { using var stringWriter = new StringWriter(); javaEx.PrintStackTrace(new PrintWriter(stringWriter)); var javaStackTrace = stringWriter.ToString(); // javaStackTrace now contains the full Java call stack }
You can also hook into Android's global uncaught exception handler to capture Java exceptions that bubble up:
Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += (sender, e) => { if (e.Exception is Java.Lang.Exception javaEx) { // Capture Java stack trace here as above } };
C++ Native Call Stacks
For C++ code (either your own or third-party libraries), you'll need to use Android NDK tools to capture the stack trace. Write a simple C++ function using the backtrace() API, then expose it to Xamarin via JNI or P/Invoke.
Step 1: C++ Implementation (NDK)
#include <backtrace.h> #include <string> #include <sstream> #include <stdlib.h> extern "C" JNIEXPORT jstring JNICALL Java_com_yourapp_YourNativeHelper_GetCppStackTrace(JNIEnv* env, jobject thiz) { void* callstack[128]; int frameCount = backtrace(callstack, 128); char** symbols = backtrace_symbols(callstack, frameCount); std::ostringstream stackStream; for (int i = 0; i < frameCount; ++i) { stackStream << symbols[i] << "\n"; } free(symbols); return env->NewStringUTF(stackStream.str().c_str()); }
Compile this into a shared library (libnativehelper.so) using the Android NDK, making sure to link against libbacktrace.so.
Step 2: Xamarin Binding
In your Xamarin.Android project, create a binding for the JNI method:
[Register("com/yourapp/YourNativeHelper", DoNotGenerateAcw = true)] internal class NativeHelper : Java.Lang.Object { [Register("GetCppStackTrace", "()Ljava/lang/String;", "")] public static string GetCppStackTrace() { var id = JNIEnv.GetStaticMethodID(classRef, "GetCppStackTrace", "()Ljava/lang/String;"); return JNIEnv.GetString(JNIEnv.CallStaticObjectMethod(classRef, id), JniHandleOwnership.TransferLocalRef); } static IntPtr classRef; static NativeHelper() { classRef = JNIEnv.FindClass("com/yourapp/YourNativeHelper"); } }
Now you can call NativeHelper.GetCppStackTrace() from your C# code to get the C++ stack trace.
For iOS, native code (Objective-C/C++) can be captured using the same backtrace() API as Android, but with slightly different setup.
Step 1: C++ Implementation
Add this to a .cpp file in your Xamarin.iOS project (mark it as "Native" build action):
#include <execinfo.h> #include <string> #include <sstream> #include <stdlib.h> extern "C" const char* GetiOSNativeStackTrace() { void* callstack[128]; int frameCount = backtrace(callstack, 128); char** symbols = backtrace_symbols(callstack, frameCount); std::ostringstream stackStream; for (int i = 0; i < frameCount; ++i) { stackStream << symbols[i] << "\n"; } free(symbols); static std::string stackStr; stackStr = stackStream.str(); return stackStr.c_str(); }
Step 2: P/Invoke from C#
[DllImport("__Internal")] private static extern IntPtr GetiOSNativeStackTrace(); public static string GetNativeStack() { var stackPtr = GetiOSNativeStackTrace(); return Marshal.PtrToStringAnsi(stackPtr); }
Note: In Release builds, Xcode strips debug symbols by default. To retain them, go to your iOS project settings, under "Build Options" set "Strip Debug Symbols During Copy" to No, and ensure you keep the .dSYM file generated during build to resolve memory addresses to function names later.
Create a unified diagnostic helper that captures all relevant stacks based on the platform:
public static class StackTraceHelper { public static string GetFullDiagnosticStack(Exception ex = null) { var sb = new StringBuilder(); // Add managed stack trace if (ex != null) { sb.AppendLine("=== Managed (C#) Stack Trace ==="); sb.AppendLine(ex.StackTrace); } // Add native stack based on platform #if ANDROID sb.AppendLine("\n=== Java Stack Trace ==="); // Capture Java stack if applicable (e.g., if ex is Java.Lang.Exception) if (ex is Java.Lang.Exception javaEx) { using var sw = new StringWriter(); javaEx.PrintStackTrace(new PrintWriter(sw)); sb.AppendLine(sw.ToString()); } sb.AppendLine("\n=== C++ Stack Trace ==="); sb.AppendLine(NativeHelper.GetCppStackTrace()); #elif IOS sb.AppendLine("\n=== iOS Native (ObjC/C++) Stack Trace ==="); sb.AppendLine(GetNativeStack()); #endif return sb.ToString(); } }
- Release Build Optimizations: Both .NET and native compilers optimize away stack trace details in Release mode. Enable debug info for .NET, and retain symbols for native code.
- Cross-Runtime Exceptions: Some native exceptions might not bubble up to C# directly. For example, a C++ crash in Android might kill the app before Xamarin can catch it—use native crash handlers (like
signal()in C++) to write stack traces to a file before the app exits. - Google Maps Specifics: If Google Maps throws a native C++ exception, it might not be wrapped in a Java exception. You may need to use Google Maps' error callbacks (like
OnMapError()) to capture errors, combined with the C++ stack trace capture.
内容的提问来源于stack exchange,提问作者mersey

