Существуют ли какие-либо директивы сборки для выравнивания определенных переменных данных стека?
Например, предположим, что функция MASM имеет эти локальные переменные с начальными значениями
LOCAL beginStack:QWORD ; ffffffffdeadbeef
LOCAL myLocalA:QWORD ; ffffffffffffffff
LOCAL myLocalB:QWORD ; 0000000000000000
LOCAL myArrayA[10]:BYTE ; AAAAAAAAAA
LOCAL myArrayB[10]:BYTE ; BBBBBBBBBB
LOCAL endStack:QWORD ; ffffffffbaadf00d
Стек памяти имеет такой макет, но обратите внимание, что endStack
смещен.
00000048`51effb60 baadf00d000906ec ; baadf00d
00000048`51effb68 42424242ffffffff ; ffffffff
00000048`51effb70 4141424242424242
00000048`51effb78 4141414141414141
00000048`51effb80 0000000000000000
00000048`51effb88 ffffffffffffffff
00000048`51effb90 ffffffffdeadbeef
Чтобы выровнять endStack
, я попытался смешать локальные переменные с выравниванием pad[4]
LOCAL beginStack:QWORD
LOCAL myLocalA:QWORD
LOCAL myLocalB:QWORD
LOCAL myArrayA[10]:BYTE
LOCAL myArrayB[10]:BYTE
LOCAL pad[4]:BYTE
LOCAL endStack:QWORD
который правильно выравнивает endStack
0000005b`950ff950 ffffffffbaadf00d ; aligned
0000005b`950ff958 42424242ffdaf38f ; pad[4] is ffdaf38f
0000005b`950ff960 4141424242424242
0000005b`950ff968 4141414141414141
0000005b`950ff970 0000000000000000
0000005b`950ff978 ffffffffffffffff
0000005b`950ff980 ffffffffdeadbeef
Другой подход (если применимо) состоит в том, чтобы перетасовать переменные стека на основе нисходящей иерархииQWORD
-> DWORD
-> WORD
-> BYTE
Вопрос
В GCC есть этот __attribute__ ((aligned (8)))
для выравнивания переменных, но есть ли эквивалентный метод для языков ассемблера?
Кажется, что языки более высокого уровня, такие как C/C++, имеют большой набор инструментов с хорошими приемами оптимизации, но, к сожалению, не перенесены на языки ассемблера более низкого уровня.
Я думаю, один из способов — просто поместить всех местных жителей в struc
. struc
может принимать значение выравнивания (например, 8 или QWORD) и соответствующим образом выравнивать элементы. Это немного дополнительная работа, но это сработает. Пример здесь: pastebin.com/4cLWm0f1
@Michael Petch Спасибо за эту очень хорошую оригинальную идею!
Частичный ответ на данный момент заключается в определении макроса MASM aligned
, который вставляет байты заполнения как в переменные DWORD, так и в переменные WORD, чтобы они выровнялись по 8 байтам в 64-битном режиме.
Этот грубый макрос принимает переменную DWORD или WORD, а затем определяет количество байтов заполнения. Чтобы предотвратить ошибки дублирования символов, он определяет локальный num
, который генерирует уникальные метки каждый раз, когда вызывается макрос. Результатом является сама локальная переменная, за которой следует дополнение: LOCAL pad??0001[4]
для DWORD или LOCAL pad??0001[6]
для WORD.
aligned MACRO var
LOCAL num
IF @InStr(1,<var>,<:DWORD>) NE 0
padBytes = 4
ELSEIF @InStr(1,<var>,<:WORD>) NE 0
padBytes = 6
ENDIF
var
@CatStr(<LOCAL pad>,<num>,<[padBytes]:BYTE>)
ENDM
Чтобы это было похоже на другие выравнивания C/C++, перед LOCAL стоит префикс вызова макроса aligned
.
main proc
aligned LOCAL AppleA:WORD
aligned LOCAL AppleB:DWORD
aligned LOCAL AppleC:WORD
aligned LOCAL AppleD:DWORD
LOCAL OrangeA:WORD
LOCAL OrangeB:DWORD
LOCAL OrangeC:WORD
LOCAL OrangeD:DWORD
mov AppleA,1
mov AppleB,2
mov AppleC,3
mov AppleD,4
mov OrangeA,5
mov OrangeB,6
mov OrangeC,7
mov OrangeD,8
ret
main endp
end
Стек памяти от запуска кода показывает
00000030`f1b1fb10 00077ff600000008 ; OrangeD 8 is misaligned
00000030`f1b1fb18 00057ff600000006 ; OrangeB 6 is misaligned
00000030`f1b1fb20 000000040000001f ; AppleD 4 is aligned
00000030`f1b1fb28 0003000000000000 ; AppleC 3 is aligned
00000030`f1b1fb30 0000000200000000 ; AppleB 2 is aligned
00000030`f1b1fb38 00017ff6915ae298 ; AppleA 1 is aligned
Примечания
Как отмечено в комментариях, этот макрос еще не разбирает массивы. Решением является использование оператора MASM MOD, который возвращает целочисленное значение остатка (по модулю) при делении выражения1 на выражение2. Идея заключалась бы в том, чтобы принять массив, скажем, aligned LOCAL myArrayA[10]:BYTE
, извлечь arraySize
из 10, а затем вычислить требуемые байты заполнения 6, используя
padBytes = (8 - (arraySize MOD 8))
Отредактировано
Майкл Петч предложил очень умный и уникальный подход, объединив всех местных жителей в структуру. Структура может принимать значение выравнивания (т.е. 8 или QWORD) и соответствующим образом выравнивать элементы. Это немного дополнительная работа, но это сработает. Пример здесь: pastebin.com/4cLWm0f1
.code
main PROC
main_locs STRUC QWORD ; 8 byte (QWORD) alignment
beginStack DQ ?
myLocalA DQ ?
myLocalB DQ ?
myArrayA BYTE 10 DUP (?)
myArrayB BYTE 10 DUP (?)
endStack DQ ?
main_locs ENDS
LOCAL stack_vars: main_locs
lea rcx, stack_vars
mov [rcx][main_locs.beginStack], 0ffffffffdeadbeefh
mov [rcx][main_locs.myLocalA], 0ffffffffffffffffh
mov [rcx][main_locs.myLocalB], 00000000000000000h
;Fill myArrayA with 10 As
mov rax, "AAAAAAAA"
mov QWORD PTR[rcx][main_locs.myArrayA], rax
mov WORD PTR[rcx][main_locs.myArrayA+8], ax
;Fill myArrayB with 10 Bs
mov rax, "BBBBBBBB"
mov QWORD PTR[rcx][main_locs.myArrayB], rax
mov WORD PTR[rcx][main_locs.myArrayB+8], ax
mov [rcx][main_locs.endStack], 0ffffffffbaadf00dh
ret
main ENDP
end
Это работает для aligned LOCAL myArrayA[10]:BYTE
? Или только для отдельных скалярных слов или переменных двойного слова, где обычно бесполезно иметь его в начале выровненного qword? Выравнивание начала массива может иметь смысл, например. для использования с SSE2, но довольно глупо не помещать отдельное двойное слово и/или слово в 6 байтов, оставшихся в 16 байтах, содержащих выровненный 10-байтовый массив. (Если только вы не хотите хранить там мусор как часть магазина movaps
.)
Кроме того, это, кажется, объявляет заполнение после локальной переменной, поэтому вам нужно сначала объявить все ваши «выровненные» переменные и полагаться на то, что MASM выкладывает их в исходном порядке. Я уверен, что это работает на практике, но мне интересно, гарантируется ли это где-нибудь на бумаге. Не то, чтобы это имело большое значение на практике, если только MASM никогда не меняется или совместимые ассемблеры, такие как JWASM, не отличаются.
Честно говоря, я не вижу в этом особой ценности, особенно если это не работает для массивов; если вы хотите, чтобы инструмент сделал это за вас, напишите на C и используйте alignas(8) int32_t foo;
; компилятор по-прежнему может оптимально упаковать локальные переменные, соблюдая ограничения выравнивания. Если вы пишете от руки на ассемблере, разместите свои локальные имена самостоятельно, если макет имеет значение. У меня сложилось впечатление, что "высокий уровень" MASM был уместным несколько десятилетий назад, когда целые проекты писались на ассемблере, и некоторые люди застряли в написании ассемблера, даже если на самом деле не хотели этого. В наши дни часто лучше избегать этого.
MASM на самом деле немного необычен тем, что директива LOCAL вообще вычисляет эти вещи для вас. С большинством ассемблеров вы просто должны были бы вычислить смещения кадров стека вручную и жестко закодировать их. Обычно смысл написания на ассемблере заключается в том, что вы хотите выполнить всю оптимизацию самостоятельно; работа ассемблера состоит в том, чтобы закодировать именно то, что вы написали, и в противном случае уйти с дороги.