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:

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.

Asset Recommendations

Disclosure: This text may contain affiliate links, which means we may receive a commission if you click a link and purchase something that we have recommended. While clicking these links won't cost you any money, they will help fund this project! The links are created by Unity and Partnerize (Unity's affiliate partner).