Assembly LanguageAan de slag met Assembly Language


Opmerkingen

Assemblage is een algemene naam die wordt gebruikt voor veel door mensen leesbare vormen van machinecode. Het verschilt natuurlijk veel tussen verschillende CPU's (Central Processing Unit), maar ook op een enkele CPU kunnen er verschillende incompatibele dialecten van assemblage bestaan, elk samengesteld door een andere assembler, in de identieke machinecode die is gedefinieerd door de CPU-maker.

Als u een vraag wilt stellen over uw eigen assemblageprobleem, vermeld dan altijd welke HW en welke assembler u gebruikt, anders zal het moeilijk zijn om uw vraag gedetailleerd te beantwoorden.

De leerassemblage van één bepaalde CPU helpt om de basis op verschillende CPU's te leren, maar elke HW-architectuur kan aanzienlijke verschillen in details hebben, dus het leren van ASM voor een nieuw platform kan bijna helemaal opnieuw leren.

Links:

X86 vergadering Wikibook

Invoering

Assembleertaal is een door mensen leesbare vorm van machinetaal of machinecode die de werkelijke reeks bits en bytes is waarop de processorlogica werkt. Het is over het algemeen gemakkelijker voor mensen om in mnemonics te lezen en te programmeren dan binair, octaal of hex, dus mensen schrijven meestal code in assembleertaal en gebruiken vervolgens een of meer programma's om het om te zetten in het formaat van de machinetaal dat door de processor wordt begrepen.

VOORBEELD:

mov eax, 4
cmp eax, 5
je point
 

Een assembler is een programma dat het assembleerprogramma leest, parseert en de bijbehorende machinetaal produceert. Het is belangrijk om te begrijpen dat in tegenstelling tot een taal zoals C ++ die een enkele taal is die in het standaarddocument is gedefinieerd, er veel verschillende assembleertalen zijn. Elke processorarchitectuur, ARM, MIPS, x86, enz. Heeft een andere machinecode en dus een andere assembleertaal. Bovendien zijn er soms meerdere verschillende assembleertalen voor dezelfde processorarchitectuur. In het bijzonder heeft de x86-processorfamilie twee populaire formaten die vaak worden aangeduid als gassyntaxis ( gas is de naam van het uitvoerbare bestand voor de GNU Assembler) en Intel-syntaxis (vernoemd naar de maker van de x86-processorfamilie). Ze zijn verschillend, maar gelijkwaardig in die zin dat elk typisch programma in beide syntaxis kan worden geschreven.

In het algemeen documenteert de uitvinder van de processor de processor en zijn machinecode en creëert een assembleertaal. Het is gebruikelijk dat die specifieke assembleertaal de enige is die wordt gebruikt, maar in tegenstelling tot compileerschrijvers die proberen te voldoen aan een taalstandaard, is de assembleertaal die is gedefinieerd door de uitvinder van de processor meestal maar niet altijd de versie die wordt gebruikt door de mensen die assemblers schrijven .

Er zijn twee algemene soorten processors:

  • CISC (Complexe Instructie Set Computer): hebben veel verschillende en vaak complexe instructies voor machinetaal

  • RISC (gereduceerde instructieset Computers): heeft daarentegen minder en eenvoudiger instructies

Voor een assembleertaalprogrammeur is het verschil dat een CISC-processor een groot aantal instructies moet leren, maar er zijn vaak instructies die geschikt zijn voor een bepaalde taak, terwijl RISC-processors minder en eenvoudiger instructies hebben, maar voor een bepaalde bewerking kan de assembleertaalprogrammeur nodig zijn om meer instructies te schrijven om hetzelfde gedaan te krijgen.

Andere programmeertalencompilers produceren soms eerst een assembler, die vervolgens wordt gecompileerd in machinecode door een assembler aan te roepen. Bijvoorbeeld, gcc met eigen gas assembler in laatste fase van de compilatie. Geproduceerde machinecode wordt vaak opgeslagen in objectbestanden , die door het linkerprogramma kunnen worden gekoppeld aan uitvoerbaar.

Een complete "toolchain" bestaat vaak uit een compiler, assembler en linker. Men kan die assembler en linker dan direct gebruiken om programma's in assembleertaal te schrijven. In de GNU-wereld bevat het binutils- pakket de assembler en linker en bijbehorende tools; diegenen die alleen geïnteresseerd zijn in het programmeren van assembler, hebben geen gcc of andere compilerpakketten nodig.

Kleine microcontrollers worden vaak puur geprogrammeerd in montagetaal of in een combinatie van montagetaal en een of meer talen van een hoger niveau, zoals C of C ++. Dit wordt gedaan omdat men vaak de specifieke aspecten van de architectuur van de instructieset voor dergelijke apparaten kan gebruiken om een compactere, efficiëntere code te schrijven dan mogelijk zou zijn in een taal van een hoger niveau en dergelijke apparaten hebben vaak een beperkt geheugen en registers. Veel microprocessors worden gebruikt in ingebedde systemen , andere apparaten dan computers voor algemene doeleinden die toevallig een microprocessor hebben. Voorbeelden van dergelijke ingebedde systemen zijn televisies, magnetrons en de motorbesturingseenheid van een moderne auto. Veel van dergelijke apparaten hebben geen toetsenbord of scherm, dus een programmeur schrijft het programma over het algemeen op een computer voor algemeen gebruik, voert een cross-assembler uit (zo genoemd omdat dit soort assembler code produceert voor een ander soort processor dan die waarop het draait ) en / of een cross-compiler en cross-linker om machinecode te produceren.

Er zijn veel leveranciers voor dergelijke tools, die net zo gevarieerd zijn als de processors waarvoor ze code produceren. Veel, maar niet alle processors hebben ook een open source-oplossing zoals GNU, sdcc, llvm of andere.

X86-assemblage uitvoeren in Visual Studio 2015

Stap 1 : Maak een leeg project via Bestand -> Nieuw project .

voer hier de afbeeldingsbeschrijving in

Stap 2 : Klik met de rechtermuisknop op de projectoplossing en selecteer Build afhankelijkheden-> Build-aanpassingen .

Stap 3 : vink het selectievakje ".masm" aan .

voer hier de afbeeldingsbeschrijving in

Stap 4 : Druk op de knop "ok" .

Stap 5 : Maak uw montagebestand en typ dit:

.386
    .model small
        .code

            public main
                main proc

                    ; Ends program normally

                    ret
                main endp
            end main
 

Stap 6 : Compileren!

voer hier de afbeeldingsbeschrijving in

Hallo wereld voor Linux x86_64 (Intel 64 bit)

section .data
    msg db "Hello world!",10      ; 10 is the ASCII code for a new line (LF)

section .text
    global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, 13
    syscall
    
    mov rax, 60
    mov rdi, 0
    syscall
 

Als u dit programma wilt uitvoeren, hebt u eerst de Netwide Assembler nodig , nasm , omdat deze code de syntaxis gebruikt. Gebruik vervolgens de volgende opdrachten (ervan uitgaande dat de code in het bestand helloworld.asm ). Ze zijn nodig voor respectievelijk assembleren, koppelen en uitvoeren.

  • nasm -felf64 helloworld.asm
  • ld helloworld.o -o helloworld
  • ./helloworld

De code maakt gebruik van Linux's sys_write syscall. Hier ziet u een lijst met alle syscalls voor de x86_64-architectuur. Wanneer u ook rekening houdt met de man-pagina's schrijven en afsluiten , kunt u het bovenstaande programma vertalen in een C die hetzelfde doet en veel leesbaarder is:

#include <unistd.h>

#define STDOUT 1

int main()
{
    write(STDOUT, "Hello world!\n", 13);
    _exit(0);
}
 

Hier zijn slechts twee commando's nodig voor compileren en koppelen (eerste) en uitvoeren:

  • gcc helloworld_c.c -o helloworld_c .
  • ./helloworld_c

Hello World voor OS X (x86_64, Intel syntax gas)

.intel_syntax noprefix

.data

.align 16
hello_msg:
    .asciz "Hello, World!"

.text

.global _main
_main:
    push rbp
    mov rbp, rsp

    lea rdi, [rip+hello_msg]
    call _puts

    xor rax, rax
    leave
    ret
 

Monteren:

clang main.s -o hello
./hello
 

Opmerkingen:

Machine code

Machinecode is een term voor de gegevens in een specifiek systeemformaat, die rechtstreeks door de machine worden verwerkt - meestal door de processor die CPU (Central Processing Unit) wordt genoemd.

Gemeenschappelijke computerarchitectuur ( von Neumann-architectuur ) bestaat uit een processor voor algemeen gebruik (CPU), geheugen voor algemeen gebruik - waarin zowel programma (ROM / RAM) als verwerkte gegevens en invoer- en uitvoerapparaten (I / O-apparaten) worden opgeslagen.

Het grote voordeel van deze architectuur is relatieve eenvoud en universaliteit van elk van de componenten - in vergelijking met eerdere computermachines (met vast bedraad programma in de machineconstructie), of concurrerende architecturen (bijvoorbeeld de Harvard-architectuur die het geheugen van het programma scheidt van het geheugen van gegevens). Nadeel is een beetje slechter algemene prestaties. Op lange termijn maakte de universaliteit een flexibel gebruik mogelijk, dat meestal groter was dan de prestatiekosten.

Hoe verhoudt dit zich tot machinecode?

Programma en gegevens worden op deze computers opgeslagen als getallen in het geheugen. Er is geen echte manier om code van gegevens te onderscheiden, dus de besturingssystemen en machine-operators geven de CPU-hints, vanaf welk beginpunt het geheugen het programma start, nadat alle nummers in het geheugen zijn geladen. De CPU leest vervolgens de instructie (het nummer) die is opgeslagen bij het invoerpunt en verwerkt deze rigoureus, waarbij de volgende getallen achtereenvolgens worden gelezen als verdere instructies, tenzij het programma zelf de CPU vertelt verder te gaan met de uitvoering elders.

Bijvoorbeeld een twee 8-bits nummers (8 bits gegroepeerd zijn gelijk aan 1 byte, dat is een niet-ondertekend geheel getal binnen het bereik 0-255): 60 201 , wanneer uitgevoerd als code op de Zilog Z80 CPU, wordt als twee instructies verwerkt: INC a (oplopende waarde in register a met één) en RET (terugkerend van subroutine, CPU aanwijzend om instructies uit een ander deel van het geheugen uit te voeren).

Om dit programma te definiëren kan een mens die getallen invoeren door een geheugen- / bestandseditor, bijvoorbeeld in hex-editor als twee bytes: 3C C9 (decimale getallen 60 en 201 geschreven in codering 16). Dat zou programmeren in machinecode zijn .

Om de taak van CPU-programmeren voor de mens gemakkelijker te maken, zijn er Assembler- programma's gemaakt die tekstbestanden kunnen lezen die zoiets bevatten als:

subroutineIncrementA:
    INC   a
    RET

dataValueDefinedInAssemblerSource:
    DB    60          ; define byte with value 60 right after the ret
 

uitvoer van byte hex-nummers reeks 3C C9 3C , omwikkeld met optionele extra nummers specifiek voor doelplatform: markeren welk deel van een dergelijke binaire uitvoerbare code is, waar is het toegangspunt voor programma (de eerste instructie ervan), welke delen zijn gecodeerd gegevens (niet uitvoerbaar), enz.

Merk op hoe de programmeur de laatste byte met waarde 60 als "data" heeft gespecificeerd, maar vanuit CPU-perspectief verschilt dit op geen enkele manier van INC a byte. Het is aan het uitvoerende programma om CPU correct te navigeren over bytes die als instructies zijn voorbereid, en databytes alleen als gegevens voor instructies te verwerken.

Dergelijke uitvoer wordt meestal opgeslagen in een bestand op een opslagapparaat, later geladen door OS ( besturingssysteem - een machinecode die al op de computer wordt uitgevoerd en helpt met de computer te manipuleren ) in het geheugen voordat deze wordt uitgevoerd en ten slotte de CPU op de beginpunt van programma.

De CPU kan alleen machinecode verwerken en uitvoeren - maar alle geheugeninhoud, zelfs willekeurige, kan als zodanig worden verwerkt, hoewel het resultaat willekeurig kan zijn, variërend van " crash " gedetecteerd en verwerkt door OS tot per ongeluk wissen van gegevens van I / O apparaten of schade aan gevoelige apparatuur die op de computer is aangesloten (niet gebruikelijk voor thuiscomputers :)).

Het vergelijkbare proces wordt gevolgd door vele andere programmeertalen op hoog niveau, waarbij de bron (door de mens leesbare tekstvorm van het programma) in getallen wordt gecompileerd, ofwel de machinecode (native instructies van CPU), of in geval van geïnterpreteerde / hybride talen in een aantal algemene taalspecifieke virtuele machinecode, die verder wordt gedecodeerd in native machinecode tijdens uitvoering door een tolk of virtuele machine.

Sommige compilers gebruiken de assembler als tussenstadium van de compilatie, waarbij de bron eerst in de assembler-vorm wordt vertaald en vervolgens de assembler-tool wordt uitgevoerd om de definitieve machinecode eruit te halen (GCC-voorbeeld: voer gcc -S helloworld.c uit om een assembleerversie van het C-programma te krijgen helloworld.c ).