В чем смысл выравнивания начала раздела?
Например:
align 4
a: dw 0
Как сохранить доступ к памяти?
В чем смысл выравнивания начала раздела?
Например:
align 4
a: dw 0
Как сохранить доступ к памяти?
Мне всегда нравилось полное объяснение Samael в следующем потоке:
Объяснение директивы ALIGN MASM, как эта директива интерпретируется компилятором?
Цитата:
ALIGN X
Директива ALIGN сопровождается числом (X).
Это число (X) должно быть степенью 2. Это 2, 4, 8, 16 и т.д.
Директива позволяет вам принудительно выполнить выравнивание инструкции или данных сразу после директивы по адресу памяти, который кратен значению X.
Дополнительное пространство между предыдущей инструкцией/данными и после директивы ALIGN дополняется инструкциями NULL (или эквивалентными, например MOV EAX, EAX) в случае сегментов кода, и NULL в случае сегментов данных.
Число X не может быть больше, чем выравнивание по умолчанию сегмента, на который ссылается директива ALIGN. Он должен быть меньше или равен стандартным выравниваниям сегмента. Подробнее об этом следовать...
а. Работа с кодом
Если директива предшествует коду, причиной будет оптимизация (со ссылкой на скорость выполнения). Некоторые инструкции выполняются быстрее, если они выровнены на границе 4 байта (32 бита). Такой вид оптимизации обычно можно использовать или ссылаться на критически важные функции, такие как циклы, которые предназначены для управления большим количеством данных, постоянно. Тем не менее, помимо улучшения скорости выполнения, нет никакой необходимости использовать директиву с кодом.
В. Работа с данными
То же самое относится и к данным - мы в основном используем директиву для повышения скорости выполнения - как средство оптимизации скорости. Бывают ситуации, когда несоосность данных может оказать огромное влияние на наше приложение.
Но с данными есть ситуации, когда правильное выравнивание является необходимостью, а не роскошью. Это особенно актуально на платформе Itanium и наборе инструкций SSE/SSE2, где смещение на 128-битной границе (X = 16) может привести к общему исключению общей защиты.
Интересная и наиболее информативная статья о выравнивании данных, хотя и ориентирована на компилятор MS C/С++, выглядит следующим образом:
Выравнивание данных Windows на IPF, x86 и x64, Кан Су Гатлин, MSDN
A. Если вы используете директиву .386, и вы явно объявили значение выравнивания по умолчанию для сегмента, выравнивание по умолчанию по умолчанию имеет размер DWORD (4 байта). Да, в этом случае X = 4. Затем вы можете использовать следующие значения с директивой ALIGN: (X = 2, X = 4). Помните, что X должен быть меньше или равно, чем выравнивание сегмента.
B. Если вы используете директиву .486 и выше, и вы явно объявили значение выравнивания по умолчанию для сегмента, выравнивание по умолчанию по умолчанию имеет размер PARAGRAPH (16 байт). В этом случае X = 16. Затем вы можете использовать следующие значения с директивой ALIGN: (X = 2, X = 4, X = 8, X = 16).
C. Вы можете объявить сегмент без выравнивания по умолчанию следующим образом:
;Here, we create a code segment named "JUNK", which starts aligned on a 256 bytes boundary
JUNK SEGMENT PAGE PUBLIC FLAT 'CODE'
;Your code starts aligned on a PAGE boundary (X=256)
; Possible values that can be used with the ALIGN directive
; within this segment, are all the powers of 2, up to 256.
JUNK ENDS
Ниже перечислены псевдонимы для значений параметров сегмента...
Align Type Starting Address
BYTE Next available byte address.
WORD Next available word address (2 bytes per word).
DWORD Next available double word address (4 bytes per double word).
PARA Next available paragraph address (16 bytes per paragraph).
PAGE Next available page address (256 bytes per page).
Рассмотрим следующий пример (прочитайте комментарии об использовании директивы ALIGN).
.486
.MODEL FLAT,STDCALL
OPTION CASEMAP:NONE
INCLUDE \MASM32\INCLUDE\WINDOWS.INC
.DATA
var1 BYTE 01; This variable is of 1 byte size.
ALIGN 4
; We enforce the next variable to be alingned in the next memory
;address that is multiple of 4.
;This means that the extra space between the first variable
;and this one will be padded with nulls. ( 3 bytes in total)
var2 BYTE 02; This variable is of 1 byte size.
ALIGN 2
; We enforce the next variable to be alingned in the next memory
;address that is multiple of 2.
;This means that the extra space between the second variable
;and this one will be padded with nulls. ( 1 byte in total)
var3 BYTE 03; This variable is of 1 byte size.
.CODE
; Enforce the first instruction to be aligned on a memory address multiple of 4
ALIGN 4
EntryPoint:
; The following 3 instructions have 7 byte - opcodes
; of the form 0F B6 05 XX XX XX XX
; In the following block, we do not enforce opcode
; alignment in memory...
MOVZX EAX, var1
MOVZX EAX, var2
MOVZX EAX, var3
; The following 3 instructions have 7 byte - opcodes
; of the form 0F B6 05 XX XX XX XX
; In the following block, we enforce opcode alignment
; for the third instruction, on a memory address multiple of 4.
; Since the second instruction opcodes end on a memory address
; that is not a multiple of 4, some nops would be injected before
; the first opcode of the next instruction, so that the first opcode of it
; will start on a menory address that is a multiple of 4.
MOVZX EAX, var1
MOVZX EAX, var2
ALIGN 4
MOVZX EAX, var3
; The following 3 instructions have 7 byte - opcodes
; of the form 0F B6 05 XX XX XX XX
; In the following block, we enforce opcode alignment
; for all instructions, on a memory address multiple of 4.
;The extra space between each instruction will be padded with NOPs
ALIGN 4
MOVZX EAX, var1
ALIGN 4
MOVZX EAX, var2
ALIGN 4
MOVZX EAX, var3
ALIGN 2
; The following instruction has 1 byte - opcode (CC).
; In the following block, we enforce opcode alignment
; for the instruction, on a memory address multiple of 2.
;The extra space between this instruction ,
;and the previous one, will be padded with NOPs
INT 3
END EntryPoint
Если мы скомпилируем программу, вот что сгенерировал компилятор:
.DATA
;------------SNIP-SNIP------------------------------
.data:00402000 var1 db 1
.data:00402001 db 0; This NULL was generated to enforce the alignment of the next instruction on an address that is a multiple of 4
.data:00402002 db 0; This NULL was generated to enforce the alignment of the next instruction on an address that is a multiple of 4
.data:00402003 db 0; This NULL was generated to enforce the alignment of the next instruction on an address that is a multiple of 4
.data:00402004 var2 db 2
.data:00402005 db 0; This NULL was generated to enforce the alignment of the next instruction oon an address that is a multiple of 2
.data:00402006 var3 db 3
.data:00402007 db 0; The rest of the NULLs are to fill the memory page in which the segment will be loaded
;------------SNIP-SNIP------------------------------
.CODE
;------------SNIP-SNIP------------------------------
.text:00401000 start:
.text:00401000 movzx eax, var1
.text:00401007 movzx eax, var2
.text:0040100E movzx eax, var3
.text:00401015 movzx eax, var1
.text:0040101C movzx eax, var2
.text:00401023 nop; This NOP was generated to enforce the alignment...
.text:00401024 movzx eax, var3
.text:0040102B nop; This NOP was generated to enforce the alignment...
.text:0040102C movzx eax, var1
.text:00401033 nop; This NOP was generated to enforce the alignment...
.text:00401034 movzx eax, var2
.text:0040103B nop; This NOP was generated to enforce the alignment...
.text:0040103C movzx eax, var3
.text:00401043 nop; This NOP was generated to enforce the alignment...
.text:00401044 int 3 ; Trap to Debugger
.text:00401044; ---------------------------------------------------------------------------
.text:00401045 db 0
.text:00401046 db 0
.text:00401047 db 0
.text:00401048 db 0
;------------SNIP-SNIP------------------------------
Как видите, после завершения кода/данных нашего приложения компилятор генерирует больше инструкций/данных. Это связано с тем, что разделы PE при загрузке в память выравниваются по размеру PAGE (512 байт).
Таким образом, компилятор заполняет дополнительное пространство для следующего 512-байтового байдара с помощью младших байтов (обычно INT 3 инструкций, NOP или NULL для сегментов кода и 0FFh, NULL для сегментов данных), чтобы гарантировать, что выравнивание памяти для загруженного изображения PE правильно...
Памяти - фиксированная ширина, сегодня либо 32-разрядная, либо, как правило, 64-разрядная (даже если это 32-разрядная система). Предположим, что на данный момент используется 32-битная шина данных. Каждый раз, когда вы читаете, будь то 8, 16 или 32 бита, это 32-битная шина, поэтому эти строки данных будут иметь что-то на них, имеет смысл просто поместить 32 бита, связанные с выровненным адресом.
Итак, если на адрес 0x100 у вас есть 32-битное значение 0x12345678. И вы должны были выполнить 32-битное чтение, все эти биты будут на шине. Если вы должны выполнить 8-битное чтение по адресу 0x101, контроллер памяти выполнит чтение адреса 0x100, он получит 0x12345678. И из этих 32 бит он будет изолировать надлежащую "байтовую полосу", 8 бит, связанных с адресом 0x101. Некоторые процессоры, которые контроллер памяти никогда не видит ничего, кроме 32-битных, обрабатывают изоляцию байтовой полосы.
Как насчет процессоров, которые допускают несвязанные обращения, такие как x86? Если у вас есть 0x12345678 по адресу 0x100 и 0xAABBCCDD по адресу 0x104. И нужно было сделать 32-битное чтение по адресу 0x102 в этой 32-битной системе на базе шины данных, они потребуют два цикла памяти, один по адресу 0x100, где 16 бит желаемого значения живут, а затем еще один в 0x104, где остальные два байта найденный. После того, как эти два чтения произойдут, вы можете объединить 32 бита и обеспечить это глубже в процессор, где он был запрошен. То же самое происходит, если вы хотите сделать 16-битное чтение по адресу 0x103, стоить вам вдвое больше циклов памяти, занимает в два раза больше.
То, что обычно выполняет директива .align на языке ассемблера (конечно, вы должны указать точное ассемблер и процессор, поскольку это директива, и каждый ассемблер может определить, что он хочет определить для директив) - это вывести вывод таким образом, чтобы вещь, которая сразу же следует за .align, хорошо выравнивается на этой границе. Если бы у меня был этот код:
b: .db 0
c: .dw 0
И получается, что когда я собираю и связываю адрес для C 0x102, но я знаю, что я получаю это очень часто как 32-битное значение, тогда я могу выровнять его, выполнив что-то вроде этого:
b: .db 0
.align 4
c: .dw 0
не принимая ничего другого, прежде чем это изменится в результате, тогда b все равно будет находиться по адресу 0x101, но ассемблер поместит еще два байта в двоичный код между b и c, так что c изменится на адрес 0x104, выровненный по 4 байтам граница.
", выровненное на границе 4 байта, просто означает, что адрес по модулю 4 равен нулю. в основном 0x0, 0x4, 0x8, 0xc, 0x10, 0x14, 0x18, 0x1C и так далее. (нижние два бита адреса равны нулю). Выровненный по 8 означает 0x0, 0x8, 0x10, 0x18 или ниже 3 бита адреса равны нулю. И так далее.
Писания хуже, чем чтение, так как вы должны делать read-modify-write для данных, меньших, чем шина. Если мы хотим изменить байт по адресу 0x101, мы будем читать 32-битное значение по адресу 0x100, изменить один байт, а затем записать это 32-битное значение обратно в 0x100. Поэтому, когда вы пишете программу, и вы думаете, что делаете что-то быстрее, используя меньшие значения, вы этого не делаете. Таким образом, запись не выровнена, а ширина памяти стоит вам на чтение-изменение-запись. Невыравнимая запись обойдется вам в два раза больше, чем при чтении. Невыравниваемая запись будет представлять собой две операции чтения-изменения-записи. Тем не менее, у писем есть функция производительности. Когда программе нужно что-то прочитать из памяти и сразу использовать это значение, следующей команде нужно дождаться завершения цикла памяти (который в эти дни может составлять сотни тактов, драм застрял на частоте 133 МГц в течение примерно десятилетия, ваша память 1333 МГц DDR3 не 1333 МГц, шина 1333 МГц /2, и вы можете отправлять запросы с такой скоростью, но ответ не возвращается долгое время). В принципе, при чтении у вас есть адрес, но вам нужно ждать данных столько, сколько потребуется. Для записи у вас есть оба элемента, адрес и данные, и вы можете "стрелять и забывать", вы даете контроллеру памяти адрес и данные, и ваша программа может продолжать работать. Если требуется следующая инструкция или набор инструкций для доступа к памяти, чтения или записи, тогда каждый должен дождаться завершения первой записи, а затем перейти к следующему доступу.
Все вышеизложенное очень упрощенно, но то, что вы увидите между процессором и кешем, с другой стороны кеша, память с фиксированной шириной (фиксированная ширина sram в кеше и фиксированная ширина драм на другой стороне не обязательно совпадать), с другой стороны кэш-памяти открывается в "кеш-строках", которые, как правило, кратно размеру ширины шины. это и помогает, и болит с выравниванием. Скажем, например, 0x100 является границей строки кэша. Слово в 0xFE позволяет сказать, что это хвост одной строки кэша и 0x100 - начало следующего. Если вы должны выполнить 32-битное чтение по адресу 0xFE, необходимо выполнить не только два цикла 32-разрядной памяти, но две выборки в кеше. В худшем случае придется выселить две строки кэша в память, чтобы освободить место для двух новых линий кэша, которые вы извлекаете. Если бы вы использовали выровненный адрес, он все равно был бы плохим, но только наполовину плохим.
В вашем вопросе не указан процессор, но характер вашего вопроса подразумевает x86, который хорошо известен этой проблеме. Другие семейства процессоров не допускают несвязанные обращения, или вы должны специально отключить ошибку исключения. И иногда неприглаженный доступ не похож на x86. Например, по крайней мере один процессор, если у вас есть 0x12345678 по адресу 0x100 и 0xAABBCCDD по адресу 0x104, и вы отключили ошибку и выполнили 32-битное чтение по адресу 0x102, вы получите 0x56781234. Один 32-битный код считывается с байт-полосами, повернутыми, чтобы поместить нижний байт в нужное место. Нет, я не говорю о системе x86, но о каком-то другом процессоре.
align
заполняет адрес с помощью NOP/0x90 (NASM), пока он не выровнётся с операндом (addr modulo operand равен нулю).
Например:
db 12h
align 4
db 32h
Когда собранные выходы:
0000 12 90 90 90
0004 32
Это быстрее для доступа к памяти и необходимо загрузить некоторые таблицы в процессорах x86 (и, возможно, и в других архитектурах). Я не могу назвать какие-либо конкретные случаи, но вы можете найти несколько ответов в SO и поисковых системах.