我有一个有趣的问题。我忘了我正在使用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中显示:
我不知道如何在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>
write(0, NULL, 12)
系统调用的参数?修改
阅读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版本吗?
答案 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 $0x80
为eax=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)
和strace
为int $0x80
(又名rdi
},您的代码集rsi
为0
}}
Linux在execve之后在一个新进程中将寄存器初始化为零。 (ABI表示未定义,Linux选择零以避免信息泄漏)。在静态链接的可执行文件中,NULL
是第一个运行的用户空间代码。 (在动态可执行文件中,动态链接器在_start
之前运行,并且在寄存器中留下垃圾。)
有关更多asm链接,请参阅x86标记wiki。