Verifica bytecode dalvik - dex2oat

Sep 24 2020

Ho configurato un'app a livello di bytecode e ricevo il seguente errore di verifica da 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)

La piccola rappresentazione per questo metodo è la seguente:

.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

La versione non strumentata non contiene l'invocazione del tracciante e la precedente istruzione const-string che definisce la stringa di traccia. Inoltre, anche le prime quattro istruzioni di movimento non sono presenti nella versione originale; vengono utilizzati per ottenere registri gratuiti alla "fine". Ho anche annotato il file smali con informazioni sul tipo di registro e l'output è il seguente (solo la parte interessante descritta dall'errore di verifica):

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

Quando guardo la posizione [0x2C] osservo solo che v1 è in uno stato di conflitto e l'unione descritta in [0x2B] mi dice che uninit è unito a un tipo di riferimento. Presumo che questo sia il problema e causa l'errore di verifica (https://android.googlesource.com/platform/art/+/master/runtime/verifier/register_line.cc#367). Tuttavia, quando si considera il file smali originale allegato con le informazioni sul tipo di registro, si osserva che v1 non è mai in uno stato di conflitto. Inoltre, la cosa strana - almeno per me - è che la mia strumentazione non tocca mai il registro v1, quindi come può accadere questo conflitto?

Risposte

1 JesusFreke Sep 24 2020 at 01:48

Il problema è che aggiungendo la chiamata alla funzione di traccia nel blocco try, stai aggiungendo un bordo da quella posizione al gestore di eccezioni catch-all.

Alcune istruzioni sono in grado di generare eccezioni e altre no. ad esempio, le istruzioni return non possono generare un'eccezione, mentre le istruzioni invoke sì. Quindi, per qualsiasi istruzione in un blocco try che può generare, viene aggiunto un margine a tutti i gestori di eccezioni per quel blocco try.

Nel metodo originale, il condizionale vicino all'inizio di method ( if-eqz v0, :cond_0) saltava direttamente a un'istruzione return, quindi non c'è alcun vantaggio nel gestore delle eccezioni poiché non può generare un'eccezione. Quindi l'unico modo per arrivare a quel gestore di eccezioni è tramite un percorso di esecuzione in cui v1è stato impostato.

Tuttavia, aggiungendo l'istruzione invoke, hai aggiunto un bordo da lì al gestore delle eccezioni, in modo che ora ci sia un percorso di esecuzione per il gestore delle eccezioni in cui v1non è impostato.

Quindi, fondamentalmente, considera il caso in cui v0è nullo al condizionale (quindi il salto è preso) e quindi la funzione di traccia genera un'eccezione. Il gestore delle eccezioni verrà chiamato ma v1non è stato impostato.