内容纲要

dalvik字节码与smali

dalvik字节码

dalvik

Dalvik中用的寄存器都是32位,64位类型数据则用两个相邻的32位寄存器表示,也就是对于double这种64位类型的数据,需要用到两个32位寄存器来存储。

Dalvik最多支持65536个寄存器(编号从0~65535),但是在ARM架构的cpu中只存在37个寄存器,

Dalvik中的寄存器是虚拟寄存器, 通过映射真实的寄存器来实现.我们知道每个Dalvik维护了一个调用栈,该调用栈就是用来支持虚拟寄存器和真实寄存器相互映射的.在执行具体函数时,Dalvik会根据.registers指令来确定该函数要用到的寄存器数目.

寄存器使用规则

对于一个使用m个寄存器(m=局部变量寄存器个数l+参数寄存器个数n)的方法而言,局部寄存器使用从v0开始的l个寄存器,而参数寄存器则使用最后的n个寄存器

这里写图片描述

寄存器命名

寄存器有两种不同的命名方法:v字命名法和p字命名法,这两种命名法仅仅是影响了字节码的可读性。

image-20220725155730098

字节码和数据类型

Davilk字节码只有两种类型:基本类型和引用类型

对象和数组都是引用类型,Davilk中对字节码类型的描述和JVM中的描述符规则一致:

对于基本类型和无返回值的void类型都是用一个大写字母表示,对象类型则用字母L加对象的全限定名来表示.数组则用[来表示

java类型 类型描述符
boolean Z
byte B
short S
char C
int I
long J
float F
double D
void V
对象类型 L
数组类型 [
全限定名

以String为例,其完整名称是java.lang.String,那么其全限定名就是java/lang/String;

对象类型

L可以表示java类型中的任何类。在java代码中以package.name.ObjectName的方式引用,而在Davilk中其描述则是以Lpackage/name/ObjectName;的形式表示。L即上面定义的java类类型,表示后面跟着的是累的全限定名。

数组类型

[类型用来表示所有基本类型的数组,[后跟着是基本类型的描述符。每一维度使用一个前置的[。比如java中的int[] 用汇编码表示便是[I;,二维数组int[][]为[[I;,三维数组则用[[[I;表示。

字段

Davilk中对字段的描述分为两种,对基本类型字段的描述和对引用类型的描述,但两者的描述格式一样:
对象类型描述符->字段名:类型描述符;
比如com.sbbic.Test类中存在String类型的name字段及int类型的age字段,那么其描述为:

Lcom/sbbic/Test;->name:Ljava/lang/String;
Lcom/sbbic/test;->age:I
方法

java中方法的签名包括方法名,参数及返回值,在Davilk相应的描述规则为:
对象类型描述符->方法名(参数类型描述符)返回值类型描述符

java方法:public char charAt(int index){...}
Davilk描述:Ljava/lang/String;->charAt(I)C

java方法:public void getChars(int srcBegin,int srcEnd,char dst[],int dstBegin){...}
Davilk描述:Ljava/lang/String;->getChars(II[CI)V

java方法:public boolean equals(Object anObject){...}
Davilk描述:Ljava/lang/String;->equals(Ljava/lang/Object)Z

指令集

中文指令整理

官方文档指令集解说

smali

尽管我们使用java来写Android应用,但是Dalvik并不直接加载.class文件,而是通过dx工具将.class文件优化成.dex文件,然后交由Dalvik加载.这样说来,我们无法通过分析.class来直接分析apk文件,而是需要借助工具baksmali.jar反编译dex文件来获得对应smali文件,smali文件可以认为是Davilk的字节码文件,但是并两者并不完全等同.

通过baksmali.jar反编译出来每个.smali,都对应与java中的一个类,每个smali文件都是Davilk指令组成的,并遵循一定的结构.smali存在很多的指令用于描述对应的java文件,所有的指令都以”.”开头,常用的指令如下:
image-20220725165245973

文件头描述

image-20220725165345561

文件正文

image-20220725165411715

注解描述

image-20220725165428480

字段描述

image-20220725165446821

方法描述

直接方法

image-20220725165849726

虚方法

image-20220725165858559

案例

public class MainActivity extends Activity implements View.OnClickListener {

    private String TAG = "MainActivity";
    private static final float pi = (float) 3.14;

    public volatile boolean running = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onClick(View view) {
        int result = add(4, 5);
        System.out.println(result);

        result = sub(9, 3);

        if (result > 4) {
            log(result);
        }
    }

    public int add(int x, int y) {
        return x + y;
    }

    public synchronized int sub(int x, int y) {
        return x + y;
    }

    public static void log(int result) {
        Log.d("MainActivity", "the result:" + result);
    }

}
#文件头描述
.class public Lcom/social_touch/demo/MainActivity;
.super Landroid/app/Activity;#指定MainActivity的父类
.source "MainActivity.java"#源文件名称

#表明实现了View.OnClickListener接口
# interfaces
.implements Landroid/view/View$OnClickListener;

#定义float静态字段pi
# static fields
.field private static final pi:F = 3.14f

#定义了String类型字段TAG
# instance fields
.field private TAG:Ljava/lang/String;

#定义了boolean类型的字段running
.field public volatile running:Z

#构造方法,如果你还纳闷这个方法是怎么出来的化,就去看看jvm的基础知识吧
# direct methods
.method public constructor <init>()V
    .locals 1#表示函数中使用了一个局部变量

    .prologue#表示方法中代码正式开始
    .line 8#表示对应与java源文件的低8行
    #调用Activity中的init()方法
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    .line 10
    const-string v0, "MainActivity"

    iput-object v0, p0, Lcom/social_touch/demo/MainActivity;->TAG:Ljava/lang/String;

    .line 13
    const/4 v0, 0x0

    iput-boolean v0, p0, Lcom/social_touch/demo/MainActivity;->running:Z

    return-void
.end method

#静态方法log()
.method public static log(I)V
    .locals 3
    .parameter "result"#表示result参数

    .prologue
    .line 42
    #v0寄存器中赋值为"MainActivity"
    const-string v0, "MainActivity"
    #创建StringBuilder对象,并将其引用赋值给v1寄存器
    new-instance v1, Ljava/lang/StringBuilder;

    #调用StringBuilder中的构造方法
    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V

    #v2寄存器中赋值为ther result:
    const-string v2, "the result:"

    #{v1,v2}大括号中v1寄存器中存储的是StringBuilder对象的引用.
    #调用StringBuilder中的append(String str)方法,v2寄存器则是参数寄存器.
    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    #获取上一个方法的执行结果,此时v1中存储的是append()方法执行后的结果,此处之所以仍然返回v1的    #原因在与append()方法返回的就是自身的引用
    move-result-object v1

    #继续调用append方法(),p0表示第一个参数寄存器,即上面提到的result参数
    invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

    #同上
    move-result-object v1

    #调用StringBuilder对象的toString()方法
    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    #获取上一个方法执行结果,toString()方法返回了一个新的String对象,因此v1中此时存储了String对象的引用
    move-result-object v1

    #调用Log类中的静态方法e().因为e()是静态方法,因此{v0,v1}中的成了参数寄存器
    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    .line 43
    #调用返回指令,此处没有返回任何值
    return-void
.end method

# virtual methods
.method public add(II)I
    .locals 1
    .parameter "x"#第一个参数
    .parameter "y"#第二个参数

    .prologue
    .line 34

    #调用add-int指令求和之后将结果赋值给v0寄存器
    add-int v0, p1, p2

    #返回v0寄存器中的值
    return v0
.end method

.method public onClick(Landroid/view/View;)V
    .locals 4
    .parameter "view" #参数view

    .prologue
    const/4 v3, 0x4 #v3寄存器中赋值为4

    .line 23#java源文件中的第23行
    const/4 v1, 0x5#v1寄存器中赋值为5

    #调用add()方法
    invoke-virtual {p0, v3, v1}, Lcom/social_touch/demo/MainActivity;->add(II)I

    #从v0寄存器中获取add方法的执行结果
    move-result v0

    .line 24#java源文件中的24行
    .local v0, result:I

    #v1寄存器中赋值为PrintStream对象的引用out
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    #执行out对象的println()方法
    invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V

    .line 26

    const/16 v1, 0x9#v1寄存器中赋值为9
    const/4 v2, 0x3#v2寄存器中赋值为3

    #调用sub()方法,{p0,v1,v2},p0指的是this,即当前对象,v1,v2则是参数
    invoke-virtual {p0, v1, v2}, Lcom/social_touch/demo/MainActivity;->sub(II)I
    #从v0寄存器中获取sub()方法的执行结果
    move-result v0

    .line 28
    if-le v0, v3, :cond_0#如果v0寄存器的值小于v3寄存器中的值,则跳转到cond_0处继续执行

    .line 29

    #调用静态方法log()
    invoke-static {v0}, Lcom/social_touch/demo/MainActivity;->log(I)V

    .line 31
    :cond_0
    return-void
.end method

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 1
    .parameter "savedInstanceState" #参数savedInstancestate

    .prologue
    .line 17

    #调用父类方法onCreate()
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    .line 18

    const v0, 0x7f04001a#v0寄存器赋值为0x7f04001a

    #调用方法setContentView()
    invoke-virtual {p0, v0}, Lcom/social_touch/demo/MainActivity;->setContentView(I)V

    .line 19
    return-void
.end method

#declared-synchronized表示该方法是同步方法
.method public declared-synchronized sub(II)I
    .locals 1
    .parameter "x"
    .parameter "y"

    .prologue
    .line 38

    monitor-enter p0#为该方法添加锁对象p0
     add-int v0, p1, p2
    #释放锁对象
    monitor-exit p0

    return v0
.end method

参考

https://blog.csdn.net/dd864140130/article/details/52076515