代码重定位详解
文章目录
- 一、段的概念以及重定位的引入
- 1.1 问题的引入
- 1.2 段的概念
- 1.3 重定位
- 二、如何实现重定位
- 2.1 程序中含有什么?
- 2.2 谁来做重定位?
- 2.3 怎么做重定位和清除BSS段?
- 2.4 加载地址和链接地址的区别
- 三、散列文件使用与分析
- 3.1 重定位的实质: 移动数据
- 3.2 散列文件示例
- 3.2.1 示例代码
- 3.2.2 散列文件语法
- 3.3 散列文件解析
- 3.4 怎么获得region的信息
- 3.4.1 可执行域的信息
- 3.4.2 加载域的信息
- 3.4.3 汇编代码里怎么使用这些信息
- 3.4.4 C语言里怎么使用这些信息
- 1.方法1
- 2.方法2
- 四、清除BSS段(ZI段)
- 4.1 C语言中的BSS段
- 4.2 清除BSS段
- 4.2.1 BSS段的位置和大小
- 4.2.2 怎么清除BSS段
- 1.汇编码
- 2.C语言
- 五、代码段重定位
- 5.1 加载地址等于链接地址
- 5.2 加载地址不等于链接地址
- 5.3 代码段不重定位的后果
- 5.4 代码段重定位
- 5.4.1 代码段的位置和大小
- 5.4.2 怎么重定位
- 1.汇编代码
- 2.C语言代码
- 5.5 为什么重定位之前的代码也可以正常运行?
- 五、重定位的纯C函数实现
- 5.1 难点
- 5.2 怎么理解上述代码
一、段的概念以及重定位的引入
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
- 只能使用branch指令(比如
- 不要访问全局变量、静态变量
- 不使用字符串
- 跳转:使用相对跳转指令,不能使用绝对跳转指令
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_regionER_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 memcpyLDR 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_regionER_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 memsetLDR 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_regionER_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_regionER_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_regionER_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),如下:
| Name | Address |
|---|---|
| g_a | xxxxxxxx |
对于散列文件中的各类Symbol,有2中声明方式:
extern int Image$$ER_IROM1$$Base; // 声明为一般变量(包括int、char还有指针)
extern char Image$$ER_IROM1$$Base[]; // 声明为数组
不管是哪种方式,它们都会保存在符号表里,比如:
| Name | Address |
|---|---|
| g_a | xxxxxxxx |
| Image E R I R O M 1 ER_IROM1 ERIROM1Base | yyyyyyyy |
- 对于
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,并调用跳转:
PRESERVE8THUMB; Vector Table Mapped to Address 0 at ResetAREA RESET, DATA, READONLYEXPORT __Vectors__Vectors DCD 0 DCD 0x08000009 ; Reset HandlerAREA |.text|, CODE, READONLY; Reset handler
Reset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT mymainIMPORT SystemInit ;定义SystemInitLDR SP, =(0x20000000+0x10000)BL SystemInit ;跳转SystemInit;BL mymainLDR R0, =mymainBLX R0ENDPEND
创建一个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、长度
}
#endifvoid 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、长度
}
三者结果一致:

相关文章:
代码重定位详解
文章目录 一、段的概念以及重定位的引入1.1 问题的引入1.2 段的概念1.3 重定位 二、如何实现重定位2.1 程序中含有什么?2.2 谁来做重定位?2.3 怎么做重定位和清除BSS段?2.4 加载地址和链接地址的区别 三、散列文件使用与分析3.1 重定位的实质…...
活动预告 | Microsoft 365 在线技术公开课:让组织针对 Microsoft Copilot 做好准备
课程介绍 通过Microsoft Learn免费参加Microsoft 365在线技术公开课,建立您需要的技能,以创造新的机会并加速您对Microsoft云技术的理解。参加我们举办的“让组织针对 Microsoft Copilot for Microsoft 365 做好准备” 在线技术公开课活动,学…...
从0到机器视觉工程师(一):机器视觉工业相机总结
目录 相机的作用 工业相机 工业相机的优点 工业相机的种类 工业相机知名品牌 光源与打光 打光方式 亮暗场照明 亮暗场照明的应用 亮暗场照明的区别 前向光漫射照明 背光照明 背光照明的原理 背光照明的应用 同轴光照明 同轴光照明的应用 总结 相机的作用 相机…...
Docker安装(Docker Engine安装)
一、Docker Engine和Desktop区别 Docker Engine 核心组件:Docker Engine是Docker的核心运行时引擎,负责构建、运行和管理容器。它包括守护进程(dockerd)、API和命令行工具客户端(docker)。适用环境&#…...
数组的深度监听deep
场景:组件提供的emit事件可能被占用,在不能使用事件提交的情况下,就要上watch数组监听了,但是是发现只有在数组的长度发生变化的时候才会触发监听,这怎么行。。。。。 对于对象数组的深度监听,如果没有正确…...
点击锁定按钮,锁定按钮要变成解锁按钮,然后状态要从待绑定变成 已锁定(升级版)
文章目录 1、updateInviteCodeStatus2、handleLock3、InviteCodeController4、InviteCodeService5、CrudRepository 点击锁定按钮,锁定按钮要变成解锁按钮,然后状态要从待绑定变成 已锁定:https://blog.csdn.net/m0_65152767/article/details…...
UniApp 性能优化策略
一、引言 在当今数字化时代,移动应用的性能成为影响用户留存与满意度的关键因素。UniApp 作为一款热门的跨平台开发框架,以一套代码适配多端的特性极大提升了开发效率,但同时也面临着性能优化的挑战。优化 UniApp 性能,不仅能够让…...
【Linux报告】实训六 重置超级用户密码
实训六 重置超级用户密码 2018编写-今日公布 【练习一】忘记root密码 步骤一:开启或重启系统,并且要在五秒之内按任何键; 步骤二:按任意键,停止进入系统,按【e】键,跳转新页面,再…...
smolagents:一个用于构建代理的简单库
HF推出 smolagents,一个非常简单的库,它能够解锁语言模型的代理功能。以下是它的简要介绍: from smolagents import CodeAgent, DuckDuckGoSearchTool, HfApiModelagent CodeAgent(tools[DuckDuckGoSearchTool()], modelHfApiModel())agent…...
通过Dockerfile来实现项目可以指定读取不同环境的yml包
通过Dockerfile来实现项目可以指定读取不同环境的yml包 1. 挂载目录2. DockerFile3. 运行脚本deploy.sh4. 运行查看日志进入容器 5. 接口测试修改application-dev.yml 6. 优化Dockerfile7. 部分参数解释8. 优化不同环境下的日志也不同调整 Dockerfile修改部署脚本 deploy.sh重新…...
云手机 —— 手机矩阵的 “超级外挂
如何打造手机矩阵 打造手机矩阵主要包括以下几个步骤: 1.确定目标与需求:首先,明确打造手机矩阵的目的和需求,是为了进行电商运营、自媒体推广、任务管理还是其他目的。这将决定后续的手机数量、操作系统选择以及应用安装等。 2.选择手机与操作系统:根据…...
OpenCV的TickMeter计时类
OpenCV的TickMeter计时类 1. TickMeter是一个计时的类1.1 计算耗时1.2 计算循环的平均耗时和FPS1.3 function 2. 案例 1. TickMeter是一个计时的类 https://docs.opencv.org/4.x/d9/d6f/classcv_1_1TickMeter.html#details 1.1 计算耗时 TickMeter tm;tm.start();// do some…...
蓝桥杯JAVA刷题--001
文章目录 题目需求2.代码3.总结 题目需求 2.代码 class Solution {public String convertDateToBinary(String date) {if (date null || date.length() ! 10 || date.charAt(4) ! - || date.charAt(7) ! -) {throw new IllegalArgumentException("输入的日期格式不正确&…...
免费又开源:企业级物联网平台的新选择 ThingsPanel
在开源领域,选择合适的开源协议是开发者和企业能否充分利用平台的关键。ThingsPanel,作为一个专注于物联网的开源平台,近日将协议从 AGPLv3 改为更开放的 Apache 2.0。这一改变对开发者和用户意味着什么? 为什么协议要从 AGPLv3 转…...
鸿蒙开发:文本合成语音
前言 Android开发的同学都知道,在Android当中,实现一段文字合成语音播放,可以使用系统提供的对象TextToSpeech来很快的实现,如果不用系统自带的,也可以使用三方提供的,比如讯飞的语音合成等等,总…...
雷军:科技传奇的逐梦之旅
亲爱的小伙伴们😘,在求知的漫漫旅途中,若你对深度学习的奥秘、Java 与 Python 的奇妙世界,亦或是读研论文的撰写攻略有所探寻🧐,那不妨给我一个小小的关注吧🥰。我会精心筹备,在未来…...
LeetCode - 初级算法 数组(删除排序数组中的重复项)
免责声明:本文来源于个人知识与公开资料,仅用于学术交流。 删除排序数组中的重复项 这篇文章讨论如何从一个非严格递增的数组 nums 中删除重复的元素,使每个元素只出现一次,并返回新数组的长度。因为数组是排序的,只要是相同的肯定是挨着的,所以我们需要遍历所有数组,然…...
2024年度培训运维总结
目前带的弟子跟我实战训练运维的总就业率基本可以稳定在 100% 左右(因为我不带小白和没学历和大龄,悟性低都筛掉了还能找我说明条件够了) 以下是弟子私教训练营的最新就业数据(2024年12月25日): 面试辅导…...
java重装小结
一、Java安装 安装路径 https://www.oracle.com/java/technologies/javase/javas e8-archive-downloads.html 具体类型可参考: Java安装配置-CSDN博客 我在这一步主要碰到的问题就是访问官网报404错误,可参考: 在oracle官网下载资源显示…...
ubuntu20.04 中文输入法安装
中文输入法安装指南 在 Ubuntu 20.04 上安装中文输入法,您可以按照以下步骤操作: 打开终端。安装 fcitx 输入法框架和中文输入法模块:sudo apt update sudo apt install fcitx fcitx-pinyin安装中文语言支持:sudo apt install l…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
