当前位置: 首页 > 知识库问答 >
问题:

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

林弘壮
2023-03-14

我编写了成功编译的汇编代码:

as power.s -o power.o

但是,当我尝试链接对象文件时,它失败了:

ld power.o -o power

为了在64位操作系统(Ubuntu 14.04)上运行,我添加了<代码>。code32在电源的开头。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

共有2个答案

唐裕
2023-03-14

我正在学习x86汇编(在64位Ubuntu 18.04上),并且在完全相同的示例中遇到了类似的问题(它来自第4章[http://savannah.nongnu.org/projects/pgubook/]中的从零开始编程)。

在四处摸索之后,我发现以下两条线组合在一起,并将其链接在一起:

as power.s -o power.o --32  
ld power.o -o power -m elf_i386

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

如果要使用gdb调试,请使用汇编器行:

as --gstabs power.s -o power.o --32.

. code32似乎是不必要的。

我没有尝试过你的方式,但gnu汇编器(gas)似乎也可以:
.globl start
#(也就是说,global中没有“a”)。

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

很高兴知道如何将其更改为使用64位R*X和RBP寄存器。

云新知
2023-03-14

TL:DR:usegcc-m32-static-nostdlib-foo。S(或等效as和ld选项)
或者,如果您没有定义自己的开始,只需定义gcc-m32-无饼图

如果您链接libc,您可能需要安装gcc-multilib,或者您的发行版包/usr/lib32/libc.so/usr/lib32/libstdc. so等等。但是如果您定义自己的_start并且不链接库,您就不需要库包,只需要一个支持32位进程和系统调用的内核。这包括大多数发行版,但不包括Linuxv1的Windows子系统。

<代码>。code32不会更改输出文件格式,这决定了程序运行的模式。这取决于您是否尝试在64位模式下运行32位代码<代码>。code32是用于组装包含16位和32位代码的内核,以及类似的东西。如果这不是您正在做的,请避免它,这样您在构建<代码>时就会出现构建时错误。例如,如果它有任何推送指令或弹出指令,则处于错误模式<代码>。code32只允许您创建令人困惑的调试运行时问题,而不是生成时错误。

建议:使用。S扩展用于手写汇编器。(gcc-c foo。S将在之前通过C预处理器作为运行它,因此您可以#包含

gcc -g foo.S -o foo -m32 -nostdlib -static  # static binary with absolutely no libraries or startup code
                       # -nostdlib still dynamically links when Linux where PIE is the default, or on OS X

gcc -g foo.S -o foo -m32 -no-pie            # dynamic binary including the startup boilerplate code.
     # Use with code that defines a main(), not a _start

NOTDLIB、nostartfiles和静态文件的文档。

一些函数,如malloc(3)或包括printf(3)的stdio函数,取决于初始化的一些全局数据(例如,文件*stdout及其实际指向的对象)。

gcc-nostart files省略了CRT_start样板代码,但仍然链接libc(默认情况下是动态的)。在Linux,共享库可以有初始化程序部分,这些部分在加载它们时由动态链接器运行,然后跳转到您的_start切入点。所以gcc-nostart files你好。S仍然允许您调用printf。对于动态可执行文件,内核在其上运行/lib/ld-linux.so.2而不是直接运行它(使用readelf-a查看二进制文件中的“ELF解释器”字符串)。当您的_start最终运行时,并非所有寄存器都将归零,因为动态链接器在您的进程中运行代码。

但是,gcc-nostartfiles-static hello。S将链接,但如果在运行时调用printf或没有调用glibc的内部init函数的东西,则会崩溃。(见迈克尔·佩奇的评论)。

当然,您可以将. c. S. o文件的任意组合放在同一个命令行上,以将它们全部链接到一个可执行文件中。如果您有任何C,请不要忘记-Og-Wall-W额外:当问题是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-notdlib-m32比as和ld的两个不同选项(32和m elf\u i386)更容易记忆和键入。此外,它适用于所有平台,包括可执行格式不是ELF的平台。(但Linux示例在OS X上不起作用,因为系统调用号不同,或者在Windows上不起作用,因为它甚至不使用int 0x80 ABI。)

gcc无法处理NASM语法。(-masm=intel更像MASM而不是NASM语法,您需要偏移符号才能立即获取地址)。当然指令是不同的(例如. globlvsglobal)。

您可以使用nasm或yasm进行构建,然后链接。o使用上述gcc,或直接使用ld。

我使用包装脚本来避免重复键入具有三个不同扩展名的相同文件名。(

#!/bin/bash
# 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
# Copyright 2020 Peter Cordes.  Public domain.  If it breaks, you get to keep both pieces

verbose=1                       # defaults
fmt=-felf64
#ldopt=-melf_i386
ldlib=()

linker=ld
#dld=/lib64/ld-linux-x86-64.so.2
while getopts 'Gdsphl:m:nvqzN' opt; do
    case "$opt" in
        m)  if [ "m$OPTARG" = "m32" ]; then
                fmt=-felf32
                ldopt=-melf_i386
                #dld=/lib/ld-linux.so.2  # FIXME: handle linker=gcc non-static executable
            fi
            if [ "m$OPTARG" = "mx32" ]; then
                fmt=-felfx32
                ldopt=-melf32_x86_64
            fi
            ;;
        #   -static
        l)  linker="gcc -no-pie -fno-plt -nostartfiles"; ldlib+=("-l$OPTARG");;
        p)  linker="gcc -pie -fno-plt -nostartfiles"; ldlib+=("-pie");;
        h)  ldlib+=("-Ttext=0x200800000");;   # symbol addresses outside the low 32.  data and bss go in range of text
                          # strace -e raw=write  will show the numeric address
        G)  nodebug=1;;      # .label: doesn't break up objdump output
        d)  disas=1;;
        s)  runsize=1;;
        n)  use_nasm=1 ;;
        q)  verbose=0 ;;
        v)  verbose=1 ;;
        z)  ldlib+=("-zexecstack") ;;
        N)  ldlib+=("-N") ;;   # --omagic = read+write text section
    esac
done
shift "$((OPTIND-1))"   # Shift off the options and optional --

src=$1
base=${src%.*}
shift

#if [[ ${#ldlib[@]} -gt 0 ]]; then
    #    ldlib+=("--dynamic-linker" "$dld")
    #ldlib=("-static" "${ldlib[@]}")
#fi

set -e
if (($use_nasm)); then
  #  (($nodebug)) || dbg="-g -Fdwarf"     # breaks objdump disassembly, and .labels are included anyway
    ( (($verbose)) && set -x    # print commands as they're run, like make
    nasm "$fmt" -Worphan-labels $dbg  "$src" "$@" &&
        $linker $ldopt -o "$base" "$base.o"  "${ldlib[@]}")
else
    (($nodebug)) || dbg="-gdwarf2"
    ( (($verbose)) && set -x    # print commands as they're run, like make
    yasm "$fmt" -Worphan-labels $dbg "$src" "$@" &&
        $linker $ldopt -o "$base" "$base.o"  "${ldlib[@]}" )
fi

# 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

if (($disas));then
    objdump -drwC -Mintel "$base"
fi

if (($runsize));then
    size $base
fi

出于几个原因,我更喜欢YASM,包括它默认生成长nop,而不是用许多单字节nop填充。这导致了混乱的反汇编输出,并且如果nops曾经运行过,则速度会变慢。(在NASM中,必须使用smartalign宏包。)

然而,YASM已经有一段时间没有维护了,只有NASM支持AVX512;这些天我更多地只是使用NASM。

# 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函数。(\uu libc\u init\u first\udl\u tls\u setup\ulibc\u csu\u init,根据Michael Petch的评论,按顺序排列。存在其他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布局regrun,看看会发生什么。

$ 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 +++

如果我们使用了退出(0),或者让系统退出用int 0x80调用我们自己,那么写入(2)就不会发生。当stdout重定向到非tty时,它默认为完全缓冲(非行缓冲),因此写入(2)仅由作为退出(3)的一部分的fflush(3)触发。如果没有重定向,使用包含换行符的字符串调用printf(3)将立即刷新。

根据stdout是否是终端而有所不同的行为可能是可取的,但前提是你是有意的,而不是错误的。

 类似资料:
  • 问题内容: 使用Java本机接口时,我遇到了一个导致此错误的问题。我相信这是因为我使用MinGW编译了.dll,而我的系统是64位的,因此我的Java运行于64位,而MinGW编译为32位的.dll。无论如何,有没有强制我的Java在32位上运行? 问题答案: 您将必须安装32位JVM,然后才能运行代码。 如果要分发应用程序,则将要构建DLL的32位和64位版本。然后,使用以下技术来加载正确的DL

  • 问题内容: 在Java中,是否保证int始终为32位,而长为64位,而不管体系结构是32位还是64位? 问题答案: Java是平台无关的。所以是32位,并且是64位的。

  • 问题内容: 是否有可能编译项目在 32位 与和一对 64位 系统?可能是,但是我该怎么做呢? 当我以“无知”的方式尝试它时,没有设置任何参数/ flags / etc,只是设置在其中查找链接的库似乎忽略了它,而只查看名为 lib64的 子目录。 问题答案:

  • 问题内容: 我想知道x86和x64中的64位长吗? 问题答案: 是。Java 在任何JVM上都是64位,无一例外。所有Java原语类型都是完全可移植的,并且在所有实现中都具有固定的大小。

  • 问题内容: 在C语言中,在64位系统上long为64位。这反映在Python的ctypes模块中吗? 问题答案: 的大小 取决于内存模型。在Windows(LLP64)上是32位,在UNIX(LP64)上是64位。 如果需要64位整数,请使用。 如果需要指针大小的整数,请使用(“该值表示为整数”)。

  • 问题内容: 我已经使用Java一段时间了,而我典型的设置新开发机的习惯要求从Oracle站点下载并安装最新的JDK。 今天这引发了一个不寻常的问题, 回想起来,我已经安装了之前的两个版本,并且很高兴将普通的工具链插入(Eclipse)。在我的日常编程中,我不会回想起曾经因为使用64位JRE(或为此目的而针对64位JRE)而不得不以其他方式进行更改或思考的事情。 根据我对64位和32位的理解- 确实