Gentoo Logo

CÓMO localizar y corregir relocalizaciones .text (TEXTRELs)

Content:

1.  Introducción

Asegúrese de leer la Introducción al código independiente de la posición antes de abordar esta guía.

Actualmente esta guía está centrada en la arquitectura x86. La razón para ello es que la mayoría de los ficheros objeto están rotos debido a código ensamblador x86 mal escrito y esto es a su vez, en la mayoría de las ocasiones, debido a que esta arquitectura tiene muy pocos registros. Otras arquitecturas tienen un conjunto mayor de registros suficientes para poder reservar uno de ellos como el "registro PIC" sin tener impacto en el rendimiento. Cada arquitectura debe tener en cuenta el uso de PIC y sus implicaciones. La arquitectura x86 simplemente parece la predominante en el mundo de escritorio del código abierto.

Actualizaremos el documento para otras arquitecturas aparte de la x86 a medida que obtengamos detalles y ejemplos útiles.

2.  Buscando código objeto roto

Antes de empezar a corregir algo, obviamente necesita saber en primer lugar si está roto o no, ¿de acuerdo?. Por esta razón, hemos desarrollado un conjunto de herramientas llamadas utilidades PaX. Si no está familiarizado con estas utilidades, debería leer la guía de utilidades PaX ahora mismo. Los usuarios de Gentoo simplemente necesitan hacer emerge pax-utils. Los usuarios de otros sistemas debería encontrar una copia del tarball con los fuentes en distfiles en un servidor réplica de Gentoo Mirror. Una vez que tenga las utilidades PaX preparadas en su sistema, podemos empezar a jugar con scanelf.

Recuerde que, aunque estas utilidades se llaman utilidades PaX, realmente no necesitan PaX o algo similar a ello instalado en su sistema. El nombre es un artificio histórico y los intentos de encontrar un nombre mejor no han sido fructíferos.

Echemos un vistazo para ver si su sistema tiene algún fichero roto.

Code Listing 2.1: Escanear su sistema

$ scanelf -lpqt
TEXTREL  /usr/lib/opengl/xorg-x11/lib/libGL.so.1.2
TEXTREL  /usr/lib/libSDL-1.2.so.0.7.2
TEXTREL  /usr/lib/libdv.so.4.0.2
TEXTREL  /usr/lib/libsmpeg-0.4.so.0.1.3
TEXTREL  /usr/lib/libOSMesa.so.4.0
TEXTREL  /usr/lib/libxvidcore.so.4.1

Idealmente, scanelf no debería mostrar ninguna salida, pero en un sistema x86 system, esto sucede raramente. Aquí tenemos seis librerías com TEXTRELs dentro de ellas. Para averiguar rápidamente a qué paquete pertenecen estas librerías, los usuarios de Gentoo pueden hacer emerge portage-utils y utilizar qfile.

Code Listing 2.2: Determinar los paquetes rotos

$ qfile `scanelf -qylpF%F#t`
media-libs/libdv (/usr/lib/libdv.so.4.0.2)
media-libs/libsdl (/usr/lib/libSDL-1.2.so.0.7.2)
media-libs/smpeg (/usr/lib/libsmpeg-0.4.so.0.1.3)
media-libs/xvid (/usr/lib/libxvidcore.so.4.1)
x11-base/xorg-x11 (/usr/lib/opengl/xorg-x11/lib/libGL.so.1.2)
x11-base/xorg-x11 (/usr/lib/libOSMesa.so.4.0)

Ahora que conocemos los ficheros problemáticos, podemos decidir. Podemos enviar una incidencia al equipo de desarrollo del paquete (normalmente no podrán atención a la incidencia a no ser que también les enviemos la corrección). También podemos enviar una incidencia a Gentoo Bugzilla (lo cual es una forma vaga de resolverlo) o podemos corregir el problema nosotros mismos (es por lo que está leyendo esta guía ¿no?). Debe comprobar que la versión del paquete que está instalado en su distribución es la misma que la última publicada por el equipo de desarrollo del paquete. Quien sabe, quizá tenga suerte y alguien ya lo ha corregido. Si necesita obtener consejos sobre su trabajo, no dude en contactar con el equipo Gentoo Hardened.

"Falsos" Positivos

A veces puede topar con un paquete que contiene gran cantidad de TEXTRELs que parecen no tener relación con ensamblador. Esto puede ser simplemente debido a que los objetos no se compilaron adecuadamente con la opción PIC . La solución es muy simple: asegúrese de que cada fichero objeto está enlazado con el fichero compartido final usando las opción PIC apropiada (normalmente -fPIC).

Por ejemplo, echemos un vistazo al paquete. Este paquete construye algunos módulos, sin embargo, solo compila algunos de los objetos con -fPIC los cuales se enlazan en el módulo final libsilc_core.so. ¡La salida de scanelf en este caso es muy extensa!.

Code Listing 2.3: Correr scanelf en silc-plugin

$ scanelf -qT /usr/lib/irssi/modules/libsilc_core.so | wc -l
10734
$ scanelf -qT /usr/lib/irssi/modules/libsilc_core.so
...
  libsilc_core.so: silc_client_ftp_ask_name [0xD542C] in silc_client_receive_new_id [0xD5380]
  libsilc_core.so: silc_client_run_one [0xD55CA] in silc_client_receive_new_id [0xD5380]
  libsilc_core.so: silc_id_payload_parse [0xD5842] in silc_client_packet_parse_type [0xD57B0]
  libsilc_core.so: fgetc@@GLIBC_2.0 [0xD5857] in silc_client_packet_parse_type [0xD57B0]
...

¿Un TEXTREL en la función fgetc() de glibc?. O se está llamando a fgetc() desde ensamblador (y se debería tener en cuenta) o algo distinto está ocurriendo. Una buena regla a seguir es que si cada referencia a una función o variable provoca un TEXTREL y todo está escrito en código C, entonces el fichero no se construyó como PIC. Simplemente revise la salida de la construcción y compruebe si la orden para compilar incluyó -fPIC. Si no, corrija el sistema de construcción ya que no necesita indagar en el código fuente. ¡Hemos esquivado la bala en esta ocasión!.

3.  Diseccionando código objeto roto

Hemos identificado algunas librerías rotas y queremos corregirlas. El problema es que el código de la librería puede ser enorme. Puede que tenga miles de funciones que proceden de miles de ficheros objeto y de miles de líneas de código fuente que ocupan varios megabytes (entre el código fuente y los objetos compilados). ¿Por dónde demonios empezamos?. Una vez más "Super Ratón" scanelf está aquí para ayudarnos. Antes de sumergirse en el código fuente, comprobemos algunas librerías.

Diseccionando libsmpeg

Code Listing 3.1: Escanear libsmpeg

[Se han cortado unas 35 líneas de la salida]
$ scanelf -qT /usr/lib/libsmpeg-0.4.so.0.1.3
  libsmpeg-0.4.so.0.1.3: (memory/fake?) [0x2FB3C] in cpu_flags [0x2FB10]
  libsmpeg-0.4.so.0.1.3: (memory/fake?) [0x2FB42] in cpu_flags [0x2FB10]
  libsmpeg-0.4.so.0.1.3: (memory/fake?) [0x2FB55] in IDCT_mmx [0x2FB48]
  libsmpeg-0.4.so.0.1.3: (memory/fake?) [0x2FB84] in IDCT_mmx [0x2FB48]
  /usr/lib/libsmpeg-0.4.so.0.1.3

La salida anterior nos dice que las funciones cpu_flags e IDCT_mmx son las responsables de nuestros TEXTRELs. El primer campo indica que se está realizando un uso muy pobre de las referencias a memoria. Lamentablemente, el nombre simbólico de la memoria que se referencia no se almacena en el código del objeto (probablemente porque el código se ha escrito a mano en ensamblador), por lo tanto, necesitaremos investigar un poco. Aquí es donde los desplazamientos de las direcciones nos van a servir de ayuda junto con la utilidad objdump del paquete binutils. La primera dirección (por ejemplo 0x2FB3C) es el desplazamiento del TEXTREL mientras que la segunda dirección es el desplazamiento de la función (por ejemplo 0x2FB10). Acostúmbrese a esto ya que lo normal es no almacenar el nombre de los símbolos.

Code Listing 3.2: Diseccionando libsmpeg

$ objdump -d /usr/lib/libsmpeg-0.4.so.0.1.3
...
   2fb0f:       90                      nop

0002fb10 <cpu_flags>:
   2fb10:       9c                      pushf
   2fb11:       58                      pop    %eax
...
   2fb32:       60                      pusha
   2fb33:       b8 01 00 00 00          mov    $0x1,%eax
   2fb38:       0f a2                   cpuid
   2fb3a:       89 15 d0 d3 03 00       mov    %edx,0x3d3d0
   2fb40:       61                      popa
   2fb41:       a1 d0 d3 03 00          mov    0x3d3d0,%eax
   2fb46:       c3                      ret
   2fb47:       90                      nop
...

Como puede ver aquí, las dos líneas remarcadas en el cuerpo de cpu_flags hacen referencia a direcciones de memoria absolutas. En este caso, ambas referencia a la dirección de memoria 0x3d3d0. Ya que debe ser posible que este objeto se cargue en cualquier dirección, obviamente, el uso de una referencia absoluta no funcionará. Esto significa que cada vez que libsmpeg se carga en memoria, el cargador dinámico tiene que reescribir 0x3d3d0 a la dirección correcta, haciendo el cálculo correspondiente.

Diseccionando libdv

Code Listing 3.3: Escanear libdv

[Se han cortado unas 180 líneas de la salida]
$ scanelf -qT /usr/lib/libdv.so.4.0.2
  libdv.so.4.0.2: (memory/fake?) [0x14AA9] in dv_parse_ac_coeffs_pass0 [0x14A84]
  libdv.so.4.0.2: (memory/fake?) [0x14C28] in dv_parse_ac_coeffs_pass0 [0x14A84]
  libdv.so.4.0.2: (memory/fake?) [0x14C8A] in dv_parse_video_segment [0x14C6F]
  libdv.so.4.0.2: (memory/fake?) [0x14CA6] in dv_parse_video_segment [0x14C6F]
  libdv.so.4.0.2: (memory/fake?) [0x15248] in _dv_idct_block_mmx [0x15210]
  libdv.so.4.0.2: (memory/fake?) [0x152BE] in _dv_idct_block_mmx [0x15210]
  libdv.so.4.0.2: (memory/fake?) [0x1583D] in _dv_dct_88_block_mmx [0x157F8]
  libdv.so.4.0.2: (memory/fake?) [0x15847] in _dv_dct_88_block_mmx [0x157F8]
  libdv.so.4.0.2: (memory/fake?) [0x15F91] in _dv_dct_248_block_mmx [0x15F58]
  libdv.so.4.0.2: (memory/fake?) [0x15FE6] in _dv_dct_248_block_mmx [0x15F58]
  libdv.so.4.0.2: (memory/fake?) [0x163D3] in _dv_rgbtoycb_mmx [0x163C8]
  libdv.so.4.0.2: (memory/fake?) [0x163DD] in _dv_rgbtoycb_mmx [0x163C8]
  libdv.so.4.0.2: dv_vlc_class_index_mask [0x149A7] in dv_decode_vlc [0x14998]
  libdv.so.4.0.2: dv_vlc_class_index_rshift [0x149B0] in dv_decode_vlc [0x14998]
  libdv.so.4.0.2: dv_vlc_classes [0x149B9] in dv_decode_vlc [0x14998]
  libdv.so.4.0.2: dv_vlc_index_mask [0x149C4] in dv_decode_vlc [0x14998]
  libdv.so.4.0.2: sign_mask [0x149EB] in dv_decode_vlc [0x14998]
  libdv.so.4.0.2: sign_mask [0x14A5D] in __dv_decode_vlc [0x14A1C]
  libdv.so.4.0.2: sign_mask [0x14B82] in dv_parse_ac_coeffs_pass0 [0x14A84]
  libdv.so.4.0.2: dv_vlc_class_lookup5 [0x14A2F] in __dv_decode_vlc [0x14A1C]
  libdv.so.4.0.2: dv_parse_ac_coeffs_pass0 [0x14E03] in dv_parse_video_segment [0x14C6F]
  libdv.so.4.0.2: dv_parse_ac_coeffs [0x14E51] in dv_parse_video_segment [0x14C6F]
  libdv.so.4.0.2: dv_quant_offset [0x14E69] in _dv_quant_88_inverse_x86 [0x14E5C]
  libdv.so.4.0.2: dv_quant_offset [0x14FB3] in _dv_quant_x86 [0x14FA4]
  /usr/lib/libdv.so.4.0.2

De nuevo, podemos ver que muchas funciones (como dv_parse_ac_coeffs_pass0 y _dv_idct_block_mmx) tienen referencias absolutas a memoria. Lo que podemos ver también es que algunas funciones hacen referencias a variables. Por ejemplo, dv_decode_vlc hace un mal uso de la variable dv_vlc_class_index_mask y dv_parse_video_segment hace un mal uso de la variable dv_parse_ac_coeffs. Estos problemas son más fáciles de localizar en el código fuente cuando se conoce el nombre del símbolo.

Diseccionar libSDL

Code Listing 3.4: Escanear libSDL

[Se han cortado unas 50 líneas de la salida]
$ scanelf -qT /usr/lib/libSDL-1.2.so.0.7.2
  libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E213] in _ConvertMMXpII32_24RGB888 [0x4E210]
  libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E29E] in _ConvertMMXpII32_16RGB565 [0x4E29B]
  libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E3F6] in _ConvertMMXpII32_16BGR555 [0x4E3F3]
  libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E402] in _ConvertMMXpII32_16RGB555 [0x4E3FF]
  libSDL-1.2.so.0.7.2: (memory/fake?) [0x4E555] in _Hermes_X86_CPU [0x4E529]
  libSDL-1.2.so.0.7.2: _copy_row [0x316A2] in SDL_SoftStretch [0x313C0]
  libSDL-1.2.so.0.7.2: _mmxreturn [0x4E4FB] in _ConvertMMXpII32_16RGB555 [0x4E3FF]
  libSDL-1.2.so.0.7.2: _x86return [0x4E590] in _ConvertX86p16_16BGR565 [0x4E560]
  libSDL-1.2.so.0.7.2: _x86return [0x4EE99] in _ConvertX86p32_16BGR555 [0x4EDCA]
  libSDL-1.2.so.0.7.2: _x86return [0x4EF4D] in _ConvertX86p32_8RGB332 [0x4EE9D]
  /usr/lib/libSDL-1.2.so.0.7.2

Parece que no hay nada nuevo aquí. El mal uso de la memoria en funciones como _ConvertMMXpII32_24RGB888 y que no hay nombre del símbolo indica que probablemente se trata de código escrito a mano en ensamblador. La función SDL_SoftStretch no utiliza correctamente el símbolo _copy_row, y debido a que se ha conservado el nombre del símbolo, es probable que se trata de código ensamblador en línea.

4.  Buscando el código fuente roto

Hemos identificado las funciones, y en algunas ocasiones las variables que nos están causando dolores de cabeza. Sin embargo, antes de que podamos corregir estos problemas, debemos encontrar las líneas exactas del código fuente que los provocan. Como sabemos los nombres de las funciones y también el nombre del símbolo o su posición relativa dentro de la función, podremos focalizar nuestros esfuerzo fácilmente.

Sumergirnos el código fuente de libsmpeg

Comencemos con libsmpeg. Sabemos que, las funciones cpu_flags y IDCT_mmx están rotas. Pero, ¿dónde están definidas?.

Code Listing 4.1: Buscar los fuentes de libsmpeg

$ tar zxf smpeg-0.4.4.tar.gz
$ cd smpeg-0.4.4.tar.gz

[Encontrar la función cpu_flags]
$ grep -Rl cpu_flags *
video/mmxflags_asm.S
video/parseblock.cpp
$ grep cpu_flags video/mmxflags_asm.S
.globl cpu_flags
        .type    cpu_flags,@function <-- aquí está lo que queremos
cpu_flags:
        jz cpu_flags.L1   # Processor is 386
        je cpu_flags.L1
cpu_flags.L1:
        .size    cpu_flags,.Lfe1-cpu_flags

[Encontrar la función IDCT_mmx]
$ grep -Rl IDCT_mmx *
video/parseblock.cpp
video/mmxidct_asm.S
$ grep IDCT_mmx video/mmxidct_asm.S
.globl IDCT_mmx
        .type    IDCT_mmx,@function <-- aquí está lo que queremos
IDCT_mmx:
        .size    IDCT_mmx,.Lfe1-IDCT_mmx

Como sospechábamos, las funciones cpu_flags y IDCT_mmx se han escrito en código ensamblador puro. Esto hace que el seguimiento de las referencias a memoria que no tienen nombre sea más sencillo ya que el código fuente debería parecerse bastante a la salida de objdump. Si revisamos la salida de arriba, sabemos que se utiliza la instrucción cpuid. Ya que no es una instrucción muy común, podemos buscarla en el código fuente.

Code Listing 4.2: Buscar cpuid en cpu_flags

$ grep -A 8 cpuid video/mmxflags_asm.S
        cpuid

        movl %edx,flags

        popa

        movl flags,%eax

cpu_flags.L1:

En el ensamblador de GNU, los registros tienen como prefijo el carácter % y las constantes utilizan el prefijo $, por ello flags parece sospechoso. También parece coincidir con la salida de objdump mostrada anteriormente. Por tanto, ¿qué es flags?

Code Listing 4.3: ¿Qué es 'flags'?

$ grep -C 2 flags video/mmxflags_asm.S
.data
        .align 16
        .type    flags,@object
flags: .long 0

.text

Parece que flags es una variable local de datos en mmxflags_asm.S cuyas funciones acceden usando referencias absolutas a memoria en lugar de utilizar referencias relativas. Casi hemos terminado. Esto es todo lo que necesitamos. Comenzamos con la librería libsmpeg.so y seguimos el camino hasta la función cpu_flags y la variable flags en el fichero video/mmxflags_asm.S. No fue tan duro como parecía ¿no?.

Si analizamos la función IDCT_mmx, encontramos un patrón similar.

Code Listing 4.4: Fragmentos de código de IDCT_mmx

[Variables locales]
.data
    .align 8
    .type   x4546454645464546,@object
    .size   x4546454645464546,8
x4546454645464546:
    .long   0x45464546,0x45464546

    .align 8
    .type   x61f861f861f861f8,@object
    .size   x61f861f861f861f8,8
x61f861f861f861f8:
    .long   0x61f861f8,0x61f861f8

    .align 8
    .type    scratch1,@object
    .size    scratch1,8
scratch1:
    .long 0,0

[Referencias absolutas a memoria]
.text
...
    psraw $1, %mm5          /* t90=t93 */
    pmulhw x4546454645464546,%mm0   /* V35 */
    psraw $1, %mm2          /* t97 */
...
    psubsw %mm2, %mm1       /* V32 ; liberar mm2 */
    pmulhw x61f861f861f861f8,%mm1   /* V36 */
    psllw $1, %mm7          /* t107 */
...
    movq 8*3(%esi), %mm7
    psraw $4, %mm4
    movq %mm2, scratch1     /* out1 */

Sumergirse en el código de libSDL

De nuevo, antes de entrar en el detalle de cómo solucionar estos problemas, analicemos algunos ficheros fuentes para tener una mejor visión para identificar el código problemático.

Code Listing 4.5: Código roto _ConvertMMXpII32_24RGB888 en libSDL

[objdump de la referencia a memoria de _ConvertMMXpII32_24RGB888]
0004e210 <_ConvertMMXpII32_24RGB888>:
   4e210:       0f 6f 35 50 55 05 00    movq   0x55550,%mm6
   4e217:       0f ef ff                pxor   %mm7,%mm7

[_ConvertMMXpII32_24RGB888 está definido en src/hermes/mmxp2_32.asm]
        SECTION .data
ALIGN 8
;; Constantes para rutinas de conversión
mmx32_rgb888_mask dd 00ffffffh,00ffffffh
...
        SECTION .text
_ConvertMMXpII32_24RGB888: comienzo de la función 0x4E210
        ; definir mm6 como la máscar, mm7 a 0
        movq mm6, qword [mmx32_rgb888_mask] referencia a memoria 0x4E213
        pxor mm7, mm7

Demasiado simple, la función _ConvertMMXpII32_24RGB888 hace referencia a la variable mmx32_rgb888_mask.

Code Listing 4.6: Código roto SDL_SoftStretch en libSDL

[SDL_SoftStretch está definido en src/video/SDL_stretch.c]
int SDL_SoftStretch(SDL_Surface *src, SDL_Rect *srcrect,
                    SDL_Surface *dst, SDL_Rect *dstrect)
{
...
#ifdef __GNUC__
            __asm__ __volatile__ (
            "call _copy_row"
            : "=&D" (u1), "=&S" (u2)
            : "0" (dstp), "1" (srcp)
            : "memory" );
#else

Otra incidencia de resolución rápida. Una referencia a la variable _copy_row en ensamblador. Si pudiéramos hacer que gcc gestione la referencia a _copy_row ...

5.  Cómo escribir PIC (en teoría)

Reglas generales

Ahora sabemos el aspecto que tiene el código roto. Podemos señalar a partes del código y declarar con seguridad "esa parte está rota". Aunque esto es bueno, no nos ayudará mucho si no sabemos como debería estar escrita. Comencemos con algunas reglas prácticas.

Reglas generales

  • No mezclar código objeto PIC y no PIC
  • Las librerías compartidas contienen objetos PIC
  • Las librerías estáticas contienen objetos no PIC (únicamente en sistemas normales o no PIE)
  • Hacer que gcc averigüe los detalles siempre que sea posible (esto es, ensamblador en línea)
  • Usar la pila en lugar de variables para la carga de máscaras grandes
  • No llenar el registro PIC cuando generamos objetos PIC

Reglas específicas para la arquitectura x86

  • Utilizar relocalizaciones @GOT cuando se usen símbolos externos
  • Utilizar relocalizaciones @GOTOFF cuando se usen símbolos locales

Registros PIC para cada arquitectura

Arquitectura Registro
blackfin P3
frv GR15
hppa r19
x86 ebx

6.  Correcciones molde para PIC

No utilice el registro PIC

Si se encuentra con código que utiliza el registro PIC en código ensamblador en línea, una posible corrección podría ser la utilización de un registro diferente. Por ejemplo, la arquitectura x86 posee seis registros de propósito general (eax, ebx, ecx, edx, esi, edi). Si el código utiliza solo eax y ebx, simplemente cambie todas las referencias a ebx por referencias a ecx y ¡ha terminado!.

Una solución más limpia podría consistir en indicar a gcc que localice los registros de la forma apropiada. Si el código ensamblador en línea no tiene en cuenta los registros que utiliza, cambie las referencias a ebx por referencias a r en la lista de objetos (clobber list), ya haga referencia a la variable usando su número.

O, si el código ensamblador utiliza una instrucción que siempre llena ebx (por ejemplo cpuid), simplemente oculte el valor en otro registro (por ejemplo esi).

Si nada de esto funciona, puede utilizar el método (más lento) push/pop ebx usando la pila.

Code Listing 6.1: Simplemente, no utilizar el registro PIC

/* cambie este código */
asm("
    mov %0, %%eax
    mov %1, %%ebx
    add %%eax, %%ebx
    " : : "m"(input1), "m"(input2) : "eax" "ebx");

/* a esta versión funcionalmente equivalente */
asm("
    mov %0, %%eax
    mov %1, %%ecx
    add %%eax, %%ecx
    " : : "m"(input1), "m"(input2) : "eax" "ecx");

Code Listing 6.2: Hagamos que gcc localice los registros

/* cambie este código */
asm("
    mov %2, %%eax
    mov %3, %%ebx
    add %%eax, %%ebx
    " : "=a"(output1) "=b"(output2) : "m"(input1), "m"(input2));

/* a esta versión funcionalmente equivalente */
asm("
    mov %2, %0
    mov %3, %1
    add %0, %1
    " : "=r"(output1) "=r"(output2) : "m"(input1), "m"(input2));

Code Listing 6.3: Ocultar el registro PIC

asm("cpuid" : : : "eax", "ebx", "ecx", "edx");

/* para ocultar ebx se puede escribir */
asm("
    movl %%ebx, %%edi
    cpuid
    movl %%edi, %%ebx
    " : : : "eax", "ecx", "edx", "edi");

/* o una versión más lenta utilizando la pila */
asm("
    pushl %%ebx
    cpuid
    popl %%ebx
    " : : : "eax", "ecx", "edx");

Máscaras MMX/SSE

Mucho código x86 MMX/SSE carga máscaras de bits desde variables locales ya que necesitan llenar un registro que es mayor (MMX/64bits o SSE/128bits) que el ancho de bit nativo (x86/32bits). Esto se realizar definiendo la máscara en los bytes consecutivos en memoria y haciendo que la CPU cargue los datos desde la región de memoria.

Una forma de evitar esto consiste en ser más creativo en el uso de la pila. En lugar de utilizar referencias absolutas a memoria para la máscara, se carga en la pila un puñado de valores de 32 bits y se utiliza la referencia a memoria que especifica el registro esp. Una vez haya terminado, simplemente añada una constante a esp en lugar de extraerla ya que los valores actuales no son importantes una vez se hayan cargado en los registros MMX/SSE.

Code Listing 6.4: Cargar máscaras en registros a través de la pila

/* Cargar máscaras desde memoria (provoca TEXTRELs) */
        .data
m0X000000: .byte   0,   0,   0,   0,   0,   0, 255,   0
        .text
        movq    m0X000000, %mm5

/* Cargar máscaras desde la pila (no aparecen TEXTRELs)*/
pushl   $0x00FF0000
pushl   $0x00000000
movq    (%esp), %mm5
addl    8, %esp

Hagamos que gcc se ocupe de ello

Gran parte del código ensamblador en línea se ha escrito con los nombres de los símbolos colocados correctamente en el código. En lugar de escribir código para gestionar PIC en ensamblador, simplemente hagamos que gcc se ocupe de ello. Pase el símbolo a través de la lista de operadores de entrada como una restricción de memoria ("m") y gcc se ocupará de todo.

Code Listing 6.5: Como hacer que gcc se ocupe de ello

unsigned long long a_mmx_mask = 0xf8007c00ffea0059ULL;
void somefunction()
{
        /* Método común (pero incorrecto) para cargar máscaras */
        asm("pmullw a_mmx_mask, %%mm0" : : );

        /* La forma correcta es que lo haga gcc */
        asm("pmullw %0, %%mm0" : : "m"(a_mmx_mask));
}

Si se obtiene una advertencia/error sobre alguna de las entradas de memoria indicando que debe ser un lvalue, entonces, normalmente esto significa que se está intentando pasar un puntero a una matriz o estructura en lugar de la localización de memoria propiamente dicha. Esto normalmente se corrige simplemente dereferenciando la variable en la lista de restricciones en lugar del propio código ensamblador.

Golpe seco en ensamblador

El código ensamblador escrito de forma manual a veces necesita acceder a variables (bien sean locales o globales). Si ninguno de los trucos indicados anteriormente funcionaron, puede afilar sus dientes y sumergirse en el código para escribir referencias PIC reales utilizando la GOT. Asegúrese de que tiene en cuenta la primera regla general: No mezcle código objeto PIC y no PIC. Eso probablemente requiera preprocesar el código ensamblador escrito a mano antes de ensamblarlo, de modo que un fichero fuente ensamblador con un sufijo .s no funcionará, necesita tener un sufijo .S.

También tenga en cuenta que el uso de @GOTOFF retornará la variable mientras que el uso de @GOT retornará un puntero a la variable. Por lo tanto, acceder a la variable a través de @GOT requerirá dos pasos.

Code Listing 6.6: Como hacer referencia a variables a través de la GOT

#ifdef __PIC__

# undef __i686 /* gcc builtin define gets in our way */
# define MUNG_LOCAL(sym)   sym ## @GOTOFF(%ebx)
# define MUNG_EXTERN(sym)  sym ## @GOT(%ebx)
# define DEREF_EXTERN(reg) movl (reg), reg
# define INIT_PIC() \
        call __i686.get_pc_thunk.bx ; \
        addl $_GLOBAL_OFFSET_TABLE_, %ebx

#else

# define MUNG_LOCAL(sym)   sym
# define MUNG_EXTERN(sym)  sym
# define DEREF_EXTERN(reg)
# define INIT_PIC()

#endif

...
some_function:
...
        /* necesita estar antes que la primera referencia a memoria*/
        INIT_PIC()
...
        movl MUNG_EXTERN(some_external_variable), %eax
        DEREF_EXTERN(%eax)
...
        movl %eax, MUNG_LOCAL(some_local_variable)
...

#ifdef __PIC__
        .section .gnu.linkonce.t.__i686.get_pc_thunk.bx,"ax",@progbits
.globl __i686.get_pc_thunk.bx
        .hidden  __i686.get_pc_thunk.bx
        .type    __i686.get_pc_thunk.bx,@function
__i686.get_pc_thunk.bx:
        movl (%esp), %ebx
        ret
#endif

Note: No se requiere usar ebx como registro para hacer direccionamiento relativo, simplemente se trata de una convención común. Los ejemplos de arriba podrían realizarse utilizando ecx o edx.

Debido a que ocultamos los detalles de PIC detrás del preprocesador, (define __PIC__), sabemos que se generará el código correcto para los casos PIC y no PIC.

La función __i686.get_pc_thunk.bx es un método estándar para obtener la dirección de la GOT en tiempo de ejecución y almacenar el resultado en ebx. Este nombre tan curioso es el que utiliza gcc por convención cuando genera objetos PIC, por lo que hemos utilizado el mismo nombre. La notación @GOT y @GOTOFF le indica al ensamblador donde puede encontrar las variables en memoria. La .section .gnu.linkonce.t es útil ya que le indica al enlazador que únicamente incluya una instancia de esta función el código objeto final. Por lo que, si tiene varios ficheros que declaran la misma función, los cuales se compilan y enlazan en la misma librería final, el enlazador descartará todas las duplicadas de la función, salvando así espacio (lo cual es siempre algo bueno).

7.  Cómo corregir PIC roto (en la práctica)

Si los fragmentos de código anteriores están rotos, se estará preguntando ¿Cómo deberían ser?. Averigüémoslo.

Corregir libsmpeg

Code Listing 7.1: Corregir cpu_flags en libsmpeg reescribiéndolo

[Versión no PIC]
.type flags,@object
flags: .long 0
...
        pusha
        movl $1,%eax
        cpuid
        movl %edx,flags
        popa
        movl flags,%eax


[Versión PIC]
        pushl %ebx
        movl $1,%eax
        cpuid
        movl %edx,%eax
        popl %ebx

Code Listing 7.2: Corregir IDCT_mmx en libsmpeg utilizando direccionamiento relativo

[Versión no PIC]
        pmulhw x5a825a825a825a82, %mm1


[Versión PIC]
#ifdef __PIC__
# undef __i686 /* gcc define gets in our way */
        call __i686.get_pc_thunk.bx
        addl $_GLOBAL_OFFSET_TABLE_, %ebx
#endif
...
        pmulhw x5a825a825a825a82@GOTOFF(%ebx), %mm1
...
#ifdef __PIC__
        .section .gnu.linkonce.t.__i686.get_pc_thunk.bx,"ax",@progbits
.globl __i686.get_pc_thunk.bx
        .hidden  __i686.get_pc_thunk.bx
        .type    __i686.get_pc_thunk.bx,@function
__i686.get_pc_thunk.bx:
        movl (%esp), %ebx
        ret
#endif

Corregir libSDL

Code Listing 7.3: Corregir _ConvertMMXpII32_24RGB888 en libSDL

[Versión no PIC]
mmx32_rgb888_mask dd 00ffffffh,00ffffffh
...
        movq mm6, qword [mmx32_rgb888_mask]


[Versión PIC]
%macro _push_immq_mask 1
        push dword %1
        push dword %1
%endmacro
%macro load_immq 2
        _push_immq_mask %2
        movq %1, [esp]
%endmacro
%define mmx32_rgb888_mask 00ffffffh
...
        load_immq mm6, mmx32_rgb888_mask
        CLEANUP_IMMQ_LOADS(1)

Code Listing 7.4: Corregir SDL_SoftStretch en libSDL

[Versión no PIC]
        __asm__ __volatile__ (
                "call _copy_row"
                : "=&D" (u1), "=&S" (u2)
                : "0" (dstp), "1" (srcp)
                : "memory" );


[Versión PIC]
        __asm__ __volatile__ (
                "call *%4"
                : "=&D" (u1), "=&S" (u2)
                : "0" (dstp), "1" (srcp), "r" (&_copy_row)
                : "memory" );

8.  Referencias



Print

Page updated August 19, 2007

Summary: Una guía para localizar y corregir relocalizaciones .text (TEXTRELs)

Mike Frysinger
Autor

solar
Autor

El equipo PaX
Contribuyente

Kevin F. Quinn
Contribuyente

José María Alonso
Traductor

Donate to support our development efforts.

Copyright 2001-2013 Gentoo Foundation, Inc. Questions, Comments? Contact us.