[llvm] [llvm-nm][GOFF] Display archive attributes in GOFF archives through --print-armap (PR #214527)

Amy Kwan via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 14 22:24:49 PDT 2026


https://github.com/amy-kwan updated https://github.com/llvm/llvm-project/pull/214527

>From c7044b7aeadeb02c7131b435ec55851d57186ea6 Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Thu, 6 Aug 2026 12:30:33 -0400
Subject: [PATCH 1/5] [llvm-nm][GOFF] Display archive attributes in GOFF
 archives through --print-armap

GOFF archive symbol table entries contain an attribute word in addition
to the archive member offset. The low three bits describe whether the symbol is
64-bit, uses XPLink, or belongs to the WSA namespace (which was briefly mentioned
in e2c8fa09872cfacba7f73599dcf8557971ebe865).

This patch extends `llvm-nm --print-armap` to print the attribute value (in hex) and
its decoded description beside a symbol and its corresponding member when processing
a GOFF archive. This will functionality will be used to help validate full support for writing
GOFF archives in a subsequent llvm-ar patch.

The output for non-z/OS archives is unchanged.
---
 llvm/include/llvm/Object/Archive.h     | 10 ++++++
 llvm/lib/Object/Archive.cpp            | 12 +++++++
 llvm/test/tools/llvm-nm/zos-armap.test | 42 ++++++++++++++++++++++
 llvm/tools/llvm-nm/llvm-nm.cpp         | 49 +++++++++++++++++++++++---
 4 files changed, 108 insertions(+), 5 deletions(-)
 create mode 100644 llvm/test/tools/llvm-nm/zos-armap.test

diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index 45877335b0778..442f61ae35c08 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -348,6 +348,16 @@ class LLVM_ABI Archive : public Binary {
     LLVM_ABI Expected<Child> getMember() const;
     LLVM_ABI Symbol getNext() const;
     LLVM_ABI bool isECSymbol() const;
+    /// Archive attribute bit masks for K_ZOS archive symbol table entries.
+    static constexpr uint32_t ZOSAttrWSA = 0x1;
+    static constexpr uint32_t ZOSAttrXPLink = 0x2;
+    static constexpr uint32_t ZOSAttr64Bit = 0x4;
+    static constexpr uint32_t ZOSKnownAttrMask =
+        ZOSAttrWSA | ZOSAttrXPLink | ZOSAttr64Bit;
+    /// For K_ZOS archives, returns the 32-bit attribute word stored alongside
+    /// the symbol-table entry. The low bits are described by the ZOSAttr*
+    /// constants above. Returns 0 for non-z/OS archives.
+    LLVM_ABI uint32_t getZOSAttributes() const;
   };
 
   class symbol_iterator {
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index ae54338bb9a71..22fefaa010d21 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1140,6 +1140,18 @@ bool Archive::Symbol::isECSymbol() const {
          SymbolIndex < SymbolCount + Parent->getNumberOfECSymbols();
 }
 
+uint32_t Archive::Symbol::getZOSAttributes() const {
+  if (Parent->kind() != K_ZOS)
+    return 0;
+  if (SymbolIndex >= Parent->getNumberOfSymbols())
+    return 0;
+
+  // The z/OS symbol table layout is:
+  //   NumSyms * { uint32_t member_offset, uint32_t attrs }  (big-endian)
+  const char *Buf = Parent->getSymbolTable().begin();
+  return read32be(Buf + sizeof(uint32_t) + SymbolIndex * 8 + sizeof(uint32_t));
+}
+
 StringRef Archive::Symbol::getName() const {
   if (isECSymbol())
     return Parent->ECSymbolTable.begin() + StringIndex;
diff --git a/llvm/test/tools/llvm-nm/zos-armap.test b/llvm/test/tools/llvm-nm/zos-armap.test
new file mode 100644
index 0000000000000..e9674bb64c54d
--- /dev/null
+++ b/llvm/test/tools/llvm-nm/zos-armap.test
@@ -0,0 +1,42 @@
+## Test that llvm-nm --print-armap prints the archive map for GOFF archives,
+## including inline z/OS attribute flags on each symbol line.
+##
+## All 8 combinations of the 3 known attribute bits are exercised, plus two
+## symbols with unknown bits set to cover the '?' flag:
+##   bit 2 (0x4): 64-bit  (AMODE == ESD_AMODE_64)
+##   bit 1 (0x2): XPLink  (LinkageType == ESD_LT_XPLink)
+##   bit 0 (0x1): WSA     (parent ED namespace == ESD_NS_Parts)
+##   bit 3+      : unknown, printed as '?'
+##
+## The archive is generated directly using generate_zos_archive.py.
+
+# RUN: rm -rf %t.dir && mkdir -p %t.dir
+
+# RUN: %python %S/../../Object/Inputs/generate_zos_archive.py \
+# RUN:   --output %t.dir/test.a          \
+# RUN:   --member "test.o:hex:abcdabcd"  \
+# RUN:   --symtab "sym000:0:0"           \
+# RUN:   --symtab "sym001:0:1"           \
+# RUN:   --symtab "sym010:0:2"           \
+# RUN:   --symtab "sym011:0:3"           \
+# RUN:   --symtab "sym100:0:4"           \
+# RUN:   --symtab "sym101:0:5"           \
+# RUN:   --symtab "sym110:0:6"           \
+# RUN:   --symtab "sym111:0:7"           \
+# RUN:   --symtab "symunk:0:8"           \
+# RUN:   --symtab "symall:0:15"
+
+# RUN: llvm-nm --print-armap %t.dir/test.a | FileCheck %s
+
+## For z/OS archives, each symbol line includes (flags: <hex> [description]).
+# CHECK:      Archive map
+# CHECK-NEXT: sym000 in test.o (flags: 0x00000000 [none])
+# CHECK-NEXT: sym001 in test.o (flags: 0x00000001 [WSA])
+# CHECK-NEXT: sym010 in test.o (flags: 0x00000002 [XPLink])
+# CHECK-NEXT: sym011 in test.o (flags: 0x00000003 [XPLink + WSA])
+# CHECK-NEXT: sym100 in test.o (flags: 0x00000004 [64-bit])
+# CHECK-NEXT: sym101 in test.o (flags: 0x00000005 [64-bit + WSA])
+# CHECK-NEXT: sym110 in test.o (flags: 0x00000006 [64-bit + XPLink])
+# CHECK-NEXT: sym111 in test.o (flags: 0x00000007 [64-bit + XPLink + WSA])
+# CHECK-NEXT: symunk in test.o (flags: 0x00000008 [?])
+# CHECK-NEXT: symall in test.o (flags: 0x0000000f [64-bit + XPLink + WSA + ?])
diff --git a/llvm/tools/llvm-nm/llvm-nm.cpp b/llvm/tools/llvm-nm/llvm-nm.cpp
index 4e3472154063a..0a178d76ea5ca 100644
--- a/llvm/tools/llvm-nm/llvm-nm.cpp
+++ b/llvm/tools/llvm-nm/llvm-nm.cpp
@@ -2045,9 +2045,40 @@ static bool checkMachOAndArchFlags(SymbolicFile *O, StringRef Filename) {
   return true;
 }
 
-static void printArchiveMap(iterator_range<Archive::symbol_iterator> &map,
-                            StringRef Filename) {
-  for (auto I : map) {
+/// Decode the low 3 bits of a z/OS archive symbol attribute word into a
+/// human-readable string, e.g. "[64-bit + XPLink]".
+/// Any bits above the known 3-bit mask produce a trailing "?" flag.
+static std::string decodeZOSAttributes(uint32_t Attrs) {
+  bool Unknown = (Attrs & ~Archive::Symbol::ZOSKnownAttrMask) != 0;
+  bool Is64Bit = (Attrs & Archive::Symbol::ZOSAttr64Bit) != 0;
+  bool IsXPLink = (Attrs & Archive::Symbol::ZOSAttrXPLink) != 0;
+  bool IsWSA = (Attrs & Archive::Symbol::ZOSAttrWSA) != 0;
+
+  std::string Result = "[";
+  bool NeedPlus = false;
+  auto append = [&](const char *S) {
+    if (NeedPlus)
+      Result += " + ";
+    Result += S;
+    NeedPlus = true;
+  };
+  if (Is64Bit)
+    append("64-bit");
+  if (IsXPLink)
+    append("XPLink");
+  if (IsWSA)
+    append("WSA");
+  if (Unknown)
+    append("?");
+  if (!NeedPlus)
+    append("none");
+  Result += "]";
+  return Result;
+}
+
+static void printArchiveMap(iterator_range<Archive::symbol_iterator> &Map,
+                            StringRef Filename, bool PrintZOSAttrs = false) {
+  for (auto I : Map) {
     Expected<Archive::Child> C = I.getMember();
     if (!C) {
       error(C.takeError(), Filename);
@@ -2059,7 +2090,15 @@ static void printArchiveMap(iterator_range<Archive::symbol_iterator> &map,
       break;
     }
     StringRef SymName = I.getName();
-    outs() << SymName << " in " << FileNameOrErr.get() << "\n";
+    outs() << SymName << " in " << FileNameOrErr.get();
+    if (PrintZOSAttrs) {
+      uint32_t Attrs = I.getZOSAttributes();
+      std::string AttrsStr;
+      llvm::raw_string_ostream(AttrsStr) << format("0x%08x", Attrs);
+      outs() << " (flags: " << AttrsStr << " " << decodeZOSAttributes(Attrs)
+             << ")";
+    }
+    outs() << "\n";
   }
 
   outs() << "\n";
@@ -2069,7 +2108,7 @@ static void dumpArchiveMap(Archive *A, StringRef Filename) {
   auto Map = A->symbols();
   if (!Map.empty()) {
     outs() << "Archive map\n";
-    printArchiveMap(Map, Filename);
+    printArchiveMap(Map, Filename, A->kind() == Archive::K_ZOS);
   }
 
   auto ECMap = A->ec_symbols();

>From 75bded60496773b5f05bb8c16abecca0fa3902dc Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Fri, 7 Aug 2026 15:40:28 -0400
Subject: [PATCH 2/5] Add assert into getZOSAttributes()

---
 llvm/lib/Object/Archive.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 22fefaa010d21..be0dcaab9ba5e 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1141,8 +1141,8 @@ bool Archive::Symbol::isECSymbol() const {
 }
 
 uint32_t Archive::Symbol::getZOSAttributes() const {
-  if (Parent->kind() != K_ZOS)
-    return 0;
+  assert(Parent->kind() == K_ZOS && "Cannot get z/OS attributes for non-z/OS "
+                                    "archives");
   if (SymbolIndex >= Parent->getNumberOfSymbols())
     return 0;
 

>From 27ac32ff2bdcef18f599214101b007cafbd82111 Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Tue, 11 Aug 2026 11:49:27 -0400
Subject: [PATCH 3/5] Address review comments.

---
 llvm/include/llvm/Object/Archive.h     |  4 ++-
 llvm/test/tools/llvm-nm/zos-armap.test | 36 ++++++++++++++----------
 llvm/tools/llvm-nm/llvm-nm.cpp         | 39 +++++++++++++-------------
 3 files changed, 43 insertions(+), 36 deletions(-)

diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index 442f61ae35c08..3b7bae296e60f 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -348,14 +348,16 @@ class LLVM_ABI Archive : public Binary {
     LLVM_ABI Expected<Child> getMember() const;
     LLVM_ABI Symbol getNext() const;
     LLVM_ABI bool isECSymbol() const;
+
     /// Archive attribute bit masks for K_ZOS archive symbol table entries.
     static constexpr uint32_t ZOSAttrWSA = 0x1;
     static constexpr uint32_t ZOSAttrXPLink = 0x2;
     static constexpr uint32_t ZOSAttr64Bit = 0x4;
     static constexpr uint32_t ZOSKnownAttrMask =
         ZOSAttrWSA | ZOSAttrXPLink | ZOSAttr64Bit;
+
     /// For K_ZOS archives, returns the 32-bit attribute word stored alongside
-    /// the symbol-table entry. The low bits are described by the ZOSAttr*
+    /// the symbol table entry. The low bits are described by the ZOSAttr*
     /// constants above. Returns 0 for non-z/OS archives.
     LLVM_ABI uint32_t getZOSAttributes() const;
   };
diff --git a/llvm/test/tools/llvm-nm/zos-armap.test b/llvm/test/tools/llvm-nm/zos-armap.test
index e9674bb64c54d..4fd062ea8436b 100644
--- a/llvm/test/tools/llvm-nm/zos-armap.test
+++ b/llvm/test/tools/llvm-nm/zos-armap.test
@@ -6,25 +6,31 @@
 ##   bit 2 (0x4): 64-bit  (AMODE == ESD_AMODE_64)
 ##   bit 1 (0x2): XPLink  (LinkageType == ESD_LT_XPLink)
 ##   bit 0 (0x1): WSA     (parent ED namespace == ESD_NS_Parts)
-##   bit 3+      : unknown, printed as '?'
+##   bits above 0x7: unknown, printed as '?'
+##
+## symunk uses the highest bit (0x80000000) — the least likely to ever become
+## a known value — so that if new low bits are defined in the future, this
+## test still covers a genuinely unknown bit.
+## symall uses 0xffffffff so that adding any new known value will cause this
+## CHECK line to fail, prompting the developer to update the decoder.
 ##
 ## The archive is generated directly using generate_zos_archive.py.
 
 # RUN: rm -rf %t.dir && mkdir -p %t.dir
 
 # RUN: %python %S/../../Object/Inputs/generate_zos_archive.py \
-# RUN:   --output %t.dir/test.a          \
-# RUN:   --member "test.o:hex:abcdabcd"  \
-# RUN:   --symtab "sym000:0:0"           \
-# RUN:   --symtab "sym001:0:1"           \
-# RUN:   --symtab "sym010:0:2"           \
-# RUN:   --symtab "sym011:0:3"           \
-# RUN:   --symtab "sym100:0:4"           \
-# RUN:   --symtab "sym101:0:5"           \
-# RUN:   --symtab "sym110:0:6"           \
-# RUN:   --symtab "sym111:0:7"           \
-# RUN:   --symtab "symunk:0:8"           \
-# RUN:   --symtab "symall:0:15"
+# RUN:   --output %t.dir/test.a             \
+# RUN:   --member "test.o:hex:abcdabcd"     \
+# RUN:   --symtab "sym000:0:0"              \
+# RUN:   --symtab "sym001:0:1"              \
+# RUN:   --symtab "sym010:0:2"              \
+# RUN:   --symtab "sym011:0:3"              \
+# RUN:   --symtab "sym100:0:4"              \
+# RUN:   --symtab "sym101:0:5"              \
+# RUN:   --symtab "sym110:0:6"              \
+# RUN:   --symtab "sym111:0:7"              \
+# RUN:   --symtab "symunk:0:2147483648"     \
+# RUN:   --symtab "symall:0:4294967295"
 
 # RUN: llvm-nm --print-armap %t.dir/test.a | FileCheck %s
 
@@ -38,5 +44,5 @@
 # CHECK-NEXT: sym101 in test.o (flags: 0x00000005 [64-bit + WSA])
 # CHECK-NEXT: sym110 in test.o (flags: 0x00000006 [64-bit + XPLink])
 # CHECK-NEXT: sym111 in test.o (flags: 0x00000007 [64-bit + XPLink + WSA])
-# CHECK-NEXT: symunk in test.o (flags: 0x00000008 [?])
-# CHECK-NEXT: symall in test.o (flags: 0x0000000f [64-bit + XPLink + WSA + ?])
+# CHECK-NEXT: symunk in test.o (flags: 0x80000000 [?])
+# CHECK-NEXT: symall in test.o (flags: 0xffffffff [64-bit + XPLink + WSA + ?])
diff --git a/llvm/tools/llvm-nm/llvm-nm.cpp b/llvm/tools/llvm-nm/llvm-nm.cpp
index 0a178d76ea5ca..d32e398d52b5e 100644
--- a/llvm/tools/llvm-nm/llvm-nm.cpp
+++ b/llvm/tools/llvm-nm/llvm-nm.cpp
@@ -2046,38 +2046,38 @@ static bool checkMachOAndArchFlags(SymbolicFile *O, StringRef Filename) {
 }
 
 /// Decode the low 3 bits of a z/OS archive symbol attribute word into a
-/// human-readable string, e.g. "[64-bit + XPLink]".
+/// human-readable description written to OS, e.g. "[64-bit + XPLink]".
 /// Any bits above the known 3-bit mask produce a trailing "?" flag.
-static std::string decodeZOSAttributes(uint32_t Attrs) {
+static void decodeZOSAttributes(raw_ostream &OS, uint32_t Attrs) {
   bool Unknown = (Attrs & ~Archive::Symbol::ZOSKnownAttrMask) != 0;
   bool Is64Bit = (Attrs & Archive::Symbol::ZOSAttr64Bit) != 0;
   bool IsXPLink = (Attrs & Archive::Symbol::ZOSAttrXPLink) != 0;
   bool IsWSA = (Attrs & Archive::Symbol::ZOSAttrWSA) != 0;
 
-  std::string Result = "[";
+  OS << "[";
   bool NeedPlus = false;
-  auto append = [&](const char *S) {
+  auto Append = [&](const char *S) {
     if (NeedPlus)
-      Result += " + ";
-    Result += S;
+      OS << " + ";
+    OS << S;
     NeedPlus = true;
   };
   if (Is64Bit)
-    append("64-bit");
+    Append("64-bit");
   if (IsXPLink)
-    append("XPLink");
+    Append("XPLink");
   if (IsWSA)
-    append("WSA");
+    Append("WSA");
   if (Unknown)
-    append("?");
+    Append("?");
   if (!NeedPlus)
-    append("none");
-  Result += "]";
-  return Result;
+    Append("none");
+  OS << "]";
 }
 
 static void printArchiveMap(iterator_range<Archive::symbol_iterator> &Map,
-                            StringRef Filename, bool PrintZOSAttrs = false) {
+                            StringRef Filename,
+                            Archive::Kind Kind = Archive::K_GNU) {
   for (auto I : Map) {
     Expected<Archive::Child> C = I.getMember();
     if (!C) {
@@ -2091,12 +2091,11 @@ static void printArchiveMap(iterator_range<Archive::symbol_iterator> &Map,
     }
     StringRef SymName = I.getName();
     outs() << SymName << " in " << FileNameOrErr.get();
-    if (PrintZOSAttrs) {
+    if (Kind == Archive::K_ZOS) {
       uint32_t Attrs = I.getZOSAttributes();
-      std::string AttrsStr;
-      llvm::raw_string_ostream(AttrsStr) << format("0x%08x", Attrs);
-      outs() << " (flags: " << AttrsStr << " " << decodeZOSAttributes(Attrs)
-             << ")";
+      outs() << format(" (flags: 0x%08x ", Attrs);
+      decodeZOSAttributes(outs(), Attrs);
+      outs() << ")";
     }
     outs() << "\n";
   }
@@ -2108,7 +2107,7 @@ static void dumpArchiveMap(Archive *A, StringRef Filename) {
   auto Map = A->symbols();
   if (!Map.empty()) {
     outs() << "Archive map\n";
-    printArchiveMap(Map, Filename, A->kind() == Archive::K_ZOS);
+    printArchiveMap(Map, Filename, A->kind());
   }
 
   auto ECMap = A->ec_symbols();

>From 7a8c44a596fbb5ff09abe2fcfc3916921896636b Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Tue, 11 Aug 2026 11:53:20 -0400
Subject: [PATCH 4/5] Update test comment.

---
 llvm/test/tools/llvm-nm/zos-armap.test | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/test/tools/llvm-nm/zos-armap.test b/llvm/test/tools/llvm-nm/zos-armap.test
index 4fd062ea8436b..800d5c00eb15b 100644
--- a/llvm/test/tools/llvm-nm/zos-armap.test
+++ b/llvm/test/tools/llvm-nm/zos-armap.test
@@ -6,13 +6,13 @@
 ##   bit 2 (0x4): 64-bit  (AMODE == ESD_AMODE_64)
 ##   bit 1 (0x2): XPLink  (LinkageType == ESD_LT_XPLink)
 ##   bit 0 (0x1): WSA     (parent ED namespace == ESD_NS_Parts)
-##   bits above 0x7: unknown, printed as '?'
+##   bits above 0x7:      unknown, printed as '?'
 ##
 ## symunk uses the highest bit (0x80000000) — the least likely to ever become
 ## a known value — so that if new low bits are defined in the future, this
 ## test still covers a genuinely unknown bit.
 ## symall uses 0xffffffff so that adding any new known value will cause this
-## CHECK line to fail, prompting the developer to update the decoder.
+## CHECK line to fail.
 ##
 ## The archive is generated directly using generate_zos_archive.py.
 

>From d0bc528211708da14e3d8ad5252dc4f40b156543 Mon Sep 17 00:00:00 2001
From: Amy Kwan <amy.kwan1 at ibm.com>
Date: Sat, 15 Aug 2026 01:24:04 -0400
Subject: [PATCH 5/5] Address review comments on the parameter.

---
 llvm/tools/llvm-nm/llvm-nm.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/llvm/tools/llvm-nm/llvm-nm.cpp b/llvm/tools/llvm-nm/llvm-nm.cpp
index d32e398d52b5e..6e7545655fdf9 100644
--- a/llvm/tools/llvm-nm/llvm-nm.cpp
+++ b/llvm/tools/llvm-nm/llvm-nm.cpp
@@ -2076,8 +2076,7 @@ static void decodeZOSAttributes(raw_ostream &OS, uint32_t Attrs) {
 }
 
 static void printArchiveMap(iterator_range<Archive::symbol_iterator> &Map,
-                            StringRef Filename,
-                            Archive::Kind Kind = Archive::K_GNU) {
+                            StringRef Filename, Archive::Kind Kind) {
   for (auto I : Map) {
     Expected<Archive::Child> C = I.getMember();
     if (!C) {
@@ -2115,7 +2114,7 @@ static void dumpArchiveMap(Archive *A, StringRef Filename) {
     warn(ECMap.takeError(), Filename);
   } else if (!ECMap->empty()) {
     outs() << "Archive EC map\n";
-    printArchiveMap(*ECMap, Filename);
+    printArchiveMap(*ECMap, Filename, A->kind());
   }
 }
 



More information about the llvm-commits mailing list