代码重定位——基本原理及实现

代码重定位——基本原理及实现

小狼@http://blog.csdn.net/xiaolangyangyang


一、链接地址、运行地址、加载地址、存储地址、位置相关与位置无关

1、运行地址:程序执行时的地址,因为命令执行分取指、译码、执行,所以运行地址就是PC-8处的地址。
2、链接地址:链接地址是给编译器用的,用来计算代码中相关地址偏移的。
3、加载地址和存储地址是等价的,只是两个不同的说法而已,指的是程序保存在Flash中的地址。
4、位置无关码:b、bl、mov等都是位置无关码
5、位置相关码:ldr pc,=xxx 等都是位置有关码

    代码重定位就是在运行地址处相应区域填充代码和数据,这个是编译时已经确定好的,存储地址处有所有需要的“原材料”,一般代码重定位包含:

  1. 将程序从存储地址我复制我自己到运行时地址中;
  2. 设置stack、清除bss段等。

二、代码的组成

程序至少包含:代码段+数据段
1、代码段:.text
2、数据段:.data
        一般存储全局变量,初值不为0的经过初始化的全局变量
        如:char g_char = 'A';    //初值为A的字符型全局变量
只读数据段:.rodata
const的全局变量,只读数据段
.bss:初值为0,或者没有初值的全局变量,不保存在bin文件中
        如:int g_A = 0;                //初值为0的整形全局变量
               int g_B;                      //无初值的整形全局变量
commen:注释,不保存在bin文件中
        关于bss段和comment段为什么不保存在bin文件中,可以这样来理解,如果代码中有非常多的全局变量(比如100万个char型变量),如果这些数据都保存在bin文件中,那么bin文件得有多大,显然这是不可能的。
        所以,一般地,在链接脚本中,需要指出这几个段在内存中的分配情况。如图1所示:

三、重定位相关概念

3.1   Nand flash和Nor flash
        以前使用单片机时,没有仔细思考过这个问题,都是认为程序是烧写到芯片内部的flash中的,也没有仔细思考过,程序是怎么跳转到flash取指令并执行的。
       对于嵌入式系统来说,它的程序可能会比较大,超出它内部的flash大小,程序无法整个放入到芯片内部的flash中;甚至有些SoC芯片内部根本就没有flash,代码无法放入到芯片内部,只能放在片外flash芯片上,这时芯片就需要外挂flash芯片了---nand flash或者nor flash。

NAND FlashNor Flash

地址线

数据线

地址线、数据线复用,所有信息通过一根线传输

独立地址线

独立数据线

价格低、容量大价格高、容量小
坏块容易有坏块,数据的有效性不能保证无坏块
访问操作因为没有单独的地址线,所以PC无法直接访问它,也就无法取指令并执行因为有单独的地址线,PC可以通过地址线取指令
读写操作不能象内存一样读,也不能像内存一项写可以像内存一样读,但是不能向内存一样直接写,写入需要单独设置操作

SDRAM:主要用于程序执行时的程序存储、执行或计算,类比于PC的内存

3.2    为什么需要重定位

Flash需要重定位的原因
NAND Flash因为PC不能直接访问它,要想执行它内部的程序,需要将NAND中的代码拷贝到SDRAM等RAM中,所以需要重定位text和data段到RAM
NOR Flash虽然PC可以直接方位NOR,但是因为不能像内存一样写NOR Flash,程序中的全局变量、静态变量等都是存在bin文件中的,也就是说存在NOR上的,当程序需要修改这些变量时,无法修改他们,所以需要重定位data段到RAM

3.3   重定位之前的状态
       重定位决定了PC取指令的地址,但是很显然SoC芯片刚刚上电时,重定位还没有实现,SoC需要执行一段代码来实现重定位,所以这时PC指针并没有指向重定位地址的地方,那么这个过程是如何实现的呢?
3.4  上电后,PC指向哪里
       对于任何一种SoC芯片来说,上电后PC指针的位置是有硬件设计决定的,一般地,对于cortex-M系列内核的芯片,上电后PC指针都为0,指向0地址处。
       对于nand启动,0地址对应片内sram,因为nand控制器上电后自动将nand中的前4k大小拷贝到了sram中,所以此时sram中存在代码(即nand中前4K代码),内核就可以取指令并执行了。
       对于nor启动,0地址对应nor芯片,已经将程序下载到nor上了,所以此时PC也可以取指令并执行了。

四、重定位过程和方法

4.1  nor flash启动-----只重定位 .data

示例:

char g_char ='A';
constchar g_char2 ='B';
int g_A =0;
int g_B;

int main(void)
{
	uart0_init();
	while(1)
	{
		putchar(g_char); 
		g_char++;    /*nor启动,此代码无效*/
		delay(1000000);
	}

	return0;
}

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 start.ostart.S
	arm-linux-ld -Ttext 0 -Tdata 0x800 start.o led.ouart.oinit.o main.o -o sdram.elf
	arm-linux-objcopy -O binary -Ssdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf> sdram.dis

clean:
	rm *.bin *.o *.elf *.dis

        按照上面的Makefile进行编译后,将bin文件下载到nor flash中,发现输出的字符一直都是‘A’,可是明明在程序中,对全局字符变量g_char进行++操作,这就说明,保存在nor flash中的g_char是不能进行写操作的
        为了实现修改,考虑将g_char保存在外部的SDRAM中,修改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 start.ostart.S
	arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.ouart.o init.omain.o -o sdram.elf
	arm-linux-objcopy -O binary -Ssdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf> sdram.dis

clean:
	rm *.bin *.o *.elf *.dis

        编译之后,发现bin文件为800多M,显然这是不合理的,BIN文件的数值为什么是805306369?发现805306369=0x30000001,在Makefile中就是指明了全局变量保存在SDRAM中,所以BIN文件的保存地址是从0~0x30000000,其大小正好是0x30000001,因此,这个时候,代码段和数据段的存储格式如下:(中间产生了巨大的空洞hole)

为了解决上面的方法,代码过大的问题,有两种方式来解决:
A.    将data段重定位到SDRAM中,text段仍在NOR Flash中
        1.    仍然将全局变量数据段和代码段烧写到nor flash中
        2.    在运行时,代码段代码要能实现将数据段拷贝(重定位)到SDRAM中;
        3.    以后每次访问全局变量,都是去SDRAM中去访问,不去nor flash中访问

这个方式是通过链接脚本(sdram.lds文件)来实现的,如下所示

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

这样在makefile文件中,通过指定这个lds文件的方式来编译代码

arm-linux-ld-T sdram.lds  start.o led.ouart.oinit.o main.o -o sdram.elf

(1)对于elf文件
    elf文件含有地址信息,bin文件不含有地址信息。使用加载器(对于裸板,就是JTAG调试工具;对于APP,加载器也是一个APP)把elf文件读入内存的加载地址(load addr),然后运行程序。
    如果load addr不等于runtime addr,程序本身需要重定位。
    核心:程序运行时,应该位于runtime addr(或者是重定位地址,或者链接地址)
(2)对于bin文件
    首先将elf文件转换成bin文件,硬件机制的启动(没有加载器),如果bin文件所在位置不等于runtime addr,程序本身实现重定位。但是如果只是按照makefile和sdram.lds文件内容的话,还是不能将data段重定位到SDRAM中,需要一个程序将一开始处于nor中的data段拷贝到SDRAM中,这个程序是在start.S文件来实现的,如下:

ldrr1, =data_load_addr    /*data段在bin文件中的地址, 加载地址*/
   ldr r2, =data_start    /*data段在重定位地址, 运行时的地址*/
   ldr r3, =data_end
cpy:
   ldrb r4, [r1]
   strb r4, [r2]
   add r1, r1, #1
   add r2, r2, #1
   cmp r2, r3
bne cpy

4.2 将text段和data段都重定位在SDRAM中

为了解决上面的问题,解决办法:
    A.链接脚本指定runtime addr为SDRAM的地址。
    B.重定位之前的代码,与位置无关,即用位置无关码写成。
    C.在nor flash中最开始的代码,需要完成这样的动作,将整个nor中的代码拷贝到SDRAM中(上面的方式是分体的程序,即代码段和数据段是分开来存放的,对于上面的示例,就是代码段保存在nor flash,数据段保存在sdram中,这种方式适用于单片机,它们本身含有内部flash,内存空间较小,如果将代码段也拷贝到内存中,会造成内存剩余可用空间变小;合体的方式就是这个示例中的链接脚本,它适用于嵌入式系统,因为嵌入式系统的内存空间较大)

SECTIONS
{
   . = 0x30000000;
   . = ALIGN(4);
   .text      :
   {
    *(.text)
   }
   . = ALIGN(4);
   .rodata : { *(.rodata) }
   . = ALIGN(4);
   .data : { *(.data) }
   . = ALIGN(4);                          
   __bss_start = .;
   .bss : { *(.bss) *(.COMMON) }
   _end = .;
}

        链接脚本只是告诉程序需要将某些段拷贝到哪些内存位置,但是它本身不完成拷贝的动作,这个动作是start.S完成的,因此在start.S文件中,需要将text段、rodata段、data段整个拷贝到SDRAM对应的位置。

/*重定位text,rodata,data段整个程序*/
   mov r1, #0 /* */
   ldr r2, =_start    /*第一条指令运行地址*/
   ldr r3, =__bss_start  /* bss段起始地址*/
cpy:
   ldr r4, [r1]
   str r4, [r2]
   add r1, r1, #4
   add r2, r2, #4
   cmp r2, r3
   ble cpy

怎么写位置无关码(地址在任何地方都能执行)?
        a.  使用相对跳转命令,B/BL
        b.  重定位之前,不可使用绝对地址,比如不可访问全局变量、静态变量、有初始值的数组(rodata,data,使用绝对地址来访问),初始值放在rodata里面,使用绝对地址来访问。
        c.  重定位之后,可以使用绝对地址ldr pc, =xxx跳转到runtime addr
        d.  不使用绝对地址!最根本的办法是看反汇编
        注:这里穿插讲一下什么是位置无关指令,什么是位置相关指令,在上面的链接脚本中,需要代码段和数据段都被保存在sdram的起始地址0x30000000位置(. = 0x30000000[zj8] ;),这样编译出来的反汇编代码如下:
        请注意上面的bl sdram_init处的机器码是eb000102。如果希望将代码段和数据段保存在sdram的起始地址为0x3200000位置(. = 0x32000000;),再次编译代码,发现bl sdram_init处的机器码仍然是eb000102,按照常规的理解,bl指令现在希望跳转的地址是3200046c(之前的是3000046c),地址不同,按道理来说机器码肯定不同呀(机器码就是对指令的翻译),那这里为什么机器码是一样的呢,这就说明bl指令是位置无关的,它和需要跳转到哪里的地址是没有关系的(只和需要跳转的指令与代码段起始地址的差值有关系,就是说他会在代码段起始位置处查找需要跳转到的地址)

4.3  nand flash启动
      很显然,对于nand启动时,.text段和.data段都需要重定位到sdram中,也就是和nor flash中第2种重定位方式一样,这里就不在展开解释了。


相关链接

代码重定位解析
加载地址和运行时地址 & 代码重定位 (2020)
链接地址、运行地址、加载地址、存储地址、位置相关与位置无关

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 在《GPS全球定位接收机——原理与软件实现》这本书中,每章图片对应的程序是为了帮助读者理解和实践书中介绍的GPS原理和算法。 第一章图片对应的程序是用来演示GPS原理的基础知识。通过这个程序,读者可以学习到GPS信号的传播原理、卫星轨道和系统时间等基本概念。 第二章图片对应的程序是用来展示GPS信号的接收和处理。通过这个程序,读者可以学习到GPS信号的接收流程,包括接收机的前端电路和数字信号处理等关键技术。 第三章图片对应的程序是用来介绍GPS信号的解调和探测过程。通过这个程序,读者可以学习到GPS信号解调和探测的算法,包括载波相位锁定和码片时钟同步等技术。 第四章图片对应的程序是用来演示GPS信号的导航和定位过程。通过这个程序,读者可以学习到GPS信号的导航原理和定位算法,包括伪距测量、卫星轨道预测和位置解算等技术。 第五章图片对应的程序是用来展示GPS信号的误差分析和校正方法。通过这个程序,读者可以学习到GPS信号的误差来源和校正技术,包括钟差校正、大气延迟校正和多路径干扰校正等方法。 总之,每章图片对应的程序是为了帮助读者通过实践来加深对GPS原理和技术的理解。通过实际操作,读者可以更加直观地感受到GPS系统的工作原理,并且可以通过修改程序参数和算法来探索不同的实验结果,提高对GPS技术的应用和理论水平。 ### 回答2: GPS全球定位接收机是一种用来接收和解析来自卫星的导航信号,从而实现全球定位的设备。它基于GPS导航原理,通过接收多个卫星的信号,并计算信号传输时间差来确定接收机的位置。 在探讨GPS全球定位接收机的原理与软件实现的每一章中,可能会涉及到一些图片对应的程序,以下是对这些图片与程序的简要描述。 第一章讲解了GPS全球定位系统的概述,介绍了GPS接收机的组成部分和工作原理。可能会附有一幅GPS接收机的结构示意图,对应的程序可能是一个简单的硬件控制程序,用于控制GPS接收机的接收和处理功能。 第二章讲解了GPS信号接收与处理的原理。可能会附有一幅GPS信号接收和解调的示意图,对应的程序可能是一个解调算法的实现,用于解码接收到的GPS信号。 第三章讲解了GPS信号的特性与误差来源。可能会附有一幅GPS信号误差来源的示意图,对应的程序可能是一个误差模型的实现,用于对接收到的GPS信号进行误差修正。 第四章讲解了GPS接收机的定位算法。可能会附有一幅定位算法的示意图,对应的程序可能是一个定位算法的实现,用于根据接收到的多个卫星信号计算接收机的位置。 第五章讲解了GPS接收机的应用与发展。可能会附有一幅GPS应用场景的示意图,对应的程序可能是一个GPS应用程序的实现,用于实现特定的GPS定位功能,比如导航、地图等。 总之,GPS全球定位接收机的原理与软件实现涉及到多个方面,每章中的图片和对应的程序都是为了帮助理解和实现GPS定位功能。具体的图片和程序可能根据教材或者课程设计的不同而有所差异,但整体上都是为了帮助读者理解GPS接收机的工作原理和实现方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值