2012-06-01 7 views
6

No estoy seguro de cuál es una buena línea de asunto para esta pregunta, pero aquí vamos ...x86_64: ¿Es posible "en línea sustituir" referencias PLT/GOT?

Para forzar el código localidad/compacidad para una sección crítica de código, estoy buscando un camino llamar a una función en una biblioteca externa (cargada dinámicamente) a través de un "salto de ranura" (una reubicación ELF R_X86_64_JUMP_SLOT) directamente en el sitio de la llamada - lo que el enlazador normalmente pone en PLT/GOT, pero tiene estos en línea en el sitio de llamada .

Si imitara a la llamada como:

#include <stdio.h> 
int main(int argc, char **argv) 
{ 
     asm ("push $1f\n\t" 
      "jmp *0f\n\t" 
      "0: .quad %P0\n" 
      "1:\n\t" 
      : : "i"(printf), "D"("Hello, World!\n")); 
     return 0; 
}
para obtener el espacio para una palabra de 64 bits, la llamada en sí funciona (por favor, no hay comentarios acerca de este ser coincidencia afortunada ya que esto rompe ciertas reglas ABI - todos estos no son objeto de este pregunta ... y puede, en mi caso, solucionarse/abordarse de otras maneras, intento mantener este ejemplo breve).

Crea el siguiente montaje:

0000000000000000 <main>: 
    0: bf 00 00 00 00   mov $0x0,%edi 
         1: R_X86_64_32 .rodata.str1.1 
    5: 68 00 00 00 00   pushq $0x0 
         6: R_X86_64_32 .text+0x19 
    a: ff 24 25 00 00 00 00 jmpq *0x0 
         d: R_X86_64_32S .text+0x11 
     ... 
         11: R_X86_64_64 printf 
    19: 31 c0     xor %eax,%eax 
    1b: c3      retq
Pero (debido al uso de printf como lo inmediato, supongo ...?) La dirección de destino aquí sigue siendo el de gancho PLT - el mismo R_X86_64_64 reubicación. Vincular el archivo de objeto con libc a un ejecutable real da como resultado:
0000000000400428 <[email protected]>: 
    400428:  ff 25 92 04 10 00  jmpq *1049746(%rip)  # 5008c0 <_GLOBAL_OFFSET_TABLE_+0x20> 
[ ... ] 
0000000000400500 <main>: 
    400500:  bf 0c 06 40 00   mov $0x40060c,%edi 
    400505:  68 19 05 40 00   pushq $0x400519 
    40050a:  ff 24 25 11 05 40 00 jmpq *0x400511 
    400511:  [ .quad 400428 ] 
    400519:  31 c0     xorl %eax, %eax 
    40051b:  c3      retq 
[ ... ] 
DYNAMIC RELOCATION RECORDS 
OFFSET   TYPE    VALUE 
[ ... ] 
00000000005008c0 R_X86_64_JUMP_SLOT printf
, es decir, esto todavía da la redirección en dos pasos, la primera ejecución de transferencia al gancho PLT, luego salta al punto de entrada de la biblioteca.

¿Hay alguna manera de cómo puedo ordenar al compilador/ensamblador/vinculador que, en este ejemplo, "en línea" el destino de la ranura de salto en la dirección 0x400511? Es decir. reemplace el "local" (resuelto en el tiempo del enlace del programa por ld) R_X86_64_64 reloc con el "control remoto" (resuelto en el tiempo de carga del programa por ld.so) R_X86_64_JUMP_SLOT uno (y fuerza la carga no lenta para esta sección de código)? Tal vez los archivos de mapas del enlazador podrían hacer esto posible, de ser así, ¿cómo?

Editar:
Para aclarar esto, la pregunta es acerca de cómo lograr esto en un ejecutable enlazado dinámicamente/a una función externa que sólo está disponible en una biblioteca dinámica. Sí, es cierto vinculación estática resuelve esto de una manera más simple, pero:

  • existen sistemas (como Solaris) donde las bibliotecas estáticas generalmente no se envían por el vendedor
  • hay bibliotecas que no están disponibles como o bien el código fuente o versiones estáticas

vinculación estática Por lo tanto no es útil aquí :(

Edit2:
he encontrado que en algunas arquitecturas (SPARC, notablemente, vea section on SPARC relocations in the GNU as manual), GNU puede crear ciertos tipos de referencias de reubicación para el enlazador in situ usando modificadores. El SPARC uno citado utilizaría %gdop(symbolname) para hacer que el ensamblador emita instrucciones al enlazador indicando "crear esa reubicación aquí mismo". El ensamblador de Intel en Itanium conoce el @fptr(symbol)link-relocation operator para el mismo tipo de cosas (ver también la sección 4 en el Itanium psABI). Pero ¿existe un mecanismo equivalente -algo para instruir al ensamblador que emita un tipo de reubicación de vinculador específico en una posición específica en el código- para x86_64?

También he encontrado que el ensamblador de GNU tiene una directiva .reloc que supuestamente debe ser utilizada para este propósito; Sin embargo, si lo intento:

#include <stdio.h> 
int main(int argc, char **argv) 
{ 
     asm ("push %%rax\n\t" 
      "lea 1f(%%rip), %%rax\n\t" 
      "xchg %%rax, (%rsp)\n\t" 
      "jmp *0f\n\t" 
      ".reloc 0f, R_X86_64_JUMP_SLOT, printf\n\t" 
      "0: .quad 0\n" 
      "1:\n\t" 
      : : "D"("Hello, World!\n")); 
     return 0; 
}

consigo un error del enlazador (nótese que 7 == R_X86_64_JUMP_SLOT):

error: /tmp/cc6BUEZh.o: unexpected reloc 7 in object file
El ensamblador crea un archivo de objeto para el que readelf dice:
Relocation section '.rela.text.startup' at offset 0x5e8 contains 2 entries: 
    Offset    Info    Type    Symbol's Value Symbol's Name + Addend 
0000000000000001 000000050000000a R_X86_64_32   0000000000000000 .rodata.str1.1 + 0 
0000000000000017 0000000b00000007 R_X86_64_JUMP_SLOT  0000000000000000 printf + 0 
Esto es lo que quiero - pero el enlazador no lo toma
El vinculador hace acepta simplemente usar R_X86_64_64 en su lugar arriba; Hacer eso crea el mismo tipo de binario que en el primer caso ... redireccionando a [email protected] no al "resuelto" ...

+0

¿qué tal prelinking? http://en.wikipedia.org/wiki/Prelink – JohnTortugo

+0

Ver también: [tratando de evitar indirectamente PLT para llamadas * dentro de * una sola biblioteca compartida] (http://stackoverflow.com/questions/36354247/how-do- i-force-gcc-to-call-a-function-directamente-en-pic-code). –

Respuesta

-1

Puede vincular estáticamente el ejecutable. Simplemente agregue -static al comando de enlace final, y todos sus saltos indirectos serán reemplazados por llamadas directas.

+0

Solo puede hacer eso cuando la biblioteca a la que se dirige está disponible en una versión estática :( –

+1

'printf' debería estar disponible :-) – hirschhornsalz

+0

Bueno, eso es cierto ;-) pero el ejemplo es solo eso, un ejemplo . Tiene razón en que si puede usar enlaces estáticos, lo soluciona. No puedo :(La función real a la que estoy tratando de llamar no está en libc, ni está disponible como lib estática. Por eso he preguntado sobre la técnica. –

2

Para alinear la llamada, necesitaría un cambio de código (.text) cuyo resultado es la dirección final de la función en la biblioteca compartida cargada dinámicamente. No existe tal reubicación (y los enlazadores estáticos modernos no los permiten) en x86_64 usando una cadena de herramientas GNU para GNU/Linux, por lo tanto no puede alinear toda la llamada como desee.

Lo más cerca que se puede conseguir es una llamada directa a través de la GOT (evita PLT):

.section .rodata 
.LC0: 
    .string "Hello, World!\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
    pushq %rbp 
    movq %rsp, %rbp 
    movl $.LC0, %eax 
    movq %rax, %rdi 
    call *[email protected](%rip) 
    nop 
    popq %rbp 
    ret 
    .size main, .-main 

Esto debería generar una reubicación R_X86_64_GLOB_DAT contra printf en el llegó a ser utilizado por la secuencia anterior. Debe evitar el código C porque, en general, el compilador puede usar cualquier número de registros guardados en el prólogo y el epílogo, lo que obliga a guardar y restaurar todos esos registros en la llamada a la función asm o arriesgarse a corromper esos registros para usarlos posteriormente. en la función de envoltura. Por lo tanto, es más fácil escribir el envoltorio en ensamblaje puro.

Otra opción es compilar con -Wl,-z,now -Wl,-z,relro que asegura que las entradas GOT relacionadas con PLT y PLT se resuelven al inicio para aumentar la localidad y la compacidad del código. Con RELLO completo, solo tendrá que ejecutar el código en el PLT y acceder a los datos en el GOT, dos cosas que ya deberían estar en algún lugar de la jerarquía de caché del núcleo lógico. Si RELRO completo es suficiente para satisfacer sus necesidades, entonces no necesitaría envoltorios y habría agregado beneficios de seguridad.

Las mejores opciones son realmente enlaces estáticos o LTO si están disponibles para usted.

+0

Tenga en cuenta que hacer una llamada desde inline asm es complicado. La rutina llamada puede atrapar un montón de registros (dependiendo del abi) que necesita para informar a gcc –

+0

Absolutamente de acuerdo. Mi ejemplo inicial en C funcionaba porque la situación era simple y el compilador generaba un código bastante pequeño alrededor de la llamada a la función de ASM en línea.Sin embargo, tiene razón, los registros guardados por la llamada asm en línea podrían ser utilizados por el compilador entre el punto de la llamada y el epílogo de la envoltura de la función. Por lo tanto, eliminé el ejemplo y utilicé el ensamblaje puro como recomendación. –

+0

¿Hay una forma C de expresar cómo obtener el puntero de función del GOT? Hacerlo de forma segura en x86-64 inline asm es torpe, y no puede ser tan bueno como C puro, ya que [no hay forma de decirle al compilador que quiere pegarle a la zona roja, y asume que el asm en línea no lo hace ] (http://stackoverflow.com/questions/34520013/using-base-pointer-register-in-c-inline-asm/34522750#34522750). Así que tendrías que 'asm (" agregar $ -128,% rsp \ n llamar * foo @ GOTPCREL (% rip) \ n sub $ -128,% rsp ":" a "(retomar):" D "(arg1), "S" (arg2), "d" (arg3): "memoria", "rcx", "r8", ...); ' –

Cuestiones relacionadas