我正在制作基本的装配减法功能并将结果打印到控制台。这里是我认为应该工作的代码:
(使用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
这样做?
非常感谢任何想法!
答案 0 :(得分:3)
.int
不会留空。您的计划没有BSS。 .int 0
应该有效,但使用仅保留空间的指令更具惯用性:
使用BSS部分中的.space 4
保留4个字节。或者使用.comm output 4
在BSS中保留4B,而不首先使用.bss
指令。 .int 0
也应该有效,但使用仅保留空间的指令更为惯用。
另请参阅gas manual和x86代码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?)
read
和write
系统调用需要明确的长度。您不需要(并且不应该)在传递给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
...