ThomasKing 发表于 2014-7-7 19:33

【转】让人纠结的finally

最近分析某一apk,被其中的各种try-catch-finally块虐得死去活来,有木有。。。
由于喜欢刨根,各种谷歌都没有给出解释。 一番研究之后,发现确实有些蹊跷。(本人水平有限,不喜勿喷,谢谢http://bbs.pediy.com/images/smilies/smile.gif)

切入正题:
try{}catch反编译后,格式:   
    eg: .catch Ljava/io/FileNotFoundException; {:try_start_0 .. :try_end_0} :catch_0
加上finally,那就多一行声明:
   eg: .catchall {:try_start_0 .. :try_end_0} :catchall_0
大家都知道,finally块中的代码始终都要执行,那么是不是类似goto到finally块中去执行代码呢? 可惜不是,是直接复制了3份。
比如:
try {
    FileInputStream stream = new FileInputStream(new File("/mnt/sdcard/test.txt"));
} catch (FileNotFoundException e) {
    e.printStackTrace();
}finally{
    System.out.println("Fin");
}
smali:
:try_start_0
    new-instance v1, Ljava/io/FileInputStream;

    new-instance v2, Ljava/io/File;

    const-string v3, "/mnt/sdcard/test.txt"

    invoke-direct {v2, v3}, Ljava/io/File;-><init>(Ljava/lang/String;)V

    invoke-direct {v1, v2}, Ljava/io/FileInputStream;-><init>(Ljava/io/File;)V
    :try_end_0
    .catchall {:try_start_0 .. :try_end_0} :catchall_0
    .catch Ljava/io/FileNotFoundException; {:try_start_0 .. :try_end_0} :catch_0

    .line 28
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v2, "Fin"

    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 30
    :goto_0
    return-void

    .line 24
    :catch_0
    move-exception v0

    .line 26
    .local v0, e:Ljava/io/FileNotFoundException;
    :try_start_1
    invoke-virtual {v0}, Ljava/io/FileNotFoundException;->printStackTrace()V
    :try_end_1
    .catchall {:try_start_1 .. :try_end_1} :catchall_0

    .line 28
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v2, "Fin"

    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    goto :goto_0

    .line 27
    .end local v0         #e:Ljava/io/FileNotFoundException;
    :catchall_0
    move-exception v1

    .line 28
    sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v3, "Fin"

    invoke-virtual {v2, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 29
    throw v1
(由于黄色看不清,这里修改为红色)

不难发现,正常执行流程和catch块中增加了finally中的代码,而且二者是同一个出口。(goto_0)
那黄色(第三块红色)那块是什么???
经过多次测试,只要catch块中调用了函数,就会隐式生成一个try,且只有.catchall
:try_start_1
    invoke-virtual {v0}, Ljava/io/FileNotFoundException;->printStackTrace()V
:try_end_1
.catchall {:try_start_1 .. :try_end_1} :catchall_0

catchall_n一般结构是:
move-exception vm
......
throw vm

另外,经过多次动态调试smali,都没有出现程序进入.catchall_n模块。估计是没有构造出相应的触发条件。由于运行时异常不需要显式声明,猜测可能运行时异常触发。故构造一下代码:
public void fun(int flag) throws IllegalArgumentException
{
    switch(flag){
      case 1: throw new IllegalArgumentException();
      case 2: throw new RuntimeException();
      default: break;
    }
}
public void Test(int flag)
{
    try {
      fun(flag);   
      foo();
    } catch (Exception e) {
      foo();
    }finally{
      Log.i("Finally", "Exit");
    }
}
public void foo(){
    throw new RuntimeException();
}
由于Log.i("Finally", "Exit");会被拷贝三份,故修改.catall中的输出,进行区别。具体如下:
    :catchall_1a
    move-exception v1

    .line 39
    const-string v2, "Finally"

    const-string v3, "Exit_Finally"
修改"Exit"为"Exit_Finally"

    invoke-static {v2, v3}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I

    .line 40
    throw v1
onCreate分别测试调用Test(0)和Test(1),均打印出"Exit_Finally",并未打印出"Exit"。
http://bbs.pediy.com/attachment.php?attachmentid=90388&thumb=1&d=1404552292

这下终于明白了.catchall的作用,确实保证了finally块中的代码一定会被执行。

PS:要是多重try-catch-finally嵌套,而且finally块中有很多代码,那就。。。 万恶的finally块,虐了我千百遍。。。                                                                       

fishyoyo 发表于 2014-7-7 19:41

不明觉厉 围观大神{:301_1001:}

453100956 发表于 2014-7-8 14:22

同上,大神

大爱如花和似玉 发表于 2014-7-10 02:40

眼花缭乱
页: [1]
查看完整版本: 【转】让人纠结的finally