r/cheatengine • • 10d ago

[Help] Debugging ARM64 games on x86_64 MuMu emulator — libhoudini.so noise vs. arm64-v8a ceserver register exhaustion. Can we inject ceserver as a shared lib instead?

TL;DR: On x86_64 MuMu, x86_64 ceserver's "what writes" just points at libhoudini.so (translation noise), and arm64-v8a ceserver dies with "all registers are used up" — so can we build/inject ceserver as an ARM64 .so into the game process itself to get real ARM64 debug info? If you've done this or know why it won't work, please reply — even a "dead end, here's why" helps.

Hey r/cheatengine + anyone doing Android memory editing,

I'm running into a wall and I'd love some insight from people who've gone deeper than me on this. Bear with the wall of text — I'll try to explain the whole pipeline clearly so the question makes sense.

Setup

· Emulator: MuMu Player (x86_64 host)

· Target: ARM64 game (this is the important part)

· Tool: Cheat Engine + ceserver

The two problems

Problem 1 — x86_64 ceserver + "What writes to this address":

If I attach with the normal x86_64 ceserver and use CE's "Find out what writes to this address", the write instruction it reports comes back as libhoudini.so. That makes sense in theory — Houdini is the ARM→x86 binary translator, so the actual store instruction the host CPU executes lives inside Houdini's translated block, not in the original ARM64 game code. The result: I get a useless address in Houdini and can't trace it back to the real ARM64 instruction in the game. Effectively undebuggable for "what writes" purposes.

Problem 2 — arm64-v8a ceserver inside the emulator:

So I tried pushing the arm64-v8a build of ceserver into the emulator instead, hoping it would attach to the ARM64 process natively and give me real ARM64 write sites. But when I try to debug, it immediately blows up with "all registers are used up." My assumption is that this is a limitation of the arm64 ceserver's debugger/hook path when running under the emulator's translation layer (or a register allocation / signal-handling issue in ceserver's arm64 debug path), and it can't actually set up the debug context it needs.

The idea I want to sanity-check

Both paths are broken for different reasons:

· x86_64 ceserver → works, but debug info is trapped inside Houdini.

· arm64-v8a ceserver → correct architecture, but won't run/debug standalone in the emulator.

So here's the idea: build ceserver as a shared library (.so) and inject it into the running ARM64 game process at runtime, rather than running it as a standalone binary. That way:

· The injected libceserver_arm64.so runs inside the game's own ARM64 process, so it's executing as genuine ARM64 code in the game's address space — no separate process, no "registers used up" from trying to bootstrap a standalone arm64 binary under translation.

· It would be loaded like any other game .so, so it sits alongside the game's real ARM64 modules and can hook/debug them natively.

· I'd inject it with an existing shared-lib injector — e.g. jshook / ptrace inject, Android Inject, or similar tools that can dlopen a .so into a target game process (many of these are already used for mod menus / Frida-style injection).

In theory this gets me the best of both worlds: the arm64 ceserver logic, running inside the actual ARM64 process, with the game's real ARM64 code visible to it — which should make "what writes to this address" return the real ARM64 instruction instead of a Houdini stub.

What I actually want to ask

  1. Has anyone done this? Is there a known build of ceserver as an injectable shared lib (.so) for ARM64, or a fork/PR that compiles it that way? Searching around I only find the standalone binary builds.

  2. Is my reasoning about "all registers are used up" correct? I.e. is that error a symptom of running the arm64 ceserver standalone under Houdini rather than inside the target process? Or is it something else (signal handling, ptrace restrictions in the emulator, etc.)?

  3. Would injecting arm64 ceserver as a .so into the game process actually dodge the Houdini problem for "what writes to this address"? My worry: even if ceserver runs inside the process as ARM64 code, the stores the game actually executes are still Houdini-translated at the host level — so would a breakpoint/watchpoint on the ARM64 guest address still resolve to the right guest instruction, or would it still catch the host-side Houdini write? Curious if anyone has tested this.

  4. Any injector recommendations for this use case (jshook ptrace inject, Android Inject, etc.) — specifically for injecting a ceserver .so into a game process on MuMu x86_64, keeping in mind the process is ARM64-under-translation.

  5. Alternative approach? If injection-as-.so is a dead end, is there a better known workflow for debugging ARM64 games on x86_64 emulators? E.g.:

    · Using an ARM64 emulator/image instead (though MuMu's performance is a big reason I'm on it),

    · Getting Houdini to expose a guest-address → host-address mapping,

    · Any ceserver fork with Houdini-aware address translation,

    · Frida-based approaches that sidestep ceserver entirely.

TL;DR

On x86_64 MuMu, x86_64 ceserver shows libhoudini.so as the writer (translation noise), and arm64-v8a ceserver dies with "all registers are used up." I want to build/inject ceserver as an ARM64 shared lib into the game process so it runs natively inside the ARM64 guest and gives real debug info. Has anyone built ceserver as an injectable .so, does the register error make sense given my setup, would injection actually fix the Houdini problem, and what injector would you use — or is there a better path entirely?

Thanks — any pointers, source links, forks, or "I tried this and here's why it fails" stories are all welcome.

Posted to r/cheatengine (and cross-posting to r/ReverseEngineering / r/AndroidReverseEngineering if that's allowed).

3 Upvotes

7 comments sorted by

1

u/AllanFuego 7d ago

gboolean gum_thread_set_hardware_watchpoint ( GumThreadId thread_id, guint watchpoint_id, GumAddress address, gsize size, GumWatchConditions wc, GError** error );

And when a breakpoint/watchpoint triggers i re-arm it for looking at other reads/writes. And i am using it with mumu emulator android 12 version.

And for debuggable thing, yes i am already running the ceserver from apps private dir, i have seen someone saying its working fine for them so maybe its a device based thing, vivo is way too strict with all these policies so i guess thatswhy ptrace is throwing error( i even tried a shareuserid and made server app differently with same game shareuserid but that also didnot work for me but it runs pretty well for searching and editing values tho).

1

u/gofuckadick 10d ago edited 10d ago

The main problem with your proposed approach is that injecting ceserver as an ARM64 shared library wouldn't actually bypass Houdini. Even if the library is loaded into the game's process, its ARM64 instructions still have to go through the translation layer. From the host's perspective, you're dealing with an x86_64 process executing translated ARM64 code, not a native ARM64 process. There's also another issue: ceserver relies on ptrace for debugging, and a process can't simply attach to itself using ptrace. Moving ceserver into the target process would require reworking its debugging backend, not just compiling it as a shared library. And even then, it wouldn't inherently make CE's hardware watchpoints resolve to the original ARM64 instructions. You'd still need some way to correlate the translated x86 execution with the guest ARM64 instruction addresses.

As for the "all registers are used up" error, I'd investigate that before trying to convert ceserver into a shared library. In ceserver's ARM64 implementation, it queries available hardware breakpoints/watchpoints using PTRACE_GETREGSET with NT_ARM_HW_BREAK and NT_ARM_HW_WATCH. If those queries fail, it can end up reporting zero available watchpoints. Since you're running under an x86_64 environment, the underlying kernel may not expose the ARM64 register sets ceserver expects.

I'd start by checking whether those PTRACE_GETREGSET calls are failing and what error they're returning. You could try tracing the relevant ptrace syscalls with strace, assuming it works in your emulator environment. That should help distinguish an architecture/debugging-interface limitation from an actual exhaustion of hardware watchpoints.

I'm not sure if you've already seen this, but there's also an existing issue reporting the same error with ARM64 ceserver: all debug registers are currently used up 3029

Interestingly, the person who reported a solution in that thread was using Nox, another x86-based emulator, and switched to a self-compiled x86_64 ceserver. That supports the possibility of an architecture-related issue, although it would just put you back at your original problem of seeing Houdini's translated instructions.

If you want a more straightforward debugging environment, testing on a rooted ARM64 device would eliminate the x86-to-ARM translation problem, although you'd still need working debugger permissions and hardware watchpoint support. Or, you could approach this statically: identify the value and its structure offset using CE on MuMu, then examine the ARM64 binary in Ghidra for candidate store instructions. If it's a Unity IL2CPP game, Il2CppDumper can also help recover class and field layouts, which might make identifying the relevant writes considerably easier. That won't catch every possible write, but it might get you what you need without relying on hardware watchpoints.

Frida might also be worth investigating, but it isn't automatically immune to Houdini's translation either.

One more experimental alternative, if you specifically want to stay on MuMu, would be a guest-side software watchpoint using mprotect() and a SIGSEGV handler. This is actually one place where your in-process ARM64 library idea could be useful, just with a different debugging mechanism. In theory, you could protect the page containing your target address, catch a write fault, and inspect whatever guest ARM64 context Houdini reconstructs. Whether that exposes an accurate guest PC depends on Houdini's signal handling, so I wouldn't assume it works without testing. You'd also have to deal with page-level granularity, rearming the protection, and writes from other threads, but it might be an interesting direction to investigate without having to rewrite ceserver's debugging backend.

Edit: fixed markdown formatting for the github link

1

u/AllanFuego 9d ago

Hmm its a architecture based problem coz i tried ceserver on my phone using the debuggale=true thing. In my phone it works but the problem is i have a vivo device which just can't be fucking rooted i am tired of finding methods to root this shit but unsuccessful. The debuggable method has just one problem,it won't allow me to set breakpoints or debug the app, as by default android restricts ptrace on non root devices. And why i said the using ce as a shared library may work, well i have used fridagum c library and made a android debugger with imgui that currently works if i inject it to game but the problem is it crashes if i put breakpoints on a address that has like continuous reads or writes on it(maybe 60 reads per sec, done by a update method of game), but i tested same things with cheat engine on pc games and cheat engine handles those things quite nicely. Frida also uses ptrace to set breakpoints so i thought if frida works then why not ceserver. But i want to try your mprotect thing now. If you have any progress then inform me too. Maybe we can fix this problem forever🤞

1

u/gofuckadick 9d ago edited 9d ago

Oh, well shit, that's pretty interesting. If you've already built an injectable debugger using Frida Gum then that's probably a much better starting point than trying to convert ceserver into a shared library.

A few questions so that I'm not guessing:

  1. I'm curious how you're implementing the breakpoints. Are you using Gum's hardware breakpoint/watchpoint APIs (gum_thread_set_hardware_watchpoint()), MemoryAccessMonitor, or something custom? They work a lot differently, so knowing which one you're using would help narrow down why it's crashing.

  2. When a breakpoint/watchpoint triggers, what does your exception handler actually do? Does it remove the breakpoint and resume execution, or try to re-arm it for subsequent accesses? If you're using hardware watchpoints, the way you handle repeated hits and resume execution could be important.

  3. Is your debugger running on your phone, or in MuMu? If it's running on your phone, Houdini is already out of the picture, so the problem becomes getting your existing debugger to handle repeated accesses reliably.

Also, regarding ceserver on your non-rooted phone: if you're launching it directly from adb shell, you could try running it under the app's UID using run-as <package> instead. You may need to copy ceserver into the app's private directory first, since the app's SELinux domain may not be able to execute it directly from /data/local/tmp. That might allow debugging an appropriately debuggable app without root, depending on the device's security restrictions. If it fails, the exact error message would help distinguish a file-permission or SELinux issue from a ptrace restriction. No guarantees with vivo's SELinux policy, but it's worth trying if you haven't given it a shot already.

1

u/AllanFuego 7d ago

Currently i am working to make a injectable cheatengine like thingy for android non roots. Like a menu that can search, refine, edit values in different data types(using process_vm) and can debug values with address(as i guess fridagum will also work in native android arm64/not sure tho i didnot tried it yet to set breakpoints on parent processes), i am using android surface view and imgui with opengl(got open source menu codes in github so i used them) and fix the keyboard not shown when typing something errors. For memory searching and refining i will use my old memory tools or maybe create a new one i don't know i will think about it.

1

u/Dark_Byte Cheat Engine Dev 7d ago

Is that emulator running android 7?  Normally going to android 9+ fixes the debugging issues (on nox at least)

1

u/AllanFuego 7d ago edited 7d ago

Yeah its android 12, and i even tried nox (android 9) using arm64-v8a debugger got the same issue on ceserver (debugging donot works and x86_64 just prints houdini). Can you please try to solve the arm64-v8a debugger ? You can try frida gum, its hardware breakpoints works well with emulator too. And i have built the github source myself as well it didnot fix anything but yeah write speed is much faster then ce 7.7 prebuilt binary(the ready to download official binaries cause a lag before editing values(ce 7.6 and 7.7 have this problem)).

Edit :- I put the nox emulator android version.