PEG:TP7 divide by zero bug
History
Turbo Pascal 7.0 (and its professional edition, Borland Pascal 7.0) are the last of Borland's Pascal compilers and emerged during a time when the capabilities of software and hardware were rapidly evolving, the age of the Intel 80286 and the Microsoft Windows 3.1 operating system. Typical clock speeds were on the order of 8 MHz. As faster and faster CPUs emerged, Turbo Pascal 7.0 continued to be used as a tool for introductory computer science classes because of its simplicity and desirable functionality and performance characteristics (it was used in PEG, for example, until discontinued in 2009.) For as long as the dominant Windows release of the time has been able to run TP7 (a DOS-mode application), there have been surprisingly few compatibility issues associated with running TP7 on newer and faster computers. The best-known compatibility issue is the divide by zero bug encountered in the initialization code of the CRT unit.
Cause of the bug
Any program that uses the CRT unit implicitly executes its initialization code, some time after basic program initialization (installed by the System unit) and before the instructions after the main begin
. This initialization prepares the program for use of procedures, functions, and variables of the CRT unit. The particular cause of the divide by zero bug is the code that prepares the program to use the Delay
procedure.
Delay(x)
induces a delay in execution with a duration of x
milliseconds. In order to do this, it performs some useless instruction (specifically, decrementing a register) some number of times. For any given delay, the number of times to execute the instruction is likely to differ from one CPU to another, since two CPUs with different speeds take different amounts of time to execute the same set of instructions. TP7 solves this problem by determining, in the initialization code, how many times it can do so in one timer tick. A single timer tick is about 55 ms, so it divides by 55 to determine the number of times it must execute the instruction in one millisecond.
The DIV
instruction on the Intel x86 architecture is used to divide the dword DX:AX
, the number of times to decrement the register in 55 ms, is divided by the word 55, the quotient being stored in AX
. Since a 32-bit value is being divided by a 16-bit value, it is possible that the result does not fit into the 16-bit AX
register. (For example, if the CPU can perform 55 million decrements in 55 ms, then the result of the division, 1 million, is too large to fit into AX
.) When this occurs, the CPU generates a divide exception (INT 0). This exception is caught by the program's RTL, which reports it as error 200, division by zero. (That is, the exception actually occurs in two cases: when division by zero occurs or when the quotient is too large to fit in the destination register, but most programs assume that the former case occurred, since it is far more common than the latter.)
Fixes
Fundamentally, the solution to this problem is to use a 32-bit counter for the number of times to execute an instruction per millisecond. Since the source code of the CRT unit is not available, all patches must be applied at the binary level. There are two programs included with our distribution of Borland Pascal 7.0: TPPATCH.EXE
and t7tplfix
(a .zip
file). The former can be used to patch any program compiled with TP/BP 7.0 that is afflicted by the bug by passing it the executable as a command-line parameter (and it will not affect any other executables). The latter patches the CRT unit itself and should be started from within the bin
directory. (Any programs compiled after the application of this patch will no longer have the bug.)