# Easy debugging of Geode mods with HWASan

> Source: <https://gist.github.com/dankmeme01/ca834fd15f30209bf6378e985437579b>
> Published: 2026-01-12 13:18:06+00:00

# Easy debugging of Geode mods with HWASan

ASan (and it's younger brother, HWASan) are incredible tools for debugging memory integrity bugs in native applications. This guide explains how to set up HWASan for Geode mods on Android and how to analyze crashes.

Requirements:
* Device running Android 14+, lower is possible but only with ASan, which has a slightly different way of setting up. See [here](https://source.android.com/docs/security/test/asan)
* Android Studio or any other way of building the launcher

## Build the mod

Simply pass `-DANDROID_SANITIZE=hwaddress` as a cmake argument:

```sh
geode build -p android64 --config Debug -- -DANDROID_SANITIZE=hwaddress
```

## Build launcher

Clone [the launcher](https://github.com/geode-sdk/android-launcher/), and add a file `app/src/main/resources/lib/arm64-v8a/wrap.sh` with the following contents:
```sh
#!/system/bin/sh
LD_HWASAN=1 exec "$@"
```

Build a debug version and deploy it to your device.

## Test

To see if it's working, let's add some example UB code to our mod

```cpp
auto test = new int;
delete test;

*(volatile int*)test = 1;
```

Before testing, **make sure to disable the crash handler!** In the launcher settings, open developer settings and enter `--geode:disable-crash-handler` in Launch arguments.

If this code does not immediately crash, ASan is likely not properly working. You can add `exit` as the first line (after the shebang) to your `wrap.sh` script and rebuild the launcher to see if the script is ever getting ran.

If you did everything right, the game should've crashed. Tombstones are available in `/data/tombstones`. Make sure you pull the most recent one, because this directory includes crash dumps of all apps on your system.

```sh
$ adb shell
$ cd /data/tombstones
$ ls -lt # puts most recent at top
$ exit
$ adb pull /data/tombstones/tombstone_xx ./
```

If the tombstone says something about seccomp disallowing syscalls, you likely pulled the wrong crashlog or did not disable the crash handler.

## Analyzing

Here's an example of a tombstone:

```
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
crDroid Version: '12.1'
Build fingerprint: 'google/tegu/tegu:16/BP2A.250805.005/13691446:user/release-keys'
Revision: 'MP1.0'
ABI: 'arm64'
Timestamp: 2026-01-12 14:05:22.951988409+0100
Process uptime: 203s
Cmdline: com.geode.launcher
pid: 31028, tid: 31963, name: Main  >>> com.geode.launcher <<<
uid: 10637
tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: '==31028==ERROR: HWAddressSanitizer: tag-mismatch on address 0x004649442de0 at pc 0x006d75f0f724
WRITE of size 4 at 0x004649442de0 tags: c4/aa (ptr/mem) in thread T48
    #0 0x006d75f0f724  (/data/data/com.geode.launcher/files/geode/unzipped/dankmeme.globed2/dankmeme.globed2.android64.so+0x495724) (BuildId: c5eada8116c0f6e8eb8a6a995810433c3dcdb594)
    #1 0x007819bc4b9c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac9b9c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)

[0x004649442de0,0x004649442e00) is a small unallocated heap chunk; size: 32 offset: 0

Cause: use-after-free
0x004649442de0 is located 0 bytes inside a 4-byte region [0x004649442de0,0x004649442de4)
freed by thread T48 here:
    #0 0x00783615e57c  (/apex/com.android.runtime/lib64/bionic/libclang_rt.hwasan-aarch64-android.so+0x2957c) (BuildId: 742636119ffb24484fe45c5d21e284c968700fe3)
    #1 0x006d75f0f710  (/data/data/com.geode.launcher/files/geode/unzipped/dankmeme.globed2/dankmeme.globed2.android64.so+0x495710) (BuildId: c5eada8116c0f6e8eb8a6a995810433c3dcdb594)
    #2 0x007819bc4b9c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac9b9c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #3 0x00785d3f0374  (<unknown module>)
    #4 0x0078178d9870  (/data/data/com.geode.launcher/files/copied/Geode.so+0x441870) (BuildId: d3561961f18e8a154709164e8596abd2b31049ff)
    #5 0x00785d3f0374  (<unknown module>)
    #6 0x007819667c14  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0x56cc14) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #7 0x007819bc21fc  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac71fc) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #8 0x00785d3f0438  (<unknown module>)
    #9 0x0078178d9ef0  (/data/data/com.geode.launcher/files/copied/Geode.so+0x441ef0) (BuildId: d3561961f18e8a154709164e8596abd2b31049ff)
    #10 0x00785d3f0438  (<unknown module>)
    #11 0x007819c0ea7c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xb13a7c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #12 0x00785d3f55e4  (<unknown module>)
    #13 0x00785d3f55e4  (<unknown module>)
    #14 0x007819bda0d8  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xadf0d8) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #15 0x007819bdf7dc  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xae47dc) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #16 0x00782714eb00  (/apex/com.android.art/lib64/libart.so+0x2f4b00) (BuildId: 46ae5976d47c8c3676437662907d49b7)
    #17 0x007860334c61  (<unknown module>)
    #18 0x007820c3739c  ([anon:stack_and_tls:31963]+0x10139c)

previously allocated by thread T48 here:
    #0 0x00783615ebd8  (/apex/com.android.runtime/lib64/bionic/libclang_rt.hwasan-aarch64-android.so+0x29bd8) (BuildId: 742636119ffb24484fe45c5d21e284c968700fe3)
    #1 0x007867474434  (/apex/com.android.runtime/lib64/bionic/hwasan/libc.so+0x66434) (BuildId: d7cecead4fb896c9aff20bdb7ae10f0d)
    #2 0x006d75f0f708  (/data/data/com.geode.launcher/files/geode/unzipped/dankmeme.globed2/dankmeme.globed2.android64.so+0x495708) (BuildId: c5eada8116c0f6e8eb8a6a995810433c3dcdb594)
    #3 0x007819bc4b9c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac9b9c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #4 0x00785d3f0374  (<unknown module>)
    #5 0x0078178d9870  (/data/data/com.geode.launcher/files/copied/Geode.so+0x441870) (BuildId: d3561961f18e8a154709164e8596abd2b31049ff)
    #6 0x00785d3f0374  (<unknown module>)
    #7 0x007819667c14  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0x56cc14) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #8 0x007819bc21fc  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac71fc) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #9 0x00785d3f0438  (<unknown module>)
    #10 0x0078178d9ef0  (/data/data/com.geode.launcher/files/copied/Geode.so+0x441ef0) (BuildId: d3561961f18e8a154709164e8596abd2b31049ff)
    #11 0x00785d3f0438  (<unknown module>)
    #12 0x007819c0ea7c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xb13a7c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #13 0x00785d3f55e4  (<unknown module>)
    #14 0x00785d3f55e4  (<unknown module>)
    #15 0x007819bda0d8  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xadf0d8) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #16 0x007819bdf7dc  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xae47dc) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
    #17 0x00782714eb00  (/apex/com.android.art/lib64/libart.so+0x2f4b00) (BuildId: 46ae5976d47c8c3676437662907d49b7)
    #18 0x007860334c61  (<unknown module>)
    #19 0x007820c3739c  ([anon:stack_and_tls:31963]+0x10139c)
```

(less useful parts cut away)

This reveals a lot of useful information, like where exactly memory was allocated, freed, and then accessed afterwards. You'll notice it is missing symbols and other debug information, and that can be fixed by using `hwasan_symbolize`. Run the following command, replacing `/opt/android-ndk` with the NDK path, and `19` with whatever clang version is used in your NDK

```
/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/19/bin/hwasan_symbolize \
    --symbols ./build-android64 \
    < ~/tombstone.txt \
    > ~/tombstone-sym.txt
```

Now all the addresses in your mod should be replaced by useful code paths:
```
WRITE of size 4 at 0x004036ae0180 tags: bd/9e (ptr/mem) in thread T39
    #0 globed::HookedMenuLayer::onGlobedButton(cocos2d::CCObject*) in src/core/hooks/MenuLayer.cpp:111:26
    #1 0x007c52cf3b9c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac9b9c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)
```

For completeness sake, here is the script I use (called symstone.sh) that pulls the most recent tombstone, symbolizes and opens it in vscode:

```sh
#!/bin/bash
adb pull /data/tombstones/$(adb shell "ls -t /data/tombstones | head -n 1") /tmp/tombstone.txt

/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/19/bin/hwasan_symbolize \
    --symbols ./build-android64 < /tmp/tombstone.txt > /tmp/tombstone-sym.txt

code /tmp/tombstone-sym.txt
```

## Bonus: debugging Geode

You must use shared libc++ instead of static when building Geode:
```sh
geode build -p android64 --config Debug -- -G Ninja \
    -DANDROID_SANITIZE=hwaddress -DANDROID_STL=c++_shared
    
adb push ./bin/nightly/Geode.android64.so /sdcard/Android/media/com.geode.launcher/

# push the libc++_shared.so
adb push \
    /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so \
    /sdcard/Android/media/com.geode.launcher/
```


