一 回忆昨天的内容
ARM A R M
cortex-a53 8个cpu 1.4GHz
流水线
三级流水线
F D E
CPI
6周期 6条指令 CPI = 1
6周期 4条指令 CPI = 1.5
arm体系结构与编程
7种工作模式
svc 管理模式
fiq 快速中断
irq 中断
undef 未定义
abort 终止
system 系统
user 用户
前6中属于特权模式 最后一种属于非特权模式
前5种异常模式 后两种正常模式
arm有两种工作模式 arm, thumb
32bit 16bit
arm中有多少寄存器
37
31 通用寄存器
6 状态寄存器
1 cpsr 程序状态寄存器
NZCV 位 F I T
5 spsr 备份cpsr
arm 汇编指令
1. blx 分支跳转指令
b cond
l link 带链接 把下一条指令的地址 ---》LR(r14)
x 带状态切换的
arm ---》 thumb ---》 arm
2. 算数运算指令
add
adc
sub
sbc
3. 数据传输指令
mov {cond} {s} <Rd>, <operand>
operand的三种表现形式:
立即数
通用寄存器
通用寄存器移位之后的结果
4. 移位指令
LSL
LSR
ASR
ROR 循环右移
RRX 带扩展位的循环右移
二 shell编程框架的第一版
获取干净的源码: make clean
编译: make
链接脚本: shell.lds
Makefile
目标:依赖
编译好之后,放在板子上执行:
1. 进入下位机 uboot的命令行
2. 通过tftp 下载 shell.bin
3. 修改开发板的ip:
setenv ipaddr 192.168.1.6
saveenv
修改PC的ip:
setenv serverip 192.168.1.8
saveenv
4. 将虚拟机的ip也设置成192.168.1.8
5. 在下位机ping 上位机
第一次ping 不通不怪你
再ping一次
6. 下载 shell.bin:
tftp 48000000 /tftpboot/shell.bin
执行:go 0x48000000
三 ARM汇编:
3.0
qemu,仿真软件 跨平台的
运行在pc机上,模拟ARM核执行指令的执行过程
qemu 的安装
sudo apt install qemu
1.重新编译程序:
arm-cortex_a9-linux-gnueabi-as add.s -o add.o -g
arm-cortex_a9-linux-gnueabi-ld add.o -o add
2.启动qemu的服务端
qemu-arm -g 1234 add
3.启动客户端调试程序
arm-cortex_a9-linux-gnueabi-gdb add
(gdb) l
(gdb) target remote localhost:1234
(gdb) b 7
(gdb) info reg
(gdb) n
(gdb) info reg r0
(gdb) info reg r1
3.1 位运算指令
and <cond> {s} <Rd>, <Rs> ,<operand> @按位与
and r0, r1, r2 @r0=r1&r2
and r0, r1, #8 @r0=r1&8
and r0, r1, r2, lsl #3 @r0=r1&(r2<<3)
orr <cond> {s} <Rd>, <Rs> ,<operand> @按位或
orrgts r0, r1, r2
eor <cond> {s} <Rd>, <Rs> ,<operand> @按位异或
eor r0, r0, #8 @r0 = r0^8
如何取反32bit整形变量的bit15?
mov r1, #1
eor r0, r0, r1, lsl #15
bic <cond> {s} <Rd>, <Rs> ,<operand>
@第2个操作数对应的bit位中为1,结果为0
@第2个操作数对应的bit位中为0,结果不变
bic r0, r0, #0x08 @将r0中的bit 3位清0
如何清零32bit整形变量的bit7,其他位保持不变?
mov r1, #1
bic r0, r0, r1, lsl #7
3.2 比较测试指令
cmp {cond} <Rn>,<operand>
特点:
1) 不需要+s,默认就影响NZCV位
2) 操作结果不保存
cmp r1, #0x10 @alu_out = r1 - 0x10
@r1 > 0x10 C = 1
@r1 == 0x10 Z = 1
@r0 < 0x10 C = 0
tst {cond} <Rn>,<operand>
tst r0, #0x08 @测试 r0的bit3 是否为0
@ alu_out = r0 & 0x08
@ 如果为0 , Z = 1
@ 如果非0 , Z = 0
teq {cond} <Rn>,<operand>
teq r0, #0x08 @ 测试r0 和 0x08是否相等
@ alu_out = r0 ^ 0x08
@ 相等 Z = 1
@ 不相等 Z = 0
练习: 求两个数的最大公约数:
128 24
算法: 104 24
80 24
56 24
32 24
8 24
16 8
8 8
3.3 加载存储指令
ARM 中所有的运算都是在通用寄存器中完成的
加载指令:将数据由内存加载到寄存器中!
操作的对象是:
通用寄存器
r0 ~ r15
mov r1, 0x48000000
ldr r0, [r1] @将内存0x48000000开始的4字节数据加载到r0中
@ r0 = *((int *) 0x48000000)
ldrb r0, [r1] @将内存0x48000000开始的1字节数据加载到r0中
@ r0 = *((unsigned char *) 0x48000000
ldrsb r0, [r1] @将内存0x48000000开始的1字节数据加载到r0中,
r0中的高24bit补符号位
ldrh r0, [r1] @将内存0x48000000开始的2字节数据加载到r0中,
r0的高16位补0
ldrsh r0, [r1] @将内存0x48000000开始的2字节数据加载到r0中,
r0的高16位补符号位
ldr r0, [r1] @[r1] ---> r0
----------------------------------
ldr r0, [r1, #0x08] @ [r1+0x08] ----> r0
ldr r0, [r1+r2] @ [r1+r2] ----> r0
ldr r0, [r1,r2,lsl #2] @ [r1 + r2<<2] ---> r0
----------------------------------
ldr r0, [r1, #0x08]! @ [r1+0x08] ----> r0 r1=r1+0x08 加!会更新基址
ldr r0, [r1, r2]! @ [r1+r2] ----> r0 r1=r1+r2
ldr r0, [r1,r2,lsl #2]! @ [r1+r2*4] ---->r0 r1=r1+r2*4
----------------------------------
ldr r0, [r1], #0x08 @ [r1] ----> r0 r1= r1+0x08
ldr r0, [r1], r2 @ [r1] ----> r0 r1= r1+r2
ldr r0, [r1], r2, lsl #3 @ [r1] ----> r0 r1=r1+r2*8
----------------------------------
存储指令:
str 从寄存器 ---》 内存
mov r1, 0x48000000
str r0, [r1] @将r0在的4个字节的数据写入到0x48000000的内存中
strb r0, [r1] @将r0在的(低8位)1个字节的数据写入到0x48000000的内存中
strh r0, [r1] @将r0在的(低16位)2个字节的数据写入到0x48000000的内存中
str r0, [r1] @r0 ----> r1
str r0, [r1+0x08] @r0 ----> r1+0x08
str r0, [r1,r2] @r0 ----> [r1+r2]
str r0, [r1,r2,lsl #2] @r0 ----> [r1+r2*4]
-------------------------------------------------
str r0, [r1+0x08]! @r0 ----> [r1+0x08] r1=r1+0x08
str r0, [r1]!, r2 @r0 ----> [r1+r2] r1=r1+r2
str r0, [r1]!, r2, lsl #2 @r0 ----> [r1+r2*4] r1=r1+r2*4
-------------------------------------------------
str r0, [r1], #0x08 @r0 ----> [r1] r1=r1+0x08
str r0, [r1], r2 @r0 ----> [r1] r1=r1+r2
str r0, [r1], r2, lsl #2 @r0 ----> [r1] r1=r1+r2*4
-------------------------------------------------
练习: memcpy,将内存0x8e00开始的64字节的数据拷贝到0x8f00开始的内存空间。
参见代码 memcpy.s
3.4 栈操作指令:
注意:满栈和空栈指的并不是栈里面的元素是否满了,
指的是栈指针上面或者下面的空间中有没有数据
或者说,接下来要存数据的时候,先放数据还是先改变sp的值
ldmxx sp!,{r0~r5, r8}
@将sp指向的栈空间的数据加载到 r0, r1, r2,r3,r4,r5,r8中
stmxx sp!, {r0~r5,r8}
@将r0,r1,r2,r3,r4.r5,r8中的数据加载到sp指向的栈空间中
xx,
FD
FA
ED
DA
stmfd sp!, {r0~r5,r8} 等价于 push {r0~r5,r8}
ldmfd sp!, {r0~r5,r8} 等价于 pop {r0~r5, r8}
按满减栈压入栈,也按满减栈弹出数据,就可以恢复寄存器
局部变量如何在栈里面分配空间
C 语言的运行是离不开栈的!
1) 局部变量在栈中分配空间
2) 函数嵌套调用时lr需要压入栈
代码参见 test.s
arm-cortex_a9-linux-gnueabi-gcc test.c -o test
arm-cortex_a9-linux-gnueabi-objdump -S test > 1.asm
fun1 ()
{
b++;
}
fun ()
{
push {fp, lr}
fun1 (); //bl fun1 ---> lr = a++;
a++;
pop {lr}
mov pc,lr
pop {pc}
}
main ()
{
fun ();//bl fun ---> lr = i++;
i++;
}
3.5 伪指令
3.5.1 ldr 伪指令
伪指令本身不被CPU所识别,
但是能被汇编器编译成一条或者多条cpu所识别的指令
实现了一个小功能
ldr r0, [r1] //汇编指令 [] 有中括号是加载指令
没有[] 是伪指令
.code 32
.global _start
.text
_start:
@mov r0, #0x1ff //作业
ldr r0, =0x1ff
ldr r0, [pc]
add r3, r4, r5
b .
.word 0x1ff
.end
ldr r0, =0x1ff
ldr r1, =test @将立即数本身放到r1
ldr r1, test @将立即数指向的内存中的数据放到r1
3.5.2 nop 伪指令
mov r0, r0
一般用于延时
3.5.3 adr 伪指令
小范围的地址加载指令
忽略
3.6 伪操作
汇编文件中 以“.” 开头的都是伪操作
伪操作是给汇编器使用的,一旦编译完成就消失了
.text
.data
.bss
.equ
.equ NUM, #20 @类似于C语言中的宏
.global
.extern
.byte
.word
.space 1024 @分配1024个字节的空间
.string
.string "abcdefg"
.ascii
.code 32 ==== .arm
.code 16 ==== .thumb
.section
练习:用汇编实现strcmp
r0 <-- "abcefg" ldrb r2,[r0],#1
r1 <-- "aabced" ldrb r3,[r1],#1
cmp r2,r3
beq
阅读uboot代码