前段时间看到有人讨论DOS EXE的加载过程,讨论其起始地址是如何确定的? 解决问题最好的办法就是实践:
写一段最简单的汇编
STSG SEGMENT STACK 'S'
DW 32 DUP(?)
STSG ENDS
DATA SEGMENT
A DW 123
B DW 456
SUM DW ?
DATA ENDS
CODE SEGMENT
MAIN PROC FAR
ASSUME CS:CODE, DS:DATA, SS:STSG, ES:NOTHING
PUSH DS
XOR AX, AX
PUSH AX
MOV AX, DATA
MOV DS, AX
MOV AX, A
ADD AX, B
MOV SUM, AX
RET
MAIN ENDP
CODE ENDS
END MAIN
使用MASM5编译
MASM test.asm
LINK test.obj
网上查知DOS文件头信息如下:
EXE文件头信息
―――――――――――――――――――
├ 偏移量 ┤ 意义 ┤
├00h-01h ┤MZ'EXE文件标记 ┤
├2h-03h ┤文件长度除512的余数 ┤
├04h-05h ┤...............商 ┤
├06h-07h ┤重定位项的个数 ┤
├08h-09h ┤文件头除16的商 ┤
├0ah-0bh ┤程序运行所需最小段数 ┤
├0ch-0dh ┤..............大.... ┤
├oeh-0fh ┤堆栈段的段值 (SS) ┤
├10h-11h ┤........sp ┤
├12h-13h ┤文件校验和 ┤
├14h-15h ┤IP ┤
├16h-17h ┤CS ┤
├18h-19h ┤............ ┤
├1ah-1bh ┤............ ┤
├1ch ┤............ ┤
使用UltraEdit打开test.exe, 16h - 17 h为05 00 也就是05:00
我们再拿Debug运行test.exe
debug test.exe
-r
可以看到
DS:1810 CS:1825 SS:1820 SP:0040
-u可以看到1825:0000正好是CODE段的第一条语句PUSH DS
静态的算一下:
CODE段前面有STSG 32个DW,共64个字节换成地址就是40H, DATA段共3个DW, 也就是6个字节, 但段地址至少也是1:0也就是10H(16)字节的整数倍,正好50H个字节,也就是5:0。正好就是CODE段的距离起始的偏移值
再动态的算一下, Dos载入EXE时,最前面是PSP头,此时DS和ES指向的就是PSP起始地址,而且PSP是固定大小256字节 = 100H = 10:0
后面就是EXE中的内容,首先是STSG段,应该是DS + 10 正好我们看到SS就是1820 = 1810(DS) + 10, SP为0040,也就是STSG段的大小(64个字节)。接下来就是DATA段,也就是说DATA段应该是1824:0,通过-u,我们可以看到
1825:0004 MOV AX, 1824 也就是源码中的MOV AX, DATA, DATA正是我们预期的1824, DATA段正如前面分析,不够10H个字节,但占10H,所以下一个段地址1825H就是CODE段了。
这里没解决为什么记录到EXE头里的就是CODE起始地址,
看源码有两处可能,一是ASSUME CS:CODE,而是END MAIN。这都是伪指令,究竟是哪条伪指令影响了assembler呢?很简单,把ASSUME注释掉,再试验前面的过程,发现没变化。把END MAIN 换成END,再试验,结果就不一样了。
EXE 投中 16 - 17h变成了00 00,debug -r 显示CS指向也错误了,和SS指向了一起(因为CS的指就来自程序首地址 + PSP大小 +头中的偏移值)