Forsamlingsspråk

Assembly
programmeringsspråk
Monteringskode for Motorola 6800-prosessor
Opprinnelsesdatodet dateres tilbake til de første programmerte datamaskinene som ble lagret
Brukgenerell språkbruk
Paradigmergenerisk programmering
Skrivingingen
Vanlige utvidelser.asm .s

Assembly language (også kalt assemblerspråk [1] eller assemblerspråk [2] eller ganske enkelt assembly ) er et programmeringsspråk som ligner veldig på maskinspråk , selv om det er forskjellig fra sistnevnte. Det blir feilaktig ofte referert til som " assembler ", men sistnevnte begrep identifiserer bare "assembler" -programmet som konverterer assemblerspråk til maskinspråk. Instruksjoner for monteringsspråk tilsvarer instruksjoner som prosessoren kan utføre. Faktisk er sistnevnte strenger av biter (vanligvis 1 eller 2 i lengde, men også 4 byte ) som i assembly-språk tar navn som er forståelige for mennesker. Alle instruksjoner har en tilsvarende opkode (Operation Code) som representerer selve instruksjonen i heksadesimalt eller binært format .

Instruksjonen som legger til 32 til EAX-registeret:

Opcode og instruksjon
05 id LEGG TIL EAX, imm32

Avhengig av typen instruksjon, er noen instruksjoner 1 til flere byte lange pluss størrelsen på operandene de jobber med.

Assembly er språket som prosessorens instruksjonssett bruker . Instruction Set Architecture (ISA) er settet med alle operasjonene som prosessoren er i stand til å utføre. Av denne grunn er det ikke et enkelt assemblerspråk, men hver prosessor har sitt eget.

Beskrivelse

RISC og CISC

Assembly språk er den såkalte ISA ( Instruction Set Architecture ) til en prosessor. De forskjellige ISA-ene kan deles inn i to store grupper: RISC ( Reduced Instruction Set Computer ) og CISC ( Complex Instruction Set Computer ). Den første gruppen har en tendens til å ha enkle og raske operasjoner, med en stor overflod av registre for å lagre mellomresultater. Den andre gir programmereren mer komplekse instruksjoner, som noen ganger etterligner språk på høyere nivå (for eksempel kopiering av strenger i x86-prosessorer). I begge tilfeller har de beste instruksjonssettene en tendens til å være de såkalte ortogonale , hvor de forskjellige adresseringsmetodene og registrene kan brukes om hverandre i alle instruksjoner. Kjente ortogonale instruksjonssett er de av Motorola 68000 (CISC) og MIPS (RISC). ISA-en til Intel x86-prosessorer var opprinnelig veldig lite ortogonal, og har blitt bedre og bedre.

Skillet mellom RISC- og CISC- instruksjonssett er noe uklart i dag, fordi de fleste forbrukerprosessorer i dag er CRISP- er , dvs. en blanding av de to. Noen prosessorer oversetter også den originale ISA til et internt instruksjonssett, av forskjellige grunner og på forskjellige måter:

Formål

Forsamlingen har det generelle formålet å la programmereren ignorere maskinspråkets binære format. Hver driftskode for maskinspråket erstattes, i sammenstillingen, av en sekvens av tegn som representerer den i mnemonisk form ; for eksempel kan opkoden for summen transkriberes som ADDog opkoden for hoppet som JMP. For det andre kan data- og minneadressene manipulert av programmet skrives, i assembly, i den mest passende tallbasen for øyeblikket: heksadesimal , binær , desimal , oktal , men også i symbolsk form, ved hjelp av tekststrenger (identifikatorer). Monteringsprogrammet er dermed relativt mer lesbart enn det på maskinspråk, som det opprettholder en total (eller nesten total) isomorfisme med . Program skrevet i assembly kan ikke kjøres direkte av prosessoren; det må oversettes til det tilsvarende (binære) maskinspråket ved å bruke et kompilatorprogram kalt assembler .

Ikke-unikhet

På grunn av denne "nærheten" til maskinvaren, er det ikke noe enkelt assemblerspråk. Omvendt har hver CPU eller CPU-familie sin egen sammenstilling, forskjellig fra de andre. For eksempel er monteringsspråkene for Intel x86-prosessorer , Motorola 68000s og Dec Alpha ganske forskjellige . Dette betyr at å kunne et bestemt assemblerspråk betyr å kunne skrive programmer bare på en bestemt CPU eller familie av CPUer. Å bytte til andre CPUer er imidlertid relativt enkelt, fordi mange mekanismer er like eller helt identiske, så overgangen er ofte begrenset til å lære nye mnemoniske koder, nye adresseringsmetoder og andre forskjellige særegenheter ved den nye prosessoren.

Det er mye mindre enkelt å bære et program skrevet i assembly på maskiner med forskjellige prosessorer eller med forskjellige arkitekturer: det betyr nesten alltid å måtte omskrive programmet fra topp til bunn, fordi monteringsspråk er helt avhengig av plattformen de var for skrevet. Mange monteringskompilatorer støtter makrosystemer som kan brukes til å delvis omgå dette problemet, men dette er ikke en effektiv løsning.

Dessuten tilbyr ikke sammenstillingen noen " kontroll over typer " (det er ingenting som ligner på konseptet " type " i lavnivåprogrammering ), men overlater programmereren ansvarlig for å ta seg av hver eneste detalj av maskinadministrasjon og krever mye disiplin og omfattende kommentararbeid for å unngå å skrive kode som er absolutt uleselig (for andre programmerere så vel som for dem selv etter en tid).

Mot disse ulempene tilbyr montering enestående effektivitet og fullstendig og absolutt kontroll over maskinvaren: monteringsprogrammer er i prinsippet de minste og raskeste som kan skrives på en gitt maskin.

Å skrive (god) kode i montering er tidkrevende, vanskelig og derfor veldig dyrt, spesielt i perspektiv (fremtidige modifikasjoner): av denne grunn er montering sjelden det eneste språket som brukes i et mainstream-prosjekt, med mindre dette er av begrenset størrelse og omfang . Det brukes vanligvis i kombinasjon med andre språk: det meste av koden er skrevet på et høyt nivå språk , mens de mest kritiske delene (av grunner til ytelse, timing nøyaktighet eller pålitelighet) er skrevet i montering.

Disse problemene finnes hovedsakelig på plattformer som nåværende personlige datamaskiner , der den kvantitative omfanget og det enorme kvalitative utvalget av tilgjengelig maskinvare skaper et objektivt problem som aldri er løst (og antagelig ikke kan løses) på nivået av enhet og standard for lavnivåapplikasjoner. . I tillegg kommer den konstante utviklingen mot en stadig større stratifisering av vanlige operativsystemer, preget av en rekke begrensninger og virtualiseringer av fysisk periferiutstyr og kommunikasjonskanaler, som ikke gjør det lett å utvikle en programvare som samhandler direkte med den underliggende maskinvaren. håndtere sine egenskaper.

Imidlertid kan vi sitere to eksempler, uansett hvor beslektede, på total inversjon av dette generelle paradigmet:

Derfor blir muligheten for å bruke en mikrokontroller med svært begrensede ROM- og RAM -minneressurser ved å skrive fastvaren helt i montasje avgjørende for å minimere kostnader, belastning på platen, elektromagnetisk følsomhet, også øke påliteligheten (mer "utdatert" har en uoverkommelig fordel i form av millioner av timer med testing og drift i feltet, som er den desidert mest verdifulle "vare" for forskjellige kritiske innebygde systemer) og optimalisering av en rekke andre faktorer.

Struktur

Strukturen til en typisk x86-monteringsliste for PC er stort sett strukturert som følger:

Det er verdt å gjenta at denne strukturen, i sin alminnelighet, nesten helt avhenger av plattformen og også av montøren som brukes, og derfor ikke kan universaliseres på noen måte. Ulike arkitekturer, fra stormaskiner til mikrokontrollere, med relative montører og kryssmontører, påtvinger kildestrukturer som noen ganger er klart forskjellige fra det enkle eksemplet som er illustrert, relatert til vanlige PC-er. For et trivielt moteksempel, i Harvard-arkitekturer brukt av nesten alle mikrokontrollere og mange superdatabehandlingsarkitekturer:

Kodeeksempel

Eksempel på et " Hello world "-program i Intel x86-montering med Intel - syntaks (benytter seg av anrop til DOS-operativsystemet). Den er ikke kompatibel med UNIX GNU Assembly-versjoner

MODELL SMALL STACK 100 H .DATA HW DB " hello , world " , 13 , 10 , ' $ ' . CODE .STARTUP MOV AX , @ data MOV DS , AX MOV DX , OFFSET HW MOV AH , 09 H INT 21 H MOVAX . , 4 C00H INT 21 H END

Dette er i stedet eksempelet på programmet skrevet for AT&T-syntaks (for UNIX GNU -arkitekturer )

.seksjon .data hei: .ascii "bye bye verden! \ n" hello_len: .lang . - hei # lengden på strengen i byte .seksjon .tekst .global _start _start: movl $ 4 , % eax # 4 tilsvarer systemkallet "write" movl $ 1 , % ebx # skriver ut til standard utgang (skjerm) leal hello , % ecx # char peker til det du vil skrive ut movl hello_len , % edx # kopier innholdet i variabelen. laster lengden på variabelen int $ 0x80 # systemanrop (int ville være "avbrudd"); med 0x80 blir en generell interaksjon # erklært (basert på verdiene som tidligere er lastet inn i registrene) lansert movl $ 1 , % eax # 1 tilsvarer systemanropet "exit" xorl % ebx , % ebx #null EBX; movl $ 0,% ebx kan også skrives, men det er mindre effektivt int $ 0x80

Mikroinstruksjoner

Når du utfører en monteringsinstruksjon, utfører prosessoren (i henhold til arkitekturen tatt i betraktning) en serie operasjoner kalt "Assembly Microinstructions", dvs. maskinvareoperasjoner som brukes til å konfigurere registrene og operatørene til CPU'en slik at den kan den instruksjonen bli henrettet.

Denne prosessen er delt inn i 3 deler for Intel x86 CPUer og noen andre:

  • Hent: lastefase der PC-registeret økes og CPU-en er forberedt til å utføre operasjonen;
  • Dekode: multiplekserne inne i CPUen er konfigurert og en koding av instruksjonen utføres om nødvendig (indirekte adressering, forskyvning, etc ...)
  • Utfør: Tidspunktet da operasjonen faktisk utføres

Derav forkortelsen FDE, Fetch-Decode-Execute, rapportert i arkitekturtekstene og i databladene.

For å gi et eksempel for Intel 80x86-arkitekturen med en enkelt BUS, i AT&T-syntaks, denne instruksjonen:

LEGG TIL % EBX , % EAX

der innholdet i EAX-registeret legges til innholdet i EBX-registeret og resultatet vil bli lagret i EAX, utføres disse mikrooperasjonene:

1. PCout , SELECT [ 4 ], ADD , Zin , MARin , READ : PC-en økes for å utføre neste operasjon 2. Zout , PCin , WMFC : den venter på lesingen i minnet 3. MDRout , IRin ; neste instruksjon sendes i IR 4. EAXout , Vin , innholdet i EAX sendes i et midlertidig register 5. EBXout , SELECT [ V ], ADD , Zin , innholdet i EBX legges til via ALU med EAX 6. Zout , EAXin , END ; resultatet kopieres til EAX

På noen arkitekturer er disse fasene i stedet fire (for eksempel i PIC Microchips, i Intel 8051 og i mange lignende kjerner), som også viser det faktiske forholdet mellom klokkehastighet eller frekvens til den eksterne krystallen (f.eks. 10 MHz) og antall av instruksjoner som faktisk ble utført på ett sekund. For PIC-er ( spesielt grunnlinje- og mellomtonefamilier ) er dette forholdet lik 1/4, siden ved hver klokkesyklus utfører kjernen faktisk en enkelt Fetch-Decode-Execute-Write-fase, og det kreves derfor fire sykluser av den eksterne klokken for å fullføre en enkelt instruksjon. På mer arkaiske eller på annen måte annerledes utformede mikrokontroller- og kjernearkitekturer kreves det enda flere klokkesykluser for hver fase (for eksempel tre eller fire), derav det forskjellige forholdet mellom klokke og MIPS, som i tilfellet med den originale 8051-designen krever for eksempel 12 klokkesykluser for hver enkelt instruksjon. Til slutt bør det huskes at noen instruksjoner, typisk inkludert ubetingede hopp, krever på et betydelig antall plattformer (både RISC og CISC, unnfanget i forskjellige tidsepoker) et høyere antall sykluser enn de andre, på grunn av tilleggsoperasjonene (ikke parallelliserbare ) kreves ved å oppdatere IP-registeret og eventuelle interne forhåndshentingskøer.

C-asm

Noen ganger, i høynivåprogrammering i miljøer som DOS, er det behov for å utføre noen operasjoner som er mye raskere ved å bruke instruksjoner fra lavnivåspråk (i Windows, på den annen side, på grunn av minnebeskyttelse, kalt WINAPI , L/M-anrop brukes mest for akselererte matematiske prosedyrer eller av sjåfører ). Blant høynivåspråkene som tillater dette er C og C++ , der deler skrevet i montering kan settes inn i kildene deres, som under kompilering vil bli oversatt med en prosedyre kjent som inline assembler . Et eksempel på kode skrevet i C-asm (ved hjelp av Intel x86-sammenstillingen), som viser et gitt tall i binær som input, er følgende eksempel som bruker stdio.h -direktivet som håndterer input/output-operasjonene, direktivet iostream. h som har samme funksjoner som den forrige, men som garanterer kompatibilitet med eldre kompilatorer og til slutt conio.h- direktivet som er ansvarlig for å lage tekstlige grensesnitt.

#include <stdio.h> #include <iostream.h> #include <conio.h> int main () { int a ; / * Anskaffelse av den numeriske verdien * / printf ( " Angi en verdi mellom -32768 og 32768 : " ) ; scanf ( " %d " , & a ) ; / * Visning av svarmeldingen * / printf ( " Den tilsvarende verdien i binær er : " ) ; / * Nøkkelord for å avgrense delene av monteringskoden * / asm { / * Visning av den tilsvarende bitstrengen * / MOV BX , WORD PTR til MOV CX , 00 Ah } / * Ekstern etikett * / Syklus: asm { / * Trekk ut en bit * / MOV DL , 00 H RCL BX , 1 / * Verdien av biten plasseres i bæreflagget * / ADC DL , ' 0 ' / * Bestem tegnet som skal vises * / MOV AH , 02 H / * Display * / INT 21 h Loop Ciclo } retur 0 ; }

For de fleste ordinære applikasjoner er det nå en vanlig oppfatning i fellesskapet av applikasjonsprogrammerere at faktorer som optimaliseringer automatisk generert av kompilatorer på overordnede språk, utviklingen av biblioteker som i seg selv er stadig mer optimaliserte (i det minste i prinsippet) og den økende prosessorkraften til ofte brukte plattformer gjør det mindre nødvendig enn tidligere å ty til vertikalisering gjennom integrerte sammenstillinger eller monteringsmoduler, også til fordel for portabiliteten og lesbarheten til koden. Imidlertid er det mange unntak i total kontrast til denne utbredte oppfatningen: spesielt i verden av dedikerte systemer, nesten per definisjon basert på et helt annet paradigme, som nevnt i avsnittet "Det er ikke en enkelt forsamling", men også i en rekke spesialistnisjer i marginene til mainstream , fra CAD/CAM-systemer til numerisk databehandling, til ulike vitenskapelige applikasjoner, opp til utvikling av drivere og annen systemprogramvare.

Kontrollstrukturer i x86-sammenstillinger

Prosessoren utfører instruksjonene etter hvert som de vises. Gjennom bestemte strukturer er det imidlertid mulig å kontrollere den utøvende flyten basert på en bestemt betingelse. På denne måten er det mulig å lage strukturer av enkel seleksjon eller av iterativ type (sykluser). Monteringsanvisningen som brukes til dette formålet er hovedsakelig av to typer: hopp og sammenlign.

Hopp kan være ubetingede eller betingede. JMP gjør et ubetinget sprang. Referanseadressen er vanligvis en etikett. Hoppbetingelsen er alltid diktert av verdiene til flaggregisteret. De mest brukte flaggene for hopp er:

  • ZF (flagg null) indikerer om den siste instruksjonen resulterte i 0
  • SF (flaggtegn) indikerer om den siste instruksjonen genererte et negativt tegnresultat
  • OF (flaggoverløp) indikerer om den siste instruksjonen genererte et overløp (med avkorting av den mest signifikante biten av resultatet)
Utvalgskonstruksjonen (hvis, annet)

Utvalg er en struktur som lar deg utføre en blokk med instruksjoner eller en annen basert på forekomsten av en tilstand.

selv- instruksjonsblokk 1 ellers instruksjonsblokk 2 greit hvis

i montering, gjennom logikken til hopp, er det representert som følger:

selv: JNbetingelse ellers instruksjonsblokk 1 JMP end_se ellers: instruksjonsblokk 2 slutt om: Kontrollløkken i kø (gjør ... mens)

Iterasjon er en struktur som lar deg gjenta en instruksjon flere ganger under kontroll av en tilstand.

gjenta bruksanvisning så lenge tilstand

i montering, gjennom logikken til hopp, er det representert som følger:

start_cycle: bruksanvisning Jsykkelstarttilstand

eksempel:

MOV AX, 0000h start_cycle: INC AX CMP AX, 000Ah; sammenlign AX og verdien 0Ah (10d) JNE start_cycle; hopp til begynnelsen (og gjenta syklusen) hvis den er annerledes

Siden tilstandskontrollen utføres på slutten av løkken, blir de sekvenserte setningene fortsatt utført minst én gang, selv om betingelsen allerede ble sjekket ved starten. Praktisk talt:

MOV AXE, 000 Ah start_cycle: INC AX CMP AXE, 000Ah JNE start_cycle

Denne kodebiten skal sjekke om AX = 10d, og hvis ikke, øke AX. I så fall, gå ut av syklusen. Vi ser imidlertid at AX allerede er verdt 10d, men dette registeret økes uansett (til slutt vil det være verdt 000Bh). Videre, i dette spesielle programmet, vil syklusen aldri ende: AX vil være verdt 11, deretter 12, så 13 og den vil aldri bli lik 10. Det ville være en god idé, under forholdene, å unngå å uttrykke en likhet:

MOV AXE, 000 Ah start_cycle: INC AX CMP AXE, 000Ah JB startsyklus; hopp over hvis mindre (i stedet for hopp over hvis ikke lik)

På denne måten har vi løst problemet med den uendelige sløyfen. Men på grunn av det faktum at sekvensen kjører minst én gang, unngår du vanligvis køkontrollsløyfen og bruker overhead-sjekksløyfen i stedet.

Den overliggende kontrollsløyfen (mens)

En iterativ struktur med overheadkontroll kan beskrives, på et høyt nivå, som følger:

mens tilstand bruksanvisning slutten av syklusen

Dette er det samme som while (tilstand) {sekvens} til C. i sammenstilling:

start_cycle: JNcycle slutttilstand sekvens JMP start_cycle slutten av syklusen

eksempel:

start_cycle: CMP AX, 0 Ah; sammenligne AX med 10d JNE sluttsyklus; hopp over hvis det er annerledes INC AX; øke AX JMP start_cycle slutten av syklusen

Forskjellen mellom denne strukturen og kontrollstrukturen i kø er at hvis betingelsen i utgangspunktet er sann, blir sekvensen av instruksjoner ikke utført en gang.

Tellerløkken (for)

Tellersyklusen har en struktur av denne typen:

TELLER = 0 mens TELLER <N sekvens øke COUNTER slutten av syklusen

En tellersløyfe kan brukes hvis vi ønsker å gjenta en blokk med instruksjoner for et antall ganger kjent a priori. løkker i montering er generelt avtagende:

TELLER = N gjenta sekvens reduserer COUNTER til COUNTER n> 0

Som teller bruker vi vanligvis CX-registeret (tellerregister, faktisk), fordi det er en instruksjon som utfører de to siste instruksjonene automatisk: LOOP-instruksjonen: reduserer CX og, hvis CX ikke er 0, hopper den til den angitte etiketten .

MOV CX, 0 start_cycle: CMP CX, N JGE sluttsyklus sekvens JMP start_cycle slutten av syklusen:

Takket være LOOP-instruksjonen blir det enkelt å skrive en motløkke i montering:

MOV CX, <N>; der N er antall repetisjoner som skal utføres start_cycle: sekvens LOOP start_cycle

Det skal understrekes at i det første eksemplet er det en syklus med kontroll i spissen, mens i det andre med kontroll i køen, og at selv om det andre er mer kompakt og raskere å skrive, kan det generere feil, som allerede nevnt ovenfor, hvis ikke vær forsiktig med hvordan du bruker den, faktisk utføres instruksjonene i den minst én gang, så hvis du ikke er sikker på at antall repetisjoner aldri kan være null, er det mindre risikabelt å bruke den første.

Inngang/utgang via DOS INT 21h

Monteringen, spesielt i PC-verdenen, inkluderer ikke ferdige input/output-funksjoner. Programmereren må derfor lage sine egne rutiner eller stole på de som er laget av tredjeparter. I DOS-miljøer er det tilstrekkelig å sette den nødvendige tjenestekoden i AX og bruke INT 21h-instruksjonen for å kalle den relaterte avbruddsprogramvaren , en av de mest særegne egenskapene til Intel x86 CPUer. Blant de vanligste funksjonene for tastaturinngang/utgang:

  • service 01h ==> Anskaffelse av et tegn fra tastaturet med ekko på skjermen. Venter på at en tast skal trykkes og returnerer ASCII-koden til tasten som trykkes
  • service 02h ==> Visning av et tegn på skjermen. Skriver ut tegnet hvis ASCII-kode er inneholdt i DL
  • service 07h ==> Innhenting av et tegn fra tastaturet uten ekko på skjermen. Samme som 01h-tjeneste, men viser ikke fonten på skjermen
  • service 09h ==> Visning av en streng på skjermen. Skriv ut strengen pekt på av minneadressen i DX
  • 4Ch-tjeneste ==> Returner tjenesten til operativsystemet. Avslutter programmet.

Så, for å skaffe en karakter (gjentatt på videoen):

MOV AH, 01t; service 01h INT 21t; hvis AX = 0001h, går ASCII-koden til den trykket tasten til AL

Og så vil du skrive det ut:

MOV DL, AL; Jeg kopierer ASCII-koden til nøkkelen som leser DL MOV AH, 02h; service 02h INT 21t; hvis AX = 0002h, skriv ut ASCII-kodetegnet i D

Som du kan se, refererer både fangst- og utskriftsoperasjonene til ASCII-tegnkoder. Hvis du vil lese et numerisk siffer som input, for å gå tilbake til den numeriske verdien, trekker du bare verdien 30h (48 i desimal) fra ASCII-koden. Faktisk tilsvarer 30t i ASCII tegnet "0", 31t (49 i desimal) til "1" og så videre ...

Hvis du vil skrive ut en streng:

DB-streng 13,10, "dette er en streng", "$"; Jeg tildeler en én-byte variabel som jeg kaller en streng og hvor jeg lagrer en sekvens av tegn (en streng faktisk) LEA DX, streng; Jeg kopierer minneadressen som peker til strengen i DX MOV AH, 09h; tjeneste 09h INT 21t; hvis AX = 0009h, skriver den ut strengen pekt på av minneadressen i DX

X86 Assembly

Assembly x86 er en familie av Assembly-språk som brukes til å lage objektkoder for Intel X86 -prosessorer . Som alle assembly-språk, bruker den korte ord for å lage CPU -instruksjoner .

Merknader

  1. ^ Tanenbaum 2006 .
  2. ^ assembler-språk , i Treccani.it - ​​Online encyclopedias , Institute of the Italian Encyclopedia.

Bibliografi

Relaterte elementer

Andre prosjekter

Eksterne lenker