Turbo Assembler
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Pascal is een taal die weliswaar dicht op het ijzer kan zitten, maar toch vooral behoorlijke abstractie biedt en daarmee ook veiligheid en leesbaarheid. Maar uiteindelijk voert de CPU alleen machinetaalinstructies uit. Assembler is de abstractie die weliswaar leesbaar is, maar anders dan in welke hogere taal dan ook, geldt bij assembler dat iedere instructie één op één vertaald wordt naar een machinetaalinstructie. Zeker in DOS waar geen sprake is van een MMU, betekent dat dat je niet dichter op het ijzer kan komen dan via assembler.
De 8086 heeft maar een handvol registers. Ze zijn formeel “general purpose”, maar in de praktijk hebben veel instructies vaste voorkeuren of zelfs verplichtingen. De registers AX, BX, CX en DX zijn 16-bits en kunnen als twee 8-bits registers, AH en AL, enzovoort, worden gelezen.
AX – de accumulator. Veel instructies gebruiken AX impliciet.
MOV AX,1234h ; zet 1234h in AX
ADD AX,BX ; tel BX bij AX op, resultaat in AX
IN AL,60h ; lees van poort 60h (toetsenbord) in AL
MUL BX ; vermenigvuldig AX met BX → resultaat in DX:AX
BX – vaak gebruikt als basisregister voor adressen.
MOV BX,OFFSET DATA ; zet adres van DATA in BX
MOV AL,[BX] ; lees byte uit geheugen waar BX naar wijst
CX – counter, gebruikt door loops.
MOV CX,10
LOOP MyLabel ; verminder CX, spring als CX≠0
DX – dataregister, betrokken bij I/O en bij MUL/DIV.
MOV DX,3F8h ; seriële poort
OUT DX,AL ; schrijf AL naar poort in DX
CS: code segment. Samen met IP bepaalt dit waar de CPU instructies haalt.
DS: data segment. Standaard voor variabelen.
SS: stack segment.
ES: extra segment, vaak gebruikt bij stringinstructies.
SP: stack pointer, positie in de stack.
BP: base pointer, handig om via de stack parameters te benaderen.
MOV BP,SP
MOV AX,[BP+4] ; lees parameter vanaf stack
SI/DI: index registers, vooral bij stringinstructies.
MOV SI,OFFSET SRC
MOV DI,OFFSET DEST
MOV CX,100
REP MOVSB ; kopieer 100 bytes van [SI] naar [DI]
Daarnaast zijn er de FLAGS die condities opslaan (zero, carry, sign, overflow) en IP (Instruction Pointer).
Load/store versus move model
De 8086 volgt grotendeels het move model: je kunt data vrij verplaatsen tussen registers en geheugen. Je kunt dus schrijven:
MOV AX,[1234h] ; laad uit geheugen
MOV [2000h],BX ; schrijf naar geheugen
MOV CX,AX ; register naar register
Dat lijkt vanzelfsprekend, maar is het niet. In pure load/store-architecturen (zoals ARM, of RISC-V) kan je alleen laden of opslaan via specifieke instructies (LDR, STR) en mag je rekenwerk alleen op registers doen.
De 8086 staat dichter bij de PDP-11-stijl (MOV-instructies), terwijl moderne RISC-architecturen bewust voor het load/store-model kiezen omdat het eenvoudiger en sneller uitvoerbaar is. Het is goed te beseffen dat dit een ontwerpkeuze is, niet zomaar "ouderwetse techniek".
De 80x86 architectuur kent enorm veel commando's, zeker in vergelijking met de ARM64 architectuur.
Een kleine greep:
Gegevens verplaatsen
MOV AX,1 ; zet AX=1
MOV BX,AX ; kopieer AX naar BX
XCHG AX,BX ; wissel AX en BX
PUSH AX ; zet AX op de stack
POP CX ; haal waarde van stack in CX
Rekenen
ADD AX,5 ; AX = AX + 5
SUB AX,BX ; AX = AX - BX
INC CX ; CX = CX + 1
DEC CX ; CX = CX - 1
MUL BX ; DX:AX = AX * BX
DIV CX ; AX = DX:AX / CX, rest in DX
NEG AX ; AX = -AX
Opmerking: MUL en DIV zijn inflexibel, ze gebruiken altijd AX (en vaak DX) impliciet; dat maakt ze lastig in te passen.
Logisch/bitwise
AND AX,0FFh ; wis bovenste byte, houd alleen AL over
OR AL,1 ; zet laagste bit van AL
XOR AX,AX ; maak AX=0
NOT AX ; bitwise negatie
SHL AX,1 ; shift links, vermenigvuldigt met 2
SHR AX,1 ; shift rechts, deelt door 2
Vergelijken en springen
CMP AX,BX ; vergelijk AX en BX, zet vlaggen
JE Equal ; spring als gelijk (Zero Flag=1)
JNE NotEq ; spring als ongelijk
JL Minder ; spring als AX<BX (signed)
JMP Onvoorw ; onvoorwaardelijke sprong
Subroutines of procedures
CALL Subr ; roep subroutine aan
RET ; keer terug
De 80x86 kent een aantal adresseringswijzen, de belangrijkste:
Immediate: MOV AX,1234h → zet waarde 1234h direct in AX.
Register: MOV AX,BX → kopieer BX naar AX.
Direct memory: MOV AX,[1234h] → lees uit geheugenlocatie 1234h.
Indirect: MOV AX,[BX] → lees uit adres waar BX naar wijst.
Base+offset: MOV AX,[BX+4].
En dat nu in een simpel programmaatje.
Een .COM-programma heeft geen header. DOS laadt het altijd op offset 100h in een segment (eerste 256 bytes zijn de PSP). Daarom moet je assembler dat weten:
; HALLO.ASM – ons eerste TASM-programma
; Assemble: TASM HALLO.ASM
; Link: TLINK /t HALLO.OBJ (/t = COM-bestand)
CODE SEGMENT
ASSUME CS:CODE, DS:CODE
ORG 100h ; COM begint altijd op offset 100h
START:
mov dx,OFFSET MSG ; DS:DX -> string
mov ah,9 ; DOS functie 9: print string
int 21h ; roep DOS aan
mov ax,4C00h ; DOS functie 4Ch: beëindig programma
int 21h
MSG db 'Hallo vanuit TASM$' ; $ = terminator voor int 21h, functie 9
CODE ENDS
END START
Toelichting directives:
CODE SEGMENT / CODE ENDS → markeren een segment. Bij .COM niet strikt nodig, maar conventie.
ASSUME CS:CODE, DS:CODE → vertel de assembler dat CS en DS naar dit segment wijzen.
ORG 100h → cruciaal voor .COM: code begint na de PSP.
END START → zegt tegen de linker: begin uitvoeren bij START.
Dit is alleen nog een heel compact begin. Volgende keer iets meer de diepte in!
Gerelateerd
Pointers en dynamisch geheugen
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Geheugen en segmenten in real mode
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Modulariteit
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Errors en IOResult
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
Nieuwe problemen met oude systemen
Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.
