본문으로 건너뛰기
AZ Tools

정적 라이브러리(.a) 분석기

.a 파일은 ar 아카이브입니다. 매직 8바이트 뒤에 멤버마다 60바이트짜리 고정폭 ASCII 헤더가 오고(이름 16, 수정 시각 12, uid 6, gid 6, 8진수 권한 8, 10진수 크기 10바이트), 그 뒤에 멤버의 데이터가 짝수 오프셋으로 정렬되어 이어집니다. 이 도구는 그 바이트를 그대로 읽어 헤더 위치와 데이터가 실제로 시작하는 위치, 크기, UTC로 표시한 시각, 소유자와 권한을 멤버마다 보여줍니다. 16바이트 필드에 들어가지 않는 긴 이름은 다른 곳에 저장되는데 방식이 둘입니다. GNU는 // 멤버에 모아 두고 헤더에는 /오프셋을 적지만, BSD와 macOS는 #1/길이를 적고 이름을 멤버 데이터의 맨 앞에 넣습니다. 즉 데이터가 헤더 바로 뒤에서 시작하지 않고 크기 필드에도 이름이 포함됩니다. 15자까지는 "이름/" 형태로 헤더에 들어가지만 16자는 들어가지 않으며, 이 경계를 잘못 읽으면 이후의 모든 오프셋이 어긋납니다. 그래서 어떤 방식인지와 각 멤버의 이름이 어디서 왔는지를 따로 표시합니다. 이 도구를 만든 진짜 이유는 심볼 인덱스입니다. / 멤버(또는 /SYM64/, BSD의 __.SYMDEF)는 심볼을 그것을 정의하는 멤버의 파일 오프셋에 대응시키는 캐시일 뿐이고, ranlib 말고는 아무도 최신 상태를 보장하지 않습니다. 아카이브를 직접 수정하거나 스크립트로 조립하거나 인덱스를 다시 만들지 않고 멤버를 덧붙이면, 인덱스가 더는 그 심볼을 정의하지 않는 멤버를 가리키거나 바로 거기 있는 심볼을 빠뜨릴 수 있습니다. 인덱스를 믿는 링커는 아카이브 안에 있는 심볼을 두고 undefined reference를 냅니다. 그래서 인덱스와 각 멤버의 ELF 심볼 테이블을 모두 읽어 어긋나는 항목을 하나씩 나열합니다. ELF 멤버마다 정의한 전역 심볼을 nm과 같은 글자와 함께 보여주고 미정의로 남긴 심볼도 함께 보여주므로 멤버들 사이의 의존 순서를 읽을 수 있습니다. 두 멤버가 강하게 정의한 심볼은 따로 모읍니다. 링크가 실패하는 "duplicate symbol"이 바로 그것이고, 약한 심볼과 common, 유일 심볼은 원래 반복되므로 표시하지 않습니다. ELF가 아닌 멤버(다른 플랫폼의 Mach-O나 COFF, 중첩 아카이브, 실수로 넣은 텍스트 파일)는 절반만 해석하지 않고 그렇다고 이름 붙여 알려 주며, 멤버가 디스크의 파일을 가리키기만 하는 thin 아카이브도 그렇게 알려 줍니다. 다만 특정 링크가 실제로 어떤 멤버를 끌어올지는 알 수 없습니다. 그것은 링크 순서와 그 시점에 무엇이 미정의로 남아 있는지에 달려 있습니다.

사용법

  1. .a 파일을 상자에 놓으세요. 아무것도 업로드되지 않고 페이지의 자바스크립트가 읽습니다.
  2. 개요에서 이름 방식(GNU //인지 BSD #1/인지), 심볼 인덱스 종류, 그리고 파일 중 얼마가 코드가 아니라 인덱스와 긴 이름 테이블인지 확인하세요.
  3. "인덱스와 멤버 대조" 칸을 먼저 보세요. 초록 줄이면 캐시와 멤버가 일치하고, 빨간 줄이면 어긋나는 심볼과 멤버를 그대로 알려 줍니다.
  4. 멤버 표에서 헤더와 데이터 위치, 크기, 저장된 날짜를 훑어보세요. 재현 가능한 빌드는 시각과 uid, gid를 0으로 쓰므로 다른 값이 보이면 ar U로 만든 아카이브입니다.
  5. "멤버별 심볼"에서 멤버를 펼쳐 무엇을 정의하고 무엇이 아직 필요한지 보고, 중복 목록으로 "duplicate symbol" 링크 오류를 설명하세요.

자주 묻는 질문

"archive has no index; run ranlib to add one"은 무슨 뜻인가요?
아카이브가 심볼 테이블 없이 만들어졌다는 뜻입니다. 보통 ar rcS로 만들었거나 ar q로 덧붙였거나 ar 형식을 직접 쓰는 도구가 만든 경우입니다. 인덱스가 없으면 링커는 무엇이 정의되어 있는지 알기 위해 모든 멤버를 열어야 하므로 대부분 거부하고 ranlib을 요구합니다. ranlib은 / 라는 이름의 첫 멤버에 지도를 넣어 줍니다. 파일에 ranlib을 돌리거나 ar s를 쓰면 그 자리에서 고쳐지고, 처음부터 ar rcs로 만들면 됩니다. 이 도구는 그런 아카이브에 "심볼 인덱스: 없음"이라고 표시하며, 인덱스는 가속 장치일 뿐이므로 멤버 목록은 그대로 읽힙니다.
ar이 인덱스를 갱신한다면 어떻게 인덱스가 틀릴 수 있나요?
ar은 아카이브를 다시 쓸 때마다 인덱스를 만들기 때문에 평범한 명령은 안전합니다. 문제는 다른 것이 파일을 건드릴 때입니다. 스크립트가 파일을 그 자리에서 수정했거나, 빌드 시스템이 ar q로 덧붙이고 ranlib을 돌리지 않았거나, ar 형식을 직접 쓰는 도구가 조립했거나, 다른 기계로 옮겨 편집한 경우입니다. 인덱스는 이름이 아니라 바이트 오프셋을 저장하므로, 인덱스를 다시 만들지 않은 채 멤버가 바뀌면 그 심볼을 더는 정의하지 않는 바이트를 가리키는 항목이 남습니다. 링커는 인덱스를 믿기 때문에 실패는 엉뚱한 곳에서, nm으로는 뻔히 보이는 심볼에 대한 undefined reference로 나타납니다.
GNU와 BSD의 .a는 무엇이 다르고 thin 아카이브는 무엇인가요?
16자를 넘는 이름과 심볼 인덱스를 저장하는 방식만 다릅니다. GNU는 긴 이름을 // 멤버에 모아 오프셋으로 가리키고 인덱스를 / 또는 64비트용 /SYM64/라고 부릅니다. BSD는 이름 칸에 #1/길이를 쓰고 이름을 멤버 데이터의 맨 앞에 저장하며 인덱스를 __.SYMDEF 또는 __.SYMDEF SORTED라고 부릅니다. 따라서 멤버 데이터가 시작하는 위치가 서로 달라지고, 아카이브를 잘못 읽는 가장 흔한 원인이 됩니다. thin 아카이브는 ar T가 만드는 세 번째 형태로, !<thin>으로 시작하고 헤더만 저장하며 데이터는 디스크의 .o 파일에 그대로 둡니다. 그래서 .a만 옮기면 깨집니다.
멤버에 미정의 심볼이 있는데 라이브러리가 망가진 건가요?
아닙니다. 정상이고 그 목록이 바로 요점입니다. 멤버는 저마다 별개의 오브젝트 파일이고, 미정의 심볼은 다른 멤버나 다른 라이브러리, 또는 프로그램이 제공해 주기를 기다리는 심볼입니다. 이것을 함께 읽으면 아카이브 안의 의존 순서를 알 수 있습니다. 정적 라이브러리는 합쳐지는 것이 아니라 탐색되기 때문에 중요합니다. 링커는 명령줄을 한 번만 훑으면서 그 시점에 미정의인 것을 해결해 주는 멤버만 끌어오므로, 늦게 끌려온 멤버가 앞선 라이브러리라면 채워 주었을 심볼을 미정의로 남길 수 있습니다. 링크 순서가 중요한 이유이자 --start-group이 있는 이유입니다.
반복되는 심볼 중 일부만 중복으로 보고하는 이유는 무엇인가요?
일부만 링크를 깨뜨리기 때문입니다. 같은 이름을 두 멤버가 강하게 정의하면 전형적인 "duplicate symbol" 오류가 납니다. 하지만 약한 정의(nm의 W와 V), common 심볼(C), GNU 유일 심볼(u)은 반복되도록 만들어진 것입니다. C++의 인라인 함수와 템플릿, vtable은 그것을 쓰는 모든 오브젝트에 들어가고 링커가 하나만 남깁니다. 실제 libstdc++.a에는 그런 반복이 수천 개 있지만 오류는 나지 않으므로, 전부 나열하면 정작 중요한 하나가 묻힙니다. 그래서 두 멤버 이상에서 강한 정의를 가진 이름만 모읍니다.
아카이브가 어딘가로 업로드되나요?
아닙니다. 브라우저의 File API로 읽고 페이지 안의 자바스크립트가 해석하며, 보낼 서버 자체가 없습니다. 정적 라이브러리는 특히 그렇습니다. 공급사나 사내 빌드에서 온 .a는 업로드해도 되는 파일이 아닌 경우가 많기 때문입니다. 페이지를 불러온 뒤 네트워크를 끊어도 동작하며, 파일은 탭 밖으로 나가지 않습니다.

관련 도구