Подключите Ваш компьютер к проекту распределённых вычислений!
Этим Вы окажете большую помощь науке и медицине.
См. подробнее: http://solidstate.karelia.ru/~yura/pyldin/yura/computing.htm



Turbo Pascal 7.0.    Модуль CRT.


1. Ошибка "200".

           Стандартный модуль CRT используется во многих программах на Turbo Pascal. Но при запуске этих программ на современных компьютерах, оснащённых быстрыми процессорами, возникает ошибка "Деление на нуль" 

Runtime error 200 at xxxx:xxxx.

           Проблема 200 получила своё название по двум причинам. Во-первых, она начинает проявляться на процессорах с тактовой частотой 200 MHz. Во-вторых, в Turbo Pascal ошибка "Деление на нуль" имеет номер 200.

           Разберёмся теперь в причинах возникновения этой ошибки. В модуль CRT включена процедура DELAY, которая производит задержку выполнения программы на заданное число миллисекунд. Она основана на выполнении двух вложенных циклов. Внешний цикл имеет пределы от заданного числа миллисекунд до нуля. Таким образом, легко понять, что внутренний цикл должен выполняться ровно 1 миллисекунду. 

Стандартная процедура DELAY:

    8BDC        mov    bx,sp
    368B4F04    mov    cx,ss:[bx+04] ; число оборотов внешнего цикла
    E313        jcxz   <2>
    8E06????    mov    es,Seg0040
    33FF        xor    di,di
    268A1D      mov    bl,es:[di]
<1> A1????      mov    ax,DelayCnt   ; число оборотов внутреннего цикла
    33D2        xor    dx,dx
    E80500      call   <3>           ; вызываем внутренний цикл
    E2F6        loop   <1>           ; продолжаем внешний цикл
<2> CA0200      retf   0002

Внутренний цикл:

<3> 2D0100      sub    ax,0001
    83DA00      sbb    dx,0000
    7205        jb     <4>
    263A1D      cmp    bl,es:[di]
    74F3        je     <3>
<4> C3          ret 

           В модуле CRT есть инициализационная часть, в которой производятся все необходимые подготовки, в том числе и расчёт, сколько же раз должен выполниться внутренний цикл, чтобы отсчитать 1 миллисекунду. 

Часть из инициализационной секции:

    8E06????     mov    es,Seg0040
    BF6C00       mov    di,006C 
    268A1D       mov    bl,es:[di]   ; 0040:006C - системный таймер
<5> 263A1D       cmp    bl,es:[di]
    74FB         je     <5>  ; дожидаемся начала очередного тика таймера
    268A1D       mov    bl,es:[di]
    B8E4FF       mov    ax,FFE4
    99           cwd
    E83C02       call   <3>          ; вызываем внутренний цикл
    F7D0         not    ax
    F7D2         not    dx           ; DX:AX = число оборотов 
                                     ; внутреннего цикла за 55 мс
    B93700       mov    cx,0037      ; CX = 55
    F7F1         div    cx           ; делим DX:AX на 55, чтобы узнать,
                                     ; сколько оборотов должен делать
                                     ; цикл, чтобы отсчитать 1 мс
    A3????       mov    DelayCnt,ax  ; запоминаем AX

           Для этого производятся следующие действия: 
1) Дожидаемся начала очередного тика системного таймера (адрес в BIOS: 0040:006С). 
2) Помещаем в DX:AX FFFF:FFE4 (начинаем с максимума, так как внутренний цикл, 
    выполняясь, уменьшает DX:AX).
3) Вызываем внутренний цикл.
4) После выполнения внутреннего цикла и команд not ax; not dx в DX:AX находится
    число оборотов цикла за 1 тик таймера (55 миллисекунд).
5) Далее, чтобы узнать, какое же число оборотов должен делать цикл, чтобы 
    отсчитать 1 мс, DX:AX делится на 55. 
6) И полученное число запоминается, чтобы в дальнейшем использоваться 
    процедурой DELAY. 

           Вспомним теперь, как работает ассемблеровская инструкция DIV. Она делит DX:AX на указанный аргумент (в нашем случае CX, который равен 55) и результат заносится в AX (в DX - остаток, но он нас сейчас не интересует). На процессорах, частота которых 200 MHz и выше, DX:AX, делённое на 55 оказывается больше 65535 и, поэтому, результат не может поместиться в AX. Происходит так называемое переполнение при делении. В случае переполнения (или в случае деления на нуль) автоматически вызывается прерывание INT 00. В Turbo Pascal (в модуле System) это прерывание "перехвачено". Новая процедура обслуживания INT 00 выводит строку Runtime error 200 at xxxx:xxxx. и завершает работу программы. И, несмотря на то, что к ошибке №200 мы привыкли как к делению на нуль, в этом случае имеет место не деление на нуль, а переполнение при делении. 

Выход из этой ситуации.

           В Internet'е можно найти много публикаций, где предложены различные варианты решения данной проблемы. Один из вариантов такой. В состав Borland Pascal 7.0 входит исходный текст модуля CRT, в него следует внести некоторые изменения, описанные ниже и перекомпилировать заново. Перед внесением изменений следует сделать резервную копию CRT.ASM.
          На данной Web-страничке находится архив NEW_CRT.ZIP, в который включены уже исправленные  модули CRT.TPU и CRT.TPP. 

           Рассмотрим далее, какие же изменения внесены в файл CRT.ASM: 

1) Определение переменной DelayCnt (строка 45).

Убрана строка:
DelayCnt            DW            ?

Вставлена строка:
DelayCnt            DD             ?

2) Инициализационная секция.

Убраны строки 105-107:
                             MOV         CX,55
                             DIV            CX
                             MOV         DelayCnt,AX

Вместо них вставлены следующие:
                            MOV          BX,AX
                            MOV          AX,DX
                            XOR           DX,DX
                            MOV          CX,55
                            DIV             CX
                            MOV          WORD PTR DelayCnt+2,AX
                            MOV          AX,BX
                            DIV             CX
                            MOV          WORD PTR DelayCnt,AX

3) Процедура Delay.

Убраны строки:
@@1:                MOV           AX,DelayCnt
                            XOR           DX,DX

Вместо них вставлены следующие:
@@1:                 MOV           AX,WORD PTR DelayCnt
                             MOV           DX,WORD PTR DelayCnt+2

          Вы можете поместить новый CRT.TPU в TURBO.TPL с помощью утилиты TPUMOVER. Но прежде чем выполнять замену, следует на всякий случай сохранить старую версию TURBO.TPL.

                  tpumover turbo.tpl -crt 
                  tpumover turbo.tpl +crt 

(Новый модуль CRT.TPU при этом должен быть в текущем каталоге). 

           В архиве так же находится исправленный модуль CRT.TPP для программ, работающих в защищённом режиме. Вы можете проделать аналогичные действия для замены старого модуля в библиотеке TPP.TPL. При этом в текущем каталоге, в котором производится замена, не следует одновременно иметь и CRT.TPU, и CRT.TPP. Дело в том, что в программе TPUMOVER есть ошибка, в результате которой в TPP.TPL в этом случае будет помещён CRT.TPU вместо CRT.TPP.


Если есть уже готовый EXE файл, но нет исходного текста.

          В случае, если есть уже готовый EXE-файл, вызывающий ошибку 200 при запуске, но нет исходного текста программы на Паскале, следует воспользоваться следующим советом. С помощью двоичного редактора, например, HIEW, следует найти в коде последовательность байт с шестнадцатеричными кодами:
F7 D0 F7 D2 B9 37 00 F7 F1
и заменить её на последовательность:
B8 01 00 90 90 90 90 90 90

          Данная замена в инициализационной секции CRT приводит к тому, что исчезает ошибка 200, но также после этого процедура Delay перестаёт давать задержку. Чтобы процедура Delay снова заработала, следует далее найти в EXE-файле последовательность байт:
33 FF 26 8A 1D A1 ?? ?? 33 D2 E8 05 00 E2 F6
и заменить её на:
B8 E8 03 F7 E1 8B CA 8B D0 B4 86 CD 15 90 90

В ассемблеровском виде новая процедура Delay представляет собой следующее:

mov    ax,03E8h
mul    cx
mov    cx,dx
mov    dx,ax
mov    ah,086h
int    15h

          Новая процедура Delay действительно работает и даёт нормальную задержку, однако не на всех компьютерах и операционных системах. Функция int 15h, используемая в данном варианте Delay не будет работать на старых компьютерах XT, PC, PCjr, а также под Windows NT/2000/XP/Server2003. (Под Windows NT/2000/XP/Server2003 программа работать будет нормально, но процедура Delay не будет давать задержки). Вставить полноценную новую процедуру Delay, не имея исходных текстов невозможно, так как её код длиннее, чем код старой.
 


2. Моя упрощенная версия модуля CRT.

           Дело в том, что, как уже говорилось, модуль CRT подключает к программе инициализационную секцию, которая увеличивает размер EXE-файла на 1,5 Kb. (На самом деле эти 1,5 Kb содержат не только инициализационные процедуры, а все процедуры и функции модуля CRT, так как он написан на ассемблере цельным файлом). В инициализационной секции находятся новые драйверы текстовых устройств. Драйвер, обслуживающий стандартный файл Input, расширяет возможности Read/ReadLn, а драйвер, обслуживающий файл Output, производит вывод в видеопамять (что ускоряет работу) и позволяет использовать цвета (TextColor, TextBackground, ...). Но существует очень много программ, которые никак не используют эти возможности модуля CRT. Очень часто пользователи подключают модуль CRT к своим программам для использования процедур KeyPressed, ReadKey, GotoXY, ClrScr и т.д. Для таких процедур инициализационная секция не нужна. В этом случае она, автоматически подключившись к программе, работает впустую, только увеличивая размер EXE-файла на 1,5 Kb. 
          Мой модуль NEW_CRT не содержит инициализационной секции. Поэтому он лишён некоторых возможностей, а именно: не работают процедуры TextColor, TextBackground, Window, не производится вывод информации в видеопамять и отслеживание нажатия Ctrl-Break. Отсутствуют некоторые переменные. Зато уверенно работают следующие процедуры: KeyPressed, ReadKey, TextMode, GotoXY, WhereX, WhereY, ClrScr, ClrEol, InsLine, DelLine, Delay, Sound, NoSound. Так же предусмотрена следующая возможность. Если сразу после begin главной программы вызвать процедуру Crt_Init, станут доступны процедуры TextColor, TextBackground, LowVideo, HighVideo, NormVideo, а также переменная TextAttr. Но в отличие от стандартного модуля CRT, вывод по-прежнему будет производиться через прерывание BIOS INT 10, что в некоторых случаях может неблагоприятно сказаться на скорости работы программы. И, во-вторых, в процедуре Crt_Init предусмотрено только переназначение драйвера вывода (Output), а Input по-прежнему остаётся "во власти" модуля System. Это приводит к тому, что процедуры Read/ReadLn остаются чёрно-белыми. Конечно, можно было бы и это предусмотреть, но тогда не было бы заметного выигрыша в размере EXE-файла по сравнению со стандартным модулем CRT. В случае применения процедуры Crt_Init выигрыш составляет уже не 1,5 Kb, а 1,3 Kb. 
           В следующей таблице приведён небольшой тест. В последнем примере используется обращение к Crt_Init, так как в этой программе есть процедура TextColor.


Имя программы
 
Исходный 
размер
DIET 
1.0
WWPACK
3.02
APACK 
0.98
ARTY
CRT
25584
17113
16648
15743
 
NEW_CRT
24080
15861
15439
14546
 
Выигрыш
1504
1252
1209
1197
BGIDEMO
CRT
42032
24979
23821
22251
 
NEW_CRT
40608
23795
22683
21151
 
Выигрыш
1424
1184
1138
1100
OVRDEMO
CRT
6224
5139
5260
4773
 
NEW_CRT
4912
4066
4210
3777
 
Выигрыш
1312
1073
1050
996

           Для использования модуля NEW_CRT необходимо в Вашей программе (и/или всех модулях, которые она использует) заменить в строчке 'uses' CRT на NEW_CRT. В случае, если программа ссылается на переменные типа CheckBreak, CheckSnow или DirectVideo, можно просто убрать эти ссылки. Как уже говорилось, в модуле NEW_CRT вывод всегда идёт через BIOS, поэтому DirectVideo подразумевается равным false, а CheckSnow, следовательно, true.


 АРХИВ    NEW_CRT.ZIP

В этом архиве находятся файлы:
              DOC_CRT.TXT    - Список изменений, которые следует внести в CRT.ASM
              CRT.TPU               - Исправленный модуль для реального режима
              CRT.TPP               - Исправленный модуль для защищённого режима
              NEW_CRT.PAS    - Альтернативный модуль CRT


Эта WEB-страничка последний раз изменялась 29 октября 2003 года.

Добавить запись в гостевую книгуПросмотреть гостевую книгу

< < < На главную страничку < < <