USING REGISTERS. ßßßßßßßßßßßßßßßß Before considering the use of registers in Turbo Pascal, it is useful to appreciate the function and behaviour of registers and to use the DOS facility DEBUG to inspect both the registers and memory. Intel 8088, 8086, 80286 and 80386 microprocessors have 14 Registers, which are used to store and process numbers quickly, within the Central Processor Unit (CPU) itself. These are 16-bit registers, although the first four may be accessed as 8-bit parts. The word-sized registers are as follows: Data registers (general purpose) - AX, BX, CX and DX Pointer registers (Base and Stack) - BP and SP Index registers (Source & Destination) - SI and DI Segment registers (Code, Data, Stack & Extra) - CS, DS, SS and ES Instruction pointer register - IP Status register (Flags). The function of each register. ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ The AX (Accumulator) register is used to store data prior to the execution of an instruction. The BX (Base) register is used in some memory addressing schemes. The CX (Count) register is often used as a loop counter. The DX (Data) register is used to store 16-bit data or as an extension to the AX register when handling 32-bit data. The BP (Base Pointer) and SP (Stack Pointer) registers are used to point to the Stack, a part of memory used for passing parameters to subroutines, storing return addresses and saving register values for later retrieval. The SI (Source Index) and DI (Destination Index) registers are used in complex addressing modes and for string manipulation. The CS (Code Segment) register identifies the segment in which the program resides. Absolute memory addresses of specific instructions are calculated by combining the segment address in the CS register with the offset address in the Instruction Pointer (IP) to get a 20-bit absolute address of the instruction - CS:IP. The DS (Data Segment) register identifies the segment in which the data (program variables) reside. In conjunction with another register containing the offset data item (e.g. DS:BX), this register can also be used to find the absolute address of that data item. The SS (Stack Segment) register points to the stack segment and is used in conjunction with the Stack Pointer (SP). The ES (Extra Segment) register is used by string instructions. Memory Usage. ÍÍÍÍÍÍÍÍÍÍÍÍÍ Referring to the Turbo Pascal Memory Map (page 210 of the Programmer's Guide), the memory is used as follows: The data segment (addressed through DS) contains all typed constants followed by all global variables. The DS register is never changed during program execution. The stack segment register (SS) and the stack pointer (SP) are loaded on entry to the program so that SS:SP points to the first byte past the stack segment. The SS register is never changed during program execution, but SP can move downward until it reaches the bottom of the segment. The ES (Extra Segment) register points to an extra segment of memory, which may be used, with DS, to mark the source and destination of string moves. The IP (Instruction Pointer) register holds the offset address of the next instruction to be executed and is used in conjunction with CS to obtain the absolute address. The Status register is a collection of nine bits (flags), which indicate the outcome of arithmetical and logical operation as below: The Carry flag (bit 0) is set to 1 if add causes a carry or subtract causes a borrow. The Parity flag (bit 2) is set to 1 for even parity, 0 for odd parity. The Auxiliary Carry flag (bit 4) is used for BCD arithmetic. The Zero flag (bit 6) is set to 1 if the result of an operation is zero. The Sign flag (bit 7) is set to 1 if the result of an arithmetical or logical operation is negative. The Trap flag (bit 8) is set by the processor to single-step through the program instructions. The Interrupt enable flag (bit 9) is set to 1 to enable interrupts. The Direction flag (bit 10) is set to either increment or decrease the index register by one. The Overflow flag (bit 11) indicates an overflow condition in arithmetic operations with signed numbers. The DOS Debug facility and registers. ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ A simple text program, which can be created by using the Turbo Pascal editor, can be loaded into memory by means of the DOS debug utility by typing: debug where can be the full file specification, including drive, directory and filename. The screen response is the minus sign (-) and then a number of debug commands can be used, such as d followed by the offset address to 'dump' a section of memory or r to indicate the state of the various registers as shown below: debug test.txt -d 0100 4DFC:0100 46 69 72 73 74 20 6C 69-6E 65 20 6F 66 20 74 65 First line of te 4DFC:0110 78 74 0D 0A 53 65 63 6F-6E 64 20 6C 69 6E 65 20 xt..Second line 4DFC:0120 66 6F 6C 6C 6F 77 73 0D-0A 54 68 69 72 64 20 6C follows..Third l 4DFC:0130 69 6E 65 20 69 73 20 6E-65 78 74 0D 0A 46 6F 75 ine is next..Fou 4DFC:0140 72 74 68 20 6C 69 6E 65-20 69 73 20 6C 61 73 74 rth line is last 4DFC:0150 0D 0A 1A 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 4DFC:0160 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 4DFC:0170 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ -r AX=0000 BX=0000 CX=0053 DX=0000 SP=2AB6 BP=0000 SI=0000 DI=0000 DS=4DFC ES=4DFC SS=4DFC CS=4DFC IP=0100 NV UP EI PL NZ NA PO NC 4DFC:0100 46 INC SI -q The last command q is used to quit debug. It can be seen that the CS register displays the segment in which the code resides, whilst the IP register indicates that the code starts at the offset address 0100, which is the normal offset used by debug. Finally, other debug commands can be used to write a new line to the file. Enter (e) is used first to add a new line and then insert a carriage return and line feed (OD OA). Then Name (n) is used to give this file a new name (test2.txt) and Write (w) is employed to write the new file, as shown below. -e 0152 "One more line added" -e 0165 0D 0A -r cx CX 0053 :67 -n test2.txt -w Writing 0067 bytes This can then be checked as follows: -d 0100 4DFC:0100 46 69 72 73 74 20 6C 69-6E 65 20 6F 66 20 74 65 First line of te 4DFC:0110 78 74 0D 0A 53 65 63 6F-6E 64 20 6C 69 6E 65 20 xt..Second line 4DFC:0120 66 6F 6C 6C 6F 77 73 0D-0A 54 68 69 72 64 20 6C follows..Third l 4DFC:0130 69 6E 65 20 69 73 20 6E-65 78 74 0D 0A 46 6F 75 ine is next..Fou 4DFC:0140 72 74 68 20 6C 69 6E 65-20 69 73 20 6C 61 73 74 rth line is last 4DFC:0150 0D 0A 4F 6E 65 20 6D 6F-72 65 20 6C 69 6E 65 20 ..One more line 4DFC:0160 61 64 64 65 64 0D 0A 00-00 00 00 00 00 00 00 00 added........... 4DFC:0170 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ - Register Usage. ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ There are three ways in which registers are used in Turbo Pascal. 1. With debug to trace a Turbo Pascal program, observing the contents of the registers, as they change with each machine code instruction. The contents of the registers can also be inspected from the Integrated Development Environment by means of the Window Menu (Alt-W) and selecting R for Registers, the contents of which are then shown in a window at the top right of the screen. The registers can also be inspected in the watch window, as explained later in these notes. 2. To accept the return of function results. 3. In conjunction with Interrupt Service Routines. 1. Tracing a Turbo Pascal program. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ When a Turbo Pascal program is compiled it is converted to machine code. It is then possible to use the DOS debug facility to locate this .EXE file in memory, find a particular instruction and trace its operation. It is also possible to 'unassemble' the machine code and obtain the assembly language instructions. The process of locating a particular instruction is facilitated by the use of a simple Inline machine code instruction, which creates three consecutive NOP instructions ( No OPeration ). In machine code this instruction is represented by the hexadecimal value 90. The occurrence of 90 90 90 clearly indicates this point in the program. A very simple Turbo Pascal program is now used to illustrate this point. program Turbo_Assembly; var x,y : Integer; begin Inline($90/$90/$90); x := 3; y := x * x; Inline($90/$90/$90) end. If this program is saved as RegsDemo.pas and compiled to RegsDemo.exe, it can then be studied with the DOS debug facility as shown below: C>debug { run debug } -n RegsDemo.exe { set file name to be used by load } -l { load file } -s cs:0000 ffff 90 90 90 { search code segment for NOPs } 13A0:0008 { the first encounter } 13A0:001b { the second encounter } -r ip { request the current value of IP } IP 0000 { the value returned by debug } :0008 { debug gives : prompt for new value } -t { trace request for single instruction } At this point the contents of all the registers are shown and the next instruction - in this case NOP. AX=0000 BX=0000 CX=0330 DX=0000 SP=4000 BP=0000 SI=0000 DI=0000 DS=1390 ES=1390 SS=1415 CS=13A0 IP=0009 NV UP EI PL NZ NA PO NC 13A0:0009 90 NOP Further trace commands repeat this procedure for each instruction, until the final three NOP instructions are encountered. The displays are similar with some values remaining unchanged. The essential changes are shown below. -t AX...... DS...... IP=000A 13A0:000A 90 NOP -t AX...... DS...... IP=000B 13A0:000B C7063E000300 MOV WORD PTR [003E],0003 DS:003E=0000 -t AX...... DS...... IP=0011 13A0:0011 A13E00 MOV AX,[003E] DS:003E=0003 -t AX=0003...... DS...... IP=0014 13A0:0014 F72E3E00 IMUL WORD PTR [003E] -t AX=0009...... DS...... IP=0018 ZR PE 13A0:0018 A34000 MOV [0040],AX DS:0040=0000 -t AX=0009...... DS...... IP=001B 13A0:001B 90 NOP The unassembled machine code is obtained by using the debug command u as shown below: -u 13A0:0000 13A0:0000 9A0000A313 CALL 13A3:0000 13A0:0005 55 PUSH BP 13A0:0006 89E5 MOV BP,SP 13A0:0008 90 NOP 13A0:0009 90 NOP 13A0:000A 90 NOP 13A0:000B C7063E000300 MOV WORD PTR [003E],0003 13A0:0011 A13E00 MOV AX,[003E] 13A0:0014 F72E3E00 IMUL WORD PTR [003E] 13A0:0018 A34000 MOV [0040],AX 13A0:001B 90 NOP 13A0:001C 90 NOP 13A0:001D 90 NOP etc. Although an understanding of machine code is required to fully appreciate the above, it can be seen that the value 0003 is sent to an address 003E and then put into the AX register and finally (integer) multiplied with itself to give 0009 in the AX register. 2. Function results in Turbo Pascal. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Function results are returned through the registers, not the stack, as shown in the table below: Function result Registers ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄ shortint, byte AL integer, word AX longint DX:AX (most significant word: least significant word) real DX:BX:AX (most: middle: least significant word) single, double ) extended, comp ) Maths coprocessor top-of-stack register pointer DX:AX (segment:offset) string Pointer on stack to temporary storage ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 3. Interrupt Service Routines. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Interrupts provide the means for I/O devices to communicate with the CPU. An interrupt informs the processor that an external device requires attention. This causes the processor to suspend its current activity in order to respond to the interrupt. The processor, however, finishes executing the last instruction, saves the address of the next instruction on the stack, and then jumps to the special interrupt handling routine in a designated part of memory. After executing this routine, the processor returns to the suspended program by 'popping' the address of the next instruction from the stack. The pointers (or vectors) to the interrupt routines are stored in the lowest 1024 bytes of memory in the Vector Interrupt Table. Interrupts can be called by the assembly language instruction INT or from Turbo Pascal by the Intr procedure. Interrupts can be categorised as logical, hardware or software interrupts. Logical interrupts include INT $00, divide-by-zero and INT $04, overflow. Hardware interrupts include INT $09, keyboard and INT $0D hard disk controller. Software interrupts include calls to BIOS and DOS routines, such as the BIOS INT $10 which relates to the Video Display and the much used DOS INT $21, which can ascertain disk drive capacity, for example. The BIOS and DOS interrupts require that certain values are placed in the registers before the interrupt is called and generally return values in these registers after the interrupt. The complete detail of these register values must be found from a suitable reference, such as Advanced MS-DOS Programming 2nd. Ed. by Ray Duncan, published by Microsoft. DOS and BIOS Functions. Quick Reference by Que Corporation. Interrupts called from Assembly Language programs feature in the section on In-Line code. For interrupts from Turbo Pascal, the Intr procedure is called (or the MsDos procedure which is equivalent to Intr with an interrupt number of $21). The 'call' and 'return' values in the registers are facilitated by a variable of the type Registers, which is defined in the DOS unit as type Registers = record case integer of 0: (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags: word); 1: (AL,AH,BL,BH,CL,CH,DL,DH: byte); end; end; This is a variant record to map the 8-bit registers on top of their 16-bit equivalents. If in the VAR declaration part of the program an instance of this Registers type is declared, it is then possible to inspect the contents of the registers from the Integrated Development Environment using the Watch Window, as indicated below: Var Regs : Registers; Then Alt-D, W, A (or Crtl-F7) to enter the watch variable as Regs,r where the ,r indicates that the variable is a record type. A typical example illustrates the use of BIOS interrupt $10 with the register AH set to $0F before the call and returns AH = number of columns on the screen, AL = the display mode and BH = the active display page. The filename for this example is INTRDEMO.PAS and it is listed below: program GetDisplayMode; uses DOS, Crt; var Regs : Registers; { Registers is a record defined in DOS unit } Mode : Byte; Width : Byte; Page : Byte; DisplayMode : String[20]; begin Regs.AX := $0F00; { Sets AH = 0F for Get Display Mode function } Intr($10,Regs); { Interrupt 10 for ROM BIOS Video Services } Mode := Lo(Regs.AX); { Display Mode returned in AL } Width := Hi(Regs.AX); { Screen width returned in AH } Page := Hi(Regs.BX); { Screen page returned in BH } ClrScr; { Uses Crt } Writeln('Display Mode is ',Mode); Writeln('Screen Width is ',Width,' Columns'); Writeln('Screen Page is ',Page); end. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Another example shows the use of a DOS interrupt $21 with the AH register set to $1C and the DL register to the drive number before the call. The disk capacity is returned as shown in registers AL, CX, DX and in DS:BX. Program GetAllocationTableInfo; uses DOS,Crt; var Regs : Registers; Sectors_Cluster : Word; Bytes_Sector : Word; Clusters_Disk : Word; Seg_Address : Word; Off_Address : Word; Disk_Type : Word; Drive : Char; begin Write('Enter drive letter A,B or C: '); Readln(Drive); Drive := UpCase(Drive); case Drive of 'A' : Regs.DL := 1; 'B' : Regs.DL := 2; 'C' : Regs.DL := 3 end; Regs.AH := $1C; Intr($21,Regs); Sectors_Cluster := Lo(Regs.AH); Bytes_Sector := Regs.CX; Clusters_Disk := Regs.DX; Seg_Address := Regs.DS; Off_Address := Regs.BX; ClrScr; Disk_Type := MemW[Seg_Address:Off_Address]; Writeln('Disk type '); case Disk_type of 240 : Writeln('Not identifiable'); 248 : Writeln('Fixed disk'); 249 : Writeln('Double sided, 9 sectors per track (720K)'); 250 : Writeln('Double sided, 15 sectors per track (1.2M)'); 252 : Writeln('Single sided, 9 sectors per track'); 253 : Writeln('Double sided, 9 sectors per track (360K)'); 254 : Writeln('Single sided, 8 sectors per track'); 255 : Writeln('Double sided, 8 sectors per track') end; Writeln; If Disk_Type <> 240 then begin Writeln('Sectors per cluster = ',Sectors_Cluster); Writeln('Bytes per sector = ',Bytes_Sector); Writeln('Clusters per disk = ',Clusters_Disk); Writeln('Media descriptor address = ',Seg_Address,':',Off_Address); Writeln end; MemW[Seg_Address:Off_Address] := 240; end. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ REGS.TXT Revised 20.1.93