글
(1) ar 이란?
: ar은 정적 라이브러리를 만드는 데 사용하는 도구이다.
사실 ar은 archive 파일을 만드는 도구이며, 반드시 라이브러리를 만들 때만 사용할 수 있는 것은 아니다.
일반적인 파일들을 묶어 archive로 만들 때도 물론 사용할 수 있다.
물론, 이런 경우 대부분은 tar를 사용하겠지만, tar와 ar은 기본적인 기능은 거의 비슷하다.
ex) 다음과 같은 텍스트 파일이 있을 때 (각각의 크기는 12 바이트이다.)
a.txt: content of a b.txt: content of b |
● 이를 ar을 이용하여 다음과 같이 c.a 라는 파일로 묶을 수 있다.
$ ar r c.a a.txt b.txt |
● c.a 파일을 살펴보면 다음과 같이 구성됨을 볼 수 있다. (참고로 1000은 지금 사용 중인 계정의 uid와 gid이다)
$ xxd c.a 0000000: 213c 6172 6368 3e0a 612e 7478 742f 2020 !<arch>.a.txt/ 0000010: 2020 2020 2020 2020 3132 3437 3939 3138 12479918 0000020: 3831 2020 3130 3030 2020 3130 3030 2020 81 1000 1000 0000030: 3130 3036 3434 2020 3132 2020 2020 2020 100644 12 0000040: 2020 600a 636f 6e74 656e 7420 6f66 2061 `.content of a 0000050: 622e 7478 742f 2020 2020 2020 2020 2020 b.txt/ 0000060: 3132 3437 3939 3138 3938 2020 3130 3030 1247991898 1000 0000070: 2020 3130 3030 2020 3130 3036 3434 2020 1000 100644 0000080: 3132 2020 2020 2020 2020 600a 636f 6e74 12 `.cont 0000090: 656e 7420 6f66 2062 ent of b |
ar은 metadata를 ASCII 문자를 이용하여 저장하므로 한 눈에 파일의 구조가 들어온다.
먼저 이 파일이 ar로 만들어 졌음을 나타내는 magic number ("!<arch>\n"), 파일 이름 (/로 끝난다), 생성 시간 (timestamp), uid, gid, mode, size 정보가 차례로 나온다. 마지막으로 metadata의 마지막을 알리는 magic number ("`\n")가 나온 후에 실제 파일 데이터가 나온다. 이 후는 각 파일마다 반복된다.
● 이 정보는 binutils 소스의 include/aout/ar.h 에 다음과 같이 정의되어 있다.
(참고로 \012는 8진수 이므로 \xA, 10, '\n'과 동일함)
/* Note that the usual '\n' in magic strings may translate to different characters, as allowed by ANSI. '\012' has a fixed value, and remains compatible with existing BSDish archives. */ #define ARMAG "!<arch>\012" /* For COFF and a.out archives. */ #define ARMAGB "!<bout>\012" /* For b.out archives. */ #define ARMAGT "!<thin>\012" /* For thin archives. */ #define SARMAG 8 #define ARFMAG "`\012" ... struct ar_hdr { char ar_name[16]; /* Name of this member. */ char ar_date[12]; /* File mtime. */ char ar_uid[6]; /* Owner uid; printed as decimal. */ char ar_gid[6]; /* Owner gid; printed as decimal. */ char ar_mode[8]; /* File mode, printed as octal. */ char ar_size[10]; /* File size, printed as decimal. */ char ar_fmag[2]; /* Should contain ARFMAG. */ }; |
따라서 ar은 기본적으로 a.out 형식을 사용한다는 것을 알 수 있다.
(보면 알 수 있듯이 파일 이름의 길이가 16바이트로 고정되어 있으므로
이보다 긴 이름의 파일이 사용되면 다른 형식을 이용하는 것 같다.)
object 파일(.o)들을 라이브러리로 만들 때도 이와 동일한 형식으로 저장된다.
(하지만 우분투 9.04에 기본으로 설치되는 binutils (2.19.1-0ubuntu3)의 경우
ar r 명령 만을 실행해도 기본적으로 s 옵션을 추가한 것처럼 symbol index를 생성하였다.
명시적으로 (대문자) S 옵션을 주어 실행하면 index 생성을 금지할 수 있으니
이를 이용하여 텍스트 파일의 경우와 동일한 형태로 생성되는지 확인할 수 있다.)
정적 라이브러리의 경우에는 단순히 object 파일을 합치는 것 이외에도
위에서 언급한 symbol index를 생성하는 작업이 필요하다.
(이는 라이브러리 내에 정의된 심볼들을 빨리 찾아서 링크 속도를 높이기 위한 목적이다.)
이를 위해서는 ar 실행 시 s 옵션을 주거나 ranlib 프로그램을 실행하면 되는데
사실 ar과 ranlib은 동일한 소스에서 flag 하나만 다르게 설정하여 컴파일 하는
거의 동일한 프로그램이니 ar 만을 살펴보기로 하겠다.
(또한 위에서 언급한대로 ar r 만 실행해도 ar rs를 실행한 것과 동일한 결과가 나왔다.)
symbol index가 어떻게 구성되는지 알아보기 위해
다음과 같은 간단한 예제 프로그램을 만들어 보자.
(test1에서는 d1과 f1이라는 심볼이 만들어지고, test2에서는 f2가 만들어 질 것이다.)
test1.c: int d1; void f1 (void) { } test2.c: void f2 (void) { } |
● 이를 컴파일 한 후 다음과 같이 libartest.a 라는 라이브러리로 만든다.
$ gcc -c test1.c $ gcc -c test2.c $ ar rs libartest.a test1.o test2.o |
● libartest.a 파일의 구조를 들여다보자.
$ xxd -l160 libartest.a 0000000: 213c 6172 6368 3e0a 2f20 2020 2020 2020 !<arch>./ 0000010: 2020 2020 2020 2020 3132 3437 3939 3432 12479942 0000020: 3534 2020 3020 2020 2020 3020 2020 2020 54 0 0 0000030: 3020 2020 2020 2020 3236 2020 2020 2020 0 26 0000040: 2020 600a 0000 0003 0000 005e 0000 005e `........^...^ 0000050: 0000 034a 6631 0064 3100 6632 0000 7465 ...Jf1.d1.f2..te 0000060: 7374 312e 6f2f 2020 2020 2020 2020 3132 st1.o/ 12 0000070: 3437 3834 3936 3739 2020 3130 3030 2020 47849679 1000 0000080: 3130 3030 2020 3130 3036 3434 2020 3638 1000 100644 68 0000090: 3720 2020 2020 2020 600a 7f45 4c46 0101 7 `..ELF.. |
앞의 magic number 부분은 (당연히) 동일하다.
대신 test1.o 파일이 0x5e 부분에서 시작하며 그 앞에 새로운 파일(?)이 추가되었음을 볼 수 있다.
(참고로 ar에서는 archive 내에 포함된 파일들을 멤버라고 부른다)
test1.o 앞에 추가된 멤버도 동일한 형식으로 구성되므로 위의 헤더 형식에 따라 살펴보면
○ ar_name : / (즉, 이름이 없다)
○ ar_date : 1247994254 (= 2009-07-19 18:04:14)
○ ar_uid : 0
○ ar_gid : 0
○ ar_mode : 0
○ ar_size : 26
○ ar_fmag : ARFMAG (= 0x600a)
헤더 이후에 실제 데이터가 시작되는 위치가 0x44이니
여기에 26(= 0x1a)을 더하면 0x5e가 된다. (아까 살펴본 test1.o 의 시작 위치와 동일하다!)
0x44 부터의 26 바이트를 좀 더 자세히 살펴보자.
0000 0003 0000 005e 0000 005e 0000 034a 6631 0064 3100 6632 0000
우선 3과 5e 라는 정보가 눈에 띈다.
(이 정보는 big endian 32bit 정수형으로 저장되는 듯 하다.)
우선 3은 라이브러리 내의 심볼 개수이다.
위에서 살펴보았듯이 test1.o에는 2개, test2.o에는 1개의 심볼이 존재한다.
그 다음에는 3개의(!) 32bit 정수(big endian)가 나오는데
각각 0x5e, 0x5e, 0x34a에 해당한다. (뭔가 느낌이 오지 않는가!?)
0x5e는 test1.o 파일 정보가 시작되는 위치이다.
그렇다면 0x34a는 test2.o 파일 정보가 시작되는 위치라고 볼 수 있을 것이다.
● 다음과 같이 확인할 수 있다. (편의상 헤더 부분 만 보기로 한다.)
$ xxd -s0x34a -l60 libartest.a 000034a: 7465 7374 322e 6f2f 2020 2020 2020 2020 test2.o/ 000035a: 3132 3437 3834 3737 3035 2020 3130 3030 1247847705 1000 000036a: 2020 3130 3030 2020 3130 3036 3434 2020 1000 100644 000037a: 3636 3820 2020 2020 2020 600a 668 `. |
역시나 생각대로다.
● 그 이후에는 해당 심볼의 이름이 NULL-terminated string 형태로 나온다.
마지막 임을 나타내기 위해 NULL 문자가 추가적으로 사용된 것 같다.
"f1" (0x66, 0x31, 0x00), "d1" (0x64, 0x31, 0x00), "f2" (0x66, 0x32, 0x00), \0
이제 프로그램 빌드 과정에서 libartest.a가 링크된다면
링커는 필요한 심볼을 먼저 index에서 찾은 후 (string match)
발견되면 해당 위치에 있는 offset 값을 통해 멤버의 시작 위치(헤더)를 찾고
해당 object 파일 만을 추출하여 프로그램에 링크시킬 것이라고 짐작할 수 있다. (별도의 dependency가 없는 경우)
※ 출처 : http://studyfoss.egloos.com/5049029
'P rogramming > Linux System' 카테고리의 다른 글
공유 라이브러리 생성 (0) | 2011.09.30 |
---|---|
정적 라이브러리 생성 (0) | 2011.09.30 |
정적 라이브러리 와 공유 라이브러리 (0) | 2011.09.29 |
라이브러리 함수 목록 (0) | 2011.09.29 |
시스템 콜 목록 (0) | 2011.09.29 |
RECENT COMMENT