assembly gnu GAS在asm中使用stdio.h并在64位主机上编译32位

qq24tv8q  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(152)

我想编译下面的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>的情况下。

yeotifhr

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_RWDRO_TRUNC这样的常量以及C原型,但它们没有。
stdio API早于C预处理器,这就是为什么它不幸地使用像fopen(name, "r+")这样的字符串而不是ORing常量的原因,所以这在stdio.h上是不必要的,也没有用。
另请参阅:

顺便说一句,你的jmp/call压入一个字符串地址的技巧看起来像外壳代码。但是你不能在外壳代码中使用call puts@plt,因为从堆栈缓冲区到PLT条目的相对偏移量将被随机化。
或者,这只是一种制作32位PIC代码的笨拙方法,在这种情况下,它很紧凑,但效率很低:使返回地址预测失效。(不匹配的调用/ret)。看看gcc或clang -O2 -m32 -fPIC如何编译C代码,用call将GOT的地址导入寄存器;请注意,call next_instruction0的相对偏移量)是一种特殊情况,在大多数CPU上不计入返回地址预测。请参见 * Reading program counter directly *
这是Godbolt编译器资源管理器的clang -O2 -m32 -fPIE输出,其中保留了必要的指令,手动删除了不必要的指令。(Godbolt的指令过滤器通常会过滤所有内容,前提是您知道哪些指令是需要的。)
这是在与位置无关的32位代码中将静态地址写入寄存器的标准方法。(不要介意它是一个main,因此它返回而不是调用exit_exit或原始系统调用,但在使用stdio函数的程序中不要这样做;如果有剩余的缓冲输出(例如,由于写入管道),则不会刷新标准输出。)

.text
        .globl  main                            # -- Begin function main
main:
        pushl   %ebx
        subl    $8, %esp
        calll   .L0$pb
.L0$pb:
        popl    %ebx
.Ltmp0:
        addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb), %ebx   # pointer to the GOT
        leal    .L.str@GOTOFF(%ebx), %eax           # .L.str address relative to GOT
        movl    %eax, (%esp)
        calll   puts@PLT
.Ltmp1:
        addl    $8, %esp
        popl    %ebx
        retl                 # note that _start can't ret, it has to call exit.

#   .section        .rodata.str1.1,"aMS",@progbits,1  # clang used this
    .section        .rodata                      # a human would normally do this
.L.str:
        .asciz  "hello"

这个两步计算得到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) *:构建命令和_startmain
请注意,从您自己的_start调用libc函数只适用于动态链接的可执行文件,因为libc使用动态链接器钩子在您的_start运行之前调用它的init函数。如果您使用-staticputs中的代码将崩溃。

相关问题