Quelle est la signification de l'alignement et du début d'une section?
Par exemple:
align 4
a: dw 0
Comment enregistre-t-il l'accès à la mémoire?
J'ai toujours aimé l'explication complète de Samael dans le fil suivant:
Explication de la directive ALIGN MASM, comment cette directive est-elle interprétée par le compilateur?
Citation:
ALIGN X
La directive ALIGN est accompagnée d'un nombre (X).
Ce nombre (X) doit être une puissance de 2. Soit 2, 4, 8, 16, etc.
La directive vous permet d'appliquer l'alignement de l'instruction ou des données immédiatement après la directive, sur une adresse mémoire qui est un multiple de la valeur X.
L'espace supplémentaire, entre l'instruction/les données précédentes et celle qui suit la directive ALIGN, est rempli avec des instructions NULL (ou équivalent, comme MOV EAX, EAX) dans le cas des segments de code, et NULL dans le cas des segments de données.
Le nombre X ne peut pas être supérieur à l'alignement par défaut du segment dans lequel la directive ALIGN est référencée. Il doit être inférieur ou égal à l'alignement par défaut du segment. Plus à ce sujet à suivre ...
A. Travailler avec du code
Si la directive précède le code, la raison serait l'optimisation (en référence à la vitesse d'exécution). Certaines instructions sont exécutées plus rapidement si elles sont alignées sur une limite de 4 octets (32 bits). Ce type d'optimisation peut généralement être utilisé ou référencé dans des fonctions critiques, telles que les boucles conçues pour manipuler constamment une grande quantité de données. Outre l'amélioration de la vitesse d'exécution, il n'y a pas de "nécessité" d'utiliser la directive avec du code.
B. Travailler avec des données
Il en va de même pour les données - nous utilisons principalement la directive afin d'améliorer la vitesse d'exécution - comme moyen d'optimisation de la vitesse. Il existe des situations où le désalignement des données peut avoir un impact énorme sur les performances de notre application.
Mais avec les données, il y a des situations où un alignement correct est une nécessité, pas un luxe. Cela est particulièrement vrai sur la plate-forme Itanium et le jeu d'instructions SSE/SSE2, où un désalignement sur une limite de 128 bits (X = 16), peut déclencher une exception de protection générale.
Un article intéressant et très informatif sur l'alignement des données, bien qu'orienté sur le compilateur MS C/C++, est le suivant:
Alignement des données Windows sur IPF, x86 et x64, par Kang Su Gatlin, MSDN
A. Si vous utilisez la directive de processeur .386 et que vous n'avez pas déclaré explicitement la valeur d'alignement par défaut pour un segment, l'alignement par défaut du segment est de DWORD ( 4 octets) taille. Oui, dans ce cas, X = 4. Vous pouvez ensuite utiliser les valeurs suivantes avec la directive ALIGN: (X = 2, X = 4). N'oubliez pas que X doit être inférieur ou égal à l'alignement du segment.
B. Si vous utilisez la directive de processeur .486 et plus, et que vous n'avez pas déclaré explicitement la valeur d'alignement par défaut pour un segment, l'alignement de segment par défaut est de PARAGRAPHE (16 octets). Dans ce cas, X = 16. Vous pouvez ensuite utiliser les valeurs suivantes avec la directive ALIGN: (X = 2, X = 4, X = 8, X = 16).
C. Vous pouvez déclarer un segment avec un alignement différent de celui par défaut de la manière suivante:
;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
Voici les alias des valeurs d'alignement des segments ...
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).
Prenons l'exemple suivant (lisez les commentaires sur l'utilisation de la directive 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
Si nous compilons le programme, voici ce que le compilateur a généré:
.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------------------------------
Comme vous le voyez, après la fin du code/des données de notre application, le compilateur génère plus d'instructions/de données. Cela est dû au fait que les sections PE, lorsqu'elles sont chargées en mémoire, sont alignées sur une taille de PAGE (512 octets).
Ainsi, le compilateur remplit l'espace supplémentaire au boudoir de 512 octets suivant avec des octets indésirables (généralement des instructions INT 3, des NOP ou des NULL pour les segments de code et 0FFh, des NULL pour les segments de données) afin de garantir que l'alignement de la mémoire pour le chargé L'image PE est correcte ...
Les mémoires ont une largeur fixe, aujourd'hui soit 32 bits, soit généralement 64 bits de large (même s'il s'agit d'un système 32 bits). Supposons pour l'instant un bus de données 32 bits. Chaque fois que vous effectuez une lecture, que ce soit 8, 16 ou 32 bits, il s'agit d'un bus 32 bits, de sorte que ces lignes de données auront quelque chose dessus, il est logique de simplement mettre les 32 bits liés à l'adresse alignée.
Donc, si à l'adresse 0x100, vous aviez la valeur 32 bits 0x12345678. Et vous deviez effectuer une lecture 32 bits et tous ces bits seraient sur le bus. Si vous deviez effectuer une lecture de 8 bits à l'adresse 0x101, le contrôleur de mémoire ferait une lecture de l'adresse 0x100, il obtiendrait 0x12345678. Et de ces 32 bits, il isolerait la "voie d'octets" appropriée, les 8 bits liés à l'adresse 0x101. Certains processeurs que le contrôleur de mémoire peut ne jamais voir que des lectures 32 bits, le processeur gérerait l'isolement de la file d'octets.
Qu'en est-il des processeurs qui autorisent des accès non alignés comme le x86? Si vous aviez 0x12345678 à l'adresse 0x100 et 0xAABBCCDD à l'adresse 0x104. Et devaient faire une lecture de 32 bits à l'adresse 0x102 sur ce système basé sur le bus de données 32 bits, puis deux cycles de mémoire sont nécessaires, un à l'adresse 0x100 où vivent 16 bits de la valeur souhaitée et puis un autre à 0x104 où les deux autres octets sont a trouvé. Après ces deux lectures, vous pouvez reconstituer les 32 bits et les fournir plus profondément dans le processeur où cela a été demandé. La même chose se produit si vous voulez faire une lecture 16 bits à l'adresse 0x103, cela vous coûte deux fois plus de cycles de mémoire, prend deux fois plus de temps.
Qu'est-ce que le .align
la directive fait normalement dans le langage d'assemblage (bien sûr, vous devez spécifier l'assembleur et le processeur exacts car il s'agit d'une directive et chaque assembleur peut définir ce qu'il veut définir pour les directives) est pad la sortie de telle sorte que la chose qui suit immédiatement le .align
est, bien, aligné sur cette frontière. Si j'avais ce code:
b: .db 0
c: .dw 0
Et il s'avère que lorsque j'assemble et que je lie l'adresse C est 0x102, mais je sais que j'y accéderai très souvent en tant que valeur 32 bits, je pourrai l'aligner en faisant quelque chose comme ceci:
b: .db 0
.align 4
c: .dw 0
en supposant que rien d'autre avant cela ne change, alors b sera toujours à l'adresse 0x101, mais l'assembleur mettra deux octets supplémentaires dans le binaire entre b et c de sorte que c change à l'adresse 0x104, aligné sur une limite de 4 octets.
"aligné sur une limite de 4 octets" signifie simplement que l'adresse modulo 4 est nulle. essentiellement 0x0, 0x4, 0x8, 0xc, 0x10, 0x14, 0x18, 0x1C et ainsi de suite. (les deux bits inférieurs de l'adresse sont nuls). Aligné sur 8 signifie que 0x0, 0x8, 0x10, 0x18 ou 3 bits inférieurs de l'adresse sont nuls. Etc.
Les écritures sont pires que les lectures car vous devez effectuer des lectures-modifications-écritures pour des données plus petites que le bus. Si nous voulions changer l'octet à l'adresse 0x101, nous lirions la valeur 32 bits à l'adresse 0x100, changerions l'un octet, puis réécririons cette valeur 32 bits à 0x100. Donc, lorsque vous écrivez un programme et que vous pensez accélérer les choses en utilisant des valeurs plus petites, vous ne le faites pas. Donc, une écriture qui n'est pas alignée et la largeur de la mémoire vous coûte la lecture-modification-écriture. Une écriture non alignée vous coûte deux fois plus cher qu’une lecture. Une écriture non alignée serait deux écritures-lecture-modification. Les écritures ont cependant une fonctionnalité de performance sur les lectures. Lorsqu'un programme doit lire quelque chose dans la mémoire et utiliser cette valeur immédiatement, l'instruction suivante doit attendre la fin du cycle de mémoire (qui peut être de nos jours des centaines de cycles d'horloge, dram est bloqué à 133 MHz pendant environ une décennie, votre mémoire DDR3 à 1333 MHz n'est pas à 1333 MHz, le bus est à 1333 MHz/2 et vous pouvez envoyer des requêtes à cette vitesse mais la réponse ne revient pas avant longtemps). Fondamentalement, avec une lecture, vous avez une adresse, mais vous devez attendre les données aussi longtemps que nécessaire. Pour une écriture, vous avez les deux éléments, l'adresse et les données, et vous pouvez "tirer et oublier" vous donnez au contrôleur de mémoire l'adresse et les données et votre programme peut continuer à fonctionner. Certes, si la prochaine instruction ou l'ensemble d'instructions doit accéder à la mémoire, lire ou écrire, alors tout le monde doit attendre la fin de la première écriture, puis passer à l'accès suivant.
Tout ce qui précède est très simpliste, mais ce que vous verriez entre le processeur et le cache, de l'autre côté du cache, la mémoire à largeur fixe (la largeur fixe du sram dans le cache et la largeur fixe du dram sur le le côté éloigné n'a pas à correspondre) de l'autre côté du cache est accessible dans des "lignes de cache" qui sont généralement des multiples de la taille de la largeur du bus. cela aide et nuit à l'alignement. Disons par exemple que 0x100 est une limite de ligne de cache. Le mot à 0xFE, disons, est la fin d'une ligne de cache et 0x100 le début de la suivante. Si vous deviez effectuer une lecture 32 bits à l'adresse 0xFE, non seulement deux cycles de mémoire 32 bits doivent se produire, mais deux récupérations de ligne de cache. Le pire des cas serait de devoir expulser deux lignes de cache en mémoire pour faire de la place aux deux nouvelles lignes de cache que vous récupérez. Si vous aviez utilisé une adresse alignée, elle serait toujours mauvaise mais seulement la moitié.
Votre question n'a pas précisé le processeur, mais la nature de votre question implique x86 qui est bien connu pour ce problème. Les autres familles de processeurs n'autorisent pas les accès non alignés, ou vous devez désactiver spécifiquement le défaut d'exception. Et parfois, l'accès non aligné n'est pas comme x86. Par exemple, sur au moins un processeur si vous aviez 0x12345678 à l'adresse 0x100 et 0xAABBCCDD à l'adresse 0x104 et que vous avez désactivé la panne et effectué une lecture 32 bits à l'adresse 0x102, vous obtiendrez 0x56781234. Une seule lecture de 32 bits avec les voies d'octets tournées pour mettre l'octet inférieur au bon endroit. Non, je ne parle pas d'un système x86 mais d'un autre processeur.
align
remplit l'adresse avec NOPs/0x90 (NASM) jusqu'à ce qu'elle soit alignée sur l'opérande (l'opérande modulo addr est nul).
Par exemple:
db 12h
align 4
db 32h
Sorties assemblées:
0000 12 90 90 90
0004 32
C'est plus rapide pour l'accès à la mémoire et nécessaire pour charger certaines tables dans les processeurs x86 (et probablement d'autres architectures également). Je ne peux nommer aucun cas spécifique, mais vous pouvez trouver plusieursréponses sur SO et les moteurs de recherche.