.init
/ .fini
não está obsoleto. Ainda faz parte do padrão ELF e ouso dizer que será para sempre. O código em .init
/ .fini
é executado pelo carregador / runtime-linker quando o código é carregado / descarregado. Ou seja, em cada código de carregamento ELF (por exemplo, uma biblioteca compartilhada) .init
será executado. Ainda é possível usar esse mecanismo para obter a mesma coisa que com __attribute__((constructor))/((destructor))
. É à moda antiga, mas tem alguns benefícios.
.ctors
/ .dtors
mecanismo, por exemplo, requer suporte do system-rtl / loader / linker-script. Isso está longe de estar disponível em todos os sistemas, por exemplo, sistemas profundamente embutidos nos quais o código é executado no bare metal. Ou seja, mesmo que o __attribute__((constructor))/((destructor))
GCC seja suportado, não é certo que ele funcione, pois cabe ao vinculador organizá-lo e ao carregador (ou, em alguns casos, código de inicialização) executá-lo. Para usar .init
/ em .fini
vez disso, a maneira mais fácil é usar sinalizadores de vinculador: -init & -fini (ou seja, na linha de comando do GCC, a sintaxe seria -Wl -init my_init -fini my_fini
).
No sistema que suporta os dois métodos, um benefício possível é que o código in .init
é executado antes .ctors
e o código .fini
depois .dtors
. Se a ordem for relevante, é pelo menos uma maneira simples, porém fácil de distinguir entre as funções de inicialização / saída.
Uma grande desvantagem é que você não pode facilmente ter mais de uma _init
e uma _fini
função por cada módulo carregável e provavelmente teria que fragmentar o código em mais do .so
que motivado. Outra é que, ao usar o método vinculador descrito acima, um substitui as _fini
funções _init e padrão originais (fornecidas por crti.o
). É aqui que geralmente ocorrem todos os tipos de inicialização (no Linux é onde a atribuição global de variáveis é inicializada). Uma maneira de contornar isso é descrita aqui
Observe no link acima que uma cascata no original _init()
não é necessária, pois ainda está em vigor. ocall
entanto, na montagem embutida é x86-mnemônico e chamar uma função de montagem pareceria completamente diferente para muitas outras arquiteturas (como o ARM, por exemplo). Ou seja, o código não é transparente.
.init
/ .fini
e .ctors
/ .detors
mecanismos são semelhantes, mas não exatamente. Código em .init
/ .fini
executado "como está". Ou seja, você pode ter várias funções em .init
/ .fini
, mas é AFAIK sintaticamente difícil colocá-las totalmente transparentes em C puro, sem quebrar o código em muitos .so
arquivos pequenos .
.ctors
/ .dtors
são organizados de forma diferente de .init
/ .fini
. .ctors
/.dtors
seções são apenas tabelas com ponteiros para funções, e o "chamador" é um loop fornecido pelo sistema que chama cada função indiretamente. Ou seja, o chamador de loop pode ser específico da arquitetura, mas como faz parte do sistema (se é que existe), isso não importa.
O fragmento a seguir adiciona novos ponteiros de .ctors
função à matriz de funções, principalmente da mesma maneira que __attribute__((constructor))
faz (o método pode coexistir __attribute__((constructor)))
.
#define SECTION( S ) __attribute__ ((section ( S )))
void test(void) {
printf("Hello\n");
}
void (*funcptr)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;
Pode-se também adicionar os ponteiros de função a uma seção auto-inventada completamente diferente. Nesse caso, é necessário um script vinculador modificado e uma função adicional que imite o carregador .ctors
/ .dtors
loop. Mas, com ele, é possível obter um melhor controle sobre a ordem de execução, adicionar argumentos e retornar código eta (em um projeto C ++, por exemplo, seria útil se você precisasse de algo em execução antes ou depois dos construtores globais).
Eu preferiria sempre __attribute__((constructor))/((destructor))
que possível, é uma solução simples e elegante, mesmo que pareça trapaça. Para codificadores bare-metal como eu, isso nem sempre é uma opção.
Alguma boa referência no livro Linkers & loaders .
#define __attribute__(x)
). Se você tiver vários atributos, por exemplo,__attribute__((noreturn, weak))
seria difícil "macro out" se houvesse apenas um conjunto de colchetes.