ar은 정적 라이브러리를 만드는 데 사용하는 도구이다.

사실 ar은 archive 파일을 만드는 도구이며
반드시 라이브러리를 만들 때만 사용할 수 있는 것은 아니다.
일반적인 파일들을 묶어 archive로 만들 때도 물론 사용할 수 있다.

물론 이런 경우 대부분은 tar를 사용하겠지만
tar와 ar은 이름도 한 끝 차이(?!)인 만큼
기본적인 기능은 거의 비슷하다.

다음과 같은 텍스트 파일이 있을 때 (각각의 크기는 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)
다른 건 별 의미가 없고 size 정보에만 관심을 가지면 된다.
헤더 이후에 실제 데이터가 시작되는 위치가 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

WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,