#声明实例
new-instance + 变量名, 对象全包名路径;
#调用构造方法(如果构造方法内还定义了成员变量,那么在调用之前需要提前声明,然后在invoke的时候当作参数一并传入)
invoke-direct {变量名}, 对象全包名路径;->
(参数)返回类型
class Test{}
new Test(); // 实例化对象
用smali代码表示为:
.class LTest;
.super Ljava/lang/Object;
new-instance v0, LTest;
invoke-direct {v0}, LTest;->()V
记住:先声明,后调用构造方法
- 字符串数据
- 字节码数据
- 数值类型数据
以下代码在Java中表示声明一个变量并返回:
public class Test{
public String getHello(){
String s = "hello";
return s;
// 或者表示为 return "hello";
}
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.method public getHello()Ljava/lang/String;
.registers 2 # 表示寄存器,后面会详细介绍
const-string v0, "hello" # 定义一个字符串
return-object v0
.end method
在smali中,想要返回一个数据,必须先声明
比如以下Java代码:
public class LTestActivity{
public void jump(){
this.startActivity(new Intent(this, GoActivity.class))
// Intent类的全包名路径为:android/content/Intent
// LTestActivity类继承android/app/Activity
}
}
用smali代码表示为:
.class public LTestActivity;
.super Landroid/app/Activity;
.method public jump()V
.registers 3
# 将Java由内到外拆解编写
# 第一步 声明class对象以及当前Activity this对象
const-class v0, LGoActivity;
# 第二步 创建Intent对象
new-instance v1, Landroid/content/Intent;
# 构造方法调用
invoke-direct {v1, p0, v0}, Landroid/content/Intent;->(Landroid/app/Activity;Ljava/lang/Class;)
# 第三步 调用startActivity方法
invoke-virtual {p0, v1}, LTestActivity;->startActivity(Landroid/content/Intent;)V
.end method
第一种 const开头 占用一个容器(寄存器)
const v0, 30
- const/4 最大只允许存放4位数值(4个二进制位)
- const/16 最大只允许存放16位数值 第一位默认为符号位 所以计算后15位的值
- const 最大32位
- const/high16 v0, 0xFF7f0000 一位16进制表示二进制4位,取前4位16进制
第二种 const-wide 占用两个容器 64位
const-wide v0, 30 占用v0和v1
const-string v0, "hello" # 定义字符串 将字符串"hello"赋值给v0
const-class v0, LGoActivity; # 定义字节码对象 将GoActivity.class对象赋值给v0
# 一下数据定义高位默认为符号位
const/4 v0, 0x2 # 定义一个容器 最大只允许存放半字节4位数据 取值范围为 -8 and 7
const/16 v0, 0xABCD # 定义一个容器 最大只允许存放16位数据 比如short类型数据 取值范围为-32768~32767
const v0, 0xA # 定义一个容器 最大只允许存放32位数据 比如int类型数据 将数字10赋值给v0 取值范围-2147483647~2147483647
const/high16 # 定义一个容器 最大只允许存放高16位数值 比如0xFFFF0000末四位补0 存入高四位0xFFFF
# const-wide 占用两个寄存器vx和vx+1 数值必须以L结尾 否则编译不通过
const-wide/16 # 定义两个相连容器 最大只允许存放16位数据
const-wide/31 # 定义两个相连容器 最大只允许存放32位数据
const-wide # 定义两个相连容器 最大只允许存放64位数据
const-wide/high16 # 定义两个相连容器 只允许存放高16位数据
# 负数的二进制要按位取反再加1得到真正地结果
1000 -> -8;
1001 -> -7;
1010 -> -6;
1011 -> -5;
1100 -> -4;
1101 -> -3;
1110 -> -2;
1111 -> -1;
0000 -> 0;
0001 -> 1;
0010 -> 2;
0011 -> 3;
0100 -> 4;
0101 -> 5;
0110 -> 6;
0111 -> 7;
算法:正数的符号位是0, 复数的符号位是1。正数的反码、补码与原码一样。负数的反码是让符号位不变,数据按位取反;补码是将反码加1。
分多步进行 关键代码:
sput-object # s代指static
比如以下Java代码:
public class Test{
private static String a = "hello";
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.source "Test.java"
.field private static a:Ljava/lang/String;
# 类初始化方法 被jvm执行 优先于构造方法
.method static constructor ()V
const-string v0, "hello" # 定义常量值
sput-object v0, LTest;->a:Ljava/lang/String; # 常量赋值
return-void
.end method
iput-object # i代表instance
比如以下Java代码:
public class Test{
private String a = "g";
public Test(String a){
}
public void setAa(){
a = "b";
}
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.field private a:Ljava/lang/String;
.method public constructor (Ljava/lang/String;)V
.registers 3
# 初始化父类构造方法
invoke-direct {p0}, Ljava/lang/Object;->()V
# 声明字符串内容
const-string v0, "g"
# 赋值
iput-object v0, p0, LTest;->a:Ljava/lang/String;
return-void
.end method
.method public setAa()V
.registers 2
.prologue
const-string v0, "b"
iput-object v0, p0, LTest;->a:Ljava/lang/String;
return-void
.end method
sget-object # s代指static
比如以下Java代码:
public class Test{
private static String a = "hello"
public Test(String a){
}
public void getA(){
String aa = a;
}
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.field private static a:Ljava/lang/String;
.method static constructor ()V
const-string v0, "hello"
sput-object v0, LTest;->a:Ljava/lang/String;
return-void
.end method
.method public getA()V
.registers 2
# 静态字段取值
sget-object v0, LTest;->a:Lava/lang/String;
return-void
.end method
iget-object # i代表instance
比如以下Java代码:
public class Test{
private String a = "hello";
public Test(String a){
}
public void getA(){
String aa = a;
}
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.field private a:Ljava/lang/String;
.method public constructor (Ljava/lang/String;)V
.registers 3
.prologue
invoke-direct {p0}, Ljava/lang/Object;->()V
const-string v0, "hello"
# 初始化成员变量
iput-object v0, p0, LTest;->a:Ljava/lang/String;
return-void
.end method
.method public getA()V
.registers 2
# 类非静态字段取值
iget-object v0, LTest;->a:Ljava/lang/String;
return-void
.end method
注意:以上取值赋值方法都是以String对象举例,如果是基本数据类型,那么按照如下表处理:
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 |
"if-eq vA, vB, :cond_**" 如果vA等于vB则跳转到:cond_** # equal
"if-ne vA, vB, :cond_**" 如果vA不等于vB则跳转到:cond_** # not equal
"if-lt vA, vB, :cond_**" 如果vA小于vB则跳转到:cond_** # less than
"if-ge vA, vB, :cond_" 如果vA大于等于vB则跳转到:cond_** # greater equal
"if-gt vA, vB, :cond_**" 如果vA大于vB则跳转到:cond_** # greater than
"if-le vA, vB, :cond_" 如果vA小于等于vB则跳转到:cond_** # less equal
"if-eqz vA, :cond_**" 如果vA等于0则跳转到:cond_** # zero
"if-nez vA, :cond_**" 如果vA不等于0则跳转到:cond_**
"if-ltz vA, :cond_**" 如果vA小于0则跳转到:cond_**
"if-gez vA, :cond_**" 如果vA大于等于0则跳转到:cond_**
"if-gtz vA, :cond_**" 如果vA大于0则跳转到:cond_**
"if-lez vA, :cond_**" 如果vA小于等于0则跳转到:cond_**
对于dalviks字节码寄存器都是32位的,它能够表示任何类型,2个寄存器用于表示64位的类型(Long and Double).
声明于方法内部(必须)
.method public getName()V
.registers 6
return-void
.end method
在一个方法(method)中有两种方式指定有多少个可用的寄存器。指令.registers指令指定了在这个方法中有多少个可用的寄存器,
指令.locals指明了在这个方法中非参(non-parameter)寄存器的数量。然而寄存器的总数也包括保存方法参数的寄存器。
.method public callMe(II)V
.registers 5
const-string v0, "1"
const-string v1, "1"
v2==>p0
v3==>p1
v4==>p2
v命名法
return-void
.end method
上面的例子中我们使用的是v命名法,也就是在本地寄存器后面依次添加参数寄存器,
但是这种命名方式存在一种问题:假如我们后期想要修改方法体的内容,涉及到增加或则删除寄存器,由于v命名法需要排序的局限性,那么会造成大量代码的改动,有没有一种办法让我们只改动registers或则locals的值就可以了呢,答案是:有的
除了v命名法之外,还有一种命名法叫做p命名法
p命名法只能给方法参数命名,不能给本地变量命名
假如有一个非静态方法如下;
.method public print(Ljava/lang/String;Ljava/lang/String;I)V
以下是p命名法参数对应表:
p0 |
this |
p1 |
第一个参数LJava/lang/String; |
p2 |
第二个参数Ljava/lang/String; |
p3 |
第三个参数I |
如前面提到的,long和double类型都是64位,需要2个寄存器。当你引用参数的时候一定要记住,例如:你有一个非静态方法
LMyObject;->MyMethod(IJZ)V
方法的参数为int、long、bool.所以这个方法的所有参数需要5个寄存器
p0 |
this |
p1 |
I |
p2,p3 |
J |
p4 |
Z |
另外当调用方法后,必须在寄存器列表,调用指令中指明,两个寄存器保存了double-wide宽度的参数。
注意:在默认的baksmali中,参数寄存器将使用p命名方式,如果除去某种原因你要禁用p命名方式,而要强制使用v命名方式,应当使用-p/--no-parameter-registers选项
- locals和registers都可以表示寄存器数量,locals指定本地局部变量寄存器个数,registers是locals和参数寄存器数量的总数,两者使用任选其一
- 同时,寄存器命名一共分两种,一种是v命名法,另一种是p命名法
v0 |
第一个局部寄存器 |
|
v1 |
第二个局部寄存器 |
|
v2 |
p0 |
第一个参数寄存器 |
v3 |
p1 |
第二个参数寄存器 |
v4 |
p2 |
第三个参数寄存器 |
表示与Java源文件代码的映射关系,比如:
.line 3 # 代表以下代码还原成Java代码在源文件第三行
const/4 v0, 0x1
iput v0, p0, LTest;->a:I
删除该关键字不影响程序执行,该关键字在反编译时能很好地帮助我们阅读smali代码,以该关键字当作代码块地分割线,方便快速阅读执行内容
条件分支,配合if使用
表示程序的开始,可省略
goto跳转分支,配合goto关键字使用
显示局部变量别名信息,作用等同.line
move-result-object v0 # 调用方法后结果存储在v0中
# 方便程序阅读,不会对执行造成影响
.local v0, "b":Ljava/lang/String; # 局部变量v0别名为b,是一个String类型 也就是 String b = v0
显示方法参数别名信息,方便程序阅读,不会对执行造成影响
.param p1, "a":Ljava/lang/String; # public void getName(String a)
以下Java代码:
public class Test{
public Test(){
}
public static void main(String[] args){
System.out.println("hello world!");
}
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.method public constructor ()V
.registers 1
invoke-direct {p0}, Ljava/lang/Object;->()V
return-void
.end method
.method public static main([Ljava/lang/String;)V
.registers 3
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "hello world!"
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method