一个连接器的实现:「二」

Elf的文件格式

我实现的简单C链接器,MiniLinker下载:

git clone git@github.com:youzhonghui/MiniLinker.git

在windows或者在linux上的可执行文件,除了记录了程序运行需要的指令和数据以外,还需要提供一些载入的信息。比如文件中那个部分是指令,那个部分是数据等等。windows下的可执行文件是PE格式,linux下的则是Elf格式。

Elf格式在文件上是这个样子的:

对于Elf文件格式的解析,在《程序员的自我修养》里写的很清楚了,我就不再重复。记录一些对写链接器必要而书中没有涉及的东西。当然,没事闲着研究链接器的人也是少数,所以资料不好找,大多数是靠自己的实验得出的。

猜想与证明

链接器的最终目标是生成可执行文件,让我们想一想这个可执行文件的组成。

  1. Elf文件头
  2. 节表
  3. 段表
  4. 已经重新定位好的指令和数据

我们再猜想,转载Elf可执行文件的过程是什么。

  1. 读取文件头,进而得到表头,程序头
  2. 根据程序头的信息,将对应的文件片段载入到对应的内存地址
  3. 根据文件头中记录的入口地址,将ip指针指向它,开始运行程序

细节[1]

从以上的猜想可以看出,转载过程起作用的是程序头。那么节表是不是可有可无呢?

文件头里有一个e_shnum元素,记录文件中的节表数量。用二进制修改软件(例如ghex),把这个值修改为0,再运行程序,一切正常!

说明,在最后的可执行文件中,节表是可有可无的。那么,我们就不需要为生成节表设计算法了

还有两个重要的需要注意的细节,都是在我实现了一个链接器原型,发现链接出来的东西运行的时候,要不是

已杀死

就是

段错误

细节[2]

对于已杀死的情况,用edb都无法加载。说明在载入初期就出现错误,甚至是没有载入,那么应该就是文件头或者段表出了问题。

将ld链接出来的正常程序与我的细细对比,发现要说什么差异,那么最大的可能就是我程序头的第一个offset并不是从0x0开始的。

如果真是这样,理论上,我用ghex修改了程序表的第一个offset之后,应该能够装载(当然不可能正确运行)

试了以后,edb果然装载了。

所以,这是第二个很重要的细节:程序头的第一条记录的offset应该是从0开始的

细节[3]

解决了一进来就已杀死的问题,还有段错误

用edb运行,会在最后的几条语句,因为违法的地址访问(一个解决于0的地址),而弹出段错误。

当时这个错误郁闷了很久,因为这个现象可能是我的链接器算法写错误,但是我又找不到bug。

使用《程序员的自我修养》中 最小程序 的那个例子来调试,猛然醒悟。

这个例子中不但用int 0x80写了一个print,还写了一个exit

也就是程序在执行main的ret是不能正常结束的。必须要有一个系统调用来终止程序。

把这个exit函数拷贝出来,链接上,正常了。这也就是为什么要_start在main之前运行的原因之一,之前竟然没有注意到。

第三个细节,程序需要系统调用来终止

大胆猜想,小心求证

posted @ 2013-08-06 11:13  南树  阅读(740)  评论(1编辑  收藏  举报