我制作了两个程序来输出两个字符串,一个在汇编中,另一个在C中。 这是汇编程序:
.section .data
string1:
.ascii "Hola\0"
string2:
.ascii "Adios\0"
.section .text
.globl _start
_start:
pushl $string1
call puts
addl $4, %esp
pushl $string2
call puts
addl $4, %esp
movl $1, %eax
movl $0, %ebx
int $0x80
我用
构建程序as test.s -o test.o
ld -dynamic-linker /lib/ld-linux.so.2 -o test test.o -lc
输出符合预期
Hola
Adios
这是C程序:
#include <stdio.h>
int main(void)
{
puts("Hola");
puts("Adios");
return 0;
}
我得到了预期的输出,但是当使用gcc -S(OS是Debian 32位)将此C程序转换为程序集时,输出程序集源代码在两个字符串中都不包含空字符,如下所示:
.file "testc.c"
.section .rodata
.LC0:
.string "Hola"
.LC1:
.string "Adios"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
leal 4(%esp), %ecx
.cfi_def_cfa 1, 0
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
.cfi_escape 0x10,0x5,0x2,0x75,0
movl %esp, %ebp
pushl %ecx
.cfi_escape 0xf,0x3,0x75,0x7c,0x6
subl $4, %esp
subl $12, %esp
pushl $.LC0
call puts
addl $16, %esp
subl $12, %esp
pushl $.LC1
call puts
addl $16, %esp
movl $0, %eax
movl -4(%ebp), %ecx
.cfi_def_cfa 1, 0
leave
.cfi_restore 5
leal -4(%ecx), %esp
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.9.2-10) 4.9.2"
.section .note.GNU-stack,"",@progbits
我的两个问题是:
1)为什么gcc生成的汇编代码不会在两个字符串的末尾附加空字符?我以为C是自动完成的。
2)如果我跳过手工制作的汇编代码中的空字符,我会得到这个输出:
HolaAdios
Adios
我明白为什么我在第一行得到“HolaAdios”部分,但为什么程序在“Adios”部分之后成功结束,如果它不是以空终止的?
答案 0 :(得分:5)
.string
始终附加一个空终结符,如here所示。puts
只是继续,直到它看到一个空字节。 \x00
是非常常见的,附近必须有一个,所以它可以工作(可能是由于.rodata
的部分对齐)。答案 1 :(得分:0)
只是添加更多细节:
你的第二个字符串是偶然的,因为在.data
部分后面没有任何内容。你动态链接glibc,它也有一个.data
部分,它被映射到你的进程的地址空间。这是一个私有映射,但我认为是映射,而不是复制,所以它是页面对齐的。保存可执行文件数据段的页面的其余部分用零填充。 (ABI可能无法保证这一点,但Linux必须采取措施避免泄漏内核数据)。
当您的可执行文件加载到内存中时,数据段将与文本段分开加载。 See this answer关于部分(链接器关心的)和可执行段(程序加载器关心的)之间的区别。
请注意,gcc将字符串常量放在.rodata
部分中,链接器将其放置在可执行文件的文本段中,以及.text
部分:只读,以便它可以在多个部分之间共享进程运行相同的可执行文件截面默认情况下使用填充对齐,因此即使您将字符串放在.rodata
而没有零终结符,也会在第二个之后填充零。
如果碰巧在右对齐边界处结束(例如,长度是16的倍数,或其他东西),则不会发生这种情况。
顺便说一句,您可以使用strace ./string-test
确认字符串后面没有任何非打印垃圾字符。您可以看到:write(1, "Adios\n", 6) = 6
.string
是.asciz
的同义词。本手册使用不同的语言来描述它们处理反斜杠转义序列的事实,并附加一个零字节,但它们做同样的事情。 GNU汇编程序有很多同义词可以与许多不同的Unix供应商提供的汇编程序兼容,所以当gcc使用.zero但clang使用.skip之类的东西时,实际上没有区别,这可能会令人困惑。
我用......构建程序
您使用的命令仅适用于32位系统。在64位主机上,您将构建一个仍使用32位系统调用ABI的64位二进制文件。 (以及32位动态链接器路径,因此它甚至不会偶然工作,即使静态数据地址是低32位,因此可以传递给sys_write的32位包装器。)
另外,我建议您调用源文件test.S
。 capital-S通常用于手写asm源。您可以与gcc -m32 -nostartfiles test.S -o test
进行汇编和链接,以便以与手动相同的方式进行汇编和链接。
有关在Linux上构建asm的完整详细信息,请参阅此问答: Assembling 32-bit binaries on a 64-bit system (GNU toolchain)
另请参阅x86标记wiki以获取许多有趣的链接。