你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 编程语言
Borland Pascal 7.0中CRT单元的一个错误及修正
 

Borland公司推出的turbo/borland系列编程环境,在dos时代,以其快速的编译速度,优良的代码效率,和功能强大的集成环境,以及丰富的开发库曾一度占领了当时的开发工具的是市场。过去的程序员很少有人没有用过大名鼎鼎的turboC/borlandC/C++borland Pascal的。即使是在现在windows大行其道的时候,仍然还是有一批忠实的用户。turbo/borland系列仍然是编写dos下的程序的最好的编程环境。尤其是对于大多数目前的高校而言,讲授程序设计语言和数据结构时,仍然大部分是用Borland Pascal的。 

Borland Pascal 7.0Borland公司于1992年推出的著名的Turbo Pascal 系列的新版本。它比Borland C/C++的优势在于它可以将pascal程序编译成为保护模式下的程序。这就意味着开发者可以使用所有的内存,它不用像BorlandC++那样要考虑640k内存的问题。但是这样优秀的开发环境,其最为常用的CRT单元,存在着一个致命的错误,使得用了crt库的程序,都将无法运行。

最初发现这个错误是在编译Borland Pascal所带的一个例子程序crtdemo.pas,发现一运行就会出现divided by zero.(除零错误)的错误信息。单步跟踪它,发现程序其实尚未运行,即单步跟踪条尚未到begin语句,就出现了上述错误。看来并不是crtdemo程序的原因。后来又发现这个错误其实也不是每次都发生,如当win98运行其他很多程序时(我是在win98下运行bp.exe的),有时它也不会出现错误的。但是大多数情况下,它出现错误的几率大得多。

于是采用Turbo Debugger跟踪该程序。发现在执行到cs::0078时有如下语句:

      cs:0078:    mov BL,ES:[DI]

      cs:007b:    cmp BL,ES:[DI]

      cs:007e:    je 007B

这几条语句的作用是把ES:[DI]的值放入BL中。但是又立即比较BLES:[DI]的值是否相等,相等则继续比较。但是这个值是刚放进去的,当然会相等,而且只要不改变这个值,它都会相等,这不是死循环了吗?于是查当时的ES:DI值,是40H:6CH,再查参考资料,原来40H:6CH的值是BIOS时钟计数器的双字单元。这个时钟单元每1/18.2秒(即55毫秒)改变一次。然后在气后面几含有出现了这样的语句:

     cs:008e   mov cx,0037(即十进制的55)

     cs:0091   div cx

看来这一段程序是求出每毫秒的计数值的。于是分别在0091之前和之后下了断点,正是确实是在0091处出现除零错误的。

既然找到了问题所在,当然就得想办法解决。于是找到Borland Pascal Runtime Library(Borland Pascal运行时间库源代码,在BorlandPascal的第十一张安装盘),找到CRT.ASM,察看其源程序。这段程序是:

    MOV ES,Seg0040

    MOV DI,OFFSET Timer

    MOV BL,ES:[DI]

@@2:    CMP BL,ES:[DI]

    JE  @@2

    MOV BL,ES:[DI]

    MOV AX,-28

    CWD

    CALL    DelayLoop

    NOT AX

    NOT DX

    MOV CX,55

    DIV CX

    MOV DelayCnt,AX

delayLoop的程序段是:

DelayLoop:

@@1:    SUB AX,1

    SBB DX,0

    JC  @@2

    CMP BL,ES:[DI]

    JE  @@1

@@2:    RET)

粗看起来这段程序一点问题也没有。最前面的几行是让程序在下一个55毫秒来的一刹那继续。然后调用DelayLoop计数,计数的结果再除以55,就可以得到每毫秒的循环所需的计数值,放入DelayCnt中。为后来的Delay过程所用。那么问题处在哪里呢?难道是计数值超过了65536*55,以致于溢出?

为了证实我的猜想,我特地编写了一小段汇编程序:

.model small

.code

start proc near

 mov ax,0040h

 mov es,ax

 mov di,6ch

 mov bl,es:[di]

loop1:cmp bl,es:[di]

 je @@1

 mov ax,0

 cwd

 mov bl,es:[di]

loop2:

 add ax,1

 adc dx,0

 cmp bl,es:[di]

 je @@2

 int 3

 mov cx,55

 div cx

 mov ax,4c00h

 int 21h

start endp

end start

td跟踪。运行的结果果然不出所料,每次的计数值大多数都是使dx的值超过了37h。这样的话当然会使除以cx的结果产生除零错误。看来这个错误的产生,是由于BP的开发人员,没有考虑到机器的速度会变得这么快,因此,只使用了一个字来存储计数值。所以造成了这样的一个溢出错误。这也真可以解释为什么在win98下运行很多程序时,系统负荷比较重,速度下降,这时计数值反而不会溢出,因而不会出现除零错误的原因。

最初我的解决办法是将div cx改为nop,这样当然能够避免除零错误。但是这样做的后果是使delay过程的延时功能失效,因为delaycnt的值和实际值是不同的。于是我就干脆修改crt.asm,修正这个错误。具体的思想是:将计数值delaycnt改为双字,使其能够表示大于65536的数值。并对原程序中使用的字寄存器改为32位寄存器。

具体的做法是:

编辑crt.asm:

①在第45delayCnt dw ?后加上一行dw  ?

②将上文的程序段和delayloop子程序改为:

    程序段:

MOV ES,Seg0040

    MOV DI,OFFSET Timer

    MOV BL,ES:[DI]

@@2:    CMP BL,ES:[DI]

    JE  @@2

    MOV BL,ES:[DI]

        DB 66H,0B8h,0D8h,0FFh,0FFh,0FFh    ;Mov eax,-28

        DB 66H,99H                ;CDQ

        CALL    DelayLoop

        DB 66H

        NOT     AX

        DB 66H

        NOT     DX

        MOV     CX,55

        DB 66H

        DIV     CX                div ecx

        DB 66H

        MOV     DelayCnt,AX       ;MOV DelayCnt,EAX

delayLoop的程序段修改后是:

DelayLoop:

@@1:

        DB 66H,2DH,01H,00H,00H,00H        ;SUB     EAX,1

        DB 66H,83H,0DAH,00H              ; SBB     EDX,0

    JC  @@2

    CMP BL,ES:[DI]

    JE  @@1

@@2:    RET

 

  ③重新编译CRT库:

设置好BorlandPascal的路径:set path=d:\bp\bin  (因为编译时要调用bpctasm)

使用make重新编译全部单元

MAKE

然后再将新生成的CRT单元拷贝到BP\BIN目录下

COPY D:\BP\RTL\BIN\TPU\CRT.TPU D:\BP\BIN

④将新的CRT库加入到TURBO.TPL中去。

BP\BIN目录下:

TPUMOVER TURBO.TPL -CRT(将旧的CRT单元移出)

TPUMOVER TURBO.TPL +CRT.TPU(加入新的CRT单元)

    对于那些没有RTL运行时间库的用户而言呢。因为无法重新编译,修改量太大。所以干脆就只是把DIV CX去掉即可。在这样的情况下,只要不使用delay延时,不会出什么大的问题。修改的方法如下:

(crt.tputurbo.tpl中取出来)bp\bin目录下:

  tpumover turbo.tpl *crt.tpu

②修改crt.tpu

debug crt.tpu

-s 0 FFFF F7 F1 A3

这时返回的地址应该为DS:0931

-E 0931 90 90   (将其改为NOP)

-W

③将修改后的CRT.TPU加入到TURBO.TPL

tpumover turbo.tpl +crt.tpu

Borland Pascal的这个问题是比较严重的。比如以前用Borland Pascal写的使用了CRT单元的可执行程序,现在在很多高速的机器上就无法执行了。如果有源程序的话,可以在按照本文的方法修改了CRT单元后,重新在编译一遍。否则的话,就只有用debug修改可执行文件,仿照第二种办法去直接修改,将div cx改为nop了。

另外,因为这个错误是因为机器速度过快造成的。所以在一些速度较慢的机器上,可能不会出现除零错误的现象。但是还是建议对CRT的这个bug进行改正,否则很可能出现以后你用BP编译的程序无法在更高配置的机器上运行的现象。本文的程序在Celeron366/32M内存/Pwin98MS-DOS方式通过。

  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089