在64位Linux&64位处理器上运行32位汇编代码:解释exception

我在一个有趣的问题。我忘了我正在使用64位机器和操作系统,并写了一个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和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 

无论如何,使用macros而不是直接数字是付清的。 它确保正确的系统呼叫号码。

当我汇编&连接&运行程序。

 $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中进行debugging。 使用教程我试图debugging它,并通过指令检查寄存器在每一步执行指令。 它总是向我展示“用01退出的程序”。 如果有人能告诉我如何debugging这将是很好的。

 (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来debugging这个程序吗?
  4. 为什么他们改变系统电话号码?
  5. 请适当地改变这个程序,以便它可以在这台机器上正确运行。

编辑:

读完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位可重定位和可执行文件。 但是这并不能回答我的问题。 我所有的问题仍然是问题。 究竟发生在这种情况下? 有人可以请回答我的问题,并提供此代码的x86-64版本?

请记住,64位操作系统默认情况下的一切都倾向于采用64位。 您需要确保您(a)在适当情况下使用32位版本的#include(b)与32位库链接,(c)构建32位可执行文件。 如果你有makefile文件的内容,或者你用来构建这个例子的命令,这可能会有帮助。

FWIW我稍微改变了你的代码(_start – > 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 

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

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

编辑

我找到的最好的地方是www.x86-64.org ,特别是abi.pdf

64位的CPU可以运行32位代码,但是他们必须使用特殊的模式才能完成。 这些指令在64位模式下都是有效的,所以没有任何东西阻止你构建一个64位的可执行文件。

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

另请参阅在64位系统(GNU工具链)上组合32位二进制文​​件以获取更多有关_startmain带/不带libc)以及静态与动态可执行文件的比较。

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

从技术上讲,如果您使用了正确的调用号码,那么您的代码也将在64位模式下工作: 如果在64位代码中使用32位int 0x80 Linux ABI,会发生什么情况? 但是在64位代码中不建议使用int 0x80 。 (实际上,从来不建议这样做,为了提高效率,32位代码应该通过内核导出的VDSO页面调用,以便在支持CPU的CPU上使用sysenter进行快速系统调用)。


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

好问题。

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

(顺便说一句,在unistd.h定义了一个STDOUT_FILENO宏,但是不能从.S包含#include <unistd.h> ,因为它也包含C原型,这些原型不是有效的asm语法。)

请注意,来自unistd_32.h和来自unistd_64.h都是1 ,所以你的第一个 int $0x80退出你的进程。 您正在调用的ABI使用错误的系统调用号码。


strace解码不正确 ,就好像你调用了syscall (因为这是一个64位进程预计使用的ABI)。 在x86-64上,UNIX和Linux系统调用的调用约定是什么?

eax=1 / syscall表示write(rd=edi, buf=rsi, len=rdx) ,这就是strace不正确地解码你的int $0x80

rdirsi在进入_start0 (aka NULL ),而你的代码用movl $(helloend-hellostr) , %edx设置rdx=12

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

另请参阅x86标签维基以获取更多的asm链接。