¿Para qué sirve _start () en C?

125

Aprendí de mi colega que uno puede escribir y ejecutar un programa en C sin escribir una main()función. Se puede hacer así:

my_main.c

/* Compile this with gcc -nostartfiles */

#include <stdlib.h>

void _start() {
  int ret = my_main();
  exit(ret); 
}

int my_main() {
  puts("This is a program without a main() function!");
  return 0; 
}

Compílalo con este comando:

gcc -o my_main my_main.c nostartfiles

Ejecútalo con este comando:

./my_main

¿Cuándo se necesitaría hacer este tipo de cosas? ¿Hay algún escenario del mundo real en el que esto sea útil?

Chico sencillo
fuente
1
Relacionado de forma remota: stackoverflow.com/questions/2548486/compiling-without-libc
Mohit Jain
77
Artículo clásico que demuestra algunos de los funcionamientos internos de cómo se inician los programas: un tutorial de Whirlwind sobre la creación de ejecutables ELF realmente para Teensy para Linux . Esta es una buena lectura que analiza algunos de los puntos más finos _start()y otros aspectos externos main().
1
El lenguaje C en sí mismo no dice nada _startni sobre ningún punto de entrada que no sea main(excepto que el nombre del punto de entrada está definido por la implementación para implementaciones independientes (incrustadas)).
Keith Thompson

Respuestas:

107

El símbolo _startes el punto de entrada de su programa. Es decir, la dirección de ese símbolo es la dirección saltada al inicio del programa. Normalmente, la función con el nombre _startes suministrada por un archivo llamado crt0.oque contiene el código de inicio para el entorno de tiempo de ejecución C. Configura algunas cosas, llena la matriz de argumentos argv, cuenta cuántos argumentos hay y luego llama main. Luego de mainretornos, exitse llama.

Si un programa no desea utilizar el entorno de tiempo de ejecución C, debe proporcionar su propio código _start. Por ejemplo, la implementación de referencia del lenguaje de programación Go lo hace porque necesitan un modelo de subprocesamiento no estándar que requiere algo de magia con la pila. También es útil proporcionar el suyo propio _startcuando desea escribir programas realmente pequeños o programas que hacen cosas poco convencionales.

fuz
fuente
2
Otro ejemplo es el enlazador / cargador dinámico de Linux que tiene su propio _start definido.
PP
2
@BlueMoon Pero eso también _startproviene del archivo objeto crt0.o.
fuz
2
@ThomasMatthews El estándar no especifica _start; de hecho, no especifica lo que sucede antes de que mainse llame, solo especifica qué condiciones se deben cumplir cuando mainse llama. Es más una convención para el punto de entrada _startque se remonta a los viejos tiempos.
fuz
1
"la implementación de referencia del lenguaje de programación Go lo hace porque necesitan un modelo de subprocesamiento no estándar" crt0.o es específico de C (tiempo de ejecución crt-> C). No hay razón para esperar que se use para cualquier otro idioma. Y el modelo de subprocesos de Go es completamente estándar
Steve Cox
8
@SteveCox Muchos lenguajes de programación se construyen sobre el tiempo de ejecución de C porque es más fácil implementar lenguajes de esta manera. Go no utiliza el modelo de subprocesamiento normal. Utilizan pequeñas pilas asignadas al montón y su propio planificador. Este ciertamente no es un modelo de roscado estándar.
fuz
45

Si bien maines el punto de entrada para su programa desde la perspectiva de los programadores, _startes el punto de entrada habitual desde la perspectiva del sistema operativo (la primera instrucción que se ejecuta después de que su programa se inició desde el sistema operativo)

En un programa típico de C y especialmente de C ++, se ha realizado mucho trabajo antes de que la ejecución entre en main. Especialmente cosas como la inicialización de variables globales. Aquí se puede encontrar una buena explicación de todo lo que está pasando entre _start()y main()y también después de principal ha salido de nuevo (ver comentario más abajo).
El código necesario para eso generalmente lo proporcionan los escritores del compilador en un archivo de inicio, pero con el indicador –nostartfilesesencialmente le dice al compilador: "No te molestes en darme el archivo de inicio estándar, dame un control total sobre lo que está sucediendo desde el comienzo".

Esto a veces es necesario y a menudo se usa en sistemas integrados. Por ejemplo, si no tiene un sistema operativo y tiene que habilitar manualmente ciertas partes de su sistema de memoria (por ejemplo, cachés) antes de la inicialización de sus objetos globales.

MikeMB
fuente
Los vars globales son parte de la sección de datos y, por lo tanto, se configuran durante la carga del programa (si son constantes, son parte de la sección de texto, la misma historia). La función _start no tiene nada que ver con eso.
Cheiron
@Cheiron: Lo siento, mi error En c ++, las variables globales a menudo son inicializadas por un constructor que se ejecuta dentro _start()(o en realidad otra función llamada por él) y en muchos Programas Bare-Metal, copia explícitamente todos los datos globales de flash a RAM primero, lo que también ocurre en _start(), pero esta pregunta no era sobre c ++ ni sobre código de metal desnudo.
MikeMB
1
Tenga en cuenta que en un programa que proporciona el suyo propio _start, la biblioteca C no se inicializará a menos que tome medidas especiales para hacerlo usted mismo; puede ser inseguro usar cualquier función segura de señal no asíncrona de dicho programa. (No hay garantía oficial de que ninguna función de biblioteca funcionará, pero las funciones seguras de señal asíncrona no pueden referirse a ningún dato global, por lo que tendrían que hacer todo lo posible por un mal funcionamiento).
zwol
@zwol eso es solo parcialmente correcto. Por ejemplo, tal función podría asignar memoria. La asignación de memoria es problemática cuando mallocno se inicializan las estructuras de datos internas para .
fuz
1
@FUZxxl Habiendo dicho que, noto que las funciones asíncrona de señales de seguridad se pueden modificar errno(por ejemplo, ready writeson async señal-seguro y puede establecer errno) y que posiblemente podría ser un problema dependiendo exactamente cuando el per-hilo errnose asigna ubicación .
zwol
2

Aquí hay una buena descripción general de lo que sucede antes durante el inicio del programa main. En particular, muestra que __startes el punto de entrada real a su programa desde el punto de vista del sistema operativo.

Es la primera dirección desde la cual el puntero de instrucción comenzará a contar en su programa.

El código allí invoca algunas rutinas de biblioteca de tiempo de ejecución de C solo para hacer algunas tareas de limpieza, luego llamar a su main, y luego bajar las cosas y llamar exitcon cualquier código de salida maindevuelto.


Una imagen vale mas que mil palabras:

Diagrama de inicio de tiempo de ejecución de C


PD: esta respuesta se trasplanta de otra pregunta que SO ha cerrado útilmente como duplicado de esta.

ulidtko
fuente
Publicación cruzada para preservar el excelente análisis y la buena imagen.
ulidtko
1

¿Cuándo se necesitaría hacer este tipo de cosas?

Cuando desee su propio código de inicio para su programa.

mainNo es la primera entrada para un programa en C, _startes la primera entrada detrás de la cortina.

Ejemplo en Linux:

_start: # _start is the entry point known to the linker
    xor %ebp, %ebp            # effectively RBP := 0, mark the end of stack frames
    mov (%rsp), %edi          # get argc from the stack (implicitly zero-extended to 64-bit)
    lea 8(%rsp), %rsi         # take the address of argv from the stack
    lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
    xor %eax, %eax            # per ABI and compatibility with icc
    call main                 # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main

    mov %eax, %edi    # transfer the return of main to the first argument of _exit
    xor %eax, %eax    # per ABI and compatibility with icc
    call _exit        # terminate the program

¿Hay algún escenario del mundo real en el que esto sea útil?

Si quiere decir, implemente el nuestro _start:

Sí, en la mayoría del software comercial integrado con el que he trabajado, necesitamos implementar el nuestro con _startrespecto a nuestros requisitos específicos de memoria y rendimiento.

Si quiere decir, suelte la mainfunción y cámbiela a otra cosa:

No, no veo ningún beneficio haciendo eso.

Trevor
fuente