2019-8-10-linux
程序的组成
- 代码段:用于存放数据
- 数据段:用于存放全变量(有初始值,且不为零)
- 只读数据段:用于存放只读数据,如:const 变量
- bss段:用于存放未初始化的全局变量,或初始化为零的全局变量
- comment段:用于存放代码的一些注释信息
- 其中bss段和comment的内容不存放在bin文件中。
代码重定位
- 为什么需要代码的重定位
- 2440的启动方式有两种,nor flash启动和nand flash启动。(1) 首先讲解norflash启动的特点,noflash启动时,norflash中的内容可以像内存一样写,但是不能像内存一样读,因此会出现程序中变量进行修改无效的情况,其内部深层次的原因是,程序中的全局变量和静态变量都存放在bin上,写在norflash中,直接修改无效,因此需要重定位。(2)若采用nand flash启动,2440默认会将nand flash的前4k内容拷贝到sram中,此时sram的地址是从0开始的。但是,当代码的大小超过4k时,我们必须采用代码重定位,前4k的代码需要将整个程序拷贝到,sdram中运行。
- 重定位的两种方式
基本概念
- 链接器把一个或多个输入文件合并成一个输出文件,目标文件的每个section至少包含两个信息:名字和大小,大部分section还包括与它关联的一块数据块,一个section可被标记为“loadable”或“allocatable”。loadable section: 在输出文件运行时,相应的section内容将被加载到进程地址空间中。allocatable section:内容为空的section可被标记为可分配的,在输出文件运行时,在进程地址空间中空出大小同section指定大小的部分,在某些情况下,这块内存必须被置零。
- 每一个“loadable”或“allocatable”输出section通常包含两个地址:VMA(virtual memory address)和LMA(load memory address),通常VMA和LMA是相同的。但在嵌入式系统中,经常存在加载地址和执行地址不相同的情况:比如将输出文件加载到开发板的flash中(由LMA决定),而在运行时将位于flash中的输出文件复制到SDRAM中(VMA决定)。
链接脚本的作用
- NORFLASH启动时,NORFLASH中的内容可以向内存一样读,但是不能想内存一样写,因此makefile时,我们需要将data数据段的内容写到SDRAM中,即将数据段的内容指定地址到0x30000000地址处。
- makefile的作用主要为预处理、编译、汇编和链接四个部分,链接的其中一个作用为符号的解析和地址的重定位,我们可以指定代码的起始地址和数据的起始地址。
- 当数据段和代码段之间的间隔较大时,为避免程序链接时,出现较大的空洞,造成程序文件过大,可以运用链接脚本。
链接脚本介绍
- 两个必须的(secname,contents),其他optional
- secname:段名,用以命名此段。
- contents:决定那些内容放在本段,可以是整个目标文件(.o),也可以是目标文件中的某段(代码段、数据段等)。
- start:是段的重定位地址,即 本段运行的地址。如果代码中有位置无关指令,程序运行时这个段必须放在这个地址上。start可以用任意一种描述地址的符号来描述。
- BLOCK(align)指定块对齐。比如,前一个段从0x30000000到0x300003F1,此处标记为ALIGN(4),表示此处最小占用4Bytes。
- NOLOAD:告述加载器程序运行时不加载这段到内存。
- AT(ldadr):定义本段存储的地址,如果不使用这个选项,则加载地址等于运行地址,通过这个选项可以控制各段分别保存在输出文件中的不同位置。
链接脚本代码实例分析
SECTIONS { . = 0x30000000; //设置运行时地址 . = ALIGN(4); //设置内存对齐方式 .text : //设置secname { *(.text) //存放所有文件的代码段 } //未设置存储地址,默认存储地址与运行地址相同 . = ALIGN(4); //同上 .rodata : { *(.rodata) }//同上(只读数据段) . = ALIGN(4); //同上 .data : { *(.data) }//同上(数据段) . = ALIGN(4); __bss_start = .;//将当前地址赋值给_bss_start变量 .bss : { *(.bss) *(.COMMON) }//同上 _end = .; //将当前地址赋值给_end变量 }
C函数怎么使用lds文件中的变量
如何编写位置无关码
-
使用相对跳转命令 b或bl
-
重定位之间,不可使用绝对地址,不可访问全局变量/静态变量,也不可访问有初始值的数组(因为初始值放在rodata里,使用绝对地址来访问);
-
重定位之后,使用ldr pc = xxx,跳转到从定位之后的地址;
-
写位置无关码,其实就是不使用绝对地址,判断有没有使用绝对地址,除了前面的几条规则,最根本的方法是查看反汇编代码
-
反汇编文件里面,B或者BL某个值,只是起到方便查看的作用,并不是真的跳转
重定位代码实例分析
方式一
void copy2sdram(volatile unsigned int *src, volatile unsigned int *dest, unsigned int len) /* src, dest, len */ { unsigned int i = 0; while (i < len) { *dest++ = *src++; //++的优先级要高于* i += 4; } } void clean_bss(volatile unsigned int *start, volatile unsigned int *end) /* start, end */ { while (start <= end) { *start++ = 0; //++的优先级要高于* } } --------------------- /* 重定位text, rodata, data段整个程序 */ mov r0, #0 ldr r1, =_start /* 第1条指令运行时的地址 */ ldr r2, =__bss_start /* bss段的起始地址 */ sub r2, r2, r1 /*长度*/ bl copy2sdram /* src, dest, len */ /* 清除BSS段 */ ldr r0, =__bss_start ldr r1, =_end bl clean_bss /* start, end */ ---------------------
重点:
- 汇编中,为C语言传入的参数,依次就是R1、R2、R3
方式二
假设我们不想通过汇编传入参数,而是通过C语言直接取参数。
void copy2sdram(void) { /* 要从lds文件中获得 __code_start, __bss_start * 然后从0地址把数据复制到__code_start */ extern int __code_start, __bss_start;//声明外部变量 volatile unsigned int *dest = (volatile unsigned int *)&__code_start; volatile unsigned int *end = (volatile unsigned int *)&__bss_start; volatile unsigned int *src = (volatile unsigned int *)0; while (dest < end) { *dest++ = *src++; } } void clean_bss(void) { /* 要从lds文件中获得 __bss_start, _end */ extern int _end, __bss_start; volatile unsigned int *start = (volatile unsigned int *)&__bss_start; volatile unsigned int *end = (volatile unsigned int *)&_end; while (start <= end) { *start++ = 0; } } ---------------------
C函数怎么使用lds文件中的变量
- 在C函数中声明变量为extern外部变量类型,比如:extern int a;
- 使用时,要取值,比如:int *p = &abc;//p的只即为lds文件中adc的值
汇编文件中可以直接使用外部连接脚本中的变量,但C函数中要加上取地址符号的原因:
- C程序是不保存lds中的 变量的,对于万一要用到的变量,编译程序时,有一个symbol table符号表,使用时加上&得到到它的值,链接脚本的变量要在C程序中声明为外部变量,任何类型都可以;