我正在尝试汇编语言,并写了一个程序,打印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 val resb 1; <------ Here
注意,我在bss段中只保留了1个字节,但实际上把2个字节(charcode for 1
和newline
)放入了内存位置。 程序运行良好。 它打印了1
字符,然后newline
。
但是我期望分段错误。 为什么不发生。 我们只保留了1个字节,但是放了2个字节。
与大多数其他现代体系结构一样,x86使用分页/虚拟内存来保护内存。 在x86上(与其他许多架构一样),粒度是4kiB。
除非链接器恰好将其放在页面的最后3个字节中,否则一个4字节的存储区将不会出现故障,并且下一页未映射。
实际上发生的是你只是覆盖val
之后的任何东西。 在这种情况下,这只是页面末尾未使用的空间。 如果您在BSS中有其他的静态存储位置,您可以踩下它们的值。 (如果需要的话,称它们为“变量”,但是“变量”的高级概念并不仅仅意味着内存位置,变量可以存在于寄存器中而不需要有地址)。
除了上面链接的维基百科文章外,另请参阅:
但实际上把2个字节(charcode for 1和换行符号)放入内存位置。
mov [val], eax
是一个4字节的商店。 操作数大小由寄存器决定。 如果你想做一个2字节的存储,使用mov [val], ax
。
有趣的事实:MASM会警告或错误操作数大小不匹配,因为它神奇地将大小与基于声明的符号名称相关联,后者在其后面保留空间。 NASM保持你的方式,所以如果你写mov [val], 0x0A31
,这将是一个错误。 无论操作数是否意味着大小,所以您需要mov dword [val], 0x0A31
(或word
或byte
)。
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
segfaults,因为它跨越了未映射的页面。 mov [var], ax
会起作用(因为我把var
2个字节放在页面结束之前)。
在这一点上, /proc/<PID>/smaps
显示:
... the rx 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。