A Guide to NASM for TASM Coders - THE.UNIX.WORLD
Pubblicato da Gij il 01/1999
Livello base

Introduzione

/* Questo articolo e' stato tratto dall'Assembly Programming Journal numero 2 E TRADOTTA DA Little-John */

::/ \::::::.
:/___\:::::::.
/|    \::::::::.
:|   _/\:::::::::.
:| _ |\ \::::::::::.
:::\_____\:::::::::::............................................THE.UNIX.WORLD

Iniziamo

Generalities
------------
La funzione primaria di ogni assemblatore e' di convertire un file asm nel suo equivalente binario; e questo e' valido per TASM, NASM, e ogni altro assemblatore. Le differenze sono nelle funzioni speciali che ogni assemblatore ti offre. Per esempio, la direttiva MODEL che e' in TASM, rende piu' facile per il programmatore il referencing delle variabili di dati negli altri segmenti. NASM non possiede una direttiva uguale, quindi devi tenere conto da solo dei segment registers, e eseguire segment overrides dove sono necessari. Questo pero' non significa che NASM non abbia un buon supporto per SEGMENT o GROUP; infatti ce li ha tutti e due, anche se non sono proprio uguali a quelli di TASM. E' una filosofia diversa di coding, e seppur puo' sembrare che richieda piu' lavoro, quando ti abitui diventa piu' facile, poiche' sai perfettamente cosa sta succedendo nel tuo codice. NASM in effetti ti da' un'idea il piu' fedele possibile di come agira' il tuo codice una volta compilato. TASM e' pieno zeppo di direttive; dando uno sguardo ad una piccola reference per TASM 4.0, c'e' almeno qualche dozzina di direttive che TASM usa, e ne devi sapere proprio bene un bel po'. NASM al controrio ne ha davvero poche. Effettivamente puoi scrivere un file asm che si assemblera' correttamente senza usare neanche una direttiva, sebbene dubito che sia utile nella maggior parte dei casi.
NASM e' perdipiu' meno ambiguo nella sintassi del codice, il che lascia meno spazio per i bug, ma rende la programmazione un po' piu' severa. Comunque penso che NASM sia piu' facile da imparare rispetto a TASM, dato che e' piu' diretto. La tua Bibbia sul NASM e' costituita, naturalmente, dai docs che vengono forniti assieme, puoi averli in un package separato dallo stesso posto da cui hai preso i file binari di NASM. In ogni caso penso che troverai NASM essere tanto capace quanto TASM, se non di piu'. Anche se gli mancano delle caratteristiche che ha TASM, puoi sempre contattare l'autore via mail e chiedere per delle funzioni, e se sei fortunato aspetta solo che venga rilasciata la versione successiva.
Il codice ASM e' il solito che trovi in ogni assemblatore ( la sintassi AT&T e' un eccezione), me ci sono poche sottigliezze che i programmatori TASM facciano bene a conoscere. I docs che accompagnano NASM hanno una bella lista di queste, e io menzionero' le piu' significative qui.
DATA offset vs DATA contents
----------------------------
TASM usa questa sintassi per spostare
mov esi, offset MyVar
O
lea esi, MyVar
LEA e' utilizzaato per caricare offset complessi come "[esi*4+ebx]" in un registro. TASM supporta LEA anche se utilizzato con un offset semplice come "Myvar".
NASM invece ha un'unica maniera di caricare un offset semplice in un registro (l'opcode LEA e' valido solo se usato con offset complessi):
mov esi, MyVar
Questo significa SEMPRE di spostare l'offest di MyVar in esi.
Invece questo:
mov eax, [MyVar]
Significhera' sempre di spostare il contenuto di MyVar in eax.
Comunque, usare LEA per caricare un offset complesso e' valido sia in TASM che in NASM: lea edi,[esi*4+EBX]
NASM supporta anche una keyword SEG:
mov ax,SEG MyVar
Questo sposta il segment della variabile in ax.
Segment Overrides
-----------------
TASM e' piu' largo nella sua sintassi, quindi questi sotto sono tutti e due validi:
mov ax,ds:[si]
E
mov ax,[ds:si]
NASM non permette cio'--se specifichi una variabile nelle parentesi quadre, tutto dovrebbe essere nelle parentesi quadre.
Quindi l'unica opzione valida e':
mov ax,[ds:si]
Specificare la grandezza degli operandi
---------------------------------------
I programmatori TASM hanno di solito difficolta' lessicali con NASM poiche' questi manca della keyword "ptr" usata tantissimo in TASM. TASM fa cose':
mov al, byte ptr [ds:si]
O
mov ax, word ptr [ds:si]
O
mov eax, dword ptr [ds:si]
In NASM questo si traduce semplicemente in:
mov al, byte [ds:si]
O
mov ax, word [ds:si]
O
mov eax, dword [ds:si]
NASM permette di usare queste keyword di dimensione in diverse situazioni, e quindi ti da' un ottimo controllo sul codice generato in maniera univoca. Per esempio, i seguenti sono tutti validi:
push dword 123
jmp [ds: word 1234] ; questi specificano entrambi la dimensione
;dell'offset
jmp [ds: dword 1234] ; for tricky code when interfacing 32bit and ; 16bit segments {It can get pretty hairy with operand size being this final}, ma la cosa importante da ricordare e' che puoi avere tutto il controllo che vuoi, quando vuoi.
Funzioni
---------
TASM ha delle direttive speciali per dichiarare una procedura e per concluderla. Perche'? Una procedura e' solo un'altra label di codice che ti chiami (con una CALL) invece di usare il JMP--NASM lo fa nella maniera giusta.
TASM usa:
ProcName PROC
xor ax,ax
ret
ProcName ENDP
mentre NASM usa solo:
Procname:
xor ax,ax
ret
Per dichiarare una procedura PUBLIC, usa solo la direttiva GLOBAL:
GLOBAL Procname
Procname:
xor ax,ax
ret
Local Labels
------------
Quelli di voi che conoscono il C sanno anche che un membro di una struct puo' essere richiamato con StructInstance.MemberName. Questo e' simile alla maniera in cui NASM ti permette di usare le local labels. Una Local Label si denota con un punto che precede il nome della label:
Label1:
nop
.local:
nop
Label2:
nop
.local:
nop
Questo codice non ti dara' un errore di definizione multipla della label (multiple definitions of label), ma potrai ancora jmp-are ad una certa label come questa:
jmp Label2.local
...quindi e' locale, e in una certa maniera e' anche una label globale.
ORG Directive
--------------
NASM supporta la direttiva org, quindi se stai codando un file COM puoi partire con:
org 0x100h
O
org 100h
(NASM permette ambo i metodi asm e c di specificare hex, quindi le due righe sopra sono tutte e due valide.)
Reserving Space
---------------
Ancora una volta, qui NASM usa sintassi diversa da quella di TASM.
In TASM dichiareresti 100 bytes di spazio non inizializzato cose': Array1: db 100 dup (?)
NASM usa delle keyword proprie per far questo; queste sono RESB, RESW e RESD, che significano REServeByte, REServeWord, e REServeDword, rispettivamente. Per riservare 10 bytes, useresti le keyword RES? cose':
Array1: RESB 100
O
Array1: RESW 100/2
O
Array1: RESD 100/4
Dichiarare spazio di memoria inizializzato e' molto simile a TASM, ma gli array sono diversi. In TASM:
Array1: db 100 dup (1)
In NASM:
Array1: TIMES 100 db 1
TIMES e' una piccola e maneggevole direttiva, dice a NASM di effettuare una certa azione un numero specificato di volte, nell'esempio sopra faccio "db 1" 100 volte. TIMES puo' essere virtualmente usato per ogni cosa; per esempio:
TIMES 69 nop
inserira' 69 nop a questo punto del file.
Il simbolo $ (locazione corrente) e' supportato da NASM, e puo' essere usato per specificare l'operatore di conteggio per TIMES, quindi questo e' corretto:
label1:
mov ax,1
xor ax,ax
label2:
TIMES $-label1 nop
Questo si espande in TIMES (label2 - label1), e mettera' tanti nop da un byte dopo label2, quanti sono i byte contati tra label1 a label2.
Making Structs
--------------
Ho combattuto duramente e a lungo per far funzionare le structs, i docs erano un po' vaghi, e ce ne voluta per averla vinta. Ecco a voi. Per usare una struct, ci sono due cose da fare 2, dichiarare il prototype, e fare un'istanza. Una semplice, struttura di 2 membri sarebbe definita cose':
struc st
stLong resd 1
stWord resw 1
endstruc
questo dichiara una prototype struct chiamata st, con 2 membri, stLong che e' una DWORD, e stWord che e' una word. Questa utilizza le direttive di riserva-spazio perche' e' un prototype, non una struct reale. Puoi usare istruc per fare una istanza reale a cui puoi far riferimento come dati nel tuo codice:
mystruc:
istruc st
at stLong, dd 1
at stWord, dw 1
iend
*Nota: e' importante porre la label su una riga diversa.
Questo crea una struct che si chiama mystruc di tipo st; la keyword "at" e' usata per assegnare dei valori iniziali ai membri della struc (p.e., ai byte di memoria allocati). La notazione per referenziare i membri e' diversa da quella in C. Cio' accade per il modo in cui le strutture sono implementate; in NASM, a ogni membro e' assegnato un offset relativo all'inizio della struct:
mystruc:
istruc st
at stLong, dd 1 ; offset 0
at stWord, dw 1 ; offset 4
iend
La notazione per il referencing di un membrp e' quindi:
mov eax, [mystruc+stLong]
Questo perche' mystruc costituisce una base costante, e il membro e' un offset relativo di questa. E' simile al referencing di un data array.
Una cosa devo menzionare: Se dichiari i prototipi structs come sopra, i nomi/label dei membri saranno globali, quindi otterrai delle collisioni se usi lo stesso nome del membro nel tuo codice o in un altro prototipo struct. Per evitare questo, fai precedere il nome del membro da un punto '.', e poi refere'nziali in relazione al nome del prototype nella dichiarazione dell'istanza. Per esempio:
struc st
.stLong resd 1
.stWord resw 1
endstruc
mystruc:
istruc st
at st.stLong, dd 1
at st.stWord, dw 1
iend
E questo e' come richiami il membro nel codice:
mov eax,[mystruc+st.stWord]
Cio' potrebbe sembrare confusionale; dovresti capire che "mystruc" e' la base di una particolare istanza, e "st.stLong" e' un offset relativo all'inizio della struct, quindi in pseudo-code si traduce in:
mov eax,[offset mystruc + (offset stWord-offset start_of_proto]
o
mov eax,[offset mystruc + 4]
...che ti da' l'offset esatto per il membro stWord dell'istanza della struttura "mystruc".
Using Macros
------------
Questa e' una sezione estesa dei docs di nasm, e anche un po' troppo per analizzarla in profondita' qui. Provero' ad analizzare le parti piu' rilevanti.
Ci sono 2 tipi di macro, una-linea e multi-linea, tutte le keyword delle macro sono precedute dal carattere '%'.
Un esempio di macro di linea-singola:
%define mul(a,b) (a*b) ...che nel codice sorgente sarebbe referenziata come segue:
mov eax,mul(2,3)
Che sara' poi convertito in:
mov eax,6
Puoi invocare altre macro all'interno di una macro:
%define fancymul(a,b) ( a * triple_mul(4) ) %define triple_mul(a) (a*3)
mov eax,fancymul(2,3)
Che diventa:
mov eax, ( 2 * ( 3 * 4 ) )
Questi non sono esempi molto utili, ma sono sicuro che ne scorgi il potenziale. Le macro Multi-Linea sono molto simili a quelle di singola-linea, ma la sintassi e' un po' diversa:
%macro name number_of_args
%endmacro
Quindi, per esempio, se tu volessi scrivere un po' di asm salva-fatica, scriveresti questo:
%macro prologue 1
push ebp
mov ebp,esp
sub esp,%1
%endmacro
...per poi usarlo nel tuo codice, in questo modo:
DemoFunc:
prologue 4*2
Questo preparerebbe lo stack frame, riservando spazio per 2 variabili locali DWORD. Noterai che gli args forniti alla macro possono esser referenziati in maniera simile a quella della programmazione shell/batch di DOS e Unix, %1....%n.
Questo e' solo un piccolo assaggio, c'e' molto di piu' da imparare sulle macro di NASM: i docs sono tuoi amici.
Includes
--------
Includere file e' facile, se vuoi includere degli .inc nel tuo file asm puoi usare:
%include "win32.inc"
Se vuoi includere file binari, devi usare una keyword diversa:
INCBIN "data.bin"
Assembly Condizionale
----------------------
NASM ha anche un supporto per l'assembly condizionale:
%define INCLUDE_WIN32_INCS
%ifdef INCLUDE_WIN32_INCS
%include "win32.inc"
%include "toolhelp.inc"
%include "messages.inc"
%endif
In questo modo puoi controllare l'inclusione dei file, definendo la riga di comando:
"nasmw -dINCLUDE_WIN32_INC"
o commentando la linea %define. Il corpo dell'%ifdef sara' processata solo se e' definita una macro/define chiamata INCLUDE_WIN32_INCS.
Externs, Globals and Commons
-----------------------------
A volte, codando un progetto multi-source-files, scrivendo una dll, o chiamando delle funzioni API, hai bisogno di dichiarare vari simboli/data/funzioni di un certo tipo per renderle disponibili all'Assembler e a te.
Ci sono 3 tipi di simboli in NASM: EXTERN, GLOBAL e COMMON. La loro 'invocation' e' sempre la stessa:
EXTERN symbol_name ; usa questo per definire chiamate API da usare
GLOBAL symbol_name
COMMON symbol_name
Questi devono tutti apparire prima che il simbolo sia definito/referenziato.
Se hai esperienza in asm/c, il loro uso dovrebbe essere chiaro -- EXTERN dichiara una reference esterna che il linker deve 'risolvere' (un "import"), GLOBAL dichiara un simbolo che sia globalmente/pubblicamente disponibile (un "export"), e COMMON dichiara una variabile che sia di tipo 'Common data' (p.e., tutte le istanze di una variabile COMMON sono fuse in una singola istanza durante la compilazione).
NASM 0.97 ha anche una estensione IMPORT/EXPORT al formato .obj, per scirvere delle DLL; leggi i docs per piu' info.
Specificare i Tipi di Segmenti
------------------------------
Puoi dichiarare i segmenti molto similmente a come faresti in TASM:
segment .data use32 CLASS=data
o
segment .text use32 CLASS=code
o
segment Gij use16 CLASS=code
Questo e' un buon metodo per settare i segmenti direttamente per il linking.
Nota che Nasm non ha bisogno della presenza di certi segmenti: hai pieno controllo della segmentazione del programma.
Formati di Output
-----------------
Nasm supporta una pletora di formati di output; a seconda di cosa stai cercando di fare, dovresti leggere i docs per estensioni ad ogni tipo.
Il formato di output e' scelto usando "nasm -f type" sulla command line, dove type puo' essere bin, obj, win32 e altri.
Ogni linker preferisce diversi formati -- tlink preferisce obj per esempio, mentre LCC-WIN32 preferisce il formato win32... investiga sul tuo tipo per trovare l'output migliore per il tuo linker.
*tip: quando assembli nel tipo "obj", renditi sicuro e usa il simbolo speciale "..start:" per specificare l'entry point del file.

Conclusioni

In Chiusura
-----------
E' tutto per adesso. Questo articolo ha l'obiettivo di essere una guida 'quick-start' per i coders TASM che vogliono --o hanno bisogno-- passare a NASM; non vuole sostituire la documentazione di NASM. Se hai bisogno di contattarmi, la mia e-mail e' [email protected]
Enjoy NASM!