링커

전두엽힘주기·2025년 4월 21일

Computer System

목록 보기
4/13

ch7

Linking

여러 개의 소스 파일을 하나의 실행 가능한 파일(executable)로 만드는 작업.

컴파일 후 수행되며, 각 오브젝트 파일(.o)을 머신 코드로 번역하여 하나로 연결한다.

이 작업은 링커(Linker)라는 프로그램에 의해 자동으로 실행된다.

각 소스 파일은 독립적으로 컴파일할 수 있으며, 이후 링커가 이들을 결합한다.

7.1 컴파일 드라이버

컴파일 시스템은 언어 전처리기, 컴파일러, 어셈블러, 링커를 필요에 따라 호출하는 컴파일러 드라이버를 제공한다

예: GCC 드라이버 호출

linux> gcc -Og -o prog main.c sum.c

main.c, sum.c를 컴파일하여 -Og 로 조정, prog 실행파일 실행

cpp  ->  cc1  ->  as  ->  ld
main.c → main.i → main.s → main.o → prog

cpp: 전처리기 – include, define 등 처리 → main.i 생성

cc1: 컴파일러 – C 코드를 어셈블리로 변환 → main.s 생성

as: 어셈블러 – 어셈블리 코드를 목적 파일로 변환 → main.o 생성

ld: 링커 – 여러 오브젝트 파일을 묶어 실행 파일(prog) 생성

정적연결

: 링커는 재배치가능 목적 파일들을 연결해서 실행 가능 목적 파일 prog를 생성

./prog

사용자가 실행 파일을 실행하면, 운영체제 내의 로더(loader)가 호출되어 실행 파일을 메모리에 적재하고, 프로그램의 진입점(main 함수 등)으로 제어를 넘긴다.

7.2 정적연결

링커의 역할

정적 링커들은 재배치 가능한 목적 파일들과 명령줄 인자들을 입력으로 받아들여 로드 및 실행이 가능한 완전히 링크된 실행가능 목적파일을 출력으로 생성한다
입력인 재배치 가능 목적 파일들은 여러 코드와 데이터 섹션(연속적인 바이트 블록)들로 이루어져 있습니다.

일반적으로:

코드 → .text 섹션

초기화된 전역 변수 → .data 섹션

초기화되지 않은 전역 변수 (예: static int x;) → .bss 섹션

실행 파일을 만들기 위해

  1. 심볼해석: 목적파일들은 심볼들을 정의하고 참조하며 여기서 각 심볼은 함수, 전역 변수, 정적 변수에 대응, 심볼 해석의 목적은 각각 심볼 참조를 정확하게 하나의 심볼 정의에 연결하는 것

    예: sum.c에서 정의된 sum() 함수가, main.c에서 참조될 수 있다

  2. 재배치: 컴파일러와 어셈블러는 주소 0번지에서 시작하는 코드와 데이터섹션들을 생성한다.
    하지만, 여러 목적 파일을 합치려면 주소 중복이 발생하므로, 링커가 각 섹션을 실제 실행 시 위치에 맞게 재배치한다.

그리고 심볼로의 참조들도 정확한 실행 주소를 가리키도록 수정한다.

이 작업은 어셈블러가 생성한 재배치 엔트리(Relocation Entry)에 따라 자동으로 수행된다. 이를 직접 조작할 필요 없이, 링커가 내부적으로 처리

(cf. 심볼: 링커의 기본 단위로, 특정 메모리 주소를 가리키는 이름
이는 메모리에 저장되는 데이터가 아닌, 개념적인 포인터로써, 디버깅 시에도 사용된다
단, 지역 변수는 심볼이 될 수 없다.)

목적 파일들은 단지 바이트 블록들의 집합이다 링커는 블록들을 하나로 연결(link)하고, 필요한 주소 정보를 수정(patch)하여 실행 가능한 상태로 만든다.

링커는 타깃 머신 구조에 대해 최소한의 지식만을 갖고 있으며, 대부분의 작업은 기계 독립적으로 처리.

목적파일

  1. 재배 가능 목적 파일 : 포멧에 컴파일 할 때 실행가는 목적파일을 생성하기 위해 다른 재구성 가능 목적파일들과 결합될 수 있는 바이너리 코드와 데이터 포함 - 컴파일러, 어셈블러
  2. 실행가능 목적파일: 메모리에 직접 복사 , 실행가능 형태로 바이너리 코드와 데이터 포함 - 링커
  3. 공유목적 파일: 로드타임 , 런타임시 동적으로 링크, 메모리에 로드될 수 있는 특수한 유형의 재배치 가능 목적파일

리눅스 목적파일 : ELF사용(ELF(Executable and Linkable Format)는 리눅스 등 Unix 계열 시스템에서 사용되는 표준 실행 파일 포맷)

7.4 재배치 가능 목적 파일

ELF Header

: 파일의 시작 16바이트는 고정된 구조의 식별자 필드. 나머지는 링커가 목적 파일을 구문 분석하고 해석하도록 하는 정보를 포함하고 있다 (ELF해더 크기, 목적파일 타입,(재배치 가능, 실행가능, 공유), 머신 타입(x86-64 etc.), 섹션 헤더 테이블의 파일 오프셋, 섹션해더 테이블의 크기, 엔트리수)

elf header 구조를 살펴보자
e_ident [EI_DENT]; 부분

32bit ELF 파일인지 , 64bit 파일인지 알 수 있음
위 사진에서는 02 니까 64bit ELF 파일임


목적 파일의 타입

재배치가능 파일 타입:

gcc -c foo.c -o foo.o

-c: 컴파일만 수행 (링킹 안 함)

결과: foo.o (재배치 가능 목적 파일)

실행 가능 파일 타입:

gcc -no-pie foo.c -o foo

-no-pie: PIE(Position Independent Executable)이 아닌 전통적인 실행 파일 생성

결과: foo (실행 가능 파일)
*최신 리눅스에서 기본적으로는 PIE를 생성하기 때문에, 전통적인 ET_EXEC을 원할 경우 -no-pie 옵션을 명시해야 함

공유객체파일
실행 시 동적으로 링크되는 공유 라이브러리(.so) 파일

시스템의 다른 프로그램들과 공유되며 메모리 사용을 절약

Section

.text : 컴파일된 프로그램의 머신 코드

.rodata : read only. static, const . prinf 문장의 포맷 스트링 etc
수정 불가능한 데이터는 이곳에 분리 저장됨

.data : 초기화된 c 전역 변수, 정적 변수. 읽고 쓰기 가능.프로그램 실행 전에 초기값을 갖는 변수들 저장 C지역변수들은 런타임 스택에 저장되며 data나 .bss 섹션에는 나타나지 않는다

.bss : 초기화 되지 않았거나 0으로 초기화된 c 전역변수와 정적 변수, 목적파일에 실제 공간을 차지하지 않음 디스크에 저장되지 않음 (공간 효율) 런타임 시 메모리에서 자동으로 0으로 초기화

.symtab : 심볼테이블 - 전역변수와 함수의 정보,링커나 디버거에서 참조재배치 가능 목적 파일에는 항상 포함 지역 변수 정보는 없음 (컴파일러 내부 심볼테이블에만 존재)

.rel.text / .rel.data (Relocation Info): 다른 파일과 링크 시 수정이 필요한 위치 정보, 외부 함수 호출, 전역 변수 참조 등
실행파일에는 포함되지 않음 사용자가 명시적으로 요청해야 포함됨

.debug / .line: 디버깅 정보, gdb 같은 디버거에서 소스 코드 라인 추적, 일반 실행에는 필요 없음, 디버깅용

.strtab (String Table): 심볼 이름, 섹션 이름 등의 문자열, .symtab 또는 섹션 헤더에서 문자열 참조 시 사용, 문자열이 중복되지 않도록 효율적으로 저장

섹션 해더 테이블: 여러가지 섹션들의 위치와 크기를 나타냄, 목적파일의 각 섹션에 대해 고정된 크기의 엔트리를 작는다

7.5 심볼과 심볼테이블

일반적인 재배치 가능한 목적 파일은 심볼테이블을 가지고 있다. 심볼 테이블에는 소스 코드에서 참조되는 심볼들의 이름과 주소가 정의되어있다
심볼은 해당 오브젝트 파일 내에서 정의/참조된 것만 포함
다른 파일에서 정의된 심볼은 심볼 테이블에 정의 없이 "참조" 상태로 표시

세가지 종류의 심볼

  • 전역 심볼 (Global Symbol)
    정의 위치: 해당 모듈(m) 내부에서 정의됨

사용 가능 범위: 다른 모듈에서도 참조 가능

대응하는 C 요소: 비정적 함수, 전역 변수

  • 외부 심볼 (External Symbol)
    정의 위치: 다른 모듈에 정의됨

현재 모듈(m)에서는 참조만 함

대응하는 C 요소: 다른 모듈내에서 정의된 전역변수들과 비정적 c함수들에 대응. extern 키워드 사용 또는 묵시적 참조

  • 지역 심볼 (Local Symbol)
    정의 위치: 모듈(m) 내부

사용 가능 범위: 해당 모듈 내부에서만 참조 가능

대응하는 C 요소: static 함수, static 전역 변수

지역 변수(local variable)는 런타임 stack에 저장되므로, .symtab에는 포함되지 않음.

같은 이름의 static 변수가 여러 파일에 있더라도 충돌이 안 나는 이유:

static int x = 0; 같은 정의는 전역 변수처럼 .data나 .bss에 저장되지만,

컴파일러가 이를 내부적으로 x.1, x.2 같은 유일한 심볼 이름으로 바꿔서 .symtab에 저장함.

// file1.c
static int x = 0;

// file2.c
static int x = 10;

→ 컴파일러: x.1, x.2

→ 링커: 충돌 없음

섹션 해더 테이블에 엔트리가 없는 pseudo section

UNDEF: 다른 파일에서 정의됨 (외부 심볼)
ABS: 재배치가 필요 없는 절대 심볼
COMMON: 초기화 안된 전역 변수 (일시적 저장 영역)

COMMON vs .bss

특징COMMON.bss
저장 대상초기화되지 않은 전역 변수초기화되지 않았거나 0으로 초기화된 static/전역 변수
위치.symtab 내 COMMON 항목.bss 섹션
링커 역할같은 이름의 COMMON 심볼이 여러 파일에 있으면 하나로 병합 가능중복 불가 (static은 로컬 심볼로 이름이 다름)
// COMMON 예시
int a;  // 외부에서 참조되는 초기화되지 않은 전역 변수

// .bss 예시
static int b;  // static 전역 변수, 초기화 안됨
int c = 0;     // 전역 변수, 0으로 초기화됨

Global/External/Local Symbol 개념은 링커의 해석 단위.
static 변수는 런타임 스택 대신 .data나 .bss에 저장되고, 이름 충돌을 방지하기 위해 내부적으로 이름이 바뀜.
.symtab은 지역 변수(local var)는 포함하지 않음.
.bss와 COMMON은 유사하지만, 링커의 처리 방식 차이 때문에 구분됨.

7.6 심볼해석

컴파일러는 모듈당 단 하나의 지역심볼 정의만을 허용한다
지역 링커 심볼들을 갖게 되는 정적 지역 변수들이 유일한 이름을 갖도록 보장한다

7.8 실행가능 목적 파일

7.9 실행가능 목적 파일 로딩

실행가능 목적 파일 prog를 실핼

linux> ./prog

쉘은 prog가 실행가능한 목적파일이라고 가정, 쉘은 로더라고 알려진 메모리 상주 운영체제 코드를 호출해서 프로그램 실행

모든 리눅스 프로그램은 execve함수를 호출해서 프로그램을 실행한다
로더는 디스크로 부터 실행가능한 목적 파일내의 코드롸 데이터를 메모리로 복사 후 엔트리 포인트로 점프해서 프로그램 실행 -> 로딩

런타임 메모리


참고자료

compile 영상
ELF Header 영상

gcc 컴파일러 옵션

0개의 댓글