C에서 main 보다 먼저 호출되는 함수 만들기
아래 글은 http://writely.com/ 에서 작성하여 http://kldp.org/xmlrpc.php 를 통하여 포스팅한 글입니다. Full HTML로 속성을 수정했습니다. 테스트 겸 중복 포스팅이네요..
원문은 http://coolengineer.com/135 입니다.
C++ 언어는 그 특성상, 전역 개체의 초기화가 main 보다 먼저 이루어지므로 전역 개체의 생성자에 들어 있는 코드는 main 보다 먼저 호출된다.
이것은 여러가지 트릭으로 사용될 수 있는데, C에는 과연 그런 것이 없을까? 표준 명세에는 없다.
하지만, gcc의 __attribute__에는 그러한 일을 가능하게 해주는 지시자가 있는데, 바로 다음과 같이 사용된다.
void __attribute__((constructor)) before_main( void )
{
/* Things to do before main function */
}
또한 main 뒤에 호출되는 전형적인 방식은 atexit에 등록하는 것인데, 이것또한
void __attribute__((destructor)) after_main( void )
{
/* Things to do after main function */
}
와 같은 방식으로 호출된다.
간단한 샘플을 돌려보자면,
$ more c.c
#ifdef STRIP_ATTR
#define __attribute__(x)
#endif
void __attribute__((constructor)) before_main( void )
{
printf("I miss you Lorthlorien ever beauty.\n");
}
void __attribute__((constructor)) before_main_2nd( void )
{
printf("Bombadil, where have you been in the morning?\n");
}
void __attribute__((destructor)) after_main( void )
{
printf("Mithlandir, help me!\n");
}
int main()
{
printf("I am working, no touch!\n");
return 0;
}
$ gcc -o c1 -save-temps c.c
$ ./c1
Bombadil, where have you been in the morning?
I miss you Lorthlorien ever beauty.
I am working, no touch!
Mithlandir, help me!
근사하지 않은가? 다만 의심가는 것은 __attribute((constructor))로 지정한 함수들이 두 개 이상일때의 순서는 어떠하냐는 것인데, 외관상 stack형식으로 먼저 발견되는 것이 나중에 실행되는 것 같다.
자세한 것은 추후에 설명하지도 모르겠다.
이것이 수행되는 원리를 좀더 파헤쳐보자면,
우선 위 코드상에 STRIP_ATTR이라고 되어 있는 부분이 수행되도록하면 평범한 함수가 되는데, 그렇게 생성된 바이너리(c2)의 심볼들을 살펴보자.
nm 은 -n 옵션을 주어 번지로 정렬되도록 하였다.
두 nm 결과를 비교하는데는 unified diff를 사용하여 보자.
그리고 -save-temps 를 통해 생성되는 c.s 라는 어셈블리어 파일은 각각 c1.s c2.s로 이름을 바꾸는 과정이 들어가 있다.
$ mv c.s c1.s
$ gcc -o c1 c.c
$ gcc -o c2 -DSTRIP_ATTR -save-temps c.c
$ mv c.s c2.s
$ nm -n c1 > c1.nm
$ nm -n c2 > c2.nm
$ diff -u c1.nm c2.nm
--- c1.nm Thu Jan 5 13:29:16 2006
+++ c2.nm Thu Jan 5 13:29:20 2006
@@ -27,14 +27,14 @@
0804952c r __FRAME_END__
08049530 D _DYNAMIC
080495f8 d __CTOR_LIST__
-08049604 d __CTOR_END__
-08049608 d __DTOR_LIST__
-08049610 d __DTOR_END__
-08049614 d __JCR_END__
-08049614 d __JCR_LIST__
-08049618 D _GLOBAL_OFFSET_TABLE_
-08049630 A __bss_start
-08049630 A _edata
-08049630 b completed.1
-08049634 b object.2
-0804964c A _end
+080495fc d __CTOR_END__
+08049600 d __DTOR_LIST__
+08049604 d __DTOR_END__
+08049608 d __JCR_END__
+08049608 d __JCR_LIST__
+0804960c D _GLOBAL_OFFSET_TABLE_
+08049624 A __bss_start
+08049624 A _edata
+08049624 b completed.1
+08049628 b object.2
+08049640 A _end
이것을 잘 보아하니 __CTOR_LIST__ 라는 값까지는 같고 그 뒤가 달라지는 것을 볼 수가 있다.
아! __CTOR_LIST__에 내용이 채워지면서 조금씩 그 뒤로 밀려나는구나!
그럼 이 둘의 어셈블코드의 차이는 어떠할까?
$ diff -u c1.s c2.s
--- c1.s Thu Jan 5 13:31:04 2006
+++ c2.s Thu Jan 5 13:31:13 2006
@@ -15,9 +15,6 @@
leave
ret
.size before_main, .-before_main
- .section .ctors,"aw",@progbits
- .align 4
- .long before_main
.section .rodata
.align 32
.LC1:
@@ -34,9 +31,6 @@
leave
ret
.size before_main_2nd, .-before_main_2nd
- .section .ctors
- .align 4
- .long before_main_2nd
.section .rodata
.LC2:
.string "Mithlandir, help me!\n"
@@ -52,9 +46,6 @@
leave
ret
.size after_main, .-after_main
- .section .dtors,"aw",@progbits
- .align 4
- .long after_main
.section .rodata
.LC3:
.string "I am working, no touch!\n"
아앗! 이것은!
단지 section .ctor, .dtor에 함수 포인터만 추가하는 일을 하는 것 아닌가.
그렇다면, .ctor를 찾아서 하나씩 호출하는 부분은 어디에 있다는 것이지? ((계속))
댓글
앗 신기하네요+_+
앗 신기하네요+_+ 다음편이 기대됩니다.
덤으로 어셈블코드를 이해 못해서,어셈블을 공부 할 필요성이 생기게되었습니다.
봄들판에서다
봄들판에서다
원문을 따라가면 이미 있습니다
생각도 않고 가져왔는데, 마지막에 ((계속))이 있었군요..
---
http://coolengineer.com
재미있기는
재미있기는 한데...
家和萬事成
좋은 글 잘 봤습니다. 그런데, 소스 판독성
좋은 글 잘 봤습니다.
그런데, 소스 판독성 유지보수관리성 측면에서 꼭 필요할 것 같진 않은데,
C언어에서 main() 함수 이전에 이렇게 꼭 처리해야 하는 것들이 있을까요?
있다면 아주 유용할 듯...
From:
*알지비 (메일: rgbi3307(at)nate.com)
*커널연구회(http://www.kernel.bz/) 내용물들을 만들고 있음.
*((공부해서 남을 주려면 남보다 더많이 연구해야함.))
main을 변경할 수 없을 때 유용하지 않을까요?
가령 (실질적인 예가 있을진 모르겠지만) main함수를 이미 컴파일된 상태로 공급받는다든지. main함수가 들어있는 소스 파일 자체를 어떤 이유로든 수정하고 싶지 않다든지 뭐 이런 경우들 말이죠.
(좀 더 생각해보니 이런 것들도 컨스트럭터 없이 해결 가능할것 같군요.)
이런 경우가 아니라면, 컨스트럭터의 경우에는 그것 자체만으로는 뚜렷한 용도는 많지 않을 듯 합니다. shared library를 로드하기 전에 실행된다고 하니 여기서 뭔가 더 잇점이 생길 수는 있을 것 같네요.
요컨대 "컨스트럭터 없으면 절대 안돼" 하는 상황은 별로 없어 보입니다. 쓰면 더 편한 경우는 있겠습니다만.
반면에, 디스트럭터의 경우에는 여러 가지 사정으로 인해 일반적인 방법으로는 보장받기 힘든 자원 반납 등을 안정적으로 처리할 수 있는 요긴한 수단이 될 것으로 보이고,
어쨌든 디스트럭터를 쓰기로 결정했다면, 거기서 해제하는 자원은 처음부터 컨스트럭터에서 할당받는 게 아무래도 일관성도 있고 관리하기도 편하겠지요.
--
댓글 달기