티스토리 뷰

지난 기사에서 .ctor 섹션에 있는 코드들이 어떻게 실행되는지에 대한 호기심만 자극한 채 벌써 일주일이 지났다. 오래 참으시었다.

지난번의 코드를 다시 인용해 보자면,
$ 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!



그리고 그 때 만들었던 심볼 덤프는 다음과 같다.

$ more c1.nm
w _Jv_RegisterClasses
w __deregister_frame_info_bases
w __gmon_start__
U __libc_start_main@@GLIBC_2.0
w __register_frame_info_bases
U printf@@GLIBC_2.0
08048230 T _init
08048280 T _start
080482a4 t call_gmon_start
080482a4 t gcc2_compiled.
080482d0 t __do_global_dtors_aux
08048330 t frame_dummy
08048394 T before_main
080483a8 T before_main_2nd
080483bc T after_main
080483d0 T main
08048400 t __do_global_ctors_aux
08048430 T _fini
08048430 t gcc2_compiled.
08048460 R _fp_hw
08048464 R _IO_stdin_used
08049520 D __data_start
08049520 W data_start
08049524 d __dso_handle
08049528 d p.0
0804952c r __EH_FRAME_BEGIN__
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


저 코드 중에서 함수들만 추려서 gdb의 브레이크 포인트 구문으로 만들어 보자.

$ cat c1.nm | grep -i " t " | grep -v gcc2 | awk '{print "b", $NF}'
b _init
b _start
b call_gmon_start
b __do_global_dtors_aux
b frame_dummy
b before_main
b before_main_2nd
b after_main
b main
b __do_global_ctors_aux
b _fini


그리고, 클립보드에 복사한 뒤 다음 명령을 수행한다.

$ gdb c1
GNU gdb 6.1.1
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) b _init
Breakpoint 1 at 0x8048236
(gdb) b _start
Breakpoint 2 at 0x8048280
(gdb) b call_gmon_start
Breakpoint 3 at 0x80482b5
(gdb) b __do_global_dtors_aux
.
.
.
(gdb) b _fini
Breakpoint 11 at 0x8048441
(gdb) r
Starting program: /home/pynoos/test/crt0/c1
Breakpoint 11 at 0x4013cab0: file soinit.c, line 66.

Breakpoint 2, 0x08048280 in _start ()
(gdb) continue
Continuing.

Breakpoint 1, 0x08048236 in _init ()
(gdb) continue
Continuing.
(이하 continue 명령으로 계속...)
Breakpoint 3, 0x080482b5 in call_gmon_start ()
Breakpoint 5, 0x08048330 in frame_dummy ()
Breakpoint 10, 0x08048404 in __do_global_ctors_aux ()
Breakpoint 7, 0x080483ae in before_main_2nd ()

Bombadil, where have you been in the morning?

Breakpoint 6, 0x0804839a in before_main ()

I miss you Lorthlorien ever beauty.

Breakpoint 9, 0x080483d6 in main ()

I am working, no touch!

Breakpoint 4, 0x080482d6 in __do_global_dtors_aux ()
Breakpoint 8, 0x080483c2 in after_main ()

Mithlandir, help me!

Breakpoint 11, _fini () at soinit.c:66

(gdb) c
Continuing.

Program exited normally.
(gdb)


약간 호출 순서대로 살펴보면

_start ()
_init ()
call_gmon_start ()
frame_dummy ()
__do_global_ctors_aux ()
before_main_2nd ()
before_main ()
main ()
__do_global_dtors_aux ()
after_main ()
_fini ()


main 앞에 실행되는 것들이 상당히 많다. 실험용으로 제작한 함수를 제외하면, _start, _init, call_gmon_start, frame_dummy, __do_global_ctors_aux 가 실행된다.
그리고 대칭으로 main 다음에 __do_global_dtors_aux 가 먼저 실행되고, _fini를 끝으로 실행한다.

예상컨데, __do_global_ctors_aux가 실험용으로 제작한 main 함수 이전 함수들을, __do_global_dtors_aux가 실험용으로 제작한 main 함수 이후 함수들을 실행하는 것임을 알 수 있다.

이 놈들이 어디서 흘러왔는지 알아보자. gcc에 -v 옵션을 주어 link에 참여하는 모든 파일들을 나열한 다음 절대 경로로 주어지는 파일들만 골라서 nm 으로 속내를 들여다 보자.

$ gcc -v -o c3 c.c 2>&1 | xargs -n1 echo | grep "^/" | xargs nm -A 2>/dev/null | grep __do_global_ctors_aux
No -c option found for c.c
/usr/local/bin/cc1:083c2c70 t __do_global_ctors_aux
/usr/local/bin/as:08123410 t __do_global_ctors_aux
/usr/local/bin/collect2:080a48f0 t __do_global_ctors_aux
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o:00000000 t __do_global_ctors_aux


아싸.. crtend.o 에서 발견되는 놈이 정답인거 같다. 나머지는 모두 빌드에 필요한 실행파일들이니까.

자세히 보니 gcc와 같이 딸려오는 라이브러리에 있는것 같다.
그럼 gcc 소스를 봐야하는군.

$ grep -lr __do_global_ctors_aux /usr/src/gcc-2.95.3
/usr/src/gcc-2.95.3/gcc/FSFChangeLog
/usr/src/gcc-2.95.3/gcc/FSFChangeLog.11
/usr/src/gcc-2.95.3/gcc/crtstuff.c
/usr/src/gcc-2.95.3/gcc/config/alpha/crtend.asm
/usr/src/gcc-2.95.3/gcc/tags


대층 보니까 crtstuff.c 에 있는 것 같다. 그 파일에서 인용하자면,

328 #ifdef CTOR_LIST_BEGIN
329 CTOR_LIST_BEGIN;
330 #else
331 asm (CTORS_SECTION_ASM_OP); /* cc1 doesn't know that we are switching! */
332 STATIC func_ptr __CTOR_LIST__[1] __attribute__ ((__unused__))
333 = { (func_ptr) (-1) };
334 #endif
335
336 #ifdef DTOR_LIST_BEGIN
337 DTOR_LIST_BEGIN;
338 #else
339 asm (DTORS_SECTION_ASM_OP); /* cc1 doesn't know that we are switching! */
340 STATIC func_ptr __DTOR_LIST__[1] = { (func_ptr) (-1) };
341 #endif
342
343 #ifdef EH_FRAME_SECTION_ASM_OP
344 /* Stick a label at the beginning of the frame unwind info so we can register
345 and deregister it with the exception handling library code. */
346
347 asm (EH_FRAME_SECTION_ASM_OP);
348 #ifdef INIT_SECTION_ASM_OP
349 STATIC
350 #endif
351 char __EH_FRAME_BEGIN__[] = { };
352 #endif /* EH_FRAME_SECTION_ASM_OP */
353
354 #endif /* defined(CRT_BEGIN) */
355
356 #ifdef CRT_END
357
358 #ifdef INIT_SECTION_ASM_OP
359
360 #ifdef OBJECT_FORMAT_ELF
361
362 static func_ptr __CTOR_END__[];
363 static void
364 __do_global_ctors_aux (void)
365 {
366 func_ptr *p;
367 for (p = __CTOR_END__ - 1; *p != (func_ptr) -1; p--)
368 (*p) ();
369 }
471 #ifdef CTOR_LIST_END
472 CTOR_LIST_END;
473 #else
474 asm (CTORS_SECTION_ASM_OP); /* cc1 doesn't know that we are switching! */
475 STATIC func_ptr __CTOR_END__[1] = { (func_ptr) 0 };
476 #endif



332, 333을 보면 __CTOR_LIST__ 가 가리키는 첫번째에는 -1 값이 들어가고, 그리고 362의 __CTOR_END__는 선언이며, 실제 정의는 475에 존재한다. 이들은 모두 앞부분에 있는 asm (CTORS_SECTION_ASM_OP); 에 의해 .ctor 섹션에 존재하도록 지시된다. Makefile을 잘보니 이 파일이 crtbegin.o와 crtend.o 를 만드는데 사용되며, 각각을 만들 때, __CTOR_LIST__와 __CTOR_END__가 두 파일에 나뉘어 들어가게 되어 있다.

전통적으로 링크할 때는 인자에 넘겨지는 순서대로 심볼들이 배치되므로 그 중간에 .ctor 섹션에 뭔가를 넣고 링커를 호출하면 __CTOR__LIST__에서 출발하여 __CTOR_END__에 이르는 배열을 만들 수 있게된다.

실제로 gcc -v 옵션을 넣어 확인해보면,

/usr/lib/gcc-lib/i386-redhat-linux/2.96/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o c3 /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crt1.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crti.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/2.96 -L/usr/lib/gcc-lib/i386-redhat-linux/2.96/../../.. /tmp/ccfOmTjx.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o /usr/lib/gcc-lib/i386-redhat-linux/2.96/../../../crtn.o


이렇게 되어 있다. 따라서 .ctor 섹션은 하나의 배열을 만들게 되며,
__do_global_ctors_aux 함수를 살펴보면
__CTOR_END__가 가리키는 윗번지에서부터, -1을 만나기 전까지 거꾸로 차례차례 함수를 호출하도록 되어 있다.

그러니 before_main_2nd 부터 수행되겠다!!, 에~~ 그런것이었구만.


반응형
댓글
댓글쓰기 폼