지난 기사에서 .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 부터 수행되겠다!!, 에~~ 그런것이었구만. |
댓글
첨언하자면
링커는 crt0.o (스타트업 코드) 와 main 함수를 정의한 사용자가
만든 object 파일을 묶어서 실행파일을 만듭니다.
스타트업 코드는 플랫폼마다 이름이 약간식 틀린데 대부분 crt0.o 을 공통적
(window는 아마 틀리겠죠) 사용합니다.
이 스타트업 코드가 하는 일이 해당 플랫폼에 맞게 정해져 있습니다.
언어에 따라 또는 컴파일러 제작자에 따라 이 스타트업 코드다 다르게 제공되고요.
언어의 레벨에 따라 해주는 일이 약간씩 틀리고 언어가 지원하는 사양에
따라 기본적으로 해줘야 하는 일이 틀리기 때문입니다.
아름다운 후킹에
아름다운 후킹에 도움을 주셔서 감사합니다.
----------------------------------------------
언제나 맑고픈 샘이가...
http://yubink.com - 강아지 필요하세요?
http://purewell.biz - 헙!!
_____________________________
언제나 맑고픈 샘이가...
http://purewell.biz
도움이 되었다니
도움이 되었다니 감사할 따름이죠.. purewell 님의 아이콘이 이젠 안보이는군요.. ;)
---
http://coolengineer.com
댓글 달기