크래프톤 정글 (컴퓨터 시스템: CSAPP)/7장 링커

컴퓨터 시스템 : CSAPP 7장 정리 - 7.13 ~ 7.15

고웅 2025. 4. 18. 08:00

7.13 라이브러리 인터포지셔닝 (Library Interpositioning)

라이브러리 인터포지셔닝공유 라이브러리 함수 호출을 가로채어 자신이 정의한 코드로 대체할 수 있게 해주는 강력한 기술이다. 이를 통해 다음과 같은 작업이 가능하다:

  • 특정 함수 호출 횟수 추적
  • 함수의 입력/출력 값 검증 및 기록
  • 특정 함수를 완전히 다른 구현으로 대체

기본 개념

인터포지셔닝은 다음 세 단계를 포함한다:

  1. 대상 함수와 동일한 시그니처의 래퍼(wrapper) 함수를 만든다.
  2. 특정 메커니즘을 이용해 시스템이 원래 함수가 아닌 래퍼를 호출하게 한다.
  3. 래퍼 함수는 자체 로직을 실행한 후 원래 함수를 호출하고 결과를 반환한다​.

인터포지셔닝의 세 가지 방식

1. 컴파일 타임 인터포지셔닝 (Compile-Time Interpositioning)

  • C 전처리기를 사용하여 malloc 같은 함수 호출을 mymalloc으로 치환
  • 예: #define malloc(size) mymalloc(size)
  • mymalloc.c에 래퍼 함수 정의
gcc -DCOMPILETIME -c mymalloc.c
gcc -I. -o intc int.c mymalloc.o

실행 예:

./intc
malloc(32)=0x9ee010
free(0x9ee010)

2. 링크 타임 인터포지셔닝 (Link-Time Interpositioning)

  • 링커 옵션 --wrap을 사용하여 함수 심볼을 가로챔
  • malloc → __wrap_malloc, __real_malloc → malloc 식으로 매핑
gcc -DLINKTIME -c mymalloc.c
gcc -c int.c
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o intl int.o mymalloc.o

실행 예:

./intl
malloc(32) = 0x18cf010
free(0x18cf010)

3. 런타임 인터포지셔닝 (Run-Time Interpositioning)

  • LD_PRELOAD 환경 변수를 이용해, 런타임에 특정 .so 파일을 우선적으로 로딩
  • 실행 시 동적 링커가 먼저 이 라이브러리에서 심볼을 찾음
gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
gcc -o intr int.c
LD_PRELOAD="./mymalloc.so" ./intr

실행 예:

malloc(32) = 0x1bf7010
free(0x1bf7010)

이 방식은 원본 실행 파일을 수정하지 않고도 인터포지셔닝이 가능하다는 점에서 매우 유용하다​.


7.14 객체 파일 조작 도구들

리눅스 시스템에는 객체 파일을 이해하고 조작하기 위한 다양한 도구들이 제공되며, 특히 GNU에서 제공하는 binutils 패키지는 매우 유용하다. 이 도구들은 ELF 객체 파일을 분석하거나 변형하는 데 사용된다.

주요 도구들

도구 설명
ar 정적 라이브러리를 생성하고, 멤버 객체 파일을 삽입/삭제/추출/목록화
strings 객체 파일 내의 출력 가능한 문자열만 추출
strip 심볼 테이블 정보를 객체 파일에서 제거
nm 객체 파일의 심볼 테이블에 정의된 심볼 목록 출력
size 각 섹션의 이름과 크기 출력
readelf ELF 헤더의 전체 구조 출력, size, nm의 기능 포함
objdump 객체 파일 전체 정보를 보여주는 강력한 도구
  • 가장 유용한 기능은 .text 섹션의 기계어 명령어를 디어셈블링하는 것

공유 라이브러리 관련 도구

도구 설명
ldd 실행 파일이 실행 시 필요한 공유 라이브러리 목록을 출력

예시 :

ldd ./prog2l

출력:

libvector.so => ./libvector.so (0x00007f...)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)

7.15 요약 (Summary)

링킹이란?

링킹(Linking)은 다양한 코드와 데이터 조각들을 하나의 파일로 모아, 메모리에 로드되어 실행 가능한 형식으로 만드는 과정이다.
링킹은 다음 세 시점 중 하나에서 수행될 수 있다:

  1. 컴파일 타임 (정적 링커)
    • 목적 파일들을 하나의 실행 파일로 결합
  2. 로딩 타임 (동적 링커)
    • 프로그램이 메모리에 로드될 때 공유 라이브러리를 연결
  3. 런타임 (애플리케이션 수준 동적 링커)
    • 실행 중 필요한 라이브러리를 dlopen을 통해 로딩

객체 파일 형식

링커가 다루는 객체 파일은 세 가지 유형이 있다:

  • 재배치 가능 객체 파일(Relocatable)
    → 정적 링커의 입력
  • 실행 객체 파일(Executable)
    → 메모리에 바로 로드되어 실행 가능
  • 공유 객체 파일(Shared)
    → 동적 링커가 로드하고 연결하는 라이브러리

링커의 주요 작업

  1. 심볼 결합(Symbol Resolution)
    • 각 전역 심볼을 정확한 정의로 연결
  2. 재배치(Relocation)
    • 각 심볼에 실행 시간 주소를 부여하고 코드 내 참조를 수정

링커 동작 방식 요약

  • gcc 같은 컴파일러 드라이버는 내부적으로 정적 링커를 호출
  • 여러 목적 파일이 동일한 심볼을 정의할 수 있으며, 링커는 암묵적인 규칙으로 이를 처리
  • 공유 라이브러리는 위치 독립 코드(PIC)로 작성되어야 하며, 여러 프로세스에서 메모리 공유 가능

실행과 동적 링킹

  • 실행 파일은 로더(loader)에 의해 메모리에 매핑됨
  • 동적 링커는 실행 파일과 함께 로드되어, 미해결 심볼을 공유 라이브러리에서 해결
  • 응용 프로그램은 dlopen 등의 함수로 직접 동적 링킹을 수행할 수도 있음​.

마무리 요약

링킹은 컴파일러, 운영체제, 하드웨어와 긴밀하게 연결된 매우 중요한 시스템 작업이다.
링커를 이해하면:

  • 링커 에러를 효과적으로 디버깅할 수 있고,
  • 복잡한 프로그램 아키텍처 설계에 능숙해지며,
  • 공유 라이브러리 및 모듈 시스템 구현이 가능해진다.