{"slug": "easy-debugging-of-geode-mods-with-hwasan", "title": "Easy debugging of Geode mods with HWASan", "summary": "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.", "body_md": "# Easy debugging of Geode mods with HWASan\n\nASan (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.\n\nRequirements:\n* 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)\n* Android Studio or any other way of building the launcher\n\n## Build the mod\n\nSimply pass `-DANDROID_SANITIZE=hwaddress` as a cmake argument:\n\n```sh\ngeode build -p android64 --config Debug -- -DANDROID_SANITIZE=hwaddress\n```\n\n## Build launcher\n\nClone [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:\n```sh\n#!/system/bin/sh\nLD_HWASAN=1 exec \"$@\"\n```\n\nBuild a debug version and deploy it to your device.\n\n## Test\n\nTo see if it's working, let's add some example UB code to our mod\n\n```cpp\nauto test = new int;\ndelete test;\n\n*(volatile int*)test = 1;\n```\n\nBefore testing, **make sure to disable the crash handler!** In the launcher settings, open developer settings and enter `--geode:disable-crash-handler` in Launch arguments.\n\nIf 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.\n\nIf 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.\n\n```sh\n$ adb shell\n$ cd /data/tombstones\n$ ls -lt # puts most recent at top\n$ exit\n$ adb pull /data/tombstones/tombstone_xx ./\n```\n\nIf the tombstone says something about seccomp disallowing syscalls, you likely pulled the wrong crashlog or did not disable the crash handler.\n\n## Analyzing\n\nHere's an example of a tombstone:\n\n```\n*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\ncrDroid Version: '12.1'\nBuild fingerprint: 'google/tegu/tegu:16/BP2A.250805.005/13691446:user/release-keys'\nRevision: 'MP1.0'\nABI: 'arm64'\nTimestamp: 2026-01-12 14:05:22.951988409+0100\nProcess uptime: 203s\nCmdline: com.geode.launcher\npid: 31028, tid: 31963, name: Main  >>> com.geode.launcher <<<\nuid: 10637\ntagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)\npac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)\nsignal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------\nAbort message: '==31028==ERROR: HWAddressSanitizer: tag-mismatch on address 0x004649442de0 at pc 0x006d75f0f724\nWRITE of size 4 at 0x004649442de0 tags: c4/aa (ptr/mem) in thread T48\n    #0 0x006d75f0f724  (/data/data/com.geode.launcher/files/geode/unzipped/dankmeme.globed2/dankmeme.globed2.android64.so+0x495724) (BuildId: c5eada8116c0f6e8eb8a6a995810433c3dcdb594)\n    #1 0x007819bc4b9c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac9b9c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n\n[0x004649442de0,0x004649442e00) is a small unallocated heap chunk; size: 32 offset: 0\n\nCause: use-after-free\n0x004649442de0 is located 0 bytes inside a 4-byte region [0x004649442de0,0x004649442de4)\nfreed by thread T48 here:\n    #0 0x00783615e57c  (/apex/com.android.runtime/lib64/bionic/libclang_rt.hwasan-aarch64-android.so+0x2957c) (BuildId: 742636119ffb24484fe45c5d21e284c968700fe3)\n    #1 0x006d75f0f710  (/data/data/com.geode.launcher/files/geode/unzipped/dankmeme.globed2/dankmeme.globed2.android64.so+0x495710) (BuildId: c5eada8116c0f6e8eb8a6a995810433c3dcdb594)\n    #2 0x007819bc4b9c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac9b9c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #3 0x00785d3f0374  (<unknown module>)\n    #4 0x0078178d9870  (/data/data/com.geode.launcher/files/copied/Geode.so+0x441870) (BuildId: d3561961f18e8a154709164e8596abd2b31049ff)\n    #5 0x00785d3f0374  (<unknown module>)\n    #6 0x007819667c14  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0x56cc14) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #7 0x007819bc21fc  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac71fc) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #8 0x00785d3f0438  (<unknown module>)\n    #9 0x0078178d9ef0  (/data/data/com.geode.launcher/files/copied/Geode.so+0x441ef0) (BuildId: d3561961f18e8a154709164e8596abd2b31049ff)\n    #10 0x00785d3f0438  (<unknown module>)\n    #11 0x007819c0ea7c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xb13a7c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #12 0x00785d3f55e4  (<unknown module>)\n    #13 0x00785d3f55e4  (<unknown module>)\n    #14 0x007819bda0d8  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xadf0d8) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #15 0x007819bdf7dc  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xae47dc) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #16 0x00782714eb00  (/apex/com.android.art/lib64/libart.so+0x2f4b00) (BuildId: 46ae5976d47c8c3676437662907d49b7)\n    #17 0x007860334c61  (<unknown module>)\n    #18 0x007820c3739c  ([anon:stack_and_tls:31963]+0x10139c)\n\npreviously allocated by thread T48 here:\n    #0 0x00783615ebd8  (/apex/com.android.runtime/lib64/bionic/libclang_rt.hwasan-aarch64-android.so+0x29bd8) (BuildId: 742636119ffb24484fe45c5d21e284c968700fe3)\n    #1 0x007867474434  (/apex/com.android.runtime/lib64/bionic/hwasan/libc.so+0x66434) (BuildId: d7cecead4fb896c9aff20bdb7ae10f0d)\n    #2 0x006d75f0f708  (/data/data/com.geode.launcher/files/geode/unzipped/dankmeme.globed2/dankmeme.globed2.android64.so+0x495708) (BuildId: c5eada8116c0f6e8eb8a6a995810433c3dcdb594)\n    #3 0x007819bc4b9c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac9b9c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #4 0x00785d3f0374  (<unknown module>)\n    #5 0x0078178d9870  (/data/data/com.geode.launcher/files/copied/Geode.so+0x441870) (BuildId: d3561961f18e8a154709164e8596abd2b31049ff)\n    #6 0x00785d3f0374  (<unknown module>)\n    #7 0x007819667c14  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0x56cc14) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #8 0x007819bc21fc  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac71fc) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #9 0x00785d3f0438  (<unknown module>)\n    #10 0x0078178d9ef0  (/data/data/com.geode.launcher/files/copied/Geode.so+0x441ef0) (BuildId: d3561961f18e8a154709164e8596abd2b31049ff)\n    #11 0x00785d3f0438  (<unknown module>)\n    #12 0x007819c0ea7c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xb13a7c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #13 0x00785d3f55e4  (<unknown module>)\n    #14 0x00785d3f55e4  (<unknown module>)\n    #15 0x007819bda0d8  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xadf0d8) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #16 0x007819bdf7dc  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xae47dc) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n    #17 0x00782714eb00  (/apex/com.android.art/lib64/libart.so+0x2f4b00) (BuildId: 46ae5976d47c8c3676437662907d49b7)\n    #18 0x007860334c61  (<unknown module>)\n    #19 0x007820c3739c  ([anon:stack_and_tls:31963]+0x10139c)\n```\n\n(less useful parts cut away)\n\nThis 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\n\n```\n/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/19/bin/hwasan_symbolize \\\n    --symbols ./build-android64 \\\n    < ~/tombstone.txt \\\n    > ~/tombstone-sym.txt\n```\n\nNow all the addresses in your mod should be replaced by useful code paths:\n```\nWRITE of size 4 at 0x004036ae0180 tags: bd/9e (ptr/mem) in thread T39\n    #0 globed::HookedMenuLayer::onGlobedButton(cocos2d::CCObject*) in src/core/hooks/MenuLayer.cpp:111:26\n    #1 0x007c52cf3b9c  (/data/app/~~dZyWKnk3vxy_emc7LuY4yA==/com.robtopx.geometryjump-6y1HhI10jOa9Pdg7lr_TzQ==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libcocos2dcpp.so+0xac9b9c) (BuildId: 42f8945feca43dcb3a375f99b8b68cf32456dda8)\n```\n\nFor completeness sake, here is the script I use (called symstone.sh) that pulls the most recent tombstone, symbolizes and opens it in vscode:\n\n```sh\n#!/bin/bash\nadb pull /data/tombstones/$(adb shell \"ls -t /data/tombstones | head -n 1\") /tmp/tombstone.txt\n\n/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/19/bin/hwasan_symbolize \\\n    --symbols ./build-android64 < /tmp/tombstone.txt > /tmp/tombstone-sym.txt\n\ncode /tmp/tombstone-sym.txt\n```\n\n## Bonus: debugging Geode\n\nYou must use shared libc++ instead of static when building Geode:\n```sh\ngeode build -p android64 --config Debug -- -G Ninja \\\n    -DANDROID_SANITIZE=hwaddress -DANDROID_STL=c++_shared\n    \nadb push ./bin/nightly/Geode.android64.so /sdcard/Android/media/com.geode.launcher/\n\n# push the libc++_shared.so\nadb push \\\n    /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so \\\n    /sdcard/Android/media/com.geode.launcher/\n```\n\n", "url": "https://wpnews.pro/news/easy-debugging-of-geode-mods-with-hwasan", "canonical_source": "https://gist.github.com/dankmeme01/ca834fd15f30209bf6378e985437579b", "published_at": "2026-01-12 13:18:06+00:00", "updated_at": "2026-05-23 23:34:51.235229+00:00", "lang": "en", "topics": ["developer-tools", "open-source"], "entities": ["Geode", "HWASan", "ASan", "Android"], "alternates": {"html": "https://wpnews.pro/news/easy-debugging-of-geode-mods-with-hwasan", "markdown": "https://wpnews.pro/news/easy-debugging-of-geode-mods-with-hwasan.md", "text": "https://wpnews.pro/news/easy-debugging-of-geode-mods-with-hwasan.txt", "jsonld": "https://wpnews.pro/news/easy-debugging-of-geode-mods-with-hwasan.jsonld"}}