全局偏移量表和过程链接表

全局偏移表(GOT)和过程链接表(PLT)
 
ELF 格式的共享库使用 PIC 技术使代码和数据的引用与地址无关,程序可以被加载到地址空间的任意位置。PIC 在代码中的跳转和分支指令不使用绝对地址。PIC 在 ELF 可执行映像的数据段中建立一个存放所有全局变量指针的全局偏移量表 GOT
对于模块外部引用的全局变量和全局函数,用 GOT 表的表项内容作为地址来间接寻址;对于本模块内的静态变量和静态函数,用 GOT 表的首地址作为一个基准,用相对于该基准的偏移量来引用,因为不论程序被加载到何种地址空间,模块内的静态变量和静态函数与 GOT 的距离是固定的,并且在链接阶段就可知晓其距离的大小。这样,PIC 使用 GOT 来引用变量和函数的绝对地址,把位置独立的引用重定向到绝对位置。
对于 PIC 代码,代码段内不存在重定位项,实际的重定位项只是在数据段的 GOT 表内。共享目标文件中的重定位类型有 R_386_RELATIVE、R_386_GLOB_DAT 和 R_386_JMP_SLOT,用于在动态链接器加载映射共享库或者模块运行的时候对指针类型的静态数据、全局变量符号地址和全局函数符号地址进行重定 位。
1.2 PLT 表
过程链接表用于把位置独立的函数调用重定向到绝对位置。通过 PLT 动态链接的程序支持惰性绑定模式。每个动态链接的程序和共享库都有一个 PLT,PLT 表的每一项都是一小段代码,对应于本运行模块要引用的一个全局函数。程序对某个函数的访问都被调整为对 PLT 入口的访问。
每个 PLT 入口项对应一个 GOT 项,执行函数实际上就是跳转到相应 GOT 项存储的地址,该 GOT 项初始值为 PLTn项中的 push 指令地址(即 jmp 的下一条指令,所以第 1 次跳转没有任何作用),待符号解析完成后存放符号的真正地址。动态链接器在装载映射共享库时在 GOT 里设置 2 个特殊值:在 GOT+4( 即 GOT[1]) 设置动态库映射信息数据结构link_map 地址;在 GOT+8(即 GOT[2])设置动态链接器符号解
析函数的地址_dl_runtime_resolve。
PLT 的第 1 个入口 PLT0 是一段访问动态链接器的特殊代码。程序对 PLT 入口的第 1 次访问都转到了 PLT0,最后跳入 GOT[2]存储的地址执行符号解析函数。待完成符号解析后,将符号的实际地址存入相应的 GOT 项,这样以后调用函数时可直接跳到实际的函数地址,不必再执行符号解析函数
操作系统运行程序时,首先将解释器程序即动态链接器ld.so 映射到一个合适的地址,然后启动 ld.so。ld.so 先完成自己的初始化工作,再从可执行文件的动态库依赖表中指定的路径名查找所需要的库,将其加载映射到内存。
Linux用一个全局的库映射信息结构 struct link_map链表来管理和控制所有动态库的加载,动态库的加载过程实际上是映射库文件到内存中,并填充库映射信息结构添加到链表中的过程。结构 struct link_map 描述共享目标文件的加载映射信息,是动态链接器在运行时内部使用的一个结构,通过它保持对已装载的库和库中符号的跟踪。
link_map 使用双向链接中间件“l_next”和“l_prev”链接进程中所有加载的共享库。当动态链接器需要去查找符号的时候,可以向前或向后遍历这个链表,通 过访问链表上的每一个库去搜索需要查找的符号。Link_map 链表的入口由每个可执行映像的全局偏移表的第 2 个入口(GOT[1])指向,查找符号时先从 GOT[1]读取 link_map 结点地址,然后沿着link-map 结点进行搜索。
动态库的加载映射过程主要分 3 步:
(1) 动态链接器调用 __mmap 函数对动态库的所有PT_LOAD 可加载段进行整体映射:
l_map_start=(ElfW(Addr))__mmap ((void *)0, maplength, prot,
MAP_COPY | MAP_FILE, fd, mapoff);
返回值 l_map_start 是实际映射的虚拟地址,和段结构成员 p_vaddr 指定的虚拟地址不一定相同,这对于位置无关代码不会产生影响。但是对于数据段和 link_map 结构中其它相关的位置描述信息还要进行修正。共享库映射的内存位置关系如图 1,l_addr 是实际映射地址和原来指定的映射地址的差值,用于其它位置信息的修正,即简单地将原来指定的虚拟地址加上 l_addr 就可以得到实际加载的虚拟地址
(2)共享文件映射完毕,动态链接器处理共享库的PT_DYNAMIC 动态段,将各项动态链接信息主要是哈希表、符号表、字符串表、重定位表、PLT 重定位项表等地址填写到 link_map 的 l_info 数组结构中。l_info 是 link_map 最重要的字段之一,几乎所有与动态链接管理相关的内容都与 l_info数组有关。动态链接器还要加载处理当前共享库的所有依赖库。
(3)由于实际的映射地址和指定的虚拟地址有可能不同,因此还要对动态库及其依赖库进行重定位。设置动态库的第1 个和第 2 个 GOT 表项:
Elf32_Addr *got =
(Elf32_Addr *) lmap->l_info[DT_PLTGOT].d_un.d_ptr;
got[1]=lmap;
got[2]=&_dl_runtime_resolve;
对动态库的所有重定位项进行重定位,在重定位项指定的偏移地址处加上修正值 l_addr。动态项 DT_REL 给出了重定位表的地址,DT_RELSZ 给出重定位表项的数目。
映射完毕后,动态链接器调用共享库(包括所有相关的依赖库)自备的初始化函数进行初始化。
程序连接表(Procedure Linkage Table)可以使被感染的文件调用外部的函数。这要比修改LD_PRELOAD环境变量实现调用的重定向优越的多,首先不牵扯到环境变量的修改
程序连接表(PLT)
  在ELF文件中,全局偏移表(Global Offset Table,GOT)能够把位置无关的地址定位到绝对地址,程序连接表也有类似的作用,它能够把位置无关的函数调用定向到绝对地址。连接编辑器(link editor)不能解决程序从一个可执行文件或者共享库目标到另外一个的执行转移。结果,连接编辑器只能把包含程序转移控制的一些入口安排到程序连接表 (PLT)中。在system V体系中,程序连接表位于共享正文中,但是它们使用私有全局偏移表(private global offset table)中的地址。动态连接器(例如:ld-2.2.2.so)会决定目标的绝对地址并且修改全局偏移表在内存中的影象。因而,动态连接器能够重定向 这些入口,而勿需破坏程序正文的位置无关性和共享特性。可执行文件和共享目标文件有各自的程序连接表。

elf的动态连接库是内存位置无关的,就是说你可以把这个库加载到内存的任何位置都没有影响。这就叫做position independent。在编译内存位置无关的动态连接库时,要给编译器加上 -fpic选项,让编译器产生的目标文件是内存位置无关的还会尽量减少对变量引用时使用绝对地址。把库编译成内存位置无关会带来一些花费,编译器会保留一 个寄存器来指向全局偏移量表(global offset table (or GOT for short)),这就会导致编译器在优化代码时少了一个寄存器可以使用,但是在最坏的情况下这种性能的减少只有3%,在其他情况下是大大小于3%的。


<think>我们正在讨论STM32单片机中关于中断向量表、链接脚本起始地址烧录偏移量的灵活设定方法。根据之前的上下文,用户已经从app0跳转到app1,并且需要了解如何设置这些参数以确保应用程序在正确的地址运行。以下将结合引用内容进行说明。 ### 一、中断向量表(VTOR)的设定 在Cortex-M系列中,向量表偏移寄存器(VTOR)用于重定位中断向量表。设定方法为: ```c SCB->VTOR = 目标地址; // 例如:0x08010000 ``` **关键点**: - 地址必须满足128字节对齐(即地址末7位为0),例如:`0x08010000`是合法的,因为它的二进制是`...0000000000000000`(低7位为0)[^1]。 - 必须在使能中断前设置,通常在`main()`函数开头或系统初始化时设置。 ### 二、链接脚本起始地址的修改 链接脚本(.ld文件)决定了代码数据的存放位置。修改步骤如下: 1. **定义内存区域**(参考引用[3]) 在.ld文件中修改`FLASH`区域的起始地址长度: ```ld MEMORY { /* 例如APP1起始地址为0x08010000,长度192K */ FLASH (rx) : ORIGIN = 0x08010000, LENGTH = 192K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K } ``` 2. **指定入口点**(可选但推荐) 确保入口点正确指向复位向量: ```ld ENTRY(Reset_Handler); /* 复位中断函数名 */ ``` 3. **添加向量表段**(关键) 确保向量表位于FLASH起始处: ```ld .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* 将.isr_vector段放在最前面 */ . = ALIGN(4); } >FLASH ``` ### 三、烧录偏移量的设置 烧录工具需指定程序写入Flash的偏移地址,方法如下: | 工具 | 设置方式 | |---------------|--------------------------------------------------------------------------| | **Keil MDK** | Options for Target → Target → Read/Only Memory Areas → 添加`0x08010000` | | **STM32CubeIDE** | 工程属性 → C/C++ Build → Settings → Tool Settings → 修改Flash起始地址 | | **OpenOCD** | 烧录命令添加偏移:`flash write_image erase app1.bin 0x08010000` | ### 四、完整配置流程 1. **修改链接脚本**:定义正确的`ORIGIN``LENGTH`[^3]。 2. **代码中重定位VTOR**:在APP1的`main()`开头设置`SCB->VTOR`[^2]。 3. **烧录时指定偏移**:确保烧录地址匹配链接脚本起始地址。 4. **验证方法**: - 调试器中查看`SCB->VTOR`的值(应等于APP1起始地址)。 - 检查Flash内容:向量表前4字节为栈顶地址,随后4字节为复位函数地址。 ### 五、常见问题处理 - **问题1**:跳转后程序卡死 **原因**:VTOR未设置或地址未对齐。 **解决**:检查`SCB->VTOR`赋值语句是否执行,并验证地址对齐性(`(addr & 0x7F) == 0`)。 - **问题2**:中断无法触发 **原因**:向量表中的中断函数地址未更新。 **解决**:确认链接脚本中`.isr_vector`段位于FLASH起始处,并使用`objdump -t app1.elf`查看符号地址。 - **问题3**:烧录后无法运行 **原因**:烧录偏移量错误。 **解决**:检查烧录工具中的起始地址是否与链接脚本一致。 ### 六、数学公式补充说明 中断向量表的地址对齐要求可表示为: $$ \text{VTOR\_ADDR} \mod 128 = 0 $$ 即地址必须能被$128$整除。 §§相关问题§§ 1. 如何验证STM32中断向量表是否正确对齐? 2. 链接脚本中如何为不同应用程序分配RAM空间? 3. STM32CubeMX如何配置多应用程序工程? </think>### 中断向量表、链接脚本起始地址烧录偏移量的灵活设定方法 #### 一、核心概念关联 这三者的关系可通过公式表达: $$ \text{运行地址} = \text{烧录偏移量} + \text{链接起始地址} $$ 其中中断向量表始终位于**代码段起始位置**,其绝对地址由链接脚本起始地址决定。 --- #### 二、中断向量表设置 1. **硬件要求** Cortex-M系列通过VTOR寄存器重定位向量表,地址需满足: $$ \text{VTOR\_ADDR} \mod 128 = 0 $$ 示例代码: ```c // 在APP启动代码中设置 #define APP_BASE 0x08010000 SCB->VTOR = APP_BASE | 0x00; // 确保地址128字节对齐 ``` > ⚠️ 地址末7位必须为0(如`0x08010000`合法,`0x08010080`非法)[^2] 2. **向量表内容** 前4字节为初始栈指针(MSP),随后为中断服务函数指针: | 偏移 | 内容 | 说明 | |------|---------------|------------------| | 0x00 | MSP初始值 | 主栈指针 | | 0x04 | Reset_Handler | 复位中断入口 | | 0x08 | NMI_Handler | 不可屏蔽中断入口 | | ... | ... | ... | --- #### 三、链接脚本(.ld)配置 1. **内存区域定义**(参考引用[3]) ```ld MEMORY { /* 关键参数:起始地址=烧录偏移量 */ FLASH (rx) : ORIGIN = 0x08010000, LENGTH = 192K /* APP1区域 */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K } ``` 2. **向量表强制定位** ```ld .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* 确保向量表在FLASH起始 */ . = ALIGN(4); } >FLASH /* 指定存储到FLASH区域 */ ``` 3. **符号导出**(用于启动代码) ```ld _sbss = .; /* .bss段起始地址符号 */ .bss : { *(.bss*) } >RAM _ebss = .; /* .bss段结束地址符号 */ ``` 启动代码使用这些符号清零.bss段[^1] --- #### 四、烧录偏移量设置 根据编译环境配置: | 工具 | 配置位置 | 参数示例 | |---------------|---------------------------------------------|------------------| | **Keil MDK** | Options for Target → Target → IROM1 | Start:0x08010000 | | **STM32CubeIDE** | .ld文件修改`FLASH`区域ORIGIN值 | 同上 | | **OpenOCD** | `flash write_image erase app.bin 0x08010000` | 烧录命令指定偏移 | --- #### 五、灵活配置示例 **场景:** 从Bootloader(0x08000000)跳转到APP(0x08010000) 1. **APP链接脚本**: ```ld FLASH (rx) : ORIGIN = 0x08010000, LENGTH = 192K ``` 2. **APP启动代码**: ```c /* startup_stm32.s 或 main.c */ SCB->VTOR = 0x08010000; // 向量表重定位 ``` 3. **Bootloader跳转代码**: ```c void jump_to_app(uint32_t app_addr) { __disable_irq(); __set_MSP(*(volatile uint32_t*)app_addr); // 重置栈指针 ((void(*)(void))(*(volatile uint32_t*)(app_addr + 4)))(); // 跳转 } ``` --- #### 六、验证方法 1. **向量表位置检查**: ```bash # OpenOCD命令 mem32 0xE000ED08 1 # 读取VTOR寄存器 ``` 预期输出:`0x08010000` 2. **符号地址验证**: ```bash arm-none-eabi-nm app.elf | grep _sbss ``` 输出示例:`20000000 B _sbss`(RAM起始地址) --- #### 七、常见问题解决 1. **HardFault异常**: - 检查`SCB->VTOR`是否128字节对齐 - 确认链接脚本中`.isr_vector`为第一个段 2. **变量地址错误**: - 确认`.ld`文件中的`ORIGIN`与烧录偏移一致 - 检查`.bss`段初始化代码是否使用`_sbss/_ebss`符号[^1] 3. **烧录失败**: - STM32CubeIDE中更新`.ld`后需**Clean Project** - Keil中修改IROM后需**Rebuild All** > 📌 **最佳实践**:在`SystemInit()`函数中设置VTOR,确保在全局中断使能前完成重定位[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值