如何使用gcc生成可以与nasm编译的汇编代码

18
我正在尝试作为一种爱好学习汇编语言,并且经常使用gcc -S来生成汇编输出。这是非常直接的,但我无法编译汇编输出。我只是好奇是否可以做到这一点。我尝试使用标准汇编输出和英特尔语法,使用-masm=intel。两者都无法用nasm编译并与ld链接。

因此,我想问一下是否有可能生成可编译的汇编代码。

更准确地说,我使用了以下C代码。

 >> cat csimp.c 
 int main (void){
 int i,j;
   for(i=1;i<21;i++)
     j= i + 100;
  return 0;
  }

使用 gcc -S -O0 -masm=intel csimp.c 生成汇编代码,尝试使用 nasm -f elf64 csimp.s 编译并使用 ld -m elf_x86_64 -s -o test csimp.o 进行链接。从 nasm 获取的输出如下:

csimp.s:1: error: attempt to define a local label before any non-local labels
csimp.s:1: error: parser: instruction expected
csimp.s:2: error: attempt to define a local label before any non-local labels
csimp.s:2: error: parser: instruction expected

这很可能是由于汇编语法错误导致的。我希望能够在不手动更正gcc -S输出的情况下修复此问题。

编辑:

有人给了我一个提示,说我的问题在另一个问题中得到了解决;不幸的是,在测试那里描述的方法后,我无法生成nasm汇编格式。您可以查看下面objconv的输出。因此,我仍需要您的帮助。

>>cat csimp.asm 
; Disassembly of file: csimp.o
; Sat Jan 30 20:17:39 2016
; Mode: 64 bits
; Syntax: YASM/NASM
; Instruction set: 8086, x64

global main:  ; **the ':' should be removed !!!** 


SECTION .text                                           ; section number 1, code

main:   ; Function begin
        push    rbp                                     ; 0000 _ 55
        mov     rbp, rsp                                ; 0001 _ 48: 89. E5
        mov     dword [rbp-4H], 1                       ; 0004 _ C7. 45, FC, 00000001
        jmp     ?_002                                   ; 000B _ EB, 0D

?_001:  mov     eax, dword [rbp-4H]                     ; 000D _ 8B. 45, FC
        add     eax, 100                                ; 0010 _ 83. C0, 64
        mov     dword [rbp-8H], eax                     ; 0013 _ 89. 45, F8
        add     dword [rbp-4H], 1                       ; 0016 _ 83. 45, FC, 01
?_002:  cmp     dword [rbp-4H], 20                      ; 001A _ 83. 7D, FC, 14
        jle     ?_001                                   ; 001E _ 7E, ED
        pop     rbp                                     ; 0020 _ 5D
        ret                                             ; 0021 _ C3
; main End of function


SECTION .data                                           ; section number 2, data


SECTION .bss                                            ; section number 3, bss

明显的解决方案:

在清理 objconv 的输出时,我犯了一个错误。我应该运行以下命令:

sed -i "s/align=1//g ; s/[a-z]*execute//g ; s/: *function//g;  /default *rel/d" csimp.asm

所有步骤都可以在一个 bash 脚本中压缩。
#! /bin/bash

a=$( echo $1 | sed  "s/\.c//" ) # strip the file extension .c

# compile binary with minimal information
gcc -fno-asynchronous-unwind-tables -s -c ${a}.c 

# convert the executable to nasm format
./objconv/objconv -fnasm ${a}.o 

# remove unnecesairy objconv information
sed -i "s/align=1//g ; s/[a-z]*execute//g ; s/: *function//g;  /default *rel/d" ${a}.asm

# run nasm for 64-bit binary

nasm -f elf64 ${a}.asm 

# link --> see comment of MichaelPetch below
ld -m elf_x86_64 -s ${a}.o 

运行此代码,我收到了`ld`警告:
 ld: warning: cannot find entry symbol _start; defaulting to 0000000000400080 

这种方式生成的可执行文件会崩溃并显示分段错误信息。我将不胜感激您的帮助。

6
输出是为GNU汇编器(as)而设计的,你为什么不使用它呢?它会“即插即用”。不幸的是,nasm具有不同的语法。 - Jester
2
你需要 nasm 做什么? - edmz
在您的编辑中,您生成的代码是64位的,但您最初的问题使用nasm -f elfld -m elf_i386(针对32位) 。所以我猜测您在objconv生成的代码方面可能遇到了混合32位和64位代码的问题。首先,您是要创建64位代码还是32位代码?你使用的是32位还是64位操作系统? - Michael Petch
@MichaelPetch 谢谢您的评论。我同时使用两者,因此有些混淆。我想生成 64 位二进制文件并在运行 3.16.7-21-desktop 内核的 Suse Linux 上运行它。然而,您看到的错误是由运行 nasm 生成的,因此链接器不是问题所在。我会纠正这个错误。感谢您的反馈。 - Alexander Cska
1
你的编辑没有显示出你如何编译和链接OBJCONV代码,但是它应该可以使用nasm进行编译,类似于nasm -felf64 csimpc.asm。如果你使用了nasm -felf csimpc.asm,那么-f elf会尝试生成32位输出。如果你正在尝试汇编64位代码,你需要使用-f elf64。如果在64位系统上,_LD_通常会默认输出64位可执行文件。所以你应该从LD命令中删除-m elf_i386或者使用ld -m elf_x86_64。带有-m elf_i386的LD正在尝试输出到32位可执行文件。 - Michael Petch
显示剩余6条评论
3个回答

7
我认为你遇到的问题是使用ld在一个包含入口点名为main的目标文件上时,ld会寻找名为_start的入口点。
有几点需要考虑。首先,如果你链接C库以使用printf等函数,链接将期望main作为入口点,但如果你不链接C库,ld将期望_start。你的脚本非常接近,但你需要一些方法来区分需要哪个入口点,以便完全自动化任何源文件的处理过程。
例如,以下是使用你的方法转换包含printf的源文件的示例。它被转换为nasm,使用objconv进行如下操作:
生成目标文件:
gcc -fno-asynchronous-unwind-tables -s -c struct_offsetof.c -o s3.obj

使用objconv将文件转换为nasm格式汇编代码

objconv -fnasm s3.obj

(注:我的版本的objconv添加了DOS换行符 - 可能是忘记选项,我只是通过dos2unix运行它)
使用略微修改过的您的sed调用,调整内容:
sed -i -e 's/align=1//g' -e 's/[a-z]*execute//g' -e \
's/: *function//g' -e '/default *rel/d' s3.asm

(注意:如果没有标准库函数,并且使用ld,则通过将以下表达式添加到sed调用中,将main更改为_start)
-e 's/^main/_start/' -e 's/[ ]main[ ]*.*$/ _start/'

编译使用nasm(替换原始目标文件):

这可能有更优雅的表达方式,这只是一个例子。

nasm -felf64 -o s3.obj s3.asm

使用gcc进行链接:

gcc -o s3 s3.obj

测试

$ ./s3

 sizeof test : 40

 myint  : 0  0
 mychar : 4  4
 myptr  : 8  8
 myarr  : 16  16
 myuint : 32  32

我将main更改为start后,ld错误消失了。 但代码仍然会产生“分段错误”。 我的代码中没有printf,实际上只有一个主函数和一个for循环,但不知何故它仍无法运行。 通常情况下,如果我使用gcc作为链接器,则一切都顺利运行。 问题是使用nasm编译并使用ld链接。 - Alexander Cska
@AlexanderCska:当然会segmentation fault。它试图从“_start”返回,而不是进行“exit(2)”系统调用。“_start”没有被任何东西调用:它是实际的入口点。x86-64 ABI指定栈保存argc,* argv和* envp,而不是返回地址。如果您将代码更改为调用“exit(0)”而不是“return 0”,则应该可以正常工作,但是然后您需要使用“libc”链接。所以你应该像David说的那样使用gcc进行链接。我不知道我是否错过了,但是你为什么要这样做呢?一旦编译并运行起来,你是要开始手动修改汇编代码吗? - Peter Cordes
如果您想要直接使用系统调用,而不是通过glibc包装器,那么以前有像_syscall1(type, name, type1, arg1)这样的宏,可以定义一个内联函数来进行系统调用。请参见_syscall(2)。或者您可以修改call指令周围的汇编代码,将参数放入正确的寄存器中进行系统调用,而不是函数调用,并使用syscall。它会破坏rax、rcx和r11:请参见https://dev59.com/Q3E85IYBdhLWcg3w8IXK - Peter Cordes
调用 exit(2)(又名 sys_exit)的方法:movq $return_code, %rdi; movq $60, %rax; syscall 或者使用旧的中断接口:movq $1, %rax; mov $rc, %rbx; int $0x80。这样你就不必经过 libc,但它也不太可移植(例如 AMD 使用 sysenter 而不是 syscall)。 - edmz
此外,正则表达式也可以匹配像defaultrelease:这样的行,该行定义了与之匹配的函数名称的标签,因为/ */可以匹配零个字符。 - Peter Cordes
显示剩余6条评论

4
有许多不同的汇编语言 - 对于每个CPU可能有多个可能的语法(例如“Intel语法”,“AT&T语法”),然后完全不同的指令,预处理器等。这使得32位80x86汇编语言仅有大约30种不同的方言。
GCC只能为32位80x86生成一种汇编语言方言。这意味着它不能与NASM、FASM、MASM、TASM、A86/A386等一起使用。它仅适用于GAS(可能还有YASM的“AT&T模式”)。
当然,您可以使用3种不同的编译器将代码编译成3种不同类型的汇编语言,然后自己编写3种不同类型的汇编代码;然后使用各自适当的汇编器将所有内容(每个都是对象文件)汇编到一起,并将所有对象文件链接在一起。

4

基本上直接翻译是不行的。GCC输出的汇编语言采用Intel语法,但NASM/MASM/TASM采用自己的Intel语法。它们在很大程度上基于Intel语法,但可能存在一些差异,汇编器可能无法理解并因此编译失败。

最接近的方法可能是使用objdump以Intel格式显示汇编代码:

objdump -d $file -M intel

Peter Cordes在评论中建议,汇编指令仍将针对GAS进行,因此例如NASM就不能识别它们。他们通常具有相同的名称,但类似于GAS的指令以 . 开头,比如 .section text (而不是 section text )。


另请参见:https://dev59.com/817Va4cB1Zd3GeqPFwMU?lq=1 - edmz
1
gcc / gas Intel 语法仍然使用类似 .align.globl 的 GNU 汇编指令,而 NASM/YASM 使用 alignglobal 等指令。因此,您需要手动移植。 - Peter Cordes
@PeterCordes 是的,没错。GCC通过另一个类似GAS的指令.intel_syntax告诉GAS切换语法。 - edmz
但是我使用的是 AMD CPU,我应该执行哪个命令? - ORHAN ERDAY

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