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

컴퓨터 시스템 : CSAPP 7장 정리 - 7.11 ~ 7.12

고웅 2025. 4. 18. 07:53

7.11 애플리케이션에서 공유 라이브러리 로딩 및 링킹

지금까지는 프로그램이 시작되기 전에 동적 링커가 .so 파일을 로드하고 심볼을 해결하는 과정만 다뤘다. 그러나 실제로는 프로그램이 실행된 이후, 즉 런타임 중에 공유 라이브러리를 로드하고 연결할 수도 있다.

런타임 동적 링킹의 유용성

 

  • 소프트웨어 배포
    • 윈도우 프로그램은 자주 사용되는 기능을 DLL로 나눠서 제공한다.
    • 새로운 DLL 버전을 배포하면, 사용자는 프로그램을 다시 컴파일하지 않고도 자동으로 새로운 기능을 사용할 수 있다.
  • 고성능 웹 서버
    • 초기 웹 서버는 CGI를 위해 fork와 execve로 외부 프로그램을 호출했다.
    • 현대 서버는 요청마다 동적 함수를 라이브러리로부터 로드하고 함수 포인터로 직접 호출한다.
    • 함수는 서버 메모리에 캐시 되며, 이후 요청에서는 단순한 함수 호출로 처리된다​.

주요 시스템 함수들 (<dlfcn.h>)

#include <dlfcn.h>

void *dlopen(const char *filename, int flag);
void *dlsym(void *handle, char *symbol);
int dlclose(void *handle);
const char *dlerror(void);

 

  • dlopen: .so 파일을 열고 로드
    • RTLD_LAZY: 사용 시에만 심볼 해석
    • RTLD_NOW: 즉시 모든 심볼 해석
  • dlsym: 특정 심볼(함수/변수)의 주소 획득
  • dlclose: 열었던 .so를 닫음
  • dlerror: 오류 메시지 반환

예제 코드

handle = dlopen("./libvector.so", RTLD_LAZY);
addvec = dlsym(handle, "addvec");
addvec(x, y, z, 2);
dlclose(handle);

이 코드는 libvector.so 안의 addvec() 함수를 런타임에 로드하고 호출한 후, 다시 언로드 하는 구조다​.

컴파일 명령:

gcc -rdynamic -o prog2r dll.c -ldl

 

  • -rdynamic: 실행 파일의 전역 심볼이 동적 링커에게 보이도록 설정
  • -ldl: 동적 링커 라이브러리 사용

7.12 위치 독립 코드 (Position-Independent Code, PIC)

공유 라이브러리(shared library)는 여러 프로세스가 같은 코드 복사본을 메모리에 공유할 수 있도록 설계되어야 한다. 이를 위해, 코드가 메모리의 어느 위치에도 문제없이 로드 가능해야 하며, 이때 필요한 개념이 바로 위치 독립 코드(PIC)다.

고정 주소 방식의 문제점

과거 방식에서는 각 공유 라이브러리에 대해 고정된 주소 공간을 미리 할당하려 했다. 하지만 이는 여러 가지 문제가 있다:

  • 주소 공간 낭비
  • 라이브러리 추가/수정 시 주소 충돌
  • 시스템마다 주소 배치가 달라짐 → 관리 복잡성 증가

PIC로 해결

현대 시스템에서는 공유 모듈의 코드 세그먼트어디든 로드 가능하게 컴파일한다. 이렇게 하면 하나의 코드 복사본을 여러 프로세스가 공유할 수 있고, 각 프로세스는 자신만의 데이터 세그먼트를 사용한다.

PIC는 gcc -fpic 옵션으로 생성되며, 공유 라이브러리는 반드시 PIC로 컴파일해야 한다​.

내부 심볼 참조: PC-relative 방식

  • 같은 모듈 내 심볼 참조는 특별한 처리 없이 PC-relative 주소 방식으로 해결할 수 있다.
  • 정적 링커가 컴파일 시점에 처리

PIC에서의 전역 변수 참조: GOT(Global Offset Table)

기본 원리

 

  • 코드 세그먼트와 데이터 세그먼트는 항상 일정한 거리를 유지
  • 이를 활용해 코드에서 데이터까지의 거리(오프셋)를 상수로 사용 가능

구현 방식

 

  • OT(Global Offset Table) 생성
    • 각 전역 변수나 함수의 주소를 담는 테이블
    • 데이터 세그먼트의 앞부분에 위치
    • 각 항목은 8바이트 주소
  • 컴파일 시점
    • 각 전역 변수 참조는 GOT 엔트리 참조로 변환됨
    • GOT 엔트리마다 재배치 정보도 함께 생성
  • 실행 시점
    • 동적 링커가 GOT의 각 항목을 실제 주소로 채움

예시

addvec:
  mov 0x2008b9(%rip), %rax   # %rax = *GOT[3] = &addcnt
  addl $0x1, (%rax)          # addcnt++

 

 

 

 

  • addvec는 addcnt 전역 변수를 GOT을 통해 간접적으로 참조
  • GOT[3]에는 addcnt의 주소가 있고, 런타임에 addcnt++가 수행됨
  • addcnt가 다른 공유 라이브러리에 있을 수도 있기 때문에 GOT 사용은 일반적인 해결책이 된다.