Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Низкоуровневое программирование > Assembler - Ассемблер (FASM, MASM, WASM, NASM, GoASM, Gas, RosAsm, HLA) и не рекомендуем TASM
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 18.12.2014, 20:30   #1
Полный 30h
Пользователь
 
Аватар для Полный 30h
 
Регистрация: 10.12.2011
Сообщений: 26
По умолчанию Соглашение о вызове функций (выравнивание стека)

Доброго дня.
Хотелось бы поделиться одним замечанием, мыслью, наблюдением, моей непоняткой ( нужное подчеркнуть). Касается вопроса о Соглашении вызова функций (в том числе вызов функций API) при 64 программировании. А именно Выравнивание стека.



Относится к коду примера из книги «Программирование на ассемблере на платформе х86-64» автор Руслан Аблязов. К коду примера 64 битной программы в теме «Особенности кодинга под x64» по адресу FAQ для раздела Assembler, MASM, TASM И одному утверждению murderer в теме «Особенности кодинга под x64» по адресу FAQ для раздела Assembler, MASM, TASM

Пара отступлений:
1. Долгое время меня этот вопрос волновал слабо в виду того что пользовал программу FASM Editor 2.0 где были забиты основные болванки под прожки (64/32/16, окна/консоль-мансоль) от которых я собственно отталкивался не напрягая себя знаниями по поводу того зачем все эти закорючки вначале и в конце. А тут вдруг решил углубиться. Ну и углубился...
2. Изучение мною ассемблера носит бессистемный характер и протекает под девизом Быдлокодерство как высшая форма рукоблудства. Поэтому на истину не претендую.
3. Отсылка к примеру из книги Аблязова носит условный характер в виду того что скачена была мной в интернетах под этим названием. Является ли она таковой на самом деле ручаться не берусь.
4. Рассматриваемые мной примеры условно считались как примеры программ под Windows и проверялись на Windows 7. Компилировались при помощи FASM

Теперь к сути
Цитата:
murderer
Выравнивание стека
Перед вызовом функции rsp должен быть кратен 16. Таким образом если ваша функция имеет чётное количество параметров и количество параметров больше 4, при передаче ей управления rsp будет кратен 8, иначе rsp кратен 16. В FASM макросы .code/.end автоматически добавляют "sub rsp,8" в точке входа для выравнивания стека.
В миру murderer должно быть работает юристом, потому как со второго предложения смысл прочитанного от меня безнадёжно ускользнул. Однако первое предложение как бы ясно.

Цитата:
Аблязов
Выравнивание стека Следует помнить, что стек всегда должен быть выровненным на границу 16 байт, т. е. указатель в RSP должен быть кратен 16. Это требование не очевидное, но тем не менее так нам говорит документация. В большинстве случаев отсутствие выравнивания на 16 байт не повлечет за собой никаких последствий, однако некоторые функции вызовут исключение именно по этой причине. В общем случае отсутствие выравнивания уменьшает производительность приложения.
пример в книге Аблязова так и вовсе песня.
Код:
;Итак, например, у нас есть функция и она принимает шесть параметров: 
;function (int 1, reall, int2, real2,int 3,real 3) - три целочисленных параметра 
;и три параметра с плавающей запятой. В соответствии с соглашением вызова в 
;Win64 вызов произойдёт следующим образом:
 
push геа13
push int3
R9=real2, XMM3L=real2
R8=int2
RDX= reall, XMMlL=reall
RCX=intl
RSP= RSP-32
; Вызов функции
; Очистка стека (если это необходимо)
т.е. резервирование стека под первые четыре параметра отсутствует как класс

А это место навеяло грусть
Цитата:
Тем не менее все эти знания вряд ли нам пригодятся, т. к. в компиляторе FASM уже есть необходимый набор макросов, позволяющий одной строчкой производить вызов функций и максимально облегчающий написание процедур и функций, отвечающих модели вызова в fastcall Win64.
Книга позиционируется как пособие для изучения ассемблера, на минуточку.

В коде исходника тоже всё как то для меня сложилось уныло. Фрагменты
Аблязов
Код:
format PE64 GUI 5.0
entry start
include 'win64a.inc'
section '.data' data readable writeable
   text  db 'Hello world!',0
section '.code' code readable executable
start:
    sub rsp, 32
    mov rcx, 0
    mov rdx, text
    mov r8,  text
    mov r9, 0
    call [MessageBox]
    xor rcx, rcx
    call [ExitProcess]
; и т.д.
Собирается но не работает. В принципе как бы зная свою рукожопость в плане компиляции сперва подумал может упускаю чего, однако в исходниках имеется так же и собранный без меня экзешник. Не работает сволочь тоже.

Продолжение ниже (не влезло в 5000 символов)
Меня постоянно преследуют умные мысли, но я быстрее!
Полный 30h вне форума Ответить с цитированием
Старый 18.12.2014, 20:30   #2
Полный 30h
Пользователь
 
Аватар для Полный 30h
 
Регистрация: 10.12.2011
Сообщений: 26
По умолчанию

Mikl___
Код:
; создать кодовую секцию с атрибутами на чтение и исполнение
section '.code' code readable executable
start:  mov        r9d,0                   ; uType == MB_OK (кнопка по умолчанию)
                                           ; аргументы по соглашению x86-64
                                           ; передаются через регистры, не через стек!
                                           ; префикс d задает регистр размером в слово,
                                           ; можно использовать и mov r9,0, но тогда
                                           ; машинный код будет на байт длиннее
        lea        r8,[_caption]           ; lpCaption, передаем смещение
                                           ; команда lea занимает всего 7 байт,
                                           ; а mov reg,offset - целых 11, так что
                                           ; lea намного более предпочтительна
        lea        rdx,[_message]          ; lpText, передаем смещение выводимой строки
        mov        rcx,0                   ; hWnd, передам дескриптор окна-владельца
                                           ; (можно также использовать xor rcx,rcx
                                           ; что на три байта короче)
        call       [MessageBox]            ; вызываем функцию MessageBox
        mov        ecx,eax                 ; заносим в ecx результат возврата
                                           ; (Функция ExitProcess ожидает 32-битный параметр;
                                           ; можно использовать и mov rcx,rax, но это будет
                                           ; на байт длиннее)
        call       [ExitProcess]           ; вызываем функцию ExitProcess
Собирается но не работает.

Отчаялся бы я совершенно на ниве самообразования если бы не два обстоятельства - Гугл и Пирогов. Захожу я через первы в ЖЖ ко второму и что я вижу?! А вижу я рабочий код, внятное пояснение почему он рабочий, а так же понимание того почему всё что сверху мной приведено нихрена не работало.

Цитирую Пирогова
Цитата:
В 64-битовых Windows принята следующая конвенция вызова функций (в том числе вызов функций API).
1. Первые четыре параметра передаются в функцию через регистры: rcx, rdx, r8, r9. Остальные параметры (если они есть) передаются через стек.
2. Перед вызовом функции резервируется область в стеке, на случай, если вызываемая функция "захочет" временно сохранить параметры в стеке. Таким образом, параметры, которые передаются через стек, помещаются туда после резервируемой области.
3. При передаче параметров, размер которых меньше 64 бит, передаются как 64-битовые параметры. При этом следует обнулить старшие биты. Параметры, большие 64-бит передаются по ссылке.
4. Данные возвращаются через регистр rax. Если возвращаемое значение имеет размер больший 64 бит, то данное передается через область памяти, адрес которой передается в первом параметре. Для возвращения может также использоваться регистр xmm0.
5. Все регистры при вызове функций сохраняются за исключением rax, rcx, rdx, r8, r9, r10, r11, сохранность которых не гарантируется.
6. Граница стека должна быть выровнена по адресу кратному 16.
Рассмотрим в общих чертах схему вызова функции API с пятью параметрами.
И код
Код:
...
sub rsp,40 ; резервируем стек
mov qword ptr [rsp+32],par5
mov r9,par4
mov r8,par3,
mov rdx,par2
mov rcx,par1
call f_api64
...
add rsp,40 ;восстанавливаем стек
...
Сперва подумалось - то ли я дурак, то ли Пирогов, на 16 делить не умеет, но следующая после примера фраза поставила всё на свои места

Обратите внимание, что в нашем случае стек с учетом адреса возврата при вызове функции оказывается выровненным на величину кратную 16 (48 байт).

После этого собственно так же появилось понимание того за каким хреном в болванке программы на 64 бита от FASM Editor стоит строчка sub rsp,8
Потому что если верить IDA pro макрос invoke FASMa не равняет стек под 16 байтовую кратность с учётом адреса возврата вызываемой функции.

Срезюмирую.
Выравнивать стек с кратностью 16 нужно не перед вызовом функции, а с учётом вызова, т.е. перед вызовом он как раз таки не должен быть кратен 16, но кратен 8. Тогда при вызове функции адрес возврата в аккурат его подравняет.

З.Ы. Столпы апологеты, сильно не пинайте. Хотел внести ясность прежде всего для себя. В чём не прав укажите. Если конечно кто то это кирпич текста осилит.
Меня постоянно преследуют умные мысли, но я быстрее!
Полный 30h вне форума Ответить с цитированием
Старый 19.12.2014, 10:37   #3
Mikl___
Участник клуба
 
Регистрация: 11.01.2010
Сообщений: 1,139
По умолчанию

Полный 30h,
алаверды!
У меня 64-разрядная профессиональная Windows 7 и Intel Pentium CPU G860 3.00GHz
пакет masm64 с набором lib- и inc-файлов для создания 64-разрядных приложений на ассемблере скачан с сайта http://dsmhelp.narod.ru/environment.htm, ml64 и link выдернуты из Windows Driver Kits Version 7.1.0 (WDK), содержимое папки masm64\bin
Код:
link.exe
ml64.exe
msobj80.dll
mspdb80.dll
msvcp80.dll
msvcp90.dll
msvcr80.dll
msvcr90.dll
создаю msgbox.asm
Код:
OPTION DOTNAME
option casemap:none
include temphls.inc
include win64.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib
OPTION PROLOGUE:rbpFramePrologue
.code
WinMain proc 
	sub rsp,5*8
        invoke MessageBox,NULL,&MsgBoxText,&MsgCaption,MB_OK
        invoke ExitProcess,NULL
WinMain endp
MsgCaption      db "Iczelion's tutorial #2",0
MsgBoxText      db "Привет, Полный 30h!",0
end
и asm.bat
Код:
set filename=msgbox
set masm64_path=c:\masm64\
%masm64_path%bin\ml64 /I"%masm64_path%Include" %filename%.asm /link /subsystem:windows /LIBPATH:"%masm64_path%Lib" /entry:WinMain
del %filename%.obj
del mllink$.lnk
запускаю, получаю ехе, которое работает
внутренности msgbox.ехе через hiew32
Код:
sub rsp,28h
xor ecx,ecx
xor r9d,r9d
lea rdx,[14000103Ch]
lea r8,[140001025h]
call MessageBoxA
xor ecx,ecx
call ExitProcess
P.S. с сохранением картинок и zip-файлов почему-то проблемы
P.P.S. Лучше читать не меня (Криса Касперски/Руслана Аблязова/ murderer'a), а статью Мэтта Питрека "Everything You Need To Know To Start Programming 64-Bit Windows Systems" ("Все, что нужно знать, чтобы начать программировать для 64-разрядных версий Windows") и msdn (Программные соглашения x64)

Последний раз редактировалось Mikl___; 19.12.2014 в 11:38.
Mikl___ вне форума Ответить с цитированием
Старый 19.12.2014, 12:12   #4
Полный 30h
Пользователь
 
Аватар для Полный 30h
 
Регистрация: 10.12.2011
Сообщений: 26
По умолчанию

Вопрос снят.
1. У меня с головой всё в порядке.
По ссылке из твоей ссылки нашёл
Цитата:
The stack must be kept 16-byte aligned. Since the "call" instruction pushes an 8-byte return address, this means that every non-leaf function is going to adjust the stack by a value of the form 16n+8 in order to restore 16-byte alignment.
2. Столпы играют в жрецов и не хотят делится сакральными знаниями с простыми людями

3. MASM макрос invoce для 64 бит не выделяет сам стек и не возвращает его после функции - фи!
Меня постоянно преследуют умные мысли, но я быстрее!

Последний раз редактировалось Полный 30h; 19.12.2014 в 12:16.
Полный 30h вне форума Ответить с цитированием
Старый 19.12.2014, 12:22   #5
Mikl___
Участник клуба
 
Регистрация: 11.01.2010
Сообщений: 1,139
По умолчанию

Полный 30h,
Цитата:
1. У меня с головой всё в порядке
Кто-то что-то говорил о голове?
Цитата:
2. Столпы играют в жрецов и не хотят делится сакральными знаниями с простыми людями
Поклеп и наглая ложь переходящая все мыслемые границы, когда это жрецы не делились своими знаниями?
Mikl___ вне форума Ответить с цитированием
Старый 19.12.2014, 12:37   #6
Полный 30h
Пользователь
 
Аватар для Полный 30h
 
Регистрация: 10.12.2011
Сообщений: 26
По умолчанию

Mikl___
Цитата:
Кто-то что-то говорил о голове?
Я ж наивный, всему верю. написано выравнивать по 16 до вызова функции ,значит так оно и есть. А тут такая несрастуха вдруг возникла. Вот я и решил первым делом здоровьем озаботится, заодно и догадки проверить.

Лучше ответь мне на вот такой вопрос. Макросы (конкретно для FASM) это вообще к чему в плане программирования относится? В MASM смотрю вообще с ними уныло, даже стек самому выделять приходится. Реально вообще написать следующее. Ставлю макрос условно Start и Stop а он во всех функциях между ними ищет функцию с самым большим кол-вом параметров, соответственно вместо Start просаживает на это число стек (с учётом 16n+8), а в самих включённых invoke только параметры распихивает, а стек не просаживает и не возвращает?
Меня постоянно преследуют умные мысли, но я быстрее!
Полный 30h вне форума Ответить с цитированием
Старый 19.12.2014, 14:48   #7
Mikl___
Участник клуба
 
Регистрация: 11.01.2010
Сообщений: 1,139
По умолчанию

Цитата:
Макросы (конкретно для FASM) это вообще к чему в плане программирования относится? В MASM смотрю вообще с ними уныло, даже стек самому выделять приходится.
Почему "уныло"? Пример, который ты приводишь на FASM написанный с использованием макросов инвок
Код:
format PE64 GUI 5.0
entry start
include 'win64a.inc'
section '.text' code readable executable
 
  start:
   invoke MessageBoxA,0,msgBoxText,msgBoxCaption,0
   invoke ExitProcess,0
под отладчиком выглядит как
Код:
    sub rsp,20h
    mov rcx,0
    mov rdx,402000;"Win64 Assembly with FASM is Great!"
    mov r8,402023;"Iczelion Tutorial #2:MessageBox"
    mov r9,0
    call MessageBoxA
    add rsp,20h
    sub rsp,20h
    mov rcx,0
    call ExitProcess
    add rsp,20h
  1. чего стоят последовательно выполненные add rsp,20h/sub rsp,20h сводящие на нет результат выполнение друг друга
  2. есть ли смысл выполнять add rsp,20h после ExitProcess ведь к тому времени программа уже закончится
Стоит упомянуть макрос vadimych который находится в F.A.Q.
Код:
invoke macro function,args:VARARG
local txt,arg,arg1
txt textequ <>
cnt = 0
 
for arg,<args>
txt catstr <arg>,<!,>,txt
cnt = cnt+1
endm
 
n = cnt
k=n mod 2
 
IF n gt 4
sub rsp,(n+k)*8
ELSE
sub rsp,20h
ENDIF
 
% for arg,<txt>
cnt = cnt-1
m=0
 
len sizestr <arg>
addr_ instr 1,<arg>,<addr>
offset_ instr 1,<arg>,<offset>
 
IF addr_ eq 1
arg1 substr <arg>,5,len-4
lea rax,arg1
m=1
 
ELSEIF offset_ eq 1
IF cnt gt 4
arg1 substr <arg>,7,len-6
lea rax,arg1
m=1
ENDIF
ENDIF
 
IF cnt eq 0
IF m eq 0
mov rcx,arg
ELSE
mov rcx,rax
ENDIF
 
ELSEIF cnt eq 1
IF m eq 0
mov rdx,arg
ELSE
MOV rdx,rax
ENDIF
 
ELSEIF  cnt eq 2
IF m eq 0
mov r8,arg
ELSE
mov r8,rax
ENDIF
 
ELSEIF cnt eq 3
IF m eq 0
mov r9,arg
ELSE
mov r9,rax
ENDIF
 
ELSE
IF m eq 1
mov [rsp+(cnt)*8],rax
ELSE
mov qword ptr [rsp+(cnt)*8],arg
ENDIF
ENDIF
endm
 
call function
IF n gt 4
add rsp,(n+k)*8
ELSE
add rsp,20h
ENDIF
endm
Пример использования:
Код:
includelib f:\masm32\lib64\kernel32.lib
include f:\masm32\include64\win64.inc
include f:\masm32\include64\rtl.inc
include f:\masm32\macros\x64.mac
 
extrn CreateProcessA:proc
extrn CloseHandle:proc
 
.data
cmd db 'c:\windows\system32\cmd.exe',0
pi PROCESS_INFORMATION <>
 
.code
start proc uses rbx rdi rsi
local stin:STARTUPINFO
 
invoke CreateProcessA,offset cmd,0,0,0,0,0,0,0,        addr           stin,   offset  pi
 
invoke CloseHandle,pi.hThread
invoke CloseHandle,pi.hProcess
 
ret
start endp
end
макросы masm64
Для упрощения использования операций в файле ksamd64.inc определен набор макросов, которые можно использовать для создания типичных прологов и эпилогов процедур.
Код:
              Макрос | Описание 
---------------------+-------------------------------
alloc_stack(n)       |Выделяет кадр стека размером в n байт (с помощью команды "sub rsp, n") 
                     |и помещает соответствующую информацию для раскрутки (".allocstack b").
save_reg reg, loc    |Сохраняет защищенный регистр "reg" в стеке по RSP-смещению "loc" и 
                     |помещает соответствующую информацию для раскрутки (".savereg reg, loc").
push_reg reg         |Сохраняет защищенный регистр "reg" в стеке и помещает соответствующую 
                    |информацию для раскрутки (".pushreg reg").
rex_push_reg reg    |Сохраните слаболетучий регистр в стеке использование внедрения 2 байт, 
                    |и выведите соответствующее размотайте сведения (reg .pushreg) это должно 
                    |использоваться, если внедрения первая инструкция в функции убедиться, что 
                    |функция высокий - patchable.
save_xmm128 reg, loc|Сохраняет защищенный XMM-регистр "reg" в стеке по RSP-смещению "loc" 
                     |и помещает соответствующую информацию для раскрутки (".savexmm128 reg, loc").
set_frame reg, offset|Присваивает регистру стекового кадра "reg" значение RSP + offset 
                     |(с помощью команды mov или lea) и помещает соответствующую информацию 
                     |для раскрутки (".set_frame reg, offset").
      push_eflags    |Сохраняет регистр "eflags" в стек с помощью команды pushfq и помещает 
                     |соответствующую информацию для раскрутки (".alloc_stack 8").
Ниже представлен пример пролога функции, в котором должным образом используются описанные макросы.
Код:
SkFrame struct 
Fill    dq ?; fill to 8 mod 16 
SavedRdi dq ?; saved register RDI 
SavedRsi dq ?; saved register RSI 
SkFrame ends
 
sampleFrame struct
Filldq?; fill to 8 mod 16
SavedRdidq?; Saved Register RDI 
SavedRsi  dq?; Saved Register RSI
sampleFrame ends
 
sample2 PROC FRAME
alloc_stack(sizeof sampleFrame)
save_reg rdi, sampleFrame.SavedRdi
save_reg rsi, sampleFrame.SavedRsi
.end_prolog
 
; function body
 
mov rsi, sampleFrame.SavedRsi[rsp]
mov rdi, sampleFrame.SavedRdi[rsp]
 
; Here’s the official epilog
 
add rsp, (sizeof sampleFrame)
ret
sample2 ENDP

Последний раз редактировалось Mikl___; 19.12.2014 в 15:38.
Mikl___ вне форума Ответить с цитированием
Старый 19.12.2014, 15:46   #8
Mikl___
Участник клуба
 
Регистрация: 11.01.2010
Сообщений: 1,139
По умолчанию

дурацкое ограничение в 5000 символов
Вывод: хочешь нормальную программу -- пиши макросы самостоятельно
Mikl___ вне форума Ответить с цитированием
Старый 19.12.2014, 20:15   #9
Полный 30h
Пользователь
 
Аватар для Полный 30h
 
Регистрация: 10.12.2011
Сообщений: 26
По умолчанию

Цитата:
под отладчиком выглядит как
Это я что бы кирпич потоньше был в примере, а так уже подучили, что несколько функций надо обрамлять frame/endf, тогда промежуточная дерготня стека исключается при компиляции.

Цитата:
Вывод: хочешь нормальную программу -- пиши макросы самостоятельно
Вооот! Я и пытался узнать по макросам что нибудь структурированное почитать есть. А то в основном везде мимоходом на простеньких примерах.
Меня постоянно преследуют умные мысли, но я быстрее!
Полный 30h вне форума Ответить с цитированием
Старый 20.12.2014, 09:14   #10
Mikl___
Участник клуба
 
Регистрация: 11.01.2010
Сообщений: 1,139
По умолчанию

Полный 30h,
в моём учебнике целая глава, будет здорово, если еще и на вопросы в конце главы ответишь...
Mikl___ вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Delphi: аппроксимация функций методом базиса из финитных функций Denna Помощь студентам 1 12.03.2012 19:23
Лицензионное соглашение Photoshop CS4 VistaSV30 Софт 5 13.12.2009 10:21
Соглашение о кодировании .NET SunKnight Общие вопросы .NET 3 08.11.2009 19:01
Лицензионное соглашение... Jupiter Свободное общение 18 03.05.2009 10:39
использование функций в качестве параметров других функций mono Помощь студентам 0 20.04.2009 18:25