Linux裸机开发5——代码段与代码重定向

Linux裸机开发5——代码段与代码重定向

声明:以下知识与图片全部来自韦东山裸机开发视频

一、重定向的引入

1、Nor与Nand的启动特性

1)Nor启动

1、Nor可以像内存一样读,不可写

2、Nor启动时SRAM的起始地址为:0x400,0000

3、程序中含有需要改变的变量,需要重定向到SDRAM中进行修改

2)Nand启动

1、Nand可读可写

2、Nand启动时SRAM的起始地址为0

3、Nand启动时先将前4k的代码读出并复制,再放入内存(SRAM)中,如果大于4k则将整个程序放入SDRAM中

2、代码段的概念

由反汇编文件可以看到,代码分成许多段,如下图:

在这里插入图片描述

3、重定向

实际上就是将一个段从原地址进行内容复制到另一个地址

二、链接脚本的引入

1、对数据段进行重定向使用链接脚本

1、由反汇编文件可得,数据段位于0x800

2、要使Nor启动的时候,可以访问全局变量,就要将数据段重定向到SDRAM——SDRAM在2440的首地址为0x300,0000

3、但是,如果直接将数据段复制到0x300,0000时,中间有大空隙,导致.bin文件有800多M

4、因此,采用链接脚本,将数据段拼在代码段的后面(紧跟着),再将其烧录到Nor Flash中,再使用重定位,即将数据段拷贝到SDRAM中,这样就可以访问

2、链接脚本的解析

1)链接脚本的语法

相关链接:

Using LD, the GNU linker

在这里插入图片描述

2)利用链接脚本进行重定向的原理:

在这里插入图片描述

加载地址:段在重定向之前在bin中的地址

运行地址:段在重定位之后的地址,即运行时的地址

程序运行时,应位于runtime address(运行地址)

1、因此重定向实际上是将段从原地址复制到另一个地址(通常为SDRAM)中并运行,改变了数据段的运行地址**

2、将数据段拷贝到SDRAM之后,实际上Nor Flash的数据是不会产生变化的,只是运行地址变成了SDRAM

3)ELF文件与BIN文件

ELF文件

在这里插入图片描述

BIN文件:

在这里插入图片描述

3、编写代码

1)指定重定向前数据段的起始地址

由重定向前的代码测试可得,数据段的起始地址为:0x900

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x900)/*表示加载地址为0x8454,运行地址为0x3000000,即SDRAM的起始地址*/
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;		/*获取重定向后数据段起始地址为data_start*/
      *(.data) 
      data_end = . ;		/*获取重定向后数据段结束地址为data_start*/
   }
   .bss  : { *(.bss) *(.COMMON) }	/*紧接着的是bss与common段*/
}

事实上,经过后面的视频学习可得:

重定位之前的代码,代码跳转到的实际位置与bin文件中显示的位置无关

2)Makefile文件加入的命令
arm-linux-ld

一般使用-T File (连接脚本),arm内存资源相对充足,可以在连接脚本里,让代码的text段,data段,bss段连在一起放到一个整块空间内,在脚本内也方便修改。最终会生成elf文件

例子:

arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf

下面将几个重要的命令介绍一下:
-T File     --script FILE指定一个链接器脚本文件,指示编译器按照脚本进行链接;
-Tbss ADDRESS    bss段的链接地址;
-Ttext ADDRESS   代码段链接地址;
-Tdata ADDRESS  数据段链接地址;
-r --relocateable 产生可重定向的输出,比如,产生一个输出文件它可再次作为‘ld'的输入,这经常被叫做“部分链接”,当我们需要将几个小的.o文件链接成为一个.o文件的时候,需要使用此选项。
-e  指定程序的入口标号;
-l   LibName 指定要链接的库;
-L  Directory 增加库文件的搜索路径;
-o  FILE 设置输出文件名;
-O  输出文件最优;
-a  指定arch体系;

arm-linux-ld链接工具的介绍

3)使用链接脚本进行链接的代码

Makefile

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o uart.o uart.c
	arm-linux-gcc -c -o init.o init.c
	arm-linux-gcc -c -o main.o main.c
	arm-linux-gcc -c -o sram_start.o sram_start.S
	arm-linux-ld -T sram.lds sram_start.o led.o uart.o init.o main.o -o sram.elf
	arm-linux-objcopy -O binary -S sram.elf sram.bin
	arm-linux-objdump -D sram.elf > sram.dis
clean:
	rm *.bin *.o *.elf *.dis
	

sram_start.S

.text
.global _start

_start:

    //关闭看门狗
    mov r0, #0x53000000
    mov r1, #0
    str r1, [r0]

    /*   先设置时钟  FCLK:HCLK:PCLK = 400:100:50   */
    //先设置lock time
    ldr r0, =0x4C000000
    ldr r1, =0xFFFFFFFF
    str r1, [r0]

    //设置HCLK= FCLK/4 与PCLK = FCLK/8
    ldr r1, =0x4C000014
    ldr r0, =0x5
    str r0, [r1]

    //设置PLL之前先设置异步模式
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0

    //设置PLL
    ldr r0, =0x4C000004
    ldr r1, =(92<<12)|(1<<4)|(1<<0)
    str r1, [r0]

    //根据nand与nor的特性自动辨认nor启动与nand启动
    //nor flash可读不可写
    mov r1, #0x0    //r1 = 0 
    ldr r0, [r1]    //将0地址的数值保存在r0中
    str r1, [r1]    //向0地址中写入0
    ldr r2, [r1]    //取出0地址的值
    cmp r1, r2      //比较r1与r2的值,相等则为nand,否则为nor
    ldr sp, = 4096+0x40000000
    moveq sp, #4096   //r1 = r2,nand启动
    streq r0, [r1]    //恢复0地址原来的值

    //调用SDRAM初始化函数
    bl sdram_init

    //获取重定向相关地址
    ldr r0, = data_load_addr        //加载地址,重定向前数据段的起始地址
    ldr r1, = data_start            //数据段重定向之后的起始地址
    ldr r2, = data_end              //数据段重定向之后的终止地址

copy:
    //执行重定向
    ldrb r3, [r0]                   //将加载地址中的值存入r3
    strb r3, [r1]                   //将r3的值赋予运行地址中的值
    add r0, r0, #1                  //加载地址加1
    add r1, r1, #1                  //运行地址加1
    cmp r1, r2                      //比较当前运行地址与终止地址
    bne copy                        //如果此时运行地址不等于终止地址,则还未结束,继续进行拷贝(重定向)

    /*调用main函数*/
    bl main

/*死循环*/
halt:
    b halt
   

sram.lds

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
   .bss  : { *(.bss) *(.COMMON) }
}

三、链接脚本的改进

1、引入字节对齐

因为原来使用的是:

ldrb 与strb,也就是一个一个字节去操作,效率是很低的

硬件(2 字节的Nand flash )例如是16字节时,

当使用单字节读取16个字节时,其余高24字节会被屏蔽掉不传入Nand flash中,

程序需要执行16次,硬件需要执行16/2 = 8次

而使用 ldr / str ——4字节读取指令时

稿24字节不会被硬件屏蔽掉,

程序只需执行 16/4 = 4,硬件需要执行16/2 = 8次

   /*以下指令表示4字节对齐*/
   . = ALIGN(4);
SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
       . = ALIGN(4);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
    . = ALIGN(4);
   bss_start = . ;
   .bss  : { *(.bss) *(.COMMON) }
   bss_end = . ;
}

2、清除bss段

一般来说 bss段是不需要写入bin文件中,因此会对其进行清除

汇编文件

    //获取bss段相关地址
    ldr r0, = bss_start             //bss段的起始地址
    ldr r1, = bss_end               //bss段的终止地址

clearb:
    //清除bss段
    mov r2, #0                    //初始化一个变量为0,用于之后的清零操作
    strb r2, [r0]                 //将起始地址保存的值清零
    add r0, r0, #1                //起始地址加1
    cmp r0, r1                    //比较当前运行地址与终止地址
    bne clearb                   //如果此时运行地址不等于终止地址,则还未结束,继续进行bss段的清零

链接文件

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
   bss_start = . ;
   .bss  : { *(.bss) *(.COMMON) }
   bss_end = . ;
}

四、位置无关码

1、重定向之前的代码与位置无关

1、重定向之前的代码与位置无关,由位置无关码构成

2、实际上重定向之前的pc跳转:b bl

并不是跳转到反汇编文件中的地址,而是跳转到pc + 偏移,偏移是由编译器决定的

在这里插入图片描述

2、如何写位置无关码

1、调用程序时使用B/BL相对跳转指令

2、重定位之前, 不可使用绝对地址,比如:
不可访问全局变量/静态变量
不可访问有初始值的数组(因为初始值放在rodata里,使用绝对地址来访问)

3、 重定位之后, 使用绝对跳转命令跳到运行地址,比如:
ldr pc, =main

3、C代码中如何使用链接脚本中定义的变量

C代码中如何使用链接脚本中定义的变量
http://www.100ask.org/bbs/forum.php?mod=viewthread&tid=16231&highlight=%C1%B4%BD%D3%BD%C5%B1%BE

参考文章:https://sourceware.org/ml/binutils/2007-07/msg00154.html

1)C函数怎么使用lds文件中的变量abc

1、在C函数中声明改变量为 extern 类型, 比如:
extern int abc;
2、使用时, 要使用 & 取址, 比如:
int *p = &abc; // p的值即为lds文件中abc的值

2)lds文件保存变量的实质

lds文件使用system table对里面所定义的变量进行保存
在这里插入图片描述

1、C程序不保存lds文件中的变量

2、借助system table保存lds的变量

4、将所有段重定向之后运行代码

1)用C语言编写重定向与清除bss段代码
//用于重定向的代码
void copy_by_c(void)
{
    extern int __data_start;
    extern int __bss_start;

    volatile unsigned int * start = (volatile unsigned int *)(&__data_start);
    volatile unsigned int * end = (volatile unsigned int *)(&__bss_start);
    volatile unsigned int * src = (volatile unsigned int *)0;

    while (start <= end)
    {
        *start++ = *src++;
    }

}

//用于清除bss段的代码
void clear_by_c(void)
{
    extern int __bss_start;
    extern int _end;

    volatile unsigned int * start = (volatile unsigned int *)(&__bss_start);
    volatile unsigned int * end = (volatile unsigned int *)(&_end);

    while (start <= end)
    {
        *start++ = 0;
    }

}

2)改进后的lds文件
SECTIONS
{
	. = 0x30000000;
	__data_start = . ;
	
	. = ALIGN(4);
	.text      :
	{
	  *(.text)
	}

	. = ALIGN(4);
	.rodata : { *(.rodata) }

	. = ALIGN(4);
	.data : { *(.data) }

	. = ALIGN(4);
	__bss_start = .;
	.bss : { *(.bss) *(.COMMON) }
	_end = .;
}

3)改进后的汇编文件
//调用SDRAM初始化函数
bl sdram_init

//执行重定向
bl copy_by_c

//执行bss段的清除
bl clear_by_c

/*调用main函数*/
// bl main                       //相对跳转
ldr pc, = main                  //绝对跳转,重定向之后要使用绝对跳转,否则跳转不到真正的地址   
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值