vérification du bytecode dalvik - dex2oat
J'ai instrumenté une application au niveau du bytecode et j'ai obtenu l'erreur de vérification suivante de 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 représentation smali pour cette méthode est la suivante:
.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 version non instrumentée ne contient pas l'appel du traceur et l'instruction précédente const-string définissant la chaîne de trace. De plus, les quatre premières instructions de déplacement ne sont pas non plus présentes dans la version originale; ils sont utilisés pour obtenir des registres gratuits à la «fin». J'ai également annoté le fichier smali avec des informations de type de registre et la sortie est la suivante (seule la partie intéressante décrite par l'erreur de vérification):
#@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
En regardant la position [0x2C], je constate seulement que v1 est dans un état de conflit et la fusion décrite à [0x2B] me dit que uninit est fusionné avec un type de référence. Je suppose que c'est le problème et provoque l'erreur de vérification (https://android.googlesource.com/platform/art/+/master/runtime/verifier/register_line.cc#367). Cependant, en considérant le fichier smali d'origine joint avec les informations de type de registre, j'observe que v1 n'est jamais dans un état de conflit. De plus, la chose étrange - du moins pour moi - est que mon instrumentation ne touche jamais le registre v1, d'où comment ce conflit peut-il se produire?
Réponses
Le problème est qu'en ajoutant l'appel à la fonction de traçage dans le bloc try, vous ajoutez un bord de cet emplacement au gestionnaire d'exceptions fourre-tout.
Certaines instructions peuvent lancer des exceptions et d'autres non. par exemple, les instructions de retour ne peuvent pas lever d'exception, tandis que les instructions d'invocation le peuvent. Donc, pour toute instruction dans un bloc try qui peut lancer, un bord est ajouté à tous les gestionnaires d'exceptions pour ce bloc try.
Dans la méthode d'origine, le conditionnel près du début de la méthode ( if-eqz v0, :cond_0) sautait directement à une instruction return, donc il n'y a pas de bord au gestionnaire d'exceptions car il ne peut pas lever d'exception. Donc, le seul moyen d'accéder à ce gestionnaire d'exceptions est via un chemin d'exécution dans lequel v1a été défini.
Cependant, en ajoutant l'instruction invoke, vous avez ajouté un bord à partir de là au gestionnaire d'exceptions, de sorte qu'il existe maintenant un chemin d'exécution vers le gestionnaire d'exceptions dans lequel v1n'est pas défini.
Donc, en gros, considérez le cas où v0est nul au conditionnel (le saut est donc effectué), puis la fonction de trace lève une exception. Le gestionnaire d'exceptions sera appelé mais v1n'a pas été défini.