smali和java基本数据类型对比
smali | java |
---|---|
B | byte |
S | short |
I | int |
J | long |
F | float |
D | double |
C | char |
Z | boolean |
V | void |
[ | 数组 |
L+全类名路径用/分割 | object |
注释
在smali语言中注释使用#
表示
1 | # 我是注释 |
类声明
1 | .class +权限修饰符 +类名; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
关于分号;
凡是L开头全包名路径结尾都需要加分号
字段声明(成员/全局变量)
1 | .field 权限修饰符+静态修饰符 +变量名:变量全类名路径; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
补充:
1 | 基本数据类型示例: |
常量声明
1 | .field 权限修饰符+静态修饰符 +final+变量名:变量全类名路径;=常量值 |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
成员方法/函数声明
1 | .method 权限修饰符+静态修饰符 +方法名(参数类型)返回值类型 |
比如以下java代码:
1 |
|
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
如果是带参并且带有返回值的方法
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .method public getName(Ljava/lang/String;)Ljava/lang/String; |
关于方法返回关键字
主要有以下四种
1 | return-void |
数据类型对应关系表如下:
smali方法返回关键字 | java |
---|---|
return | byte |
return | short |
return | int |
return-wide | long |
return | float |
return-wide | double |
return | char |
return | boolean |
return-void | void |
return-object | 数组 |
return-object | object |
构造方法/构造函数声明
1 | .method 权限修饰符 +constructor <init>(参数类型)返回值类型 |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
静态代码块的声明
1 | .method static +constructor <clinit>()V |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
方法调用
关键字
1 | invoke-virtual //用于非私有实例方法的调用 |
非私有实例方法的调用
1 | invoke-virtual {参数}, 方法所属类名;->方法名(参数类型)返回值类型; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
私有方法或者构造方法的调用
1 | invoke-direct {参数}, 方法所属类名;->方法名(参数类型)返回值类型; |
私有方法调用:
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
构造方法调用:
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
静态方法的调用并获取返回值(不区分私有公有 静态优先)
1 | invoke-static {参数}, 方法所属类名;->方法名(参数类型)返回值类型; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
父类成员的方法调用
1 | invoke-super |
比如以下java代码
1 | @Override |
用smali代码表示为
1 | .method protected onCreate(Landroid/os/Bundle;)V |
接口的调用
1 | invoke-interface {参数}, 方法所属类名;->方法名(参数类型)返回值类型; |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
创建对象
对象的创建分多步进行:
1 | # 声明实例 |
数组的创建
const/4 v0, 0x4
new-array v0, v0, [I
fill-array-data v0, :array_a
:array_a
.array-data 4 # 表示占用四个字节
0x0
0x1
0x2
0x3
.end array-data
数据的定义
分三大类
1 | 字符串类型数据 |
数值类型数据拆分
1 | 第一种 const开头 占用一个容器(寄存器) 32位/容器 |
总结
1 | const-string v0 , "hello"# 定义字符串 将字符串hello赋值给v0 |
数据取值范围算法
1 | 1000 → -8; |
算法:正数的符号位是0,负数的符号位是1。正数的反码、补码与原码一样。负数的反码是让符号位不变,数据位按位取反;补码是将反码加1。
静态字段赋值
分多步进行 关键代码:
1 | sput-object # s代指static |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
类非静态字段赋值
分多步进行 关键代码:
1 | iput-object # i代表instance |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
静态字段取值
关键代码
1 | sget-object # s代指static |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
类非静态字段取值
关键代码:
1 | iget-object # i代表instance |
比如以下java代码:
1 | public class Test |
用smali代码表示为:
1 | .class public LTest;#声明类 (必须) |
注意:以上取值赋值方法都是以String对象举例,如果是基本数据类型,那么按照如下表处理:
值定义
1 | const/4 v0, 0x1 # 实例变量值内容定义 值皆为十六进制 |
取值:
1 | iget #实例变量int型取值 |
赋值
1 | iput #实例变量int型赋值 |
下表以实例变量举例:
smali取值赋值和值定义关键字 | java |
---|---|
iget-byte iput-byte const/4 |
byte |
iget-short iput-short const/4 |
short |
iget iput const/4 |
int |
iget-wide iput-wide const-wide/16 |
long |
iget- iput const/high16 |
float |
iget-wide- iput-wide const/high16 |
double |
iget-char- iput-char const/16 |
char |
iget-boolean- iput-boolean const/4 |
boolean |
#### 如果是基本数据类型,那么按照如下表处理: |
smali取值赋值和值定义关键字 | java |
---|---|
iget-object- iput-object new-array v0, v0, [数据类型签名 fill-array-data v0, :array_c |
数组 |
iget-object- iput-object 以下两步为类对象定义 new-instance v0, 全包名类路径; invoke-direct #调用构造方法 |
类和接口 |
iget-object- iput-object sget-object |
枚举 |
iget-object- iput-object const-string |
String |
以上表结果示例java代码如下,可自行试验:
1 | public class Test |
$$
$$
1 | public class Test |
逻辑语句之条件跳转分支
1 | "if-eq vA, vB, :cond_**" 如果vA等于vB则跳转到:cond_** #equal |
逻辑语句之循环
比如以下java代码
1 | public class Test { |
对应的smali代码为:
1 | .method public static main([Ljava/lang/String;)V |
如果将int改成long, 结果又不一样,这里使用到了比较运算符cmp(comparable)
1 | .method public static main([Ljava/lang/String;)V |
smali语法关键字
.line
表示与java源文件代码的映射关系,比如:
1 | .line 3 # 代表以下代码还原成java代码在源文件第三行 |
删除该关键字不影响程序执行,该关键字在反编译时能很好地帮助我们阅读smali代码,以该关键字当作代码块的分割线,方便快速阅读执行内容
:cond_0
条件分支,配合if使用
.prologue
表示程序的开始 可省略
:goto_0
goto跳转分支,配合goto关键字使用
.local
显示局部变量别名信息,作用等同.line
1 | move-result-object v0 # 调用方法后结果储存在v0中 |
.locals N
注意这个和上面local的区别多加了一个s
标明了你在这个函数中最少要用到的本地寄存器的个数 也即是指明了在这个方法中非参(non-parameter)寄存器的数量
locals和registers具体区别参见:点击跳转
.registers N
在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double
声明可使用的寄存器数量的方式为:.registers N
,N代表需要的寄存器的总个数
示例:
1 | .method private test(I)V |
那么,如何确定需要使用的寄存器的个数?
由于非static方法,需要占用一个寄存器以保存this指针,那么这类方法的寄存器个数,最低就为1,如果还需要处理传入的参数,则需要再次叠加,此时还需要考虑Double和Float这种需要占用两个寄存器的参数类型,举例来看:
如果一个Java方法声明如下:
1 | myMethod(int p1, float p2, boolean p3)1 |
那么对应的Smali则为:
1 | method LMyObject;->myMethod(IJZ)V1 |
此时,寄存器的对应情况如下:
寄存器名称 | 对应的引用 |
---|---|
p0 | this |
p1 | int型的p1参数 |
p2, p3 | float型的p2参数 |
p4 | boolean型的p3参数 |
那么最少需要的寄存器个数则为:5
如果方法体内含有常量、变量等定义,则需要根据情况增加寄存器个数,数量只要满足需求,保证需要获取的值不被后面的赋值冲掉即可,方法有:存入类中的字段中(存入后,寄存器可被重新赋值),或者长期占用一个寄存器
寄存器数量只能多不能少
Dalvik指令集
如果需要使用Smali编写程序,还需要掌握常用的Dalvik虚拟机指令,其合集称为Dalvik指令集。这些指令有点类似x86汇编的指令,但指令更多,使用也非常简单方便。最详尽的介绍,可以参考Android官方的Dalvik相关文档:
https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions
一般的指令格式为:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选)
,比如:move v1,v2
,move-wide/from16 v1,v2
这里也列举一些常用的指令,并结合Smali进行说明:
- 移位操作:
此类操作常用于赋值
指令 | 说明 |
---|---|
move v1,v2 | 将v2中的值移入到v1寄存器中(4位,支持int型) |
move/from16 v1,v2 | 将16位的v2寄存器中的值移入到8位的v1寄存器中 |
move/16 v1,v2 | 将16位的v2寄存器中的值移入到16位的v1寄存器中 |
move-wide v1,v2 | 将寄存器对(一组,用于支持双字型)v2中的值移入到v1寄存器对中(4位,猜测支持float、double型) |
move-wide/from16 v1,v2 | 将16位的v2寄存器对(一组)中的值移入到8位的v1寄存器中 |
move-wide/16 v1,v2 | 将16位的v2寄存器对(一组)中的值移入到16位的v1寄存器中 |
move-object v1,v2 | 将v2中的对象指针移入到v1寄存器中 |
move-object/from16 v1,v2 | 将16位的v2寄存器中的对象指针移入到v1(8位)寄存器中 |
move-object/16 v1,v2 | 将16位的v2寄存器中的对象指针移入到v1(16位)寄存器中 |
move-result v1 | 将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用) |
move-result-object v1 | 将上条计算结果的对象指针移入v1寄存器 |
move-result-wide v1 | 将上条计算结果(双字)的对象指针移入v1寄存器 |
move-exception v1 | 将异常移入v1寄存器,用于捕获try-catch语句中的异常 |
- 返回操作:
用于返回值,对应Java中的return语句
指令 | 说明 |
---|---|
return-void | 返回void,即直接返回 |
return v1 | 返回v1寄存器中的值 |
return-object v1 | 返回v1寄存器中的对象指针 |
return-wide v1 | 返回双字型结果给v1寄存器 |
另外
android studio自带的.class转smali就非常好用,如图:
从错误总学习
1 | SLog.smali[24,4] Invalid register: v-1. Must be between v0 and v15, inclusive. |
寄存器命名从v0-v15 一共15个
1 | SLog.smali[17,0] A .registers or .locals directive must be present for a non-abstract/non-final method |
.registers或者.locals必须存在, 除非是抽象方法或者final方法
1 | java.lang.VerifyError: Rejecting class com.pangshu.SLog because it failed compile-time verification (declaration of 'com.pangshu.SLog' appears in /sdcard/ex.dex) |
这种错误一般很难定位,因为没有提示具体原因或者具体的行数,有可能是静态方法调用你写成了虚方法的调用,或者是构造函数调用没有加尖括号, 甚至是寄存器数量过少 等等
思考
为什么方法中包括参数在内需要3个寄存器,但是在定义的时候只写了两个却也不报错呢?
如:
1 | .method public static print(Ljava/lang/String;)V |
答案是:系统会更具最大寄存器的位置进行判断,从v0到vN,数量必须大于N,
本文为作者原创 转载时请注明出处 谢谢
乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站