verifikasi bytecode dalvik - dex2oat
Saya telah menginstrumentasi aplikasi di tingkat bytecode dan mendapatkan kesalahan verifikasi berikut dari 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)
Representasi smali untuk metode ini adalah sebagai berikut:
.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
Versi yang tidak diinstrumentasi tidak berisi pemanggilan pelacak dan instruksi const-string sebelumnya yang mendefinisikan string jejak. Selain itu, instruksi empat langkah pertama juga tidak ada dalam versi aslinya; mereka digunakan untuk mendapatkan register gratis di 'akhir'. Saya juga telah menganotasi file smali dengan informasi tipe register dan hasilnya adalah sebagai berikut (hanya bagian yang menarik yang dijelaskan oleh kesalahan verifikasi):
#@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
Saat melihat posisi [0x2C], saya hanya mengamati bahwa v1 dalam status konflik dan penggabungan yang dijelaskan di [0x2B] memberi tahu saya bahwa uninit digabungkan dengan jenis referensi. Saya menganggap ini masalahnya dan menyebabkan kesalahan verifikasi (https://android.googlesource.com/platform/art/+/master/runtime/verifier/register_line.cc#367). Namun, ketika mempertimbangkan file smali asli yang dilampirkan dengan informasi tipe register, saya mengamati bahwa v1 tidak pernah dalam keadaan konflik. Selain itu, hal yang aneh - setidaknya bagi saya - adalah instrumentasi saya tidak pernah menyentuh register v1, lalu bagaimana konflik ini bisa terjadi?
Jawaban
Masalahnya adalah dengan menambahkan panggilan ke fungsi pelacakan di blok percobaan, Anda menambahkan tepi dari lokasi itu ke penangan pengecualian penampung semua.
Beberapa instruksi dapat memberikan pengecualian dan beberapa tidak. misalnya instruksi pengembalian tidak bisa mengeluarkan pengecualian, sedangkan instruksi pemanggilan bisa. Jadi untuk instruksi apa pun dalam blok percobaan yang dapat melempar, tepi ditambahkan ke penangan pengecualian apa pun untuk blok percobaan itu.
Dalam metode asli, kondisional di dekat awal metode ( if-eqz v0, :cond_0) melompat langsung ke pernyataan return, jadi tidak ada tepi ke penangan pengecualian karena tidak bisa menampilkan pengecualian. Jadi satu-satunya cara untuk mendapatkan pengendali pengecualian itu adalah melalui jalur eksekusi yang v1telah ditetapkan.
Namun, dengan menambahkan instruksi pemanggilan, Anda menambahkan tepi dari sana ke penangan pengecualian, sehingga sekarang ada jalur eksekusi ke penangan pengecualian yang v1tidak disetel.
Jadi pada dasarnya, pertimbangkan kasus ketika v0null pada kondisional (sehingga lompatan diambil) dan kemudian fungsi jejak melontarkan pengecualian. Penangan pengecualian akan dipanggil tetapi v1belum disetel.