使用gcc -m32.
.code32是吗?不更改输出文件格式,这就是程序运行模式的决定因素。不尝试在64位模式下运行32位代码是由您自己决定的。.code32用于组装您可能希望作为数据的“外部”机器代码,或用于在共享内存段中导出。如果这不是您所要做的,那么在构建一个.S如果它有任何错误的模式push或pop例如说明。
建议:使用.S手写体汇编程序的扩展。(gcc foo.S之前会在C预处理程序中运行它。as所以你可以#include例如,带有SysCall数字的头)。同时,它也区别于.s编译器输出(来自gcc foo.c -O3 -S).
要构建32位二进制文件,请使用以下命令之一
gcc -g foo.S -o foo -m32 -nostdlib -static # static binary with absolutely no libraries or startup code
# -nostdlib by itself makes static executables on Linux, but not OS X.
gcc -g foo.S -o foo -m32 # dynamic binary including the startup boilerplate code. Use with code that defines a main() but not a _start
文件nostdlib, -nostartfiles,和-static.
使用libc函数_start(示例见这个答案的结尾)
一些功能,比如malloc(3),或stdio函数,包括printf(3),依赖于正在初始化的一些全局数据(例如,FILE *stdout以及它实际指向的对象)。
gcc -nostartfiles忽略CRT_start样板代码,但仍然链接libc(在默认情况下是动态的)。在linux上,共享库可以在动态链接器加载它们时由初始化器部分运行,然后跳到_start进入点。所以gcc -nostartfiles hello.S还让你打电话printf..对于动态可执行文件,内核运行/lib/ld-linux.so.2而不是直接运行它(使用readelf -a查看二进制文件中的“ELF解释器”字符串)。当你_start最终运行时,并不是所有的寄存器都会被归零,因为动态链接器在您的进程中运行代码。
然而,gcc -nostartfiles -static hello.S将链接,但在运行时崩溃。如果你打电话printf或者没有调用glibc内部init功能的东西。(见Michael Petch的评论)。
当然,你可以把任何组合.c, .S,和.o在同一个命令行中的文件,将所有文件链接到一个可执行文件中。如果你有C,别忘了-Og -Wall -Wextra:当C中的问题很简单时,您不想调试ASM,编译器可能已经警告过您了。
使用-v让GCC向您展示它运行的组装和链接命令。“手动”:
as foo.S -o foo.o -g --32 && # skips the preprocessor
ld -o foo foo.o -m elf_i386
file foo
foo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
gcc -nostdlib -m32比AS和ld的两个不同选项更容易记住和键入(--32和-m elf_i386)。而且,它可以在所有平台上工作,包括那些可执行格式不是ELF的平台。(但是Linux示例不能在OSX上工作,因为系统调用号是不同的,或者在Windows上,因为它甚至不使用int 0x80(ABI.)
NASM/YASM
GCC不会处理NASM语法。(-masm=intel更像是MASM,而不是NASM语法,在这里您需要offset symbol以立即获得地址)。当然,指令是不同的。.globlVSglobal).
你可以用nasm或yasm,然后链接.o带着gcc如上所示,或ld直接。
我使用包装器脚本来避免重复输入具有三个不同扩展名的相同文件名。(NASM和Yasm默认为file.asm -> file.o,不像GNU的默认输出a.out)。用这个和-m32装配和连接32位ELF可执行文件。并不是所有的操作系统都使用ELF,所以这个脚本比使用gcc -nostdlib -m32链接就是.。
#!/bin/sh
# usage: asm-link [-q] [-m32] foo.asm [assembler options ...]
# Just use a Makefile for anything non-trivial. This script is intentionally minimal and doesn't handle multiple source files
verbose=1 # defaults
fmt=-felf64
#ldopt=-melf_i386
while getopts 'm:vq' opt; do
case "$opt" in
m) if [ "m$OPTARG" = "m32" ]; then
fmt=-felf32
ldopt=-melf_i386
fi
if [ "m$OPTARG" = "mx32" ]; then
fmt=-felfx32
ldopt=-melf32_x86_64
fi
# default is -m64
;;
q) verbose=0 ;;
v) verbose=1 ;;
esac
done
shift "$((OPTIND-1))" # Shift off the options and optional --
src=$1
base=${src%.*}
shift
[ "$verbose" = 1 ] && set -x # print commands as they're run, like make
#yasm "$fmt" -Worphan-labels -gdwarf2 "$src" "$@" &&
nasm "$fmt" -Worphan-labels -g -Fdwarf "$src" "$@" &&
ld $ldopt -o "$base" "$base.o"
# yasm -gdwarf2 includes even .local labels so they show up in objdump output
# nasm defaults to that behaviour of including even .local labels
# nasm defaults to STABS debugging format, but -g is not the default
我更喜欢亚玛斯,有几个原因,包括默认情况下不做太久-nops而不是填充许多单字节。nopS.这会导致混乱的反汇编输出,如果NOPS运行的话也会变慢。(在NASM中,您必须使用smartalign)宏包。)
示例:使用libc函数的程序
# hello32.S
#include // syscall numbers. only #defines, no C declarations left after CPP to cause asm syntax errors
.text
#.global main # uncomment these to let this code work as _start, or as main called by glibc _start
#main:
#.weak _start
.global _start
_start:
mov $__NR_gettimeofday, %eax # make a syscall that we can see in strace output so we know when we get here
int $0x80
push %esp
push $print_fmt
call printf
#xor %ebx,%ebx # _exit(0)
#mov $__NR_exit_group, %eax # same as glibc's _exit(2) wrapper
#int $0x80 # won't flush the stdio buffer
movl $0, (%esp) # reuse the stack slots we set up for printf, instead of popping
call exit # exit(3) does an fflush and other cleanup
#add $8, %esp # pop the space reserved by the two pushes
#ret # only works in main, not _start
.section .rodata
print_fmt: .asciz "Hello, World!\n%%esp at startup = %#lx\n"
$ gcc -m32 -nostdlib hello32.S
/tmp/ccHNGx24.o: In function `_start':
(.text+0x7): undefined reference to `printf'
...
$ gcc -m32 hello32.S
/tmp/ccQ4SOR8.o: In function `_start':
(.text+0x0): multiple definition of `_start'
...
在运行时失败,因为没有调用glibc init函数。(__libc_init_first, __dl_tls_setup,和__libc_csu_init根据迈克尔·佩奇的评论。其他libc存在实现,包括MUSL它是为静态链接而设计的,无需初始化调用即可工作。)
$ gcc -m32 -nostartfiles -static hello32.S # fails at run-time
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, BuildID[sha1]=ef4b74b1c29618d89ad60dbc6f9517d7cdec3236, not stripped
$ strace -s128 ./a.out
execve("./a.out", ["./a.out"], [/* 70 vars */]) = 0
[ Process PID=29681 runs in 32 bit mode. ]
gettimeofday(NULL, NULL) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
你也可以gdb ./a.out,然后跑b _start, layout reg, run看看会发生什么。
$ gcc -m32 -nostartfiles hello32.S # Correct command line
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=7b0a731f9b24a77bee41c13ec562ba2a459d91c7, not stripped
$ ./a.out
Hello, World!
%esp at startup = 0xffdf7460
$ ltrace -s128 ./a.out > /dev/null
printf("Hello, World!\n%%esp at startup = %#lx\n", 0xff937510) = 43 # note the different address: Address-space layout randomization at work
exit(0
+++ exited (status 0) +++
$ strace -s128 ./a.out > /dev/null # redirect stdout so we don't see a mix of normal output and trace output
execve("./a.out", ["./a.out"], [/* 70 vars */]) = 0
[ Process PID=29729 runs in 32 bit mode. ]
brk(0) = 0x834e000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
.... more syscalls from dynamic linker code
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
mmap2(NULL, 1814236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xfffffffff7556000 # map the executable text section of the library
... more stuff
# end of dynamic linker's code, finally jumps to our _start
gettimeofday({1461874556, 431117}, NULL) = 0
fstat64(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0 # stdio is figuring out whether stdout is a terminal or not
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0xff938870) = -1 ENOTTY (Inappropriate ioctl for device)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7743000 # 4k buffer for stdout
write(1, "Hello, World!\n%esp at startup = 0xff938fb0\n", 43) = 43
exit_group(0) = ?
+++ exited with 0 +++
如果我们用_exit(0),或使sys_exit系统自称为int 0x80, 这个write(2)不会发生..当stdout重定向到非TTY时,它默认为全缓冲(不是行缓冲),因此write(2)仅由fflush(3)作为exit(3)..没有重定向,呼叫printf(3)包含换行符的字符串将立即刷新。
不同的行为取决于stdout是否是终端可能是可取的,但前提是您是故意的,而不是错误的。