gcc linker linker-scripts

gcc - ¿Cómo usar correctamente un script de enlazador simple? El ejecutable obtiene SIGKILL cuando se ejecuta



linker linker-scripts (1)

Intento entender el proceso de vinculación más profundo y los scripts del enlazador ... mirando el documento de binutils encontré una sencilla implementación del script del enlazador que he mejorado agregando algunos comandos:

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_ARCH(i386) ENTRY(mymain) SECTIONS { . = 0x10000; .text : { *(.text) } . = 0x8000000; .data : { *(.data) } .bss : { *(.bss) } }

Mi programa es un programa muy simple:

void mymain(void) { int a; a++; }

Ahora traté de construir un ejecutable:

gcc -c main.c ld -o prog -T my_script.lds main.o

Pero si intento ejecutar prog , recibe un SIGKILL durante el inicio. Sé que cuando un programa se compila y se vincula con el comando:

gcc prog.c -o prog

el ejecutable final es también el producto de otros archivos objeto como crt1.o , crti.o y crtn.o pero ¿qué pasa con mi caso? ¿Cuál es la forma correcta de utilizar estos scripts de enlazador?


Sospecho que tu código funciona bien y te metes en problemas al final: ¿qué esperas que suceda después del a++ ?

mymain() es solo una función C normal, que intentará regresar a su llamador.

Pero lo ha establecido como el punto de entrada ELF, que le dice al cargador ELF que salte a él una vez que haya cargado los segmentos del programa en el lugar correcto, y no espera que regrese.

Esos "otros archivos objeto como crt1.o , crti.o y crtn.o " normalmente manejan esto para programas C. El punto de entrada ELF para un programa C no es main() ; en cambio, es un contenedor que configura un entorno apropiado para main() (por ejemplo, configura los argumentos argc y argv en la pila o en los registros, dependiendo de la plataforma) , llama a main() (con la expectativa de que pueda regresar), y luego invoca la llamada al sistema de exit (con el código de retorno de main() ).

[Actualizar los siguientes comentarios:]

Cuando pruebo tu ejemplo con gdb , veo que sí falla al regresar de mymain() : después de establecer un punto de interrupción en mymain , y luego de recorrer las instrucciones, veo que realiza el incremento, luego se mete en problemas en la función epílogo:

$ gcc -g -c main.c $ ld -o prog -T my_script.lds main.o $ gdb ./prog ... (gdb) b mymain Breakpoint 1 at 0x10006: file main.c, line 4. (gdb) r Starting program: /tmp/prog Breakpoint 1, mymain () at main.c:4 4 a++; (gdb) display/i $pc 1: x/i $pc 0x10006 <mymain+6>: addl $0x1,-0x4(%ebp) (gdb) si 5 } 1: x/i $pc 0x1000a <mymain+10>: leave (gdb) si Cannot access memory at address 0x4 (gdb) si 0x00000001 in ?? () 1: x/i $pc Disabling display 1 to avoid infinite recursion. 0x1: Cannot access memory at address 0x1 (gdb) q

Para i386 al menos, el cargador ELF configura una pila sensible antes de ingresar el código cargado, por lo que puede establecer el punto de entrada ELF en una función C y obtener un comportamiento razonable; Sin embargo, como mencioné anteriormente, debe manejar un proceso de limpieza. Y si no está utilizando el tiempo de ejecución de C, es mejor que no use ninguna biblioteca que dependa del tiempo de ejecución de C tampoco.

Así que aquí hay un ejemplo de eso, usando su secuencia de comandos del enlazador original, pero con el código C modificado para inicializar a valor conocido e invocar una llamada al sistema de exit (usando ensamblaje en línea) con el valor final de a como código de salida. (Nota: Me acabo de dar cuenta de que no me has dicho exactamente qué plataforma estás usando, supongo que aquí Linux).

$ cat main2.c void mymain(void) { int a = 42; a++; asm volatile("mov $1,%%eax; mov %0,%%ebx; int $0x80" : : "r"(a) : "%eax" ); } $ gcc -c main2.c $ ld -o prog2 -T my_script.lds main2.o $ ./prog2 ; echo $? 43 $