2010-03-23 20 views
14

Estoy en un problema interesante. Olvidé que estoy usando el sistema operativo de 64 bits & y escribí un código ensamblador de 32 bits. No sé cómo escribir código de 64 bits.Ejecutando código de ensamblado de 32 bits en un procesador Linux y 64 bit de 64 bit: explique la anomalía

Este es el código de ensamblaje x86 de 32 bits para Gnu Assembler (AT & T sintaxis) en 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 

Ahora, este código debería funcionar bien en un procesador de 32 bits & 32 bits del sistema operativo correcto? Como sabemos, los procesadores de 64 bits son compatibles con versiones anteriores de procesadores de 32 bits. Entonces, eso tampoco sería un problema. El problema surge debido a las diferencias en las llamadas al sistema & mecanismo de llamada en el SO de 64 bits & SO de 32 bits. No sé por qué, pero cambiaron los números de llamada del sistema entre Linux de 32 bits & Linux de 64 bits.

asm/unistd_32.h define:

#define __NR_write  4 
#define __NR_exit   1 

asm/unistd_64.h define:

#define __NR_write    1 
#define __NR_exit    60 

De todos modos el uso de macros en lugar de números directos se paga. Es asegurar los números correctos de llamadas del sistema.

cuando ensamblo & enlace & ejecutar el programa.

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

No está imprimiendo helloworld.

En su GDB, mostrando:

  • Programa salieron con código 01.

No sé cómo depurar en GDB. Usando el tutorial, intenté depurarlo y ejecutar instrucciones mediante registros de comprobación de instrucciones en cada paso. siempre me está mostrando "programa salido con 01". Sería genial si alguien pudiera mostrarme cómo depurar esto.

(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 

He intentado ejecutar strace. Esta es su salida:

execve("./helloworld", ["./helloworld"], [/* 39 vars */]) = 0 
write(0, NULL, 12 <unfinished ... exit status 1> 
  1. Explicar los parámetros de write(0, NULL, 12) llamada al sistema en la salida de strace?
  2. ¿Qué exactamente está sucediendo? Quiero saber la razón por la cual exactamente su salida con exitstatus = 1?
  3. ¿Puede alguien mostrarme cómo depurar este programa usando gdb?
  4. ¿Por qué cambiaron los números de llamada del sistema?
  5. Cambie amablemente este programa de modo que se pueda ejecutar correctamente en esta máquina.

EDIT:

Después de leer la respuesta de Pablo R. Revisé mis archivos

[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 

Estoy de acuerdo con él en que estos deben ser ELF 32-bit reubicable & ejecutable. Pero eso no responde a mis preguntas. Todas mis preguntas aún son preguntas. ¿Qué está pasando exactamente en este caso? ¿Puede alguien responder mis preguntas y proporcionar una versión x86-64 de este código?

Respuesta

7

Recuerde que todo por defecto en un sistema operativo de 64 bits tiende a asumir 64 bits. Debe asegurarse de estar (a) usando las versiones de 32 bits de sus #includes donde corresponda (b) enlazar con bibliotecas de 32 bits y (c) compilar un ejecutable de 32 bits. Probablemente sería útil mostrar los contenidos de su archivo MAKE si tiene uno, o los comandos que está utilizando para construir este ejemplo.

Fwiw he cambiado su código ligeramente (_start -> principal):

#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 

y construido de esta manera:

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

verfied que tenemos un 32-bit de ejecución:

$ 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 

y parece funcionar bien:

$ ./test 
hello wolrd 
+0

'' _start' o main' qué diferencia habría que hacer? – claws

+0

@claws: solo Me hacen que el cambio de modo que el código podría construir y enlazar fácilmente con gcc, pero supongo que también significa que el código de inicio biblioteca de ejecución C se ejecutará antes del principal se llama. –

+0

He editado mi pregunta. Además, cuando intento construir tu código usando 'gcc -Wall test.S -m32 -o test' Proporciona este error (los caracteres de subrayado se utilizan como separadores): /usr/bin/ld: omisión incompatible /usr/lib/gcc/x86_64-linux-gnu/4.4.1/libgcc.a al buscar -lgcc ______________ /usr/bin/ld: omitir /usr/lib/gcc/x86_64-linux-gnu/4.4.1/libgcc.a incompatible al buscar -lgcc ______________ /usr/bin/ld: no se puede encontrar -lgcc ______________ collect2: ld devuelto 1 estado de salida – claws

6

Según lo observado por Paul, si desea compilar binarios de 32 bits en un sistema de 64 bits, debe utilizar el indicador -m32, que puede no estar disponible por defecto en su instalación (algunos de 64 bits Las distribuciones de Linux no incluyen soporte de compilador/enlazador/lib de 32 bits por defecto).

Por otro lado, podría en cambio construir su código como de 64 bits, en cuyo caso deberá usar las convenciones de llamadas de 64 bits. En ese caso, el número de llamadas al sistema va en% rax, y los argumentos van en% RDI, RSI%, y% RDX

Editar

El mejor lugar que he encontrado para esto es www.x86-64.org, específicamente abi.pdf

+0

Gracias por mencionar la convención de 64 bits. Estoy buscando desesperadamente eso. Quiero saber más acerca de la convención de 64 bits. ¿Puedes darme algún enlace? (Oficial sería mejor). – claws

1

Las CPU de 64 bits pueden ejecutar código de 32 bits, pero tienen que usar un modo especial para hacerlo. Esas instrucciones son todas válidas en el modo de 64 bits, por lo que nada le impidió crear un ejecutable de 64 bits.

Su código se compila y se ejecuta correctamente con gcc -m32 -nostdlib hello.S. Esto se debe a que -m32 define __i386, por lo que /usr/include/asm/unistd.h incluye <asm/unistd_32.h>, que tiene las constantes correctas para int $0x80 ABI.

Consulte también Assembling 32-bit binaries on a 64-bit system (GNU toolchain) para obtener más información acerca de _start frente a main con/sin ejecutables libc y estáticos frente a dinámicos.

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

Técnicamente, si hubiera utilizado los números de llamada derecha, su código pasaría a trabajar en el modo de 64 bits, así: What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? Pero int 0x80 no se recomienda en código de 64 bits. (En realidad, nunca se recomienda. Para una mayor eficacia, código de 32 bits debe llamar a través de la página VDSO exportados del núcleo por lo que puede utilizar para sysenter sistema rápido pide a las CPU que lo soportan).


Pero eso no responde a mis mis preguntas. ¿Qué exactamente está pasando en este caso?

Buena pregunta.

En Linux, int $0x80 con eax=1 es sys_exit(ebx), independientemente de qué modo el proceso de llamada se encontraba. El 32 bits ABI está disponible en modo de 64 bits (a menos que su núcleo ha sido compilado sin soporte i386 ABI), pero no lo uses El estado de su salida es desde movl $(STDOUT), %ebx.

(Por cierto, hay una macro STDOUT_FILENO definido en unistd.h, pero no se puede #include <unistd.h> de un .S ya que también contiene prototipos C que no son asm sintaxis válida.)

en cuenta que __NR_exit de unistd_32.h y __NR_write de unistd_64.h son ambos 1, por lo que sus primeros int $0x80 salidas de su proceso. Está utilizando los números de llamada del sistema incorrectos para el ABI al que está invocando.


strace decodifica incorrectamente, como si te hubieran invocado syscall (porque esa es la ABI se espera un proceso de 64 bits para su uso). What are the calling conventions for UNIX & Linux system calls on x86-64

eax=1/syscall significa write(rd=edi, buf=rsi, len=rdx), y así es como strace está incorrectamente decodifica su int $0x80.

rdi y rsi son 0 (también conocido como NULL) en la entrada a _start, y su código establece rdx=12 con movl $(helloend-hellostr) , %edx.

Linux inicializa registros a cero en un proceso fresco después de execve. (El ABI dice indefinido, Linux elige cero para evitar filtraciones de información). En su archivo ejecutable enlazado estáticamente, _start es el primer código de espacio de usuario que se ejecuta. (En un ejecutable dinámico, el enlazador dinámico se ejecuta antes del _start, y deja la basura en los registros).

Véase también la etiqueta de wiki para más enlaces asm.

+0

Respuesta sólida, ¿puede explicar las implicaciones de no establecer los registros en 0 después de excve? ¿Cómo podría ser eso dañino? – Trey

+1

@Trey: El kernel no quiere filtrar ningún dato del kernel a procesos de espacio de usuario que no sean de confianza. Linux es un sistema operativo multiusuario que se encarga de aislar a los usuarios entre sí, y no hay manera de predecir si alguna información confidencial (como una contraseña, o más probablemente una dirección de memoria útil para un atacante que intenta explotar una vulnerabilidad del núcleo) podría sucede que se deja tirado en un registro. –

+0

¿Qué sucede si usa ** int $ 0x80 ** con el registro ** rax ** igual a 60, en una máquina de 64 bits? Lo intenté, es dar un SIGSEV, ¿por qué es eso? – Trey

Cuestiones relacionadas