[yaml2obj][obj2yaml] - Teach tools to work with regular archives.
authorGeorgii Rymar <grimar@accesssoftek.com>
Tue, 20 Oct 2020 12:12:28 +0000 (15:12 +0300)
committerGeorgii Rymar <grimar@accesssoftek.com>
Wed, 28 Oct 2020 12:27:11 +0000 (15:27 +0300)
This teaches obj2yaml to dump valid regular (not thin) archives.
This also teaches yaml2obj to recognize archives YAML descriptions,
what allows to craft all different kinds of archives (valid and broken ones).

Differential revision: https://reviews.llvm.org/D89949

14 files changed:
llvm/include/llvm/ObjectYAML/ArchiveYAML.h [new file with mode: 0644]
llvm/include/llvm/ObjectYAML/ObjectYAML.h
llvm/include/llvm/ObjectYAML/yaml2obj.h
llvm/lib/ObjectYAML/ArchiveEmitter.cpp [new file with mode: 0644]
llvm/lib/ObjectYAML/ArchiveYAML.cpp [new file with mode: 0644]
llvm/lib/ObjectYAML/CMakeLists.txt
llvm/lib/ObjectYAML/ObjectYAML.cpp
llvm/lib/ObjectYAML/yaml2obj.cpp
llvm/test/tools/obj2yaml/Archives/regular.yaml [new file with mode: 0644]
llvm/test/tools/yaml2obj/Archives/regular.yaml [new file with mode: 0644]
llvm/tools/obj2yaml/CMakeLists.txt
llvm/tools/obj2yaml/archive2yaml.cpp [new file with mode: 0644]
llvm/tools/obj2yaml/obj2yaml.cpp
llvm/tools/obj2yaml/obj2yaml.h

diff --git a/llvm/include/llvm/ObjectYAML/ArchiveYAML.h b/llvm/include/llvm/ObjectYAML/ArchiveYAML.h
new file mode 100644 (file)
index 0000000..8d05fee
--- /dev/null
@@ -0,0 +1,77 @@
+//===- ArchiveYAML.h - Archive YAMLIO implementation ------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file declares classes for handling the YAML representation of archives.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_OBJECTYAML_ARCHIVEYAML_H
+#define LLVM_OBJECTYAML_ARCHIVEYAML_H
+
+#include "llvm/Support/YAMLTraits.h"
+#include "llvm/ObjectYAML/YAML.h"
+#include "llvm/ADT/MapVector.h"
+
+namespace llvm {
+namespace ArchYAML {
+
+struct Archive {
+  struct Child {
+    struct Field {
+      Field() = default;
+      Field(StringRef Default, unsigned Length)
+          : DefaultValue(Default), MaxLength(Length) {}
+      StringRef Value;
+      StringRef DefaultValue;
+      unsigned MaxLength;
+    };
+
+    Child() {
+      Fields["Name"] = {"", 16};
+      Fields["LastModified"] = {"0", 12};
+      Fields["UID"] = {"0", 6};
+      Fields["GID"] = {"0", 6};
+      Fields["AccessMode"] = {"0", 8};
+      Fields["Size"] = {"0", 10};
+      Fields["Terminator"] = {"`\n", 2};
+    }
+
+    MapVector<StringRef, Field> Fields;
+
+    Optional<yaml::BinaryRef> Content;
+    Optional<llvm::yaml::Hex8> PaddingByte;
+  };
+
+  StringRef Magic;
+  Optional<std::vector<Child>> Members;
+  Optional<yaml::BinaryRef> Content;
+};
+
+} // end namespace ArchYAML
+} // end namespace llvm
+
+LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ArchYAML::Archive::Child)
+
+namespace llvm {
+namespace yaml {
+
+template <> struct MappingTraits<ArchYAML::Archive> {
+  static void mapping(IO &IO, ArchYAML::Archive &A);
+  static std::string validate(IO &, ArchYAML::Archive &A);
+};
+
+template <> struct MappingTraits<ArchYAML::Archive::Child> {
+  static void mapping(IO &IO, ArchYAML::Archive::Child &C);
+  static std::string validate(IO &, ArchYAML::Archive::Child &C);
+};
+
+} // end namespace yaml
+} // end namespace llvm
+
+#endif // LLVM_OBJECTYAML_ARCHIVEYAML_H
index 0015fd3dc50152475a15889c86d74bb1a54041e9..dd26ce3e9703b533bfbc81be082f82be46a1d5e3 100644 (file)
@@ -9,6 +9,7 @@
 #ifndef LLVM_OBJECTYAML_OBJECTYAML_H
 #define LLVM_OBJECTYAML_OBJECTYAML_H
 
+#include "llvm/ObjectYAML/ArchiveYAML.h"
 #include "llvm/ObjectYAML/COFFYAML.h"
 #include "llvm/ObjectYAML/ELFYAML.h"
 #include "llvm/ObjectYAML/MachOYAML.h"
@@ -23,6 +24,7 @@ namespace yaml {
 class IO;
 
 struct YamlObjectFile {
+  std::unique_ptr<ArchYAML::Archive> Arch;
   std::unique_ptr<ELFYAML::Object> Elf;
   std::unique_ptr<COFFYAML::Object> Coff;
   std::unique_ptr<MachOYAML::Object> MachO;
index 34def363a55bcda3235c441d3958265785c785e9..1f693475c9463afb8796ef9449c9c9783643aaa5 100644 (file)
@@ -40,12 +40,17 @@ namespace WasmYAML {
 struct Object;
 }
 
+namespace ArchYAML {
+struct Archive;
+}
+
 namespace yaml {
 class Input;
 struct YamlObjectFile;
 
 using ErrorHandler = llvm::function_ref<void(const Twine &Msg)>;
 
+bool yaml2archive(ArchYAML::Archive &Doc, raw_ostream &Out, ErrorHandler EH);
 bool yaml2coff(COFFYAML::Object &Doc, raw_ostream &Out, ErrorHandler EH);
 bool yaml2elf(ELFYAML::Object &Doc, raw_ostream &Out, ErrorHandler EH,
               uint64_t MaxSize);
diff --git a/llvm/lib/ObjectYAML/ArchiveEmitter.cpp b/llvm/lib/ObjectYAML/ArchiveEmitter.cpp
new file mode 100644 (file)
index 0000000..a0cf8fe
--- /dev/null
@@ -0,0 +1,51 @@
+//===- ArchiveEmitter.cpp ---------------------------- --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ObjectYAML/ArchiveYAML.h"
+#include "llvm/ObjectYAML/yaml2obj.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+using namespace ArchYAML;
+
+namespace llvm {
+namespace yaml {
+
+bool yaml2archive(ArchYAML::Archive &Doc, raw_ostream &Out, ErrorHandler EH) {
+  Out.write(Doc.Magic.data(), Doc.Magic.size());
+
+  if (Doc.Content) {
+    Doc.Content->writeAsBinary(Out);
+    return true;
+  }
+
+  if (!Doc.Members)
+    return true;
+
+  auto WriteField = [&](StringRef Field, uint8_t Size) {
+    Out.write(Field.data(), Field.size());
+    for (size_t I = Field.size(); I != Size; ++I)
+      Out.write(' ');
+  };
+
+  for (const Archive::Child &C : *Doc.Members) {
+    for (auto &P : C.Fields)
+      WriteField(P.second.Value, P.second.MaxLength);
+
+    if (C.Content)
+      C.Content->writeAsBinary(Out);
+    if (C.PaddingByte)
+      Out.write(*C.PaddingByte);
+  }
+
+  return true;
+}
+
+} // namespace yaml
+} // namespace llvm
diff --git a/llvm/lib/ObjectYAML/ArchiveYAML.cpp b/llvm/lib/ObjectYAML/ArchiveYAML.cpp
new file mode 100644 (file)
index 0000000..d2ea1ea
--- /dev/null
@@ -0,0 +1,58 @@
+//===- ArchiveYAML.cpp - ELF YAMLIO implementation -------------------- ----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines classes for handling the YAML representation of archives.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ObjectYAML/ArchiveYAML.h"
+
+namespace llvm {
+
+namespace yaml {
+
+void MappingTraits<ArchYAML::Archive>::mapping(IO &IO, ArchYAML::Archive &A) {
+  assert(!IO.getContext() && "The IO context is initialized already");
+  IO.setContext(&A);
+  IO.mapTag("!Arch", true);
+  IO.mapOptional("Magic", A.Magic, "!<arch>\n");
+  IO.mapOptional("Members", A.Members);
+  IO.mapOptional("Content", A.Content);
+  IO.setContext(nullptr);
+}
+
+std::string MappingTraits<ArchYAML::Archive>::validate(IO &,
+                                                       ArchYAML::Archive &A) {
+  if (A.Members && A.Content)
+    return "\"Content\" and \"Members\" cannot be used together";
+  return "";
+}
+
+void MappingTraits<ArchYAML::Archive::Child>::mapping(
+    IO &IO, ArchYAML::Archive::Child &E) {
+  assert(IO.getContext() && "The IO context is not initialized");
+  for (auto &P : E.Fields)
+    IO.mapOptional(P.first.data(), P.second.Value, P.second.DefaultValue);
+  IO.mapOptional("Content", E.Content);
+  IO.mapOptional("PaddingByte", E.PaddingByte);
+}
+
+std::string
+MappingTraits<ArchYAML::Archive::Child>::validate(IO &,
+                                                  ArchYAML::Archive::Child &C) {
+  for (auto &P : C.Fields)
+    if (P.second.Value.size() > P.second.MaxLength)
+      return ("the maximum length of \"" + P.first + "\" field is " +
+              Twine(P.second.MaxLength))
+          .str();
+  return "";
+}
+
+} // end namespace yaml
+
+} // end namespace llvm
index 92ea1ec9866cf95f304ee4fea8741a9decce0953..a024fa96535e6ee889e5dfd101aa9c79c964c523 100644 (file)
@@ -1,4 +1,6 @@
 add_llvm_component_library(LLVMObjectYAML
+  ArchiveEmitter.cpp
+  ArchiveYAML.cpp
   CodeViewYAMLDebugSections.cpp
   CodeViewYAMLSymbols.cpp
   CodeViewYAMLTypeHashing.cpp
index 7f636f4eabac40859a88c02f080cf46835ce3b3c..4564b537c9a145dab378ddb64587f91a07a502c9 100644 (file)
@@ -33,7 +33,14 @@ void MappingTraits<YamlObjectFile>::mapping(IO &IO,
                                                          *ObjectFile.FatMachO);
   } else {
     Input &In = (Input &)IO;
-    if (IO.mapTag("!ELF")) {
+    if (IO.mapTag("!Arch")) {
+      ObjectFile.Arch.reset(new ArchYAML::Archive());
+      MappingTraits<ArchYAML::Archive>::mapping(IO, *ObjectFile.Arch);
+      std::string Err =
+          MappingTraits<ArchYAML::Archive>::validate(IO, *ObjectFile.Arch);
+      if (!Err.empty())
+        IO.setError(Err);
+    } else if (IO.mapTag("!ELF")) {
       ObjectFile.Elf.reset(new ELFYAML::Object());
       MappingTraits<ELFYAML::Object>::mapping(IO, *ObjectFile.Elf);
     } else if (IO.mapTag("!COFF")) {
index a04345f1294a24f78ede11de7f47a5b369baa89d..ef2ab83dcd24ea81f7db46b23bdd51d62f244862 100644 (file)
@@ -32,6 +32,8 @@ bool convertYAML(yaml::Input &YIn, raw_ostream &Out, ErrorHandler ErrHandler,
       return false;
     }
 
+    if (Doc.Arch)
+      return yaml2archive(*Doc.Arch, Out, ErrHandler);
     if (Doc.Elf)
       return yaml2elf(*Doc.Elf, Out, ErrHandler, MaxSize);
     if (Doc.Coff)
diff --git a/llvm/test/tools/obj2yaml/Archives/regular.yaml b/llvm/test/tools/obj2yaml/Archives/regular.yaml
new file mode 100644 (file)
index 0000000..8b2969f
--- /dev/null
@@ -0,0 +1,158 @@
+## Check how obj2yaml dumps regular archives.
+
+## Check how we dump an empty archive.
+
+# RUN: rm -f %t.empty.a
+# RUN: llvm-ar rc %t.empty.a
+# RUN: obj2yaml %t.empty.a | FileCheck %s --check-prefix=EMPTY
+
+# EMPTY:      --- !Arch
+# EMPTY-NEXT: Members: []
+# EMPTY-NEXT: ...
+
+## Check how we dump archives with multiple members.
+## Check we don't dump excessive spaces when dumping fields.
+## Check we don't dump fields with values that are equal to default values.
+## Check how we dump empty field values.
+
+# RUN: yaml2obj %s --docnum=1 -o %t.multiple.a
+# RUN: obj2yaml %t.multiple.a | FileCheck %s --check-prefix=MULTIPLE
+
+# MULTIPLE:      --- !Arch
+# MULTIPLE-NEXT: Members:
+# MULTIPLE-NEXT:   - Name:         'bbb/'
+# MULTIPLE-NEXT:     LastModified: '1'
+# MULTIPLE-NEXT:     UID:          '2'
+# MULTIPLE-NEXT:     GID:          '3'
+# MULTIPLE-NEXT:     AccessMode:   '644'
+# MULTIPLE-NEXT:     Size:         '6'
+# MULTIPLE-NEXT:     Content:      20616161200A
+# MULTIPLE-NEXT:   - Name:         'dddd/'
+# MULTIPLE-NEXT:     LastModified: '4'
+# MULTIPLE-NEXT:     UID:          '5'
+# MULTIPLE-NEXT:     GID:          '6'
+# MULTIPLE-NEXT:     AccessMode:   '987'
+# MULTIPLE-NEXT:     Size:         '7'
+# MULTIPLE-NEXT:     Content:      2063636363200A
+# MULTIPLE-NEXT:     PaddingByte:  0x0A
+# MULTIPLE-NEXT:   - LastModified: ''
+# MULTIPLE-NEXT:     UID:          ''
+# MULTIPLE-NEXT:     GID:          ''
+# MULTIPLE-NEXT:     AccessMode:   ''
+# MULTIPLE-NEXT:     Terminator:   ''
+# MULTIPLE-NEXT:     Content:      ''
+# MULTIPLE-NEXT:   - {}
+# MULTIPLE-NEXT: ...
+
+--- !Arch
+Members:
+  - Name:         'bbb/'
+    LastModified: '1'
+    UID:          '2'
+    GID:          '3'
+    AccessMode:   '644'
+    Size:         '6'
+    Terminator:   "`\n"
+    Content:      20616161200A ## " aaa \n"
+  - Name:         'dddd/'
+    LastModified: '4'
+    UID:          '5'
+    GID:          '6'
+    AccessMode:   '987'
+    Size:         '7'
+    Terminator:   "`\n"
+    Content:      2063636363200A ## " cccc \n"
+    PaddingByte:  0x0A
+## All fields are empty (where possible).
+  - Name:         ''
+    LastModified: ''
+    UID:          ''
+    GID:          ''
+    AccessMode:   ''
+    Size:         '0'
+    Terminator:   ''
+    Content:      ''
+## All fields are explicitly set to the default values.
+  - Name:         ''
+    LastModified: '0'
+    UID:          '0'
+    GID:          '0'
+    AccessMode:   '0'
+    Size:         '0'
+    Terminator:   "`\n"
+    Content:      ""
+...
+
+## Check we report an error for non-regular archives.
+
+# RUN: yaml2obj %s --docnum=2 -o %t.not.regular.a
+# RUN: not obj2yaml %t.not.regular.a 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t.not.regular.a --check-prefix=NOT-REGULAR-ERR
+
+# NOT-REGULAR-ERR: Error reading file: [[FILE]]: only regular archives are supported
+
+--- !Arch
+Magic: "!<thin>\n"
+Members:
+  - {}
+
+## Check we report an error when unable to read the header of an archive member.
+
+# RUN: yaml2obj %s --docnum=3 -o %t.truncated.a
+# RUN: not obj2yaml %t.truncated.a 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t.truncated.a --check-prefix=TRUNCATED-ERR
+
+# TRUNCATED-ERR: Error reading file: [[FILE]]: unable to read the header of a child at offset 0x8
+
+--- !Arch
+Content: "00"
+
+## Check we report an error when unable to read the data of an archive member.
+
+# RUN: yaml2obj %s --docnum=4 -o %t.entdata.a
+# RUN: not obj2yaml %t.entdata.a 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t.entdata.a --check-prefix=ENTDATA-ERR
+
+# ENTDATA-ERR: Error reading file: [[FILE]]: unable to read the data of a child at offset 0x8 of size 1: the remaining archive size is 0
+
+--- !Arch
+Members:
+  - Size: [[SIZE='1']]
+
+## Check we report an error when unable to read the size of an archive member.
+
+# RUN: yaml2obj %s --docnum=4 -DSIZE='x' -o %t.entsize.a
+# RUN: not obj2yaml %t.entsize.a 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t.entsize.a --check-prefix=ENTSIZE-ERR
+
+# ENTSIZE-ERR: Error reading file: [[FILE]]: unable to read the size of a child at offset 0x8 as integer: "x"
+
+## Check we don't try to dump the padding byte when the size of the content is odd and
+## the content ends at the end of a file.
+
+# RUN: yaml2obj %s --docnum=5 -DCONTENT="61" -o %t.no.padding.byte.a
+# RUN: obj2yaml %t.no.padding.byte.a | FileCheck %s --check-prefix=NO-PADDING-BYTE
+
+#      NO-PADDING-BYTE: --- !Arch
+# NO-PADDING-BYTE-NEXT: Members:
+# NO-PADDING-BYTE-NEXT:   - Size:    '1'
+# NO-PADDING-BYTE-NEXT:     Content: '61'
+# NO-PADDING-BYTE-NEXT: ...
+
+--- !Arch
+Members:
+  - Size:    '1'
+    Content: [[CONTENT]]
+
+## Check we dump the padding byte when the size of the content is odd and the content ends
+## before the end of a file.
+
+# RUN: yaml2obj %s --docnum=5 -DCONTENT="610A" -o %t.padding.byte.a
+# RUN: obj2yaml %t.padding.byte.a | FileCheck %s --check-prefix=PADDING-BYTE
+
+#      PADDING-BYTE: --- !Arch
+# PADDING-BYTE-NEXT: Members:
+# PADDING-BYTE-NEXT:   - Size:        '1'
+# PADDING-BYTE-NEXT:     Content:     '61'
+# PADDING-BYTE-NEXT:     PaddingByte: 0x0A
+# PADDING-BYTE-NEXT: ...
diff --git a/llvm/test/tools/yaml2obj/Archives/regular.yaml b/llvm/test/tools/yaml2obj/Archives/regular.yaml
new file mode 100644 (file)
index 0000000..29b0cb5
--- /dev/null
@@ -0,0 +1,132 @@
+## Check how yaml2obj creates archives.
+
+## Check we create an empty archive when neither "Members" nor "Content" are specified.
+
+# RUN: yaml2obj --docnum=1 %s -o %t.empty.a
+# RUN: llvm-ar t %t.empty.a | FileCheck %s --allow-empty --implicit-check-not={{.}}
+# RUN: wc -c < %t.empty.a | FileCheck %s --check-prefix=EMPTY-SIZE
+# RUN: od -t x1 -v %t.empty.a | FileCheck %s --ignore-case --check-prefix=EMPTY-DATA
+
+# EMPTY-SIZE: 8{{$}}
+# EMPTY-DATA: 21 3c 61 72 63 68 3e 0a
+
+--- !Arch
+Magic:   "[[MAGIC=!<arch>\n]]"
+Content: [[CONTENT=<none>]]
+Members: [[MEMBERS=<none>]]
+
+## Check we report an error when both "Content" and "Members" keys are used together.
+
+# RUN: not yaml2obj --docnum=1 -DMEMBERS="[]" -DCONTENT="00" %s 2>&1 | FileCheck %s --check-prefix=BOTH
+
+## BOTH: error: "Content" and "Members" cannot be used together
+
+# RUN: yaml2obj --docnum=1 -DCONTENT="12" %s -o %t.content.a
+# RUN: od -t x1 -v %t.content.a | FileCheck %s --ignore-case --check-prefix=CONTENT
+
+# CONTENT: 21 3c 61 72 63 68 3e 0a 12{{$}}
+
+## Check we can specify magic bytes of size greater than the normal size (size of "!<arch>\n").
+
+# RUN: yaml2obj --docnum=1 -DMAGIC="123456789" %s -o %t.magic2.a
+# RUN: wc -c < %t.magic2.a | FileCheck %s --check-prefix=MAGIC-SIZE-GR
+# RUN: od -t x1 -v %t.magic2.a | FileCheck %s --ignore-case --check-prefix=MAGIC-DATA-GR
+
+# MAGIC-SIZE-GR: 9{{$}}
+# MAGIC-DATA-GR: 31 32 33 34 35 36 37 38 39
+
+## Check we can specify magic bytes of size less than the normal size (size of "!<arch>\n").
+
+# RUN: yaml2obj --docnum=1 -DMAGIC="1234567" %s -o %t.magic3.a
+# RUN: wc -c < %t.magic3.a | FileCheck %s --check-prefix=MAGIC-SIZE-LESS
+# RUN: od -t x1 -v %t.magic3.a | FileCheck %s --ignore-case --check-prefix=MAGIC-DATA-LESS
+
+# MAGIC-SIZE-LESS: 7{{$}}
+# MAGIC-DATA-LESS: 31 32 33 34 35 36 37
+
+## Check we can produce a valid archive with multiple members.
+## Check we are able to omit the "Magic" key and this defaults to "!<arch>\n".
+
+# RUN: yaml2obj --docnum=2 %s -o %t.two.a
+# RUN: llvm-ar -t %t.two.a | FileCheck %s --check-prefix=TWO
+# RUN: FileCheck --input-file=%t.two.a %s \
+# RUN:   --match-full-lines --strict-whitespace --check-prefix=TWO-DATA
+
+# TWO:      {{^}}bbbbbbbbbbbbbbb{{$}}
+# TWO-NEXT: {{^}}a{{$}}
+# TWO-NOT:  {{.}}
+
+#      TWO-DATA:!<arch>
+# TWO-DATA-NEXT:bbbbbbbbbbbbbbb/1234567890abqwertyasdfgh876543217         `
+# TWO-DATA-NEXT: cccc {{$}}
+# TWO-DATA-NEXT:za/              1           2     3     456     6         `
+# TWO-DATA-NEXT: aaa {{$}}
+#  TWO-DATA-NOT:{{.}}
+
+--- !Arch
+Members:
+## An arbitrary entry where each of fields has maximum allowed length.
+  - Name:         'bbbbbbbbbbbbbbb/'
+    LastModified: '1234567890ab'
+    UID:          'qwerty'
+    GID:          'asdfgh'
+    AccessMode:   '87654321'
+    Size:         '7'
+    Terminator:   "`\n"
+    Content:      "2063636363200A"
+    PaddingByte:  0x7a ## 'z'
+## An arbitrary entry to demonstrate that we use the 0x20 byte (space character)
+## to fill gaps between field values.
+  - Name:         'a/'
+    LastModified: '1'
+    UID:          '2'
+    GID:          '3'
+    AccessMode:   '456'
+    Size:         '6'
+    Terminator:   "`\n"
+    Content:      "20616161200A"
+
+## Check how we validate maximum sizes of fields.
+
+# RUN: not yaml2obj --docnum=3 -DNAME="123456789ABCDEF01" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="Name" -DVAL=16
+# RUN: not yaml2obj --docnum=3 -DLAST="123456789ABCD" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="LastModified" -DVAL=12
+# RUN: not yaml2obj --docnum=3 -DUID="1234567" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="UID" -DVAL=6
+# RUN: not yaml2obj --docnum=3 -DGID="1234567" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="GID" -DVAL=6
+# RUN: not yaml2obj --docnum=3 -DACCESSMODE="123456789" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="AccessMode" -DVAL=8
+# RUN: not yaml2obj --docnum=3 -DSIZE="123456789AB" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="Size" -DVAL=10
+# RUN: not yaml2obj --docnum=3 -DTERMINATOR="123" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="Terminator" -DVAL=2
+
+# ERROR: error: the maximum length of "[[FIELD]]" field is [[VAL]]
+
+--- !Arch
+Members:
+  - Name:         '[[NAME=""]]'
+    LastModified: '[[LAST=""]]'
+    UID:          '[[UID=""]]'
+    GID:          '[[GID=""]]'
+    AccessMode:   '[[ACCESSMODE=""]]'
+    Size:         '[[SIZE=""]]'
+    Terminator:   '[[TERMINATOR=""]]'
+
+## Check that all keys are optional for members.
+
+# RUN: yaml2obj --docnum=4 %s -o %t.all.defaults.a
+# RUN: FileCheck --input-file=%t.all.defaults.a %s \
+# RUN:   --match-full-lines --strict-whitespace --check-prefix=DEFAULTS
+
+#      DEFAULTS:!<arch>
+# DEFAULTS-NEXT:                0           0     0     0       0         `
+# DEFAULTS-NEXT:                0           0     0     0       0         `
+#  DEFAULTS-NOT:{{.}}
+
+--- !Arch
+Members:
+  - {}
+  - {}
index 33cb36d9de470ebf60e03c4247640c2ec1546228..9bd86a77dbe33e2a901645eedac8167ed3ef450c 100644 (file)
@@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_llvm_utility(obj2yaml
+  archive2yaml.cpp
   obj2yaml.cpp
   coff2yaml.cpp
   dwarf2yaml.cpp
diff --git a/llvm/tools/obj2yaml/archive2yaml.cpp b/llvm/tools/obj2yaml/archive2yaml.cpp
new file mode 100644 (file)
index 0000000..c7b0ee4
--- /dev/null
@@ -0,0 +1,114 @@
+//===------ utils/archive2yaml.cpp - obj2yaml conversion tool ---*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "obj2yaml.h"
+#include "llvm/BinaryFormat/Magic.h"
+#include "llvm/ObjectYAML/ArchiveYAML.h"
+
+using namespace llvm;
+
+namespace {
+
+class ArchiveDumper {
+public:
+  Expected<ArchYAML::Archive *> dump(MemoryBufferRef Source) {
+    StringRef Buffer = Source.getBuffer();
+    assert(file_magic::archive == identify_magic(Buffer));
+
+    std::unique_ptr<ArchYAML::Archive> Obj =
+        std::make_unique<ArchYAML::Archive>();
+
+    StringRef Magic = "!<arch>\n";
+    if (!Buffer.startswith(Magic))
+      return createStringError(std::errc::not_supported,
+                               "only regular archives are supported");
+    Obj->Magic = Magic;
+    Buffer = Buffer.drop_front(Magic.size());
+
+    Obj->Members.emplace();
+    while (!Buffer.empty()) {
+      uint64_t Offset = Buffer.data() - Source.getBuffer().data();
+      if (Buffer.size() < sizeof(ArchiveHeader))
+        return createStringError(
+            std::errc::illegal_byte_sequence,
+            "unable to read the header of a child at offset 0x%" PRIx64,
+            Offset);
+
+      const ArchiveHeader &Hdr =
+          *reinterpret_cast<const ArchiveHeader *>(Buffer.data());
+      Buffer = Buffer.drop_front(sizeof(ArchiveHeader));
+
+      auto ToString = [](ArrayRef<char> V) {
+        // We don't want to dump excessive spaces.
+        return StringRef(V.data(), V.size()).rtrim(' ');
+      };
+
+      ArchYAML::Archive::Child C;
+      C.Fields["Name"].Value = ToString(Hdr.Name);
+      C.Fields["LastModified"].Value = ToString(Hdr.LastModified);
+      C.Fields["UID"].Value = ToString(Hdr.UID);
+      C.Fields["GID"].Value = ToString(Hdr.GID);
+      C.Fields["AccessMode"].Value = ToString(Hdr.AccessMode);
+      StringRef SizeStr = ToString(Hdr.Size);
+      C.Fields["Size"].Value = SizeStr;
+      C.Fields["Terminator"].Value = ToString(Hdr.Terminator);
+
+      uint64_t Size;
+      if (SizeStr.getAsInteger(10, Size))
+        return createStringError(
+            std::errc::illegal_byte_sequence,
+            "unable to read the size of a child at offset 0x%" PRIx64
+            " as integer: \"%s\"",
+            Offset, SizeStr.str().c_str());
+      if (Buffer.size() < Size)
+        return createStringError(
+            std::errc::illegal_byte_sequence,
+            "unable to read the data of a child at offset 0x%" PRIx64
+            " of size %" PRId64 ": the remaining archive size is %zu",
+            Offset, Size, Buffer.size());
+      if (!Buffer.empty())
+        C.Content = arrayRefFromStringRef(Buffer.take_front(Size));
+
+      const bool HasPaddingByte = (Size & 1) && Buffer.size() > Size;
+      if (HasPaddingByte)
+        C.PaddingByte = Buffer[Size];
+
+      Obj->Members->push_back(C);
+      // If the size is odd, consume a padding byte.
+      Buffer = Buffer.drop_front(HasPaddingByte ? Size + 1 : Size);
+    }
+
+    return Obj.release();
+  }
+
+private:
+  struct ArchiveHeader {
+    char Name[16];
+    char LastModified[12];
+    char UID[6];
+    char GID[6];
+    char AccessMode[8];
+    char Size[10];
+    char Terminator[2];
+  };
+};
+
+} // namespace
+
+Error archive2yaml(raw_ostream &Out, MemoryBufferRef Source) {
+  ArchiveDumper Dumper;
+  Expected<ArchYAML::Archive *> YAMLOrErr = Dumper.dump(Source);
+  if (!YAMLOrErr)
+    return YAMLOrErr.takeError();
+
+  std::unique_ptr<ArchYAML::Archive> YAML(YAMLOrErr.get());
+  yaml::Output Yout(Out);
+  Yout << *YAML;
+
+  return Error::success();
+}
index 16fc428755e2c789954d22f11e29893892d8e07b..da70450503f33eddb9e2a987b8b29f9e1fa1701a 100644 (file)
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "obj2yaml.h"
+#include "llvm/BinaryFormat/Magic.h"
 #include "llvm/Object/Archive.h"
 #include "llvm/Object/COFF.h"
 #include "llvm/Object/Minidump.h"
@@ -34,16 +35,26 @@ static Error dumpObject(const ObjectFile &Obj) {
 }
 
 static Error dumpInput(StringRef File) {
-  Expected<OwningBinary<Binary>> BinaryOrErr = createBinary(File);
-  if (!BinaryOrErr)
-    return BinaryOrErr.takeError();
+  ErrorOr<std::unique_ptr<MemoryBuffer>> FileOrErr =
+      MemoryBuffer::getFileOrSTDIN(File, /*FileSize=*/-1,
+                                   /*RequiresNullTerminator=*/false);
+  if (std::error_code EC = FileOrErr.getError())
+    return errorCodeToError(EC);
+  std::unique_ptr<MemoryBuffer> &Buffer = FileOrErr.get();
+  MemoryBufferRef MemBuf = Buffer->getMemBufferRef();
+  if (file_magic::archive == identify_magic(MemBuf.getBuffer()))
+    return archive2yaml(outs(), MemBuf);
 
-  Binary &Binary = *BinaryOrErr.get().getBinary();
+  Expected<std::unique_ptr<Binary>> BinOrErr =
+      createBinary(MemBuf, /*Context=*/nullptr);
+  if (!BinOrErr)
+    return BinOrErr.takeError();
+
+  Binary &Binary = *BinOrErr->get();
   // Universal MachO is not a subclass of ObjectFile, so it needs to be handled
   // here with the other binary types.
   if (Binary.isMachO() || Binary.isMachOUniversalBinary())
     return macho2yaml(outs(), Binary);
-  // TODO: If this is an archive, then burst it and dump each entry
   if (ObjectFile *Obj = dyn_cast<ObjectFile>(&Binary))
     return dumpObject(*Obj);
   if (MinidumpFile *Minidump = dyn_cast<MinidumpFile>(&Binary))
index c41010f111b6893c0df8692e909e3facf3ecc481..e31205027106788a24e02aba01cd65f22b99ab06 100644 (file)
@@ -17,6 +17,7 @@
 #include "llvm/Object/Wasm.h"
 #include "llvm/Object/XCOFFObjectFile.h"
 #include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/MemoryBufferRef.h"
 #include <system_error>
 
 std::error_code coff2yaml(llvm::raw_ostream &Out,
@@ -31,6 +32,7 @@ std::error_code xcoff2yaml(llvm::raw_ostream &Out,
                            const llvm::object::XCOFFObjectFile &Obj);
 std::error_code wasm2yaml(llvm::raw_ostream &Out,
                           const llvm::object::WasmObjectFile &Obj);
+llvm::Error archive2yaml(llvm::raw_ostream &Out, llvm::MemoryBufferRef Source);
 
 // Forward decls for dwarf2yaml
 namespace llvm {