在64位系统上组装32位二进制文​​件(GNU工具链)

buw*_*ilv 9 linux x86 assembly build att

我编写了可以编译的汇编代码:

as power.s -o power.o
Run Code Online (Sandbox Code Playgroud)

当我链接power.o目标文件时出现问题:

ld power.o -o power
Run Code Online (Sandbox Code Playgroud)

为了在64位操作系统(Ubuntu 14.04)上运行,我.code32power.s文件的开头添加了,但是我仍然得到错误:

分段故障(核心转储)

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
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 13

TL:DR:使用gcc -m32.

.code32不会改变输出的文件格式,这就是决定你的程序将在运行模式.这是给你不尝试在64位模式下运行32位代码. .code32用于组装您可能想要作为数据的"外部"机器代码,或者用于在共享内存段中导出.如果那不是您正在做的事情,请避免它,以便在构建.S错误模式时遇到构建时错误(例如,如果它有任何pushpop说明).

建议:使用.S手写汇编程序的扩展名.(之前gcc foo.S将通过C预处理器运行它as,因此您可以#include使用带有系统调用号的标头).此外,它将它与.s编译器输出(from 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
Run Code Online (Sandbox Code Playgroud)

文档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,将它们全部链接到一个可执行文件中.如果您有任何C,请不要忘记:当C语言中的问题很简单时,您不希望调试您的asm,编译器可能已经警告过您..S.o-Og -Wall -Wextra

使用-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
Run Code Online (Sandbox Code Playgroud)

gcc -nostdlib -m32比as和ld(--32-m elf_i386)的两个不同选项更容易记忆和输入.此外,它适用于所有平台,包括可执行格式不是ELF的平台.(但Linux示例在OS X上不起作用,因为系统调用号码不同,或者在Windows上,因为它甚至不使用int 0x80ABI.)


NASM/YASM

gcc无法处理NASM语法.(-masm=intel更像是MASM而不是NASM语法,你需要offset symbol立即获取地址).当然,指令也不同(例如.globlvs global).

你可以建立nasmyasm,然后链接.ogcc如上,或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
Run Code Online (Sandbox Code Playgroud)

我更喜欢yasm有几个原因,包括它默认使用long- nops而不是填充许多单字节nops.这会导致混乱的反汇编输出,以及如果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"
Run Code Online (Sandbox Code Playgroud)
$ 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'
...
Run Code Online (Sandbox Code Playgroud)

在运行时失败,因为没有调用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)
Run Code Online (Sandbox Code Playgroud)

你也可以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 <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", ["./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 +++
Run Code Online (Sandbox Code Playgroud)

如果我们想用_exit(0),或者作出sys_exit系统称自己有int 0x80,write(2)就不会发生.将stdout重定向到非tty时,默认为全缓冲(不是行缓冲),因此write(2)仅由fflush(3)部分触发exit(3).如果没有重定向,printf(3)使用包含换行符的字符串调用将立即刷新.

根据stdout是否是一个终端,表现不同可能是可取的,但只有你故意这样做,而不是错误.

  • 使用 `-nostartfiles -static` 构建时,如果 _C_ 运行时需要提前运行,在许多环境中可能会很危险。对于简单的事情,您可能不会遇到问题,但即使是 printf 也可能会成为问题。这就是为什么如果你打算静态使用 `glibc`,你的代码应该调用 `__libc_init_first`、`__dl_tls_setup`、`__libc_csu_init` 在程序启动时被手动调用(按这个顺序)。您可以通过使用像 [MUSL](https://www.musl-libc.org/) 这样的 _C_ 库来避免这种情况,这些库在调用函数之前不需要初始化。它们专为静态链接而设计 (2认同)

muo*_*tus 5

我正在学习 x86 程序集(在 64 位 Ubuntu 18.04 上)并且在完全相同的示例中遇到了类似的问题(它来自Programming From the Ground Up,在第 4 章 [ http://savannah.nongnu.org/projects/pgubook / ])。

在四处探索之后,我发现以下两行组合在一起并链接起来:

as power.s -o power.o --32  
ld power.o -o power -m elf_i386
Run Code Online (Sandbox Code Playgroud)

这些告诉计算机您只在 32 位下工作(尽管是 64 位架构)。

如果要使用gdb debugging,请使用汇编行:

as --gstabs power.s -o power.o --32.
Run Code Online (Sandbox Code Playgroud)

.code32 似乎是不必要的。

我还没有按照你的方式尝试过,但是 gnu 汇编程序(gas)似乎也可以:
.globl start
# (即,全局中没有“a”)。

此外,我建议您可能希望保留原始代码中的注释,因为似乎建议在汇编中进行大量注释。(即使你是唯一一个查看代码的人,如果你在几个月或几年后查看它,也会更容易弄清楚你在做什么。)

很高兴知道如何更改它以使用64-bit R*Xand RBP,RSP寄存器。