Static Linking
Category: Computer Systems
Linking
여러 파일의 code와 data를 하나의 파일로 연결해서 바로 실행 가능하도록 만들어주는 프로세스.
아래 예시를 보면 main.c 파일을 컴파일할 때, sum 함수에 대한 정보가 필요하다. 이때 linker가 sum함수에 대한 정보를 main.c와 연결해주는 매개체 역할을 한다.
👩🏻💻 컴파일 단계에서는 sum 함수의 정의가 어딘가 있다는 가정하에 컴파일 작업을 진행한다.
컴파일을 진행한 후 마지막에 linking을 하기 때문에 둘은 분리된 프로세스!
Linking 하는 과정
main.c와 sum.c를 각각 컴파일해서 relocatable(재배치 가능한) 오브젝트 파일들이 형성되고, 이후 linker에 의해 합쳐져서 최종적으로 하나의 executable 오브젝트 파일이 형성된다.
👩🏻💻 위의 과정은 Static Linking에 해당된다.
컴파일 과정 중에서 고정된 특정 시점에 라이브러리나 오브젝트 파일을 링크하기 때문에 “정적(static)” 링크라고 부른다.
-
GCC 컴파일
소스코드를 기계어로 컴파일할 때 사용되는 컴파일 툴
gcc -c
: 링크 전까지 컴파일ld -l
: 링크하는 단계 (-l
: 라이브러리 따로 지정 )gcc -Og
: 처음부터 링크까지 한 번에 컴파일 (-Og
: optimization) -
OBJDUMP
컴파일된 오브젝트 파일과 실행파일을 분석할 때 사용되는 툴
objdump -d -t
(-d
: disassemble,-t
: symbol table)
Linker를 사용하는 이유
링커는 프로그램을 여러 개의 오브젝트 파일로 구성할 수 있도록 해준다.
-
Modularity (모듈화)
하나의 거대한 파일이 아닌 여러 개의 작은 소스 파일들이 모여서 하나의 파일을 형성
예를 들면 반복되는 특정 함수들의 라이브러리 (math, standard C)
-
Efficiency (효율성)
컴파일과 링크 단계가 분리되어 있기 때문에, 수정 사항이 생겼을 때 전체 코드를 다시 컴파일할 필요없이 특정 source 파일만 다시 컴파일하고 링크하면 된다.
Linker가 하는 일
링커의 작업은 크게 심볼 해석(symbol resolution)과 재배치(relocation)로 나눌 수 있다.
-
Symbol Resolution
프로그램은 symbol(= global 변수 및 함수)을 정의하고 참조해서 사용한다.
각 오브젝트 파일은 symbol table을 통해 각 symbol 이름과 사이즈, 메모리 주소를 저장한다. 어떤 symbol이 reference된 경우, 링커는 symbol table을 탐색해서 해당 symbol이 defined된 위치를 찾아준다.
👩🏻💻 이 단계에서 링커는 각 symbol reference를 하나의 symbol definition과 연결한다.
여러 개인 경우에는 linker error 발생 -
Relocation
링커는 해당 symbol을 변수 혹은 함수가 위치한 정확한 메모리 주소와 연결해준다. 기존의 오브젝트 파일 상대 주소를 메모리 절대 주소로 변환해서 하나의 최종 실행 파일을 생성한다.
-> 이후 수업 내용에서 각 단계별로 더 자세하게 다뤄질 예정
Type of Object Files
오브젝트 파일은 프로그램의 컴파일 및 링킹 과정에서 사용되는 파일 형식으로, 실행 파일로 변환되기 전 단계의 파일이다.
-
Relocatable object file (
.o
)다른 오브젝트 파일과 결합해서 하나의 실행 가능한 오브젝트 파일 형성
-> compiler와 assembler에 의해 형성된 파일
-
Executable object file (
a.out
)주소를 통해 메모리에 바로 복사해서 실행이 가능한 상태
-> linker에 의해 형성된 최종 실행 파일
-
Shared object file (
.so
)메모리에 로드해서 dynamic linking이 가능한 특수한 relocatable 오브젝트 파일
👩🏻💻 objdump -d -t linkerfile.o
: 일부 unresolved 값들이 남아 있는 상태
objdump -d -t linkerfile
: 모두 resolved된 상태, 남아있으면 linker error
오브젝트 파일들은 Executable and Linkable Format(ELF) 표준 파일 형식을 따른다.
ELF Object File Format
아래는 대표적인 ELF relocatable 오브젝트 파일 형식을 나타낸다.
Linker Symbols
Linker의 관점에서 크게 세 가지의 symbol로 나눌 수 있다.
-
Global symbols
전체 프로그램 내에서 접근 가능하도록 정의된 global 변수의 집합
non-static attribute 을 가진 함수나 전역변수를 말한다.
👩🏻💻 static : 오직 그 파일 내에서만 접근 가능한 변수를 선언할 때 사용
-
External symbols
다른 파일에 정의되어 참조된 global 변수의 집합
-
Local symbols
특정 모듈 내에서 static attribute 으로 정의된 ** local 변수의 집합
👩🏻💻 Local 변수의 경우, 특정 함수 외부에서 접근할 수 없기 때문에 컴파일러와 런타임 스택에 의해서만 관리된다.
즉, 로컬 변수는 linker와는 관련이 없다!
Step 1 : Symbol Resolution
위의 예시를 보면 global, external, local symbol을 다음과 같이 구분할 수 있다.
int sum(int *a, int n);
: sum.c에서 참조된 external symbol
int array[2] = {1, 2};
: main.c에서 정의된 global symbol
int main() {...}
: main.c에서 정의된 global symbol
local symbol이 정확히 어떤 것인지는 다음 예시를 통해 더 살펴볼 수 있다.
- non-static : 스택에 저장되는 local variable (linker 관여 X)
- static : .bss 또는 .data에 저장되는 local symbol
- 초기 값이 있는 경우 : .data 영역에 저장
- 초기 값이 없는 경우 : .bss 영역에 저장
Linker’s Symbol Rules
위와 같이 local symbol이 중복된 경우와 다르게 중복된 global symbol이 선언된 경우에는 여러 문제점을 초래할 수 있다. 컴파일러는 이 경우를 대비해서 일종의 규칙을 가지고 있다.
먼저 프로그램의 symbol은 세기를 기준으로 다음과 같이 분류할 수 있다.
- Strong : 함수 혹은 initialized 변수 (.data 영역)
- Weak : uninitialized 변수 (.bss 영역)
-
Rule 1: Multiple strong symbols are not allowed
하나의 strong definition만 선언 가능하며, 그렇지 않은 경우 linker error
-
Rule 2: Given a strong and weak symbols, choose the strong
weak reference가 strong으로 바뀌게 된다. (프로그램 오작동 가능)
-
Rule 3: If there are multiple weak symbols, pick an arbitrary one
weak symbol 중에서 randomly 선택을 하게 된다. (가장 위험 상황)
문제가 발생할 수 있는 example 상황들을 예시로 들어보면,
👩🏻💻 따라서 global 변수는 가능하면 사용을 피하는 것이 좋다.
하지만 사용해야 하는 경우에는 1. static으로 선언 2. 값으로 initialize 3. 참조할 땐 extern으로 선언
Step 2 : Relocation
다음으로는 relocation 과정을 통해 여러 오브젝트 파일을 하나의 실행 파일로 합친다.
Relocation Entries
Relocated .text section
Solution : Static, Shared Libraries
-
Static Libraries (.a archive files)
관련된 relocatable 오브젝트 파일끼리 병합해서 하나의 archive 파일(.a)을 형성한다. 그래서 그 파일에서 symbol을 탐색해서 reference를 채우는 방식이다.
문제점
- 실행 파일에 중복된 코드를 포함하여 용량이 커지고 메모리 낭비가 발생
- 라이브러리를 수정할 때마다 다시 컴파일하고 링크해야 하는 번거로움
-
Shared Libraries (.dll, .so files)
- dynamic linking을 사용해서 load time이나 runtime에 수행
- 파일 크기를 줄여 메모리를 아낄 수 있고, 다시 컴파일하거나 링크하지 않고도 라이브러리 수정 가능하다! (static library의 단점을 보안)
Dynamic Linking
Static Linking vs. Dynamic Linking 비교
Dynamic Linking at Load time
추가적으로 Loader가 도입되어 이 단계에서 linking 과정이 마무리 된다.
Dynamic Linking at Run time
직접 코드 상에서 작성해서 library를 불러오고 병합하는 방법도 있다.
@Computer Systems a programmer’s perspective third edition 내용을 참고함