Windows: не помещайте полный контекст x86 в стек - PullRequest
13 голосов
/ 15 июня 2009

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

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

[EDIT 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 и т. Д.

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

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

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

Ответы [ 5 ]

4 голосов
/ 17 июня 2009

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

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

3 голосов
/ 04 декабря 2013

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

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

1 голос
/ 01 сентября 2009

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

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

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

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

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

1 голос
/ 15 июня 2009

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

Концепция шлюза x86 одно из:

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

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

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

0 голосов
/ 15 июня 2009

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

...