AIX 동적 라이브러리 컴파일 방법 (dynamic, *.so) - 1
genkld명령은 이미 시스템 메모리에 올라간 공유 오브젝트를 보는데 사용한다.
다음의 genkld8결과에서 이 테스트 시스템의 시스템 전역 메모리(system global memory)에 libc.a 로부터 세 개의 공유 오브젝트 모듈이 올라와 있음을 알 수 있다.
$ genkld | grep 'libc.a'
d2023070 1df /usr/lib/libc.a/dl.o
d012cd7a 1da /usr/lib/libc.a/pse.o
d01cfbe0 1e6257 /usr/lib/libc.a/shr.o
위의 예제에서 libc.a의 shr.o만 필요하지만 나머지 두개의 파일은 다른 프로그램에서 필요하기 때문에 올라와 있다.
다른 UNIX OS는 공유 라이브러리 (동적 링킹 라이브러리 혹은 DLL-dynamic link library 라고도 한다)가 실제로 공유 오브젝트인 반면 AIX는 한 개의 라이브러리 안에 여러 개의 공유 오브젝트를 포함할 수 있는 점이 AIX와 다른 UNIX OS와의 큰 차이점이라고 할 수 있다.
공유 라이브러리와 공유 오브젝트라는 용어는 다른 UNIX OS에서는 같은 의미로 사용할 수 있지만 AIX에서는 서로 다른 의미가 된다.
공유 오브젝트
공유 오브젝트는 XCOFF 헤더에 SHROBJ 플랙이 설정된 한 개의 오브젝트 파일이다.
AIX에서 공유 오브젝트는 보통 name.o 형식의 이름을 갖는데 AIX에서는 일반적으로 공유 오브젝트에 대해 다른 UNIX에서 사용하는 .so 확장자를 사용하지 않는다. 이 이름은 컴파일러에서 디폴트로 만드는 파일이름의 확장자이다.
공유 라이브러리
공유 라이브러리는 ar포맷으로 아카이브된 라이브러리 파일인 ar 명령으로 만든 파일을 말한다.
그 라이브러리 파일은 멤버로 하나 이상의 공유 오브젝트를 가진다. 그러나 아카이브 멤버로 공유 오브젝트 외에도 일반적인 비공유 오브젝트 파일도 있으며 이 파일은 링커에서 일반적인 방법으로 처리하게 된다. AIX에서 공유 라이브러리는 libname.a와 같은 형식의 이름이 된다.
링커는 파일이 올바른 오브젝트 파일인지 아닌지 알아보기 위해 파일의 매직넘버를 사용한다. 따라서 .o를 확장자로 하지 않는 파일을 공유 오브젝트로 사용할 수 있다.(물론 사용자가 헛갈릴 위험이 있다) 그러나 라이브러리는 반드시 libname.a과 같은 형식의 이름을 사용해야 하며 이렇게 하지 않으면 링커가 -l 옵션으로 지정한 라이브러리를 찾지 못한다.
AIX상에서 공유 오브젝트와 정적 오브젝트의 차이점에 대해서 알아보자.
AIX의 링킹/로딩 메커니즘에서는 공유 오브젝트와 정적 오브젝트를 사용하기 위해 AIX만의 파일이름형식을 사용하게 된다. 대부분의 UNIX OS에서 공유 오브젝트 파일은 “.so”확장자(shared object를 의미한다)를 사용하고 정적 오브젝트 파일은 “.o”확장자(object를 의미한다)를 사용한다.
그러나 AIX에서는 파일이 공유 오브젝트인지 정적 오브젝트인지에 관계없이 “.o”를 사용한다. 따라서 AIX에서는 단순히 확장자만 보고 이 파일이 공유 오브젝트 파일인지 정적 오브젝트 파일인지 구분할 수 없다. (AIX에서도 다른 UNIX OS처럼 파일확장자가 “so”인 공유 오브젝트를 지원하며 이 파일은 실시간 링킹에 사용된다.)
오브젝트 파일이 공유 오브젝트인지 정적 오브젝트인지 구분하려면 dump 명령을 사용해야 한다. dump 명령 뒤 Flags 줄에 SHROBJ 키워드가 나오는데 이 경우 이 오브젝트 파일은 공유 오브젝트이고 이 키워드가 안 나오면 정적 오브젝트이다.
$ dump -ov shr.o
shr.o:
***Object Module Header***
# Sections Symbol Ptr # Symbols Opt Hdr Len Flags
5 0x00251764 26925 72 0x3002
Flags=( EXEC DYNLOAD SHROBJ )
Timestamp = "Feb 03 08:59:14 2003"
Magic = 0x1df (32-bit XCOFF)
라이브러리의 아카이브 멤버가 공유 오브젝트인지 정적 오브젝트인지 알아보려면 dump 명령과 함께 -g 옵션(dump명령에서 -g 옵션을 사용하면 지정한 라이브러리에서 아카이브 멤버를 검사한다)을 사용해야 한다.
예를 들어 예제 2-4에서 frexp.o 아카이브 멤버는 정적 오브젝트인 반면 /usr/lib/libc.a 의 shr.o는 공유 오브젝트이다.
$ dump -gov /usr/lib/libc.a
/usr/lib/libc.a[frexp.o]:
***Object Module Header***
# Sections Symbol Ptr # Symbols Opt Hdr Len Flags
3 0x0000030c 34 28 0x0000
Flags=( )
Timestamp = "Sep 15 16:12:35 2002"
Magic = 0x1df (32-bit XCOFF)
/usr/lib/libc.a[shr.o]:
***Object Module Header***
# Sections Symbol Ptr # Symbols Opt Hdr Len Flags
5 0x00250c0c 26913 72 0x3002
Flags=( EXEC DYNLOAD SHROBJ )
Timestamp = "Sep 19 00:14:43 2002"
Magic = 0x1df (32-bit XCOFF)
------------------------------------------------------------------------------------------------------------------
AIX에서는 링크할 때 심볼 해석을 하게 되며 프로그램 로드 시에 다시 심볼 해석을 하지 않는다.
실행파일을 생성하면 동일한 경로 정보를 이용하여 의존관계가 있는 공유 오브젝트와 라이브러리의 아카이브 멤버를 참조할 수 있어야 한다.
만약 이렇게 안되면 실행파일을 수행할 수 없다.
그러나 의존 관계가 있는 공유 오브젝트와 라이브러리의 아카이브 멤버가 항상 같은 디렉토리에 있어야 하는 것은 아니다.
만약 오브젝트나 관련된 멤버에게 경로정보를 따로 주지 않으면, 시스템 로더는 -L 링커 옵션에서 지정한 몇 개의 디렉토리를 자동으로 찾게 되어 있다. 다음 LIBPATH 환경변수에서 지정한 디렉토리를 찾고 디폴트 라이브러리 경로인 /usr/lib, /lib 를 찾는다.
이렇게 정해진 방식에 의해 움직이므로 모듈을 로드할 때 시스템 로더의 작업이 좀더 쉬워지고 속도도 개선될 수 있다.
경로정보는 PATH 칼럼에서 Index 0(첫번째) 다음에 보이는데, 이 PATH 칼럼은 연관성 있는 모듈에 대해서 optional path component라고 부른다.
공유 오브젝트나 라이브러리를 만들 때 그리고 실행파일을 만들 때 optional path component 값이 없는 편이 좋다.
연관성 있는 모듈을 찾을 때 엉뚱한 파일을 찾을 위험성이 있기 때문이다.
$ dump -H a.out
a.out:
.
.
.
***Import File Strings***
INDEX PATH BASE MEMBER
0 /usr/lpp/xlopt:/usr/lib:/lib
1 /tmp libc.a shr.o
$ cp /usr/lib/libc.a /tmp
$ ls -l /tmp/libc.a
-r-x------ 1 k5 k5 6793964 Apr 18 15:35 /tmp/libc.a
$ chmod a+r /tmp/libc.a
$ ls -l /tmp/libc.a
-r-xr--r-- 1 k5 k5 6793964 Apr 18 15:35 /tmp/libc.a
$ xlc helloworld.c /tmp/libc.a
$ ./a.out
Hello World
$ rm -i /tmp/libc.a
rm: Remove /tmp/libc.a? y
$ ./a.out
exec(): 0509-036 Cannot load program ./a.out because of the following errors:
0509-150 Dependent module /tmp/libc.a(shr.o) could not be loaded.
0509-022 Cannot load module /tmp/libc.a(shr.o).
0509-026 System error: A file or directory in the path name does not exist.
만약 위 예제의 소스를 다음과 같이 다시 컴파일하면
$ xlc helloworld.c
아래에서는 libc.a에 대해 XCOFF 헤더에서 아무 PATH 정보도 가지지 않는다.
(링커가 호출하기 전 컴파일러 드라이버가 이미 디폴트로 -lc 를 추가한다.)
***Import File Strings***
INDEX PATH BASE MEMBER
0 /usr/lpp/xlopt:/usr/lib:/lib
1 libc.a shr.o
libc.a 가 PATH에 아무 정보도 포함하고 있지 않기 때문에, 시스템 로더는 우선 실행파일의 XCOFF헤더첫번째 로더헤더 섹션 (Index 0)에 기록된 대로 /usr/lpp/xlopt:/usr/lib/:/lib 디렉토리에서 라이브러리를 찾는다.
따라서 프로그램에서 라이브러리를 참조하려고 한다면 직접 라이브러리 이름을 적어주는 것보다 링커 옵션인 -L과 -l 을 쓰는 편이 더 좋다.
예를 들어 /project/test/lib 의 라이브러리 libabc.a를 사용하려면 아래처럼 하면 된다.
$ xlc -o a.out main.c -labc -L/project/test/lib
링커는 참조할 공유 오브젝트나 라이브러리를 찾기 위해 여러 경로를 검색하게 되는데
/project/test/lib 디렉토리를 디폴트 라이브러리 디렉토리보다 더 우선 순위에 놓는다.
이 경우 실행파일의 XCOFF헤더 중 로더 헤더 섹션의 첫번째 엔트리(Index 0)에 디렉토리 /project/test/lib 가 들어간다.
공유 오브젝트와 라이브러리는 실행파일의 XCOFF 로더 헤더섹션에 다른 선택적 경로정보(optional path component)를 가지고 있으면 안 된다.
$ dump -H a.out
.
.
.
***Import File Strings***
INDEX PATH BASE MEMBER
0 /project/test/lib:/usr/lpp/xlopt:/usr/lib:/lib
1 libc.a shr.o
링커는 입력으로 두 가지 종류의 파일(오브젝트 파일과 라이브러리)을 받을 수 있다.
이 두 가지 파일은 링커명령으로 지정할 수 있으며 아래처럼 명령행에서 직접 사용할 수 있다.
1) 오브젝트 파일을 절대경로까지 함께 지정한다.
$ xlc -o a.out main.c /prod/obj/shr1.o
2) 오브젝트 파일을 상대경로로 지정한다.
$ xlc -o a.out main.c ../../prod/obj/shr1.o
3) 현제 디렉토리에 오브젝트 파일이 있다면 파일이름만 지정한다.
$ ls main.c shr.o main.c shr.o
$ xlc -o a.out main.c shr1.o
마지막 경우만 제외하고, 생성된 실행파일은 의존하는 공유 오브젝트가 있을 경우 공유 오브젝트 shr.o를 검색하는 추가탐색경로를
XCOFF헤더에 두게 된다.
라이브러리를 절대경로까지 함께 지정한다.
$ xlc -o a.out main.c /prod/lib/libabc.a
라이브러리를 상대경로로 지정한다.
$ xlc -o a.out main.c ../../prod/lib/libabc.a
현재 디렉토리에 라이브러리 파일이 있다면 파일 이름만 지정한다.
$ ls
main.c libabc.a libabc.a main.c
$ xlc -o a.out main.c libabc.a
-L이나 -l 옵션으로 라이브러리가 설치되어 있는 디렉토리를 지정한다.
$ xlc -o a.out main.c -L/prod/lib -labc
마지막 두 경우를 제외하고, 생성된 실행파일은 의존하는 공유 라이브러리가 있을 경우 공유 라이브러리 libabc.a를 검색하는 추가탐색경로를 XCOFF헤더에 두게 된다. 링커옵션 -bnoipath는 결과 모듈에 파일경로 이름이 포함되지 않도록 한다. 디폴트 옵션은 -bipath이며, 파일경로에 관한 정보를 포함한다.
실행파일이 공유 오브젝트를 참조하거나 공유 아카이브 멤버를 포함하는 라이브러리를 참조할 때, 시스템 로더는 오브젝트와 아카이브 멤버를 찾기 위해 LIBPATH 환경변수에서 지정한 디렉토리를 검색한다. 링커는 공유 오브젝트 파일과 라이브러리를 찾기 위해 LIBPATH 값을 이용하지 않는다.
LIBPATH값이 정의되어 있다면, 시스템 로더는 모듈을 로드할 때 다음 과정을 따른다.
1. 실행파일의 XCOFF헤더의 첫번째(Index 0) 로더 헤더 섹션에 저장되어 있는 PATH정보 바로 앞에 LIBPATH에 정의되어 있는 텍스트 정보를 추가함
2. 1번에서 만들어진 디렉토리 순서에 따라 시스템 로더는 공유 오브젝트와 라이브러리를 찾게 된다.
root가 아닌 사용자가 setuid, setgid가 설정된 실행파일을 수행하면 실행파일의 헤더섹션에 기록되어 있는 디렉토리만 검색하고 LIBPATH값에 저장된 디렉토리 값은 무시한다. LIBPATH값을 지정해줘도 역시 이 값을 무시한다.
예를 들어 LIBPATH 환경변수를 다음처럼 정의한 다음 앞에 나왔던 프로그램을 수행해보자.
LIBPATH=/project/build/lib
이때 시스템 로더는 공유 오브젝트와 라이브러리를 다음과 같은 순서로 찾는다.
1. /project/build/lib
2. /project/test/lib
3. /usr/lpp/xlopt
4. /usr/lib
5. /lib
공유 오브젝트와 라이브러리는 XCOFF 로더 헤더 섹션에 LIBPATH 환경변수에서 지정한 추가 경로값 정보를 가지고 있으면 안 된다.
예를 들어 다음 상황을 가정해보자.
실행파일 a.out 을 만들기 위해 main.o와 공유 오브젝트 shr.o를 링크 시킨다.
shr.o와 main.o는 모두 현제 디렉토리에 있다.
이제 실행파일을 만들기 위해 shr.o 파일이름을 다음 두 가지 방법으로 지정해줄 수 있다.
1. xlc -o a.out main.o shr.o
2. xlc -o a.out main.o ./shr.o
첫번째 방법으로 만든 실행파일은 예제 2-7과 같이 shr.o에 대한 로더정보를 가지게 된다. 이 경우, 시스템 로더는 /usr/lpp/xlopt, /usr/lib, /lib 디렉토리에서 shr.o 파일을 찾는다. 만약 shr.o 파일이 이 세 디렉토리중 한 군데에라도 들어있다면 검색을 멈추고 LIBPATH 까지 가지 않는다. 만약 shr.o가 이 세 디렉토리외에 다른 디렉토리에 들어있다면, LIBPATH에 그 디렉토리값을 넣어줘야 한다.
예를 들어 shr.o가 현재 디렉토리에 있다면, LIBPATH 환경변수를 다음처럼 바꾼 다음 명령을 수행하자.
$ export LIBPATH=$PWD ;./a.out
만약 shr.o을 /project/lib 디렉토리로 옮기려 하면, 다음과 같은 명령을 수행하면 된다.
$ export LIBPATH=/project/lib ;./a.out
두번째 방법으로 생성한 실행 파일이라면, dump -H 의 결과가 제일 마지막 한줄이 달라진다.
***Import File Strings***
INDEX PATH BASE MEMBER
0 /usr/lpp/xlopt:/usr/lib:/lib
1 libc.a shr.o
2 . shr.o
PATH 컬럼의 . 는 shr.o의 추가경로정보이며 앞의 경우와 큰 차이가 있다. 두 번째 방법으로 생성한 실행파일을 수행하면, 시스템 로더는 shr.o를 찾기 위해 LIBPATH도 참조하지 않고 첫번째(Index 0) 로더 헤더 섹션(이 예제에서는 /usr/lpp/xlopt:/usr/lib:/lib 디렉토리)도 찾지 않는다. 따라서 이 실행파일을 수행할 때는 현재 디렉토리에 shr.o가 항상 같이 있어야 한다.