c - Mac OS X使用main和scanf/printf的32位nasm汇编程序?

标签 c macos gcc assembly nasm

我花了整整一天的时间试图编译一些简单的程序,但到目前为止运气不佳。我要做的是编译和运行以nasm汇编语言编写的程序。

我已升级到最新的nasm(v2.10.09)。因此,让我跳入代码,因为我对这些事情还不了解。这是在linux上使用elf格式和链接的gcc gcc运行的汇编代码的一部分(评论是我对正在发生的事情的理解):

bits 32
extern printf
global main

section .data
    message db "Hello world!", 10, 0

section .text
main:
    pushad                      ;push all registers on stack
    push dword message                  ;push the string on stack
    call printf                 ;call printf
    add esp, 4                  ;clear stack
    popad                       ;pop all registers back
    ret                     ;return to whoever called me

没什么大不了的。但是,我到底该如何在OS X上运行它呢?我什至无法以任何方式来编译/链接。如果它编译了,我就无法链接它(关于i386和x86的东西不能链接在一起(我知道,但是如何解决?)。我已经尝试了十二种方法,但都没有碰到运气。

另外,如何在OS X组件上使用printfscanf

这是scanfprintf值返回的另一种徒劳尝试(实际上是编译和链接-甚至运行!):
[bits 32] ; why the []?

section .data
    input_string    db  "Enter limit: %d", 0
    output_string   db  "Value %d", 10, 0
    counter         dd  10
    limit           dd  0

;nasm -f macho -o test.o test.asm 
;ld -lc -o test -arch i386 test.o -macosx_version_min 10.7

section .text

global start
extern _printf
extern _scanf
extern _exit

start:
    push ebp                ;push stack base
    mov ebp, esp            ;base is now current top
    and esp, 0xFFFFFFF0     ;align the stack - WHY? I just googled this?

    sub esp, 16             ;16 bytes for variables - 16 to keep the stack "aligned". WHY?

    mov dword[esp], input_string         ;ordinary push just isint good nuff for mac... WHY?
    mov dword[esp + 4], limit
    call _scanf                          ;does scan something but doesnt print the message, it just scans and then prints the message

    mov eax, [limit]                      ;since i cant push this lets put it in eax first


    mov dword[esp + 8], output_string     ;push the output string. WHY again MOV?
    mov dword[esp + 12], eax              ;and the previusly "scanned" variable
    call _printf                          ;print it out

    mov dword[esp], 0       ;return value
    call _exit              ;return

使用nasm -f macho -o test.o test.asm进行编译,并将其与d -lc -o test -arch i386 test.o -macosx_version_min 10.7链接。无法正常工作。在linux上,此scanf和printf东西 super 容易。怎么了能做得更简单吗?

我不想在这个问题上添加更多的内容,因为人们有时会看到一个大问题,并且“嗯,太长了,不会读”。但是,如果有人需要更多信息,我会尽力而为。

请帮我,因为我不知道。

编辑
第一个使用nasm -f macho -o out.o test.asm进行编译,但是不使用gcc -o test out.o或通过使用ld -lc -o test -arch i386 out.o -macosx_version_min 10.7进行链接,并附加平面-arch i386也不能解决该问题。如果可以编写“类似于Linux的程序集”,我将非常高兴,因为我不必担心堆栈对齐和类似的问题。
gcc错误说:
ld: warning: ignoring file out.o, file was built for i386 which is not the architecture being linked (x86_64): out.o
Undefined symbols for architecture x86_64:
  "_main", referenced from:
      start in crt1.10.6.o
ld: symbol(s) not found for architecture x86_64

和ld错误如下:
Undefined symbols for architecture i386:
  "printf", referenced from:
      main in out.o
  "start", referenced from:
     -u command line option
ld: symbol(s) not found for architecture i386

请帮忙。

最佳答案

您在询问有关代码的很多问题,而您实际上根本不了解其中的汇编代码。

首先,由于您编写代码的方式,main例程将成为C风格程序的入口。由于mac os x链接的工作方式;您必须将其命名为_main,以匹配链接程序在生成可执行文件时引入/usr/lib/crt1.o时作为默认程序入口点的链接程序所寻找符号的名称(如果对文件进行nm处理,则将看到类似U _main的条目。类似地,所有库例程都以前导下划线开头,因此如果要使用它们,则必须使用该前缀。

其次,MAC OS调用约定要求所有call的堆栈的16字节对齐,这意味着您必须确保堆栈指针在每个点都进行了相关对齐。在主例程的入口点,您已经知道由于将返回地址存储在堆栈中以便从main返回而错位了。这意味着,即使您要进行单个调用,也必须将堆栈向下移动至少12个字节才能进行调用。

有了这些信息,我们将省略ebp,只为代码目的而专门使用esp。

这是假设的序言:

bits 32
extern _printf
global _main

section .data
    message db "Hello world!", 10, 0

section .text
_main:

在输入_main时,重新调整堆栈:
sub esp, 12

接下来,我们将消息的地址存储到esp指向的地址中:
mov dword[esp], message

然后我们调用printf:
call _printf

然后我们还原堆栈:
add esp, 12

设置main的返回码,并返回:
mov eax, 0
ret

MAC OS X的ABI使用eax作为例程的返回码,只要它适合寄存器即可。编译并链接代码后:
nasm -f macho -o test.o test.asm 
ld -o test -arch i386 test.o -macosx_version_min 10.7 -lc /usr/lib/crt1.o

它运行并打印消息,并以0退出。

接下来,我们将处理您的扫描和打印示例。

首先,scanf仅扫描,您不能在那里看到提示;它根本无法正常工作,因此您必须将提示与扫描分开。我们已经向您展示了如何进行打印,现在我们需要显示的是scanf。

在数据部分设置变量:
scan_string     db  "%d", 0
limit           dd  0

首先将scan_string的地址存储在esp中,然后将limit的地址存储在esp + 4中,然后调用scanf:
mov dword[esp], scan_string
mov dword[esp + 4], limit
call _scanf

现在,我们应该将扫描的值存储在极限存储位置中。

下一步打印此消息:
output_string   db  "Value %d", 10, 0

接下来,我们将output_string的地址放在堆栈中:
mov dword[esp], output_string

将限制地址的值读入eax寄存器并将其放入esp + 4-即printf的第二个参数:
mov eax, [limit]
mov dword[esp + 4], eax
call _printf

接下来,我们调用exit,因此我们必须将退出代码存储在堆栈中并调用_exit函数-这与简单的print变体不同,因为我们实际上是在调用exit,而不是简单地返回。
mov dword[esp], 0
call _exit

至于一些问题:

为什么要对齐?

Because that's how Mac OS X does it



为什么推力不够好?

It is, but we aligned the stack at the start of the routine and an aligned stack is a functioning stack, by pushing and popping you're messing with the alignment. This is one of the purposes behind using the ebp register rather than the esp register.



如果我们要使用ebp寄存器,则函数prolog如下所示:
push ebp
mov ebp, esp
sub esp, 8 ; in this case to obtain alignment

并且函数Epilog看起来像:
add esp, 8
pop ebp

您也可以在其中放入对称的pusha/popa调用,但是如果您不使用寄存器,为什么还要使堆栈复杂化。

OS X Developer guide上可以更好地了解32位函数的调用机制,ABI函数调用指南提供了有关参数传递和返回方式的更多详细信息。它基于AT&T System V ABI for the i386,列出了一些区别:
  • 返回结构的不同规则
  • 堆栈在函数调用
  • 的位置对齐了16个字节
  • 大型数据类型(大于4个字节)保持其自然对齐
  • 除了使用长double值时,大多数浮点运算都是使用SSE单元而不是x87 FPU进行的。 (对于x87 FPU,IA-32环境默认为64位内部精度。)
  • 关于c - Mac OS X使用main和scanf/printf的32位nasm汇编程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20082414/

    相关文章:

    c - 如何从用户输入中查找数组元素

    c++ - gcc: 加载共享库时出错 -> 查找错误的库版本

    linux - id_rsa.pub 文件 SSH 错误 : invalid format

    objective-c - cocoa WebView中innerhtml和outerhtml的区别

    c++ - gcc 或 makefile 是否有一种机制可以描述源文件的包含文件以替换 #include "xxxx.h"?

    c - 在 C 中添加两个 float 组的有效方法?

    c - fscanf 只读取零

    c++ - g++ -fdump-class-hierarchy 的输出中的第一个 (int (*)(...))0 vtable 条目是什么?

    ios - Objective-C - 将自身的引用传递给子对象

    c - 是否可以更改 x64 的 gcc 中的调用约定?