1 为什么要对全局变量重定位
首先研究为什么要对Bootloader的全局变量执行重新定位的问题。在Bootloader的源代码中不可避免的要定义一些全局变量,这些全局变量被放置在编译得到的可执行二进制文件的数据段存储区。Bootloader镜像文件或在ROM中以XIP方式运行或被复制到一块在.bib工程文件中定义为RAMIMAGE的区域内,尽管这块区域位于系统RAM存储器中,但却是被当作ROM来使用的。因此无论以何种方式运行,Bootloader镜像所在的存储区域都是只读的,所以有必要把镜像中的数据段读到程序内存中来以保证其中的全局变量可写,这就是对Bootloader的全局变量进行重定位的原因。
此外,后面我们还会了解到Windows CE系统的全局变量也同样需要重定位,实现全局变量重定位功能的函数有KernelRelocate,该函数位于%_winceroot%/private/winceos/coreos/nk/ldr/ldrcmn.c源文件。
2 认识pTOC指针
在继续介绍全局变量重定位之前,有必要介绍一下pTOC指针。先看这个指针的定义,如下。
代码1.摘录自%_winceroot%/private/winceos/coreos/nk/ldr/ldrcmn.c
ROMHDR *const volatile pTOC=(ROMHR *) -1;//Gets replaced by RomLoader with real address
在定义时,pTOC被定义成一个全局常量形式,读者可以想想为什么不定义成全局变量的形式。此外,pTOC指向的是一个无效的地址,因为给它赋值的地址值是-1。这让它显得有点神秘莫测,其实在ROMImage阶段这个-1会被ROMImage.exe[1]改掉,这个后面也会说到。
接着要说的是pTOC指针指向的地址空间所保存的数据的类型为ROMHDR,那么ROMHDR是何许类型呢?下面我们找到ROMHR类型的定义原型,如下。
代码2.摘录自%_winceroot%/public/common/oak/inc/romldr.h
1typedef structROMHDR {
2ULONGdllfirst;// first DLL address
3ULONGdlllast;// last DLL address
4ULONGphysfirst;// first physical address
5ULONGphyslast;// highest physical address
6ULONGnummods;// number of TOCentry's
7ULONGulRAMStart;// start of RAM
8ULONGulRAMFree;// start of RAM free space
9ULONGulRAMEnd;// end of RAM
10ULONGulCopyEntries;// number of copy section entries
11ULONGulCopyOffset;// offset to copy section
12ULONGulProfileLen;// length of PROFentries RAM
13ULONGulProfileOffset;// offset to PROFentries
14ULONGnumfiles;// number of FILES
15ULONGulKernelFlags;// optional kernel flags from ROMFLAGS .bib config option
16ULONGulFSRamPercent;// Percentage of RAM used for filesystem
17// from FSRAMPERCENT .bib config option
18// byte 0 = #4K chunks/Mbyte of RAM for filesystem 0-2Mbytes 0-255
19// byte 1 = #4K chunks/Mbyte of RAM for filesystem 2-4Mbytes 0-255
20// byte 2 = #4K chunks/Mbyte of RAM for filesystem 4-6Mbytes 0-255
21// byte 3 = #4K chunks/Mbyte of RAM for filesystem > 6Mbytes 0-255
22ULONGulDrivglobStart;// device driver global starting address
23ULONGulDrivglobLen;// device driver global length
24USHORTusCPUType;// CPU (machine) Type
25USHORTusMiscFlags;// Miscellaneous flags
26PVOIDpExtensions;// pointer to ROM Header extensions
27ULONGulTrackingStart;// tracking memory starting address
28ULONGulTrackingLen;// tracking memory ending address
29} ROMHDR;
在ROMImage阶段,ROMImage.exe会直接填充84字节数据构成ROMHDR
在ROMImage阶段,ROMImage.exe会直接填充84字节数据构成ROMHDR数据结构,并修改pTOC指针的地址,将之指向填充后得到的ROMHDR数据结构。
暂且先不完全介绍ROMHDR各数据成员的含义,仅仅提一下与全局变量重定位操作有关的两个成员,它们是ulCopyEntries和ulCopyOffset。其含义分别是CopyEntry的数量和第1个CopyEntry的地址。
3 如何实现全局变量的重定位
全局变量重定位由KernelRelocate函数实现,在分析KernelRelocate函数之前我们先认识一下CopyEntry。简单的说CopyEntry就是一种表示拷贝入口信息的数据结构,我们可以从其定义加深对它的理解,其定义以及各数据成员函数如下。
代码3.摘录自%_winceroot%/public/common/oak/inc/romldr.h
1typedef struct COPYentry {
2ULONGulSource;// copy source address
3ULONGulDest;// copy destination address
4ULONGulCopyLen;// copy length
5ULONGulDestLen;// copy destination length
6// (zero fill to end if > ulCopyLen)
7} COPYentry;
表.CopyEntry结构体数据成员含义
成员名
成员含义
ulSource
全局变量在ROM中的起始地址
ulDest
全局变量复制到RAM中的目的地址
ulCopyLen
全局变量真实的长度
ulDestLen
全局变量期望的长度
这里对于ulCopyLen和ulDestLen所表示的全局变量的真实长度和期望长度做如下补充说明:ulDestLen一定不小于ulCopyLen,如果ulDestLen大于ulCopyLen则说明该region的全局变量除了有非零数据之外还存在若干字节的清零数据空间。
理解了CopyEntry之后,就很容易理解KernelRelocate函数拷贝全局变量的过程了,下面是KernelRelocate函数的源代码。
代码4.摘录自%_winceroot%/private/winceos/coreos/nk/ldr/ldrcmn.c
1voidKernelRelocate (ROMHDR *const pTOC){
2ULONGloop;
3COPYentry*cptr;
4// copy globals
5for(loop = 0; loop < pTOC->ulCopyEntries; loop++) {
6cptr = (COPYentry *)(pTOC->ulCopyOffset + loop*sizeof(COPYentry));
7if(cptr->ulCopyLen) {
8memcpy((LPVOID)cptr->ulDest,(LPVOID)cptr->ulSource,cptr->ulCopyLen);
9}
10if(cptr->ulCopyLen != cptr->ulDestLen) {
11memset((LPVOID)(cptr->ulDest+cptr->ulCopyLen),0,cptr->ulDestLen-cptr->ulCopyLen);
12}
13}
14}
第5~13句使用for语句构成循环结构,循环次数等于CopytEntry的数量,也就是pTOC指针所指向的数据结构中的ulCopyEntries成员的取值,每次循环都拷贝一次。拷贝之前先得到拷贝入口信息(第6句)。拷贝分两步执行,首先,如果有数据拷贝(第7句),则拷贝这些数据(第8句);然后,如果期望的大小比实际大小大(第10句),则用0填充其余部分(第11句)。