x86 assemblerspråk


x86 assemblerspråk är namnet på familjen assemblerspråk som ger en viss nivå av bakåtkompatibilitet med processorer tillbaka till Intel 8008 -mikroprocessorn, som lanserades i april 1972. Den används för att producera objektkod för x86 -klassen av processorer.

Ansett som ett programmeringsspråk är monteringen maskinspecifik och på låg nivå . Som alla assemblerspråk använder x86 assembly mnemonics för att representera grundläggande CPU-instruktioner eller maskinkod . Assembly-språk används oftast för detaljerade och tidskritiska applikationer som små inbyggda realtidssystem , operativsystemkärnor och enhetsdrivrutiner , men kan också användas för andra applikationer. En kompilator kommer ibland att producera monteringskod som ett mellansteg när ett högnivåprogram översätts till maskinkod.

Nyckelord

Reserverade nyckelord för x86 assemblerspråk

  • lds
  • les
  • lfs
  • lgs
  • lss
  • pop
  • skjuta på
  • i
  • ins
  • ut
  • outs
  • lahf
  • sahf
  • popf
  • pushf
  • cmc
  • clc
  • stc
  • cli
  • sti
  • cld
  • std
  • Lägg till
  • adc
  • sub
  • sbb
  • cmp
  • inkl
  • dec
  • testa
  • sal
  • shl
  • sar
  • shr
  • shld
  • shrd
  • inte
  • neg
  • bunden
  • och
  • eller
  • xor
  • imul
  • mul
  • div
  • idiv
  • cbtw
  • cwtl
  • cwtd
  • cltd
  • daa
  • das
  • aaa
  • aas
  • aam
  • aad
  • vänta
  • vänta
  • movs
  • cmps
  • stos
  • lods
  • scas
  • xlat
  • rep
  • repnz
  • repz
  • l ringa
  • ring upp
  • röta
  • lret
  • stiga på
  • lämna
  • jcxz
  • slinga
  • loopnz
  • loopz
  • jmp
  • ljmp
  • int
  • in i
  • iret
  • sldt
  • str
  • lldt
  • ltr
  • verr
  • verw
  • sgdt
  • sidt
  • lgdt
  • lite
  • smsw
  • lmsw
  • lar
  • lsl
  • clts
  • arpl
  • bsf
  • bsr
  • bt
  • btc
  • btr
  • bts
  • cmpxchg
  • fsin
  • fcos
  • fsincos
  • fld
  • fldcw
  • fldenv
  • fprem
  • fucom
  • fucomp
  • fucompp
  • lea
  • mov
  • movw
  • movsx
  • movzb
  • popa
  • pusha
  • rcl
  • rcr
  • roll
  • ror
  • setcc
  • bswap
  • xadd
  • xchg
  • wbinvd
  • invd
  • invlpg
  • låsa
  • nej
  • hlt
  • fld
  • fst
  • fstp
  • fxch
  • fil
  • näve
  • fistp
  • fbld
  • fbstp
  • fadd
  • faddp
  • fiadd
  • fsub
  • fsubp
  • fsubr
  • fsubrp
  • fisubrp
  • fisubr
  • fmul
  • fmulp
  • fimul
  • fdiv
  • fdivp
  • fdivr
  • fdivrp
  • fidiv
  • fidivr
  • fsqrt
  • fscale
  • fprem
  • frndint
  • fxtract
  • fabs
  • fchs
  • fcom
  • fcomp
  • fcompp
  • ficom
  • ficomp
  • ftst
  • fxam
  • fptan
  • fpatan
  • f2xm1
  • fyll2x
  • fyll2xp1
  • fldl2e
  • fldl2t
  • fldlg2
  • fldln2
  • fldpi
  • fldz
  • finit
  • fnint
  • fnop
  • fspara
  • fnsave
  • fstew
  • fngryta
  • fstenv
  • fnstenv
  • fstsw
  • fnstsw
  • frstor
  • vänta
  • vänta
  • fclex
  • fnclex
  • fdecstp
  • ffritt
  • fincstp

Mnemonics och opcodes

Varje x86-monteringsinstruktion representeras av en mnemonik som, ofta kombinerad med en eller flera operander, översätts till en eller flera byte som kallas en opcode ; NOP - instruktionen översätts till 0x90, till exempel, och HLT- instruktionen översätts till 0xF4. Det finns potentiella opkoder utan dokumenterad minnesminne som olika processorer kan tolka olika, vilket gör att ett program som använder dem beter sig inkonsekvent eller till och med genererar ett undantag på vissa processorer. Dessa opkoder dyker ofta upp i kodskrivtävlingar som ett sätt att göra koden mindre, snabbare, mer elegant eller bara visa upp författarens skicklighet.

Syntax

x86 assemblerspråk har två huvudsyntaxgrenar : Intel - syntax och AT&T- syntax . Intel-syntax är dominerande i DOS- och Windows -världen, och AT&T-syntax är dominerande i Unix -världen, eftersom Unix skapades på AT&T Bell Labs . Här är en sammanfattning av de viktigaste skillnaderna mellan Intel-syntax och AT&T-syntax :

AT&T Intel
Parameterordning
   movl  $5  ,  %eax 
Källa före destinationen.
   mov  eax  ,  5 
Destination före källa.
Parameterstorlek
   addl  $4  ,  %esp 
Mnemonics suffixas med en bokstav som anger storleken på operanderna: q för qword, l för long (dword), w för word och b för byte.
  lägg  till  speciellt  4  
Kommer från namnet på registret som används (t.ex. rax, eax, ax, al imply q, l, w, b ).
Sigils Omedelbara värden med prefixet "$", register med prefixet "%". Samlaren känner automatiskt av typen av symboler; dvs om de är register, konstanter eller något annat.
Effektiva adresser
   movl  offset  (  %ebx  ,  %ecx  ,  4  ),  %eax 
Allmän syntax för DISP(BAS,INDEX,SKALA) .
       mov  eax  ,  [  ebx  +  ecx  *  4  +  offset  ] 
Aritmetiska uttryck inom hakparenteser; Dessutom måste storleksnyckelord som byte , word eller dword användas om storleken inte kan bestämmas från operanderna.

Många x86-montörer använder Intel-syntax , inklusive FASM , MASM , NASM , TASM och YASM. GAS , som ursprungligen använde AT&T-syntax , har stödt båda syntaxerna sedan version 2.10 via .intel_syntax -direktivet. En egenhet i AT&T-syntaxen för x86 är att x87 -operander är omvända, en ärvd bugg från den ursprungliga AT&T-assemblern.

AT&T-syntaxen är nästan universell för alla andra arkitekturer med samma rörelseordning ; det var ursprungligen en syntax för PDP-11-sammansättning. Intel-syntaxen är specifik för x86-arkitekturen och är den som används i x86-plattformens dokumentation.

Register

x86-processorer har en samling register tillgängliga för att användas som lagrar för binär data. Tillsammans kallas data- och adressregistren för allmänna register. Varje register har ett speciellt syfte utöver vad de alla kan göra:

  • AX multiplicera/dividera, strängladda och lagra
  • BX indexregister för MOVE
  • CX-antal för strängoperationer och skift
  • DX- portadress för IN och OUT
  • SP pekar mot toppen av stapeln
  • BP pekar på basen av stapelramen
  • SI pekar på en källa i strömningsoperationer
  • DI pekar på en destination i stream-operationer

Tillsammans med de allmänna registren finns det dessutom:

  • IP-instruktionspekare
  • FLAGGOR
  • segmentregister (CS, DS, ES, FS, GS, SS) som bestämmer var ett 64k-segment börjar (inga FS och GS i 80286 och tidigare)
  • extra tilläggsregister ( MMX , 3DNow! , SSE , etc.) (endast Pentium och senare).

IP-registret pekar på minnesförskjutningen för nästa instruktion i kodsegmentet (det pekar på den första byten av instruktionen). IP-registret kan inte nås av programmeraren direkt.

x86-registren kan användas genom att använda MOV- instruktionerna. Till exempel, i Intel-syntax:

    mov  yxa  ,  1234h  ; kopierar värdet 1234hex (4660d) till register AX  
       mov  bx  ,  yxa  ; kopierar värdet på AX-registret till BX-registret  

Segmenterad adressering

x86 -arkitekturen i verkligt och virtuellt 8086-läge använder en process som kallas segmentering för att adressera minne, inte den platta minnesmodellen som används i många andra miljöer. Segmentering innebär att komponera en minnesadress från två delar, ett segment och en offset ; segmentet pekar på början av en 64 KB (64×2 10 ) grupp av adresser och förskjutningen avgör hur långt från denna startadress den önskade adressen är. Vid segmenterad adressering krävs två register för en komplett minnesadress. En för att hålla segmentet, den andra för att hålla offset. För att översätta tillbaka till en platt adress flyttas segmentvärdet fyra bitar åt vänster (motsvarande multiplikation med 2 4 eller 16) och läggs sedan till offseten för att bilda hela adressen, vilket gör det möjligt att bryta 64k-barriären genom smart val av adresser , även om det gör programmeringen betydligt mer komplex.

I verkligt läge /skyddad, till exempel, om DS innehåller det hexadecimala talet 0xDEAD och DX innehåller talet 0xCAFE skulle de tillsammans peka på minnesadressen 0xDEAD * 0x10 + 0xCAFE = 0xEB5CE. Därför kan CPU:n adressera upp till 1 048 576 byte (1 MB) i verkligt läge. Genom att kombinera segment- och offsetvärden hittar vi en 20-bitars adress.

Den ursprungliga IBM PC:n begränsade program till 640 KB men en utökad minnesspecifikation användes för att implementera ett bankväxlingsschema som gick ur bruk när senare operativsystem, som Windows, använde de större adressområdena för nyare processorer och implementerade sitt eget virtuella minne system.

Skyddat läge, som börjar med Intel 80286, användes av OS/2 . Flera brister, såsom oförmågan att komma åt BIOS och oförmågan att växla tillbaka till verkligt läge utan att återställa processorn, förhindrade utbredd användning. 80286 var också fortfarande begränsad till att adressera minne i 16-bitars segment, vilket innebär att endast 2 16 byte (64 kilobyte ) kunde nås åt gången. För att komma åt den utökade funktionaliteten hos 80286 skulle operativsystemet sätta processorn i skyddat läge, vilket möjliggör 24-bitars adressering och därmed 2 24 byte minne (16 megabyte ).

I skyddat läge kan segmentväljaren delas upp i tre delar: ett 13-bitars index, en tabellindikatorbit som bestämmer om posten är i GDT eller LDT och en 2-bitars begärd privilegienivå ; se x86-minnessegmentering .

används notationen segment : offset , så i exemplet ovan kan den platta adressen 0xEB5CE skrivas som 0xDEAD:0xCAFE eller som ett segment- och offsetregisterpar; DS:DX.

Det finns några speciella kombinationer av segmentregister och allmänna register som pekar på viktiga adresser:

  • CS:IP (CS är kodsegment , IP är instruktionspekare ) pekar på adressen där processorn kommer att hämta nästa byte med kod.
  • SS:SP (SS är Stack Segment , SP är Stack Pointer ) pekar på adressen till toppen av stacken, dvs den senast skickade byten.
  • DS:SI (DS är Data Segment , SI är Source Index ) används ofta för att peka på strängdata som är på väg att kopieras till ES:DI.
  • ES:DI (ES är Extra Segment , DI är Destination Index ) används vanligtvis för att peka på destinationen för en strängkopia, som nämnts ovan.

Intel 80386 hade tre driftslägen: verkligt läge, skyddat läge och virtuellt läge. Det skyddade läget som debuterade i 80286 utökades för att tillåta 80386 att adressera upp till 4 GB minne, det helt nya virtuella 8086-läget ( VM86 ) gjorde det möjligt att köra ett eller flera real-mode-program i en skyddad miljö som till stor del emulerade real mode, även om vissa program inte var kompatibla (vanligtvis som ett resultat av minnesadresseringstrick eller användning av ospecificerade op-koder).

32-bitars plattminnesmodellen av 80386: s utökade skyddade läge kan vara den viktigaste funktionsändringen för x86-processorfamiljen tills AMD släppte x86-64 2003, eftersom den hjälpte till att driva storskalig användning av Windows 3.1 (som förlitade sig på skyddat läge) eftersom Windows nu kunde köra många applikationer samtidigt, inklusive DOS-applikationer, genom att använda virtuellt minne och enkel multitasking.

Utförandelägen

x86-processorerna stöder fem driftlägen för x86-kod, Real Mode , Protected Mode , Long Mode , Virtual 86 Mode och System Management Mode , där vissa instruktioner är tillgängliga och andra inte. En 16-bitars delmängd av instruktioner är tillgänglig på 16-bitars x86-processorer, som är 8086, 8088, 80186, 80188 och 80286. Dessa instruktioner är tillgängliga i verkligt läge på alla x86-processorer och i 16-bitars skyddat läge ( 80286 och framåt) finns ytterligare instruktioner relaterade till skyddat läge. På 80386 och senare är 32-bitars instruktioner (inklusive senare tillägg) också tillgängliga i alla lägen, inklusive verkligt läge; på dessa processorer läggs V86-läge och 32-bitars skyddat läge till, med ytterligare instruktioner i dessa lägen för att hantera deras funktioner. SMM, med några av sina egna specialinstruktioner, finns på vissa Intel i386SL, i486 och senare processorer. Slutligen, i långt läge (AMD Opteron och framåt), 64-bitars instruktioner och fler register finns också tillgängliga. Instruktionsuppsättningen är likartad i varje läge men minnesadressering och ordstorlek varierar, vilket kräver olika programmeringsstrategier.

Lägena som x86-kod kan köras i är:

  • Real mode (16-bitars)
    • 20-bitars segmenterat minnesadressutrymme (vilket betyder att endast 1 MB minne kan adresseras – faktiskt något mer), direkt programvara åtkomst till perifer hårdvara och inget koncept för minnesskydd eller multitasking på hårdvaran nivå. Datorer som använder BIOS startar i detta läge.
  • Skyddat läge (16-bitars och 32-bitars)
    • Expanderar adresserbart fysiskt minne till 16 MB och adresserbart virtuellt minne till 1 GB . Ger behörighetsnivåer och skyddat minne , vilket förhindrar att program korrumperar varandra. 16-bitars skyddat läge (används under slutet av DOS- eran) använde en komplex, multisegmenterad minnesmodell. 32-bitars skyddat läge använder en enkel, platt minnesmodell.
  • Långt läge (64-bitars)
    • Mestadels en förlängning av 32-bitars (skyddat läge) instruktionsuppsättning, men till skillnad från 16-till-32-bitars övergången släpptes många instruktioner i 64-bitarsläget. Pionjärer av AMD .
  • Virtuellt 8086-läge (16-bitars)
    • Ett speciellt hybriddriftsläge som gör att program och operativsystem i verkligt läge kan köras medan de kontrolleras av ett operativsystem för övervakare av skyddat läge
  • Systemhanteringsläge (16-bitars)
    • Hanterar systemomfattande funktioner som strömhantering, systemhårdvarukontroll och proprietär OEM-designad kod. Den är endast avsedd att användas av systemets firmware. All normal körning, inklusive operativsystemet , är avstängd. Ett alternativt mjukvarusystem (som vanligtvis finns i datorns firmware , eller en hårdvaruassisterad debugger ) exekveras sedan med höga privilegier.

Växla lägen

Processorn körs i verkligt läge omedelbart efter påslagning, så en operativsystemkärna , eller annat program, måste uttryckligen byta till ett annat läge om det vill köras i något annat än verkligt läge . Växlingslägen åstadkommes genom att modifiera vissa bitar av processorns styrregister efter viss förberedelse, och viss ytterligare inställning kan krävas efter växlingen.

Exempel

Med en dator som kör äldre BIOS körs BIOS och starthanteraren i verkligt läge , sedan kontrollerar och växlar 64-bitars operativsystemkärnan processorn till långt läge och startar sedan nya kärnlägestrådar som kör 64-bitars kod.

Med en dator som kör UEFI körs UEFI-firmware (förutom CSM och äldre Option ROM ), UEFI- starthanteraren och UEFI-operativsystemkärnan i långt läge.

Instruktionstyper

är funktionerna i det moderna x86-instruktionssetet :

  • En kompakt kodning
    • Variabel längd och inriktningsoberoende (kodad som lite endian , liksom all data i x86-arkitekturen)
    • Huvudsakligen en- och tvåadressinstruktioner, det vill säga den första operanden är också destinationen.
    • Minnesoperander som både källa och destination stöds (används ofta för att läsa/skriva stackelement adresserade med små omedelbara förskjutningar).
    • Både allmän och implicit registeranvändning ; även om alla sju (räknande ebp ) allmänna registren i 32-bitarsläge, och alla femton (räknande rbp ) allmänna registren i 64-bitarsläge, fritt kan användas som ackumulatorer eller för adressering, används de flesta av dem också implicit av vissa ( mer eller mindre) särskilda instruktioner; Berörda register måste därför tillfälligt bevaras (normalt staplade), om de är aktiva under sådana instruktionssekvenser.
  • Producerar villkorliga flaggor implicit genom de flesta heltals ALU- instruktioner.
  • Stöder olika adresseringslägen inklusive omedelbart, offset och skalat index men inte PC-relativt, förutom hopp (infört som en förbättring av x86-64 -arkitekturen).
  • Inkluderar flyttal till en stapel med register.
  • Innehåller speciellt stöd för atomic läs-modifiera-skriv- instruktioner ( xchg , cmpxchg / cmpxchg8b , xadd och heltalsinstruktioner som kombineras med låsprefixet )
  • SIMD- instruktioner (instruktioner som utför parallella samtidiga enkla instruktioner på många operander kodade i intilliggande celler i bredare register).

Stapla instruktioner

x86-arkitekturen har hårdvarustöd för en exekveringsstackmekanism. Instruktioner som push , pop , call och ret används med den korrekt inställda stacken för att skicka parametrar, för att allokera utrymme för lokal data och för att spara och återställa anropsreturpunkter. Ret size -instruktionen är mycket användbar för att implementera utrymmeseffektiva (och snabba) anropskonventioner där den anropade är ansvarig för att återta stackutrymme som upptas av parametrar.

När du ställer in en stackram för att hålla lokal data för en rekursiv procedur finns det flera val; Enter -instruktionen på hög nivå (introducerad med 80186) tar ett argument för procedurkapsling och ett lokalt storleksargument och kan vara snabbare än mer explicit manipulation av registren (som push bp ; mov bp, sp ; sub sp, storlek ). Huruvida det är snabbare eller långsammare beror på den speciella x86-processorimplementeringen såväl som den anropskonvention som används av kompilatorn, programmeraren eller speciell programkod; den mesta x86-koden är avsedd att köras på x86-processorer från flera tillverkare och på olika tekniska generationer av processorer, vilket innebär mycket varierande mikroarkitekturer och mikrokodlösningar samt varierande gate- och transistornivådesignval .

Hela utbudet av adresseringslägen (inklusive omedelbar och bas+offset ) även för instruktioner som push och pop , gör direkt användning av stacken för heltal , flyttal och adressdata enkel, samt håller ABI -specifikationerna och mekanismerna relativt enkla. jämfört med vissa RISC-arkitekturer (kräver mer explicita samtalsstackdetaljer).

Heltals ALU-instruktioner

x86 assembly har de vanliga matematiska operationerna, add , sub , mul , med idiv ; de logiska operatorerna och , eller , xor , neg ; bitförskjutning aritmetik och logisk, sal / sar , shl / shr ; rotera med och utan carry, rcl / rcr , rol / ror , ett komplement av BCD aritmetiska instruktioner, aaa , aad , daa och andra.

Flyttalsinstruktioner

x86 assemblerspråk innehåller instruktioner för en stackbaserad flyttalsenhet (FPU). FPU:n var en valfri separat samprocessor för 8086 till 80386, det var ett on-chip-alternativ för 80486-serien, och det är en standardfunktion i alla Intel x86-processorer sedan 80486, från och med Pentium. FPU-instruktionerna inkluderar addition, subtraktion, negation, multiplikation, division, rest, kvadratrötter, heltalsstympning, bråkstympning och skala med två potens. Operationerna inkluderar också konverteringsinstruktioner, som kan ladda eller lagra ett värde från minnet i något av följande format: binärkodad decimal, 32-bitars heltal, 64-bitars heltal, 32-bitars flyttal, 64-bitars flytande- punkt eller 80-bitars flyttal (vid laddning konverteras värdet till det flyttalsläge som används för närvarande). x86 innehåller också ett antal transcendentala funktioner, inklusive sinus, cosinus, tangent, arctangent, exponentiering med basen 2 och logaritmer till baserna 2, 10 eller e .

Instruktionernas format för stackregister till stackregister är vanligtvis f op st, st( n ) eller f op st( n ), st , där st är ekvivalent med st(0) , och st( n ) är en av de 8 stackregister ( st(0) , st(1) , ..., st(7) ). Liksom heltalen är den första operanden både den första källoperanden och måloperanden. fsubr och fdivr bör pekas ut som att först byta källoperander innan subtraktionen eller divisionen utförs. Instruktionerna för addition, subtraktion, multiplikation, division, lagring och jämförelse inkluderar instruktionslägen som poppar upp i högen efter att deras operation är klar. Så, till exempel, faddp st(1), st utför beräkningen st(1) = st(1) + st(0) , tar sedan bort st(0) från toppen av stapeln, vilket gör det som blev resultatet i st (1) toppen av stapeln i st(0) .

SIMD instruktioner

Moderna x86-processorer innehåller SIMD- instruktioner, som i stort sett utför samma operation parallellt på många värden kodade i ett brett SIMD-register. Olika instruktionsteknologier stöder olika operationer på olika registeruppsättningar, men i sin helhet (från MMX till SSE4.2 ) inkluderar de allmänna beräkningar av heltals- eller flyttalsaritmetik (addition, subtraktion, multiplikation, skift, minimering, maximering, jämförelse, division eller kvadratrot). Så till exempel, paddw mm0, mm1 utför 4 parallella 16-bitars (indikerat med w ) heltal adderar (indikeras av padd ) av mm0 -värden till mm1 och lagrar resultatet i mm0 . Streaming SIMD Extensions eller SSE inkluderar också ett flyttalsläge där endast det allra första värdet i registren faktiskt modifieras (expanderat i SSE2 ). Några andra ovanliga instruktioner har lagts till, inklusive en summa av absoluta skillnader (används för rörelseuppskattning vid videokomprimering , såsom görs i MPEG ) och en 16-bitars multiplikationsinstruktion (användbar för mjukvarubaserad alfablandning och digital filtrering ) . SSE (sedan SSE3 ) och 3DNow! tillägg inkluderar additions- och subtraktionsinstruktioner för att behandla parade flyttalsvärden som komplexa tal.

Dessa instruktionsuppsättningar innehåller också många fasta underordsinstruktioner för att blanda, infoga och extrahera värdena runt omkring i registren. Dessutom finns instruktioner för att flytta data mellan heltalsregistren och XMM (används i SSE)/FPU (används i MMX) register.

Minnesinstruktioner

x86-processorn inkluderar också komplexa adresseringsmoder för adressering av minne med en omedelbar offset, ett register, ett register med en offset, ett skalat register med eller utan en offset, och ett register med en valfri offset och ett annat skalat register. Så till exempel kan man koda mov eax, [Tabell + ebx + esi*4] som en enda instruktion som laddar 32 bitar av data från adressen beräknad som (Tabell + ebx + esi * 4) offset från ds- väljaren, och lagrar det i eax -registret. I allmänhet kan x86-processorer ladda och använda minne anpassat till storleken på vilket register som helst som den arbetar på. (SIMD-instruktionerna inkluderar även instruktioner för halvladd.)

De flesta 2-operand x86-instruktioner, inklusive heltals ALU-instruktioner, använder en standard " adresseringslägesbyte " som ofta kallas MOD-REG-R/M-byte . Många 32-bitars x86-instruktioner har också en SIB-adresseringslägesbyte som följer MOD-REG-R/M-byten.

I princip, eftersom instruktionsopkoden är separat från adresseringsmodbyten, är dessa instruktioner ortogonala eftersom vilken som helst av dessa opkoder kan blandas och matchas med vilken adresseringsmod som helst. Men x86-instruktionsuppsättningen anses generellt vara icke-ortogonal eftersom många andra opkoder har något fast adresseringsläge (de har ingen byte för adresseringsläge), och varje register är speciellt.

x86-instruktionsuppsättningen inkluderar strängladda, lagra, flytta, skanna och jämföra instruktioner ( lods , stos , movs , scas och cmps ) som utför varje operation till en specificerad storlek ( b för 8-bitars byte, w för 16-bitars ord, d för 32-bitars dubbelord) ökar/minskar sedan (beroende på DF, riktningsflagga) det implicita adressregistret ( si för lods , di för stos och scas , och både för movs och cmps ). För laddnings-, lagrings- och skanningsoperationerna finns det implicita mål-/källa-/jämförelseregistret i registret al , axe eller eax (beroende på storlek). De implicita segmentregistren som används är ds för si och es för di . cx- eller ecx -registret används som en dekrementerande räknare, och operationen stannar när räknaren når noll eller (för skanningar och jämförelser) när olikhet detekteras .

Stacken implementeras med en implicit dekrementerande (push) och inkrementerande (pop) stackpekare. I 16-bitarsläge adresseras denna implicita stackpekare som SS:[SP], i 32-bitarsläge är den SS:[ESP] och i 64-bitarsläge är den [RSP]. Stackpekaren pekar faktiskt på det senaste värdet som lagrades, under antagandet att dess storlek kommer att matcha processorns driftläge (dvs. 16, 32 eller 64 bitar) för att matcha standardbredden på push / pop / anropet / ret instruktioner. Också inkluderade är instruktionerna för att gå in och lämna som reserverar och tar bort data från toppen av stacken medan du ställer in en stapelrampekare i bp / ebp / rbp . Direkt inställning, eller addition och subtraktion till sp / esp / rsp -registret stöds också, så in- och lämna -instruktionerna är i allmänhet onödiga.

Denna kod i början av en funktion:

            
         
            tryck  ebp  ; spara anropsfunktionens stackram (ebp)   mov  ebp  ,  esp  ; skapa en ny stackram ovanpå vår anropares   stacksub  esp  ,  4  ; allokera 4 byte stackutrymme för denna funktions lokala variabler  

...är funktionellt likvärdig med bara:

     ange  4  ,  0

Andra instruktioner för att manipulera stacken inkluderar pushf / popf för att lagra och hämta (E)FLAGS-registret. Instruktionerna pusha / popa kommer att lagra och hämta hela heltalsregistertillståndet till och från stacken.

Värden för en SIMD-laddning eller -butik antas vara packade i angränsande positioner för SIMD-registret och kommer att rikta in dem i sekventiell ordning. Vissa SSE-inläsnings- och lagringsinstruktioner kräver 16-byte-justering för att fungera korrekt. SIMD-instruktionsuppsättningarna inkluderar också "förhämtning"-instruktioner som utför laddningen men inte riktar sig till något register, som används för cacheladdning. SSE-instruktionsuppsättningarna inkluderar också icke-temporala lagringsinstruktioner som kommer att utföra lagringar direkt till minnet utan att utföra en cacheallokering om destinationen inte redan är cachad (annars kommer den att bete sig som en vanlig lagring.)

De flesta generiska heltals- och flyttalsinstruktioner (men inga SIMD)-instruktioner kan använda en parameter som en komplex adress som den andra källparametern. Heltalsinstruktioner kan också acceptera en minnesparameter som en destinationsoperand.

Programflöde

x86-enheten har en ovillkorlig hoppoperation, jmp , som kan ta en omedelbar adress, ett register eller en indirekt adress som en parameter (observera att de flesta RISC-processorer endast stöder ett länkregister eller kort omedelbar förskjutning för hoppning).

Det stöds också flera villkorliga hopp, inklusive jz (hoppa på noll), jnz (hoppa på icke-noll), jg (hoppa på större än, signerad), jl (hoppa på mindre än, signerad), ja (hoppa på ovan/ större än, unsigned), jb (hoppa på under/mindre än, unsigned). Dessa villkorade operationer är baserade på tillståndet för specifika bitar i (E)FLAGS- registret. Många aritmetiska och logiska operationer ställer in, rensar eller kompletterar dessa flaggor beroende på deras resultat. Jämförelse- cmp (jämför) och testinstruktionerna ställer in flaggorna som om de hade utfört en subtraktion respektive en bitvis AND-operation utan att ändra operandernas värden. Det finns också instruktioner som clc (clear carry flag) och cmc (complement carry flag) som fungerar på flaggorna direkt. Flyttalsjämförelser utförs via fcom- eller ficom -instruktioner som så småningom måste omvandlas till heltalsflaggor.

Varje hoppoperation har tre olika former, beroende på operandens storlek. Ett kort hopp använder en 8-bitars signerad operand, som är en relativ offset från den aktuella instruktionen. Ett nära hopp liknar ett kort hopp men använder en 16-bitars signerad operand (i verkligt eller skyddat läge) eller en 32-bitars signerad operand (endast i 32-bitars skyddat läge). Ett långt hopp är ett som använder hela segmentets bas:offset-värde som en absolut adress. Det finns också indirekta och indexerade former av var och en av dessa.

Förutom de enkla hoppoperationerna finns instruktionerna för anrop (anropa en subrutin) och ret (retur från subrutin). Innan kontrollen överförs till subrutinen, anrop segmentoffsetadressen för instruktionen efter anropet till stacken; ret släpper detta värde från stacken och hoppar till det, vilket effektivt återför flödet av kontroll till den delen av programmet. I fallet med ett långt samtal skjuts segmentbasen efter offset; far ret poppar offset och sedan segmentbasen för att återgå.

Det finns också två liknande instruktioner, int ( interrupt ), som sparar det aktuella (E)FLAGS -registervärdet på stacken och sedan utför ett far-anrop , förutom att istället för en adress använder den en avbrottsvektor , ett index i en tabell av avbrottshanterarens adresser. Vanligtvis sparar avbrottshanteraren alla andra CPU-register som den använder, såvida de inte används för att returnera resultatet av en operation till det anropande programmet (i programvara som kallas avbrott). Den matchande returen från avbrottsinstruktionen är iret , som återställer flaggorna efter återkomst. Mjuka avbrott av den typ som beskrivs ovan används av vissa operativsystem för systemanrop och kan också användas för att felsöka hårda avbrottshanterare. Hårda avbrott utlöses av externa hårdvaruhändelser och måste bevara alla registervärden eftersom tillståndet för det program som körs för närvarande är okänt. I skyddat läge kan avbrott ställas in av operativsystemet för att utlösa en uppgiftsomkopplare, som automatiskt sparar alla register för den aktiva uppgiften.

Exempel

Följande exempel använder den så kallade Intel-smaken . Det finns en alternativ AT&T-smak där källa och destination byts ut, bland andra skillnader.

"Hej världen!" program för DOS i MASM stil montering

Använder avbrott 21h för utdata – andra exempel använder libcs ​​printf för att skriva ut till stdout .

 
 


		



		    
		 
		
		   
		

 .model  small  .stack  100h  .data  msg  db  'Hello world!$'  .code  start:  mov  ah  ,  09h  ; Visa meddelandet   lea  dx  ,  msg  int  21h  mov  ax  ,  4C00h  ;  Avsluta  den körbara  int  21h  slutstart  

"Hej världen!" program för Windows i MASM-stil montering



 
 


      0


 
 
 
 

 
 

 
 
             
            
            0
            
 

 ; kräver /coff switch på 6.15 och tidigare versioner   .386  .model  small  ,  c  .stack  1000h  .data  msg  db  "Hello world!"  ,  .code  includelib  libcmt.lib  includelib  libvcruntime.lib  includelib  libucrt.lib  includelib  legacy_stdio_definitions.lib  extrn  printf  :  near  extrn  exit  :  near  public  main  main  proc  push  offset  msg  call  printf  push  call  exit  main  endp  end 

"Hej världen!" program för Windows i NASM-stil montering



 
  
  
  0
  


 
  

 
 
 
 0
 
 
   0 

   0

 
 
 0


  
  
 0


 
  0

 
  0
 ; Bildbas = 0x00400000   %definiera RVA(x) (x-0x00400000)  sektion  .text  push  dword  hej  call  dword  [  printf  ]  push  byte  +  call  dword  [  exit  ]  ret  sektion  .data  hej  db  "Hej värld!"  avsnitt  .idata  dd  RVA  (  msvcrt_LookupTable  )  dd  -  1  dd  dd  RVA  (  msvcrt_string  )  dd  RVA  (  msvcrt_imports  )  gånger  5  dd  ;  avslutar  deskriptortabellen  msvcrt_string  dd  "  msvcrt.dll"  ,  msvcrt_LookupTable  :  dd  RVA  (  msvcrt_printf  )  dd  RVA  (  msvcrt_exit  )  dd  msvcrt_imports  :  printf  dd  RVA  (  msvcrt_printrf  )  msvcrt_printrf  _exit  _printf:  dw  1  dw  "printf  "  ,  msvcrt_exit  :  dw  2  dw  "utgång"  ,  dd  0

"Hej världen!" program för Linux i NASM-stil montering









                            
                
                        
                                            

                            
                            
                                            
                                 
		                    
		                    
                                             
		                  
		              
		                      
                                             
		                    
		 0                   
		                       ;  ; Detta program körs i 32-bitars skyddat läge.   ; build: nasm -f elf -F stabs name.asm   ; länk: ld -o namn namn.o   ;  ; I 64-bitars långt läge kan du använda 64-bitars register (t.ex. rax istället för eax, rbx istället för ebx, etc.)   ; Ändra också "-f elf " för "-f elf64" i byggkommandot.   ;  avsnitt  .data  ; avsnitt för initierade data   str:  db  'Hej världen!'  ,  0Ah  ; meddelandesträng med nyrads tecken i slutet (10 decimaler)   str_len:  equ  $  -  str  ; beräknar längden på strängen (byte) genom att subtrahera strns startadress   ; från denna adress ($-symbol)   avsnitt  .text  ; detta är kodavsnittet   globala  _start  ; _start är startpunkten och behöver global räckvidd för att "seas" av   ; linker --motsvarande main() i C/C++   _start:  ; definition av _startproceduren börjar här   mov  eax  ,  4  ; ange sys_write-funktionskoden (från OS-vektortabellen)   mov  ebx  ,  1  ; specificera filbeskrivning stdout --i gnu/linux, allt behandlas som en fil,   ; även hårdvaruenheter   mov  ecx  ,  str  ; flytta start_adress_ för strängmeddelande till ecx-registret   mov  edx  ,  str_len  ; flytta meddelandets längd (i byte)   int  80h  ; avbryt kärnan för att utföra systemanropet vi just satte upp -   ; i gnu/linux begärs tjänster genom kärnan   mov  eax  ,  1  ; specificera sys_exit funktionskod (från OS vektortabell)   mov  ebx  ,  ; ange returkod för OS (noll talar om för OS att allt gick bra)   int  80h  ; avbryt kärnan för att utföra systemanrop (för att avsluta)  

"Hej världen!" program för Linux i NASM-stil montering med C-standardbiblioteket











                                          
                                         
                                                           

                                        
	               
                                                    

 

                                          
                                          
                                            
                                                  ;  ; Detta program körs i 32-bitars skyddat läge.   ; gcc länkar standard-C-biblioteket som standard   ; build: nasm -f elf -F stabs name.asm   ; länk: gcc -o namn namn.o   ;  ; I 64-bitars långt läge kan du använda 64-bitars register (t.ex. rax istället för eax, rbx istället för ebx, etc..)   ; Ändra också "-f elf " för "-f elf64" i byggkommandot.   ;  global  main  ;main måste definieras som att den kompileras mot C-Standard Library  extern  printf  ;deklarerar användning av extern symbol eftersom printf deklareras i en annan objektmodul.  ;Linker löser denna symbol senare.  segment  .data  ;sektion för initierad datasträng  db  '  Hello world!'  ,  0Ah  ,  0h  ;meddelandesträng med nyradstecken (10 decimaler) och NULL-terminatorn  ;sträng hänvisar nu till startadressen där 'Hello, World' är lagrad.  segment  .text  main:  push  string  ;push adressen för det första tecknet i strängen på stack. Detta kommer att vara argument för printf   call  printf  ; calls printf  add  esp  ,  4  ;avancerar stack-pekaren med 4 spola ut det pushade strängargumentet  ret  ;return 

"Hej världen!" program för 64-bitarsläge Linux i NASM-liknande montering




 			

 			
		         
	                  


 

 

	  				
	  				
	   			
	  		
						
     

	  				
	  				
						 ; bygga: nasm -f elf64 -F dvärg hello.asm   ; länk: ld -o hej hello.o   DEFAULT  REL  ; använd RIP-relativa adresseringslägen som standard, så [foo] = [rel foo]   SECTION  .rodata  ; skrivskyddad data kan hamna i .rodata-sektionen på GNU/Linux, som .rdata på Windows   Hello:  db  "Hello world!"  10  ;  _ 10 = `\n`.   len_Hello:  equ  $  -  Hej  ; få NASM för att beräkna längden som en monteringstidskonstant   ;; write() tar en längd så en 0-terminerad sträng i C-stil behövs inte.  Det skulle vara för sätter   SEKTION  .text  global  _start  _start:  mov  eax  ,  1  ; __NR_skriv syscall-nummer från Linux asm/unistd_64.h (x86_64)   mov  edi  ,  1  ; int fd = STDOUT_FILENO   lea  rsi  ,  [  rel  Hej  ]  ; x86-64 använder RIP-relativ LEA för att sätta statiska adresser i regs   mov  rdx  ,  len_Hello  ; size_t count = len_Hello   syscall  ; write(1, Hej, len_Hello);  anropa in i kärnan för att faktiskt göra systemanropet   ;; returvärde i RAX.  RCX och R11 skrivs också över av syscall   mov  eax  ,  60  ; __NR_avsluta samtalsnummer (x86_64)   xor  edi  ,  edi  ; status = 0 (avsluta normalt)   syscall  ; _exit(0)  

Att köra det under strace verifierar att inga extra systemanrop görs under processen. Printf-versionen skulle göra många fler systemanrop för att initiera libc och göra dynamisk länkning . Men det här är en statisk körbar eftersom vi länkade med ld utan -pie eller några delade bibliotek; de enda instruktionerna som körs i användarutrymmet är de du tillhandahåller.





 $  strace ./hello > /dev/null  # utan omdirigering, ditt programs stdout är blandad straces inloggning på stderr. Vilket normalt är bra   execve("./hello", ["./hello"), 0x7ffc8b0b3570 /* 51 vars */) = 0  write(1, "Hello world!\n", 13) = 13  exit(0) = ?  +++ avslutade med 0 +++ 

Använder flaggregistret

Flaggor används flitigt för jämförelser i x86-arkitekturen. När en jämförelse görs mellan två data sätter CPU:n den eller de relevanta flaggorna. Efter detta kan villkorliga hoppinstruktioner användas för att kontrollera flaggorna och förgrena sig till koden som ska köras, t.ex.

		 
		
	

	 cmp  eax  ,  ebx  jne  göra_något  ; ...   gör_något:  ; gör något här  

Flaggor används också i x86-arkitekturen för att slå på och av vissa funktioner eller exekveringslägen. Till exempel, för att inaktivera alla maskerbara avbrott kan du använda instruktionen:

	cli

Flaggregistret kan också nås direkt. De låga 8 bitarna i flaggregistret kan laddas in i ah med hjälp av lahf -instruktionen. Hela flaggregistret kan också flyttas till och från stacken med hjälp av instruktionerna pushf , popf , int (inklusive into ) och iret .

Använda instruktionspekarregistret

Instruktionspekaren kallas ip i 16-bitarsläge, eip i 32-bitarsläge och rip i 64-bitarsläge. Instruktionspekarregistret pekar på minnesadressen som processorn härnäst kommer att försöka exekvera; den kan inte nås direkt i 16-bitars eller 32-bitars läge, men en sekvens som följande kan skrivas för att sätta adressen för nästa_rad i eax :

		

		 call  next_line  next_line:  pop  eax 

Denna sekvens av instruktioner genererar positionsoberoende kod eftersom anropet tar en instruktionspekare-relativ omedelbar operand som beskriver förskjutningen i byte av målinstruktionen från nästa instruktion (i detta fall 0).

Det är enkelt att skriva till instruktionspekaren - en jmp -instruktion ställer in instruktionspekaren till måladressen, så, till exempel, en sekvens som följande kommer att sätta innehållet i eax i eip :

		 jmp  eax 

I 64-bitarsläge kan instruktioner referera till data i förhållande till instruktionspekaren, så det finns mindre behov av att kopiera värdet på instruktionspekaren till ett annat register.

Se även

Vidare läsning

handböcker

Böcker