Skip to content

Instantly share code, notes, and snippets.

@dankmeme01
Last active May 26, 2026 19:57
Show Gist options
  • Select an option

  • Save dankmeme01/ca834fd15f30209bf6378e985437579b to your computer and use it in GitHub Desktop.

Select an option

Save dankmeme01/ca834fd15f30209bf6378e985437579b to your computer and use it in GitHub Desktop.
Easy debugging of Geode mods with HWASan

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
  • Android Studio or any other way of building the launcher

Build the mod

Simply pass -DANDROID_SANITIZE=hwaddress as a cmake argument:

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

Build launcher

Clone the launcher, and add a file app/src/main/resources/lib/arm64-v8a/wrap.sh with the following contents:

#!/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

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.

$ 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:

#!/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:

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/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment