Redundant opcodes in Android dex - android

I'm looking into some Android performance issues at the moment and noticing some sub-optimal patterns in the dex code. I'm just wondering if anyone knows if this is to be expected, and what the rationale behind it might be.
For example, consider the following Java code:
m_testField += i;
doSomething(m_testField);
When this is built and then run through baksmali it looks like the following:
iget v1, p0, Lcom/example/MainActivity$FieldTest;->m_testField:I
add-int/2addr v1, v0
iput v1, p0, Lcom/example/MainActivity$FieldTest;->m_testField:I
iget v1, p0, Lcom/example/MainActivity$FieldTest;->m_testField:I
invoke-direct {p0, v1}, Lcom/example/MainActivity$FieldTest;->doSomething(I)V
The part that's concerning me is the iget opcode to read the value of the instance field into register v1. The same field was written from the very same v1 register in the preceding opcode, so the opcode would appear to be completely redundant.
The only thing I can think of is that this is done to make this more thread-safe. But surely that should be the programmer's responsibility (by using sync blocks) instead of the compiler's responsibility. Although I'm not 100% certain, I think the above behaviour is quite different to what most C/C++ compilers would do.
I should say that essentially the same dex is produced when ProGuard is used. I should also probably mention that I'm using the very latest Android tools and a late model JDK.

Every access to a field is independent. To get the behavior you describe, you need to add an extra local variable:
int local = m_testField; // iget
local = local + i;
m_testField = local; // iput
doSomething(local);
That said, some combination of the interpreter, just-in-time compiler and ahead-of-time compiler may end up making these optimizations for you at runtime anyway.

On a hunch, I've done some further research and I think I'm in a position to answer my own question...
The sub-optimal dex seems to be a by-product of the fact that it is generated from standard Java bytecode which is stack-based rather than register-based. I disassembled the .class file corresponding to the sample code in my question. The relevant section looks like this:
5: aload_0
6: dup
7: getfield #22 // Field m_testField:I
10: iload_1
11: iadd
12: putfield #22 // Field m_testField:I
15: aload_0
16: aload_0
17: getfield #22 // Field m_testField:I
20: invokespecial #33 // Method doSomething:(I)V
After the iadd opcode on line 11 is executed, the value of m_testField is at the top of the stack and the 'this' reference is second from the top. The problem is that the putfield opcode on line 12 removes these from the stack. This means that the field value has to be re-pushed to the stack on line 17.
I must say I'm pretty surprised by this inefficiency. I'd have thought that the dx tool that converts bytecode to dex would be clever enough to remove this redundancy. I'm just hoping that ART is clever enough to do this at runtime instead.

Related

Confused about the `move-result-wide` smali instruction

The following smali codes, trying to move a 64-bit result to the register v4. But the size of the v4 is 32-bit. So how does the v4 store the value?
invoke-virtual {v0}, Landroid/location/Location;->getTime()J
move-result-wide v4
The move-result-wide vx instruction will move the long/double result value of the previous method invocation into vx,vx+1.
See Dalvik opcodes.

Smali - Undeclared static method

After compiling a certain apk and going through the smali code I stumbled upon this line in file com/name/name/r.smali:
invoke-static {v1, v0, v2}, Lcom/name/name/p;->a(Ljava/lang/String;Ljava/util/List;Lcom/name/name/CallbackI;)V
name is a placeholder in this situation
Unfortunately both of the classes p and CallbackI don't exist in com/name/name, how is this possible?
There are a few possibilities:
The code referencing those missing classes is dead code that never gets executed.
The app crashes when it tries to run that code.
The classes are defined elsewhere. Is the app using multi-dex? Or maybe the app uses a DexClassLoader to load another dex file?

#NonNull and #Nullable annotations - Does it effect the performance in Android?

Do #NonNull and #Nullable annotations effect the performance in Android during runtime? I mean they only have the purpose to support LINT and other tools to detect possible bugs, so they probably/hopefully will be ignored during compiling. Do you have any sources for me proving this?
The #NonNull and Nullable annotations are declared with CLASS retention policy. They are seen in the compiled bytecode but can be ignored by the VM at runtime.
If you're concerned about performance, run an experiment to see if it is an issue for you. I would hypothesize that class-retention annotations don't add any significant overhead.
(If the retention policy was runtime and the annotations were queried via reflection at runtime, then there would be performance issues especially in the past.)
As a little experiment I created the following method in an Android app:
private void test(#NonNull Object o) {
o.toString();
}
Compiled the project, 'un-compiled' it with ApkTool and took a look at the smali file:
.method private test(Ljava/lang/Object;)V
.locals 0
.param p1, "o" # Ljava/lang/Object;
.annotation build Landroid/support/annotation/NonNull;
.end annotation
.end param
.prologue
.line 22
invoke-virtual {p1}, Ljava/lang/Object;->toString()Ljava/lang/String;
.line 23
return-void
.end method
So the Nonnull annotation is not actually removed from the code at compile time.
I could though turn off the checks in Android studio, call the test-method with null as an argument and compile the app. It then crashes with a NullPointerExeption at o.toString() of course, but there is no hint of the VM actually using the annotation to check the parameter at runtime. Exactly what laalto said in his answer.
I do wonder though if the VM would be capable of doing so and throwing an exception when the method is called with the null argument.

Junk byte injection in Android

After reading this interesting article about code obfuscation in Android, I'm trying to do it for research purposes but after applying the technique into a classes.dex file I'm getting a crash.
The next is the code I'm trying to run after applying the technique:
0006e8: |[0006e8] com.example.root.bji.MainActivity.paintGUI:()V
0006f8: 1202 |0000: const/4 v2, #int 0 // #0
0006fa: 1a01 0000 |0001: const-string v1, "" // string#0000
0006fe: 1200 |0003: const/4 v0, #int 0 // #0
000700: 1303 1400 |0004: const/16 v3, #int 20 // #14
000704: 3244 0900 |0006: if-eq v4, v4, 000f // +0009
000708: 2600 0300 0000 |0008: fill-array-data v0, 0000000b // +00000003
00070e: 0003 0100 1600 0000 1212 0000 0000 ... |000b: array-data (15 units)
00072c: 0000 |001a: nop // spacer
00072e: 0000 |001b: nop // spacer
... more NOPs ...
000742: 0000 |0025: nop // spacer
000744: 0000 |0026: nop // spacer
000746: 1503 087f |0027: const/high16 v3, #int 2131230720 // #7f08
...
To give you some context, I want to keep clear some assignations like the 0 value into the v2 register at 0x6f8 ("const/4 v2, 0" => 12 02), which will be shown in the GUI at the end of this method (at 0x746 and beyond); and using this obfuscation technique, "hide" the modification of the v2 register setting a value of 1 into the v2 register at 0x716 ("const/4 v2, 1" => 12 12).
If you follow the code at 0x704 the branch is done to 0x716, where the "const/4 v2, 1"r esides, inside the fill-data-array-payload.
And the problem I'm facing is a crash when I'm running the code (I've tried it from 4.3 to 5.1), and what logcat tells me when the crash happens is:
W/dalvikvm(13874): VFY: invalid branch target 9 (-> 0xf) at 0x6
W/dalvikvm(13874): VFY: rejected Lcom/example/root/bji/MainActivity;.paintGUI ()V
W/dalvikvm(13874): VFY: rejecting opcode 0x32 at 0x0006
W/dalvikvm(13874): VFY: rejected Lcom/example/root/bji/MainActivity;.paintGUI ()V
W/dalvikvm(13874): Verifier rejected class Lcom/example/root/bji/MainActivity;
W/dalvikvm(13874): Class init failed in newInstance call (Lcom/example/root/bji/MainActivity;)
D/AndroidRuntime(13874): Shutting down VM
For what I understand in the logs, the OS is rejecting the "if-eq" jump because the offset pointed (I've tried other branch instructions but the result is the same). The only way the code works is if I point to an offset outside the fill-array-data-payload, but then there is no obfuscation technique applied :P.
Anyone have tried something similar to this technique or have fight against this branch verification rejection?
This is not expected to work. The bytecode verifier explicitly checks all branches for validity. The question of whether or not an address is an instruction or data is determined by a linear walk through the method. Data chunks are essentially very large instructions, so they get stepped over.
You can make this work if you modify the .odex output, and set the "pre-verified" flag on the class so the verifier doesn't examine it again -- but you can't distribute an APK that way.
This "obfuscation" technique worked due to an issue in dalvik. This issue was fixed somewhere around the 4.3 timeframe, although I'm not sure the first released version that contained the fix. And lollipop uses ART, which never had this issue.
Here is the change that fixed this issue: https://android-review.googlesource.com/#/c/57985/

Native Android - Can't locate undefined method when loading a shared lib even though previously loaded shared lib contains the definition

I am building an Android app that loads in 2 native shared libraries at runtime: 1 that was built with an unresolved symbol in it and the other which resolves and defines that symbol. In Java, I load the shared library that defines the symbol first, then load the library that has the symbol declared as unresolved, and at this point, the runtime fails with:
"Cannot load library: reloc_library[]: 33 cannot locate 'someMethod'
So here's the one unique difference. The shared library with the undefined symbol obviously doesn't know about the shared library with the definition for the symbol in it.
I just assumed that if I loaded the library with the definition of the method first that when I loaded the 2nd library that called the method, it would be able to find it. Am I wrong on that? It seems in my case, an explicit dependency HAS to be compiled in between the two native libs, which means (I think) making .so's with unresolved symbols is useless.
I have searched vigorously for a similar issue with no luck. I think my problem is due to an architectural limitation, and I am considering approaching it a couple of other ways, but I would like to know if it can be fixed simply.
To be sure it wasn't some complexity of the library itself, I created two very simple C files:
fcn_defined.c:
int someMethod()
{
return 1;
}
fcn_undefined.c:
extern int someMethod();
int someOtherMethod()
{
someMethod();
}
Then build two shared objects where the fcn_undefined.c code creates a .so with someMethod still undefined and fcn_defined.c builds a .so with someMethod defined:
gcc -o libfcn_undefined.so fcn_undefined.c -shared -Wl,--export-dynamic
gcc -o libfcn_defined.so fcn_defined.c -shared -Wl,--export-dynamic
Doing a nm on these produces:
libfcn_undefined.so:
0001f08 d _DYNAMIC
00001fe8 d _GLOBAL_OFFSET_TABLE_
00002004 A __bss_start
U __cxa_atexit
U __cxa_finalize
00002000 d __dso_handle
00000290 t __on_dlclose
00002004 A _edata
00002004 A _end
000002a0 t atexit
000002b4 T someOtherMethod
U someMethod
and libfcn_defined.so:
00001f0c d _DYNAMIC
00001fec d _GLOBAL_OFFSET_TABLE_
00002004 A __bss_start
U __cxa_atexit
U __cxa_finalize
00002000 d __dso_handle
0000025c t __on_dlclose
00002004 A _edata
00002004 A _end
0000026c t atexit
00000280 T someMethod
So you can see someMethod() is defined in libfcn_defined.so (and it appears in the read elf dynsym section) and is undefined in the other lib.
If anyone is interested in the readelf output, I can add that as well.
In the Java side, I have a simple button in the emulator that I click, and it creates a class with the following in it:
static
{
System.loadLibrary("fcn_defined");
System.loadLibrary("fcn_undefined");
}
Just out of curiosity, I added a "-lfcn_defined" to the fcn_undefined compile line, and compared the nm and readelf outputs. the only difference in nm was that the "T someOtherMethod" started a few bytes further out and the readelf difference was the "NEEDED" line for fcn_defined. That's pretty much about what I expected. And it doesn't crash like this.
That's pretty much the full explanation. I did find some details about how Android forces you to load your libraries in reverse dependency order in Java, because it has (rather it had, has been fixed in API 18) no reference to your app's lib path in the LD_LIBRARY_PATH envvar. Unfortunately, I am requiring a minimum API lvl 10 to be able to use my app because of the market penetration, and secondly I tried API 19 anyway, and it still fails.
If I had to guess, I believe Android just doesn't support finding a symbol if you haven't explicitly told it to look at library X for the symbol. In other words, because I didn't build the library fcn_undefined with an explicit dependency on libfcn_defined.so, Android can't resolve it. Does anyone know if this is a bug or by design? Is this normal? It seems like you wouldn't have the option to create a .so with unresolved symbols if this was the case, and even funnier is that the Android NDK toolchain I'm using to build this has this feature on by default when you use ld (it doesn't complain about unresolved), and I tried turning the feature off but didn't seem to do anything, no warnings or errors generating the library.
So you may ask why I don't just compile the fcn_undefined library with a dependency on the fcn_defined library. Well that gets into a much bigger architectural discussion. The code I'm working with (fcn_undefined.c in this example) is a python extension built with a cross compiled python toolchain for ARM, and I'm calling this library from an NDK library, so now the NDK library depends on the python module which has an unresolved method in Python, which is defined in a static lib. Linking the static lib into the NDK shared lib means that I can't load the native shared libs in the correct order in Java (due to the issue mentioned previously that they fixed in API 18). I'm trying to work with the existing system since a team of others use it, and it is used to build for many platforms. sigh I clearly have other things to figure out, but I was hoping to nail the one above down at least.
The behavior you so beautifully demonstrated is by design (or lack of, if you will). You are right in part, the crazy_linker does resolve some of such issues (but not all of them). There is an easy but ugly workaround. Build a dummy libfcn_defined.so which only has T someMethod in its nm. Use it to link libfnc_undefined.so, using LD_LIBS. NDK will show a warning, but that's OK. LAoad the real libfcn_defined.so in your Java.
By Unix/ELF design, you need a NEEDED entry in libfnc_undefined.so that lists libfnc_defines.so as a dependency for the dynamic linker to look into it for missing symbols.
I.e. you should ensure that -lfcn_defined (or /path/to/libfcn_defined.so) appears in the link command that generated libfcn_undefined.so.
If you use ndk-build to generate both libraries, just list libfcn_defined.so as a LOCAL_STATIC_LIBRARIES or LOCAL_SHARED_LIBRARIES entry for libfcn_undefined.so.
If you use another build system, adapt accordingly.

Categories

Resources