Выравнивание переменных стека в языках ассемблера

Существуют ли какие-либо директивы сборки для выравнивания определенных переменных данных стека?

Например, предположим, что функция 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++, имеют большой набор инструментов с хорошими приемами оптимизации, но, к сожалению, не перенесены на языки ассемблера более низкого уровня.

MASM на самом деле немного необычен тем, что директива LOCAL вообще вычисляет эти вещи для вас. С большинством ассемблеров вы просто должны были бы вычислить смещения кадров стека вручную и жестко закодировать их. Обычно смысл написания на ассемблере заключается в том, что вы хотите выполнить всю оптимизацию самостоятельно; работа ассемблера состоит в том, чтобы закодировать именно то, что вы написали, и в противном случае уйти с дороги.

Nate Eldredge 02.01.2023 19:20

Я думаю, один из способов — просто поместить всех местных жителей в struc. struc может принимать значение выравнивания (например, 8 или QWORD) и соответствующим образом выравнивать элементы. Это немного дополнительная работа, но это сработает. Пример здесь: pastebin.com/4cLWm0f1

Michael Petch 03.01.2023 16:04

@Michael Petch Спасибо за эту очень хорошую оригинальную идею!

vengy 03.01.2023 16:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Частичный ответ на данный момент заключается в определении макроса 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.)

Peter Cordes 03.01.2023 07:20

Кроме того, это, кажется, объявляет заполнение после локальной переменной, поэтому вам нужно сначала объявить все ваши «выровненные» переменные и полагаться на то, что MASM выкладывает их в исходном порядке. Я уверен, что это работает на практике, но мне интересно, гарантируется ли это где-нибудь на бумаге. Не то, чтобы это имело большое значение на практике, если только MASM никогда не меняется или совместимые ассемблеры, такие как JWASM, не отличаются.

Peter Cordes 03.01.2023 07:24

Честно говоря, я не вижу в этом особой ценности, особенно если это не работает для массивов; если вы хотите, чтобы инструмент сделал это за вас, напишите на C и используйте alignas(8) int32_t foo;; компилятор по-прежнему может оптимально упаковать локальные переменные, соблюдая ограничения выравнивания. Если вы пишете от руки на ассемблере, разместите свои локальные имена самостоятельно, если макет имеет значение. У меня сложилось впечатление, что "высокий уровень" MASM был уместным несколько десятилетий назад, когда целые проекты писались на ассемблере, и некоторые люди застряли в написании ассемблера, даже если на самом деле не хотели этого. В наши дни часто лучше избегать этого.

Peter Cordes 03.01.2023 07:27

Другие вопросы по теме