位置无关代码的程序设计

本文介绍了位置无关代码(PIC)的设计要点及其在Bootloader程序中的应用。重点讲述了如何使用基于PC的符号引用实现程序跳转和常量访问,以确保代码在不同存储空间中的正确运行。此外,还详细说明了位置无关代码在Bootloader设计中的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为保证程序能在ROM和SDRAM空间都能正确运行(如裸机状态下的Bootloader程序),必须采用位置无关代码程序设计。下面重点介绍PIC的程序设计要点。

PIC遵循只读段位置无关ROPI(ReadOnly Position Independence)的ATPCS(ARMThumb Procedure Call Standard)的程序设计规范:

(1) 程序设计规范

引用同一ROPI段或相对位置固定的另一ROPI段中的符号时,必须是基于PC的符号引用,即使用相对于当前PC的偏移量来实现跳转或进行常量访问。

① 位置无关的程序跳转。在ARM汇编程序中,使用相对跳转指令B/BL实现程序跳转。指令中所跳转的目标地址用基于当前PC的偏移量来表示,与链接时分配给地址标号的绝对地址值无关,因而代码可以在任何位置进行跳转,实现位置无关性。

另外,还可使用ADR或ADRL伪指令将地址标号值读取到PC中实现程序跳转。这是因为ADR或ADRL等伪指令会被编译器替换为对基于PC的地址值进 行操作,但这种方式所能读取的地址范围较小,并且会因地址值是否为字对齐而异。但在ARM程序中,使用LDR等指令直接将地址标号值读取到 PC中实现程序跳转不是位置无关的。例如:

LDR PC, =main

上面的LDR汇编伪指令编译后的结果为:

LDR PC, [PC, OFFSET_TO_LPOOL]
LPOOL DCD main

可见,虽然LDR是把基于PC的一个存储单元LPOOL的内容加载到PC中,但该存储单元中保存的却是链接时所决定的main函数入口的绝对地址,所以main函数实际所在的段不是位置无关。

② 位置无关的常量访问。在应用程序中,经常要读写相关寄存器以完成必要的硬件初始化。为增强程序的可读性,利用EQU伪指令对一些常量进行赋值,但在访问过程中,必须实现位置无关性。下面以PXA270的GPIO初始化介绍位置无关的常量访问方法。

GPIO_BASE EQU 0x40e00000; //GPIO基址寄存器地址
GPDR0 EQU 0x00c; //相对于GPIO基址寄存器的偏移量
init_GPDR0 EQU 0xfffbfe00; //寄存器GPDR0初值
LDR R1, =GPIO_BASE
LDR R0, =init_GPDR0
STR R0, [R1, #GPDR0]

上述汇编代码段经编译后的结果为:

LDR R1, [PC, OFFSET_TO_GPIO_BASE]
LDR R0, [PC, OFFSET_TO_init_GPDR0]
STR R0, [R1, #0xc]
GPIO_BASE DCD 0x40e00000
GPDR0 DCD 0x00c
init_GPDR0 DCD 0xfffbfe00

可见,LDR伪指令实际上使用基于PC的偏移量来对符号常量GPIO_BASE和init_GPDR0进行引用,因而是位置无关的。由此可以得出如下结 论:使用LDR伪指令将一个常量读取到非PC的其他通用寄存器中可实现位置无关的常量访问;但将一个地址值读取到PC中进行程序跳转时,跳转目标则是位置相关的。

其他被ROPI段中的代码引用的必须是绝对地址,或者是基于可读写位置无关(RWPI)段的静态基址寄存器的可写数据。

使用绝对地址只能引用被重定位到特定位置的代码段中的符号,通过在位置无关代码中引入绝对地址,可以让程序跳转到指定位置。例如,假设 Bootloader的阶段1将其自身代码拷贝到链接时所指定的SDRAM地址空间后,当要跳转到阶段2的C程序入口时,可以使用指令“LDR PC, =main”跳转到程序在SDRAM中的main函数入口地址开始执行。这是因为程序在编译链接时给main函数分派绝对地址,系统通过将main函数的 绝对地址直接赋给PC实现程序跳转。如果使用相对跳转指令“B main”,那么只会跳转到启动ROM内部的main函数入口。

(2)位置无关代码在Bootloader设计中的应用

在使用GNU工具开发Bootloader时,程序在链接时会通过一个链接脚本(linker script)来设定映像文件的内存映射。一个简单的链接脚本结构如下:

OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS {
. = BOOTADDR;/Bootloader的起始地址/
__boot_start = .;
.text ALIGN(4): {/代码段.text/
*(.text)
}
.data ALIGN(4): {/数据段.data/
*(.data)
}
.got ALIGN(4): {/全局偏移量表.got段/
*(.got)
}
__boot_end = .;/Bootloader映像文件的结束地址/
.bss ALIGN(16): {/堆栈段.bss/
__bss_start = .;
*(.bss)
__bss_end =.;
}
}

这里不再介绍链接脚本的语法。需要指出的是,链接脚本中所描述的输出段地址为虚拟地址VMA(Virtual Memory Address)。这里的“虚拟地址”仅指映像文件执行时,各输出段所重定位到相应的存储地址空间,与内存管理无关。因此,上面的链接脚本实际上指定了 Bootloader映像在执行时,将被重定位到BOOTADDR开始的存储地址空间,以保证在相关位置对符号进行正确引用,使程序正常运行。

ARM处理器复位后总是从0x0地址取第1条指令,因此只需把BOOTADDR设置为0,再把编译后生成的可执行二进制文件下载到ROM的 0x0地址开始的存储空间,程序便可正常引导;但是,一旦在链接时指定映像文件从0x0地址开始,那么Bootloader就只能在0x0地址开始的 ROM空间内运行,而无法拷贝到SDRAM空间运行实现快速引导

利用ARM的基于位置无关的程序设计可以解决上述问题。只需在程序链接时,将BOOTADDR设置为SDRAM空间的地址(一般情况 下利用 SDRAM中最高的1 MB存储空间作为起始地址),这样ARM处理器上电复位后Bootloader仍然可以从地址0开始执行,并将自身拷贝到指定的__boot_start 起始的SDRAM中运行。实现上述功能的链接脚本所对应的启动代码架构如下:

.section .text
.globl _start
_start:
B reset /复位异常/

/其他异常处理代码/
reset:
/复位处理程序/
copy_boot: /拷贝Bootloader到SDRAM/
LDR R0, =0x0
LDR R1, =__boot_start
LDR R2, =__boot_end
1:
LDRMIA R0!, { R3-R10 }
STRMIA R1!, { R3-R10 }
CMP R1, R2
BLT 1b
clear_bss:

/清零.bss段/
BL init_Stack /初始化堆栈/
LDR PC, = main /跳转到阶段2的C程序入口/
.end

程序入口为_start,即复位异常,所有其他异常向量都使用相对跳转指令B来实现,以保证位置无关特性。在完成基本的硬件初始化后,利用链接脚本传递 过来__boot_start和__boot_end的参数,将Bootloader映像整个拷贝到指定的SDRAM空间,并清零.bss段,初始化堆栈 后,程序将main函数入口的绝对地址赋给PC,进而跳转到SDRAM中继续运行。程序在跳转到main函数之前,所有的代码都在ROM中运行,因而必须 要保证代码的位置无关性,所以在调用初始化GPIO、存储系统和堆栈等子程序时,都使用相对跳转指令来完成。

使用位置无关设计Bootloader程序有如下优点:

① 简化设计,方便实现系统的快速引导。位置无关代码可以避免在引导时进行地址映射,并方便地跳转到SDRAM中实现快速引导。

② 实现复位处理智能化。由于位置无关代码可以被加载到任意地址空间运行,因此其运行时的当前地址与链接时所指派的地址并不一定相同。利用这一特性,可以在复 位处理程序中使处理器进入SVC模式并关闭中断后加入如下代码,便可根据当前运行时的地址进行不同的复位处理:

ADR R0, _start/读取当前PC附近的_start标号所在指令地址/
LDR R1,=__boot_start/读取Bootloader在SDRAM的起始地址/
CMP R0, R1
BEQ clear_bss

上述代码中的ADR指令读取的_start标号地址由指令的执行地址决定。若是从SDRAM中的Bootloader启动,则上述比较结果相等,程序直 接跳转到clear_bss标号地址处执行,这样可以避免存储系统的重新初始化和Bootloader的拷贝过程;若是上电或硬件复位,程序从 ROM启动,则上述比较结果不等,程序便进行包括系统初始化和Bootloader拷贝等过程的全面复位处理操作。

③ 便于调试。Bootloader的调试通常也是一个繁琐的过程,使用位置无关代码,则可以将映像文件加载到SDRAM中进行调试,这既能真实地反映程序从ROM中进行系统引导的情况,又可以避免频繁烧写程序存储器。

总结:
位置无关码:CPU取指时,总是相对于本条执行指令的相对地址去取指。比如指行一个ADD指令时,PC要取下一指令的地址,就在原来的基础上+4。这就不管你代码放在存储器的任何位置,只要他们的相对地址没有改变,就能正常执行程序。一般上电复位那几条语句就必须是位置无关码指令。

位置相关码:可以这样来说,就是CPU每次取指都从绝对位置去取,而不是上面的相对位置。这个绝对地址就是相对起始地址0来说的。这样,就要求你在存放程序时,必须给连接脚本所规定的一样,把代码放到指定位置。

<think>我们正在处理一个IAR编译错误,错误信息为:Error[Ta114] Option --no_literal_pool only valid for ARMv7 and ARMv6-M non-ROPI/RWPI。这个错误表明,用户在使用`--no_literal_pool`编译选项时,目标处理器架构不符合要求。 ### 错误原因分析 在IAR编译器中,`--no_literal_pool`选项用于控制是否禁止生成文字池(literal pool)。文字池是编译器用于存储常量数据(如浮点数、大整数等)的一块内存区域。该选项仅适用于特定的ARM架构版本: - ARMv7 - ARMv6-M(且非ROPI/RWPI模式) 如果用户的目标处理器不是上述架构之一,或者虽然目标处理器是ARMv6-M但使用了ROPI(Read-Only Position Independent)或RWPI(Read-Write Position Independent)模式,那么使用`--no_literal_pool`选项就会导致这个错误。 ### 解决方法 根据错误原因,我们可以采取以下步骤: 1. **检查目标处理器架构**: - 确认项目设置的处理器架构是否为ARMv7或ARMv6-M。如果不是,则不能使用`--no_literal_pool`选项。 - 在IAR Embedded Workbench中,可以通过项目选项(Project > Options)中的“General Options”>“Target”选项卡来查看和修改处理器架构。 2. **检查是否启用了ROPI/RWPI**: - 如果目标处理器是ARMv6-M,还需要检查是否启用了ROPI或RWPI模式。在“General Options”>“Target”选项卡下的“Position Independence”设置区域,确保没有选择ROPI或RWPI。 - 如果启用了ROPI或RWPI,则不能使用`--no_literal_pool`选项。 3. **移除或替换编译选项**: - 如果目标处理器架构不符合要求,或者启用了ROPI/RWPI,那么需要移除`--no_literal_pool`选项。 - 在项目选项中的“C/C++ Compiler”>“Extra Options”或“Command Line”选项卡中,检查是否添加了`--no_literal_pool`选项,将其删除。 - 如果项目确实需要避免使用文字池(例如,为了优化性能或减少代码大小),可以考虑其他替代方案,例如使用`--literal_pool`选项(如果可用)或者调整代码以减少常量数据的使用。 4. **替代优化方法**: - 如果目标处理器是Cortex-M0/M0+(属于ARMv6-M架构),但项目启用了ROPI/RWPI,那么可能需要权衡是否必须使用ROPI/RWPI。如果不必要,可以关闭ROPI/RWPI以使用`--no_literal_pool`。 - 如果必须使用ROPI/RWPI,那么就不能使用`--no_literal_pool`,此时可以尝试其他优化选项,如`--optimize`调整优化级别,或者使用`--inline`控制内联函数等。 ### 示例步骤(IAR Embedded Workbench IDE) 1. 打开项目选项(右键点击项目 > Options)。 2. 选择“General Options”>“Target”选项卡: - 确认Device是否正确,并查看Core是否为ARMv7或ARMv6-M(如Cortex-M0/M0+)。 - 在“Position Independence”区域,确保ROPI和RWPI都未勾选。 3. 选择“C/C++ Compiler”>“Extra Options”选项卡,检查是否有`--no_literal_pool`,如果有则删除。 4. 或者,在“C/C++ Compiler”>“List”选项卡中,可以查看实际使用的命令行,确认是否有该选项。 5. 重新编译项目。 ### 总结 该错误的根本原因是编译选项与目标架构不兼容。解决方法主要是调整目标架构设置或移除冲突的编译选项。在调整设置时,需要根据项目的实际需求来权衡。 §§相关问题§§ 1. 如何查看IAR项目中当前使用的编译选项? 2. 在ARM架构中,文字池(literal pool)的作用是什么? 3. ROPI/RWPI模式在ARM嵌入式开发中有什么作用? 4. 除了移除`--no_literal_pool`选项,还有哪些方法可以优化代码大小?</think>### IAR编译错误 `Error[Ta114]` 解决方法 #### 错误原因分析 该错误的核心是 **编译选项与目标架构不兼容**: 1. **`--no_literal_pool` 选项的限制**: - 该选项仅适用于特定 ARM 架构: - ARMv7(如 Cortex-M3/M4/M7) - ARMv6-M(如 Cortex-M0/M0+)**且未启用 ROPI/RWPI 模式** - 其他架构(如 ARMv7-M with ROPI/RWPI、ARMv8-M)或配置不满足条件时使用会触发此错误。 2. **ROPI/RWPI 模式冲突**: - ROPI(Read-Only Position Independence)和 RWPI(Read-Write Position Independence)是位置无关代码技术。 - 当项目启用 ROPI/RWPI 时,`--no_literal_pool` 与位置无关需求冲突,导致错误[^1]。 --- #### 解决步骤 ##### ✅ 方法1:移除不兼容选项(推荐) 1. 在 IAR 中打开项目选项: **Project > Options > C/C++ Compiler > Extra Options** 2. 删除 `--no_literal_pool` 或取消勾选其启用状态。 3. 重新编译项目。 ##### ✅ 方法2:调整目标架构(按需选用) - **情况1:目标为 ARMv6-M(如 Cortex-M0)且启用了 ROPI/RWPI** 在 **Project > Options > General Options > Target** 中: - 关闭 ROPI/RWPI 模式(取消勾选) - 或移除 `--no_literal_pool` 选项(优先推荐) - **情况2:目标非 ARMv7/ARMv6-M** 检查设备型号是否选择正确: - 路径:**Project > Options > General Options > Target > Device** - 确保选择兼容架构(如 Cortex-M3/M4 属于 ARMv7)。 ##### ✅ 方法3:替代优化方案 若需减少文字池(literal pool)使用: 1. 使用 `--literal_pool` 替代(自动管理) 2. 启用编译器优化: **Project > Options > C/C++ Compiler > Optimizations** 选择 **High** 或 **Balanced** 3. 减少全局常量:将大型常量数组移至函数内部(需权衡 RAM/ROM 占用)。 --- #### 配置验证示例 ```c // 测试代码(编译后观察文字池使用) const float DATA[] = {3.14, 1.618}; // 可能触发文字池 int main() { return (int)DATA[0]; } ``` - **正确配置**:无 `--no_literal_pool` 时编译通过 - **错误配置**:在 ARMv8-M 或 ROPI 模式下强制启用该选项时触发 Ta114 --- ### 相关问题 1. 如何检查 IAR 项目中的 ROPI/RWPI 设置状态? 2. ARMv7 与 ARMv6-M 架构在文字池处理上有何区别? 3. 位置无关代码(ROPI/RWPI)在嵌入式开发中有哪些应用场景? 4. IAR 编译器优化选项对代码大小和性能有何影响? [^1]: IAR C/C++ Development Guide, Section "ARM-specific compiler options".
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值