在Mac OSX上使用x86汇编语言编写的“Hello World”程序

17

我想在我的Mac上进行一些x86汇编程序设计,但在生成可执行文件时遇到了问题。似乎问题出现在链接阶段。

helloWorld.s:

.data

    HelloWorldString:
    .ascii "Hello World\n"

.text

.globl _start

_start:
    # load all the arguments for write()
    movl $4, %eax
    movl $1, %ebx
    movl $HelloWorldString, %ecx
    movl $12, %edx
    # raises software interrupt to call write()
    int $0x80

    # call exit()
    movl $1, %eax
    movl $0, %ebx
    int $0x80

组装程序:
as -o helloWorld.o helloWorld.s

链接目标文件:

ld -o helloWorld helloWorld.o

我现在得到的错误信息是:
ld: could not find entry point "start" (perhaps missing crt1.o) for inferred architecture x86_64

有没有关于我做错或遗漏的建议将非常有帮助。谢谢。


4
我知道我有点晚了,但你需要将“_start”更改为“start”。 - sidyll
5个回答

21

你可能会发现使用gcc而不是试图微调汇编器和链接器更容易构建,例如:

$ gcc helloWorld.s -o helloWorld

如果你按照这个方法前进,你可能想把_start改为_main

顺便说一句,从一个可工作的C程序开始,并研究生成的汇编代码可能是很有益的。例如:

#include <stdio.h>

int main(void)
{
    puts("Hello world!\n");

    return 0;
}

使用gcc -Wall -O3 -m32 -fno-PIC hello.c -S -o hello.S编译时,生成以下内容:

    .cstring
LC0:
    .ascii "Hello world!\12\0"
    .text
    .align 4,0x90
.globl _main
_main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $LC0, (%esp)
    call    _puts
    xorl    %eax, %eax
    leave
    ret
    .subsections_via_symbols

你可能想考虑使用这个模板来编写自己的“Hello world”或其他实验性的汇编程序,特别是考虑到它已经可以构建和运行:

$ gcc -m32 hello.S -o hello
$ ./hello 
Hello world!

最后一个评论:小心从面向Linux的汇编书籍或教程中获取示例并尝试在OS X下应用它们 - 存在重要的差异!


你知道为什么我在执行 gcc -m32 hello.S -o hello 时会出现这个错误吗?ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not allowed in code signed PIE, but used in _main from /var/folders/9b/n3lsk87513d57pzh0qvxjmz00000gn/T/hello-r4fQK2.o. 要解决此警告,请不要使用 -mdynamic-no-pic 进行编译或链接 -Wl,-no_pie。 - michaelsnowden
你使用的平台是否不寻常? - Paul R
2
@michaelsnowden 这段代码是使用 -fno-PIC 生成的,所以如果你想编译它,需要使用 gcc -m32 -Wl,-no_pie hello.S -o hello。或者,可以不使用 -fno-PIC 生成代码。这样会产生稍微复杂一些的样板代码,但是你就可以编译它而不必处理 PIE 问题了。 - Rikkles
1
2023 年更新:i386 架构已被弃用,不再受支持。您应该使用 -m64 参数,而不是 -m32 参数。令人惊讶的是,在这么多年之后,只需要这两个小改动就能编译和链接上述代码。 - RAllen

4
尝试:
ld -e _start -arch x86_64 -o HelloWorld HelloWorld.S

然后:
./HelloWorld

信息:

-e <entry point>
-arch <architecture>, You can check your architecture by uname -a 
-o <output file>

是的,这样可以汇编+链接,但问题中的代码不是针对x86-64 MacOS。它是针对i386 Linux的。在修复此问题后(请参见NoOffenceIntended的答案),然后此回答将起作用。如果您将入口点命名为(适用于MacOS的)标准标签start:而不是_start:,则可以省略-e _start部分。 - Peter Cordes

2

hello.asm

.data

    HelloWorldString:
    .ascii "Hello World!\n"

.text

.globl start

start:
    ; load all the arguments for write()
    movl $0x2000004, %eax
    movl $1, %ebx
    movq HelloWorldString@GOTPCREL(%rip), %rsi
    movq $100, %rdx
    ; raises software interrupt to call write()
    syscall

    ; call exit()
    movl $0x2000001, %eax
    movl $0, %ebx
    syscall

然后运行:

$ as -arch x86_64  -o hello.o hello.asm
$ ld -o hello hello.o
$ ./hello

这是针对Mac OS X Mach-0基于GNU的汇编器的可行解决方案。

这对我起作用,但是必须删除.asm文件中的注释代码。 同时将-macosx_version_min 10.6传递给链接器命令可以避免警告,尽管它无论如何都会编译。 - lacostenycoder
当你的字符串较短时,打印100个字节将在输出中包含一堆垃圾。这些垃圾可能都是0字节,在终端上没有任何影响,因此你没有修复这个错误。另外,exit()函数从EDI获取参数,而不是EBX。也许你在想32位Linux int 0x80 ABI?哦,这是从问题中复制的。此外,没有必要从内存(GOT)中获取指针以访问自己的静态数据。只需像@NoOffenceIntended的答案一样直接使用RIP相对LEA进行计算即可。 - Peter Cordes
此外,对于x86-64,;不是“as”的注释字符。它用于分隔同一行上的语句/指令,因此汇编器将尝试将您的注释解析为代码。如果您进行了任何测试,我假设这是在添加注释之前进行的。 - Peter Cordes

1

问题中的代码看起来像是针对使用 int $0x80 ABI 的 32 位 Linux,参数存储在 EBX、ECX、EDX 中。

MacOS 上的 x86-64 代码使用 syscall 指令,参数传递和返回值与 x86-64 System V ABI for Linux 的文档相似。它与 int $0x80 完全不同,唯一的相似之处是调用号码存储在 EAX/RAX 中。但是调用号码是不同的:https://sigsegv.pl/osx-bsd-syscalls/0x2000000 做 OR 运算。

参数存储在与函数调用相同的寄存器中(除了 RCX 替换为 R10)。

另请参阅基本汇编在Mac(x86_64 + Lion)上无法工作?如何使这个简单的汇编运行?


我认为这是另一个答案中所建议的更整洁和更直观的版本。
OS X使用start而不是_start作为进程入口点。
.data
str:
  .ascii "Hello world!\n"
  len = . - str                  # length = start - end.   . = current position

.text
.globl start
start:
    movl   $0x2000004, %eax
    movl   $1, %edi
    leaq   str(%rip), %rsi  
    movq   $len, %rdx          
    syscall                       # write(1, str, len)

    movl   $0x2000001, %eax 
    movl   $0, %edi
    syscall                       # _exit(0)

通常情况下,当寄存器暗示操作数大小时,可以省略操作数大小后缀。使用xor %edi,%edi来将RDI清零。
而且使用mov $len, %edx,因为你知道大小小于4GB,所以更高效的32位零扩展mov-immediate将起作用,就像你设置RAX为调用编号一样。
请注意使用RIP相对LEA将静态数据的地址加载到寄存器中。在MacOS上的x86-64代码无法使用32位绝对寻址,因为可执行文件映射的基地址在2 ^ 32以上。
没有32位绝对地址的重定位类型,因此无法使用它们。(即使也支持64位绝对地址,但你需要使用RIP相对地址。)

是的,这段代码更好了。它仍然有一个错误,我已经修复了,现在我认为它是一个很好的例子。我还添加了一些解释性文本。我没有修复浪费代码大小的低效率(我让操作数大小匹配文档化的系统调用参数宽度,而不是使用隐式零扩展)。我也没有将只读字符串数据放入只读数据部分。(像Linux上的.rodata一样,我不知道OS X叫什么。) - Peter Cordes

0

在 MacOS 10.15 上汇编和链接 @NoOffenceIntended's answer 中的代码,需要进行以下更改:

.global _start 改为 .global main,并将 _start: 改为 main:

使用以下命令进行汇编和链接:

as -arch x86_64 -o hello.o hello.asm
ld -arch x86_64 -o hello hello.o -lSystem

假设正在使用“Apple clang version 12.0.0”的“as”,并且使用相应的“ld”。


你说的“上面的代码”是指哪个?如果你指的是其他带有代码的答案,请包括链接(并可能提到作者的姓名)。或者,如果你指的是问题中的代码,则它使用32位Linux int $0x80系统调用,参数在寄存器中按照Linux 32位ABI传递;MacOS/Darwin将int $0x80的参数放在堆栈上。但是我记得,10.15根本不支持32位代码,所以我怀疑你是否在谈论这个问题。 - Peter Cordes
抱歉,我指的代码是由@NoOffenceIntended于7/9/19 3:39发布,并由Peter Cordes于7/9/19 4:24发布/编辑的。(我不确定如何直接链接到该代码示例。) - znih
每个答案下面都有一个“分享”链接,您可以使用它直接获取链接。我编辑了这个以添加链接。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接