如何解决内存对齐检查机制检查的地址是有效地址、线性地址还是物理地址?
我正在研究对齐检查的问题。但我不知道处理器是在检查有效地址、线性地址还是物理地址,还是全部检查。
比如一个数据的有效地址已经对齐,但是加上段描述符的基地址形成的线性地址不再对齐,此时处理器抛出#AC异常。
解决方法
TL;DR
我认为是线性地址。
继续阅读测试方法和测试代码。
它不是有效地址(也就是偏移量)
为了测试这一点,使用一个基数未对齐的段就足够了。
在我的测试中,我使用了基数为 1 的 32 位数据段。
该测试是一个“简单”的传统(即非 UEFI)引导加载程序,它将创建所述描述符并测试访问具有 DWORD 宽度的偏移量 0x7000 和 0x7003。
前者会生成#AC,后者不会。
这表明检查的不仅仅是偏移量,因为 0x7000 是对齐的偏移量,但仍然以 1 为基数出错。
这是预期的。
我有在测试中使用最少输出的传统,因此必须进行解释。
首先在VGA缓冲区中连续六行写入六个蓝色As。
然后在执行加载之前,为每个 As 设置一个指针。
#AC 处理程序将增加指向的字节。
因此,如果一行包含 B,则访问会生成一个 #AC。
前四行用于:
- 使用基数为 0 且偏移量为 0x7000h 的段进行访问。正如预期的那样,没有#AC
- 使用基数为 0 且偏移量为 0x7003h 的段进行访问。正如预期的那样,#AC
- 使用基数为 1 且偏移量为 0x7000h 的段进行访问。这确实会生成一个 #AC,从而证明它是所检查的物理地址的线性。
- 使用基数为 1 且偏移量为 0x7003h 的段进行访问。这不会生成 #AC,确认第 3 点。
接下来的两行用于检查线性地址与物理地址。
这不是物理地址:#AC 而不是 #PF
#AC 测试最多只能对齐 16 个字节,但线性地址和物理地址共享相同的对齐方式,最多可达 4KiB。
我们需要一个内存访问,它需要一个至少 8KiB 对齐的数据结构来测试它是用于检查的物理地址还是线性地址。
不幸的是,目前还没有这样的访问权限。
我认为我仍然可以通过检查未对齐的加载目标未映射的页面时生成的异常来收集一些见解。
如果生成#PF,CPU 将首先转换线性地址,然后进行检查。反过来说,如果生成了#AC,CPU 会在翻译前检查(记住页面没有被映射)。
我修改了测试以启用页面、映射最小数量的页面并通过将指针下的字节增加 2 来处理 #PF。
执行加载时,如果生成#AC,则相应的A 将变为B,如果生成#PF,则相应的A 将变为C。
请注意,两者都是错误(堆栈上的 eip
指向违规指令),但两个处理程序都从 next 指令恢复(因此每次加载仅执行一次)。
这些是最后两行的含义:
- 使用基数为 1 且偏移量为 0x7003h 的段访问未映射的页面。这会按预期生成 #PF(访问已对齐,因此此处唯一可能的异常是 #PF)。
- 使用基数为 1 且偏移量为 0x7000h 的段访问未映射的页面。这会生成一个 #AC,因此 CPU 在尝试转换地址之前会检查对齐。
第 6 点似乎表明 CPU 将对线性地址执行检查,因为没有访问页表。
在第 6 点中,可能会生成两个异常,未生成 #PF 的事实意味着 CPU 在执行对齐检查时尚未尝试转换地址。 (或者,#AC 逻辑上优先。但硬件可能不会在处理 #AC 异常之前执行页面遍历,即使它在执行基址 + 偏移量计算后确实探测了 TLB。)
测试代码
代码凌乱,比想象中更繁琐。
主要的障碍是#AC 仅在 CPL=3 下工作。
所以我们需要创建 CPL=3 描述符,加上一个 TSS 段和一个 TSS 描述符。
为了处理异常,我们需要一个 IDT,我们还需要分页。
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,DWORD [es:7000h]
;Base 1 + Offset 3 = 4,Should not fault (marker stays A)
add bx,DWORD [es:7003h]
;Base 1 + Offset 3 = 4 but page not mapped,Should #PF (markers becomes C)
add bx,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,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,DPL 0
dd 0000ffffh,00cffa00h ;18 Code,DPL 3
dd 0000ffffh,00cff200h ;20 Data,DPL 3
dd 0001ffffh,00cff200h ;28 Data,DPL 3,Base = 1
dd 7000ffffh,00cf8900h ;30 Data,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 的微架构中设置得很深。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。