dalvik bytecode verifikation - dex2oat

Sep 24 2020

Ich habe eine App auf Bytecode-Ebene instrumentiert und von dex2oat den folgenden Überprüfungsfehler erhalten:

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)

Die kleine Darstellung für diese Methode ist die folgende:

.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

Die nicht instrumentierte Version enthält nicht den Aufruf des Tracers und den vorhergehenden Befehl const-string, der den Trace-String definiert. Darüber hinaus sind die ersten vier Bewegungsanweisungen in der Originalversion nicht vorhanden. Sie werden verwendet, um am Ende freie Register zu erhalten. Ich habe auch die smali-Datei mit Registertypinformationen versehen und die Ausgabe ist die folgende (nur der interessante Teil, der durch den Überprüfungsfehler beschrieben wird):

    #@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

Wenn ich mir die Position [0x2C] anschaue, stelle ich nur fest, dass sich v1 in einem Konfliktzustand befindet und die unter [0x2B] beschriebene Zusammenführung sagt mir, dass uninit mit einem Referenztyp zusammengeführt wird. Ich gehe davon aus, dass dies das Problem ist und den Überprüfungsfehler verursacht (https://android.googlesource.com/platform/art/+/master/runtime/verifier/register_line.cc#367). Wenn ich jedoch die ursprüngliche smali-Datei betrachte, die den Registertypinformationen beigefügt ist, stelle ich fest, dass sich v1 niemals in einem Konfliktzustand befindet. Darüber hinaus ist das Seltsame - zumindest für mich -, dass meine Instrumentierung niemals Register v1 berührt. Wie kann dieser Konflikt also auftreten?

Antworten

1 JesusFreke Sep 24 2020 at 01:48

Das Problem besteht darin, dass Sie durch Hinzufügen des Aufrufs zur Ablaufverfolgungsfunktion im try-Block dem Catch-All-Exception-Handler eine Kante von dieser Position hinzufügen.

Einige Anweisungen können Ausnahmen auslösen, andere nicht. Beispielsweise können Rückgabeanweisungen keine Ausnahme auslösen, während Aufrufanweisungen dies können. Für jede Anweisung in einem try-Block, die ausgelöst werden kann, wird allen Ausnahmebehandlungsroutinen für diesen try-Block eine Kante hinzugefügt.

In der ursprünglichen Methode ist die Bedingung am Anfang von method ( if-eqz v0, :cond_0) direkt zu einer return-Anweisung gesprungen, sodass der Ausnahmehandler keine Kante hat, da er keine Ausnahme auslösen kann. Der einzige Weg, um zu diesem Ausnahmebehandler zu gelangen, ist über einen Ausführungspfad, in dem v1festgelegt wurde.

Durch Hinzufügen der Anweisung invoke haben Sie dem Exception-Handler jedoch eine Kante von dort hinzugefügt, sodass jetzt ein Ausführungspfad zum Exception-Handler vorhanden v1ist, der nicht festgelegt ist.

Betrachten Sie also im Grunde den Fall, v0in dem die Bedingung null ist (damit der Sprung ausgeführt wird) und die Trace-Funktion dann eine Ausnahme auslöst. Der Ausnahmebehandler wird aufgerufen, v1wurde jedoch nicht festgelegt.