如何使用 gcc 生成可以用 nasm 编译的汇编代码 [重复]
Posted
技术标签:
【中文标题】如何使用 gcc 生成可以用 nasm 编译的汇编代码 [重复]【英文标题】:How to generate assembly code with gcc that can be compiled with nasm [duplicate] 【发布时间】:2016-05-08 05:33:12 【问题描述】:我正在尝试学习汇编语言作为一种爱好,我经常使用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
的输出
编辑:
我收到了hint,表明我的问题已在另一个问题中得到解决;不幸的是,在测试了那里描述的方法后,我无法生成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
以这种方式生成的可执行文件崩溃并显示分段错误消息。非常感谢您的帮助。
【问题讨论】:
输出是为 gnu 汇编器 (as
) 准备的,你有什么特别的理由不使用它吗?它会“正常工作”。不幸的是,nasm
有不同的语法。
我不知道这个。我会试试的,谢谢你的回答。我很惊讶汇编语法不是通用的。
@NateEldredge 不幸的是,那里写的内容对我不起作用。我猜C
汇编转换不是那么简单。
你需要nasm
做什么?
您的编辑没有显示您是如何编译和链接该 OBJCONV 代码的,但它应该可以与带有类似 nasm -felf64 csimpc.asm
的 nasm 进行编译。如果您使用了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 位可执行文件
【参考方案1】:
你基本上不能,至少直接不能。 GCC 以 Intel 语法输出汇编;但 NASM/MASM/TASM 有自己的 Intel 语法。它们主要基于它,但也有一些差异,汇编器可能无法理解,因此无法编译。
最接近的可能是让objdump
以 Intel 格式显示程序集:
objdump -d $file -M intel
Peter Cordes 在 cmets 中建议汇编程序指令仍以 GAS 为目标,因此它们不会被 NASM 识别。它们通常具有相同的名称,但类似 GAS 的指令以 .
开头,如 .section text
(与 section text
相比)。
【讨论】:
另见:***.com/questions/8406188/… gcc / gas 英特尔语法仍然使用像.align
、.globl
这样的GNU汇编指令,而NASM/YASM使用像align
和global
这样的指令。所以你必须手动移植。
@PeterCordes 是的,这是真的。 GCC“限制”自己通过另一个类似 GAS 的指令 .intel_syntax
来告诉 GAS 切换语法。【参考方案2】:
我认为您遇到的入口点错误的困难是尝试在包含名为main
的入口点的目标文件上使用ld
,而ld
正在寻找入口点命名为_start
。
有几个考虑因素。首先,如果您与 C 库链接以使用 printf
之类的函数,则链接将期望 main
作为入口点,但如果您不与 C 库链接,ld
将期望 _start
.您的脚本非常接近,但您需要一些方法来区分您需要哪个入口点来完全自动化任何源文件的过程。
例如,以下是使用您的方法转换的源文件,包括printf
。使用objconv
将其转换为nasm
,如下所示:
生成目标文件:
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
改为启动,ld
错误消失了。但是代码仍然会产生Segmentation fault
错误。我的代码中没有printf
,实际上它只是一个主循环和一个for
循环,但不知何故它仍然无法运行。一般来说,如果我使用gcc
作为链接器,一切都会顺利进行。问题是用nasm
编译并用ld
链接。
@AlexanderCska:当然是段错误。它尝试从_start
到ret
,而不是进行exit(2)
系统调用。 _start
不会被任何东西调用:它是真正的入口点。 x86-64 ABI 指定堆栈保存 argc、*argv 和 *envp,而不是返回地址。如果您将代码更改为调用exit(0)
而不是return 0
,它应该可以工作,但是您需要与libc
链接。所以你应该像大卫所说的那样使用 gcc 链接。如果我错过了,IDK,但你为什么还要这样做?编译和运行后,您是否要开始手动修改 asm?
如果你想直接使用系统调用,而不通过glibc包装器,曾经有像_syscall1(type, name, type1, arg1)
这样的宏会定义一个内联函数来使系统称呼。见_syscall(2)
。或者您可以修改 call
指令周围的 asm 以将 args 放入正确的寄存器中以进行系统调用而不是函数调用,并使用 syscall
。它破坏了 rax、rcx 和 r11:请参阅 ***.com/questions/2535989/…
调用exit(2)
(又名sys_exit
):movq $return_code, %rdi; movq $60, %rax; syscall
或使用旧的中断接口:movq $1, %rax; mov $rc, %rbx; int $0x80
。这样您就不必通过 libc,但它的可移植性也较差(例如 AMD 使用 sysenter
而不是 syscall
)【参考方案3】:
有许多不同的汇编语言——对于每个 CPU,可能有多种可能的语法(例如“Intel 语法”、“AT&T 语法”),然后是完全不同的指令、预处理器等。仅针对 32 位 80x86,它就添加了大约 30 种不同的汇编语言方言。
GCC 只能为 32 位 80x86 生成一种汇编语言方言。这意味着它不能与 NASM、FASM、MASM、TASM、A86/A386 等一起使用。它仅适用于 GAS(可能还适用于“AT&T 模式”下的 YASM)。
当然你可以用 3 种不同的编译器将代码编译成 3 种不同类型的程序集,然后自己再编写 3 段不同的代码(在 3 种不同类型的程序集中);然后将所有这些(每个都使用适当的汇编程序)组装成目标文件并将所有目标文件链接在一起。
【讨论】:
以上是关于如何使用 gcc 生成可以用 nasm 编译的汇编代码 [重复]的主要内容,如果未能解决你的问题,请参考以下文章
如何从汇编例程中调用 C 函数并使用 nasm 和 gcc 链接 C 和汇编文件