Alweer geen Excel

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!

Arnout van Kempen is naast computernerd ook directeur compliance & risk bij aaff. Hij schrijft op persoonlijke titel.

Gerelateerd

reacties

Reageer op dit artikel

Spelregels debat

    Aanmelden nieuwsbrief

    Ontvang elke werkdag (maandag t/m vrijdag) de laatste nieuwsberichten, opinies en artikelen in uw mailbox.

    Bent u NBA-lid? Dan kunt u zich ook aanmelden via uw ledenprofiel op MijnNBA.nl.