Qual é o uso de _start () em C?

125

Aprendi com meu colega que se pode escrever e executar um programa C sem escrever uma main()função. Isso pode ser feito assim:

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; 
}

Compile com este comando:

gcc -o my_main my_main.c nostartfiles

Execute-o com este comando:

./my_main

Quando alguém precisaria fazer esse tipo de coisa? Existe algum cenário do mundo real em que isso seria útil?

Cara simples
fonte
1
Remotamente relacionado: stackoverflow.com/questions/2548486/compiling-without-libc
Mohit Jain 17/04/2015
7
Artigo clássico que demonstra algumas das ações internas de como os programas são iniciados: um tutorial sobre como criar executáveis ​​realmente difíceis para o Linux . Esta é uma boa leitura que discute alguns dos pontos mais delicados _start()e outras coisas fora de main().
1
A própria linguagem C não diz nada sobre _start, ou sobre qualquer ponto de entrada que não seja main(exceto que o nome do ponto de entrada é definido pela implementação para implementações independentes (incorporadas)).
Keith Thompson

Respostas:

107

O símbolo _starté o ponto de entrada do seu programa. Ou seja, o endereço desse símbolo é o endereço saltado no início do programa. Normalmente, a função com o nome _starté fornecida por um arquivo chamado crt0.oque contém o código de inicialização para o ambiente de tempo de execução C. Ele cria algumas coisas, preenche a matriz de argumentos argv, conta quantos argumentos existem e depois chama main. Após mainretornos, exité chamado.

Se um programa não quiser usar o ambiente de tempo de execução C, precisará fornecer seu próprio código _start. Por exemplo, a implementação de referência da linguagem de programação Go faz isso porque eles precisam de um modelo de encadeamento não padrão que exija alguma mágica com a pilha. Também é útil fornecer o seu próprio _startquando você deseja escrever programas realmente minúsculos ou programas que fazem coisas não convencionais.

difuso
fonte
2
Outro exemplo é o vinculador / carregador dinâmico do Linux, que possui seu próprio _start definido.
PP
2
@BlueMoon Mas isso também _startvem do arquivo de objeto crt0.o.
fuz 17/04/2015
2
@ThomasMatthews O padrão não especifica _start; de fato, ele não especifica o que acontece antes de mainser chamado, apenas especifica quais condições devem ser atendidas quando mainé chamado. É mais uma convenção para o ponto de entrada _startque remonta aos velhos tempos.
fuz 17/04/2015
1
"a implementação de referência da linguagem de programação Go faz isso porque eles precisam de um modelo de encadeamento não padrão" crt0.o é específico de C (crt-> tempo de execução de C). Não há razão para esperar que seja usado em qualquer outro idioma. E modelo de segmentação do Go é completamente compatível com padrão
Steve Cox
8
@SteveCox Muitas linguagens de programação são criadas sobre o tempo de execução C porque é mais fácil implementar linguagens dessa maneira. O Go não usa o modelo de segmentação normal. Eles usam pequenas pilhas alocadas em heap e seu próprio agendador. Certamente este não é um modelo de encadeamento padrão.
fuz 17/04/2015
45

Embora mainseja o ponto de entrada para o seu programa da perspectiva dos programadores, _starté o ponto de entrada usual da perspectiva do SO (a primeira instrução executada após o início do programa no SO)

Em um programa típico em C e especialmente em C ++, muito trabalho foi feito antes da execução entrar no main. Especialmente coisas como inicialização de variáveis ​​globais. Aqui você pode encontrar uma boa explicação de tudo o que está acontecendo entre _start()e main()depois da saída do main novamente (veja o comentário abaixo).
O código necessário para isso geralmente é fornecido pelos gravadores do compilador em um arquivo de inicialização, mas com o sinalizador –nostartfilesvocê basicamente diz ao compilador: "Não se incomode em me fornecer o arquivo de inicialização padrão, me dê controle total sobre o que está acontecendo diretamente no começar".

Às vezes, isso é necessário e frequentemente usado em sistemas embarcados. Por exemplo, se você não possui um sistema operacional e precisa habilitar manualmente certas partes do seu sistema de memória (por exemplo, caches) antes da inicialização de seus objetos globais.

MikeMB
fonte
Os vars globais fazem parte da seção de dados e, portanto, são configurados durante o carregamento do programa (se são const, fazem parte da seção de texto, mesma história). A função _start não tem nenhuma relação com isso.
Quíron
@ Cheiron: Desculpe, meu emista Em c ++, variáveis ​​globais são frequentemente inicializadas por um construtor executado dentro _start()(ou na verdade outra função chamada por ele) e em muitos programas bare-metal, você copia explicitamente todos os dados globais do flash para a RAM primeiro, o que também acontece _start(), mas essa pergunta não era sobre c ++ nem código bare-metal.
MikeMB
1
Observe que em um programa que fornece seus próprios recursos _start, a biblioteca C não será inicializada, a menos que você tome medidas especiais para fazer isso sozinho - pode ser perigoso usar qualquer função segura de sinal não-assíncrono desse programa. (Não há garantia oficial de que qualquer função da biblioteca funcione, mas as funções seguras de sinal assíncrono não podem se referir a nenhum dado global, portanto elas teriam que se
esforçar
@ zwol isso é apenas parcialmente correto. Por exemplo, essa função pode alocar memória. A alocação de memória é problemática quando as estruturas de dados internas para mallocnão são inicializadas.
fuz 17/04/2015
1
@FUZxxl Tendo dito isso, noto que as funções seguras de sinal assíncrono são autorizados a modificar errno(por exemplo, reade writesão async-signal-seguro e pode definir errno) e que poderia concebivelmente ser um problema dependendo exatamente quando o por thread errnolocal é alocado .
Zwol 17/04
2

Aqui está uma boa visão geral do que acontece antes da inicialização do programa main. Em particular, mostra que esse __starté o ponto de entrada real do seu programa do ponto de vista do SO.

É o primeiro endereço a partir do qual o ponteiro de instruções começará a contar no seu programa.

O código lá chama algumas rotinas da biblioteca de tempo de execução C apenas para realizar algumas tarefas domésticas, depois chamar seu maine, em seguida, interromper as coisas e chamar exitcom qualquer código de saída mainretornado.


Uma imagem vale mais que mil palavras:

Diagrama de inicialização do tempo de execução C


PS: esta resposta é transplantada de outra pergunta que SO fechou útil como duplicada desta.

ulidtko
fonte
Publicada em cruz para preservar a excelente análise e a bela imagem.
ulidtko 20/01
1

Quando alguém precisaria fazer esse tipo de coisa?

Quando você deseja seu próprio código de inicialização para o seu programa.

main não é a primeira entrada para um programa C, _start é a primeira entrada atrás da cortina.

Exemplo no 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

Existe algum cenário do mundo real em que isso seria útil?

Se você quer dizer, implemente o nosso _start:

Sim, na maioria dos softwares comerciais incorporados com os quais trabalhei, precisamos implementar nossos próprios em _startrelação aos nossos requisitos específicos de memória e desempenho.

Se você quer dizer, largue a mainfunção e altere-a para outra coisa:

Não, não vejo nenhum benefício em fazer isso.

Trevor
fonte