Windows: не помещайте полный контекст x86 в стек

Я реализовал PARLANSE, язык под MS Windows, который использует стеки кактусов для реализации параллельных программ. Чанки стека распределяются для каждой функции отдельно и имеют правильный размер для обработки локальных переменных, временных / динамических выражений выражений и вызовов библиотек (включая пространство стека для работы библиотечных подпрограмм). Такие стековые фреймы на практике могут составлять всего 32 байта и часто таковы.

Все это прекрасно работает, если код не делает глупостей и не вызывает аппаратную ловушку... в этот момент Windows, кажется, настаивает на том, чтобы поместить весь контекст компьютера x86 "в стек". Это более 500 байтов, если вы включите FP/MMX/ и т. Д. регистрирует, что делает. Естественно, 500-байтовое нажатие на 32-байтовый стек ломает то, что не должно. (Аппаратная часть помещает несколько слов в ловушку, но не весь контекст).

[РЕДАКТИРОВАНИЕ 27/11/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   *

с очевидными изменениями, необходимыми для сохранения некоторого регистра и т. д.

[Что я делаю сейчас: проверяю сгенерированный код для каждой функции. Если у него есть шанс сгенерировать ловушку (например, разделить на ноль), или мы отлаживаем (возможный неправильный указатель и т.д.), добавьте достаточно места в кадр стека для контекста FP. Размеры стековых фреймов теперь составляют ~ 500-1000 байт, программы не могут обрабатывать их до такой степени, что иногда является реальной проблемой для приложений, которые мы пишем. Итак, у нас есть работоспособное решение, но оно усложняет отладку.]

РЕДАКТИРОВАТЬ 25 августа. Мне удалось донести эту историю до внутреннего инженера Microsoft, который, по всей видимости, имеет полномочия выяснить, кого на самом деле волнует MS. Там может быть слабая надежда на решение.

РЕДАКТИРОВАТЬ 14 сентября: MS Kernal Group Архитектор услышал историю и сочувствует. Он сказал, что MS рассмотрит решение (как предложенное), но вряд ли будет в пакете обновления. Возможно, придется ждать следующей версии Windows. (Вздох... Я мог бы состариться...)

РЕДАКТИРОВАТЬ: 13 сентября 2010 года (1 год спустя). Никаких действий со стороны Microsoft. Мой последний кошмар: захватывает ли 32-битный процесс в Windows X64 ловушку, помещает весь контекст X64 в стек до того, как обработчик прерываний фальсифицирует 32-битный контекст? Это было бы еще больше (вдвое больше целочисленных регистров в два раза шире, в два раза больше SSE-регистров (?))?

РЕДАКТИРОВАТЬ: 25 февраля 2012: (1,5 года прошло...) Никакой реакции со стороны Microsoft. Я думаю, им просто наплевать на мой вид параллелизма. Я думаю, что это плохая услуга для сообщества; "Модель большого стека", используемая MS в обычных условиях, ограничивает количество параллельных вычислений, которые можно получить в любой момент, съев огромное количество виртуальных машин. Модель PARLANSE позволяет использовать приложение с миллионом живых "зерен" в различных состояниях работы / ожидания; это действительно происходит в некоторых наших приложениях, где граф на 100 миллионов узлов обрабатывается "параллельно". Схема PARLANSE может сделать это с 1 ГБ ОЗУ, что довольно легко управляемо. Если вы попробуете это с "большими стеками" MS 1Mb, вам потребуется 10^12 байтов виртуальной машины только для стекового пространства, и я уверен, что Windows не позволит вам управлять миллионами потоков.

РЕДАКТИРОВАТЬ: 29 апреля 2014 года: (прошло 4 года). Я думаю, что MS просто не читает ТАК. Я достаточно разработал PARLANSE, поэтому мы платим только за большие кадры стека во время отладки или когда выполняются операции FP, поэтому нам удалось найти очень практичные способы, чтобы справиться с этим. MS продолжал разочаровывать; количество вещей, помещаемых в стек различными версиями Windows, кажется, значительно и вопиюще отличается от потребности только в аппаратном контексте. Есть некоторый намек на то, что некоторая часть этой изменчивости вызвана зависанием продуктов других производителей (например, антивируса), которые застревают в цепочке обработки исключений; почему они не могут сделать это вне моего адресного пространства? В любом случае, мы справляемся со всем этим, просто добавляя большой коэффициент наклона для ловушек FP / отладки и ожидая неизбежной системы MS в поле, которое превышает это количество.

5 ответов

По сути, вам нужно будет повторно реализовать много обработчиков прерываний, то есть подключиться к таблице дескрипторов прерываний (IDT). Проблема заключается в том, что вам также необходимо повторно реализовать обратный вызов kernelmode -> usermode (для SEH этот обратный вызов находится в ntdll.dll и назван KiuserExceptionDispatcher, это запускает всю логику SEH). Дело в том, что остальная часть системы полагается на то, что SEH работает так, как она работает сейчас, и ваше решение сломает ситуацию, потому что вы делали это для всей системы. Может быть, вы могли бы проверить, в каком процессе вы находитесь во время прерывания. Однако общая концепция подвержена ошибкам и очень плохо влияет на стабильность системы imho.
Это на самом деле руткит-подобные методы.

Редактировать:
Еще несколько подробностей: причина, по которой вам нужно было бы повторно реализовать обработчики прерываний, состоит в том, что исключения (например, деление на ноль) по сути являются программными прерываниями, и они всегда проходят через IDT. Когда выдается исключение, ядро ​​собирает контекст и передает исключение обратно в пользовательский режим (через вышеупомянутый KiUserExceptionDispatcher в ntdll). Вам нужно будет вмешаться в этот момент, и, следовательно, вам также потребуется предоставить механизм для возврата в режим пользователя. (В ntdll есть функция, которая используется как точка входа из режима ядра - я не помню названия, но что-то с KiUserACP.....)

Подумайте об отделении параметра / локального стека от реального. Используйте другой регистр (например, EBP) в качестве эффективного указателя стека, оставьте стек на основе ESP так, как этого хочет Windows.

Вы не можете больше использовать PUSH / POP. Вы должны будете использовать комбо SUB / MOV / MOV / MOV вместо PUSH. Но, эй, лучше, чем патчить ОС.

Если Windows использует аппаратное обеспечение x86 для реализации своего кода прерываний, вам нужен доступ по кольцу 0 (через драйвер или API), чтобы изменить, какой шлюз используется для прерываний.

Концепция ворот x86 указывает на одно из:

  • адрес прерывания (сегмент кода + указатель смещения), который вызывается, пока весь контекст регистра, включая адрес возврата, помещается в текущий стек (= текущий esp), или
  • дескриптор задачи, который переключается на другую задачу (может рассматриваться как аппаратно поддерживаемый поток). Вместо этого все соответствующие данные помещаются в стек (esp) этой задачи.

Вы, конечно, хотите последнее. Я бы посмотрел на то, как Wine реализовал это, что может оказаться более эффективным, чем задавать вопросы в Google.

Я предполагаю, что вам, к сожалению, нужно реализовать драйвер, чтобы он работал на x86, и, согласно Википедии, драйверы не могут изменить его на платформе IA64. Вторым лучшим вариантом может быть чередование пространства в ваших стеках, чтобы контекстный пуш из ловушки всегда подходил?

Мне не хватило места в поле для комментариев...

В любом случае, я не уверен, куда указывает вектор, я основывал комментарий на ответе SDD и упоминании "KiUserExceptionDispatcher"... за исключением случаев дальнейшего поиска ( http://www.nynaeve.net/?p=201) его Похоже, на данный момент это может быть слишком поздно.

SIDT может быть выполнен в кольце 3... это покажет содержимое таблицы прерываний, и вы сможете загрузить сегмент и, по крайней мере, прочитать содержимое таблицы. Если вам повезет, вы можете прочитать запись для (например) вектора 0/ делить на ноль и прочитать содержимое обработчика.

На этом этапе я бы попытался сопоставить шестнадцатеричные байты, чтобы сопоставить код с системным файлом, но может быть лучший способ определить, к какому файлу принадлежит код (это не обязательно DLL, это может быть win32k.sys или это может быть динамически сгенерировано, кто знает). Я не знаю, есть ли способ вывести физическую схему памяти из пользовательского режима.

Если ничего не помогает, вы можете настроить отладчик в режиме ядра или эмулировать Windows ( Bochs), где вы можете просматривать таблицы прерываний и структуру памяти напрямую. Затем вы можете проследить, пока точка КОНТЕКСТ не будет нажата, и искать возможность получить контроль до того, как это произойдет.

Обработка исключений Windows называется SEH. IIRC вы можете отключить его, но время выполнения языка, который вы используете, может не понравиться.

Другие вопросы по тегам