曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 7864|回复: 0
打印 上一主题 下一主题

[8086] 调试16位DOS程序

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
跳转到指定楼层
楼主
发表于 2009-4-1 01:55:45 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
  用 BC4.5 写个 C的小程序,想反汇编过去看看,结果一开始不得其法,连相应的工具都找不到。因为是16位的DOS程序,现在一些调试工具似乎不支持。一度去找什么DOS 下的 SOFTICE,TR。而且还专门用虚拟机装了MS-DOS 7的版本,然后把工具拷到虚拟机里调试,反正就是按着工具的说明去做,竟然要么提示无法加载,要么就是 CPU 出错,郁闷!     其实一个调试的好工具近在眼前,就是在安装了 BC45 后自带的 TD4.2,用 BC 写的 C 程序再用自家的调试器是再合适不过了。所以花在找工具的时间相当于浪费掉了。


为了简单起见,也是为了初步掌握 TD 的使用方法及一些特性,所以就写个非常简单的 C程序进行调试。这个程序的主要目的是看反汇编后,汇编器是如何对程序进行子程序的调用,如何安排堆栈,如何对指针进行应用。程序如下(不需要调用任何函数,连基本的输入输出库都省掉了):


int main()
{
    unsigned int StkTest[5] = {1,2,3,4,5};
    unsigned int *ptr = &StkTest[0];
    *ptr = 10;
     ptr++;
     *ptr =  20;
     ptr++;
     *ptr =  30;
     ptr++;
     *ptr =  40;
      return 0;
}
为了方便调试时的比较,似乎是要把生成的 exe 文件和源代码文件放在一个目录底下,然后才能在调试器的 CPU 窗口中看到汇编代码以及相应的 C 程序代码。
汇编代码解析
从进入 _main() 到 unsigned int StkTest[5] = {1,2,3,4,5}; 的代码是:
#1-0
enter  000e, 00
lea     ax, [ bp - 0a]
push  ss
push  ax
push  ds
push 0090
mov  cx, 000a
call   far  f_scopy@   
说明
enter 000e,0 ; 这个指令表明开始进入 _main() 中,指令中的第二个操作数 00 ,是指明 子程序的嵌套层数为0.
这条指令相当于执行了下面 3 条指令:
push bp
mov bp, sp
sub sp, 000e
每当进入一个子程序时,push bp 是必须的,这个压入站的 bp 是父程序的堆栈 bp。由于进入子程序后,子程序也有自己的堆栈,也要用到 bp ,所以要把父程序的 bp 压栈保存起来。
mov bp, sp 是让 bp 指向子程序堆栈的最高端处,也就是 bp 所压的地方,这样一来,以后就可以用 bp 方便的进行寻址了。
sub  sp, 000e,这语句是说明腾挪出 0eH 个字节的堆栈空间,这里会往往会存放临时变量参数等东西。
call   far  f_scopy@ 调用 f_scopy@ 程序正是对应上面的 C 定义语句:unsigned int StkTest[5] = {1,2,3,4,5};
跟进去这个 CALL 看看编译器是如何安排这么一条语句的,相应代码为:
#1-1
push   bp                    
mov   bp,sp
push  si
push  di
push  ds                    ;以上语句均为进入子程序的一般操作
lds   si, [ bp + 06 ]
les   di, [ bp + 0a ]
cld
shr   cx, 1
rep  movsw
adc  cx, cx
rep movsb
pop  ds                          ;以下为子程序返回时进行堆栈的释放
pop  di
pop  si
pop  bp
retf   0008
说明:lds   si, [ bp + 06 ] 和 les   di, [ bp + 0a ]
像 lds , les 这样的指令是装载远指针指令。含义为:从源操作数(远指针)的低地址取到指针的偏移量送往目的操作数中,从高地址取到指针的段地址送往ds(lds),es(les) 中。
从上面的堆栈操作中可以看到,lds si,[ bp + 06 ] 从堆栈中取得 0090  送往 SI 中,原来的 DS 同样的送往 DS中,也就是说,DS 并没有发生变化。同样可以分析 les   di, [ bp + 0a ] 指令。
至于说问什么是 0090 而不是别的其它什么数,我想这应该是有编译器来安排的,如果改成 0080 也是可以的。
从下面的代码分析中,很快的能知道,其实 DS:0090 处正是编译器把要初始化 StkTest[5] 这个数组的1,2,3,4,5 这 5个数从那连续的存放的,这应该只是个临时的存放点。而真正最终要存入到数组里面的是 es:di 这里。
而 es 就是先前压入栈的 ss,di 的值就是先前压入栈的 ax,而 ax 的值又是 #1-0 中的 bp - 0a 。由于 ss从来没有被改变过,所以1,2,3,4,5 这 5 个数字就是送往 sp - 0e所开辟的这块堆栈中的!换句话说,数组StkTest[]就是安排在这个堆栈中,数组的首地址 &StkTest 就是 ss:ax。

接着分析一下语句 unsigned int *ptr = &StkTest[0]; 在汇编中的处理,对应的代码为:
lea   ax, [ bp - 0a]
mov [ bp - 0c ], ss
mov [ bp - 0e ], ax
LEA 是装有效地址指令。在 LEA  AX, [BP-0A] 语句中,[BP-0A] 是一个内存操作数,而这个内存操作数中 BP - 0A就是其对应的地址,所以 AX = BP-0A。
*ptr = &StkTest[0]-----这是一条给指针赋值的操作。在大模式编译下,ptr 为远指针。所以对远指针进行赋值操作,要对其 段地址 和 偏移量 同时进行赋值。
容易看出,ptr 自身的地址也就是安排在 enter 指令当初开辟的堆栈中(这个堆栈用来存放 5 个整数供10个字节,一个指针供 4 个字节,一共14即 0x0e 个字节)。
mov [ bp - 0c ],ss 是把数组的段地址赋给指针的高字地址处;mov [ bp - 0e ], ax 是把数组的偏移地址赋给指针的低字地址处。这样一来,就完成了赋值操作。
至于后面的指针移动修改操作,原理就是赋值操作,不再重复去分析了。
从上面的汇编代码中可以看到,在实模式下的远指针实质是 32 位的。它包含了它指向数据的 段地址+偏移量。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-20 14:58 , Processed in 0.073536 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表