Post

생성자, 소멸자 low-level

소멸자

.dtors
1
2
080495f4 d \_\_DTOR\_END\_\_

소멸자로 프로그램이 종료되기 전에 여기에 명시되어 있는 주소(함수)가 호출된다. \_\_DTOR\_END\_\_의 위치는 nm을 사용해도 좋고 readelf나 objdump로 확인해도 좋다.

.fini_array

gcc 4.7 이상 버전은 .ctors .dtors를 사용하지 않는 대신 다음을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
[18] .init\_array       INIT\_ARRAY       0000000000600e10  00000e10
0000000000000008  0000000000000000  WA       0     0     8
[19] .fini\_array       FINI\_ARRAY       0000000000600e18  00000e18
0000000000000008  0000000000000000  WA       0     0     8

  

gdb-peda$ x/12x 0x600e18
0x600e18:       0x00000000004004a0
gdb-peda$ x/4i 0x4004a0
0x4004a0 <\_\_do\_global\_dtors\_aux>:

종료될 때 0x600e18위치에 있는 함수가 실행되므로, 저 위치의 값을 변경해주면 된다.

GCC __attribute__((constructor / destructor))

*nix에는 windows의 DllMain()에 대응되는 함수가 없다. 그러나 GCC의 constructor / destructor attribute로 비슷한 효과를 낼 수 있다. constructor attribute가 붙은 함수는 프로그램이 시작되면서나, shared library가 loading되면서 바로 호출된다. (생성자) destructor attribute가 붙은 함수는 프로그램이 종료되면서나, dlclose()되면서 바로 호출된다. (소멸자)

1
2
3
4
5
6
7
8
9
10
11
\_\_attribute\_\_((constructor))
void constructorPrint(){
printf("Ok, loaded\n\n");
}

  

void normalPrint(){
printf("normalPrint\n\n");
}

<.so가 로딩되면서 constructorPrint()가 바로 호출된다.>

__attribute__((destructor)) 호출 시퀀스 bt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
main에서 ret하면 \_\_libc\_start\_main으로 나가게 된다.
#0  0xb7e1f72e in \_\_libc\_start\_main (main=0x8048433 <main>, argc=1, argv=0xbffff184,
init=0x8048470 <\_\_libc\_csu\_init>, fini=0x80484d0 <\_\_libc\_csu\_fini>, rtld\_fini=0xb7feb210 <\_dl\_fini>,
stack\_end=0xbffff17c) at libc-start.c:289
#1  0x08048341 in \_start ()
\_\_libc\_start\_main에서
...
0xb7e1f735 <+229>:
call   0xb7e35c90 <\_\_GI\_exit>
-> ...
0xb7e35cac <+28>:
call   0xb7e35b70 <\_\_run\_exit\_handlers>
-> ...
0xb7e35c51 <+225>:
call   \*%edx (%edx = \_dl\_fini의 주소)
->
(gdb) disas
Dump of assembler code for function \_dl\_fini:
...
0xb7feb3fa <+490>:
mov    %edi,%ecx
0xb7feb3fc <+492>:
shr    $0x2,%eax
0xb7feb3ff <+495>:
test   %eax,%eax
0xb7feb401 <+497>:
je     0xb7feb41c <\_dl\_fini+524>
0xb7feb403 <+499>:
mov    %esi,-0x28(%ebp)
0xb7feb406 <+502>:
mov    %ecx,%edi
0xb7feb408 <+504>:
mov    %eax,%esi
0xb7feb40a <+506>:
lea    0x0(%esi),%esi
0xb7feb410 <+512>:
call   \*-0x4(%edi,%esi,4)        //destructor 호출
...
(gdb) info reg edi
edi            0x8049f08
134520584
(gdb) x/4x 0x8049f08
0x8049f08:
0x080483d0
0x0804841b
0x00000000
0x00000001
\* 08049f08 t \_\_do\_global\_dtors\_aux\_fini\_array\_entry
080483d0 t \_\_do\_global\_dtors\_aux
0804841b T des

아무튼, 실제로 소멸자 호출이 이루어지는건 \_dl\_fini이므로 뭔가 조정하고 싶다면 \_dl\_fini에서 어디를 참조하는지를 알아보면 된다.

_init(), _fini()

void \_init(), void \_fini()를 이용해 생성자, 소멸자를 정의할 수 있으나 요즘에는 안쓴다. 이를 사용하려면 컴파일 시 gcc에 -nostartfiles 옵션을 줘야 한다. 옵션을 안주고 그냥 컴파일 하려 하면 multiple definition 에러가 발생하는데, 이는 이미 _init과 _fini가 startfile에 정의되어 있기 때문이다. -nostartfiles 옵션을 주면 아예 _init과 _fini 둘 다 정의되지 않기 때문에 한쪽만 사용하는 것은 불가능 하고 둘 다 정의해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(gdb) disas
Dump of assembler code for function \_dl\_fini:
...
0xb7feb410 <+512>:
call   \*-0x4(%edi,%esi,4)        //destructor 호출
0xb7feb414 <+516>:
sub    $0x1,%esi
0xb7feb417 <+519>:
jne    0xb7feb410 <\_dl\_fini+512>
0xb7feb419 <+521>:
mov    -0x28(%ebp),%esi
0xb7feb41c <+524>:
mov    0x54(%esi),%edx
0xb7feb41f <+527>:
test   %edx,%edx
0xb7feb421 <+529>:
je     0xb7feb42a <\_dl\_fini+538>
0xb7feb423 <+531>:
mov    (%esi),%eax
0xb7feb425 <+533>:
add    0x4(%edx),%eax
0xb7feb428 <+536>:
call   \*%eax                      //\_fini() 호출
...

This post is licensed under CC BY 4.0 by the author.