1. 문제 제기 (Introduction & Problem Statement)
관찰 현상 또는 질문:
여러 개의 C 파일을 컴파일해서 실행 파일로 만드는 과정에서, 각각의 함수, 전역 변수 등은 어떻게 하나의 완성된 파일로 통합되는가? 함수명이 겹치면 어떤 일이 벌어지는가? GCC 명령어 한 줄 뒤엔 무슨 일이 진행되는가?탐구 목표:
GCC의 컴파일/빌드 과정, 특히 정적 링킹 과정에서 핵심 역할을 하는 심볼 해석, 재배치 메커니즘을 Elf 포맷 기준으로 분석한다. 이를 통해 소스코드가 실행파일로 합쳐지는 실제 동작 원리와 의도치 않은 링킹 에러 이슈의 트러블슈팅 능력을 기르는 것이 목표이다.
2. 기술 분석 및 핵심 원리 (Technical Deep Dive)
2-1. 컴파일러 드라이버의 동작 순서와 역할
- 전처리기 (cpp):
#include,#define등 전처리 지시자를 해석해 소스 코드에서 매크로를 확장하고 헤더 파일을 삽입한다. 결과는 .i 파일의 개념이다.
- 컴파일러 (cc1):
- 전처리된 코드를 분석해서 어셈블리 코드(.s)로 변환.
- 어셈블러 (as):
- 어셈블리 코드를 기계어 오브젝트 파일(.o)로 변환.
- 링커 (ld):
- 여러 개의 오브젝트 파일(.o)과 라이브러리(.a, .so)를 묶어 하나의 실행파일(ELF)로 완성.
GCC는 위 순서를 자동으로 호출하는 컴파일러 드라이버다.
2-2. 정적 링킹(Static Linking)과 ELF 파일 구조
빌드 명령 예시:
gcc -c main.c # main.o 생성 gcc -c addvec.c # addvec.o 생성 gcc main.o addvec.o -o prog # 링크코드 예시
// main.c void addvec(int *x, int *y, int *z, int n); int main() { int x = {1, 2};[2] int y = {3, 4};[2] int z;[2] addvec(x, y, z, 2); return 0; } // addvec.c void addvec(int *x, int *y, int *z, int n) { int i; for (i = 0; i < n; i++) z[i] = x[i] + y[i]; }ELF 주요 섹션
.text: 기계어(실행 코드).data: 초기값 있는 전역/정적 변수.bss: 0으로 초기화된 전역/정적 변수(단지 공간만 차지).symtab: 심볼(함수명, 변수명) 정보.rel.text,.rel.data: 재배치 정보(외부 참조 주소 채워넣기)- ELF 헤더, 섹션 헤더 등 메타정보 존재
동작 순서 요약
- main.c → main.o, addvec.c → addvec.o 각각 만들어짐.
main.o엔addvec함수의 심볼만 있고, 실제 정의는 없다.- 링커가
.o파일의 심볼 테이블, 재배치 정보, 코드/데이터를 종합해 하나의 주소 공간에 배치. .rel.text정보에 따라 외부 함수(여기서는 addvec)의 실제 주소로 코드를 패치.
2-3. 심볼 해석과 Strong/Weak Symbol 규칙
| 구분 | Strong Symbol | Weak Symbol |
|---|---|---|
| 정의 | 함수, 초기화된 전역 변수 | 초기화되지 않은 전역 변수 |
| 우선순위 | 높음 | 낮음 |
- 룰 요약
- Strong 심볼이 두 개 이상: 에러(Linker error)
- Strong 1개 + Weak 여러 개: Strong이 선택됨
- Weak만 여러 개: 아무거나 하나(비결정적, 버그 유발 가능)
3. 결론 및 고찰 (Conclusion & Takeaways)
핵심 요약:
- 컴파일
어셈블링킹까지 일련의 과정 속에 각 파일의 코드와 데이터를 합치기 위한 정보/구조가 치밀하게 설계되어 있다. - ELF는 코드(.text), 데이터(.data/.bss), 심볼(.symtab), 재배치(.rel) 등을 명확히 분리해 파일을 구조화하며, 링커는 이 정보를 이용해 주소 공간을 완성한다.
- Strong/Weak 심볼 규칙은 글로벌 네임스페이스에서의 충돌/우선순위 해결 논리를 제공. 무의식적으로 전역변수를 남용하면 링킹 오류나 예측 불가 결과가 생긴다.
- 컴파일
기술적 통찰 및 나의 생각:
실행파일은 단순 바이너리 집합이 아닌, 운영체제가 안전하게 로드하고 동작하기 위한 약속(EFL 포맷)에 맞춘 "논리적 컴포지션" 결과이다. 특히 심볼 관리, 전역변수의 초기화 여부에 따른 우선순위, 재배치 등은 로우레벨 시스템 엔지니어링의 정수를 보여주며 전역 네임 충돌 리스크를 명확히 알게 됐다.향후 과제 / 추가 질문:
"동적 링킹은 syscall, 라이브러리의 로딩 타이밍 문제를 어떻게 해결할까?", "ELF의 PLT/GOT는 어떤 구조와 메커니즘으로 동작할까?"를 다음 탐구 주제로 삼고 싶다.
4. 참고 자료 (References)
- 시스템 프로그래밍 강의 노트, 링커 및 ELF 분석
- Computer Systems: A Programmer's Perspective (CS:APP)
'Study > CSAPP' 카테고리의 다른 글
| [CSAPP] 정적/동적 라이브러리와 Java 동적 로딩: DLL, JNI, AOP까지 (0) | 2025.12.10 |
|---|---|
| [CSAPP] 링킹(Linking)과 로딩(Loading): 실행 파일이 메모리에서 프로세스가 되기까지 (0) | 2025.12.10 |
| [CSAPP] 메모리 계층과 지역성: CPU–메모리 갭을 줄이는 하드웨어·소프트웨어 전략 (0) | 2025.11.18 |
| [CSAPP] 컴파일러는 왜 내 코드를 최적화하지 못할까?: 데이터 의존성과 분기 예측의 비밀 (0) | 2025.11.12 |
| [CSAPP] Y86-64 ISA의 순차적 구현: SEQ 프로세서 동작 원리 분석 (0) | 2025.11.11 |