Learn Assembly Language / Comprehensive Review
3, 5, 7, and 8 are programming question sources. The compulsory exam format at our Nanchang campus consists of 20 points for multiple choice, 30 points for short answers, and 50 points for programming. You can find the PDF version of this article in the group files or database.
0. Overview of Learning Review
Machine language is a collection of machine instructions.
Assembly language is a mnemonic for machine instructions, corresponding one-to-one with machine instructions.
The process of writing a program in assembly language: the programmer writes the source program in assembly language, then uses an assembler to compile it into machine code, which is ultimately executed by the computer.
Components of assembly language instructions: assembly instructions, pseudo-instructions, and other symbols.
In memory, instructions and data are indistinguishable; both are binary information.
Memory units are sequentially numbered starting from zero.
A memory unit can store 8 bits, i.e., an 8-bit binary number.
The CPU’s read/write process for memory: (1) The CPU sends address information via the address bus; (2) The CPU sends memory read/write instructions via the control bus, selects the memory chip, and notifies it whether to read data from or write data to it; (3) The memory sends data from a specific unit to the CPU via the data bus, or the CPU sends data to the memory unit via the data bus.
The width of the address bus determines the CPU’s addressing capability.
The width of the data bus determines the amount of data transferred in one operation between the CPU and other components.
The width of the control bus determines the CPU’s control capability over other components in the system.
A typical CPU consists of an arithmetic logic unit, a controller, registers, and other components, all connected by an internal bus.
The arithmetic logic unit processes information, registers store information, the controller manages the operation of various components, and the internal bus connects these components for data transfer.
DEBUG commands
R | View or change the contents of CPU registers |
---|---|
D | View contents in memory |
E | Modify contents in memory |
U | Translate machine instructions in memory to assembly instructions |
T | Execute a machine instruction |
A | Write a machine instruction in memory in assembly instruction format |
P | Skip the loop instruction |
G | Execute to a specified memory address |
A word unit: a memory unit that stores a word-sized data (16 bits), composed of two consecutively addressed memory units, using little-endian storage (high bits in high addresses, low bits in low addresses).
When accessing a memory unit with the mov instruction, you can specify only the offset address in the mov instruction; in this case, the segment address defaults to the DS register.
[address] represents a memory unit with an offset address of address.
When transferring word-sized data between memory and registers, high address units correspond to high 8-bit registers, and low address units correspond to low 8-bit registers.
The segment address and offset address of the stack top are stored in the SS and SP registers.
Execution steps of the PUSH instruction: ① SP=SP-2; ② Data is sent to the word unit pointed to by SS:SP.
Execution steps of the POP instruction: ① Data is read from the word unit pointed to by SS:SP; ② SP=SP+2.
At any moment, SS:SP points to the top element of the stack.
The 8086 CPU only records the stack top; the size of the stack space must be managed manually.
When using the stack to temporarily store data, the order of popping must be the reverse of the order of pushing.
The PUSH and POP instructions are essentially memory transfer instructions; note to apply them flexibly.
In assembly source programs, data cannot start with a letter; A000h must be written as 0A000h.
[idata] is interpreted by DEBUG as a memory unit and by the compiler as idata, so in assembly source programs, if you access a memory unit with an instruction, you must use […] to represent the memory unit in the instruction. If you directly give the offset address of a memory unit in [ ] with a constant idata, you must explicitly provide the segment address in the segment register before the [ ], for example, mov al, ds:[0], where “ds:” is called the segment prefix. If you use a register like bx in [ ], the segment address defaults to ds.
dw ? defines word-sized data, db ? defines byte-sized data, dd ? defines double word-sized data, ? Dup (?) defines data in bulk.
The pseudo-instruction end marks the entry address label of the program.
The method of defining a segment is the same as defining a code segment; different segments have different names, and when referencing segment addresses, the segment name acts as a label representing the segment address.
Transfer instructions are divided into intra-segment transfers (only modify IP) and inter-segment transfers (modify both CS and IP).
Intra-segment transfers are divided into short transfers (-128~127) and near transfers (-32768~32767).
The transfer instructions of the 8086 CPU include: unconditional transfer instructions, conditional transfer instructions, loop instructions, procedures, and interrupts.
The function of the offset operator is to obtain the offset address of a label.
The significance of transferring based on displacement is to ensure that a segment of code can execute correctly when loaded at different memory locations.
1. Working Principle of the 8086 CPU
(1) Read instructions from the memory unit pointed to by CS:IP, and the read instruction enters the instruction buffer.
(2) IP=IP+length of the read instruction, thus pointing to the next instruction.
(3) Execute the instruction. Go back to step 1 and repeat this process.
After powering on or resetting the 8086 CPU, CS and IP are set to CS=FFFFH, IP=0000H.
2. Method of Providing Physical Addresses by the 8086 CPU and Locating Memory Addresses
The 8086 CPU uses a method of combining two 16-bit addresses internally to form a 20-bit physical address.
The address adder combines the physical address using the formula Physical Address = Segment Address × 16 + Offset Address
.
The essence is that when the CPU accesses memory, it adds a base address (segment address × 16) to an offset address relative to the base address to provide the physical address of the memory unit.
3. [BX] and LOOP Instructions
[BX]: represents a memory unit whose offset address is in bx.
LOOP instruction: loop label, when executed by the CPU, performs two operations: ① (cx)=(cx)-1; ② Checks the value in cx; if not zero, it jumps to the label to execute the program; if zero, it continues down.
Programming, sequentially transferring data 0~63 (3FH) to memory 0:200-0:23F, using only 9 instructions, including “mov ax,4c00h” and “int 21h”.
assume cs:code
code segment
mov ax,0020h
mov ds,ax
mov bx,0
mov cx,40h ; loop 64 times
s: mov [bx],bx
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
4. Two Basic Issues in Data Processing and Comprehensive Application of Addressing Modes
Where is the data being processed?
For machine instructions: memory, CPU internal registers, CPU internal instruction buffers.
For assembly language: immediate values (idata), registers, segment addresses, and offset addresses.
How long is the data to be processed?
The 8086 CPU can process two sizes of data: byte and word, so in machine instructions, it is necessary to specify whether the instruction is performing word operations or byte operations. Assembly language handles this in the following ways.
(1) By specifying the size of the data to be processed through the register name.
(2) In the absence of a register name, using the operator X ptr to specify the length of the memory unit, where X can be word or byte in assembly instructions.
(3) Other methods, some instructions default to accessing either sub-units or byte units, such as push.
Addressing modes

Experiment 7, Application of Addressing Modes in Structured Data Access
assume cs:code,ds:data,ss:stack
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
; Year data
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 34590,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
; Income data
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
; Employee number data
data ends
table segment
db 21 dup ('year summ ne ?? ')
table ends
stack segment
dw 8 dup (0)
stack ends
code segment
start:mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,10h
mov ax,table
mov es,ax
mov bx,0 ; increment in table by 10h
mov di,0 ; increment in data
; First loop for year data
mov cx,21
s10: mov si,0 ; increment within element
push cx
mov cx,4
s00: mov al,ds:[di]
mov es:[bx+0h+si],al
inc si
inc di
loop s00
add bx,10h
pop cx
loop s10
; Second loop for income data
mov bx,0
mov cx,21
s11: mov si,0 ; increment within element
push cx
mov cx,4
s01: mov al,ds:[di]
mov es:[bx+5+si],al
inc si
inc di
loop s01
add bx,10h
pop cx
loop s11
; Third loop for employee number data
mov bx,0
mov cx,21
s12: mov si,0 ; increment within element
push cx
mov cx,2
s02: mov al,ds:[di]
mov es:[bx+10+si],al
inc si
inc di
loop s02
add bx,10h
pop cx
loop s12
mov di,0
; Fourth loop to calculate and write average income data
mov bx,0
mov cx,21
s13: mov si,0
mov ax,es:[bx+5] ; low 16 bits
mov dx,es:[bx+7] ; high 16 bits
div word ptr es:[bx+10]
mov es:[bx+13],ax
add bx,10h
loop s13
mov ax,4c00h
int 21h
code ends
end start
5. CALL and RET Instructions and Subroutine Design
The ret instruction uses data from the stack to modify the content of IP, thus achieving near transfer (pop IP);
the retf instruction uses data from the stack to modify the contents of CS and IP, thus achieving far transfer (pop IP / pop CS).
The call instruction pushes the current IP or CS and IP onto the stack, then transfers.
We typically use call and ret instructions to implement the subroutine mechanism, with the subroutine framework as follows:
call label
label:
instruction
ret
Using registers to store parameters and results is the most common method.
Data that needs to be passed in bulk is placed in memory, and the starting address of the memory space is placed in a register.
At the beginning of the subroutine, all the contents of the registers used in the subroutine are saved, and before returning from the subroutine, they are restored, which can be done using the stack to save the contents of the registers.
Programming, displaying a string
Name: show_str
Function: Display a null-terminated string at a specified position with a specified color.
Parameters: (dh)=line number (range 0~24), (dl)=column number (range 0~79)
(cl)=color, ds:si points to the string’s starting address
Returns: none
Application example: Display the string in the data segment in green at row 8, column 3 on the screen.
assume cs:code
data segment
db 'This is my assembly thing 612348',0
data ends
code segment
start: mov dh,10 ; line number 0-24
mov dl,15 ; column number 0-79
mov cl,7 ; color
mov ax,data
mov ds,ax
mov si,0 ; ds:si points to string address
call show_str
mov ax,4c00h
int 21h
show_str:
push ax
push bx
push es
push di
push cx
push dx
mov ax,0b800h
mov es,ax ; store video memory address in es
mov al,dh
mov bl,160
mul bl ; row * 160 bytes to ax
mov bh,0
mov bl,dl
add ax,bx
add ax,bx ; column data stored in bx and added twice to ax
mov di,ax ; di stores the calculated video memory offset address
mov dx,0
mov dl,cl ; dl stores color value, freeing cl for loop check
s: mov ch,0
mov cl,ds:[si]
jcxz ok ; use cx and jcxz to check loop
mov byte ptr es:[di],cl
mov byte ptr es:[di+1],dl
inc si
add di,2
jmp short s
ok:
pop dx
pop cx
pop di
pop es
pop bx
pop ax
ret
code ends
end start
Programming, solving division overflow issues
Name: divdw
Function: Perform division operations that will not cause overflow, with the dividend being dword type, divisor being word type, and result being dword type.
Parameters: (ax)=low 16 bits of dword type data
(dx)=high 16 bits of dword type data
(cx)=divisor
Returns: (dx)=high 16 bits of the result, (ax)=low 16 bits of the result
(cx)=remainder
Application example: Calculate 1000000/10 (F4240H/0AH)
Tip
Provide a formula:
X: Dividend, range: [0, FFFFFFFF]
N: Divisor, range: [0, FFFF]
H: High 16 bits of X, range: [0, FFFF]
L: Low 16 bits of X, range: [0, FFFF]
int(): descriptive operator, takes the quotient, e.g., int(38/10) =3
rem(): descriptive operator, takes the remainder, e.g., rem(38/10)= 8
Formula: X/N = int(H/N) * 65536 + [rem(H/N) * 65536 + L] / N
This formula transforms the potentially overflowing division operation: X/N, into multiple non-overflowing division operations. All division operations on the right side of the equation can be performed using the div instruction, which will not cause division overflow.
assume cs:code
code segment
start: mov ax,4240h
mov dx,000fh
mov cx,0ah
call divdw
mov ax,4c00h
int 21h
divdw:
push bx
push si
push di
mov si,dx ; store high 16 bits
mov di,ax ; store low 16 bits
mov bx,cx ; store divisor
mov ax,dx
mov dx,0
div cx ; rem h/n
; remainder h/n is in dx, needs to be used as high part of dividend
mov ax,di ; directly put low part into ax
div cx ; now dx & ax is the lower part, perform division on n, result in ax quotient dx remainder
push ax
push dx
mov ax,si
mov dx,0
div cx
; quotient is in ax
mov dx,ax
pop cx
pop ax
pop di
pop si
pop bx
ret
code ends
end start
Value display
Name: dtoc
Function: Convert word-sized number to a string representing a decimal number, with the string ending with 0.
Parameters: (ax)=word-sized data, ds:si points to the string’s starting address
Returns: none
Application example: Programming to display the data 12666 in decimal form at row 8, column 3 on the screen in green. In the display, we call the first subroutine of this experiment, show_str.
assume cs:code
data segment
db 10 dup (0)
data ends
code segment
start: mov ax,12666
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov dh,8
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
dtoc: push ax
push dx
push cx
push si
d_c: mov cx,ax ; calculate remainder one by one
jcxz d_ok ; each time will put ax's quotient into cx to check if 0
mov dx,0
mov cx,10
call divdw
add cl,30h ; get ascii code
push cx ; push to stack to avoid reversing
inc si ; record the number of pushes
jmp short d_c
d_ok: ; first give the number of pushes stored in si to cx as loop count
mov cx,si
mov si,0
d_p: ; pop to dx and then give to ds:[si] corresponding data block
pop dx
mov ds:[si],dl
inc si
loop d_p ; loop for count, cx records the count
pop si
pop cx
pop dx
pop ax
ret
divdw:
show_str:
; parameters dh line number dl column number cl color ds:si string address
code ends
end start
6. Functions of Each Flag Bit in the Flag Register
The flag register can store certain execution results of related instructions, providing a basis for the CPU to execute related instructions and controlling the CPU’s operational mode.
The 8086 CPU’s flag register has 16 bits, and the information stored is commonly referred to as the Program Status Word (PSW).
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF | DF | IF | TF | SF | ZF | AF | PF | CF |
6: ZF: Zero Flag, records whether the result of the executed instruction is 0; if the result is 0, then ZF=1.
2: PF: Parity Flag, records whether the number of 1 bits in the result of the executed instruction is even.
7: SF: Sign Flag, records whether the result of the executed instruction is negative.
0: CF: Carry Flag, records the carry value of the highest significant bit in unsigned arithmetic operations, or the borrow value from higher bits.
11: OF: Overflow Flag, records whether the result of signed arithmetic operations has overflowed.
10: DF: Direction Flag, controls the increment or decrement of si and di after each operation in string processing instructions; DF=0 increments.
8: TF: Related to interrupt handling, set to 0 to avoid re-triggering interrupts while executing the interrupt handling program.
9: IF: Related to interrupt handling, set to 0 to not respond to maskable interrupts.
After executing cmp ah,bh
If SF=1, OF=0, it indicates no overflow, logical result = actual result, actual result is negative, (ah) < (bh).
If SF=1, OF=1, it indicates overflow, logical result ≠ actual result, actual result is negative, (ah) > (bh).
If SF=0, OF=1, it indicates overflow, logical result ≠ actual result, actual result is positive, (ah) < (bh).
If SF=0, OF=0, it indicates no overflow, logical result = actual result, actual result is non-negative, (ah) ≥ (bh).
7. Causes of Interrupts, Process, and Writing Interrupt Handlers
Interrupt: A capability of the CPU, indicating that it can detect special information sent from outside the CPU or generated internally after completing the currently executing instruction, and can immediately process the received information. The CPU does not continue executing the (just completed) instruction but instead processes this special information called interrupt information.
Interrupt information from within the CPU:
Division error (overflow), single-step execution, execution of into instruction, instruction int instruction.
The 8086 CPU uses a data called interrupt type code to identify the source of interrupt information.
The interrupt type code is a byte-sized data that can represent 256 types of interrupt information sources.
The source of interrupt information is called the interrupt source.
After the CPU receives interrupt information, it needs to process it. The program we write to handle interrupt information is called the interrupt handler.
The CPU uses an 8-bit interrupt type code to find the entry address of the corresponding interrupt handler through the interrupt vector table.
The interrupt vector table is a list of interrupt vectors.
An interrupt vector is the entry address of the interrupt handler.
The interrupt vector table is stored in memory; for the 8086 CPU, the interrupt vector table is specified to be stored at memory address 0. The first 1024 units from 0000:0000 to 0000:03FF store the interrupt vector table; if stored elsewhere, the 8086 CPU will not be able to read it.
In the interrupt vector table, each entry occupies two words, with the high address word storing the segment address and the low address word storing the offset address.
The memory unit address storing the offset address of the interrupt handler corresponding to interrupt source N is N×4.
The memory unit address storing the segment address of the interrupt handler corresponding to interrupt source N is N×4+2.
Interrupt process
(1) Obtain the interrupt type code N
(2) pushf
(3) TF=0 (to avoid re-triggering interrupts while executing the interrupt handler), IF=0 (do not respond to maskable interrupts)
(4) push CS
(5) push IP
(6) (IP)=(N*4), (CS)=(N*4+2)
Special case: For example, after executing an instruction to transfer data to the ss register, even if an interrupt occurs, it will not respond.
Steps to write an interrupt handler
(1) Save the registers used
(2) Process the interrupt
(3) Restore the registers used
(4) Return using the iret instruction
Generally, the space corresponding to the interrupt vector table from 0000:0200 to 0000:02FF is empty, and the operating system and other applications do not occupy it. We can send the interrupt handler to this memory space as long as we ensure that the interrupt handler does not exceed 256 bytes.
Install the interrupt handler and set the interrupt vector, taking interrupt 0 as an example, Experiment 12.
assume cs:codesg
codesg segment
start: ; d0 install program
; es:di points to target address
mov ax,0
mov es,ax
mov di,200h
; ds:si points to source address
mov ax,cs
mov ds,ax
mov si,offset d0
mov cx,offset d0end-offset d0
cld
rep movsb
; set interrupt vector table
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
d0: ; display string
jmp short d0start
db 'divide error!'
d0start:mov ax,cs
mov ds,ax
mov si,202h
mov ax,0b800h
mov es,ax
mov di,12*160+36*2
mov cx,13
s: mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
d0end: nop
codesg ends
end start
Interrupts triggered by the int instruction
int n, where n is the interrupt type code, functions to trigger the interrupt process.
Using this instruction, you can call any interrupt handler in the program.
Write and install the int 7ch interrupt routine, which displays a null-terminated string, with the interrupt routine installed at 0:200.
Parameters: (dh)=line number, (dl)=column number, (cl)=color, ds:si points to the string’s starting address.
assume cs:code
code segment
start: ; install program
; target area es:di
mov ax,0
mov es,ax
mov di,200h
; source area ds:si
mov ax,cs
mov ds,ax
mov si,offset showstr
mov cx,offset showend-offset showstr
cld
rep movsb
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
showstr:push ax
push cx
push si
push di
push bx
; provide segment address
mov ax,0b800h
mov es,ax
; start calculating offset address
mov bx,0 ; use bx to store the calculated result, al for intermediate storage
mov al,dh
mov bh,160
mul bh ; row * 160 stored in ax
mov dh,0
add dl,dl ; column number multiplied by 2
add ax,dx ; ax stores the offset address
; store in di, es:di is done
mov di,ax
; free ax, continue using
mov ax,0
mov al,cl ; color data to bl, cx to be used for comparison
; si points to the string to be displayed, ch is set to 0, if all 0, exit loop
change: mov cl,[si]
mov ch,0
jcxz ok
; normally execute transfer content to video memory area
mov byte ptr es:[di],cl
mov byte ptr es:[di+1],al
inc si
add di,2
loop change
ok:
pop bx
pop di
pop si
pop cx
pop ax
iret
showend:nop
code ends
end start
Write and install the int 7ch interrupt routine, which completes the functionality of the loop instruction.
Parameters: (cx)=loop count, (bx)=offset.
assume cs:code
code segment
start: ; install program
; target area es:di
mov ax,0
mov es,ax
mov di,200h
; source area ds:si
mov ax,cs
mov ds,ax
mov si,offset lp
mov cx,offset lpend-offset lp
cld
rep movsb
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
lp:
push bp
mov bp,sp
dec cx
jcxz lpret
add [bp+2],bx
lpret: pop bp
iret
lpend: nop
code ends
end start
The following program displays four lines of English poetry on the 2nd, 4th, 6th, and 8th rows of the screen.
assume cs:code
code segment
s1: db 'Good, better, best,','$'
s2: db 'Never let it rest,','$'
s3: db 'Till good is better','$'
s4: db 'And better, best.','$'
s: dw offset s1,offset s2,offset s3,offset s4
row: db 2,4,6,8
start: mov ax,cs
mov ds,ax
mov bx,offset s
mov si,offset row
mov cx,4
ok: mov bh,0 ; set cursor
mov dh,[si]
mov dl,0
mov ah,2
int 10h
mov dx,[bx] ; set content
mov ah,9
int 21h
inc si
add bx,2
loop ok
mov ax,4c00h
int 21h
code ends
end start
8. Interface Chips and Port Access
In PC systems, the CPU can locate up to 64KB of different ports, with port addresses ranging from 0 to 65535.
Port read/write instructions: in and out.
Only ax or al can be used to store data read from the port or data to be sent to the port.
The time information stored in CMOS RAM, with each piece of information being 1 byte long, is stored in BCD code:
Year | Month | Day | Hour | Minute | Second |
---|---|---|---|---|---|
9 | 8 | 7 | 4 | 2 | 0 |
Programming to display the current date and time in the format of year, month, day, hour, minute, second.
assume cs:code
data segment
db 9,16,8,16,7,20,4,17,2,17,0 ; year/month/day hour:minute:second
data ends
code segment
start: mov cx,0FFFFh
call start0
loop start
mov ax,4c00h
int 21h
start0:
push cx
mov ax,data
mov ds,ax
mov si,0
mov cx,11
mov di,7A8h
ok: mov ah,16
cmp [si],ah
je to1
mov ah,20
cmp [si],ah
je to2
mov ah,17
cmp [si],ah
je to3
mov dl,[si]
call read
call showstr
inc si
add di,4
loop ok
pop cx
ret
to1:
mov al,2Fh
call show
inc si
add di,2
loop ok
to2:
mov al,20h
call show
inc si
add di,2
loop ok
to3:
mov al,3Ah
call show
inc si
add di,2
loop ok
read:
push dx
push cx
; read port data, parameter dl address
mov al,dl
out 70h,al
in al,71h
mov ah,al
mov cl,4
shr ah,cl
and al,00001111b
add ah,30h
add al,30h
pop cx
pop dx
ret
showstr:
push bx
mov bx,0b800h
mov es,bx
mov byte ptr es:[di],ah
mov byte ptr es:[di+2],al
pop bx
ret
show:
push bx
mov bx,0b800h
mov es,bx
mov byte ptr es:[di],al
pop bx
ret
code ends
end start
The CPU communicates with external devices through ports.
External interrupt sources are divided into: maskable interrupts and non-maskable interrupts.
Maskable interrupts: external interrupts that the CPU can choose not to respond to, depending on the setting of the IF bit.
Non-maskable interrupts: external interrupts that the CPU must respond to, with a fixed interrupt type code of 2.
Almost all external interrupts triggered by peripherals are maskable interrupts.
When a key is pressed on the keyboard, a scan code is generated and written into the register of the interface chip at port address 60h.
When a key is released, a scan code is also generated and written into the register.
The code generated when a key is pressed is called a make code, and the code generated when a key is released is called a break code, with break code = make code + 80h.
When keyboard input reaches port 60h, the related chip sends a maskable interrupt with an interrupt type code of 9.
If the CPU detects interrupt information and IF=1, it responds to the interrupt, triggering the interrupt process and executing the int9 interrupt routine.
The int9 interrupt routine reads the scan code; if it is a character key’s scan code, it places that scan code and the corresponding character code into the BIOS keyboard buffer in memory; if it is a control key or toggle key’s scan code, it converts it into a status byte (a byte that records the key status using binary bits) and writes it into the memory unit storing the status byte, then controls the keyboard system accordingly.
The keyboard buffer can store 15 keyboard inputs, with each keyboard input stored in a word unit, high byte for scan code, low byte for character.
0040:17 unit stores the keyboard status byte.
Experiment 15, programming to install a new int 9 interrupt routine, function: In DOS, when the “A” key is pressed, unless released, it displays “A” across the screen; other keys are processed normally.
assume cs:code
stack segment
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
push cs
pop ds
mov ax,0
mov es,ax
mov si,offset int9 ; ds:si source address cs:int9
mov di,204h ; es:di target address 0:204
mov cx,offset int9end-offset int9
cld
rep movsb
push es:[9*4]
pop es:[200h] ; 200 202 store original int9 cs and ip
push es:[9*4+2]
pop es:[202h]
cli
mov word ptr es:[9*4],204h ; replace int9 entry address
mov word ptr es:[9*4+2],0
sti
mov ax,4c00h
int 21h
int9: push ax
push bx
push cx
push es
in al,60h
pushf ; match call within iret instruction to prevent stack data disorder
call dword ptr cs:[200h] ; internally call the original int9 address
cmp al,9eh
jne int9ret
mov ax,0b800h
mov es,ax
mov cx,0fa0h
mov al,41h
mov bx,0
s: mov byte ptr es:[bx],al
add bx,2
loop s
int9ret:pop es
pop cx
pop bx
pop ax
iret
int9end:nop
code ends
end start
9. Important Instructions and Concepts
jmp, conditional transfer instructions, call, ret, push, pop, int, iret, cmp, loop, segmentation, addressing modes, etc.
Operation | Instruction | Example or Note |
---|---|---|
Modify the value of the object | mov object1, object2 | mov ax,8 mov ax,bx mov ax,[0] mov [0],ax |
Addition | add object1, object2 | Similar to above |
Subtraction | sub object1, object2 | Similar to above |
Carry addition (ax)=(ax)+(ax)+CF | adc object1, object2 | adc ax,1 |
Borrow subtraction (ax)=(ax)-(bx)-CF | sbb object1, object2 | sbb ax,0020H |
Simultaneously modify CS, IP content | jmp segment address:offset address | jmp 2AE3:3 |
Only modify IP content | jmp a valid register | jmp ax |
Push object data onto the stack | push object | push ax push ds push [0] |
Pop data from the stack into an object | pop object | Similar to above |
Bitwise logical AND, can clear to zero | and object1, object2 | and al,10111111B |
Bitwise logical OR, can set to one | or object1, object2 | or al,01000000B |
Division | div reg/memory object | div ds:[0] |
Multiplication | mul reg/memory object | mul bx |
IP=IP+8-bit displacement | jmp short label | jmp instruction transfers based on displacement |
IP=IP+16-bit displacement | jmp near ptr label | |
Modify CS (high) and IP (low) | jmp far ptr label | |
Modify IP | jmp word ptr memory unit address | |
Modify CS (high) and IP (low) | jmp dword ptr memory unit address | |
If (cx)=0, transfer to label, otherwise do nothing | jcxz label | 8-bit displacement=label address – jcxz instruction’s first byte address |
(cx)=(cx)-1; if (cx)≠0, transfer to label, otherwise continue down | loop label | 8-bit displacement=label address – loop instruction’s first byte address |
Equivalent to push IP; jmp near ptr label | call label | |
Equivalent to push CS; push IP; jmp far ptr label | call far ptr label | |
Equivalent to push IP; jmp 16-bit reg | call 16-bit reg | |
Equivalent to push IP; jmp word ptr memory unit address | call word ptr memory unit address | |
Equivalent to push CS; push IP; jmp dword ptr memory unit address | call dword ptr memory unit address | |
Object1 minus object2 does not store the result; only changes the flag bits based on the calculation result | cmp object1, object2 | Determine comparison result based on SF and OF flag bits |
If equal, transfer | je | ZF=1 |
If not equal, transfer | jne | ZF=0 |
If less than, transfer | jb | CF=1 |
If not less than, transfer | jnb | CF=0 |
If greater than, transfer | ja | CF=0 and ZF=0 |
If not greater than, transfer | jna | CF=1 or ZF=1 |
Transfer ds:si to es:di, incrementing or decrementing si and di based on DF | movsb | |
Transfer ds:si to es:di, incrementing or decrementing si and di twice based on DF | movsw | |
Repeat execution of a certain instruction based on cx value | rep | Used with movsb |
DF=0 | cld | |
DF=1 | std | |
Push the value of the flag register onto the stack | pushf | |
Send data from the stack to the flag register | popf | |
Similar to pop IP pop CS popf | iret | |
Call the handler for interrupt n | int n | |
Read data from the port | in al/ax, port number | |
Write data to the port | out port number, al/ax | |
Logical left shift, filling low bits with 0; the last bit shifted out is written to CF | shl object, count | shl al,1 |
Logical right shift, filling high bits with 0; the last bit shifted out is written to CF | shr | shr al,cl |
IF=1 | sti | |
IF=0 | cli |
Details of the Div instruction
Dividend | Divisor | Quotient / Remainder |
---|---|---|
AX 16 bits | reg or memory unit 8 bits | AL / AH |
DX high 16 bits, AX low 16 bits | reg or memory unit 16 bits | AX / DX |
Details of the Mul instruction
Multiplicand | Multiplier | Product |
---|---|---|
AL 8 bits | 8-bit reg or memory byte unit | AX |
AX 16 bits | 16-bit reg or memory unit | DX high / AX low |
Addresses with a “:” at the end of the label can only be used in the code segment, not in other segments.
Data labels: a type of label that describes the length of a unit
a db 1,2,3,4,5,6,7,8
b dw 0
a and b labels do not have a colon after them; they simultaneously describe the memory address and unit length.
If you want to use data labels in the code segment to access data, you need to use the pseudo-instruction assume to link the segment where the label is located with a segment register.
Data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a,b
data ends
The seg operator functions to obtain the segment address of a certain label.
Direct addressing table: allows you to directly calculate the position of the element you are looking for based on the data.
setscreen: jmp short set
table: dw sub1,sub2,sub3,sub4
set: push bx
cmp ah,3
ja sret
mov bl,ah
mov bh,0
add bx,bx
call word ptr table[bx]
sret: pop bx
ret
Note that using direct addressing tables in interrupt routines will lead to incorrect subroutine addresses after installation.
This article was originally published on 2022/11/28