[llvm] [z/OS] Add z/OS archive reading support (PR #187110)

Uyiosa Iyekekpolor via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 15 09:14:29 PDT 2026


https://github.com/uyoyo0 updated https://github.com/llvm/llvm-project/pull/187110

>From 12727a9a353bf7dead4aba1086b58e05a9a92cdf Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Tue, 17 Mar 2026 15:29:38 -0400
Subject: [PATCH 1/7] [z/OS] Add z/OS archive reading support

---
 llvm/include/llvm/Object/Archive.h         |  53 +++++-
 llvm/lib/Object/Archive.cpp                | 211 ++++++++++++++++++++-
 llvm/lib/Object/ArchiveWriter.cpp          |   3 +
 llvm/test/Object/Inputs/zos-archive-test.a | Bin 0 -> 1424 bytes
 llvm/test/Object/zos-archive-read.test     |   9 +
 5 files changed, 273 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Object/Inputs/zos-archive-test.a
 create mode 100644 llvm/test/Object/zos-archive-read.test

diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index c97018d3231d5..9a72460846502 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -158,6 +158,35 @@ class LLVM_ABI BigArchiveMemberHeader
   Expected<bool> isThin() const override { return false; }
 };
 
+// Define file member header of z/OS archive.
+class ZOSArchiveMemberHeader : public ArchiveMemberHeader {
+public:
+  ZOSArchiveMemberHeader(Archive const *Parent, const char *RawHeaderPtr,
+                         uint64_t Size, Error *Err);
+  std::unique_ptr<AbstractArchiveMemberHeader> clone() const override {
+    return std::make_unique<ZOSArchiveMemberHeader>(*this);
+  }
+
+  // Converted EBCDIC to ASCII header string fields.
+  std::string RawMemberName;
+  std::string MemberName;
+  std::string LastModified;
+  std::string UID;
+  std::string GID;
+  std::string AccessMode;
+
+  void setMemberHeaderStrings(Error *Err, uint64_t Size);
+
+  Expected<StringRef> getRawName() const override;
+  Expected<StringRef> getName(uint64_t Size) const override;
+  StringRef getRawAccessMode() const override;
+  StringRef getRawLastModified() const override;
+  StringRef getRawUID() const override;
+  StringRef getRawGID() const override;
+  Expected<uint64_t> getSize() const override;
+  Expected<bool> isThin() const override { return false; }
+};
+
 class LLVM_ABI Archive : public Binary {
   virtual void anchor();
 
@@ -343,7 +372,16 @@ class LLVM_ABI Archive : public Binary {
   /// Size field is 10 decimal digits long
   static const uint64_t MaxMemberSize = 9999999999;
 
-  enum Kind { K_GNU, K_GNU64, K_BSD, K_DARWIN, K_DARWIN64, K_COFF, K_AIXBIG };
+  enum Kind {
+    K_GNU,
+    K_GNU64,
+    K_BSD,
+    K_DARWIN,
+    K_DARWIN64,
+    K_COFF,
+    K_AIXBIG,
+    K_ZOS
+  };
 
   Kind kind() const { return (Kind)Format; }
   bool isThin() const { return IsThin; }
@@ -434,6 +472,19 @@ class BigArchive : public Archive {
   bool has64BitGlobalSymtab() { return Has64BitGlobalSymtab; }
 };
 
+class ZOSArchive : public Archive {
+public:
+  // Fixed-Length header.
+  struct FixLenHdr {
+    char Magic[sizeof(ZOSArchiveMagic) - 1]; ///< ZOS archive magic string.
+  };
+
+public:
+  ZOSArchive(MemoryBufferRef Source, Error &Err);
+
+private:
+  std::string SymbolTableBuf; // __.SYMDEF strings converted to ASCII.
+};
 } // end namespace object
 } // end namespace llvm
 
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 17c926e621f36..c7a3e75f0f05e 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -17,6 +17,7 @@
 #include "llvm/Object/Binary.h"
 #include "llvm/Object/Error.h"
 #include "llvm/Support/Chrono.h"
+#include "llvm/Support/ConvertEBCDIC.h"
 #include "llvm/Support/Endian.h"
 #include "llvm/Support/EndianStream.h"
 #include "llvm/Support/Error.h"
@@ -104,7 +105,11 @@ ArchiveMemberHeader::ArchiveMemberHeader(const Archive *Parent,
     *Err = createMemberHeaderParseError(this, RawHeaderPtr, Size);
     return;
   }
-  if (ArMemHdr->Terminator[0] != '`' || ArMemHdr->Terminator[1] != '\n') {
+  if ((ArMemHdr->Terminator[0] != '`' || ArMemHdr->Terminator[1] != '\n') &&
+      (ArMemHdr->Terminator[0] != '\x79' ||
+       ArMemHdr->Terminator[1] !=
+           '\x15') // '\x79\x15' is '`\n' in EBCDIC for z/OS archive terminator.
+  ) {
     if (Err) {
       std::string Buf;
       raw_string_ostream OS(Buf);
@@ -368,6 +373,121 @@ Expected<uint64_t> BigArchiveMemberHeader::getSize() const {
   return *SizeOrErr + alignTo(*NameLenOrErr, 2);
 }
 
+template <class T, std::size_t N>
+StringRef getFieldRawStringE2A(const T (&Field)[N], SmallString<64> &Dst) {
+  StringRef Src = StringRef(Field, N);
+  ConverterEBCDIC::convertToUTF8(Src, Dst);
+  return Dst.str().rtrim(" ");
+}
+
+ZOSArchiveMemberHeader::ZOSArchiveMemberHeader(const Archive *Parent,
+                                               const char *RawHeaderPtr,
+                                               uint64_t Size, Error *Err)
+    : ArchiveMemberHeader(Parent, RawHeaderPtr, Size, Err) {
+  ErrorAsOutParameter ErrAsOutParam(Err);
+  setMemberHeaderStrings(Err, Size);
+}
+
+Expected<uint64_t> ZOSArchiveMemberHeader::getSize() const {
+  SmallString<64> Dst;
+  return getArchiveMemberDecField(
+      "size", getFieldRawStringE2A(ArMemHdr->Size, Dst), Parent, this);
+}
+
+Expected<StringRef> ZOSArchiveMemberHeader::getRawName() const {
+  return StringRef(RawMemberName);
+}
+
+Expected<StringRef> ZOSArchiveMemberHeader::getName(uint64_t Size) const {
+  return StringRef(MemberName);
+}
+
+StringRef ZOSArchiveMemberHeader::getRawAccessMode() const {
+  return StringRef(AccessMode);
+}
+
+StringRef ZOSArchiveMemberHeader::getRawLastModified() const {
+  return StringRef(LastModified);
+}
+
+StringRef ZOSArchiveMemberHeader::getRawUID() const { return StringRef(UID); }
+
+StringRef ZOSArchiveMemberHeader::getRawGID() const { return StringRef(GID); }
+
+void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
+  SmallString<64> Dst;
+  uint64_t Offset =
+      reinterpret_cast<const char *>(ArMemHdr) - Parent->getData().data();
+
+  // Set RawMemberName.
+  StringRef RawNameSR = getFieldRawStringE2A(ArMemHdr->Name, Dst);
+  if (RawNameSR.empty() || RawNameSR[0] == ' ') {
+    *Err = malformedError("name contains a leading space for archive member "
+                          "header at offset " +
+                          Twine(Offset));
+    return;
+  }
+  RawMemberName.append(RawNameSR);
+
+  // Set MemberName.
+  if (RawNameSR.starts_with("#1/")) {
+    Expected<StringRef> NameOrErr = ArchiveMemberHeader::getName(Size);
+    if (!NameOrErr) {
+      *Err = NameOrErr.takeError();
+      return;
+    }
+    StringRef Name = NameOrErr.get();
+    Dst.clear();
+    ConverterEBCDIC::convertToUTF8(Name, Dst);
+    MemberName.append(Dst.str());
+  } else
+    MemberName = RawMemberName;
+
+  // LastModified
+  Dst.clear();
+  StringRef LastModifiedSR = getFieldRawStringE2A(ArMemHdr->LastModified, Dst);
+  if (LastModifiedSR.empty()) {
+    *Err = malformedError("problem converting LastModified field in "
+                          "header at offset " +
+                          Twine(Offset));
+    return;
+  }
+  LastModified.append(LastModifiedSR);
+
+  // UID
+  Dst.clear();
+  StringRef UIDSR = getFieldRawStringE2A(ArMemHdr->UID, Dst);
+  if (UIDSR.empty()) {
+    *Err = malformedError("problem converting UID field in "
+                          "header at offset " +
+                          Twine(Offset));
+    return;
+  }
+  UID.append(UIDSR);
+
+  // GID
+  Dst.clear();
+  StringRef GIDSR = getFieldRawStringE2A(ArMemHdr->GID, Dst);
+  if (GIDSR.empty()) {
+    *Err = malformedError("problem converting GID field in "
+                          "header at offset " +
+                          Twine(Offset));
+    return;
+  }
+  GID.append(GIDSR);
+
+  // AccessMode
+  Dst.clear();
+  StringRef AccessModeSR = getFieldRawStringE2A(ArMemHdr->AccessMode, Dst);
+  if (AccessModeSR.empty()) {
+    *Err = malformedError("problem converting AccessMode field in "
+                          "header at offset " +
+                          Twine(Offset));
+    return;
+  }
+  AccessMode.append(AccessModeSR);
+}
+
 Expected<uint64_t> BigArchiveMemberHeader::getRawNameSize() const {
   return getArchiveMemberDecField(
       "NameLen", getFieldRawString(ArMemHdr->NameLen), Parent, this);
@@ -668,6 +788,8 @@ Expected<std::unique_ptr<Archive>> Archive::create(MemoryBufferRef Source) {
 
   if (Buffer.starts_with(BigArchiveMagic))
     Ret = std::make_unique<BigArchive>(Source, Err);
+  else if (Buffer.starts_with(ZOSArchiveMagic))
+    Ret = std::make_unique<ZOSArchive>(Source, Err);
   else
     Ret = std::make_unique<Archive>(Source, Err);
 
@@ -680,6 +802,10 @@ std::unique_ptr<AbstractArchiveMemberHeader>
 Archive::createArchiveMemberHeader(const char *RawHeaderPtr, uint64_t Size,
                                    Error *Err) const {
   ErrorAsOutParameter ErrAsOutParam(Err);
+
+  if (kind() == K_ZOS)
+    return std::make_unique<ZOSArchiveMemberHeader>(this, RawHeaderPtr, Size,
+                                                    Err);
   if (kind() != K_AIXBIG)
     return std::make_unique<ArchiveMemberHeader>(this, RawHeaderPtr, Size, Err);
   return std::make_unique<BigArchiveMemberHeader>(this, RawHeaderPtr, Size,
@@ -695,7 +821,6 @@ uint64_t Archive::getArchiveMagicLen() const {
 
   return sizeof(ArchiveMagic) - 1;
 }
-
 void Archive::setFirstRegular(const Child &C) {
   FirstRegularData = C.Data;
   FirstRegularStartOfFile = C.StartOfFile;
@@ -714,6 +839,10 @@ Archive::Archive(MemoryBufferRef Source, Error &Err)
     Format = K_AIXBIG;
     IsThin = false;
     return;
+  } else if (Buffer.starts_with(ZOSArchiveMagic)) {
+    Format = K_ZOS;
+    IsThin = false;
+    return;
   } else {
     Err = make_error<GenericBinaryError>("file too small to be an archive",
                                          object_error::invalid_file_type);
@@ -971,6 +1100,8 @@ object::Archive::Kind Archive::getDefaultKindForTriple(const Triple &T) {
     return object::Archive::K_AIXBIG;
   if (T.isOSWindows())
     return object::Archive::K_COFF;
+  if (T.isOSzOS())
+    return object::Archive::K_ZOS;
   return object::Archive::K_GNU;
 }
 
@@ -1042,6 +1173,8 @@ Expected<Archive::Child> Archive::Symbol::getMember() const {
     // the archive of the member that defines the symbol.  Which is what
     // is needed here.
     Offset = read64le(Offsets + SymbolIndex * 16 + 8);
+  } else if (Parent->kind() == K_ZOS) {
+    Offset = read32be(Offsets + SymbolIndex * 8);
   } else {
     // Skip offsets.
     uint32_t MemberCount = read32le(Buf);
@@ -1171,6 +1304,9 @@ Archive::symbol_iterator Archive::symbol_begin() const {
     buf += ran_strx;
   } else if (kind() == K_AIXBIG) {
     buf = getStringTable().begin();
+  } else if (kind() == K_ZOS) {
+    uint32_t symbol_count = read32be(buf);
+    buf += sizeof(uint32_t) + (symbol_count * (sizeof(uint64_t)));
   } else {
     uint32_t member_count = 0;
     uint32_t symbol_count = 0;
@@ -1244,6 +1380,9 @@ uint32_t Archive::getNumberOfSymbols() const {
     return read32le(buf) / 8;
   if (kind() == K_DARWIN64)
     return read64le(buf) / 16;
+  if (kind() == K_ZOS) {
+    return read32be(buf);
+  }
   uint32_t member_count = 0;
   member_count = read32le(buf);
   buf += 4 + (member_count * 4); // Skip offsets.
@@ -1448,3 +1587,71 @@ BigArchive::BigArchive(MemoryBufferRef Source, Error &Err)
   setFirstRegular(*I);
   Err = Error::success();
 }
+
+ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
+    : Archive(Source, Err) {
+  ErrorAsOutParameter ErrAsOutParam(&Err);
+
+  // Get the special members.
+  child_iterator I = child_begin(Err, false);
+  if (Err)
+    return;
+  child_iterator E = child_end();
+
+  // See if this is a valid empty archive and if so return.
+  if (I == E) {
+    Err = Error::success();
+    return;
+  }
+  const Child *C = &*I;
+
+  auto Increment = [&]() {
+    ++I;
+    if (Err)
+      return true;
+    C = &*I;
+    return false;
+  };
+
+  Expected<StringRef> NameOrErr = C->getRawName();
+  if (!NameOrErr) {
+    Err = NameOrErr.takeError();
+    return;
+  }
+  StringRef Name = NameOrErr.get();
+
+  if (Name == "__.SYMDEF") {
+    // We know that the symbol table is not an external file, but we still must
+    // check any Expected<> return value.
+    Expected<StringRef> BufOrErr = C->getBuffer();
+    if (!BufOrErr) {
+      Err = BufOrErr.takeError();
+      return;
+    }
+
+    // Copy symbol table converting embedded EBCDIC names to ASCII.
+    StringRef ESymbolTable = BufOrErr.get();
+    uint32_t ESymbolCount = read32be(ESymbolTable.data());
+    uint32_t OffsetToENames =
+        sizeof(uint32_t) + (ESymbolCount * (sizeof(uint64_t)));
+    uint32_t ENamesSize = (uint32_t)ESymbolTable.size() - OffsetToENames;
+    const char *ENamesPtr = (const char *)ESymbolTable.data() + OffsetToENames;
+    StringRef ENames(ENamesPtr, ENamesSize);
+
+    SmallString<64> Dst;
+    ConverterEBCDIC::convertToUTF8(ENames, Dst);
+    SymbolTableBuf.append(ESymbolTable.data(), OffsetToENames);
+    SymbolTableBuf.append(Dst.str());
+    SymbolTable = StringRef(SymbolTableBuf.data(), SymbolTableBuf.size());
+    if (Increment())
+      return;
+    setFirstRegular(*C);
+
+    Err = Error::success();
+    return;
+  }
+
+  setFirstRegular(*C);
+  Err = Error::success();
+  return;
+}
\ No newline at end of file
diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp
index 6d2bbca179836..4610fb4303274 100644
--- a/llvm/lib/Object/ArchiveWriter.cpp
+++ b/llvm/lib/Object/ArchiveWriter.cpp
@@ -192,6 +192,7 @@ static bool isBSDLike(object::Archive::Kind Kind) {
   case object::Archive::K_GNU64:
   case object::Archive::K_AIXBIG:
   case object::Archive::K_COFF:
+  case object::Archive::K_ZOS:
     return false;
   case object::Archive::K_BSD:
   case object::Archive::K_DARWIN:
@@ -287,6 +288,7 @@ static bool is64BitKind(object::Archive::Kind Kind) {
   case object::Archive::K_BSD:
   case object::Archive::K_DARWIN:
   case object::Archive::K_COFF:
+  case object::Archive::K_ZOS:
     return false;
   case object::Archive::K_AIXBIG:
   case object::Archive::K_DARWIN64:
@@ -517,6 +519,7 @@ getSymbolicFile(MemoryBufferRef Buf, LLVMContext &Context,
       case object::Archive::K_COFF:
       case object::Archive::K_DARWIN:
       case object::Archive::K_DARWIN64:
+      case object::Archive::K_ZOS:
         return ObjOrErr.takeError();
       }
     }
diff --git a/llvm/test/Object/Inputs/zos-archive-test.a b/llvm/test/Object/Inputs/zos-archive-test.a
new file mode 100644
index 0000000000000000000000000000000000000000..0244f176c64478cec3918b1ca6bed78b897eb2c1
GIT binary patch
literal 1424
zcmbtUPfO!K6n}C0?;uOvZJ{h8rFa%ULOkuEMS4&O;=wPFy^9_q3yVkbQ1K`T{Twyz
zQZEwNMe!n`dT>uWjcN2`_q~}U9b at CN_~6a^|C{$_CfR809e>;ZS~ku4;Qm*)_d}P&
zLrFsF>Z<F*AWGOS1AvCSM*O|i0_1ut3di-*p`rx7FPuZ~Dco+m%s8U_AJN{a#AM{n
zoYZ1~EioBD7H8VAF%j_yYX&)q*;jEiOSM9sK>;8`M$R_v%WhBL5f-&cGL at 2>Q6d)2
zr1VFdOe=};nH8%Og=yFywE_j(+}*vp>Ue08Ba18=r(j5!J0Vt`=!&L+Ch&6g=F!of
zC%~w$9CEV1no<YT&#zbjOX8iG=iTd{0+0I2Pr*~+UTr3V49`zaRj_-#!ENXI7SZV*
zXtwA+mR|scMzVQwi(1hhF!~OJZ%&8^OBlHH8PsH^^i-mN$Gw0*3?GMqKk|J3z>#pg
z{1?tk8aBy3QKp5wi7xHn&Y3k_r?CRAwGMWvy$GcyxQ)Z23r)w(RBjC3@!L<J8IF^|
z%vnQs7PAWmx92OX*+0NO5?<RM-+#V1$&JTjhisx}Dd2#l4n<s-Fn$`krHJQ at yo~bX
zdx=~mU%VAH at Gl+lqW7Yv*6^?wM!{(G`*J{k7(A!M$#FG(qO&UM%XGQ&;>GKx+sSOv
Xf{c`};-bXq^`{Wi<G_bldx!EDV(Pt2

literal 0
HcmV?d00001

diff --git a/llvm/test/Object/zos-archive-read.test b/llvm/test/Object/zos-archive-read.test
new file mode 100644
index 0000000000000..7adf2a5256697
--- /dev/null
+++ b/llvm/test/Object/zos-archive-read.test
@@ -0,0 +1,9 @@
+## Test reading a z/OS archive.
+
+# RUN: llvm-ar t %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=LIST
+# RUN: llvm-nm --print-armap %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=SYMS
+
+# LIST: foo.o
+
+# SYMS: Archive map
+# SYMS-NEXT: foo in foo.o
\ No newline at end of file

>From 12e3e4b6314642020707919ab0218bd31803d6ba Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Thu, 9 Apr 2026 10:51:04 -0400
Subject: [PATCH 2/7] Address review comments

---
 llvm/include/llvm/Object/Archive.h | 1 -
 llvm/lib/Object/Archive.cpp        | 6 +++---
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index 9a72460846502..b45a8e4673402 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -479,7 +479,6 @@ class ZOSArchive : public Archive {
     char Magic[sizeof(ZOSArchiveMagic) - 1]; ///< ZOS archive magic string.
   };
 
-public:
   ZOSArchive(MemoryBufferRef Source, Error &Err);
 
 private:
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index c7a3e75f0f05e..1554161eec34b 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -373,8 +373,8 @@ Expected<uint64_t> BigArchiveMemberHeader::getSize() const {
   return *SizeOrErr + alignTo(*NameLenOrErr, 2);
 }
 
-template <class T, std::size_t N>
-StringRef getFieldRawStringE2A(const T (&Field)[N], SmallString<64> &Dst) {
+template <std::size_t N>
+StringRef getFieldRawStringE2A(const char (&Field)[N], SmallString<64> &Dst) {
   StringRef Src = StringRef(Field, N);
   ConverterEBCDIC::convertToUTF8(Src, Dst);
   return Dst.str().rtrim(" ");
@@ -1654,4 +1654,4 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
   setFirstRegular(*C);
   Err = Error::success();
   return;
-}
\ No newline at end of file
+}

>From 1d9da96ac58bab96f1f247d86b85c202e8b29502 Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Thu, 9 Apr 2026 13:36:25 -0400
Subject: [PATCH 3/7] Address additional review comments

---
 llvm/include/llvm/Object/Archive.h | 10 ++++++++++
 llvm/lib/Object/Archive.cpp        | 17 ++++++++++-------
 2 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index b45a8e4673402..59eca4babd60d 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -159,6 +159,16 @@ class LLVM_ABI BigArchiveMemberHeader
 };
 
 // Define file member header of z/OS archive.
+// The fixed part of the member header (in EBCDIC) is:
+// struct ar_hdr {
+//   char ar_name[16]; /* blank terminated member name */
+//   char ar_date[12]; /* date (decimal) */
+//   char ar_uid[6];   /* user id (decimal) */
+//   char ar_gid[6];   /* group id (decimal) */
+//   char ar_mode[8];  /* access mode (octal) */
+//   char ar_size[10]; /* length in bytes (decimal) */
+//   char ar_fmag[2];  /* contains backtick (X'79'), followed by new line (X'15') */
+// };
 class ZOSArchiveMemberHeader : public ArchiveMemberHeader {
 public:
   ZOSArchiveMemberHeader(Archive const *Parent, const char *RawHeaderPtr,
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 1554161eec34b..98690a554356f 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -375,6 +375,7 @@ Expected<uint64_t> BigArchiveMemberHeader::getSize() const {
 
 template <std::size_t N>
 StringRef getFieldRawStringE2A(const char (&Field)[N], SmallString<64> &Dst) {
+  Dst.clear();
   StringRef Src = StringRef(Field, N);
   ConverterEBCDIC::convertToUTF8(Src, Dst);
   return Dst.str().rtrim(" ");
@@ -444,7 +445,6 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
     MemberName = RawMemberName;
 
   // LastModified
-  Dst.clear();
   StringRef LastModifiedSR = getFieldRawStringE2A(ArMemHdr->LastModified, Dst);
   if (LastModifiedSR.empty()) {
     *Err = malformedError("problem converting LastModified field in "
@@ -455,7 +455,6 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
   LastModified.append(LastModifiedSR);
 
   // UID
-  Dst.clear();
   StringRef UIDSR = getFieldRawStringE2A(ArMemHdr->UID, Dst);
   if (UIDSR.empty()) {
     *Err = malformedError("problem converting UID field in "
@@ -466,7 +465,6 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
   UID.append(UIDSR);
 
   // GID
-  Dst.clear();
   StringRef GIDSR = getFieldRawStringE2A(ArMemHdr->GID, Dst);
   if (GIDSR.empty()) {
     *Err = malformedError("problem converting GID field in "
@@ -477,7 +475,6 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
   GID.append(GIDSR);
 
   // AccessMode
-  Dst.clear();
   StringRef AccessModeSR = getFieldRawStringE2A(ArMemHdr->AccessMode, Dst);
   if (AccessModeSR.empty()) {
     *Err = malformedError("problem converting AccessMode field in "
@@ -1174,6 +1171,12 @@ Expected<Archive::Child> Archive::Symbol::getMember() const {
     // is needed here.
     Offset = read64le(Offsets + SymbolIndex * 16 + 8);
   } else if (Parent->kind() == K_ZOS) {
+    // The contents of the symbol table member in order are:
+    // 1. The number of symbols, NS (4 byte integer).
+    // 2. NS pairs of integers (the first in each pair of integers is the offset 
+    //    to the header of the entry... the second being coded attributes). 
+    //    Length is NS*(4+4) bytes.
+    // 3. NS null terminated strings of corresponding symbol names.
     Offset = read32be(Offsets + SymbolIndex * 8);
   } else {
     // Skip offsets.
@@ -1608,9 +1611,9 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
   auto Increment = [&]() {
     ++I;
     if (Err)
-      return true;
+      return false;
     C = &*I;
-    return false;
+    return true;
   };
 
   Expected<StringRef> NameOrErr = C->getRawName();
@@ -1643,7 +1646,7 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
     SymbolTableBuf.append(ESymbolTable.data(), OffsetToENames);
     SymbolTableBuf.append(Dst.str());
     SymbolTable = StringRef(SymbolTableBuf.data(), SymbolTableBuf.size());
-    if (Increment())
+    if (!Increment())
       return;
     setFirstRegular(*C);
 

>From 1b28b03be296581ef447824bf68c68acb920e015 Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Thu, 9 Apr 2026 13:51:06 -0400
Subject: [PATCH 4/7] Run clang-format

---
 llvm/include/llvm/Object/Archive.h | 3 ++-
 llvm/lib/Object/Archive.cpp        | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index 59eca4babd60d..cff8367e891ab 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -167,7 +167,8 @@ class LLVM_ABI BigArchiveMemberHeader
 //   char ar_gid[6];   /* group id (decimal) */
 //   char ar_mode[8];  /* access mode (octal) */
 //   char ar_size[10]; /* length in bytes (decimal) */
-//   char ar_fmag[2];  /* contains backtick (X'79'), followed by new line (X'15') */
+//   char ar_fmag[2];  /* contains backtick (X'79'), followed by new line
+//   (X'15') */
 // };
 class ZOSArchiveMemberHeader : public ArchiveMemberHeader {
 public:
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 98690a554356f..375a57126bfad 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1173,8 +1173,8 @@ Expected<Archive::Child> Archive::Symbol::getMember() const {
   } else if (Parent->kind() == K_ZOS) {
     // The contents of the symbol table member in order are:
     // 1. The number of symbols, NS (4 byte integer).
-    // 2. NS pairs of integers (the first in each pair of integers is the offset 
-    //    to the header of the entry... the second being coded attributes). 
+    // 2. NS pairs of integers (the first in each pair of integers is the offset
+    //    to the header of the entry... the second being coded attributes).
     //    Length is NS*(4+4) bytes.
     // 3. NS null terminated strings of corresponding symbol names.
     Offset = read32be(Offsets + SymbolIndex * 8);

>From 8cef225f1d00739788ada50e8b506ea7f993057c Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Thu, 9 Apr 2026 15:05:54 -0400
Subject: [PATCH 5/7] Move Increment lambda closer to its first use

---
 llvm/lib/Object/Archive.cpp | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 375a57126bfad..90479b0330cbf 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1608,14 +1608,6 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
   }
   const Child *C = &*I;
 
-  auto Increment = [&]() {
-    ++I;
-    if (Err)
-      return false;
-    C = &*I;
-    return true;
-  };
-
   Expected<StringRef> NameOrErr = C->getRawName();
   if (!NameOrErr) {
     Err = NameOrErr.takeError();
@@ -1646,6 +1638,15 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
     SymbolTableBuf.append(ESymbolTable.data(), OffsetToENames);
     SymbolTableBuf.append(Dst.str());
     SymbolTable = StringRef(SymbolTableBuf.data(), SymbolTableBuf.size());
+
+    auto Increment = [&]() {
+      ++I;
+      if (Err)
+        return false;
+      C = &*I;
+      return true;
+    };
+
     if (!Increment())
       return;
     setFirstRegular(*C);

>From 6fd2aae260b150d60f1d4f1d1c0e82fe1cf75fa1 Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Mon, 13 Apr 2026 09:53:02 -0400
Subject: [PATCH 6/7] Clarify z/OS symbol table offset math and layout comments

---
 llvm/lib/Object/Archive.cpp | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 90479b0330cbf..694a054aedae5 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1171,12 +1171,10 @@ Expected<Archive::Child> Archive::Symbol::getMember() const {
     // is needed here.
     Offset = read64le(Offsets + SymbolIndex * 16 + 8);
   } else if (Parent->kind() == K_ZOS) {
-    // The contents of the symbol table member in order are:
-    // 1. The number of symbols, NS (4 byte integer).
-    // 2. NS pairs of integers (the first in each pair of integers is the offset
-    //    to the header of the entry... the second being coded attributes).
-    //    Length is NS*(4+4) bytes.
-    // 3. NS null terminated strings of corresponding symbol names.
+    // Each entry in the offset array is 8 bytes long:
+    // A 4-byte offset followed by 4 bytes of coded attributes.
+    // We multiply the SymbolIndex by 8 to reach the correct entry,
+    // and read the first 4 bytes (the offset).
     Offset = read32be(Offsets + SymbolIndex * 8);
   } else {
     // Skip offsets.
@@ -1308,6 +1306,12 @@ Archive::symbol_iterator Archive::symbol_begin() const {
   } else if (kind() == K_AIXBIG) {
     buf = getStringTable().begin();
   } else if (kind() == K_ZOS) {
+    // The contents of the z/OS symbol table member are:
+    // 1. The number of symbols, NS (4-byte integer).
+    // 2. NS pairs of 4-byte integers (offset and attributes). Length is NS*8
+    // bytes.
+    // 3. NS null terminated strings of corresponding symbol names.
+    // Here we skip parts 1 and 2 to reach the start of the string table.
     uint32_t symbol_count = read32be(buf);
     buf += sizeof(uint32_t) + (symbol_count * (sizeof(uint64_t)));
   } else {

>From bda2dff8bd163e4f31d5a6f03dee6dab1d3e5b4f Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Mon, 13 Apr 2026 11:36:44 -0400
Subject: [PATCH 7/7] Add test for extracting z/OS archive member

---
 llvm/test/Object/Inputs/zos-expected-foo.o | Bin 0 -> 1280 bytes
 llvm/test/Object/zos-archive-read.test     |   6 +++++-
 2 files changed, 5 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/Object/Inputs/zos-expected-foo.o

diff --git a/llvm/test/Object/Inputs/zos-expected-foo.o b/llvm/test/Object/Inputs/zos-expected-foo.o
new file mode 100644
index 0000000000000000000000000000000000000000..4caf23315e4c9f140c3a0c11b92af7e0dfc77c7c
GIT binary patch
literal 1280
zcmbtSy-EW?5T4EE=L(UiDT0WEU at bnvSy?EOM!3QvFOVnD;%uzNLTf?DbHp>CjT{I{
z8abkcR<4)3Xl0z)y`S|=BI3aA{D1rH?r;Hr6*jGbtK`(0Dg8}QorS}blRXCu5sxA{
zTv^O39f#Sv7ug&PfIK-xr{&yTb|oHR5lzw)C3_eZVpDDcKRRSwOM=hs#7qs&kRMS2
z!)fjB-`{t9^e9lkC5clq60A*y*HT-v7%+)f+-#hl9{3WBcolJF`PH>P82^4{18jwN
z(Yn6u-AX*-RT_h*!~Hl+CON*Jx|QK{yZy&b?-9}IALwrJf2 at 52ikvKY3d>$e4>-<?
ztmJc2#aPn8?TSNPRjNoePWZwfiox6a+b{_IKnw&5UazzvM1>(qzCe{Wu3ZeVqbGO9
z^4zU;@a!FMD(w}hG{D<BE_={$y?ph-5}ly^0nBpUJXX$Frn^#DwuG}(-7dTWpC`O_
zFnV6RIV+AvBbR)VPo?09v<YS0S22Gcg_TO~8AmzQsd<S*q`%FIp7_m1ym(&p(mFo&
zaySgbm%F}cn)FVYmlJw<YV#V}YxKG5<K=YI at 8p}b;X*0ZaMSWM{S$<nNn$p!{$YFp
DKWm9X

literal 0
HcmV?d00001

diff --git a/llvm/test/Object/zos-archive-read.test b/llvm/test/Object/zos-archive-read.test
index 7adf2a5256697..2afcb1b79ee22 100644
--- a/llvm/test/Object/zos-archive-read.test
+++ b/llvm/test/Object/zos-archive-read.test
@@ -3,7 +3,11 @@
 # RUN: llvm-ar t %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=LIST
 # RUN: llvm-nm --print-armap %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=SYMS
 
+## Test extracting a member and verifying its content.
+# RUN: llvm-ar p %p/Inputs/zos-archive-test.a foo.o > %t.foo.o
+# RUN: cmp %t.foo.o %p/Inputs/zos-expected-foo.o
+
 # LIST: foo.o
 
 # SYMS: Archive map
-# SYMS-NEXT: foo in foo.o
\ No newline at end of file
+# SYMS-NEXT: foo in foo.o



More information about the llvm-commits mailing list