Подтвердить что ты не робот

В чем смысл выравнивания начала раздела?

В чем смысл выравнивания начала раздела?

Например:

 align 4
 a: dw 0

Как сохранить доступ к памяти?

4b9b3361

Ответ 1

Мне всегда нравилось полное объяснение Samael в следующем потоке:
Объяснение директивы ALIGN MASM, как эта директива интерпретируется компилятором?

Цитата:

1. ИСПОЛЬЗОВАНИЕ

ALIGN X

Директива ALIGN сопровождается числом (X).
Это число (X) должно быть степенью 2. Это 2, 4, 8, 16 и т.д.

Директива позволяет вам принудительно выполнить выравнивание инструкции или данных сразу после директивы по адресу памяти, который кратен значению X.

Дополнительное пространство между предыдущей инструкцией/данными и после директивы ALIGN дополняется инструкциями NULL (или эквивалентными, например MOV EAX, EAX) в случае сегментов кода, и NULL в случае сегментов данных.

Число X не может быть больше, чем выравнивание по умолчанию сегмента, на который ссылается директива ALIGN. Он должен быть меньше или равен стандартным выравниваниям сегмента. Подробнее об этом следовать...

2. ЦЕЛЬ

а. Работа с кодом

Если директива предшествует коду, причиной будет оптимизация (со ссылкой на скорость выполнения). Некоторые инструкции выполняются быстрее, если они выровнены на границе 4 байта (32 бита). Такой вид оптимизации обычно можно использовать или ссылаться на критически важные функции, такие как циклы, которые предназначены для управления большим количеством данных, постоянно. Тем не менее, помимо улучшения скорости выполнения, нет никакой необходимости использовать директиву с кодом.

В. Работа с данными

То же самое относится и к данным - мы в основном используем директиву для повышения скорости выполнения - как средство оптимизации скорости. Бывают ситуации, когда несоосность данных может оказать огромное влияние на наше приложение.

Но с данными есть ситуации, когда правильное выравнивание является необходимостью, а не роскошью. Это особенно актуально на платформе Itanium и наборе инструкций SSE/SSE2, где смещение на 128-битной границе (X = 16) может привести к общему исключению общей защиты.

Интересная и наиболее информативная статья о выравнивании данных, хотя и ориентирована на компилятор MS C/С++, выглядит следующим образом:

Выравнивание данных Windows на IPF, x86 и x64, Кан Су Гатлин, MSDN

3. Что такое выравнивание по умолчанию сегмента?

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).

4. Пример

Рассмотрим следующий пример (прочитайте комментарии об использовании директивы 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 правильно...

Ответ 2

Памяти - фиксированная ширина, сегодня либо 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, но о каком-то другом процессоре.

Ответ 3

align заполняет адрес с помощью NOP/0x90 (NASM), пока он не выровнётся с операндом (addr modulo operand равен нулю).

Например:

db 12h
align 4
db 32h

Когда собранные выходы:

0000  12 90 90 90 
0004  32

Это быстрее для доступа к памяти и необходимо загрузить некоторые таблицы в процессорах x86 (и, возможно, и в других архитектурах). Я не могу назвать какие-либо конкретные случаи, но вы можете найти несколько ответов в SO и поисковых системах.