Skip to content
AZ Tools

Static Library (.a) Inspector

A .a file is an ar archive: eight bytes of magic, then one 60-byte fixed-width ASCII header per member — 16 bytes of name, 12 of modification time, 6 of uid, 6 of gid, 8 of octal mode and 10 of decimal size — followed by that member’s bytes, padded to an even offset. This tool reads all of that out of the bytes and shows every member with the offset of its header, the offset where its data really begins, its size, its stored timestamp in UTC, its owner and its permissions. A name longer than the 16-byte field goes somewhere else, and the two conventions do it differently: GNU keeps the long names in a member called // and writes /offset in the header, while BSD and macOS write #1/length and put the name in the first bytes of the member’s own data — which means the data does not start where the header ends and the size field counts the name. Fifteen characters still fit inline as "name/", sixteen do not; reading that boundary wrong shifts every offset after it, so the convention is reported explicitly and each member says where its name came from. The reason for the tool, though, is the symbol index. The member named / (or /SYM64/, or __.SYMDEF on BSD) maps a symbol to the file offset of the member that defines it, and it is only a cache: nothing keeps it in step with the members except ranlib. If an archive is edited in place, assembled by a script, or appended to without rebuilding the index, the index can point at a member that no longer defines the symbol, or miss a symbol that is sitting right there — and the linker, which trusts the index, then reports an undefined reference to a symbol the archive contains. So both sides are read: the index, and every member’s own ELF symbol table. Where they disagree is listed entry by entry. Each ELF member also lists the global symbols it defines, with nm’s letter, and the ones it leaves undefined, which is what tells you the order the members depend on each other in. Symbols with a strong definition in two members are collected separately, because that is the "duplicate symbol" a link fails on; weak, common and unique definitions repeat legitimately and are not flagged. Members that are not ELF objects — a Mach-O or COFF object from another platform, a nested archive, a text file added by mistake — are named as such rather than half-parsed, and a thin archive, whose members are references to files on disk, is reported as one because its symbols simply are not in the file. What the tool cannot tell you is which members a particular link will actually pull in: that depends on the link order and on what is still undefined when the archive is reached.

How to use

  1. Drop a .a file on the box. Nothing is uploaded: the archive is read by JavaScript in the page.
  2. Read the overview for the convention (GNU // or BSD #1/), the kind of symbol index and how many bytes of the file are index and long-name table rather than code.
  3. Check the "index against members" panel first. A green line means the cached index and the members agree; a red one names the symbol and the member that disagree.
  4. Scan the member table for the header and data offsets, the sizes and the stored dates — deterministic builds write a timestamp, uid and gid of 0, so anything else means the archive was built with ar U.
  5. Open a member under "symbols per member" to see what it defines and what it still needs, and read the duplicate list to explain a "duplicate symbol" link error.

Frequently asked questions

What does "archive has no index; run ranlib to add one" mean?
The archive was built without its symbol table — usually by ar rcS, by ar q appending to it, or by a tool that wrote the archive itself. Without the index a linker would have to open every member to find out what it defines, so most of them refuse and ask for ranlib, which adds a first member named / holding the map. ranlib on the file, or ar s, fixes it in place, and ar rcs builds it in the first place. This tool shows "Symbol index: none" for such an archive, and the member list is still perfectly readable, because the index is an accelerator and not part of the members.
How can the symbol index be wrong if ar keeps it up to date?
ar rebuilds the index whenever it rewrites the archive, so the usual commands are safe. The index goes stale when something else touches the file: an archive patched in place by a script, a member replaced by a build system that appends with ar q and never runs ranlib, an archive assembled by hand or by a tool that writes the ar format itself, or one copied between machines and edited. Because the index stores a byte offset rather than a name, a member that changes without the index being rebuilt leaves an entry pointing at bytes that no longer define that symbol. The linker trusts the index, so the failure surfaces far away, as an undefined reference to a symbol you can plainly see in nm output.
What is the difference between a GNU and a BSD .a, and what is a thin archive?
Only in how names longer than 16 characters and the symbol index are stored. GNU puts the long names in a // member and refers to them by offset, and calls the index / or /SYM64/ for the 64-bit form; BSD writes #1/length in the name field and stores the name in the first bytes of the member data, and calls the index __.SYMDEF or __.SYMDEF SORTED. The member data therefore starts in a different place in the two formats, which is the single most common way of misreading an archive. A thin archive is a third variant, written by ar T: it begins with !<thin> and stores only headers, with the member data left in the original .o files on disk, so moving the .a alone breaks it.
Why does a member list symbols as undefined? Is the library broken?
No — that is normal and it is the point of the listing. Each member is a separate object file, and an undefined symbol is one it expects some other member, another library or the program itself to provide. Reading them together tells you the dependency order inside the archive, which matters because a static library is searched, not merged: a linker walks the command line once, pulls in only the members that resolve something still undefined at that moment, and a member pulled in late can leave a symbol undefined that an earlier library would have supplied. That is why link order matters and why --start-group exists.
Why are only some repeated symbols reported as duplicates?
Because only some of them break a link. A strong definition of the same name in two members is the classic "duplicate symbol" error: whichever member the linker pulls in second collides. But weak definitions (nm letters W and V), common symbols (C) and GNU unique symbols (u) are meant to repeat — C++ inline functions, templates and vtables are emitted into every object that uses them and the linker keeps one copy. A real libstdc++.a has thousands of such repeats and no error, so listing them all would bury the one that matters. Only names with a strong definition in more than one member are collected here.
Is the archive uploaded anywhere?
No. It is read with the browser File API and parsed by JavaScript in the page; there is no server side to send it to. That matters more for a static library than for most files, because a .a from a vendor or an internal build is often something you are not free to upload. You can disconnect from the network after the page has loaded and the tool still works, and the file never leaves the tab.

Related tools