За последние две-три недели узнал много нового…
За последние две-три недели узнал много нового про архитектуру amd64. Во-первых, я наткнулся сначала на документ, а затем на аналогичный раздел в MSDN, где толково описаны не только соглашения о вызовах (этого добра везде навалом), но и дано детальное описание того, как должен выглядеть стек функции, как происходит раскрутка стека, что такое и зачем нужны пролог и эпилог функции. Полезный документ, особенно если нужно разобраться как работает stack unwinder.
Во-вторых, пришлось плотно познакомиться с тем, как именно работает переключение контекста на amd64 и x86, на кой нужны trap и exception frames и сопутствующие прелести ядра NT. С удивлением узнал, например, что функция переключения контекста работает совсем не так, как я себе это раньше представлял. То есть я думал, что когда происходит переключение контекста, то сначала сохраняется состояние регистров процессора для текущего потока, переключаются стеки и сопутствующие структуры, а затем восстанавливается состояние регистров нового потока. Оказалось, что регистры сохранять/восстанавливать по большей части не нужно, так как они сохраняются автоматически в силу выполнения правил соглашения о вызовах. Часть регистров сохраняется вызывающей функцией (любой), друга часть – вызываемой (тоже любой) и на долю функции переключения контекста практически ничего не остаётся.
В общем, я сейчас в состоянии «О сколько нам открытий чудных…». Только времени на написание постов в журнал не остаётся.
Что-то насчёт сохранения контекста непонятно:
1) А если приложение на соглашения положит? Это же всего лишь MSVC так работает, а вообще велосипед в каждом компиляторе свой. Это же не IA64 на котором действительно с регистрами и вызовами функций всё очень интересно.
2) Вот внутри произвольной функции стоит: mov ebx, 3. И тут пришла пора переключить поток. Кто этот ebx сохранит?
Тогда это приложение работать не будет. Это все равно, как если бы прилоение решило параметры функций в другом порядке указывать.
При переходе в kernel mode на стеке будет создан trap frame, куда ebx и сохранится. На amd64 возможны варианты. Вообще-то поскольку rbx – non volatile, он сохраняется в exception frame, но если ситуации когда он сохраняется и в trap frame.
Получается, что к моменту непосредственного переключения большинство регистров уже сидит в стеке.
Т.е. у приложений отобрали их законное право вытворять со своим стеком что хотят? Как-то не верится. Всякие cconv всё таки придуманы не только под процессор, но и под языки.
Ну то есть сохранение всё таки есть, просто в другом куске ядра.
Не совсем так. Приложение может вытворять со своим стеком все, что ему вздумается. Но в результате:
1. Отладчик не покажет корректный корректный стек. Это верно и для x86, но в отличие от x64 там позволяются куда большие вольности;
2. Не будет корректно работать stack unwinding при обработке исключения. Плохо в этом случае будет исключительно самому приложению.
Ну и понятное дело, что при вызовах системных API параметры должны идти в правильном порядке.
Есть конечно. Только работает оно совсем не так, как я себе представлял.