컴퓨터 시스템 7장-링커(Linking) 1-2

2025. 4. 19. 10:24개념 공부

 7.6 링커는 여러 심볼을 어떻게 병합할까?

링커는 여러 개의 .o 파일을 하나의 실행 파일로 만들 때, 같은 이름의 심볼이 여러 파일에 있을 수 있다는 걸 고려해!

그래서 심볼의 우선순위 (강한/약한)을 기준으로 어떤 걸 쓸지 판단해.

정적 라이브러리란?

  • .a 확장자를 가진 아카이브 파일
  • 여러 개의 .o 파일을 묶은 것
  • ar 명령어로 생성
ar rcs libfoo.a foo1.o foo2.o
  • r: 교체
  • c: 생성
  • s: 심볼 테이블 추가
gcc -L. -o main main.o -lfoo
  • -L. : 현재 디렉토리에서 라이브러리 검색
  • -lfoo : libfoo.a 사용

 매우 중요한 주의점

  • 링커는 왼쪽 → 오른쪽 순서로 처리!
gcc -lfoo main.o   ❌ 링크 실패
gcc main.o -lfoo   ✅ 올바른 순서
항목 설명
정적 라이브러리 .a 확장자, 여러 .o 묶음
생성 도구 ar rcs libfoo.a ...
링크 명령 gcc main.o -lfoo
특징 필요한 .o만 꺼내 링크
주의 main.o → -lfoo 순서 중요

7.8 실행 가능한 오브젝트 파일 (Executable Object File)

  • 링커가 .o 파일들을 합쳐 만든 최종 실행 파일
  • 보통 a.out, prog, .exe 등으로 생성됨

ELF 구조

┌────────────────────────────┐
│ ELF Header                │ ← 진입점 등 정보
├────────────────────────────┤
│ Program Header Table      │ ← 로딩 정보
├────────────────────────────┤
│ .text (.init 포함)        │ ← 실행 코드
├────────────────────────────┤
│ .rodata                   │ ← 상수 문자열 등
├────────────────────────────┤
│ .data                     │ ← 초기값 있는 전역 변수
├────────────────────────────┤
│ .bss                      │ ← 초기값 없는 전역 변수
├────────────────────────────┤
│ .symtab, .debug           │ ← 심볼 및 디버깅 정보
└────────────────────────────┘

 실행 시

  • .text + .rodata → 읽기/실행 세그먼트
  • .data + .bss → 읽기/쓰기 세그먼트

로딩 과정 (7.9)

  1. 디스크에서 실행파일 읽음
  2. ELF 헤더 분석
  3. 세그먼트들을 메모리에 배치
  4. Entry Point로 점프 (보통 _start()) → main() 호출

 실행 중 메모리 구조 (x86-64)

주소 ↓ 낮음
─────────────────────
코드 세그먼트 (.text)
읽기 전용 데이터 (.rodata)
초기화된 데이터 (.data)
초기화 안된 데이터 (.bss)
힙 영역 (malloc)
[ 중간 공간 ]
공유 라이브러리
스택 (위 → 아래 성장)
─────────────────────
주소 ↑ 높음
  • ASLR로 인해 주소는 매 실행마다 달라질 수 있음 (보안)

 7.11 런타임 공유 라이브러리 로딩

 질문: 실행 중에 .so 파일을 불러와 사용할 수 있을까?

➡ 가능함! dlopen, dlsym, dlclose 함수 사용 (<dlfcn.h>)

실전 예시

  • DLL 업데이트
  • 웹서버 동적 처리
  • 플러그인 시스템
함수 설명
dlopen() 라이브러리 열기
dlsym() 함수 주소 얻기
dlclose() 라이브러리 닫기
dlerror() 에러 메시지 확인

7.12 위치 독립 코드 (PIC)

문제: 공유 라이브러리는 어디든 로드될 수 있는데, 주소가 고정돼 있지 않음

해결 방법 👉 PIC + PLT + GOT

이름 설명
PLT 함수 호출을 위한 중간 점프 테이블
GOT 주소 테이블 (함수/데이터의 실제 주소 저장)
RIP-relative 현재 명령어 위치 기준으로 접근 (x86-64 특징)

printf() 호출 흐름

  1. call printf@PLT 실행
  2. → PLT는 GOT의 printf 주소를 참조
  3. → 처음엔 동적 링커 호출, 이후엔 실제 printf 주소 저장

 

 

 라이브러리 인터포지셔닝이란?

실행 중에 공유 라이브러리 함수 호출을 '가로채서'
내가 만든 함수(wrapping function)가 먼저 실행되게 만드는 기술이야!

대표적인 예: malloc(), free() 같은 함수 대신 my_malloc()을 실행시키는 경우

주요 사용 목적

  • 함수 호출 로그 추적 / 모니터링
  • 성능 측정 및 최적화
  • 디버깅
  • 라이브러리 함수 기능 변경 (ex: 고속 메모리 할당기)

 인터포지셔닝 3가지 방식

① 컴파일 타임 인터포지셔닝

  • 전처리기(#define)를 이용해서 함수 호출 자체를 바꿔치기함
  • 함수를 대체할 헤더 파일을 만들어 사용
#define malloc(size) mymalloc(size)

 컴파일할 때 함수 이름이 아예 바뀌는 방식

gcc -DCOMPILETIME -c mymalloc.c
gcc -I. -o prog prog.c mymalloc.o

 특징

  • 간단하고 제어 쉬움
  • 하지만 소스코드 수정 및 재컴파일 필요

② 링크 타임 인터포지셔닝

  • --wrap 옵션을 사용해서 링커가 malloc → __wrap_malloc으로 심볼 교체
  • 내가 만든 __wrap_malloc()이 호출되고, 진짜 함수는 __real_malloc()로 호출 가능
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o prog prog.o mymalloc.o

특징

  • 소스코드 수정 없이도 가능
  • 컴파일할 때 링커 옵션 필요

예시 함수 코드


// mymalloc.c
void *__wrap_malloc(size_t size) {
  printf("[LOG] malloc called with size %zu\n", size);
  return __real_malloc(size);
}

③ 런타임 인터포지셔닝 (LD_PRELOAD)

  • 가장 강력하고 유연한 방법!
  • LD_PRELOAD 환경변수를 이용해서 공유 라이브러리를 먼저 로딩
  • 내가 만든 malloc()이나 free()가 먼저 호출됨
gcc -shared -fpic -o mymalloc.so mymalloc.c -ldl
LD_PRELOAD=./mymalloc.so ./myprog

특징

  • 실행 파일 수정 불필요!
  • 동적 링커가 내가 만든 .so를 먼저 로딩함

예시 함수 코드

// mymalloc.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

void *malloc(size_t size) {
  static void *(*real_malloc)(size_t) = NULL;
  if (!real_malloc)
    real_malloc = dlsym(RTLD_NEXT, "malloc");

  printf("[HOOKED] malloc(%zu)\n", size);
  return real_malloc(size);
}

실행 방법

LD_PRELOAD=./mymalloc.so ./myprog

 작동 원리 정리

  1. 내가 만든 .so 파일 안에 malloc(), free() 등을 재정의
  2. LD_PRELOAD로 이 파일을 먼저 로딩시킴
  3. 프로그램이 해당 함수를 호출하면 → 내 함수가 먼저 실행됨
  4. 그 안에서 dlsym()으로 진짜 함수 주소를 얻어 다시 호출 가능

 비교 요약

방식 설명 장점 단점
컴파일 타임 전처리기 이용 함수 이름 바꿈 간단함 소스코드 수정 필요
링크 타임 --wrap 옵션으로 심볼 교체 소스 수정 없이 사용 가능 링커 옵션 필요
런타임 (LD_PRELOAD) 실행 시 동적 링커가 먼저 내 .so 로딩 가장 유연, 코드 수정 불필요 환경변수 사용 필요

 활용 예시

  • 성능 분석기: malloc/free 호출 시간 측정
  • 메모리 누수 추적: 호출 로그를 남겨서 추적
  • 보안 검사기: 특정 함수의 인자 값을 검사
  • 게임 치트 방지: 의심 함수 감시

 즉, 프로그램을 건드리지 않고 외부에서 제어하거나 분석하고 싶을 때 매우 강력한 기술!

'개념 공부' 카테고리의 다른 글

컴퓨터 네트워킹 & 소켓 인터페이스  (0) 2025.05.07
컴퓨터 시스템 8장 시그널  (0) 2025.04.21
컴퓨터 시스템 7장-링커(Linking) 1-1  (0) 2025.04.19
포인터  (0) 2025.04.14
컴퓨터 시스템에서의 가상화  (1) 2025.04.12