Tips and tricks while developing Xamarin.Android.
When a Xamarin.Android app launches on an Android device, and the app was
built in the Debug
configuration, it will create an "update" directory
during process startup, printing the created directory to adb logcat
:
W/monodroid( 2796): Creating public update directory: `/data/data/Mono.Android_Tests/files/.__override__`
When the app needs to resolve native libraries and assemblies, it will look for those files within the update directory first. This includes the Mono runtime library and BCL assemblies.
Note that the update directory is per-app. The above mentioned Mono.Android_Tests
directory is created when running the
Mono.Android-Tests.csproj
unit tests.
The update directory is not used in Release
configuration builds.
(Note: Release
configuration for the app itself, not for xamarin-android.)
Keep in mind that only the app that owns the update directory has permission to
write to it, so on a normal non-rooted device or emulator, you'll need to use
adb shell run-as
to add new files. For example, if you're working on a
mono/x86 bug and need to quickly update the app on the device to test
libmonosgen-2.0.so
changes:
$ make -C src/mono-runtimes/obj/Debug/x86 && \
adb push src/mono-runtimes/obj/Debug/x86/mono/mini/.libs/libmonosgen-2.0.so \
/data/local/tmp/ && \
adb shell run-as Mono.Android_Tests cp /data/local/tmp/libmonosgen-2.0.so \
/data/data/Mono.Android_Tests/files/.__override__/
Alternatively, if you're working on an mscorlib.dll
bug:
$ make -C external/mono/mcs/class/corlib PROFILE=monodroid && \
adb push external/mono/mcs/class/lib/monodroid/mscorlib.dll \
/data/local/tmp/ && \
adb shell run-as Mono.Android_Tests cp /data/local/tmp/mscorlib.dll \
/data/data/Mono.Android_Tests/files/.__override__/
On some devices, the run-as
command might not have permission to read the
files in /data/local/tmp/
. In that case, you can use a cat
command that
pipes to the run-as
command:
$ make -C external/mono/mcs/class/corlib PROFILE=monodroid && \
adb push external/mono/mcs/class/lib/monodroid/mscorlib.dll \
/data/local/tmp/ && \
adb shell "cat /data/local/tmp/mscorlib.dll | \
run-as Mono.Android_Tests sh -c \
'cat > /data/data/Mono.Android_Tests/files/.__override__/mscorlib.dll'"
Download the precompiled lldb
and lldb-server
binaries from
https://github.com/mono/lldb-binaries/releases, and follow the instructions
within README.md.
If you need to run the app with managed debugger attached, first edit the
xa-lldb
script to comment out the following line:
shell "am start -S -n $PKG/$CLASS -a android.intent.action.MAIN -c android.intent.category.LAUNCHER"
Then start the app from Visual Studio with managed debugging as usual. After
that, run the xa-lldb
script as described in README.md.
First, you'll need to get a version of libmonosgen-2.0.so
that includes debug
symbols. You can either use a custom local build or download the debug version
of libmonosgen-2.0.so
for a published Xamarin.Android version:
-
Go to https://github.com/xamarin/xamarin-android/tags and click on the Xamarin.Android version you are debugging.
-
Find the OSS core section at the bottom of the release information and click the link to the open-source build.
-
Navigate to Azure Artifacts in the left sidebar and download the
xamarin-android/xamarin-android/bin/Release/bundle*.7z
file. For thed16-2
branch and earlier, the file extension will be.zip
instead of.7z
. -
Extract the
libmonosgen-2.0.d.so
files from the bundle. For example, if you havebrew
installed, run:$ brew install p7zip $ 7z x -r bundle*.7z libmonosgen-2.0.d.so
On Windows, Visual Studio includes the
7z.exe
executable when the Mobile development with .NET workload is installed, so you can use that:"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\Xamarin.VisualStudio\7-Zip\7z.exe" x -r bundle*.7z libmonosgen-2.0.d.so
For
d16-2
and earlier, useunzip
instead:$ unzip bundle*.zip '**libmonosgen-2.0.d.so'
(On Windows, the Git Bash command prompt includes the
unzip
command, so that's one way to complete this step.)
Next, there are a few options to get LLDB to see the debug symbols.
This option is convenient because it doesn't require any adb
commands. On the
other hand, it requires rebuilding and redeploying the app to test each version
of libmonosgen-2.0.so
, so it's not ideal if you need to test several different
versions of libmonosgen-2.0.so
.
-
Ensure that Android Options > Use Shared Runtime is enabled.
-
Add the appropriate architecture of
libmonosgen-2.0.d.so
to the correspondinglib
subdirectory of the app project as described in the Using Native Libraries documentation. For example, if debugging an arm64-v8a app, add the arm64-v8a version oflibmonosgen-2.0.d.so
to the project inlib/arm64-v8a/
. -
Rename the file to
libmonosgen-2.0.so
. -
Set the Build Action of the file to AndroidNativeLibrary.
-
Build, deploy, and run the app. Then attach LLDB.
-
If desired, follow the
image lookup
andsettings set -- target.source-map
steps from the Debugging Mono binaries with LLDB guide to allow stepping through the Mono runtime source files.
This option is useful for testing a number of different libmonosgen-2.0.so
versions quickly without rebuilding or redeploying the app, but it requires a
little care to complete the adb
steps correctly on the command line.
-
Push the appropriate architecture of
libmonosgen-2.0.d.so
into the application's update directory and rename it tolibmonosgen-2.0.so
:$ adb push libmonosgen-2.0.d.so \ /data/local/tmp/libmonosgen-2.0.so && \ adb shell run-as Mono.Android_Tests cp /data/local/tmp/libmonosgen-2.0.so \ /data/data/Mono.Android_Tests/files/.__override__/
-
Ensure all users have execute permissions on the application's data directory:
$ adb shell run-as Mono.Android_Tests \ chmod a+x /data/data/Mono.Android_Tests/
This will allow LLDB to re-download
libmonosgen-2.0.so
and load the symbols from it. -
Run the app and attach LLDB.
This option allows testing an existing debuggable APK without pushing anything new to the device. The other options are usually more convenient, but loading the symbols by hand might be useful in some cases.
-
After attaching LLDB to the app, add the appropriate architecture of
libmonosgen-2.0.d.so
into LLDB with a command like:(lldb) image add ~/Downloads/lib/xamarin.android/xbuild/Xamarin/Android/lib/arm64-v8a/libmonosgen-2.0.d.so
-
Find the current in-memory address of the
.text
section oflibmonosgen-2.0
. For example, for a 64-bit app that's using the shared runtime, run the following command:(lldb) image dump sections libmonosgen-64bit-2.0.so
Look for the row of the table that shows "code" as the "Type":
SectID Type Load Address Perm File Off. File Size Flags Section Name ---------- ---------------- --------------------------------------- ---- ---------- ---------- ---------- ---------------------------- 0x0000000a code [0x00000071106c4e80-0x0000007110932674) r-x 0x0002ee80 0x0026d7f4 0x00000006 libmonosgen-64bit-2.0.so..text
-
Load the
.text
section fromlibmonosgen-2.0.d.so
at the in-memory starting memory address of the.text
section:(lldb) image load -f libmonosgen-2.0.d.so .text 0x00000071106c4e80
Visual Studio can attach GDB to Xamarin.Android for native debugging. The integration includes the usual features like the graphical thread and call stack windows and the ability to set breakpoints using the source code editor.
-
In the Visual Studio Installer, under the Individual components tab, ensure that Development activities > C++ Android development tools is installed.
-
Install the Android NDK if you don't already have it. For example, use Tools > Android > Android SDK Manager to install it.
-
Set Tools > Options > Cross Platform > C++ > Android > Android NDK to the Android NDK path. For example:
C:\Program Files (x86)\Android\android-sdk\ndk-bundle
-
Quit and relaunch Visual Studio.
-
Use File > Open > Project/Solution to open the signed debuggable APK for the application.
-
Set the Build > Configuration Manager > Active solution platform to the application ABI. If debugging an arm64-v8a application, explicitly add a platform named
ARM64
and set it as the active platform. -
If you need symbols for
libmonosgen-2.0
, copy the library file with symbols to a convenient local location, making sure the file name matches the name on device (for example,libmonosgen-64bit-2.0.so
if using the 64-bit shared runtime), and add the local location of the library to Project > Properties > Additional Symbol Search Paths. -
Start the app, for example by launching it with or without managed debugging from Visual Studio, or by tapping the app on the device.
-
Select Debug > Attach to Android process and wait for the connection to complete.
-
If needed, you can use Debug > Windows > Immediate to interact with the GDB command line. Prefix GDB commands with
-exec
to get the expected behavior. For example to view the stack backtrace:-exec backtrace
-
You can set GDB to continue through the various native signals that Mono uses for its normal internal operations by running the following command in the Immediate window:
-exec handle SIGXCPU SIG33 SIG35 SIGPWR SIGTTIN SIGTTOU SIGSYS nostop noprint
The LLDB integration in Android Studio is quite similar to the GDB integration in Visual Studio. This is a useful option if you are debugging on Windows and are more familiar with LLDB than GDB or if you already have Android Studio installed and don't currently have the Android NDK installed.
-
Install Android Studio. If you already have an Android SDK installation for Xamarin.Android, you can click Cancel on the Android Studio Setup Wizard when you launch Android Studio.
-
Open the signed debuggable APK for the application in Android Studio via Profile or debug APK on the start window or the File > Profile or Debug APK menu item.
-
If you skipped the Android Studio Setup Wizard, navigate to File > Project Structure > Modules > Mono.Android_Tests-Signed > Dependencies, click New > Android SDK next to the Module SDK.
Select the Android SDK folder you're using with Xamarin.Android, and then under Build target, pick the appropriate Android API to match the APK.
-
Wait for the Indexing status message at the bottom of the Android Studio window to disappear.
-
Start the app, for example by launching it with or without managed debugging from Visual Studio, or by tapping the app on the device.
-
In Android Studio, select Run > Attach Debugger to Android Process, or click the corresponding toolbar icon.
-
Set the Debugger to Native, select the running app, and click OK.
If the
adb
connection is slow, the first connection to the app will take a while to download all the system libraries. The connection might time out if this takes too long, but the next connection attempt will have fewer libraries left to download and will likely succeed. -
You can set LLDB to continue through the various native signals that Mono uses for its normal internal operations by opening View > Tool Windows > Debug, selecting the Android Native Debugger tab, navigating to the inner Debugger [tab] > LLDB [tab] command prompt, and running the following
process handle
command:(lldb) process handle -p true -n true -s false SIGXCPU SIG33 SIG35 SIGPWR SIGTTIN SIGTTOU SIGSYS
If the automated methods to attach LLDB or GDB are hitting errors or getting stuck, you can try to attach GDB from the command line as a fallback. These steps rely on having the Android NDK installed.
-
Push the appropriate architecture of
gdbserver
to the device for the app you are debugging. For example, if debugging an arm64-v8a app:$ adb push ~/Library/Developer/Xamarin/android-sdk-macosx/ndk-bundle/prebuilt/android-arm64/gdbserver/gdbserver \ /data/local/tmp/ && \ adb shell run-as Mono.Android_Tests cp /data/local/tmp/gdbserver ./ && \ adb shell run-as Mono.Android_Tests chmod +x ./gdbserver
-
Ensure all users have execute permissions on the application's data directory:
$ adb shell run-as Mono.Android_Tests \ chmod a+x /data/data/Mono.Android_Tests/
-
Start the app, for example by launching it with or without managed debugging from Visual Studio, or by tapping the app on the device.
-
Find the process ID of the running app, for example by using
adb shell ps
:$ adb shell ps | grep -F 'Mono.Android_Tests'
Example output:
u0_a247 15087 336 780568 69200 SyS_epoll_ 00000000 S Mono.Android_Tests
-
Start
gdbserver
, attaching it to the running app process:$ adb shell run-as Mono.Android_Tests ./gdbserver \ +debug_socket --attach 15087
-
In another console window, use
adb
to forward thedebug_socket
UNIX domain socket to a TCP port on the local host:$ adb forward tcp:50999 localfilesystem:/data/data/Mono.Android_Tests/debug_socket
-
Pull the appropriate
app_process*
file for the application to a local location. For example, if debugging an arm64-v8a app:$ adb pull /system/bin/app_process64 /tmp/gdb-symbols/
-
If you need symbols for
libmonosgen-2.0
, copy the library file with symbols to the same local location, making sure the file name matches the name on device (for example,libmonosgen-64bit-2.0.so
if using the 64-bit shared runtime). -
Run
gdb
:$ ~/Library/Developer/Xamarin/android-sdk-macosx/ndk-bundle/prebuilt/darwin-x86_64/bin/gdb
-
Run the following commands in GDB to set up the debugger and attach it to the app:
(gdb) file /tmp/gdb-symbols/app_process64 (gdb) set sysroot /tmp/gdb-symbols (gdb) set solib-search-path /tmp/gdb-symbols (gdb) target remote :50999