我正在编写一个程序,它只是获取我的 CPU 名称并要求我的操作系统将其打印出来。 我使用cpuid操作,效果很好。但一开始我必须为字符串保留一些内存,48 个字节用于我从 eax:ebx:ecx:edx 获得的 3 部分信息,还有一个用于存储 符号:
section .bss
cpuname resb 49
所以,我的代码对于初学者来说效果非常好:
section .text
global _start
_start:
xor edi, edi ; using edi for memory address shift because every step I use 16 bytes (4 bytes*4 registers)
regcpy:
mov eax, 0x80000002 ; specific number for cpuid command to get my CPU name
mov esi, edi
shr esi, 4 ; I want to do it three times
add eax, esi ; up to 0x80000004 as it was described for cpuid
cpuid
mov [cpuname + edi], eax
mov [cpuname + 4 + edi], ebx
mov [cpuname + 8 + edi], ecx
mov [cpuname + 12 + edi], edx
add edi, 16
cmp edi, 48 ; three iterations overall: 16, 32, 48 and when 48
jne regcpy ; I don't repeat it and proceed the program downwards
mov [cpuname + 48], byte 10 ; adding \n because otherwise it's ugly
mov rax, 1
mov rdi, 1
mov rsi, cpuname
mov rdx, 49 ; asking the OS to print ASCII symbols with addresses
syscall ; from 'cpuname' byte up to 'cpuname+48' byte out
mov rax, 60
xor rbx, rbx
syscall ; 60 = 0x3c it's sys_exit for x86_64
如果我尝试在 .bss 部分保留更少的内存,就会发生我不明白的事情:
section .bss
cpuname resb 40
或
section .bss
cpuname resb 2
编译并工作正常,我得到输出:我的 CPU 的全名,而看起来应该存在分段错误。 但是当我预订0时,
section .bss
cpuname resb 0
它成功地出现了段错误。 我有一个模糊的假设,当我使用 resb [number >= 0] 时,我的操作系统至少保留了一些特定数量的字节,因此它不会出现段错误,除非我的写入尝试的“长度”达到一些具体的数字。 就像即使我写 resb 1,Linux 也会保留 64、128 或...我不知道,我只是想猜测。或者我完全错了,不是吗?但是……当我尝试在 C 中做类似的事情时,即使 1 字节溢出也会导致段错误? 请告诉我为什么当我想要的时候却无法让我的程序出现段错误。
虚拟内存在 pages 上运行,在 x86 上为 4 Kib。当您访问尚未映射到进程地址空间的页面时,就会发生段错误。
因此,如果访问“无效”地址与任何有效内存不在同一页面上,只会导致段错误。您的程序可能以这样的方式链接,即
.bss
部分是页面对齐的,以便它从新页面开始,并且其大小是整数页。因此,如果大小非零,则至少会分配一页,并且 4096 字节以内的访问不会出错。如果大小为零,则可能会分配 0 个页面,因此 .bss 部分的开头位于未映射的页面上,并且所有访问都会出错。然而,这种行为并不能得到保证;在某些情况下 .bss
可能会与其他程序数据共享一个页面,那么大小和出错地址之间的关系就会不同。
请注意,无论是否发生段错误,访问未专门为此目的分配的内存位置仍然是“错误”。如果您写入这样的地址,您可能会无意中覆盖程序中的一些其他数据。如果你读了它,即使它没有错误,如果你不知道它的含义或是谁把它放在那里,你读到的值也是没有用的。