自制x86玩具操作系统 week8

2019-05-01 (更新于2021-10-10) / OSDev / #操作系统 #DIY

DAY 0x17

关于链接器obj2bim

翻看源码发现,obj2bim其实就是个链接器,依据一个.rul规则文件来生成链接后的二进制文件。

输入文件

它接受的输入文件有obj和lib这两种文件,其中lib文件也有两种,一种是标准的pe文件,有一种是作者自定义的格式,在链接前会decode成标准的lib文件,然后从lib文件解压出obj文件来进行处理,所以这个链接器主要进行的是对obj文件的处理。 例如haribote / golibc.lib就是作者自定义的一种格式的文件,它原本是一个标准的win下的lib文件,编写工具将其转换出来后发现里头并没有什么有意思的东西,只是几个字符串相关的处理函数的实现。

处理obj

处理过程在loadobj函数中,主要是用一个全局的数组存储所有的obj文件对象。

typedef struct _IMAGE_RELOCATION {
	union {
		DWORD  VirtualAddress;
		DWORD  RelocCount;
	} M;
	DWORD  SymbolTableIndex;
	WORD Type;
} IMAGE_RELOCATION;

其中VirtualAddress指向的是需要被重定位的位置相对于当前段的偏移量。 每个重定位表项有一个指向符号表的索引号,这部分操作总的来说就是把重定位表里面的项解析成程序中的结构,最后把重定位表的第一个元素的指针以及重定位表项的数目记录到obj中的对应的段中

typedef struct _IMAGE_SYMBOL {
	union {
		BYTE    ShortName[8];
		struct {
			DWORD   Short;     // if 0, use LongName
			DWORD   Long;      // offset into string table
		} Name;
		DWORD   LongName[2];  //two byte potioner
	} N;
	DWORD   Value;
	SHORT   SectionNumber;
	WORD    Type;
	BYTE    StorageClass;
	BYTE    NumberOfAuxSymbols;
} IMAGE_SYMBOL;
开始链接
输出

最后输出作者规定的一种格式的文件,将代码段和数据段的信息(大小,文件中的位置,逻辑地址)写到文件起始位置。 接下来是是写入了rul文件中指定的那个label符号表示的值(在文件中偏移量为24的位置)。例如rul中label项写的是_HariStartup,那么就是把_HariStartup符号的入口地址写到这了。 然后是在指定的位置写入了代码段和数据段的内容,至此链接过程完成。

一些思考
0000047a <_HariMain>:
 ...
 480:   81 ec dc 00 00 00       sub    $0xdc,%esp
 486:   e8 00 00 00 00          call   48b <_HariMain+0x11>	; 这里
 48b:   e8 00 00 00 00          call   490 <_HariMain+0x16>
 490:   e8 00 00 00 00          call   495 <_HariMain+0x1b>
 495:   e8 00 00 00 00          call   49a <_HariMain+0x20>
 49a:   c7 44 24 04 f8 00 00    movl   $0xf8,0x4(%esp)
 ...

注意0x486那里第一个call汇编应该是

call    __Z7init_dtv    ; init_dt(void)`

init_dt(void)应该是dsctbl.obj中的导出函数,

再看解析出来的重定位表的一部分

                                                Symbol    Symbol
 Offset    Type              Applied To         Index     Name
 --------  ----------------  -----------------  --------  ------
 ...
 00000487  REL32                      00000000        35  __Z7init_dtv
 ...

表中__Z7init_dtv这个符号的Offset00000487,这是什么意思呢? 注意看上面汇编中,0x486的那条call指令,长度为5个字节,后面从0x487开始的四个字节都是十六进制的00,我们可以大胆的猜测,这应该是留空给链接器填入链接后的地址的。

事实上查阅i386汇编手册就可以知道,这是call指令的一种五字节长的版本,在0xE8后面接四字节的相对地址构成一条call指令,这对链接器来说就很方便了,不知道对于其它架构的机器码链接器是怎么做链接的。

也就是说,最后我们把__Z7init_dtv的地址填到这里就能call了吧?其实不是这样的啦,call需要的是相对地址,所以链接器得先根据重定位表中该项的type进行个判断,把符号的值减去(当前指令的地址+4)的值写入到这个call语句中,所以说,链接时的重定位是要修改.text段的。

同理,

mov     dword ptr ds:__ZN3sys8memtotalE, eax ; sys::memtotal

对应的是

4e4:   a3 00 00 00 00          mov    %eax,0x0

这里指令中也有四字节的空间是留出来让链接器时重定位__ZN3sys8memtotalE这个外部引用用的

关于bim2hrb

bim2hrb对比obj2bim就简单的多了,只是把bim的前面若干个字节进行了一些变动,加上了堆的空间的指定,然后调整了一下开头处存储的地址和大小什么的数据,还在前面加上了作者自己的魔术字Hari

参考

异常中断

x86中,从0x00到0x1f都是异常所使用的中断,IRQ的中断号都是从0x20之后开始的。 0x00 除零异常(当试图除以0时产生) 0x0c 栈异常 0x0d 非法内存访问 0x06 非法指令异常(当试图执行CPU无法理解的机器语言指令, 例如当试图执行一段数据时,有可能会产生)