GNU/Linux >> Tutoriales Linux >  >> Linux

No se puede llamar a la función de biblioteca estándar de C en Linux de 64 bits desde el código ensamblador (yasm)

Su gcc está creando ejecutables PIE de forma predeterminada (¿las direcciones absolutas de 32 bits ya no se permiten en x86-64 Linux?).

No estoy seguro de por qué, pero al hacerlo, el enlazador no resuelve automáticamente call puts a call [email protected] . Todavía hay un puts Entrada PLT generada, pero el call no va allí.

En tiempo de ejecución, el enlazador dinámico intenta resolver puts directamente al símbolo libc de ese nombre y corrija el call rel32 . Pero el símbolo está a más de +-2^31 de distancia, por lo que recibimos una advertencia sobre el desbordamiento del R_X86_64_PC32 reubicación Los 32 bits inferiores de la dirección de destino son correctos, pero los bits superiores no lo son. (Por lo tanto, su call salta a una dirección incorrecta).

Tu código funciona para mí si construyo con gcc -no-pie -fno-pie call-lib.c libcall.o . El -no-pie es la parte crítica:es la opción del enlazador. Su comando YASM no tiene que cambiar.

Al crear un ejecutable tradicional dependiente de la posición, el enlazador convierte el puts símbolo para el destino de la llamada en [email protected] para usted, porque estamos vinculando un ejecutable dinámico (en lugar de vincular estáticamente libc con gcc -static -fno-pie , en cuyo caso el call podría ir directamente a la función libc.)

De todos modos, esta es la razón por la que gcc emite call [email protected] (sintaxis GAS) al compilar con -fpie (el predeterminado en su escritorio, pero no el predeterminado en https://godbolt.org/), pero solo call puts al compilar con -fno-pie .

Vea ¿Qué significa @plt aquí? para obtener más información sobre el PLT, y también el estado de las bibliotecas dinámicas en Linux desde hace unos años. (El moderno gcc -fno-plt es como una de las ideas en esa publicación de blog).

Por cierto, un prototipo más preciso/específico permitiría a gcc evitar poner a cero EAX antes de llamar a foo :

extern void foo(); en C significa extern void foo(...);
Podrías declararlo como extern void foo(void); , que es lo que () significa en C++. C++ no permite declaraciones de funciones que dejen los argumentos sin especificar.

mejoras de asm

También puedes poner message en section .rodata (datos de solo lectura, vinculados como parte del segmento de texto).

No necesita un marco de pila, solo algo para alinear la pila en 16 antes de una llamada. Un muñeco push rax lo hará.

O podemos llamar a la cola puts por saltando a él en lugar de llamarlo, con la misma posición de pila que en la entrada a esta función. Esto funciona con o sin PIE. Simplemente reemplace call con jmp , siempre que RSP apunte a su propia dirección de devolución.

Si desea crear ejecutables PIE (o bibliotecas compartidas), tiene dos opciones

  • call puts wrt ..plt - llamar explícitamente a través del PLT.
  • call [rel puts wrt ..got] - haz explícitamente una llamada indirecta a través de la entrada GOT, como -fno-plt de gcc estilo de código-gen. (Usando un modo de direccionamiento relativo a RIP para llegar al GOT, de ahí el rel palabra clave).

WRT =Con respecto a. Los documentos del manual NASM wrt ..plt , y consulte también la sección 7.9.3:símbolos especiales y WRT.

Normalmente usarías default rel en la parte superior de su archivo para que pueda usar call [puts wrt ..got] y todavía obtener un modo de direccionamiento relativo a RIP. No puede usar un modo de direccionamiento absoluto de 32 bits en código PIE o PIC.

call [puts wrt ..got] se ensambla en una llamada indirecta a la memoria utilizando el puntero de función que el enlace dinámico almacena en GOT. (Enlace anticipado, no enlace dinámico perezoso).

Documentos NASM ..got para obtener la dirección de las variables en la sección 9.2.3. Las funciones en (otras) bibliotecas son idénticas:obtiene un puntero de GOT en lugar de llamar directamente, porque el desplazamiento no es una constante de tiempo de enlace y es posible que no quepa en 32 bits.

YASM también acepta call [puts wrt ..GOTPCREL] , como la sintaxis de AT&T call *[email protected](%rip) , pero NASM no.

; don't use BITS 64.  You *want* an error if you try to assemble this into a 32-bit .o

default rel          ; RIP-relative addressing instead of 32-bit absolute by default; makes the [rel ...] optional

section .rodata            ; .rodata is best for constants, not .data
message:
  db 'foo() called', 0

section .text

global foo
foo:
    sub    rsp, 8                ; align the stack by 16

    ; PIE with PLT
    lea    rdi, [rel message]      ; needed for PIE
    call   puts WRT ..plt          ; tailcall puts
;or
    ; PIE with -fno-plt style code, skips the PLT indirection
    lea   rdi, [rel message]
    call  [rel  puts wrt ..got]
;or
    ; non-PIE
    mov    edi, message           ; more efficient, but only works in non-PIE / non-PIC
    call   puts                   ; linker will rewrite it into call [email protected]

    add   rsp,8                   ; remove the padding
    ret

En una posición-dependiente ejecutable, puede usar mov edi, message en lugar de un LEA relativo a RIP. Tiene un tamaño de código más pequeño y puede ejecutarse en más puertos de ejecución en la mayoría de las CPU.

En un ejecutable que no sea PIE, también podría usar call puts o jmp puts y deje que el enlazador lo resuelva, a menos que desee un enlace dinámico de estilo sin plt más eficiente. Pero si elige vincular estáticamente libc, creo que esta es la única forma en que obtendrá un jmp directo a la función libc.

(Creo que la posibilidad de enlaces estáticos para no PIE es por qué ld está dispuesto a generar stubs PLT automáticamente para no PIE, pero no para PIE o bibliotecas compartidas. Requiere que diga lo que quiere decir al vincular objetos compartidos ELF).

Si usaste call puts en un PIE (call rel32 ), solo podría funcionar si vinculaste estáticamente una implementación independiente de la posición de puts en su PIE, por lo que todo era un ejecutable que se cargaba en una dirección aleatoria en tiempo de ejecución (mediante el mecanismo habitual de vinculación dinámica), pero simplemente no dependía de libc.so.6


El 0xe8 El código de operación es seguido por un desplazamiento firmado que se aplicará a la PC (que ha avanzado a la siguiente instrucción en ese momento) para calcular el objetivo de la bifurcación. Por lo tanto objdump está interpretando el objetivo de la rama como 0x671 .

YASM está representando ceros porque probablemente ha colocado una reubicación en ese desplazamiento, que es como le pide al cargador que complete el desplazamiento correcto para puts durante la carga. El cargador se encuentra con un desbordamiento al calcular la reubicación, lo que puede indicar que puts está en un desplazamiento mayor de su llamada que el que se puede representar en un desplazamiento con signo de 32 bits. Por lo tanto, el cargador no corrige esta instrucción y se bloquea.

66c: e8 00 00 00 00 muestra la dirección despoblada. Si observa su tabla de reubicación, debería ver una reubicación en 0x66d . No es raro que el ensamblador llene direcciones/compensaciones con reubicaciones como ceros.

Esta página sugiere que YASM tiene un WRT directiva que puede controlar el uso de .got , .plt , etc.

Según S9.2.5 en la documentación de NASM, parece que puede usar CALL puts WRT ..plt (suponiendo que YASM tenga la misma sintaxis).


Linux
  1. Cómo llamar a la función C en C++, función C++ en C (Mezclar C y C++)

  2. Tabla de llamadas del sistema Linux o hoja de trucos para ensamblaje

  3. x86_64 Ensamblaje Linux Confusión de llamadas del sistema

  4. ¿Biblioteca C para leer la versión EXE de Linux?

  5. Llame a una función de espacio de usuario desde un módulo del kernel de Linux

Conceptos básicos de la compilación de software a partir del código fuente en Linux

Cómo obtener noticias al instante desde la línea de comandos en Linux

Linux:¿monitoreo de todo el sistema de llamadas a una función de biblioteca?

¿Cómo puedo perfilar el código C++ que se ejecuta en Linux?

¿Puedo iniciar Linux desde un VHD?

¿Cómo puedo convertir fácilmente entidades especiales HTML de un flujo de entrada estándar en Linux?