I've been working with smali/baksmali for some time now, and I understand the instructions to a reasonable extent. Lately, I've encountered an error that is really weird.
Inserting an instruction after the aligned try-catch
block results in Conflicted type VerifyError at runtime.
I've made some assumptions about what the cause might be, including assuming that ART
doesn't accept invocations that expects moveable results, but that's not the case. I also tried inserting an sput
instruction to check if it'll work, but it doesn't. But for some methods I've seen, put
instructions inserted at the same position works quite well, which leads me to believe it's an alignment problem. But I don't know what rule this is based on.
From my understanding, conflicted type error occurs when the type of the register is changed probably via branching to different conditionals or goto
statements. But in this case, I can't seem to figure out where the jump that'd warrant this happening.
Take this method for example
.method private static zzd(Ljava/lang/String;)I
.locals 7
const/4 v0, 0x0
const/4 v1, 0x2
const/4 v2, 0x3
const/4 v3, 0x1
:try_start_0
const-string v4, "[.-]"
.line 19
invoke-static {v4}, Lcom/a/b/c/d/zzax;->zza(Ljava/lang/String;)Lcom/a/b/c/d/zzax;
move-result-object v4
invoke-virtual {v4, p0}, Lcom/a/b/c/d/zzax;->zza(Ljava/lang/CharSequence;)Ljava/util/List;
move-result-object v4
.line 20
invoke-interface {v4}, Ljava/util/List;->size()I
move-result v5
if-ne v5, v3, :cond_0
.line 21
invoke-static {p0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result p0
return p0
.line 22
:cond_0
invoke-interface {v4}, Ljava/util/List;->size()I
move-result v5
if-lt v5, v2, :cond_1
.line 23
invoke-interface {v4, v0}, Ljava/util/List;->get(I)Ljava/lang/Object;
move-result-object v5
check-cast v5, Ljava/lang/String;
invoke-static {v5}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result v5
const v6, 0xf4240
mul-int v5, v5, v6
.line 24
invoke-interface {v4, v3}, Ljava/util/List;->get(I)Ljava/lang/Object;
move-result-object v6
check-cast v6, Ljava/lang/String;
invoke-static {v6}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result v6
mul-int/lit16 v6, v6, 0x3e8
add-int/2addr v5, v6
.line 25
invoke-interface {v4, v1}, Ljava/util/List;->get(I)Ljava/lang/Object;
move-result-object v4
check-cast v4, Ljava/lang/String;
invoke-static {v4}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result p0
# Inserting any instruction here causes a type p0 to become a conflicted type
:try_end_0
.catch Ljava/lang/IllegalArgumentException; {:try_start_0 .. :try_end_0} :catch_0
add-int/2addr v5, p0
return v5
:catch_0
move-exception v4
const-string v5, "LibraryVersionContainer"
.line 29
invoke-static {v5, v2}, Landroid/util/Log;->isLoggable(Ljava/lang/String;I)Z
move-result v2
if-eqz v2, :cond_1
new-array v1, v1, [Ljava/lang/Object;
# Trying to insert the value of p0 into the object array causes the VerifyError
aput-object p0, v1, v0
aput-object v4, v1, v3
const-string p0, "Version code parsing failed for: %s with exception %s."
.line 31
invoke-static {p0, v1}, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
move-result-object p0
.line 32
invoke-static {v5, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
:cond_1
const/4 p0, -0x1
return p0
.end method
baksmali's --register-info flag is your friend. It will add comments for each instruction with the details about the register types. It should show you exactly where the register type is becoming conflicted, including the incoming edges and the incoming types from each edge.
baksmali d --off --register-info ARGS,DEST,FULLMERGE -b "" blah.dex
e.g. here is the comment regarding p0
from your original source, where it becomes conflicted at the catch block, after adding an instruction (e.g. invoke-static {v4}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
) just before the try_end.
#p0=(Conflicted):merge{0x3:(Reference,Ljava/lang/String;),0x4:(Reference,Ljava/lang/String;),0x9:(Reference,Ljava/lang/String;),0xd:(Reference,Ljava/lang/String;),0x12:(Reference,Ljava/lang/String;),0x1d:(Reference,Ljava/lang/String;),0x22:(Reference,Ljava/lang/String;),0x23:(Reference,Ljava/lang/String;),0x2c:(Reference,Ljava/lang/String;),0x31:(Reference,Ljava/lang/String;),0x32:(Reference,Ljava/lang/String;),0x3a:(Reference,Ljava/lang/String;),0x3e:(Reference,Ljava/lang/String;),0x3f:(Reference,Ljava/lang/String;),0x44:(Integer)}
Each of those incoming edges are from instructions within the try block that could potentially throw an exception. By adding the new instruction that can throw an exception after the move-result p0
instruction at the end of the try block, you're adding a new incoming edge to the catch block, and the value of p0
from that edge (Integer
) is not compatible with the types from all the other edges, and so the merged type is considered CONFLICTED
.
The numbers in the merge comment refer to the code offset of the incoming edge. When you disassemble with the --code-offsets option (--off is a shortcut for that), baksmali will add a comment with the code offset for each instruction, so you can match up the code offset from the register info comment back to the actual instruction.
It's worth noting that the code offset in a merge comment refers to the instruction just prior to the one that can actually throw. The instruction that throws can't have affected any registers if it throws an exception, so the incoming types are from the post-instruction types from the instruction just prior.