2010-03-23 22 views
14

Sono in un problema interessante. Ho dimenticato di utilizzare il sistema operativo a 64 bit & e ho scritto un codice assembly a 32 bit. Non so come scrivere codice a 64 bit.Esecuzione di codice assembly a 32 bit su un processore Linux a 64 bit e 64 bit: Spiegazione dell'anomalia

Questo è il codice assembly x86 a 32 bit per Gnu Assembler (sintassi AT &) su Linux.

//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 

Ora, questo codice dovrebbe funzionare bene su un processore a 32 bit a 32 bit del sistema operativo & giusto? Come sappiamo i processori a 64 bit sono compatibili con le versioni precedenti con processori a 32 bit. Quindi, anche questo non sarebbe un problema. Il problema si pone a causa delle differenze nelle chiamate di sistema meccanismo di chiamata & in OS a 64 bit OS & sistema operativo a 32 bit. Non so perché, ma hanno cambiato i numeri di chiamata di sistema tra linux a 32 bit & linux a 64 bit.

asm/unistd_32.h definisce:

#define __NR_write  4 
#define __NR_exit   1 

asm/unistd_64.h definisce:

#define __NR_write    1 
#define __NR_exit    60 

Comunque utilizzando Macro invece dei numeri diretti è pagato. Garantisce i numeri di chiamata di sistema corretti.

quando assemblare & collegamento & eseguire il programma.

$cpp hello.S hello.s //pre-processor 
$as hello.s -o hello.o //assemble 
$ld hello.o // linker : converting relocatable to executable 

Non stampa helloworld.

In gdb la sua mostra:

  • programma è terminato con il codice 01.

non so come eseguire il debug in gdb. usando il tutorial ho provato a eseguirne il debug e ad eseguire le istruzioni con i registri di controllo delle istruzioni ad ogni passaggio. mi mostra sempre "il programma è uscito con 01". Sarebbe bello se qualcuno su potrebbe mostrarmi come eseguire il debug di questo.

(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 

Ho provato a eseguire strace. Questa è la sua uscita:

execve("./helloworld", ["./helloworld"], [/* 39 vars */]) = 0 
write(0, NULL, 12 <unfinished ... exit status 1> 
  1. spiegare i parametri di write(0, NULL, 12) chiamata di sistema nella produzione di strace?
  2. Cosa sta succedendo ? Voglio sapere il motivo per cui esattamente è uscito con exitstatus = 1?
  3. Qualcuno può mostrarmi come eseguire il debug di questo programma utilizzando gdb?
  4. Perché hanno cambiato i numeri di chiamata di sistema?
  5. Gentilmente modificare questo programma in modo appropriato in modo che possa funzionare correttamente su questa macchina.

EDIT:

Dopo aver letto la risposta di Paolo R. Ho controllato i miei file

[email protected]:~$ file ./hello.o 
./hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped 

[email protected]:~$ file ./hello 
./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped 

Sono d'accordo con lui che questi dovrebbero essere ELF a 32 bit relocatable & eseguibile. Ma questo non risponde alle mie mie domande. Tutte le mie domande sono ancora domande. Cosa sta succedendo esattamente in questo caso? Qualcuno può rispondere alle mie domande e fornire una versione x86-64 di questo codice?

risposta

7

Ricordare che tutto per impostazione predefinita su un sistema operativo a 64 bit tende ad assumere 64 bit. Devi assicurarti di essere (a) usando le versioni a 32 bit del tuo #include, laddove appropriato (b) collegandoti con le librerie a 32 bit e (c) costruendo un eseguibile a 32 bit. Sarebbe probabilmente d'aiuto se mostrassi il contenuto del tuo makefile se ne hai uno, oppure i comandi che stai usando per costruire questo esempio.

FWIW ho cambiato il codice leggermente (_start -> principale):

#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 

e costruito in questo modo:

$ gcc -Wall test.S -m32 -o test 

verfied che abbiamo un 32-bit di esecuzione:

$ 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 

e sembra funzionare OK:

$ ./test 
hello wolrd 
+0

'_start' o' main' che differenza farebbe? – claws

+0

@claws: ho apportato questa modifica solo in modo che il codice potesse essere creato e collegato facilmente con gcc, ma suppongo che significhi anche che il codice di avvio della libreria di runtime C verrà eseguito prima che venga chiamato main. –

+0

Ho modificato la mia domanda. Inoltre, quando provo a creare il tuo codice usando 'gcc -Wall test.S -m32 -o test' Fornisce questo errore (i caratteri di sottolineatura sono usati come separatori): /usr/bin/ld: saltare incompatibile /usr/lib/gcc/x86_64-linux-gnu/4.4.1/libgcc.a durante la ricerca di -lgcc ______________ /usr/bin/ld: saltando incompatibile /usr/lib/gcc/x86_64-linux-gnu/4.4.1/libgcc.a durante la ricerca di -lgcc ______________ /usr/bin/ld: impossibile trovare -lgcc ______________ collect2: ld ha restituito 1 stato di uscita – claws

6

Come notato da Paul, se si desidera creare binari a 32 bit su un sistema a 64 bit, è necessario utilizzare il flag -m32, che potrebbe non essere disponibile per impostazione predefinita sull'installazione (alcuni 64 bit Le distribuzioni Linux non includono il supporto del compilatore/linker/lib a 32 bit per impostazione predefinita).

D'altra parte, è possibile invece creare il codice come 64 bit, nel qual caso è necessario utilizzare le convenzioni di chiamata a 64 bit. In tal caso, il numero di chiamata di sistema va in% Rax, e gli argomenti vanno in% RDI,% rsi, e% RDX

Modifica

Posto migliore che ho trovato per questo è www.x86-64.org, specificamente abi.pdf

+0

Grazie per aver menzionato la convenzione a 64 bit. Lo sto cercando disperatamente. Voglio sapere di più sulla convenzione a 64 bit. Puoi per favore darmi un link? (Funzionario sarebbe meglio). – claws

1

Le CPU a 64 bit possono eseguire codice a 32 bit, ma devono utilizzare una modalità speciale per farlo. Queste istruzioni sono tutte valide in modalità 64 bit, quindi niente ti ha impedito di creare un eseguibile a 64 bit.

Il codice viene generato e eseguito correttamente con gcc -m32 -nostdlib hello.S. Questo perché -m32 definisce __i386, quindi /usr/include/asm/unistd.h include <asm/unistd_32.h>, che ha le costanti giuste per l'ABI int $0x80.

Vedere anche Assembling 32-bit binaries on a 64-bit system (GNU toolchain) per ulteriori informazioni su _start rispetto a main con/senza libc e eseguibili statici o dinamici.

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

Tecnicamente, se avessi usato i numeri di chiamata di destra, il codice sarebbe accaduto lavorare da modalità a 64 bit così: What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? Ma int 0x80 non è raccomandato in codice a 64 bit. (In realtà, non è mai raccomandato. Per l'efficienza, il codice a 32 bit dovrebbe chiamare attraverso la pagina VDSO esportata del kernel in modo che possa usare sysenter per chiamate di sistema veloci su CPU che lo supportano).


Ma questo non risponde alla mia mie domande. Cosa succede esattamente in questo caso?

Buona domanda.

Su Linux, int $0x80 con eax=1 è sys_exit(ebx), indipendentemente da ciò che la modalità del processo chiamante era in. Il 32-bit ABI è disponibile in modalità a 64 bit (a meno che il kernel è stato compilato senza il supporto i386 ABI), ma non usarlo Lo stato di uscita è da movl $(STDOUT), %ebx.

(a proposito, c'è una macro STDOUT_FILENO definito unistd.h, ma non può #include <unistd.h> da un .S perché contiene anche i prototipi C che non sono la sintassi asm validi.)

noti che __NR_exit da unistd_32.h e __NR_write da unistd_64.h sono entrambi 1, quindi il tuo primoint $0x80 termina il processo. Stai utilizzando i numeri di chiamata di sistema errati per l'ABI che stai invocando.


strace decodifica in modo errato, come se avessi invocato syscall (perché questo è l'ABI è previsto un processo a 64 bit da utilizzare). What are the calling conventions for UNIX & Linux system calls on x86-64

eax=1/syscall significa write(rd=edi, buf=rsi, len=rdx), e questo è il modo in strace decodifica correttamente il vostro int $0x80.

rdi e rsi sono 0 (aka NULL) in entrata per _start, e il tuo codice imposta rdx=12 con movl $(helloend-hellostr) , %edx.

Linux inizializza i registri a zero in un nuovo processo dopo l'esecuzione. (L'ABI dice indefinito, Linux sceglie zero per evitare perdite di informazioni). Nel file eseguibile collegato in modo statico, _start è il primo codice spazio utente che viene eseguito. (In un eseguibile dinamico, il linker dinamico viene eseguito prima di _start e lascia immondizia nei registri).

Vedere anche il tag wiki per ulteriori collegamenti asm.

+0

Risposta solida, puoi spiegare le implicazioni di non impostare i registri su 0 dopo la scadenza? Come potrebbe essere dannoso? – Trey

+1

@Trey: il kernel non vuole perdere alcun dato del kernel in processi dello spazio utente non attendibili. Linux è un sistema operativo multiutente che si occupa di isolare gli utenti gli uni dagli altri, e non c'è modo di prevedere se alcune informazioni sensibili (come una password o più probabilmente un indirizzo di memoria utile a un utente malintenzionato che tenta di sfruttare una vulnerabilità del kernel) potrebbero capita di essere lasciato in giro in un registro. –

+0

Cosa succede se si utilizza ** int $ 0x80 ** con il registro ** rax ** uguale a 60, su una macchina a 64 bit? L'ho appena provato, sta dando un SIGSEV, perché è così? – Trey

Problemi correlati