Windows: избегайте загрузки полного контекста x86 в стек

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

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

[EDIT 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   *

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

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

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

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

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


person Community    schedule 15.06.2009    source источник
comment
Если вы пропатчите ntdll.dll в памяти, изменения будут видны только в текущем процессе (копирование при записи). Я бы предположил, что используется прямой адрес, а не IAT, но вы можете перезаписать первые несколько байтов обработчика с помощью JMP в свой собственный код и вернуться к кольцу 3. Windows может иметь некоторую защиту для предотвращения такого рода вещь, но это стоит того.   -  person zildjohn01    schedule 29.08.2009
comment
Это мысль. Вы предполагаете, что целью IDT является ntdll.dll и что я могу наступить на нее? Как мне выяснить, куда указывает IDT, или это опубликованная точка входа в ntdll.dll? Где я могу узнать больше о структуре ntdll.dll? Повторяя фразу, которую я только что услышал, это займет меня некоторое время. Спасибо!   -  person Ira Baxter    schedule 29.08.2009
comment
ой .. Я использовал IDT, я имею в виду вектор прерывания или как его сейчас называет архитектура x86. (У меня есть руководства по x86, так что это риторическое утверждение :-)   -  person Ira Baxter    schedule 29.08.2009
comment
Как насчет этого... Перед инструкциями, которые могут вызвать исключение, вы устанавливаете xSP так, чтобы оно указывало на расположение, в котором достаточно места для всех этих данных исключения в стеке, содержащих состояние ЦП/ФПУ, а что нет, и после этой инструкции вы восстанавливаете xSP? Если нет исключений, накладные расходы невелики. Если есть, вы даже не заметите накладных расходов.   -  person Alexey Frunze    schedule 07.03.2012
comment
@Alex: Неплохая идея, если все прерывания чисто синхронны по отношению к какому-то кодовому событию. Для этого языка я также запускаю и останавливаю поток асинхронно, чтобы обеспечить некоторую степень вычислительной справедливости.. поэтому иногда такой толчок может быть вызван извне. Я мог бы отказаться от этого, чтобы получить более управляемые кадры стека.   -  person Ira Baxter    schedule 07.03.2012
comment
@Alex: Одна из проблем заключается в сообщении о незаконном доступе к памяти (неправильные разыменования указателя; люди могут сделать эту ошибку в PARLANSE). Если я хочу иметь возможность сообщить об этом и раскрутить кадры стека, чтобы получить обратную трассировку, кадры стека не должны быть повреждены ловушкой. Но это означает, что все кадры стека должны иметь место, чтобы попасть в ловушку... ick. [На самом деле у нас есть опция компилятора для этого, которую мы используем во время тестирования кода, где мы можем позволить себе место. Рабочий код может действительно вкладывать миллионы вызовов вглубь, и мы не можем себе этого позволить].   -  person Ira Baxter    schedule 03.08.2012


Ответы (5)


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

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

person SDD    schedule 17.06.2009
comment
Да, это довольно радикально. Я не уверен, что хочу исправлять ОС. - person Ira Baxter; 17.06.2009
comment
Да, но нет другого способа добиться желаемого, потому что весь процесс обработки исключений запускается из режима ядра. - person SDD; 17.06.2009
comment
Я надеялся, что MS достаточно умна, чтобы понять, какая у меня проблема (в конце концов, разве они не обеспечивают основу для будущего в Windows :-), так что все, что мне нужно было сделать, это использовать правильный API. Звучит как «Нет такой удачи». - person Ira Baxter; 17.06.2009
comment
Так является ли IDT видимым/изменяемым простым пользовательским процессом? Как? - person Ira Baxter; 08.09.2012

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

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

person Community    schedule 04.12.2013
comment
Да, технически это сработает. Это, безусловно, дает много в плотности кода. Схема, которая у меня есть, работает за счет того, что кадры стека становятся слишком большими, когда вокруг есть операции с плавающей запятой, и/или когда программа может поймать недопустимую ссылку на память, и я хочу обеспечить хорошую обратную трассировку. В настоящее время мы компилируем в двух режимах: а) производственный режим с минимальными кадрами стека (иногда всего 32 байта), но без возможности восстановления после машинной ловушки, кроме программы, умершей @xxx, и б) режим отладки, который добавляет вопиющее количество (1500 байт) на каждый кадр стека, что дает достаточный отстой для MS. - person Ira Baxter; 04.12.2013
comment
Я думал, вы стремитесь оптимизировать скорость за счет памяти. - person Seva Alekseyev; 04.12.2013
comment
Ограничение используемого вами набора инструкций (особенно базовых, высокооптимизированных инструкций, таких как push и pop) путем симуляции с несколькими инструкциями, заменяющими их эффект, не приведет к увеличению скорости. Вы правы, я на самом деле не против плотности кода, поскольку я думаю, что процессоры удивительно хороши в получении инструкций. Но компромисс, который мы сделали, означает, что мы не жертвуем возможностью использовать какую-либо часть набора инструкций; это просто означает, что мы пересекаемся с бездумным управлением стеком MS. (Я предложил действительно простое решение в своем вопросе, но я сомневаюсь, что MS когда-либо это сделает.) - person Ira Baxter; 04.12.2013
comment
Даже гораздо более известные поставщики программного обеспечения, такие как Parallels, публично жалуются, что MS не пускает их в ядро. Тем не менее, допускает ли ваша модель восстанавливаемые исключения на уровне ЦП? Другими словами, какова цена затирания пространства стека ядром - просто неспособность получить хороший аварийный дамп? Кроме того, на x86_64 есть куча дополнительных регистров; просто говорю'. :) Кроме того, внедрите соглашение о вызовах на основе регистров - это значительно уменьшит потребность в PUSH. - person Seva Alekseyev; 05.12.2013
comment
Кроме того, подумайте об этом. Необходимость в действующем стеке на основе ESP проистекает из того, как x86 обрабатывает прерывания, в том числе аппаратные. Все, что выше ESP, является честной игрой, поскольку прерывание может произойти в любое время. Когда вы перемещаете параметры и сохраняете регистры в искусственном стеке, вам не нужно, чтобы указатель стека был все время непротиворечивым. И статические смещения от указателя кадра могут быть рассчитаны во время компиляции. Иными словами, дело для PUSH/POP не такое срочное, как с настоящим стеком, тем, на который приходят прерывания. - person Seva Alekseyev; 05.12.2013
comment
На данный момент, с большим количеством тщательной инженерии, да, просто неспособность получить хороший аварийный дамп, и даже не это, если мы компилируем в режиме отладки. Что-нибудь выше ESP... вы имеете в виду ниже? Я понимаю ваше решение и почему искусственный стек не страдает от злоупотреблений со стороны Microsoft :-} К сожалению, на x64 мне, вероятно, придется использовать соглашение о вызовах MS для обеспечения совместимости, поэтому продолжайте решать проблему ESP. Согласитесь, гораздо меньше необходимости в толчке и поп-музыке. - person Ira Baxter; 05.12.2013
comment
Ментальные модели различаются в зависимости от того, растет стек вверх или вниз :) По словам Рэймонда Чена, нужно уметь переключаться между обоими. - person Seva Alekseyev; 06.12.2013
comment
Кажется, Intel не слушает Рэймонда Чена. - person Ira Baxter; 10.12.2013
comment
На самом деле я не ответил на ваш вопрос о восстанавливаемых исключениях на уровне ЦП. Да, делая наши кадры стека искусственно большими, когда мы работаем с плавающей запятой, у нас есть достаточно места в стеке для MS, чтобы сделать свое грязное дело в ловушке FP и выйти невредимым с другой стороны. Таким образом, в PARLANSE есть исключения (FP) DivisionByZero и (FP)Overflow, которые приложения могут перехватывать и использовать ожидаемым образом. Это работает, потому что компилятор может легко определить, работает ли блок кода PARLANSE с плавающей запятой или нет, и добавить необходимый стек для этого кода. - person Ira Baxter; 29.04.2014
comment
Йосси Крейнин из Proper Fixation пишет то же, что и я: в какой-то момент развертывание собственного управления контекстом становится оправданным. - person Seva Alekseyev; 29.04.2014
comment
Хорошая ссылка. Мы сосредоточены на эффективном мелкозернистом параллелизме SMP для больших нечисловых вычислений, которые мы выполняем. PARLANSE может оказаться хорошим ожиданием. Внутренне он уже может создавать миллионы событий и эффективно ожидать миллионы зёрен PARLANSE. Внешне жизнь сложнее, поскольку мы мультиплексируем гранулы PARLANSE поверх потоков ОС, и поэтому у нас кончается поток на пределе потоков ОС... это просто означает, что нам нужно больше сохранять состояние. - person Ira Baxter; 30.04.2014
comment
Именно о таких системах пишет Йосси. Его точка зрения заключается в том, что вы можете расширить логический параллелизм за пределы разумного количества потоков, налагаемого ОС, за счет необходимости разворачивать собственное управление контекстом, которое может быть или не быть основанным на стеке. - person Seva Alekseyev; 30.04.2014

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

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

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

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

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

person Jonas Byström    schedule 15.06.2009
comment
Я могу посмотреть на Wine, но я не уверен, что узнаю о Windows. Во-первых, Wine работает под Linux; нет особых причин полагать, что его вызовы ОС можно использовать для Windows. Во-вторых, нет особых причин полагать, что Windows позволит мне взять под контроль шлюз аппаратных прерываний или дескриптор задачи. (Но, может случиться чудо, я пойду посмотрю... вы говорите мне, что я могу получить доступ через стандартный MS API? Какой? Или вы предлагаете мне собрать драйвер и обмануть?) - person Ira Baxter; 17.06.2009
comment
ваше предположение о том, что полный контекст передается обработчику int, неверно. Единственное, что гарантированно лежит в стеке, это: errorCode (необязательно), eip, селектор сегмента кода, eflags, esp и селектор сегмента стека (именно в таком порядке). Вы не можете изменить это поведение, потому что оно жестко запрограммировано в ЦП. - person newgre; 17.06.2009
comment
Верно, аппаратное обеспечение должно подтолкнуть какой-то контекст. И это скромное количество в порядке, и я всегда могу включить его в отступы, необходимые для моих кадров стека. Существуют машинные инструкции для хранения контекста FP; аккуратно сделанный, его можно хранить в любом достаточно большом буфере, в том числе и в стеке. Но аппаратное обеспечение не загружает контекст FP в мой стек. Windows, кажется, делает это. С моей точки зрения, не имеет значения, делает ли это аппаратное обеспечение или Windows, если оно получает толчок, а мой кадр стека мал. Важно то, смогу ли я заставить Windows не использовать контекст FP. - person Ira Baxter; 18.06.2009
comment
Ну, как я уже сказал, вы можете изменить то, что добавляется дополнительно, путем повторной реализации соответствующих обработчиков прерываний, остальное изменить нельзя. Конечно, окна должны будут сохранять полный контекст сами по себе, иначе обработчик исключений пользовательского режима не сможет получить контекст потока (и, возможно, изменить его и применить к следующему расписанию потока). - person newgre; 18.06.2009
comment
Быстрый комментарий. Хотя Wine может быть скомпилирован для Windows (предположительно), IIRC работает полностью в пользовательском режиме, поэтому я не думаю, что просмотр его кода поможет. - person zildjohn01; 29.08.2009

У меня закончилось место в поле для комментариев...

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

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

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

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

person zildjohn01    schedule 31.08.2009
comment
Я действительно действительно не хочу исправлять код ядра. Я просто хочу, чтобы MS позволила мне попросить поместить контекст в буфер, который я предоставляю, а не запихивать его в горло моего текущего стека. - person Ira Baxter; 14.09.2010

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

person Marco van de Voort    schedule 15.06.2009
comment
Я знаю о SEH, и мы настроили его так, чтобы он указывал на наш обработчик ловушки исключений. Как его отключить и куда тогда деться аппаратная ловушка? Время выполнения языка, который я использую, полностью находится под моим контролем. Большая часть среды выполнения параллельного языка реализована на C, но программное обеспечение ловко переключает стеки со стека в стиле кактуса на стандартный большой стек MS при выполнении такого кода; Я мог бы также переключить обработчики исключений, если это решит мою проблему с переполнением стека. - person Ira Baxter; 15.06.2009
comment
Если вы отключите SEH, ваше приложение вылетит при делении на ноль. И если бы вы могли каким-то образом отключить исключения, что бы вы ожидали от процессора при делении на ноль... тройной ошибке? - person zildjohn01; 29.08.2009
comment
Я не отключал SEH, я просто настроил его так, чтобы он указывал на мой обработчик. К тому времени, как мой обработчик получает управление, Windows уже помещает в стек полный кадр стека. - person Ira Baxter; 14.09.2010