크래프톤 정글 (컴퓨터 시스템: CSAPP)/7장 링커
컴퓨터 시스템 : CSAPP 7장 정리 - 7.13 ~ 7.15
고웅
2025. 4. 18. 08:00
7.13 라이브러리 인터포지셔닝 (Library Interpositioning)
라이브러리 인터포지셔닝은 공유 라이브러리 함수 호출을 가로채어 자신이 정의한 코드로 대체할 수 있게 해주는 강력한 기술이다. 이를 통해 다음과 같은 작업이 가능하다:
- 특정 함수 호출 횟수 추적
- 함수의 입력/출력 값 검증 및 기록
- 특정 함수를 완전히 다른 구현으로 대체
기본 개념
인터포지셔닝은 다음 세 단계를 포함한다:
- 대상 함수와 동일한 시그니처의 래퍼(wrapper) 함수를 만든다.
- 특정 메커니즘을 이용해 시스템이 원래 함수가 아닌 래퍼를 호출하게 한다.
- 래퍼 함수는 자체 로직을 실행한 후 원래 함수를 호출하고 결과를 반환한다.
인터포지셔닝의 세 가지 방식
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)은 다양한 코드와 데이터 조각들을 하나의 파일로 모아, 메모리에 로드되어 실행 가능한 형식으로 만드는 과정이다.
링킹은 다음 세 시점 중 하나에서 수행될 수 있다:
- 컴파일 타임 (정적 링커)
- 목적 파일들을 하나의 실행 파일로 결합
- 로딩 타임 (동적 링커)
- 프로그램이 메모리에 로드될 때 공유 라이브러리를 연결
- 런타임 (애플리케이션 수준 동적 링커)
- 실행 중 필요한 라이브러리를 dlopen을 통해 로딩
객체 파일 형식
링커가 다루는 객체 파일은 세 가지 유형이 있다:
- 재배치 가능 객체 파일(Relocatable)
→ 정적 링커의 입력 - 실행 객체 파일(Executable)
→ 메모리에 바로 로드되어 실행 가능 - 공유 객체 파일(Shared)
→ 동적 링커가 로드하고 연결하는 라이브러리
링커의 주요 작업
- 심볼 결합(Symbol Resolution)
- 각 전역 심볼을 정확한 정의로 연결
- 재배치(Relocation)
- 각 심볼에 실행 시간 주소를 부여하고 코드 내 참조를 수정
링커 동작 방식 요약
- gcc 같은 컴파일러 드라이버는 내부적으로 정적 링커를 호출
- 여러 목적 파일이 동일한 심볼을 정의할 수 있으며, 링커는 암묵적인 규칙으로 이를 처리
- 공유 라이브러리는 위치 독립 코드(PIC)로 작성되어야 하며, 여러 프로세스에서 메모리 공유 가능
실행과 동적 링킹
- 실행 파일은 로더(loader)에 의해 메모리에 매핑됨
- 동적 링커는 실행 파일과 함께 로드되어, 미해결 심볼을 공유 라이브러리에서 해결
- 응용 프로그램은 dlopen 등의 함수로 직접 동적 링킹을 수행할 수도 있음.
마무리 요약
링킹은 컴파일러, 운영체제, 하드웨어와 긴밀하게 연결된 매우 중요한 시스템 작업이다.
링커를 이해하면:
- 링커 에러를 효과적으로 디버깅할 수 있고,
- 복잡한 프로그램 아키텍처 설계에 능숙해지며,
- 공유 라이브러리 및 모듈 시스템 구현이 가능해진다.