Intel x86 Assembly Language & Microarchitecture Handledning
Komma igång med Intel x86 Assembly Language & Microarchitecture
Sök…
Anmärkningar
Det här avsnittet ger en översikt över vad x86 är, och varför en utvecklare kanske vill använda den.
Det bör också nämna alla stora ämnen inom x86 och länka till relaterade ämnen. Eftersom dokumentationen för x86 är ny, kan du behöva skapa initialversioner av relaterade ämnen.
x86 Församlingsspråk
Familjen x86-samlingsspråk representerar decennier av framsteg med den ursprungliga Intel 8086-arkitekturen. Förutom att det finns flera olika dialekter baserade på den använda monteraren, har ytterligare processorinstruktioner, register och andra funktioner lagts till under åren medan de fortfarande förblir bakåtkompatibla med 16-bitarsenheten som användes på 1980-talet.
Det första steget att arbeta med x86-montering är att bestämma vad målet är. Om du till exempel vill skriva kod inom ett operativsystem, vill du dessutom bestämma om du kommer att välja att använda en fristående monterare eller inbyggda inline-monteringsfunktioner på ett högre språk som C. Om du vill koda ner på "barmetalen" utan ett operativsystem, du behöver helt enkelt installera valfri monterare och förstå hur du skapar binär kod som kan förvandlas till flashminne, startbar bild eller på annat sätt laddas i minnet på lämplig plats för att påbörja körningen.
En mycket populär monterare som stöds väl på ett antal plattformar är NASM (Netwide Assembler), som kan erhållas från http://nasm.us/ . På NASM-webbplatsen kan du fortsätta att ladda ner den senaste versionen för din plattform.
Windows
Både 32-bitars och 64-bitarsversioner av NASM är tillgängliga för Windows. NASM kommer med ett bekvämt installationsprogram som kan användas på din Windows-värd för att installera monteringsanläggningen automatiskt.
Linux
Det kan vara så att NASM redan är installerat på din version av Linux. Så här kontrollerar du:
nasm -v
Om kommandot inte hittas måste du utföra en installation. Såvida du inte gör något som kräver NASM-funktioner som blöder ut, är den bästa vägen att använda ditt inbyggda pakethanteringsverktyg för din Linux-distribution för att installera NASM. Till exempel, under Debian-härledda system som Ubuntu och andra, kör följande från en kommandotolk:
sudo apt-get install nasm
För RPM-baserade system kan du försöka:
sudo yum install nasm
Mac OS X
Nya versioner av OS X (inklusive Yosemite och El Capitan) kommer med en äldre version av NASM förinstallerad. Till exempel har El Capitan version 0.98.40 installerad. Även om detta sannolikt kommer att fungera för nästan alla normala ändamål, är det faktiskt ganska gammalt. Vid detta skrivande släpps NASM-version 2.11 och 2.12 har ett antal release-kandidater tillgängliga.
Du kan få NASM-källkoden från ovanstående länk, men såvida du inte har ett specifikt behov att installera från källan, är det mycket enklare att ladda ner det binära paketet från OS X-utsläppskatalogen och packa upp det.
När du har packat upp rekommenderas det starkt att du inte skriver över den systeminstallerade versionen av NASM. Istället kan du installera det i / usr / local:
$ sudo su
<user's password entered to become root>
# cd /usr/local/bin
# cp <path/to/unzipped/nasm/files/nasm> ./
# exit
Just nu är NASM i /usr/local/bin , men det är inte i din väg. Du bör nu lägga till följande rad i slutet av din profil:
$ echo 'export PATH=/usr/local/bin:$PATH' >> ~/.bash_profile
Detta kommer att bero /usr/local/bin till din väg. Att nasm -v nasm nasm -v vid kommandotolken ska nu visa rätt, nyare version.
x86 Linux Hello World Exempel
Detta är ett grundläggande Hello World-program i NASM-montering för 32-bitars x86 Linux med systemsamtal direkt (utan några libc-funktionssamtal). Det är mycket att ta in, men med tiden kommer det att bli förståeligt. Linjer som börjar med en semikolon ( ; ) är kommentarer.
Om du inte redan känner till Unix-systemprogram på låg nivå kanske du bara vill skriva funktioner i asm och ringa dem från C- eller C ++ -program. Då kan du bara oroa dig för att lära dig att hantera register och minne, utan att också lära dig POSIX system-samtal API och ABI för att använda det.
Detta gör två systemsamtal: write(2) och _exit(2) (inte exit(3) libc-omslaget som spolar stdio-buffertar och så vidare). (Tekniskt _exit() sys_exit_group, inte sys_exit, men det spelar bara roll i en flertrådad process .) Se även syscalls(2) för dokumentation om systemsamtal i allmänhet och skillnaden mellan att göra dem direkt mot att använda libc omslagsfunktioner.
Sammanfattningsvis görs systemsamtal genom att placera args i lämpliga register, och systemanropsnumret i eax och sedan köra en int 0x80 instruktion. Se även Vilka är returvärdena för systemsamtal i församlingen? för mer förklaring av hur asm syscall-gränssnittet är dokumenterat med mestadels C-syntax.
Systemets samtalsnummer för 32-bitars ABI är i /usr/include/i386-linux-gnu/asm/unistd_32.h (samma innehåll i /usr/include/x86_64-linux-gnu/asm/unistd_32.h ).
#include <sys/syscall.h> kommer i slutändan att inkludera rätt fil, så du kan köra echo '#include <sys/syscall.h>' | gcc -E - -dM | less att se makro-defs (se detta svar för mer om att hitta konstanter för asm i C-rubriker )
section .text ; Executable code goes in the .text section
global _start ; The linker looks for this symbol to set the process entry point, so execution start here
;;;a name followed by a colon defines a symbol. The global _start directive modifies it so it's a global symbol, not just one that we can CALL or JMP to from inside the asm.
;;; note that _start isn't really a "function". You can't return from it, and the kernel passes argc, argv, and env differently than main() would expect.
_start:
;;; write(1, msg, len);
; Start by moving the arguments into registers, where the kernel will look for them
mov edx,len ; 3rd arg goes in edx: buffer length
mov ecx,msg ; 2nd arg goes in ecx: pointer to the buffer
;Set output to stdout (goes to your terminal, or wherever you redirect or pipe)
mov ebx,1 ; 1st arg goes in ebx: Unix file descriptor. 1 = stdout, which is normally connected to the terminal.
mov eax,4 ; system call number (from SYS_write / __NR_write from unistd_32.h).
int 0x80 ; generate an interrupt, activating the kernel's system-call handling code. 64-bit code uses a different instruction, different registers, and different call numbers.
;; eax = return value, all other registers unchanged.
;;;Second, exit the process. There's nothing to return to, so we can't use a ret instruction (like we could if this was main() or any function with a caller)
;;; If we don't exit, execution continues into whatever bytes are next in the memory page,
;;; typically leading to a segmentation fault because the padding 00 00 decodes to add [eax],al.
;;; _exit(0);
xor ebx,ebx ; first arg = exit status = 0. (will be truncated to 8 bits). Zeroing registers is a special case on x86, and mov ebx,0 would be less efficient.
;; leaving out the zeroing of ebx would mean we exit(1), i.e. with an error status, since ebx still holds 1 from earlier.
mov eax,1 ; put __NR_exit into eax
int 0x80 ;Execute the Linux function
section .rodata ; Section for read-only constants
;; msg is a label, and in this context doesn't need to be msg:. It could be on a separate line.
;; db = Data Bytes: assemble some literal bytes into the output file.
msg db 'Hello, world!',0xa ; ASCII string constant plus a newline (0x10)
;; No terminating zero byte is needed, because we're using write(), which takes a buffer + length instead of an implicit-length string.
;; To make this a C string that we could pass to puts or strlen, we'd need a terminating 0 byte. (e.g. "...", 0x10, 0)
len equ $ - msg ; Define an assemble-time constant (not stored by itself in the output file, but will appear as an immediate operand in insns that use it)
; Calculate len = string length. subtract the address of the start
; of the string from the current position ($)
;; equivalently, we could have put a str_end: label after the string and done len equ str_end - str
På Linux kan du spara den här filen som Hello.asm och bygga en 32-bitars körbar fil från den med dessa kommandon:
nasm -felf32 Hello.asm # assemble as 32-bit code. Add -Worphan-labels -g -Fdwarf for debug symbols and warnings
gcc -nostdlib -m32 Hello.o -o Hello # link without CRT startup code or libc, making a static binary
Se detta svar för mer information om hur man bygger montering i 32 eller 64-bitars statiska eller dynamiskt länkade Linux-körbara filer, för NASM / YASM-syntax eller GNU AT&T-syntax med GNU as direktiv. (Nyckelpunkt: se till att använda -m32 eller motsvarande när du bygger 32-bitars kod på en 64-bitars värd, eller så har du förvirrande problem vid körning.)
Du kan spåra dess körning med strace att se strace som det gör:
$ strace ./Hello
execve("./Hello", ["./Hello"], [/* 72 vars */]) = 0
[ Process PID=4019 runs in 32 bit mode. ]
write(1, "Hello, world!\n", 14Hello, world!
) = 14
_exit(0) = ?
+++ exited with 0 +++
Spåret på stderr och den vanliga utgången på stdout går båda till terminalen här, så de stör i linje med write . Omdirigera eller spåra till en fil om du bryr dig. Lägg märke till hur det här gör att vi enkelt kan se värdena för systemets återvändande utan att behöva lägga till kod för att skriva ut dem, och det är faktiskt ännu enklare än att använda en vanlig felsökare (som gdb) för detta.
X86-64-versionen av detta program skulle vara extremt likadant och skicka samma arg till samma systemsamtal, bara i olika register. Och använda syscall istället för int 0x80 .