본문 바로가기

IT/Basic

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

 


 

 

 


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

 

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

운영체제에서 프로그램이 어떻게 동작하는지 이해하는 것은 견고한 서비스를 만들고 문제가 생겼을 때 대응을 하는데 도움이 되는 지식이지만  바쁜 시대에서 이런 지식을 체계적으로 학습하기는 쉽지가 않습니다.

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

 

공유 라이브러리(shared library)

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

 

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

 

 

 

공유 라이브러리

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

 

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

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

 

어떤 프로그램이 공유 라이브러리를 사용하는지 알아보려면 리눅스에는 file 명령어를 사용하면 되며

아래는 공유 라이브러리를 사용했을 때 결과입니다.

 

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

 

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

$ 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. 프로그램 실행

 

어디에서 shared library 를 찾을까?

LD_LIBRARY_PATH

loader 는 program 구동에 필요한 shared library 를 찾을 때 LD_LIBRARY_PATH(리눅스의 경우)와 같은 고유의 환경 변수를 참고하며 OS 마다 경로를 찾는 순서가 다릅니다.

운영체제환경 변수비고

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

  • Solaris : LD_LIBRARY_PATH, 
  •  AIX: LIBPATH
  • HP-UX: SHLIB_PATH

RPATH

리눅스는 ELF 형식의 바이너리는  rpath 라고 부르는 "실행시 라이브러리를 찾을 경로 정보"를 컴파일 시점에 넣어줄 수 있으며 이 정보는 LD_LIBRARY_PATH 환경 변수 전에 참고됩니다.

rpath 정보는 아래와 같이 gcc 를 실행할 때 -Wl,-rpath 옵션을 추가해 주면 됩니다.

 

LD_PRELOAD

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

대표적인 용도로는 debugging 때문에 구동시에 특정 동적 library 를 변경해야할 경우등에 사용합니다.

예로 메모리 leak을 찾는데 사용하는 memory debugger는 runtime 에 기존 프로그램에서 사용한 malloc/free 를 debugger 가 구현한 함수로 대체해서 동작하기 위해 LD_PRELOAD 환경 변수를 사용하여 디버깅을 수행합니다.

라이브러리 의존성 확인

환경 변수가 잘못 설정되어 있거나 프로그램 설치를 잘못했거나 등의 이유로 프로그램 실행시 "Can't load library libevent.so"와 비슷한 류의 에러를 만나면 경험이 없을 경우 머리털을 쥐어 뜯게 됩니다.

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

 

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

Windows

Dependency Walker 가 32/64 Windows에서 모두 사용 가능하고 공개이며 유용하니 이 프로그램을 사용하여 라이브러리의 의존성을 확인하세요. 

Linux

특정 프로그램이 참고하는 shared library를 확인하려면 ldd 명령을 사용하면 됩니다.

 

 

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

라이브러리가 없다면 우측 항목에는 대신 "not found" 라는 문구가 표시되며 프로그램을 실행하면 에러가 발생하며 이 경우 /lib64, /usr/lib64 에 해당 라이브러리가 있는지 확인한 후에

없을 경우 해당 라이브러리가 있는 경로를 LD_LIBRARY_PATH 환경 변수에 추가해 주면 됩니다.

 

 

프로그램에서 dynamic link를 하려면?

프로그램내에서도 런타임에 사용할 라이브러리와 함수를 동적으로 로딩할 수 있습니다.

windows 에서는 LoadLibrary 를 사용하여 dll 을 로딩한 후에 GetProcAddress 로 실행하려는 함수의 주소를 얻은 후에 호출할 수 있으며 리눅스는 dlopen, dlsym 함수를  사용하면 됩니다.

각 운영체제마다 dynamic link를 처리하는 함수와 호출 방식이 상이하므로 이식성 좋은 프로그램을 작성하려면 GNU의 libtool에 포함되어 있는 libltdl 을 사용하면 됩니다.

 

 

www.lesstif.com/software-architect/shared-library-linker-loader-12943542.html

 

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

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

www.lesstif.com

 

'IT > Basic' 카테고리의 다른 글

크로스 컴파일(cross compile)  (0) 2021.01.07
라이브러리 .a 파일 .so 파일  (0) 2020.10.22
[Basic] DRAM, SRAM  (0) 2020.10.14
aarch64  (0) 2020.09.23
arm architecture와 x86 architecture  (0) 2020.09.23