ARM-bootloader-C语言环境设计
来源:互联网 发布:网络电玩城网站 编辑:程序博客网 时间:2024/05/01 17:02
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 <stdio.h>int main(){ int a; a++; return a;}反汇编后代码:
stack1: file format elf32-littlearmDisassembly of section .text:00000000 <main>:#include <stdio.h>int main(){ 0:e52db004 push{fp}; (str fp, [sp, #-4]!) 4:e28db000 addfp, sp, #0; 0x0 8:e24dd00c subsp, sp, #12; 0xc int a; a++; c:e51b3008 ldrr3, [fp, #-8] //显然变量a存放在了栈中。 10:e2833001 addr3, r3, #1; 0x1 14:e50b3008 strr3, [fp, #-8] return a; 18:e51b3008 ldrr3, [fp, #-8]} 1c:e1a00003 movr0, r3 20:e28bd000 addsp, fp, #0; 0x0 24:e8bd0800 pop{fp} 28:e12fff1e bxlr
从反汇编代码可以看出:局部变量a存放在了栈中。
2.2、参数传递
#include <stdio.h>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-littlearmDisassembly of section .text:00000000 <func1>:#include <stdio.h>void func1(int a,int b,int c,int d,int e,int f){ 0:e52db004 push{fp}; (str fp, [sp, #-4]!) 4:e28db000 addfp, sp, #0; 0x0 8:e24dd01c subsp, sp, #28; 0x1c c:e50b0010 strr0, [fp, #-16] 10:e50b1014 strr1, [fp, #-20] 14:e50b2018 strr2, [fp, #-24] 18:e50b301c strr3, [fp, #-28]int k;k=e+f; 1c:e59b3004 ldrr3, [fp, #4] 20:e59b2008 ldrr2, [fp, #8] 24:e0833002 addr3, r3, r2 28:e50b3008 strr3, [fp, #-8]} 2c:e28bd000 addsp, fp, #0; 0x0 30:e8bd0800 pop{fp} 34:e12fff1e bxlr00000038 <main>:int main(){ 38:e92d4800 push{fp, lr} 3c:e28db004 addfp, sp, #4; 0x4 40:e24dd008 subsp, sp, #8; 0x8 func1(1,2,3,4,5,6); 44:e3a03005 movr3, #5; 0x5 48:e58d3000 strr3, [sp] 4c:e3a03006 movr3, #6; 0x6 50:e58d3004 strr3, [sp, #4] 54:e3a00001 movr0, #1; 0x1 58:e3a01002 movr1, #2; 0x2 5c:e3a02003 movr2, #3; 0x3 60:e3a03004 movr3, #4; 0x4 // 当参数<=4时,可以用通用寄存器来传递参数,否则就要用到栈。 64:ebfffffe bl0 <func1> return 0; 68:e3a03000 movr3, #0; 0x0} 6c:e1a00003 movr0, r3 70:e24bd004 subsp, fp, #4; 0x4 74:e8bd4800 pop{fp, lr} 78:e12fff1e bxlr
从反汇编代码可以看出:栈可以传递参数,当参数不大于4时,可以用寄存器来传递,一旦大于4就把剩余的参数,用栈来传递。
2.3、保存寄存器值
#include <stdio.h>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-littlearmDisassembly of section .text:00000000 <func2>:#include <stdio.h>void func2(int a,int b){ 0:e52db004 push{fp}; (str fp, [sp, #-4]!) 4:e28db000 addfp, sp, #0; 0x0 8:e24dd014 subsp, sp, #20; 0x14 c:e50b0010 strr0, [fp, #-16] 10:e50b1014 strr1, [fp, #-20] int k; k=a+b; 14:e51b3010 ldrr3, [fp, #-16] 18:e51b2014 ldrr2, [fp, #-20] 1c:e0833002 addr3, r3, r2 20:e50b3008 strr3, [fp, #-8]} 24:e28bd000 addsp, fp, #0; 0x0 28:e8bd0800 pop{fp} 2c:e12fff1e bxlr00000030 <func1>:void func1(int a,int b){ 30:e92d4800 push{fp, lr} 34:e28db004 addfp, sp, #4; 0x4 38:e24dd010 subsp, sp, #16; 0x10 3c:e50b0010 strr0, [fp, #-16] 40:e50b1014 strr1, [fp, #-20] //保存了寄存器的值int c;func2(3,4); 44:e3a00003 movr0, #3; 0x3 48:e3a01004 movr1, #4; 0x4 4c:ebfffffe bl0 <func2>c=a+b; 50:e51b3010 ldrr3, [fp, #-16] 54:e51b2014 ldrr2, [fp, #-20] 58:e0833002 addr3, r3, r2 5c:e50b3008 strr3, [fp, #-8]} 60:e24bd004 subsp, fp, #4; 0x4 64:e8bd4800 pop{fp, lr} 68:e12fff1e bxlr0000006c <main>:int main(){ 6c:e92d4800 push{fp, lr} 70:e28db004 addfp, sp, #4; 0x4 func1(1,2); 74:e3a00001 movr0, #1; 0x1 78:e3a01002 movr1, #2; 0x2 7c:ebfffffe bl30 <func1> return 0; 80:e3a03000 movr3, #0; 0x0} 84:e1a00003 movr0, r3 88:e24bd004 subsp, fp, #4; 0x4 8c:e8bd4800 pop{fp, lr} 90:e12fff1e bxlr
反汇编代码中:栈可以用来保存寄存器中值。
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, =0x54000000mov pc, lr
二、初始化BSS段
1、BSS段的作用
小知识:初始化的全局变量,存放在数据段。 局部变量存放在栈中。 malloc分配空间来自堆。 未初始化的全局变量存在BSS段。
实例:
# include <stdio.h>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_startldr r1, =bss_endcmp r0, r1moveq pc, lrclean_loop:mov r2, #0str r2, [r0], #4cmp r0, r1bne clean_loopmov 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*)0x7f008808int 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_ledlight_led:ldr r0, =GPKCON0ldr r1, =0x11110000str r1, [r0]ldr r0, =GPKDATldr r1, =0xa0str r1, [r0]mov pc, lrC语言:
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, 0\n"::"r"(value)@编译器选择了一个R*寄存器value为R中的值:"memory");}unsigned long read_p15_c1(void){unsigned long value;asm("mrc p15, 0, %0, c1, c0, 0\n":"=r"(value)@'='表示只写操作数,用于输出部::"memory");return value;}下面有关一个优化问题:
unsigned long old;unsigned long temp;asm volatile("mrs %0, cpsr\n""orr %1, %0, #128\n""msr cpsr_c, %1\n":"=r"(old), "=r"(temp)::"memory");使用volatile来告诉编译器,不要对接下来的这部分代码进行优化。
下面是用C内嵌汇编完成的点亮led灯
#define GPKCON 0x7f008800#define GPKDAT 0x7f008808int gboot_main(){asm("ldr r1, =0x11110000\n""str r1, [%0]\n""ldr r1, =0xa0\n""str r1, [%1]\n""mov pc, lr\n"::"r"(GPKCON),"r"(GPKDAT):"r1");return 0;}
- ARM-bootloader-C语言环境设计
- bootloader C语言环境的初始化
- ARM C语言笔记
- ARM Bootloader 的实现-------C 和ASM 混合编程
- ARM bootloader 详细介绍
- STM32 Arm-Cortex bootloader
- ARM Linux bootloader笔记
- 20150121 【 ARM 】 C语言+变量
- 20150122 【 ARM 】 C语言+循环
- 20150123 【 ARM 】 C语言+函数
- 20150124 【 ARM 】 C语言+指针
- ARM裸机开发-bootloader-我是bootloader设计师
- 基于C语言烧写C6713的二级bootloader
- Arm Linux BOOTLOADER全程详解
- ARM Linux BOOTLOADER全程详解
- 一点点arm bootloader的理解
- Luminary ARM 芯片bootloader用法
- Arm Linux bootloader 全程详解
- 菜鸟程序员的成长之路(二)——时间都去哪儿了
- 20 个超酷的 HTML5/CSS3 应用及源码
- Linux fstab自动挂载分区
- 火车站杀人事件 中国的领导人这是怎么领导的
- 计算复杂数学表达式的值
- ARM-bootloader-C语言环境设计
- 分享自己的一个小数据库“Bug”
- VS2010编程调试笔记1
- 解决Cannot create JDBC driver of class '' for connect URL 'null'问题
- PAT 1010. Radix
- 从《Puss in Boots》看职场生存之道
- C#面向对象之多态
- 为什么中国会走到这一步
- 程序员面试题精选100题(36)-在字符串中删除特定的字符[算法]