особенности структурной обработки исключений в Win64
От: кт  
Дата: 01.12.16 05:42
Оценка:
как и предлагали в http://rsdn.org/forum/flame.comp/6616369.flat
Автор: кт
Дата: 21.11.16

продолжаю выкладывать по просьбе автора статьи, уже не попавшие в исчезнувший журнал RSDN magazine
По теме, на мой взгляд, в "Архитектуру" или в "WinAPI", но уж не в "Низкоуровневое программирование", куда без разбора помещены все предыдущие статьи этого же автора. Ассемблерные команды в статье лишь для пояснений, программирования в командах там нет.

Сама статья, написана полтора года назад:
http://files.rsdn.org/122727/pl1ex21.doc

Напоминаю, что автор не я, а уже печатавшийся здесь Караваев http://rsdn.org/users/99493.aspx
Re: особенности структурной обработки исключений в Win64
От: ononim  
Дата: 02.12.16 19:32
Оценка:

Какое собственно Windows дело кратен или не кратен указатель стека 8 в программе, раз обработчик все равно уже найден и стек дальше исследовать не надо?

Думаю дело такое, что компилятор рассчитывает на определенное выравнивание стека при вызове функций. И рассчитывает он на это не просто так, а для того чтобы можно было использовать инструкции SSE при работе со стековыми переменными. Впрочем конечно можно было бы насильно подравнять стек, запомнив в нем же оригинальное "неровное" значение.

Диспетчер берет следующие 8 байт «вглубь» стека, надеясь, что это очередной адрес возврата, и пытается по ним все же определить, какая из известных, т.е. зарегистрированных подпрограмм выполнялась. Но это очень шаткая и сомнительная технология.

Технология конечно ужас-ужас в имплементации, зато имеет нулевой оверхед в рантайме. Самый интересный момент в статье не рассмотрен: а именно то что может быть в RtlAddFunctionTable(..RUNTIME_FUNCTION..) — UNWIND_INFO, которая является в том числе подсказчиком для того дизассемблера-симулятора. Прологи/эпилоги функций соотстветственно должны быть не абы-какими, а соответствовать возможностям симулятора, это как бы часть соглашения. Вобщем х64 стал еще немного дальше от любителей завернуть чтонить на асме.

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

Технология придумана для "санкционированных" исключений, то есть брошенных руками. Работоспособность программы после какого нить illegal instruction или поломанной памяти на самом деле никому не интересна.
Как много веселых ребят, и все делают велосипед...
Отредактировано 02.12.2016 19:33 ononim . Предыдущая версия .
Re[2]: особенности структурной обработки исключений в Win64
От: кт  
Дата: 03.12.16 07:05
Оценка:
Здравствуйте, ononim, Вы писали:

O>Думаю дело такое, что компилятор рассчитывает на определенное выравнивание стека при вызове функций. И рассчитывает он на это не просто так, а для того чтобы можно было использовать инструкции SSE при работе со стековыми переменными. Впрочем конечно можно было бы насильно подравнять стек, запомнив в нем же оригинальное "неровное" значение.


По поводу выравнивания стека я и с автором и с Вами согласен. Охренели они все, простите мой французский. Зачем, например, во вшивой API InternetGetConnectedState использовать SSE2? Ответ: да не используется там такие команды, просто по дороге один чудила написал
...
movdqa xmmword ptr [rsp+20h],xmm0
movdqa xmmword ptr [rsp+30h],xmm1
movdqa xmmword ptr [rsp+40h],xmm2
movdqa xmmword ptr [rsp+50h],xmm3
mov rdx,rax
lea rcx,[WININET!Ordinal103+0x43524
call WININET!DispatchAPICall+0x3f0 (
movdqa xmm0,xmmword ptr [rsp+20h]
movdqa xmm1,xmmword ptr [rsp+30h]
movdqa xmm2,xmmword ptr [rsp+40h]
movdqa xmm3,xmmword ptr [rsp+50h]
...
т.е. запомнил и восстановил эти чертовы регистры. Ну, даже, если и нужно их внутри использовать – ну и выровни стек на 16 ВНУТРИ свой программы. Зачем ты заставляешь всех нарезать стек крупными ломтями?

O>Технология придумана для "санкционированных" исключений, то есть брошенных руками. Работоспособность программы после какого нить illegal instruction или поломанной памяти на самом деле никому не интересна.


Жизнь в программе после исключения вполне может быть
Я часто видел в программах конструкции типа такой:

On underflow begin; x=0; goto m1; end;

x=x/y;
m1:…
Re[3]: особенности структурной обработки исключений в Win64
От: ononim  
Дата: 03.12.16 17:47
Оценка:
кт>По поводу выравнивания стека я и с автором и с Вами согласен. Охренели они все, простите мой французский. Зачем, например, во вшивой API InternetGetConnectedState использовать SSE2? Ответ: да не используется там такие команды, просто по дороге один чудила написал
кт>...
кт>movdqa xmmword ptr [rsp+20h],xmm0
кт>movdqa xmmword ptr [rsp+30h],xmm1
кт>movdqa xmmword ptr [rsp+40h],xmm2
кт>movdqa xmmword ptr [rsp+50h],xmm3
кт>mov rdx,rax
кт>lea rcx,[WININET!Ordinal103+0x43524
кт>call WININET!DispatchAPICall+0x3f0 (
кт>movdqa xmm0,xmmword ptr [rsp+20h]
кт>movdqa xmm1,xmmword ptr [rsp+30h]
кт>movdqa xmm2,xmmword ptr [rsp+40h]
кт>movdqa xmm3,xmmword ptr [rsp+50h]
Ну прежде чем делать какие либо выводы нужно бы заглянуть в WININET!DispatchAPICall+0x3f0. А предде чем заглядывать в вывод отладчика вообще нужно бы настроить символы..

кт>т.е. запомнил и восстановил эти чертовы регистры. Ну, даже, если и нужно их внутри использовать – ну и выровни стек на 16 ВНУТРИ свой программы. Зачем ты заставляешь всех нарезать стек крупными ломтями?

Это часть соглашений о вызове.


кт>Жизнь в программе после исключения вполне может быть

кт>Я часто видел в программах конструкции типа такой:
кт>On underflow begin; x=0; goto m1; end;
кт>…
кт>x=x/y;
кт>m1:…
Такое исключение не приведет к падению. 64 стек всегда аллоцируется кратным sizeof(void *) то есть 8. Добиться неровности можно только некорректным кодом, а не исключением в корректном коде, сгерененным компилятором с учетом известных ему правил. Если писать свой код на асме который делает add rsp, 3, а потом хвататься за свою голову остается только посочувтсовать. Как я уже сказал — меньше всего авторы соглашений колдогенерации в х64 позаботились о любителях теплого лампового ассемблера.
Как много веселых ребят, и все делают велосипед...
Отредактировано 03.12.2016 17:49 ononim . Предыдущая версия . Еще …
Отредактировано 03.12.2016 17:48 ononim . Предыдущая версия .
Re[4]: особенности структурной обработки исключений в Win64
От: кт  
Дата: 03.12.16 18:19
Оценка:
Здравствуйте, ononim, Вы писали:

O>Ну прежде чем делать какие либо выводы нужно бы заглянуть в WININET!DispatchAPICall+0x3f0. А предде чем заглядывать в вывод отладчика вообще нужно бы настроить символы..

Не понял, какие символы настроить? Это была всего лишь иллюстрация бездумного сохранения при вызове простейшей функции.
SSE2 используются не часто, ну и запоминайте их в тех редких случаях, когда их действительно используют

O>Это часть соглашений о вызове.

Lunix без этого обходится. Там как раз запоминается, что нужно, а не вообще все

O>Такое исключение не приведет к падению. 64 стек всегда аллоцируется кратным sizeof(void *) то есть 8. Добиться неровности можно только некорректным кодом, а не исключением в корректном коде, сгерененным компилятором с учетом известных ему правил. Если писать свой код на асме который делает add rsp, 3, а потом хвататься за свою голову остается только посочувтсовать. Как я уже сказал — меньше всего авторы соглашений колдогенерации в х64 позаботились о любителях теплого лампового ассемблера.

Это вообще не понял. В примере речь шла вообще не о стеке, а об исчезновении порядка ("антипереполнении") переменной x типа double. В программе в этом случае ей присваивается "чистый" 0.0E0. В ряде вычислений так и надо, но иногда антипереполнение — это признак ошибки расчета. Перехватывается аппаратное прерывание от FPU и корректируется переменная. После исключения программа продолжает нормальное (предусмотренное) выполнение. Это был пример на "жизнь после исключения".
Re[5]: особенности структурной обработки исключений в Win64
От: ononim  
Дата: 03.12.16 19:04
Оценка:
O>>Ну прежде чем делать какие либо выводы нужно бы заглянуть в WININET!DispatchAPICall+0x3f0. А предде чем заглядывать в вывод отладчика вообще нужно бы настроить символы..
кт>Не понял, какие символы настроить?
Дебажные символы. windbg настраивается на публичный микрософтовский символ-сервер двумя командами: .symfix и .reload

кт>Это была всего лишь иллюстрация бездумного сохранения при вызове простейшей функции.

С чего вы взяли что сохранение бездумное, а функция простейшая? Что за код по адресу WININET!DispatchAPICall+0x3f0 которая вызывается между сохранением/восстановлением MMX регистров вы смотрели? Ну и к выравниванию стека это вообще отношения не имеет.

O>>Это часть соглашений о вызове.

кт>Lunix без этого обходится. Там как раз запоминается, что нужно, а не вообще все
кт>SSE2 используются не часто, ну и запоминайте их в тех редких случаях, когда их действительно используют
Во-первых, очень часто используется. Во-вторых обязательнон сохранение MMX регистров прям таки всегда не предусмотрено calling convention, предусмотрено только выравнивание стека для того чтоб компилятор мог без дополнительных проверок в любом месте воспользоваться SSE. А MMX контекст сохраняется только если используется.
В вашем примере компилятор решил сохранить SSE перед вызовом непубличной функции. Это его право, так как для непубличных функций компилятор вправе положить на volatile/preserved registers. Уверен что по адресу WININET!DispatchAPICall+0x3f0 находится код который таки юзает SSE, потому компилятор и решил сохранить ММХ контекст. А поскольку функция по адресу WININET!DispatchAPICall+0x3f0 не является публичной, то компилятор вполне вправе поместить сохранение/восстановление ММХ контекста в вызывающем коде. Соглашения о volatile/preserved регистрах — это только для публичных символов. Внутренние вызовы компилятор волен оптимизировать как угодно, в том числе не подчиняясь соглашениям о сохранении регистров, и он это делает повсеместно. В х64 кстати оптимизатор стал особенно злым.


O>>Такое исключение не приведет к падению. 64 стек всегда аллоцируется кратным sizeof(void *) то есть 8. Добиться неровности можно только некорректным кодом, а не исключением в корректном коде, сгерененным компилятором с учетом известных ему правил. Если писать свой код на асме который делает add rsp, 3, а потом хвататься за свою голову остается только посочувтсовать. Как я уже сказал — меньше всего авторы соглашений колдогенерации в х64 позаботились о любителях теплого лампового ассемблера.

кт>Это вообще не понял. В примере речь шла вообще не о стеке, а об исчезновении порядка ("антипереполнении") переменной x типа double. В программе в этом случае ей присваивается "чистый" 0.0E0. В ряде вычислений так и надо, но иногда антипереполнение — это признак ошибки расчета. Перехватывается аппаратное прерывание от FPU и корректируется переменная. После исключения программа продолжает нормальное (предусмотренное) выполнение. Это был пример на "жизнь после исключения".
Еще раз: есть пример кода, когда конкретно этот сценарий использования исключений работает некорректно на х64? Или ваши жалобы касаются лишь того что сложно все это понять "на глазок" как оно все теперь работает и быстро на коленке обработать это дело самописным дебаггером?
Как много веселых ребят, и все делают велосипед...
Отредактировано 03.12.2016 19:11 ononim . Предыдущая версия .
Re[6]: особенности структурной обработки исключений в Win64
От: кт  
Дата: 03.12.16 19:16
Оценка:
Здравствуйте, ononim, Вы писали:

O>Дебажные символы. windbg настраивается на публичный микрософтовский символ-сервер двумя командами: .symfix и .reload

O>С чего вы взяли что сохранение бездумное, а функция простейшая? Что за код по адресу WININET!DispatchAPICall+0x3f которая вызывается между сохранением/восстановлением MMX регистров вы смотрели? Ну и к выравниванию стека это вообще отношения не имеет.
По-моему мы друг друга не понимаем. Какие "дебажные символы" отобразятся в командах запоминания? Я в Windbg всего лишь прошел пошагово несколько десятков команд внутри вызова InternetGetConnectedState и обнаружил запоминание/восстановление XMM, хотя по смыслу эта функция всего лишь должна достать внутренний флаг. Запоминание ненужных в данном случае регистров потребовало выравнивания стека на 16, а если бы не было ненужных XMM, достаточно обычного выравнивания на 8.


O>Во-первых, очень часто используется. Во-вторых сохранение SSE2 не предусмотрено calling convention, предусмотрено только выравнивание стека для того чтоб компилятор мог без дополнительных проверок в любом месте воспользоваться SSE.

O>В вашем примере компилятор решил сохранить SSE перед вызовом непубличной функции. Это его право, так как для непубличных функций компилятор вправе положить на volatile/preserved registers.
Да пусть себе что хочет делает внутри. А снаружи вызова InternetGetConnectedState зачем знать какие функции он ВНУТРИ вызывает. Если внутри есть экзотические вызовы, пусть внутри и разбирается с дополнительными требованиями. А то так перед ЛЮБЫМ вызовом придется все сохранять и выравнивать. Это потеря времени и увеличение размера кода.


OO>Еще раз: есть пример кода, когда конкретно этот сценарий использования исключений работает некорректно на х64? Или ваши жалобы касаются лишь того что сложно все это понять "на глазок" как оно все теперь работает и быстро на коленке обработать это дело самописным дебаггером?

И опять мы друг друга не поняли. Причем здесь стек? Пример опровергал утверждение
"Работоспособность программы после какого нить illegal instruction или поломанной памяти на самом деле никому не интересна". На мой взгляд, ОС должна передать исключение обработчику, а дальше не ее собачье дело "интересно" это кому-нибудь или "неинтересно". Автор статьи ругается, что исключение есть, обработчик на него есть, а ОС его не вызывает, потому, что дескать стек обязательно должен был быть кратен 8. Ну не кратен 8, аппаратно ведь запрещения на это нет? ОС-то зачем это запрещает?
Re[7]: особенности структурной обработки исключений в Win64
От: ononim  
Дата: 03.12.16 19:41
Оценка:
O>>Дебажные символы. windbg настраивается на публичный микрософтовский символ-сервер двумя командами: .symfix и .reload
O>>С чего вы взяли что сохранение бездумное, а функция простейшая? Что за код по адресу WININET!DispatchAPICall+0x3f которая вызывается между сохранением/восстановлением MMX регистров вы смотрели? Ну и к выравниванию стека это вообще отношения не имеет.
кт>По-моему мы друг друга не понимаем. Какие "дебажные символы" отобразятся в командах запоминания? Я в Windbg всего лишь прошел пошагово несколько десятков команд внутри вызова InternetGetConnectedState и обнаружил запоминание/восстановление XMM, хотя по смыслу эта функция всего лишь должна достать внутренний флаг. Запоминание ненужных в данном случае регистров потребовало выравнивания стека на 16, а если бы не было ненужных XMM, достаточно обычного выравнивания на 8.
Деюажные символы, которые бы помогли понять что за код находится по WININET!DispatchAPICall+0x3f0, возможно это навело бы вас на мысль что InternetGetConnectedState далеко не так проста как кажется. К примеру на моей системе InternetGetConnectedState просто вызывает InternetGetConnectedStateEx, а та внутри дохрениша чего делает, включая вызов колбэков и обращение по RPC к системным сервисам.


O>>В вашем примере компилятор решил сохранить SSE перед вызовом непубличной функции. Это его право, так как для непубличных функций компилятор вправе положить на volatile/preserved registers.

кт>Да пусть себе что хочет делает внутри. А снаружи вызова InternetGetConnectedState зачем знать какие функции он ВНУТРИ вызывает. Если внутри есть экзотические вызовы, пусть внутри и разбирается с дополнительными требованиями. А то так перед ЛЮБЫМ вызовом придется все сохранять и выравнивать. Это потеря времени и увеличение размера кода.
Вот этот код который вы привели, это как я полагаю и есть код InternetGetConnectedState? А значит он и есть ВНУТРИ InternetGetConnectedState. Почему вас смущает что InternetGetConnectedState сохраняет внутри себя MMX регистры перед тем как вызывать какие то свои внутренние потроха (которые юзают SSE)?


OO>>Еще раз: есть пример кода, когда конкретно этот сценарий использования исключений работает некорректно на х64? Или ваши жалобы касаются лишь того что сложно все это понять "на глазок" как оно все теперь работает и быстро на коленке обработать это дело самописным дебаггером?

кт>И опять мы друг друга не поняли. Причем здесь стек? Пример опровергал утверждение
кт>"Работоспособность программы после какого нить illegal instruction или поломанной памяти на самом деле никому не интересна".
Действительно не понимаем. Я имел ввиду что есть два рода исключений. Первое — исключения уровня логики приложения. Второе — исключения уровня защиты ОС от неверного функционирования программы. Ваше underflow exception — относится к первым. Такие не могут привести к вызову обработчика исключений на невыровненном стеке, поскольку места из которых они могут вылететь известны компилятору и он генерит код таким образом, чтобы исключения логики генерились на корректном стеке.
Если же исключение возникает изза жесткой ошибки защиты — изза неверно выделенной памяти или изза того что после расстрела стека исполнение пошло по мусорному коду, это означает что исполнение программы дальше невозможно. Баста, остается только позвать по LPC post-mortem дебаггер.

кт>На мой взгляд, ОС должна передать исключение обработчику, а дальше не ее собачье дело "интересно" это кому-нибудь или "неинтересно". Автор статьи ругается, что исключение есть, обработчик на него есть, а ОС его не вызывает, потому, что дескать стек обязательно должен был быть кратен 8. Ну не кратен 8, аппаратно ведь запрещения на это нет? ОС-то зачем это запрещает?

Потому что есть соглашения об адекватном валидном коде, которые несколько выше чем "аппаратно". И согласно которым стек должен всегда оставаться выровненным на 8 (а при вызове публичной функции, в ее прологе — на 16). Если он таковым вдруг не стал — значит код не валидный. Время любителей писать выверты на асме прошло. Теперь балом правит кодогенератор.
Это чисто измышления автора. Я лично понимаю разработчиков ОС. Факт того что стек оказался не выравнен на 8 указывает на то что исключене является фатальным, потому что в корректном коде такого произойти не должно. А значит лучше всего ничего не делать на этом стеке чтобы не наломать еще больших дров.
Как много веселых ребят, и все делают велосипед...
Отредактировано 03.12.2016 19:48 ononim . Предыдущая версия . Еще …
Отредактировано 03.12.2016 19:48 ononim . Предыдущая версия .
Re[8]: особенности структурной обработки исключений в Win64
От: кт  
Дата: 04.12.16 06:26
Оценка:
Здравствуйте, ononim, Вы писали:

O>Почему вас смущает что InternetGetConnectedState сохраняет внутри себя MMX регистры перед тем как вызывать какие то свои внутренние потроха (которые юзают SSE)?

Потому что я знаю, что делают команды SSE2, для которых задействованы регистры XMM. И на кой ляд в подпрограмме возвращающей статус соединения в интернете, нужны команды (и регистры) обрабатывающие по два double за раз, не понимаю.

O>Действительно не понимаем. Я имел ввиду что есть два рода исключений. Первое — исключения уровня логики приложения. Второе — исключения уровня защиты ОС от неверного функционирования программы. Ваше underflow exception — относится к первым. Такие не могут привести к вызову обработчика исключений на невыровненном стеке, поскольку места из которых они могут вылететь известны компилятору и он генерит код таким образом, чтобы исключения логики генерились на корректном стеке.

Компилятор никаких исключений не генерирует. По определению исключение может произойти в любом месте. Автор статьи справедливо показывает, что попытка по текущему содержимому стека определить подпрограмму — это непродуманная ерунда. Например, идет подготовка параметров к вызову API. Выделили место, достаем очередной параметр — ошибка, неинициализированный указатель — исключение доступа. Опа, а в стеке еще просто мусор (туда еще ничего не поместили). Мусор начинает "исследоваться" ОС.

O>Если же исключение возникает изза жесткой ошибки защиты — изза неверно выделенной памяти или изза того что после расстрела стека исполнение пошло по мусорному коду, это означает что исполнение программы дальше невозможно. Баста, остается только позвать по LPC post-mortem дебаггер.

Для этого введен механизм обработчиков исключений. Задача ОС найти обработчик. Только, если она обработчик не нашла — тогда заканчивать задачу с сообщением. Домыслы фатальное-не фатальное исключение — это не дело ОС.

O>Потому что есть соглашения об адекватном валидном коде, которые несколько выше чем "аппаратно". И согласно которым стек должен всегда оставаться выровненным на 8 (а при вызове публичной функции, в ее прологе — на 16). Если он таковым вдруг не стал — значит код не валидный. Время любителей писать выверты на асме прошло. Теперь балом правит кодогенератор.

O>Это чисто измышления автора. Я лично понимаю разработчиков ОС. Факт того что стек оказался не выравнен на 8 указывает на то что исключене является фатальным, потому что в корректном коде такого произойти не должно. А значит лучше всего ничего не делать на этом стеке чтобы не наломать еще больших дров.
Выше "аппаратного" ничего нет. Требования кратности 16 дает не соглашение, а всего лишь используемые команды типа
movdqa xmm0,xmmword ptr [rsp+20h] — обращение к XMM и памяти. Они дают аппаратное исключение из-за невыровненного доступа.
Смешной штрих: чтобы внутри вызова стек был кратен 16, снаружи вызова он должен быть НЕ кратен 16.
Но автор статьи не из-за этого пострадал. Он согласен со всеми требованиями вызова функций. Но ему-то хотелось в момент исключения пошагового режима обращаться со стеком как он привык в Win32. А исключение пошагового режима превращается в фатальное из-за того, что какой-то чудак на букву М не может поверить, что в этот момент (просто выполнение очередной команды) стек используется не так как при вызове API.
Re[9]: особенности структурной обработки исключений в Win64
От: ononim  
Дата: 04.12.16 11:00
Оценка:
O>>Почему вас смущает что InternetGetConnectedState сохраняет внутри себя MMX регистры перед тем как вызывать какие то свои внутренние потроха (которые юзают SSE)?
кт>Потому что я знаю, что делают команды SSE2, для которых задействованы регистры XMM. И на кой ляд в подпрограмме возвращающей статус соединения в интернете, нужны команды (и регистры) обрабатывающие по два double за раз, не понимаю.
Да хоть и для банального memcpy. Откройте IDA и поройтесь по графу вызовов этой функции сами. Он между прочим очень развесистый.

O>>Действительно не понимаем. Я имел ввиду что есть два рода исключений. Первое — исключения уровня логики приложения. Второе — исключения уровня защиты ОС от неверного функционирования программы. Ваше underflow exception — относится к первым. Такие не могут привести к вызову обработчика исключений на невыровненном стеке, поскольку места из которых они могут вылететь известны компилятору и он генерит код таким образом, чтобы исключения логики генерились на корректном стеке.

кт>Компилятор никаких исключений не генерирует. По определению исключение может произойти в любом месте. Автор статьи справедливо показывает, что попытка по текущему содержимому стека определить подпрограмму — это непродуманная ерунда. Например, идет подготовка параметров к вызову API. Выделили место, достаем очередной параметр — ошибка, неинициализированный указатель — исключение доступа. Опа, а в стеке еще просто мусор (туда еще ничего не поместили). Мусор начинает "исследоваться" ОС.
Исключения логики могут возникнуть при throw и при арифметике. Исключение доступа — это исключение, после которого не живут, а выживают. Без гарантий. Кроме того, как я понял проблема возникла с кодом, который был сгенерен доморощенным PL/1 компилятором. Фиксить ее нужно было начинать с того, чтоб после каждой инструкции сгенеренного кода RSP оставался выровненным на 8 байт. И не забывать генерить правильные прологи/эпилоги функций.

O>>Потому что есть соглашения об адекватном валидном коде, которые несколько выше чем "аппаратно". И согласно которым стек должен всегда оставаться выровненным на 8 (а при вызове публичной функции, в ее прологе — на 16). Если он таковым вдруг не стал — значит код не валидный. Время любителей писать выверты на асме прошло. Теперь балом правит кодогенератор.

кт>Выше "аппаратного" ничего нет. Требования кратности 16 дает не соглашение, а всего лишь используемые команды типа
"Выше" аппаратного имеется ввиду по соглашениям. Впрочем кулхакерам, привыкшим писать на асме пофиг на соглашения, они такого понятия не понимают..

кт>movdqa xmm0,xmmword ptr [rsp+20h] — обращение к XMM и памяти. Они дают аппаратное исключение из-за невыровненного доступа.

кт>Смешной штрих: чтобы внутри вызова стек был кратен 16, снаружи вызова он должен быть НЕ кратен 16.
кт>Но автор статьи не из-за этого пострадал. Он согласен со всеми требованиями вызова функций. Но ему-то хотелось в момент исключения пошагового режима обращаться со стеком как он привык в Win32. А исключение пошагового режима превращается в фатальное из-за того, что какой-то чудак на букву М не может поверить, что в этот момент (просто выполнение очередной команды) стек используется не так как при вызове API.
SetUnhandledExceptionFilter* не предназначена для реализации дебаггера, всем кто в теме давно известно что она ненадежна. Для дебаггера есть DebugAPI у коготорых нет таких проблем, т.к. они работают через LPC из соседнего процесса. Автор статьи пошел через реку вброд, хотя рямом был мост и жалуется**.

* Использование SetUnhandledExceptionFilter для дебаггера тем более странно, что оно не ловит уже пойманные исключения. Полагаю AddVectoredExceptionHandler для самодебаггера было бы более адекватным решением, хотя все еще не адекватным. Установка обработчика через RtlAddFunctionTable на всевозможные адреса чревата сломом других обработчиков исключений, написанных не автором: в системном коде и в стороннем коде, который оказался в его процессе по каким либо причинам (например windows hooks dll, COM серверы). Кроме того однажды RtlAddFunctionTable может отказаться регистрировать такую функцию, т.к. ее диапазон перекрывается с уже зарегистрированными и будет совершенно права. Аккуратнее надо быть.
** Я сам ходил через реку вброд и реализовывал аналогичный механизм в одном исследовательском проекте для трейсинга софта, а в другом, неисследовательском — для реализации подкачки на уровне юзермода. Но в отличии от автора статьи я примерно понимал с чем имею дело и вместо того чтоб жаловаться на гнусный микрософт, не дающий кулхацкерам жизним, просто похукал ntdll!KiUserExceptionDispatcher.
Как много веселых ребят, и все делают велосипед...
Отредактировано 04.12.2016 11:18 ononim . Предыдущая версия . Еще …
Отредактировано 04.12.2016 11:08 ononim . Предыдущая версия .
Re[10]: особенности структурной обработки исключений в Win64
От: кт  
Дата: 04.12.16 12:16
Оценка:
Здравствуйте, ononim, Вы писали:

O>Да хоть и для банального memcpy.

Это ведь так банально с помощью SSE2. Почти как гланды автогеном через задницу.

O>Исключения логики могут возникнуть при throw и при арифметике. Исключение доступа — это исключение, после которого не живут, а выживают. Без гарантий.

Почему? Ну обратились к нулевому указателю, бывает. Зачем же сразу падать?

O>Кроме того, как я понял проблема возникла с кодом, который был сгенерен доморощенным PL/1 компилятором. Фиксить ее нужно было начинать с того, чтоб после каждой инструкции сгенеренного кода RSP оставался выровненным на 8 байт. И не забывать генерить правильные прологи/эпилоги функций.

Я так понял, что проблема была в организации передачи объектов-строк через стек. В Win32 все прерывания ( в том числе пошаговой отладки и контрольных точек) не требовали никакого выравнивания стека. В Win64 любое исключение требует выровненного стека – иначе принудительное завершение задачи. В любом трансляторе легко обеспечить любое выравнивание. Здесь же пришлось менять парадигму размещения строк в стеке и дорабатывать системную библиотеку, а не транслятор.

O>Впрочем кулхакерам, привыкшим писать на асме пофиг на соглашения, они такого понятия не понимают..

Такое впечатление, что у Вас к ассемблеру что-то личное…

O>SetUnhandledExceptionFilter* не предназначена для реализации дебаггера, всем кто в теме давно известно что она ненадежна. Для дебаггера есть DebugAPI у коготорых нет таких проблем, т.к. они работают через LPC из соседнего процесса. Автор статьи пошел через реку вброд, хотя рямом был мост и жалуется**.

В Win32 все было надежно. На все исключения есть встроенная реакция PL/1 run-time библиотеки, т.е. в язык уже была изначально встроена структурная обработка исключений. Поэтому автору и нужны были все исключения, а не только отладка. И таки-да, в пресловутом PL/1 все исключения делятся на фатальные и нефатальные. Отличия – что делать, если не задан обработчик. Фатальные – оканчивают задачу, нефатальные – продолжают.
«Исследования» стека автор легко (и законно) отключил, сторонние обработчики исключений идут в сад.
И проблема в Win64 осталась ровно одна – исключение есть, обработчик есть, стек в допустимых пределах, но обработчик вызывается только на выровненном на 8 стеке. Это противоречит принципу универсальности ОС, причем процессору на это выравнивание плевать, а одному кретину в микрософте – нет.

O>* Использование SetUnhandledExceptionFilter для дебаггера тем более странно, что оно не ловит уже пойманные исключения. Полагаю AddVectoredExceptionHandler для самодебаггера было бы более адекватным решением, хотя все еще не адекватным.

Это все было в Win32 без нареканий. Теперь SetUnhandledExceptionFilter не используется.

O>Установка обработчика через RtlAddFunctionTable на всевозможные адреса чревата сломом других обработчиков исключений, написанных не автором: в системном коде и в стороннем коде, который оказался в его процессе по каким либо причинам (например windows hooks dll, COM серверы). Кроме того однажды RtlAddFunctionTable может отказаться регистрировать такую функцию, т.к. ее диапазон перекрывается с уже зарегистрированными и будет совершенно права. Аккуратнее надо быть.

Еще раз – все исключения должны проходить через системную библиотеку по философии языка.

O>** Я сам ходил через реку вброд и реализовывал аналогичный механизм в одном исследовательском проекте для трейсинга софта, а в другом, неисследовательском — для реализации подкачки на уровне юзермода. Но в отличии от автора статьи я примерно понимал с чем имею дело и вместо того чтоб жаловаться на гнусный микрософт, не дающий кулхацкерам жизним, просто похукал ntdll!KiUserExceptionDispatcher.

Я разговаривал вчера с Караваевым на эту тему. Все давным-давно сделано и нормально работает. Но, говорит, дополнительных команд в системную библиотеку пришлось много добавить. И не только из-за одной этой проверки. Еще INTO выкинули, двоично-десятичную арифметику похерили. Кому они в х86-64 мешали? Микрософт здесь вроде не причем
Re[11]: особенности структурной обработки исключений в Win64
От: ononim  
Дата: 04.12.16 15:48
Оценка: +2
O>>Да хоть и для банального memcpy.
кт>Это ведь так банально с помощью SSE2. Почти как гланды автогеном через задницу.
Если это выгоднее, почему нет?

O>>Исключения логики могут возникнуть при throw и при арифметике. Исключение доступа — это исключение, после которого не живут, а выживают. Без гарантий.

кт>Почему? Ну обратились к нулевому указателю, бывает. Зачем же сразу падать?
Ну например, в С null pointer dereferencing — это undefined behaviour. Но вобщем-то на уровне системы — это конечно же обрабатываемая ситуация. Но для этого нужно играть по правилам. Законы физики позволяют автомобилю ехать как угодно, но все почемуто едят по дорогам. Когда ктото выезжает с дороги и наматывается на столб никто не винит создателей дорог/автомобилей. Сам виноват. Так и в этом случае — то что разрешает процессор — это законы физики. Но есть и более высокоуровневые правила, как то корректность прологов/эпилогов, обязательное выравнивание стека в любой точке исполнения программы. Процессор позволяет их нарушать, но функционал, предоставляемый ОС предполагает что те кто его используют — таки следуют правилам. Ну а раз не следует и их намотало на столб — сами виноваты.

кт>Я так понял, что проблема была в организации передачи объектов-строк через стек. В Win32 все прерывания ( в том числе пошаговой отладки и контрольных точек) не требовали никакого выравнивания стека. В Win64 любое исключение требует выровненного стека – иначе принудительное завершение задачи. В любом трансляторе легко обеспечить любое выравнивание. Здесь же пришлось менять парадигму размещения строк в стеке и дорабатывать системную библиотеку, а не транслятор.

Классическое "оно же у меня работало". Вы полагались на недокументированные особенности имплементации на определенной платформе. Теперь они изменились. Более того — они могут измениться в любой момент, с любым аптейтом.

O>>Впрочем кулхакерам, привыкшим писать на асме пофиг на соглашения, они такого понятия не понимают..

кт>Такое впечатление, что у Вас к ассемблеру что-то личное…
У меня просто неплохой такой опыт в хаканье, хуканье, асме/дизасме и прочем этом болоте, потому я крайне не разделяю решения "а давайте сделаем так как работает, оно же работает". Вначале нужно читать документацию и делать сразу как полагается. И лишь если если так как полагается не работает как хочется — тогда уже думать о хаках.

кт>В Win32 все было надежно. На все исключения есть встроенная реакция PL/1 run-time библиотеки, т.е. в язык уже была изначально встроена структурная обработка исключений. Поэтому автору и нужны были все исключения, а не только отладка. И таки-да, в пресловутом PL/1 все исключения делятся на фатальные и нефатальные. Отличия – что делать, если не задан обработчик. Фатальные – оканчивают задачу, нефатальные – продолжают.

кт>«Исследования» стека автор легко (и законно) отключил, сторонние обработчики исключений идут в сад.
кт>И проблема в Win64 осталась ровно одна – исключение есть, обработчик есть, стек в допустимых пределах, но обработчик вызывается только на выровненном на 8 стеке. Это противоречит принципу универсальности ОС, причем процессору на это выравнивание плевать, а одному кретину в микрософте – нет.
Не стоит путать свои хотелки и "принципы универсальности ОС".

O>>Установка обработчика через RtlAddFunctionTable на всевозможные адреса чревата сломом других обработчиков исключений, написанных не автором: в системном коде и в стороннем коде, который оказался в его процессе по каким либо причинам (например windows hooks dll, COM серверы). Кроме того однажды RtlAddFunctionTable может отказаться регистрировать такую функцию, т.к. ее диапазон перекрывается с уже зарегистрированными и будет совершенно права. Аккуратнее надо быть.

кт>Еще раз – все исключения должны проходить через системную библиотеку по философии языка.
Системная библиотека — в смысле рантайм конкретно этого языка, который работает ПОД ОС Windows? В таком случае это нарушает принцип разделения ответственности. Так как юзая функционал ОС — вызывая системные функции, пользуясб COM интерфейсом — вы должны полагаться на то что этот функционал является для вас черным ящиком. Своим сквозным перехватом исключеный вы вмешиваетесь в его работу. В итоге некоторые системные функции будут работать не так как следует. К примеру LookupAccountSid вместо возврата кода ошибки может бросить исключение, т.к. внутри она юзает RPC, а RPC внутри себя юзает механизм SEH. Таким образом, замкнув на себя обработку исключений, вы рискуете сломать чужой код, который работает в вашем процессе. Это не ДОС. Ваш далеко не единственный в системе. Но разумеется у программиста все всегда будет работать. Проблемы вылезут у юзеров.
Как много веселых ребят, и все делают велосипед...
Отредактировано 04.12.2016 15:49 ononim . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.