align:机器码opcode缩进(align)你会了吗?
-
align:带有一个数字,规定了没条指令或数据的对应的opcode(机器码)的长度
对于数据: 多余的部分补充为NULLs
对于指令: 用NULL指令或者(等效如 MOV EAX,EAX )
数据段
.DATA
var2 BYTE 02; 变量长度为1
ALIGN 4
var2 BYTE 02; 变量长度为1
;对应的的编译器生成代码如下:
.DATA
;------------分割线------------------------------
.data:00402000 var1 db 1
.data:00402001 db 0; 这个NULL是用来强制下一条指令的机器代码长度为4的倍数
.data:00402002 db 0; 这个NULL是用来强制下一条指令的机器代码长度为4的倍数
.data:00402003 db 0; 这个NULL是用来强制下一条指令的机器代码长度为4的倍数
.data:00402004 var2 db 2
代码段
.CODE
;强制第一条指令的机器码长度为4的倍数
ALIGN 4
;入口点
EntryPoint:
MOVZX EAX, var1
MOVZX EAX, var2
MOVZX EAX, var3
;对应的的编译器生成代码如下:
.CODE
;------------分割线------------------------------
.text:00401000 start:
;不能强制缩进入口点的代码对应的机器码长度
.text:00401000 movzx eax, var1
.text:00401007 movzx eax, var2
.text:0040100E movzx eax, var3
;下面的指令是七字节指令,强制第三条指令的机器码长度为4的倍数
MOVZX EAX, var1
MOVZX EAX, var2
ALIGN 4
MOVZX EAX, var3
;这个指令的是1字节长度--opcode(cc)
;强制将其机器码缩进成2字节的倍数
ALIGN 2
INT 3
;------------分割线------------------------------
.text:00401015 movzx eax, var1
.text:0040101C movzx eax, var2
.text:00401023 nop; 这个NOP的填充用来强制第三条指令的机器码为4的倍数
.text:00401024 movzx eax, var3
.text:00401043 nop; 这个NOP的填充用来强制第三条指令的机器码为2的倍数
.text:00401044 int 3 ; Trap to Debugger
.text:00401044; --------------------------------------------------------------------------
- 以句号(period)开头的是汇编程序的提示符(directives)
表示一个循环LOOP
: .L4:
下面这两条指令声明下面的函数使用16位的thumb指令集
.code 16
.thumb_func
- 函数指令
函数调用
:bl
从函数中返回
:bx
函数调用使用的参数存放在r0-r3中,如果超过三个参数,使用堆栈存放参数,并且返回值存放在r0
函数调用完后,r0-r3(r12)被清除,所以想要使用函数调用完得到的值,是需要手动将其保存下来的
r4-r15
:在函数调用时被push到栈中
https://stackoverflow.com/questions/11277652/what-is-the-meaning-of-align-an-the-start-of-a-section
one example:使用GCC来生成汇编程序
使用参数
-S
或者-save-temps
来捕获
指令(instruction) | 解释说明 |
---|---|
ldr 字数据加载指令 |
ldr 目的寄存器,源 ,将源(内存中的数据)存进目的寄存器中 |
str 字数据存储指令 | str {条件} 源寄存器,<存储器地址>,将源寄存器中的数据存入内存地址 |
小知识 | |
r0 | arm的寄存器,寄存器中可以存储数据,并且r0就可以看成存储的数据
|
r11(FP) | 可看做intel架构的EBP,指向栈帧的底部,是一个固定值 |
r13(SP) | 栈指针,可看做ESP,动态的,随着栈的pop,push而改变 |
[r0] | r0存储的数据作为内存地址,[r0]表示这个内存地址
|
LDR例子 | |
ldr r2,[r0] | 将[r0]这个数据存进r2这个寄存器内 |
LDR R1,=0xE0000000 | R1=0xE0000000,立即数赋值,将立即数存进R1寄存器中 |
LDR R1,0xE0000000 | 将内存地址为0xE0000000所指向的数据存入寄存器R1 |
STR例子 | |
STR R1,[R2] | 将寄存器R1中存储的数据存入,内存地址为R2(寄存器R2存储的值为内存地址)的区域 |
小结:mov指令是寄存器之间的数据操作,STR和LDR是寄存器和内存地址之间的操作
非常简单的一个小函数
func1.cpp
(C++)+ARM gcc 5.4(linux)
//从参数num2中得到数据,然后乘以240赋值给参数num1,然后返回参数num1
//实际上参数num1的内容并没有使用,但是就这样分析吧,懒得更改了
int main(int num1, int num2) {
num1 = num2 * 240;
return num1;
}
;------------分割线------------------------------
main:
@@ int main(int num1, int num2) {
str fp, [sp, #-4]! //push {fp},sp-4=fp,保存fp的值(感叹号:先修改寄存器的值再使用)
add fp, sp, #0 //fp=sp,将fp指向sp位置
sub sp, sp, #12 //sp=sp-12,栈指针下移,存储变量
str r0, [fp, #-8] //引入参数数据(r0),将数据赋值给变量num1(栈地址:fp-8)
str r1, [fp, #-12] //变量num2,栈地址:fp-12
@@ num1 = num2 * 240;
@@ 基数左移4位即乘以16,减一个基数即乘以15,在左移4(乘以16),总计乘以15*16=240
ldr r2, [fp, #-12] //r2 = num2
mov r3, r2 //r3=r2=num2
mov r3, r3, asl #4 //r3左移4位,赋值给r3(r3 = r3*16 = num2*16)
rsb r3, r2, r3 //r3 = r3-r2,反向减法(r3=r3*16-r3=num2*15)
mov r3, r3, asl #4 //r3 =num2 *15 *16
str r3, [fp, #-8] //num1 = r3=num2*240
@@ return num1;
ldr r3, [fp, #-8]
@@ }
mov r0, r3 //r0中也存有返回值
sub sp, fp, #0
ldr fp, [sp], #4 //pop {fp}, post-indexed后变,将内存地址的数据赋给fp后,再sp=sp+4
bx lr //返回到下一条指令,LinkPointer存有下一条指令的内存地址
基础功能 Basic Features
- 寄存器
ARM有
16
个32
位的寄存器r0-r15
std | gcc | arm | 描述 |
---|---|---|---|
r0-r3 | r0-r3 | a1-a3 | 函数参数的存放,暂存寄存器scratch registers |
r12 | ip | IP | 和r0-r4同类 |
r4-r8 | r4-r8 | v1-v5 | variable函数调用过程中,存储变量的寄存器 |
r9 | r9 | v6/SB | 特殊平台/variable |
r10 | sl | v7 | variable |
r11 | fp | v8 | varible/帧指针,用于存储栈底地址(EBP) |
r13 |
sp | SP | 栈指针,指向栈顶 |
r14 |
lr | LR | 链接寄存器,存储下一条指令的内存地址 |
r15 |
pc | PC | 程序计数器,随着指令的执行而增加;当执行到子函数时,用来存储当前指令的地址 |
- 指令分类
1、数据操作
,算术和位操作
2、内存操作
,load或者store
3、分支操作
,条件跳转
每个ARM的指令都可以有条件的执行
- 不带条件的指令,不管是否执行都必须内存中读取
@@ r2 = r0>=r1 ? r0:r1;
@@ 如果r0 >= r1,就将r0赋值给r2
@@ 传统汇编
cmp r0, r1 //比较r0,r1
blt .Lbmax //r0<r1 brance less than小于就跳转到.Lbmax
mov r2, r0 //r2 = r0
b .Lrest
.Lbmax:
mov r2, r1
.Lrest
带有条件的汇编
,指令简便、就会快捷
cmp r0, r1
movge r2, r1 //大于等于则执行
movlt r2, r0 //less than小于就执行
指令 | 解释 |
---|---|
sub |
不设置状态寄存器 |
subs |
设置状态寄存器 |
- 桶型位移器(Barrel Shifter)
内嵌的ARM核心组成之一
,所以比任何算数运算都快
任何指令都可以再没有消耗的情况使用桶型位移器移动到其中一个操作数
桶型位移操作 | 注释 |
---|---|
lsl |
logic left shift逻辑左移(同asl算数左移) |
lsr |
logic right shift逻辑右移 |
asr |
arithmetic right shift算数右移 |
ror |
rotate right循环右移 |
逻辑右移和算数右移的区别在于signed/unsigned有符号/无符号详情查看bit ops section
移位寄存器可以用来操作Op2这个操作数
@@r0= r1+(r1<<3) = r1 + r1*8
add r0, r1, r1, lsl #3
受到限制的立即数
op{cond}{s} Rd, Rn, Op2
- ARM每条指令
32
位长度,4位条件代码、4位目的操作数、4位的第一操作寄存器,最后只剩下12
位留给了立即数 - 12位立即数:
8
位数字(n),4
位循环移位字段(r)
立即数
= n ror 2*r
数据指令
- 只能再寄存器中,不能
直接
在内存变量中 - 大多数据指令是有一个目的寄存器Rd,两个操作数Rn,Op2,Rn经常是寄存器,Op2可以是四类(立即数
#n
、寄存器Rm
、带有立即数的移位寄存器Rm ,lsl #n
,带有寄存器的移位寄存器Rm ,lsl Rs
)
-
Arithmetic
算数表,只有加减法。rsb
reverse subtract是特殊的减法,Op2是被减数,并且Op2只允许是立即数和移位寄存器
结尾带有c(carry)的指令表示条件指令,带有进位的指令,允许算数值超过寄存器的大小,例如计算0x00FF+0x0104,因为第二个数超过了8位,需要分步计算,从最低有效位开始
,最低字节位给出,0xFF + 0x04这个加法再c的条件下,允许进位,设置进位标志,计0x03加一个进位标志。然后是第二部分,高位加法0x00+0x01+1最后一个1是进位,计0x02,合计0x203。
QUESTION
- 当程序从子方法返回到父方法时,PC是接着进入子方法之前,还是接着子方法结果来继续?