Notice
Recent Posts
Recent Comments
Link
«   2026/04   »
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
Tags
more
Archives
Today
Total
관리 메뉴

Log4KJS

C 의 링크 과정 본문

C

C 의 링크 과정

IceMelon404 2022. 2. 13. 19:26

대략적인 컴파일 과정

 

C 에서 어떻게 링크가 이루어지는지 살펴보기 이전에 대략적인 컴파일 과정에 대해 살펴보도록 하겠습니다.

 

 

 

 

 

preprocessor (전처리기) 는 소스 파일 내의 # 로 시작하는 전처리 지시문들을 수행합니다.

즉, 헤더 파일을 읽어 소스 파일에 삽입하고, 매크로를 치환합니다.

 

compiler 는 이렇게 전처리된 .i 파일을 .s 어셈블리 파일로 컴파일합니다.

assembler 는 .s 어셈블리 파일을 기계어로 컴파일하여 relocatable object file 을 생성하는데,

리눅스에서 사용하는 relocatable object file 의 포맷을 ELF(Executable and Linkable Format) 이라고 부릅니다.

linker 는 여러 relocatable object file 들을 symbol resolution 과 relocation 의 과정을 거쳐 executable object file,

즉 실행 파일을 만듭니다.

 

ELF 파일의 구조

 

 

 

 

이 중에서 링크 과정에서 중요한 .symtab 영역과 .rel.text, .rel.data 영역만 살펴도록 하겠습니다.

 

.symtab

프로그램에서 정의되거나 참조된 함수, 전역 변수에 대한 정보를 가지고 있는 심볼 테이블입니다.

static 전역 변수, static 함수, static 로컬 변수 또한 심볼 테이블에 기록되지만,

링크 작업을 할 때는 무시됩니다. (디버그용)

 

.rel.text

.text 섹션에서 relocate 되어야 하는 외부 참조들의 위치를 기록합니다.

즉, 컴파일러가 함수, 전역 변수에 대한 정의를 발견하지 못했을 경우,

외부 참조로 가정하고 이 영역에 링크를 위한 정보를 기록합니다.

.rel.text 영역에 있는 각각의 엔트리는 심볼에 대한 참조를 가집니다

 

.rel.data

.rel.text 와 마찬가지이지만, .data 영역에서 relocate 되어야 하는 외부 참조들의 위치를 기록합니다.

 

Symbol Table

 

typedef struct {
	int name; /* String table offset */
    char type:4, /* Function or data (4 bits) */
    binding:4; /* Local or global (4 bits) */
    char reserved; /* Unused */
    short section; /* Section header index */
    long value; /* Section offset or absolute address */
    long size; /* Object size in bytes */
} Elf64_Symbol

 

 

심볼 테이블은 여러 엔트리로 구성되어 있고, 엔트리의 구조는 위와 같습니다.

name 필드는 string table 에서의 offset 으로 심볼의 이름을 가리킵니다.

type 은 해당 심볼이 함수인지 데이터인지 나타내는 필드이고,

binding 은 함수 또는 변수의 접근 범위가 로컬 (static) 인지 global 인지 나타냅니다.

section 과 value 는 심볼이 정의된 위치를 가리키는데,

section 은 section header 에서의 인덱스, 즉 해당 심볼이 어떤 섹션(.text 인지 .data 인지)에 정의되어 있는지,

value 에는 해당 섹션에서 심볼이 정의되어 있는 offset 을 기록합니다.

해당 relocatable obejct file 에 정의되어 있는 심볼이 아닌 외부 참조라면, section 영역에 UNDEF (undefined) 가 기록됩니다.

 

직접 심볼 테이블을 조회해보겠습니다.

 

 

extern int sum(int a, int b);
extern int global_a;

int global_local_1= 0; 
int global_local_2= 1;
int global_local_3= 2;

static int static_function() {
        return 1;
}

int main() {
        int a = global_a;
        int b = 4;
        int c = sum(a, b);
        int d = sum(b, c);
        int e = static_function();
}


int function() {
        return 1;
}

 

 

작성한 main.c 파일을

 

gcc -c main.c
readelf -a main.o

 

명령어를 통해 relocatable object file 로 만들고, symbol table 을 조회할 수 있습니다.

 

 

# Section index
# [1] .text
# [2] .rela.text
# [3] .data
# [4] .bss


Symbol table '.symtab' contains 18 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 0000000000000000    15 FUNC    LOCAL  DEFAULT    1 static_function
    10: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 global_local_1
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_local_2
    12: 0000000000000004     4 OBJECT  GLOBAL DEFAULT    3 global_local_3
    13: 000000000000000f    84 FUNC    GLOBAL DEFAULT    1 main
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND global_a
    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND sum
    17: 0000000000000063    15 FUNC    GLOBAL DEFAULT    1 function

 

 

Ndx -> Section index 입니다.

스태틱 함수인 static_function 은 Bind 가 LOCAL, Type 은 FUNC(function)

정의된 위치는 section: .text, offset: 0 즉 .text 영역의 시작 부분에 정의되어 있습니다.

반면, 글로벌 함수인 main, function 은 Bind 가 GLOBAL 이고, .text 영역에 각각 offset 이 f, 63인 것을 확인할 수 있습니다.

 

main.c 내에 정의되지 않은 sum 함수와 global_a 전역 변수는 외부 참조로 해석되어 Section index 에 UND 가 기록되었습니다.

 

objdump -d main.o

 

를 이용해 .text 영역을 확인해 보면,

 

 

0000000000000000 <static_function>:
   0:	f3 0f 1e fa          	endbr64 
   4:	55                   	push   %rbp
   5:	48 89 e5             	mov    %rsp,%rbp
   8:	b8 01 00 00 00       	mov    $0x1,%eax
   d:	5d                   	pop    %rbp
   e:	c3                   	retq   

000000000000000f <main>:
   f:	f3 0f 1e fa          	endbr64 
  13:	55                   	push   %rbp
  14:	48 89 e5             	mov    %rsp,%rbp
  17:	48 83 ec 20          	sub    $0x20,%rsp
  1b:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # 21 <main+0x12>
  21:	89 45 ec             	mov    %eax,-0x14(%rbp)
  24:	c7 45 f0 04 00 00 00 	movl   $0x4,-0x10(%rbp)
  2b:	8b 55 f0             	mov    -0x10(%rbp),%edx
  2e:	8b 45 ec             	mov    -0x14(%rbp),%eax
  31:	89 d6                	mov    %edx,%esi
  33:	89 c7                	mov    %eax,%edi
  35:	e8 00 00 00 00       	callq  3a <main+0x2b>
  3a:	89 45 f4             	mov    %eax,-0xc(%rbp)
  3d:	8b 55 f4             	mov    -0xc(%rbp),%edx
  40:	8b 45 f0             	mov    -0x10(%rbp),%eax
  43:	89 d6                	mov    %edx,%esi
  45:	89 c7                	mov    %eax,%edi
  47:	e8 00 00 00 00       	callq  4c <main+0x3d>
  4c:	89 45 f8             	mov    %eax,-0x8(%rbp)
  4f:	b8 00 00 00 00       	mov    $0x0,%eax
  54:	e8 a7 ff ff ff       	callq  0 <static_function>
  59:	89 45 fc             	mov    %eax,-0x4(%rbp)
  5c:	b8 00 00 00 00       	mov    $0x0,%eax
  61:	c9                   	leaveq 
  62:	c3                   	retq   

0000000000000063 <function>:
  63:	f3 0f 1e fa          	endbr64 
  67:	55                   	push   %rbp
  68:	48 89 e5             	mov    %rsp,%rbp
  6b:	b8 01 00 00 00       	mov    $0x1,%eax
  70:	5d                   	pop    %rbp
  71:	c3                   	retq

 

 

Symbol table 에 기록된 오프셋에 각 함수가 정의되어 있는 것을 볼 수 있습니다.

또, 외부 참조인 global_a 와 sum 의 주소가 00 00 00 00 으로 비워져 있습니다.

 

 

# Section index
# [1] .text
# [2] .rela.text
# [3] .data
# [4] .bss

    10: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 global_local_1
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_local_2
    12: 0000000000000004     4 OBJECT  GLOBAL DEFAULT    3 global_local_3
    13: 000000000000000f    84 FUNC    GLOBAL DEFAULT    1 main
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND global_a
    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND sum

 

이어서 symbol table 을 살펴보면,

global_local_1 은 0 으로 초기화되었으므로 .bss 영역에 정의되었고,

global_local_2, global_local_3 은 0이 아닌 값으로 초기화 되었으므로 .data 영역에 정의되었습니다.

global_local_2 의 offset 이 0, global_local_3 의 offset 이 4 이므로 두 전역 변수가 .data 영역에 차례로 정의되었음을 알 수 있습니다.

 

 

.rel.data, .rel.text

 

relocatable object file 을 만들 때, 어셈블러는 코드와 데이터가 런타임 메모리의 어디에 위치할지 알지 못합니다.

또, 함수와 전역 변수에 대한 외부 참조가 어디에 정의될지도 알지 못합니다. 그래서 어셈블러는 정의된 위치를 알 수 없는  참조를 만날 때마다 이를 relocation entry 의 형식으로 .rel.text 또는 .rel.data 영역에 기록합니다.

이 relocation entry 들은 linker 가 relocatable object files 를 이용해 executable 을 만들 때 이러한 참조들을 어떻게 치환해야 하는지 알려주는 역할을 합니다.

 

relocation entry 의 구조는 다음과 같습니다.

 

 

typedef struct {
	long offset; /* Offset of the reference to relocate */
	long type:32, /* Relocation type */
	symbol:32; /* Symbol table index */
	long addend; /* Constant part of relocation expression */
} Elf64_Rela;

 

 

offset 은 relocate 해야하는 레퍼런스의 위치를 기록합니다. .rel.text 일 경우 .text 섹션, .rel.data 일 경우 .data 섹션에서의 오프셋입니다. type 은 레퍼런스를 relocate 할 때 절대 주소를 쓸지 PC 상대 주소를 쓸지 등을 결정합니다.

symbol 은 Symbol table 에의 참조로, 어떤 심볼에 대한 레퍼런스인지 나타냅니다.

addend 는 relocate 시 더해져 주소를 보정하는 데에 쓰입니다.

 

 

위의 main.o 파일의 .rel.text 영역을 직접 살펴보겠습니다.

 

 

Relocation section '.rela.text' at offset 0x3a0 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000001d  000e00000002 R_X86_64_PC32     0000000000000000 global_a - 4
000000000036  001000000004 R_X86_64_PLT32    0000000000000000 sum - 4
000000000048  001000000004 R_X86_64_PLT32    0000000000000000 sum - 4

 

 

.text 영역의 offset 1d 에서 global_a, offset 36, 48 에서 sum 에 대한 미해결된 외부 참조를 가지고 있는 것을 볼 수 있습니다.

 

 

1b:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax

...

35:	e8 00 00 00 00       	callq  3a <main+0x2b>

...

47:	e8 00 00 00 00       	callq  4c <main+0x3d>

 

 

objdump 를 이용해 확인해 보면, 해당 위치가 global_a, sum 에 대한 참조가 들어와야 할 위치이고, 00 00 00 00 으로 비워져 있는 것을 확인할 수 있습니다.

 

링크 과정

여러 relocatable object file 들을 링크하는 과정은,

Symbol Resolution 과 Relocation 과정으로 나눌 수 있습니다.

 

Symbol Resolution

Symbol Resolution 이란 여러 obj 파일에 있는 같은 심볼에 대한 참조를 하나로 통일하는 것을 말합니다.

각 relocatable object file 들에는 정의된 심볼들과 정의되지 않은 심볼(UNDEF 심볼) 들이 존재합니다.

이 과정에서 정의되지 않은 심볼에 대한 참조는 다른 obj 파일의 정의된 심볼에 대한 참조로 변경됩니다.

링커가 정의되지 않은 심볼에 대한 정의를 다른 obj 에서 찾지 못하면 링크 에러를 던지고 종료됩니다.

이 과정에서 충돌하는 심볼에 대한 처리도 이루어집니다.

 

Relocation

Symbol Resolution 이 끝났다면 심볼에 대한 모든 참조는 정의된 심볼을 가리키고 있을 것입니다.

이제 Relocation 을 수행하는데, 다시 두가지 단계로 나뉩니다.

 

1. Relocating sections and symbol definitions.

링커는 여러 obj 파일에 있는 모든 같은 타입의 섹션들을 하나로 합칩니다.

예를 들어, .text 는 .text 끼리, .data 는 .data 끼리 합쳐 하나의 .text, .data 영역을 만듭니다.

이 과정에서 링커는 각 섹션의 런타임 메모리 주소를 정하게 됩니다.

즉, 모든 함수와 데이터의 런타임 메모리 주소가 정해지고,

심볼 테이블의 offset 역시 심볼이 가리키는 정의(함수, 변수 등)의 런타임 메모리 주소로 바뀝니다.

 

 

2.  Relocating symbol references within sections.

이제 컴파일시에 비워두고 .rel.text, .rel.data 영역에 기록한 미해결된 외부 참조를 해결해야 합니다.

.rel.text, .rel.data 에서 참조하는 심볼은 원래 UNDEF 인 심볼이었지만, 이제 위 과정으로 인해 심볼이 정의된 런타임 메모리 주소 를 알 수 있습니다. 따라서 .rel.text, .rel.data 의 relocation entry 들을 순회하며 offset 에 0 으로 비워둔 참조를 심볼이 정의된 런타임 메모리 주소로 치환합니다.

 

 

예시를 보면,

 

Source Code  --------------------------------------------------------------

extern int sum(int a, int b);
extern int global_a;

int global_local_1= 0; 
int global_local_2= 1;
int global_local_3= 2;

static int static_function() {
        return 1;
}

int main() {
        int a = global_a;
        int b = 4;
        int c = sum(a, b);
        int d = sum(b, c);
        int e = static_function();
}


int function() {
        return 1;
}

Symbol Table --------------------------------------------------------------


# Section index
# [1] .text
# [2] .rela.text
# [3] .data
# [4] .bss


Symbol table '.symtab' contains 18 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 0000000000000000    15 FUNC    LOCAL  DEFAULT    1 static_function
    10: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 global_local_1
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_local_2
    12: 0000000000000004     4 OBJECT  GLOBAL DEFAULT    3 global_local_3
    13: 000000000000000f    84 FUNC    GLOBAL DEFAULT    1 main
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND global_a
    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND sum
    17: 0000000000000063    15 FUNC    GLOBAL DEFAULT    1 function
    
    
.rel.text section ----------------------------------------------------------

 
Relocation section '.rela.text' at offset 0x3a0 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000001d  000e00000002 R_X86_64_PC32     0000000000000000 global_a - 4
000000000036  001000000004 R_X86_64_PLT32    0000000000000000 sum - 4
000000000048  001000000004 R_X86_64_PLT32    0000000000000000 sum - 4

    
.text section --------------------------------------------------------------
    
    
0000000000000000 <static_function>:
   0:	f3 0f 1e fa          	endbr64 
   4:	55                   	push   %rbp
   5:	48 89 e5             	mov    %rsp,%rbp
   8:	b8 01 00 00 00       	mov    $0x1,%eax
   d:	5d                   	pop    %rbp
   e:	c3                   	retq   

000000000000000f <main>:
   f:	f3 0f 1e fa          	endbr64 
  13:	55                   	push   %rbp
  14:	48 89 e5             	mov    %rsp,%rbp
  17:	48 83 ec 20          	sub    $0x20,%rsp
  1b:	8b 05 00 00 00 00    	mov    0x0(%rip),%eax        # 21 <main+0x12>
  21:	89 45 ec             	mov    %eax,-0x14(%rbp)
  24:	c7 45 f0 04 00 00 00 	movl   $0x4,-0x10(%rbp)
  2b:	8b 55 f0             	mov    -0x10(%rbp),%edx
  2e:	8b 45 ec             	mov    -0x14(%rbp),%eax
  31:	89 d6                	mov    %edx,%esi
  33:	89 c7                	mov    %eax,%edi
  35:	e8 00 00 00 00       	callq  3a <main+0x2b>
  3a:	89 45 f4             	mov    %eax,-0xc(%rbp)
  3d:	8b 55 f4             	mov    -0xc(%rbp),%edx
  40:	8b 45 f0             	mov    -0x10(%rbp),%eax
  43:	89 d6                	mov    %edx,%esi
  45:	89 c7                	mov    %eax,%edi
  47:	e8 00 00 00 00       	callq  4c <main+0x3d>
  4c:	89 45 f8             	mov    %eax,-0x8(%rbp)
  4f:	b8 00 00 00 00       	mov    $0x0,%eax
  54:	e8 a7 ff ff ff       	callq  0 <static_function>
  59:	89 45 fc             	mov    %eax,-0x4(%rbp)
  5c:	b8 00 00 00 00       	mov    $0x0,%eax
  61:	c9                   	leaveq 
  62:	c3                   	retq   

0000000000000063 <function>:
  63:	f3 0f 1e fa          	endbr64 
  67:	55                   	push   %rbp
  68:	48 89 e5             	mov    %rsp,%rbp
  6b:	b8 01 00 00 00       	mov    $0x1,%eax
  70:	5d                   	pop    %rbp
  71:	c3                   	retq

 

 

main 의 소스 코드와 링크 전 main.o 의 symbol table, .rel.text, .text 영역입니다.

 

 

Source code ---------------------------------------------------------------


int global_a = 4;

int sum(int a, int b) {
        return a + b;
}


Symbol Table ---------------------------------------------------------------


Symbol table '.symtab' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 global_a
    10: 0000000000000000    24 FUNC    GLOBAL DEFAULT    1 sum
    
    
.text area   ---------------------------------------------------------------


0000000000000000 <sum>:
   0:	f3 0f 1e fa          	endbr64 
   4:	55                   	push   %rbp
   5:	48 89 e5             	mov    %rsp,%rbp
   8:	89 7d fc             	mov    %edi,-0x4(%rbp)
   b:	89 75 f8             	mov    %esi,-0x8(%rbp)
   e:	8b 55 fc             	mov    -0x4(%rbp),%edx
  11:	8b 45 f8             	mov    -0x8(%rbp),%eax
  14:	01 d0                	add    %edx,%eax
  16:	5d                   	pop    %rbp
  17:	c3                   	retq

 

 

main 의 외부 참조(global_a, sum) 이 정의된 sum 의 소스 코드, 심볼 테이블, .text 영역입니다.

 

gcc main.o sum.o
readelf -a a.out
objdump -d a.out

 

을 통해 두 오브젝트 파일을 링크하여 실행 파일을 만들고, 그 심볼 테이블과 .text 영역의 코드를 확인해 보도록 하겠습니다.

 

 

# Section Index
# [23] = .data
# [14] = .text
# [24] = .bss

Symbol table '.symtab' contains 70 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
    49: 0000000000004018     4 OBJECT  GLOBAL DEFAULT   23 global_a
    50: 0000000000004014     4 OBJECT  GLOBAL DEFAULT   23 global_local_3
    57: 000000000000119b    24 FUNC    GLOBAL DEFAULT   14 sum
    62: 0000000000004010     4 OBJECT  GLOBAL DEFAULT   23 global_local_2
    64: 0000000000001138    84 FUNC    GLOBAL DEFAULT   14 main
    65: 000000000000118c    15 FUNC    GLOBAL DEFAULT   14 function
    66: 0000000000004020     4 OBJECT  GLOBAL DEFAULT   24 global_local_1
    

-------------------------------------------------------------------------------


0000000000001129 <static_function>:
    1129:	f3 0f 1e fa          	endbr64 
    112d:	55                   	push   %rbp
    112e:	48 89 e5             	mov    %rsp,%rbp
    1131:	b8 01 00 00 00       	mov    $0x1,%eax
    1136:	5d                   	pop    %rbp
    1137:	c3                   	retq   

0000000000001138 <main>:
    1138:	f3 0f 1e fa          	endbr64 
    113c:	55                   	push   %rbp
    113d:	48 89 e5             	mov    %rsp,%rbp
    1140:	48 83 ec 20          	sub    $0x20,%rsp
    1144:	8b 05 ce 2e 00 00    	mov    0x2ece(%rip),%eax        # 4018 <global_a>
    114a:	89 45 ec             	mov    %eax,-0x14(%rbp)
    114d:	c7 45 f0 04 00 00 00 	movl   $0x4,-0x10(%rbp)
    1154:	8b 55 f0             	mov    -0x10(%rbp),%edx
    1157:	8b 45 ec             	mov    -0x14(%rbp),%eax
    115a:	89 d6                	mov    %edx,%esi
    115c:	89 c7                	mov    %eax,%edi
    115e:	e8 38 00 00 00       	callq  119b <sum>
    1163:	89 45 f4             	mov    %eax,-0xc(%rbp)
    1166:	8b 55 f4             	mov    -0xc(%rbp),%edx
    1169:	8b 45 f0             	mov    -0x10(%rbp),%eax
    116c:	89 d6                	mov    %edx,%esi
    116e:	89 c7                	mov    %eax,%edi
    1170:	e8 26 00 00 00       	callq  119b <sum>
    1175:	89 45 f8             	mov    %eax,-0x8(%rbp)
    1178:	b8 00 00 00 00       	mov    $0x0,%eax
    117d:	e8 a7 ff ff ff       	callq  1129 <static_function>
    1182:	89 45 fc             	mov    %eax,-0x4(%rbp)
    1185:	b8 00 00 00 00       	mov    $0x0,%eax
    118a:	c9                   	leaveq 
    118b:	c3                   	retq   

000000000000118c <function>:
    118c:	f3 0f 1e fa          	endbr64 
    1190:	55                   	push   %rbp
    1191:	48 89 e5             	mov    %rsp,%rbp
    1194:	b8 01 00 00 00       	mov    $0x1,%eax
    1199:	5d                   	pop    %rbp
    119a:	c3                   	retq   

000000000000119b <sum>:
    119b:	f3 0f 1e fa          	endbr64 
    119f:	55                   	push   %rbp
    11a0:	48 89 e5             	mov    %rsp,%rbp
    11a3:	89 7d fc             	mov    %edi,-0x4(%rbp)
    11a6:	89 75 f8             	mov    %esi,-0x8(%rbp)
    11a9:	8b 55 fc             	mov    -0x4(%rbp),%edx
    11ac:	8b 45 f8             	mov    -0x8(%rbp),%eax
    11af:	01 d0                	add    %edx,%eax
    11b1:	5d                   	pop    %rbp
    11b2:	c3                   	retq   
    11b3:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
    11ba:	00 00 00 
    11bd:	0f 1f 00             	nopl   (%rax)

 

 

심볼 테이블에 있는 모든 심볼들은 심볼이 정의된 메모리 주소를 가집니다. 즉, UNDEF 심볼이 사라진 것을 확인할 수 있습니다.

.text 영역에 있는 코드에서도, 링크를 수행하고 나서는 00 00 00 00 으로 비워둔 외부 참조들이 모두 링크되어 적절한 메모리 주소를 참조하고 있는 것을 확인할 수 있습니다.