auermich
auermich

Reputation: 150

dalvik bytecode verification - dex2oat

I have instrumented an app at bytecode level and getting the following verification error from dex2oat:

2020-09-23 19:39:04.005 4864-4864/? W/dex2oat: Verification error in int a.d.cg.b(byte[], int, int)
2020-09-23 19:39:04.005 4864-4864/? W/dex2oat: int a.d.cg.b(byte[], int, int): [0x25] 
2020-09-23 19:39:04.005 4864-4864/? W/dex2oat: int a.d.cg.b(byte[], int, int): [0x27] 
2020-09-23 19:39:04.005 4864-4864/? W/dex2oat: int a.d.cg.b(byte[], int, int) failed to verify: int a.d.cg.b(byte[], int, int): [0x2C] monitor-exit on non-object (Undefined)

The smali representation for this method is the following:

.method b([BII)I
    .locals 4

    move-object/from16 v2, p0

    move-object/from16 v3, p1

    move/from16 p0, p2

    move/from16 p1, p3

    iget-object v0, v2, La/d/cg;->a:La/d/bj;

    iget-object v0, v0, La/d/bj;->p:Ljava/io/InputStream;

    if-eqz v0, :cond_0

    const-string p2, "La/d/cg;->b([BII)I->3"

    invoke-static/range {p2 .. p2}, Lde/tracer/Tracer;->trace(Ljava/lang/String;)V

    iget-object v0, v2, La/d/cg;->a:La/d/bj;

    iget-object v0, v0, La/d/bj;->p:Ljava/io/InputStream;

    check-cast v0, La/d/cn;

    iget-object v1, v0, La/d/cn;->b:Ljava/lang/Object;

    monitor-enter v1

    :try_start_0
    invoke-virtual {v0, v3, p0, p1}, La/d/cn;->b([BII)I

    iget-object v0, v0, La/d/cn;->b:Ljava/lang/Object;

    invoke-virtual {v0}, Ljava/lang/Object;->notify()V

    monitor-exit v1

    :cond_0
    const-string p2, "La/d/cg;->b([BII)I->12"

    invoke-static/range {p2 .. p2}, Lde/tracer/Tracer;->trace(Ljava/lang/String;)V

    return p1

    :catchall_0
    move-exception v0

    monitor-exit v1
    :try_end_0
    .catchall {:try_start_0 .. :try_end_0} :catchall_0

    throw v0
.end method

The un-instrumented version doesn't contain the invocation of the tracer and the preceeding const-string instruction defining the trace string. In addition, the first four move instructions are also not present in the original version; they are used to get free registers at the 'end'. I have also annotated the smali file with register type information and the output is the following (only the interesting part described by the verification error):

    #@1b
    #v0=(Reference,La/d/cn;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);
    monitor-enter v1
    #v0=(Reference,La/d/cn;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);

    #@1c
    :try_start_1c
    #v0=(Reference,La/d/cn;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);
    invoke-virtual {v0, v3, p0, p1}, La/d/cn;->b([BII)I
    #v0=(Reference,La/d/cn;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);

    #@1f
    #v0=(Reference,La/d/cn;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);
    iget-object v0, v0, La/d/cn;->b:Ljava/lang/Object;
    #v0=(Reference,Ljava/lang/Object;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);

    #@21
    #v0=(Reference,Ljava/lang/Object;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);
    invoke-virtual {v0}, Ljava/lang/Object;->notify()V
    #v0=(Reference,Ljava/lang/Object;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);

    #@24
    #v0=(Reference,Ljava/lang/Object;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);
    monitor-exit v1
    #v0=(Reference,Ljava/lang/Object;);v1=(Reference,Ljava/lang/Object;);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);

    #@25
    :cond_25
    #v0=(Reference,Ljava/lang/Object;):merge{0xc:(Reference,Ljava/io/InputStream;),0x24:(Reference,Ljava/lang/Object;)}
    #v1=(Conflicted):merge{0xc:(Uninit),0x24:(Reference,Ljava/lang/Object;)}
    #v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);
    #p2=(Conflicted):merge{0xc:(Integer),0x24:(Reference,Ljava/lang/String;)}
    #p3=(Integer);
    const-string p2, "La/d/cg;->b([BII)I->12"
    #v0=(Reference,Ljava/lang/Object;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);

    #@27
    #v0=(Reference,Ljava/lang/Object;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);
    invoke-static/range {p2 .. p2}, Lde/tracer/Tracer;->trace(Ljava/lang/String;)V
    #v0=(Reference,Ljava/lang/Object;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);

    #@2a
    #v0=(Reference,Ljava/lang/Object;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);
    return p1
    #v0=(Reference,Ljava/lang/Object;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Reference,Ljava/lang/String;);p3=(Integer);

    #@2b
    :catchall_2b
    #v0=(Reference,Ljava/lang/Object;):merge{0xc:(Reference,Ljava/io/InputStream;),0x1b:(Reference,La/d/cn;),0x1c:(Reference,La/d/cn;),0x1f:(Reference,Ljava/lang/Object;),0x21:(Reference,Ljava/lang/Object;),0x24:(Reference,Ljava/lang/Object;),0x25:(Reference,Ljava/lang/Object;),0x2b:(Reference,Ljava/lang/Throwable;)}
    #v1=(Conflicted):merge{0xc:(Uninit),0x1b:(Reference,Ljava/lang/Object;),0x1c:(Reference,Ljava/lang/Object;),0x1f:(Reference,Ljava/lang/Object;),0x21:(Reference,Ljava/lang/Object;),0x24:(Reference,Ljava/lang/Object;),0x25:(Conflicted),0x2b:(Conflicted)}
    #v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);
    #p2=(Conflicted):merge{0xc:(Integer),0x1b:(Reference,Ljava/lang/String;),0x1c:(Reference,Ljava/lang/String;),0x1f:(Reference,Ljava/lang/String;),0x21:(Reference,Ljava/lang/String;),0x24:(Reference,Ljava/lang/String;),0x25:(Reference,Ljava/lang/String;),0x2b:(Conflicted)}
    #p3=(Integer);
    move-exception v0
    #v0=(Reference,Ljava/lang/Throwable;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Conflicted);p3=(Integer);

    #@2c
    #v0=(Reference,Ljava/lang/Throwable;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Conflicted);p3=(Integer);
    monitor-exit v1
    #v0=(Reference,Ljava/lang/Throwable;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Conflicted);p3=(Integer);
    :try_end_2d
    .catchall {:try_start_1c .. :try_end_2d} :catchall_2b

    #@2d
    #v0=(Reference,Ljava/lang/Throwable;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Conflicted);p3=(Integer);
    throw v0
    #v0=(Reference,Ljava/lang/Throwable;);v1=(Conflicted);v2=(Reference,La/d/cg;);v3=(Reference,[B);p0=(Integer);p1=(Integer);p2=(Conflicted);p3=(Integer);
.end method

When looking at the position [0x2C] I only observe that v1 is in a conflicted state and the merge described at [0x2B] is telling me that uninit is merged with a reference type. I assume this is the problem and causes the verification error (https://android.googlesource.com/platform/art/+/master/runtime/verifier/register_line.cc#367). However, when considering the original smali file attached with the register type information, I observe that v1 is never in a conflicted state. Moreover, the strange thing - at least to me - is that my instrumentation never touches register v1, hence how can this conflict happen?

Upvotes: 1

Views: 324

Answers (1)

JesusFreke
JesusFreke

Reputation: 20262

The issue is that by adding the call to the tracing function in the try block, you're adding an edge from that location to the catch-all exception handler.

Some instructions are able to throw exceptions and some are not. e.g. return instructions cannot throw an exception, while invoke instructions can. So for any instruction in a try block that can throw, an edge is added to any exception handlers for that try block.

In the original method, the conditional near the beginning of the method (if-eqz v0, :cond_0) jumped directly to a return statement, so there is no edge to the exception handler since it can't throw an exception. So the only way to get to that exception handler is via an execution path in which v1 has been set.

However, by adding the invoke instruction, you added an edge from there to the exception handler, so that there is now an execution path to the exception handler in which v1 is unset.

So basically, consider the case when v0 is null at the conditional (so the jump is taken) and then the trace function throws an exception. The exception handler will be called but v1 has not been set.

Upvotes: 1

Related Questions