В х64 соглашение __fastcall. Аргументы в апи передаются через регистры (rcx, rdx, r8, r9), потом через стек. Я беру АПИ из кернел32 в windows7. Загоняю аргументы в стек. Код (Text): ;;;;;;;; PUSH REGS ;;;;;;;;;;;; xor rcx, rcx xor rax, rax push rax push rcx ;;;;;;;; API CALLING ;;;;;;;;;;;; mov rcx, [arg1] mov rdx, [arg2] mov r8, [arg3] mov r9, [arg4] call VirtualProtect ; после вызова rbp и rsp те же, что и до вызова ;;;;;;;; POP REGS ;;;;;;;;;;;;;;; pop rax pop rcx Объясните, почему из стека достаются уже не нулевые регистры? Я отладчиком прошелся по функции, там действительно бьется стек. На прологе функции код вида: Код (Text): push rbp mov rbp, rsp mov [rbp+0x18], rcx Насколько я понимаю, в х64 теперь по другому со стеком работать нужно. Расскажите, где можно прочитать по устройство стека в х64 и как теперь с ним работать, если перед вызовом АПИ нужно сохранить пару регистров?
dermatolog Я читал, но все никак не мог понять, что такое stack-backing. На примере вашего кода понял. Т.е., если АПИ принимает 3 аргумента, то перед вызовом нужно делать вот так: sub rsp, _ALIGNUP(8*3, 0x10) Или выравнивание не обязательно в данном случае? И наверное глупый вопрос, но для чего это сделали? Зачем фаст-кол, если все-равно стек юзается..?
И еще. Я так понимаю, далеко не все компиляторы знают про stack-backing? stack-backing применим для всех _fastcall функций в х64, либо только для системных АПИ?
Конвенция x64 более всего похоже на 32-битную Cишную. А так на практике лучше не делать. Код (Text): sub rsp, 0x20 call VirtualProtect add rsp, 0x20 Перед call`ом в рантайме стэк должен быть выровнен на границу 16 байт, и даже про push/pop`ы вне пролога/эпилога лучше забыть.
s_d_f Не. _cdecl x32 - стек не бьет. Там тот же _stdcall х32, только стек не очищается. После вызова к esp добавляем (ArgsNum*4). А тут странности (для меня). Так я и не юзаю push/pop. Это просто как пример. На деле голая функция и внутри функции вызов АПИ. После вызова бьется стек. А если делать, как сказал dermatolog, то стек не бьется. А бага всплыла, потому что не все компиляторы знают о stack-backing. Это уже выяснил. А так не хочется использовать классический студийный компилятор
при вызове х64-fastcall функции всегда подразумевается что ей передаётся 4 параметра. Даже если ей передаётся меньше четырёх, то ВСЁ РАВНО надо зарезервировать в стеке место под 4 параметра. чтобы функция могла сохранить параметры из rcx rdx r8 r9 в стек и вызвать другую функцию fastcall это только название, не более, никакой быстроты не подразумевается, хотя в некоторых случаях вызов происходит действительно быстрее чем stdcall. это сделано для унификации и стандартизации вызовов функций. к тому же резервирование места происходит обычной sub rsp, 32, а не 4хpush, изменить значение регистра в разы быстрее чем запихать в стек 4 параметра по 8 байт в win32 полный беспорядок по этой части, в win64 решили таким образом решить эту проблему раз и навсегда
Т.е. в простейшем случае свои функции на асме нужно оформлять так: 1. открываем фрейм 2. сохраняем регистры в стек 2. дополнительно отнимаем 0x20 от rsp 3. ..... тут идет тело функции с вызовами АПИ ...... 4. прибавляем к rsp 0х20 5. достаем регистры 6. закрываем фрейм 7. возвращаем управление
сохранять и доставать их надо только в том случае еслио ни нужны для работы. соглашение x64-fatcall не предсматривает обязательное сохранение регистров RCX, RDX, R8-R11
А что значит бьет, не бьет? Сохранение rcx, rdx, r8, r9. Так место под них должно еще в прологе выделяться, во всяком случае так делает Cишный компилятор.
s_d_f Вы бы умничали поменьше что-ли. Человеку нужно для начала со стеком разобраться, а вы его начинаете грузить про раскрутку стека при исключениях.
После нескольких push/pop`ов трудно сказать выровнен стэк на 16 байт или не выровнен, а конвенция этого требует. Первый-же вызов АПИ может все сохранения затереть.
s_d_f Перед тем как писать сюда всякую ерунду рекомендую ознакомиться с матчастью. Итак попорядку: Выравнивание стека на границу 16-ти байт совершенно не запрещает нам делать sub/add rsp, 0x20 вокруг вызова API (можете проверить это на практике). Все работает, не правда ли? Затереть где? В вершине стека? А 4 QWORD-а там как раз и сделаны для того чтобы его использовал вызываемый код (тот который мы вызываем) и логично полагать что в вызывающем коде эту область на стеке нельзя использовать под хранение каких либо данных. Вы видимо думаете как раз с точностью до наоборот. Ну а теперь на самое вкусное я вам подброшу еще одну тему, над которой вы можете подумать на досуге - почему в реальном коде действительно не используются push/pop кроме как в прологе а в эпилоге. Это все связано исключительно с обработкой исключений и с раскруткой стека, т.к. компилятор создается специальные структуры на уровне РЕ, которые парсятся операционкой на этапе обработки исключений и предполагается, что никто крое пролога и эпилога со стеком не балуется. Так что перед тем как умничать на форуме не плохобы почитать матчасть.
К каждому call`у add/sub цеплять. И это разве оптимально по скорости и по размеру кода? Да. Но add/sub`ами вы этого избегаете. Про это я не знал. Обработкой исключений не занимался, кроме как VEH`ами. Я и прочитал необходимый минимум.
более, того стек можно вообще не выравнивать, и всё равно программа будет работать. это не троллинг, просто к слову
я говорю про выравнивание на 8 байт. я не говорю про то что не выравнивать на 16, а просто если оставить как есть (не делать sub rsp, 8 в точке входа) то всё равно программа будет работать. но MessageBox я не тестил поэтому ничего сказать не могу