smali语法中的疑惑与易混淆点

2,098 阅读2分钟

最近在完成移动智能终端安全的课程实验时研究了一下smali的语法,顺便记录一下几个比较具有迷惑性的地方 比如以下两行代码:

invoke-virtual {p0, v0}, Lcom/example/smali/MainActivity;->setContentView(I)V
iput-object v0, p0, Lcom/example/smali/MainActivity;->tv:Landroid/widget/TextView;

在这两行代码中,寄存器v0和p0究竟是拿来干什么的?

我的研究方法是:先写一段java源代码,把它反编译成smali文件,研究两者对应的关系,如果有发现结果,还可以适当的更改源代码以验证自己的结论。这样的优点是结论是自己发现的,印象会更深刻;缺点是频繁的反编译比较繁琐,要是在AS上有即时的反编译插件就好了。 话不多说,源代码如下,一个按钮注册点击事件,点击后改变TextView内容的demo:

public class MainActivity extends AppCompatActivity {
    TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv = findViewById(R.id.tv);
        Button btn = findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tv.setText("Hello Xidian!");
            }
        });
    }

对应的smali代码如下:

.class public Lcom/example/smali/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"

.field tv:Landroid/widget/TextView;

.method public constructor <init>()V    #MainActivity的构造器
    .locals 0  
    invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V
    return-void
.end method

.method protected onCreate(Landroid/os/Bundle;)V    #重写的onCreate方法
    .locals 2   
    .param p1, "savedInstanceState"   
    invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
    const v0, 0x7f09001c
    invoke-virtual {p0, v0}, Lcom/example/smali/MainActivity;->setContentView(I)V      #setContentView()
    
    const v0, 0x7f07008e
    invoke-virtual {p0, v0}, Lcom/example/smali/MainActivity;->findViewById(I)Landroid/view/View;
    move-result-object v0
    check-cast v0, Landroid/widget/TextView;    #强制类型转换
    iput-object v0, p0, Lcom/example/smali/MainActivity;->tv:Landroid/widget/TextView;    #tv的findViewById
    
    const v0, 0x7f070022
    invoke-virtual {p0, v0}, Lcom/example/smali/MainActivity;->findViewById(I)Landroid/view/View;
    move-result-object v0
    check-cast v0, Landroid/widget/Button;    #btn的findViewById
    
    .local v0, "btn":Landroid/widget/Button;
    new-instance v1, Lcom/example/smali/MainActivity$1;     #new了一个OnClickListener,它是一个匿名内部类
    invoke-direct {v1, p0}, Lcom/example/smali/MainActivity$1;-><init>(Lcom/example/smali/MainActivity;)V
    invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
    
    return-void
.end method

比对后得出的几个结论就直接写了:

1.类和方法中的变量

  • 方法内的变量寄存器有两种
    • 寄存参数使用p寄存器,形如p0,p1,用.param定义,且从p1开始计数,因为p0为非静态方法自动创建的寄存器,存储着this,也就是该方法所属类的实例本身
    • 寄存方法中的内部变量使用v寄存器,形如v0,v1,用.locals在方法开头表示所需的内部变量数,在声明变量时用.local ...声明。值得一提的是方法中的内部类也属于方法的内部变量,从上述例子中可以看出这种情况下使用new instance ...声明而非.local ...
  • 至于类中的域则用.field表示

2.方法调用指令的区别与后置寄存器的作用

  • invoke指令最常用的为invoke-virtualinvoke-directinvoke-staticinvoke-super
    • invoke-virtual表示android原生api,即不是你写的方法
    • invoke-direct表示因你而产生的方法,包含你定义的类的构造器
    • invoke-superinvoke-static比上述两者具有更高优先级,当属于静态或父类方法时,优先使用invoke-staticinvoke-static而非 invoke-staticinvoke-static
  • invoke-virtual {v0, p0~pn}...中,v0存有该方法所属的类,p0至pn表示该方法需要的传参,...表示v0所存类与该方法的联系

3.变量操作指令的区别与后置寄存器的作用

  • s开头的指令表示对静态变量的指令符,普通的指令符则用i开头
  • 如果iget-object v0, p0,...的源代码为a = b,则v0存有b,p0存有a所在的类的实例,...为p0到a的联系

4.内部类的特殊格式

  • 如果B为A的内部类,则B在Smali中的表示为一个单独的类文件,并且

    • 在A中B的命名方式为A$B,如果B为匿名内部类,比如OnClickListener,则B的命名方式为A$1
    • 在B中会有一个类型为A的域,该域的命名方式为.field final synthetic this$0,并且在B的构造器中需传入一个A的实例,JVM用这种方法保证内部类对外部类的持有
  • 在B中调用A的外部方法,调用指令统一采用invoke-static,方法名会采用acces$000acces$100......之类的命名形式,并且方法内的参数里会默认添加一个外部类