Child pages
  • 동적 라이브러리(shared library)와 Linker/Loader 이해하기

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

이제는 직접 C 언어를 사용하여 개발을 할 일이 많지 않지만 C 언어로 만들어진 프로그램과 라이브러리는 여전히 서비스 인프라에서 중요한 위치를 차지하고 있습니다.

 


linux 등 운영체제가 C 로 만들어져 있고 편리하게 개발을 할수 있는 생산성 좋은 script(ruby 나 python, PHP 등..) 언어의 엔진들은 대부분 C 로 제작되었고 openssl, database driver 등의 기능은 C 로 작성된 라이브러리를 호출하여 언어의 기능을 확장하고 있습니다.

...

그래서 프로그램의 동작 방식을 이해하기 위한 공유 라이브러리(shared library, 또는 동적 라이브러리라고도 합니다.)와 프로그램 로더에 대해서 다뤄볼까 합니다.

 


공유 라이브러리(shared library)

컴파일러는 무슨 일들을 할까.

...

위와 같은 hello.c 소스가 있을 경우 gcc  hello.c 명령어로 컴파일을 하면 컴파일러는 다음과 같은 절차를 거쳐서 실행 파일을 생성하게 됩니다. (괄호 안에 있는 것은 gcc 에서 해당 기능을 수행하는 명령어입니다.)

 


  1. C Pre Processor(cpp) 가 #define, #include 구문등을 전처리하여 hello.i 생성

    Code Block
    cpp hello.c > hello.i
  2. C compiler(cc1) 가 전처리한 hello.i 소스를 어셈블리로 컴파일하여 hello.s 생성

    Code Block
    gcc -S hello.i
  3. assembler(as) 는 hello.s 를 어셈블하여 object (hello.o) 생성

    Code Block
    as -o hello.o hello.s
  4. linker(collect2) 는 printf 등 외부 library 에 있는 symbol 을 링크해서 최종 프로그램 생성(a.out)

     


gcc는 위와 과정을 간편하게 해주는 wrapper 역할을 수행하며 -v 옵션을 주고 실행할 경우 이 모든 과정을 확인할 수 있습니다.

Code Block
gcc -v hello.c

 


공유 라이브러리

printf 같이 프로그램마다 자주 사용하는 함수를 실행 프로그램에 포함시킬 경우(이렇게 만드는 것을 정적 링크; static linking 이라고 부릅니다.)  프로그램의 덩치가 커지고 외부 라이브러리가 업그레이드 됐을 경우 이를 사용하는 프로그램을 다시 컴파일해야 하는 부담이 있습니다.

 


그래서 라이브러리를 공유 라이브러리(shared library)라는 형식으로 만들어 놓고 컴파일 시점에 사용할 라이브러리를 연결만 하는 방법을 사용합니다. 

Note

windows 에서는 공유 라이브러리대신 DLL(Dynamic Link Library) 라고 부릅니다.

 


어떤 프로그램이 공유 라이브러리를 사용하는지 알아보려면 리눅스에는 file 명령어를 사용하면 되며 아래는 공유 라이브러리를 사용했을 때 결과입니다.

Panelcode
languagebash
$ file a.out


a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped

 



정적 링크된 프로그램은 아래와 같이 표시합니다.

Panelcode
$ file a.out



a.out: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.18, not stripped

 


dynamic loader

공유 라이브러리와 연결된 프로그램을 실행하면 내부적으로 dynamic loader라는 프로그램이 먼저 동작하여 대략 다음과 같은 작업을 실행합니다.

  1. dynamic link 된 공유 라이브러리를 찾아서 메모리에 로딩
  2. entry function (C 언어일 경우 main 함수)를 찾아서 호출
  3. 프로그램 실행

 


각 운영체제의 로더 이름은 아래와 같습니다.

운영체제로더
Linuxld.so 또는 ld-linux.so
Mac OS Xdyld
Solarisld.so
HP-UXdld.so

...

운영체제환경 변수비고
WindowsPATHWindows에서 DLL을 찾는 데 사용되는 검색 경로 순서

Linux

LD_LIBRARY_PATHLD_LIBRARY_PATH 가 설정되지 않아도 /lib64, /usr/lib64 폴더는 기본적으로 설정됩니다.
Mac OS X

DYLD_LIBRARY_PATH

LD_LIBRARY_PATH 와 동일 역할

DYLD_FALLBACK_LIBRARY_PATH

라이브러리를 못 찾을 경우 검색할 경로.
$(HOME)/lib:/usr/local/lib:/lib:/usr/lib 로 설정되어 있음

 


사용할 일이 많지는 않겠지만 상용 유닉스의 경우 아래와 같은 환경 변수를 사용합니다.

...

바이너리에 rpath가 있는지 여부는 readelf 명령어를 사용하여 파일의 헤더를 확인하면 됩니다.


Panelcode
languagebash
$ readelf -d a.out | grep
RPATH 0x000000000000000f (RPATH)              Library rpath:
 RPATH


 0x000000000000000f (RPATH)              Library rpath: [/usr/local/lib]


LD_PRELOAD

LD_PRELOAD 환경 변수는 라이브러리의 후킹(hooking)이 필요할 때 사용하는 환경 변수로 이게 설정되어 있으면 여기에 지정된 라이브러리내 함수를 먼저 호출해 줍니다.

...

그래서 프로그램이 의존하는 shared library 는 무엇이고 로더가 어떻게 찾는지를 이해하는 것은 에러 처리를 위해 꼭 필요한 지식입니다.

 


운영체제 별로 의존하는 shared library를 찾는 방법은 다음과 같습니다.

...

Code Block
$ ldd /usr/bin/vim


 linux-vdso.so.1 =>  (0x00007ffd9167b000)
 libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd8375d5000)
 libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fd8373ac000)
 libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fd837189000)
 libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x00007fd836f81000)
 libgpm.so.2 => /usr/lib/x86_64-linux-gnu/libgpm.so.2 (0x00007fd836d7a000)

 


좌측에 있는 항목(예: libm.so.6) 이 의존하는 라이브러리 이름이며 우측에 있는 항목이 로딩할 실제 라이브러리(예: /lib/x86_64-linux-gnu/libm.so.6) 입니다.

...

사용자 계정으로는 잘 실행되는데 cron 에 등록하여 프로그램을 실행할 때 제대로 실행이 안 된다면 대부분의 원인은 바로 해당 프로그램을 실행하는데 필요한 동적 라이브러리를 못 찾아서입니다.

 


이럴 경우 처리 방안은 cron에서 실행하는 프로그램을 별도의 쉘 스크립트를 통해서 구동하고 그 안에서 PATH, LD_LIBRARY_PATH 변수를 설정하고 실행하는 것입니다.

...