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>
  1. expliquer les paramètres de write(0, NULL, 12) appel système dans la sortie de strace?
  2. que exactement se passe-t-il? Je veux savoir pourquoi exactement sort avec exitstatus=1?
  3. est-ce que quelqu'un peut me montrer comment pour déboguer ce programme en utilisant gdb?
  4. pourquoi ont-ils changé les numéros d'appel du système?
  5. 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?

14
demandé sur claws 2010-03-23 16:48:20

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
7
répondu Paul R 2010-03-23 15:14:38

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

6
répondu Chris Dodd 2010-03-24 18:57:38
Les CPU

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 pour plus de liens asm.

1
répondu Peter Cordes 2017-09-07 04:31:17