嵌入式ARM架构学习3——启动代码
一 汇编补充:
area reset, code, readonlycode32entry;mov r0, #4 ; r0 = 4;mov r1, r0 ; r1 = r0;mov r2, r1, lsl #1 ;r2 = r1 << 1 乘2;mov r3, r1, lsr #1 ;r3 = r1 >> 1 除2;mov r4, r1, ror #2;mov r0, #100 ;100是十进制 转为16进制赋值给十进制;mov r1, #8;add r2, r0, #100;add r2, r0, r1;add r2, r0, #(100 << 2);;mov r0, #0xF0;mvn r0, r0;ldr r0, =0xfac0;把任意 32 位常数送进寄存器;指定位置置0;mov r0, #0xFFFFFFFF;mov r1, #1;bic r0, r0, #4 ;指定位 4:(0100)r0里4对应的1所在位置置0;bic r0, r0, r1, lsl #2 ;先把r1 左移2位 然后对应的1的位置对应于r0中 给置0;bic r0, r0, #(1 << 2);清理掉第2位;指定位置1;mov r0, #0x80000000;orr r0, r0, #(1<<31);mov r0, #0x0 ;1011;orr r0, r0, #(1 << 10);三个数之间找最大值;mov r0, #100;mov r1, #200;mov r2, #300;cmp r0, r1;movge r3, r0;movlt r3, r1;cmp r3, r2;movge r3, r3;movlt r3, r2;if分支;mov r0, #0x100;mov r1, #0x200;cmp r0, r1;bge greatr;blt less;greatr;mov r2, r0;b finish ;跳过另一个分支 跳转到finish;less;mov r2, r1;finish;b finish;while 循环;mov r0, #0;mov r1, #0
;loop;cmp r0, #100;bge finish;add r1, r1, r0;add r0, r0, #1;b loop;finish;b finish ;死循环;do_while循环;mov r0, #0;mov r1, #0
;loop;add r1, r1, r0;add r0, r0, #1;cmp r0, #100;bge finish;b loop
;finish;b finish ;死循环;b main
;asm_maxTwoNum
; mov r0, #100
; mov r1, #200
; cmp r0, r1
; movge r3, r0
; movlt r3, r1
; mov pc, lr ;把返回地址赋给 PC → 立即跳回调用点继续执行
; bx lr ;bx 跳转到调用点的下一行
;main
; mov r0, #10;mov r1, #20
; bl asm_maxTwoNum ;bl 跳转之前保留当前地址
; mov r2, #10
; mov r3, #20;========= 子函数:返回 r0、r1 中的较大值 =========
asm_maxTwoNummov r0, #100 ;把参数写成100、200(覆盖main传入的10/20)mov r1, #200cmp r0, r1stmfd sp!, {r0-r12, lr} ; 先压栈保护现场bl asm_fun0 ; 调用asm_fun0(无意义,返回后r0/r1仍保持100/200)ldmfd sp!, {r0-r12, lr} ; 弹栈恢复现场movge r3, r0 ; 若 r0>=r1 大数→r3 (100<200,不执行)movlt r3, r1 ; 若 r0< r1 大数→r3 (执行,r3=200)mov r0, r3 ; 把结果200写回r0作为返回值bx lr ; 返回;========= 另一个函数,仅演示跳转 =========
asm_fun0mov r0, #100mov r1, #200bx lr ; 直接返回,无实质操作;========= 主函数:入口 =========
mainldr sp, =0x40001000 ; 设栈顶(裸机常用)mov r0, #10 ; 准备参数 1mov r1, #20 ; 准备参数 2stmfd sp!, {r0-r12, lr} ; 保存现场bl asm_maxTwoNum ; 调最大值函数ldmfd sp!, {r0-r12, lr} ; 恢复现场mov r2, #10 mov r3, #20ldr r0, =0x40000000 ;地址指针ldr r1, =0x12345678 ;数据str r1,[r0],#4 ;*p = 0x12345678; p++ldr r2,[r0] ; r2 = *(p+1)(未初始化,值未知);ARM 是 32 位架构,一个“字(word)” = 4 字节。;地址按字节编号,因此索引写 #4 才能指到下一个字。finishb finish ; 死循环,程序结束end
汇编asm和c文件之间的传参
preserve8
且魔术棒配置ROM地址区域
mainldr sp, =0x40001000 ; 设栈顶(裸机常用)import c_add;mov r0, #10 ; 准备参数 1;mov r1, #20 ; 准备参数 2stmfd sp!, {r0-r12, lr} ; 保存现场;bl asm_maxTwoNum ; 调最大值函数mov r0, #100 mov r1, #200bl c_add;r0默认接收函数返回值ldmfd sp!, {r0-r12, lr} ; 恢复现场;mov r2, #10 ;mov r3, #20ldr r0, =0x40000000 ;地址指针ldr r1, =0x12345678 ;数据str r1,[r0],#4 ;*p = 0x12345678; p++ldr r2,[r0] ; r2 = *(p+1)(未初始化,值未知);ARM 是 32 位架构,一个“字(word)” = 4 字节。;地址按字节编号,因此索引写 #4 才能指到下一个字。finishb finish ; 死循环,程序结束extern c_add(int a, int b);int c_add(int a, int b)
{return a + b;
}
当传参数大于4个——通过压栈传参
b main
asm_maxTwoNummov r0, #100 ;把参数写成100、200(覆盖main传入的10/20)mov r1, #200cmp r0, r1stmfd sp!, {r0-r12, lr} ; 先压栈保护现场bl asm_fun0 ; 调用asm_fun0(无意义,返回后r0/r1仍保持100/200)ldmfd sp!, {r0-r12, lr} ; 弹栈恢复现场movge r3, r0 ; 若 r0>=r1 大数→r3 (100<200,不执行)movlt r3, r1 ; 若 r0< r1 大数→r3 (执行,r3=200)mov r0, r3 ; 把结果200写回r0作为返回值bx lr ; 返回;========= 另一个函数,仅演示跳转 =========
asm_fun0mov r0, #100mov r1, #200bx lr ; 直接返回,无实质操作;========= 主函数:入口 =========
mainldr sp, =0x40001000 ; 设栈顶(裸机常用)import c_addmov r0, #10 ; 准备参数 1mov r1, #20 ; 准备参数 2stmfd sp!, {r0-r12, lr} ; 保存现场;bl asm_maxTwoNum ; 调最大值函数mov r0, #10 mov r1, #20mov r2, #30 mov r3, #40mov r4, #50; 传参大于4个需要通过压栈进行传参 stmfd sp!, {r4} ; 压栈r4传参 bl c_add;r0默认接收函数返回值ldmfd sp!, {r4} ; 弹栈ldmfd sp!, {r0-r12, lr} ; 恢复现场;mov r2, #10 ;mov r3, #20ldr r0, =0x40000000 ;地址指针ldr r1, =0x12345678 ;数据str r1,[r0],#4 ;*p = 0x12345678; p++ldr r2,[r0] ; r2 = *(p+1)(未初始化,值未知);ARM 是 32 位架构,一个“字(word)” = 4 字节。;地址按字节编号,因此索引写 #4 才能指到下一个字。finishb finish ; 死循环,程序结束
C语言 调用汇编
extern int c_add(int a, int b, int c, int d, int e);extern int asm_maxTwoNum(int a, int b);int c_add(int a, int b, int c, int d, int e)
{int ret;//ret = a + b + c + d + e;ret = asm_maxTwoNum(5, 10);return ret;
}b main
asm_fun0mov r0, #100mov r1, #200bx lrexport asm_maxTwoNum
asm_maxTwoNumcmp r0, r1movge r3, r0movlt r3, r1 mov r0, r3bx lr
启动代码:最小可运行的 ARM 裸机启动/中断向量表/软中断(SWI) 示例
上电后建立向量表 → 切到用户模式并开 IRQ → 给 User 栈留空间 → 跳进 C 的 main()
;同时提供 asm_swi_fun()
让 C 主动触发 7 号软中断,异常处理完整保存/恢复现场并自动返回用户模式
preserve8area reset, code, readonlycode32entry;中断向量表ldr pc, =start_hander ; 0x00 复位ldr pc, =undefine_hander ; 0x04 未定义指令ldr pc, =software_hander ; 0x08 SWI(软中断)ldr pc, =prefetch_hander ; 0x0C 预取指中止ldr pc, =data_hander ; 0x10 数据中止nop ; 0x14 保留ldr pc, =irq_hander ; 0x18 IRQldr pc, =fiq_hander ; 0x1C FIQundefine_handerb undefine_handerprefetch_handerb prefetch_handerdata_handerb data_handerirq_handerb irq_handerfiq_handerb fiq_handerimport software_vector;告诉汇编器 C 里实现该函数
software_handerstmfd sp!, {r0-r12, lr} ; 保存用户现场(lr = SWI 返回地址)bl software_vector ; 调到 C 处理函数ldmfd sp!, {r0-r12, pc}^ ; 恢复寄存器 且 把保存的 lr 弹进 pc; ^ 表示 同时把 SPSR_svc → CPSR(自动返回用户模式)export asm_swi_fun ; 供c调用
asm_swi_funswi #7 ; 触发 7 号软中断(编号任意)bx lr ; 从 SWI 返回后再回到 Cstart_handerldr sp, =0x40001000 ; 设栈顶(裸机常用)import main ;引入c语言中的main函数;--- 下面 5 行 = 切到用户模式并打开 IRQ ----------mrs r0, cpsr ;r0 ← CPSRbic r0, r0, #(0x1F << 0) ;CPSR的M域是后五位 先清零(清模式位)bic r0, r0, #(1 << 7) ;清CPSR 的 I 位(允许 IRQ)orr r0, r0, #(0x10 << 0) ;设 M[4:0]=0b10000(User 模式)msr cpsr_c, r0 ;**只把模式/中断位写回**;给 User 模式建栈ldr sp, =0x40001000sub sp, sp, #1024 ;; 留 1 KB 给 User 栈b main ; 跳转c语言的函数end
15、arm汇编调用c语言函数以及c语言函数调用汇编编写的函数,函数参数和返回值如何处理?
核心原则:
参数和返回值主要通过 寄存器 传递,当寄存器不够用时,使用 栈。
ARM汇编调用C语言函数 过程如下:
参数传递:
前4个参数 (对于32位ARM): 如果参数是32位整型或指针类型,依次使用寄存器
R0
,R1
,R2
,R3
来传递。第5个及以后的参数: 从第5个参数开始,将其压入栈 (Stack) 中。调用者负责在调用前分配栈空间并压入这些参数。
执行调用 :
使用
BL
(Branch with Link) 指令跳转到C函数地址。BL
指令会将下一条指令的地址(返回地址)保存到链接寄存器LR
(R14) 中。返回值获取:
32位及以下的返回值: C函数执行完毕后,返回值通过寄存器
R0
返回给汇编调用者。C语言函数调用ARM汇编函数 过程如下:
编写汇编函数:
汇编函数需要将自己视为一个被C代码调用的普通函数
它可以从
R0-R3
中获取前4个传入的参数。如果需要更多的参数,它需要从栈上的特定位置去获取。SP寄存器指向的栈顶之后的位置就是第5、6...个参数。
保存上下文 :
汇编函数如果会修改一些被调用者保存寄存器 (Callee-saved registers),如
R4-R11
,SP
,LR
,则必须在函数开头将它们压栈保存 (PUSH),并在函数结束返回前出栈恢复 (POP)。对于调用者保存寄存器 (Caller-saved registers),如
R0-R3
,R12
,则可以自由使用,无需保存(因为调用者C代码已经假定它们可能被破坏)。返回值设置 (Setting the Return Value):
在函数返回前,将需要返回的值放入
R0
(或R0
和R1
)。返回 (Returning):
通常使用
BX LR
指令返回到调用者(C代码)。这条指令会跳转到LR
寄存器中保存的返回地址。16. ARM内核异常、类型及工作模式切换
核心概念:
异常是CPU对内部或外部紧急事件的一种响应机制。当异常发生时,ARM内核会暂停当前执行的程序,跳转到预先设置好的异常向量表中的特定地址去执行对应的异常处理程序,同时CPU的工作模式会自动切换到对应的特权模式,以便操作系统内核有足够的权限来处理异常。
ARM经典架构(如ARMv7-A)中的7种异常:
异常类型 触发条件 使内核进入的工作模式 优先级(通常) 1. 复位 (Reset) 处理器上电或按下复位键 Supervisor (SVC) 最高 (1) 2. 未定义指令 (Undefined Instruction) CPU遇到无法识别的指令 Undefined 6 3. 软件中断 (SWI) / SVC 程序执行 SVC
或SWI
指令Supervisor (SVC) 6 4. 指令预取中止 (Prefetch Abort) 预取指令时发生内存访问错误 Abort 5 5. 数据中止 (Data Abort) 读写数据时发生内存访问错误 Abort 2 6. 中断请求 (IRQ) 外部设备发出普通中断请求 IRQ 4 7. 快速中断请求 (FIQ) 外部设备发出高优先级中断请求 FIQ 3 工作模式详解:
异常会导致内核从User等非特权模式切换到下表中的特权模式:
工作模式 简称 用途 用户模式 User 正常程序执行(非异常模式) 快速中断模式 FIQ 处理高速数据传输、DMA等 (异常模式) 中断模式 IRQ 处理普通中断 (异常模式) 管理模式 SVC 操作系统内核、软件中断、复位 (异常模式) 中止模式 Abort 处理内存访问失败 (异常模式) 未定义模式 Undefined 处理未定义指令异常 (异常模式) 系统模式 System 运行特权级操作系统任务 (ARMv4及以上,非异常模式) 重点说明:
复位 (Reset) 是最高优先级异常,它让系统从一个已知的初始状态开始运行,直接进入SVC模式。
软件中断 (SVC) 是用户程序主动切换到内核态(SVC模式)的方式,用于请求系统服务,例如打开文件、创建线程等(类似Linux中的系统调用)。
IRQ和FIQ 是异步异常,由外部硬件触发。FIQ的向量地址在向量表末尾,允许处理程序直接开始执行而无需跳转,并且有更多的专用寄存器,从而可以实现更快的处理速度。
中止异常 通常与内存管理单元 (MMU) 相关,用于实现虚拟内存和内存保护。预取中止发生在取指阶段,数据中止发生在数据访问阶段。
每种异常模式都有自己独立的栈指针 (SP) 和链接寄存器 (LR) 副本,这避免了在处理异常时破坏用户模式下的寄存器状态,确保了异常处理的安全和可靠。