80x86 Assembly Language Programming J.K. Hardy, Sept '95 This short paper introduces 8086 assembly language programming using the DOS debug program. Note that, even with the latest version of DOS and '486 and Pentium processors, debug works only with the original 8086 microprocessor instructions and so is limited to 16-bit registers. For initial learning this approach is good enough, but for advanced learning you will need to work with a proper assembler such as Borland's TASM.EXE. 8086 registers A simple model of the 8086 registers available to the programmer is shown below... All 8086 registers are 16 bits in size although some - AX, BX, CX and DX, can also be used as two individual 8-bit registers. In that case the lower half of, for example, register AX would be called AL and the upper half would be AH. Each register tends to have a special use; these aren't general purpose registers. Part of the fun in understanding 8086 assembler is in learning which register, or which pair of registers, to use with which instruction. The Status bits (or Flags) change as numbers change in the individual registers. Often these changes are ignored but they are important when decisions must be made. For example, if a register happens to hold all zeros as the result of some operation then the Z bit in the status register will become "1". 8086 Addresses In DOS Mode (also called Real Mode) the 8086 can only handle 1 MByte of memory. But this requires 20 address bits which is more than any one register can hold. For addresses in main memory, one of the Segment:Offset register pairs is therefore needed to handle 20 bits. Four different pairs are available and the normal pair association is shown in the right side of the previous diagram. When operating as a pair the two 16-bit values are added together, with a 4-bit offset, to yield the 20-bit address - enough to identify one byte out of 1,048,000 bytes. Some basic 8086 instructions: The following list is just a brief introduction to some common 8086 instruction mnemonics. You will have to look elsewhere for a full description of all instructions and the intricies of their operation and different addressing mode options. Intel instructions tend to read backwards. Note how the first instruction below adds from the last register (BX) into the first register (AX). The answer is left in AX. ADD AX,BX - add the value held in BX to the value in AX MOV AX,05 - move a specific value into a register MOV AX,[200] - move a value from a memory location into AX AND AX,BX - logically AND BX into AX OR AX,BX - logically OR BX into AX XOR AX,BX - logically Exclusive-OR BX into AX NEG AX - form the 2's complement negative value NOT AX - form the 1's complement of AX INC AX - add one to AX DEC AX - reduce AX by one DIV BX - divide AX by BX. AX = quotient, DX = remainder MUL BX - multiply the value in BX with the value in AX. Result in DX+AX. Caution - When experimenting, don't mess with registers other than AX, BX, CX and DX. While it is unlikely, you could damage files or even format your hard disk. Examples 1 - This machine language program simply adds two numbers ( 5 + 7 = ?? ) It is written here as a debug script. You type all the material into a text editor and then run it in one shot. To create the source file you need a text editor. We will assume the MS-DOS EDIT.COM editor. Start the editor by typing EDIT C:\MATH.SRC Type in each of the 10 lines exactly as shown below. Finish each line by pressing the Enter key. Notice the blank line after ADD and the extra press of the Enter key after the last Q. The cursor should be sitting below the Q when you exit the editor. After this works correctly try changing the two values 05 and 07 to something different - but remember that you are working in hexadecimal. This assembly language program contains only three instructions - MOV, MOV and ADD. The remaining lines are debug commands needed to assemble (A) , trace (T) and quit (Q) the program. For learning try to mentally separate the program from the extra debug commands. ----------------------------------------A 100 MOV AX,05 MOV BX,07 ADD AX,BX T=100 T T Q ----------------------------------------Now run your program by typing.. DEBUG < C:\MATH.SRC The "trace" command T runs each line of your program one step at a time so you can see the registers change. 2 - Here is another. You could call it MATH2.SRC The program calculates ( 5 + 3 ) * 7 = ??? The result of the multiplication will be 32 bits. The lower 16 bits will be left in AX. The upper 16 bits, if needed, will use the DX register. The program itself consists of five 8086 instructions. The rest are debug commands. ---------------------------------------------A 100 MOV AX,05 MOV BX,03 ADD AX,BX MOV BX,07 MUL BX T=100 5 Q --------------------------------------------If you can't see all of this program on the screen when it runs, try this... DEBUG < MATH2.SRC > RESULTS.TXT Then use the text editor to look at the results.txt file. 3 - The third example adds four numbers together. You could call it ADD4.SRC. What is new in this case, is that the numbers are already in memory, starting at location 200, when the first instruction starts. They were placed there with the examine (E) command in debug. The [200] addresses in memory are called Offset values. The exact memory location is controlled by a segment register that is under debug's control. We don't modify that value in this example. --------------------------------------------E 200 05 06 07 08 A 100 MOV AL,[200] ADD AL,[201] ADD AL,[202] ADD AL,[203] T=100 4 Q --------------------------------------------- 4 - The fourth example moves to full segment offset addressing. It places a flashing letter 'A' someplace on the screen. The exact memory location is B800:0950 - as defined by the DI and DS registers. The 41 value is the ASCII character 'A' and the BF value is the colour and flashing attribute for the screen. Notice that the DS register could not be loaded directly. We have to load the AX register and then copy that into the DS register. ------------------------------------------------A 100 MOV AX,B800 MOV DS,AX MOV DI,0950 MOV AX,BF41 MOV [DI],AX T=100 5 Q ------------------------------------------------- 5 - This next program is different. Debug will create an executable file which will then be able to run on its own. Debug will no longer be needed. Use your editor to type this file up as MESSAGE.SRC. Type in each line as shown. Don't leave any extra spaces at the beginning of each line - no indents. Be sure to leave one blank line after DB "Hello There" and also to include an extra [Enter] after the final "Q". Check your typing carefully and then save the file. ------------------------------------------------------------N MESSAGE.COM A MOV DX,10B MOV AH,9 ; Request DOS to INT 21 ; display message. MOV AH,4C ; Request DOS to INT 21 ; terminate program. DB "Hello There ! $" RCX 3F W Q ------------------------------------------------------------Then type the following command... DEBUG < MESSAGE.SRC Your program will be assembled and turned into MESSAGE.COM Run your program by typing MESSAGE. This program uses two features built into DOS itself: - a request to display the message. Code inside DOS does the actual displaying. - a request to terminate the program. Again, DOS code handles this. The request mechanism is a "DOS function call" which involves: MOV AH,?? and INT 21 5 - Ten Second Time delay ------------------------------------------------N TEN_SEC.COM A 100 PUSH ES PUSH BX PUSH AX MOV AX,0000 MOV ES,AX MOV BX,46C ES: MOV AX,[BX] ADD AX,00A0 ; This is the time value. JB 11B ES: CMP AX,[BX] JNZ 113 JMP 126 NOP ES: TEST BYTE PTR [BX],00 JNZ 11B ES: CMP AX,[BX] JNZ 121 POP AX POP BX POP ES MOV AH,4C INT 21 RCX 30 W Q DEBUG Can be used to: - Display and change memory locations. - Read and write to I/O port locations. - Assemble mnemonic instructions for the 8088. - Unassemble instructions. - Run and single step through programs. - Read and write from/to disk files. Debug is started with: DEBUG - if you don't need a specific file. DEBUG Junk.abc - to work with a specific file. All commands begin with a single letter: A [address] - assemble from 8088 mnemonics. C range address - compare two ranges. C100,1FF 300 D [range] - display memory. E address [list] - examine/enter memory values. F range list - fill a range of memory with some values. G [=address [address]] - execute a program with optional breakpoints. H value value - perform Hexadecimal addition and subtraction. I port - input a byte from an IO port. L [address [drive:logical_sector]] - load information from disk sectors. M range address - move a block of memory. N filename - provide a file name for L or W commands. O port byte - output a byte to an IO port. Q - quit debug. R [register_name] - display or alter registers. T [=address] [step] - trace several instruction steps. U [range] - unassemble code. W [address [drive:logical_record]] - write information to disk.