Portación de MiniCRT a Linux de 64 bits: consideraciones clave para gcc 64

Para compilar, se utilizan los siguientes comandos:

gcc -c -fno-builtin -nostdlib entry.c malloc.c stdio.c string.c printf.c
ar -rs minicrt.a malloc.o printf.o stdio.o string.o
gcc -c -ggdb -fno-builtin -nostdlib test.c
ld -static -e mini_crt_entry entry.o test.o minicrt.a -o test

Durante la compilación de entry.c, aparece un error en la instrucción ensamblador en línea:

entry.c:59: Error: unsupported instruction `mov'

El código problemático es:

//ebp_reg = %ebp
asm("movl %%ebp,%0 \n":"=r"(ebp_reg));

La causa raíz es el tamaño de los punteros. En un sistema de 64 bits, los registros son de 64 bits. La instrucción movl y el registro ebp son de 32 bits, mientras que ebp_reg (asignado a %0) es de 64 bits. La corrección es utilizar instrucciones y registros de 64 bits:

asm("movq %%rbp,%0 \n":"=r"(ebp_reg));

Tras corregir esto, se resuelven las advertencias relacionadas cambiando todos los tipos int en el código fuente por long (el tipo de retorno de main puede permanecer como int).

Al ejecutar el programa de prueba, no se produce salida. Un ejemplo de prueba simplificado es:

#include "minicrt.h"
int main()
{
        printf("hello world\n");
        return 0;
}

La depuración revela que la interrupción int 0x80 con el número de llamada 4 (sys_write) ya no funciona correctamente. Se descubrió que en sistemas de 64 bits, esta interrupción solo puede direccionar memoria dentro del espacio de 4 GB inferior (es decir, utiliza ecx en lugar de rcx). Las variables globales y estáticas dentro de la dirección 0x400000 a 0x60FFFFF están en este rango, pero los datos en la pila pueden estar fuera de él.

La solución es copiar el contenido del búfer de la pila a una variable global antes de llamar a la interrupción. Se modifican las funciones fputc y fputs:

static char __fputc_tmp_val__ = 0;
long fputc(char c,FILE* stream)
{
        __fputc_tmp_val__ = c;
        if (fwrite(&__fputc_tmp_val__,1,1,stream) != 1)
        {
                return EOF;
        }
        else
        {
                return c;
        }
}
 
static char __fputs_tmp_array__[256] = {0};
static int __fputs_tmp_size__ = 256;
long fputs(const char* str,FILE *stream)
{
        long len        = strlen(str);
        if( len >= __fputs_tmp_size__ )
                return EOF;
        strcpy( __fputs_tmp_array__,str );
        if (fwrite(__fputs_tmp_array__,1,len,stream) != len)
        {
                return EOF;
        }
        else
        {
                return len;
        }
}

Sin embargo, al probar con el programa de prueba original que incluye una llamada a printf con múltiples argumentos, nuevamente no hay salida. El análisis del desensamblado muestra un comportamiento diferente en la pasada de parámetros.

En la arquitectura x86-64, la convención de llamada (System V AMD64 ABI) utiliza registros para los primeros argumentos. Los argumentos enteros se pasan en rdi, rsi, rdx, rcx, r8 y r9. Los argumentos de punto flotante se pasan en xmm0 a xmm7. El desensamblado de la función printf muestra que, al entrar, los registros se guardan en la pila.

Para implementar va_start y va_arg de manera compatible con esta convención, se toma un atajo. Observando que en la compilación sin optimización (-O0), los argumentos (a partir del segundo, ya que el primero es la cadena de formato) se copian a la pila a partir de un desplazamiento fijo desde rbp. Para el caso de la función printf de MiniCRT, que no maneja arugmentos de punto flotante, se puede acceder a la lista de argumentos asumiendo que están en el desplazamiento -0xa8 desde el valor actual de rbp.

Se modifica la función printf para obtener el puntero a los argumentos mediante ensamblador en línea:

long printf(const char *format,...)
{
        char* arglist;
        asm( "movq %%rbp,%0":"=r"(arglist) );
        arglist -= 0xa8;
        return vfprintf(stdout,format,arglist);
}

Esta solución es específica para el caso de compilación con -O0 y sin argumentos de punto flotante, pero permite que la biblioteca MiniCRT funcione en un entorno de 64 bits.

Etiquetas: GCC x86-64 ABI System V portación libc

Publicado el 7-6 20:34