2019-8-10-linux

走远了吗. 提交于 2019-11-27 02:24:30

2019-8-10-linux

程序的组成

  1. 代码段:用于存放数据
  2. 数据段:用于存放全变量(有初始值,且不为零)
  3. 只读数据段:用于存放只读数据,如:const 变量
  4. bss段:用于存放未初始化的全局变量,或初始化为零的全局变量
  5. comment段:用于存放代码的一些注释信息
  • 其中bss段和comment的内容不存放在bin文件中。

代码重定位

  1. 为什么需要代码的重定位
  • 2440的启动方式有两种,nor flash启动和nand flash启动。(1) 首先讲解norflash启动的特点,noflash启动时,norflash中的内容可以像内存一样写,但是不能像内存一样读,因此会出现程序中变量进行修改无效的情况,其内部深层次的原因是,程序中的全局变量和静态变量都存放在bin上,写在norflash中,直接修改无效,因此需要重定位。(2)若采用nand flash启动,2440默认会将nand flash的前4k内容拷贝到sram中,此时sram的地址是从0开始的。但是,当代码的大小超过4k时,我们必须采用代码重定位,前4k的代码需要将整个程序拷贝到,sdram中运行。
  1. 重定位的两种方式
    重定位的两种方式

基本概念

  • 链接器把一个或多个输入文件合并成一个输出文件,目标文件的每个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的作用主要为预处理、编译、汇编和链接四个部分,链接的其中一个作用为符号的解析和地址的重定位,我们可以指定代码的起始地址和数据的起始地址。
  • 当数据段和代码段之间的间隔较大时,为避免程序链接时,出现较大的空洞,造成程序文件过大,可以运用链接脚本。

链接脚本介绍

GNU链接脚本
链接脚本介绍
连接脚本的格式

  • 两个必须的(secname,contents),其他optional
    1. secname:段名,用以命名此段。
    2. contents:决定那些内容放在本段,可以是整个目标文件(.o),也可以是目标文件中的某段(代码段、数据段等)。
    3. start:是段的重定位地址,即 本段运行的地址。如果代码中有位置无关指令,程序运行时这个段必须放在这个地址上。start可以用任意一种描述地址的符号来描述。
    4. BLOCK(align)指定块对齐。比如,前一个段从0x30000000到0x300003F1,此处标记为ALIGN(4),表示此处最小占用4Bytes。
    5. NOLOAD:告述加载器程序运行时不加载这段到内存。
    6. 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程序中声明为外部变量,任何类型都可以;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!