Меню Рубрики

Ассемблер linux системные вызовы

Ассемблер. Системные вызовы и режимы адресации

Обновл. 23 Фев 2020 |

Системные вызовы — это программные интерфейсы между пользователем и ядром. Мы уже использовали следующие системные вызовы:

sys_write — для вывода на экран;

sys_exit — для выхода из программы.

Системные вызовы Linux

В своих программах на ассемблере вы можете использовать системные вызовы Linux. Для этого нужно:

поместить номер системного вызова в регистр EAX;

сохранить аргументы системного вызова в регистрах EBX, ECX и т.д.;

вызвать соответствующее прерывание (80h);

результат обычно возвращается в регистр EAX.

Есть шесть регистров, в которых хранятся и используются аргументы необходимого системного вызова:

Эти регистры принимают последовательные аргументы. Если есть более шести аргументов, то ячейка памяти первого аргумента сохраняется в регистре EBX.

В следующем примере мы будем использовать системный вызов sys_exit :

А в следующем — sys_write :

Все системные вызовы перечислены в /usr/include/asm/unistd.h вместе с их номерами (значение, которое помещается в EAX перед вызовом int 80h ).

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

%eax Название %ebx %ecx %edx %esx %edi
1 sys_exit int
2 sys_fork struct pt_regs
3 sys_read unsigned int char * size_t
4 sys_write unsigned int const char * size_t
5 sys_open const char * int int
6 sys_close unsigned int

В следующей программе мы запрашиваем число, а затем выводим его на экран:

Результат выполнения программы выше:

Please enter a number:
1234
You have entered:1234

Режимы адресации

Большинство инструкций на языке ассемблера требуют обработки операндов. Адрес операнда предоставляет место, где хранятся данные, подлежащие обработке. Некоторые инструкции не требуют операнда, в то время как другие могут требовать один, два или три операнда. В тех случаях, кода инструкции требуется два операнда, то первый операнд обычно является местом назначения, содержащий данные в регистре или в ячейке памяти, а второй — исходником. Исходник содержит либо данные для доставки (немедленная адресация), либо адрес (в регистре или в памяти) данных. Как правило, исходные данные остаются неизменными после операции.

Есть три основных режима адресации:

Регистровая адресация

Прямая (или ещё «непосредственная») адресация

Адресация памяти

Регистровая адресация

В режиме регистровой адресации регистр содержит операнд. В зависимости от инструкции регистр может быть первым операндом, вторым или обоими. Например:

Источник

Системные вызовы в Linux

В Linux, в отличие от Windows, прямые системные вызовы используются довольно часто. По меньшей мере, консольные приложения, написанные на ассемблере, порой содержат лишь системные вызовы, без обращений к функциям библиотек. Причём, этот механизм (как и номера функций(!)) различается для кода 32- и 64-битной разрядности (кстати, в Linux существует ещё и x32 ABI – это, попросту говоря, 64-битный код с 32-битными указателями).

32-битный код (x86 ABI, архитектура i386)

Для приложений x86 имеется 2 варианта вызова системных функций:

  • Через прерывание 80h ( int 0x80 ).
  • Через инструкцию sysenter .

Системный вызов через прерывание 80h

Наиболее распространённый (хотя и более медленный), поскольку осуществляется проще и поддерживается любым процессором 386+.

  1. В регистр EAX загружается номер функции.
  2. Параметры (зависящие от функции) загружаются в регистры EBX (первый параметр), ECX (второй), EDX (третий), ESI (четвёртый), EDI (пятый), EBP (шестой, хотя насколько я знаю, больше 5 параметров не используется).
  3. Осуществляется вызов прерывания: int 0x80 .

Системный вызов через инструкцию sysenter

Осуществляется быстрее, но немного сложнее и поддерживается только процессорами Pentium II и старше (а что, у кого-то более старый? )

  1. В регистр EAX загружается номер функции.
  2. Параметры (зависящие от функции) загружаются в регистры EBX (первый параметр), ECX (второй), EDX (третий), ESI (четвёртый), EDI (пятый).
  3. В стек заносится адрес возврата и регистры ECX, EDX, EBP (именно в таком порядке).
  4. В регистр EBP загружается значение ESP.
  5. Выполняется инструкция sysenter .

64-битный код (x64 и x32 ABI, архитектура x86-64)

Здесь всё несколько проще (за исключением странной очерёдности использования регистров для передачи параметров).

  1. В регистр RAX загружается номер функции.
  2. Параметры (зависящие от функции) загружаются в регистры RDI (первый параметр), RSI (второй), RDX (третий), R10 (четвёртый), R8 (пятый), R9 (шестой).
  3. Выполняется инструкция syscall (не путайте с sysenter – это две разные инструкции!)

Внимание! Значения регистров RCX и R11 при выполнении системного вызова уничтожаются!

Дело в том, что инструкция входа в режим ядра syscall сохраняет в регистре RCX значение RIP, а в R11 – значение регистра флагов (указатель стека RSP при этом не меняется) и переходит к выполнению функции ядра (адрес которой хранится в специальном MSR-регистре). Инструкция sysret же выполняет всё наоборот: восстанавливает RIP из регистра RCX и регистр флагов (почти весь) из регистра R11.
Поскольку в x64 кол-во регистров довольно много, а RCX и R11 не участвуют для передачи параметров, создатели системы решили не заморачиваться с сохранением этих регистров (возможно, заодно и для ускорения системного вызова и возврата).
Почему используются именно эти 2 регистра? Спросите об этом у специалистов Intel (и заодно про sysexit ) – потом расскажете

Что же касается странной очерёдности использования регистров для передачи параметров, то здесь ситуация такова. В соответствии с соглашением о вызовах в 64-битной Linux параметры функций заносятся в регистры в следующем порядке: RDI, RSI, RDX, RCX, R8, R9. Однако поскольку регистр ECX имеет специальное назначение при системном вызове (сохраняет RIP), его заменили на другой свободный регистр – R10.

Схема работы x64 и x32 ABI (напомню, что x32 – это 64-битный код с 32-битными указателями) одинаковая, разве что для x32 есть несколько дополнительных функций.

Возврат результата системных вызовов

Системные вызовы, как и [почти] все библиотечные функции возвращают результат в регистре EAX (RAX).
Все остальные регистры сохраняются (разумеется, кроме RCX и R11 в x64 и x32).

Реализация механизма системных вызовов

В MASM и fasm имеется директива (макрос) invoke , cinvoke для вызова функций WinAPI, а как насчёт Linux? Для Linux чаще всего используют ассемблеры NASM и GAS.
Для NASM есть проект NASMX, включающий в себя 3 include-файла, в которых описан 1 макрос syscall, номера функций для x86 и x64 (про доп.функции x32 забыли) и константы с кодами ошибок и т.п. Для GAS, как я понимаю, можно использовать стандартные Linux’овские include’ы, а про макросы я ничего не знаю (знаете – напишите мне).

Мои макросы системных вызовов для NASM

Предлагаю вам несколько написанных мной include-файлов для NASM с различными макросами системных вызовов для 32- и 64-битного кода, а также примеры их использования.

Основные файлы:

  • linux_syscall.inc – универсальный include, автоматически определяющий разрядность кода и включающий соответствующие .inc и .mac файлы.
  • linux_syscall_32.inc – include, включающий все необходимые .inc и .mac файлы для 32-битного кода.
  • linux_syscall_64.inc – include, включающий все необходимые .inc и .mac файлы для 64-битного кода.

Для файла linux_syscall.inc наличие linux_syscall_32.inc и linux_syscall_64.inc не требуется, зато для всех этих 3-х include’ов требуется наличие следующих файлов (которые можно подключать и по-отдельности, без указанных выше файлов):

  • linux_sysfunc_32.inc – номера функций системных вызовов для 32-битного кода (x86).
  • linux_syscall_32.mac – макросы системных вызовов для 32-битного кода (x86).
  • linux_sysfunc_64.inc – номера функций системных вызовов для 64-битного кода (x64 и x32).
  • linux_syscall_64.mac – макросы системных вызовов для 64-битного кода (x64 и x32).
  • linux_syscall_fn.mac – именованные макросы для некоторых функций системных вызовов (см. ниже).
  • linux_const.inc – константы с кодами ошибок, хендлами для стандартного (консольного) ввода-вывода и пр.

Как этим пользоваться?

Всё предельно просто

    Подключаем в заголовке нашего исходника один из основных include-файлов: linux_syscall.inc (для кода любой разрядности) или linux_syscall_32.inc, linux_syscall_64.inc (для 32- и 64-битного кода соответственно):

В чём разница между всеми этими макросами?

  • Макрос lsyscall загружает параметры в регистры в прямом порядке ( EBX, ECX, EDX, ESI, EDI, EBP для 32-битного кода или RDI, RSI, RDX, R10, R8, R9 для 64-битного).
  • Макрос lsyscallr загружает параметры в регистры в обратном порядке ( EBP, EDI, ESI, EDX, ECX, EBX для 32-битного кода или R9, R8, R10, RDX, RSI, RDI для 64-битного).
  • Макросы $lsyscall и $lsyscallr работают так же, но перед записью параметров сохраняют в стеке (а после системного вызова восстанавливают) регистры EBX, ESI, EDI, EBP (которые в соответствии с соглашением о вызовах необходимо сохранять при изменении внутри функций), если конечно, эти регистры используются. Эти макросы определены только для 32-битного кода, а также при использовании linux_syscall.inc (для 64-битного кода это только псевдонимы макросов lsyscall и lsyscallr , поскольку в 64-х битах сохраняемыми регистрами являются RBX, RBP, R12R15 , которые не используются в качестве параметров системных вызовов – не путайте соглашения для Windows и Linux).
    Важно: используйте последовательности идущих друг за другом макросов $lsyscall и $lsyscallr с осторожностью, понимая работу этого механизма, поскольку пропуск параметров или использование регистров в качестве параметров может повлечь за собой передачу неверных значений, восстановленных предыдущей функцией.
  • Макросы lsys_con_read и lsys_con_read_r, lsys_con_write и lsys_con_write_r, а также lsys_err_write и lsys_err_write_r отличаются порядком загрузки параметров в регистры аналогично макросам lsyscall и lsyscallr .
  • Макросы lsys_err_write и lsys_err_write_r отличаются от lsys_con_write и lsys_con_write_r тем, что осуществляют вывод на устройство вывода ошибки, которым почти всегда является экран. В отличие от обычного устройства вывода, вывод ошибки не перенаправляется в файл с помощью символа «>» в командной строке.
  • Значение регистра EAX/RAX (номер функции) загружается всегда последним, вне зависимости от наличия суффикса ‘r‘ в имени макроса!

Если у вас пока нет желания более глубоко разбираться в моих макросах, либо вы хотите сперва опробовать описанное выше, можете пропустить нижеследующие списки

Дополнительные макросы 32-битного режима (linux_syscall_32.mac):

  • Макрос lsyscall_use_int80 – использовать int 0x80 для системных вызовов во всех нижеследующих макросах [$]lsyscall[r] (действует по умолчанию).
  • Макрос lsyscall_use_sysenter – использовать sysenter для системных вызовов во всех нижеследующих макросах [$]lsyscall[r].
  • Макросы lsyscalli, lsyscallir, $lsyscalli, $lsyscallir – аналогичны макросам [$]lsyscall[r], но используют именно int 0x80 вне зависимости от выбранного режима (lsyscall_use_XXX).
  • Макросы lsyscallse, lsyscallser, $lsyscallse, $lsyscallser – аналогичны макросам [$]lsyscall[r], но используют именно sysenter вне зависимости от выбранного режима (lsyscall_use_XXX).

Дополнительные макросы именованных вызовов (linux_syscall_fn.mac):

  • Макрос lsyscall_fn_noregsaving – использовать макросы lsyscall и lsyscallr (не сохраняющие регистры) для всех нижеследующих системных вызовов в именованных макросах вроде lsys_read_con , lsys_write_con и пр. (действует по умолчанию).
  • Макрос lsyscall_fn_regsaving – использовать макросы $lsyscall и $lsyscallr (сохраняющие регистры) для всех нижеследующих системных вызовов в именованных макросах.

p.s. Макрос lsys_exit всегда использует lsyscall (т.е. режим lsyscall_fn_noregsaving ), т.к. сохранение регистров при завершении программы бессмысленно.

Макросы для внутреннего применения, которые тем не менее можно использовать и в программах (любой разрядности):

  • Макрос movx reg,value – оптимизированный вариант инструкции mov : использует xor reg,reg при записи нулевого значения в регистр и or reg,-1 при записи значения -1. NASM автоматически заменяет 64-битный регистр 32-битным при записи в первый числового значения, не превышающего 32-х бит, поэтому здесь подобная оптимизация не требуется. Обнуление 64-битного регистра через xor NASM не оптимизирует, но макрос делает это сам (например, movx rax,0 преобразуется в xor eax,eax ). Если указаны 2 одинаковых регистра, макрос не генерирует инструкций (за исключением 32-битных регистров в 64-битном режиме, т.к. приём вида mov eax,eax используется для очистки старших 32-х бит 64-битного регистра вместо недопустимого movzx rax,eax ).
    Важно: макрос не предназначен для записи в память, т.к. вызов вида movx [eax],0 сгенерирует недопустимый код xor [eax],[eax] , а mov [eax],-1 более медленный or [eax],-1 .
  • Макрос find_in_list exp, list выполняет поиск выражения exp в списке list (например, find_in_list REG, eax,ebx,ecx,edx ). Устанавливает в качестве результата значение ?found_in_list = -1, если выражение найдено, ?found_in_list = 0 в противном случае.

При подключении файла linux_syscall.inc становятся доступны следующие идентификаторы (которые позволяют создавать программы разной разрядности без изменения кода):

  • ?ax, ?bx, ?cx, ?dx, ?si, ?di, ?bp, ?sp – псевдонимы регистров EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP или RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP в зависимости от разрядности приложения.
  • ?rfn – регистр, используемый для записи номера функции (EAX или RAX, то же, что и ?ax, по сути), ?rp1..?rp6 – регистры, используемые для параметров №1..6 в зависимости от разрядности кода.
  • ?size = 4 для 32-битного кода, ?size = 8 для 64-битного кода.
  • ?dd – псевдоним dd или dq, ?resd – псевдоним resd или resq в зависимости от разрядности кода.

Пара слов про чувствительность к регистру букв.

  • Имена всех макросов (включая именованные макросы и макросы для внутреннего использования) НЕчувствительны к регистру.
  • Псевдонимы регистров (?ax, ?rfn, ?rp1 и пр), а также ?size, ?dd, ?resdНЕчувствительны к регистру.
  • Номера функций ( sys_exit, sys_read, sys_write . ) и константы из файла linux_const.inc ( STDIN_FILENO, STDOUT_FILENO . ) чувствительны к регистру.

Вот, собственно, и всё.
Все исходники прикреплены к данной статье! (см. ниже файл Linux_syscall.NASM.zip)
Примеры использования находятся в папке examples (все они выполняют одно и то же, но разными способами, при этом генерируется код x86 , x64 и x32 ). Там же расположены cmd/sh-файлы для компиляции и готовые программы

p.s. Есть планы по доработке и созданию новых макросов, по пока обещать ничего не буду.

Где найти описание системных функций, их номера и параметры?

Приведу несколько ссылок:

  • Документация по системным вызовам Linux (на русском, неполный набор функций, без номеров)
  • Linux Syscall Reference (для x86, с номерами)
  • LINUX SYSTEM CALL TABLE FOR X86 64 (для x64, с номерами, но без описания)
  • LINUX System Call Quick Reference.pdf (неполная таблица номеров системных вызовов x86)

Буду рад, если пришлёте мне ссылки на хорошие справочники по системным вызовам!

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

  • Установка yii2 mac os
  • Установка xampp на mac os
  • Установка wordpress на mac os
  • Установка wine в mac os
  • Установка windows программ на mac os