Linux 为什么没有';当存储超过BSS的末尾时,是否会出现分段错误?
我正在试验汇编语言,并编写了一个程序,将2个硬编码字节打印到标准输出中。这是:Linux 为什么没有';当存储超过BSS的末尾时,是否会出现分段错误?,linux,assembly,x86,segmentation-fault,nasm,Linux,Assembly,X86,Segmentation Fault,Nasm,我正在试验汇编语言,并编写了一个程序,将2个硬编码字节打印到标准输出中。这是: section .text global _start _start: mov eax, 0x0A31 mov [val], eax mov eax, 4 mov ebx, 1 mov ecx, val mov edx, 2 int 0x80 mov eax, 1 int 0x80 segment .bss
section .text
global _start
_start:
mov eax, 0x0A31
mov [val], eax
mov eax, 4
mov ebx, 1
mov ecx, val
mov edx, 2
int 0x80
mov eax, 1
int 0x80
segment .bss
val resb 1; <------ Here
section.text
全球启动
_开始:
mov-eax,0x0A31
mov[val],eax
mov-eax,4
mov-ebx,1
移动电子计算机,val
mov edx,2
int 0x80
mov-eax,1
int 0x80
第2部分:bss
val-resb-1 与大多数其他现代体系结构一样,x86在x86上使用(同样与许多其他体系结构一样),粒度为4KB
对val
的4字节存储不会出错,除非链接器碰巧将其放置在页面的最后3个字节,并且下一个页面未映射
实际情况是,您只需覆盖val
之后的内容。在这种情况下,它只是页面末尾未使用的空间。如果您在BSS中有其他静态存储位置,您将使用它们的值。(如果您愿意,可以称之为“变量”,但“变量”的高级概念不仅仅意味着内存位置,变量可以存在于寄存器中,并且不需要地址。)
除了上面链接的维基百科文章外,另请参见:
- (页表格式的内部结构,以及操作系统如何管理和CPU如何读取)
但实际上将2个字节(charcode代表1和换行符)放入内存位置
mov[val],eax
是一个4字节的存储。操作数大小由寄存器决定。如果要进行2字节存储,请使用mov[val],ax
有趣的事实:MASM会在操作数大小不匹配时发出警告或出错,因为它会根据在其后面保留空间的声明,将大小与符号名神奇地关联起来。NASM不会妨碍您,因此,如果您编写了mov[val],0x0A31
,这将是一个错误。这两个操作数都不表示大小,因此需要mov-dword[val],0x0A31
(或word
或byte
)
将val
放在页面末尾以获取segfault
由于某种原因,BSS在32位二进制文件中不会从页面的开头开始,但它接近页面的开头。您没有链接到任何其他将占用BSS中大部分页面的内容nm bss no SEGFULT
显示它位于0x080490a8
,4k页面是0x1000
字节,因此bss映射中的最后一个字节将是0x08049fff
当我向.text
部分添加指令时,BSS开始地址似乎会发生变化,因此这里链接器的选择可能与将内容打包到ELF可执行文件有关。这没有多大意义,因为BSS没有存储在文件中,它只是一个基址+长度。我不会去兔子洞;我确信将.text
稍微增大会导致BSS从页面的开头开始,这是有原因的,但我不知道它是什么
无论如何,如果我们构造BSS使val
正好位于页面末尾之前,我们可能会得到一个错误:
... same .text
section .bss
dummy: resb 4096 - 0xa8 - 2
val: resb 1
;; could have done this instead of making up constants
;; ALIGN 4096
;; dummy2: resb 4094
;; val2: resb
然后构建并运行:
$ asm-link -m32 bss-no-segfault.asm
+ yasm -felf32 -Worphan-labels -gdwarf2 bss-no-segfault.asm
+ ld -melf_i386 -o bss-no-segfault bss-no-segfault.o
peter@volta:~/src/SO$ nm bss-no-segfault
080490a7 B __bss_start
080490a8 b dummy
080490a7 B _edata
0804a000 B _end <--------- End of the BSS
08048080 T _start
08049ffe b val <--------- Address of val
gdb ./bss-no-segfault
(gdb) b _start
(gdb) r
(gdb) set disassembly-flavor intel
(gdb) layout reg
(gdb) p &val
$2 = (<data variable, no debug info> *) 0x8049ffe
(gdb) si # and press return to repeat a couple times
关键事项:rwxp
表示读/写/执行和私有。即使在第一条指令之前就停止了,不知何故它已经“脏”(即写入)。文本段也是如此,但这是gdb将指令更改为int3
所期望的
08049000-0804a000(以及映射的4KB
大小)向我们显示,BSS只有一个页面被映射。没有数据段,只有文本和BSS。听起来像是未定义的行为保存了您的字节,因为保留字节后仍有内存映射。在普通计算机上,页面粒度为4096字节。@St.Antario否,在考虑整个环境(硬件和操作系统)时,它的定义很好。如果在val:
之前添加resb 4094
,它会崩溃吗?(4094+1=4095,因此对于assembler+linker,它看起来仍然只值一个页面,但是4字节写入应该访问另一个页面)。同样,出于某种奇怪的原因(链接器脚本/etc),在这种情况下,您的可执行文件可能已经保留了2+个页面,因此无法保证崩溃(您可以使用类似objdump
或链接器的映射文件来检查二进制的元数据,为特定部分设置了多少空间)@Ped7g:原来BSS不一定从一页的开头开始。请参阅我的答案更新,了解如何执行您的建议nm
是检查符号地址最有用的工具。它听起来可能很奇怪,但我想感谢您对出现分段错误的解释:)。我使用nasm
并使用-elf64
选项编译源代码。bss段从页面的开头开始0000000000 601000
。放置一些虚拟内容后,我得到了0000000000 601ffa b val
。然后是mov[val]rax
segfault。这取决于虚拟大小,bss段可能从开头开始,也可能不从开头开始。如果dummy db 0xFFE
则0000000000 6000db\u bss\u启动
。如果dummy db 0xFFA
则0000000000 601000 B\uuu bss\u启动
。你能解释一下吗?那个汇编器/链接器是特定的吗?编译并链接如下nasm-f elf64 segfult.asm ld segfault.o
@St.Antario:在64位代码中,除非您已经阅读并理解,否则不要使用int 0x80
接口。我假设是32位代码,因为您使用的是32位ABI(它会截断所有内容,包括指向32位的指针)。@St.Antario:我假设您的意思是dummy resb 0xFFE
db
将一个字节组合到输出中,并且无论如何都不能在BSS中放入非零值。我注意到BSS开始作为一个页面的开始,甚至不仅仅是在.text
部分添加mov[val],ax
的奇怪之处。如果你好奇的话,你应该问一个新问题,我不知道
... the r-x private mapping for .text
08049000-0804a000 rwxp 00000000 00:15 2885598 /home/peter/src/SO/bss-no-segfault
Size: 4 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
...
[vvar] and [vdso] pages exported by the kernel for fast gettimeofday / getpid