欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

改进并修复DEBUG中的七个问题:一份关于COMEXE实现优化报告

最编程 2024-07-22 16:38:40
...
 

首先声明,本文所指DEBUG,系DOS 6.22,win 98及2k三者DEBUG.EXE.

(1) DEBUG命令T及P的2处缺点

跟踪命令T,建立在8086标志寄存器第8位(自陷位)置1后,处理器执行完一条
被跟踪指令,就进单步中断1的基础上(进入时,被调试进程栈顶3个字,被无辜破坏).
DEBUG预先接管中断1,在那里,对被跟踪指令的执行完现场,先保存,后显示.

于是,DEBUG用以下5步,让欲跟踪的指令,在自陷位持有1的处理器环境下,间接执行:

(1.1) or    标志寄存器,100h
(1.2) push    标志寄存器
(1.3) push    欲跟踪的指令CS
(1.4) push    欲跟踪的指令IP
(1.5) iret

欲跟踪的指令是int 21h等中断指令时,遇到1个问题:

处理器执行此int指令时,先压标志寄存器F(自陷位已被DEBUG置为1)入栈,
然后清标志寄存器的自陷位及中断位,接着压int下条指令y的CS,IP入栈,
最后,查找位于0:0的中断矢量表,执行此中断号对应的中断入口指令x
(21h中断的入口IP,CS,在0:84h,0:86h字).而此时的自陷位刚被清为0,
于是,就不能跟踪x这条指令.

再次进入中断1的时刻,只能是此int例程通过iret指令返回到用户态时,
处理器从栈中弹出y的ip,cs及标志寄存器F,间接执行完y的那个时刻.

命令T能跟进int例程内部,靠以下模拟int进入过程的5步:

(1.6) 压标志寄存器F入用户态堆栈.
(1.7) 压y的CS,IP入用户态堆栈.
(1.8) 清标志寄存器F的中断位.
(1.9) 改欲跟踪的指令为x.
(1.10) 用(1.1)开始的5步,间接执行x.注意,在依此弹出x的IP,CS后,弹出的标志
       寄存器F,其中断位已被(1.8)清除,从而与int指令实际执行后的环境相同,
       x在中断位关闭的氛围中执行.

置断点命令G,建立在一执行机器码为0cch的INT 3指令,就进断点中断3的
基础上.DEBUG预先接管中断3,然后用0cch替换断点首字节,运行被调试进程,如果
运行路径遇到此断点,就会进中断3.在那里,命令G对被调试进程进入int 3之前的
现场,先保存,后显示,再恢复断点首字节.

继续命令P,能执行int指令而不跟进去,靠的是对int的下条指令y置断点.
这样,就把整个int例程视为1条指令,使int的下条指令y,停于中断3.

命令T及P有2处缺点:

(1.11) 它们的步数从1到ffff,对想无限次运行,直至被调试进程自身终止的用户,
       这是缺点1.

(1.12) 遇int或call指令,T进入而P不进入,对想跟踪自编的proc,而不想跟踪
       int例程的用户,要不断用T与P,手工解决进入问题,T与P自动运行多步
       的功能,只得弃用,这是缺点2.

(2) DEBUG对INTO执行命令P的1处缺点

F第11位(溢出位)为1时,执行机器码为0ceh的INTO,将进入int 4.例如,

mov al,7fh
inc al        ;al变为80h,置1溢出位
into

,不想进入中断4内部,执行P命令时,DEBUG却与命令T相同,进了内部,这是缺点3.

(3) DEBUG不认首字被5a4d标识,后缀改为他名的exe文件的1处缺点

exe文件,以头两个字节为4d(M),5a(Z)做标识,不管后缀名.本文所用的4b01装入功能,
就按此规则,解决exe文件的重定位.

但DEBUG却依后缀名解决重定位,这是缺点4.

刚进DEBUG的bx:cx值,体现装入文件长度,对exe文件,此值,少去exe头占用的200h.

(4) DEBUG与用户交互时的3处缺点

(4.1) 用户想输出DEBUG的运行结果到foo.1时,只能发:DEBUG foo.com>foo.1
但运行提示及用户的键盘echo,也被重定向,不可见,这是缺点5.

(4.2) 改标志寄存器的标志位时,需知各标志的两字母简名,如溢出位为1/0,
简名为OV/NV,对不知简名,而想改标志的用户,这是缺点6.

(4.3) 被调试进程用功能4ch/31h,使al带errorlevel值5a,正常/驻留终止时,
DEBUG显出Program terminated normally信息,但不反映errorlevel及终止类型
字节,用户这时只得手工汇编mov ah,4d,int 21,从al,ah取两值,这是缺点7.

(5) comexe改好这七处缺点的做法:

(5.1) 仿命令T及P运行时,comexe将用'val[0001~fffe,ffff]',询问步数,
回答ffff时,comexe将无限次运行,直至被调试进程自身终止,这改好缺点1.

(5.2) 遇操作码为0cdh的int,或0cch,或0ceh且F溢出位为1,comexe将用
'proceed,n(ever enter int):',问是否进入中断内部,答p,本次不进入,
答n,从此不进入,答其他,将进入;call指令,comexe都进入,这改好缺点2,3.

(5.3) comexe调用4b01功能,装入被调试进程,依返回的sp初值,决定是否exe文件,
这改好缺点4.

(5.4) comexe先保存输出句柄1到oldstd1,对提示及用户的键盘echo,
让stderr代替句柄1,从而可见;对运行结果,让oldstd1代替句柄1,输出
能到屏幕或文件,这改好缺点5.

(5.5) comexe能读,写任意段:位移上的字,被调试进程AX,BX,CX,DX,SP,BP,SI,DI,
DS,ES,SS,CS,IP,F这14个字,从低存到高,AX的段:位移,被AX(seg:off=0526:0190)
指明,F有效位C等,被F(????ODITSZ?A?P?C)指明,这改好缺点6.

(5.6) comexe仿DEBUG,能捕获被调试进程5类终止,并改好缺点7:

装入映象后,comexe使被调试进程psp偏移0ah处的终止IP及CS,指向comexe的i22,
被调试进程用int 20h,或int 21h的功能0,或功能4ch正常终止,或用int 27h,
int 21h的功能31h驻留终止,DOS都将终止IP及CS写入中断22h,然后执行int 22h,
于是,comexe在i22,可感知这些终止,并用4d功能,取终止信息,显出
exit_type/errlev=00/5a.

(7) comexe仿DEBUG,能捕获被调试进程CTRL-BREAK,CTRL-C中断的做法:

按ctl-break/ctl-c键时,键盘电路中断CPU,键盘中断服务程序,会将40:71字节
第7位,置为1/0,并在屏幕光标处,显示^C,最后,调用中断23h.

comexe让中断23h指向i23,被调试进程被ctl中断,而进入i23时,sp指向键盘中断
服务程序调用中断23h那条指令后面的ip,加6后,指向被调试程序中,能检ctl
中断的int指令后面的ip,如int后面的cmp:

mov ah,1    ;读键盘
int 21h
cmp al,'q'

comexe对这两键,分显为^b/^c.而DEBUG,不分显.

(8) comexe仿DEBUG能求指令操作码的助记符的做法:

占1到6个字节的8086的指令,分3部分:操作码字节[寻址方式字节][位移或数据字节].

第2部分的出现被第1部分决定.第3部分的出现被第1,2部分决定.

8086有6种操作数寻址方式:

(8.1) 寄存器对寄存器,如mov cx,bx,把bx的内容送入cx.

(8.2) 立即数寻址,如mov cx,10,把10送入cx(10值,随.RADIX变,写.RADIX 4,cx为4)

(8.3) 直接存储器寻址,如mov cx,wd1,把写为wd1 dw 0的wd1字内容,送入cx.

(8.4) 寄存器(bx,bp,si,di)间接寻址,如mov cx,[bx],把bx的内容,作为有效地址EA,
将EA处的字,送入cx.

(8.5) 寄存器+位移disp的间接寻址,如mov cx,[bx]+1234h,把bx的内容加上1234h,
作为EA,将EA处的字,送入cx.

(8.6) 基址寄存器(bx,bp)+变址寄存器(si,di)+位移的间接寻址,如mov cx,
[bx]+[si]+13h,把bx的内容,si的内容,13h,三者相加,作为EA,将EA处的字,送入cx.

有时,操作码字节的第0位w,用1/0,指明字/字节操作.

有时,操作码字节的第1位vds,在移位指令中,用1/0,指明cl次/1次移动;在含运算
结果的指令中,用1/0,指明结果存于reg域/有效地址EA;在含立即数加减的指令
中,用1指明加减字时(w为1),寻址方式字节的下个字节,将依符号位,展成1个运算字.

寻址方式字节,从高到低,含mod域(2位),reg域(3位),r/m域(3位).

mod域解释disp的形成:

mod为0,则disp为0,即无位移低字节及位移高字节.(r/m域为6时,EA仅被位移低字节,
及位移高字节构成)

mod为1,则disp为位移低字节,及此字节的符号扩展字节,无位移高字节.

mod为2,则disp为位移低字节,及位移高字节.

mod为3,则r/m域是寄存器域.

reg域依w的1/0,索引ax,cx,dx,bx,sp,bp,si,di及al,cl,dl,bl,ah,ch,dh,bh

r/m域解释16位有效地址(EA)的组成:
r/m=0,EA=[BX]+[SI]+disp
r/m=1,EA=[BX]+[DI]+disp
r/m=2,EA=[BP]+[SI]+disp
r/m=3,EA=[BP]+[DI]+disp
r/m=4,EA=[SI]+disp
r/m=5,EA=[DI]+disp
r/m=6,EA=[BP]+disp(mod域为0时,EA仅被位移低字节,及位移高字节构成)
r/m=7,EA=[BX]+disp

(8.7) 举10个例子,解释反汇编思想:

(8.7.1) 字节操作,运算结果存于EA:
机器码00c1,反出add cl,al,mod为3,reg为0,索引到al,结果存于1值r/m索引到的cl

(8.7.2) 字操作,运算结果存于reg:
机器码03ad3412,反出add bp,[di]+1234h,mod为2,reg为5,索引到bp,r/m为5,
EA就是[DI]+disp,先低后高存储的disp为1234h

(8.7.3) mod为0,使位移为0:
机器码f630(div的reg,恒为6),反出div byte ptr [bx]+[si]+0,这时,r/m为0,
EA就是[BX]+[SI]+disp

(8.7.4) mod为0,r/m为6,使EA仅被16位disp构成:
机器码f6363412,反出div byte ptr [1234h]

(8.7.5) mod为1,使disp为位移低字节,及此字节的符号扩展字节:
机器码8042fd80,反出add byte ptr [bx]+[di]-3,80h,这时,disp为fd的16位符号
扩展字fffd,即-3,r/m为1,EA就是[BX]+[DI]+disp,加数为80h

(8.7.6) mod为2,使disp为位移高字节,及位移低字节:
机器码c78257136824,反出mov word ptr 1357h[bp+si],2468h,这时,mov的reg规定
为0,r/m为2,EA就是[BP]+[SI]+disp

(8.7.7) vds为1,w为1:
机器码83c3fc,反出add bx,-4,这时,add的reg规定为0,r/m为3,索引到bx,fc符号
扩展成运算字fffc,即-4

(8.7.8) vds为0,w为1:
机器码81c3fcff,反出add bx,-4,未用符号扩展优点,字-4存为fcff

(8.7.9) 偏移108h处,相等则跳到10fh的指令:
机器码7405,反出jz 10f,74不分域,下一条指令地址10ah,加上补码形式的位移
字节5,就是目的地址10fh.若想转101h,位移字节为f7,对应-9.

(8.7.10) 指定段前缀:
机器码26a03412,反出mov al,es:[1234h],段前缀26,2e,36,3e,对应es,cs,ss,ds.

隐含段前缀时的寻址方法:

[ip],[sp],[bx],[bp],[si],用cs,ss,ds,ss,ds

[di]不含串时,用ds,如mov [di],al,否则用es,如stosb

基址+变址的段前缀,依基址.

DEBUG靠4个表:mnemo,mnemo_idx,x4857_idx,x4857,求指令操作码的助记符.

(8.8) mnemo是指令助记符表,各助记符被空格隔开,如"CLD CLI".

(8.9) mnemo_idx被操作码索引,对应的字值为助记符对mnemo的偏移,例如,cli的
机器码是fa,mnemo_idx的第fa个字,值为48h,此为CLI串对mnemo的偏移.mnemo_idx
的字值为0ffffh时,要用操作码索引x4857_idx:

(8.9.1) x4857_idx的对应字节值不为0ffh时,是对x4857的偏移off,这时,要用寻址
方式字节的reg域,索引x4857的始于字节偏移off的8个字,这组字中被索引的字值,
为助记符对mnemo的偏移.

例如,dec ah,机器码是fecc,mnemo_idx的第fe个字,值为0ffffh,找x4857_idx的
第fe个字节,值为30h,再用寻址方式字节cc的reg值1,索引x4857的偏移30h字节
开始的8个字,找这组字中的第1个字,值为6ch,此为"DEC"对mnemo的偏移.

(9) comexe的跨段检查功能

DEBUG.EXE等,常用retf等指令,同时改IP,CS,comexe的t,遇CS改值,
用0033 gap seg,n(o check):,指明老CS的偏移33处的指令执行后,
使CS改值,并问还查跨段否,答n表示不查.

DEBUG依4表求助记符,是靠comexe,调试2k的DEBUG.EXE,对t,答ffff,对proceed,
答n,过了33,103,1aa,104跨段,遇显出'-'的28fc,读u等命令的478,显助记符
的292c,然后用DEBUG调试此EXE,得mnemo始于40ba,mnemo_idx始于3cba,
化简后,得x4857_idx,32个在4857的字,减40ba,得x4857.

想从文件输入的用户,对进中断及跨段,可写两个n,不管谁先到,再预写其他
命令,可使comexe自动运行,如用pctools写不含LF的29字节二进制文件foo.0:

0100  64 65 62 75 67 2E 65 78-65 0D 66 2E 65 78 65 0D   debug.exe.f.exe.
0110  74 66 66 66 66 0D 6E 6E-75 0D 71 0D 71            tffff.nnu.q.q

其中,f.exe是debug.exe的参数,由

code    segment
        assume  es:code,cs:code,ss:code,ds:code
@        proc    far
        push    ds
        xor        ax,ax
        push    ax
        ret
@        endp
code    ends
        end        @

生成.执行comexe<foo.0,相当于执行了debug.exe f.exe的命令u,q

(10) comexe.asm全文:

RADIX    =    16            ;可接收各进制
RADIX1    =    400h+RADIX    ;4是移次
WANTSZ    =    4            ;不含CR的输入长

setstd1    macro std1
        mov ah,46h        ;使bx起cx作用
        mov    bx,std1
        mov cx,1        ;stdout
        int 21h
        endm

makebrk    macro
        mov al,es:[bx]                ;保存首字节
        mov brkval,al
        mov byte ptr es:[bx],0cch    ;换入0cch

        mov brkoff,bx
        mov brkseg,es
        endm

alasc    macro      
        mov        ah,al

        and        al,15    ;低nibble
        xlat

        xchg    ah,al

        rept    4
        shr        al,1    ;高nibble
        endm

        xlat

        stosw
        endm

axasc    macro
        xchg ah,al            ;处理ah
        
        push ax
        alasc
        pop ax

        xchg ah,al            ;处理al
        alasc
        endm

newline    macro
        mov ah,9
        lea dx,CRLF
        int 21h
        endm

code    segment
        assume es:code,cs:code,ss:code,ds:code

        org 100h
@:        jmp @1

w16        dw 0            ;参数块起始,0继承父env
        dw 128+15+1        ;实长打头的参数串偏移
paraseg    dw 5 dup(0)        ;参数串段值及4字fcb
initsp    dw 0
initss    dw 0
initip    dw 0
initcs    dw 6 dup(0)

.w0        db    13,10
w0_w14    db '0000 0000 0000 0000 0000 0000 0000 0000 '
        db '0000 0000 0000 0000 0000 0000 0000 '
w15        dw 2 dup(0)
32db"gap seg,n(o check):";bufszdbWANTSZ+1;charcntdb0charbufdbWANTSZ+1dup(0)caxdw0cbxdw?;,.exe,200hccxdw?cdxdw0cspdw?cbpdw0csidw0cdidw0cdsdw?cesdw?cssdw?ccsdw?cipdw?cfdw3202h;仿debug,.0:0oldstd1dw0,1,1;comexestdoutb0b1labelwordbyte0db0byte1db0intszdw0,3,3;.axdb13,10,AX=axdiascdb0000BX=0000CX=0000DX=0000dbSP=0000BP=0000SI=0000DI=0000|codeascdw0;ascvecasclabelword;ascdb0,0,13,10,DS=dsipascdb0000ES=0000SS=0000CS=0000IP=0000ocdb23dup(32),|;8mne8086db7dup(0);8086CRLFdb13,10,36F01dbNVOVUPDNDIEIPLNGNZZRNAACPOPENCCYi?labelword;1,3,22h,23hdbcooff23labelworddbm,seg23labelworddbexsteps1labelworddbe:chkgapdb36;?stepsdw0a0dh;

上一篇: 如何运用File::Find::Rule模块来探索和遍历文件目录结构

下一篇: 常用安卓UE4开发命令指南