ARM汇编

一前备小理解

1.平台

linux内核可以被交叉编译为任意平台的系统。只需对应的编译器即可。因此可以在arm下运行(google采用arm为最初的android支持的处理器架构)。类比于一些linux发行版(ubuntu)支持i386(intel32位)和amd64

2.x86汇编与AT&T汇编

x86汇编与AT&T汇编。其实层次应该是AT&T与intel都属于x86汇编。二者区别主要为语法与格式。(我学的王爽汇编是intel)举几个例子:
AT&T: movw 4(%bp), %dx
Intel: mov dx,[bp+4]

3.中断

中断用于cpu停止执行当前代码,转而执行中断处理,处理后可以回到原代码处。
分类:可分为CPU内部与外部中断,百度上:
硬件中断:

  1. 可屏蔽中断:可以被屏蔽
  2. 非可屏蔽中断:不可屏蔽的硬件中断
  3. 处理器间中断:一种特殊的硬件中断。由处理器发出,被其它处理器接收。用于多处理器间同步
  4. 伪中断:一类不希望被产生的硬件中断。

软件中断:

  1. 软件中断。是一条CPU指令,用以自陷一个中断。由于软中断指令通常要运行一个切换CPU至内核态(Kernel Mode/Ring 0)的子例程,它常被用作实现系统调用(System call)。x86中的od基于int 3中断

内存中从0开始的部分有着中断向量表,用于cpu寻找对应的中断发生时该执行的代码位置

4.端口

和cpu通过总线相连的除了内存RAM、内部寄存器,还有各种芯片(外设),它们对cpu提供端口(寄存器)从而cpu可操控。

另注:读懂操作系统底层资源管理需要:汇编,微机原理

二ARM处理器

ARM采用RISC指令集,主要全面用于嵌入式及移动领域
ARM处理器家族:

相同架构兼容同一套ARM指令集
特性解释:

  1. Thumb是ARM指令压缩形式子集,1为16位,2扩充32位
  2. VFP提供浮点运算能力
  3. Jazelle为 .class文件运行加速dex无用
  4. NEON、SIMD多媒体加速

我的1+5:骁龙835是兼容ARM指令集的处理器,由高通公司产,支持快充。
ARM公司本身并不参与终端处理器芯片的制造和销售,而是通过向其它芯片厂商授权设计方案,来获取收益。但也有少数手机处理器厂商,如高通,直接在ARM v7指令集的基础上深度开发自己的处理器微架构,如高通公司的Scorpion和Krait,进而设计自主的CPU,具有更大的灵活性。

三android支持的架构

最初选择ARM并做了大量优化。但也支持其他架构cpu。比如:x86、atom(凌动)、MIPS

四android native 与arm汇编程序

android ndk可将c++编译为基于特定处理器的可执行文件于apk的lib中。这是我环境下打包apk/lib中的:

可见不同cpu架构下的可执行文件不同,操作系统只能提供api来屏蔽底层。但为了执行还需ABI相同。
在这里主要为arm elf
android的arm汇编是gun arm汇编格式,使用的汇编器为GAS
android ndk中编译使用的是gcc。过程:

  1. 预处理 -E 生成.i全部展开
  2. 编译 -S 语法检查生成arm汇编代码.s
  3. 汇编 -c 生成.o目标文件
  4. 连接 生成可执行文件

底层汇编与c同用一套api接口
arm处理器有7种运行模式,一般为用户模式,不同模式寄存器与可访问资源不同
arm处理器有俩种工作状态:arm与thumb

arm汇编程序结构

示例:

c:

1
2
3
4
5
6
#include <stdio.h>
int main(int argc, char* argv[]){
printf("Hello ARM!\n");
return 0;
}

ARM:.s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@开头为架构定义
.arch armv5te @指定arm处理器架构确定指令集
.fpu softvfp @协处理类型,这里表示用浮点运算库模拟协处理器。
.eabi_attribute 20, 1 @接口属性
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.file "hello.c"
.section .rodata @段1:只读
.align 2
.LC0:
.ascii "Hello ARM World\000"
.text @段2:代码
.align 2
.global main
.type main, %function
main:
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
stmfd sp!, {fp, lr}
add fp, sp, #4
sub sp, sp, #8
str r0, [fp, #-8]
str r1, [fp, #-12]
ldr r3, .L3
.LPIC0:
add r3, pc, r3
mov r0, r3
bl puts(PLT)
mov r3, #0
mov r0, r3
sub sp, fp, #4
@ sp needed
ldmfd sp!, {fp, pc}
.L4:
.align 2
.L3:
.word .LC0-(.LPIC0+8) @字符串相对于调用处的偏移
.size main, .-main
.ident "GCC: (GNU) 4.8.2"
.section .note.GNU-stack,"",%progbits @段3:禁止生成可执行堆栈,放溢出漏洞

EXPORT 表示被程序导出

c等语言的段(节区)是编译器自动生成的:列如.data .text之类
section的存在并不是为了以数据功能为准则划分数据,而是以数据属性为标准来归类数据。就是可读性、可写性以及可执行性。
c中的节区十分自由,常见的有:

  1. .text - 不多说了,就是保存代码的节
  2. .data - 保存数据的节,这个对应C语言中以初始化的全局变量数据。想想为什么你在源码里初始化一个全局变量后运行时这个变量的值正是你想要的那个?int a = 12;并不意味着CRT为你执行了一个赋值语句,而是a在PE文件中保存的位置已经被硬编码了一个12的值。这样loader加载程序时,你给的初值被从PE文件读取到了内存中变量a的位置,这样才使你的变量a有了初值。
  3. .rdata - 保存常量数据的节。这个可以对应C语言中的常数和常量字符串,同上面一样的原因,他们的初值被保存到了PE文件的次Section中,而不是在运行时被赋值。
  4. .bss - (Block Start with Symbol)这个section对应C程序中的全局未初始化变量。啥?你说C中未初始化的全局变量实际上全被初始化成了0?这是因为实际上操作系统是这样干的——你的全局未初始化变量由于没有初值,所以不需要将值像上面两个一样保存到PE文件中(所以.bss节除了描述信息之外不占据磁盘空间),但是.bss会描述一段内存区域,loader在加载.bss section时直接开辟这么一块包括所有未初始化数据的内存区域,然后直接将这区域清零。这就是C中全局未初始化数据之所以为零的原因了。
  5. .idata - 这个是保存程序导入表(Import Table)的节。当然,IT、ILT以及IAT也常常被保存在.rdata中,为什么?是考虑到安全的因素,所以将IAT放在不可写的.rdata里以防止IAT被恶意更改从而造成程序的安全隐患吧。
  6. .edata - 这个是保存导处表的(Export Table)的节。
  7. .reloc - 这个节是保存重定位数据的
  8. .rsrc - 这节是保存程序资源的。想你的程序字符串啊、对话框模板啊、位图、鼠标光标什么的都在这里。实际上这个节储存.resx文件编译后的结果。
  9. .textbss - 这节比较好玩,它是和微软Incremental Linking(增量链接)特性相关的。

而在汇编中,我们需要手动分段(汇编基本就是机器码助记符,基本内存中就不变了)
arm下:
.section name flag type 平台参数
flag:段的属性:读写执行
type:段的类型progbits表示有数据,note表示数据非本程序使用

注释

arm汇编支持俩种:

  1. / /
  2. @一行

标号

标号名 :
标号名任意,前面不用加.

编译器指令

arm汇编中所有.开头的都是汇编器指令,汇编器指令与汇编器相关,不属于arm指令集
GAS本示例中有:

  1. .file指定了源文件名
  2. .align指定代码对齐方式,后面的数是2的几次方
  3. .ascii申明字符串
  4. .global申明全局符号
  5. .type指定符号类型
  6. .word用来存放地址
  7. .size设置指定符号的大小
  8. ident编译器标识,无用。生成后位于.comment段中。(注解)

子程序与参数传递

.global 函数名
.type 函数名,%function
函数名:
函数体

ARM汇编规定:R0-R3这4个寄存器用来传递函数调用的1到4个参数,多的用堆栈来传递。R0寄存器用于存放函数返回值

五ARM寄存器

概述

ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。但是这些寄存器不能被同时访问,具体哪些寄存器是可以访问的,取决ARM处理器的工作状态及具体的运行模式。但在任何时候,通用寄存器R14~R0、程序计数器PC、一个状态寄存器都是可访问的。都是32位。

7种状态

ARM手册中的寄存器状态图:

上面为通用寄存器。下面为状态寄存器。
CPU会根据不同的工作模式,使部分寄存器可见(即可被使用),图中有灰色下标的正是不同模式下可见的寄存器,被称为分组寄存器。未分组的寄存器在不同模式下都是可见的,但是在寄存器使用时,分组寄存器会屏蔽共享寄存器,从而实现特殊用途。

详解

通用寄存器

  1. 不分组寄存器(R0-R7) :R0-R7是不分组寄存器。这意味着在所有处理器模式下,访问的都是同一个物理寄存器。不分组寄存器没有被系统用于特别的用途,任何可采用通用寄存器的应用场合都可以使用未分组寄存器。
  2. 分组寄存器(R8-R14) :在不同模式下可能作用不同,且每个寄存器不同状态可能对应不同物理寄存器。(表中的_后缀)。主要用于在中断等的备份。
  3. 程序计数器R15(PC):寄存器R15被用作程序计数器,也称为PC。其值等于当前正在执行的指令的地址+8。

几个特殊的:
1、R13 ——> SP,为堆栈寄存器,用于C语言类程序之间调用所需的空间指针;

2、R14 ——> LR,为连接寄存器,在发生程序调用时,一般用户存放程序返回地址;

3、R15 ——> PC,为程序计数寄存器,存放西一条要执行的程序码地址。

cpu执行过程详解

LR的俩个功能:

  1. 保存子程序返回地址,使用BL或BLX时,跳转指令自动把返回地址放入r14中
  2. 当异常发生时,异常模式的r14用来保存异常返回地址,将r14入栈可以处理嵌套中断。

ARM处理器的三级流水线和多级流水线:

一条汇编指令的运行有三个步骤,取指、译码、执行,当第一条汇编指令取指完成后,紧接着就是第二条指令的取指,然后第三条…如此嵌套
因此一条arm指令为4字节但是却+8
注意存的LR是自动存跳转时+4。所以直接赋给pc就回去了。
但异常时就不一定了,pc要单独计算。

状态寄存器

CPSR在每个异常模式下都有一个对应的物理寄存器——程序状态保存寄存器SPSR。当异常出现时,SPSR用于保存CPSR的值,以便异常返回后恢复异常发生时的工作状态。

条件标志位

N(Negative)、Z(Zero)、C(Carry)及V(oVerflow)统称为条件标志位。大部分的ARM指令可以根据CPSR中的这些条件标志位来选择性地执行。

  1. N:本位设置成当前指令运算结果的bit[31)的值当两个补码表示的有符号整数运算时,N=I表示运算的结果为负数;N=0表示结果为正数或零。
  2. Z:Z=1表示运算的结果为零;Z=0表示运算的结果不为零。对于CMP指令,Z=1表示进行比较的两个数大小相等。
  3. C:在加法指令中(包括比较指令CMN),当结果产生了进位,则C=1,表示无符号数运算发生上溢出;其他情况下C=0。
    在减法指令中(包括比较指令CMP),当运算中发生借位则C=0表示无符号数运算发生下溢出;其他情况下C=1。
    对于包含移位操作的非加/减法运算指令,C中包含最后一次溢出的位数数值。
    对于其他非加/减法运算指令,C位的值通常不受影响。
  4. V:对于加/减法运算指令,当操作数和运算结果为二进制的补码表示的带符号数时V=1表示符号位溢出。通常其他的指令不影响V位.
Q标志位

主要用于指示增强的DSP指令是否发生了溢出。

CPSR中的控制位

CPSR的低8位I、F、T及M[4:0]统称为控制位。当异常中断发生时这些位发生变化。在特权级的处理器模式下,软件可以修改这些控制位。

  1. 当I=1时禁止IRQ中断。
    当F=1时禁止FIQ中断。
  2. 对于ARMv4以及更高版本的T系列的ARM处理器:
    T=0表示执行ARM指令。
    T=1表示执行Thumb指令。
    对于ARMv5以及更高的版本的非T系列的ARM处理器:
    T=0表示执行ARM指令。
    T=1表示强制下一条执行的指令产生未定义指令中断。
  3. 控制位M[4:0]控制处理器模式:

CPSR中的其他位用于将来ARM版本的扩展。

六ARM汇编

ARM处理器寻址方式

寻址:找操作数

  1. 立即寻址:
    mov R0,#123 R0=123
    立即数以#前缀,表示16进制时用0x开头。
  2. 寄存器寻址:
    mov R0,R1 R0=R1
  3. 寄存器位移寻址:
    LSL逻辑左移,LSL逻辑右移,ASR算数(用符号位补)右移,ROR循环右移,RRX带扩展的循环右移
    mov R0,R1,LSL #2 R0=R1*4;
  4. 寄存器间接寻址:
    LDR R0,[R1]
    取地址为R1值上的值。
  5. 基址寻址:
    LDR R0,[R1,#-4]
    带偏移的指针取值
  6. 多寄存器寻址:
    LMDIA R0,{R1-R4}
    R1=[R0] R2=[R0+#4]……
  7. 堆栈寻址:
    LMDFD SP!,{R1-R7,LR}
    针对栈的LMD
  8. 块拷贝寻址:
    LMDIA R0!,{R1-R3}
    !表示带写回
  9. 相对寻址:
    BL NEXT

    NEXT:

    偏移+当前执行PC

ARM指令集

指令和伪指令:前者编译后会生成一串1和0组成的机器码,后者帮助前者进行编译过程,不会生成机器码。
<必须>{可选}
指令格式:助记符{执行条件}{S}{.W|.N}+参1,参2,{参3}
格式说明
助记符:指令助记符
执行条件后缀:

S表示是否影响CPSR寄存器的值
.W.N表示有些代码可以生成的宽度。
参1表示目的,参2,参3表示源

LDR/STR架构
ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理。ldr/str组合用来实现 ARM CPU和内存数据交换。

指令后缀
大部分指令适用
依次:

  1. type 默认为4字节
  2. 条件 默认AL
  3. S 有的默认改变
  4. WN

type有:
B、H:无符号8、16位
SB、SH:有符号8、16位

1.跳转

跳转方式:使用指令或者给pc寄存器赋值

  1. B : B{执行条件}+标号 直接跳
  2. BL : BL{执行条件}+标号 存下一条地址到LR中,然后跳
  3. BX : BX{执行条件}+Rm 判断Rm的位0是否为1,如果1就跳时改变CPSR的T标志位为1否则为0。
  4. BLX : BX+BL

2.存储器访问

ldmia r0, {r2 - r3}

LDR :寄存器<-存储器
STR :寄存器->存储器
批量
LDM、STM可以加后缀大块存储器与多个寄存器传输,其后缀:
ia先传输,再地址+4
ib先地址+4,再传输
da先传输,再地址-4
db先地址-4,再传输
fd满递减堆栈
ed空递减堆栈
fa满递增堆栈
ea空递增堆栈
后四个最后针对栈的主要:
空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出。
满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针。
增栈:栈指针移动时向地址增加的方向移动的栈。
减栈:栈指针移动时向地址减小的方向移动的栈。

!:感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说ldm时会改变r0的值。
^ :在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。

各种后缀以理解为主,不需记忆,最常见的是stmia和stmfd。
谨记:操作栈时使用相同的后缀就不会出错,不管是满栈还是空栈、增栈还是减栈。
ia、ib、da、db都是手动决定加减传输。fd、ed、fa、ea指的是专门4种栈的方式,由stm、ldm自动识别并相反处理。

—————————————————————————
PUSH:将寄存器推入满递减堆栈,可批量
POP:从满递减堆栈中弹出。
SWP:SWP R1,R2,[R3] R3所指到R1,R2赋给R3所指

3.数据处理指令

mov R<-X数据传送
mvn R<-X数据非后传送

add R1,R2,R3 R1=R2+R3
ADC R1,R2,R3 R1=R2+R3+CPSR的C条件位

SUB R1,R2,R3 R1=R2-R3
RSB R1,R2,R3 R1=R3-R2
SBC R1,R2,R3 R1=R2-R3再减去取反的C条件位
RSC R1,R2,R3 R1=R3-R2再减去取反的C条件位

MUL R1,R2,R3 R1=R2R3
MLS R1,R2,R3,R4 R1=R4-R2
R3
MLA R1,R2,R3,R4 R1=R4+R2R3
UMULL R1,R2,R3,R4 (R2,R1)=R3
R4 无符号
UMLAL R1,R2,R3,R4 (R2,R1)=R3R4+(R2,R1) 无符号
SMULL R1,R2,R3,R4 (R2,R1)=R3
R4 有符号
SMLAL R1,R2,R3,R4 (R2,R1)=R3*R4+(R2,R1) 有符号

SDIV R1,R2,R3 有符号除法
UDIV R1,R2,R3 R0=R1/R2无符号除法

ASR算数右移
LSL逻辑左移
LSR逻辑右移
ROR循环右移
RRX带扩展循环右移

AND逻辑与
ORR逻辑或
EOR异或
BIC R1,R2,R3 R3取反与R2与赋给R1 清除指令

CMP R1,R2 R1-R2仅改标志位
CMN R1,R2 R1+R2仅改标志位
TST R1,R2 R1&R2仅改标志位
TEQ R1,R2 R1异或^R2仅改标志位

SWI R0 软中断R0为24位中断号,系统中断为0
NOP 空指令
MRS R1,CPSR读取状态寄存器
MSR R1,CPSR写入状态存储器