Tooprogram.ru

Компьютерный справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Ассемблер x86 64

Изучаем архитектуру Intel x86-64 при помощи ассемблера (Часть 1 — Hello World на голом железе)

Я давно хотел научиться писать операционную систему или хотя бы попробовать эту задачу на зуб. Почему ОС начинают писать на языке ассемблера? Дело в том, что работа ОС обеспечивается определенными особенностями архитектуры процессора. Это, например, наличие нескольких уровней привилегий (режим ядра, режим пользователя), поддержка виртуальной памяти, поддержка многозадачности. Работа с этими особенностями архитектуры процессора подразумевает использование таких машинных команд и регистров процессора, о которых компиляторы высокоуровневых языков программирования (например C/C++) ничего не знают. Поэтому — ассемблер. В следующей серии заметок я буду писать о своих опытах исследования архитектуры процессоров Intel как то: работа в реальном режиме, переход в защищенный режим, написание загрузчика (bootloader), обработка прерываний, включение механизма виртуальной памяти и пр.

В Интернете я нашел несколько хороших ресурсов по теме:

Кроме того, если вы не знакомы с языком ассемблера для процессоров семейства x86, рекомендую вам книгу Kip Irvine — Assembly Language for x86 Processors, 7th edition — 2014.

Начнем с того, что напишем программу HelloWorld, запускающуюся на голом железе (без операционной системы) и запустим ее на виртуальной машине.

Программа HelloWorld

Ниже показан исходный код программы HelloWorld на языке ассемблера Flat Assembler (FASM), которая будет стартовать на голом железе в реальном режиме работы x86-совместимого процессора (комментарии пишу на английском — привычка, которая возникла у меня в связи с проблемами с кодировками кириллицы).

; HelloWorld real mode
use16 ; generate 16-bit code
org 7C00h ; the code starts at 0x7C00 memory address

start :
jmp far dword 0x0000 : entr ; makes CS=0, IP=0x7c00
entr :
xor ax , ax ; ax = 0
mov ds , ax ; setup data segment ds=ax=0
cli ; when we set up stack we need disable interrupts because stack is involved in interrupts handling
mov ss , ax ; setup stack segment ss=ax=0
mov sp , 0x7C00 ; stack will grow starting from 0x7C00 memory address
sti ; enable interrupts

mov si , message
cld ; clear direction flag (DF is a flag used for string operations)
mov ah , 0Eh ; BIOS function index (write a charachter to the active video page)
puts_loop :
lodsb ; load to al the next charachter located at [si] address in memory (si is incremented automatically because the direction flag DF = 0)
test al , al ; zero in al denotes the end of the string
jz puts_loop_exit
int 10h ; call BIOS standard video service’s function
jmp puts_loop
puts_loop_exit :
cli ; disable interrupts before halting the processor
hlt ; halt the processor
;jmp $ ; alternatively to hlt we could run an infinite loop

message db ‘Hello World!’ , 0
finish :
; The size of a disk sector is 512 bytes. Boot sector signature occupies the two last bytes.
; The gap between the end of the source code and the boot sector signature is filled with zeroes.
times 510 — finish + start db 0
db 55h , 0AAh ; boot sector signature

Компиляция исходного кода

Объяснения того, как работает программа — потом, сначала давайте скомпилируем исходный код. Компилировать будем ассемблером Flat Assembler (FASM). Скачиваем с официального сайта архив с дистрибутивом FASM. Внутри архива находится компилятор fasm.exe. Я рекомендовал бы распаковать архив в папку C:FASM и добавить путь к этой папке в переменную окружения PATH. Далее запускаем командную строку и компилируем исходный код:

HelloWorld.asm — это приведенный выше файл с исходным кодом, HelloWorld.bin — это скомпилированный машинный код.

Создание образа дискеты

Операционная система должна загружаться с какого-то носителя (жесткого диска, CD-ROM, флешки, дискеты и т. д.). Создадим образ загрузочной дискеты с нашим машинным кодом. Виртуальная машина сможет загрузиться с виртуальной дискеты, используя этот образ. Создавать образ дискеты умеет unix’овая утилита под названием dd. Существует версия этой утилиты под Windows. Если вы пользуетесь средой Cygwin, то утилита dd есть в ее составе. Итак, запускаем командную строку:

1-я команда создает образ дискеты floppy.img и заполняет его нулями, 2-я — записывает в самое начало образа нашу программу.

Установка и запуск виртуальной машины Bochs

Я имел дело всего с тремя виртуальными машинами: Oracle VM VirtualBox, VMware Workstation Player и Bochs. Bochs хотя и обладает очень скромным графическим интерфейсом, хорош тем, что он легкий и может выполнять машинный код пошагово, т. е. с ним можно производить отладку исходного кода программы. Скачиваем с официального сайта программу установки (файл с расширением .exe) и запускаем его (все настройки я оставлял по-умолчанию). После установки запускаем Bochs. Возникает диалоговое окно Bochs Start Menu. В списке Edit Options выбираем Disk & Boot и нажимаем кнопку Edit. Открывается диалог Bochs Disk Options. На вкладке Floppy Options в группе First Floppy Drive устанавливаем следующие настройки:

Читать еще:  Ассемблер арифметические операции
Type of floppy drive3.5 1.44M
First floppy image/deviceжмем Browse и выбираем ранее созданный нами файл floppy.img
Type of floppy media1.44M
Write Protectionгалка снята
Statusinserted

Жмем кнопку OK. В окне Bochs Start Menu жмем кнопку Start (предварительно можно сохранить сделанные нами настройки в текстовом файле с расширением .bxrc нажав кнопку Save, впоследствии их можно будет загрузить, нажав кнопку Load). Открывается окно, в котором мы видим надпись

Особенности FASM

Прежде всего обратите внимание на документацию Flat Assembler Documentation and Tutorials, которая также поставляется в дистрибутиве FASM в виде файла PDF. Синтаксис FASM имеет особенности, которые отличают его например от MASM, которым мне доводилось пользоваться до сих пор. Особенности касаются обращений к памяти и работы с метками:

Внутри x86-64 SystemV ABI. Как говорить с ядром Linux на его языке

Содержание статьи

Вспомним сигнатуру функции main в C:

Откуда берутся число аргументов ( argc ) и массив указателей на их строки ( argv )? Как возвращаемое значение main становится кодом возврата самой программы?

Краткий ответ: зависит от архитектуры процессора. К сожалению, доступных для начинающих материалов по самой распространенной сейчас архитектуре x86-64 не так много, и интересующиеся новички вынуждены сначала обращаться к старой литературе по 32-битным x86, которая следует другим соглашениям.

Попробуем исправить этот пробел и продемонстрировать прямое взаимодействие с машиной и ядром Linux сразу в 64-битном режиме.

Демонстрационная задача

Для демонстрации мы напишем расширенную версию hello world, которая может приветствовать любое количество объектов или людей, чьи имена передаются в аргументах команды.

Среда разработки

Для демонстрации мы будем использовать Linux и GNU toolchain (GCC и binutils), как самые распространенные ОС и среда разработки. Писать мы будем на языке ассемблера, потому что продемонстрировать низкоуровневое взаимодействие с ОС из языка сколько-нибудь высокого уровня невозможно.

Очень краткая справка

Чтобы упростить чтение статьи тем, кто вообще никогда не сталкивался с ассемблером x86, я использовал только самые простые инструкции и постарался аннотировать их псевдокодом везде, где возможно. Я использую синтаксис AT&T, который все инструменты GNU используют по умолчанию. Нужно помнить, что регистры пишутся с префиксом % (например, %rax ), а константы — c префиксом $ . Например, $255 , $0xFF , $foo — значение символа foo .

Синтаксис указателей: смещение(база, индекс, множитель) . Очень краткая справка:

  • mov , — копирует значение из источника (регистра или адреса) в приемник;
  • push — добавляет значение из источника на стек;
  • pop — удаляет значение из стека и копирует в приемник;
  • call — вызывает функцию по указанному адресу;
  • ret — возврат из функции;
  • jmp — безусловный переход по адресу (метке);
  • inc и dec — инкремент и декремент;
  • cmp — сравнение и установка флагов (например, равенство);
  • je — переход на метку в случае, если аргументы cmp оказались равными.

Условные переходы и циклы реализуются через инструкции сравнения и условные переходы. Инструкции сравнения устанавливают определенные разряды в регистре флагов, команда условного перехода проверяет их и принимает решение, переходить или нет. Например, следующий цикл увеличивает значение регистра %rax , пока оно не станет равным 10, а затем копирует его в %rbx .

Для изучения ассемблера x86 я могу посоветовать книгу Programming From the Ground Up — к сожалению, ориентированную на 32-битную архитектуру, но очень хорошо написанную и подходящую новичкам.

О регистрах: в x86-64 их куда больше. Кроме традиционных, добавлены регистры от %r8 до %r15 , всего шестнадцать 64-битных регистров. Чтобы обратиться к нижним байтам новых регистров, нужно использовать суффиксы d , w , или b . То есть %r10d — нижние четыре байта, %r10w — нижние два байта, %r10b — нижний байт.

Что входит в соглашения ABI?

SystemV ABI, которой в большей или меньшей степени следуют почти все UNIX-подобные системы, состоит из двух частей. Первая часть, общая для всех систем, описывает формат исполняемых файлов ELF. Ее можно найти на сайте SCO.

К общей части прилагаются архитектурно зависимые дополнения. Они описывают:

  • соглашение о системных вызовах;
  • соглашение о вызовах функций;
  • организацию памяти процессов;
  • загрузку и динамическое связывание программ.
Читать еще:  Язык си уроки

Формат ELF

Знать формат ELF в деталях, особенно его двоичную реализацию, нужно только авторам ассемблеров и компоновщиков. Эти задачи мы в статье не рассматриваем. Тем не менее пользователю следует понимать организацию формата.

Файлы ELF состоят из нескольких секций. Компиляторы принимают решение о размещении данных по секциям автоматически, но ассемблеры оставляют это на человека или компилятор. Полный список можно найти в разделе Special Sections. Вот самые распространенные:

  • .text — основной исполняемый код программы;
  • .rodata — данные только для чтения (константы);
  • .data — данные для чтения и записи (инициализированные переменные);
  • .bss — неинициализированные переменные известного размера.

Соглашения о вызовах

Соглашение о вызовах — важная часть ABI, которая позволяет пользовательским программам взаимодействовать с ядром, а программам и библиотекам — друг с другом. В соглашении указывается, как передаются аргументы (в регистрах или на стеке), какие именно регистры используются и где хранится результат. Кроме того, оговаривается, какие регистры вызываемая функция обязуется сохранить нетронутыми (callee-saved), а какие может свободно перезаписать (caller-saved).

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Assembly registers in 64-bit architecture

First, what sizes are eax , ax , ah and their counterparts, in the 64-bit architecture? How to access a single register’s byte and how to access all the 64-bit register’s eight bytes?

I’d love attention for both x86-64 (x64) and Itanium processors.

Second, what is the correct way to use the four registers for holding the first four parameters in function calls in the new calling convention?

1 Answer 1

With the old names all registers remain the same size, just like when x86-16 was extended to x86-32. To access 64-bit registers you use the new names with R-prefix such as rax, rbx.

Register names don’t change so you just use the byte registers (al, bl, cl, dl, ah, bh, ch, dh) for the LSB and MSB of ax, bx, cx, dx like before.

There are also 8 new registers called r8-r15. You can access their LSBs by adding the suffix b (or l if you’re using AMD). For example r8b, r9b. You can also use the LSB of esi, edi, esp, ebp by the names sil, dil, spl, bpl with the new REX prefix, but you cannot use it at the same time with ah, bh, ch or dh.

Likewise the new registers’ lowest word or double word can be accessed through the suffix w or d .

Regarding the calling convention, on a specific system there’s only one convention 1 .

  • RCX, RDX, R8, R9 for the first four integer or pointer arguments
  • XMM0, XMM1, XMM2, XMM3 for floating-point arguments

1 Since MSVC 2013 there’s also a new extended convention on Windows called __vectorcall so the «single convention policy» is not true anymore.

On Linux and other systems that follow System V AMD64 ABI, more arguments can be passed on registers and there’s a 128-byte red zone below the stack which may make function calling faster.

  • The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, and R9
  • Floating-point arguments are passed in XMM0 through XMM7

For more information should read x86-64 and x86-64 calling conventions

There’s also a convention used in Plan 9 where

  • All registers are caller-saved
  • All parameters are passed on the stack
  • Return values are also returned on the stack, in space reserved below (stack-wise; higher addresses on amd64) the arguments.

In fact Plan 9 was always a weirdo. For example it forces a register to be 0 on RISC architectures without a hardware zero register. x86 register names on it are also consistent across 16, 32 and 64-bit x86 architectures with operand size indicated by mnemonic suffix. That means ax can be a 16, 32 or 64-bit register depending on the instruction suffix. If you’re curious about it read

OTOH Itanium is a completely different architecture and has no relation to x86-64 whatsoever. It’s a pure 64-bit architecture so all normal registers are 64-bit, no 32-bit or smaller version is available. There are a lot of registers in it:

  • 128 general-purpose integer registers r0 through r127, each carrying 64 value bits and a trap bit. We’ll learn more about the trap bit later.
  • 128 floating point registers f0 through f127.
  • 64 predicate registers p0 through p63.
  • 8 branch registers b0 through b7.
  • An instruction pointer, which the Windows debugging engine for some reason calls iip. (The extra «i» is for «insane»?)
  • 128 special-purpose registers, not all of which have been given meanings. These are called «application registers» (ar) for some reason. I will cover selected register as they arise during the discussion.
  • Other miscellaneous registers we will not cover in this series.

Ассемблер x86 64

В данной книге речь идет о работе процессора в двух его основных режимах: защищенном режиме и 64-битном, который также называют long mode («длинный режим»). Также помимо изложения принципов и механизмов работы процессора в защищенном и 64-битном режимах, речь пойдет о программировании на ассемблере в операционных системах семейства Windows, как в 32-битных, так и 64-битных версиях. Рассматривается не только разработка обычных приложений для операционных систем Windows, но и разработка драйверов на ассемблере. При написании книги уделялось большое внимание именно практической составляющей, т.е. изложение материала идет только по делу и только то, что необходимо знать любому системному и низко-уровневому программисту. Последний раздел книги посвящен принципам работы многопроцессорных систем, а также работе с расширенным программируемым контроллером прерываний (APIC).

Читать еще:  Команды перехода ассемблер

Отрывок из введения книги

.
А теперь расскажем вкратце, о чём идёт речь в данной книге. О программировании на языке ассемблер написано немало книг, и придумать что-то новое, что было бы интересно читателю, – не самая лёгкая задача. При написании книги была поставлена цель: изложить тот материал, который либо трудно, либо попросту невозможно найти в русскоязычной технической литературе о языке ассемблер и работе процессора вообще.
В данной книге описывается работа процессора в двух его основных режимах: защищённом и 64-разрядном (который также называют long mode, или «длинный режим»). Книга посвящена программированию на ассемблере в этих режимах, включая программирование под операционными системами Windows, т. к. системы Windows работают в защищённом и 64-разрядном режимах.
Большинство книг об ассемблере рассказывают о режиме реальных адресов, а также о наборе команд ассемблера, который использовался в 1990-95 гг. С учётом того что подавляющее большинство современных операционных систем (Windows и UNIX) работают в защищённом и 64-разрядном режимах, большинство книг об ассемблере без преувеличения можно назвать устаревшими, поскольку они рассказывают о том режиме процессора, который уже почти не используется. Иногда даже удивляешься, что в высших учебных заведениях на занятиях, посвящённых архитектуре ЭВМ, изучают работу процессора в том режиме, который 20 лет как устарел, и при этом ничего не говорят про режим, в котором работают современные операционные системы. Данная книга поможет не только узнать, как работает процессор, но и изучить основные принципы разработки операционных систем.
После изложения принципов и механизмов работы процессора в защищённом и 64-битном режимах речь пойдёт о программировании на ассемблере в самых распространённых операционных системах, работающих в этих двух режимах, а именно системах семейства Windows – как в 32-битных, так и в 64-битных версиях. Будут рассмотрены не только создание обычных приложений для операционных систем Windows, но и разработка драйверов.
Последняя глава книги освещает принципы работы многопроцессорных систем, а также работу с расширенным программируемым контроллером прерываний.
При написании книги большое внимание уделялось практической составляющей. Здесь рассматривается только то, что необходимо знать любому системному программисту и программисту, разрабатывающему низкоуровневые приложения.
Книга построена так, чтобы приведённый в ней материал мог усвоить каждый интересующийся. Поэтому первая глава посвящена основам языка ассемблер – после её прочтения даже тот, кто никогда не сталкивался с ассемблером, получит хоть и не большой, но всё-таки фундамент для изучения следующих глав книги.
В каждом разделе (за исключением первой главы) есть как минимум один практический пример для закрепления изученного материала. Полный исходный код всех примеров книги можно найти на компакт-диске, прилагающемся к книге.

Обсуждение книги все коментарии не касающиеся ассемблера писать в той теме.

Просьба оставлять здесь коментарии только касающиеся тематики книги, недочётов, ошибок и «недоделок».

Ссылка на основную публикацию
Adblock
detector