(三)smali 入门

本文深入解析Smali代码的结构和语法,涵盖数据类型、对象、数组、方法、字段的表示形式,以及各种指令如寄存器、数据操作、返回、数据定义、方法调用、比较、跳转、字段操作、数据运算、数据转换、锁与异常、实例操作等。通过对比Java代码,提供详细的Smali代码示例。

Android代码在 Dalvik虚拟机 运行
 
baksmali 是安卓系统中Dalvik中所用的汇编器 和 反汇编器
 
dex文件 反汇编后得到 smali文件
 
smali代码拥有自己特定的格式语法
 
smali目前在Google code是一个开源项目,被广泛的用于广告注入、汉化、破解等方面

1. smali 数据类型
java 数据类型Type descriptor 字节码类型描述符
intI
floatF
doubleD
booleanZ
charC
byteB
shortS
longJ
voidV
2. smali 对象(引用类型的对象)
java 对象类型Type descriptor 字节码类型描述符
package.name.ObjectLpackage/name/Object;
 L表示 对象类型,这是一个对象
 xxx.yyy.zzz 变成 xxx/yyy/zzz 格式的目录层级结构
 ; 表示对象名称结束

String字符串本身是个对象

3. smali 数组
java 数据类型Type descriptor 字节码类型描述符
int [ ][ I
object [ ] [ ][ [ Ljava/lang/Object

二维数组:
在java中:
在这里插入图片描述
在smali中:

#声明了三个不同类型的二维数组
.field private double_int:[[I

.field private double_obj:[[Ljava/lang/Object;

.field private double_string:[[Ljava/lang/String;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4. smali 方法的表现形式

方法在smali中以.method 指令声明

# direct methods
.method <访问权限> [关键字] 方法名(参数)返回值类型
.locals 指定局部变量的个数
.param  方法的参数
.line  代码在原文件中的行号
.end  结尾

Lcom/example/hello/MainActivity;->methodName(IZD)Z

  • Lcom/example/hello/MainActivity;:用于声明具体的类型,以便JVM寻找
  • methodName:表示方法名称
  • IZD:连写的,没有分隔符,表示该方法需要三个参数,分别为int、boolean、double
  • Z:最后括号后的Z,表示这个函数的返回对象类型是boolean型,如果是V,表示返回值为空
  • (如果需要调用构造方法,则MethodName为:<init>)
5. smali 字段的表现形式
java 中字段的表现形式smali 中字段的表现形式
private objectType fieldName;.field fieldName:objectType
# instance fields   实例字段
.field <访问权限> [修饰关键字] <字段名> : 类型

# static field  静态字段
 .field <访问权限> static [修饰关键字] <字段名> : 类型

例如:字段的声明对比如下:
在java中:
在这里插入图片描述
smali中:
在这里插入图片描述

文件格式:
无论是 普通类、抽象类、接口类、内部类,反编译是,都会用单独的smali文件存放。
java中:
在这里插入图片描述
smali中:
在这里插入图片描述

.class <访问权限> [修饰关键字] <类名称>
.super <父类>
.source <源文件名称>

每个smali文件开头都是如下三行:

.class public Lcom/example/hello/MainActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "MainActivity.java"
  • 第一行:.class 指令 指定当前的类名称。此处是public
  • 第二行:.super 指令 指定当前类的父类
  • 第三行:.source 指令指定当前的源文件名称
6. smali 寄存器指令

在Dalvil虚拟机字节码中,寄存器有两种命名方式:

  • v 命名法(v0、v1、v2……)
  • p 命名法(p0、p1、p2……)
v 命名法p 命名法寄存器含义
v0v0第一个局部变量寄存器
v1v1第二个局部变量寄存器
…………中间的局部变量寄存器依次递增且名称相同
vM-Np0第一个参数寄存器
…………中间的参数寄存器分别依次递增
vM-1pN-1第N局部变量寄存器

比较v命名法与p命名法:

v 命名法p 命名法
以 .registers开头以 .limit开头
以 v2 作为this的引用以 p0 作为this的引用
以M-N命名N 个参数的寄存器用 p 命名法

v 指向寄存器
p 一般是某个java原有的代码有参数,或者指向this的或者直接变量

Dalvil参数传递规则:如果一个函数使用了M个寄存器、拥有N个参数,则参数使用最后N个寄存器,局部变量从v0开始一直递增到前M-N个(被传入的“隐藏”的对象引用this)

7. smali 指令
7.1 空指令:
指令助记符指令功能描述
nop代码对齐,无操作
7.2 数据操作指令(此类操作常用于赋值):

指令基本格式:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选)

指令助记符指令功能描述
move v0,v1将v1寄存器的数据赋值给v0寄存器,非对象类型
move-object v0,v1将v1寄存器的数据赋值给v0寄存器,对象类型
move-wide v0,v1将寄存器对v1中的值移入到v0寄存器对中
move-result v1将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用)
move-result-object v1将上条计算结果的对象指针移入v1寄存器(多用于函数的返回值)
move-result-wide v1将上条计算结果(双字)的对象指针移入v1寄存器
move-exception v1将异常移入v1寄存器,用于捕获try-catch语句中的异常
7.3 返回操作指令:
指令助记符指令功能描述
return-void返回void
return v1返回v1寄存器中的值
return-object v1返回v1寄存器中的对象指针
return-void返回void
7.4 数据定义指令:
  • const:数据定义 指令
  • /number:表示赋值到寄存器的数据的长度
  • vA:寄存器
  • #+B……:表示数据
指令助记符指令功能描述
const/4 vA,#+B将4位宽度的立即数 带符号 扩展到32位,赋值到vA寄存器
const/16 vA,#+BBBB将16位宽度的立即数 带符号 扩展到32位,赋值到vA寄存器
const vA,#+BBBBBBBB将32位宽度的立即数 赋值到vA寄存器
const wide/16 vA,#+BBBB将16位宽度的立即数 带符号 扩展64位,赋值到vA寄存器
const wide/32 vA,#+BBBB将32位宽度的立即数 带符号 扩展到64位,赋值到vA寄存器
const wide vA,#+BBBBBB将64位宽度的立即数 ,赋值到vA寄存器
const-string vA,string@aaaa将字符串常量的引用 赋值给vA
const/high16 vA+BBBB0000将16位宽度的立即数 右扩展扩展到32位,赋值给vA寄存器
const-class vA,type@AAA将一个类class 的引用赋值给vA寄存器

const/4和const/16的区别:

  • const/4 表示半个字节,也就是4位。-23~23-1 -8~7

  • const/16 表示两个字节,16位。-215~215-1 -32768~32767

局部变量定义:

.local v0,“a”:I:将v0的值赋值给 变量a

7.5 方法调用指令:

调用方法的基本格式:invoke-kind{vA,vB,vC,vD},meth@BBBB

  • invoke:调用方法 指令
  • -xxxx:调用的方法 的类型
  • vA~vD:为需要的参数,根据顺序一一对应
  • BBBB:代表方法引用
指令助记符指令功能描述
invoke-virtual调用实例的虚方法,通常成员对象实例的方法都有该指令调用(用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针)
invode-super调用父类的虚拟方法(用于调用父类中的方法,其他和invoke-virtual保持一致)
invoke-direct调用直接方法,通常私有方法都以该指令调用(用于调用private修饰的方法,或者构造方法)
invoke-static调用静态方法(比如一些工具类)
invoke-interface用于调用interface中的方法
invoke-kind / range {vB-vN},method@BBB参数列表是连续的寄存器列表
7.6 比较指令:

cmp:比较两个寄存器中值的大小,并将结果存储在目标寄存器中。
基本格式为:cmp 目标寄存器 vB vC

指令助记符指令功能描述
cmpl vA,vB,vC(less than)比较 vB,vC 较小值。如果vB=vC ,则vA=0 ;如果 vC较小 ,则vA存储正数。vB小 ,则vA存储负数
cmpg vA,vB,vC(greater than)比较 vB,vC 较大值。如果vB=vC ,则vA=0 ;如果 vC较大 ,则vA存储正数。vB大 ,则vA存储负数
指令助记符指令功能描述
cmpl-float vA,vB,vC比较浮点型和长整型的数据。
cmpg-float vA,vB,vC比较浮点型和长整型的数据。
cmpl-double vA,vB,vC比较double型的数据
cmpg-double vA,vB,vC比较double型的数据
cmpg-long vA,vB,vC比较long型的数据
7.7 跳转指令:
指令助记符指令功能描述
if-test vA,vB,+CCC条件跳转指令,比较两个寄存器vA和vB的值,然后进行条件跳转
if-eq v1,v2,:cond_0如果v1 = v2,则进入分支cond_0
if-ne v1,v2,:cond_0如果v1 != v2,则进入分支cond_0
if-gt v1,v2,:cond_0如果v1 > v2,则进入分支cond_0
if-ge v1,v2,:cond_0如果v1 >= v2,则进入分支cond_0
if-lt v1,v2,:cond_0如果v1 < v2,则进入分支cond_0
if-le v1,v2,:cond_0如果v1 <= v2,则进入分支cond_0
  
就与0比较:
if-testz vA,+cccc条件跳转指令,比较寄存器vA与0的值,满足后跳转
if-eqz v1,:cond_0如果寄存器v1的值 ==0,则跳转到cond_0
if-gtz v1,:cond_0如果寄存器v1的值 >0,则跳转到cond_0
if-gez v1,:cond_0如果寄存器v1的值 >=0,则跳转到cond_0
if-ltz v1,:cond_0如果寄存器v1的值 <0,则跳转到cond_0
if-lez v1,:cond_0如果寄存器v1的值 <=0,则跳转到cond_0
  
其他跳转:
goto +cccc无条件跳转到指定位置,偏移量为8位 宽度,且不为0
goto/16 +cccc无条件跳转到指定位置,偏移量为16位 宽度,且不为0
goto/32 +cccc无条件跳转到指定位置,偏移量为32位 宽度,且不为0
  
packed-switch vA,+bbbbbb根据+bbbbbb给定的跳转偏移列表进行匹配vA寄存器的值,跳转到指定指令。其中,偏移量的匹配值是 有规律递增的
sparse-switch vA,+bbbbbb根据+bbbbbb给定的跳转偏移列表进行匹配vA寄存器的值,跳转到指定指令。其中,偏移量的匹配值是 无规律的

作业1

在java中:

	# 实际中不这样写,需要用一个函数,这里只说逻辑
    private int age = 20;
    private String  identity;
if  (age>18){
        identity = "成年";
    }else{
        identity = "未成年";
    }

在smali中:

.field private age:I
.field private identity:Ljava/lang/String;
const/4 v0,0x14
const/4 v1,0x12

if-le v0,v1,:cond_0     #这里与原java代码是反着判断的
const-string v4,"成年"

cond_0:
const-string v3,"未成年"

7.8 字段操作指令:

基本格式:iinstance-op vA,vB,field @CCCC

  • vA寄存器存放读的结果或写的数据
  • vB字段的引用
  • field是字段值的引用
  • type是类型后缀,如char、byte、short、
指令助记符指令功能描述
普通字段操作:
iget取值,用于操作int这种的值类型
iget-wide取值,用于操作wide型字段
iget-object取值,用于操作对象引用
iget-boolean取值,用于操作布尔类型
iget-byte取值,用于操作字节类型
iget-char取值,用于操作字符类型
iget-short取值,用于操作short类型
iput赋值,用于操作int这种的值类型
iput-wide赋值,用于操作wide型字段
iput-object赋值,用于操作对象引用
iput-boolean赋值,用于操作布尔类型
iput-byte赋值,用于操作字节类型
iput-char赋值,用于操作字符类型
iput-short赋值,用于操作short类型

以上的 i 都可以换成 a 或者 s,分别用于操作 数组字段 或者 静态字段

作业2

在java中:

    private String name1 = "zhangsan";
    private String name2 = "lisi";
    private int age1 = 20;
    private Object names[]= {name1,name2,"wangwu"};

在smali中:

# instance fields
.field private name1:Ljava/lang/String;
.field private name2:Ljava/lang/String;
.field private age1:I
.field private names:[Ljava/lang/Object;

# direct methods
.method public constructor <init>()V
	.locals 9
	#调用了一个方法
	invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;-><init>()V
	
	#上面是声明了,这里需要为声明的对象赋值
	const-string v0, "zhangsan"
    iput-object v0, p0, Lcom/example/hello/Regist;->name1:Ljava/lang/String;
    const-string v1, "lisi"
    iput-object v1, p0, Lcom/example/hello/Regist;->name2:Ljava/lang/String;
	const/4 v2,0x14
	# 用来放数组长度
	const/4 v6,0x3
	
	# 声明一个新数组,寄存器位置为v5,长度为v6
	new-array v5, v6, [Ljava/lang/Object;
	
	const/4 v7,0x0
	aput-object v0, v5, v7    # 将v0的数据放入v5中,下标位置为v7
	
	const/4 v8,0x1
	aput-object v1, v5, v8    # 将v1的数据放入v5中,下标位置为v8
	
	const/4 v9,0x2
	const-string v4,"wangwu"
	aput-object v4, v5, v9    # 将v4的数据放入v5中,下标位置为v9
	# 将v5中的数据放入对象names中
	 iput-object v5, p0, Lcom/example/hello/MainActivity;->names:[Ljava/lang/Object;
	 return-void
	 .end method

作业3

循环:

private int a=2;
private int aa;
public void test(){
for(int i=0;i<4;i++){}
aa=a;
}

.class public Lcom/example/hello/Regist;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "Regist.java"

# instance fields
.field private a:I
.field private aa:I

# direct methods
.method public constructor <init>()V
    invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;-><init>()V
    
    const/16 v0, 0x2
    iput v0, p0, Lcom/example/hello/Regist;->a:I
 .end method
 
 # virtual methods
.method public test()V
	#准备一个0
	 const/4 v1, 0x0
	 #准备一个i
	.local v2, "i":I
	#开始循环
	 :goto_0    
	 # 准备一个4
	const/4 v1, 0x4
	#判断i>=4
	if-ge v2, v1, :cond_0
	#这里中间是循环体中的内容
	# i自增
	add-int/lit8 v2, v2, 0x1
	#再跳转到循环
	 goto :goto_0
	 .end local v2    # "i":I
	 #判断不成功,跳出循环
    :cond_0
    iget v0, p0, Lcom/example/hello/LoginSuccess;->a:I   # 取出a的值

    iput v0, p0, Lcom/example/hello/LoginSuccess;->aa:I   # 赋值给aa
    return-void
.end method
7.9 数据运算指令:

基本格式:bin-op vA,vB,vC(对寄存器vB和vC进行binop算数运算,结果复制到vA寄存器)

指令助记符指令功能描述
算数运算:
add-type加法(type有:int、long、float、double)
sub-type减法
mul-type乘法
div-type除法
rem-type取模
逻辑运算与位运算:
and-type2逻辑与(type2只有:int、long)
or-type2逻辑或
xor-type2逻辑非
shl-type2有符号数左移
shr-type2有符号数右移
ushr-type2无符号数右移
 
binop/lit8 vA,vB,#+cccc将寄存器vB的值与常量cccc进行算数运算,结果赋值给vA
binop/lit16 vA,vB,#+cccc
binop/2addr vA,vB将vA寄存器与vB寄存器的值进行binop运算,值赋值给vA
7.10 数据转换指令
指令助记符指令功能描述
unop vA,vB数据类型转换
neg-type vx,vy计算 vx =- vy(type-后可以接类型int、long、float、double)
not-type vx,vy
type1-to-type2转换类型
int-to-type
int-to-char
int-to-short
例如:
double-to-int v0,v1
7.11 锁与异常操作指令
指令助记符指令功能描述
锁:(多用在多线程程序中,对同一对象操作)
monitor-enter vA为指定的对象获取锁
monitor-exit vA释放指定对象的锁
异常:
throw抛出一个指定类型的异常
7.12 实例操作指令
指令助记符指令功能描述
new-instance vA,type@BB构造一个指定类型的实例,并把引用值给寄存器
instance-of vA,vB,type@CC判断vB寄存器的引用对象是否可以转为指定类型,是vA就复制1,否vA复制0
check-cast vA,type@BB把寄存器引用对象转为指定类型,不行就抛出异常

**指令学习英文文档**:http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值