mov r1, r2 ;将r2里面的值复制到r1中
mov r1, #4096 ;常数必须用立即数表示
当不知道数是否是"立即数"来表示时,可以使用ldr来赋值。ldr是伪指令, 由编译器会把它扩展成真正的指令。
如果是立即数用mov指令替换,否则 编译时将改常数保存在某个位置,使用内存读取指令把它读出来。
ldr 本意为大范围的地址读取伪指令
ldr r1, =4096 ; 把常量赋给寄存器
ldr r2, =label ; 获取代码的绝对地址
label:
...
movs : 数据传输,并且影响z条件标志位,如果目标寄存器为0,则z条件标志位为1,
接下来beq就成立,如下:
movs r10, r5 ; invalid processor (r5=0)?
beq error_p ; yes, error 'p'
error_p:
...
ldr的第二个参数前面是"="时,表示伪指令,否则为内存访问指令
ldr r0, [r1] //从r1中的存储器地址处读取一个字,然后放入到r0中
str r0, [r1] //把r0的值写入寄存器r1所指向的地址中。
‘!’ : 表示指令执行后寄存器被更新。
stmia r0!, {r2-r7} //将r2-r7的数据存储到r0指向的地址,r0值更新
ldmia r0!, {r2-r7} //加载r0指向的地址上的多字数据,保存到r2-r7中,r0的值更新。
IA 执行后增加 (increase after), 这时对应的{r2-r7},赋值的顺序是r2~r7
IB 执行前增加 (increase before)
DA 执行后减少 (decrease after), 这时对应的{r2-r7}, 赋值的顺序是r7-r2
DB 执行前减少 (decrease before)
ldm ldr 都是往寄存器里面load,ldm是内存往寄存器组,ldr是寄存器往寄存器。
stm str 都是往内存里面loader,stm是寄存器组往寄存器所指向内存,str是寄存器往寄存器所指向内存。并且stm str 语法位置相反。
str 源寄存器, [目标寄存器]
stm目标内存首地址, [源寄存器组]
ldr 目标寄存器, [源寄存器] or 立即数 //源寄存器里面的值给目标寄存器
ldm 源内存首地址, [目的寄存器组]
add r1, r2, #1 ; r1=r2+1
sub r1, r2, #1 ; r1=r2-1
msr cpsr, r0 ; 赋值r0的值到cpsr
mrs r0, cpsr ; 赋值cpsr的值到r0
注:cpsr_cxsf
c : controlfield mask byte (PSR[7:0])
x : extension field mask byte (PSR[15:8])
s : status field mask byte (PSR[23:16)
f : flags field mask byte (PSR[31:24]).
如果加后缀,那么就代表只操作这一个区域,其他的不操作,避免对某些位操作而影响其他位。
如msr cpsr_c,#0xdf //只操作control field
ldr r1, =0b1111 ; mov r1, #0b1111
ldr r2, =0b0101 ; mov r2, #0b0101
bic r0, r1, r2 ; r0 = 0b1010
ldr r1, =0b1111 ; mov r1, #0b1111
ldr r2, =0b0101 ; mov r2, #0b0101
orr r0, r1, r2 ; r0 = 0b1111
//adr读当前运行地址,以uboot为例,如果是从norflash启动, _start为nor flash首地址,为0x0,那么r0 = 0x0
//如果是通过仿真器dump到sdram中的,那么就是链接地址,0x33F80000
adr r0, _start ; r0 <- current position of code
通过下面一段代码分析ldr,adr的区别
/*
* start.s
*/
_start: nop
nop
ldr r0, _start
adr r1, _start
ldr r2, =_start
nop
mov pc, lr
/*
* link.lds
*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x0c000080;
. = ALIGN(4); .text : { *(.text)}
. = ALIGN(4); .rodata : { *(.rodata)}
. = ALIGN(4); .data : { *(.data) }
. = ALIGN(4); .bss : { *(.bss) }
}
/*
* 编译,链接,反汇编
*/
#arm-linux-gcc -c start.s -o start.o通过上面的反汇编得出如下结论:
ldr r0, _start ; <链接时确定>从内存地址 _start 的地方把值读入。执行这个后,r0 = 0xe1a00000
adr r1, _start ; <运行时确定>取得_start的地址到 r0,但是请看反编译的结果,它是与位置无关的, 其实取得的是相对的位置。
; (位置无关码请查验《常用交叉编译工具整理》一篇, arm-linux-ld部分)
; 例如这段代码在 0x0c008000 运行,那么 adr r0, _start 得到 r0 = 0x0c008000;如果在地址 0 运行,就是 0x00000000 了。
ldr r2, =_start ; <链接时确定>取得标号_start 的绝对地址。
; 这条指令看上去只是一个指令,但是它要占用 2 个 32bit 的空间,一条是指令,另一条是 _start 的数据
; 因为在编译的时候不能确定 _start 的值,而且也不能用 mov 指令来给 r0 赋一个 32bit 的常量,所以需要
;多出一个空间存放 _start 的真正数据,在这里就是 0x0c008014, 因此可以看出,这个是绝对的寻址,
; 不管这段代码在什么地方运行,它的结果都是 r0 = 0x0c008014
Q Saturation the result causes an overflow and/or saturation
V oVerflow the result causes a signed overflow
C Carry the result causes an unsigned carry
Z Zero the result is zero, frequently used to indicate equality
N Negative bit 31 of the result is a binary 1
如果成立为1,大写:
Loop: mov r0, #4 //比较前 cpsr = nzcvqiFt_USER
mov r1, #4
cmp r0, r1 //比较后 cpsr = nZcvqiFt_USER
bne loop //如果cpsr中的z位是小z就跳到loop,否则不执行,继续往下。明显是继续往下执行
mov pc, lr //所以会执行这句话
Loop: mov r0, #4 //比较前 cpsr = nzcvqiFt_USER
mov r1, #4
teq r0, r1 //比较后 cpsr = nZcvqiFt_USER
beq exit //如果cpsr中的z位是大Z就跳到exit,否则不执行,继续往下。明显是调到exit
b loop //
exit : mov pc, lr //所以会执行这句话
//表示引用的main是一个外部函数
.extern main //比如_start是全局变量
.global _start_start:
.equ MEM_CTL_BASE,0x48000000 //定义MEM_CTL_BASE
;定义以下为4字节对齐
.align 4 .long 0x00000030 ;将0x00000030值放于当前位置
.word,.int, .short, .byte类似
stmia r0, {sp}^ ;保存user mode下的sp 到 r0
stmia r0, {lr}^ ;保存user mode下的lr 到 r0
ldmia sp!, {r0-r12, pc}^ ;中断返回,^表示将spsr的值copy到cpsr中。
区别:
当执行多寄存器加载操作时(ldmxx)带有”^”, 如果含pc :表示该指令在执行完毕后,实现程序跳转同时恢复spsr 到 cpsr.
如果不含pc :表示内存加载到用户模式下寄存器。
当执行多寄存器存储操作时(stmxx)带有”^”, 表示将user mode下寄存器列表的值存储到内存中,与寄存器列表中有无pc无关。
r15 即 pc; r14即lr; r13即 sp
descending栈 : 递减栈
ascending栈 : 递增栈
FULL栈 : 栈指针指向栈顶元素(最后一个入栈的数据)
EMPTY栈 : 栈指针指向栈顶元素(最后一个入栈的数据)相邻的一个空的数据单元
即组合成 : FD EDFA EA
ATPCS规定数据栈为FD类型,数据栈是8字节对齐,
stmdb:往数据栈中保存内容,先递减sp指针,再保存数据
ldmia:从数据中恢复数据时,先获得数据,再递增sp指针
当参数个数不超过4个时,使用r0~r3这4个寄存器来传递参数;如果超过4个,剩余的参数通过数据栈来传递; 返回结果,通常使用a0~a3来传递
如CopyCode2SDRAM函数是用C语言实现的, 类型如下:int CopyCode2SDRAM(unsigned char *buf, unsigned long start_addr, int size);
在汇编代码中,使用下面的代码调用它,并判断返回值:
ldr r0, =0x30000000 // 1. 目标地址=0x30000000,SDRAM的起始地址
mov r1, #0 // 2. 源地址 = 0
mov r2, #16*1024 // 3. 复制长度 = 16K
bl CopyCode2SDRAM // 4. 调用C函数CopyCode2SDRAM
cmp a0, #0 // 5. 判断函数返回值
beq XXX