Limbajul de asamblare sub Linux

Demult, foarte demult, pe când se stocau datele pe cartele de hârtie perforată si Bill Gates nu avea bani, programatorii aveau o sarcină foarte grea: erau nevoiţi să-şi scrie aplicaţiile în cod maşină.

Cod maşină

Ce este, de fapt, acest cod maşină de care tot aud? Răspunsul scurt: instrucţiuni de care microprocesorul "ştie" direct din hardware.
Raspunsul mai lung (dar mai detaliat) trebuie cautat tot in istorie. Cele mai vechi calculatoare aveau programele stocate direct in circuite, astfel incat nu puteau rezolva decat un singur tip de problema. Lucru lesne de inteles, foarte costisitor - pentru orice fel nou de problema trebuia construita o alta masina. Ulterior, s-a gandit o rezolvare asemanatoare cu ceea ce se intampla astazi sub "capota" PC-ului nostru: o serie de conutatoare trimiteau, in binar, codul instructiunii ce va fi folosita (sa presupunem ca avem 3 astfel de comutatoare, iar primul si ultimul sunt scurtcircuitate; in acest fel, se va trimite catre microprocesor instructiunea a 5-a pt. ca 1012=510). Binenteles ca unele comanzi aveau neevoie de unul sau mai multi operazi (de exemplu "move" are doi operanzi: sursa si destinatie) care erau trimis in acelasi fel.
Urmatorul pas logic au fost programele stcate in memorie, iar restul este istorie. Cum erau stocate in memorie? inenteles ca binar, in calupuri de 4, 8, 16, 32 de biti. Metoda de impachetare este destul de simpla: un numar de biti pentru instructiunea propriu-zisa, iar restul pentru operanzi. In realitate, lucrurile sunt ceva mai complicate, daca luam in calcul faptul ca unele comenzi au un singur operand, altele nici unu etc., iar inginerii nu vor sa iroseasca nici un bit. Spre exemplu, familia de microprocesoare 80x86 de la Intel duce acest lucru la extrem, avand opcode-urile ("Operation Codes" in lb. engleza, adica toata comanda impachetata, includiv operanzii) de dimensiune ce variaza de la 8 biti pana la 10 octeti.

Limbajul de ansamblare

Probabil ca deja va intrebati care etse legatura dintre comutatore, opcode-uri si lmbajul de ansamblare. cum spuneam si la inceput, primii programatori trebuiau sa cunoasca foarte multe numare binare si sa stie sa lege intr-o forma coerenta pentru a crea chiar si cel mai simplu programel. Fiind un proces foarte greoi si complicat s-a inceput cautarea de alternative la sirurile de 0 si 1. Astfel a aparut ideea unui limbaj mai apropiat de limba engleza, care sa poata exprima mai usor instructiunile si cursul unui program. Baza procesoarelor programabile fiind deja construite pe opcode-uri binare, cel mai simplu pas de facut a fost creearea unor asocieri de /mnemonici// (abrevieri ale unor cuvinte din engleza) cu opcode-uri. Textul reprezentat de aceste cuvinte este trecut printr-un at program, numit ansamblator, care transforma numitii mnemonici in opcode-uri gata de folosit de catre microprocesor.
De atunci lucrurile au evoluat, in sensul ca ansablatoarele stiu de rutine predefinite, de functii si o gramada de alte "smecherii", dar baza a ramas aceiasi, un tabel in care se cauta instructiunea asociata unui sir de caractere (cuvant prescurtat)
Din punct de vedere al sintaxei, ne intereseaza cele doua mari tipuri:

  • sintaxa Intel (folosita de nasm,masm)
  • sintaxa AT&T (folosita de gas - gnu assembler)

Diferentele cele mai notabile dintre cee doua tipuri de sintaxa sunt, in primul rind la ordinea operanzilor.Intel are inai destinatia si apoi sursa:

mov eax, 1

AT&T are intai sursa si apoi destinatia:
movl $1, %eax

Ambele fragmente de mai sus pun in valoare 1 zecimal in registrul eax. Se mai poate observa ca in sintaxa AT&T, valorile imediate sunt precedate de caracterul '$' iar registrii au inainte '%'
Alta deosebire importanta o reprezinta faptul ca in cadrul sintaxei AT&T apare un sufix la mnemonic astfel: l pentru cuvant (word) dublu, w pentru cuvant si b pentru octet. In exemplul de mai sus, movleste operatia de copiere a unui cuvant in registrul eax. Normal ca nu am epuizat diferentele, dar acestea sunt cele mai importante.

De ce e bun limbajul de ansamblare

În primul rând este foarte rapid. Având în vedere că este atât de apropiat de instrucţiunile "scrise" direct în procesor, este relativ uşor pentru un programator experimentat să scrie cod ce rulează rapid. Spun experimentat pentru că majoritatea compilatoarelor de limbaje de nivel înalt (Pascal, C etc.) au ca rezultat cod de foarte bună calitate. Totuşi, un om poate fi mult mai creativ şi poate folosi anumite instrucţiuni specifice anumitor maşini pentru a-şi accelera aplicaţiile (spre exmplu, instrucţiunile MMX, specifice procesoarelor mai noi de Pentium, sau SSE, specifice Pentium III).
De multe ori (80%) un program realizat integral intr-un limbaj de ansamblare, ocupa mult mai putin spatiu decat un program ce face acelasi lucru, dar este scris in C, bunaoara. acest lucru este util antunci cand vrei sa rulezi foarte multe de pe o simpla discheta de 1,44 M sau cand nu prea ai RAM, sau cand scrii pentru, sa zicem, un procesor inglobat intr-un ceas sau …
Atunci cand scrii in limbaj de ansamblare ai control total asupra masini pe care rulezi procesul si de multe ori poti face lucruri imposibile sau complicate din punct de vedere a unui limbaj de nivel inalt.
Ghiar si daca programezi intr-un limbaj de cod inalt, eu sunt de parere ca este cel putin util sa ai idee despre ce face compilatorul cu codul tau, astfel vei putea sa-ti optimizezi codul.

De ce nu e bun ???

Nu e portabil. Motivele sunt evidente: producatori diferiti de hardware au opcode-uri diferite. Un program scris pentru arhitectura Intel nu va putea fi rulat pe Apple sau alpha si invers.
Programatorii C au parte de o biblioteca standard de functii foarte utile si foarte des intallnite. Programatorii de ansamblare nu au asa ceva, fiind nevoie de multe ori sa "reinventeze roata" in loc sa se ocupe de restul codului.
Pe masinile noi, sporul de performanta adus este insignifiant datorita faptului ca s-a ajuns la o viteza extraordinara, iar la cata memorie avem zilele astea, cine se mai uita la cativa KB in plus sau in minus? Aceasta afirmatie este adevarata, dar numai pe calculatoarele noi. In schimb, pe masinile vechi, limbajul de ansamblare devine o adevarata mana cereasca.

Arhitectura 80x86

Cele mai raspandite calculatoare sunt cele monitorizate de procesoare Intel sau compatibile. de aceea, voi prezenta cateva lucruri de baza despre acestea.
registrii sunt locatii de memorie speciala, aflate chiar in cip-ul procesorului si sunt foarte rapide. Acesta este locul in care au loc majoritatea operatiilor aritetice si logice. Pe platforme Intel, registrii sunt: eax, ebx, ecx, edx, esi, edi, ebp, si esp. Prezentarea fiecarui registru depaseste scopul acestui articol, dar e bine de mentionat faptul ca exista si un registru care tine starea in care se afla procesorul si poate fi citit de catre programator.
Este bine de mentionat ca proceoarele 80x86 sunt CISC (Complex Instruction set computers). acest lucru se observa si din faptul ca exista circa 8 clase diferite de instructiuni:

  1. pentru mutarea datelor (mov, lea, les, push, pop, etc.)
  2. pentru conversii (cbw, cwd, xlat, etc.)
  3. aritmetice (add, inc, sub, dec, cmp, neg, mul, dev, idiv, etc.)
  4. operatii logice si la nivel de biti (and, or, xor, not, shl, shr, rcl, rcr, etc.)
  5. intrare / iesire
  6. pentru lucru cu siruri de caractere 9ceva neintalnit pe RISC - Reduced Instruction Set Computers)
  7. instructiuni care controloeaza cursul programului (jmp, call, ret, etc.)
  8. altele (clc, stc, cmc, etc.)

Ansamblare sub Linux

Din pacate, linux nu este o platforma atractiva pentru programatorii de limbaj de ansamblare pentru ca, din motive de securitate nu ofera decat controlul limitat asupra procesorului. cu alte cuvinte, vei putea face o adunare cu add, dar nu ai nici o sansa sa schimbi starea procesorului din mod protejat in mod real. Totusi, exista o metoda eleganta de a realiza programe in limbaj de ansamblare pe acest sistem de operare: interfata oferita de kernel.
Prin intermediul intreruperii 0x80 se pot apela functii oferite de Kernel-ul Linuxului sau apeluri de sistem. acestea sunt implementate in kernel si cand un proces foloseste o astfel de functie, argumentele sunt impachetate si trimise catre kernel, care preia executia programului pana la finalul apelului de sistem respectiv. Practic, in momentul in care se cheama intreruperea 0x80, sistemul apeleaza functia cu numarul citit din registrul eax, iar parametrii ii citeste, pe rand, din ebx, ecx, esx si edi.
Numarul de apeluri de sistem se poate afla din /usr/include/sys/syscall.h, iar pentru a afla cuma sa-l folosim este suficient un:

# man 2 <nume>
Bibliography
Revista Linux360 nr. 10 din ianuarie 2005
Unless otherwise stated, the content of this page is licensed under GNU Free Documentation License.