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, 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, 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;}

1 0
原创粉丝点击