Surinkimo instrukcijos ir mašinos instrukcijos. Surinkimo ir mašinos instrukcijos Bendrosios paskirties registrai

Rašau programą assembleriu (x86) projektoriui. Esmė ta, kad yra automatinis skaidrių perjungimo režimas. Reikia atidėti skaidrių demonstravimą 10 sekundžių žingsniais. Jie man padėjo taip vėluoti. Žemiau pateikiamos dvi procedūros (delsimo sukūrimas ir delsos mažinimas)

MakeDelay Proc Near mov al,Timer; delsos reikšmė diapazone nuo 10 iki 90 vienetų shr al,4 mov ah,al xor al,al ror ax,2 mov word ptr Delay+1,ax mov byte ptr Delay,0 mov byte ptr Delay + 3.0 ret MakeDelay Endp

mov ax,word ptr Delay arba axe,word ptr Delay + 2 cmp ax,0 ; je nxtslide ;Taip - pereiti prie skaidrės pasirinkimo sub word ptr Delay,1 ;No - eiti sumažinti sbb word ptr Delay + 2,0 ;Delays jmp ext5 ;Išeiti paprogramę

Pirmoje procedūroje neaišku, kaip jie iš viso priėjo prie tokio vėlavimo algoritmo. O delsos mažinimo procedūroje neaišku, kodėl daryti atskyrimą. Problema ta, kad vėlavimo įgyvendinimas labai priklauso nuo operacinės sistemos. Ir, pavyzdžiui, „Virtual Windows XP“ nustačius reikšmę į 60, vėluojama 65 sekundes, o sistemoje „Windows 7“ – 50 sekundžių. Prašau padėti man atsikratyti šios netvarkos.

Užduoties kodas: "Assembler x86"

tekstinis

Programų sąrašas

ACP proc netoli teisinga vertė su ADC push ax ;išsaugoti registrus, naudojamus šioje rutinoje push dx ; mov al,01h ;įjunkite "Pradėti" iš 03h,al ; Laukiama: po al,02h ;palaukite kol užsidegs "Rdy" testavimas al,01h ; jz Laukiu ; mov al,0 ; atstatyti "Pradėti" iš 03h,al ; in al,01h ;perskaitykite reikšmę įvesties registre mov ah,10d ; mov dl,0FFh ;įkelti į dl maksimalų skaičių, kurį galima pritaikyti įvesties registrui mul ah ;skaičius iš įvesties registro padauginti iš viršutinės ADC (maks. įtampos) ribos div dl ;rezultatą padalyti iš didžiausio reikšmę įvesties registre ir gaukite skaičių iš diapazono 1...10 mov skor_ACP,al pop dx pop ax ret ACP endp

Galima laikyti kaip automatinis kodas(žr. toliau), pratęstas konstrukcijomis . Tai iš esmės priklauso nuo platformos. Įvairių aparatinės įrangos platformų surinkimo kalbos yra nesuderinamos, nors iš esmės gali būti panašios.

Rusiškai tai gali būti vadinama tiesiog " surinkėjas“ (būdingi tokie posakiai kaip „parašyti programą asambliuke“), o tai, griežtai kalbant, nėra tiesa, nes surinkėjas programa, skirta versti programą surinkimo kalbaį kompiuterio kodą.

Bendras apibrėžimas

Surinkimo kalba yra žymėjimas, naudojamas programoms, parašytoms mašininiu kodu, skaitoma forma pavaizduoti. Asamblėjos kalba leidžia programuotojui naudoti abėcėlinius mnemoninius operacijų kodus, savo nuožiūra priskirti simbolinius pavadinimus kompiuterio registrams ir atminčiai, taip pat nustatyti jam patogias adresavimo schemas (pavyzdžiui, rodyklės ar netiesioginės). Be to, tai leidžia naudoti įvairios sistemos skaičiavimas (pavyzdžiui, dešimtainis arba šešioliktainis), kad būtų pateiktos skaitinės konstantos, ir leidžia pažymėti programos eilutes etiketėmis su simboliniais pavadinimais, kad jas būtų galima pasiekti (pagal vardą, o ne pagal adresą) iš kitų programos dalių (pvz., valdyti valdymą ) .

Atliekamas asamblėjos kalbos programos vertimas į vykdomąjį mašininį kodą (reiškinių skaičiavimas, makrokomandų išplėtimas, mnemonikos pakeitimas mašininiais tinkamais kodais, o simbolinius adresus - absoliučiais arba santykiniais adresais). surinkėjas- vertėjo programa, suteikusi asamblėjos kalbai pavadinimą.

Surinkimo kalbos instrukcijos atitinka procesoriaus instrukcijas. Tiesą sakant, jie yra simbolinė žymėjimo forma, kuri yra patogesnė asmeniui - mnemokodai- komandos ir jų argumentai. Tokiu atveju vieną asamblėjos kalbos nurodymą gali atitikti keli procesoriaus instrukcijų variantai.

Be to, asamblėjos kalba vietoj atminties langelių adresų leidžia naudoti simbolines etiketes, kurios surinkimo metu pakeičiamos absoliučiais arba santykiniais adresais, apskaičiuotais asemblerio ar linkerio, taip pat vadinamaisiais. direktyvas(assemblerio instrukcijos, kurios nėra verčiamos į mašinines procesoriaus instrukcijas, o vykdomos paties surinkėjo).

Surinkimo direktyvos leidžia visų pirma įtraukti duomenų blokus, nustatyti programos fragmento surinkimą pagal sąlygas, nustatyti etiketės reikšmes, naudoti makrokomandas su parametrais.

Kiekvienas procesorių modelis (arba šeima) turi savo instrukcijų rinkinį – sistemą – ir atitinkamą surinkimo kalbą. Populiariausios asamblėjos kalbos sintaksės yra „Intel“ sintaksė ir AT&T sintaksė.

Yra kompiuterių, kurie diegia aukšto lygio programavimo kalbą (Fort, Lisp, El-76) kaip mašininę kalbą. Tiesą sakant, tokiuose kompiuteriuose jie atlieka surinkimo kalbų vaidmenį.

Galimybės

Asamblėjos kalbos naudojimas suteikia programuotojui daugybę funkcijų, kurių paprastai nėra programuojant aukšto lygio kalbomis. Dauguma jų yra susiję su kalbos artumu aparatinės įrangos platformai.

  • Galimybė visapusiškai išnaudoti visas aparatinės įrangos platformos ypatybes leidžia teoriškai parašyti greičiausią ir kompaktiškiausią kodą, koks tik įmanomas tam tikram procesoriui. Įgudęs programuotojas, kaip taisyklė, gali žymiai optimizuoti programą, palyginti su vertėju iš aukšto lygio kalbos pagal vieną ar kelis parametrus ir sukurti kodą, artimą Pareto optimaliam (paprastai programos greitis). pasiekiamas pailginant kodą ir atvirkščiai):
    • dėl racionalesnio procesoriaus resursų panaudojimo, pavyzdžiui, efektyviausiai visų pradinių duomenų talpinimo registruose, galima panaikinti nereikalingą prieigą prie RAM;
    • dėl rankinio skaičiavimų optimizavimo, įskaitant efektyvesnį tarpinių rezultatų panaudojimą, galima sumažinti kodo kiekį ir padidinti programos greitį.
  • Galimybė tiesiogiai pasiekti aparatinę įrangą, ypač įvesties / išvesties prievadus, specifinius atminties adresus, procesoriaus registrus (tačiau šią galimybę labai riboja tai, kad daugelyje operacinių sistemų tiesioginė prieiga iš taikomųjų programų, kad būtų galima rašyti į periferinių įrenginių registrus įranga yra užblokuota, kad sistema veiktų patikimai).

Kuriant surinkėją beveik nėra alternatyvos:

  • aparatinės įrangos tvarkyklės ir operacinės sistemos branduolys (bent jau nuo mašinos priklausomi OS branduolio posistemiai), kai svarbu suderinti periferinių įrenginių darbą su centriniu procesoriumi;
  • programos, kurios turi būti saugomos ribotoje ROM ir (arba) paleidžiamos riboto našumo įrenginiuose (kompiuterių ir įvairių elektroninių įrenginių „programinė įranga“)
  • platformai būdingi aukšto lygio kalbų kompiliatorių ir vertėjų komponentai, sistemos bibliotekos ir kodas, įgyvendinantis platformos suderinamumą.

Atskirai galima pastebėti, kad disassembler programos pagalba galima sukompiliuotą programą paversti asamblėjos kalbos programa. Daugeliu atvejų tai yra vienintelis (nors ir labai daug laiko reikalaujantis) būdas pakeisti programos algoritmus, jei jos šaltinio kodas aukšto lygio kalba nepasiekiamas.

Apribojimai

Taikymas

Istoriškai, jei mašininiai kodai laikomi pirmąja programavimo kalbų karta, tai asamblėjos kalba gali būti laikoma antrosios kartos programavimo kalbomis. Asamblėjos kalbos trūkumai, didelių programinės įrangos sistemų kūrimo sudėtingumas lėmė trečiosios kartos kalbų - aukšto lygio programavimo kalbų (tokių kaip Fortran, Lisp, Cobol, Pascal, C ir kt.) atsiradimą. ). Būtent aukšto lygio programavimo kalbos ir jų įpėdinės šiuo metu daugiausia naudojamos informacinių technologijų pramonėje. Tačiau surinkimo kalbos išlaiko savo nišą dėl savo unikalių pranašumų, susijusių su efektyvumu ir galimybe visiškai išnaudoti specifines konkrečios platformos ypatybes.

Programos arba jų fragmentai parašyti asamblėjos kalba tais atvejais, kai jie yra labai svarbūs:

  • pasirodymas (vairuotojai, žaidimai);
  • naudojamos atminties kiekis (įkrovos sektoriai, įdėtoji (angl. embedded) programinė įranga, programos mikrovaldikliams ir procesoriams su ribotais ištekliais, virusai, programinė apsauga).

Naudojant asamblėjos kalbos programavimą, sukuriama:

  • Greitai kritinių programų skyrių optimizavimas programose aukšto lygio kalbomis, tokiomis kaip C++ arba Pascal. Tai ypač pasakytina apie žaidimų konsoles, kurių našumas yra fiksuotas, ir daugialypės terpės kodekams, kurie paprastai naudoja mažiau išteklių ir greitesni.
  • Operacinių sistemų (OS) ar jų komponentų kūrimas. Šiuo metu didžioji dauguma operacinių sistemų yra parašytos aukštesnio lygio kalbomis (daugiausia C, aukšto lygio kalba, kuri buvo specialiai sukurta rašyti vienai pirmųjų UNIX versijų). Nuo aparatinės įrangos priklausomos kodo dalys, pvz., OS įkroviklis, aparatinės įrangos abstrakcijos sluoksnis ir branduolys, dažnai rašomi surinkimo kalba. Tiesą sakant, „Windows“ ar „Linux“ branduoliuose yra labai mažai surinkimo kodo, nes autoriai siekia užtikrinti perkeliamumą ir patikimumą, tačiau vis dėlto jis yra. Kai kurios mėgėjiškos OS, tokios kaip MenuetOS ir KolibriOS, yra parašytos tik asamblėjos kalba. Tuo pačiu metu „MenuetOS“ ir „KolibriOS“ yra įdėtos į diskelį ir turi grafinę kelių langų sąsają.
  • Mikrovaldiklių (MC) ir kitų integruotų procesorių programavimas. Pasak profesoriaus Tanenbaumo, MC raida pakartoja istorinę šiuolaikinių kompiuterių raidą. Dabar (2013 m.) MK programavimui labai dažnai naudojama asamblėjos kalba (nors tokios kalbos kaip C taip pat plačiai naudojamos šioje srityje). MK turite perkelti atskirus baitus ir bitus tarp skirtingų atminties ląstelių. MK programavimas yra labai svarbus, nes, pasak Tanenbaumo, šiuolaikinio civilizuoto žmogaus automobilyje ir bute vidutiniškai yra 50 mikrovaldiklių.
  • Vairuotojų kūrimas. Tvarkyklės (arba kai kurie jų programinės įrangos moduliai) programa asamblėjos kalba. Nors šiuo metu vairuotojai taip pat linkę rašyti aukšto lygio kalbomis (aukšto lygio kalba parašyti patikimą tvarkyklę daug lengviau) dėl išaugusių šiuolaikinių procesorių patikimumo ir pakankamo našumo reikalavimų (greitis užtikrina procesų laikas įrenginyje ir procesoriuje) ir pakankamas kompiliatorių tobulumas aukšto lygio kalbomis (sugeneruotame kode nėra nereikalingų duomenų perdavimo), didžioji dauguma šiuolaikinių tvarkyklių yra parašytos asamblėjos kalba. Tvarkyklių patikimumas vaidina ypatingą vaidmenį, nes Windows NT ir UNIX (įskaitant Linux) tvarkyklės veikia sistemos branduolio režimu. Viena subtili vairuotojo klaida gali sugadinti visą sistemą.
  • Antivirusinių ir kitų apsauginių programų kūrimas.
  • Kodo rašymas žemo lygio programavimo kalbų vertėjų bibliotekoms.

Programų susiejimas įvairiomis kalbomis

Kadangi ilgą laiką asamblėjos kalba dažnai buvo koduojami tik programų fragmentai, jie turi būti susieti su likusia programinės įrangos sistema, parašyta kitomis programavimo kalbomis. Tai pasiekiama dviem pagrindiniais būdais:

  • Kompiliavimo stadijoje - asemblerio fragmentų (angl. inline assembler) įterpimas į programos šaltinio kodą aukšto lygio kalba naudojant specialias kalbos direktyvas. Metodas patogus atliekant paprastus duomenų transformavimus, tačiau neįmanoma sukurti visaverčio surinkėjo kodo su duomenimis ir paprogramėmis, įskaitant paprogrames su daugybe įėjimų ir išėjimų, kurių nepalaiko aukšto lygio kalba.
  • Nuorodos etape, kai sudaroma atskirai. Kad sukomponuojami moduliai sąveikautų, pakanka, kad importuotos funkcijos (nubrėžtos kai kuriuose moduliuose ir naudojamos kituose) palaiko tam tikras iškvietimo taisykles. Atskiri moduliai gali būti parašyti bet kuria kalba, įskaitant surinkimo kalbą.

Sintaksė

Surinkimo kalbos sintaksė nustatoma pagal konkretaus procesoriaus instrukcijų rinkinį.

Komandų rinkinys

Įprastos surinkimo kalbos komandos yra (dauguma pavyzdžių pateikiami x86 architektūros „Intel“ sintaksei):

  • Duomenų perdavimo komandos (mov ir kt.)
  • Aritmetinės komandos (add , sub , imul ir kt.)
  • Loginės ir bitinės operacijos (arba , ir , xor , shr ir kt.)
  • Programos srauto valdymo komandos (jmp , loop , ret ir kt.)
  • Nutraukimo skambučio instrukcijos (kartais vadinamos valdymo instrukcijomis): tarpt
  • Įvesties / išvesties komandos prievadams (į, išvestis)
  • Mikrovaldikliams ir mikrokompiuteriams taip pat būdingos komandos, kurios atlieka patikrinimus ir perėjimus pagal sąlygas, pavyzdžiui:
  • cjne – šuolis, jei ne lygus
  • djnz - mažinti, o jei rezultatas yra ne nulis, tada peršokti
  • cfsneq – palyginkite, o jei ne lygūs, praleiskite kitą komandą

Instrukcijos

Įprastas komandų įrašymo formatas

[etiketė:] [ [priešdėlis] mnemokodas [operandas (, operandas)] ] [ ;komentaras]

kur mnemokodas- tiesiogiai procesoriui skirtos instrukcijos mnemonika. Prie jo galima pridėti priešdėlių (pakartojimų, adresų tipo pasikeitimų ir kt.).

Naudojama mnemonika paprastai yra vienoda visiems tos pačios architektūros ar architektūros šeimos procesoriams (tarp plačiai žinomų yra x86, ARM, SPARC, PowerPC, M68k procesorių ir valdiklių mnemonika). Jie aprašyti procesoriaus specifikacijose. Galimos išimtys:

  • jei surinkėjas naudoja kelių platformų AT&T sintaksę (originali mnemonika konvertuojama į AT&T sintaksę);
  • jei iš pradžių buvo du mnemonikos įrašymo standartai (instrukcijų sistema buvo paveldėta iš kito gamintojo procesoriaus).

Pavyzdžiui, Zilog Z80 procesorius paveldėjo Intel 8080 instrukcijų rinkinį, jį išplėtė ir savaip pakeitė mnemoniką (ir registrų pavadinimus). „Motorola Fireball“ procesoriai paveldėjo Z80 instrukcijų rinkinį, jį šiek tiek sumažindami. Tuo pačiu metu „Motorola“ oficialiai grįžo prie „Intel“ mnemonikos ir šiuo metu pusė „Fireball“ surinkėjų dirba su „Intel“ mnemonika, o pusė su „Zilog“ mnemonika.

direktyvas

Asamblėjos kalbos programoje gali būti direktyvas: instrukcijos, kurios tiesiogiai neverčiamos į mašinos instrukcijas, bet kontroliuoja kompiliatoriaus veikimą. Jų rinkinys ir sintaksė labai skiriasi ir priklauso ne nuo aparatinės įrangos platformos, o nuo naudojamo vertėjo (dėl to atsiranda kalbų dialektai toje pačioje architektūrų šeimoje). Kaip „džentelmenišką direktyvų rinkinį“, galima išskirti:

  • duomenų apibrėžimas (konstantos ir kintamieji),
  • valdyti programos organizavimą atmintyje ir išvesties failo parametrus,
  • kompiliatoriaus režimo nustatymas,
  • visų rūšių abstrakcijos (tai yra aukšto lygio kalbų elementai) - nuo procedūrų ir funkcijų projektavimo (siekiant supaprastinti procedūrinio programavimo paradigmos įgyvendinimą) iki sąlyginių struktūrų ir kilpų (struktūrinio programavimo paradigmai),

Programos pavyzdys

Programų pavyzdžiai Sveiki, pasauli! skirtingoms platformoms ir skirtingiems dialektams:

SECTION .data msg: db " Sveiki , pasauli " , 10 len: equ $-msg SECTION .text global _start _start: mov edx , len mov ecx , msg mov ebx , 1 ; stdout mov eax , 4 ; write(2) int 0x80 mov ebx , 0 mov eax , 1 ; exit(2) int 0x80

SECTION .data msg: db " Sveiki , pasauli " , 10 len: equ $-msg SECTION .text global _start syscall: int 0x80 ret _start: push len push msg push 1 ; stdout mov eax , 4 ; write(2) skambinti syscall add esp , 3 * 4 push 0 mov eax , 1 ; exit(2) skambinkite syscall

386 .model flat , stdcall parinkties atvejo žemėlapis : none include \ masm32 \ include \ windows.inc include \ masm32 \ include \ kernel32.inc includelib \ masm32 \ lib \ kernel32.lib .data msg db, 1 Sveiki 1 pasaulis , 1 len equ $-msg .data ? parašyta dd ? .code start: push - 11 call GetStdHandle push 0 push OFFSET parašyta push len push OFFSET msg push eax call WriteFile push 0 call ExitProcess pabaigos pradžia

formato PE konsolės įrašo pradžia apima " include \ win32a.inc " skyrius " .data " duomenys skaitomas rašomas pranešimas db " Sveiki , pasauli ! " , 0 skyrius " .kodas " kodas skaitomas vykdomasis pradžia: ; CINVOKE makrokomandą FASM. ; Leidžia iškviesti CDECL funkcijas. cinvoke printf , pranešimas cinvoke getch ; INVOKE yra panaši STDCALL funkcijų makrokomanda. iškviesti ExitProcess , 0 skyrius " .idata " importuoti skaitomus bibliotekos branduolius , " kernel32.dll " , \ msvcrt , " msvcrt.dll " importuoti branduolį , \ ExitProcess , " ExitProcess " importuoti msvcrt , \ printf , \ printf , , "_getch"

;yasm-1.0.0-win32.exe -f win64 HelloWorld_Yasm.asm;setenv /Release /x64 /xp ;nuoroda HelloWorld_Yasm.obj Kernel32.lib User32.lib /entry:main /subsystem:windows /LARGEADRESSAWARE:NO bitai 64 global main extern MessageBoxA extern ExitProcess skyrius .data mytit db " 64 bitų Windows & assembler pasaulis... " , 0 mymsg db " Hello World ! " , 0 skyrius .text pagrindinis: mov r9d , 0 ; uType = MB_OK mov r8, mytit; LPCSTR lpCaption mov rdx, mymsg; LPCSTR lpText mov rcx , 0 ; hWnd = HWND_DESKTOP skambutis MessageBoxA mov ecx, eax; uExitCode = MessageBox(...) skambinkite ExitProcess ret

Skyrius ".data" labas: .asciz "Sveikas pasaulis!\n" .skiltis ".text" .align 4 .global pagrindinis pagrindinis: išsaugoti %sp , - 96 , %sp ! paskirstykite atmintį mov 4 , %g1 ! 4 = RAŠYTI (sistemos iškvietimas) mov 1 , %o0 ! 1 = STDOUT rinkinys labas , %o1 mov 14 , %o2 ! simbolių skaičius ta 8! sistemos skambutis! programos išeiti mov 1 , %g1 ! perkelti 1 (išeiti () syscall ) į %g1 mov 0 , %o0 ! perkelti 0 (grąžinti adresą) į %o0 ta 8! sistemos skambutis

O h HidnSec dw 00000 h kodas: cli mov ax , cs mov ds , ax mov ss , ax mov sp , 7 c00h sti mov ax , 0 b800h mov es , ax mov di , 200 mov ah , 2 mov ah , 2 mov ah , 2 mov: mov ds al ,[ cs : bx ] mov [ es : di ], ax inc bx add di , 2 cmp bx , MessEnd jnz msg_print loo: jmp loo MessStr equ $ Pranešimas db " Sveiki , pasauli! " MessEnd equ $

Istorija ir terminija

Šis kalbos tipas gavo savo pavadinimą iš šių kalbų vertėjo (kompiliatoriaus) pavadinimo - asamblėjas (anglų asembleris - asamblėjas). Pavadinimas atsirado dėl to, kad programa buvo „automatiškai surinkta“, o ne rankiniu būdu įvedama komanda po komandos tiesiai į kodus. Tuo pačiu metu yra terminų painiava: asembleris dažnai vadinamas ne tik vertėju, bet ir atitinkama programavimo kalba („assembler programa“).

Atleiskite, ar turite minutę pakalbėti apie mūsų gelbėtoją, surinkėją? Paskutiniame straipsnyje mes parašėme savo pirmąją hello world programą asma, išmokome ją kompiliuoti ir derinti, taip pat išmokome atlikti sistemos skambučius Linux sistemoje. Šiandien mes tiesiogiai susipažinsime su surinkėjo instrukcijomis, registrų sąvoka, kaminu ir visa tai. x86 (dar žinomas kaip i386) ir x64 (dar žinomas kaip amd64) surinkėjai yra labai panašūs, todėl nėra prasmės juos nagrinėti atskiruose straipsniuose. Be to, pabandysiu sutelkti dėmesį į x64, kartu atkreipdamas dėmesį į skirtumus nuo x86, jei tokių yra. Toliau daroma prielaida, kad jūs jau žinote, pavyzdžiui, kuo skiriasi krūva nuo krūvos, ir nereikia aiškinti tokių dalykų.

Bendrosios paskirties registrai

Registras yra maža (paprastai 4 arba 8 baitų) atminties dalis procesoriuje su itin didelis greitis prieiga. Registrai skirstomi į registrus specialus tikslas ir bendrieji registrai. Dabar domimės bendrosios paskirties registrais. Kaip galima atspėti iš pavadinimo, programa šiuos registrus gali naudoti savo reikmėms, kaip tik nori.

X86 sistemoje galimi aštuoni 32 bitų bendrosios paskirties registrai – eax, ebx, ecx, edx, esp, ebp, esi ir edi. Registrai neturi iš anksto nustatyto tipo, tai yra, jie gali būti traktuojami kaip pasirašyti arba nežymėti sveikieji skaičiai, rodyklės, loginiai skaičiavimai, ASCII simbolių kodai ir pan. Nors teoriškai šie registrai gali būti naudojami bet kokiu būdu, praktiškai kiekvienas registras dažniausiai naudojamas tam tikru būdu. Taigi, esp nurodo į krūvos viršų, ecx atlieka skaitiklio vaidmenį, o eax yra operacijos ar procedūros rezultatas. Yra 16 bitų registrai ax, bx, cx, dx, sp, bp, si ir di, kurie yra mažiausiai reikšmingi 16 bitų iš atitinkamų 32 bitų registrų. Taip pat galimi 8 bitų registrai ah, al, bh, bl, ch, cl, dh ir dl, kurie atitinkamai nurodo viršutinį ir apatinį ax, bx, cx ir dx registrų baitus.

Apsvarstykite pavyzdį. Tarkime, kad vykdomos šios trys instrukcijos:

(gdb) x/3i $ vnt
=> 0x8048074: mov $0xaabbccdd,%eax
0x8048079: mov $0xee,%al
0x804807b: mov $0x1234,%ax

Užregistruokite reikšmes po to, kai į eax įrašote 0 x AABBBCCDD:

(gdb) p/x $eax
1 USD = 0xaabbccdd
(gdb) p/x $ax
2 USD = 0xccdd
(gdb) p/x $ah
3 USD = 0xcc
(gdb) p/x $al
4 USD = 0xdd

Reikšmės parašius 0 užregistruoti al x EE:

(gdb) p/x $eax
5 USD = 0xaabbccee
(gdb) p/x $ax
6 USD = 0xccee
(gdb) p/x $ah
7 USD = 0xcc
(gdb) p/x $al
8 USD = 0xee

Užregistruokite reikšmes po to, kai rašote nuo 0 iki ax x 1234:

(gdb) p/x $eax
9 USD = 0xaabb1234
(gdb) p/x $ax
10 USD = 0 x 1234
(gdb) p/x $ah
11 USD = 0 x 12
(gdb) p/x $al
12 USD = 0 x 34

Kaip matote, nieko sudėtingo.

Pastaba: GAS sintaksė leidžia aiškiai nurodyti operandų dydžius naudojant priesagas b (baitas), w (žodis, 2 baitai), l (ilgas žodis, 4 baitai), q (keturžodis, 8 baitai) ir kai kurias kitas. Pavyzdžiui, vietoj komandos mov $0xEE , % al tu gali rašyti movb $0xEE , %al , vietoj Mov $0x1234 , % axmovw $0x1234 , %ax , ir taip toliau. Šiuolaikinėje GAS šios priesagos yra neprivalomos ir aš asmeniškai jų nenaudoju. Tačiau neišsigąskite, jei juos pamatysite kieno nors kito kode.

X64 sistemoje registro dydis padidintas iki 64 bitų. Atitinkami registrai vadinami rax, rbx ir pan. Be to, vietoj aštuonių yra šešiolika bendrosios paskirties registrų. Papildomi registrai pavadinti r8, r9, ..., r15. Atitinkami registrai, žymintys apatinius 32, 16 ir 8 bitus, vadinami r8d, r8w, r8b ir pagal analogiją registrams r9-r15. Be to, atsirado registrai, kurie yra 8 apatiniai rsi, rdi, rbp ir rsp registrų bitai - atitinkamai sil, dil, bpl ir spl.

Apie kreipimąsi

Kaip jau minėta, registrai gali būti laikomi rodyklėmis į atmintyje esančius duomenis. Norint pašalinti nuorodas į tokias nuorodas, naudojama speciali sintaksė:

mov(%rsp) , %rax

Šis įrašas reiškia "perskaitykite 8 baitus iš adreso rsp registre ir išsaugokite juos rax registre". Kai programa paleidžiama, rsp nurodo į krūvos viršų, kurioje saugomas programai perduotų argumentų skaičius (argc), rodyklės į tuos argumentus, taip pat aplinkos kintamieji ir kita informacija. Taigi, įvykdžius aukščiau pateiktą komandą (žinoma, su sąlyga, kad prieš ją nebuvo vykdomos kitos instrukcijos), į rax bus įrašytas argumentų, kuriais buvo paleista programa, skaičius.

Vienoje komandoje galite nurodyti adresą ir su juo susijusį poslinkį (tiek teigiamą, tiek neigiamą):

mov 8 (% rsp ) , % rax

Šis įrašas reiškia "paimkite rsp, pridėkite prie jo 8, gautu adresu perskaitykite 8 baitus ir įdėkite juos į rax". Taigi, rax turės eilutės, atstovaujančios pirmąjį programos argumentą, adresą, ty vykdomojo failo pavadinimą.

Dirbant su masyvais, gali būti patogu nurodyti tam tikro indekso elementą. Atitinkama sintaksė:

# xchg instrukcija sukeičia reikšmes
xchg 16 (% rsp , % rcx , 8 ), % rax

Jis skamba taip: „apskaičiuokite rcx*8 + rsp + 16 ir pakeiskite 8 baitus (registro dydis) gautu adresu ir rax registro verte. Kitaip tariant, rsp ir 16 vis dar atlieka poslinkio vaidmenį, rcx masyve atlieka indekso vaidmenį, o 8 yra masyvo elemento dydis. Naudojant šią sintaksę, galiojantys elementų dydžiai yra 1, 2, 4 ir 8. Jei reikia kitokio dydžio, galite naudoti daugybos, dvejetainio poslinkio ir kitas instrukcijas, kurias aptarsime toliau.

Galiausiai galioja ir šis kodas:

Duomenys
žinutė:
. ascii „Sveikas, pasauli!\n“
. tekstą

Globl_start
_start:
# atstatyti rcx
x arba %rcx , %rcx
mov msg(,% rcx , 8 ) , % al
mov msg, %ah

Ta prasme, kad negalite nurodyti registro su poslinkiu arba apskritai jokių registrų. Vykdant šį kodą į al ir ah registrus bus įrašytas H raidės arba 0 ASCII kodas. x 48.

Šiame kontekste norėčiau paminėti dar vieną naudingą surinkėjo instrukciją:

# rax:= rcx*8 + rax + 123
lea 123 (% rax , % rcx , 8 ), % rax

„Lea“ instrukcija yra labai patogi, nes leidžia vienu metu atlikti dauginimą ir kelis sudėjimus.

Linksmi faktai! Jei naudojate x64, instrukcijos baito kodas niekada nenaudoja 64 bitų poslinkių. Skirtingai nuo x86, instrukcijos dažnai veikia ne absoliučiais adresais, o adresais, susijusiais su pačios instrukcijos adresu, o tai leidžia pasiekti artimiausią +/- 2 GB RAM. Atitinkama sintaksė:

movb msg(% rip) , % al

Palyginkime „įprasto“ ir „santykinio“ mov opkodų (objdump -d) ilgius:

4000b0: 8a 0c 25 e8 00 60 00 mov 0x6000e8,%cl
4000b7: 8a 05 2b 00 20 00 mov 0x20002b(%rip),%al # 0x6000e8

Kaip matote, "santykinis" mov taip pat yra vienu baitu trumpesnis! Kokio tipo registras yra šis plėšimas, sužinosime šiek tiek žemiau.

Norėdami įrašyti visą 64 bitų reikšmę į registrą, pateikiama speciali instrukcija:

movabs $0x1122334455667788 , %rax

Kitaip tariant, x64 procesoriai koduoja instrukcijas taip pat taupiai kaip ir x86 procesoriai, o šiais laikais nėra prasmės naudoti x86 procesorius sistemose, kuriose yra pora gigabaitų RAM ar mažiau (mobilieji įrenginiai, šaldytuvai, mikrobangų krosnelės ir pan. įjungta). Tikėtina, kad x64 procesoriai bus dar efektyvesni dėl daugiau prieinamų registrų ir didesnio dydžiošiuos registrus.

Aritmetiniai veiksmai

Apsvarstykite pagrindines aritmetines operacijas:

# inicijuoti registro reikšmes
Mov $123, %rax
Mov $456, %rcx

# padidėjimas: rax = rax + 1 = 124
inc%rax

# sumažinimas: rax = rax - 1 = 123
dec%rax

# papildymas: rax = rax + rcx = 579
pridėti % rcx , % rax

# atimtis: rax = rax - rcx = 123
sub % rcx , % rax

# keitimo ženklas: rcx = - rcx = -456
neg %rcx

Čia ir žemiau operandai gali būti ne tik registrai, bet ir atminties sritys arba konstantos. Tačiau abu operandai negali būti atminties vietos. Ši taisyklė taikoma visoms x86/x64 surinkėjo instrukcijoms, bent jau aptartoms šiame straipsnyje.

Daugybos pavyzdys:

Mov $100, % al
Mov $3, %cl
mul % cl

Šiame pavyzdyje mul instrukcija padaugina al iš cl ir išsaugo daugybos rezultatą al ir ah registrų poroje. Taigi, axe bus 0 reikšmė x 12C arba 300 dešimtainiu žymėjimu. Blogiausiu atveju gali prireikti iki 2*N baitų, kad būtų išsaugotas dviejų N baitų verčių padauginimas. Priklausomai nuo operando dydžio, rezultatas išsaugomas al:ah, ax:dx, eax:edx arba rax:rdx. Be to, pirmasis iš šių registrų ir instrukcijai perduotas argumentas visada naudojami kaip daugikliai.

Ženklo daugyba atliekama lygiai taip pat, naudojant imul instrukciją. Be to, yra imul variantų su dviem ir trimis argumentais:

Mov $123, %rax
Mov $456, %rcx

#rax=rax*rcx=56088
imul % rcx , % rax

#rcx=rax*10=560880
imul $10, % rax, % rcx

Div ir idiv instrukcijos veikia priešingai nei mul ir imul. Pavyzdžiui:

Mov $0, %rdx
Mov $456, %rax
Mov $123, %rcx

# rax = rdx:rax / rcx = 3
# rdx = rdx:rax % rcx = 87
div %rcx

Kaip matote, buvo gautas sveikojo skaičiaus padalijimo rezultatas, taip pat likusi padalijimo dalis.

Tai dar ne visos aritmetinės instrukcijos. Pavyzdžiui, taip pat yra adc (pridėti su nešiojimo vėliavėle), sbb (atimti su skolinimu), taip pat juos atitinkančios instrukcijos, kurios nustato ir išvalo atitinkamas vėliavėles (ctc, clc) ir daugelis kitų. Tačiau jie yra daug rečiau paplitę, todėl šiame straipsnyje jie nėra nagrinėjami.

Loginės ir bitų operacijos

Kaip jau buvo pažymėta, x86/x64 surinkėjo specialaus spausdinimo nėra. Todėl nenustebkite, kad jame nėra atskirų instrukcijų Būlio operacijoms atlikti ir atskirų nurodymų bitų operacijoms atlikti. Vietoj to, yra vienas instrukcijų rinkinys, kuris veikia su bitais, o kaip interpretuoti rezultatą, priklauso nuo konkrečios programos.

Taigi, pavyzdžiui, paprasčiausios loginės išraiškos apskaičiavimas atrodo taip:

mov $0 , % rax # a = false
mov $1 , % rbx # b = true
mov $0 , % rcx # c = false

# rdx:= a || !(b & c)
mov % rcx , % rdx # rdx = c
ir % rbx , % rdx # rdx &= b
ne %rdx#rdx=~rdx
arba % rax , % rdx # rdx |= a
ir $1 , % rdx # rdx &= 1

Atkreipkite dėmesį, kad čia mes panaudojome vieną mažiausiai reikšmingą bitą kiekviename iš 64 bitų registrų. Taigi aukštuosiuose bituose susidaro šiukšlės, kurias paskutine komanda atstatome į nulį.

Kitas naudingas nurodymas yra xor (išskirtinis arba). Būlio išraiškose xor naudojamas retai, tačiau dažnai iš naujo nustato registrus. Jei pažvelgsite į instrukcijų kodus, paaiškės, kodėl:

4000b3: 48 31 db xarba %rbx,%rbx
4000b6: 48 ff c3 inc %rbx
4000b9: 48 c7 c3 01 00 00 00 mov $0x1,%rbx

Kaip matote, „xor“ ir „inc“ instrukcijos yra užkoduotos tik po tris baitus, o tą patį atliekanti „mov“ komanda užima net septynis baitus. Kiekvieną atskirą atvejį, žinoma, geriau lyginti atskirai, tačiau bendra euristinė taisyklė yra tokia – kuo trumpesnis kodas, tuo daugiau jis telpa į procesoriaus talpyklas, tuo greičiau jis veikia.

Šiame kontekste taip pat turėtume prisiminti bitų poslinkio, bitų testo (bitų testo) ir bitų nuskaitymo (bitų nuskaitymo) instrukcijas:

# įrašykite ką nors į registrą
movabs $0xc0de1c0ffee2beef , %rax

# poslinkis į kairę 3 bitai
# rax = 0x0de1c0ffee2beef0
shl $ 4, % rax

# poslinkis į dešinę 7 bitai
#rax = 0x001bc381ffdc57dd
Shr $ 7, % rax

# pasukti dešinėn 5 bitus
#rax=0xe800de1c0ffee2be
ror $5 , % rax

# pasukti į kairę 5 bitais
#rax = 0x001bc381ffdc57dd
ritinys $5 , % rax

# tas pats + bitų nustatymas (bitų patikrinimas ir nustatymas)

bts 13 USD, % rax

# tas pats + atstatymo bitas (bitų patikrinimas ir nustatymas iš naujo)
#rax=0x001bc381ffdc57dd, CF=1
btr 13 USD, % rax

# tas pats + apverstas bitas (bitų patikrinimas ir papildymas)
#rax=0x001bc381ffdc77dd, CF=0
btc $13, % rax

# rasti mažiausiai reikšmingą nulinį baitą (bitų nuskaitymas pirmyn)
#rcx=0, ZF=0
bsf %rax , %rcx

# rasti svarbiausią nulinį baitą (bitų nuskaitymas atvirkštine)
#rdx=52, ZF=0
bsr % rax , % rdx

# jei visi bitai lygūs nuliui, ZF = 1, rdx reikšmė neapibrėžta
xor % rax , % rax
bsf %rax , %rdx

Taip pat yra pažymėti bitų poslinkiai (sal, sar), cikliniai poslinkiai su pernešimo vėliavėle (rcl, rcr) ir dvigubi tikslumo poslinkiai (shld, shrd). Bet jie naudojami ne taip dažnai, ir jūs pavargsite išvardindami visas instrukcijas apskritai. Todėl jų studijas palieku jums kaip namų darbus.

Sąlygos ir kilpos

Kai kurios vėliavos buvo paminėtos kelis kartus, pavyzdžiui, perdavimo vėliava. Vėliavos yra specialaus registro vėliavėlių / rflag bitai (pavadinimas atitinkamai x86 ir x64). Šis registras negali būti tiesiogiai pasiekiamas naudojant mov, add ir panašias instrukcijas, tačiau jis keičiamas ir naudojamas netiesiogiai įvairiomis instrukcijomis. Pavyzdžiui, jau minėta nešiojimo vėliavėlė (CF) yra saugoma vėliavėlių / rflags nuliniame bite ir naudojama, pavyzdžiui, toje pačioje bt instrukcijoje. Kitos dažnai naudojamos vėliavėlės yra nulio vėliavėlė (ZF, 6-asis bitas), ženklo vėliavėlė (SF, 7-asis bitas), krypties vėliavėlė (DF, 10-asis bitas) ir perpildymo vėliavėlė (OF, 11-asis bitas).

Kitas iš šių numanomų registrų turėtų būti vadinamas eip / rip, kuriame saugomas dabartinės instrukcijos adresas. Jis taip pat negali būti pasiekiamas tiesiogiai, bet yra matomas GDB kartu su vėliavėlėmis / rflags, jei sakote info registers , ir keičiamas netiesiogiai Visi nurodymus. Dauguma instrukcijų tiesiog padidina eip / rip tos instrukcijos trukme, tačiau yra šios taisyklės išimčių. Pavyzdžiui, jmp instrukcija tiesiog peršoka į nurodytą adresą:

# iš naujo nustatyti rax
xor % rax , % rax
jmp kitas
# ši instrukcija bus praleista
inc%rax
Kitas:
inc%rax

Dėl to rax reikšmė bus lygi vienetui, nes pirmoji inc instrukcija bus praleista. Atkreipkite dėmesį, kad šuolio adresą taip pat galima įrašyti registre:

xor % rax , % rax
mov $kitas, %rcx
jmp*%rcx
inc%rax
Kitas:
inc%rax

Tačiau praktikoje tokio kodo geriausia vengti, nes jis pažeidžia šakų numatymą ir todėl yra mažiau efektyvus.

Pastaba: GAS leidžia etiketėms suteikti skaitinius pavadinimus, pvz., 1: , 2: ir tt, ir pereiti prie artimiausios ankstesnės arba kitos etiketės su nurodytu numeriu su tokiomis instrukcijomis kaip jmp1b ir jmp 1f. Tai gana patogu, nes kartais gali būti sunku sugalvoti prasmingus etikečių pavadinimus. Išsamią informaciją galima rasti.

Sąlyginiai šuoliai paprastai įgyvendinami naudojant cmp komandą, kuri lygina du savo operandus ir nustato atitinkamas vėliavėles, o po to seka instrukcija iš je, jg ​​ir panašių šeimų:

cmp %rax , %rcx

taip 1f # šuolis, jei lygus (lygus)
jl 1f # šuolis, jei ženklas mažiau (mažiau)
jb 1f # šuolis, jei nepasirašytas mažesnis nei (toliau)
jg 1f # šuolis, jei ženklas didesnis nei (didesnis)
ir 1f # šuolis, jei nepasirašytas didesnis nei (aukščiau)

Taip pat yra instrukcijos jne (šokti, jei ne lygus), jle (šokti, jei ženklas mažesnis arba lygus), jna (šokti, jei nepasirašytas ne didesnis nei) ir panašiai. Jų įvardijimo principas, tikiuosi, yra akivaizdus. Vietoj je / jne dažnai rašoma jz / jnz, nes je / jne instrukcijos tiesiog patikrina ZF reikšmę. Taip pat yra instrukcijos, kurios tikrina kitas vėliavėles – js, jo ir jp, tačiau praktikoje jos naudojamos retai. Visos šios instrukcijos kartu paprastai vadinamos jcc. Tai yra, vietoj konkrečių sąlygų rašomos dvi raidės „c“, iš „sąlyga“. galite rasti gerą visų jcc instrukcijų suvestinę lentelę ir kokias vėliavėles jie tikrina.

Be cmp, taip pat dažnai naudojamas testo teiginys:

testas %rax , %rax
jz 1f # šuolis, jei rax == 0
js 2f # šuolis, jei rax< 0
1 :
# kažkoks kodas
2 :
# kitas kodas

Linksmi faktai!Įdomu tai, kad cmp ir test iš esmės yra tokie patys kaip sub ir ir, tik jie nekeičia savo operandų. Šios žinios gali būti panaudotos norint vienu metu vykdyti sub arba ir sąlyginę šaką be papildomų cmp ar testavimo instrukcijų.

Kitas nurodymas, susijęs su sąlyginiais šuoliais, yra toks.

jrcxz 1f
# kažkoks kodas
1 :

Instrukcija jrcxz šokinėja tik tuo atveju, jei rcx registro reikšmė lygi nuliui.

cmovge %rcx , %rax

Cmovcc šeimos instrukcijos (sąlyginis judėjimas) veikia kaip mov, bet tik tada, kai įvykdoma nurodyta sąlyga, pagal analogiją su jcc.

setnz % al

Setcc instrukcijos nustato vieno baito registrą arba baitą atmintyje į 1, jei nurodyta sąlyga yra teisinga, ir 0 kitu atveju.

cmpxchg % rcx , (% rdx )

Palyginkite rax su duota atminties dalimi. Jei lygi, nustatykite ZF ir išsaugokite nurodyto registro reikšmę nurodytu adresu, šiame pavyzdyje rcx. Kitu atveju išvalykite ZF ir įkelkite reikšmę iš atminties į rax. Be to, abu operandai gali būti registrai.

cmpxchg8b(%rsi)
cmpxchg16b(%rsi)

Instrukcija cmpxchg8b dažniausiai reikalinga x86. Jis veikia panašiai kaip cmpxchg, tik lygina ir keičia 8 baitus vienu metu. Palyginimui naudojami edx:eax registrai, o ecx:ebx registrai saugo tai, ką norime parašyti. Instrukcija cmpxchg16b tuo pačiu principu lygina ir keičia 16 baitų vienu metu x64.

Svarbu! Atminkite, kad be užrakto priešdėlio visos šios palyginimo ir keitimo instrukcijos nėra atominės.

Mov $10, %rcx
1 :
# kažkoks kodas
kilpa 1b
# kilpa 1b
# loopnz 1b

Ciklo instrukcija sumažina rcx registro reikšmę vienu, o jei po to rcx != 0 , pereina prie nurodytos etiketės. Loopz ir loopnz instrukcijos veikia panašiai, tik sąlygos yra sudėtingesnės - atitinkamai (rcx != 0) && (ZF == 1) ir (rcx != 0) && (ZF == 0).

Nereikia smegenų, kad suprastume „jei-tada-else“ konstrukcijas arba „for/while“ kilpas su šiomis instrukcijomis, todėl judėkime toliau.

„Styginių“ operacijos

Apsvarstykite šią kodo dalį:

mov $str1, %rsi
mov $str2, % red
cld
cmpsb

Rsi ir rdi registrai užpildyti dviejų eilučių adresais. Komanda cld išvalo krypties vėliavėlę (DF). Instrukcija, kuri veikia priešingai, vadinama std. Tada pradeda veikti cmpsb instrukcija. Jis lygina baitus (%rsi) ir (%rdi) ir nustato vėliavėles pagal palyginimo rezultatą. Tada, jei DF = 0, rsi ir rdi padidės vienu (baitų skaičius, kurį palyginome), kitu atveju jie sumažėja. Panašios instrukcijos cmpsw, cmpsl ir cmpsq lygina žodžius, ilgi žodžiai ir atitinkamai keturių žodžių.

CMP instrukcijos įdomios, nes jas galima naudoti su rep priešdėliu, repe (repz) ir repne (repnz). Pavyzdžiui:

mov $str1, %rsi
mov $str2, % red
mov $len, %rcx
cld
kartoti cmpsb
etc ne_lygus

Rep prefiksas pakartoja nurodymą tiek kartų, kiek nurodyta rcx registre. Tą patį daro ir priešdėliai repz ir repnz, tačiau tik po kiekvieno komandos vykdymo papildomai tikrinamas ZF. Ciklas baigiasi, jei ZF = 0 c repz atveju ir jei ZF = 1 repnz atveju. Taigi aukščiau pateiktas kodas tikrina dviejų tokio paties dydžio buferių lygybę.

Panašios instrukcijos movs perkelia duomenis iš buferio, kurio adresas nurodytas rsi, į buferį, kurio adresas nurodytas rdi (lengva įsiminti – rsi reiškia šaltinį, rdi – paskirties vietą). Stos instrukcija užpildo buferį adresu rdi baitais rax (arba eax, arba ax, arba al, priklausomai nuo konkrečios instrukcijos). Lods instrukcijos daro priešingai – nukopijuokite baitus nurodytu adresu rsi į rax registrą. Galiausiai, scas instrukcijos ieško baitų rax registre (arba atitinkamuose mažesniuose registruose) buferyje, į kurį nurodo rdi. Kaip ir cmps, visos šios instrukcijos veikia su rep, repz ir repnz priešdėliais.

Remiantis šiomis instrukcijomis, memcmp, memcpy, strcmp ir panašios procedūros yra lengvai įgyvendinamos. Įdomu tai, kad, pavyzdžiui, norėdami iš naujo nustatyti atmintį, „Intel“ inžinieriai rekomenduoja naudoti šiuolaikiniuose procesoriuose rep stosb, tai yra, atstatyti baitas po baito, o ne, tarkime, keturiais žodžiais.

Krūvos tvarkymas ir procedūros

Su kaminu viskas labai paprasta. Puslapio instrukcija perkelia savo argumentą į krūvą, o pop instrukcija iškelia reikšmę iš krūvos. Pavyzdžiui, jei laikinai pamiršote xchg instrukciją, galite pakeisti dviejų registrų vertę taip:

stumti %rax
mov % rcx , % rax
pop %rcx

Yra instrukcijos, kurios stumia ir iškelia rflags / flags registrą ant krūvos:

pushf
# padaryti ką nors, kas pakeis vėliavas
popf
Atkurta # vėliavėlė, laikas atlikti JCC

Taigi, pavyzdžiui, galite gauti CF vėliavėlės vertę:

pushf
pop %rax
ir $1 , %rax

X86 sistemoje taip pat yra pusha ir popa instrukcijos, kurios išsaugo ir atkuria visų kamino registrų reikšmes. Naudojant x64 šios instrukcijos nebepasiekiamos. Matyt, kadangi registrų yra daugiau, o patys registrai dabar ilgesni – juos visus išsaugoti ir atkurti tapo daug brangiau.

Procedūros dažniausiai „sukuriamos“ naudojant iškvietimo ir atšaukimo instrukcijas. Skambinimo instrukcija stumia adresą į krūvą sekanti instrukcija ir perduoda valdymą argumente nurodytu adresu. Instrukcija ret nuskaito grąžinimo adresą iš kamino ir perduoda jo valdymą. Pavyzdžiui:

someproc:
# tipinės procedūros prologas
# Pavyzdžiui, vietiniams kintamiesiems paskirkite 0x10 baitų
# rbp – rodyklė į krūvos rėmelį
stumti %rbp
judėjimo % rsp , % rbp
sub $0x10 , % rsp

# čia kažkoks skaičiavimas...
Mov $1, %rax

# tipinės procedūros epilogas
pridėti $0x10, %rsp
pop %rbp

# išėjimo procedūra
ret

pradžia:
# kaip ir jmp, šuolio adresas gali būti registre
paskambink kazkam proc
testas %rax , %rax
jnz klaida

Pastaba: Panašų prologą ir epilogą galima parašyti naudojant instrukcijas įveskite $0x10, $0 ir palikti. Tačiau šiais laikais šie teiginiai retai naudojami, nes jie vykdomi lėčiau dėl papildomos įdėtųjų procedūrų palaikymo.

Paprastai grąžinama reikšmė perduodama registre rax arba, jei jos dydis nėra pakankamai didelis, įrašoma į struktūrą, kurios adresas perduodamas kaip argumentas. Argumentų perdavimo klausimu. Yra daug skambinimo susitarimų. Vienuose visi argumentai visada perduodami per krūvą (atskiras klausimas kokia tvarka) ir pati procedūra yra atsakinga už argumentų krūvos išvalymą, kitose dalis argumentų perduodami per registrus, o dalis per steką. , o skambinantysis yra atsakingas už argumentų krūvos išvalymą ir daugybę parinkčių viduryje, su atskiromis taisyklėmis, kaip suderinti argumentus krūvoje, perduoti, jei tai OOP kalba, ir pan. Bendruoju atveju savavališkai architektūrai, kompiliatoriui ir programavimo kalbai iškvietimo taisyklė gali būti bet kokia.

aš] ;
}
grąžinti maišą;
}

Disassembler sąrašas (kai sudarytas su -O0, komentarai yra mano):

# tipinės procedūros prologas
# registras rsp nesikeičia, nes procedūra nekviečia jokių
# kitos procedūros
400950: 55 stumti %rbp
400951: 48 89 e5 mov %rsp,%rbp

# vietinių kintamųjų inicijavimas:
# -0x08(%rbp) – pastovus nepasirašytas simbolis *duomenys (8 baitai)
# -0x10(%rbp) - const size_t data_len (8 baitai)
# -0x14(%rbp) – nepasirašyta int maiša (4 baitai)
# -0x18(%rbp) - int i (4 baitai)
400954: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400958: 48 89 75 f0 judėjimas %rsi,-0x10(%rbp)
40095c: c7 45 ec 4b 43 41 48 movl $0x4841434b,-0x14(%rbp)
400963: c7 45 e8 00 00 00 00 movl $0x0,-0x18(%rbp)

#rax:= i. jei pasiekiamas data_len, išeikite iš ciklo
40096a: 48 63 45 e8 movslq -0x18(%rbp),%rax
40096e: 48 3b 45 f0 cmp -0x10(%rbp),%rax
400972: 0f 83 28 00 00 00 jae 4009a0

# eax:= (maiša<< 5) + hash
400978: 8b 45ec mov -0x14(%rbp),%eax
40097b: c1 e0 05 shl $0x5,%eax
40097e: 03 45 ec pridėti -0x14(%rbp),%eax

# eax += duomenys[i]
400981: 48 63 4d e8 movslq -0x18(%rbp),%rcx
400985: 48 8b 55 f8 mov -0x8(%rbp),%rdx
400989: 0f b6 34 0a movzbl(%rdx,%rcx,1),%esi
40098d: 01 f0 pridėti %esi,%eax

# hash:= eax
40098f: 89 45 ec mov %eax,-0x14(%rbp)

# i++ ir eikite į ciklo pradžią
400992: 8b 45 e8 mov -0x18(%rbp),%eax
400995: 83 c0 01 pridėti $0x1,%eax
400998: 89 45 e8 mov %eax,-0x18(%rbp)
40099b: e9 ca ff ff ff jmpq 40096a

# grąžinimo vertė (hash) įtraukiama į eax registrą
4009a0: 8b 45ec mov -0x14(%rbp),%eax

# tipiškas epilogas
4009a3: 5d pop %rbp
4009a4: c3 retq

Čia sutikome dvi naujas instrukcijas – movs ir movz. Jie veikia lygiai taip pat kaip mov, tik išplečia vieną operandą iki antrojo dydžio, atitinkamai pasirašyto ir nepasirašyto. Pavyzdžiui, instrukcija movzbl (%rdx,%rcx,1),%esi nuskaito baitą (b) adresu (%rdx,%rcx,1) ir išplečia jį į ilgą žodį (l), pridėdama nulius (z ) ir įtraukia rezultatą į esi registrą.

Kaip matote, procedūrai per rdi ir rsi registrus buvo perduoti du argumentai. Atrodo, kad naudojamas susitarimas, vadinamas System V AMD64 ABI. Teigiama, kad tai yra de facto x64 standartas *nix sistemose. Nematau jokios priežasties čia perpasakoti šios konvencijos aprašymą, susidomėję skaitytojai gali perskaityti visą aprašymą pateiktoje nuorodoje.

Išvada

Nereikia nė sakyti, kad viename straipsnyje neįmanoma aprašyti viso x86 / x64 surinkėjo (be to, nesu tikras, ar aš pats tai gerai žinau visas). Mažiausiai tokios temos kaip operacijos su slankiojo kablelio skaičiais, MMX, SSE ir AVX instrukcijos, taip pat visokios egzotiškos instrukcijos, pvz., lidt, lgdt, bswap , rdtsc, cpuid, movbe, xlatb arba prefetch, buvo paliktos. scenos. Bandysiu juos aprėpti būsimuose straipsniuose, bet nieko nežadu. Taip pat reikėtų pažymėti, kad daugumos tikrų programų objdump -d išvestyje labai retai pamatysite ką nors kita, išskyrus tai, kas aprašyta aukščiau.

Dar viena įdomi tema, palikta už kadro – atominės operacijos, atminties barjerai, suktukai, ir tiek. Pavyzdžiui, palyginimas ir keitimas dažnai įgyvendinamas tiesiog kaip cmpxchg instrukcija su priešdėliu lock . Analogiškai įgyvendinamas atominis prieaugis, mažėjimas ir pan. Deja, visa tai patraukia atskiro straipsnio temą.

Kaip papildomos informacijos šaltinius galime rekomenduoti knygą Modern X86 Assembly Language Programming ir, žinoma, Intel vadovus. „X86 Assembly“ knyga wikibooks.org taip pat yra gana gera.

Internetinėse surinkėjo instrukcijų nuorodose turėtumėte atkreipti dėmesį į šiuos dalykus:

Ar mokate surinkimo kalbą ir, jei taip, ar šios žinios jums naudingos?

Ši informacija iš pradžių buvo paskelbta pagrindinių lentelių paaiškinimų puslapyje. Bet tada buvo nuspręsta, kad šiuos ilgus bendrus argumentus reikia sudėti į atskirą puslapį. Tačiau po tokio perdavimo šie argumentai šiek tiek padaugėjo. Dabar galbūt jie tinka tik skyreliui „Įvairūs užrašai“ ...

Surinkimo instrukcijos ir mašinos instrukcijos

Visų pirma, neturime pamiršti, kad asamblėjos kalbos instrukcijos ir mašinos kalbos instrukcijos yra du skirtingi dalykai. Nors akivaizdu, kad šios dvi sąvokos yra glaudžiai susijusios.

Montuotojo instrukcija yra tam tikras mnemoninis pavadinimas. x86 šeimos procesoriams šis pavadinimas rašomas anglų kalba. Pavyzdžiui, pridėjimo komanda turi pavadinimą PAPILDYTI, o atimties komanda turi pavadinimą SUB.

Komanda rodo komandos pavadinimą asamblėjos kalba.

Mašinos instrukcijos pagrindas yra opkodas, kuris yra tiesiog skaičius. X86 procesoriams (tačiau ir kitiems procesoriams) įprasta naudoti šešioliktainius skaičius. (Aplenkiant pažymime, kad aštuntainiai skaičiai buvo priimti sovietiniams kompiuteriams, su jais buvo mažiau painiavos, nes tokie skaičiai susideda tik iš skaičių ir juose nėra raidžių).

Šio vadovo lentelėse stulpelyje Kodas rodomas mašinos instrukcijos opkodas, o stulpelyje Formatas rodomas mašinos instrukcijos formatas.

Galime daryti prielaidą, kad skirtingų mašinos instrukcijų skaičius tam tikram procesoriui yra lygus galimų operacijų kodų skaičiui. Pagal formatą galite sužinoti, iš kokių komponentų susideda tam tikra mašinos instrukcija. Įvairios mašinos instrukcijos gali turėti skirtingus formatus. Mašinos instrukcijos opkodas visiškai apibrėžia jos formatą.

Dažnai vienoje surinkėjo instrukcijoje yra keli skirtingi mašinos instrukcijų variantai. Be to, šių mašinų komandų formatai skirtingoms parinktims gali skirtis.

Pavyzdžiui, surinkėjo instrukcijoje ADD yra dešimt mašininių instrukcijų variantų su skirtingais veikimo kodais. Tačiau skirtingų formatų yra mažiau, tik trys. Ir kiekvienas iš šių trijų formatų reikalauja skirtingų operandų tipų, kai rašoma instrukcija asamblėjos kalba.

Čia svarbu pažymėti, kad visos šios dešimt mašinos instrukcijų atlieka tą pačią elementarią operaciją, kuri surinkimo kalba vadinama ADD.

Ir todėl pasirodo, kad galima samprotauti taip: procesorius gali atlikti tiek įvairių elementarių operacijų, kiek skirtingų surinkėjo instrukcijų. Tačiau šis paprastas principas vis dar reikalauja išlygų ir pastabų. Kadangi kai kurios surinkėjo komandos taip pat turi sinonimus.

Bendras visų procesoriaus instrukcijų sąrašas gali būti sudarytas įvairiais būdais, pasirenkant skirtingą instrukcijų tvarką. Pagrindiniai du būdai yra.

1 metodas. Paimkite surinkimo kalbos komandas kaip pagrindą ir išdėstykite komandas abėcėlės tvarka. Tada galima gauti tokias lenteles. Visos komandos abėcėlės tvarka (trumpai)

(2) metodas. Paimkite mašinos instrukcijos operatyvinį kodą ir išdėstykite instrukcijas operacinių kodų tvarka. Šiuo atveju būtų geriau, jei bendras sąrašas būtų padalintas į dvi dalis, sudaryti atskirus sąrašus komandoms su vieno baito opkodais ir komandoms su dviejų baitų opkodais. Pirmasis veiksmo kodo baitas Antrasis operacinės kodo baitas

Žinoma, yra ir trečias būdas, kuris dažniausiai naudojamas vadovėliuose. Visas komandas suskirstykite į grupes pagal jų reikšmę ir išstudijuokite jas grupėse, pradedant nuo paprastesnių.

Pagrindinis operacijos kodo baitas

x86 komandų sistemoje vieno baito (256 skirtingos kombinacijos) nepakako visoms komandoms užkoduoti. Todėl operacijos kodas mašinos instrukcijoje užima vieną arba du baitus.

Jei pirmame baite yra kodas 0F, tada opkodas susideda iš dviejų baitų.

Jei operacijos kodas mašinos komandoje susideda iš vieno baito, tai šis vienas baitas yra pagrindinis operacijos kodo baitas. Ir šio baito turinys lemia, kokia operacija.

Jei operacijos kodas mašinos komandoje susideda iš dviejų baitų, tada ne pirmasis, o antrasis baitas bus pagrindinis ir apibrėžiantis operacinės kodo baitas.

Rankinėse lentelėse, kuriose rodomas mašinos instrukcijų kodavimas, pagrindinis operacijos kodo baitas paprastai rodomas du kartus, pirmiausia stulpelyje „Kodas“ kaip šešioliktainis skaičius, o po to stulpelyje „Formatas“ kaip sąlyginiai aštuoni brūkšniai. , ant kurių pažymėti specialūs bitai, jei tokių yra pagrindiniame opcode baite.

Pagrindiniai vadovo puslapiai

x86 procesoriaus instrukcijų nuoroda - pagrindinis puslapis (čia yra visų vadovo puslapių žemėlapis)

Assembler yra žemo lygio programavimo kalba, naudojama įvairiems procesoriams, mikroprocesoriams ir mikrovaldikliams programuoti. Šiame bandyme atsižvelgiama į x86 procesorių surinkėją.

Asamblėjos kalbos programos susideda iš konkrečių instrukcijų rinkinio. Tada šios komandos su vertėjo pagalba konvertuojamos į mašininį kodą, kurį vėliau vykdo centrinis procesorius. Komandų pagalba galima atlikti aritmetinius skaičiavimus, dirbti su atmintimi ir prievadais ir kt.

Paprastai asembleris naudojamas, kai reikia optimizuoti kritines kodo dalis, kad būtų užtikrintas greitis, įrenginių tvarkyklėse, virusuose ir kitose kenkėjiškose programose, operacinėse sistemose, kompiliatoriuose ir kt.

Tikslinė testo auditorija Assembler x86

Testas tikrina asamblėjos kalbos ir x86 architektūros žinias. Testas labiau orientuotas į praktines kalbos ir architektūros žinias, todėl bus įdomus sistemų programuotojams ir studentams pasitikrinti žinias, taip pat bus naudingas visiems programuotojams tobulinant žinias apie kompiuterių architektūrą ir žemo lygio programavimą.

Bandymo struktūra x86 surinkime

Galima savavališkai nustatyti šias temas:

  • Bendrieji klausimai
  • Procesoriaus veikimo režimai (tikrasis, apsaugotas)
  • Procesoriaus instrukcijos

Tolesnis x86 surinkėjo testo tobulinimas

Ateityje planuojame pridėti klausimų neaptartomis temomis (FPU, darbas su įrenginiais / prievadais). Be to, kuriamas vidutinio lygio testas, kurį netrukus bus galima išlaikyti.