TL;DR
Android Studio has a bug in its AVD manager (Android Virtual Device - basically a virtual machine managed by Android Studio) that causes it to incorrectly report that it doesn’t have enough memory to start an AVD. The error code returned has a misleading name, in the code it is called “AccelerationErrorCode.NOT_ENOUGH_MEMORY”1 but the error message produced by this references some Intel-only virtualization technology called “HAXM”2 which is not used by default on Linux and is not related to the source of the error.
A binary patch in bsdiff format that disables the faulty memory check is provided below for Android Studio Canary 2021.03, as well as a Nix overlay to apply the patch. I also filed an upstream bug report here.
The Story
I recently wanted to use Android Studio on NixOS to do some Android emulation. Unfortunately, when I tried to create an AVD, I got a weird error, and it wouldn’t start:
I did some googling and I found out that the error is only produced in one place, the AVDConnectionManager class, in the checkAcceleration() function3:
|
|
The failing check in question
On closer inspection, it seems that the getMemorySize() codepath depends on JVM vendor, with a fallthrough case that returns 32GB4:
|
|
The offending function(s)
This seems to be where things are going wrong. Presumably, it’s not returning 32GiB, since otherwise the check wouldn’t trigger, but I have no idea why checkMemory() is returning a value smaller than 1GiB. In any case, just skipping this check should be enough to get Android Studio to launch AVD’s. Sounds simple - since Android Studio’s source code is online, it should be easy enough to patch, right? Well, no, apparently despite being technically “open source”, mere mortals cannot actually build Android Studio themselves5:
Which leaves only unconventional patch methods…
So I decided I would just patch the .jar file containing the AVDManagerConnection class, located in plugins/android/lib/android.jar. It shouldn’t be too hard to unpack it, poke around in the .class file and remove the offending checkMemorySize() check, and repack it. I first decided to use Recaf for this, which was great at disassembly and looking at the JVM bytecode. It has a decompiler mode as well as a JVM bytecode editor and a hex editor. You can even edit the decompiled code in-place, which is pretty cool.
Unfortunately, the assembler feature was not working that day, and despite some helpful suggestions from the Recaf discord, the modified .jar file didn’t work when I tried to run the original program:
Instead, I ended up just deciding to just overwrite the code with NOP instructions using a hex editor. The encoding of the NOP instruction is only one byte, so I can just replace the failing check with a series of NOP instructions without screwing up any offsets in the file. Since there is no repacking or recomputation involved in this, it should “just work” - with some caveats, namely that the JVM is a stack-based virtual machine so I need to make sure the stack is in the right state after the JVM executes my modified code. After looking at the analyzer in Recaf’s bytecode editor to decide exactly which instructions to rewrite, I decided on just overwriting the GETSTATIC and ARETURN operations starting on line 34 of the disassembled bytecode, since the stack is empty after those two instructions - no need to worry about the stack state.
Now I need to figure out where exactly in the binary to write my NOP instructions, since Recaf doesn’t display this information in the bytecode editor. Luckily, there is another cool program mentioned in the Recaf docs, called Kaitai https://ide.kaitai.io/, that will help with this. You just upload your file, pick a file format analyzer, and it will display the kinds of objects that are contained in your file and where exactly they are located within it. For us, we’re looking for the checkAcceleration() method, which Recaf tells us in the table view is index number 44 (Recaf’s table mode indexes from 1) in the methods table and that its code section begins at offset 0xC68D in the .class file.
From here, we can just read the bytecode byte-by-byte (I just used https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions as a reference) and look for the instructions we want to replace with NOPs. We can skip ahead bit by looking for an INVOKESTATIC, since it’s used right before the section we’re looking for. GETSTATIC is three bytes and ARETURN is one byte, so we end up zeroing out a grand total of four bytes. Once this is done, it will skip returning after the faulty checkAcceleration() check and everything will Just Workâ˘.
For actually editing the bytes, I can’t rely on Recaf, since it recomputes some sections of the binary when you use the hex editor and I want to change as little as possible to minimize possible variables in case something goes wrong. Instead, I used Bless, a handy hex editor for Linux.
Finally, after repacking the .jar file, it just works!
It would have been nice if I could have just written a patch for the affected software and recompiled it myself, but when that’s not an option, I guess it’s nice to know how you can still proceed from there.
If you are affected by this bug in Android Studio, feel free to comment on my bug report: https://issuetracker.google.com/issues/229453055
Patching Instructions
This is a binary patch for a specific version of Android Studio. If you are using a different version of Android Studio where the file being patched is different in any way at all from what is expected, then it probably won’t work.
$INSTALL_DIR is wherever you installed it to. I use NixOS so it’s /nix/store/ydpc8sjgd93lznxippyvnc9dvz9crfdd-android-studio-canary-2021.3.1.7-unwrapped/
but yours will probably be different.
To apply the patch, you need to install bsdiff from your package manager or find a bspatch.exe binary somewhere if you use Windows (https://www.romhacking.net/utilities/929/ maybe? I haven’t tested this). Then, run in a console bspatch /path/to/original/android.jar /path/to/patched/android.jar /path/to/android_studio_haxmfix.bsdiff
, and then replace your original android.jar with the patched version. Don’t try to patch it in-place.
Field | Value |
---|---|
Software Version | Android Studio Canary 2021.03 |
File to patch | $INSTALL_DIR/plugins/android/lib/android.jar |
Link to patchfile | https://ftp1.ornx.net/patch/android_studio_haxmfix/android_studio_haxmfix.bsdiff |
Patchfile SHA256 | de62c3618bb9475f9698e99ea3b6de07bde120e99e6c5f8a488797fff8de4131 |
Pre-patch SHA256 | d99fc83f85e7d0d1513776cd51a080a6655432606249b99a3a59df1b46e8a450 |
Post-patch SHA256 | 223b27aa2beb7a199a097233c110b512cabc2806f0ac81ce2c5e0e29307ea173 |
NixOS
Here is an overlay that applies the bsdiff binary patch:
|
|
-
https://git.jetbrains.org/?p=idea/android.git;a=blob;f=android/src/com/android/tools/idea/avdmanager/AvdManagerConnection.java;h=8d0a1d15a52047136ef72f7143875b6d4ed6d575;hb=59f7cd324489bf935a448d07088dd03489dd4e2f#l868 ↩︎
-
https://git.jetbrains.org/?p=idea/android.git;a=blob;f=android/src/com/android/tools/idea/avdmanager/AccelerationErrorCode.java;h=e3bf63c440841e56607911c5ab7d126bc5c303e8;hb=59f7cd324489bf935a448d07088dd03489dd4e2f#l74 ↩︎
-
https://git.jetbrains.org/?p=idea/android.git;a=blob;f=android/src/com/android/tools/idea/avdmanager/AvdManagerConnection.java;h=8d0a1d15a52047136ef72f7143875b6d4ed6d575;hb=59f7cd324489bf935a448d07088dd03489dd4e2f#l866 ↩︎
-
https://git.jetbrains.org/?p=idea/android.git;a=blob;f=android/src/com/android/tools/idea/avdmanager/AvdManagerConnection.java;h=8d0a1d15a52047136ef72f7143875b6d4ed6d575;hb=59f7cd324489bf935a448d07088dd03489dd4e2f#l1174 ↩︎