历史上的今天
今天是:2025年03月28日(星期五)
2020年03月28日 | ARM-bootloader-C语言环境设计
2020-03-28 来源:eefocus
一、栈初始化
1、概念解析
1.1栈
栈是一种具有后进先出性质的数据组织方式,也就是说后存放的先取出,先存放的后取出。栈底是第一个进栈的数据所处的位置,栈顶是最后进栈数据所处的位置。
1.2满栈和空栈
根据SP指针指向的位置,栈可以分为满栈和空栈。
1、满栈:当堆栈指针SP总是指向最后压入堆栈的数据
2、空栈:当堆栈指针SP总是指向下一个将要放入数据的空位置
ARM采用的是满栈
1.3、升/降栈
1、升栈:随着数据的入栈,SP指针从低地址->高地址移动
2、降栈:随着数据的入栈,SP指针从高地址->低地址移动
ARM采用的是降栈。有时候我们会说ARM采用的是满降栈。
1.4、栈帧
简单的讲,栈帧(stack frame)就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。栈帧的两个边界分别由fp(r11)和sp(r13)来界定。

2、栈的作用
2.1、保存局部变量
#include int main() { int a; a++; return a; } 反汇编后代码: stack1: file format elf32-littlearm Disassembly of section .text: 00000000 #include int main() { 0: e52db004 push {fp} ; (str fp, [sp, #-4]!) 4: e28db000 add fp, sp, #0 ; 0x0 8: e24dd00c sub sp, sp, #12 ; 0xc int a; a++; c: e51b3008 ldr r3, [fp, #-8] //显然变量a存放在了栈中。 10: e2833001 add r3, r3, #1 ; 0x1 14: e50b3008 str r3, [fp, #-8] return a; 18: e51b3008 ldr r3, [fp, #-8] } 1c: e1a00003 mov r0, r3 20: e28bd000 add sp, fp, #0 ; 0x0 24: e8bd0800 pop {fp} 28: e12fff1e bx lr 从反汇编代码可以看出:局部变量a存放在了栈中。 2.2、参数传递 #include void func1(int a,int b,int c,int d,int e,int f) { int k; k=e+f; } int main() { func1(1,2,3,4,5,6); return 0; } 反汇编代码: stack2: file format elf32-littlearm Disassembly of section .text: 00000000 #include void func1(int a,int b,int c,int d,int e,int f) { 0: e52db004 push {fp} ; (str fp, [sp, #-4]!) 4: e28db000 add fp, sp, #0 ; 0x0 8: e24dd01c sub sp, sp, #28 ; 0x1c c: e50b0010 str r0, [fp, #-16] 10: e50b1014 str r1, [fp, #-20] 14: e50b2018 str r2, [fp, #-24] 18: e50b301c str r3, [fp, #-28] int k; k=e+f; 1c: e59b3004 ldr r3, [fp, #4] 20: e59b2008 ldr r2, [fp, #8] 24: e0833002 add r3, r3, r2 28: e50b3008 str r3, [fp, #-8] } 2c: e28bd000 add sp, fp, #0 ; 0x0 30: e8bd0800 pop {fp} 34: e12fff1e bx lr 00000038 int main() { 38: e92d4800 push {fp, lr} 3c: e28db004 add fp, sp, #4 ; 0x4 40: e24dd008 sub sp, sp, #8 ; 0x8 func1(1,2,3,4,5,6); 44: e3a03005 mov r3, #5 ; 0x5 48: e58d3000 str r3, [sp] 4c: e3a03006 mov r3, #6 ; 0x6 50: e58d3004 str r3, [sp, #4] 54: e3a00001 mov r0, #1 ; 0x1 58: e3a01002 mov r1, #2 ; 0x2 5c: e3a02003 mov r2, #3 ; 0x3 60: e3a03004 mov r3, #4 ; 0x4 // 当参数<=4时,可以用通用寄存器来传递参数,否则就要用到栈。 64: ebfffffe bl 0 return 0; 68: e3a03000 mov r3, #0 ; 0x0 } 6c: e1a00003 mov r0, r3 70: e24bd004 sub sp, fp, #4 ; 0x4 74: e8bd4800 pop {fp, lr} 78: e12fff1e bx lr 从反汇编代码可以看出:栈可以传递参数,当参数不大于4时,可以用寄存器来传递,一旦大于4就把剩余的参数,用栈来传递。 2.3、保存寄存器值 #include void func2(int a,int b) { int k; k=a+b; } void func1(int a,int b) { int c; func2(3,4); c=a+b; } int main() { func1(1,2); return 0; } 反汇编: stack3: file format elf32-littlearm Disassembly of section .text: 00000000 #include void func2(int a,int b) { 0: e52db004 push {fp} ; (str fp, [sp, #-4]!) 4: e28db000 add fp, sp, #0 ; 0x0 8: e24dd014 sub sp, sp, #20 ; 0x14 c: e50b0010 str r0, [fp, #-16] 10: e50b1014 str r1, [fp, #-20] int k; k=a+b; 14: e51b3010 ldr r3, [fp, #-16] 18: e51b2014 ldr r2, [fp, #-20] 1c: e0833002 add r3, r3, r2 20: e50b3008 str r3, [fp, #-8] } 24: e28bd000 add sp, fp, #0 ; 0x0 28: e8bd0800 pop {fp} 2c: e12fff1e bx lr 00000030 void func1(int a,int b) { 30: e92d4800 push {fp, lr} 34: e28db004 add fp, sp, #4 ; 0x4 38: e24dd010 sub sp, sp, #16 ; 0x10 3c: e50b0010 str r0, [fp, #-16] 40: e50b1014 str r1, [fp, #-20] //保存了寄存器的值 int c; func2(3,4); 44: e3a00003 mov r0, #3 ; 0x3 48: e3a01004 mov r1, #4 ; 0x4 4c: ebfffffe bl 0 c=a+b; 50: e51b3010 ldr r3, [fp, #-16] 54: e51b2014 ldr r2, [fp, #-20] 58: e0833002 add r3, r3, r2 5c: e50b3008 str r3, [fp, #-8] } 60: e24bd004 sub sp, fp, #4 ; 0x4 64: e8bd4800 pop {fp, lr} 68: e12fff1e bx lr 0000006c int main() { 6c: e92d4800 push {fp, lr} 70: e28db004 add fp, sp, #4 ; 0x4 func1(1,2); 74: e3a00001 mov r0, #1 ; 0x1 78: e3a01002 mov r1, #2 ; 0x2 7c: ebfffffe bl 30 return 0; 80: e3a03000 mov r3, #0 ; 0x0 } 84: e1a00003 mov r0, r3 88: e24bd004 sub sp, fp, #4 ; 0x4 8c: e8bd4800 pop {fp, lr} 90: e12fff1e bx lr 反汇编代码中:栈可以用来保存寄存器中值。 3、初始化堆栈 通过上面的代码反汇编代码,我们可以知道栈对C语言非常重要,所以我们要对栈进行初始化。然后就可以用C语言来编程了。 无论是2440、6410还是210它们的SP指针都指向内存64M位置处。 2440内存:64MB 6410内存:256MB 210内存:512MB或1GB 所以它们的SP地址分别为 2440:0x34000000 6410:0x54000000 210:0x24000000 以6410为例进行编写: init_stack: ldr sp, =0x54000000 mov pc, lr 二、初始化BSS段 1、BSS段的作用 小知识:初始化的全局变量,存放在数据段。 局部变量存放在栈中。 malloc分配空间来自堆。 未初始化的全局变量存在BSS段。 实例: # include int year; int main() { year = 1024; return year; } 使用交叉编译:arm-linux-gcc -g year.c -o year 阅读elf文件:arm-linux-readelf -a year >readelf 进入elf文件查找year存放位置 89: 0001052c 4 OBJECT GLOBAL DEFAULT 23 year /*很显然year位于bss起始地址与终止之间,也就是说year在bss段*/ 90: 00010530 0 NOTYPE GLOBAL DEFAULT ABS __end__ 91: 00008380 116 FUNC GLOBAL DEFAULT 12 __libc_csu_init 92: 00010530 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__ /*bss终止地址*/ 93: 00010528 0 NOTYPE GLOBAL DEFAULT ABS __bss_start /*bss起始地址*/ 2、为什么要初始化bss段 我们都知道在编写C语言代码过程中,有时候我们定义全局变量时,并不会同时进行初始化,而是到使用时再进行初始化。但有时候我们会忘记进行初始化。所以程序员就希望我们存放在bss段未初始化的全局变量都赋给0x0,如果我们一看到这个值就知道,我们没有对全局变量赋值,这样就会避免很多错误的出现。当然bss段初始化就由我们的系统工程师来完成。 3、编写代码 对BSS段清零操作 init_bss: ldr r0, =bss_start ldr r1, =bss_end cmp r0, r1 moveq pc, lr clean_loop: mov r2, #0 str r2, [r0], #4 cmp r0, r1 bne clean_loop mov pc, lr 三、一跃跳进C语言 1、采用什么方式跳转 这里我们采用的是绝对跳转的方式。因为我们知道,main()函数是从SRAM中拷贝过去的,所以说相对跳转回调到SRAM中的main(),而我们要去内存中运行main()函数。 main.c int gboot_main() { return 0; } 然后还要在makefie中添加上main.o; 在start.S中添加代码为: ldr pc, gboot_main 跳转到gboot_main函数处执行C函数。 2、代码的编写 main.c #define GPKCON (volatile unsigned long*)0x7f008800 /*volatile是避免优化忽略,起保护作用*/ #define GPKDAT (volatile unsigned long*)0x7f008808 int gboot_main() { *(GPKCON) = 0x11110000; *(GPKDAT) = 0xa0; return 0; } 使用make编译后,生产.bin文件,然后下载到开发板,运行。 注:对于210开发板,还有一个地方要修改。210有16个字节的头,所以copy_sram时,要跳过16个字节后进行复制操作。 四、C与汇编混合编程 1、为什么要使用混合编程 汇编语言:执行效率高;编写繁琐 C语言:可读性强,移植性好,调试方便 这样可以提高执行效率,同时可以更直接的控制cpu的内部寄存器。 2、混合编程的类型 一共有三种:1、汇编调用C语言 2、c调用汇编语言 3、c内嵌汇编 2.1、汇编调用c语言(已经在上面部分讲过,不再赘述) 2.2、C调用汇编函数 汇编函数: #define GPKCON0 0x7f008800 #define GPKDAT 0x7f008808 .global light_led light_led: ldr r0, =GPKCON0 ldr r1, =0x11110000 str r1, [r0] ldr r0, =GPKDAT ldr r1, =0xa0 str r1, [r0] mov pc, lr C语言: int gboot_main() { light_led(); return 0; } 2.3、C内嵌汇编 格式: asm(_asm_)( 汇编语言部分 :输出部分 :输入部分 :修改描述部分 ); 汇编语句部分:汇编语句的集合,可以包含多条汇编语句,每条语句之间需要使用换行符“n”隔开或使用分号“;”隔开 输出语句:在汇编中被修改的C变量列表 输入语句:作为参数输入到汇编中的变量列表 修改描述语句:执行汇编指令会修改的寄存器描述 下面是C内嵌汇编的范例 void write_p15_c1(unsigned long value) { asm( "mcr p15, 0, %0, c1, c0, 0n" : :"r"(value)@编译器选择了一个R*寄存器value为R中的值 :"memory" ); } unsigned long read_p15_c1(void) { unsigned long value; asm( "mrc p15, 0, %0, c1, c0, 0n" :"=r"(value)@'='表示只写操作数,用于输出部 : :"memory" ); return value; } 下面有关一个优化问题: unsigned long old; unsigned long temp; asm volatile( "mrs %0, cpsrn" "orr %1, %0, #128n" "msr cpsr_c, %1n" :"=r"(old), "=r"(temp) : :"memory" ); 使用volatile来告诉编译器,不要对接下来的这部分代码进行优化。 下面是用C内嵌汇编完成的点亮led灯 #define GPKCON 0x7f008800 #define GPKDAT 0x7f008808 int gboot_main() { asm( "ldr r1, =0x11110000n" "str r1, [%0]n" "ldr r1, =0xa0n" "str r1, [%1]n" "mov pc, lrn" : :"r"(GPKCON),"r"(GPKDAT) :"r1" ); return 0; }
史海拾趣
|
我是一只菜鸟。遇到一个问题,烦请大侠帮忙解决。有一个喷墨墨盒,上有一芯片,记录打印次数,如果超过限制,就不让使用,想解开此芯片,把新墨盒的数据写到老墨盒上,在灌上代用墨汁,就不用买原装的了。本菜鸟在很偏僻的树林(沈阳)没有找到办法 ...… 查看全部问答> |
|
产品设计的可测试性(De sign For Testability. OFT) 也是产品可制造性的主要内容从生产角度考虑也是设计的工艺性之一.它是指在设计时考虑产品性能能够检测的难易程度,也就是说设计产品时应考虑如何以最简单的方法对产品的性能和加工质量进行检测, ...… 查看全部问答> |
|
DEBUGMSG(ZONE_TRACE, (TEXT("XXXX\r\n")));这样的语句在驱动里面怎么打印到调试串口? DEBUGMSG(ZONE_TRACE, (TEXT(\"XXXX\\r\\n\")));这样的语句在驱动里面怎么打印到调试串口? … 查看全部问答> |
|
近期对嵌入式系统开发很有兴趣,像微软的WM,谷歌的android,苹果的iphone OS 本身我学习计算机语言就是想涉足计算机系统开发,但是开始学了C#,被微软的VS小黑盒子式编程给洗脑了...感觉编程好像比画图还简单!好想学了C#跟没学似地, 所以现在觉 ...… 查看全部问答> |
|
程序的目的是把采集的温度通过串口显示,现在做的程度是我把硬件连接好后,用MSP430仿真器查看温湿度的数值不正确,比如humi_val.i用quick watch查看value为62850这样的数据,单片机的txd用示波器观察没有波形,可能程序存在很大问题,现在比 ...… 查看全部问答> |
|
我把所有的程序都只注释掉了,只留IO初始化,SD16_A初始化,和一些延时子程序。 一运行到SD16_A初始化完成 中断允许后 单片机就复位了。这是什么情况 坐等高人解惑~~~~… 查看全部问答> |
|
BB Black 入门基础之Eclipse C++ 控制LED灯(中) 本帖最后由 lonerzf 于 2014-1-13 10:57 编辑 接着上一篇。之前的地方设置是有问题的,先不讲可能是哪,大家帮忙给查个错呗。谢谢啦{:soso_e100:} 补充下,如果用 SSH Only方式进行远程部署,则在某些情况下方便得多。 还是在编译好之后选择绿 ...… 查看全部问答> |




