【问题标题】:Errors when linking 32/64bit assembly in Linux在 Linux 中链接 32/64 位程序集时出错
【发布时间】:2016-06-01 19:52:42
【问题描述】:

我是汇编语言编程的新手。我正在尝试按照here 概述的步骤来更好地了解组装和优化。我的操作系统是 Linux Mint,我正在尝试使用 NASM 汇编程序,尽管没有成功。

和演练一样,代码是:

BITS 32
GLOBAL main
SECTION .text
main:
    mov eax, 42
    ret

使用 nasm 编译成功,使用命令:nasm -f elf tiny.asm

但如果我尝试使用 gcc 与命令链接:gcc -Wall -s tiny.o

我收到以下错误: /usr/bin/ld: 输入文件 `tiny.o' 的 i386 架构与 i386:x86-64 输出不兼容

快速搜索告诉我应该使用以下 ld 命令链接:ld -m elf_i386 -s -o tiny tiny.o

但是,这样做会给我以下警告: ld:警告:找不到入口符号_start;默认为 0000000008048060

如果我 ./tiny 我得到一个分段错误。并且./tiny ; echo $? 还返回数字“139”,这是......意想不到的。

环顾四周,我发现通过将 1 传递给 eax 寄存器并将 0 传递给 ebx 并使用我不熟悉的 int 命令来结束程序,解决了一些问题......但考虑到我的目标是让程序尽可能小,我宁愿不添加额外的代码行。

我应该添加编译和链接这个类似的代码(GAS):

.global main
.text
main:
    mov $32, %eax

使用 gcc 编译器似乎可以完美运行。我在这里不知所措。任何正确方向的观点都将不胜感激。

【问题讨论】:

  • 如果您在 64 位 Linux Mint 上并尝试创建 32 位应用程序(使用 GCC 链接)尝试执行 sudo apt-get install gcc-multilib g++-multilib 然后尝试 nasm -f elf32 tiny.asmgcc -m32 -Wall -s tiny.o -o tiny
  • 非常感谢!这为我解决了所有问题,您介意再解释一下附加选项的作用吗?他们只是让 32 位代码在 64 位环境中运行吗?
  • 默认情况下,GCC 创建 32 位程序所需的文件未安装在 64 位 Linux 上。 sudo apt get... 行简单地下载并安装在 64 位 Linux Mint 上进行 32 位 GCC 开发所需的 GCC 文件。由于您在汇编程序的顶部有BITS 32,因此您希望将其编译为nasm -f elf32 tiny.asm 所做的32 位ELF 对象)(它默认输出一个名为tiny.o 的文件。对象文件需要链接到一个可执行文件gcc -m32 -Wall -s tiny.o -o tiny 就是这样做的。我添加的-m32 选项表示我们要从32 位对象生成一个32 位可执行文件。
  • 通过使用 GCC 链接可执行文件,生成的程序实际上做了一些 C 运行时初始化,然后像 C 一样调用名为 main 的标签 函数。当您在 main 中执行 ret 时,它会返回到 C 运行时代码并为您干净地退出程序。

标签: gcc assembly x86 linker-errors ld


【解决方案1】:

这适用于 Linux Mint、Ubuntu、Xubuntu 和其他基于 Ubuntu 的现代发行版。对于其他 Debian 系统,信息相同,但您可以从安装命令中删除 sudo 命令并以 root 用户身份运行该命令。


默认情况下,GCC 创建 32 位程序所需的文件未安装在 64 位 Linux Mint 上。此命令将安装所需的文件:

sudo apt-get install gcc-multilib g++-multilib 

要组装为 32 位对象,然后链接到 32 位可执行文件,您可以这样做:

nasm -f elf32 tiny.asm -o tiny.o
gcc -m32 -Wall -s tiny.o -o tiny

由于您在汇编程序文件的顶部有 BITS 32,因此您希望将其编译为 32 位 ELF 对象,这就是 NASM 命令行上的 -f elf32 选项所做的。

GCC 用于将对象链接到最终的可执行文件。 -m32 的添加告诉 GCC 将对象链接到 32 位可执行文件。 -m32 选项会覆盖在 64 位 Linux 发行版上生成 64 位可执行文件的正常默认行为。


通过使用 GCC 链接可执行文件,生成的程序实际上提供了一个 _start,它执行 C 运行时初始化,然后调用名为 main 的标签,就好像它是一个 C 函数。

当您在 main 函数中执行 ret 时,它会返回到 C 运行时代码并为您干净地退出程序。

使用 GCC 对于新的汇编语言开发人员来说是一种更简单、更安全的方法。它还允许您添加调用 C 运行时函数的代码,例如 printfscanfatoi 和大多数其他标准 C 库函数。

【讨论】:

  • 感谢您的清晰解释,现在一切都变得更有意义了。我不确定 C 库所扮演的角色。最后一个问题:如果没有 main 函数,我还能使用 gcc 访问 C 的库吗?
  • 可以使用 Pikrass 回答中的方法绕过 C 运行时并仍然调用 C 运行时函数,但对于那些如果运行时设置不正确,则功能会不正确。如果您使用 GCC-static 选项构建,这可能是一个大问题。按照我在此答案中建议的方式进行操作通常更安全。 Pikrass 的回答是不安全的方法,但可以这样做,但如果未正确初始化,您可能会冒 C 运行时函数在未来版本中失败的风险。
  • 如果您根本不需要 C 运行时,您可以使用 Pikrass 的答案,尽管他没有提供正确编译和链接所需的命令。
  • 您使用的tutorial 作为示例实际上会在稍后的文档中详细介绍绕过C 运行时启动文件。如果您是装配开发新手,我不会考虑。
【解决方案2】:

程序的默认入口点是_start 符号。当您使用 gcc 编译时,编译器会创建一个名为 _start 的函数,在调用您的 main 之前进行一些设置。我相信它实际上是 C 库的一部分。因为您使用的是更底层的工具,所以您没有链接到 C 库,因此您必须自己执行此操作。

因此,只需将您的 main 更改为 _start,ld 就不会再抱怨了。现在,您可能最好使用 libc 的 _start,因为您当前的实现仍然会出现段错误:您需要使用 exit 系统调用停止您的程序,而不是使用 ret

Michael 的评论告诉你如何使用 gcc 从汇编代码编译(所以你有 libc 的_start)。如果您仍想自己做,您的程序需要如下所示:

BITS 32
GLOBAL _start
SECTION .text
_start:
    mov ebx, 42
    mov eax, 1
    int 0x80

你把exit的系统调用号放在eax,返回码放在ebx,触发0x80中断。

【讨论】:

  • 谢谢,这个解决方案可以阻止链接器抱怨。但是,在运行时,仍然会出现“Segmentation”错误错误,然后返回不正确的值 139 而不是 42。
  • 对不起,我有一个坏习惯,先发布我的答案,然后在尝试添加更多信息时对其进行编辑。现在应该已经完成​​了。
  • 感谢您提供更多信息并解释 int 0x80 和 ebx 返回给我。我打算再玩一下,我找到了一个类似的解决方案来解决其他人的问题,但担心移动额外的值会增加我的程序的大小。
猜你喜欢
  • 2010-12-28
  • 1970-01-01
  • 2019-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-08
  • 2011-04-20
相关资源
最近更新 更多