我遇到了一个有趣的问题,我忘了我正在使用64位的机器&操作系统,并编写了一个32位的汇编代码。我不知道怎么写64位代码。
这是Linux上用于Gnu汇编程序的x86 32位汇编代码(AT&T语法)。
//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 1asm/unistd_64.h定义:
#define __NR_write 1
#define __NR_exit 60无论如何,使用宏而不是直接数字是有回报的。确保正确的系统呼叫号码。
当我组装、链接和运行程序时。
$cpp hello.S hello.s //pre-processor
$as hello.s -o hello.o //assemble
$ld hello.o // linker : converting relocatable to executable它没有打印helloworld。
在gdb中,它显示:
我不知道如何在gdb中调试。使用教程,我尝试调试它,并通过指令检查寄存器在每一步执行指令。它总是给我看“01退出的节目”。如果一些on能向我展示如何调试它,那就太好了。
(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>write(0, NULL, 12)系统调用在strace输出中的参数?编辑:
在读了保罗·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版本的这段代码吗?
发布于 2010-03-23 15:03:59
记住,默认情况下,64位操作系统上的所有东西都是64位的.您需要确保您正在(a)使用您#的32位版本,包括(b)与32位库的链接和(c)构建32位可执行文件。如果您有makefile的内容,或者显示用于构建这个示例的命令,可能会有所帮助。
我稍微修改了您的代码(_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发布于 2010-03-24 04:47:34
正如Paul所指出的,如果您想在64位系统上构建32位二进制文件,则需要使用-m32标志,默认情况下在安装过程中它可能不可用(有些64位Linux发行版默认不包括32位编译器/链接器/lib支持)。
另一方面,您可以将代码构建为64位,在这种情况下,您需要使用64位调用约定。在这种情况下,系统调用号在%rax中,参数在%rdi、%rsi和%rdx中。
编辑
我找到的最好的地方是www.x86-64.org,特别是abi.pdf
发布于 2017-09-03 04:06:19
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位二进制文件,以获得关于带有/不含libc和静态与动态可执行文件的_start和main的更多信息。
$ 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位INT0x80LinuxABI怎么办?,但64位代码中不推荐使用int 0x80。(实际上,它从来没有被推荐过。为了提高效率,32位代码应该通过内核导出的VDSO页面进行调用,这样它就可以在支持它的CPU上使用sysenter进行快速系统调用)。
但这不能回答我的问题。在这种情况下,到底发生了什么呢?
问得好。
在Linux上,无论调用进程采用何种模式,带int $0x80 eax=1 的 都是 sys_exit(ebx)**,。**32位ABI在64位模式下可用(除非您的内核在编译时没有i386 ABI支持),但不要使用它。您的退出状态是来自movl $(STDOUT), %ebx的。
(顺便说一句,STDOUT_FILENO宏是在unistd.h中定义的,但是您不能从.S中获取#include <unistd.h>,因为它还包含C原型,这些原型都是无效的asm语法。)
注意,来自unistd_32.h的unistd_32.h和来自unistd_64.h的__NR_write都是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的。
rdi和rsi在进入_start时是0 (又名NULL),您的代码使用movl $(helloend-hellostr) , %edx设置rdx=12。
Linux在execve之后的新进程中将寄存器初始化为零。( ABI称未定义,Linux选择零以避免信息泄漏)。在静态链接的可执行文件中,_start是第一个运行的用户空间代码。(在动态可执行文件中,动态链接器运行在_start之前,并将垃圾留在寄存器中)。
还请参阅x86标记wiki以获得更多的asm链接。
https://stackoverflow.com/questions/2500362
复制相似问题