我想编译下面的asm.S文件
#include <stdio.h>
.global _start
.text
message_text:
call program
str: .string "hello world"
.equ len, (. - str)
_start:
jmp message_text
program:
call puts@PLT
我已经尝试了gcc -m32 asm.S
以及gcc -m32 asm.S -fPIC
和一系列其他的东西,但我不断遇到错误,如
/usr/包含/标准音频.h:847:错误:没有此指令:外部空白...
我做错了什么?
我知道预处理器只是把头插入到asm文件中,所以汇编程序会出错b/c C函数原型不是汇编指令。然而,我已经能够成功地使用#include <asm/unistd.h>
和#include <syscall.h>
。那么为什么stdio. h不能工作呢?
顺便说一句,我试着注解掉标题,只保留call puts@PLT
这一行,这样就可以通过
as --32 -o output.o asm.S
ld -m elf_i386 -o output output.o -lc
然而现在,当我尝试./output
时,我得到
没有这样的文件或目录:./输出
尽管 output 显示在ls
中,如果我运行file output
,
输出:ELF 32位LSB可执行文件,英特尔80386,版本1(SYSV),动态链接,解释程序/usr/bin/libc.so.1,未剥离
如果相反我做
as --32 -o output.o asm.S
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o output -lc output.o
我可以运行生成的二进制文件,但同样,这是在删除#include <stdio.h>
的情况下。
1条答案
按热度按时间yeotifhr1#
stdio.h
是一个带有C语法的C头文件,而不仅仅是C预处理器的东西。在GAS中,任何未声明的符号都被假定为extern
,因此您可以只使用call puts@plt
,而无需任何其他指令。(与NASM不同,NASM需要extern puts
,它与C的语法不同,因此C头也没有任何帮助。)一些C头文件,如
<asm/unistd.h>
,* 仅 * 包含#if
和#define
预处理器指令,并且也可以包含在.S
文件中。大多数头文件不能包含,尽管Linux内核的asm/*.h
通常可以。标准头 * 可以 * 被编写为在C特定部分周围使用
#ifndef __ASSEMBLY__
,这对像<fcntl.h>
这样的头有用,它具有像O_RWDR
和O_TRUNC
这样的常量以及C原型,但它们没有。stdio API早于C预处理器,这就是为什么它不幸地使用像
fopen(name, "r+")
这样的字符串而不是ORing常量的原因,所以这在stdio.h
上是不必要的,也没有用。另请参阅:
grep
的标准头文件中提取O_*
宏常量,以使您可以在.S
中使用#include
。gcc -E -dM
的定义,例如,获取O_RDONLY
和其他常量,以便与系统调用一起使用。顺便说一句,你的
jmp
/call
压入一个字符串地址的技巧看起来像外壳代码。但是你不能在外壳代码中使用call puts@plt
,因为从堆栈缓冲区到PLT条目的相对偏移量将被随机化。或者,这只是一种制作32位PIC代码的笨拙方法,在这种情况下,它很紧凑,但效率很低:使返回地址预测失效。(不匹配的调用/ret)。看看gcc或
clang -O2 -m32 -fPIC
如何编译C代码,用call
将GOT的地址导入寄存器;请注意,call next_instruction
(0
的相对偏移量)是一种特殊情况,在大多数CPU上不计入返回地址预测。请参见 * Reading program counter directly *这是Godbolt编译器资源管理器的clang
-O2 -m32 -fPIE
输出,其中保留了必要的指令,手动删除了不必要的指令。(Godbolt的指令过滤器通常会过滤所有内容,前提是您知道哪些指令是需要的。)这是在与位置无关的32位代码中将静态地址写入寄存器的标准方法。(不要介意它是一个
main
,因此它返回而不是调用exit
、_exit
或原始系统调用,但在使用stdio函数的程序中不要这样做;如果有剩余的缓冲输出(例如,由于写入管道),则不会刷新标准输出。)这个两步计算得到GOT基是不必要的,因为我们不使用全局偏移表;这只是编译器在32位模式下为与位置无关的代码选择的引用锚(其中RIP相对寻址不可用)。我们可以在
pop
之后使用add $.L.str - .L0$pb, %ebx
。而且我们可以使用%eax
,这样我们就不必保存/恢复EBX。在32位中使用位置相关的代码要简单得多,这样你就可以
push $.L.str
,或者使用64位代码,这样你就可以lea .L.str(%rip), %rdi
;call puts@plt
.x1米38英寸1x
这就是你的问题。这个路径可能不存在,当然也不是运行时动态链接器
/lib/ld-linux.so.2
(也就是ELF解释器)。如果你想写你自己的_start
,但是让它用libc和正确的ELF解释器生成一个动态链接的可执行文件,那么就使用gcc -m32 -nostartfiles
。(使用gcc -v
可以查看它传递给汇编器和链接器的参数是什么)。我不明白为什么你的
ld
会有这样一个奇怪的默认值;通常默认的解释器路径对于32位模式是正确的,但对于64位可执行文件是错误的。另请参阅 * Assembling 32-bit binaries on a 64-bit system (GNU toolchain) *:构建命令和
_start
与main
。请注意,从您自己的
_start
调用libc函数只适用于动态链接的可执行文件,因为libc使用动态链接器钩子在您的_start
运行之前调用它的init函数。如果您使用-static
,puts
中的代码将崩溃。