uboot-C语言环境初始化
程序员文章站
2024-02-25 11:02:46
...
1-栈初始化
栈是一种具有后进先出性质的数据组织方式,也就是说,后存放的先取出,先存放的后取出。
根据sp指针指向的位置,栈可以分为满栈和空栈。
满栈:当堆栈指针sp总是指向最后压入堆栈的数据。
空栈:当堆栈指针sp总是指向下一个将要放入数据的空位置。
ARM采用满栈!
根据SP指针移动方向,栈可以分为升栈和降栈。
升栈:随着数据的入栈,SP指针从低地址->高地址移动。
降栈:随着数据的入栈,SP指针从高地址->低地址移动。
ARM采用降栈。
ARM系统采用满降栈!!
栈帧:就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。栈帧的两个边界分别由fp(r11)和sp(r13)来限定。
栈的作用
1.保存局部变量
2.参数传递
3.保存寄存器值
通过汇编来说明局部变量保存在栈中。
范例解析 stack1.c --- 保存局部变量
arm-linux-objdump -D -S stack1 > dump1
打开反汇编文件,然后找到main函数这里,分析汇编。
push {fp} ; (str fp, [sp, #-4]!) 把fp指针保存到堆栈中,push {fp}翻译过来就是分号后面的汇编代码。ARM采用的是满栈的方式,所以SP指针指向的数据,一定是有效的数据。将fp数据保存在sp-4 的位置。在修改fp之前,先保存。[sp, #-4]! “!”表示要将sp-4 的值再赋值给sp,是需要改变sp值。没有!是不改变sp值。
add fp, sp, #0 ; 0x0 将sp的数据保存在fp中,也就是上一行代码sp-4之后的值。此时的fp正好是这个程序的栈开始地方。
sub sp, sp, #12 ; 0xc sp-12,
ldr r3, [fp, #-8] 将fp-8的值存进r3寄存器 也就是说a存放在fp-8的这个位置
add r3, r3, #1 ; 0x1 执行a++
str r3, [fp, #-8] 将计算结果存进fp-8位置,也就是放在变量a中。
stack2.c --- 传递参数
反汇编之后来分析汇编代码,
函数传递参数时,当参数<=4时,是利用r0-r3寄存器来传递。
当大于4个时候,采用栈来传递。
stack3.c --- 保存寄存器的值
在函数调用的过程中,栈保存寄存器的值。main中,把func1的两个参数保存在r0、r1中,func1又调用func2,func2的两个参数也是需要保存在r0、r1中的,这样的话就会修改原来r0、r1的值,所以,就要先把r0 r1的值保存在栈中。
代码编写:6410内存的起始地址为50000000,然后我们规定,64MB的地方为栈的起始地址。
局部变量:栈
arm-linux-readelf -a bss > dump
year的地址在bss开始和结束之间,所以为初始化的全局变量是放在bss段。
为什么要初始化bss段呢?因为有可能在写程序的时候,全局变量没有初始化就使用了。我们希望bss段中的值开始就是0的。
bss_start bss_end在连接器脚本当中定义了,代码编写如下:
相对:b bl指令 利用两个标号之间的差值,将差值加到pc指针中完成跳转。
绝对:ldr pc, =main
在这之前,所有的代码都是运行在SRAM(垫脚石中,6410为4k),所有之前的b bl都是在SRAM中运行的。现在的板子中,垫脚石和内存中都有一份代码,跳转到c语言我们希望pc指针指向内存中的main,所以用ldr pc,=main
4-c与汇编混合编程
汇编语言:执行效率高,但编写繁琐
c语言:可读性强,移植性好,调试方便
为什么需要混合编程?
1.执行效率
ldr pc,=main 这就是汇编调用C函数。
二、c调用汇编函数
编译的时候会出错,没有定义light_led。汇编语言中所实现的标号(函数),想让其它文件引用,需要将标号声明为.global属性。
三、c内嵌汇编
格式:
后面三部分是可以省略,但是冒号:不能省略!
1.汇编语句部分:汇编语句的集合,可以包含多条汇编
语句,每条语句之间需要使用换行符 “\n”隔开或
使用分号“ ; ”隔开。
2.输出部分:在汇编中被修改的C变量列表
3.输入部分: 作为参数输入到汇编中的变量列表
栈是一种具有后进先出性质的数据组织方式,也就是说,后存放的先取出,先存放的后取出。
根据sp指针指向的位置,栈可以分为满栈和空栈。
满栈:当堆栈指针sp总是指向最后压入堆栈的数据。
空栈:当堆栈指针sp总是指向下一个将要放入数据的空位置。
ARM采用满栈!
根据SP指针移动方向,栈可以分为升栈和降栈。
升栈:随着数据的入栈,SP指针从低地址->高地址移动。
降栈:随着数据的入栈,SP指针从高地址->低地址移动。
ARM采用降栈。
ARM系统采用满降栈!!
栈帧:就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。栈帧的两个边界分别由fp(r11)和sp(r13)来限定。
栈的作用
1.保存局部变量
2.参数传递
3.保存寄存器值
通过汇编来说明局部变量保存在栈中。
范例解析 stack1.c --- 保存局部变量
#include <stdio.h>
int main()
{
int a;
a++;
return a;
}
arm-linux-gcc -g stack1.c -o stack1arm-linux-objdump -D -S stack1 > dump1
打开反汇编文件,然后找到main函数这里,分析汇编。
push {fp} ; (str fp, [sp, #-4]!) 把fp指针保存到堆栈中,push {fp}翻译过来就是分号后面的汇编代码。ARM采用的是满栈的方式,所以SP指针指向的数据,一定是有效的数据。将fp数据保存在sp-4 的位置。在修改fp之前,先保存。[sp, #-4]! “!”表示要将sp-4 的值再赋值给sp,是需要改变sp值。没有!是不改变sp值。
add fp, sp, #0 ; 0x0 将sp的数据保存在fp中,也就是上一行代码sp-4之后的值。此时的fp正好是这个程序的栈开始地方。
sub sp, sp, #12 ; 0xc sp-12,
ldr r3, [fp, #-8] 将fp-8的值存进r3寄存器 也就是说a存放在fp-8的这个位置
add r3, r3, #1 ; 0x1 执行a++
str r3, [fp, #-8] 将计算结果存进fp-8位置,也就是放在变量a中。
stack2.c --- 传递参数
#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;
}
反汇编之后来分析汇编代码,
函数传递参数时,当参数<=4时,是利用r0-r3寄存器来传递。
当大于4个时候,采用栈来传递。
stack3.c --- 保存寄存器的值
#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;
}
在函数调用的过程中,栈保存寄存器的值。main中,把func1的两个参数保存在r0、r1中,func1又调用func2,func2的两个参数也是需要保存在r0、r1中的,这样的话就会修改原来r0、r1的值,所以,就要先把r0 r1的值保存在栈中。
代码编写:6410内存的起始地址为50000000,然后我们规定,64MB的地方为栈的起始地址。
init_stack:
ldr sp, =0x54000000
mov pc ,lr
2-初始化BSS段
初始化的全局变量:数据段局部变量:栈
malloc:堆
未初始化的全局变量:bss段
bss.c
#include <stdio.h>
int year;
int main()
{
year = 2017;
return year;
}
arm-linux-gcc bss.c -o bssarm-linux-readelf -a bss > dump
year的地址在bss开始和结束之间,所以为初始化的全局变量是放在bss段。
为什么要初始化bss段呢?因为有可能在写程序的时候,全局变量没有初始化就使用了。我们希望bss段中的值开始就是0的。
bss_start bss_end在连接器脚本当中定义了,代码编写如下:
clean_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
3-汇编语言跳到c语言运行
跳转有相对跳转和绝对跳转,相对:b bl指令 利用两个标号之间的差值,将差值加到pc指针中完成跳转。
绝对:ldr pc, =main
在这之前,所有的代码都是运行在SRAM(垫脚石中,6410为4k),所有之前的b bl都是在SRAM中运行的。现在的板子中,垫脚石和内存中都有一份代码,跳转到c语言我们希望pc指针指向内存中的main,所以用ldr pc,=main
4-c与汇编混合编程
汇编语言:执行效率高,但编写繁琐
c语言:可读性强,移植性好,调试方便
为什么需要混合编程?
1.执行效率
2.能够更直接地控制处理器 访问MSR MRS CPSR寄存器等。
一、汇编调用C函数ldr pc,=main 这就是汇编调用C函数。
二、c调用汇编函数
假设在start.s中用汇编实现了light_led 标号。
int gboot_main()
{
light_led();
return 0;
}
编译的时候会出错,没有定义light_led。汇编语言中所实现的标号(函数),想让其它文件引用,需要将标号声明为.global属性。
三、c内嵌汇编
格式:
__asm__(
汇编语句部分
:输出部分 -- 可能修改了c语言中变量的值修改了,这些变量就放在输出部分
:输入部分 -- 可能需要从c语言中拿到一些参数作为汇编的操作数,这些参数就是放在输入部分
:破坏描述部分 -- mov r0,#0 这样把r0原来的值破坏了,所以要放到这个地方来。
);
C内嵌汇编以关键字”__asm__”或”asm”开始,下辖四个部分,各部分之间使用":"分开, 第一部分是必须写的,后面三部分是可以省略,但是冒号:不能省略!
1.汇编语句部分:汇编语句的集合,可以包含多条汇编
语句,每条语句之间需要使用换行符 “\n”隔开或
使用分号“ ; ”隔开。
2.输出部分:在汇编中被修改的C变量列表
3.输入部分: 作为参数输入到汇编中的变量列表
4.破坏描述部分: 执行汇编指令会破坏的寄存器描述
void write_p15_c1(unsigned long value)
{
__asm__(
"mcr p15, 0, %0, c1,c0, 0\n"
:
: "r" (value) @编译器选择一个rx寄存器。"r"表示是一个通用寄存器。值从value中来。
);
}
凡是要去读的参数,都是放在输入部分,凡是要是写进去的参数,都是放在输出部分。
unsigned long read_p15_c1(void)
{
unsigned long value;
__asm__(
"mcr p15, 0, %0, c1,c0, 0\n"
: "=r" (value) @ "=" 表示只写操作数,用于输出部。r0中的值要存放到value变量中。
:
: "memory"); //value变量放在栈中的,也就是内存中的值被修改了,通过“memory”通知系统有语句去修改内存。
return value;
}
使用volatile来告诉编译器,不要对接下来的这部分代码进行优化
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");
使用内嵌汇编点亮LED
#define GPKCON 0x7f008800
#define GPKDAT 0x7f008808
int gboot_main()
{
__asm__(
"ldr r1, =0x11110000"
"str r1, [%0]\n"
"ldr r1, =0xa0\n"
"str r1, [%1]\n"
:
:"r" (GPKCON), "r" (GPKDAT)
:"r1"
);
}