Smali语法小记

Smali语法小记

介绍

在执行 Android Java 层的代码时,其实就是 Dalvik(ART) 虚拟机(使用 C 或 C++ 代码实现)在解析 Dalvik 字节码,从而模拟程序的执行过程。

自然,Dalvik 字节码晦涩难懂,研究人员们给出了 Dalvik 字节码的一种助记方式:smali 语法。通过一些工具(如 apktool),我们可以把已有的 dex 文件转化为若干个 smali 文件(一般而言,一个 smali 文件对应着一个类),然后进行阅读。对于不同的工具来说,其转换后的 smali 代码一般都不一样,毕竟这个语法不是官方的标准。

下文说明

<> 中的内容必须存在,[] 的内容是可选的

结构

  • 声明语句
  • 执行语句

声明语句

声明语句一般都是以 . 开始

寄存器

Dalvik 最多支持 65536 个寄存器 (编号从 0~65535),但是 ARM 架构的 cpu 中只有 37 个寄存器。那 Dalvik 是怎么做的呢?其实,每个 Dalvik 虚拟机维护了一个调用栈,该调用栈用来支持虚拟寄存器和真实寄存器相互映射的。

寄存器命名规则

  • v命名
    • 局部变量 v0 ~ vm-1
    • 函数参数vm ~ vm+n
  • P命名
    • 局部变量 v0 ~ vm-1
    • 函数参数pm ~ pm+n

变量

javasmali
booleanZ
byteB
shortS
charC
intI
longL
floatF
doubleD
voidV
objectL
array[

对象类型

对象类型可以表示 Java 代码中的所有类
例如:

  • Lpackage/name/ObjectName;
  • Ljava/lang/String;

数组

比如说 int 数组 int [] 在 smali 中的表示形式为 [I 。
比如说数组类型 String[][] 在 smali 中的表示形式为 [[Ljava/lang/String;

字段声明

#instance fields
.field <访问权限修饰符> [非权限修饰符] <字段名>:<字段类型>

访问权限修饰符

  • public
  • private
  • protected

非权限修饰符

  • final
  • volidate
  • transient

综上举例:

  • private java.lang.String str1; == .field private str1:Ljava/lang/String;
  • public static java.lang.String str2; == .field public static str2:Ljava/lang/String;

方法

# 描述方法类型
.method <访问权限修饰符> [修饰符] <方法原型>
      <.locals>(.locals 会指定方法使用的局部变量。)
      [.parameter](参数就正常用 p0 开始)
      [.prologue](程序的开始处)
      [.line](第几行)
      <代码逻辑>
      [.line]
      <代码逻辑>
.end

方法类型

一般是反编译工具加上去的

  • 直接方法,direct method
  • 虚方法,virtual method

方法原型

方法原型一般为方法名(参数类型描述符)返回值类型描述符 。

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

例如:

.class public final Lcom/a/b/c;
.super Ljava/lang/Object;
.source "Demo.java"

类的引用

this$[层数]
例如

public class MainActivity extends Activity {   //this$0
   public class firstinner  //this$1
   {
      public class secondinner //this$2
      {
         public class thirdinner //this$3
         {

         }
      }
   }
}

接口

#interfaces
.implements <接口名称>

注解

#annotations
.annotation [注解的属性] <注解范围>
    [注解字段=]
    ...
.end

位描述元素

  • 一个 op,8 位指令码
  • 若干个字符,每一个字符表示 4 位
  • 若干个 | ,进行分割,方便阅读。
  • 若干个 ∅同样也是 4 个字符,表示该部分位为 0。
    例如:
    指令 B|A|op CCCC 包含 2 个 word,一共 32 位。其中,第一个字的低 8 位是操作码,中间 4 位是 A,高 4 位是 B。第二个字是单独的 16 位的数值

格式ID

指令格式,根据 ID 的不同,仍然可以表示不同的指令含义
在这里插入图片描述

  • 第一个数字表示 word 的数量
  • 第二个数字的话表示指令包含的寄存器的最大数量
  • 第二个如果是r 的话,表示使用了一定范围内的寄存器 (range)。
  • 第三个字符表示指令使用到的额外数据的类型
  • 第四个s表示静态链接
  • 第四个i表示内联

指令句语法

  • 指令以操作码 op 开始
  • op后面跟参数
  • 参数间以逗号分隔。
  • 参数Vx表示寄存器,如 v0、v1 等。
  • 参数 #+X 表示常量数字。
  • 参数 +X 表示相对指令的地址偏移。
  • 参数 kind@X 表示常量池索引值,其中 kind 表示常量池类型,可以是string type field meth

例如:
指令 op vAA, type@BBBB 为例,指令使用了 1 个寄存器 vAA,一个 32 位的类型常量池索引。

指令后缀

  • 32 位运算不标记。
  • 64 位运算以 -wide 为后缀。
  • 特定类型的运算码以其类型(或简单缩写)为后缀,这些类型包括-boolean、-byte、-char、-short、-int、-long、-float、-double、-object、-string、-class 和 -void。

空指令

nop 无任何操作

数据定义指令

主要就是const
举例

byte b = 1; ==const/4 v0, 0x1
short s = 2;==const/4 v8, 0x2
String str = "test"; ==const-string v9, "test"
Class c = Object.class; const-class v1, Ljava/lang/Object;

数据移动指令

主要就是move

  • move、move-result 用于处理小于等于 32 位的基本类型。
  • move-wide、move-result-wide用于处理 64 位类型
  • move-object系列指令和move-result-object用于处理对象
  • 后缀(/from16、/16)只影响字节码的位数和寄存器的范围

数据转换指令

明显的两种类型的单词组合
举个例子int-to-short v0,v1 即将寄存器 v1 的值强制转换为 short 类型,并放入 v0 中。

运算指令

运算类型如下

类型说明
add-typevBB + vCC
sub-typevBB - vCC
mul-typevBB * vCC
div-typevBB / vCC
rem-typevBB % vCC
and-typevBB & vCC
or-typevBB | vCC
xor-typevBB ^ vCC
shl-typevBB << vCC
shr-typevBB >> vCC
ushr-typevBB >>> vCC无符号
  • type 可以是 - int,-long, -float,-double。
    举例:
int a = 5, b = 2;
a += b;
a -= b;
a *= b;
a /= b;
a %= b;
a &= b;
a |= b;
a ^= b;
a <<= b;
a >>= b;
a >>>= b;

smali

const/4 v0, 0x5
const/4 v1, 0x2
add-int/2addr v0, v1
sub-int/2addr v0, v1
mul-int/2addr v0, v1
div-int/2addr v0, v1
rem-int/2addr v0, v1
and-int/2addr v0, v1
or-int/2addr v0, v1
xor-int/2addr v0, v1
shl-int/2addr v0, v1
shr-int/2addr v0, v1
ushr-int/2addr v0, v1

数组操作

指令说明
array-length vA, vB获取给定 vB 寄存器中数组的长度并赋给 vA 寄存器
new-array vA, vB, type@CCCC新建数组
new-array/jumbo vAAAA, vBBBB,type@CCCCCCCC新建数组范围更大
filled-new-array {vC, vD, vE, vF, vG},type@BBBB新建数组指定内容
filled-new-array/range {vCCCC …vNNNN}, type@BBBB新建数组指定范围
fill-array-data vAA, +BBBBBBBB填充数组

例如:

int[] arr = new int[10];
int[] arr = {1, 2, 3, 4, 5};

==

const/4 v1, 0xa
new-array v0, v1, I
const/4 v1, 0x1
const/4 v2, 0x2
const/4 v3, 0x3
const/4 v4, 0x4
const/4 v5, 0x5
filled-new-array {v1, v2, v3, v4, v5}, I
move-result v0
寄存器若连续,则可改为
filled-new-array {v1...v5}, I

实例操作

指令说明
check-cast vAA, type@BBBB、check-cast/jumbo vAAAA, type@BBBBBBBB转成B
instance-of vA, vB, type@CCCC、instance-of/jumbo vAAAA, vBBBB, type@CCCCCCCCif能转成B,vA=1 else vA=0
new-instance vAA, type@BBBB、new-instance/jumbo vAAAA, type@BBBBBBBB构造新实例

例如:

Object obj = new Object();

String s = "test";
boolean b = s instanceof String;

String s = "test";
Object o = (Object)s;

==

new-instance v0, Ljava/lang/Object;
invoke-direct-empty {v0}, Ljava/lang/Object;-><init>()V

const-string v0, "test"
instance-of v1, v0, Ljava/lang/String;

const-string v0, "test"
check-cast v0, Ljava/lang/Object;
move-object v1, v0

字段操作指令

字段操作指令主要是对实例的字段进行读写操作

  • 读=get
  • 写=put
  • 普通字段+i
  • 静态字段+s

例如:iget,iget-wide,iget-object,iget-boolean,iget-byte,iget-char,iget-short

例如:

int[] arr = new int[2];
int b = arr[0];
arr[1] = b;

==

const/4 v0, 0x2
new-array v1, v0, I
const/4 v0, 0x0
aget-int v2, v1, v0
const/4 v0, 0x1
aput-int v2, v1, v0

比较指令

cmp(l/g)-kind(kind可以选float、long、double) vAA(结果放这里), vBB, vCC
如果 vBB 寄存器大于 vCC 寄存器,结果为 - 1,相等则结果为 0,小于的话结果为 1

跳转指令

  • goto,无条件跳转
  • switch,分支跳转
    • packed-switch vAA,+BBBBBBBB 规律递增跳转
    • sparse-switch vAA,+BBBBBBBB 无规律递增跳转
  • if,条件跳转
    • if-eq vA,vB,target 如果 vA=vB,跳转。
    • if-ne vA,vB,target 如果 vA!=vB,跳转。
    • if-lt vA,vB,target 如果 vA<vB,跳转。
    • if-gt vA,vB,target 如果 vA>vB,跳转。
    • if-ge vA,vB,target 如果 vA>=vB,跳转。
    • if-le vA,vB,target 如果 vA<=vB,跳转。
    • if-eqz vAA,target 如果 vA=0,跳转(加z则是和0比较)

例如

int a = 10
if(a > 0)
    a = 1;
else
    a = 0;

==

const/4 v0, 0xa
if-lez v0, :cond_0 # if 块开始
const/4 v0, 0x1
goto :cond_1       # if 块结束
:cond_0            # else 块开始
const/4 v0, 0x0
:cond_1            # else 块结束

锁指令

  • monitor-enter vAA 为指定的对象获取锁
  • monitor-exit vAA 释放指定的对象的锁

方法调用指令

  • invoke-kind {vC, vD, vE, vF, vG},meth@BBBB
  • invoke-kind/range {vCCCC … vNNNN},meth@BBBB 两类

kind类型:

  • invoke-direct 直接调用
  • invoke-super 调父类
  • invoke-virtual 虚方法(对于protected或者public方法都叫做虚方法。)
  • invoke-static 静态
  • invoke-interface 接口

异常指令

try-catch

int a = 10;
try {
    callSomeMethod();
} catch (Exception e) {
    a = 0;
}
callAnotherMethod();

==

const/16 v0, 0xa

:try_start_0            # try 块开始
invoke-direct {p0}, Lnet/flygon/myapplication/SubActivity;->callSomeMethod()V
:try_end_0              # try 块结束

.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0

:goto_0
invoke-direct {p0}, Lnet/flygon/myapplication/SubActivity;->callAnotherMethod()V
return-void

:catch_0                # catch 块开始
move-exception v1
const/4 v0, 0x0
goto :goto_0            # catch 块结束

返回指令

return-kind

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值