Dalvik,smali学习笔记
基础了解
说到smali(对Dalvik虚拟机字节码的一种解释),首先应该提一下Google专门为Android平台涉及的用于运行Android程序的虚拟机–Dalvik虚拟机
Dalvik与Jvm主要在字节码是有差别。
在程序运行时,Jvm会频繁对栈进行操作,而Dalvik则是对寄存器
用一组各自的字节码做对比
这是原程序(通过异或交换两个数的值)
1 | public class math{ |
Jvm下的字节码
1 | javap -c -classpath . math.class |
重点看main函数的代码:
可以看到,Jvm的字节码明显要比Dalvik的字节码复杂iconst_5表示申明一个整型常量5,istore_0表示栈顶int数值存入第1局部变量,等…
Dalvik下的字节码(此图不准确,请看下面的smali)
(该工具通常在Android SDK的build-tools目录下,使用的时候需要注意jdk版本问题,写这文章的时候用的jdk18,不能成功执行下面的命令,换成jdk8或者其他版本就行)
1 | dx --dex --output=math.dex math.class |
smali一般的指令格式为: [op]-[type] (可选) / [位宽,默认4位] [目标寄存器], [源寄存器] (可选)
诸多命令可以到这篇文章查找
(41条消息) 【Android安全】Dalvik字节码含义查询表_Walter_Jia的博客-CSDN博客
也或者是官方文档
可以看到Dalvik指令后面,都是用寄存器保存返回值,对寄存器进行频繁操作,而Jvm是对栈进行频繁操作。
实现相同的功能其,相比于Jvm,Dalvik所用到的指令更少,自然也更简洁。
我们将刚得到的dex文件用baksmali编译为smali文件
1 | java -jar baksmali.jar d math.dex |
只需要把baksmali和smali的jar包放在SDK的tools目录下,然后切换到该目录下执行命令就可以编译出smali文件了(默认在执行后生成的output文件夹里)。(注意以上过程都需要在同一个jdk版本的前提下进行,否则会出现一些错误)
记事本打开生成的smali文件(这里我是用IDEA的插件(java2smali)直接生成的,跟上面那张图不一样,上面是用的另一台电脑的java8编译出来的,下面这个是java18的结果)
1 | .class public Lmath; |
上面 Ljava/lang/System;->out:Ljava/io/PrintStream 表示Java中的System类的out字段的PrintStream字段类型
这里因为我baksmali和smali版本(都是2.5.2)的原因,指令会跟老版本不一样,老版本可能是这样的
再贴一张Dalvik的两种寄存器命名法
对于有M个寄存器,N个参数的函数来说
在P命名法中p0是参数寄存器,v0是变量寄存器
在V命名法中,局部变量寄存器是v0–vn 参数寄存器则为vn–v(n+m)
(在Dalvik汇编代码较长的,且使用寄存器较多的时候,P命名法可以更清楚的阅读代码。)
对于math的代码来说,参数寄存器就是用来存放参数的,即存储println引用的字段,即被交换值过后的a (=3)。v0则是用来存放变量值3,5。
Smali简单编写尝试
首先说下,有些指令助记符后添加了jumbo后缀,这是在 Android 4.0 开始的扩展指令,增加了寄存器和指令索引的取值范围。另外,除去加了jumbo后缀的扩展指令,每个指令的字节码只占1字节范围是0x0~0xff。
1 | .class public Lmath; |
我们用上面将smali转化为dex的命令,将其转化为math2.dex,再用d2jdex将dex转化为jar包
1 | d2j-dex2jar math2.dex |
然后用jd-gui打开生成的jar包,查看代码
可以看到虽然长相不同,但其实现的功能与原来的math还是差不多的。(雾)