diff --git a/include/llvm/ObjectYAML/ArchiveYAML.h b/include/llvm/ObjectYAML/ArchiveYAML.h new file mode 100644 index 00000000000..8d05feedcc6 --- /dev/null +++ b/include/llvm/ObjectYAML/ArchiveYAML.h @@ -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 Fields; + + Optional Content; + Optional PaddingByte; + }; + + StringRef Magic; + Optional> Members; + Optional Content; +}; + +} // end namespace ArchYAML +} // end namespace llvm + +LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ArchYAML::Archive::Child) + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &IO, ArchYAML::Archive &A); + static std::string validate(IO &, ArchYAML::Archive &A); +}; + +template <> struct MappingTraits { + 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 diff --git a/include/llvm/ObjectYAML/ObjectYAML.h b/include/llvm/ObjectYAML/ObjectYAML.h index 0015fd3dc50..dd26ce3e970 100644 --- a/include/llvm/ObjectYAML/ObjectYAML.h +++ b/include/llvm/ObjectYAML/ObjectYAML.h @@ -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 Arch; std::unique_ptr Elf; std::unique_ptr Coff; std::unique_ptr MachO; diff --git a/include/llvm/ObjectYAML/yaml2obj.h b/include/llvm/ObjectYAML/yaml2obj.h index 34def363a55..1f693475c94 100644 --- a/include/llvm/ObjectYAML/yaml2obj.h +++ b/include/llvm/ObjectYAML/yaml2obj.h @@ -40,12 +40,17 @@ namespace WasmYAML { struct Object; } +namespace ArchYAML { +struct Archive; +} + namespace yaml { class Input; struct YamlObjectFile; using ErrorHandler = llvm::function_ref; +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/lib/ObjectYAML/ArchiveEmitter.cpp b/lib/ObjectYAML/ArchiveEmitter.cpp new file mode 100644 index 00000000000..a0cf8fe360d --- /dev/null +++ b/lib/ObjectYAML/ArchiveEmitter.cpp @@ -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/lib/ObjectYAML/ArchiveYAML.cpp b/lib/ObjectYAML/ArchiveYAML.cpp new file mode 100644 index 00000000000..d2ea1eaf521 --- /dev/null +++ b/lib/ObjectYAML/ArchiveYAML.cpp @@ -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::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, "!\n"); + IO.mapOptional("Members", A.Members); + IO.mapOptional("Content", A.Content); + IO.setContext(nullptr); +} + +std::string MappingTraits::validate(IO &, + ArchYAML::Archive &A) { + if (A.Members && A.Content) + return "\"Content\" and \"Members\" cannot be used together"; + return ""; +} + +void MappingTraits::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::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 diff --git a/lib/ObjectYAML/CMakeLists.txt b/lib/ObjectYAML/CMakeLists.txt index 92ea1ec9866..a024fa96535 100644 --- a/lib/ObjectYAML/CMakeLists.txt +++ b/lib/ObjectYAML/CMakeLists.txt @@ -1,4 +1,6 @@ add_llvm_component_library(LLVMObjectYAML + ArchiveEmitter.cpp + ArchiveYAML.cpp CodeViewYAMLDebugSections.cpp CodeViewYAMLSymbols.cpp CodeViewYAMLTypeHashing.cpp diff --git a/lib/ObjectYAML/ObjectYAML.cpp b/lib/ObjectYAML/ObjectYAML.cpp index 7f636f4eaba..4564b537c9a 100644 --- a/lib/ObjectYAML/ObjectYAML.cpp +++ b/lib/ObjectYAML/ObjectYAML.cpp @@ -33,7 +33,14 @@ void MappingTraits::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::mapping(IO, *ObjectFile.Arch); + std::string Err = + MappingTraits::validate(IO, *ObjectFile.Arch); + if (!Err.empty()) + IO.setError(Err); + } else if (IO.mapTag("!ELF")) { ObjectFile.Elf.reset(new ELFYAML::Object()); MappingTraits::mapping(IO, *ObjectFile.Elf); } else if (IO.mapTag("!COFF")) { diff --git a/lib/ObjectYAML/yaml2obj.cpp b/lib/ObjectYAML/yaml2obj.cpp index a04345f1294..ef2ab83dcd2 100644 --- a/lib/ObjectYAML/yaml2obj.cpp +++ b/lib/ObjectYAML/yaml2obj.cpp @@ -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/test/tools/obj2yaml/Archives/regular.yaml b/test/tools/obj2yaml/Archives/regular.yaml new file mode 100644 index 00000000000..8b2969fdb10 --- /dev/null +++ b/test/tools/obj2yaml/Archives/regular.yaml @@ -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: "!\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/test/tools/yaml2obj/Archives/regular.yaml b/test/tools/yaml2obj/Archives/regular.yaml new file mode 100644 index 00000000000..29b0cb5eea8 --- /dev/null +++ b/test/tools/yaml2obj/Archives/regular.yaml @@ -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=!\n]]" +Content: [[CONTENT=]] +Members: [[MEMBERS=]] + +## 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 "!\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 "!\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 "!\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:! +# 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:! +# DEFAULTS-NEXT: 0 0 0 0 0 ` +# DEFAULTS-NEXT: 0 0 0 0 0 ` +# DEFAULTS-NOT:{{.}} + +--- !Arch +Members: + - {} + - {} diff --git a/tools/obj2yaml/CMakeLists.txt b/tools/obj2yaml/CMakeLists.txt index 33cb36d9de4..9bd86a77dbe 100644 --- a/tools/obj2yaml/CMakeLists.txt +++ b/tools/obj2yaml/CMakeLists.txt @@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS ) add_llvm_utility(obj2yaml + archive2yaml.cpp obj2yaml.cpp coff2yaml.cpp dwarf2yaml.cpp diff --git a/tools/obj2yaml/archive2yaml.cpp b/tools/obj2yaml/archive2yaml.cpp new file mode 100644 index 00000000000..c7b0ee48029 --- /dev/null +++ b/tools/obj2yaml/archive2yaml.cpp @@ -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 dump(MemoryBufferRef Source) { + StringRef Buffer = Source.getBuffer(); + assert(file_magic::archive == identify_magic(Buffer)); + + std::unique_ptr Obj = + std::make_unique(); + + StringRef Magic = "!\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(Buffer.data()); + Buffer = Buffer.drop_front(sizeof(ArchiveHeader)); + + auto ToString = [](ArrayRef 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 YAMLOrErr = Dumper.dump(Source); + if (!YAMLOrErr) + return YAMLOrErr.takeError(); + + std::unique_ptr YAML(YAMLOrErr.get()); + yaml::Output Yout(Out); + Yout << *YAML; + + return Error::success(); +} diff --git a/tools/obj2yaml/obj2yaml.cpp b/tools/obj2yaml/obj2yaml.cpp index 16fc428755e..da70450503f 100644 --- a/tools/obj2yaml/obj2yaml.cpp +++ b/tools/obj2yaml/obj2yaml.cpp @@ -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> BinaryOrErr = createBinary(File); - if (!BinaryOrErr) - return BinaryOrErr.takeError(); + ErrorOr> FileOrErr = + MemoryBuffer::getFileOrSTDIN(File, /*FileSize=*/-1, + /*RequiresNullTerminator=*/false); + if (std::error_code EC = FileOrErr.getError()) + return errorCodeToError(EC); + std::unique_ptr &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> 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(&Binary)) return dumpObject(*Obj); if (MinidumpFile *Minidump = dyn_cast(&Binary)) diff --git a/tools/obj2yaml/obj2yaml.h b/tools/obj2yaml/obj2yaml.h index c41010f111b..e3120502710 100644 --- a/tools/obj2yaml/obj2yaml.h +++ b/tools/obj2yaml/obj2yaml.h @@ -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 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 {