In acest tutorial destinat limbajului de asamblare pentru procesoare din familia Intel 8086 sunt prezentate modalitățile de lucru cu segmente. Aceasta abordare permite realizarea de aplicații complexe în care datele și codul nu sunt limitate doar la un singur segment.
Arhitectura de segmente
Familia de procesoare 8086 implementează o arhitectura de segmente prin faptul ca fiecare adresa fizica este reprezentata prin perechea adresa segment:offset.
Generația de procesoare 8086/286 pe 16 biți permit lucru cu segmente mai mici de 64K (cea mai mare adresa posibila din cadrul unui segment – offset – este 216).
Procesoarele 90386/486 utilizează arhitectura pe 16 biti (segmente de maxim 64K) când prelucrează date în mod real, însa in mod protejat utilizează registre pe 32 de biți ce pot retine adrese de pana la 4 GBytes.
Un segment fizic poate începe numai de la o adresa divizibila cu 16, aceste locații fiind numite (Intel) paragrafe.
Modul în care sunt definite datele și codul nu are importanta la programele mici pentru ca acestea ocupa mai puțin de 64Kbytes fiind grupate în segmente individuale și sunt accesate prin intermediul offset-ului relativ la adresa de început a segmentului respectiv. Problema se complica în cazul programelor mari în care codul sau datele ocupa mai mult de 64K și atunci sunt segmentate. In aceasta situație, programatorul trebuie sa se asigure ca fiecare data sau secventa de cod referita poate fi accesata prin segment:adresa.
Problema segmentarii datelor sau codului este mai puțin acuta în cazul procesoarelor pe 32 de biți pentru ca segmentele sunt destul de mari (4 GBytes) astfel încât și cele mai complexe aplicații sa se comporte asemenea programelor mici, compacte.
Segmentele logice
Segmentele reprezinta seturi de date sau instrucțiuni ce sunt disponibile prin intermediul offset-ului relativ de adresa de început a segmentului.
Adresele fizice ale segmentelor sunt cunoscute prin intermediul registrelor de segment:
Registru | Descriere |
---|---|
DS | – segmentul de date |
CS | – segmentul de cod |
SS |
– segmentul de stiva |
ES |
– segmentul extins |
, însă trebuie avut în vedere faptul ca, în cazul în care exista mai multe segmente definite de programator, numai unul dintre ele, la un moment dat, poate fi asociat cu segmentele logice de date, de cod, de stiva sau extins.
La încărcarea în memorie a programului, sistemul inițializează registrul de segment CS cu prima adresa se segment disponibila instrucțiune, iar registrul IP cu adresa relativa din cadrul segmentului, a primei instrucțiuni ce trebuie executata. Segmentele logice se încarcă în memorie în ordinea cod, date, stiva.
Definirea segmentelor
Segmentele logice contin cele tei componente ale unui program: cod, date si stiva. Modul in care pot fi specificate segmentele sunt:
- definire simplificata;
- definire completa a segmentelor.
Definirea simplificata ascunde multe detalii ale definirii segmentelor si utilizează aceleași convenții implementate de Microsoft in limbajele de nivel înalt.
Structura unui program ale cărui segmente sunt descrise utilizând definirea simplificata este: (descrisa si in prima parte a tutorialului)
.MODEL [tip_model] [conventia_de_limbaj] \ ;declaratie obligatorie inainte de a [tip_stiva] ;utiliza directivele simplificate de ;definire a unui segment [.tip_procesor] ;definire tip procesor, implicit 8086 .STACK [expresie] ;definire segment de stiva, implicit 1KB (1024 ;de octeti) .DATA ;definire segment de date .CODE ;definire segment de cod start: ;eticheta ce indica adresa de start a codului END start ;indica sfarsit program - obligatoriu
in care:
Optiune |
Descriere |
Valori posibile |
tip_model |
defineste modelul de memorie; determina dimensiunea codului si datelor (segmentelor) precum si a adreselor; |
TINY, SMALL, COMPACT, MEDIUM, LARGE, HUGE, FLAT |
conventia_de_limbaj |
faciliteaza compatibilitatea cu limbajele de nivel inalt prin determinarea codarii interne a numelor simbolurilor externe si publice; |
C, BASIC,FORTRAN, PASCAL, SYSCALL, STDCALL |
tip_stiva |
modul in care este definita stiva ca distanta fata de segmentul de date; NEARSTACK plaseaza stiva in acelasi segment fizic cu cel al datelor; pentru FARSTACK stiva are propriul ei segment fizic; |
NEARSTACK (implicit), FARSTACK |
tip_procesor |
odata selectat tipul procesorului se utilizeaza doar setul de instructiuni asociat; |
.186, .286, .386, .486 |
expresie |
dimensiunea in octeti a stivei |
Pot fi utilizate o serie de simboluri predefinite pentru a descrie segmentele:
Simbol | Descriere |
---|---|
@Model |
– intoarce modelul de memorie ca valoare intreaga cuprinsa intre 1 si 7; |
@Data |
– segmentul de date |
@DataSize |
– dimensiunea modelului de memorie |
@CurSeg |
– segmentul curent |
@CodeSize |
– tipul pointerilor: far sau near |
Definirea completa a segmentelor utilizează sintaxa:
nume_segment SEGMENT [tip_aliniere] [READONLY] [tip_combinare] [tip_adresare] [clasa_segmente] ……… nume_segment ENDS
in care:
Element | Descriere |
---|---|
nume_segment |
– defineste numele segmentului; in cadrul unui modul toate segmentele definite cu acelasi nume sunt considerate ca reprezentand acelasi segment; de asemenea sunt combinate si segmentele cu acelasi nume, dar care se gasesc in module diferite. |
tip_aliniere |
– descrie adresa la care incepe un segment nou |
READONLY |
– indica programului ca segmentul este de tip read-only si se genereaza eroare la incercarile de modifica valori; element optional; |
tip_combinare |
– indica modul in care sunt combinate segmentele cu acelasi nume dar din module separate la crearea executabilului; |
tip_adresare |
– descrie modul de lucru al procesoarelor (de la 386 in sus), fie pe 16 biti, fie pe 32 de biti; |
clasa_segmente |
– nume dat intre ” ce indica o clasa de segmente; segmentele din aceeasi clasa sunt aranjate secvential; utilizat pentru a indica ordinea de aranjare a segmentelor. |
Atenție ! Nu se poate specifica mai mult de o valoarea pentru fiecare element.
Un segment se poate închide cu ENDS si apoi redeschide ulterior doar cu directiva nume_segment SEGMENT, fără a modifica opțiunile din prima definire.
[tip_aliniere]
- element opțional;
- definește mulțimea din care sa fie selectata adresa de la care este alocat segmentul;
- descrie faptul ca adresa fizica de început a segmentului este divizibila cu 1,2,4,16 sau 265;
- link-erul utilizează aceasta informație pentru a determina adresa relativa de start a fiecărui segment; sistemul determina adresa de start actuala când programul este încărcat;
- valorile pe care le poate lua:
Tip aliniere | Adresa de start |
---|---|
BYTE | – următoarea adresa de tip byte disponibila; |
WORD | – următoarea adresa de tip word disponibila; |
DWORD | – următoarea adresa de tip double disponibila; |
PARA | – următoarea adresa de tip paragraf (16 octeti) disponibila; valoare default |
PAGE | – următoarea adresa de tip pagina (256 octeti) disponibila; |
[tip_combinare]
- element opțional;
- indica modul în care sunt combinate segmente cu același nume, dar care apar în module diferite; informație utilizata de link-er, nu de asamblor, pentru ca este necesara la crearea executabilului și nu la asamblarea fiecărui modul în parte;
- valorile pe care le poate lua:
Tip combinare | Descriere |
---|---|
PRIVATE | – nu combina segmentul respectiv cu alte segmente (din alte module) ce au acelasi nume; optiune default; |
PUBLIC | – combina segmentele cu acelasi nume din module separate intr-o zona de memorie continua; |
COMMON | – combina segmentele cu acelasi nume din module separate suprapunandu-le pe aceeasi zona de memorie de marime egala cu dimensiunea celui mai mare segment; |
AT expresie | – segmentul este plasat la adresa indicata de expresie; nu se permite declararea de cod sau initializare de date; reprezinta o masca care se poate suprapune datelor si codului existent in memorie; asamblorul nu genereaza date sau cod pentru acest segment, insa permite adresarea in cadrul sau; |
MEMORY | – segmentul curent este asezat in memorie in spatiul ramas disponibil dupa alocarea celorlalte segmente (catre sfarsitul memoriei); utilizat si ca sinonim pentru PUBLIC; |
STACK | – concateneaza toate segmentele cu acelasi nume si pune in SS adresa de inceput a segmentului rezultat, iar SP pe sfarsitul sau; segmentul rezultat este asociat stivei; |
[tip_adresare]
- element opțional;
- reprezinta atributul modului de utilizare pentru procesoare, începând de la .386;
- valorile pe care le poate lua:
Tip adresare | Descriere |
---|---|
USE16 | – indica faptul ca elementele din segment sunt adresate utilizand un offset pe 16 de biti; daca se specifica .386 sau .486 dupa de directiva .MODEL atunci default este modul USE16; |
USE32 | – indica faptul ca elementele din segment sunt adresate utilizand un offset pe 32 de biti; daca se specifica .386 sau .486 inainte de directiva .MODEL atunci default este modul USE32; |
[clasa_segmente]
- element opțional;
- indica modul de aranjare in memorie a segmentelor din aceeași clasa;
- doua segmente cu același nume, dar cu clase diferite nu sunt combinate;
- segmentele din aceeași clasa sunt aranjate secvențial în memorie în ordinea în care sunt întâlnite.
In mod normal, asamblorul aranjează segmentele in fișierul .obj
in ordinea in care apar in codul sursa. In schimb, link-editorul procesează fișierele .obj
in ordinea din linia de comanda. In cadrul fiecărui modul, acesta aloca memorie pentru segmente in ordinea apariției, apartenentei la un grup, clasa și cerințe date de modul de aranjare .DOSSEG
(convenția de ordonare a segmentelor MS-DOS).
Modul de aranjare a segmentelor este important atunci când e dorește ca anumite segmente sa apară la începutul sau sfârșitul unui program sau când se fac presupuneri în legătura cu ce segmente se găsesc lângă altele.
Pentru a controla ordinea în care segmentele apar în memorie se utilizează directivele:
Directiva | Descriere |
---|---|
.SEQ | – in ordinea in care sunt declarate; modalitate implicita |
.ALPHA | – ordonare alfabetica a segmentelor dintr-un modul; |
.DOSSEG | – aranjeaza segmentele in ordinea standard a limbajelor Microsoft; nu se utilizeaza directiva in module apelate in cadrul altor module; ordinea segmentelor este cod, date, stiva. |
Inițializarea segmentelor de registru
In cadrul programelor cu structura simplificata (.model) initializarea registrelor de segment (DS, SS, ES, CS) si asocierea lor cu segmentele de date, cod si stiva era realizata de asamblor si lnk-editor. In cadrul programelor a caror structura este definita de programator, initializarea acestora se face de catre acesta. Pasii ce trebuie urmati sunt:
Atenție !
Asocierea logica dintre registrul de segment și segmentul declarat se face prin intermediul directivei ASSUME. Aceasta informație este necesara asamblorului și link-editorului la transformarea programului in cod mașină.
Multe dintre instrucțiunile assembler presupun existenta unui segment implicit. Accesul la o zona de memorie din segmentul de date fie prin nume variabila, [registru index] sau [registru de baza][registru index] (toate reprezinta adrese offset) se realizează fata de adresa fizica conținută de registrul DS
. Fiecare instrucțiune de citire/scriere cu stiva (POP/PUSH) se realizează implicit fata de SS
. Instrucțiunile de salt condiționat/necondiționat reprezinta salturi la adrese offset în cadrul segmentului de cod, a carui adresa se gaseste in CS.
De aceea în cazul programelor cu mai multe segmente este necesar sa se indice asocierea logica dintre segment și registru de segment pentru ca programul (mai precis asamblorul și link-editorul care transforma codul în cod mașină) sa știe unde (în ce segmente) se găsesc adresele respective (date sau instrucțiuni).
Asocierea se realizează cu instrucțiunea
ASSUME registru_segment:nume_segment[,registru_segment:nume_segment] ASSUME [registru_segment:] NOTHING [,registru_segment:NOTHING]
Un registru_segment reprezinta unul dintre registrele de segment: CS
, DS
, ES
sau SS
; nume_segment reprezinta numele unui segment sau grup de segmente.
Atenție !
Odata ce s-a modificat unul dintre registrele DS
, ES
sau SS
trebuie precizat acest lucru procesorului prin încărcarea registrului de segment utilizat implicit cu adresa noului segment. Daca segmentul utilizat reprezinta o zona de date inițializarea registrului DS
se face prin secventa:
mov AX, nume_segment mov DS, AX
Daca segmentul reprezinta o zona de stiva inițializarea registrului SS
se realizează prin:
mov AX, nume_segment mov SS, AX mov SP, offset baza_stiva ;pentru ca adresele in stiva scad trebuie ca SP sa fie ;initializat cu offset-ul ultimei zone de memorie din stiva
Modul simplificat de definire a unui segment ce va fi utilizat pe post de stiva este:
Stiva SEGMENT dw 50 dup (?) ;rezerv o zona de 50 * 2 octeti baza_stiva label word Stiva ENDS
Daca segmentul utilizat reprezinta o zona de date extinsa initializarea registrului ES se face prin secventa:
mov AX, nume_segment mov ES, AX
Se observa ca registrul CS nu trebuie încărcat. Acest lucru este realizat implicit (daca nu s-ar fi realizat acest lucru nu se putea executa nici o instrucțiune din program ,iar inițializările reprezinta de fapt secvente de instrucțiuni) prin indicarea punctului de start al programului (NU UITA de eticheta start:
si de instrucțiunea de final end start
) si prin asocierea logica ASSUME CS:nume_segment
.
Instrucțiunea ASSUME registru_segment:NOTHING șterge asocierea logica anterioara. Instrucțiunea ASSUME NOTHING
șterge asocierile logice pentru toate registrele de segment.
Definirea grupurilor de segmente
Un grup reprezinta o colecție de segmente ce nu trebuie sa depășească (cumulat !!!) mai mult de 64 KBytes pe arhitectura de 16 biți si nu mai mult de 4GBytes pe 32 de biți. Un grup permite asocierea dintre mai multe segmente definite logic si elimina repetarea instrucțiunilor de încărcare a registrelor de segment la schimbarea registrelor utilizate. De exemplu programul conține mai multe segmente de date iar accesul la ele implica schimbarea adresei din registrul DS
. Gruparea acestor segmente într-un grup implica realizarea unui singur segment obținut prin concatenarea segmentelor din grup și încărcarea o singura data a registrului DS
cu adresa grupului. Toate datele/codul este astfel accesibil relativ la adresa de început a grupului. Sintaxa de definire a grupului este:
nume_grup GROUP nume_segment [, nume_segment]
Atentie ! Se are in vedere:
- un segment nu poate fi definite in mai multe grupuri
- dimensiunea grupului nu trebuie sa depășească 65 KBytes pe 16 biți si 4 GBytes pe 32 de biți;
- nu indica modul de încărcare în memorie a segmentelor din grup și de aceea trebuie acordata atenție la modul de definire a segmentelor astfel încât segmente ce nu aparțin grupului sa nu fie alocate intre segmentele din grup, iar în final acesta sa depășească dimensiunea maxima admisa.
De exemplu secventa de program:
Date1 SEGMENT a dw 1234h b dw 5678h Date1 ENDS Date2 SEGMENT vector dw 5 dup (9) Date2 ENDS Temporare SEGMENT temp dw 5 dup (3) Temporare ENDS Date3 SEGMENT c dw 1111h d dw 2222h Date3 ENDS Stiva SEGMENT dw 100 dup(?) varf label word Stiva ENDS Date GROUP Date1, Date2, Date3 Principal SEGMENT ASSUME CS:Principal,SS:Stiva,DS:Date start: mov AX,Date ;incarc in DS adresa grupului de segmente mov DS,AX mov AX,Stiva ;incarc in SS adresa segmentului de stiva mov SS,AX mov AX, offset varf ;incarc SP mov SP,AX mov AX, b ;pun in AX valoarea din b mov AX, vector ;pun in AX valoarea din vector[0] mov AX, c ;pun in AX valoarea din c mov AX, d ;pun in AX valoarea din d ;se observa ca datele sunt accesibile fara a incarca de fiecare data DS cu adresa ;segmentului parinte mov AX,4c00h int 21h Principal ENDS end start
generează dispunerea in memorie a datelor astfel:
DS:0000 34 12 78 56 00 00 00 00 ;date segment Date1 DS:0008 00 00 00 00 00 00 00 00 ;zona de date DS:0010 09 00 09 00 09 00 09 00 ;date segment Date2 DS:0018 09 00 00 00 00 00 00 00 ;date segment Date2 DS:0020 03 00 03 00 03 00 03 00 ;date segment Temporare DS:0028 03 00 00 00 00 00 00 00 ;date segment Temporare DS:0030 11 11 22 22 00 00 00 00 ;date segment Date3
Atenție ! De ce fiecare segment pare ca ocupa 16 octeți (fiecare segment ocupa atâta spațiu cate date sunt definite in el, iar restul de octeți sunt nerezervați) ? Pentru ca implicit la definire tipul alinierii este PARA segmentele sunt alocate in memorie numai de la adrese divizibile cu 16. Restul de octeți (pana la al 16-lea) sunt nerezervați, dar pot fi accesați relativ fata de adresa de început a segmentului.
Atentie ! In exemplul anterior instrucțiunea mov AX,offset
d va pune in AX
valoarea 0002h si nu 0030h deoarece întoarce offset-ul fata de adresa segmentului părinte si nu fata de adresa de start a grupului.
In Debugger (la dezasamblarea instrucțiunilor) se observa ca variabilele b
, vector
, c
si d
sunt referite prin offset-uri relative fata de adresa de început a grupului: 0002h, 0010h, 0030h, 0032h evidențiindu-se faptul ca intre segmentele grupului s-a intercalat segmentul Temporare (ordinea de alocare in memorie este implicit .SEQ). Pentru a evita situația:
- se declara segmentul Temporare dupa declararea celor 3 segmente
Date1
,Date2
șiDate3
; - se utilizează o clasa de segmente ‘clasa_seg’ în care sunt puse segmentele Date1, Date2 si Date3.
De exemplu:
Date1 SEGMENT 'clasa_date' a dw 1234h b dw 5678h Date1 ENDS Date2 SEGMENT 'clasa_date' vector dw 5 dup (9) Date2 ENDS Temporare SEGMENT BYTE ;alocare la o adresa divizibila cu 1 temp dw 5 dup (3) Temporare ENDS Date3 SEGMENT 'clasa_date' c dw 1111h d dw 2222h Date3 ENDS Stiva SEGMENT dw 100 dup(?) varf label word Stiva ENDS Date GROUP Date1, Date2, Date3 .................
In aceasta situatie segmentele sunt aranjate in memorie astfel:
DS:0000 34 12 78 56 00 00 00 00 ;date segment Date1 DS:0008 00 00 00 00 00 00 00 00 ;zona de date DS:0010 09 00 09 00 09 00 09 00 ;date segment Date2 DS:0018 09 00 00 00 00 00 00 00 ;date segment Date2 DS:0020 11 11 22 22 03 00 03 00 ;date segment Date3 si Temporare DS:0028 03 00 03 00 03 00 00 00 ;date segment Temporare
Se observa ca indicând ca segmentul Temporare sa fie alocat la o adresa de tip BYTE el urmează dupa segmentul Date3 eliminându-se octeții nefolosiți.