操作系统开发系列—12.c.从Loader加载ELF内核,顺便解释下函数调用过程 ●
实际上,我们要做的工作是根据内核的Program header table的信息进行类似下面这个C语言语句的内存复制:
memcpy(p_vaddr, BaseOfLoaderPhyAddr+p_offset, p_filesz);
复制可能不止一次,如果Program header有n个,复制就进行n次。
每一个Program header都描述一个段,语句中的P_offset为段在文件中的偏移,p_filesz为段在文件中的长度,p_vaddr为段在内存中的虚拟地址。
由ld生成的可执行文件中p_vaddr的值总是一个类似于0x8048XXX的值,至少我们的例子中是一个这样的值。可是我们启动分页机制时地址都是对等映射的,内存地址0x8048XXX已经处在128MB内存以外(128MB的十六进制表示是0x8000000),如果计算机的内存小于128MB的话,这个地址显然已经超出了内存大小。
即便计算机有足够大的内存,显然,我们也不能让编译器来决定内核加载到什么地方。解决它有两个办法,一是通过修改页表让0x8048XXX映射到较低的地址,另一种方法就是通过修改ld的选项让它生成的可执行代码中p_vaddr的值变小。
nasm -f elf -o kernel.o kernel.asm
ld -m elf_i386 -s -Ttext 0x30400 -o kernel.bin kernel.o
程序的入口地址就变成0x30400了,ELF header等信息会位于0x30400之前。此时的ELF header和Program header table的情况如下表所示:


根据上表,我们应该这样放置内核:
memcpy(30000h, 90000h+0, 40Dh);
也就是说,我们应该把文件从开头开始40Dh字节的内容放到内存30000h处。由于程序的入口在30400h处,所以从这里就可以看出,实际上代码只有0Dh+1个字节。下面是Kernel.bin的内容:


上面被星号省去的部分都是0.从中可以看出,从400h到40Dh是仅有的代码,0xEBFE正是代码最后的“jmp $”。
下面的代码实现了将Kernel.bin根据ELF文件信息转移到正确的位置。它很简单,找出每个Program header,根据其信息进行内存复制:
; InitKernel ---------------------------------------------------------------------------------
; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置
; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。
; --------------------------------------------------------------------------------------------
InitKernel:
xor esi, esi
mov cx, word [BaseOfKernelFilePhyAddr+2Ch];`. ecx <- pELFHdr->e_phnum
movzx ecx, cx ;/
mov esi, [BaseOfKernelFilePhyAddr + 1Ch] ; esi <- pELFHdr->e_phoff
add esi, BaseOfKernelFilePhyAddr;esi<-OffsetOfKernel+pELFHdr->e_phoff
.Begin:
mov eax, [esi + 0]
cmp eax, 0 ; PT_NULL
jz .NoAction
push dword [esi + 010h] ;size ;`.
mov eax, [esi + 04h] ; |
add eax, BaseOfKernelFilePhyAddr; | memcpy((void*)(pPHdr->p_vaddr),
push eax ;src ; | uchCode + pPHdr->p_offset,
push dword [esi + 08h] ;dst ; | pPHdr->p_filesz;
call MemCpy ; |
add esp, 12 ;/
.NoAction:
add esi, 020h ; esi += pELFHdr->e_phentsize
dec ecx
jnz .Begin ret
; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
InitKernel源码解析:
ELF文件首先是ELF header,首先要获取的e_phnum值离文件开头有1*16+2*2+4+4+4+4+4+2*2=44=2C字节,e_phnum本身是2个字节;e_phoff离文件开头有1*16+2*2+4+4=28=1C字节,e_phoff值本身是4个字节,它代表的是Program header table在文件中的偏移量(以字节计数),所以在.Begin之前esi最终指向的是ELF文件中的Program header table的开头。
MemCpy源码解析:
16位CPU入栈和出栈操作都是以字(16位)为单位进行的。
32位CPU入栈和出栈操作是以32位为单位进行的。
现在esi是从Program header里找,p_filesz离Program header开头有4+4+2*4=16=10h字节,p_filesz本身是4个字节,把它最先压入栈,push dword [esi + 010h];紧接着是压入p_offset(段的第一个字节在文件中的偏移)的值对应的那个位置;最后压入的是p_vaddr的值,然后调用MemCpy函数,栈内的内容如下所示:

现在来看MemCpy的代码:
; ------------------------------------------------------------------------
; 内存拷贝,仿 memcpy
; ------------------------------------------------------------------------
; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
MemCpy:
push ebp
mov ebp, esp push esi
push edi
push ecx mov edi, [ebp + 8] ; Destination
mov esi, [ebp + 12] ; Source
mov ecx, [ebp + 16] ; Counter
.1:
cmp ecx, 0 ; 判断计数器
jz .2 ; 计数器为零时跳出 mov al, [ds:esi] ; ┓
inc esi ; ┃
; ┣ 逐字节移动
mov byte [es:edi], al ; ┃
inc edi ; ┛ dec ecx ; 计数器减一
jmp .1 ; 循环
.2:
mov eax, [ebp + 8] ; 返回值 pop ecx
pop edi
pop esi
mov esp, ebp
pop ebp ret ; 函数结束,返回
; MemCpy 结束------------------------------
调用call指令的时候会自动把eip压入栈中,MemCpy第一句把ebp压栈,所以现在栈内情况如下图:

然后把esp的值赋给ebp,因此[ebp + 8]的值就是dst,[ebp+12]就是src,[ebp+16]就是栈最底部的size。然后就是逐字节把src的内容复制到dst,最后把dst的值当成返回值赋给eax寄存器。最后依次弹出压入的值并调用ret(pop eip),那么栈里还剩下dst、src、size共12字节的数据,所以在call MemCpy之后是add esp, 12.
接下来就是向内核跳转
;***************************************************************
jmp SelectorFlatC:KernelEntryPointPhyAddr ; 正式进入内核 *
;***************************************************************
KernelEntryPointPhyAddr定义在头文件load.inc中,其值为0x30400.当然,它必须跟我们的ld的参数-Ttext指定的值是一致的。将来如果我们想将内核放在另外的位置,只需改动这两个地方就可以了。
运行结果如下:

成功了,出现字符“K”,这表明我们的内核在执行了。Loader的使命圆满结束。

【源码】
操作系统开发系列—12.c.从Loader加载ELF内核,顺便解释下函数调用过程 ●的更多相关文章
- 操作系统开发系列—12.b.从Loader跳入保护模式
现在,内核已经被我们加载进内存了,该是跳入保护模式的时候了. 首先是GDT以及对应的选择子,我们只定义三个描述符,分别是一个0~4GB的可执行段.一个0~4GB的可读写段和一个指向显存开始地址的段: ...
- 操作系统开发系列—12.a.从Loader到内核 ●
Loader要做两项工作,我们先来做第一项,把内核加载到内存: 1.加载内核到内存. 2.跳入保护模式. 首先编译无内核时: nasm boot.asm -o boot.bin nasm loader ...
- cocos2d-x游戏开发系列教程-坦克大战游戏加载地图的编写
上节课写了关卡选择场景,那么接下来写关卡内容,先写最基本的地图的加载 我们新建一个场景类,如下所示: class CityScene : public cocos2d::CCLayer { publi ...
- arcgis api 4.x for js 结合 react 入门开发系列react全家桶实现加载天地图(附源码下载)
基于两篇react+arcgis的文章介绍,相信大家也能体会两者的开发区别了.在“初探篇”中作者也讲述了自己的选择,故废话不多说,本篇带大家体验在@arcgis/webpack-plugin环境下,使 ...
- 操作系统开发系列—12.f.在内核中添加中断处理 ●
因为CPU只有一个,同一时刻要么是客户进程在运行,要么是操作系统在运行,如果实现进程,需要一种控制权转换机制,这种机制便是中断. 要做的工作有两项:设置8259A和建立IDT. /*========= ...
- 操作系统开发系列—12.e.Makefile
先来看一个简单的Makefile,我们把它放在目录/boot下,可以用来编译boot.bin和loader.bin. # Makefile for boot # Programs, flags, et ...
- 操作系统开发系列—12.d.扩充内核 ●
现在把esp.GDT等内容放进内核中,我们现在可以用C语言了,只要能用C,我们就避免用汇编. 下面看切换堆栈和GDT的关键代码: ; 导入函数 extern cstart ; 导入全局变量 exter ...
- 操作系统开发系列—12.g.在内核中设置键盘中断
8259A虽然已经设置完成,但是我们还没有真正开始使用它呢. 所有的中断都会触发一个函数spurious_irq(),这个函数的定义如下: PUBLIC void spurious_irq(int i ...
- C#开发BIMFACE系列49 Web网页中加载模型与图纸的技术方案
BIMFACE二次开发系列目录 [已更新最新开发文章,点击查看详细] 在BIMFACE二次系列博客中详细介绍了服务器端API的调用方式,如下列表 C#开发BIMFACE系列1 BIMFAC ...
随机推荐
- Android中BroadcastReceiver广播
BroadCastReceiver 简介 广播接收者( BroadcastReceiver )用于接收广播 Intent ,广播 Intent 的发送是通过调用 Context.sendBroadca ...
- Ionic2学习笔记(0):HelloWorld
作者:Grey 原文地址:http://www.cnblogs.com/greyzeng/p/5529153.html 操作系统: Windows 10 环境配置: Node.js Java SE D ...
- 3种Java从文件路径中获取文件名的方法
package test; import java.io.File; public class FileName { /** * @param args */ public static void m ...
- 30天C#基础巩固------了解委托,string练习
---->了解委托. 生活中的例子:我要打官司,我需要找一个律师,法庭上面律师为当事人辩护,它真正执行的是当事人的陈词,这时律师 就相当于一个委托对象.当事人则委托律师为自己辩解. ...
- 局部页面传值Model
1:新建个局部页面,将这里页面的Model数据传递过去,在局部页面进行和一般页面一样的操作就行. 这里和HTML.Action不一样,对于HTML.action来说,它是新建了一个action来进行传 ...
- AdjustWindowRect与AdjustWindowRectEx
AdjustWindowRect 根据所需的矩形大小,计算所需的矩形的大小;然后,窗口矩形可以传递给CreateWindow函数创建一个窗口. AdjustWindowRectEx 是AdjustWi ...
- IIS发布网站遇到的异常
1.0 HTTP 错误 401.3 - Unauthorized由于 Web 服务器上此资源的访问控制列表(ACL)配置或加密设置,您无权查看此目录或页面.详细错误信息模块 IIS Web Core通 ...
- 使用OWIN 为WebAPI 宿主 跨平台
OWIN是什么? OWIN的英文全称是Open Web Interface for .NET. 如果仅从名称上解析,可以得出这样的信息:OWIN是针对.NET平台的开放Web接口. 那Web接口是谁和 ...
- SUI分页组件和avalon搞定ajax无刷新分页
<div ms-controller="main"> <h2 class="pagination-centered">{{ title ...
- 使用POI替换word中的特定字符/文字改进版
package com.xfzx.test.POI.main; import java.io.File; import java.io.FileInputStream; import java.io. ...