32位装载程序 - 装载系统
1. 可执行内存映像(Executable Memory Image)
平时经常会说到可执行文件,我们都会认为它是可以运行,毕竟大多数时候用鼠标双击之后,就运行了。但是,如果深究的话,它只是包含了可以运行的代码和数据,但它不仅仅包含可以运行的代码和数据,还包含了文件的格式信息、扩展信息、重定位信息等等,更关键的是,这些数据都不在它应该在的内存位置上,所以对于CPU来说,可执行文件并不可执行。因此,对于系统来说,还要将可执行文件翻译成CPU可执行的格式。
为了区别于可执行文件这个概念,我基本就参照微软的说法了,把CPU可以直接执行的格式称为可执行内存映像(Executable Memory Image),微软的PE格式文档是称为内存映像(Memory Image),后面就直接称为映像(Image)。
我引入可执行内存映像这个概念,是为了区别于可执行文件这个概念。毕竟可执行文件这个概念在开发操作系统的语境里,出现了多重意思,已经造成理解混乱了。然后可执行文件就特指PE格式的文件了,
微软使用内存映像这个词,其实我在刚开始看微软文档的时候,都没注意到这个词,然后总/是到处看到IMAGE这个词。虽然后来能理解它的意思了,但那也是读书百遍,其意自现的结果,跟猜出来的没什么区别。所以我就在这个词前加了可执行三个字,而且专门用一个小章节来说明。
2. 装载(Load)
要运行可执行文件中的代码,就要把可执行文件翻译成可执行内存映像,我把这个翻译的过程称为装载。
装载比较关键的步骤有两个,一个是把代码和数据读入内存,另一个是重定位。
读入代码和数据
这个需要按相对位置把代码和数据读入内存,因为可执行文件中的代码和数据并不是按相对位置保存的,有的未初始化数据在可执行文件中都没有空间,要在装载的时候为其提供空间。
重定位
这个步骤在微软的文档中称为基址重定位,因为系统分配的内存不一定就是编译时指定的基址,这个时候就不能正确运行程序了,必须要重定位后,才能正确运行。
如果系统给可执行文件分配的内存与编译时指定的基址一致时,就不用重定位了,把代码和数据读入内存后就形成了可执行内存映像。
3. PE格式
微软的可执行文件格式,本身就可以是一个大主题,就不再这里细说了,至于在书里是作为正文还是附录,就要考虑考虑,毕竟PE格式本身并不是系统的内容。
4. 我的装载程序
在16位装载程序的部分,已经将系统核心文件读入内存了,这里只需要把它翻译成可执行内存映像就行了。不用在32位装载程序里编写磁盘驱动程序和文件系统驱动程序了。
读入内存 PE_LoadToMem
把PE文件中的各个节(SECTION)按相对位置读入内存即可
void PE_LoadToMem(void * pImgBuf, void * pMemFile)
{
int i, nTotalSect;
uint32_t nImgSize;
coff_section_t* pCOFFSect;
//
nImgSize = PE_ImageSize(pMemFile);
/* initialize the image bufer, and copy file head */
memset(pImgBuf, 0, nImgSize);
/* copy PE header */
memcpy(pImgBuf, pMemFile, 4096);
nTotalSect = PE_HEADER(pMemFile)->pe_coff_header.hdr_num_of_sections;
pCOFFSect = PE_COFF_SECTION(pMemFile);
for( i = 0; i < nTotalSect; i++, pCOFFSect++){
memcpy((byte_t *)pImgBuf + pCOFFSect->sect_virtual_address,
(byte_t *)pMemFile + pCOFFSect->sect_raw_data_pointer,
pCOFFSect->sect_size_of_raw_data);
}
}
重定位 PE_Relocate
PE中的基址重定位要处理重定位表,如果没有重定位信息,又没有分配在默认的内存位置上时,程序是可以执行,只是会出错。
result_t PE_Relocate(void * pImgBuf, uint32_t nImgBase)
{
pe_header_t * pPeHdr;
pe_img_data_dir_t
* iddrel;
fixup_blk_t * relblk;
fixup_entry_t * fe;
dword_t rel_base, rel_pos, size, blksize, delta;
//
pPeHdr = PE_HEADER(pImgBuf);
iddrel = PE_GET_IDD(pImgBuf,IDD_BASE_RELOC_TABLE);
relblk = (fixup_blk_t *)((byte_t *)pImgBuf + iddrel->idd_rva);
size = iddrel->idd_size;
delta = nImgBase - pPeHdr->pe_optional_header.ohdr_image_base;
//
if( size == 0 && pPeHdr->pe_optional_header.ohdr_image_base != nImgBase){
//LDR_Printf("have no relocation information, ");
//LDR_Printf("and relocation address not equare the default address!\n");
return RESULT_FAILED;
}
/* the loading address is the same as default address*/
if( pPeHdr->pe_optional_header.ohdr_image_base == nImgBase)
return RESULT_SUCCEED;
while( size ){
fe = (fixup_entry_t *)(relblk + 1);
rel_base = ((dword_t)pImgBuf + relblk->fxb_page_rva);
blksize = relblk->fxb_block_size;
relblk = (fixup_blk_t *)((byte_t *)relblk + blksize);
size -= blksize;
blksize -= 8;
//
while( blksize ){
rel_pos = rel_base + fe->fxe_offset;
switch(fe->fxe_type){
case REL_BASED_ABSOLUTE: break;
case REL_BASED_HIGH :*(word_t*)rel_pos+=HIGH_WORD(delta);break;
case REL_BASED_LOW :*(word_t*)rel_pos+=(word_t)delta; break;
case REL_BASED_HIGHLOW :*(dword_t*)rel_pos += delta; break;
default: return RESULT_FAILED;
}
fe++;
blksize -= 2;
}
}
PEHDR_OPTIONAL_HEADER(pImgBuf)->ohdr_image_base = nImgBase;
PEHDR_OPTIONAL_HEADER(pImgBuf)->ohdr_entry += nImgBase;
return RESULT_SUCCEED;
}
5. 运行操作系统
装载系统完成后,就可以运行系统了,这里把32位装载程序的主程序贴出来
#define LDR_ENABLE_PAGE() do{ __asm mov eax, cr0 \
__asm or eax, 0x80000000 \
__asm mov cr0, eax \
__asm mov eax, cr3 \
__asm mov cr3, eax \
}while(0)
#define LDR_RUN_SYSTEM(_entry, _sp) do{ __asm mov eax, _entry \
__asm mov ebx, _sp \
__asm mov esp, ebx \
__asm call eax \
}while(0)
void main(void)
{
uint32_t nUsedSize, nSysImgOffset;
byte_t * pSP, * pFPT;
void (* pRbxEntry)(void);
pe_optional_header_t * pOpHdr;
phymemlayout_t* pPML = &pmlPhyMemLayout;
//
pLdrParamListHead = (loaderparanode_t *)LDR_PARAM_LIST_HEAD;
BVD_Init();
LDR_Printf("32-BIT RBXLDR running...\n");
//
/* PE relocation need two import function, so set it first */
PE_SetGetSymAddr(GetSymbolAddr);
PE_SetLoadModule(LoadModule);
CheckLdrParamList(pLdrParamListHead);
/* [SYSTEM BASE] only 0, or 3G */
pSysAddrBase = GetSysAddrBase(pLdrParamListHead);
pPML->pml_SysAddrBase = (dword_t)pSysAddrBase;
BuildSystMemLayout(&nSysImgOffset);
//
LDR_Printf("system base: %P, image offset: %P. image base: %P\n",
pSysAddrBase, nSysImgOffset, pSysAddrBase + nSysImgOffset);
pRbxEntry = LoadCoreFile((void *)nSysImgOffset,
(void *)(pSysAddrBase + nSysImgOffset), &nUsedSize);
if( NULL == pRbxEntry ){
BootFailed("failed to load core file\n");
return ;
}
pOpHdr = PEHDR_OPTIONAL_HEADER((void *)nSysImgOffset);
pPML->pml_SysImgSize = pOpHdr->ohdr_image_size + 256 * K;
pPML->pml_SysImgSize = ALIGN_UPTO(pPML->pml_SysImgSize, M) - 4 * K;
pFPT = (byte_t *)(pPML->pml_SysImgBase + pPML->pml_SysImgSize);
pSP = pSysAddrBase + (uint32_t)pFPT - 4 * K;
SYS_MEM_LAYOUT = (dword_t)pPML;
//
pPML->pml_SysPageTabBase = (dword_t)pFPT;
IA32_BuildPageMap(pFPT, nPhyMemSize);
pPML->pml_FreeMemBase = pPML->pml_SysPageTabBase;
pPML->pml_FreeMemBase += pPML->pml_SysPageTabSize;
pPML->pml_FreeMemBase = ALIGN_UPTO(pPML->pml_FreeMemBase, M);
LDR_ENABLE_PAGE();
/* from here, run in 32-bit paging mode */
LDR_Printf("\n");
LDR_Printf(":> ROBIX system entry: %08X. size: %d Kb. SP: %08X\n",
pRbxEntry, pOpHdr->ohdr_image_size / K, pSP);
LDR_RUN_SYSTEM(pRbxEntry, pSP);
}
本文详细介绍了32位系统装载程序的工作原理,包括可执行内存映像的概念,装载过程中的读取与重定位步骤,PE格式解析,以及如何通过自定义装载程序将系统核心文件读入内存并转换为可执行格式,最终运行操作系统。
5334

被折叠的 条评论
为什么被折叠?



