Поиск…


замечания

В этом разделе представлен обзор того, что такое x86, и почему разработчик может захотеть его использовать.

Следует также упомянуть любые крупные темы в x86 и ссылки на связанные темы. Поскольку документация для x86 является новой, вам может потребоваться создать начальные версии этих связанных тем.

x86 Язык ассемблера

Семейство языков ассемблера x86 представляет собой десятилетия достижений в оригинальной архитектуре Intel 8086. В дополнение к наличию нескольких разных диалектов на основе используемого ассемблера, на протяжении многих лет были добавлены дополнительные инструкции процессора, регистры и другие функции, в то же время оставаясь обратно совместимыми с 16-разрядной сборкой, используемой в 1980-х годах.

Первым шагом к работе с сборкой x86 является определение цели. Например, если вы хотите написать код в операционной системе, вы также захотите дополнительно определить, будете ли вы использовать автономный ассемблер или встроенные встроенные функции сборки языка более высокого уровня, например C. Если вы хотите скомпоновать «голый металл» без операционной системы, вам просто нужно установить ассемблер по вашему выбору и понять, как создать двоичный код, который можно превратить в флеш-память, загрузочное изображение или иначе загрузить в память на подходящее место для начала выполнения.

Очень популярным ассемблером, который хорошо поддерживается на нескольких платформах, является NASM (Netwide Assembler), который можно получить на http://nasm.us/ . На сайте NASM вы можете приступить к загрузке последней версии для вашей платформы.

Windows

Для Windows доступны 32-разрядные и 64-разрядные версии NASM. NASM поставляется с удобным установщиком, который можно использовать на вашем хосте Windows для установки ассемблера автоматически.

Linux

Вполне возможно, что NASM уже установлен в вашей версии Linux. Чтобы проверить, выполните:

nasm -v

Если команда не найдена, вам необходимо выполнить установку. Если вы не делаете что-то, что требует кратковременных функций NASM, лучший путь - использовать встроенный инструмент управления пакетами для вашего дистрибутива Linux для установки NASM. Например, в Debian-производных системах, таких как Ubuntu и другие, выполните следующее из командной строки:

sudo apt-get install nasm

Для систем на основе RPM вы можете попробовать:

sudo yum install nasm

Mac OS X

Последние версии OS X (включая Yosemite и El Capitan) поставляются со старой версией NASM, предварительно установленной. Например, El Capitan имеет версию 0.98.40. Хотя это, вероятно, будет работать практически во всех нормальных целях, оно на самом деле довольно старое. На этом написании выпущена версия 2.11 NASM, а в версии 2.12 имеется несколько доступных релизов.

Вы можете получить исходный код NASM из приведенной выше ссылки, но если у вас нет конкретной потребности в установке из исходного кода, гораздо проще загрузить двоичный пакет из каталога выпуска OS X и разархивировать его.

После разархивирования настоятельно рекомендуется не перезаписывать системную версию NASM. Вместо этого вы можете установить его в / usr / local:

 $ sudo su
 <user's password entered to become root>
 # cd /usr/local/bin
 # cp <path/to/unzipped/nasm/files/nasm> ./
 # exit

На данный момент NASM находится в /usr/local/bin , но это не на вашем пути. Теперь вы должны добавить следующую строку в конец своего профиля:

 $ echo 'export PATH=/usr/local/bin:$PATH' >> ~/.bash_profile

Это добавит /usr/local/bin на ваш путь. Выполнение nasm -v в командной строке должно теперь отображать правильную, более новую версию.

x86 Linux Привет, мир

Это базовая программа Hello World в сборке NASM для 32-разрядного x86 Linux, используя системные вызовы напрямую (без каких-либо вызовов функций libc). Это очень много, но со временем это станет понятным. Строки, начинающиеся с точки с запятой ( ; ), являются комментариями.

Если вы еще не знаете низкоуровневое системное программирование Unix, вы можете просто написать функции в asm и вызвать их из программ C или C ++. Тогда вы можете просто беспокоиться о том, как обращаться с регистрами и памятью, не изучая также API-интерфейс POSIX и API ABI для его использования.


Это делает два системных вызова: write(2) и _exit(2) (а не оболочка libc exit(3) которая сбрасывает буферы stdio и т. Д.). (Технически, _exit() вызывает sys_exit_group, а не sys_exit, но это имеет значение только в многопоточном процессе .) См. Также syscalls(2) для документации о системных вызовах вообще и разницу между их непосредственным использованием или использованием libc обертки.

Таким образом, системные вызовы производятся путем размещения аргументов в соответствующих регистрах и номера системного вызова в eax , а затем выполнения команды int 0x80 . См. Также Каковы возвращаемые значения системных вызовов в Assembly? для более подробного объяснения того, как интерфейс asm syscall документирован в основном синтаксисе Си.

Номера вызовов syscall для 32-разрядного ABI находятся в /usr/include/i386-linux-gnu/asm/unistd_32.h (то же самое содержимое в /usr/include/x86_64-linux-gnu/asm/unistd_32.h ).

#include <sys/syscall.h> конечном итоге включит нужный файл, поэтому вы можете запустить echo '#include <sys/syscall.h>' | gcc -E - -dM | less чтобы увидеть макрос defs (см. этот ответ для получения дополнительной информации о поиске констант для asm в заголовках C )


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

В Linux вы можете сохранить этот файл как Hello.asm и создать из него 32-битный исполняемый файл с помощью следующих команд:

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

См. Этот ответ для получения дополнительной информации о сборке в 32 или 64-разрядных статических или динамически связанных Linux-исполняемых файлах, для синтаксиса NASM / YASM или синтаксиса GNU AT & T с GNU в as директив. (Ключевой момент: обязательно используйте -m32 или эквивалент при построении 32-битного кода на 64-битном хосте или у вас будут проблемы с запуском во время выполнения.)

Вы можете проследить его выполнение с помощью strace чтобы увидеть, как он вызывает системные вызовы:

$ 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 +++

След на STDERR и регулярный выход на стандартном выводе оба собираются терминал здесь, так как они мешают в соответствии с write системного вызова. При необходимости перенаправляйте или трассируйте файл. Обратите внимание на то, как это позволяет нам легко увидеть значения возвратов syscall без необходимости добавлять код для их печати, и на самом деле даже проще, чем использовать обычный отладчик (например, gdb).

Версия этой программы x86-64 была бы очень похожей, передавая одни и те же аргументы на одни и те же системные вызовы, только в разных регистрах. И используя команду syscall вместо int 0x80 .



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow