Exécution de code d'assemblage 32 bits sur un processeur 64 bits Linux & 64 bits: expliquer l'anomalie
je suis dans un problème intéressant.J'ai oublié que j'utilise 64bit machine & OS et j'ai écrit un code d'assemblage 32 bits. Je ne sais pas écrire du code 64 bits.
c'est le code d'assemblage de 32 bits x86 pour Gnu Assembler (syntaxe AT&T) sous Linux.
//hello.S
#include <asm/unistd.h>
#include <syscall.h>
#define STDOUT 1
.data
hellostr:
.ascii "hello wolrdn";
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 "151900920"x80
movl $(SYS_exit), %eax //void _exit(int status);
xorl %ebx, %ebx
int "151900920"x80
ret
maintenant, ce code devrait fonctionner correctement sur un processeur 32 bits et un OS 32 bits n'est-ce pas? Comme nous le savons, les processeurs 64 bits sont rétrocompatibles avec les processeurs 32 bits. Alors, qui également ne pas être un problème. Le le problème se pose à cause des différences dans les appels système et le mécanisme d'appel dans les OS 64-bit et 32-bit. Je ne sais pas pourquoi, mais ils ont changé les numéros d'appel système entre linux 32 bits et linux 64 bits.
asm / unistd_32.h définit:
#define __NR_write 4
#define __NR_exit 1
asm / unistd_64.h définit:
#define __NR_write 1
#define __NR_exit 60
de toute façon, utiliser des Macros au lieu de numéros directs est payant. Il assure des numéros d'appel système corrects.
quand je assembler & lien & exécuter le programme.
$cpp hello.S hello.s //pre-processor
$as hello.s -o hello.o //assemble
$ld hello.o // linker : converting relocatable to executable
ce n'est pas l'impression de helloworld
.
Dans gdb son montrant:
- programme terminé avec le code 01.
je ne sais pas comment déboguer dans gdb. en utilisant le tutoriel, j'ai essayé de le déboguer et d'exécuter l'instruction en vérifiant les registres d'instructions à chaque étape. ça me montre toujours "programme terminé avec 01". Ce serait bien si certains pouvaient montrez-moi comment déboguer.
(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
j'ai essayé de courir strace
. C'est sa sortie:
execve("./helloworld", ["./helloworld"], [/* 39 vars */]) = 0
write(0, NULL, 12 <unfinished ... exit status 1>
- expliquer les paramètres de
write(0, NULL, 12)
appel système dans la sortie de strace? - que exactement se passe-t-il? Je veux savoir pourquoi exactement sort avec exitstatus=1?
- est-ce que quelqu'un peut me montrer comment pour déboguer ce programme en utilisant gdb?
- pourquoi ont-ils changé les numéros d'appel du système?
- veuillez modifier ce programme de manière appropriée afin qu'il puisse fonctionner correctement sur cette machine.
EDIT:
après avoir lu la réponse de Paul R. J'ai vérifié mes dossiers
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
je suis d'accord avec lui que ceux-ci devraient être ELF 32-bit relogeable & exécutable. Mais cela ne veut pas répondre à mes questions. Toutes mes questions sont toujours des questions. Ce qui se passe exactement dans ce cas? Est-ce que quelqu'un peut répondre à mes questions et fournir une version x86-64 de ce code?
3 réponses
rappelez-vous que tout par défaut sur un OS 64 bits tend à supposer 64 bits. Vous devez vous assurer que vous êtes (a) en utilisant les versions 32 bits de votre #includes le cas échéant (B) en créant des liens avec des bibliothèques 32 bits et (c) en construisant un exécutable 32 bits. Cela aiderait probablement si vous montriez le contenu de votre makefile si vous en avez un, ou bien les commandes que vous utilisez pour construire cet exemple.
FWIW j'ai changé votre code légèrement (_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 "151900920"x80
movl $(SYS_exit), %eax //void _exit(int status);
xorl %ebx, %ebx
int "151900920"x80
ret
et construit comme ceci:
$ gcc -Wall test.S -m32 -o test
verfied que nous avons un exécutable 32 bits:
$ 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
et il semble OK:
$ ./test
hello wolrd
comme L'a noté Paul, Si vous voulez construire des binaires 32 bits sur un système 64 bits, vous devez utiliser l'option-m32, qui peut ne pas être disponible par défaut sur votre installation (certains distros de Linux 64 bits n'incluent pas le support de compilateur/linker/lib par défaut).
d'un autre côté, vous pouvez à la place construire votre code en 64 bits, auquel cas vous devez utiliser les conventions d'appel 64 bits. Dans ce cas, le numéro d'appel système va en % rax, et les arguments vont en %rdi, %rsi, et % rdx
Modifier
le meilleur endroit que j'ai trouvé pour ça est www.x86-64.org , spécifiquement abi.pdf
64 bits peuvent exécuter du code 32 bits, mais ils doivent utiliser un mode spécial pour le faire. Ces instructions sont toutes valides en mode 64 bits, donc rien ne vous a empêché de construire un exécutable 64 bits.
votre code se construit et tourne correctement avec gcc -m32 -nostdlib hello.S
. C'est parce que -m32
définit __i386
, donc /usr/include/asm/unistd.h
inclut <asm/unistd_32.h>
, qui a les bonnes constantes pour le int "151960920"x80
ABI.
voir aussi Assemblage de binaires 32 bits sur un système 64 bits (chaîne D'outils GNU) pour en savoir plus sur _start
vs. main
avec/sans libc et exécutables statiques vs. dynamiques.
$ 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 +++
techniquement, si vous aviez utilisé les bons numéros d'appel, votre code fonctionnerait aussi en mode 64 bits: que se passe-t-il si vous utilisez le 32-bit int 0x80 Linux ABI en code 64 bits? mais int 0x80
n'est pas recommandé en code 64 bits. (En fait, c'est qui n'est jamais recommandée. Pour des raisons d'efficacité, le code 32 bits doit passer par la page vDSO exportée du noyau pour qu'il puisse utiliser sysenter
pour les appels système rapides sur les CPU qui le supportent).
mais ça ne répond pas à mes questions. Qu'est-ce exactement est qui se passe dans ce cas?
bonne question.
sur Linux, int "151960920"x80
avec eax=1
est sys_exit(ebx)
, quel que soit le mode dans lequel le processus d'appel était. L'ABI 32 bits est disponible en mode 64 bits (à moins que votre noyau n'ait été compilé sans le support de L'ABI i386), mais ne l'utilisez pas. Votre statut de sortie est de movl $(STDOUT), %ebx
.
(BTW, il y a une macro STDOUT_FILENO
définie dans unistd.h
, mais vous ne pouvez pas #include <unistd.h>
à partir d'une .S
parce qu'elle contient aussi des prototypes C qui ne sont pas une syntaxe asm valide.)
Avis __NR_exit
à partir de unistd_32.h
et __NR_write
à partir de unistd_64.h
sont à la fois 1
, de sorte que votre première int "151960920"x80
sort de votre processus. Vous utilisez les mauvais numéros d'appel système pour L'ABI que vous invoquez.
strace
est en train de le décoder incorrectement , comme si vous aviez invoqué syscall
(parce que C'est L'ABI qu'un processus 64 bits est censé utiliser). quelles sont les conventions d'appel pour les appels UNIX et Linux sur x86-64
eax=1
/ syscall
ça veut dire write(rd=edi, buf=rsi, len=rdx)
, et c'est comme ça que strace
déchiffre incorrectement votre int " 151960920"x80
.
rdi
et rsi
sont 0
(aka NULL
) sur l'entrée _start
, et vos jeux de code rdx=12
avec movl $(helloend-hellostr) , %edx
.
Linux initialise les registres à zéro dans un nouveau processus après execve. (L'ABI dit indéfini, Linux choisit zéro pour éviter les fuites d'information). Dans votre exécutable lié statiquement, _start
est le premier code d'espace utilisateur qui s'exécute. (Dans un exécutable dynamique, le linker dynamique tourne avant _start
, et laisse des déchets dans les registres).
Voir aussi la balise wiki x86 pour plus de liens asm.