工作关系,必须看u-boot启动的第一阶段,所以就得看汇编了。
看了一天,感觉还行,下面是几篇比较好的文章,
|
文件: |
ARM_zhiling.pdf |
大小: |
1775KB |
下载: |
下载 | |
|
文件: |
Assembly_In_Linux.pdf |
大小: |
287KB |
下载: |
下载 | |
|
文件: |
AT&T_GCC_ASM.pdf |
大小: |
156KB |
下载: |
下载 | |
无论是体系结构还是指令集,大家或多或少都应该对X86汇编有些了解,而对于嵌入式领域已被广泛采用的ARM 处理器,了解的可能并不多。如果你有兴趣从事嵌入式方面的开发,那么了解一些RISC 体系结构和ARM汇编的知识还是有必要的。这里,我们找出了这两种体系结构最明显的不同之处,并对此进行介绍,让大家对于RISC体系结构的汇编有一个基本的了解。首先,我们就来看一看基于RISC的ARM的体系结构。
基于RISC 的ARM CPU
ARM是一种RISC体系结构的处理器芯片。和传统的CISC体系结构不同,RISC 有以下的几个特点:
◆ 简洁的指令集——为了保证CPU可以在高时钟频率下单周期执行指令,RISC指令集只提供很有限的操作(例如add,sub,mul等),而复杂的操作都需要由这些简单的指令来组合进行模拟。并且,每一条指令不仅执行时间固定,其指令长度也是固定的,这样,在译码阶段就可以对下一条指令进行预取。
◆ Load-Store 结构——这个应该是RISC 设计中比较有特点的一部分。在RISC 中,CPU并不会对内存中的数据进行操作,所有的计算都要求在寄存器中完成。而寄存器和内存的通信则由单独的指令来完成。而在CSIC中,CPU是可以直接对内存进行操作的,这也是一个比较特别的地方。
◆ 更多的寄存器——和CISC 相比,基于RISC的处理器有更多的通用寄存器可以使用,且每个寄存器都可以进行数据存储或者寻址。
当然,作为RISC 领域最成功的处理器,ARM也遵从上面的特点。这里,我们不妨来看一看在user 模式下,ARM处理器的体系结构,这对于我们了解其汇编语言是有好处的。而其它模式下只是有一些寄存器分组略有不同,大家可以在ARM的手册上查到。这里要说明的是,尽管ARM处理器也支持16位指令,不过在下文中,我们都假定ARM处理器在32 位模式下工作。
图1:user模式下ARM处理器体系结构
从图1中我们看到,在user 模式下,ARM CPU 有16个数据寄存器,被命名为r0~r15(这个要比x86的多一些)。r13~r15有特殊用途,其中:
◆ r13 - 指向当前栈顶,相当于x86的esp,这个东西在汇编指令中要用sp 表示
◆ r14 - 称作链接寄存器,指向函数的返回地址。用lr表示,这和x86将返回地址保存在栈中是不同的
◆ r15 - 类似于x86的eip,其值等于当前正在执行的指令的地址+8(因为在取址和执行之间多了一个译码的阶段),这个用pc表示
另外,ARM处理器还有一个名为cspr的寄存器,用来监视和控制内部操作,这点和x86 的状态寄存器是类似的。具体的内容就用到再说了。
ARM 指令集
ARM处理器可以支持3种指令集——ARM,Thumb和Jazelle。
采用那种指令集,由cspr中的标志位来决定。大体说来:
◆ ARM——这是ARM自身的32 位指令集
◆ Thumb ——这是一个全16 位的指令集,在16 位外部数据总线宽度下,这个指令集的效率要比32 位的ARM指令高一些。
◆ Jazelle ——这是一个8位指令集,用来加速Java字节码的执行
整个ARM指令集由数据处理指令、分支指令、Load-Store指令、程序中断指令和一些系统控制指令构成,除了Load-Store指令外,其他部分和x86指令集是比较类似的。但和x86相比,ARM指令最显著的特点它们都是32-bit 定长的。另外,由于arm是基于RISC指令集的,所以CPU只处理在寄存器中的数据并通过独立的load-store指令在内存和寄存器之间进行数据的传递。
在使用方面,ARM指令的格式也要比Intel的复杂些。一般说来,一条ARM指令有如下的形式:
{S} [Rd], [Rn], [Rm]
其中:
* {S} —— 加上这个后缀的指令会更新cpsr 寄存器
* [Rd] —— 目的寄存器
* [Rn]/[Rm] —— 源寄存器
一般来说,arm 指令有3个操作数,其中Rm寄存器在执行指令前可以进入桶形移位器进行移位操作,而Rn则会直接进入ALU 单元。如果一条arm 指令只有2 个操作数,那么源寄存器按照Rm 来处理。例如,一条加法指令:
add r0, r1, #1
就会把r1+1的结果存放到r0中。
在熟悉了基本的汇编格式后,读者就可以自行去查询基本的ARM汇编指令了,下面,我们找出ARM中比较有特色部分——Load-Store指令结构,它是CPU 和内存进行通信的一个重要媒介。
Load-Store 指令体系
由于ARM CPU并不直接处理内存中的数据,这个指令体系就担起了在寄存器和内存之间交换数据的重要媒介。它要比x86 的内存访问机制复杂一些。该指令体系分成3 类:
◆ 单寄存器传输(这是与x86 最为相像的)
◆ 多寄存器传输
◆ 交换指令
单寄存器传输
先看第一个,很简单:把单一的数据传入(LDR) 或传出(STR)寄存器,对内存的访问可以是DWORD(32-bit), WORD(16-bit)和BYTE(8-bit)。指令的格式如下:
DWORD:
Rd, addressing1
WORD:
H Rd, addressing2 无符号版
SH Rd, addressing2 有符号版
BYTE:
B Rd, addressing1 无符号版
SB Rd, addressing2 有符号版
addressing1 和addressing2 的分类下面再说,现在理解成某种寻址方式就可以了。
在单寄存器传输方面,还有以下三种变址模式,他们是:
◆ preindex
这种变址方式和x86的寻址机制是很类似的,先对寄存器进行运算,然后寻址,但是在寻之后,基址寄存器的内容并不发生改变,例如:
ldr r0, [r1, #4]
的含义就是把r1+4 这个地址处的DOWRD 加载到r0,而寻址后,r1 的内容并不改变。
◆ preindex with writeback
这种变址方式有点类似于++i的含义,寻址前先对基地址寄存器进行运算,然后寻址. 其基本的语法是在寻址符[]后面加上一个"!" 来表示.例如:
ldr r0, [r1, #4]!
就可以分解成:
add r1, r1, #4
ldr r0, [r1, #0]
◆ postindex
自然这种变址方式和i++的方式就很类似了,先利用基址寄存器进行寻址,然后对基址寄存器进行运算,其基本语法是把offset 部分放到[]外面,例如:
ldr r0, [r1], #4
就可以分解成:
ldr r0, [r1, #0]
add r1, r1, #4
如果你还记得x86 的SIB 操作的话,那么你一定想ARM是否也有,答案是有也没有。在ss上面提到的addressing1 和addressing2的区别就是比例寄存器的使用,addressing1可以使用[base, scale, 桶形移位器]来实现SB 的效果,或者通过[base,offset](这里的offset 可以是立即数或者寄存器)来实现SI 的效果,而addressing2则只能用后者了。于是每一种变址方式最多可以有3 种寻址方式,这样一来,最多可以有9种用来寻址的指令形式。例如:
ldr r0, [r1, r2, LSR #0x04]!
ldr r0, [r1, -#0x04]
ldr r0, [r1], LSR #0x04
每样找了一种,大概就是这个意思。到此,单寄存器传输就结束了,掌握这些足够应付差事了。下面来看看多寄存器传输吧。
多寄存器传输
说得很明白,意思就是通过一条指令同时把多个寄存器的内容写到内存或者从内存把数据写到寄存器中,效率高的代价是会增加系统的延迟,所以armcc 提供了一个编译器选项来控制寄存器的个数。指令的格式有些复杂:
<寻址模式> Rn{!}, {r^}
我们先来搞明白寻址模式,多寄存器传输模式有4 种:
也就是说以A开头的都是在Rn的原地开始操作,而B开头的都是以Rn的下一个位置开始操作。如果你仍然感到困惑,我们不妨看个例子。
所有的示例指令执行前:
mem32[0x1000C] = 0x04
mem32[0x10008] = 0x03
mem32[0x10004] = 0x02
mem32[0x10000] = 0x01
r0 = 0x00010010
r1 = 0x00000000
r3 = 0x00000000
r4 = 0x00000000
1) ldmia r0!, {r1-r3} 2) ldmib r0!, {r1-r3}
执行后: 执行后:
r0 = 0x0010001C r0 = 0x0010001C
r1 = 0x01 r1 = 0x02
r2 = 0x02 r2 = 0x03
r3 = 0x03 r3 = 0x04
至于DA 和DB 的模式,和IA / IB 是类似的,不多说了。
最后要说的是,使用ldm 和stm指令对进行寄存器组的保护是很常见和有效的功能。配对方案:
stmia / ldmdb
stmib / ldmda
stmda / ldmib
stmdb / ldmia
继续来看两个例子:
执行前:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
执行的指令:
stmib r0!, {r1-r3}
mov r1, #1 ; These regs have been modified
mov r2, #2
mov r3, #3
当前寄存器状态:
r0 = 0x0000100C
r1 = 0x00000001
r2 = 0x00000002
r3 = 0x00000003
ldmia r0!, {r1-r3}
最后的结果:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
另外,我们还可以利用这个指令对完成内存块的高效copy:
loop
ldmia r9!, {r0-r7}
stmia r10!, {r0-r7}
cmp r9, r11
bne loop
说到这里,读者应该对RISC的Load-Store体系结构有一个大概的了解了,能够正确配对使用指令,是很重要的。
ARM 汇编语言程序格式
可执行映象文件的格式: *.axm *.bin *.elf *.hex
代码段示例:
汇编语言源程序的基本结构:
AREA Init,CODE,READONLY
ENTRY
Start
LDR R0,=0x3FF50000
LDR R1,0xFF
STR R1,[R0]
LDR R0,=0x3FF5008
LDR R1,0x01
STR R1,[R0]
END
Arm 体系结构3种执行流程:
1 顺序执行
2 跳转执行
3 异常中断执行
Arm 子程序调用使用命令 BL 子程序名称
子程序调用示例:
AREA Init , CODE , READONLY
ENTRY
Start
LDR R0 , =0x3FF5000
LDR R1 , 0xFF
STR R1 , [R0]
LDR R0 , =0x3FF5008
LDR R1 , 0x01
STR R1 , [R0]
BL PRINT_TEXT
┉┉
PRINT_TEXT
┉┉
MOV PC,LR
┉┉
END
C/C++及汇编语言的混合编程
ARM集成开发环境中包含的C/C++编译器。
编译器
名称 |
编译器
种类 |
源文件
类型 |
源文件
后缀 |
输出目标文件类型 |
armcc |
C |
C |
*.C |
32位ARM代码 |
tcc |
C |
C |
*.C |
16位Thumb代码 |
armcpp |
C++ |
C/C++ |
*.C/*.C++ |
32位ARM代码 |
tcpp |
C++ |
C/C++ |
*.C/*.C++ |
16位Thumb代码 |
在C\C++程序中使用内嵌的汇编指令的语法格式:
在ARM C语言程序中,使用关键字__asm来标识一段汇
编指令程序。
__asm ;2 个下划线
{
汇编语言程序
~~~~~~~~
汇编语言程序
}
其中:如果一行中有多个汇编指令,指令之间使用分号(;)分开。
在一条指令占多行,要使用续行符号(\).
在C/C++程序中内嵌汇编指令注意事项:
o 必须小心使用物理寄存器,如R0~R3,SP,LR 和CPSR 中的N,Z,C,V 标志位.因为计算汇编代码中的C 表达式时,可能会使用这些物理寄存器,并会修改N,Z,C,V标志位。
__asm
{
MOV R0,x
ADD y,R0,x/y //计算x/y 时R0 会被修改
}
在计算x/y 时R0 会被修改,从而影响R0+x/y 的结果.用一个C 程序的变量代替
R0就可以解决这个问题:
__asm
{
MOV var,x
ADD y,var,x/y
}
内嵌汇编器探测到隐含的寄存器冲突就会报错.
o 不要使用寄存器代替变量.尽管有时寄存器明显对应某个变量,但也不能直接使用寄存器代替变量.
int bad_f(int x) //x 存放在R0 中
{
__asm
{
ADD R0,R0,#1 //发生寄存器冲突,实际上x 的值没有变化
}
return(x);
}
尽管根据编译器的编译规则似乎可以确定R0 对应x,但这样的代码会使内嵌汇编器认为
发生了寄存器冲突.用其他寄存器代替R0 存放参数x,使得该函数将x 原封不动地返回.
这段代码的正确写法如下:
int bad_f(intx)
{
__asm
{
ADD x,x,#1
}
return(x)
}
从汇编程序中访问C程序变量
在C程序中声明的全局变量可以被汇编程序通过地址间接访问。具体访问方
法如下:
o 使用IMPORT伪指令声明这个全局变量。
o 使用LDR指令读取该全局变量的内存地址,通常该全局变量的内存地址存放在程序的数据缓冲池中。
o 根据该数据类型,使用相应的LDR指令读取该全局变量的值;使用相应的STR指令修改该全局变量的值。
AREA globals,CODE,READONLY
EXPORT asmsub
IMPORT glovbvar;声明外部变量glovbvar
asmsub
LDR R1,=glovbvar;装载变量地址
LDR R0,[R1] ;读出数据
ADD R0,R0,#1;加1 操作
STR R0,[R1];保存变量值
MOV PC, LR
END
C程序与汇编程序互相调用规则
寄存器的使用规则
- 子程序间通过寄存器R0~R3来传递参数。
- 在子程序中,使用寄存器R4~R11来保存局部变量。
- 寄存器R12用于子程序间scratch寄存器(用于保存SP,在函数返回时使用该寄存器出桟),记作IP。
- 寄存器R13用于数据栈指针,记作SP。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。
- 寄存器R14称为链接寄存器,记作LR。它用于保存子程序的返回地址。
- 寄存器R15是程序计数器,记作PC
*.axf (下载到sdram 里面调试(AXD))
ARM fromelf(转化) ----> *.bin *.elf *.hex *.i32 烧写到flash里面保存
1. 将映象文件(*.axf)下载到SDRAM内调试,工具为JTAG 板 或者仿真器.
RO BASE: 设置SDRAM内的地址,可以设置SDRAM 的首地址,或者是靠近首
地址值的地址值,RO BASE的值一定要按照字对齐.
RW BASE: 也可以不设置,如果要设置,RW BASE –RO BASE >映象文件的大下
最好不设置, 值一定要按照字对齐.
2. 将映象文件(*.bin *.hex) 烧写到nor flash 内
RO BASE: 设置flash 首地址(0x00000000), 值一定要按照字对齐.
RW BASE: 一定要设置,设置的地址值在SD RAM 内, 值一定要按照字对齐.
IMAGE ENTRY POINT: 可以不设置,如果设置就和RO BASE的值.
PLACE AT BEGINNING OF IMAGE
Object/Symbol:填写映象文件中,第一个要执行的源文件的目标文件.
(异常中断的跳转函数)
阅读(3610) | 评论(0) | 转发(0) |