(compile process) Shared Library
windows에서 lib과 dll을 사용하듯, Linux-based system에서도 비슷한 개념의 라이브러리가 존재한다. 크게 static library와 shared library로 나뉘며, shared library가 다시 link 시점에 따라 나뉜다.
Linking과 Loading의 차이
Linking
All of the object files and any libraries are linked together to make your final program. For static libraries, the actual library is placed in your final program, while for shared libraries, only a reference to the library is placed inside.
Now you have a complete program that is ready to run.
Loading
This stage happens when your program starts up. Your program is scanned for references to shared libraries. Any references found are resolved and the libraries are mapped into your program.
Virtual Address Space에 mapping하고, reference를 resolve한다. library를 참조하는 주소들이 실제로 결정되는 시점이다. 이때 reference를 resolve하는 것을 library와 link한다고 말하면 헷갈릴 수 있다. 개념적으로는 library와 실행파일을 연결한다고 볼 수 있으므로 그렇게 말하는게 틀리지는 않은 것 같은데, 다른 의미로 사용되는 용어이므로 link라고 하지 않는 편이 좋겠다.
library type
- Static libraries (.a) : Library of object code which is linked with, and becomes part of the application.
- Dynamically linked shared object libraries (.so) : There is only one form of this library but it can be used in two ways. ① Dynamically linked at run time but statically aware. The libraries must be available during compile/link phase. The shared objects are not included into the executable component but are tied to the execution. ② Dynamically loaded/unloaded and linked during execution using the dynamic linking loader system functions.
정적 라이브러리
.a의 확장자를 가지며 컴파일시 링크된다. static 컴파일이라고 생각하면 된다.
공유 라이브러리
확장자는 .so이고 일반적으로 사용하는 공유 라이브러리 라고 생각하면 되며, 프로그램이 시작될 때 link된다. 공유 라이브러리를 사용하게 되면 프로세스가 시작될 때 ld.so
라는 링커를 실행하게 된다. ld.so
는 /etc/ld.so.conf
, LD\_LIBRARY_PATH
환경변수를 참고하여 프로세스가 필요로 하는 공유 라이브러리를 찾아 메모리에 루틴을 load(적재) 한다. 이를 gcc의 -L
옵션과 혼동해서는 안된다. -L
옵션은 compile시 link 하기 위해 공유 라이브러리 파일을 찾기 위한 경로를 명시해주는 옵션이므로 실행시점에 적재하기 위해 찾는 경로와는 다르다. 그러니까, compile시 link를 통해 라이브러리를 loading할 준비를 하고 실행시 준비한 대로 loading하는 것이다.
ld.so.conf
를 수정하는 경우 ldconfig
명령을 입력하는 등 캐시를 갱신해주는 작업이 필요하다. ldconfig -v
로 공유 라이브러리 탐색 경로를 확인할 수 있다. 컴파일시 -rpath
옵션을 주는 방식으로도 load path를 지정할 수 있지만 잘 사용하지 않는다.
헤더파일을 만들 경우, <>
말고 ""
로 지정해야 한다. #include "foo.h"
동적 라이브러리
프로그램이 동작하는 도중에
1
2
3
4
5
6
7
8
#include <dlfcn.h>
void \*dlopen(const char \*filename, int flag);
const char \*dlerror(void);
void \*dlsym(void \*handle, char \*symbol);
int dlclose(void \*handle);
등을 호출하면서 라이브러리를 link하게 된다. 동적 라이브러리라는게 새로운 라이브러리 파일 형식이 아니고, 공유 라이브러리를 프로그램 시작시점이 아니라 런타임 도중 load 및 link하는 것 뿐이다.
Usage
- 동적으로 로드할 공유 라이브러리를 제작한다.
libshell.c :
1
2
3
4
5
6
7
\_\_attribute\_\_((constructor))
void constructorPrint(){
printf("Ok, loaded\n\n");
}
void normalPrint(){
printf("normalPrint\n\n");
}
\_\_attribute\__((constructor))
가 붙은 함수는 library가 로딩되면서 바로 호출된다.
2017/01/13 - [System/LINUX & UNIX] - 생성자, 소멸자 / GCC __attribute__((constructor / destructor))
- 컴파일
- 적당한 디렉토리로 옮긴 다음 이를 library path에 추가한다. 이 경우
ld.so.conf
를 이용했다.
- 프로그램을 작성한다. dlopen 등을 이용해 라이브러리를 로드할 수 있도록 한다.
tttsym.c:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <dlfcn.h>
int main(){
void \*handle;
void (\*func)(); //function pointer
handle = dlopen("/tmp/libshell.so", RTLD\_NOW);
func = dlsym(handle, "normalPrint");
func();
getchar();
return 0;
}
1
void \*dlopen(const char \*filename, int flag);
리턴값은 library file의 handle
이다. RTLD\_LAZY
와 RTLD_NOW
둘 중 하나는 반드시 dlopen()
의 flag
에 포함되어야 하며, RTLD\_GLOBAL, RTLD\_LOCAL, RTLD\_NODELETE, RTLD\_NOLOAD, RTLD_DEEPBIND
를 추가로 정의할 수 있다. RTLD_LAZY
는 symbol을 참조하는 코드가 실행될 때, resolution이 일어난다. 그래서 symbol 이 참조되지 않으면 resolution되지 않는다. * 이는 function references에 대해서만 성립한다. references to variables는 shared library가 loading되면서 즉시 bound된다. RTLD_NOW
는 dlopen()
이 호출되면, 리턴하기 전에 모든 undifined symbols에 대해 resolution이 일어난다.
1
void \*dlsym(void \*handle, char \*symbol);
symbol에 function name 등을 적으면 된다. 리턴값은 symbol에 대한 function pointer다. 따라서 리턴값을 담을 function pointer를 선언 시 void \*
로 선언하지 말고 리턴받을 함수의 리턴과 파라미터에 맞춰서 선언하는 것이 좋다. handle
에는 보통 dlopen()
으로 return받은 handle
을 넘기게 되지만, 다음 두가지 pseudo-handle이 올 수 있다. RTLD_DEFAULT
default shared object search order에 따라 symbol 이름과 일치하는 첫번째 함수를 반환한다. RTLD_NEXT
search order에 따라 symbol 이름과 일치하는 다음 함수를 반환한다. 이를 이용해 LD_PRELOAD
에서 원래 함수의 포인터를 얻을 수 있다.
- 컴파일 하고 실행하면 된다.
\_\_attribute\__((constructor))
의 영향으로 OK, loaded가 바로 출력되었고, 그 후dlsym()
으로 받은function pointer ( func() )
를 호출하여 normalPrint가 출력되었다.
ld
ld는 loader의 줄임말 일 것 같지만 GNU Linker다.
1
2
3
4
man ld
man ld.so
ldd filename