CÓMO localizar y corregir relocalizaciones .text (TEXTRELs)
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 |
$ 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 |
$ 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 |
$ 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
$ 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
cpu_flags:
jz cpu_flags.L1 # Processor is 386
je cpu_flags.L1
cpu_flags.L1:
.size cpu_flags,.Lfe1-cpu_flags
$ 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
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 |
.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
.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 |
0004e210 <_ConvertMMXpII32_24RGB888>:
4e210: 0f 6f 35 50 55 05 00 movq 0x55550,%mm6
4e217: 0f ef ff pxor %mm7,%mm7
SECTION .data
ALIGN 8
;; Constantes para rutinas de conversión
mmx32_rgb888_mask dd 00ffffffh,00ffffffh
...
SECTION .text
_ConvertMMXpII32_24RGB888:
; definir mm6 como la máscar, mm7 a 0
movq mm6, qword [mmx32_rgb888_mask]
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 |
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 |
asm("
mov %0, %%eax
mov %1, %%ebx
add %%eax, %%ebx
" : : "m"(input1), "m"(input2) : "eax" "ebx");
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 |
asm("
mov %2, %%eax
mov %3, %%ebx
add %%eax, %%ebx
" : "=a"(output1) "=b"(output2) : "m"(input1), "m"(input2));
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");
asm("
movl %%ebx, %%edi
cpuid
movl %%edi, %%ebx
" : : : "eax", "ecx", "edx", "edi");
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 |
.data
m0X000000: .byte 0, 0, 0, 0, 0, 0, 255, 0
.text
movq m0X000000, %mm5
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()
{
asm("pmullw a_mmx_mask, %%mm0" : : );
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:
...
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 |
.type flags,@object
flags: .long 0
...
pusha
movl $1,%eax
cpuid
movl %edx,flags
popa
movl flags,%eax
pushl %ebx
movl $1,%eax
cpuid
movl %edx,%eax
popl %ebx
|
Code Listing 7.2: Corregir IDCT_mmx en libsmpeg utilizando direccionamiento relativo |
pmulhw x5a825a825a825a82, %mm1
#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 |
mmx32_rgb888_mask dd 00ffffffh,00ffffffh
...
movq mm6, qword [mmx32_rgb888_mask]
%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 |
__asm__ __volatile__ (
"call _copy_row"
: "=&D" (u1), "=&S" (u2)
: "0" (dstp), "1" (srcp)
: "memory" );
__asm__ __volatile__ (
"call *%4"
: "=&D" (u1), "=&S" (u2)
: "0" (dstp), "1" (srcp), "r" (&_copy_row)
: "memory" );
|
8.
Referencias
|
|