Комментарии 48
Кстати да, а может ли вообще wine обработать код с прямым syscall или есть ли какой-то способ (исключая наверное установку гипервизора)? Я бы не сказал, что такие программы встречаются в обычных условиях, но к примеру некоторые обфускаторы (как vmprotect, но он так не делает) могут заменять вызовы условного OpenProcess на NtOpenProcess через syscall (иначе говоря воспроизводя точный код из ntdll), это позволяет обойти хуки на winapi вызовы с целью анализа.
Если есть поддержка в ядре - не вижу никаких препятствий. WSL-1 работает именно так. В дяре windows реализовали поддержку системных вызовов Linux. И int/syscall выполняются как линуксовые.
А она есть?
Я правда сам не ковырял но чет подозреваю что это документировано мягко говоря не очень. Т.к. эти все штуки не подразумеваются для использования Win32 разработчиками)
Да, можно. Работает вполне нормально. Ведь, процессор выполнит инструкции будь они syscall или int 80h.
Я это использую в своих проектов.
Конечно надо проверять работает программа в Windows или в Linux/Wine а то в Windows просто упадёт.
И не может, и не имеет смысла. Я вижу основную проблему в том, что в настоящей Windows номера системных вызовов не фиксированы, а раздаются в каждой версии по-своему, и версия ntdll.dll должна быть точно согласована с версией ядра, иначе ничего работать не будет. Таблица для справки. Видно, что номера иногда между версиями прыгают непредсказуемо.
Если бы программа хотела работать под одной конкретной версией Windows, она могла бы зафиксировать эти номера, но кому это нужно? Поэтому всё кроме ну очень хакерского делают все вызовы через ntdll.dll через имена, где API уже предсказуемо.
UPD: А ещё не обязательно один вызов ntdll.dll отображается в один сисколл. Может быть несколько вызовов, может быть локальная обработка… всё в их руках. Тут и есть граница стабильного userland API.
> могут заменять вызовы условного OpenProcess на NtOpenProcess через syscall
Чтобы работать, условно говоря, на винде 1803, но не 1809… такой софт точно не нужен под Wine.
Вполне себе существуют парсеры ntdll, способные вытаскивать номера вызовов SSDT, пример на шарпе, вопрос лишь чисто о возможности обработки подобного.
И да, в принципе это возможно (но wine не делает) с помощью обработки SEH исключений, при таком вызове нужно лишь перебросить исполнение на вайновский ntdll в нужную функцию и оттуда продолжить исполнение.
Тут тогда второй вопрос возникает (надо было, наверно, написать сразу) — ntdll.dll не обязана всё заворачивать в сисколлы ядра. Она вполне может сделать какую-то обработку сама — и иногда это делает. Может транслировать в два сисколла. И так далее. Граница стабильного userland API это именно вход в ntdll.dll. И обрабатывать уже весь код — это тьюринг-полная задача.
Я там обновил с примечанием, пока было ещё две минуты:)
> И да, в принципе это возможно (но wine не делает) с помощью обработки SEH исключений,
Я попрошу подробностей, но это всё равно не покрывает всех случаев.
Пример можно найти здесь, это эмулятор kernel mode для запуска драйверов в целях анализа (очень специфичных, как Вы можете заметить), при этом применяется SEH для обработки вызовов ntoskrnl, чтения/записи памяти. Аналогично, как я предполагаю, можно обработать ситуации и в wine, но я с Вами согласен - задача слишком широкая для таких узких случаев. Благодарю за дискуссию.
Есть возможность с версии ядра 5.11, добавлено специально для wine
https://www.kernel.org/doc/html/v5.15/admin-guide/syscall-user-dispatch.html
Если кратко, на поток включается prctl(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_ON...), ядро начинает слать signal, когда происходит системный вызов в этом потоке, вместо вызова реализации системного вызова. В обработчике сигнала можно считать/обновить состояние регистров, как и при практически любом другом сигнале, и таком образом имитировать поведение другого ядра. Из минусов - избыточное количество переключений контекстов: если нативно выходит такая схема user -> kernel -> user, то с syscall user dispatch получается user -> kernel -> user -> kernel -> user. Из плюсов - машинный код не требует изменений, любой, даже самый хитрый, код будет работать.
Альтернативой будет (это можно сделать и на старых версиях ядра), патч блоков машинного кода, в которых есть syscall/int инструкции, что не всегда просто/возможно реализовать, но работать будет скорей всего быстрее чем в оригинале, т.к. переключений контекстов не будет вообще.
может ли вообще wine обработать код с прямым syscall
Ради интереса проверил этот момент. Парсер сисколов не смог ничего спарсить. Если на винде внутри заглушки (в плане, Nt/Zw функции в юзермоде) будет код вида
mov eax/rax,N
Где N собственно номер сискола, то под вайном там ничего такого нет. Негде взять номер сискола, ведь их здесь не существует, а значит задача не выполнима (номера сисколов нельзя же ввести от балды, они зависимы от конкретной версии винды , и даже сервис-пака).
Вот бы еще кто-то написал бы такую же понятную статью, чем все-таки syscall отличается от обычного call. И там и там стек (надеюсь). И там и там регистры. И там и там можно вернуть результат. В чем разница? Пока везде из объяснений вижу (тут читать с придыханием) "ну это же syscall!"
Если очень-очень кратко и упрощённо - различные уровни исполнения, которые отслеживаются аппаратно. Изначально пользовательский уровень не имел доступа к памяти уровня супервизора (ядро). Потом и в обратную сторону добавили подобное ограничение. Страницы памяти супервизора помечены специальным битом. В режиме супервизора (ядра) нам доступно больше инструкций, главным образом системных, управляющих трансляцией страниц памяти,состоянием процессора(-ов), управление кешами т.д.. Итого - супервизор изолирован от пользовательского кода, и имеет над ним полный контроль. Учитывает выделенные ресурсы, и способен уничтожить проблемную пользовательскую задачу, корректно освободить ресурсы, оставляя всю систему в рабочем и стабильном состоянии.
Также ядро отвечает за обработку прерываний. Как аппаратных, обеспечивая работу устройств. Так и программных. Этот ваш syscall - в прошлом просто программное прерывание (2е или 80, для win/lin x86) которое обрабатывается ядром как системный вызов. Потом сделали специальную инструкцию, но суть осталась прежней.
syscall - это инструкция процессора, вызывающая переход исполнения кода в режим ядра, то есть связующая часть между UserMode и KernelMode, у нее несколько иное поведение, нежели у обычной инструкции call
То, что на x86-64 (x64 в терминах Windows) сейчас для этого используется syscall, это уже частности.
В x86-32 на Windows использовалось несколько разных прерываний и у каждого задавался номер функции.
Я извиняюсь за оффтоп, а RSDN работает? Третий день выдаёт мне 404, а на почту приходят ответы, как-то, значит, на него заходят. Через Home?
Вообще-то, syscall - это новая инструкция. До её появления системные вызовы выполнялись с помощью инструкции int с параметром 0x80 (это число для Линукса, для винды какое-то другое, должно быть). Это означало, что процессор инициирует прерывание с номером 0х80, а далее передаёт управление коду операционной системы, который обрабатывает это прерывание, вызывая нужный системный вызов.
У этой инструкции int очень хитрое описание того, что она делает, но если вкратце - сохраняет часть информации, типо сегментных регистров и меняет уровень привелегий.
syscall и sysret появились позже и делают нечто похожее, при условии что ОС настроила их работы.
А есть где-то почитать историю того, что именно настраивается, что значит настроить аппаратную инструкцию (новые же транзисторы на чипе не вырастишь), и зачем это было нужно, и как к этому пришли?
Видимо, использование int 0x80 для системных вызовов было неоптимально - инструкция делает что-то весьма хитрое и делает работу, которая, возможно, и не нужна. В итоге AMD и Intel решили в 64-ёх битной архитектуре сделать специальные инструкции для этого.
Однако, операционная система может не поддерживать вызов системных вызовов с помощью инструкции syscall. Поэтому процессор позволит выполнить её только если операционная система настроит её, а именно:
" Процессор x86 даст выполнить инструкцию syscall
только если установлен бит IA32_EFER.SCE
в регистре IA32_EFER
. IA32_EFER
- это model specific register (MSR) с номером 0xC0000080
. Бит IA32_EFER.SCE
- нулевой бит этого регистра. Чтение и запись таких регистров делается специальными инструкциями rdmsr
/wrmsr
" и "Далее, помимо того чтобы разрешить вызывать syscall
, нужно указать точку входа в ядре ОС. Точка входа - это адрес инструкции, на которую будет совершён переход при выполнении syscall
. Адрес должен быть записан в MSR IA32_LSTAR
(номер 0xC0000082
)."
Взято отсюда: https://gitlab.com/slon/shad-os/-/tree/master/gate64
Очень долго программные прерывания делали ровно тем же механизмом, который использовался для аппаратных прерываний. Аппаратное прерывание должно отрабатываться так, что если оно происходит, то исполняющаяся при этом программа (пользовательская — почти всегда, ядро — обычно) не замечает ничего, кроме задержки во времени. Для него оправданы сложности с полным сохранением контекста. На x86 это делается через шлюз привилегий, при этом процессор сохраняет старые значения всех регистров разом и загружает новые, это долго и нудно.
Но когда решили, что это стало слишком тормозить пользовательскую активность, кто-то вспомнил, что если программа вызывает действие явно и в предназначенный ею момент, то не обязательно сохранять всё и идти дорогим путём. Вот тогда и появились, примерно одновременно, машинные команды sysenter в варианте Intel и syscall — в AMD (в 64 битах победила, соответственно, эта).
У этих двух команд есть два важных отличия. Первое — они позволяют только переходить из минимально привилегированного пользовательского режима (3-е кольцо в терминах x86) в супервизора (0-е кольцо). (Для сравнения: ARM, RISC-V — там номера у них соответственно 0 и 1, перевёрнуто направление и нет так и не заработавших 1 и 2 в x86.) Второе — они, в отличие от int N, явно перезаписывают некоторые регистры вместо использования стека, как в старых прерываниях. За счёт этого, как выше сказано, резко удешевляется переключение, но программа должна быть готова, что в каком-то регистре изменится значение — а она готова, раз выполняет sysenter или syscall.
> что значит настроить аппаратную инструкцию (новые же транзисторы на чипе не вырастишь)
Любой обработчик прерывания надо настроить, задав ему адрес перехода (как минимум; другие параметры не к этой теме). Для старого стиля прерываний это таблица прерываний в памяти. Для обсуждаемого тут это служебные регистры процессора (Intel их зовёт model-specific registers, MSR, что некорректно, но у них уже устоялось). Настройка — запись в такой регистр адреса перехода в режиме ядра. После этого команда SYSCALL сохраняет адрес возврата в RCX, флаги в R11, переключает режим в супервизора (ring 0) во флагах, и перезаписывает RIP из служебного регистра (IA32_LSTAR). (Там ещё пляски с CS и SS касательно длины адреса, пропустим.) SYSRET делает обратное. По сравнению с переключением через шлюз привилегий, как в старом стиле, это удешевление, наверно, раз в сто-двести. Работа уже на уровне кода ядра с сохранением остальных регистров, конечно, смягчает эти сто раз, но всё равно время переключения сокращается ну очень заметно.
Вообще про потроха всего системного лучше всего читать на osdev, в частности https://wiki.osdev.org/System_Calls
Спасибо за перевод. Стало гораздо понятнее и интересно.
"Поэтому приложению нужен способ как бы «прервать своё выполнение» и передать управление ядру (эта операция обычно называется переключением контекста). "
Нет, эта операция не называется "переключением контекста". Эта операция так и называется, переход из пользовательского режима в режим ядра и в общем случае вообще никак не связана с переключением контекста.
Огромное спасибо за статью. Базовое понимание как работает wine я давно приобрёл, но вот никогда не умел его объяснить ньюфагам. Теперь буду просто делиться ссылкой!!
Чёрт возьми, иногда производительность Wine даже выше, чем у Windows!
некоторое время назад забавлялся сравнивая производительность пары игр, приведу примеры:
1) WoW, под wine производительность была заметно лучше чем под windows 10
2) ARK, под wine производительность хуже чем под win (но ark в целом то ещё тормозилово)
3) valheim, нативный под linux уделывает wine под linux и нативный под винду
4) snowrunner, wine версия уделывает нативную под винду
5) DarkVoid, wine версия работает шикарно, нативная под виндой вызывает bsod и на win10 и на win7
все игры (кроме wow поставлены из steam). тесты проводились на одном и том же PC на чистой свежеустановленной системе.
Т.е. в Стиме продолжает продаваться игра, приводящая к BSOD?
не знаю продаётся ли она сейчас. мне она доступна так как была куплена очень давно. я её прошёл пиратской так как на момент покупки она вызывала BSOD системы. тогда я грешил на захламлённую винду, но спустя много лет опыт показал что на чистой системе и совсем другом железе ситуация не меняется.
UPD: проверил, если разлогиниться и пройтись поиском игры нет, видимо она доступна только купившим её ранее.
Оказывается, действительно снята с продажи, но в комментариях не нашёл про BSOD. Это происходит прямо на запуске?
Видимо, проблема из-за SecuROM.
Да, после нажатия на кнопку "играть" в Steam появляется окошко с какими то стимовскими препендами как для каждой игры, посде чего оно закрывается, на долю секунды мигает полноэкранная вьюха игры и сразу BSOD. В причинах не разбирался так как не интересно, единственное что проверил две версии винды, и семёрку и десятку. Так как мои знания о работе ОС мелкософта и софта под них практически равны нулю копаться дальше не было желания.
У меня очень предсказуемо gta V делала BSOD и сейчас red dead redemption 2 примерно после 30-60м игры тоже. Кажется это появилось после обновления на win11.
Абсолютно дико такое встречать в современное время.
Особенно дико это выглядит для меня. Последний раз я комп на винде использовал в 2003м году, тут решил купить чисто игровой, там вообще ничего кроме Стима и battle.net ну и игр из них не установлено. Я и дрова всякие обновлял и температуру пытался мерить, в общем странная фигня, но мой опыт использования PC 20 лет назад такой же как и сейчас с синим экраном ;-) Сейчас даже более консистентно.
Ну просто игры, виснущие у половины игроков, или прямо ворующие данные, там продаются. Почему бы и с BSOD не продавать?
С удовольствием прочитал. Wine стоит аж с 1998 года, в основном для word и excel.
Получается, фактически WSL это такой себе антивайн?
Нет. WSL1 это реализация линуксовых сисколлов в ядре винды. А WSL2 это виртуалка. Wine же работает в юзерспейсе.
WSL1 вроде же тоже виртуалка? Т.к. для того чтобы оно заработало, нужно включить capabilities Hyper-V в винде (и в биосе в некоторых ноутах)
Нет, WSL1 — это нативный запуск линуксовых elf-бинарников, и Hyper-V ему не нужен.
Docker on windows использует wsl и без включенного Hyper-V не работает. Видимо поэтому у меня был такой вывод. Речь не про wsl2
Docker on Windows использует или WSL2 или Hyper-V напрямую: https://docs.docker.com/desktop/install/windows-install/#system-requirements
WSL1 вообще не может быть использован для докера, потому что там не реализовано ядерное API неймспейсов и контейнеров.
За такой перевод нужно по пальцам бить
Чёрт возьми, иногда производительность Wine даже выше, чем у Windows!
А можно привести конкретные примеры? Понимаю, что эта статья - перевод, но может быть, кто-то ещё знает ответ на этот вопрос?
Как работает Wine