我正在研究对齐检查的问题。但我不知道处理器是在有效地址、线性地址还是物理地址上进行检查,或者是否都要进行检查。
例如,数据的有效地址已经对齐,但通过添加段描述符的基地址形成的线性地址不再对齐,此时处理器会抛出 #AC 异常。
我正在研究对齐检查的问题。但我不知道处理器是在有效地址、线性地址还是物理地址上进行检查,或者是否都要进行检查。
例如,数据的有效地址已经对齐,但通过添加段描述符的基地址形成的线性地址不再对齐,此时处理器会抛出 #AC 异常。
我认为这是线性地址。
请继续阅读测试方法和测试代码。
要测试它,只需使用一个未对齐的基础段。
在我的测试中,我使用了一个32位数据段,其基础为1。
该测试是一个“简单”的传统(即非UEFI)引导程序,将创建上述描述符并测试使用DWORD宽度访问偏移量0x7000和0x7003。
前者将生成#AC,后者则不会。
这表明仅检查偏移量是不够的,因为当基础为1时,即使0x7000是对齐的偏移量,仍然会出现故障。
这是预期的。
我有一个使用最小输出进行测试的传统,因此需要解释。
首先,在VGA缓冲区的六个连续行中写入六个蓝色的A。
然后在执行加载操作之前,将指针设置到这些A中的每一个。
#AC处理程序将递增指向的字节。
因此,如果一行包含B,则访问将生成#AC。
前四行用于:
接下来的两行用于检查线性地址与物理地址之间的关系。
#AC测试只对齐到16个字节,但线性地址和物理地址至少在4KiB上共享相同的对齐方式。
我们需要进行一次需要数据结构对齐至少8KiB的内存访问,以测试是使用了物理地址还是线性地址进行检查。
不幸的是,目前没有这样的访问方式。
我认为通过检查当未对齐的加载指向未映射页面时生成的异常可以收集一些洞见。第6点似乎表明CPU将对线性地址执行检查,因为没有访问页表。
在第6点中,两种异常都可能被生成,没有生成#PF意味着当执行对齐检查时,CPU尚未尝试翻译地址。(或者#AC逻辑上优先。但是,即使在执行基址+偏移量计算后探测TLB,硬件也不太可能在引发#AC异常之前进行页面遍历。)
BITS 16
ORG 7c00h
;Skip the BPB (My BIOS actively overwrite it)
jmp SHORT __SKIP_BPB__
;I eyeballed the BPB size (at least the part that may be overwritten)
TIMES 40h db 0
__SKIP_BPB__:
;Set up the segments (including CS)
xor ax, ax
mov ds, ax
mov ss, ax
xor sp, sp
jmp 0:__START__
__START__:
;Clear and set the video mode (before we switch to PM)
mov ax, 03h
int 10h
;Disable the interrupts and load the GDT and IDT
cli
lgdt [GDT]
lidt [IDT]
;Enable PM
mov eax, cr0
or al, 1
mov cr0, eax
;Write a TSS segment, we zeros 104h DWORDs and only set the SS0:ESP0 fields
mov di, 7000h
mov cx, 104h
xor ax, ax
rep stosd
mov DWORD [7004h], 7c00h ;ESP0
mov WORD [7008h], 10h ;SS0
;Set AC in EFLAGS
pushfd
or DWORD [esp], 1 << 18
popfd
;Set AM in CR0
mov eax, cr0
or eax, 1<<18
mov cr0, eax
;OK, let's go in PM for real
jmp 08h:__32__
__32__:
BITS 32
;Set the stack and DS
mov ax, 10h
mov ss, ax
mov esp, 7c00h
mov ds, ax
;Set the #AC handler
mov DWORD [IDT+8+17*8], ((AC_handler-$$+7c00h) & 0ffffh) | 00080000h
mov DWORD [IDT+8+17*8+4], 8e00h | (((AC_handler-$$+7c00h) >> 16) << 16)
;Set the #PF handler
mov DWORD [IDT+8+14*8], ((PF_handler-$$+7c00h) & 0ffffh) | 00080000h
mov DWORD [IDT+8+14*8+4], 8e00h | (((PF_handler-$$+7c00h) >> 16) << 16)
;Set the TSS
mov ax, 30h
ltr ax
;Paging is:
;7xxx -> Identity mapped (contains code and all the stacks and system structures)
;8xxx -> Not present
;9xxx -> Mapped to the VGA text buffer (0b8xxxh)
;Note that the paging structures are at 6000h and 5000h, this is OK as these are physical addresses
;Set the Page Directory at 6000h
mov eax, 6000h
mov cr3, eax
;Set the Page Directory Entry 0 (for 00000000h-00300000h) to point to a Page Table at 5000h
mov DWORD [eax], 5007h
;Set the Page Table Entry 7 (for 00007xxxh) to identity map and Page Table Entry 8 (for 000008xxxh) to be not present
mov eax, 5000h + 7*4
mov DWORD [eax], 7007h
mov DWORD [eax+4], 8006h
;Map page 9000h to 0b8000h
mov DWORD [eax+8], 0b801fh
;Enable paging
mov eax, cr0
or eax, 80000000h
mov cr0, eax
;Change privilege (goto CPL=3)
push DWORD 23h ;SS3
push DWORD 07a00h ;ESP3
push DWORD 1bh ;CS3
push DWORD __32user__ ;EIP3
retf
__32user__:
;
;Here we are at CPL=3
;
;Set DS to segment with base 0 and ES to one with base 1
mov ax, 23h
mov ds, ax
mov ax, 2bh
mov es, ax
;Write six As in six consecutive row (starting from the 4th)
xor ecx, ecx
mov ecx, 6
mov ebx, 9000h + 80*2*3 ;Points to 4th row in the VGA text framebuffer
.init_markers:
mov WORD [ebx], 0941h
add bx, 80*2
dec ecx
jnz .init_markers
;ebx points to the first A
sub ebx, 80*2 * 6
;Base 0 + Offset 0 = 0, Should not fault (marker stays A)
mov eax, DWORD [ds:7000h]
;Base 0 + Offset 1 = 1, Should fault (marker becomes B)
add bx, 80*2
mov eax, DWORD [ds:7001h]
;Base 1 + Offset 0 = 1, Should fault (marker becomes B)
add bx, 80*2
mov eax, DWORD [es:7000h]
;Base 1 + Offset 3 = 4, Should not fault (marker stays A)
add bx, 80*2
mov eax, DWORD [es:7003h]
;Base 1 + Offset 3 = 4 but page not mapped, Should #PF (markers becomes C)
add bx, 80*2
mov eax, DWORD [es:8003h]
;Base 1 + Offset 0 = 1 but page not mapped, if #PF the markers becomes C, if #AC the markers becomes B
add bx, 80*2
mov eax, DWORD [es:8000h]
;Loop foever (cannot use HLT at CPL=3)
jmp $
;#PF handler
;Increment the byte pointed by ebx by two
PF_handler:
add esp, 04h ;Remove the error code
add DWORD [esp], 6 ;Skip the current instruction
add BYTE [ebx], 2 ;Increment
iret
;#AC handler
;Same as the #PF handler but increment by one
AC_handler:
add esp, 04h
add DWORD [esp], 6
inc BYTE [ebx]
iret
;The GDT (entry 0 is used as the content for GDTR)
GDT dw GDT.end-GDT - 1
dd GDT
dw 0
dd 0000ffffh, 00cf9a00h ;08 Code, 32, DPL 0
dd 0000ffffh, 00cf9200h ;10 Data, 32, DPL 0
dd 0000ffffh, 00cffa00h ;18 Code, 32, DPL 3
dd 0000ffffh, 00cff200h ;20 Data, 32, DPL 3
dd 0001ffffh, 00cff200h ;28 Data, 32, DPL 3, Base = 1
dd 7000ffffh, 00cf8900h ;30 Data, 32, 0 (TSS)
.end:
;The IDT, to save space the entries are set dynamically
IDT dw 18*8-1
dd IDT+8
dw 0
;Signature
TIMES 510-($-$$) db 0
dw 0aa55h
我认为这并不特别相关。
如上所述,线性地址和物理地址在4KiB范围内具有相同的对齐方式。
因此,目前完全没有关系。
现在,宽度大于64字节的访问仍需要分块执行,而此限制深入到x86 CPU的微架构中。
rep stos
(但当然从SIMD / FPU状态获取数据,而不是AL)。因此,每个存储uop都将进行自己的TLB访问。或者当您说“假设”时,您是否只是指#AC目的? - Peter Cordesfxsave m512byte
(或后来的 xsave
/ xsaveopt
)来说则不然。它们是非特权指令,连续的物理内存可能会让用户空间破坏/覆盖未映射到其上的内存,例如从mmap获得的单个页面的末尾开始。因此,这几乎可以排除掉。 - Peter Cordes