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

我编写了可以编译的汇编代码
as power.s -o power.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

解决方法

TL:DR:使用gcc -m32.

.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).

您可以使用nasmyasm进行构建,然后将.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是否是一个终端,表现不同可能是可取的,但只有你故意这样做,而不是错误.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


在Linux上编写运行C语言程序,经常会遇到程序崩溃、卡死等异常的情况。程序崩溃时最常见的就是程序运行终止,报告 Segmentation fault (core dumped) 错误。而程序卡死一般来源于代码逻辑的缺陷,导致了死循环、死锁等问题。总的来看,常见的程序异常问题一般可以分为 非法内存访
git使用小结很多人可能和我一样,起初对git是一无所知的。我也是因为一次偶然的机会接触到git,并被它强大的功能所蛰伏。git其实就是一种版本控制工具,就像svn一样,但是git是分布式的。我不想给git打广告,我们直入正题——git能帮我们做什么?1)源码版本控制。平常写一写demo程序可能和g
1. 操作系统环境、安装包准备 宿主机:Max OSX 10.10.5 虚拟机:Parallel Desktop 10.1.1 虚拟机操作系统:CentOS 7 x86_64 DVD 1511.iso Oracle:linux.x64_11gR2_database_1of2.zip linux.x6
因为业务系统需求,需要对web服务作nginx代理,在不断的尝试过程中,简单总结了一下常见的nginx代理配置。 1. 最简反向代理配置 在http节点下,使用upstream配置服务地址,使用server的location配置代理映射。 upstream my_server { server 10
Linux模块机制浅析 Linux允许用户通过插入模块,实现干预内核的目的。一直以来,对linux的模块机制都不够清晰,因此本文对内核模块的加载机制进行简单地分析。 模块的Hello World! 我们通过创建一个简单的模块进行测试。首先是源文件main.c和Makefile。 f...
一、Hadoop HA的Web页面访问 Hadoop开启HA后,会同时存在两个Master组件提供服务,其中正在使用的组件称为Active,另一个作为备份称为Standby,例如HDFS的NameNode、YARN 的ResourceManager。HDFS的web页面只有通过Active的Name
一个简单的通用Makefile实现Makefile是Linux下程序开发的自动化编译工具,一个好的Makefile应该准确的识别编译目标与源文件的依赖关系,并且有着高效的编译效率,即每次重新make时只需要处理那些修改过的文件即可。Makefile拥有很多复杂的功能,这里不可能也没必要一一介绍,为了
Linux内核源码分析方法一、内核源码之我见Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次。如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径。我们都知道,想成为优秀的程序员,需要大量的实践和代码的编写。编程固然重要,但是往往
题记:自从接触到“跳板机”的概念后,一直就被烦不胜烦的机器名,ip地址,用户名,密码折腾的死去活来,心说能有个小精灵随时帮我输入那些重复的登录信息就好了。我见过最挫的方式就是用记事本把一堆机器的ip、登录用户、密码记录下来,每次登录机器就像是一场战斗:打开笔记本 勾选复制 写ssh命令 登录 再打开
统计一下你写过多少代码最近整理了一下自己从开始学习编程以来写过的程序和代码,林林总总,花了不少的时间,最后把一些自认为还算不错的代码提交到github上做一个简单的分类和备份。当然我并不奢求它们能成为多好的开源代码,只是希望通过这种方式分享自己的劳动成果罢了。如果大家有兴趣可以访问我的github,
一直以来被Linux的hostname和fqdn(Fully Qualified Domain Name)困惑了好久,今天专门抽时间把它们的使用细节弄清了。 一、设置hostname/fqdn&#xD;在Linux系统内设置hostname很简单,如: $ hostname florian 如果...
Linux的原子操作与同步机制 并发问题 现代操作系统支持多任务的并发,并发在提高计算资源利用率的同时也带来了资源竞争的问题。例如C语言语句“count++”在未经编译器优化时生成的汇编代码为。 当操作系统内存在多个进程同时执行这段代码时,就可能带来并发问题。 假设count变量初始值为0。进程1
最简git Server配置如何保持多台计算机的项目代码的同步更新呢?通过在一个公用计算机上开启git服务,任何能与该计算机互联的终端都可以同步最新的项目代码。每个终端所负责的就是下载代码更新,修改代码,提交代码更新,这些工作产生的变化能全部反应到git服务器上。同时,这么做也能避免使用github
建议收藏!!!Linux 服务器必备的安全设置~
QQ 用 Electron 重构后,终实现 Linux、macOS、Windows 三端架构统一!
Shell 分析日志文件高效命令,超级好用!
Linux下的Docker容器网络:如何设置容器间的网络连接和通信?
Linux 服务器必备的安全设置,建议收藏!!!
以为很熟悉 Linux,万万没想到在生产环境翻车了.....
Linux 或 Windows 上实现端口映射