Windows: избегать нажатия полного контекста x86 в стеке
Я реализовал PARLANSE, язык под MS Windows, который использует стеки кактусов для реализации параллельных программ. Колонки стека выделяются по каждой функции
основы и являются только правильным размером для обработки локальных переменных,
выражение temp push/pops и вызовы в библиотеки (в том числе
пространство стека для работы библиотечных процедур). Такой стек
кадры могут быть как 32 байт на практике и часто являются.
Все это отлично работает, если код не делает что-то глупое и
вызывает аппаратную ловушку... в этот момент Windows появляется
настаивайте на том, чтобы нажимать весь контекст компьютера x86 "на стек".
Это примерно 500 байтов, если вы включили FP/MMX/etc. регистры,
который он делает. Естественно, 500 байтов нажимают на 32-байтовый стек
разбивает то, что не должно. (Аппарат подталкивает несколько слов
на ловушке, но не на весь контекст).
[РЕДАКТИРОВАТЬ 11/27/2012: см. это для измерения деталей на смешном
количество стека Windows на самом деле толкает]
Можно ли заставить Windows хранить блок контекста исключения
где-нибудь еще (например, к месту, определенному для потока)?
Тогда программное обеспечение может принять исключение
ударить по потоку и обработать его, не переполнив мой
небольшие кадры стека.
Я не думаю, что это возможно, но я думал, что попрошу гораздо больше
аудитория. Существует ли стандартный вызов/интерфейс ОС
что может привести к этому?
Было бы тривиально делать в ОС, если бы я мог заставить MS разрешить мне
процесс необязательно определяет место хранения контекста, "contextp", которое
инициализируется для включения текущего поведения по умолчанию.
Затем замените вектор-прерыватель/ловушку:
hardwareint: push context
mov contextp, esp
... с...
hardwareint: mov <somereg> contextp
test <somereg>
jnz $2
push context
mov contextp, esp
jmp $1
$2: store context @ somereg
$1: equ *
с очевидными изменениями, необходимыми для сохранения somereg и т.д.
[Что я делаю сейчас: проверьте сгенерированный код для каждой функции.
Если у него есть шанс создать ловушку (например, делить на ноль),
или мы отлаживаем (возможно, плохой указатель deref и т.д.), добавьте
достаточно места для кадра стека для контекста FP. Рамы для стеков
теперь в конечном итоге размер ~ ~ 500-1000 байт, программы не могут
как можно дальше, что иногда является реальной проблемой для
приложения, которые мы пишем. Таким образом, у нас есть работоспособное решение,
но это усложняет отладку]
EDIT 25 августа: мне удалось получить эту историю у внутреннего инженера Microsoft
который обладает полномочиями, чтобы выяснить, кто в MS может на самом деле
забота. Там может быть слабая надежда на решение.
EDIT 14 сентября: MS Kernal Group Architect услышал эту историю и сочувствует. Он сказал, что MS рассмотрит решение (подобное предложено), но вряд ли окажется в пакете обновления. Возможно, придется ждать следующей версии Windows. (Вздох... Я могу состариться...)
EDIT: 13 сентября 2010 года (1 год спустя). Никаких действий со стороны Microsoft. Мой последний кошмар: берет ловушку, запускающую 32-битный процесс в Windows X64, выталкивает весь контекст X64 в стек до того, как обработчик прерываний подделывает 32-битный контекст? Это будет еще больше (в два раза больше целочисленных регистров в два раза больше, в два раза больше SSE-регистров (?))?
EDIT: 25 февраля 2012 года: (прошло 1,5 года...) Никакой реакции на часть Microsoft. Я думаю, они просто не заботятся о моем виде parallelism. Я думаю, что это вред для сообщества; "модель большого стека", используемая MS при нормальных обстоятельствах, ограничивает количество параллельных вычислений, которые можно оживить в любой момент, потребляя огромное количество VM. Модель PARLANSE позволит использовать приложение с миллионами живых "зерен" в различных состояниях работы/ожидания; это действительно происходит в некоторых наших приложениях, где параллельно обрабатывается 100 миллионов графиков node. Схема PARLANSE может сделать это примерно с 1 ГБ ОЗУ, что вполне управляемо. Если вы попробовали это с MS 1Mb "большими стеками", вам понадобится 10 ^ 12 байтов виртуальной машины только для пространства стека, и я уверен, что Windows не позволит вам управлять миллионами потоков.
EDIT: 29 апреля 2014 года: (прошло 4 года). Я предполагаю, что MS просто не читает SO. Я сделал достаточную технику в PARLANSE, поэтому мы платим только цену больших кадров стека во время отладки или когда происходят операции FP, поэтому нам удалось найти очень практичные способы жить с этим. MS продолжает разочаровывать; количество вещей, которые вставляются в стек различными версиями Windows, по-видимому, значительно варьируется и значительно выше и выше необходимости только в аппаратном контексте. Там есть какой-то намек на то, что некоторые из этой изменчивости вызваны прилипанием не-MS (например, антивирусом) к носу в цепочке обработки исключений; почему они не могут сделать это из-за пределов моего адресного пространства? В любом случае, мы справляемся со всем этим, просто добавляя большой коэффициент отказов для ловушек FP/debug и ожидая неизбежной системы MS в поле, которое превышает эту сумму.
Ответы
Ответ 1
В основном вам нужно будет повторно реализовать много обработчиков прерываний, т.е. подключиться к таблице Дескриптор прерываний (IDT).
Проблема в том, что вам также потребуется повторно реализовать обратный вызов kernelmode → usermode (для SEH этот обратный вызов находится в ntdll.dll
и имеет имя KiuserExceptionDispatcher
, это вызывает всю логику SEH). Дело в том, что остальная часть системы опирается на работу SEH так, как она делает сейчас, и ваше решение нарушит все, потому что вы делаете это по всей системе. Возможно, вы можете проверить, в каком процессе вы находитесь во время прерывания.
Однако общая концепция подвержена ошибкам и очень плохо влияет на стабильность системы imho.
Это действительно руткит-подобные методы.
Edit:
Более подробная информация: причина, по которой вам потребуется повторная реализация обработчиков прерываний, заключается в том, что исключения (например, деление на ноль) являются, по существу, программными прерываниями, и они всегда проходят через IDT. Когда исключение выбрано, ядро собирает контекст и возвращает исключение обратно в usermode (через вышеупомянутый KiUserExceptionDispatcher в ntdll). Вам нужно будет вмешаться в этот момент, и поэтому вам также необходимо будет предоставить механизм для возврата в пользовательский режим. (В ntdll есть функция, которая используется как точка входа из режима ядра - я не помню имя, но его что-то с KiUserACP.....)
Ответ 2
Рассмотрим развязку параметра/локального стека из реального. Используйте другой регистр (например, EBP) в качестве эффективного указателя стека, оставьте стек на основе ESP так, как Windows хочет его.
Вы больше не можете использовать PUSH/POP. Вы должны использовать комбо SUB/MOV/MOV/MOV вместо PUSH. Но эй, бьет, исправляя ОС.
Ответ 3
Если Windows использует аппаратное обеспечение x86 для реализации своего кода ловушки, вам необходимо установить кольцевой доступ (через драйвер или API), чтобы изменить, какой затвор используется для ловушек.
Концепция ворот x86 имеет один из следующих элементов:
- адрес прерывания (сегмент кода + указатель смещения), который вызывается, пока весь контекст регистра, включая адрес возврата, помещается в текущий стек (= текущий esp) или
- дескриптор задачи, который переключается на другую задачу (можно рассматривать как поддерживаемый аппаратным потоком). Все соответствующие данные затем помещаются в стек (esp) этой задачи.
Вы, конечно, хотите последнего. Я бы посмотрел, как это реализовано Wine, что может оказаться более эффективным, чем запрос google.
Я предполагаю, что вам, к сожалению, нужно реализовать драйвер, чтобы он работал на x86, и согласно Wikipedia невозможно для драйверов, чтобы изменить его на платформе IA64. Вторым лучшим вариантом может быть чередование пространства в ваших стеках, так что контекстное нажатие из ловушки всегда подходит?
Ответ 4
У меня закончилось пространство в поле комментариев...
В любом случае я не уверен, где указаны векторные точки, я основывал комментарий на ответе SDD и упоминал о "KiUserExceptionDispatcher"... кроме дальнейшего поиска (http://www.nynaeve.net/?p=201), похоже, что в этот момент может быть слишком поздно.
SIDT можно выполнить в кольце 3... это покажет содержимое таблицы прерываний, и вы сможете загрузить сегмент и, по крайней мере, прочитать содержимое таблицы. В любом случае вы можете прочитать запись для (например) вектора 0/делить на ноль и прочитать содержимое обработчика.
В этот момент я попытаюсь совместить шестнадцатеричные байты, чтобы соответствовать коду с системным файлом, но может быть лучший способ определить, к какому файлу принадлежит код (это не обязательно DLL, это может быть win32k. sys, или он может быть динамически генерирован, кто знает). Я не знаю, есть ли способ выгрузить физическую память из пользовательского режима.
Если все остальное не удается, вы можете либо настроить отладчик в режиме ядра, либо подражать Windows (Bochs), где вы можете непосредственно просматривать таблицы прерываний и макет памяти. Затем вы можете проследить до тех пор, пока не будет нажата CONTEXT, и найдите возможность получить контроль до того, как это произойдет.
Ответ 5
Обработка исключений Windows называется SEH. IIRC вы можете отключить его, но время выполнения используемого вами языка может не понравиться.