在64位Linux和Linux上运行32位汇编代码64位处理器:解释异常

时间:2010-03-23 13:48:21

标签: linux assembly gdb x86 x86-64

我有一个有趣的问题。我忘了我正在使用64位机器和放大器。 OS并写了一个32位汇编代码。我不知道如何编写64位代码。

这是Linux上Gnu Assembler(AT& T语法)的x86 32位汇编代码。

//hello.S
#include <asm/unistd.h>
#include <syscall.h>
#define STDOUT 1

.data
hellostr:
    .ascii "hello wolrd\n";
helloend:

.text
.globl _start

_start:
    movl $(SYS_write) , %eax  //ssize_t write(int fd, const void *buf, size_t count);
    movl $(STDOUT) , %ebx
    movl $hellostr , %ecx
    movl $(helloend-hellostr) , %edx
    int $0x80

    movl $(SYS_exit), %eax //void _exit(int status);
    xorl %ebx, %ebx
    int $0x80

    ret

现在,这个代码应该可以在32位处理器上正常运行。 32位操作系统对吗?我们知道64位处理器向后兼容32位处理器。所以,这也不是问题。出现问题的原因是系统调用和系统调用的差异。 64位操作系统中的调用机制32位操作系统。我不知道为什么但是他们改变了32位linux和Linux之间的系统调用号码。 64位linux。

asm / unistd_32.h定义:

#define __NR_write        4
#define __NR_exit         1

asm / unistd_64.h定义:

#define __NR_write              1
#define __NR_exit               60

无论如何使用宏而不是直接号码都可以获得回报。它确保正确的系统呼叫号码。

当我组装&amp;链接&amp;运行程序。

$cpp hello.S hello.s //pre-processor
$as hello.s -o hello.o //assemble
$ld hello.o // linker : converting relocatable to executable

不打印helloworld

在gdb中显示:

  • 程序退出,代码为01。

我不知道如何在gdb中调试。使用教程我尝试调试它并在每一步通过指令检查寄存器执行指令。它总是向我显示“程序退出01”。如果有人可以告诉我如何调试它,那将是很好的。

(gdb) break _start
Note: breakpoint -10 also set at pc 0x4000b0.
Breakpoint 8 at 0x4000b0
(gdb) start
Function "main" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Temporary breakpoint 9 (main) pending.
Starting program: /home/claws/helloworld 

Program exited with code 01.
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
8       breakpoint     keep y   0x00000000004000b0 <_start>
9       breakpoint     del  y   <PENDING>          main

我尝试了strace。这是它的输出:

execve("./helloworld", ["./helloworld"], [/* 39 vars */]) = 0
write(0, NULL, 12 <unfinished ... exit status 1>
  1. 在strace的输出中解释write(0, NULL, 12)系统调用的参数?
  2. 完全正在发生什么?我想知道为什么完全退出exitstatus = 1?
  3. 有人可以告诉我如何使用gdb调试这个程序吗?
  4. 他们为什么要更改系统电话号码?
  5. 请妥善更改此程序,以便它可以在此计算机上正常运行。
  6. 修改

    阅读Paul R的回答后。我检查了我的文件

    claws@claws-desktop:~$ file ./hello.o 
    ./hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
    
    claws@claws-desktop:~$ file ./hello
    ./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
    

    我同意他的说法,这些应该是ELF 32位可重定位的&amp;可执行文件。但这并不能回答我的问题。我的所有问题仍然存在问题。在这种情况下究竟发生了什么?有人可以回答我的问题并提供此代码的x86-64版本吗?

3 个答案:

答案 0 :(得分:8)

请记住,默认情况下,64位操作系统上的所有内容都倾向于采用64位。您需要确保(a)在适当的时候使用#includes的32位版本(b)链接32位库和(c)构建32位可执行文件。如果您显示makefile的内容(如果有的话),或者您用于构建此示例的命令,则可能会有所帮助。

FWIW我稍微改变了你的代码(_start - &gt; main):

#include <asm/unistd.h>
#include <syscall.h>
#define STDOUT 1

    .data
hellostr:
    .ascii "hello wolrd\n" ;
helloend:

    .text
    .globl main

main:
    movl $(SYS_write) , %eax  //ssize_t write(int fd, const void *buf, size_t count);
    movl $(STDOUT) , %ebx
    movl $hellostr , %ecx
    movl $(helloend-hellostr) , %edx
    int $0x80

    movl $(SYS_exit), %eax //void _exit(int status);
    xorl %ebx, %ebx
    int $0x80

    ret

并按照以下方式构建:

$ gcc -Wall test.S -m32 -o test

验证我们有32位可执行文件:

$ file test
test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.4, dynamically linked (uses shared libs), not stripped

似乎运行正常:

$ ./test
hello wolrd

答案 1 :(得分:6)

如Paul所述,如果要在64位系统上构建32位二进制文​​件,则需要使用-m32标志,默认情况下可能无法在安装中使用(某些64位Linux发行版)默认情况下不包括32位编译器/链接器/ lib支持。)

另一方面,您可以将代码构建为64位,在这种情况下,您需要使用64位调用约定。在这种情况下,系统调用号在%rax中,参数在%rdi,%rsi和%rdx

修改

我发现的最佳位置是www.x86-64.org,特别是abi.pdf

答案 2 :(得分:1)

64位CPU可以运行32位代码,但它们必须使用特殊模式才能执行此操作。这些指令在64位模式下都是有效的,因此没有什么能阻止您构建64位可执行文件。

您的代码使用gcc -m32 -nostdlib hello.S构建并正确运行。这是因为-m32定义了__i386,因此/usr/include/asm/unistd.h包含<asm/unistd_32.h>,其中int $0x80 ABI具有正确的常量。

有关{/ 1}}与_start有/无libc以及静态与动态可执行文件的详细信息,另请参阅Assembling 32-bit binaries on a 64-bit system (GNU toolchain)

main

从技术上讲,如果您使用了正确的电话号码,您的代码也可能在64位模式下工作:What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?但不建议在64位代码中使用$ file a.out a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=973fd6a0b7fa15b2d95420c7a96e454641c31b24, not stripped $ strace ./a.out > /dev/null execve("./a.out", ["./a.out"], 0x7ffd43582110 /* 64 vars */) = 0 strace: [ Process PID=2773 runs in 32 bit mode. ] write(1, "hello wolrd\n", 12) = 12 exit(0) = ? +++ exited with 0 +++ 。 (实际上,它从不推荐。为了提高效率,32位代码应该调用内核导出的VDSO页面,这样它就可以使用int 0x80在支持它的CPU上进行快速系统调用。

  

但这并不能回答我的问题。在这种情况下, 究竟发生了什么?

好问题。

在Linux上,sysenter int $0x80eax=1,无论调用进程处于何种模式。 32位ABI可用于64位模式(除非你的内核是在没有i386 ABI支持的情况下编译的),但是不要使用它。您的退出状态来自sys_exit(ebx)

(顺便说一句,在movl $(STDOUT), %ebx中定义了STDOUT_FILENO个宏,但您可以unistd.h#include <unistd.h>,因为它还包含C原型这些语法无效。)

请注意,来自.S的{​​{1}}和来自__NR_exit的{​​{1}}都是unistd_32.h,因此您的首先 {{1}退出你的过程。您正在为您正在调用的ABI使用错误的系统调用号码。

__NR_write正在对其进行错误解码,就像您调用了unistd_64.h一样(因为ABI预计会有64位进程)使用)。 What are the calling conventions for UNIX & Linux system calls on x86-64

1 / int $0x80表示strace,这就是syscall错误地解码您的eax=1的方式。

在进入syscall时,

write(rd=edi, buf=rsi, len=rdx)straceint $0x80(又名rdi},您的代码集rsi0 }}

Linux在execve之后在一个新进程中将寄存器初始化为零。 (ABI表示未定义,Linux选择零以避免信息泄漏)。在静态链接的可执行文件中,NULL是第一个运行的用户空间代码。 (在动态可执行文件中,动态链接器在_start之前运行,并且在寄存器中留下垃圾。)

有关更多asm链接,请参阅标记wiki。