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

前端之家收集整理的这篇文章主要介绍了linux – 在64位系统上组装32位二进制文​​件(GNU工具链)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我编写了可以编译的汇编代码
  1. as power.s -o power.o

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

  1. ld power.o -o power

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

分段故障(核心转储)

power.s:

  1. .code32
  2. .section .data
  3. .section .text
  4. .global _start
  5. _start:
  6. pushl $3
  7. pushl $2
  8. call power
  9. addl $8,%esp
  10. pushl %eax
  11.  
  12. pushl $2
  13. pushl $5
  14. call power
  15. addl $8,%esp
  16.  
  17. popl %ebx
  18. addl %eax,%ebx
  19.  
  20. movl $1,%eax
  21. int $0x80
  22.  
  23.  
  24.  
  25. .type power,@function
  26. power:
  27. pushl %ebp
  28. movl %esp,%ebp
  29. subl $4,%esp
  30. movl 8(%ebp),%ebx
  31. movl 12(%ebp),%ecx
  32. movl %ebx,-4(%ebp)
  33.  
  34. power_loop_start:
  35. cmpl $1,%ecx
  36. je end_power
  37. movl -4(%ebp),%eax
  38. imull %ebx,%eax
  39. movl %eax,-4(%ebp)
  40.  
  41. decl %ecx
  42. jmp power_loop_start
  43.  
  44. end_power:
  45. movl -4(%ebp),%eax
  46. movl %ebp,%esp
  47. popl %ebp
  48. ret

解决方法

TL:DR:使用gcc -m32.

.code32不会改变输出文件格式,这就是决定程序运行模式的原因.您不能尝试在64位模式下运行32位代码. .code32用于组装您可能需要作为数据的“外部”机器代码,或者用于在共享内存段中导出.如果那不是您正在做的事情,请避免它,以便当您在错误模式下构建.S时,如果它有任何推送或弹出指令,则会出现构建时错误.

建议:对手写汇编程序使用.S扩展名. (gcc foo.S会在之前通过C预处理器运行它,因此你可以#include带有系统调用号的头文件).此外,它区别于.s编译器输出(来自gcc foo.c -O3 -S).

要构建32位二进制文​​件,请使用以下命令之一

  1. gcc -g foo.S -o foo -m32 -nostdlib -static # static binary with absolutely no libraries or startup code
  2. # -nostdlib by itself makes static executables on Linux,but not OS X.
  3.  
  4. 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显示它运行以组装和链接的命令.要“手动”执行此操作:

  1. as foo.S -o foo.o -g --32 && # skips the preprocessor
  2. ld -o foo foo.o -m elf_i386
  3.  
  4. file foo
  5. 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链接更便携.

  1. #!/bin/sh
  2. # usage: asm-link [-q] [-m32] foo.asm [assembler options ...]
  3. # Just use a Makefile for anything non-trivial. This script is intentionally minimal and doesn't handle multiple source files
  4.  
  5. verbose=1 # defaults
  6. fmt=-felf64
  7. #ldopt=-melf_i386
  8.  
  9. while getopts 'm:vq' opt; do
  10. case "$opt" in
  11. m) if [ "m$OPTARG" = "m32" ]; then
  12. fmt=-felf32
  13. ldopt=-melf_i386
  14. fi
  15. if [ "m$OPTARG" = "mx32" ]; then
  16. fmt=-felfx32
  17. ldopt=-melf32_x86_64
  18. fi
  19. # default is -m64
  20. ;;
  21. q) verbose=0 ;;
  22. v) verbose=1 ;;
  23. esac
  24. done
  25. shift "$((OPTIND-1))" # Shift off the options and optional --
  26.  
  27. src=$1
  28. base=${src%.*}
  29. shift
  30.  
  31. [ "$verbose" = 1 ] && set -x # print commands as they're run,like make
  32.  
  33. #yasm "$fmt" -Worphan-labels -gdwarf2 "$src" "$@" &&
  34. nasm "$fmt" -Worphan-labels -g -Fdwarf "$src" "$@" &&
  35. ld $ldopt -o "$base" "$base.o"
  36.  
  37. # yasm -gdwarf2 includes even .local labels so they show up in objdump output
  38. # nasm defaults to that behavIoUr of including even .local labels
  39.  
  40. # nasm defaults to STABS debugging format,but -g is not the default

我更喜欢yasm有几个原因,包括它默认使用long-nops而不是使用许多单字节nop填充.这会导致混乱的反汇编输出,以及如果nops运行会变慢. (在NASM中,您必须使用smartalign宏包.)

示例:使用_start中的libc函数的程序

  1. # hello32.S
  2.  
  3. #include <asm/unistd_32.h> // syscall numbers. only #defines,no C declarations left after CPP to cause asm Syntax errors
  4.  
  5. .text
  6. #.global main # uncomment these to let this code work as _start,or as main called by glibc _start
  7. #main:
  8. #.weak _start
  9.  
  10. .global _start
  11. _start:
  12. mov $__NR_gettimeofday,%eax # make a syscall that we can see in strace output so we know when we get here
  13. int $0x80
  14.  
  15. push %esp
  16. push $print_fmt
  17. call printf
  18.  
  19. #xor %ebx,%ebx # _exit(0)
  20. #mov $__NR_exit_group,%eax # same as glibc's _exit(2) wrapper
  21. #int $0x80 # won't flush the stdio buffer
  22.  
  23. movl $0,(%esp) # reuse the stack slots we set up for printf,instead of popping
  24. call exit # exit(3) does an fflush and other cleanup
  25.  
  26. #add $8,%esp # pop the space reserved by the two pushes
  27. #ret # only works in main,not _start
  28.  
  29. .section .rodata
  30. print_fmt: .asciz "Hello,World!\n%%esp at startup = %#lx\n"
  1. $gcc -m32 -nostdlib hello32.S
  2. /tmp/ccHNGx24.o: In function `_start':
  3. (.text+0x7): undefined reference to `printf'
  4. ...
  5. $gcc -m32 hello32.S
  6. /tmp/ccQ4SOR8.o: In function `_start':
  7. (.text+0x0): multiple definition of `_start'
  8. ...

在运行时失败,因为没有调用glibc init函数. (根据Michael Petch的评论,按顺序排列__libc_init_first,__ dl_tls_setup和__libc_csu_init.其他libc实现存在,包括MUSL,它专为静态链接而设计,无需初始化调用.)

  1. $gcc -m32 -nostartfiles -static hello32.S # fails at run-time
  2. $file a.out
  3. a.out: ELF 32-bit LSB executable,version 1 (GNU/Linux),BuildID[sha1]=ef4b74b1c29618d89ad60dbc6f9517d7cdec3236,not stripped
  4. $strace -s128 ./a.out
  5. execve("./a.out",["./a.out"],[/* 70 vars */]) = 0
  6. [ Process PID=29681 runs in 32 bit mode. ]
  7. gettimeofday(NULL,NULL) = 0
  8. --- SIGSEGV {si_signo=SIGSEGV,si_code=SI_KERNEL,si_addr=0} ---
  9. +++ killed by SIGSEGV (core dumped) +++
  10. Segmentation fault (core dumped)

你也可以gdb ./a.out,然后运行b _start,layout reg,run,看看会发生什么.

  1. $gcc -m32 -nostartfiles hello32.S # Correct command line
  2. $file a.out
  3. a.out: ELF 32-bit LSB executable,dynamically linked,interpreter /lib/ld-linux.so.2,BuildID[sha1]=7b0a731f9b24a77bee41c13ec562ba2a459d91c7,not stripped
  4.  
  5. $./a.out
  6. Hello,World!
  7. %esp at startup = 0xffdf7460
  8.  
  9. $ltrace -s128 ./a.out > /dev/null
  10. printf("Hello,World!\n%%esp at startup = %#lx\n",0xff937510) = 43 # note the different address: Address-space layout randomization at work
  11. exit(0 <no return ...>
  12. +++ exited (status 0) +++
  13.  
  14. $strace -s128 ./a.out > /dev/null # redirect stdout so we don't see a mix of normal output and trace output
  15. execve("./a.out",[/* 70 vars */]) = 0
  16. [ Process PID=29729 runs in 32 bit mode. ]
  17. brk(0) = 0x834e000
  18. access("/etc/ld.so.nohwcap",F_OK) = -1 ENOENT (No such file or directory)
  19. .... more syscalls from dynamic linker code
  20. open("/lib/i386-linux-gnu/libc.so.6",O_RDONLY|O_CLOEXEC) = 3
  21. mmap2(NULL,1814236,PROT_READ|PROT_EXEC,MAP_PRIVATE|MAP_DENYWRITE,3,0) = 0xfffffffff7556000 # map the executable text section of the library
  22. ... more stuff
  23. # end of dynamic linker's code,finally jumps to our _start
  24.  
  25. gettimeofday({1461874556,431117},NULL) = 0
  26. fstat64(1,{st_mode=S_IFCHR|0666,st_rdev=makedev(1,3),...}) = 0 # stdio is figuring out whether stdout is a terminal or not
  27. ioctl(1,SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS,0xff938870) = -1 ENOTTY (Inappropriate ioctl for device)
  28. mmap2(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0) = 0xfffffffff7743000 # 4k buffer for stdout
  29. write(1,"Hello,World!\n%esp at startup = 0xff938fb0\n",43) = 43
  30. exit_group(0) = ?
  31. +++ 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是否是一个终端,表现不同可能是可取的,但只有你故意这样做,而不是错误.

猜你在找的Linux相关文章