How to symbolicate an Android native stack in Unity
If your app crashes on a device then you will get a crash log and it may be a native backtrace.
It may look like this:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Version '2021.3.26f1 (a16dc32e0ff2)', Build type 'Release', Scripting Backend 'il2cpp', CPU 'arm64-v8a'
Build fingerprint: 'Nokia/Onyx...'
Revision: '0'
ABI: 'arm64'
Timestamp: 2023-06-16 16:11:05+0200
pid: 8877, tid: 8946, name: Thread-5 >>> com.kamgam.sbr2 <<<
uid: 10398
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x17
Cause: null pointer dereference
x0 00000072101ca8c0 x1 0000000000000000 x2 0000000000000858 x3 000000000000e56e
x4 000000000000005b x5 00000072530c4ddc x6 0000000000000000 x7 00000000840088d2
x8 00000072535d7530 x9 00000000fffffff1 x10 0000000000000000 x11 0000000000000000
x12 0000000000000000 x13 0000000000000000 x14 0000000000029873 x15 0000000000000001
x16 000000725351b618 x17 000000733e8c7070 x18 0000007167606000 x19 0000000000000858
x20 0000000000000000 x21 ffffffffffffffff x22 00000071d0132308 x23 00000072101e1c70
x24 00000072101e1cf0 x25 0000000000000000 x26 00000072104b3780 x27 000000715000018c
x28 000000000000273d x29 000000716841fc20
sp 000000716841f8c0 lr 0000007252840538 pc 0000007252840548
backtrace:
#00 pc 0000000000b7c548 /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#01 pc 0000000000b8cd8c /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#02 pc 0000000000b8ccfc /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#03 pc 0000000000b44abc /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#04 pc 0000000000b449a0 /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#05 pc 0000000000d39570 /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#06 pc 0000000000d3b9a8 /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#07 pc 0000000000d32ff0 /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#08 pc 0000000000513ff0 /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#09 pc 00000000000e6890 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36) (BuildId: cf739dbc84bcc78f7a1500721bfb3758)
#10 pc 0000000000084b6c /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: cf739dbc84bcc78f7a1500721bfb3758)
Forwarding signal 11
Info --------- beginning of crash
Fatal libc Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x17 in tid 8946 (Thread-5), pid 8877 (com.kamgam.sbr2)
The interesting part starts after "backtrace". Anything before that is just the error name and some memory dumps. What we want is to convert it to something readable like this:
#00 pc 0000000000b7c548 (DataBufferGLES::FlushMappedRange(unsigned long, unsigned long) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#01 pc 0000000000b8cd8c (BufferGLES::EndWrite(unsigned long) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#02 pc 0000000000b8ccfc (GfxDeviceGLES::EndBufferWrite(GfxBuffer*, unsigned long) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#03 pc 0000000000b44abc (GeometryJobTasks::PutGeometryJobTask(GfxDevice&, GeometryJobTasks::GeometryJobTask*) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#04 pc 0000000000b449a0 (GeometryJobTasks::EndFrame(GfxDevice&) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#05 pc 0000000000d39570 (GfxDeviceWorker::RunCommand(ThreadedStreamBuffer&) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#06 pc 0000000000d3b9a8 (GfxDeviceWorker::RunExt(ThreadedStreamBuffer&) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#07 pc 0000000000d32ff0 (GfxDeviceWorker::RunGfxDeviceWorker(void*) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#08 pc 0000000000513ff0 (Thread::RunThreadWrapper(void*) at ??:?) /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
#09 pc 00000000000e6890 (libc.so not found) /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36) (BuildId: cf739dbc84bcc78f7a1500721bfb3758)
#10 pc 0000000000084b6c (libc.so not found) /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: cf739dbc84bcc78f7a1500721bfb3758)
Forwarding signal 11
Here are some useful links regarding native stack symbolication on Android:
- https://support.unity.com/hc/en-us/articles/115000292166-Symbolicate-Android-crash
- https://developer.android.com/games/engines/unity/unity-symbolicate
- https://source.android.com/docs/core/tests/debug
- https://source.android.com/docs/core/tests/debug/native-crash
Prerequisites
When you build your app don't forget to enable the creation of symbol files. These are needed to map the backtrace frames (#01, #02, ...) to function calls.
From Unity 2020.3 and higher, you can upload the symbolication file to Google Play Console to see the resolved stack traces directly on the Android Vitals dashboard.
Automatic Symbolication in the Google Play Console
The most convenient way of symbolicating is by having Google do it for you. However, this only works for apps you have already uploaded to Google Play.
The spot to upload the symbols is a bit hidden. First find the "App bundle explorer".
In there click the little blue arrow on the right.
Hint: it may be cut off if your browser window is too narrow. Best go fullscreen for this.
Finally in the Downloads section under Assets you can upload you symbol file.
Symbolicating the error stack automatically
Since Unity 2019 you can use the Stacktrace Utility which is part of the Android Logcat window.
It is hidden in the "Tools" in the top right corner.
In there you can add your symbol file path and paste in the backtrace. It will resolve it for you.
Symbolicating the error stack manually
To do that we need a tool that can convert an address into something readable (method name). This tool is part of the Android NDK and is called addr2line. However, there are many versions of this in the NDK. We need to use the one that matches our binary.
Check the ABI part of the crash log:
ABI: 'arm64'
This tells us that we need to use the arm 64 version of addr2line. In our case that is located under:
C:\Program Files\2021.3.26f1\Editor\Data\PlaybackEngines\AndroidPlayer\NDK\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin\aarch64-linux-android-addr2line.exe
We also need the symbols file for the library that caused the crash. To find it we look at the backlog again.
backtrace:
#00 pc 0000000000b7c548 /data/app/com.kamgam.sbr2-MA6dTM-G3o_UvCLJ-IzJ0g==/lib/arm64/libunity.so (BuildId: 0a19a0525ef8627736f4cb908c7e7b6083772483)
In our example it is libunity.so, which we can find in our unpacked symbols.zip
Now the addr2line tool has quite some parameters.
aarch64-linux-android-addr2line.exe [option(s)] [addr(s)]
Convert addresses into line number/file name pairs.
If no addresses are specified on the command line, they will be read from stdin
The options are:
@<file> Read options from <file>
-a --addresses Show addresses
-b --target=<bfdname> Set the binary file format
-e --exe=<executable> Set the input file name (default is a.out)
-i --inlines Unwind inlined functions
-j --section=<name> Read section-relative offsets instead of addresses
-p --pretty-print Make the output easier to read for humans
-s --basenames Strip directory names
-f --functions Show function names
-C --demangle[=style] Demangle function names
-h --help Display this information
-v --version Display the program's version
We have to feed in the symbol file and the address.
"aarch64-linux-android-addr2line.exe" -f -C -e "libunity.so" -a 0000000000513ff0
And there we have it. That line corresponds to "Thread::RunThreadWrapper(void *)". You can find some more details in this Unity Knowledgebase article.