存储超过BSS结束时,为什么没有出现分段错误?

时间:2017-11-08 13:07:00

标签: linux assembly x86 segmentation-fault nasm

我正在尝试使用汇编语言并编写了一个程序,它将2个硬编码字节打印到stdout中。这是:

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

请注意,我在bss段中只保留了1个字节,但实际上将2个字节(1newline符号的字符代码)放入内存位置。该计划运作良好。它打印了1字符,然后是newline

但我预计分段错误。为什么不发生。我们只保留了1个字节,但是放了2个。

1 个答案:

答案 0 :(得分:4)

与大多数其他现代架构一样,x86使用paging / virtual memory for memory protection.在x86上(与许多其他架构一样),粒度为4kiB。

val的4字节存储将不会发生故障,除非链接器恰好将其放在页面的最后3个字节中,并且下一页未映射。

实际发生的事情是你只是覆盖val之后的任何内容。在这种情况下,它只是页面末尾的未使用空间。如果您在BSS中有其他静态存储位置,那么您将踩到它们的值。 (如果需要,可以将它们称为“变量”,但“变量”的高级概念不仅仅意味着存储位置,变量可以存在于寄存器中,而且永远不需要有地址。)

除了上面链接的维基百科文章,另见:

  

但实际上将2个字节(charcode为1和换行符号)放入内存位置。

mov [val], eax是一个4字节的存储区。操作数大小由寄存器决定。如果您想要执行2字节存储,请使用mov [val], ax

有趣的事实:MASM会对操作数大小不匹配发出警告或错误,因为它会根据在其后面保留空间的声明神奇地将大小与符号名称相关联。 NASM不会让你失望,所以如果你写了mov [val], 0x0A31,那将是一个错误。这两个操作数都不是一个大小,因此您需要mov dword [val], 0x0A31(或wordbyte)。

在页面末尾放置val以获取段错误

由于某种原因,BSS不是以32位二进制文​​件的页面开头开头,而是靠近页面的开头。您没有链接任何会占用BSS中大部分页面的内容。 nm bss-no-segfault显示它位于0x080490a8,4k页面为0x1000字节,因此BSS映射中的最后一个字节将为0x08049fff

当我向.text部分添加指令时,似乎BSS起始地址发生了变化,因此这里的链接器选择可能与将事物打包成ELF可执行文件有关。它没有多大意义,因为BSS没有存储在文件中,它只是一个基址+长度。我不会去那个兔子洞;我确信有一个原因是在页面开头的BSS中使.text略大一些,但IDK是什么。

无论如何,如果我们构建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

mov [var], eax段错误,因为它跨越了未映射的页面。 mov [var], ax会起作用(因为我在页面结尾之前放了var 2个字节。)

此时,/proc/<PID>/smaps显示:

... 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

关键词:rwxp表示读/写/执行,私有。甚至在第一条指令之前停止,不知何故它已经“脏”(即写入)。文本段也是如此,但是gdb会将指令更改为int3

08049000-0804a000(以及映射的4 kB大小)向我们显示BSS只有1页映射。没有数据段,只有文本和BSS。