auermich
auermich

Reputation: 150

dexlib2 - Branch coverage instrumentation

I am trying to instrument smali code using dexlib2 in order to measure branch coverage. In particular, I am inserting at each branch (if and corresponding label) basically two instructions; const-string to load a unique trace for each branch and invoke-static to call a static method. However, there are a couple of issues:

First, I had to increase the number of register by one. This led to re-arranging the registers of certain instructions, which seems to work (used reflection to increase the register number, e.g. originally p0 got v20 by introducing a new local register v21). However, I have noticed that certain labels, e.g. .end local v15 also would require this sort of modification, which seems to be not possible with dexlib2, since the labels don't track this kind of information nor a name. I am also unaware what's the meaning/intention of those .end/.restart./start local labels. Are those labels interesting for garbage collection or also some kind of type information for the corresponding registers?

Second, certain instructions only accept as arguments v0-v15. That's why I had to differentiate whether the number of local registers exceeds 16 or not. In this case, I basically use two additional move instructions: (in the other case, the instrumentation is much easier)

move-object/16 vNew, v0 # save value of v0
(two above mentioned instructions) # use v0 for keeping the trace
move-object/16 v0, vNew # restore value of v0

However, recently I am getting the following error (and similar verify errors):

[0x25C] 'this' argument 'Reference: java.lang.Object' not instance of 'Reference: com.android.calendar.GeneralPreferences'

I have observed that it makes a difference between using move and move-object, but I am unaware of the concrete difference. I would assume that constants are non-objets while the rest represents objects. If this distinction is necessary, I would have to perform some analysis of v0's last type at each branch, which makes everything even more complicated.

Third, I have noticed that the labels associated with branches are somewhat behaving strange in some rare cases. There are branches in the entire smali file that gets instrumented twice. Debugging reveals that querying the if instruction for its target labels (the other branch) one time returns more labels than the other time. That's why I now use the index of the target label (instruction.getTarget().getLocation().getIndex()), but I still obtain a single branch, which gets instrumented twice.

I am asking for any help on that particular matter as well as general hints/facts I should consider. Is there a better way to get more detailed information about errors; the output of logcat is not the best, e.g. which particular instruction caused the verify error (the hexadecimal value treated as offest doesn't make any sense for me).

Thanks in advance.

Upvotes: 0

Views: 313

Answers (1)

JesusFreke
JesusFreke

Reputation: 20282

The best way I've seen to deal with the issues you noticed when increasing register count is to add a prologue that moves all post-increase parameter register back to their pre-increase locations, and then use the last register/registers as the new "scratch" registers.

e.g. if the parameters were at v14-v20, and you increase the register count by one, you would add code that would move v15 back to v14, v16 back to v15, etc. and use v21 as the new scratch register.

Alternately, you can try not to allocate any registers. e.g. create a new method, and pass in the value from the target method that you want to instrument. You can use invoke-*/range to pass in any single register. But in your case, this doesn't seem too feasible, since you want to pass in an additional string to identify the branch. You could theoretically create a new method for every branch, but you'll run into method limits in no time that way.


.end/.restart./start local are purely for debugging information. They tell the debugger which register is associated with which local variable in the original java code. The easiest thing would be to just strip them. See https://source.android.com/devices/tech/dalvik/dex-format#debug-info-item for more information.


Yes, move-object must be used for reference types, move-wide for primitive longs/doubles, and move for other primitives. Also, don't forget that wide types take up 2 registers. So if you may need to allocate 2 extra scratch registers if you happen to need to move a wide value (a long or double primitive)

dexlib2 has an api for performing type analysis on registers, if needed. See https://github.com/JesusFreke/smali/blob/master/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java

Upvotes: 1

Related Questions