Skip to content

Latest commit

 

History

History
226 lines (185 loc) · 19.5 KB

File metadata and controls

226 lines (185 loc) · 19.5 KB

Языки: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية

← ABI плагинов NeverC

Плагины DynCode

-fdyncode компилирует единицу трансляции в плоский позиционно-независимый образ (.bin), код которого не содержит ни одной релокации и не имеет секции данных. Он нацелен на arm64/x86_64 в macOS, Linux, Android и Windows, на пользовательском или ядерном уровне исполнения. Плагины наблюдают, перехватывают или заменяют типизированные фазы, превращающие C в этот образ, через тот же чистый C-ABI, что и остальные домены: никаких C++-объектов LLVM, типов STL, исключений или указателей хоста, время жизни которых не задано таблицей API.

Интерфейсы

#include "neverc/Plugin/PluginDynCode.h"
Интерфейс Таблица Слоты Назначение
NEVERC_INTERFACE_DYNCODE_{HIGH,LOW} NevercDynCodeAPI 16 Чтение запроса, образа, отчёта и карт секций/символов/релокаций/внешних ссылок
NEVERC_INTERFACE_DYNCODE_REGISTRAR_{HIGH,LOW} NevercDynCodeRegistrarAPI 5 RegisterTarget, RegisterImportProvider, RegisterExtractor, RegisterCharsetEncoder, RegisterBinaryVerifier
NEVERC_INTERFACE_DYNCODE_PHASE_{HIGH,LOW} NevercDynCodePhaseAPI 4 GetPhaseInfo, GetRequest, GetImage, GetReport

Все три имеют статус NEVERC_INTERFACE_STABLE в мажорной версии 1. Внутри обратного вызова фазы точкой входа служит NevercDynCodePhaseAPI — она превращает кадр в дескрипторы, которые потребляет другая таблица:

NevercDynCodeRequestHandle Request;
Phase->GetRequest(Phase->Context, Frame, Frame->Input, &Request);

NevercDynCodeRequestInfo Info = {0};
Info.Header = (NevercABITableHeader){sizeof(Info), NEVERC_DYNCODE_API_MAJOR,
                                     NEVERC_DYNCODE_API_MINOR, 0};
DynCode->GetRequestInfo(DynCode->Context, Task, Request, &Info);

Все четыре семейства карт — карты секций, карты символов, релокации и внешние ссылки — обходятся одной и той же тройкой first/next/info, например GetFirstRelocation, GetNextRelocation, GetRelocationInfo. Так плагин читает решения, принятые извлечением, не разбирая JSON отчёта.

DynCode — это продукт компиляции, а не постобработка после main()

-fdyncode — это обычная Action/Job в графе DAG драйвера. Задание компиляции публикует проверенный ObjectGraph в памяти; задание -dyncode-extract потребляет этот граф и записывает образ пользователя, заданный -o. -###, печать фаз и граф заданий — все показывают задание извлечения, поэтому плагину никогда не приходится восстанавливать переписанный argv, чтобы узнать режим. Замороженный запрос разделяется локально для задачи с генерацией кода внутри процесса; нет ни getCurrentDynCodeOptions(), ни глобального для процесса флага режима, ни обхода через временный объект.

Ровно одна единица трансляции понижается до одного образа. Множественные входы, -c/-S/-E и неподдерживаемые триплеты отклоняются заранее со стабильными диагностиками.

Уровни совместимости

Идентификаторы фаз, идентификаторы артефактов, контейнеры запроса/отчёта/образа и контракты обратных вызовов — это STABLE-ABI первого выпуска. Специфичные для цели виды релокаций и схемы секций/символов формата объекта — это LOCKSTEP: перед их потреблением сравните идентификатор и дайджест схемы цели. NeverC отклоняет несовпадающую схему до вызова поставщика.

Замороженный запрос

В начале задания драйвер нормализует командную строку в неизменяемый DynCodeRequest и замораживает его. Дочерние задачи заимствуют снимок и никогда его не изменяют. Запрос несёт ключ цели и формат объекта, уровень исполнения (пользователь/ядро), политику точки входа (явный символ, список кандидатов по умолчанию, требование входа по нулевому смещению), политику PIC/секций, политику внешних ссылок, набор/профиль запрещённых байтов и флаг переписывания, идентификатор поставщика кодировки, а также максимальную длину, выравнивание и байт-заполнитель.

Типизированный граф фаз

DynCode — это фиксированный граф из 34 фаз. Тридцать обычных переходов имеют статус OBSERVABLE | INTERCEPTABLE | REPLACEABLE; четыре — OBSERVABLE | SEALED_HOST_GATE. Запечатанные ворота — это финальная проверка IR, финальная проверка MIR, проверка образа и фиксация (commit). Плагин может наблюдать любую фазу, обернуть заменяемый переход перехватчиком или заменить его поставщика целиком; он никогда не может заменить, пропустить или обойти запечатанные ворота, и он не может выразить отключённое преобразование как невызванный обратный вызок — отключённое преобразование запускает явного поставщика-no-op, эквивалентный вывод которого верификатор хоста всё равно доказывает.

Фазы по порядку таковы:

  1. заморозка запроса;
  2. преобразования IR — prepare, понижение косвенных переходов, понижение интринсик памяти (до и после кучи), понижение строкового рантайма, арена кучи, три позиции compiler_rt (pre/post/final), понижение импортов syscall/PEB/ядра, две позиции data_to_text (pre/post), встраивающая оптимизация, финализация строк, stackify, all-blr и запечатанная финальная проверка IR;
  3. преобразование MIR prepare и запечатанная финальная проверка MIR;
  4. импорт объекта — привязка проверенного ObjectGraph к задаче;
  5. извлечение — планирование, компоновка, релокация и построение образа-кандидата;
  6. ограниченные бинарные фазы — post-extract, переписывание запрещённых байтов, кодирование набора символов, размер/выравнивание/заполнение и pre-verify;
  7. запечатанная проверка образа;
  8. запечатанная фиксация.

Нормативным источником идентификаторов, политик, уровней стабильности и ворот служит Schema/PhaseSchema.json; исполняемый контракт покрытия — coverage.json.

Встроенные преобразования тоже поставщики

Каждый встроенный проход IR/MIR обёрнут как типизированный поставщик; объект прохода LLVM никогда не выставляется через C-ABI. Замена фазы означает, что встроенный поставщик не выполняется — проходящий тест доказывает поведение или трассу, а не просто успех регистрации. Фазы mem_intrin, compiler_rt и data_to_text встречаются более чем в одной позиции; каждая позиция — это отдельный идентификатор фазы со своим собственным доказательством, поэтому повторный запуск идемпотентен и никогда не полагается на скрытое состояние прохода.

ObjectGraph — единственный вход обычного объекта

Извлечение потребляет ровно один проверенный ObjectGraph, произведённый маршрутом генерации кода цели. dyncode.object.import привязывает этот граф и проверяет ключ цели и происхождение; она никогда не перечитывает байты с диска и не выполняет второй разбор объекта. Пользовательский формат объекта входит в DynCode, как только его можно прочитать в ObjectGraph и он имеет соответствующие поставщики релокаций и цели. Множественные объекты и наборы графов LTO отклоняются на заморозке со стабильным CAPABILITY_UNAVAILABLE.

Внешние ссылки и понижение импортов

Набор разрешённых внешних ссылок запроса означает лишь «поставщик может это обработать»; он никогда не позволяет неразрешённой релокации дожить до плоского образа. Каждая внешняя ссылка должна завершиться одним из способов: устранена в IR/MIR, разрешена в символ внутри образа, преобразована в объявленный и принятый верификатором контракт рантайм-резолвера или жёсткая ошибка. Заглушка syscall, импорт PEB и импорт ядра — это три встроенных ImportProvider; каждый объявляет свой сопоставитель цель/уровень/символ и производимый им контракт ABI. Плагин может добавить ImportProvider, но он должен вернуть происхождение замены, изменение ABI входа, параметры резолвера и остаточные ссылки.

Образ, отчёт и ограниченные правки байтов

Извлечение производит DynCodeImage и DynCodeReport. Образ — это ограниченный построитель байтов плюс смещение/символ входа, выходные карты исходных секций и исходных символов, распоряжения релокаций и записи внешних/рантайм-контрактов. Каждая правка байта проходит через проверяемый API построителя read/write/insert/append/resize; никакого uint8_t ** нет. Правка обновляет поколение образа и аннулирует любое доказательство релокации/PIC/входа, пересекающееся с изменённым диапазоном.

Отчёт — это неизменяемый детерминированный продукт аудита: дайджесты запроса/маршрута/входа/выхода, пофазный журнал поставщиков, выбранные/отклонённые секции и почему, выбор входа, исправленные/отклонённые/рантайм-контрактные релокации, оставшиеся внешние ссылки, размер/выравнивание/заполнение, сканирование запрещённых байтов и контрольный список верификатора. -fdyncode-report=<path> записывает его канонический JSON; подробные диагностики рендерятся из того же отчёта, а не из второго набора счётчиков.

Цепочка переписывания запрещённых байтов выполняется в замороженном топологическом порядке, и каждый шаг возвращает запись об изменении. Кодировщик набора символов выбирается по точному стабильному идентификатору и возвращает заглушку декодера, закодированную полезную нагрузку, обновление входа и доказательство цели; неизвестный или неоднозначный идентификатор — жёсткая ошибка. Отключение переписывания выбирает явный шаг-no-op — финальный аудит всё равно выполняется.

Финальный верификатор и время после финализации

Все записываемые фазы завершаются до запечатанного финального верификатора. Верификатор проверяет, что не осталось необработанных внешних релокаций/ссылок, что отсутствует запрещённая секция данных/TLS/раскрутки/отладки/метаданных, что вход существует, корректно выровнен и (когда требуется) находится по нулевому смещению, что каждый сайт релокации лежит в границах с соответствующим PIC-доказательством для текущих байтов образа, что карты секций/символов не перекрываются, что правила длины/выравнивания/заполнения соблюдены и что финальные байты — включая декодер, заголовок и заполнение — не содержат ни одного запрещённого байта. Любой отказ возвращает структурированную диагностику и отбрасывает весь пакет вывода.

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

Опции драйвера

-fdyncode включает режим. -fdyncode-entry= выбирает символ входа. -fdyncode-bad-bytes= / -fdyncode-bad-byte-profile= задают запрещённые байты, -fdyncode-bad-byte-rewrite (включён по умолчанию) выбирает цепочку переписывания, а -fdyncode-charset= выбирает зарегистрированный кодировщик. -fdyncode-max-length=, -fdyncode-align= и -fdyncode-pad= ограничивают финальный размер. -fdyncode-keep-obj= ответвляет промежуточный перемещаемый объект, а -fdyncode-report= записывает отчёт аудита. -mdyncode-context=user|kernel выбирает уровень исполнения.

Правила параллелизма и отказов

  • Храните изменяемое состояние в предоставленных хостом областях процесса/сессии/задачи; никогда не используйте синглтон текущего плагина или текущих опций.
  • Не кэшируйте дескрипторы задач или заимствованные представления после возврата из обратного вызова.
  • Вызывайте продолжение перехватчика не более одного раза, в потоке обратного вызова.
  • Возвращайте исходный NevercStatus; объявленный REPLACE, который потерпел неудачу, не откатывается молча к встроенному поставщику.
  • Объявляйте самые узкие правдивые модели параллелизма и реентерабельности.

См. PluginDynCode.h для нормативных объявлений, pluginsdk/examples/DynCodeTracePlugin.c для трассировщика фаз только для чтения и pluginsdk/examples/DynCodeEncoderPlugin.c для кодировщика набора символов.