0x80 что это c

Что означает «int 0x80» в коде сборки?

может кто-нибудь объяснить, что делает следующий код сборки?

8 ответов

он передает управление для прерывания вектора 0x80

имейте в виду, что 0x80 = 80h = 128

резюмировать из PDF:

INT n / INTO / INT 3-вызов для прерывания Процедура

инструкция INT n генерирует вызов прерывания или исключения обработчик, указанный с операндом назначения. Назначение операнд задает вектор от 0 до 255, закодированный как 8-разрядный без знака промежуточное значение. Инструкция INT n является общей мнемонической для выполнение программного вызова обработчика прерываний.

одним из самых полезных программных прерываний DOS было прерывание 0x21. Вызывая его с различными параметрами в регистрах (в основном ah и al), вы можете получить доступ к различным операциям ввода-вывода, строкового вывода и многое другое.

большинство систем и производных Unix не используют программные прерывания, за исключением прерываний 0x80, используется для системных вызовов. Это достигается путем ввода 32-разрядное значение, соответствующее функции ядра в регистр EAX процессора и затем выполнение INT 0x80.

взгляните на это, пожалуйста, где показаны другие доступные значения в таблицах обработчика прерываний:

0x80 что это c. Смотреть фото 0x80 что это c. Смотреть картинку 0x80 что это c. Картинка про 0x80 что это c. Фото 0x80 что это c

Как вы можете видеть, таблица указывает CPU для выполнения системного вызова. Вы можете найти системы Linux настольный вызов здесь.

таким образом, перемещая значение 0x1 в регистр EAX и вызывая INT 0x80 в вашей программе, вы можете заставить процесс выполнить код в ядре, который остановит (выйдет) текущий запущенный процесс (на Linux, x86 Intel CPU).

аппаратное прерывание не следует путать с программным прерыванием. здесь очень хороший ответ на этот счет.

этой также является хорошим источником.

вы можете увидеть int 80h в действиях здесь.

int 0x80-это язык сборки инструкция, используемая для вызова системные вызовы в Linux на x86 (т. е., Процессоры Intel-совместимого).

минимальный запускаемый пример системного вызова Linux

Linux устанавливает обработчик прерываний для 0x80 такой, что он реализует системные вызовы, способ для программ userland общаться с ядром.

скомпилировать и запустить с:

результат: программа печатает в stdout:

и выходит аккуратно.

вы не можете установить свои собственные обработчики прерываний непосредственно из пользовательского потому что у вас есть только ring 3 и Linux мешает вам сделать это.

лучшие альтернативы

минимальный 16-битный пример

сначала узнайте, как создать минимальную ОС загрузчика и запустить его на QEMU и реальном оборудовании, как я объяснил здесь:https://stackoverflow.com/a/32483545/895245

теперь вы можете работать в 16-битном режиме реального:

это будет сделано по порядку:

пример минимального защищенного режима

современные операционные системы работают в так называемые защищенные режим.

обработка имеет больше вариантов в этом режиме, так что это сложнее, но дух тот же.

ключевым шагом является использование инструкций LGDT и LIDT, которые указывают адрес структуры данных в памяти (таблица дескрипторов прерываний), которая описывает обработчики.

инструкция «int»вызывает прерывание.

что такое прерывание?

Простой Ответ: прерывание, проще говоря, это событие, которое прерывает процессор и говорит ему выполнить определенную задачу.

Подробный Ответ!—15—>:

CPU имеет таблицу процедур обслуживания прерываний (или ISRs), хранящихся в памяти. В реальном (16-битном) режиме это сохраняется как IVT или яnterrupt VЭктор Tсостоянии. IVT обычно находится по адресу 0x0000:0x0000 (адрес 0x00000 ), и это серия адресов смещения сегмента, которые указывают на ISRs. ОС может заменить ранее существовавшие записи IVT своими собственными ISR.

(Примечание: размер IVT фиксирован на 1024 (0x400) байтах.)

в защищенном (32-разрядном) режиме процессор использует IDT. IDT-это структура переменной длины, которая состоит из дескрипторов (иначе известные как gates), которые сообщают CPU о обработчиках прерываний. Структура этих дескрипторов намного сложнее, чем простые записи смещения сегмента IVT; вот она:

*IDT может иметь переменный размер, но он должен быть последовательным, т. е. если вы объявляете свой IDT от 0x00 до 0x50, вы должны иметь каждое прерывание от 0x00 до 0x50. ОС не обязательно использует все из них, поэтому настоящий бит позволяет CPU правильно обрабатывать прерывания, которые ОС не использует намерены справиться.

когда происходит прерывание (либо внешним триггером (например, аппаратным устройством) в IRQ, либо int инструкция из программы), процессор толкает EFLAGS, затем CS, а затем EIP. (Они автоматически восстанавливаются iret прерывание инструкции return.) ОС обычно хранит больше информации о состоянии машины, обрабатывает прерывание, восстанавливает состояние машины и продолжает работу.

во многих * NIX ОС (в том числе Linux), системные вызовы основаны на прерываниях. Программа помещает аргументы системного вызова в регистры (EAX, EBX, ECX, EDX и др..), и вызовы прерывают 0x80. Ядро уже установило IDT, чтобы содержать обработчик прерываний на 0x80, который вызывается, когда он получает прерывание 0x80. Затем ядро считывает Аргументы и соответственно вызывает функцию ядра. Он может хранить возврат в EAX / EBX. Системные вызовы в значительной степени были заменены на sysenter и sysexit (или syscall и sysret на AMD) инструкции, которые позволяют быстрее войти в кольцо 0.

это прерывание может иметь другое значение в другой ОС. Обязательно проверьте его документацию.

Как уже упоминалось, это заставляет управление прыгать, чтобы прервать вектор 0x80. На практике это означает (по крайней мере, в Linux), что вызывается системный вызов; точный системный вызов и аргументы определяются содержимым регистров. Например, exit () можно вызвать, установив %eax в 1, а затем «int 0x80».

он сообщает процессору активировать вектор прерывания 0x80, который в ОС Linux является прерыванием системного вызова, используемым для вызова системных функций, таких как open() для файлов, и так далее.

Источник

Что означает «int 0x80» в ассемблерном коде?

Может кто-нибудь объяснить, что делает следующий код сборки?

Передает управление вектору прерывания 0x80

Имейте в виду, что 0x80 = 80h = 128

Подведем итоги из PDF:

Одним из наиболее полезных программных прерываний DOS было прерывание 0x21. Вызывая его с разными параметрами в регистрах (в основном ah и al), вы можете получить доступ к различным операциям ввода-вывода, строковому выводу и многому другому.

Большинство систем Unix и их производных не используют программные прерывания, за исключением прерывания 0x80, используемого для выполнения системных вызовов. Это достигается путем ввода 32-битного значения, соответствующего функции ядра, в регистр EAX процессора и последующего выполнения INT 0x80.

Взгляните на это, пожалуйста, где показаны другие доступные значения в таблицах обработчиков прерываний:

0x80 что это c. Смотреть фото 0x80 что это c. Смотреть картинку 0x80 что это c. Картинка про 0x80 что это c. Фото 0x80 что это c

Таким образом, переместив значение 0x1 в регистр EAX и вызвав INT 0x80 в своей программе, вы можете заставить процесс выполнять код в ядре, который остановит (завершит) текущий запущенный процесс (в Linux, x86 Intel CPU).

Не следует путать аппаратное прерывание с программным прерыванием. Вот очень хороший ответ на этот счет.

Это тоже хороший источник.

Пример минимального запускаемого системного вызова Linux

Linux настраивает обработчик прерывания 0x80 таким образом, чтобы он реализовывал системные вызовы, способ взаимодействия программ пользовательского уровня с ядром.

Скомпилируйте и запустите с:

Результат: программа выводит на стандартный вывод:

Лучшие альтернативы

Минимальный 16-битный пример

Сначала узнайте, как создать ОС с минимальным загрузчиком и запустить ее на QEMU и реальном оборудовании, как я объяснил здесь: https://stackoverflow.com/a/32483545/895245

Теперь вы можете работать в 16-битном реальном режиме:

Это будет по порядку:

Пример минимально защищенного режима

Современные операционные системы работают в так называемом защищенном режиме.

Управление в этом режиме имеет больше возможностей, поэтому он более сложный, но дух тот же.

Ключевым шагом является использование инструкций LGDT и LIDT, которые указывают адрес структуры данных в памяти (таблица дескрипторов прерываний), которая описывает обработчики.

Инструкция «int» вызывает прерывание.

Что за прерывание?

Подробный ответ :

(Примечание: размер IVT фиксирован и составляет 1024 (0x400) байтов.)

* IDT может иметь переменный размер, но он должен быть последовательным, т.е. если вы объявляете IDT от 0x00 до 0x50, вы должны иметь каждое прерывание от 0x00 до 0x50. ОС не обязательно использует их все, поэтому бит «Присутствует» позволяет процессору правильно обрабатывать прерывания, которые ОС не намерена обрабатывать.

Когда происходит прерывание (либо внешним триггером (например, аппаратным устройством) в IRQ, либо int инструкцией из программы), ЦП нажимает EFLAGS, затем CS, а затем EIP. (Они автоматически восстанавливаются командой iret возврата из прерывания.) ОС обычно хранит дополнительную информацию о состоянии машины, обрабатывает прерывание, восстанавливает состояние машины и продолжает работу.

Во многих ОС * NIX (включая Linux) системные вызовы основаны на прерываниях. Программа помещает аргументы системного вызова в регистры (EAX, EBX, ECX, EDX и т. Д.) И вызывает прерывание 0x80. Ядро уже установило IDT, чтобы содержать обработчик прерывания на 0x80, который вызывается, когда он получает прерывание 0x80. Затем ядро ​​считывает аргументы и соответственно вызывает функцию ядра. Он может хранить возврат в EAX / EBX. Системные вызовы в значительной степени заменены инструкциями sysenter and sysexit (или syscall and sysret на AMD), которые позволяют быстрее войти в кольцо 0.

Это прерывание могло иметь другое значение в другой ОС. Обязательно проверьте его документацию.

Источник

Эволюция системных вызовов архитектуры x86

Про системные вызовы уже много было сказано, например здесь или здесь. Наверняка вам уже известно, что системный вызов — это способ вызова функции ядра ОС. Мне же захотелось копнуть глубже и узнать, что особенного в этом системном вызове, какие существуют реализации и какова их производительность на примере архитектуры x86-64. Если вам также интересны ответы на данные вопросы, добро пожаловать под кат.

System call

Каждый раз, когда мы хотим что-то отобразить на мониторе, записать в устройство, считать с файла, нам приходится обращаться к ядру ОС. Именно ядро ОС отвечает за любое общение с железом, именно там происходит работа с прерываниями, режимами процессора, переключениями задач… Чтобы пользователь программой не смог завалить работу всей операционной системы, было решено разделить пространство памяти на пространство пользователя (область памяти, предназначенная для выполнения пользовательских программ) и пространство ядра, а также запретить пользователю доступ к памяти ядра ОС. Реализовано это разделение в x86-семействе аппаратно при помощи сегментной защиты памяти. Но пользовательской программе нужно каким-то образом общаться с ядром, для этого и была придумана концепция системных вызовов.

Системный вызов — способ обращения программы пользовательского пространства к пространству ядра. Со стороны это может выглядеть как вызов обычной функции со своим собственным calling convention, но на самом деле процессором выполняется чуть больше действий, чем при вызове функции инструкцией call. Например, в архитектуре x86 во время системного вызова как минимум происходит увеличение уровня привилегий, замена пользовательских сегментов на сегменты ядра и установка регистра IP на обработчик системного вызова.

Программист обычно не работает с системными вызовами напрямую, так как системные вызовы обернуты в функции и скрыты в различных библиотеках, например libc.so в Linux или же ntdll.dll в Windows, с которыми и взаимодействует прикладной разработчик.

Теоретически, реализовать системный вызов можно при помощи любого исключения, хоть при помощи деления на 0. Главное — это передача управления ядру. Рассмотрим реальные примеры реализаций исключений.

Способы реализации системных вызовов

Выполнение неверной инструкции.

Ранее, ещё на 80386 это был самый быстрый способ сделать системный вызов. Для этого обычно применялась бессмысленная и неверная инструкция LOCK NOP, после исполнения которой процессором вызывался обработчик неверной инструкции. Это было больше 20 лет назад и, говорят, этим приёмом обрабатывались системные вызовы в корпорации Microsoft. Обработчик неверной инструкции в наши дни используется по назначению.

Call gates

Для того, чтобы иметь доступ к сегментам кода с различным уровнем привилегий, в Intel был разработан специальный набор дескрипторов, называемый gate descriptors. Существует 4 вида таких дескрипторов:

Нам интересны только call gates, так как именно через них планировалось реализовывать системные вызовы в x86.

Call gate реализован при помощи инструкции call far или jmp far и принимает в качестве параметра call gate-дескриптор, который настраивается ядром ОС. Является достаточно гибким механизмом, так как возможен переход и на любой уровень защитного кольца, и на 16-битный код. Считается, что call gates производительней прерываний. Этот способ использовался в OS/2 и Windows 95. Из-за неудобства использования в Linux механизм так и не был реализован. Со временем совсем перестал использоваться, так как появились более производительные и простые в обращении реализации системных вызовов (sysenter/sysexit).

Системные вызовы, реализованные в Linux

В архитектуре x86-64 ОС Linux существует несколько различных способов системных вызовов:

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

Рассмотрим немного подробнее каждый системный вызов.

int 80h

Изначально, в архитектуре x86, Linux использовал программное прерывание 128 для совершения системного вызова. Для указания номера системного вызова, пользователь задаёт в eax номер системного вызова, а его параметры располагает по порядку в регистрах ebx, ecx, edx, esi, edi, ebp. Далее вызывается инструкция int 80h, которая программно вызывает прерывание. Процессором вызывается обработчик прерывания, установленный ядром Linux ещё во время инициализации ядра. В x86-64 вызов прерывания используется только во время эмуляции режима x32 для обратной совместимости.

В принципе, никто не запрещает пользоваться инструкцией в расширенном режиме. Но вы должны понимать, что используется 32-битная таблица вызовов и все используемые адреса должны помещаться в 32-битное адресное пространство. Согласно SYSTEM V ABI [4] §3.5.1, для программ, виртуальный адрес которых известен на этапе линковки и помещается в 2гб, по умолчанию используется малая модель памяти и все известные символы находятся в 32-битном адресном пространстве. Под это определение подходят статически скомпилированные программы, где и возможно использовать int 80h. Пошаговая работа прерывания подробно описана на stackoverflow.

В ядре обработчиком этого прерывания является функция entry_INT80_compat и находится в arch/x86/entry/entry_64_compat.S

Или в расширенном режиме (программа работает так как компилируется статически)

sysenter/sysexit

Спустя некоторое время, ещё когда не было x86-64, в Intel поняли, что можно ускорить системные вызовы, если создать специальную инструкцию системного вызова, тем самым минуя некоторые издержки прерывания. Так появилась пара инструкций sysenter/sysexit. Ускорение достигается за счёт того, что на аппаратном уровне при выполнении инструкции sysenter опускается множество проверок на валидность дескрипторов, а так же проверок, зависящих от уровня привилегий [3] §6.1. Также инструкция опирается на то, что вызывающая её программа использует плоскую модель памяти. В архитектуре Intel, инструкция валидна как для режима совместимости, так и для расширенного режима, но у AMD данная инструкция в расширенном режиме приводит к исключению неизвестного опкода [3]. Поэтому в настоящее время пара sysenter/sysexit используется только в режиме совместимости.

В ядре обработчиком этой инструкции является функция entry_SYSENTER_compat и находится в arch/x86/entry/entry_64_compat.S

Несмотря на то, что в реализации архитектуры от Intel инструкция валидна, в расширенном режиме скорее всего такой системный вызов никак не получится использовать. Это из-за того, что в регистре ebp сохраняется текущее значение стека, а адрес верхушки независимо от модели памяти находится вне 32-битного адресного пространства. Это всё потому, что Linux отображает стек на конец нижней половины каноничного адреса пространства.

Разработчики ядра Linux предостерегают пользователей от жесткого программирования sysenter из-за того, что ABI системного вызова может измениться. Из-за того, что Android не последовал этому совету, Linux пришлось откатить свой патч для сохранения обратной совместимости. Правильно реализовывать системный вызов нужно используя vDSO, речь о которой будет идти далее.

syscall/sysret

Так как именно AMD разработали x86-64 архитектуру, которая и называется AMD64, то они решили создать свой собственный системный вызов. Инструкция разрабатывалась AMD, как аналог sysenter/sysexit для архитектуры IA-32. В AMD позаботились о том, чтобы инструкция была реализована как в расширенном режиме, так и в режиме совместимости, но в Intel решили не поддерживать данную инструкцию в режиме совместимости. Несмотря на всё это, Linux имеет 2 обработчика для каждого из режимов: для x32 и x64. Обработчиками этой инструкции является функции entry_SYSCALL_64 для x64 и entry_SYSCALL_compat для x32 и находится в arch/x86/entry/entry_64.S и arch/x86/entry/entry_64_compat.S соответственно.

Кому интересно более подробно ознакомиться с инструкциями системных вызовов, в мануале Intel [0] (§4.3) приведён их псевдокод.

Для тестирования следующего примера потребуется ядро с конфигурацией CONFIG_IA32_EMULATION=y и компьютер AMD. Если же у вас компьютер фирмы Intel, то можно запустить пример на виртуалке. Linux может без предупреждения изменить ABI и этого системного вызова, поэтому в очередной раз напомню: системные вызовы в режиме совместимости правильнее исполнять через vDSO.

Непонятна причина, по которой AMD решили разработать свою инструкцию вместо того, чтобы расширить инструкцию Intel sysenter на архитектуру x86-64.

vsyscall

При переходе из пространства пользователя в пространство ядра происходит переключение контекста, что является не самой дешёвой операцией. Поэтому, для улучшения производительности системных вызовов, было решено их обрабатывать в пространстве пользователя. Для этого было зарезервировано 8 мб памяти для отображения пространства ядра в пространство пользователя. В эту память для архитектуры x86 поместили 3 реализации часто используемых read-only вызова: gettimeofday, time, getcpu.

Со временем стало понятно, что vsyscall имеет существенные недостатки. Фиксированное размещение в адресном пространстве является уязвимым местом с точки зрения безопасности, а отсутствие гибкости в размере выделяемой памяти может негативно сказаться на расширении отображаемой области ядра.

Для того, чтобы пример работал, необходимо, чтобы в ядре была включена поддержка vsyscall: CONFIG_X86_VSYSCALL_EMULATION=y

Linux не отображает vsyscall в режиме совместимости.

На данный момент, для сохранения обратной совместимости, ядро Linux предоставляет эмуляцию vsyscall. Эмуляция сделана для того, чтобы залатать дыры безопасности в ущерб производительности.

Эмуляция может быть реализована двумя способами.

Первый способ — при помощи замены адреса функции на системный вызов syscall. В таком случае виртуальный системный вызов функции gettimeofday на x86-64 выглядит следующим образом:

Где 0x60 — код системного вызова функции gettimeofday.

Второй же способ немного интереснее. При вызове функции vsyscall генерируется исключение Page fault, которое обрабатывается Linux. ОС видит, что ошибка произошла из-за исполнения инструкции по адресу vsyscall и передаёт управление обработчику виртуальных системных вызовов emulate_vsyscall (arch/x86/entry/vsyscall/vsyscall_64.c).

vDSO (Virtual Dynamic Shared Object)

Чтобы исправить основной недостаток vsyscall, было предложено реализовать системные вызовы в виде отображения динамически подключаемой библиотеки, к которой применяется технология ASLR. В «длинном» режиме библиотека называется linux-vdso.so.1, а в режиме совместимости — linux-gate.so.1. Библиотека автоматически подгружается для каждого процесса, даже статически скомпилированного. Увидеть зависимости приложения от неё можно при помощи утилиты ldd в случае динамической компоновки библиотеки libc.

Также vDSO используется в качестве выбора наиболее производительного способа системного вызова, например в режиме совместимости.

Список разделяемых функций можно посмотреть в руководстве.

Для режима совместимости:

Правильнее всего искать функции vDSO при помощи извлечения адреса библиотеки из вспомогательного вектора AT_SYSINFO_EHDR и последующего парсинга разделяемого объекта. Пример парсинга vDSO из вспомогательного вектора можно найти в исходном коде ядра: tools/testing/selftests/vDSO/parse_vdso.c

Или если интересно, то можно покопаться и посмотреть, как парсится vDSO в glibc:

Согласно System V ABI AMD64 [4] вызовы должны происходить при помощи инструкции syscall. На практике же к этой инструкции добавляются вызовы через vDSO. Поддержка системных вызовов в виде int 80h и vsyscall остались для обратной совместимости.

Сравнение производительности системных вызовов

С тестированием скорости системных вызовов всё неоднозначно. В архитектуре x86 на выполнение одной инструкции влияет множество факторов таких как наличие инструкции в кэше, загруженность конвейера, даже существует таблица задержек для данной архитектуры [2]. Поэтому достаточно сложно определить скорость выполнения участка кода. У Intel есть даже специальный гайд по замеру времени для участка кода [1]. Но проблема в том, что мы не можем замерить время согласно документу из-за того, что нам нужно вызывать объекты ядра из пользовательского пространства.

Поэтому было решено замерить время при помощи clock_gettime и тестировать производительность вызова gettimeofday, так как он есть во всех реализациях системных вызовов. На разных процессорах время может отличаться, но в целом, относительные результаты должны быть схожи.

Программа запускалась несколько раз и в итоге бралось минимальное время исполнения.
Тестирование int 80h, sysenter и vDSO-32 производилось в режиме совместимости.

Таблица Результатов

Реализациявремя (нс)
int 80h498
sysenter338
syscall278
vsyscall emulate692
vsyscall native278
vDSO37
vDSO-3251

Как можно увидеть, каждая новая реализация системного вызова является производительней предыдущей, не считая vsysvall, так как это эмуляция. Как вы наверное уже догадались, если бы vsyscall был таким, каким его задумывали, время вызова было бы аналогично vDSO.

Все текущие сравнения производительности были произведены с патчем KPTI, исправляющим уязвимость meltdown.

Бонус: Производительность системных вызовов без KPTI

Патч KPTI был разработан специально для исправления уязвимости meltdown. Как известно, данный патч замедляет производительность ОС. Проверим производительность с выключенным KPTI (pti=off).

Таблица результатов с выключенным патчем

Переход в режим ядра и обратно в среднем после патча стал занимать примерно на 180 нс. больше времени, видимо это и есть цена сброса TLB-кэша.

Производительность системного вызова через vDSO не ухудшилась по причине того, то в данном типе вызова нет перехода в режим ядра, и, следовательно, нет причин сбрасывать TLB-кэш.

Источник

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

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