cd /news/developer-tools/easy-debugging-of-geode-mods-with-hw… · home topics developer-tools article
[ARTICLE · art-12644] src=gist.github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Easy debugging of Geode mods with HWASan

This article provides a guide on using HWASan (Hardware-Assisted AddressSanitizer) to debug memory bugs in Geode mods on Android devices running Android 14 or later. It explains how to enable the tool by passing a cmake argument during the build process, deploy a debug launcher, and analyze resulting crash logs (tombstones) to identify issues like use-after-free errors. The guide also includes troubleshooting tips, such as disabling the crash handler and verifying that the sanitizer is properly running.

read4 min views28 publishedJan 12, 2026

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/

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/
── more in #developer-tools 4 stories · sorted by recency
── more on @geode 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/easy-debugging-of-ge…] indexed:0 read:4min 2026-01-12 ·