常见ARM汇编指令介绍

2023-11-02

ARM 指令介绍

  • 指令的基本格式 <opcode>{<cond>}{S} <Rd>,<Rn>{,<opcode2>}{} 表示可选项
  • 说明
    • opcode 指令助记符,如LDR,STR等
    • cond 执行条件,如EQ,NE等
    • S 是否影响CPSR寄存器的值,书写时影响CPSR,否则不影响
    • operand2 第二个操作数
    • Rd (Destination) 目的寄存器,存放最终结果
    • Rn (Operand N) 第一源寄存器,第一个操作数 / 基地址
    • Rm (Operand M) 第二源寄存器,第二个操作数 / 可被移位
    • Rt (Target) 目标寄存器,内存传输的数据源或目的地
    • Ra (Accumulate) 累加寄存器,在乘法运算中用于累加

汇编常用指令

简单说明几个不同种类常见汇编指令。指令的详细介绍、参数说明与例子 见《常用ARM指令集及汇编》

跳转指令

B (Branch)

  • BL 带链接的无条件跳转。即跳转前会保存当前的地址,跳转部分指令执行完成会回到该地址。链接的保存可见本文 # 通过寄存器确定函数调用 中寄存器列表R14的说明
  • BX 带状态切换的无条件跳转。arm指令集下,CPSR中T标识位为0;thumb指令集下,T标识位为1。 观察每条指令间的地址差值 可判断当前为ARM指令(4字节) or thumb指令

存储器与寄存器交互指令

  • LDR与STR

    • LDR <待加载寄存器> <数据地址>(Load from memory into a regisiter) 从存储器中加载数据到寄存器,赋值方向 <-。eg:将R9+0x4指向的存储单元内的数据加载到R8中。其中[]意为取值,类似指针中的*。与立即数的操作 不会修改参数寄存器内的值 。该方法为基址寻址,常用于查表等操作。详见《常用ARM指令集及汇编》基址寻址章节

      ; 将寄存器中的数据作为地址,找到指定位置的数据存储到R3寄存器中
      LDR R3 [R0]
      ; IDA动态调试查看R0中数据为415E6F98,点击数值后方的箭头跳转到目标位置,发现如下数据
      ; 415E6F98 DCB 0xE8
      ; 415E6F99 DCB 0x63
      ; 415E6F9A DCB 0x5B
      ; 415E6F9B DCB 0x41
      ; ARM指令4字节数据写入R3,R3中数据为 0x415B63E8 (小端存储,低地址为低位) 
      
      ; 先与立即数进行计算后取值,不会修改R9内的值
      LDR R8,[R9,#4]
      
      ; 将 sp寄存器的值 + (#0x10+var_4) 的值所指向地址内的数据值 赋值给LR寄存器 不会修改SP指针内的值
      LDR LR, [SP,#0x10+var_4]
      
    • STR <数据寄存器> <待存储地址> 将寄存器中数据存储到存储器,赋值方向->。 eg:将R8寄存器中的数据存储到R9+0x4指向的存储单元

      STR R8,[R9,#4]
      
      ; 将 R0寄存器的值 存储到 SP寄存器的值 + (#0x10+var_4) 的值作为地址所指向的单元格中
      STR R0, [SP,#0x10+var_4]
      
      ; 将LR内的数据压栈(栈顶上移一格, 并将LR内的值写入上移的空间)
      ; `!` 在执行完出入栈后会相应修改SP指向
      STR LR, [SP, #-4]!
      
  • LDM与STM

    • LDM 将存储器数据依次加载到寄存器列表,赋值方向->。eg:将R0指向的存储单元数据依次加载到R1,R2,R3。此时第一个参数相当于指针取值,R1=*R0R2=*(R0+#1)R3=*(R0+#2)

      LDM R0, {R1-R3}
      
    • STM 将寄存器列表数据存储到指定存储器,赋值方向<-。关于读取与存储的顺序,详见本文 # 堆栈寻址

  • PUSH与POP

    • PUSH 入栈
    • POP 出栈
  • SWP:将寄存器与存储器之间的数据进行交换。三个参数 SWP Rd,Rm,[Rn],将 Rn 指向存储器空间的内容放入目的寄存器 Rd,后将 Rm 的内容放入 Rn 指向的存储器空间。SWP 共两个步骤:使用 LDR 将数据载入 Rd,再通过 STR 将输入写入 R0 指向。通过 SWP 将两个步骤原子化

    ; 将 R1 寄存器与 R0 指向的存储器之间的数据进行交换
    SWP R1,R1 [R0]
    

数据传送指令

MOV 将立即数货寄存器数据传送到目标寄存器,赋值方向<-。eg1:R0赋值为8。eg2:R2先左移3位后放入R0,即R0 = R2 * 2^3 = R2 * 8(关于 移位 详见《常用ARM指令集及汇编》章节:寄存器偏移寻址)

MOV R0, #8
MOV R0, R2, LSL #3

数据算术运算指令

三个参数Rd,Rm,Rn。其中Rd存放结果,Rm为操作数1,Rn为操作数2

  • ADD
  • SUB
  • MUL
  • DIV

逻辑运算

三个参数Rd,Rm,Rn。其中Rd存放结果,Rm为操作数1,Rn为操作数2

  • AND
  • OR
  • EOR:异或。不同为1(真)

比较指令

CMP(compare)在CPRS寄存器中标志位存储比较结果,Z=0表示不相等

伪指令

为了编程方便,编译器定义了伪指令。伪指令在编译时会被替换成合适的指令。arm中常用伪指令有ADRLDR等。此处着重说明LDR指令。

伪指令LDR 与 存储寄存器交互指令LDR 的不同

由于伪指令会被替换,存储器与寄存器交互指令LDR与伪指令LDR本质上是 不同 的指令。以下是详细说明:

  • ARM指令集中的LDR是寻址指令。参数2常为指定地址的上的值(LDR Rd,[地址] -- 多伴随取值符[])。例如:读取 R0-0x12 地址上的值,存储到R1

    LDR R1, [R0, #-0x12]
    
  • 伪指令LDR用户指定寄存器加载内容。参数2常为表达式(LDR reg,=立即数/表达式 -- 等号开头)。例如:

    LDR R0, =(#0x1)
    

通过伪指令加载指定数据

使用IDA工具并设置机器码的显示( Option -> General -> Disassembly -> Number of opcode bytes: 5)。以下两例均来自参考文章https://bbs.kanxue.com/thread-268869.htmopen in new window中流程打包构建的 hello_arm 可执行文件在 IDA 中反编译的内容

例1 :以C++程序helloworld代码main函数为例说明如何通过 伪指令LDR引用字符串常量

; 目标字符串存放位置
.rodata:000008E4 68 65 6C 6C 6F+aHelloWorld     DCB "hello world",0     ; DATA XREF: main+A↑o
.rodata:000008E4 20 77 6F 72 6C+                                        ; .text:off_788↑o

; main函数中的代码片段 - 获取目标字符串起始地址
.text:00000760 09 4B                          LDR             R3, =(aHelloWorld - 0x766)
.text:00000762 7B 44                          ADD             R3, PC  ; "hello world"

; 函数结束后的内存偏移表
.text:00000788 7E 01 00 00    off_788         DCD aHelloWorld - 0x766 ; DATA XREF: main+8↑r
.text:00000788                                                        ; "hello world"
  • 说明:

    • main函数中LDR的第二个参数为表达式(带等号),此LDR为伪指令

    • 对应的指令字节码长度,和地址间隔均为2字节,说明为thumb指令

    • LDRADD两条指令组成引用字符串常量的过程(LDR读取偏移,ADD计算目标地址)。如果同时引用多个,LDRADD可能不会连续出现,通常为多条LDR后接多条对应的ADD

    • 伪指令的表达式是 内存偏移表 中的内容。 将对应内存偏移表中的机器码 17E 存储到R3

    • ADD执行完成后 R3 = R3 + PC = 17E + 762 + 4 = 8E4 ,即R3存放.rodata节区中字符串的起始地址

    • 被减数为内存偏移表对应项地址,此时 adr加载对应项地址 + 对应项偏移量 = 目标地址

内存偏移表

  • 由于地址长度为4字节。在thumb指令集中指令长度为2字节,不可直接将.bss段或.rodata段中目标的偏移量放入指令中(目标偏移量过大会超过两字节大小)。arm32中引入内存偏移表存放偏移量,指令中通过内存偏移表与当前代码的偏移量读取对应项,并将存放的目标偏移量直接存入寄存器而无需写在指令中

  • 内存偏移表 通常在一个函数的结尾处(BLX LR函数返回后。与pc偏移量可控,不会超过2字节)。其中 被减数 即为 引用地址(便于自己理解的定义) ,如上例中的位于 762ADD R3 PC,此时R3指向目标地址,PC为引用地址,所以被减数为当前地址值+PC固有两条指令偏移,即PC实际值为762+4=766

  • LDR伪指令加载目标地址的方式:读取内存偏移表中的引用地址与目标地址偏移量 + 当前代码位置 = 目标地址

  • 由于三级流水线的存在导致PC并不是调用行的地址,内存偏移表 涉及PC引用的项 会存在2条指令地址的偏移即 汇编代码内计算得到的地址需要额外再+2条指令的地址才是目标地址 。因此例1说明中的第三点需要+4。详见本文 # 三级流水线 中PC的说明

例2 :以C++程序helloworld代码_start函数为例说明如何通过 伪指令LDRADR载入全局偏移表(.got)

; _start函数中的代码片段 - 获取全局偏移表起始地址
.text:00000658 DF F8 24 A0                    LDR.W           R10, =($_GLOBAL_OFFSET_TABLE_ - 0x680)
.text:0000065C 08 A3                          ADR             R3, off_680
.text:0000065E 9A 44                          ADD             R10, R3 ; $_GLOBAL_OFFSET_TABLE_

; 函数结束后的内存偏移表
.text:00000680 80 09 01 00    off_680         DCD $_GLOBAL_OFFSET_TABLE_ - 0x680
.text:00000680                                                        ; DATA XREF: _start+10↑r
.text:00000680                                                        ; _start+14↑o

; 全局偏移表起始位置 - 目标地址
.got:00011000 00 0F 01 00    $_GLOBAL_OFFSET_TABLE_ DCD stru_10F00   ; DATA XREF: .plt:000005D8↑o
.got:00011000                                                        ; .plt:off_5E4↑o ...
  • 说明:
    • R10 加载内存偏移表中的机器码(偏移量) 10980
    • R3 加载内存偏移表对应项 off_680 的地址 680 - 此处不涉及PC寄存器,因此最后不需要额外+4,详见参数文章链接https://bbs.kanxue.com/thread-268869.htmopen in new window
    • ADD执行完成后 R10 = 10980 + 680 = 11000 ,即R10存放全局偏移表的起始地址

补充:如何修改需要读取的字符串,请查看后续文章中的内容

例3:以C++程序helloworld代码deregister_tm_clones函数为例说明如何通过 伪指令LDR载入全局偏移表(.got)内的数据

.text:000006B4 deregister_tm_clones                    ; CODE XREF: __do_global_dtors_aux:loc_732↓p
.text:000006B4                 LDR             R0, =(__bss_start - 0x6BE)
.text:000006B6                 LDR             R3, =(__bss_start - 0x6C0)
.text:000006B8                 LDR             R2, =($_GLOBAL_OFFSET_TABLE_ - 0x6C2)
.text:000006BA                 ADD             R0, PC  ; __bss_start
.text:000006BC                 ADD             R3, PC  ; __bss_start
;                              通过ldr与add,此时R2为全局偏移表基地址
.text:000006BE                 ADD             R2, PC  ; $_GLOBAL_OFFSET_TABLE_
.text:000006C0                 CMP             R3, R0
.text:000006C2                 BEQ             locret_6CC
;                              r3存放目标与内存偏移表基地址偏移量
.text:000006C4                 LDR             R3, =(_ITM_deregisterTMCloneTable_ptr - 0x11000)
;                              通过基地址+偏移量 使 r3指向目标
.text:000006C6                 LDR             R3, [R2,R3] ; _ITM_deregisterTMCloneTable
.text:000006C8                 CBZ             R3, locret_6CC
.text:000006CA                 BX              R3      ; _ITM_deregisterTMCloneTable
.text:000006CC ; ---------------------------------------------------------------------------
.text:000006CC
.text:000006CC locret_6CC                              ; CODE XREF: deregister_tm_clones+E↑j
.text:000006CC                                         ; deregister_tm_clones+14↑j
.text:000006CC                 BX              LR
.text:000006CC ; End of function deregister_tm_clones
.text:000006CC
.text:000006CC ; ---------------------------------------------------------------------------
.text:000006CE                 ALIGN 0x10
.text:000006D0 off_6D0         DCD __bss_start - 0x6BE ; DATA XREF: deregister_tm_clones↑r
.text:000006D4 off_6D4         DCD __bss_start - 0x6C0 ; DATA XREF: deregister_tm_clones+2↑r
.text:000006D8 off_6D8         DCD $_GLOBAL_OFFSET_TABLE_ - 0x6C2
.text:000006D8                                         ; DATA XREF: deregister_tm_clones+4↑r
;                              目标在全局偏移表内的偏移量 - 全局偏移表基地址为0x11000
.text:000006DC off_6DC         DCD _ITM_deregisterTMCloneTable_ptr - 0x11000
.text:000006DC                                         ; DATA XREF: deregister_tm_clones+10↑r

堆栈寻址

堆栈的工作方式按照不同方式分类有多种

  • 根据栈顶指针指向分为

    • 满栈(Full Stask):指向栈顶元素

    • 空栈(Empty Stack):指向栈顶的下一个空位(栈顶元素上方,新入栈数据将存放的空间)

  • 根据堆栈数据存放的地址方向分为

    • 递增堆栈(Ascending Stack):由低地址向高地址生成

    • 递减堆栈(Decending Stack):由高地址向低地址生成

处理器在设计时便定好了采用哪种堆栈方式,用户不可更改。例如:ARM堆栈的工作方式为 满递减堆栈(FD - full decending) ,即按照视觉顺序,栈顶位于低地址,且栈顶指针 SP 指向栈顶元素。

; 数据入栈,最后入栈的数据为r0,即执行完成后栈顶为r0(降序入栈,第一个最后入)!
STMFD SP! {r0,r1,r3-r5}
; 数据出栈,第一个出栈数据赋值给r0(降序出栈,第一个出栈存入第一个寄存器)
LDMFD SP! {r0,r1,r3-r5}

; `!` 在执行完出入栈后会相应修改SP指向

函数在调用时会产生一系列变量,此时需要在栈中创建存储空间进行存储。因此STMFDLDMFD常常成对出现

STMFD将寄存器列表内的值入栈,保证第一项在栈顶。LDMFD将栈内的数据放回寄存器列表,按寄存器列表顺序取值。存放数据与读取数据顺序可理解为井中放绳,即排在最后的先进后出。SP! 会自动进行sp指针值变化的计算,不需手动对SP值进行加或减

指令转换

通过将操作数转换格式以实现伪指令的转化,如通过快捷键d将偏移量转化成基本数据,或将字符串转换成字符数据

以下为IDA中一段代码实例。注意 STM 存放数据顺序与 ADR/LDR伪代码

;             内存偏移表        字符串地址001468EA
LOAD:00145C4C off_145C4C      DCD aCk1234567+0xA - 0x145C4C

;                             将lr, r2, r1, r0 依次压入栈 保证 r0 为栈顶
LOAD:00145C5C                 STMFD           SP!, {R0-R2,LR}
;                             r1 为内存偏移表对应项的地址,adr内部已做偏移处理
LOAD:00145C60                 ADR             R1, off_145C4C
;                             LDR伪指令 通过内存偏移表加载字符串
LOAD:00145C64                 LDR             R0, =(aCk1234567+0xA - 0x145C4C)
;                             内存偏移表做加法,r0指向目标
LOAD:00145C68                 ADD             R0, R0, R1 ; ""
LOAD:00145C6C                 LDR             R2, =off_145C4C
LOAD:00145C70                 SUB             R1, R1, R2
LOAD:00145C74                 STMFD           SP!, {R1}
LOAD:00145C78                 LDR             R2, =dword_4B4BC
LOAD:00145C7C                 ADD             R1, R1, R2
LOAD:00145C80                 STMFD           SP!, {R1}
LOAD:00145C84                 BL              sub_146568
LOAD:00145C88                 MOV             R2, R0
LOAD:00145C8C                 ADR             R1, off_145C4C
LOAD:00145C90                 LDR             R0, =(aCk1234567+0xA - 0x145C4C)
LOAD:00145C94                 ADD             R0, R0, R1 ; ""
LOAD:00145C98                 LDMFD           SP!, {R1}
LOAD:00145C9C                 BL              sub_146248
LOAD:00145CA0                 LDMFD           SP!, {R1}

; ... 此处省略部分指令 ...

; 字符串定义部分                 
LOAD:001468EA                 aCk1234567      DCB "ck1234567_",0
  • 说明
    • 内存偏移表中 对应项的被减数为对应项地址,该地址被r1加载(偏移已被adr内部处理)

    • 在上一条的条件下,执行ldr加载内存偏移表并add,则会指向目标地址(已在例2中说明)

通过将偏移量转换成基本数据,得到如下等效代码(省略部分)

;             内存偏移表转换成具体数值 0x1468EA + 0xA - 0x145C4C = 0xCA8
LOAD:00145C4C dword_145C4C    DCD 0xCA8

LOAD:00145C5C                 STMFD           SP!, {R0-R2,LR}
LOAD:00145C60                 ADR             R1, dword_145C4C
;                             直接赋值偏移表内的偏移量做加法得到字符串地址
LOAD:00145C64                 LDR             R0, =0xCA8
LOAD:00145C68                 ADD             R0, R0, R1 ; ""
LOAD:00145C6C                 LDR             R2, =dword_145C4C
LOAD:00145C70                 SUB             R1, R1, R2
LOAD:00145C74                 STMFD           SP!, {R1}
LOAD:00145C78                 LDR             R2, =dword_4B4BC
LOAD:00145C7C                 ADD             R1, R1, R2

三级流水线

ARM7TDM使用三级流水线执行指令。

  1. 取指 (Fetch):内存中取回指令
  2. 译码 (Decode):开始解码。看懂这条指令是要干什么(是加法、减法还是跳转?)
  3. 执行 (Execute):实际执行指令

程序计数器(PC)总是指向正在读取的指令而非正在执行的指令,超出当前执行指令的两条指令 。即第x条指令若读取pc寄存器内的值,实际上是读取第(x+2)条指令的地址(当前地址 + 4,每条指令2字节)

IDA 动态调试时,若F7单步执行到某一条指令时(与其他ide类似,卡在将执行的指令上),其实CPU已经开始处理目标指令后面的两条指令了(下一条指令已开始解码,下下条指令已开始读取)。指令窗口中的PC(假PC)指向的是将执行的指令,而寄存器窗口中 PC 的真实值已为下下条指令的地址(当前地址 + 4,每条指令2字节,参考文章https://www.cnblogs.com/dliv3/p/5285771.htmlopen in new window

故在动态调试过程中需要修改指令时,需在 执行前两条之前进行修改 (执行到的指令与要修改的指令至少间隔2条指令)

寄存器

寄存器是 CPU 内部存储数据的容器,读写速度远高于内存。ARM 架构中每个寄存器容量均为 4 字节(32 位)。

在最常用的**用户模式(User)**下,程序员能接触到 17 个寄存器:R0 ~ R15 以及 CPSR。

常用寄存器说明

  • R0 ~ R12 通用寄存器
    • R0 ~ R3 共4个寄存器用于函数参数及返回值的传递(函数相关)若函数有返回值,通常存放在 R0 中。
      • 若函数有返回值,通常放在R0中。查看函数调用B后的下一行有没有读取或MOV寄存器R0中的值来判断是否有返回值
    • R4 ~ R11 普通的通用寄存器。保存函数内部的局部变量
      • R7 数据栈帧的指针,链接寄存器 LR 在栈上的地址。Thumb 指令集的栈帧指针 FP
      • R9 系统保留寄存器
      • R11 ARM 指令集的栈帧指针 FP 通常指向堆栈栈底
  • R12 ~ R15 特殊功能寄存器
    • R12 临时寄存器 IP。存储函数调用中的临时内容,主要用于链接器(Linker)在函数长跳转前的临时地址计算和中转,函数调用后其值不可靠。例:
      • 存储链接地址(待跳转地址)
        ldr r12, =target_address  ; 存储函数地址,方便使用 B 指令跳转
        bx  r12
        
      • 函数调用后,R12 的值是不可靠的(仅用于函数调用前的临时数据处理)
    • R13 栈顶指针 SP
      • 满栈: 指针指向的地址为栈顶元素所在地址
      • 空栈: 指向栈顶元素上方预留的一个空位的地址(递减栈,栈顶元素地址-4)
    • R14 链接寄存器 LR。存放当前函数执行完毕后的返回地址(即调用指令 BL 的下一条指令地址),而不是调用者函数的首地址
    • R15 程序计数器 PC。存放 CPU 当前正在取指的指令地址(对于三级流水线,PC = 当前执行指令地址 + 8)

CPSR寄存器

全称 Current Program Status Register, 各个bit位表明了CPU的某些状态信息

关键 bit 的介绍

  • 条件位
    标识位说明
    NSet when the result of the operation was Negative.(负数置1,非负数置0)
    ZSet when the result of the operation was Zero.(为零置1,非零置0)
    CSet when the operation result in a Carry.(发生进位,或借位时,置1)
    VSet when the operation caused oVerflow.(操作造成溢出时,置1。如计算补码)
  • T 位: 0 - ARM 状态; 1 - Thumb 状态

ARM 处理器工作模式

经典 ARM 架构共有 7 种基本工作模式,主要分为“非特权”与“特权”两类:

  • 普通(非特权)模式(1种)
    • User 模式,大部分任务执行在这种模式
  • 特权模式(6种)
    • System: 与 User 模式完全共用一套寄存器,用于 OS 内核任务。
    • FIQ: 快速中断模式。发生高优先级中断时进入此模式
    • IRQ: 普通中断模式。发生低优先级中断时进入此模式
    • Supervisor: 复位或程序执行软中断指令时进入此模式
    • Abort: 存取异常时进入此模式
    • Undef: 执行到未定义(无法识别)指令时,触发未定义指令异常,进入此模式(防止在正常模式下执行未知指令,从而被黑客进行攻击,如内存泄露等)

寄存器结构

ARM 共 37 个寄存器,每个寄存器都是 4字节(每个寄存器可存放32位的数据)

看起来只有 R0-R15 和 CPSR,但为了在不同工作模式间快速切换而不破坏其他模式的数据,ARM 在硬件物理上实际设计了 37 个 寄存器。

核心机制叫做 影子寄存器(Banked Registers):当 CPU 切换到特定模式时,某些寄存器会被硬件自动替换成该模式专属的物理寄存器。

如 Undef 模式下,拥有自己 R13 与 R14 ,以及额外的 SPSR 寄存器(3个)作为备份寄存器,用于切换到其他模式。其他的 R0 - R12、R15、CPSR 共用普通模式的寄存器

  • 基础寄存器细分

    • 基础共有寄存器 (17 个):User 和 System 模式使用的全套寄存器

    • FIQ: R8-R14 SPSR 共8个(因为 FIQ 追求极速,备份的通用寄存器最多,中断时连 R8-R12 都不用压栈,直接用专属的)

    • IRQ: R13 R14 SPSR 共3个

    • SVC: R13 R14 SPSR 共3个

    • Undef: R13 R14 SPSR 共3个

    • Abort: R13 R14 SPSR 共3个

    • 以上备份寄存器与17个用户模式寄存器一共为 37 个寄存器

  • SPSR: Saved Program Status Register. 每个异常模式(FIQ, IRQ, SVC, ABT, UND)都有一个自己专属的 SPSR 寄存器

    • 作用:当系统从 User 模式陷入异常模式时,硬件会自动将 User 模式的 CPSR 复制到该异常模式的 SPSR 中保存起来。
    • 恢复:当异常处理完毕准备返回时,只需要将 SPSR 的值恢复给 CPSR,系统状态就能完美还原

so内调用函数的入参分析

BL与BLX指令常常伴随函数调用出现。函数参数的传递是通过寄存器与堆栈实现的

通过寄存器确定函数调用

  • 需要函数调用时会使用 B 跳转指令进行跳转
  • 常常伴随着通过链接寄存器 R14(LR) 存储当前位置,使函数调用结束后跳转回该位置
  • 若函数需要参数,使用 R0 ~ R3 及堆栈传递函数所需参数

判断即将跳转函数的参数个数

  • BLX的目标值是否为 R0~R3 寄存器(根据参数寄存器为 R0~R3 进行判断)
    • 是:参数个数小于4个时,BLX的跳转地址为目标函数地址,之前的寄存器皆为函数参数。例如:BLX R3 说明函数有3个参数,分别存储在 R0、R1、R2,而R3存放目标函数地址。

    • 否:参数个数量大。前4个参数在 R0~R3 寄存器中。剩下的在跳转指令前通过STRSTM放入堆栈(并不一定入栈的数据都是参数,需要分析。若前面没有STRSTM压栈操作,则为4个)

    • 分析:在被调用函数内,一开始便读取寄存器,一开始读取堆栈的,读取的数据为函数参数

可通过ida反编译的伪代码辅助分析。例如ida识别为方法名BLX func_xxx,通过 tab 查看ida解析出来的参数个数辅助自己分析

mov r0, #5
mov r3, #4
mov r2, #3
str r0, [sp, #0]
mov r1, #2
mov r0, #1
BL func_test

此时,第5个参数在调用前被存储在堆栈上