代码重定位详解

一、段的概念以及重定位的引入

1.1 问题的引入

复制之前的串口程序,修改为:”01_uart_question“,添加全局变量,把它打印出来,看看会发生什么事。

#include "uart.h"

char g_char1 = 'A';
const char g_char2 = 'B';

void delay(int d)
{
	while(d--);
}

int main()
{
	char c;
	
	uart_init();
	
	putchar('l');
	putchar('j');
	putchar('g');
	putchar('6');
	putchar('6');
	putchar('6');
	putchar('\n');
	putchar('\r');
	
	putchar(g_char1);
	putchar(g_char2);
	
	while (1)
	{
		c = getchar();
		
		putchar(c);
		putchar('\n');
		putchar('\r');
		
		putchar(c+1);	
		putchar('\n');
		putchar('\r');		
	}
	
	return 0;
}

在这里插入图片描述
可以看见,我们在定义了g_char1和g_char2(定义为常量)后,分别打印时g_char1显示的是乱码,而g_char2显示正常。

为了研究该问题,我们写了一个string.c和string.h,里面是打印函数,我们将该文件包含进该工程中,查看g_char1和g_char2的地址。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

由图可见,g_char2指向ROM,且表现为只读,所以能被成功打印,而g_char1指向内存RAM,该区域是可读可写的,而我们对于内存未进行赋值,所以当访问到g_char1指向的地址时,就会打印乱码(该内存中的任意值)。

1.2 段的概念

代码段、只读数据段、可读可写的数据段、BSS段。

char g_Char = 'A';           // 可读可写,不能放在ROM上,应该放在RAM里
const char g_Char2 = 'B';    // 只读变量,可以放在ROM上
int g_A = 0;   // 初始值为0,干嘛浪费空间保存在ROM上?没必要
int g_B;       // 没有初始化,干嘛浪费空间保存在ROM上?没必要

所以,程序分为这几个段:

  • 代码段(RO-CODE):就是程序本身,不会被修改
  • 可读可写的数据段(RW-DATA):有初始值的全局变量、静态变量,需要从ROM上复制到内存
  • 只读的数据段(RO-DATA):可以放在ROM上,不需要复制到内存
  • BSS段或ZI段:
    • 初始值为0的全局变量或静态变量,没必要放在ROM上,使用之前清零就可以
    • 未初始化的全局变量或静态变量,没必要放在ROM上,使用之前清零就可以
  • 局部变量:保存在栈中,运行时生成
  • 堆:一块空闲空间,使用malloc函数来管理它,malloc函数可以自己写

1.3 重定位

保存在ROM上的全局变量的值,在使用前要复制到内存,这就是数据段重定位。

想把代码移动到其他位置,这就是代码重定位。

二、如何实现重定位

2.1 程序中含有什么?

  • 代码段:如果它不在链接地址上,就需要重定位
  • 只读数据段:如果它不在链接地址上,就需要重定位
  • 可读可写的数据段:如果它不在链接地址上,就需要重定位
  • BSS段:不需要重定位,因为程序里根本不保存BSS段,使用前把BSS段对应的空间清零即可

2.2 谁来做重定位?

  • 程序本身:它把自己复制到链接地址去
  • 一开始,程序可能并不位于它的链接地址上,为什么它可以执行重定位的操作?
    • 因为重定位的代码是使用“位置无关码”写的
  • 什么叫位置无关码:这段代码扔在任何位置都可以运行,跟它所在的位置无关
  • 怎么写出位置无关码:
    • 跳转:使用相对跳转指令,不能使用绝对跳转指令
      • 只能使用branch指令(比如bl main),不能给PC直接复制,比如ldr pc, =main
    • 不要访问全局变量、静态变量
    • 不使用字符串

2.3 怎么做重定位和清除BSS段?

  • 核心:复制
  • 复制的三要素:源、目的、长度
    • 怎么知道代码段/数据段保存在哪?(加载地址)
    • 怎么知道代码段/数据段要被复制到哪?(链接地址)
    • 怎么知道代码段/数据段的长度?
  • 怎么知道BSS段的地址范围:起始地址、长度?
  • 这一切
    • 在keil中使用散列文件(Scatter File)来描述
    • 在GCC中使用链接脚本(Link Script)来描述

2.4 加载地址和链接地址的区别

程序运行时,应该位于它的链接地址处,因为:

  • 使用函数地址时用的是"函数的链接地址",所以代码段应该位于链接地址处
  • 去访问全局变量、静态变量时,用的是"变量的链接地址",所以数据段应该位于链接地址处

但是: 程序一开始时可能并没有位于它的"链接地址":

  • 比如对于STM32F103,程序被烧录器烧写在Flash上,这个地址称为"加载地址"
  • 比如对于IMX6ULL/STM32MP157,片内ROM根据头部信息把程序读入内存,这个地址称为“加载地址”

加载地址 != 链接地址时,就需要重定位。

三、散列文件使用与分析

3.1 重定位的实质: 移动数据

把代码段、只读数据段、数据段,移动到它的链接地址处(mcmcpy)。
也就是复制
数据复制的三要素:源、目的、长度。

  • 数据保存在哪里?加载地址

  • 数据要复制到哪里?链接地址

  • 长度

这3要素怎么得到?
在keil中,使用散列文件来描述。
散列?分散排列?
是的,在STM32F103这类资源紧缺的单片机芯片中,

  • 代码段保存在Flash上,直接在Flash上运行(当然也可以重定位到内存里)
  • 数据段保存在Flash上,使用前被复制到内存里

3.2 散列文件示例

3.2.1 示例代码

复制”01_uart_question“,改名为”02_uart_sct“。

在 Keil 中进行如下配置:
在这里插入图片描述
会生成一个.sct结尾的文件,使用Nope++打开:
在这里插入图片描述

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

3.2.2 散列文件语法

一个散列文件由一个或多个Load region(加载域)组成:

load_region_description ::=
	load_region_name (base_address | ("+" offset)) [attribute_list] [max_size]
		"{"
			execution_region_description+
		"}

Load region中含有一个或多个Execution region(可执行域)
在这里插入图片描述

Execution region语法如下:

execution_region_description ::=
	exec_region_name (base_address | "+" offset) [attribute_list] [max_size | length]
		"{"
			input_section_description*
		"}

Execution region中含有一个或多个Input section
Input section语法如下:

input_section_description ::=
module_select_pattern [ "(" input_section_selector ( ","
input_section_selector )* ")" ]
input_section_selector ::=
"+" input_section_attr |
input_section_pattern |
input_section_type |
input_symbol_pattern |
section_properties

3.3 散列文件解析

在这里插入图片描述
*.o :所有objects文件

*:所有objects文件和库,在一个散列文件中只能使用一个*

.ANY:等同于*,优先级比*低;在一个散列文件的多个可执行域中可以有多个.ANY

.ANY (+RO):所有.o文件、库文件的只读数据段

.ANY (+XO):所有.o文件、库文件的可执行数据段

.ANY (+RW +ZI):所有.o文件、库文件的可读可写段和BSS段

3.4 怎么获得region的信息

3.4.1 可执行域的信息

在这里插入图片描述

3.4.2 加载域的信息

在这里插入图片描述

3.4.3 汇编代码里怎么使用这些信息

Load$$region_name$$Base	//下面修改时候要注意region_name(区域名)

在这里插入图片描述
在这里插入图片描述
通过上图可知,可执行域1的源地址和目的地址一致,所以能够正常执行,不需要重定位;而可执行域2的源地址紧随1后,但其目的地址与源地址不同,所以需要对可执行域进行重定位。所以需要将区域名字修改成 RW_IRAM1

示例代码如下:
string.c中的memcpy函数:

void mcmcpy(void *dest, void *src, unsigned int len)
{
	unsigned char *pcDest;
	unsigned char *pcSrc;
	
	while (len--)
	{
		*pcDest = *pcSrc;	//让目的地与源地址相等,后面两者长度++,知道len=0,完成复制
		pcSrc++;
		pcDest++;
	}
}
IMPORT |Image$$RW_IRAM1$$Base|
IMPORT |Image$$RW_IRAM1$$Length|
IMPORT |Load$$RW_IRAM1$$Base|
IMPORT memcpy

LDR R0, = |Image$$RW_IRAM1$$Base|    ; DEST
LDR R1, = |Load$$RW_IRAM1$$Base|     ; SORUCE
LDR R2, = |Image$$RW_IRAM1$$Length|  ; LENGTH
BL memcpy

在这里插入图片描述

修改start.s,让目的地址与源地址一样后,编译烧录,可以发现A被成功打印。

在这里插入图片描述

3.4.4 C语言里怎么使用这些信息

1.方法1

声明为外部变量。
注意:使用时需要使用取址符:

extern int Image$$RW_IRAM1$$Base;
extern int Load$$RW_IRAM1$$Base;
extern int Image$$RW_IRAM1$$Length;

memcpy(&Image$$RW_IRAM1$$Base, &Image$$RW_IRAM1$$Length, &Load$$RW_IRAM1$$Base);
2.方法2

声明为外部数组。
注意:使用时不需要使用取址符:

extern char Image$$RW_IRAM1$$Base[];
extern char Load$$RW_IRAM1$$Base[];
extern int Image$$RW_IRAM1$$Length;

memcpy(Image$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length, &Load$$RW_IRAM1$$Base);

四、清除BSS段(ZI段)

4.1 C语言中的BSS段

程序里的全局变量,如果它的初始值为0,或者没有设置初始值,这些变量被放在BSS段里,也叫ZI段。

char g_Char = 'A';
const char g_Char2 = 'B';
int g_A = 0;  // 放在BSS段
int g_B;      // 放在BSS段

BSS段并不会放入bin文件中,否则也太浪费空间了。
在使用BSS段里的变量之前,把BSS段所占据的内存清零就可以了。

  • 注意:对于keil来说,一个本该放到BSS段的变量,如果它所占据的空间小于等于8字节自己,keil仍然会把它放在data段里。只有当它所占据的空间大于8字节时,才会放到BSS段。

    int g_A[3] = {0, 0};   // 放在BSS段
    char g_B[9];           // 放在BSS段
    
    int g_A[2] = {0, 0};   // 放在data段
    char g_B[8];           // 放在data段
    

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.2 清除BSS段

4.2.1 BSS段的位置和大小

在散列文件中,BSS段(ZI段)在可执行域RW_IRAM1中描述:

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

BSS段(ZI段)的链接地址(基地址)、长度,使用下面的符号获得:
在这里插入图片描述

4.2.2 怎么清除BSS段

1.汇编码

string.c中的memset函数:

void memset(void *dest, unsigned char val, unsigned int len)
{
	unsigned char *pcDest = dest;
	
	while (len --)
	{
		*pcDest = val;
		pcDest++;
	}
}
IMPORT |Image$$RW_IRAM1$$ZI$$Base|			;IMPORT在这里相当于定义
IMPORT |Image$$RW_IRAM1$$ZI$$Length|
IMPORT memset

LDR R0, = |Image$$RW_IRAM1$$ZI$$Base|       ; DEST
MOV R1, #0   								; VAL:VAL为0 并赋给dest 完成清零操作
LDR R1, = |Image$$RW_IRAM1$$ZI$$Length|     ; Length
BL memset

在这里插入图片描述

调用memset函数后,可见放入BSS段成功被清除为0:

在这里插入图片描述

2.C语言
  • 方法1
    声明为外部变量,使用时需要使用取址符:
extern int Image$$RW_IRAM1$$ZI$$Base;
extern int Image$$RW_IRAM1$$ZI$$Length;

memset(&Image$$RW_IRAM1$$ZI$$Base, 0, &Image$$RW_IRAM1$$ZI$$Length);
  • 方法2
    声明为外部数组,使用时不需要使用取址符:
extern char Image$$RW_IRAM1$$ZI$$Base[];
extern int Image$$RW_IRAM1$$ZI$$Length[];

memset(Image$$RW_IRAM1$$ZI$$Base[], 0, Image$$RW_IRAM1$$ZI$$Length);

五、代码段重定位

5.1 加载地址等于链接地址

在默认散列文件中,代码段的load address = execution address
也就是加载地址和执行地址(链接地址)一致,所以无需重定位:

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

5.2 加载地址不等于链接地址

有时候,我们需要把程序复制到内存里运行,比如:

  • 想让程序执行得更快:需要把代码段复制到内存里
  • 程序很大,保存在片外SPI Flash中,SPI Flash上的代码无法直接执行,需要复制到内存里

这时候,需要修改散列文件,把代码段的可执行域放在内存里。
那么程序运行时,需要尽快把代码段重定位到内存。
散列文件示例:

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x20000000   {  ; load address != execution address
   *.o (RESET, +First)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 +0   {  ; RW data
   .ANY (+RW +ZI)
  }
}

在这里插入图片描述

上面的散列文件中:

  • 可执行域ER_IROM1
    • 加载地址为0x08000000,可执行地址为0x20000000,两者不相等
    • 板子上电后,从0x08000000处开始运行,需要尽快把代码段复制到0x20000000
  • 可执行域RW_IRAM1
    • 加载地址:紧跟着ER_IROM1的加载地址(+0)
    • 可执行地址:紧跟着ER_IROM1的可执行地址(+0)
    • 需要尽快把数据段复制到可执行地址处

数据段的重定位我们做过实验,
如果代码段不重定位的话,会发生什么事?
修改完上面的散列文件,发现程序编译烧录后在Mobe软件上毫无反应,说明程序发生崩溃。我们进行解析:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们程序运行时,在start.s中,第一条指令是从不是从Reset_Handler开始执行,而是从__Vectors,对于__Vectors的第一个数据DCD是用来设置栈,但是我们并不需要第一个数据,我们自己手动设置了栈(LDR SP, =(0x20000000+0x10000)),对于第二个数据,CPU会在DCD中取出Reset_Handler这个值,并将其作为函数地址跳转过去。为了了解函数地址,我们打开反汇编代码,可以看见其对应的地址为20000009,而我们的代码烧写在Flash上时,在开始是0,CPU一上电会用第一个值设置栈,但我们没有使用,我们后面自己去设置栈,在第二个位置取出该值,将其作为函数地址跳过去执行(注意:这里的20000009中的bit0并不使用,bit0只是用来表示跳过去后的指令为Tumb指令集),会跳到Reset Handler对应的20000008,但在20000008上,我们并未对该内存进行任何操作,此时如果PC跳到位置则会崩溃,由第三张图可见Reset_Handler对应的是08000008,所以我们需要在20000009的位置放入08000009(设置成09是因为bit(0)等于1表示后面执行的是Tumb指令集)。修改完毕后程序得以正常执行,成功清除ss段:

在这里插入图片描述

5.3 代码段不重定位的后果

不能使用链接地址来调用函数

  • 汇编中

    ldr  pc, =main   ; 这样调用函数时,用到main函数的链接地址,如果代码段没有重定位,则跳转失败
    
  • C语言中

    void (*funcptr)(const char *s, unsigned int val);
    funcptr = put_s_hex;
    funcptr("hello, test function ptr", 123);
    

5.4 代码段重定位

5.4.1 代码段的位置和大小

在散列文件中,代码段在可执行域ER_IROM1中描述:

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

代码段的链接地址(基地址)、长度,使用下面的符号获得:

在这里插入图片描述

代码段的加载地址,使用下面的符号获得:

在这里插入图片描述

5.4.2 怎么重定位

1.汇编代码

把代码段从Flash复制到内存中:

IMPORT |Image$$ER_IROM1$$Base|		;IMPORT在这里相当于定义
IMPORT |Image$$ER_IROM1$$Length|
IMPORT |Load$$ER_IROM1$$Base|

LDR R0, = |Image$$ER_IROM1$$Base|    ; DEST
LDR R1, = |Load$$ER_IROM1$$Base|     ; SORUCE
LDR R2, = |Image$$ER_IROM1$$Length|  ; LENGTH
BL memcpy

在这里插入图片描述

在这里插入图片描述

注意:第二段代码已经进行重定位,而第一段代码还位于Flash上,为什么该段代码还没位于他的链接地址上却可以正常执行,因为该段代码使用的是位置无关码。所谓位置无关就是放在任何地方都能得以执行,跟他的位置没有任何关系。关键在于跳转,他使用的是相对跳转指令,而第二段代码使用的是链接地址跳转指令。

2.C语言代码
  • 方法1

声明为外部变量,使用时需要使用取址符:

extern int Image$$ER_IROM1$$Base;
extern int Load$$ER_IROM1$$Base;
extern int Image$$ER_IROM1$$Length;

memcpy(&Image$$ER_IROM1$$Base, &Image$$ER_IROM1$$Length, &Load$$ER_IROM1$$Base);
  • 方法2

声明为外部数组,使用时不需要使用取址符:

extern char Image$$ER_IROM1$$Base[];
extern char Load$$ER_IROM1$$Base[];
extern int Image$$ER_IROM1$$Length;

memcpy(Image$$ER_IROM1$$Base, Image$$ER_IROM1$$Length, &Load$$ER_IROM1$$Base);

5.5 为什么重定位之前的代码也可以正常运行?

因为重定位之前的代码是使用位置无关码写的:

  • 只使用相对跳转指令:B、BL

  • 不只用绝对跳转指令:

    LDR R0, =main
    BLX R0
    
  • 不访问全局变量、静态变量、字符串、数组

  • 重定位完后,使用绝对跳转指令跳转到XXX函数的链接地址去

    BL main         ; bl相对跳转,程序仍在Flash上运行
    
    LDR R0, =main   ; 绝对跳转,跳到链接地址去,就是跳去内存里执行
    BLX R0
    

在这里插入图片描述

五、重定位的纯C函数实现

5.1 难点

难点在于,怎么得到各个域的加载地址、链接地址、长度。

  • 方法1

声明为外部变量,使用时需要使用取址符:

extern int Image$$ER_IROM1$$Base;
extern int Load$$ER_IROM1$$Base;
extern int Image$$ER_IROM1$$Length;

memcpy(&Image$$ER_IROM1$$Base, &Image$$ER_IROM1$$Length, &Load$$ER_IROM1$$Base);
  • 方法2

声明为外部数组,使用时不需要使用取址符:

extern char Image$$ER_IROM1$$Base[];
extern char Load$$ER_IROM1$$Base[];
extern int Image$$ER_IROM1$$Length;

memcpy(Image$$ER_IROM1$$Base, Image$$ER_IROM1$$Length, &Load$$ER_IROM1$$Base);

5.2 怎么理解上述代码

对于这样的C变量:

int g_a;

编译的时候会有一个符号表(symbol table),如下:

NameAddress
g_axxxxxxxx

对于散列文件中的各类Symbol,有2中声明方式:

extern int Image$$ER_IROM1$$Base;     // 声明为一般变量(包括int、char还有指针)
extern char Image$$ER_IROM1$$Base[];  // 声明为数组

不管是哪种方式,它们都会保存在符号表里,比如:

NameAddress
g_axxxxxxxx
Image E R I R O M 1 ER_IROM1 ERIROM1Baseyyyyyyyy
  • 对于int g_a变量
    • 使用&g_a得到符号表里的地址。
  • 对于extern int Image$$ER_IROM1$$Base变量
    • 要得到符号表中的地址,也是使用&Image$$ER_IROM1$$Base
  • 对于extern char Image$$ER_IROM1$$Base[]变量
    • 要得到符号表中的地址,直接使用Image$$ER_IROM1$$Base,不需要加&
    • 为什么?mage$$ER_IROM1$$Base本身就表示地址啊

示例代码:
在start.s中定义SystemInt,并调用跳转:


                PRESERVE8
                THUMB


; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
				EXPORT  __Vectors
					
					
__Vectors       DCD     0                  
                DCD     0x08000009              ; Reset Handler

				AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler   PROC
				EXPORT  Reset_Handler             [WEAK]
                IMPORT  mymain
				IMPORT	SystemInit				;定义SystemInit

				LDR SP, =(0x20000000+0x10000)
				
				BL SystemInit					;跳转SystemInit

				;BL mymain
				LDR R0, =mymain
				BLX R0

                ENDP
                
                 END

创建一个init.c,在其中写入c函数的重定位(包括int、char和指针):

#include "string.h"

#if 0
void SystemInit()
{               
	extern int Image$$ER_IROM1$$Base;
	extern int Image$$ER_IROM1$$Length;
	extern int Load$$ER_IROM1$$Base;
	extern int Image$$RW_IRAM1$$Base;
	extern int Image$$RW_IRAM1$$Length;
	extern int Load$$RW_IRAM1$$Base;
	extern int Image$$RW_IRAM1$$ZI$$Base;
	extern int Image$$RW_IRAM1$$ZI$$Length;
	
	/* 代码段重定位 */
	memcpy(&Image$$ER_IROM1$$Base, &Load$$ER_IROM1$$Base, &Image$$ER_IROM1$$Length); //目的地址、源地址、长度
	
	/* 数据段重定位 */
	memcpy(&Image$$RW_IRAM1$$Base, &Load$$RW_IRAM1$$Base, &Image$$RW_IRAM1$$Length); //目的地址、源地址、长度

	/* 清除BSS段 */
	memset(&Image$$RW_IRAM1$$ZI$$Base, 0, &Image$$RW_IRAM1$$ZI$$Length);			 //源地址、清除为0、长度
}
#endif

#if 0
void SystemInit()
{               
	extern int Image$$ER_IROM1$$Base[];
	extern int Image$$ER_IROM1$$Length[];
	extern int Load$$ER_IROM1$$Base[];
	extern int Image$$RW_IRAM1$$Base[];
	extern int Image$$RW_IRAM1$$Length[];
	extern int Load$$RW_IRAM1$$Base[];
	extern int Image$$RW_IRAM1$$ZI$$Base[];
	extern int Image$$RW_IRAM1$$ZI$$Length[];
	
	/* 代码段重定位 */
	memcpy(Image$$ER_IROM1$$Base, Load$$ER_IROM1$$Base, Image$$ER_IROM1$$Length); //目的地址、源地址、长度
	
	/* 数据段重定位 */
	memcpy(Image$$RW_IRAM1$$Base, Load$$RW_IRAM1$$Base, &Image$$RW_IRAM1$$Length); //目的地址、源地址、长度

	/* 清除BSS段 */
	memset(Image$$RW_IRAM1$$ZI$$Base, 0, Image$$RW_IRAM1$$ZI$$Length);			 //源地址、清除为0、长度
}
#endif

void SystemInit()
{               
	extern int *Image$$ER_IROM1$$Base;
	extern int *Image$$ER_IROM1$$Length;
	extern int *Load$$ER_IROM1$$Base;
	extern int *Image$$RW_IRAM1$$Base;
	extern int *Image$$RW_IRAM1$$Length;
	extern int *Load$$RW_IRAM1$$Base;
	extern int *Image$$RW_IRAM1$$ZI$$Base;
	extern int *Image$$RW_IRAM1$$ZI$$Length;
	
	/* 代码段重定位 */
	memcpy(&Image$$ER_IROM1$$Base, &Load$$ER_IROM1$$Base, &Image$$ER_IROM1$$Length); //目的地址、源地址、长度
	
	/* 数据段重定位 */
	memcpy(&Image$$RW_IRAM1$$Base, &Load$$RW_IRAM1$$Base, &Image$$RW_IRAM1$$Length); //目的地址、源地址、长度

	/* 清除BSS段 */
	memset(&Image$$RW_IRAM1$$ZI$$Base, 0, &Image$$RW_IRAM1$$ZI$$Length);			 //源地址、清除为0、长度
}

三者结果一致:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值