| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 游戏开发 -> [012] [STM32] 代码重定位与清除BSS段深入分析 -> 正文阅读 |
|
[游戏开发][012] [STM32] 代码重定位与清除BSS段深入分析 |
STM32
Contents
重定位的概念与引入
重定位的概念
为什么需要重定位
重定位分析与实现
需要重定位的段
重定位的实现
重定位程序编写
数据段重定位
清除BSS段·
代码段重定位
总结
|
Symbol | Description |
---|---|
Image$$region_name$$Base | Execution address of the region. |
Image$$region_name$$Length | Execution region length in bytes excluding ZI length. |
Image$$region_name$$Limit | Address of the byte beyond the end of the non-ZI part of the execution region. |
Image$$region_name$$RO$$Base | Execution address of the RO output section in this region. |
Image$$region_name$$RO$$Length | Length of the RO output section in bytes. |
Image$$region_name$$RO$$Limit | Address of the byte beyond the end of the RO output section in the execution region. |
Image$$region_name$$RW$$Base | Execution address of the RW output section in this region. |
Image$$region_name$$RW$$Length | Length of the RW output section in bytes. |
Image$$region_name$$RW$$Limit | Address of the byte beyond the end of the RW output section in the execution region. |
Image$$region_name$$XO$$Base | Execution address of the XO output section in this region. |
Image$$region_name$$XO$$Length | Length of the XO output section in bytes. |
Image$$region_name$$XO$$Limit | Address of the byte beyond the end of the XO output section in the execution region. |
Image$$region_name$$ZI$$Base | Execution address of the ZI output section in this region. |
Image$$region_name$$ZI$$Length | Length of the ZI output section in bytes. |
Image$$region_name$$ZI$$Limit | Address of the byte beyond the end of the ZI output section in the execution region. |
Image$$
表示执行域符号,分别为执行域本身和其输出的RO、RW、XO、ZI段的起始地址、长度与结束地址。Image$$
换成Load$$
即为执行域的加载符号,与其一一对应。Load$$region_name$$Base
:域region_name加载时的起始地址;Image$$region_name$$Base
:域region_name运行时的起始地址;Image$$region_name$$Length
:域region_name运行时的长度(为4字节的倍数);Image$$region_name$$Limit
:域region_name运行时存储域末尾的下一个字节地址(此地址不属于region_name所占的存储区域)注意:Load$$region_name
符号只适用于执行区域。Load$$LR$$load_region_name
符号只适用于加载区域。
symbol 本身即为地址
IMPORT |Image$$ZI$$Limit|
zi_limit DCD |Image$$ZI$$Limit|
LDR r1, zi_limit
声明为外部变量(包括指针,使用时需要使用取址符&
):
extern unsigned int Image$$RW_IRAM1$$Base; //extern unsigned int* Image$$RW_IRAM1$$Base;也一样需要使用取指符
extern unsigned int Load$$RW_IRAM1$$Base;
extern unsigned int Image$$RW_IRAM1$$Length;
memcpy(&Image$$RW_IRAM1$$Base, &Image$$RW_IRAM1$$Length, &Load$$RW_IRAM1$$Base);
声明为外部数组(使用时不需要使用取址符&
):
extern char Image$$RW_IRAM1$$Base[];
extern char Load$$RW_IRAM1$$Base[];
extern unsigned int Image$$RW_IRAM1$$Length;
memcpy(Image$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length, &Load$$RW_IRAM1$$Base);
综上,Image$$region_name$$xxx
存放的是值的地址,而&Image$$region_name$$xxx
相当于解引用,取出该地址中的值!(&symbol == *var)
参考:野火-MDK编译过程及文件类型全解
RW_ERAM1
为外部SRAM,需要在RW段重定位前,初始化FSMC SRAM
ldr r0, =systemInit
blx r0
ldr r0, =fsmc_sram_init ; 初始化fsmc sram, 确保RW段被正常复制
blx r0
LR_IROM1 0x08000000 0x00080000 {
ER_IROM1 0x08000000 0x00080000 {
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00010000 { // on chip ram
*.o(STACK) // 外部SRAM初始化函数需要用到栈,必须放在IRAM中,不然会默认放在内存比较大的RW_ERAM1中,但此时外部sram还未初始化, 不能正常使用! (当然也可以直接操作寄存器, 无需使用stack)
.ANY (+RW +ZI)
}
RW_ERAM1 0x68000000 0x00100000 { // 外部SRAM
*.o(EXRAM)
}
}
使用__attribute__
来指定:
// 内存池32字节对齐, 指定存放起始地址为0x20001000
__align(32) uint8_t membase[MEM_MAX_SIZE] __attribute__(at(0x20001000));
uint8_t EXgroup[1024] __attribute__(section("EXRAM")) = {1, 2, 3};
__attribute__((section("EXRAMt"))) uint16_t test_func(void); // 函数
数据段的加载地址(源): Load$$RW_IRAM1$$Base
数据段的链接地址(目的): Image$$RW_IRAM1$$Base
数据段的长度(长度): Image$$RW_IRAM1$$Length // 以字节为单位的执行区域长度,不包括ZI长度
relocate PROC
IMPORT |Load$$RW_IRAM1$$Base| ; 数据段的加载起始地址(源)
IMPORT |Image$$RW_IRAM1$$Base| ; 数据段的链接起始地址(目的)
IMPORT |Image$$RW_IRAM1$$Length| ; 数据段的长度(长度)
ldr r0, =|Load$$RW_IRAM1$$Base|
ldr r1, =|Image$$RW_IRAM1$$Base|
ldr r2, =|Image$$RW_IRAM1$$Length|
memcpy
ldrb r3, [r0], #1 ; r3 = *(char*)&r0, r0 += 1
strb r3, [r1], #1 ; *(char*)&r1 = r3, r1 += 1
cmp r2,#0 ; 在r2-1前进行比较, 判断其是否为0(不然会少执行一次)
sub r2, r2, #1 ; r2 -= 1
bne memcpy ; z != 0 跳转 (取决于r2 是否为 0)
bx lr ; 返回
ENDP
注意:cmp r2,#0
+sub r2, r2, #1
是先判断再将r2值-1(i--
),而subs r2, r2, #1
是先-1再判断(--i
)。
在Reset_Handler
中进行调用(必须在C库初始化前):
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT my_main
BL relocate
LDR SP, =(0x20000000 + 0xC000)
BL my_main
ENDP
这样就能正常使用存放在RW_IRAM1
执行域中RW-Data中
的变量了。
// src = r0, dest = r1, len = r2
void c_memcpy(void *src, void *dest, unsigned int len){
while (len--){
*(char*)dest++ = *(char*)src++;
}
}
根据ATPCS规则,使用r0~r3给函数传参
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT my_main
IMPORT c_memcpy
ldr r0, =|Load$$RW_IRAM1$$Base|
ldr r1, =|Image$$RW_IRAM1$$Base|
ldr r2, =|Image$$RW_IRAM1$$Length|
LDR SP, =(0x20000000 + 0xC000)
BL c_memcpy
BL my_main
ENDP
注意:调用c_memcpy
函数前需先指定SP
,因为函数调用会用栈保存/恢复现场,c_memcpy
反汇编代码如下:
当然也可以在main函数中调用(但不建议这么做,因为在重定位完成前可能会进入ISR/ESR中,使用未重定位的数据):
extern char Load$$RW_IRAM1$$Base[];
extern char Image$$RW_IRAM1$$Base[];
extern unsigned int Image$$RW_IRAM1$$Length;
int my_main(){
// ....
c_memcpy(Load$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Base, &Image$$RW_IRAM1$$Length);
// ....
}
keil对bss段进行了优化,如果变量所占据的空间≤8字节,则会把它放在data段,重定位后该值会被初始化为0,只有当它>8字节,才会被放到bss段,此时如果不清除,则会得到随机值:
// char g_c[8]; // data段
char g_c[9]; // bss段
int my_main()
{
static int s_c[3] = {0}; // bss段
USART_TypeDef *usart1 = (USART_TypeDef*)0x40013800;
uart_init(usart1, 115200);
put_s_hex(usart1, "\ng_c[0] = ", g_c[0]);
put_s_hex(usart1, "\ns_c[0] = ", s_c[0]);
}
因此,需要手动清除bss段。(__main
也会帮我们做),其链接器符号为:
//清除bss时只需用到起始地址和长度
bss/ZI段的链接(起始)地址: Image$$RW_IRAM1$$ZI$$Base
bss/ZI段的结束地址: Image$$RW_IRAM1$$ZI$$Limit
bss/ZI段的长度: Image$$RW_IRAM1$$ZI$$Length
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT my_main
LDR SP, =(0x20000000 + 0xC000)
; relocate data section
BL relocate
; clear bss section
BL clear_bss
BL my_main
ENDP
clear_bss PROC
IMPORT |Image$$RW_IRAM1$$ZI$$Base| ; ZI段的链接起始地址
IMPORT |Image$$RW_IRAM1$$ZI$$Length| ; ZI段的长度
ldr r0, =|Image$$RW_IRAM1$$ZI$$Base|
mov r1, #0
ldr r2, =|Image$$RW_IRAM1$$ZI$$Length|
memset
strb r1, [r0], #1 ; *(char*)&r1 = r0 = 0, r0 += 1
cmp r2,#0 ; 在r2-1前进行比较, 判断其是否为0(不然会少执行一次)
sub r2, r2, #1 ; r2 -= 1
bne memset ; z != 0 跳转 (取决于r2 是否为 0)
bx lr ; 返回
mov r0, r0 ; 字节对齐
ENDP
成功将bss段清0:
void c_memset(void *dest, char val, unsigned int len){
while (len --){
*(char*)dest++ = val;
}
}
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT my_main
LDR SP, =(0x20000000 + 0xC000)
; relocate data section
BL relocate
; clear bss section
IMPORT c_memset
ldr r0, =|Image$$RW_IRAM1$$ZI$$Base|
mov r1, #0
ldr r2, =|Image$$RW_IRAM1$$ZI$$Length|
BL c_memset
BL my_main
ENDP
之前散列文件中的代码段的加载地址=链接地址,因此无需重定位,但是想让程序执行得更快,需要把代码段复制到内存(RAM)里,下面修改散列文件:
LR_IROM1 0x08000000 0x00040000 { ; 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
RW_IRAM1
ER_IROM1
的加载地址ER_IROM1
的可执行地址指令从0x08000000
位置开始存储(加载地址),但是实际运行地址(链接地址)是从0x20000000
处开始的,但此空间并没有放入指令,所以此时程序不能正常执行,分析反汇编文件:
第一列为链接地址,第二列为机器码,复位后跳到0x20000009 - 1
(LSB=1表示Thumb状态)处开始执行,然后加载pc->0x2000c000
,但此空间并没有放入指令,导致程序崩溃。
如果将程序执行第二条指令存放的Reset_Handler
地址设为0x08000009
(第一条指令为给SP赋值,这里先赋0,后面在复位isr中再进行赋值),程序全部使用位置无关码,即可正常运行。
__Vectors DCD 0 ; Top of Stack
DCD 0x08000009 ; Reset Handler
AREA |.text|, CODE, READONLY
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
...
第二条指令存放的Reset_Handler
地址变为了0x08000009
,其他地方并未发送变化,但程序却可以正常运行了:
这是因为反汇编文件中看到的都是程序的链接地址,但是在实际运行中,都是使用的位置无关码,从Reset_Handler
开始执行时,pc = 0x08000008 + 4
(因为ARM指令流水线机制),然后从pc+60 = 0x08000048
取指执行(链接地址0x20000048
也是相对pc偏移计算出来的),后面每条指令都是相对pc偏移运行的,即依然是在flash中取指的。
如果不使用位置无关码,即用链接地址来调用函数:
汇编中
ldr pc, =my_main ; 调用函数时,用到main函数的链接地址,如果代码段没有重定位,则跳转失败
将启动文件的BL my_main
修改成绝对跳转后,程序无法正常执行。
C语言中
void (*funcptr)(USART_TypeDef* usart, const char *s);
funcptr = send_str;
funcptr(usart1, "hello, test function ptr");
当程序运行到函数指针处时,指针指向的函数运行时的地址(链接地址)是存放在RAM中的,程序会以绝对跳转的方式到该空间取指,但并没有存入指令,程序跑飞。
代码段的加载地址(源): Load$$ER_IROM1$$Base
代码段的链接地址(目的): Image$$ER_IROM1$$Base
代码段的长度(长度): Image$$ER_IROM1$$Length
注意:即使使用位置无关码,但是第一条指令地址仍然需要指定为flash中的地址,即让pc的初值为flash中的地址,后面重定位代码才能相对于pc在flash中正常运行,重定位完成后在使用绝对跳转指令到RAM中运行。
具体步骤:
0x08000008 + 1
main
函数中去执行(使用相对跳转最后不会跳到ram, 程序依然在flash中运行,因为flash中也存放着指令的,而重定位要做的就是将flash的指令拷贝到ram中)完整代码如下:
PRESERVE8
THUMB
AREA RESET, DATA, READONLY
EXPORT __Vectors
__Vectors DCD 0 ; Top of Stack
DCD 0x08000009 ; Reset Handler ; pc初始值必须在flash中, 且必须为flash起始地址偏移2个指令长度
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT my_main
IMPORT |Load$$ER_IROM1$$Base| ; 代码段的加载起始地址(源)
IMPORT |Image$$ER_IROM1$$Base| ; 代码段的链接起始地址(目的)
IMPORT |Image$$ER_IROM1$$Length| ; 代码段的长度(长度)
IMPORT |Load$$RW_IRAM1$$Base| ; 数据段的加载起始地址(源)
IMPORT |Image$$RW_IRAM1$$Base| ; 数据段的链接起始地址(目的)
IMPORT |Image$$RW_IRAM1$$Length| ; 数据段的长度(长度)
LDR SP, =(0x20000000 + 0xC000)
; relocate text section
ldr r0, =|Load$$ER_IROM1$$Base|
ldr r1, =|Image$$ER_IROM1$$Base|
ldr r2, =|Image$$ER_IROM1$$Length|
BL relocate
; relocate data section
ldr r0, =|Load$$RW_IRAM1$$Base|
ldr r1, =|Image$$RW_IRAM1$$Base|
ldr r2, =|Image$$RW_IRAM1$$Length|
BL relocate
; clear bss section
BL clear_bss
LDR R0, =my_main ; ldr pc, =my_main
BLX R0 ; X带状态跳转
; BL my_main ; 使用相对跳转最后不会跳到ram, 程序依然在flash中运行(flash中也存放着指令)
ENDP
relocate PROC
memcpy
ldrb r3, [r0], #1 ; r3 = *(char*)&r0, r0 += 1
strb r3, [r1], #1 ; *(char*)&r1 = r3, r1 += 1
cmp r2,#0 ; 在r2-1前进行比较, 判断其是否为0(不然会少执行一次)
sub r2, r2, #1 ; r2 -= 1
bne memcpy ; z != 0 跳转 (取决于r2 是否为 0)
bx lr ; 返回
ENDP
clear_bss PROC
IMPORT |Image$$RW_IRAM1$$ZI$$Base| ; ZI段的链接起始地址
IMPORT |Image$$RW_IRAM1$$ZI$$Length| ; ZI段的长度
ldr r0, =|Image$$RW_IRAM1$$ZI$$Base|
mov r1, #0
ldr r2, =|Image$$RW_IRAM1$$ZI$$Length|
memset
strb r1, [r0], #1 ; *(char*)&r1 = r0 = 0, r0 += 1
cmp r2,#0 ; 在r2-1前进行比较, 判断其是否为0(不然会少执行一次)
sub r2, r2, #1 ; r2 -= 1
bne memset ; z != 0 跳转 (取决于r2 是否为 0)
bx lr ; 返回
ENDP
ALIGN ; 填充字节使地址对齐
END
首先需要修改启动文件:
PRESERVE8
THUMB
AREA RESET, DATA, READONLY
EXPORT __Vectors
__Vectors DCD 0 ; Top of Stack
DCD 0x08000009 ; Reset Handler ; pc初始值必须在flash中, 且必须为flash起始地址偏移2个指令长度
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT my_main
IMPORT SystemInit
LDR SP, =(0x20000000+0xC000)
BL SystemInit
;BL mymain
LDR R0, =my_main
BLX R0
ENDP
END
而后直接使用C库提供的memset
和memcpy
:
void SystemInit(void)
{
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[];
/* text relocate */
memcpy(Image$$ER_IROM1$$Base, Load$$ER_IROM1$$Base, Image$$ER_IROM1$$Length);
/* data relocate */
memcpy(Image$$RW_IRAM1$$Base, Load$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length);
/* bss clear */
memset(Image$$RW_IRAM1$$ZI$$Base, 0, Image$$RW_IRAM1$$ZI$$Length);
}
重定位完成后函数指针可以正常调用函数的链接地址:
Reset_Handler
地址修改为flash起始地址+8 +1,这样重定位程序才能在flash中正常复制数据到ram中此外,如果将所有数据都存放到ram中,也可以:
IRAM
段LR_IROM1 0x08000000 0x00040000 { ; load region size_region
IRAM 0x20000000{ ; load address = execution address
*.o (RESET, +First)
.ANY (+RO)
.ANY (+XO)
.ANY (+RW +ZI)
}
}
void SystemInit(void)
{
extern int Image$$IRAM$$Base[];
extern int Image$$IRAM$$Length[];
extern int Load$$IRAM$$Base[];
extern int Image$$IRAM$$ZI$$Base[];
extern int Image$$IRAM$$ZI$$Length[];
/* text + data relocate */
memset(Image$$IRAM$$Base, Load$$IRAM$$Base, Image$$IRAM$$Length);
/* bss clear */
memset(Image$$IRAM$$ZI$$Base, 0, Image$$IRAM$$ZI$$Length);
}
memcpy
和memset
查看反汇编都是每次复制4个字节,因为各segment是4字节对齐的,且Image$$region_name$$Length
为4字节倍数,因此汇编代码也可以改为:
ldr r0, =|Load$$IRAM$$Base|
ldr r1, =|Image$$IRAM$$Base|
ldr r2, =|Image$$IRAM$$Length|
relocate_loop
sub r2, r2, #4 ; 每次复制4个字节
ldr r3, [r0, r2] ; r3 = *(r0 + r2)
str r3, [r1, r2] ; *(r1 + r2) = r3
cmp r2,#0
bne relocate_loop
bx lr ; 返回
; 也可以利用段的起始和结束地址
ldr r0, =|Load$$IRAM$$Base|
ldr r1, =|Image$$IRAM$$Base|
ldr r2, =|Image$$IRAM$$Limit|
relocate_loop
cmp r1, r2
ldrls r3, [r0], #4
strls r3, [r1], #4
bls relocate_loop
bx lr ; 返回
ENDP
ldr r0, =|Image$$IRAM$$ZI$$Base|
mov r1, #0
ldr r2, =|Image$$IRAM$$ZI$$Length|
clear_bss_loop
sub r2, r2, #4
str r1, [r0, r2]
cmp r2,#0
bne clear_bss_loop
bx lr ; 返回
; 也可以利用zi的起始和结束地址
ldr r0, =|Image$$IRAM$$ZI$$Base|
mov r1, #0
ldr r2, =|Image$$IRAM$$ZI$$Limit|
clear_bss_loop ; ls: r0<=r2时执行
cmp r0, r2
strls r1, [r0], #4 ; *r0 = r1, r0+=4;
bls clear_bss_loop
bx lr ; 返回
注意:是从后往前复制的,因此需要先减小长度。
参考:
END
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/16 19:05:06- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |