ARM汇编基础


参考文章:

  • https://bbs.pediy.com/thread-220461.htm
  • 《逆向工程权威指南》
  • https://developer.arm.com/documentation/ddi0595/latest
  • https://bbs.pediy.com/thread-220907.htm
  • https://www.cnblogs.com/viiv/p/15705846.html

1. ARM指令集架构

ARM属于RISC cpu,最初尽力保持各指令长度为4字节。

Intel是CISC指令集,内容很庞大。

最初的arm模式指令集,指令长4字节。后来的thumb模式指令集,指令长2字节。

ARM模式和Thumb模式,可类比x86的实模式和保护模式。

ARM v7引入 thumb-2指令集,指令长2或4字节。后来还有ThumbEE:在Thumb-2基础上包含了针对动态代码生成(代码在执行前或执行期间编译代码)的一些变更和补充。

64位ARM使用ARM 64指令集,指令长4字节。

截至2016年,xcode使用thumb-2指令集。后来也引入了ARVv8和ARM64。

总结三种ARM模式指令集(Jazelle先不考虑):

  • arm模式
  • thumb模式(包括thumb-2)
  • arm64模式(需要单独学习)

ARM和Thumb的区别:

  • 条件执行:ARM状态下的所有指令都支持条件执行。某些ARM处理器版本允许使用IT指令在Thumb中进行条件执行。条件执行提高了代码密度,因为它减少了要执行的指令数量,并节省了昂贵的分支指令;
  • 32位Thumb指令具有.w后缀;
  • 桶形移位器,ARM模式特有的功能,可以将多个指令合并成一个,例如Mov R1,R0,LSL#1; R1 = R0 * 2;
  • 如果当前程序状态寄存器中的T位置1,我们知道我们处于Thumb模式。

mips, powerpc, alphaAXP等处理器都是固定4字节指令。

arm系列从arm11开始,以后的就命名为cortex,并且性能上大幅度提升。从cortex开始,分为三个系列,a系列,r系列,m系列:

  • a系列应用在人机互动要求较高的场景,类似于cpu,用于运行os;
  • r系列,用于实时控制;
  • m系列,可以理解为高级的单片机。
ARM 处理器家族ARM指令集架构
ARM7ARM v4
ARM9ARM v5
ARM11ARM v6
Cortex-AARM v7-A
Cortex-RARM v7-R
Cortex-MARM v7-M

Intel处理器的差异

ARM指令只处理寄存器中的数据,也就是说只有load/store指令可以访问存储器。所以如果我们要增加某个内存地址中保存的值,至少需要三种类型的指令(load指令、加法指令和store指令)。

ARM和x86/x64之间更多的区别还包括:

  • 在ARM中大多数指令可以用于分支跳转的条件判断。
  • Intel的x86/x64系列CPU是小端序的。
  • ARM架构在ARMv3之前是小端序的,在那之后,ARM处理器提供一个配置项,可以通过配置在大端和小端之间切换。

2. 编写

$ as program.s –o program.o
$ ld program.o –o program

3. 数据类型和寄存器

可以load(或store)的数据类型包括signed/unsigned words,halfwords或者bytes。默认表示word。

ldr = Load Word
ldrh = Load unsigned Half Word
ldrsh = Load signed Half Word
ldrb = Load unsigned Byte
ldrsb = Load signed Bytes
  
str = Store Word
strh = Store unsigned Half Word
strsh = Store signed Half Word
strb = Store unsigned Byte
strsb = Store signed Byte

字节序

v3之后,ARM处理器可以通过硬件配置在大小端之间切换

以ARMv6为例,指令是固定的以小端序存储的,而内存数据的读取方式可以通过控制程序状态寄存器CPSR的第9位实现在大端和小端之间切换。

寄存器

https://developer.arm.com/documentation/ddi0595/2021-12?lang=en

除了基于ARMv6-M和ARMv7-M的处理器,其它的ARM处理器都有30个32 bit的通用寄存器。

我们关注17个可以在任何运行模式下被访问的寄存器:r0~r15还有CPSR。它们被分为被分为两组:通用寄存器和专用寄存器。

寄存器别名作用
R0-通用
R1-通用
R2-通用
R3-通用
R4-通用
R5-通用
R6-通用
R7-常用于保存系统调用号
R8-通用
R9-通用
R10-通用
R11FP用于保存栈帧
专用寄存器
R12IP内部调用暂存寄存器
R13SP栈顶指针
R14LR用于保存函数返回地址
R15PC用于保存下一条指令的地址
CPSR-当前程序状态寄存器

和x86寄存器做一个简单类比:

ARM简述X86
R0通用寄存器EAX
R1~R5通用寄存器EBX,ECX,EDX,ESI,EDI
R6~R10通用寄存器-
R11(FP)栈帧寄存器EBP
R12内部调用暂存寄存器-
R13(SP)堆栈寄存器ESP
R14(LR)链接寄存器-
R15(PC)程序计数器EIP
CPSR当前程序状态寄存器EFLAGS

ARM的函数调用约定规定,函数的前四个参数存储在寄存器r0~r3中。

r12要慎重使用.

CPU的流水线机制

CPU取指令,解码指令和执行指令时使用的是不同的硬件部件,可以并行执行。

因为RISC CPU的指令长度一定,所以CPU可以在解码指令之前就知道下一条指令的长度,从而在解码指令时取下一条指令,在执行指令时,对下一条指令进行解码,并取下下一条指令,这称为三级流水线。

可以用这么一条汇编来测试:

 mov r0, pc

执行完这一句时,pc+4(或2)== r0。

执行这句指令时,PC已经指向下一处进行取指令操作了。这是硬件中真实发生的情况,而调试器为了令展示更有逻辑性,所以PC寄存器显示了当前执行指令的地址,当我们真实调试时不要受此影响。

CPSR

CPSR: Current Program Status Register

标记含义
N(Negative)指令执行结果为负时置1
Z(Zero)指令执行结果为0时置1
C(Carry)加法有进位则置1否则置0,减法有借位则置0否则置1
V(oVerflow)指令执行结果超出32位补码存储范围时置1
E(Endian-bit)置0时使用小端序,置1时使用大端序
T(Thumb-bit)置1时使用Thumb模式,置0时使用ARM模式
M(Mode-bit)共5位表示处理器运行模式
J(Jazelle)对于有的处理器,置位表示允许以硬件执行java字节码

4. Opcode

ARM指令通常跟一到两个操作数, 模板如下:

MNEMONIC{S}{condition} {Rd}, Operand1, Operand2

MNEMONIC  
	- 指令的助记符如ADD
{S} - 可选的扩展位
	- 如果指令后加了S,将依据计算结果更新CPSR寄存器中相应的FLAG
{condition}
	- 执行条件,如果没有指定,默认为AL(无条件执行)
{Rd}
	- 目的寄存器,存储指令计算结果
Operand1
	- 第一个操作数,可以是一个寄存器或一个立即数
Operand2
	- 第二个(可变)操作数,可以是一个立即数或寄存器甚至带移位操作的寄存器

补充解释一下执行条件和第二个操作数。

  • 设置了执行条件的指令需要在执行指令前先校验CPSR寄存器中的标志位。
  • 第二个操作数被称为可变操作数,因为它可以被设置为多种形式,包括立即数、寄存器、带移位操作的寄存器,如下
#123         - 立即数
Rx           - 寄存器比如R1
Rx, ASR n    - 对寄存器中的值进行算术右移n位后的值
Rx, LSL n    - 对寄存器中的值进行逻辑左移n位后的值
Rx, LSR n    - 对寄存器中的值进行逻辑右移n位后的值
Rx, ROR n    - 对寄存器中的值进行循环右移n位后的值
Rx, RRX      - 对寄存器中的值进行带扩展的循环右移1位后的值

5. 交叉编译环境

ubuntu 18.04

搜索交叉编译器:

$ apt search gcc-5-arm-linux

可以看到gcc-5-arm-linux-gnueabi和gcc-5-arm-linux-gnueabihf,后者对浮点数的编译选项做了特殊处理。安装前者即可。另外需要安装qemu-arm模拟器:

$ sudo apt install gcc-5-arm-linux-gnueabi
$ sudo apt-get install qemu qemu-system qemu-user

用法和gcc一样:

$ arm-linux-gnueabi-as [source file] –o [object file]
$ arm-linux-gnueabi-ld [object file] –o [executable file]
$ arm-linux-gnueabi-gcc-5 hello.c –g –o hello -static

其它工具还有arm-linux-gnueabi-objdump, arm-linux-gnueabi-elf等。

设置ld-linux.so的加载路径, 否则执行会报错/lib/ld-linux.so.3: No such file or directory

$ export QEMU_LD_PREFIX=/usr/arm-linux-gnueabi
$ qemu-arm -L /usr/arm-linux-gnueabi

5.1 arm架构模拟器

$ sudo apt install qemu-user-static

执行 hello 程序:

$ qemu-arm-static hello

启动 gdbserver 等待 gdb 连接:

$ qemu-arm-static -g 1234 ./hello

5.2 虚拟 Raspberry

略 有时间实践后再补充。

5.3 调试环境

gdb-multiarch

gdb 支持多种硬件体系架构的版本。

$ sudo apt install gdb-multiarch
$ gdb-multiarch
(gdb) set architecture arm    非必须
(gdb) target remote localhost:1234
(gdb) b main
(gdb) c

编译 gdbserver

分析 IoT 设备时,往往需要上传 gdbserver 进行远程调试。在我们的实验环境中(如果没有gdb的话 ),可以模拟搭建一个远程调试环境。首先,获取与本地gdb 版本一致的 gdb 源码(包含了 gdb 源码和 gdbserver 源码),因为 gdbserver 需要与 gdb 版本保持一致。

http://ftp.gnu.org/gnu/gdb/

$ CC="arm-linux-gnueabi-gcc-5" CXX="arm-linux-gnueabi-g++-5" ./configure --target=arm-linux-gnueabi --host="arm-linux-gnueabi" --prefix="setup-directory"
$ make install

编译完成后用scp 将 gdbserver 上传到我们的虚拟arm环境中并启动:

$ ln -s arm-linux-gnueabi-gdbserver gdbserver
$ gdbserver 0.0.0.0:2333 hello
Process hello created; pid = 702
Listening on port 2333

gef

支持多种硬件体系结构的 gdb 插件。

https://github.com/hugsy/gef

注意keystone-engine 需要提前手动安装

$ wget -O ~/.gdbinit-gef.py -q https://gef.blah.cat/py
$ echo source ~/.gdbinit-gef.py >> ~/.gdbinit

安装成功后:

gef> set architecture arm
gef> gef-remote –q 127.0.0.1:1234
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值