한양과학기술고등학교

에듀테크 프로젝트 대시보드

링커와 로더

여러 조각의 코드가 하나의 실행 파일이 되기까지

소스 파일 하나하나는 따로 컴파일되어 목적 파일(.o)이 된다. 이때 다른 파일에 있는 함수·변수의 주소는 아직 알 수 없어 빈칸(????)으로 남는다. 링커는 이 조각들을 이어 붙이고(섹션 병합), 빈칸의 정체를 찾아내고(심볼 해석), 실제 주소로 메워 넣는다(재배치). 로더는 완성된 파일을 메모리에 올려 프로세스로 실행한다.

1 · 빌드 파이프라인

전처리 → 컴파일 → 어셈블 → 링크 → 로드. 앞 세 단계는 파일마다 독립적이고, 링크에서 처음으로 모든 조각이 만난다.

SOURCE
main.c
사람이 쓴 코드
→
COMPILE + ASSEMBLE
main.o
기계어 + 심볼표
미해결 주소는 ????
→
LINK (ld)
a.out
섹션 병합·심볼 해석
재배치 완료
→
LOAD (실행 시)
process
메모리 매핑
동적 링킹·시작
2 · 심볼 해석과 재배치 — 단계별 실행

main.o 는 add() 와 PI 를 쓰지만 정의하지 않는다(UND). math.o 가 그것들을 정의한다. 링커가 어떻게 연결하는지 버튼으로 따라가 보자.

시작: 두 목적 파일은 아직 서로를 모른다. “다음 단계”를 눌러 링킹을 시작하세요.
main.o 목적 파일
심볼 테이블
0x000000mainDEF .text
????addUND
????PIUND
.text (기계어 발췌)
main: ... mov eax, [????????] ; PI 읽기 call ???????? ; add() 호출 ret
math.o 목적 파일
심볼 테이블
0x000000addDEF .text
0x000000PIDEF .data
.text / .data
add: lea eax, [rdi+rsi] ret ; .data PI: .double 3.14159
a.out — 실행 파일
.textmain + add
.dataPI
3 · 정적 링킹 vs 동적 링킹

라이브러리(예: libm, libc) 코드를 실행 파일 안에 복사할지, 실행 시점에 공유 라이브러리에서 빌려 쓸지의 선택.

실행 파일 크기

2.4 MB
  • libm·libc 코드가 통째로 들어감
  • 혼자서도 실행 — 의존성 0
  • 라이브러리 보안 패치 시 재빌드 필요

메모리 (같은 libc 쓰는 프로세스 100개)

libc × 100
  • 각 프로세스가 자기 사본을 적재
  • 디스크·RAM 중복
동적 링킹을 고르면 — 실행 파일엔 printf 코드 대신 PLT 스텁만 있고, 실제 주소는 GOT 테이블에 저장된다. 처음 호출 때 딱 한 번만 해석하는 지연 바인딩을 눌러 보자.
동적 링킹 모드에서만 동작합니다.
PLT (프로시저 연결 테이블)
printf@plt:
  jmp *[GOT+printf]
  push idx; jmp resolver
GOT (전역 오프셋 테이블)
GOT[printf] = PLT+6 (미해결)
동적 링커 (ld.so)
대기 중…
지연 바인딩(lazy binding): 첫 호출은 PLT → GOT(미해결) → 동적 링커 로 우회해 실제 printf 주소를 찾고 그 값을 GOT에 덮어쓴다. 두 번째 호출부터는 PLT → GOT(해결됨) → printf 로 곧장 점프한다. 프로그램이 실제로 안 부르는 함수는 영영 해석되지 않아 시작이 빠르다. (LD_BIND_NOW=1 로 끌 수 있음)
4 · 직접 확인해 보는 명령어
gcc -c main.c목적 파일 main.o 만 생성 (링크 안 함)
nm main.o심볼 목록 — T=정의된 텍스트, U=미해결(undefined)
readelf -r main.o재배치 항목 표: 어떤 오프셋을 어떤 심볼로 메울지
ld / gcc main.o math.o실제 링크 — 여기서 undefined reference 오류가 남
ldd a.out이 실행 파일이 실행 시 필요로 하는 .so 목록
gcc -static정적 링킹 — 라이브러리를 바이너리에 포함
LD_LIBRARY_PATH=…동적 링커가 .so 를 찾는 경로 추가