ARM处理器支撑方位无关的程序规划,这种程序加载到存储器的恣意地址空间都能够正常运转,其规划办法在嵌入式运用体系开发中具有重要的作用。本文首要 介绍方位无关代码的根本概念和完结原理,然后论述依据ARM汇编方位无关的程序规划办法和完结进程,最终以嵌入式Bootloader程序规划为例,介绍 方位无关程序规划在Bootloader程序规划中的作用。
导言
依据方位无关代码 PIC(Position Independent Code)的程序规划在嵌入式运用体系开发中具有重要的作用,尤其在裸机状态下开发Bootloader程序及进行内核初始化规划;运用方位无关的程序设 计办法还能够在详细运用中用于构建高效率动态链接库,因而深化了解和熟练掌握方位无关的程序规划办法,有助于开发人员规划出结构简略、明晰的运用程序。本 文首要介绍方位无关代码的根本概念和完结原理,然后论述依据ARM汇编方位无关的程序规划办法和完结进程,最终以Bootloader程序规划为例,介绍 了方位无关程序规划在Bootloader程序规划中的作用。
1 方位无关代码及程序规划办法
1.1 根本概念与完结原理
运用程序有必要经过编译、汇编和链接后才变成可履行文件,在链接时,要对一切方针文件进行重定位(relocation),树立符号引证规矩,一起为变 量、函数等分配运转地址。当程序履行时,体系有必要把代码加载到链接时所指定的地址空间,以确保程序在履行进程中对变量、函数等符号的正确引证,使程序正常 运转。在具有操作体系的体系中,重定位进程由操作体系主动完结。
在规划Bootloader程序时,有必要在裸机环境中进行,这时 Bootloader映像文件的运转地址有必要由程序员设定。一般状况下,将 Bootloader程序下载到ROM的0x0地址进行发动,而在大多数运用体系中,为了快速发动,首要将Bootloader程序复制到SDRAM中再 运转。一般状况下,这两者的地址并不相同,程序在SDRAM中的地址重定位进程有必要由程序员完结。实践上,因为Bootloader是体系上电后要履行的 榜首段程序,Bootloader程序的复制和在这之前的一切作业都有必要由其本身来完结,而这些指令都是在ROM中履行的。也便是说,这些代码即便不在链 接时所指定的运转时地址空间,也能够正确履行。这便是方位无关代码,它是一段加载到恣意地址空间都能正常履行的特别代码。
方位无关代码常用于以下场合:
◆ 程序在运转期间动态加载到内存;
◆ 程序在不同场合与不同程序组合后加载到内存(如同享的动态链接库);
◆ 在运转期间不同地址相互之间的映射(如Bootloader程序)。
尽管在用GCC编译时,运用-fPIC选项可为C言语发生方位无关代码,但这并不能批改程序规划中固有的方位相关性缺点。特别是汇编言语代码,有必要由程序员遵从必定的程序规划原则,才干确保程序的方位无关性。
1.2 ARM处理器的方位无关程序规划关键
ARM程序的方位无关可履行文件PIE(PositionIndependent Executable)包含方位无关代码PIC和方位无关数据PID(PositionIndependent Data)两部分。
PID首要针对可读写数据段(.data段),其间保存已赋初值的大局变量。为完结其方位无关性,一般运用寄存器R9作为静态基址寄存器,使其指向该可 读写段的首地址,并运用相对于基址寄存器的偏移量来对该段的变量进行寻址。这种办法常用于为可重入程序的多个实例发生多个独立的数据段。在程序规划中,一 般不用考虑可读写段的方位无关性,这首要是因为可读写数据首要分配在SDRAM中。
PIC包含程序中的代码和只读数据(.text段),为确保程序能在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中完结程序跳转不是方位无关的。例如:
LDRPC, =main
上面的LDR汇编伪指令编译后的成果为:
LDRPC, [PC, OFFSET_TO_LPOOL]
LPOOLDCD main
可见,尽管LDR是把依据PC的一个存储单元LPOOL的内容加载到PC中,但该存储单元中保存的却是链接时所决议的main函数进口的肯定地址,所以main函数实践地点的段不是方位无关。
② 方位无关的常量拜访。在运用程序中,常常要读写相关寄存器以完结必要的硬件初始化。为增强程序的可读性,运用EQU伪指令对一些常量进行赋值,但在拜访进程中,有必要完结方位无关性。下面以PXA270的GPIO初始化介绍方位无关的常量拜访办法。
GPIO_BASEEQU0x40e00000;
GPIO基址寄存器地址GPDR0EQU0x00c;相对于GPIO基址寄存器的偏移量
init_GPDR0EQU0xfffbfe00;寄存器GPDR0初值
LDRR1, =GPIO_BASE
LDRR0, =init_GPDR0
STRR0, [R1, #GPDR0]
上述汇编代码段经编译后的成果为:
LDRR1, [PC, OFFSET_TO_GPIO_BASE]
LDRR0, [PC, OFFSET_TO_init_GPDR0]
STRR0, [R1, #0xc]
GPIO_BASEDCD0x40e00000
GPDR0DCD0x00c
init_GPDR0DCD0xfffbfe00
可见,LDR伪指令实践上运用依据PC的偏移量来对符号常量GPIO_BASE和init_GPDR0进行引证,因而是方位无关的。由此能够得出如下结 论:运用LDR伪指令将一个常量读取到非PC的其他通用寄存器中可完结方位无关的常量拜访;但将一个地址值读取到PC中进行程序跳转时,跳转方针则是方位 相关的。
(2) 程序规划标准2
其他被ROPI段中的代码引证的有必要是肯定地址,或者是依据可读写方位无关(RWPI)段的静态基址寄存器的可写数据。
运用肯定地址只能引证被重定位到特定方位的代码段中的符号,经过在方位无关代码中引进肯定地址,能够让程序跳转到指定方位。例如,假定 Bootloader的阶段1将其本身代码复制到链接时所指定的SDRAM地址空间后,当要跳转到阶段2的C程序进口时,能够运用指令“LDRPC, =main”跳转到程序在SDRAM中的main函数进口地址开端履行。这是因为程序在编译链接时给main函数分配肯定地址,体系经过将main函数的 肯定地址直接赋给PC完结程序跳转。假如运用相对跳转指令“Bmain”,那么只会跳转到发动ROM内部的main函数进口。
2 方位无关代码在Bootloader规划中的运用
在运用GNU东西开发Bootloader时,程序在链接时会经过一个链接脚本(linker script)来设定映像文件的内存映射。一个简略的链接脚本结构如下:
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS {
. = BOOTADDR;/*Bootloader的开端地址*/
__boot_start = .;
.textALIGN(4): {/*代码段.text*/
*(.text)
}
.dataALIGN(4): {/*数据段.data*/
*(.data)
}
.gotALIGN(4): {/*大局偏移量表.got段*/
*(.got)
}
__boot_end = .;/*Bootloader映像文件的完毕地址*/
.bssALIGN(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空间运转完结快速引导。当然,对PXA270等具有MMU功用的微处理器来说,尽管能够先将 Bootloader映像整个复制到SDRAM中,再运用MMU功用将SDRAM空间映射到0x0地址,从而持续在SDRAM中运转;但这样一方面会使得 Bootloader的规划与完结复杂化,另一方面在一些有必要屏蔽MMU功用的运用中(例如引导armlinux体系),无法运用MMU进行地址重映射。
运用ARM的依据方位无关的程序规划能够处理上述问题。只需在程序链接时,将BOOTADDR设置为SDRAM空间的地址(一般状况 下运用 SDRAM中最高的1 MB存储空间作为开端地址),这样ARM处理器上电复位后Bootloader依然能够从地址0开端履行,并将本身复制到指定的__boot_start 开端的SDRAM中运转。完结上述功用的链接脚本所对应的发动代码架构如下:
.section .text
.globl _start
_start:
Breset/*复位反常*/
/*其他反常处理代码*/
reset:
/*复位处理程序*/
copy_boot:/*复制Bootloader到SDRAM*/
LDRR0, =0x0LDRR1, =__boot_start
LDRR2, =__boot_end
1:LDRMIA R0!, { R3-R10 }
STRMIA R1!, { R3-R10 }
CMPR1, R2
BLT1b
clear_bss:
/*清零.bss段*/
BL
init_Stack/*初始化仓库*/
LDRPC, = main/*跳转到阶段2的C程序进口*/
.end
程序进口为_start,即复位反常,一切其他反常向量都运用相对跳转指令B来完结,以确保方位无关特性。在完结根本的硬件初始化后,运用链接脚本传递 过来__boot_start和__boot_end的参数,将Bootloader映像整个复制到指定的SDRAM空间,并清零.bss段,初始化仓库 后,程序将main函数进口的肯定地址赋给PC,从而跳转到SDRAM中持续运转。程序在跳转到main函数之前,一切的代码都在ROM中运转,因而有必要 要确保代码的方位无关性,所以在调用初始化GPIO、存储体系和仓库等子程序时,都运用相对跳转指令来完结。
运用方位无关规划Bootloader程序有如下长处:
① 简化规划,便利完结体系的快速引导。方位无关代码能够防止在引导时进行地址映射,并便利地跳转到SDRAM中完结快速引导。
② 完结复位处理智能化。因为方位无关代码能够被加载到恣意地址空间运转,因而其运转时的当时地址与链接时所指使的地址并不用定相同。运用这一特性,能够在复 位处理程序中使处理器进入SVC形式并封闭中断后参加如下代码,便可依据当时运转时的地址进行不同的复位处理:
ADRR0, _start/*读取当时PC邻近的_start标号地点指令地址*/
LDRR1,=__boot_start/*读取Bootloader在SDRAM的开端地址*/
CMPR0,R1
BEQclear_bss
上述代码中的ADR指令读取的_start标号地址由指令的履行地址决议。若是从SDRAM中的Bootloader发动,则上述比较成果持平,程序直 接跳转到clear_bss标号地址处履行,这样能够防止存储体系的从头初始化和Bootloader的复制进程;若是上电或硬件复位,程序从 ROM发动,则上述比较成果不等,程序便进行包含体系初始化和Bootloader复制等进程的全面复位处理操作。
③ 便于调试。Bootloader的调试一般也是一个繁琐的进程,运用方位无关代码,则能够将映像文件加载到SDRAM中进行调试,这既能真实地反映程序从ROM中进行体系引导的状况,又能够防止频频烧写程序存储器。
3 定论
本文所介绍的依据方位无关的程序规划是经过依据PC或基址寄存器的符号引证标准来完结的。这种办法在实践体系开发中运用广泛,既能用于引导程序的规划, 也可用于一般的运用程序或嵌入式同享库的开发。而在Bootloader的规划中引进方位无关代码,能够使程序结构更为简略明晰,并能防止地址重映射并从 SDRAM进行快速体系引导;引证方位无关的规划办法使Bootloader的复位处理功用更为灵敏,还使得在SDRAM中和在ROM中进行程序调试具有 相同的作用。