as power.s -o power.o
ld power.o -o power
为了在64位操作系统(Ubuntu 14.04)上运行,我在power.s文件的开头添加了.code32,但是我仍然得到错误:
分段故障(核心转储)
power.s:
.code32 .section .data .section .text .global _start _start: pushl $3 pushl $2 call power addl $8,%esp pushl %eax pushl $2 pushl $5 call power addl $8,%esp popl %ebx addl %eax,%ebx movl $1,%eax int $0x80 .type power,@function power: pushl %ebp movl %esp,%ebp subl $4,%esp movl 8(%ebp),%ebx movl 12(%ebp),%ecx movl %ebx,-4(%ebp) power_loop_start: cmpl $1,%ecx je end_power movl -4(%ebp),%eax imull %ebx,%eax movl %eax,-4(%ebp) decl %ecx jmp power_loop_start end_power: movl -4(%ebp),%eax movl %ebp,%esp popl %ebp ret
解决方法
.code32不会改变输出文件格式,这就是决定程序运行模式的原因.您不能尝试在64位模式下运行32位代码. .code32用于组装您可能需要作为数据的“外部”机器代码,或者用于在共享内存段中导出.如果那不是您正在做的事情,请避免它,以便当您在错误模式下构建.S时,如果它有任何推送或弹出指令,则会出现构建时错误.
建议:对手写汇编程序使用.S扩展名. (gcc foo.S会在之前通过C预处理器运行它,因此你可以#include带有系统调用号的头文件).此外,它区别于.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
Documentation for nostdlib
,-nostartfiles
,and -static
.
使用_start中的libc函数(参见本答案的结尾部分)
一些函数,如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:你不想调试你的asm,当问题是简单的C调用它时,编译器可能会警告你.
使用-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示例在OS X上不起作用,因为系统调用数字不同,或者在Windows上,因为它甚至不使用int 0x80 ABI.)
NASM / YASM
gcc无法处理NASM语法. (-masm = intel更像是MASM而不是NASM语法,你需要偏移符号来获取地址作为立即数).当然,指令也不同(例如.globl vs global).
您可以使用nasm
或yasm
进行构建,然后将.o与上面的gcc链接,或直接链接.
我使用包装器脚本来避免重复键入具有三个不同扩展名的相同文件名. (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
我更喜欢yasm有几个原因,包括它默认使用long-nops而不是使用许多单字节nop填充.这会导致混乱的反汇编输出,以及如果nops运行会变慢. (在NASM中,您必须使用smartalign宏包.)
示例:使用_start中的libc函数的程序
# hello32.S #include <asm/unistd_32.h> // 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函数. (根据Michael Petch的评论,按顺序排列__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,version 1 (GNU/Linux),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,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 <no return ...> +++ 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",[/* 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,the write(2)
wouldn’t have happened调用自己.使用stdout重定向到非tty,它默认为全缓冲(不是行缓冲),所以写(2) )仅由fflush(3)触发,作为退出(3)的一部分.如果没有重定向,使用包含换行符的字符串调用printf(3)将立即刷新.
根据stdout是否是一个终端,表现不同可能是可取的,但只有你故意这样做,而不是错误.