ARM3.(汇编函数和c语言相互调用及ARM裸机开发环境搭建)
1.汇编语言调用c语言函数
首先我们编写一个简单的c语言函数并声明:
extern int c_add(void);
int c_add(void)
{return 10;
}
在汇编语言中用bl指令调用:
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entry;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;调用c语言函数
mainldr sp, =0x40001000import c_add;导入c语言函数stmfd sp!, {r0-r12,lr};带!栈帧移动,不带不移动,将r0-r12,lr压栈bl c_addldmfd sp!, {r0-r12,lr};与压栈列表对应,否则会产生错位,将r0-r12,lr弹出
;finish
; b finishend
需要注意的是:汇编语言在调用c函数时需要在最开始加上伪指令preserve8,保证你的汇编代码会维护 8 字节栈对齐的规则。这样才能准确跳转并返回。并且需要用import指令在汇编文件中“声明”一个外部函数,相当于 C 语言中的 extern void c_add(void);
1.1汇编语言调用c函数传参
写一个简单的c语言代码:
extern int c_add(int a, int b);
int c_add(int a, int b)
{return a + b;
}
我们通过通用寄存器r0,r1,r2,r3进行传参,参数不能超过4个。
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entry;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;调用c语言函数
mainldr sp, =0x40001000import c_add;导入c语言函数stmfd sp!, {r0-r12,lr};带!栈帧移动,不带不移动,将r0-r12,lr压栈mov r0, #100mov r1, #200bl c_add ;调用c语言函数ldmfd sp!, {r0-r12,lr};与压栈列表对应,否则会产生错位,将r0-r12,lr弹出
finishb finishend
可以看出a和b的值被r0,r1赋予。返回值默认存储到r0。
如果参数超过四个,该怎么解决呢?
写一个简单的c语言函数:
extern int c_add(int a, int b, int c, int d, int e);
int c_add(int a, int b, int c, int d, int e)
{return a + b + c + d + e;
}
传递5个参数:
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entry;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mainldr sp, =0x40001000import c_add;导入c语言函数stmfd sp!, {r0-r12,lr};带!栈帧移动,不带不移动,将r0-r12,lr压栈mov r0, #1mov r1, #2mov r2, #3mov r3, #4mov r4, #5bl c_add ;调用c语言函数ldmfd sp!, {r0-r12,lr};与压栈列表对应,否则会产生错位,将r0-r12,lr弹出
finishb finishend
可以发现参数超过四个,e没有被赋值:
我们要想传递5个参数,就需要通过压栈操作进行传参。
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entrymainldr sp, =0x40001000import c_add;导入c语言函数stmfd sp!, {r0-r12,lr};带!栈帧移动,不带不移动,将r0-r12,lr压栈mov r0, #1mov r1, #2mov r2, #3mov r3, #4mov r4, #5stmfd sp!, {r4} ;将r4压入栈中bl c_add ;调用c语言函数ldmfd sp!, {r4} ;将r4弹出 ldmfd sp!, {r0-r12,lr};与压栈列表对应,否则会产生错位,将r0-r12,lr弹出
finishb finishend
可以看到e被成功赋值了
r0保存的结果正确:
1.2c语言调用汇编函数
汇编函数在被调用时,需要用export指令声明,同时在c语言程序中也需要声明:
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entry;c语言调用汇编函数b mainexport asm_maxTwoNum ;导出汇编函数,类似于声明
asm_maxTwoNumcmp r0, r1movge r3, r0movlt r3, r1mov r0, r3mov pc, lr
mainldr sp, =0x40001000import c_add;导入c语言函数;stmfd sp!, {lr};带!栈帧移动,不带不移动,将lr压栈bl c_add ;调用c语言函数 ldmfd sp!, {lr};与压栈列表对应,否则会产生错位,将lr弹出
finishb finishend
extern int c_add(void);
extern int asm_maxTwoNum(int a, int b);
int c_add(void)
{int ret;ret = asm_maxTwoNum(1, 2); // 参数1通过R0传递,参数2通过R1传递return ret;
}
可以看出ret被正确赋值:
2.ARM裸机开发环境搭建
2.1异常向量表
ARM架构规定,异常向量表必须位于内存地址0x00000000或0xFFFF0000(高端向量)
因此我们先搭建好异常向量表及处理函数框架
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entry
; 异常向量表 (Exception Vector Table)
; ARM架构规定,异常向量表必须位于内存地址0x00000000或0xFFFF0000(高端向量)
; 每个向量占4字节,包含一条跳转指令ldr pc, =start_hander ; 复位向量(Reset) - 0x00: 系统上电或复位时执行ldr pc, =undefine_hander ; 未定义指令异常(Undefined) - 0x04: 遇到无法识别的指令时执行ldr pc, =software_hander ; 软件中断异常(SWI) - 0x08: 执行SWI指令时触发ldr pc, =prefetch_hander ; 预取指中止(Prefetch Abort)- 0x0C: 指令预取失败时执行ldr pc, =data_hander ; 数据中止(Data Abort) - 0x10: 数据访问失败时执行nop ; 占位(Reserved地址预留) - 0x14: ARM保留,未使用ldr pc, =irq_hander ; 普通中断(IRQ) - 0x18: 外设中断请求时执行ldr pc, =fiq_hander ; 快速中断(FIQ) - 0x1C: 高优先级中断请求时执行undefine_handerb undefine_handersoftware_handerb software_handerprefetch_handerb prefetch_handerdata_handerb data_handerirq_handerb irq_handerfiq_handerb fiq_handerstart_handerldr sp, =0x40001000finishb finishend
再来编写初始化函数,令其处于user模式
此时cpsr的m字段处于0x13(100011)Supervisor工作模式中。
这时我们需要通过MRS指令读取,MSR指令写入来修改M字段从而改变工作状态了。
2.1MRS指令,用于从系统寄存器读取数据到通用寄存器。MSR指令,用于将通用寄存器的数据写入系统寄存器。
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entry
; 异常向量表 (Exception Vector Table)
; ARM架构规定,异常向量表必须位于内存地址0x00000000或0xFFFF0000(高端向量)
; 每个向量占4字节,包含一条跳转指令ldr pc, =start_hander ; 复位向量(Reset) - 0x00: 系统上电或复位时执行ldr pc, =undefine_hander ; 未定义指令异常(Undefined) - 0x04: 遇到无法识别的指令时执行ldr pc, =software_hander ; 软件中断异常(SWI) - 0x08: 执行SWI指令时触发ldr pc, =prefetch_hander ; 预取指中止(Prefetch Abort)- 0x0C: 指令预取失败时执行ldr pc, =data_hander ; 数据中止(Data Abort) - 0x10: 数据访问失败时执行nop ; 占位(Reserved地址预留) - 0x14: ARM保留,未使用ldr pc, =irq_hander ; 普通中断(IRQ) - 0x18: 外设中断请求时执行ldr pc, =fiq_hander ; 快速中断(FIQ) - 0x1C: 高优先级中断请求时执行undefine_handerb undefine_handersoftware_handerb software_handerprefetch_handerb prefetch_handerdata_handerb data_handerirq_handerb irq_handerfiq_handerb fiq_handerstart_handerldr sp, =0x40001000 ;给Supervisor分配栈指针寄存器
;切换user模式(10000)mrs r0, cpsr ;将cpsr数据读取到r0bic r0, r0, #(0x1f << 0) ;低5位(m字段)全部清零bic r0, r0, #(1 << 7) ;irq中断模式启动orr r0, r0, #(0x10 << 0) ;第5位置1,切换为user模式msr cpsr_c, r0 ;将r0数据写回cpsrldr sp, = 0x40001000 ;给user分配栈指针寄存器sub sp, sp, #1024 ;给Supervisor预留1k大小的栈,在0x40001000-1024处设定user的栈指针寄存器
finishb finishend
这样就完成了user模式的切换。
2.2swi指令,触发一个软件异常,使处理器从用户模式切换到特权模式(Supervisor),并跳转到操作系统预设的异常处理程序
我们试试触发软件中断异常
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entry
; 异常向量表 (Exception Vector Table)
; ARM架构规定,异常向量表必须位于内存地址0x00000000或0xFFFF0000(高端向量)
; 每个向量占4字节,包含一条跳转指令ldr pc, =start_hander ; 复位向量(Reset) - 0x00: 系统上电或复位时执行ldr pc, =undefine_hander ; 未定义指令异常(Undefined) - 0x04: 遇到无法识别的指令时执行ldr pc, =software_hander ; 软件中断异常(SWI) - 0x08: 执行SWI指令时触发ldr pc, =prefetch_hander ; 预取指中止(Prefetch Abort)- 0x0C: 指令预取失败时执行ldr pc, =data_hander ; 数据中止(Data Abort) - 0x10: 数据访问失败时执行nop ; 占位(Reserved地址预留) - 0x14: ARM保留,未使用ldr pc, =irq_hander ; 普通中断(IRQ) - 0x18: 外设中断请求时执行ldr pc, =fiq_hander ; 快速中断(FIQ) - 0x1C: 高优先级中断请求时执行undefine_handerb undefine_hander;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;import software_vecetor;导入c语言函数
software_handerbl software_vecetorbx lr
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
prefetch_handerb prefetch_handerdata_handerb data_handerirq_handerb irq_handerfiq_handerb fiq_handerexport asm_swi_fun
asm_swi_funswi #7 ;触发一个软件异常,使处理器从用户模式切换到特权模式(Supervisor),并跳转到操作系统预设的异常处理程序(0x08)。bx lrstart_handerldr sp, =0x40001000 ;给Supervisor分配栈指针寄存器import main ;声明主函数
;切换user模式(10000)mrs r0, cpsr ;将cpsr数据读取到r0bic r0, r0, #(0x1f << 0) ;低5位(m字段)全部清零bic r0, r0, #(1 << 7) ;irq中断模式启动orr r0, r0, #(0x10 << 0) ;第5位置1,切换为user模式msr cpsr_c, r0 ;将r0数据写回cpsrldr sp, = 0x40001000 ;给user分配栈指针寄存器sub sp, sp, #1024 ;给Supervisor预留1k大小的栈,在0x40001000-1024处设定user的栈指针寄存器b main ;进入main函数end
在c语言调用asm_swi_fun:
extern main(void);
extern void asm_swi_fun(void);
extern void software_vecetor(void);
void software_vecetor(void)
{}
int main(void)
{asm_swi_fun();while(1){}return 0;
}
由于swi在触发软件异常的时候会切换为Supervisor模式,因此我们在调用完成后需要重新将模式切换为user模式,也就是进行压栈弹栈操作:
preserve8;栈指针按照8字节对齐area reset, code, readonly code32entry
; 异常向量表 (Exception Vector Table)
; ARM架构规定,异常向量表必须位于内存地址0x00000000或0xFFFF0000(高端向量)
; 每个向量占4字节,包含一条跳转指令ldr pc, =start_hander ; 复位向量(Reset) - 0x00: 系统上电或复位时执行ldr pc, =undefine_hander ; 未定义指令异常(Undefined) - 0x04: 遇到无法识别的指令时执行ldr pc, =software_hander ; 软件中断异常(SWI) - 0x08: 执行SWI指令时触发ldr pc, =prefetch_hander ; 预取指中止(Prefetch Abort)- 0x0C: 指令预取失败时执行ldr pc, =data_hander ; 数据中止(Data Abort) - 0x10: 数据访问失败时执行nop ; 占位(Reserved地址预留) - 0x14: ARM保留,未使用ldr pc, =irq_hander ; 普通中断(IRQ) - 0x18: 外设中断请求时执行ldr pc, =fiq_hander ; 快速中断(FIQ) - 0x1C: 高优先级中断请求时执行undefine_handerb undefine_hander;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;import software_vecetor;导入c语言函数
software_handerstmfd sp!, {r0-r12, lr}bl software_vecetorldmfd sp!, {r0-r12, pc}^;bx lr
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
prefetch_handerb prefetch_handerdata_handerb data_handerirq_handerb irq_handerfiq_handerb fiq_handerexport asm_swi_fun
asm_swi_funswi #7 ;触发一个软件异常,使处理器从用户模式切换到特权模式(Supervisor),并跳转到操作系统预设的异常处理程序(0x08)。bx lrstart_handerldr sp, =0x40001000 ;给Supervisor分配栈指针寄存器import main ;声明主函数
;切换user模式(10000)mrs r0, cpsr ;将cpsr数据读取到r0bic r0, r0, #(0x1f << 0) ;低5位(m字段)全部清零bic r0, r0, #(1 << 7) ;irq中断模式启动orr r0, r0, #(0x10 << 0) ;第5位置1,切换为user模式msr cpsr_c, r0 ;将r0数据写回cpsrldr sp, = 0x40001000 ;给user分配栈指针寄存器sub sp, sp, #1024 ;给Supervisor预留1k大小的栈,在0x40001000-1024处设定user的栈指针寄存器b main ;进入main函数end