标签:sas 十分 ima out sub 重点 结构 示例 后缀
在上一篇进行了汇编语言的编写之后,我们采用C语言来编写程序,毕竟C语言才是我们使用最多的语言。
仅仅是点亮LED灯显然太过于简单,我们需要分析最后的反汇编,了解函数调用栈,深入C语言骨髓去分析代码,并且自己编写C语言的库函数版本,方便以后开发,同时也是对自己C语言封装能力的锻炼。
先贴韦老大的代码:
start.s:
.text .global _start _start: /* 设置内存: sp 栈 */ ldr sp, =4096 /* nand启动 */ // ldr sp, =0x40000000+4096 /* nor启动 */ /* 调用main */ bl main halt: b halt
LED.c:
int main() { unsigned int *pGPFCON = (unsigned int *)0x56000050; unsigned int *pGPFDAT = (unsigned int *)0x56000054; /* 配置GPF4为输出引脚 */ *pGPFCON = 0x100; /* 设置GPF4输出0 */ *pGPFDAT = 0; return 0; }
C语言操作,的传统IDE开发当中,我们只用从main函数开始写代码就行了,但是IDE隐藏了太多技术细节。
我们在arm嵌入式linux开发过程中,都需要自己来,这对初学者是不友好,但是对深入学习却是很有帮助的。
首先,第一点,nand flash启动,使用片内4k sram。
我们都知道,函数调用和局部变量的存储需要使用到一种叫做栈的数据结构。
这里说明,s3c2440,采用默认的栈生长方式,这也是我们最常见的方式,高地址往低地址生长。
要调用main函数,我们需要开辟栈,这里使用片内4k 内存作为栈。
看看反汇编:
led.elf: file format elf32-littlearm Disassembly of section .text: 00000000 <_start>: 0: e3a0da01 mov sp, #4096 ; 0x1000 4: eb000000 bl c <main> 00000008 <halt>: 8: eafffffe b 8 <halt> 0000000c <main>: c: e1a0c00d mov ip, sp 10: e92dd800 stmdb sp!, {fp, ip, lr, pc} 14: e24cb004 sub fp, ip, #4 ; 0x4 18: e24dd008 sub sp, sp, #8 ; 0x8 1c: e3a03456 mov r3, #1442840576 ; 0x56000000 20: e2833050 add r3, r3, #80 ; 0x50 24: e50b3010 str r3, [fp, #-16] 28: e3a03456 mov r3, #1442840576 ; 0x56000000 2c: e2833054 add r3, r3, #84 ; 0x54 30: e50b3014 str r3, [fp, #-20] 34: e51b2010 ldr r2, [fp, #-16] 38: e3a03c01 mov r3, #256 ; 0x100 3c: e5823000 str r3, [r2] 40: e51b2014 ldr r2, [fp, #-20] 44: e3a03000 mov r3, #0 ; 0x0 48: e5823000 str r3, [r2] 4c: e3a03000 mov r3, #0 ; 0x0 50: e1a00003 mov r0, r3 54: e24bd00c sub sp, fp, #12 ; 0xc 58: e89da800 ldmia sp, {fp, sp, pc} Disassembly of section .comment: 00000000 <.comment>: 0: 43434700 cmpmi r3, #0 ; 0x0 4: 4728203a undefined 8: 2029554e eorcs r5, r9, lr, asr #10 c: 2e342e33 mrccs 14, 1, r2, cr4, cr3, {1} 10: Address 0x10 is out of bounds.
说明:
<.comment>:在上面的反汇编当中,它不是汇编代码的一部分,是注解,给我们一些提示,便于我们阅读、理解的。在讲解这个汇编之前,我们需要先看一个arm寄存器的别名表。
好,现在开始分析,其中最重要的也是新出现的两个指令:
stmdb,ldmia
详细介绍可以参见韦老大书籍P53,arm嵌入式系统开发P58。
简单说明,db表示事先递减方式,ia表示事后递增方式。
先对反汇编进行注解:
led.elf: file format elf32-littlearm Disassembly of section .text: 00000000 <_start>: 0: e3a0da01 mov sp, #4096 ; 赋值4096给sp堆栈寄存器 4: eb000000 bl c <main> ;跳转到main函数,同时保存main函数返回地址到lr寄存器,返回地址为下一条指令的地址,这里lr应为8 00000008 <halt>: 8: eafffffe b 8 <halt>;死循环 0000000c <main>: c: e1a0c00d mov ip, sp;备份sp寄存器的值到ip,ip是r12寄存器的别名
10: e92dd800 stmdb sp!, {fp, ip, lr, pc} ; 这条指令需要重点讲解,首先,fp,ip,lr,pc分别对应寄存器:r11,r12,r14,r15 ; stm指令多寄存器操作的时候,后面的寄存器从编号高的先开始存储,把后面几个寄存器的值写入sp所对应的内存块中,(注意和但寄存器操作区分开来)后缀db表示 先减后存储 ; sp后面加了一个感叹号!,表示sp最后的值等于最后被修改的值,不加感叹号就算操作使sp更改过,sp也等于最初的值 ; 首先操作r15,即pc,sp先减,sp=sp-4,此时sp=4092,pc等于当前指令地址加8,即此时pc=0x10+8=0x18,相当于把0x18写入sram地址4092 ; 同理操作r14,即lr,sp=sp-4,lr=8,相当于把0x8写入4088地址 ; r12,ip是等于4096的,上面的move指令,r11,fp的值此时未定
14: e24cb004 sub fp, ip, #4 ; fp=ip-4=4092 18: e24dd008 sub sp, sp, #8 ; sp=sp-8=4072 1c: e3a03456 mov r3, #1442840576 ;#1442840576等于十六机制0x56000000,把这个数存放在r3中 20: e2833050 add r3, r3, #80 ;r3=r3+80=0x56000050 24: e50b3010 str r3, [fp, #-16];fp-16=4076,把0x56000050放入内存4076中 28: e3a03456 mov r3, #1442840576 ; 0x56000000 2c: e2833054 add r3, r3, #84 ; r3=0x56000054 30: e50b3014 str r3, [fp, #-20];fp=fp-20=4072,把0x56000054放入内存4072中 34: e51b2010 ldr r2, [fp, #-16];r2=[fp-16=4076]=0x56000050 38: e3a03c01 mov r3, #256; r3==256=0x100 3c: e5823000 str r3, [r2];把0x100存入[0x56000050] 40: e51b2014 ldr r2, [fp, #-20];r2=[fp-20=4072]=0x56000054 44: e3a03000 mov r3, #0 ; r3=0=0x0 48: e5823000 str r3, [r2];把0x0存入0x56000054内存中 4c: e3a03000 mov r3, #0 ; r3=0x0 50: e1a00003 mov r0, r3;r0=r3=0x0,这里编译器有点笨,可以直接r0给0的,这里对应return 0. 54: e24bd00c sub sp, fp, #12 ; sp=fp-12=4080
58: e89da800 ldmia sp, {fp, sp, pc} ;恢复保存的现场,ldmia,事后增加,从sp所对应内存块中取出数据存放到后面的寄存器,高编号的寄存器放在高地址,低编号的寄存器 ;放在低地址,此时sp=4080,从低地址往高地址开始恢复,这也符合ia后缀 ;先恢复fp,此时4080地址存放的值,注意是值不是地址,等于4092,恰好就是等于fp,即一顿操作之后,fp还是等于原来的fp ;再恢复sp,sp=sp+4=4084,内存4084对应的值是4096,即一顿操作之后,sp又等于4096了; ;最后恢复pc,sp=sp+4=4088,内存4088对应的值是8,即一段操作之后,pc=8了,pc等于8意味着什么?意味着函数从main函数返回了,将去执行那个死循环halt
为了更好的理解函数入栈,让我们深入理解C语言底层汇编,画出内存示意图:
你可能会说。0-4096不是4097了吗?这样问非常好,不放过任何有疑问的细节,但是,4096,可以是4096的开始,也可以是4095的结尾,这里表示的是4095的结尾,因为4096我们一来是要先减4的。上图的4096是没用使用到真正属于4096地址后扩4字节的,而是在刚好到达4096时,之前的内存。
到这里,终于完成了一大半,我们知道了函数入栈之后,似乎函数调用的参数传递,也要用到栈啊,那么我们继续挖掘汇编。这里又需要补充几点arm方面的知识。
现在编写汇编代码, 传递一个参数:
.text .global _start _start: /* 设置内存: sp 栈 */ ldr sp, =4096 /* nand启动 */ // ldr sp, =0x40000000+4096 /* nor启动 */ mov r0, #4 bl led_on ldr r0, =100000 bl delay mov r0, #5 bl led_on halt: b halt
对应的c代码:
void delay(volatile int d) { while (d--); } int led_on(int which) { unsigned int *pGPFCON = (unsigned int *)0x56000050; unsigned int *pGPFDAT = (unsigned int *)0x56000054; if (which == 4) { /* 配置GPF4为输出引脚 */ *pGPFCON = 0x100; } else if (which == 5) { /* 配置GPF5为输出引脚 */ *pGPFCON = 0x400; } /* 设置GPF4/5输出0 */ *pGPFDAT = 0; return 0; }
上面汇编中,只对应一个参数,所以只用r0就可以达到函数参数传递的效果,汇编比较简单就不赘述了。
这个例子是为了让我们了解汇编通过寄存器传递函数参数,前提是必须先设置sp寄存器。
甚至不必编写main函数,当然不建议这样做,这样示例是为让你明白一点,main函数也是因为启动代码去调用了它,我们通过改写启动代码,可以没有main函数。
终于到了我们最熟悉的阶段了,编写C语言应用程序,一般来说,复杂的和可复用性更高的代码我们肯定是用C语言编写,全用汇编编写代码真是很慢而且麻烦,更不用说机器码编程了,但是了解它们对我们深入学习又十分有用,这或许就是arm对初学者不友好的原因吧,因为现在为止我们还没有大型项目需要编写复杂的Makefile,后面还有很多技能需要get,这也是在买了板子大半年了才真的开始上手的原因,需要花时间补习其他知识。
言归正传,如上面的C语言程序,虽然完成了要求,可是复用性太差,既然你觉得你C语言最熟悉,那么就请封装一个复用性高的代码出来看看吧。
/*明天补c代码,先洗漱休息了*/
标签:sas 十分 ima out sub 重点 结构 示例 后缀
原文地址:http://www.cnblogs.com/yangguang-it/p/7758167.html