为什么没有.data部分的x86程序段错误?

时间:2016-09-06 19:11:12

标签: assembly x86 segmentation-fault

我正在制作基本的装配减法功能并将结果打印到控制台。这里是我认为应该工作的代码: (使用as output.s编译,ld 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这样做?

非常感谢任何想法!

1 个答案:

答案 0 :(得分:3)

带有空列表的

.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

read中终止0字节并且不需要write

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