驻留exe文件
   作者:马文晓 于2003-11-28上传 

运行重定位exe文件,重定位因子调度覆盖模块,链接器3制静态覆盖块,析栈初始startup

(1) X86汇编语言的组,段,类

伪指令group/segment,定义组/段,这使:

(1.1) 同组的各段,处于同一64k空间

(1.2) 段可声明'对齐,组合,类'可选属性:

(1.2.1) 段首,能对齐到BYTE,WORD,PARA(16字节),PAGE(256字节),缺省对齐到PARA.
(1.2.2) 不同源文的同名同类段,想靠链接,形成空间邻接或重叠的同名组合逻辑
段时,需用PUBLIC,COMMON,指明组合形式:

(1.2.2.1) PUBLIC,指明此段按对齐属性,邻在已包含在此组合逻辑段内的最末字面段之后
(1.2.2.2) COMMON,指明此段与同属此组合逻辑段的其他字面段,从组合逻辑段首铺展

同属组合逻辑段的各参与段的最大对齐属性(例如,WORD比BYTE大),决定组合逻辑段的对齐属性.

不与其它段组合的段,称为单逻辑段.

以下,用"逻辑段",统称组合逻辑段及单逻辑段.

(1.3) 类用'cls'指明,未指明类的各段,属'匿名'类.同类各段,邻接排列.

(2) 解释各逻辑段空间关系的3个源文

a1.asm,声明1个组,4个段,涉及'匿名','dec_'类

(2.1) grp组,含seg1段
(2.2) seg1段
(2.3) ovlap段,用COMMON参与组合,'dec_'类
(2.4) touch段,用PUBLIC参与组合
(2.5) stk1段,用STACK指明栈段及PARA对齐

grp  GROUP seg1

seg1 SEGMENT PAGE
str1 DB "S1$"
seg1 ENDS

ovlap SEGMENT PARA COMMON 'dec_'
str3 DB '11$'
ovlap ENDS

touch SEGMENT PUBLIC
  ASSUME cs:touch,ds:NOTHING

  ORG 11H

@:  mov ah,9

        mov bx,grp    ;取grp段值
        mov ds,bx

        mov dx,OFFSET str1  ;取str1相对seg1偏置
  int 21h

        EXTRN str2:far

        mov dx,OFFSET grp:str2 ;取str2相对grp偏置
        int 21h

  ASSUME ds:ovlap

        mov bx,SEG str3   ;取str3段值
        mov ds,bx
        lea dx,str3    ;取str3相对ds偏置
        int 21h

touch ENDS

stk1    SEGMENT PAGE STACK
  DW 16H DUP (4AH)  ;初值4AH的16H个字
stk1 ENDS

  END @

a2.asm,声明1个组,3个段,涉及'匿名','dec_','_stk'类:

(2.6) ovlap段,用COMMON参与组合,'dec_'类
(2.7) stk2段,用STACK指明栈段及PARA对齐,'_stk'类
(2.8) grp组,含seg3段
(2.9) seg3段,'dec_'类

ovlap SEGMENT COMMON 'dec_'
  DB '2'
ovlap ENDS

stk2 SEGMENT WORD STACK '_stk'
        DW 13H DUP (2BH)  ;初值2BH的13H个字
stk2 ENDS

grp  GROUP seg3

seg3 SEGMENT 'dec_'
  PUBLIC str3
str3 DB '33$'
seg3 ENDS

  END

a3.asm,声明1个组,4个段,涉及'匿名','_stk'类:

(2.10) grp组,含seg2段
(2.11) touch段,用PUBLIC参与组合
(2.12) seg2段
(2.13) stk2段,用STACK指明栈段及PARA对齐,'_stk'类
(2.14) seg1段

grp  GROUP seg2

touch SEGMENT BYTE PUBLIC

        EXTRN str3:far
        ASSUME cs:touch,ds:NOTHING

@1:  mov cx,SEG grp    ;取grp段值
  mov ds,cx

  mov dx,OFFSET grp:str3  ;取str3相对grp偏置
  int 21h

  mov cx,seg1     ;取seg1段值
  mov ds,cx

  mov dx,OFFSET seg1:str4  ;取str4相对seg1偏置
  int 21h

  mov ah,4ch     ;程序终止
  int 21h

touch ENDS

seg2 SEGMENT BYTE
  PUBLIC str2
str2 DB "S2$"
seg2 ENDS

stk2 SEGMENT WORD STACK '_stk'
        DW 31H DUP (0B2H)  ;初值B2H的31H个字
stk2 ENDS

seg1 SEGMENT BYTE
str4 DB "%$"
seg1 ENDS

        END @1

用masm5.exe(版5.10B,囿95 DDK),生成同名obj及lst文件.例如,汇编a1,命令行是:
masm5 a1,,a1;

用含覆盖管理器的link4.exe(版5.01.17,同囿DDK),依序链接obj,生成a.exe程序文件,a.map映像文件,命令行是:link4 a1+a2+a3,a,a,,,

a.exe,显'S1S22133%'

a.map含:

Start  Stop   Length Name                   Class
00000H 00002H 00003H SEG1                  
00010H 00055H 00046H TOUCH                  
00060H 0008BH 0002CH STK1                  
0008CH 0008EH 00003H SEG2                  
0008FH 00090H 00002H SEG1                  
000A0H 000A2H 00003H OVLAP                  DEC_
000B0H 000B2H 00003H SEG3                   DEC_
000C0H 00147H 00088H STK2                   _STK

Origin   Group
0000:0   GRP

entry point at 0001:0011

(3) 逻辑段排列准则:按链接次序,按字面段的源文次序,按类名大写ASCII次序

因此,a.exe含8个逻辑段(段名被大写):

(3.1) '匿名'类的a1的单逻辑段SEG1
(3.2) '匿名'类的a1与a3的组合逻辑段TOUCH,参与的a1及a3中touch字面段,代码相接.
(3.3) '匿名'类的a1的单逻辑段STK1
(3.4) '匿名'类的a3的单逻辑段SEG2
(3.5) '匿名'类的a3的单逻辑段SEG1
(3.6) 'DEC_'类的a1与a2的组合逻辑段OVLAP,参与的a2及a3中ovlap字面段,使str3变为'21$'
(3.7) 'DEC_'类的a2的单逻辑段SEG3
(3.8) '_STK'类的a2与a3的组合逻辑段STK2,参与的a2及a3中stk2字面段,按PUBLIC,两空间邻接

(4) 链接时,程序cs:ip及ss:sp设定

用'END expression'形式指明的各伪指令,最先者,expression所在段,及expression相对所在段的偏置,是程序的cs:ip.无此伪指令,cs,ip是0.

例如,a1的'END @'先于a3的'END @1',因此,程序a.exe入口,cs=1,是@所在段相对exe文件装入模块首的节数,ip=11,是@相对所在段的偏置.

用组合属性STACK指明的各逻辑段,map文件最末者,是栈段;无此逻辑段,map文件最先逻辑段,是栈段.

例如,指明STACK的组合逻辑段STK2,后于单逻辑段STK1,因此,程序a.exe栈区,ss=STK2相对exe文件装入模块首的节数0ch,sp=88h,是STK2尺寸.

(5) 汇编时的机器码暂定

能/不能被链接解决的作为操作数的段值/段偏置,其指令后面,带后缀R被暂定;能被链接解决的外部定义,其指令后面,带后缀E被暂定.因此,

a1.lst含:

0013  BB ---- R          mov bx,grp
0018  BA 0000 R          mov dx,OFFSET str1
001D  BA 0000 E          mov dx,OFFSET grp:str2
0022  BB ---- R          mov bx,SEG str3
0027  8D 16 0000 R          lea dx,str3

a3.lst含:

0000  B9 ---- R  @1:  mov cx,SEG grp
0005  BA 0000 E    mov dx,OFFSET grp:str3
000A  B9 ---- R    mov cx,seg1  
000F  BA 0000 R    mov dx,OFFSET seg1:str4

(6) 链接时的机器码暂定,及运行重定位exe文件

作为操作数的段值,相对装入模块首,被暂定.外壳command.com启动命令行上的程序时,先开辟被节号x,节长1的存储控制块(MCB)标识的空闲内存,此MCB前3个域值为:字节值5A(内存块链尾),字0(无效进程PSP),字y(x+1+y=A000段,即640K内存尾),然后在x+1段首,先建10节程序段前缀(PSP),后铺展程序的装入模块.

这时,exe装入头的cs/ss值,与x+11h(叫"装入段值")相加,形成运行cs/ss值,此为"运行重定位exe".装入模块中,链接时的各机器码暂定,与外壳指定的"重定位因子"相加,形成运行逻辑段值.

一般地,重定位因子=装入段值.也可用功能4b03的装入段值/重定位因子两参数,分别指定,如(8)所述.

因此,改a1.exe为a,做debug a,用U 321,反出代码区:

0321 B409          MOV AH,09                              
0323 BB0000        MOV BX,0000  ;取grp段值                            
0326 8EDB          MOV DS,BX                              
0328 BA0000        MOV DX,0000  ;取str1相对seg1偏置
032B CD21          INT 21                                
032D BA8C00        MOV DX,008C  ;取str2相对grp偏置
0330 CD21          INT 21                                
0332 BB0A00        MOV BX,000A  ;取str3段值
0335 8EDB          MOV DS,BX                              
0337 8D160000      LEA DX,[0000]  ;ASSUME ds:ovlap时,取str3相对ds偏置                          
033B CD21          INT 21                                
033D B90000        MOV CX,0000  ;取grp段值                            
0340 8ED9          MOV DS,CX                              
0342 BAB000        MOV DX,00B0  ;取str3相对grp偏置                            
0345 CD21          INT 21                                
0347 B90800        MOV CX,0008  ;取seg1段值                            
034A 8ED9          MOV DS,CX                              
034C BA0F00        MOV DX,000F  ;取str4相对seg1偏置                            
034F CD21          INT 21                                
0351 B44C          MOV AH,4C                              
0353 CD21          INT 21                                

(7) MS-DOS的exe文件两部构成:

前部,含装入头(1ch字节长)及重定位表;后部,是装入模块.

链接时的各机器码暂定,相对装入模块首的偏置:节,用2个字w_o,w_p,存于重定位表.

(7.1) 装入头的字节偏置:

0,1  : 4d(M),5a(Z)标识
2,3  : exe文件净长度,除以512后的余数
4,5  : exe文件净长度,被512量的值
6,7  : 重定位表的项数
8,9  : 前部占用节数
a,b  : 装入模块之后,所需最小节数
c,d  : 装入模块之后,所需最大节数
e,f  : ss相对装入模块首的节数
10,11 : sp
12,13 : exe文件的字检查和
14,15 : ip
16,17 : cs相对装入模块首的节数
18,19 : 重定位表相对exe文件的字节偏置
1a,1b : 覆盖号

a,长840字节(348h),做debug a,用D 100 L2e,显出exe文件头的前2Eh个字节:

0100  4D 5A 48 01 02 00 04 00-20 00 00 00 FF FF 0C 00
0110  88 00 D3 A2 11 00 01 00-1E 00 00 00 01 00 14 00
0120  01 00 23 00 01 00 2E 00-01 00 38 00 01 00

(7.2) 解释装入头:

(7.2.1) a的长度,除以512后,余148H,放2,3字节
(7.2.2) a的长度,被512量,占2,放4,5字节
(7.2.3) 重定位表,含4项
(7.2.4) exe文件前部,占20H节
(7.2.5) 重定位表相对a,字节偏置是1eh

(7.3) 相对a,字节偏置是1eh的重定位表解释:

第1项,w_o=14h,w_p=1,指出此机器码暂定,相对装入模块首,是1节14h偏置,即1*10h+14h=24h(字节).

debug从100h装a,向高20h节(200h字节),是装入模块首,(100h+200h)加上24h的324h处,恰为MOV BX,0000中的段值位置.

第2项,w_o=23,w_p=1,相对装入模块首,是1节23h偏置,即1*10h+23h=33h(字节).300h加上33h的333h处,恰为MOV BX,000A中的段值位置.

同理,第3,4项,是MOV CX,0000及MOV CX,0008中的段值位置.

(8) 重定位因子调度覆盖模块

功能4b03,铺展exe装入模块.前部略去PSP的装入模块,称覆盖模块.

调用覆盖模块cs:ip入口的常驻者(如ovlayer.exe),要做:

(8.1) 将覆盖模块所在exe装入头中的程序cs:ip,读到entry
(8.2) 调用功能4a,释放其尾后内存
(8.3) 填写4b03的装入段值/重定位因子两参数
(8.4) 调用功能4b03,它在空闲内存MCB之高1节,铺展覆盖模块
(8.5) 用call entry语句,调用覆盖模块cs:ip入口

覆盖模块(如ovlayee.exe)工作完时,用retf,返回控制到常驻者.

覆盖模块被功能403铺展时,与(6)同样处理,作为mov ax,work操作数的段值work,与重
定位因子相加,这使ovlayee.exe,被ovlayer.exe调度,显示某区/某串值,例如,先显
o1区串s1值'os1',再显o2区串s2值'os2'.

(8.6) ovlayee文
要点: 只含1个逻辑段work,故work的机器码暂定,相对装入模块首,必为0,与重
定位因子的相加结果,恰=重定位因子.

要点: 不需写"END @"伪指令,让cs/ip为0,而ss/sp,借用ovlayer.exe栈区.

work segment
assume cs:work

push ds
mov ax,work
mov ds,ax

mov ah,9  ;显串
int 21h  ;dx被ovlayer指定
pop ds

retf   ;远返回

work ends
END

(8.7) ovlayer文
要点: 引入PARA对齐的tail空段,使长度可变的ovlayer.exe,从PSP首到tail,作为新占用内存,tail之上,是空闲内存MCB.
要点: mov bx,tail语句之后,勿写标号,否则masm5报错.

o1 segment
s1 db 'os1$'
o1 ends

o2 segment
s2 db 'os2$'
o2 ends

arg segment
loadseg dw      2       ;装入段值
factor  dw      2       ;重定位因子
entry   dd      4
filenam db 'ovlayee.exe',0
header  db      1ch     dup(1)
arg ends

stk     segment STACK
        dw      16      dup(9)
stk     ends

root    segment
        assume  cs:root,ds:NOTHING

@: mov ax,arg
mov ds,ax

mov ax,3d00h ;读打开
        mov     dx,offset arg:filenam
int 21h
        jnc     @F         ;@F,@B,向前/后找最近的@@标号

        mov     ah,4ch
        int     21h

@@:     mov     bx,ax           ;句柄送bx

        mov ah,3fh  ;读装入头到header
        mov cx,1ch
        mov     dx,offset arg:header
        int 21h

mov ah,3eh  ;关闭
int 21h

mov     bx,tail       ;tail指向映像尾节
mov di,es
        sub     bx,di           ;bx是此程序新占用内存的节数

        mov ah,4ah  ;新占用内存,始于es所指PSP节,长bx节
        int 21h

add bx,di
       inc bx

        mov     ds:loadseg,bx ;装入段值=MCB之高1节

mov ax,seg s1 ;重定位因子=s1所在段
        mov     ds:factor,ax

        mov  ax,word ptr ds:[header+14h] ;取覆盖模块ip
        mov     word ptr ds:[entry],ax

        add bx,word ptr ds:[header+16h] ;覆盖模块的cs,加上MCB之高1节
        mov     word ptr ds:[entry+2],bx

mov ax,ds
mov es,ax

        mov ax,4b03h
        mov     bx,offset arg:loadseg      ;es:bx指参数块
        mov     dx,offset arg:filenam      ;ds:dx指程序名
        int 21h

        mov     dx,offset o1:s1    ;dx是s1相对o1的偏置
        call    ds:entry

mov ax,seg s2
        mov     ds:factor,ax       ;重定位因子=s2所在段

        mov ax,4b03h
        mov     bx,offset arg:loadseg      ;es:bx指参数块
        mov     dx,offset arg:filenam      ;ds:dx指程序名
        int 21h

        mov     dx,offset o2:s2    ;dx是s2相对o2的偏置
        call    ds:entry

        mov     ah,4ch
        int     21h

root ends

tail    segment   ;空段
tail ends

        END @

(9) 链接器3制静态覆盖块

link4的obj参数,囿于括号时,生成OVERLAY_DATA,OVERLAY_AREA,OVERLAY_END块,使DOS的exe长达16M.

用4个源文阐述:

(9.1) root文
要点: 入口@需处于装入模块首
要点: extrn的near/far,声明近/远标号

c_root   segment
        assume  cs:c_root

        extrn   ov_near:near,ov_far:far,ov_far1:far

@:      call    ov_near         ;近调用
        call    ov_far          ;远调用      
        jmp     ov_far1         ;远转移
c_root   ends

stk   segment   STACK
        dw      16      dup(1)
stk   ends

        END     @    

(9.2) ov_near文
要点: ret是近返回

c_near   segment
        assume  cs:c_near

        public  ov_near

ov_near:mov     ah,2
        mov     dl,'N'
        int     21h

        ret   ;近返回

c_near   ends
        END

(9.3) ov_far文
要点: retf是远返回

c_far   segment
        assume  cs:c_far

        public  ov_far

ov_far: mov     ah,2
        mov     dl,'F'
        int     21h
        retf   ;远返回

c_far   ends
        END

(9.4) ov_far1文:
c_far1  segment
        assume  cs:c_far1

        public  ov_far1

ov_far1:mov     ah,2
        mov     dl,'f'
        int     21h

     mov   ah,4ch
   int   21h

c_far1  ends
        END

用masm5,生成扩展名为obj的root,ov_near,ov_far,ov_far1

用link4 root (ov_near ov_far) (ov_far1),root3,3,,,
生成root3.exe,3.map

exe文件,未囿于括号的obj的各逻辑段,及不属于'CODE'类的代码,是常驻部分,计为0号覆盖块.括号组,生成1号,2号覆盖块.

root3.exe,显'NFf'

改root3.exe为root3,做debug root3,用U 300,反出代码区:

0300 E82D00        CALL   0330                              
0303 9A40000000    CALL   0000:0040                          
0308 EA50000000    JMP   0000:0050                          

3.map含:

Start  Stop   Length Name                   Class
Resident
00000H 0000CH 0000DH C_ROOT                
00010H 0002FH 00020H STK                    
00030H 00036H 00007H C_NEAR                
00040H 00046H 00007H C_FAR                  
00050H 00059H 0000AH C_FAR1                
00060H 00082H 00023H OVERLAY_DATA           DATA
00090H 00090H 00000H OVERLAY_AREA           CODE
00090H 00090H 00000H OVERLAY_END            CODE
Overlay 1H
Overlay 2H

Origin   Group
0006:0   DGROUP

改root3.exe为root3,做debug root3,用D 100 L2A,显出exe文件头的前2Ah个字节:

0100  4D 5A 83 00 02 00 03 00-20 00 01 00 FF FF 01 00
0110  20 00 79 52 00 00 00 00-1E 00 00 00 01 00 04 00
0120  06 00 06 00 00 00 0B 00-00 00

root3的重定位表,含3项:

第1项,w_o=4,w_p=6,指出OVERLAY_DATA块的字节偏置4,5
第2项,w_o=6,w_p=0,指出CALL 0000:0040中的段值位置.
第3项,w_o=b,w_p=0,指出JMP 0000:0050中的段值位置.

用D 360 L23,显出OVERLAY_DATA块:

0360  01 00 03 00 00 00 00 00-00 00 00 00 00 00 00 00
0370  00 00 00 52 4F 4F 54 33-2E 45 58 45 00 00 00 00
0380  00 00 3F

笔者探出,对OVERLAY_DATA块,字节偏置2,3,是覆盖块数3,偏置4,5,是cs相对装入模块首的节数.

(10) 析栈初始

ML611,汇编map含

00000H , 00000H _TEXT                  CODE
00000H , 00019H _DATA                  DATA
00020H , 00020H STACK                  STACK
00040H , 00033H TAIL                  

Origin   Group
0000:0   DGROUP

entry point at 0004:0000

的t.asm

.model small   ;仅数据段,代码段

.data   ;随psp

assume  cs:@data

cry     dw   '$!'

i72:    push ax
   push dx
   push ds

   mov ah,9

        mov dx,cs
   mov ds,dx

        lea dx,cry
        int 21h

   pop ds
   pop dx
   pop ax
        iret

i27   dw   0,@data

.stack   32

tail   segment

.startup

        mov ax,2572h
   lea dx,i72
        int 21h

   mov dx,256+i27   ;计psp的驻容

   mov byte ptr es:[1],27h   ;es矢psp

   sub i27[2],16   ;矢psp
   call dword ptr i27   ;转psp:0

tail   ends
        end

lst含

0015 0000 ---- R      i27   dw   0,@data

0000            tail   segment

            .startup

0017   mov ax,2572h
002F   call dword ptr i27

exe文件头

0100  4D 5A 73 00 02 00 02 00-20 00 00 00 FF FF 02 00
0110  20 00 00 00 00 00 04 00-1E 00 00 00 01 00 17 00
0120  00 00 01 00 04 00

重定位表,第1项,矢i27的@data,第2项,矢17字节长的.startup第1字节

00 BA0000        MOV   DX,0000   ;运行时,字节1,2,加mcb所占节号及11h,制DS
03 8EDA          MOV   DS,DX
05 8CD3          MOV   BX,SS   ;外壳,已加好mcb所占节号及13h到SS
07 2BDA          SUB   BX,DX   ;13h-11h,差值2节,合20h字节
09 D1E3          SHL   BX,1
0B D1E3          SHL   BX,1                              
0D D1E3          SHL   BX,1                              
0F D1E3          SHL   BX,1                              
11 FA            CLI      ;防来中断                                12 8ED2          MOV   SS,DX   ;                              
14 03E3          ADD   SP,BX   ;本模式,移栈段环境到数据段高端
16 FB            STI                                     

(10.1) DEBUG t.exe实例

栈初始前,DS矢psp,SS已加好,SP=exe头偏移10,11字节值20h:

AX=0000  BX=0000  CX=0073  DX=0000  SP=0020  BP=0000  SI=0000  DI=0000  
DS=188B  ES=188B  SS=189D  CS=189F  IP=0000

初始后,DS,SS矢@data,SP多加栈容20h字节:

AX=0000  BX=0020  CX=0073  DX=189B  SP=0040  BP=0000  SI=0000  DI=0000  
DS=189B  ES=188B  SS=189B  CS=189F  IP=0017



欢迎访问AoGo汇编小站:http://www.aogosoft.com