2005年02月17日

    
    前言

    很早就想写些东西了,这想法即使到了现在,看起来依然还是那么的莫名其妙。虽然想了好久,但是觉得也不能写出什么来,所以一直很踌躇,不过最终还是决定写了,也写了不少。写下这些文字虽然没有什么大的缘由,但是既然写了,就总该有个名分了,至少可以为自己过去的二十余年做个见证吧?不是有人说过了么?以历史做镜子,会怎么怎么样的吗?所以我就把自己的历史(姑且这么叫吧)写下来,尽管不知道会怎么样,但是把它写下来,寻找所有的处事得失,作为以后自己人生路上的调整准则,这样看来觉得还是有些价值的,至少不会重复昔日的错误吧?想着想着心里也舒适了很多,万一写出的全是垃圾也不会难堪,更不会后悔了。至于起个这样的名字只是因为近期忽然觉悟了,觉得这些年来我实在真的是堕落了许多,似乎再也找不到以前的影子了,就当是给自己敲个警钟吧。嗯,键盘不大好使,有点麻烦。

第一节 在沉沦的火车上

    2000年8月29日,晴,没有风,很热。黄历上写着:宜出行,却没有说宜不宜栽种。
    火车站。伴随着最后一声长啸,536次列车开始了它通向北京的又一次同样的旅程,唯一不同的是,这次旅行,有我参加。第一次坐火车,实在有些不习惯,在座位上扭来扭去,觉得时间实在是太慢了,不禁想起了以前在菜园里抓到的蜗牛。
   “想什么呢?”在我出神想蜗牛的时候,刚醒的表哥突然问我。
   “没什么”,我有些慌乱,没敢说是想蜗牛,“嗯,二哥,到了北京,然后再怎么走啊?”我赶紧岔开话题。“哎,到时候再说吧,应该还是坐火车吧,晚了,睡觉吧”。表哥说完就换了个方向,继续趴在小桌子上。
   “哦”,我应了一声。不怎么困,睡不着,其实关键在于没有地方可以睡觉,不知道他是怎么能睡着的。无聊中,突然想起上车后不久表哥和对面的人的交谈。
    …….
   “天大?”,看他很疑惑,我赶紧插进来一句,“嗯,天津大学。”
   “哦,是天津理工大学吧?简称天大,我很熟”那人恍然大悟的说,“我在那儿干活,今年是第四年去了,嗯,学校不错啊”。表哥也跟着打着哈哈,然后开始了热烈的攀谈。
    顿时觉得车厢内更热了,我便没有再插话。其实也插不进去。
    有东西想更是睡不着。于是就很无趣的看着窗外。窗外的景色逐渐不在熟悉,甚至慢慢荒凉起来,不禁勾画起天津的模样来,这样想着,也慢慢进入了梦乡。

第二节 首都北京

    睡眼朦胧中忽然被人拉了一把,费劲的睁开眼,是二哥。“准备下车了,到了”,二哥看来也是刚醒,眼角还有昨夜留下的痕迹。赶紧洗洗脸,匆匆下车。
    北京西站。出站都走了好久,拖着个里面什么都有其实我也不知道里面究竟有什么的大包来来去去,累得快不行了。好不容易出了站,觉得一阵热浪袭来。好热!首都就是牛啊,连温度都不肯输给别的城市,火炉里没有北京实在是冤枉啊,不禁为首都鸣不平。这样想着,心里似乎也心安理得,也觉得没有热的那么夸张了。
    接着就是坐公交了,到北京站。说是空调车,说到底却是没有,只能把窗户打开一条缝来进行空气调节(还是空调),开的太大就是把自己献给太阳了。一路红灯,走走停停,停停走走,耗了将近两小时,才晃晃悠悠的到了北京站。虽然很累了,不过路过了天安门,心里顿时也舒适了很多,心想以后一定要来好好看看。
    肚子早就咕咕叫了。饭馆的首都人民就是热情,大老远的就开始吆喝着,简单的挑选了一下,我们走进了一家快餐店,吃完了一结帐,才知道接受热情也是要付出代价的。
    买票去天津。心里对天津的盼望不禁又多了几分。昨夜勾画的轮廓还在脑海中有些印象,于是趴在小桌子上继续勾画,二哥也继续和现在看起来和他很熟的陌生人聊天,各得其乐。
第三节 天津印象

    也不知道过了多久,据说是一个半小时后,我们准时到达天津。带着对天津的渴望,我以最快速度出了站,感觉比在西站轻松多了,一会就出去了,大概是心急吧。首先映入眼帘的就是天津大学新生接待处几个大字,一刹那心里顿时快崩溃了,似乎是见到阔别已久的亲人一般,马上就扑了上去,可惜没有人接受我火热的激情。接待的老师,学长简单而机械的问了学院,系别,就把我们和行李一股脑推上了车。车上大家都很激动的样子,东张西望,我也到处观摩。一路观光之后,到达东门(我43斋,怎么不北门?累死了,还差点迷路),兴致也减了不少,看来我昨天勾画的景象只是天津的未来蓝图。
    又是简单而机械的领钥匙,找宿舍,交费,完毕,马上回宿舍,什么也没有说,睡个大觉先。现在实在也想不起来当时遇见了谁,和谁说话了,反正就是睡醒了才有点反应,才知道原来还有很多事情要我去做。
    首先就是把宿舍的同志们认熟。

第四节 相约宿舍

DOS下可执行文件的加载过程

一    COM文件的加载

COM文件格式的历史可以追溯到CP/M时代,被沿用至今,所有的Windows系统都支持此格式。
COM文件包含程序的一个绝对映象――就是说,为了运行程序准确的处理器指令和内存中的数据,MS-DOS通过直接把该映象从文件拷贝到内存而加载COM程序,而不作任何改变。
加载过程如下:
1  分配内存
   因为COM程序必须位于一个64K的段中,所以COM文件的大小不能超过65,278(65,536减去用于PSP(程序段前缀)的256字节和用于一个起始堆栈的至少2字节)。如果MS-DOS不能为程序、一个PSP、一个起始堆栈分配足够内存,分配尝试失败。否则,MS-DOS分配尽可能多的内存(直至所有保留内存),即使COM程序本身不能大于64K。在试图运行另一个程序或分配另外的内存之前,大部分COM程序释放任何不需要的内存。
2  设置PSP
   分配内存后,MS-DOS在该内存的头256字节建立一个PSP,如果PSP中的第一个FCB含有一个有效驱动器标识符,则置AL为00h,否则为0FFh。MS-DOS还置AH为00h或0FFh,这依赖于第二个FCB是否含有一个有效驱动器标识符。
3  加载COM程序
   建造PSP后,MS-DOS在PSP后立即开始(偏移100h)加载COM文件
4  设置寄存器
   它置CS,SS,DS和ES为PSP的段地址,接着创建一个堆栈。为创建一个堆栈,MS-DOS置SP为0000h,若已分配了至少64K内存;否则,它置寄存器为比所分配的字节总数大2的值。最后,它把0000h推进栈(这是为了保证与在早期MS-DOS版本上设计的程序的兼容性)。MS-DOS通过把控制传递给偏移100h处的指令而启动程序。程序设计者必须保证COM文件的第一条指令是程序的入口点。注意,因为程序是在偏移100h处加载,因此所有代码和数据偏移也必须相对于100h。汇编语言程序设计者可通过置程序的初值为100h而保证这一点(例如通过在原程序的开始使用语句org 100h)。
由于COM文件没有任何标志,所以识别COM文件的时候有些难度。

二    EXE文件的加载
EXE文件不同于COM文件,由于它可以跨段执行,大大扩展了内存的使用。它有一个文件头,来提供文件有关信息。EXE文件是由文件头和加载模块(Loader Module)线性组成。
文件头格式如下:

00-01 e_magic; // 文件特征,为0×5A4D(MZ)或者0×4D5A(ZM)
02-03 e_cblp; // 文件最后页的字节数(为0说明这个块或者是页全被使用)
               //如果为4,应当视作0处理,这是由MS-Linker决定的。
04-05 e_cp;  // 文件页数(包含最后没有写满的页)每页512字节。
              //用于计算DOSEXE文件长度
             //File_length=(e_cp-1)×0×200+e_cblp,均为16进制
06-07 e_crlc; // 重定位项个数,很可能是0
08-09 e_cparhdr; // 文件头结构节数,对于DOSEXE来说,就是头结构大小,头     
                  //结构结束处就是文件开始加载的地方;
0A-0B  e_minalloc; // 所需的最小内存
0C-0D  e_maxalloc; // 所需的最大内存
0E-0F  e_ss; // 初始的相对SS值,用程序装入处的段地址加上此值来初始化SS
10-11  e_sp; // SP寄存器的初始值
12-13  e_csum; // 字校验和。一般不用,如果设置正确的话,文件所有字节相加为0
14-15  e_ip; // IP寄存器的初始值
16-17  e_cs; // CS寄存器的初始值
18-19  e_lfarlc; // 重定位表偏移量。程序加载的时候起始段地址加上它来重定位
1A-1B  e_ovno; // 可覆盖的数目,常为0,即不可覆盖

程序入口EP=初始IP+(初始CS+文件头结构的节数)×0×10 (均为16进制计算)

加载过程如下:
1  根据从头结构中计算所需的内存大小来申请内存
   OS将PSP大小+文件映象大小【文件头后面的部分】+【0C-0D  e_maxalloc所需的最大内存】后得到的值和可得到的内存相比较,满足则加载,且返回起始段地址,否则返回错误。
2  加载加载模块(Loader Module)到起始段地址。如果exMinAlloc域和exMaxAlloc域中的值都为零,则MS-DOS把映象尽可能地加载到内存最高端。

否则,它把映象加载到紧挨着PSP域之上
3  按照可重定位项数【06-07 e_crlc】和【18-19  e_lfarlc】来重定位。
   解释一下重定位。
   mov dx,ds
   当此语句加载到不同的段时,ds时不同的。所以当程序编制好后,linker程序将这样的位置记下来,也就是将此位置距离加载模块起始位置的偏移(PPP)存入可重定位表(4B的结构数组),并在e_lfarlc处设置一个指针来指向第一个可重定位项。执行的时候将起始段地址加上PPP即可找到要重定位的地方。
4  构造PSP
5  设置寄存器
   ds,es指向PSP首地址。
6  设置CS和IP来指向程序入口

摘录的

程序映象,包含处理器代码和程序的初始数据,紧接在文件头之后。它的大小以字节为单位,等于.EXE文件的大小减去文件头的大小,也等于exHeaderSize的域的值乘以16。MS-DOS通过把该映象直接从文件拷贝到内存加载.EXE程序然后调整定位表中说明的可重定位段地址。
定位表是一个重定位指针数组,每个指向程序映象中的可重定位段地址。文件头中的exRelocItems域说明了数组中指针的个数,exRelocTable域说明了分配表的起始文件偏移量。每个重定位指针由两个16位值组成:偏移量和段值。 为加载.EXE程序,MS-DOS首先读文件头以确定.EXE标志并计算程序映象的大小。然后它试图申请内存。首先,它计算程序映象文件的大小加上PSP的大小再加上EXEHEADER结构中的exMinAlloc域说明的内存大小这三者之和,如果总和超过最大可用内存块的大小。则MS-DOS停止加载程序并返回一个出错值。否则面,它计算程序映象的大小加上PSP的大小再加上EXEHEADER结构中exMaxAlloc域说明的内存大小之和,如果第二个总和小于最大可用内存块的大小,则MS-DOS 分配计算得到的内存量。否则,它分配最大可用内存块。分配完内存后,MS-DOS确定段地址,也称为起始段地址,MS-DOS从此处加载程序映象。如果exMinAlloc域和exMaxAlloc域中的值都为零,则MS-DOS把映象尽可能地加载到内存最高端。否则,它把映象加载到紧挨着PSP域之上。接下来,MS-DOS读取重定位表中的项目调整所有由可重定位指针说明的段地址。对于重定位表中的每个指针,MS-DOS寻找程序映象中相应的可重定位段地址,并把起始段地址加到它之上。一旦调整完毕,段地址便指向了内存中被加载程序的代码和数据段。 MS-DOS在所分配内存的最低部分建造256字节的PSP,把AL和AH设置为加载 .COM程序时所设置的值。MS-DOS使用文件头中的值设置SP与SS,调整SS初始值,把起始地址加到它之上。MS-DOS还把ES和DS设置为PSP的段地址.最后,MS-DOS从程序文件头读取CS和IP的初始值,把起始段地址加到CS之 上,把控制转移到位于调整后地址处的程序。

三    MCB结构

Flag(1BYTES)    所有者的PSP(2B)   内存块的节数(2B)   保留(3B)    所有者名称(8B)

如果是M(4D)      若为8表示                            
不是最后一块    为系统占有                            
若是Z(5A)
是最后一块                                            
                                                      


四    PSP结构

 偏移        大小(Byte)                 说 明
 
0000h        02      中断20H
 
0002h        02                 已分配内存的高端地址,用节计算,相当于 
                                段地址(利用这个可看出是否感染引导型病毒)
0004h        01   保留
 
0005h      05   至DOS的长调用
 
000Ah       02   INT 22H(终止地址,当前程序结束后应转向的程序地址)
     入口 IP
000Ch        02   INT 22H 入口 CS
 
000Eh        02   INT 23H(CTTL+C或者ctrl+break)入口 IP
 
0010h        02   INT 23H 入口 CS
 
0012h        02   INT 24H(严重错误处理程序) 入口 IP
 
0014h        02   INT 24H 入口 CS
 
0016h        02   父进程的PSP段值(可测知是否被跟踪)
 
0018h        14   存放20个SOFT号
 
002Ch        02   环境块段地址(从中可获知执行的程序名)
 
002Eh      04   存放用户栈地址指针
 
0032h      1E   保留
 
0050h        03     DOS调用(INT 21H / RETF)
 
0053h      02   保留
 
0055h      07       扩展的FCB头
 
005Ch      10   格式化的FCB1
 
006Ch      10    格式化的FCB2
 
007Ch        04   保留
 
0080h        80   命令行参数长度
 
0081h        127    命令行参数