程序入口: _start
c 语言入口: main
@: 注释;
main: 标签;
伪指令: 给汇编器读的指令;
.global main 导出符号main;
.section .text 申明以下内容存放在代码段;
.section .rodata..................只读段;
.section .data ..................数据段;
.section .bss ..................bss段;
.string "hello" 申明后面的内容是带尾0的字符串;
.ascii "hello" 申明后面的内容是不带尾0的字符串;
.align 2 后面有效内容地址必须的2^2(4)的倍数;
.align 3 ......................2^3(8)......;
.align
1) 提高数据的访问效率;
内存硬件; 32bit一个数据块;
2) 精简指令集(定长指令集): arm thumb
arm : 32bit 每条指令长度固定,
thumb : 16bit/32bit
1. cpu解析更快;
2. 指令也更简单;
3. 更容易学习;
4. 一个复杂的操作需要多条指令完成;
复杂指令集(不定长指令集): x86
x86 : 每一条指令长度不定;
1. 一个复杂的操作只是需要一条指令完成;
寄存器: 通用寄存器, 特殊寄存器;
通用寄存器: 位于arm 核内部, r0-r15描述;
ATPCS调用规范: 规定:
r15 -> pc;
pc 程序指针;
r14 -> lr;
lr 用于保存函数调用返回地址;
r13 -> sp
sp 栈指针; 指向栈底部;
规定:
r0 作为返回值使用;
规定:
传参:小于等于4个参数, 分别使用r0 r1 r2 r3;
大于4个参数, 大出4 个的参数使用内存栈传递;
指令:
分支指令:
b 跳转;
b label
bl 函数调用;
bl label mov lr, pc
b label
bx 跳转;
bx rd rd代表目标寄存器;
数据处理指令:
mov =
mvn =~
lsl <<
asl <<
lsr >>
asr >>
ror
rrx
add +
adc
sub -
sbc
rsb 反-
rsc
and &
orr |
eor ^
bic &~
mov 移动;
mov rd, #num rd代表目标寄存器;
rd = num;
#num : 立即数;(数字常量);
合法立即数:
可以由一个8位数, 循环右移偶数位得到的立即数;
mov rd, rn rd代表目标寄存器;
rn代表源寄存器;
rd = rn;
mov rd, #(10 - 20 * 2 / 20) #(10 - 20 * 2 / 20)是编译器翻译
rd = 10 - 20 * 2 /20
mov rd, rn, LSL ri rd = rn << ri
mov rd, rn, LSL #10 rd = rn <<10 #10 在0-31之间
^ 逻辑左移
LSR 逻辑右移
ASL 算术左移
ASR 算术右移 符号位为1,右移补1
0 0
ROR 32bit 数据循环右移
RRX C+32bit数据循环右移1 位
lsl
lsl rd, rn, #10
同于
mov rd, rn, lsl #10
lsr
asl
asr
ror
rrx
同上
mvn
mvn rd,#num rd = ~num
0x0 -> 0xffffffff
0xff000000 -> 0x00ffffff
0x00ff0000 -> 0xff00ffff
add
add rd, rn, rm rd = rn + rm
rd代表目标寄存器;
rn代表源寄存器;
rm代表操作数;
add rd, rn, #10 rd = rn + 10
add rd, #10, rn X
add rd, rn, #(10 - 4 / 2 * 7)
add rd, rn, rm, LSL ri
add rd, rn, rm, LSL #20
asl
lsr
asr
ror
rrx
add rd, rd, #1
add rd, #1 rd ++;
add rd, #10 rd += 10
add rd, rn rd += rn
sub
sub rd,rn, rm rd = rn - rm
sub rd,rn, #12
同上
rsb
rsb rd,rn, rm rd = rm - rn
rsb rd,rn, #100 rd = 100 - rn
同上
and 测试某一位是否为1, 或者清除某一个位;
and rd, rn, rm rd = rn & rm
and rd, rn, #100 rd = rn & 100
and rd, rn, #(1<<8) rd = rn & (1<<8)
and rd, rn, rm, LSL #8
orr 某一位置1;
orr rd,rn, rm rd = rn | rm
orr rd, rn, #(1<<5) rd = rn | (1<<5)
orr rd, rn, #20 rd = rn | 20
orr rd, rn, rm, lsl #4
rd = rn | (rm<< 4)
eor 反转某些位;
eor rd,rn, rm rd = rn ^ rm
eor rd,rn, #(1<<5)
eor rd,rn, #10
eor rd,rn, rm, LSL #2
bic 清除某一位;
bic rd,rn, rm rd = rn & (~rm)
bic rd,rn, #(1<<3)
bic rd,rn, #20
bic rd,rn, rm, LSL #4
乘法指令:
mul
mul rd,rn, rm rd = rn * rm
mul rd,rn, #100 X
div armv6 X
armv7 V
内存访问指令:
ldr
ldr rd, =label rd = label
push
push {rn, rm, ri, ...} 把rn rm ri压到栈中;
pop
pop {rn, rm, ri, ...} 把rn rm ri从栈中取出来;
练习:
1. printf("val = %d \n",1023 * 23 - (34 + 12) >> 3 | (1<<14) + 3487 & (0x37<<12)^ 0xff);
2. r1 = 0xff;
把r1第3 位清零;
3. r1 = 0xff;
把r1第3 5 7位清零;
4. r1 = 0xf0;
把r1第3 位置1;
5. r1 = 0xff00;
把r1第3 5 7位置1;
6. r1 = 0xff37;
把r1第3~12位设置成0x327;
7. r1 = 0xff37;
把r1第3~12位设置成取反;
8. r1 = 0xff37
把r1第3~12位打印出来;
================================================
CPSR: 当前程序状态寄存器 (user模式下只能修改高5位)
[31] [30] [29] [28]
N Z C V
杂指令:
mrs (与平台相关指令)
mrs rd,cpsr rd = cpsr
msr
msr cpsr,rd cpsr = rd
数据处理指令:
adc: 同add
adc rd, rn, rm rd = rn + rm + Cbit
^进位
...
sbc: 同sub
sbc rd,rn, rm rd = rn - rm - 1 +Cbit
...
rsc: 同rsb
rsc rd, rn, rm rd = rm - rn - 1 + Cbit
...
指令+s 后缀
表示该指令会更新cpsr;
========================================
cmp
cmp rn,rm
等同于:
subs rd, rn, rm rd > 0
rd < 0
rd == 0
rd != 0
N Z
tst 测试某个位是否为1
tst rn, rm
等同于
ands rd, rn, rm
teq 测试两个数是否相等
teq rn,rm
等同于
eors rd, rn, rm
cmn 测试两个数想加是否有进位
cmn rn, rm
等同于
adds rd, rn, rm
1.顺序结构
2.分支结构
if(...)
....
if(...) > < == !=
...
else
...
if(...)
... b out
else if(...)
... b out
else if(...)
... b out
else
...
switch()
{
case: eq
....;
break; b out
case: eq
....;
break; b out
case: eq
....;
break; b out
}
1 23 257 123 1098 76 13 3
1 3 13 23 76 123 257 1098
1) 二分查找;
132 120 117 123 128 134 137 121
117 120121 123 128 132 134 137
2) 哈希表;
-117
0 1 2 3 4 5 6 7 ... 11 15 17 20
do1 outout do2 do3 out do4 out do5 do7 do8do9
3.循环结构
while(1) 1: b .(.表示当前地址)
; b 1b
while(...) 1: cmp ....
...; b 1b
for(...; ...; ...) mov ...
... 1: cmp ...
b 1b
1:
do{ xxxx
... xxxx
}while(...); cmp ...
b 1b
label:
goto label
arm 指令的条件执行:
mov == moval
addlt ==> 小于条件成立执行
后缀决定条件:
al 无条件执行;
ne != Z = 0
eq == Z = 1
lt < N
le <= N Z
gt > N
ge >= N Z
homework:
1. if( i > 10 && i <20)
printf("i > 10 &&i < 20\n");
else if( i > 20 && i <60 )
printf("i > 20 &&i < 60\n");
else if( i > 60 && i <90 )
printf("i > 60 &&i < 90\n");
else if( i < 10 || i > 90)
printf("other\n");
(注意: cmp后不要加条件)
2. 输出1-100的奇数;
3. 输出100个随机数; random();
4. 输出100个随机奇数;
5. 输出100个256内随机奇数;
6. 9*9 乘法表;
7.
高从1 到 10的三角; **********
^ ^ ^
*
**
***
**** 高为4;
***
**
*
8.
半径从1 到 10的菱形;
*
***
*****
******* 半径为4 ;
*****
***
*
代码效率:
if(...)
for()
...
else
for()
...
for()
{
if()
...
else
...
}
指令的执行:
取出指令 翻译指令 执行指令
取指 译码 执行
(条件判断)
流水线:
mov 3T
sub 1T
add 1T
mov 1T
nop 1Tpc
b V 1T
nop V
nop V
nop
for(1000w)
... 3000w
for(1000w)
... 1000w+2
for(i = 0; i < 1000w; i++)
sum ++; 3000w 流水线没有被使用到;
循环展开:
for(i = 0; i < 100w; i++) 1200W
{
sum++; 3T 12T
sum++; 1T
... 10次; ...
sum++; 1T
sum++; 1T
}
CACHE: 缓存;
取指快了;
分支预测:
静态预测;
动态预测;
减少分支指令下一条和下下一条指令的取指和译码;
if(...)
1000w
else
1000w
分支汇编效率:
cmp xxx, xxx
addeq
subeq
muleq 浪费了1000w个取指译码周期;
lsleq
.... 1000w
xxxne
xxxne
xxxne
xxxne
.... 1000w
以下效率更高:
cmp xxx, xxx
beq do1 多了两条指令;
bne do2
do1:
... 1000w
do2:
... 1000w
内存拷贝效率:
for(i = 0; i < 1000w; i++)
{
br[i] = ar[i]; ldr r0, [r5, r4, lsl #2]; str r0, [r6, r4, lsl #2]
}
for(i = 0; i < 100w; i++)
{
ldr
str
... 10个ldr str
ldr
str
}
for(i = 0; i < 100w; i++)
ldm
stm 1个拷贝10个数据
硬件拷贝: (没有代码)
DMA 直接内存访问;
内存访问指令:
ldr 取
ldr rd, [rn] rd = *(unsigned int *)rn
ldr rd, [rn, #(1+2-1*2+3)]
ldr rd, [rn, #4] rd = *(unsigned int *)(rn + 4)
^ #4范围 -4K ~ 4K
ldr rd, [rn, rm] rd = *(unsigned int *)(rn + rm)
ldr rd, [rn, rm, lsl #2]
rd = *(unsignedint *)(rn + (rm << 2))
ldr rd, [rn], #4 rd = *(unsigned int *)(rn)
rn += 4;
ldr rd, [rn, #4]! rd = *(unsigned int *)(rn + 4)
rn += 4;
ldrh rd, [rn] rd = *(unsigned short*)rn
ldrb rd, [rn] rd = *(unsigned char *)rn
伪指令:
ldr
1.
ldr rd, =.LC0 rd = .LC0 访问的标签.LC0可以跨段;(绝对地址)
==> 跨文件;
ldr rd, [pc, #?]
... ^ 汇编器计算所得;
.word .LC0
^ 连接器替换成具体的地址;
2.
ldr rd, .L0 rd = *(u32*)(.L0) 访问的标签.L0不能跨段;(相对地址)
... 不能跨文件;
.L0:
.word .LC0
==>
ldr rd, [pc, #?]
... ^ 汇编器计算所得;
^ -4K ~ 4K
.L0:
.word .LC0
3.
adr rd,.L0 rd = .L0 访问的标签.L0不能跨段;(相对地址)
不能跨文件;
=>
add rd, pc, #?
-?
^ 汇编器计算所得;
str 存
str rd, [rn] *(unsigned int *)rn = rd
str rd, [rn, #(1+2-1*2+3)]
str rd, [rn, #4]
str rd, [rn, rm]
str rd, [rn, rm, lsl #2]
str rd, [rn], #4
str rd, [rn, #4]!
strh
strb
atpcs: 规定
r13 -> sp
sp 栈指针, 指向栈底;
满递减栈
开辟num 字节空间: sub sp, sp, #num
释放num 字节空间: add sp, sp, #num
练习:
1. int i;
int ar[100];
for(i = 0; i < 100; i++)
ar[i] = i;
for(i = 0; i < 100; i++)
printf("ar[%d] =%d\n", i, ar[i]);
2. inti;
int ar[100];
int br[100];
srandom(getpid());
for(i = 0; i < 100; i++)
ar[i] = random() % 256;
for(i = 0; i < 100; i++)
br[i] = ar[i];
for(i = 0; i < 100; i++)
printf("ar[%d] = %d br[i]= %d\n", i, ar[i], i, br[i]);
3. int i;
int ar[100];
int br[100];
srandom(getpid());
for(i = 0; i < 100; i++)
ar[i] = random() % 256;
for(i = 0; i < 100; i++)
br[i] = ar[i];
qsort(br, 100, 4, cmp_int);
for(i = 0; i < 100; i++)
printf("ar[%d] = %d br[i]= %d\n", i, ar[i], i, br[i]);
4. char *p = "hello world\n";
char str[mystrlen(p) + 1];
mystrcpy(str, p);
printf(str);
内存访问指令:
ldr str ldm stm
ldr:
cpu -> AMBA -> ADDR -> dramc -> BANK -> RAW -> COL ->DDR3
<- <-DATA
ldr:
cpu -> addr -> dramc -> ADDR -> DDR3
<- <- DATA
ldr:
cpu -> addr -> dramc -> ADDR -> DDR3
<- <- DATA
ldr:
cpu -> addr -> dramc -> ADDR -> DDR3
<- <- DATA
str:
ldm:
cpu -> addr -> dramc -> ADDR -> DDR3
<- <- DATA 突发访问;
<- <- DATA
<- <- DATA
<- <- DATA
stm:
f : 满
e : 空
d : 递减
a : 递增
stmfd stmfa stmed stmea
ldmfd ldmfa ldmed ldmea
满递减栈 满递增栈 空递减栈 空递增栈
^
ATPCS
push ==> stmfd sp!, {...}
pop ==> ldmfd sp!, {...}
stmfd sp!, {lr} *(u32 *)(sp - 4) = lr;!: sp -= 4;
stmfd sp, {lr} *(u32 *)(sp - 4) =lr;
- 4
递减: -
减后再存: 满栈;
ldmfd sp!, {pc} pc = *(u32 *)sp; !: sp+= 4;
ldmfd sp, {pc} pc = *(u32 *)sp;
f: stm:先加减, 再存;
ldm:先取, 再减加;
e: stm:先存, 再加减;
ldm:先减加, 再取;
d: stm:递减;
ldm:递增;
a: stm:递增;
ldm:递减;
i : 增;
d : 减;
a : 后;
b : 先;
ib : 先增;
ia : 后增;
db : 先减;
da : 后减;
stmib : 先增, 后存; (stmfa)
stmia : 先存, 后增; (stmea)
stmdb : 先减, 后存; (stmfd)
stmda : 先存, 后减; (stmed)
ldmib : 先增, 后取; (ldmed)
ldmia : 先取, 后增; (ldmfd)
ldmdb : 先减, 后取; (ldmea)
ldmda : 先取, 后减; (ldmfa)
ATPCS:
r15 : pc 程序指针, 指向要取指的指令;
r14 : lr 返回地址;
r13 : sp 栈指针, 指向栈底;
r12 : ip 临时寄存器; 临时保存sp;
r11 : fp 幀指针寄存器; 当前函数栈内数据访问; 恢复sp;
[fp,#-4] [fp, #-8]
r0-r3 参数<=4;参数>4 (sp);
r0 返回值;
push -> stmfd
pop -> ldmfd
====================================================
上层开发:
策略;
底层开发:
机制;
tiny4412 EXYNOS4412 ARM CORTEX-A9
友善之臂 三星soc ARMV7 ARMV6
查手册查找指令用法:
arm: armv1 armv2 armv3armv4
32bit
mov r0, r1
4 4
thumb: ..................thumbv1
16bit
add r1,r0, #6
ARM --> THUMB
THUMB --> ARM
bx r0 r0[0] = 1 : ---> THUMB
r0[0] = 0 : --->ARM
blx
相对跳转:
bl printf (相对于当前pc跳转+/-32M地址)
绝对跳转:
mov lr, pc
ldr pc, =printf (直接对pc赋值, 跳转范围是32bit 地址)
homework:
(1)通过手册找指令用法, 并且转换二进制: arm-linux-gcc -c
1.
cpylt r1,r3
10110001101000000001000000000011
2.
revgt r1,r2
11000110101111110001111100110010
3.
smlalbtlt r4, r3, r1, r2
10110001010000110100001011000001
(2)通过二进制找到汇编指令: arm-linux-objdump-D
4.
0x00000010
andeq r0, r0, r0, lsl r0
5.
0xe28fc600
1110 0010 1000 1111 1100 0110 00000000
adr ip, 0x0
add ip, pc, #0x0
(3)汇编实现以下代码:
1. 打开一个文件passwd;
2. 把文件中每一行数据进行排序;
if(d1 > d2)
return 1;
3. 把文件中所有行数据进行排序;
if(strcmp(str1, str2) >0)
return 1;
4. 写回到文件中;
4)汇编实现单链表/双向链表;