【问题标题】:Why does x86 program segfault without .data section? [duplicate]为什么 x86 程序在没有 .data 部分的情况下会出现段错误? [复制]
【发布时间】:2017-01-14 08:06:51
【问题描述】:

我正在制作一个基本的汇编减法函数并将结果打印到控制台。这是我认为应该工作的代码: (编译as output.sld a.out -e _start -o output

    .bss
output:
    .int

    .text
    .global _start

_start: 

movl $9, %eax
movl %eax, %ebx
movl $8, %eax
subl %eax, %ebx

movl %ebx, (output)

# ASCII for digits is 0x30 greater than digit value
addl    $0x30, output

movl    $2, %edx        # write 2 bytes (need 1 for null?)
movl    $output, %ecx   # output
movl    $1, %ebx        # write to stdin
movl    $4, %eax        # syscall number for write
int $0x80               # invoke syscall

# CR
movl    $2, %edx
movl    $13, (output)
movl    $output, %ecx
movl    $1, %ebx
movl    $4, %eax
int $0x80

# LF
movl    $2, %edx
movl    $10, (output)
movl    $output, %ecx
movl    $1, %ebx
movl    $4, %eax
int $0x80

# exit
movl    $0, %ebx
movl    $1, %eax
int $0x80

但是,这个程序有段错误。 我发现如果我在最后添加一个简单的 .data 部分:

    .data   
pingle:
    .int 666

它工作正常。为什么我需要 .data 段?当我每次写入 2 个字节时,我是否会溢出其中一个段?还是多次覆盖output

非常感谢任何想法!

【问题讨论】:

  • 段错误在哪里?在 gdb 中运行您的程序以找出答案。可能有一个.data 部分恰好将可写内存放在您的程序错误地尝试读取或写入的位置,但这只是一个猜测,直到您显示哪条指令出现段错误以及寻址模式中使用的寄存器和符号的内容.
  • 终于找到了副本。 stackoverflow.com/questions/36821123/… 有完全不同的症状,但原因是一样的:认为.double 带有一个空列表会为一个 double 保留空间。

标签: assembly x86 segmentation-fault


【解决方案1】:

.int 为空列表不保留空间。您的程序没有 BSS。 .int 0 应该可以工作,但是使用只保留空间的指令更习惯:

在 BSS 部分使用 .space 4 保留 4 个字节。或者使用.comm output 4 在BSS 中保留4B,而不首先使用.bss 指令。 .int 0 也应该可以工作,但是使用只保留空间的指令更符合习惯。

另请参阅 gas manual 标签 wiki。

IIRC,BSS 最终可能与数据段位于同一页面中,并且内存访问检查具有页面粒度。这解释了为什么从/到 (output) 加载/存储碰巧可以工作,即使它已经过了 BSS 的末尾。


一个例子

 ## nobss.S
.bss
.globl output       # put this symbol 
output: .int

.text
.globl _start
_start:
    mov (output), %eax

$ gcc -g -nostdlib nobss.S
$ nm -n  ./a.out            # nm -a -n  to also include debug syms, but gas doesn't make debug info automatically (unlike NASM/YASM)
00000000004000d4 T _start
00000000006000db T __bss_start
00000000006000db T _edata
00000000006000db T output
00000000006000db T end_of_bss       # same address as output, proving that .int reserved no space.
00000000006000e0 T _end

$ gdb ./a.out
(gdb) b _start
(gdb) r
 # a.out is now running, but stopped before the first instruction

# Then, in another terminal:
$ less /proc/$(pidof a.out)/maps 
00400000-00401000 r-xp 00000000 09:7e 9527300                            /home/peter/src/SO/a.out
7ffff7ffb000-7ffff7ffd000 r--p 00000000 00:00 0                          [vvar]
7ffff7ffd000-7ffff7fff000 r-xp 00000000 00:00 0                          [vdso]
7ffffffdd000-7ffffffff000 rwxp 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

请注意没有任何可能是 BSS 的匿名映射,或任何可写的 a.out(数据)映射。只有我们的程序文本被映射。 (使用私有映射,但实际上仍然是写时复制共享。)请参阅this answer for what the fields mean


不需要在 readwrite 中终止 0 个字节

movl    $2, %edx        # write 2 bytes (need 1 for null?)

readwrite 系统调用采用明确的长度。您不需要(也不应该)在传递给 write() 的长度中包含终止零字节。例如,

# You want this
$ strace echo foo > /dev/null
...
write(1, "foo\n", 4)                    = 4
...

# not this:
$ strace printf 'foo\n\0' > /dev/null

...
write(1, "foo\n\0", 5)                  = 5
...

【讨论】:

    猜你喜欢
    • 2013-12-28
    • 2011-07-05
    • 2020-02-27
    • 1970-01-01
    • 2019-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-31
    相关资源
    最近更新 更多