__attribute __ ((constructor))는 정확히 어떻게 작동합니까?


348

그것이 설정되어야한다는 것이 분명해 보입니다.

  1. 정확히 언제 실행됩니까?
  2. 왜 두 개의 괄호가 있습니까?
  3. 인가 __attribute__하는 기능은? 매크로? 통사론?
  4. 이것은 C에서 작동합니까? C ++?
  5. 작동하는 함수는 정적이어야합니까?
  6. 언제 __attribute__((destructor))실행됩니까?

Objective-C의 예 :

__attribute__((constructor))
static void initialize_navigationBarImages() {
  navigationBarImages = [[NSMutableDictionary alloc] init];
}

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}

답변:


273
  1. 공유 라이브러리가로드 될 때 (일반적으로 프로그램 시작 중) 실행됩니다.
  2. 이것이 모든 GCC 속성의 방식입니다. 아마도 함수 호출과 구별 할 수 있습니다.
  3. GCC 특정 구문.
  4. 예, 이것은 C 및 C ++에서 작동합니다.
  5. 아니요, 함수는 정적 일 필요는 없습니다.
  6. 소멸자는 공유 라이브러리가 언로드 될 때 (일반적으로 프로그램 종료시) 실행됩니다.

따라서 생성자와 소멸자가 작동하는 방식은 공유 객체 파일에 각각 생성자와 소멸자 속성으로 표시된 함수에 대한 참조를 포함하는 특수 섹션 (ELF의 .ctors 및 .dtor)이 포함되어 있다는 것입니다. 라이브러리가로드 / 언로드 될 때 동적 로더 프로그램 (ld.so 또는 somesuch)은 이러한 섹션이 존재하는지 확인하고 존재하는 경우 참조 된 함수를 호출합니다.

생각해보십시오. 일반 정적 링커에는 비슷한 마법이있을 수 있으므로 사용자가 정적 또는 동적 링크를 선택하는지 여부에 관계없이 시작 / 종료시 동일한 코드가 실행됩니다.


49
이중 괄호를 사용하면 쉽게 "매크로 아웃"( #define __attribute__(x)) 할 수 있습니다. 예를 들어, 속성이 여러 개인 __attribute__((noreturn, weak))경우 괄호 집합이 하나만 있으면 "매크로 아웃"하기가 어렵습니다.
Chris Jester-Young

7
로 끝나지 않았습니다 .init/.fini. (하나의 번역 단위에 여러 생성자와 소멸자를 유효하게 가질 수 있으며 단일 라이브러리에 여러 개를 신경 쓰지 마십시오-어떻게 작동합니까?) 대신 ELF 이진 형식 (Linux 등)을 사용하는 플랫폼에서는 생성자와 소멸자가 참조됩니다 헤더 의 .ctors.dtors섹션에서 사실, 옛날에, 함수의 이름 initfini그들이 존재하는 경우 동적 라이브러리로드 및 언로드에서 실행 될 수 있지만이 더 나은 메커니즘으로 대체, 지금은 사용되지 않는 것.
ephemient

7
@jcayzac 아니요, variadic 매크로는 gcc 확장이므로 매크로를 사용하는 주된 이유 __attribute__는 gcc를 사용하지 않는 경우에도 gcc 확장이기 때문입니다.
Chris Jester-Young 5

9
@ ChrisJester-Young variadic 매크로는 GNU 확장이 아닌 표준 C99 기능입니다.
jcayzac

4
"현재 시제 사용 ("만들기 "대신에"만들기 ") – 이중 괄호는 여전히 거칠게 만들 수 있습니다. 당신은 잘못된 나무를 짖었습니다.
Jim Balter

64

.init/ .fini더 이상 사용되지 않습니다. 그것은 여전히 ​​ELF 표준의 일부이며 나는 그것이 영원히 될 것이라고 감히 말할 것입니다. 코드 입력 .init/ .fini로드는 코드가로드 / 언로드 될 때 로더 / 런타임 링커에 의해 실행됩니다. 즉, 각 ELF로드 (예 : 공유 라이브러리) 코드 .init가 실행됩니다. 와 같은 것을 달성하기 위해 해당 메커니즘을 사용하는 것이 여전히 가능합니다 __attribute__((constructor))/((destructor)). 구식이지만 몇 가지 이점이 있습니다.

.ctors.dtors예를 들어 / 메커니즘은 system-rtl / loader / linker-script의 지원이 필요합니다. 이것은 모든 시스템에서 사용할 수있는 것과는 거리가 멀다 (예 : 베어 메탈에서 코드가 실행되는 깊이 포함 된 시스템). 즉 __attribute__((constructor))/((destructor)), GCC가 지원 하더라도 이를 구성하는 링커와이를 실행하는 로더 (또는 경우에 따라 부트 코드)에 따라 실행 되는지 확실하지 않습니다. 대신 .init/ 를 사용하려면 .fini가장 쉬운 방법은 링커 플래그를 사용하는 것입니다 : -init & -fini (예 : GCC 명령 줄에서 구문은 -Wl -init my_init -fini my_fini).

두 가지 방법을 모두 지원하는 시스템에서 한 가지 가능한 이점은 코드 인 .init이 전에 실행 .ctors되고 코드 인이에 따른다는 .fini.dtors입니다. 주문이 관련이 있다면 적어도 하나의 조잡하지만 초기화 / 종료 기능을 쉽게 구별 할 수있는 방법입니다.

주요 단점은 각로드 가능한 모듈 당 _init하나 이상의 _fini함수를 쉽게 가질 수 없으며 .so동기 부여 된 것 이상으로 코드를 조각화해야한다는 것입니다 . 다른 하나는 위에서 설명한 링커 방법을 사용할 때 원래 _init 및 _fini기본 함수 (로 제공 crti.o)를 대체한다는 것 입니다. 이것은 모든 종류의 초기화가 일반적으로 발생하는 곳입니다 (Linux에서는 전역 변수 할당이 초기화됩니다). 그 주위에 방법이 여기 에 설명되어 있습니다

위의 링크에서 원본에 대한 계단식 배열 _init()은 여전히 ​​필요하므로 필요하지 않습니다. call조립 그러나 인라인에서는 86-연상 기호 및 조립 (예를 들어, ARM 등) 많은 다른 아키텍처에 완전히 다를 것에서 함수를 호출합니다. 즉 코드가 투명하지 않습니다.

.init/ .fini.ctors/ .detors메카니즘은 비슷하지만 꽤 아닙니다. 에서 코드 .init/ .fini실행은 "있는 그대로". 즉 , .init/에 여러 함수를 가질 수 .fini있지만 AFAIK는 많은 작은 .so파일의 코드를 손상시키지 않고 순수한 C에서 완전히 투명하게 배치하는 것이 구문 적으로 어렵습니다 .

.ctors/ .dtors다른 것보다 구성되어 있습니다 .init/ .fini. .ctors/ .dtors섹션은 함수에 대한 포인터가있는 표일 뿐이며 "호출자"는 각 함수를 간접적으로 호출하는 시스템 제공 루프입니다. 즉, 루프 호출자는 아키텍처에 따라 다를 수 있지만 시스템의 일부이므로 (즉, 존재하는 경우) 중요하지 않습니다.

다음 코드는 새로운 함수 포인터 추가 .ctors기능 어레이로서 주로 동일한 방식 __attribute__((constructor))(방법과 공존을 않는다 __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;

완전히 다른 자체 발명 된 섹션에 함수 포인터를 추가 할 수도 있습니다. 이 경우 수정 된 링커 스크립트와 로더 .ctors/ .dtors루프를 모방 한 추가 기능 이 필요합니다. 그러나 그것으로 실행 순서를 더 잘 제어하고 인수 내 추가 및 코드 처리 에타를 추가 할 수 있습니다 (예를 들어 C ++ 프로젝트에서는 전역 생성자 전후에 무언가가 필요한 경우 유용합니다).

나는 __attribute__((constructor))/((destructor))가능한 곳을 선호합니다 . 속임수처럼 느껴지는 간단하고 우아한 솔루션입니다. 나와 같은 베어 메탈 코더의 경우 항상 옵션이 아닙니다.

링커 & 로더 책에서 좋은 참고 자료 .


로더는 이러한 함수를 어떻게 호출 할 수 있습니까? 이러한 함수는 프로세스 주소 공간에서 전역 및 기타 기능을 사용할 수 있지만 로더는 자체 주소 공간이있는 프로세스입니까?
user2162550

@ user2162550 아니오, ld-linux.so.2 (모든 동적으로 링크 된 실행 파일에서 실행되는 동적 라이브러리의 로더 인 일반적인 "인터프리터")는 실행 파일 자체의 주소 공간에서 실행됩니다. 일반적으로 동적 라이브러리 로더 자체는 라이브러리 자원에 액세스하려고하는 스레드 컨텍스트에서 실행되는 사용자 공간에 특정한 것입니다.
Paul Stelian

__attribute__((constructor))/((destructor))소멸자가 있는 코드에서 execv ()를 호출하면 실행되지 않습니다. 위와 같이 .dtor에 항목을 추가하는 것과 같은 몇 가지 시도를했습니다. 그러나 성공하지 못했습니다. numactl로 코드를 실행하면 문제를 쉽게 복제 할 수 있습니다. 예를 들어, test_code에 소멸자가 포함되어 있다고 가정하십시오 (생성자에 printf를 추가하고 문제점을 디버그하기 위해 desctructor 함수). 그런 다음를 실행하십시오 LD_PRELOAD=./test_code numactl -N 0 sleep 1. 생성자는 두 번 호출되지만 소멸자는 한 번만 호출됩니다.
B Abali

39

이 페이지는 constructordestructor속성 구현과 ELF 내에서 작동 할 수있는 섹션 에 대한 이해를 제공합니다 . 여기에 제공된 정보를 요약 한 후 약간의 추가 정보를 컴파일하고 개념을 설명하고 학습에 도움이되는 예제를 작성했습니다 (위의 Michael Ambrus의 섹션 예제를 차용). 이러한 결과는 예제 소스와 함께 아래에 제공됩니다.

이 스레드에서 설명한대로 constructordestructor속성 은 객체 파일 의 .ctors.dtors섹션에 항목을 만듭니다 . 세 가지 방법 중 하나로 두 섹션 중 하나에 함수에 대한 참조를 배치 할 수 있습니다. (1) section속성 중 하나를 사용하는 것; (2) constructordestructor속성 또는 (3) 인라인 어셈블리 호출 (Ambrus의 답변 링크 참조)

의 사용 constructordestructor속성은 추가로 이전에 실행의 순서 제어하기 위해 생성자 / 소멸자에 우선 순위를 할당 할 수 main()호출하거나 반환 후를. 주어진 우선 순위 값이 낮을수록 실행 우선 순위가 높습니다 (main () 이전의 우선 순위가 높을수록 우선 순위가 낮고 main () 이후의 우선 순위가 높음). 컴파일러가 구현을 위해 0-100 사이의 우선 순위 값을 예약 할 때 제공하는 우선 순위 값 보다 커야합니다100 . constructor또는 destructor사전이 우선 실행하는 지정된 constructor또는 destructor우선 순위없이 지정.

'section'속성 또는 인라인 어셈블리를 사용하면 생성자 및 소멸자 각각에서 실행될 함수 참조를 .init.finiELF 코드 섹션 에 배치 할 수도 있습니다. .init섹션에 배치 된 함수 참조에 의해 호출 된 모든 함수는 함수 참조 보다 먼저 실행됩니다 (통상).

아래 예제에서 각각을 설명하려고 노력했습니다.

#include <stdio.h>
#include <stdlib.h>

/*  test function utilizing attribute 'section' ".ctors"/".dtors"
    to create constuctors/destructors without assigned priority.
    (provided by Michael Ambrus in earlier answer)
*/

#define SECTION( S ) __attribute__ ((section ( S )))

void test (void) {
printf("\n\ttest() utilizing -- (.section .ctors/.dtors) w/o priority\n");
}

void (*funcptr1)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;

/*  functions constructX, destructX use attributes 'constructor' and
    'destructor' to create prioritized entries in the .ctors, .dtors
    ELF sections, respectively.

    NOTE: priorities 0-100 are reserved
*/
void construct1 () __attribute__ ((constructor (101)));
void construct2 () __attribute__ ((constructor (102)));
void destruct1 () __attribute__ ((destructor (101)));
void destruct2 () __attribute__ ((destructor (102)));

/*  init_some_function() - called by elf_init()
*/
int init_some_function () {
    printf ("\n  init_some_function() called by elf_init()\n");
    return 1;
}

/*  elf_init uses inline-assembly to place itself in the ELF .init section.
*/
int elf_init (void)
{
    __asm__ (".section .init \n call elf_init \n .section .text\n");

    if(!init_some_function ())
    {
        exit (1);
    }

    printf ("\n    elf_init() -- (.section .init)\n");

    return 1;
}

/*
    function definitions for constructX and destructX
*/
void construct1 () {
    printf ("\n      construct1() constructor -- (.section .ctors) priority 101\n");
}

void construct2 () {
    printf ("\n      construct2() constructor -- (.section .ctors) priority 102\n");
}

void destruct1 () {
    printf ("\n      destruct1() destructor -- (.section .dtors) priority 101\n\n");
}

void destruct2 () {
    printf ("\n      destruct2() destructor -- (.section .dtors) priority 102\n");
}

/* main makes no function call to any of the functions declared above
*/
int
main (int argc, char *argv[]) {

    printf ("\n\t  [ main body of program ]\n");

    return 0;
}

산출:

init_some_function() called by elf_init()

    elf_init() -- (.section .init)

    construct1() constructor -- (.section .ctors) priority 101

    construct2() constructor -- (.section .ctors) priority 102

        test() utilizing -- (.section .ctors/.dtors) w/o priority

        test() utilizing -- (.section .ctors/.dtors) w/o priority

        [ main body of program ]

        test() utilizing -- (.section .ctors/.dtors) w/o priority

    destruct2() destructor -- (.section .dtors) priority 102

    destruct1() destructor -- (.section .dtors) priority 101

이 예제는 생성자 / 소멸자 동작을 구체화하는 데 도움이되었으므로 다른 사람들에게도 유용 할 것입니다.


"제공하는 우선 순위 값이 100보다 커야합니다"는 어디에서 발견 되었습니까? 이 정보는 GCC 기능 속성 문서에 없습니다.
저스틴

4
IIRC에는 PATCH : 생성자 / 소멸자 인수에 대한 지원 우선 순위 인수 ( MAX_RESERVED_INIT_PRIORITY)가 있으며 C ++ ( init_priority) 7.7 C ++ 특정 변수, 함수 및 유형 속성과 동일하다는 참조가있었습니다 . 그 때 나는 그것을 시도 99: warning: constructor priorities from 0 to 100 are reserved for the implementation [enabled by default] void construct0 () __attribute__ ((constructor (99)));.
David C. Rankin

1
아 clang으로 우선 순위 <100을 시도했지만 작동하는 것처럼 보였지만 간단한 테스트 사례 (단일 컴파일 단위) 가 너무 간단했습니다 .
저스틴

1
정적 전역 변수 (정적 ctor)의 우선 순위는 무엇입니까?
dashesy

2
정적 전역 의 효과와 가시성은 프로그램이 어떻게 구성되어 있는지 (예 : 단일 파일, 여러 파일 ( 번역 단위 )) 전역이 선언되는 방식에 따라 달라집니다 . 정적 (키워드) , 특히 정적 전역 변수 설명을 참조하십시오.
David C. Rankin

7

다음은 이러한 편리하지만 보기 흉한 구성 을 사용 하는 방법, 이유 및시기 에 대한 "콘크리트"(및 가능한 유용한 ) 예입니다 .

엑스 코드는 결정하기 위해 "글로벌" "사용자 기본"를 사용하는 XCTestObserver클래스 에서 결선에서 그것의 마음을 받는 포위 콘솔.

이 예제에서 ...이 psuedo-library를 내재적으로로드 할 때 libdemure.a테스트 대상의 플래그 la를 통해 호출합니다 .

OTHER_LDFLAGS = -ldemure

나는 ..

  1. 로드시 (예 : XCTest테스트 번들을로드 할 때 ) "기본" XCTest"관찰자"클래스를 재정의합니다 ( constructor함수 를 통해 ). PS : 내가 알 수있는 한 여기에서 수행 한 모든 작업은 클래스의 + (void) load { ... }방법.

  2. 이 경우 내 테스트를 실행하십시오 ....이 경우 로그에서 덜 자세한 표시 (요청시 구현)

  3. "글로벌" XCTestObserver클래스를 원래 상태로 되돌 XCTest립니다. 밴드 밴드에서 얻지 못한 다른 런 (즉,에 링크 됨 libdemure.a)을 없애지 않습니다 . 나는 이것이 역사적으로 dealloc.. 에서 이루어 졌다고 생각한다 . 그러나 나는 그 오래된 망치로 엉망을 시작하지 않을 것이다.

그래서...

#define USER_DEFS NSUserDefaults.standardUserDefaults

@interface      DemureTestObserver : XCTestObserver @end
@implementation DemureTestObserver

__attribute__((constructor)) static void hijack_observer() {

/*! here I totally hijack the default logging, but you CAN
    use multiple observers, just CSV them, 
    i.e. "@"DemureTestObserverm,XCTestLog"
*/
  [USER_DEFS setObject:@"DemureTestObserver" 
                forKey:@"XCTestObserverClass"];
  [USER_DEFS synchronize];
}

__attribute__((destructor)) static void reset_observer()  {

  // Clean up, and it's as if we had never been here.
  [USER_DEFS setObject:@"XCTestLog" 
                forKey:@"XCTestObserverClass"];
  [USER_DEFS synchronize];
}

...
@end

링커 플래그가 없으면 ... (패션 - 경찰 떼 쿠퍼 티노는 보복을 요구 , 아직 애플의 기본 우선, 원하는대로, 여기 )

여기에 이미지 설명을 입력하십시오

함께 -ldemure.a링커 플래그 (이해 결과, 헐떡 거림 ... "감사 constructor/ destructor"... 군중의 환호 ) 여기에 이미지 설명을 입력하십시오


1

다음은 또 다른 구체적인 예입니다. 공유 라이브러리 용입니다. 공유 라이브러리의 주요 기능은 스마트 카드 리더와 통신하는 것입니다. 그러나 udp를 통해 런타임에 '구성 정보'를 수신 할 수도 있습니다. udp는 반드시 초기화시에 시작 해야하는 스레드에 의해 처리됩니다 .

__attribute__((constructor))  static void startUdpReceiveThread (void) {
    pthread_create( &tid_udpthread, NULL, __feigh_udp_receive_loop, NULL );
    return;

  }

도서관은 c로 작성되었습니다.


1
일반적인 전역 변수 생성자가 C ++에서 코드를 미리 실행하는 관용적 방법이므로 라이브러리가 C ++로 작성된 경우 이상한 선택입니다.
Nicholas Wilson

@NicholasWilson이 라이브러리는 실제로 c. c 대신 c ++를 어떻게 입력했는지 모릅니다.
drlolly
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.