Skip to content

Instantly share code, notes, and snippets.

@santagada
Created January 26, 2018 15:26
Show Gist options
  • Save santagada/544136b1ee143bf31653b1158ac6829e to your computer and use it in GitHub Desktop.
Save santagada/544136b1ee143bf31653b1158ac6829e to your computer and use it in GitHub Desktop.
llvm-objcopy
set(LLVM_LINK_COMPONENTS
DebugInfoCodeView
Object
ObjectYAML
Support
MC
)
add_llvm_tool(llvm-objcopy
llvm-objcopy.cpp
Object.cpp
)
if(LLVM_INSTALL_BINUTILS_SYMLINKS)
add_llvm_tool_symlink(objcopy llvm-objcopy)
endif()
//===- llvm-objcopy.cpp ---------------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "llvm-objcopy.h"
#include "Object.h"
#include "llvm/DebugInfo/CodeView/CVDebugRecord.h"
#include "llvm/DebugInfo/CodeView/DebugSubsectionRecord.h"
#include "llvm/DebugInfo/CodeView/GlobalTypeTableBuilder.h"
#include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h"
#include "llvm/DebugInfo/CodeView/MergingTypeTableBuilder.h"
#include "llvm/DebugInfo/CodeView/RecordName.h"
#include "llvm/DebugInfo/CodeView/SymbolDeserializer.h"
#include "llvm/DebugInfo/CodeView/SymbolSerializer.h"
#include "llvm/DebugInfo/CodeView/TypeDeserializer.h"
#include "llvm/DebugInfo/CodeView/TypeDumpVisitor.h"
#include "llvm/DebugInfo/CodeView/TypeIndex.h"
#include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h"
#include "llvm/DebugInfo/CodeView/TypeStreamMerger.h"
#include "llvm/DebugInfo/MSF/MSFBuilder.h"
#include "llvm/DebugInfo/MSF/MSFCommon.h"
#include "llvm/DebugInfo/PDB/GenericError.h"
#include "llvm/DebugInfo/PDB/Native/DbiModuleDescriptorBuilder.h"
#include "llvm/DebugInfo/PDB/Native/DbiStream.h"
#include "llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h"
#include "llvm/DebugInfo/PDB/Native/GSIStreamBuilder.h"
#include "llvm/DebugInfo/PDB/Native/InfoStream.h"
#include "llvm/DebugInfo/PDB/Native/InfoStreamBuilder.h"
#include "llvm/DebugInfo/PDB/Native/NativeSession.h"
#include "llvm/DebugInfo/PDB/Native/PDBFile.h"
#include "llvm/DebugInfo/PDB/Native/PDBFileBuilder.h"
#include "llvm/DebugInfo/PDB/Native/PDBStringTableBuilder.h"
#include "llvm/DebugInfo/PDB/Native/TpiHashing.h"
#include "llvm/DebugInfo/PDB/Native/TpiStream.h"
#include "llvm/DebugInfo/PDB/Native/TpiStreamBuilder.h"
#include "llvm/DebugInfo/PDB/PDB.h"
#include "llvm/Object/COFF.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Object/Binary.h"
#include "llvm/Object/ELFObjectFile.h"
#include "llvm/Object/ELFTypes.h"
#include "llvm/Object/Error.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <memory>
#include <string>
#include <system_error>
#include <utility>
#include "../obj2yaml/coff2yaml.cpp"
#include "../yaml2obj/yaml2coff.cpp"
#include "llvm/Support/ToolOutputFile.h"
using namespace llvm;
using namespace llvm::codeview;
using namespace object;
using namespace ELF;
// The name this program was invoked as.
static StringRef ToolName;
namespace llvm {
LLVM_ATTRIBUTE_NORETURN void error(Twine Message) {
errs() << ToolName << ": " << Message << ".\n";
errs().flush();
exit(1);
}
LLVM_ATTRIBUTE_NORETURN void reportError(StringRef File, std::error_code EC) {
assert(EC);
errs() << ToolName << ": '" << File << "': " << EC.message() << ".\n";
exit(1);
}
LLVM_ATTRIBUTE_NORETURN void reportError(StringRef File, Error E) {
assert(E);
std::string Buf;
raw_string_ostream OS(Buf);
logAllUnhandledErrors(std::move(E), OS, "");
OS.flush();
errs() << ToolName << ": '" << File << "': " << Buf;
exit(1);
}
} // end namespace llvm
static cl::opt<std::string> InputFilename(cl::Positional, cl::desc("<input>"));
static cl::opt<std::string> OutputFilename(cl::Positional, cl::desc("<output>"),
cl::init("-"));
static cl::opt<std::string>
OutputFormat("O", cl::desc("Set output format to one of the following:"
"\n\tbinary"));
static cl::list<std::string> ToRemove("remove-section",
cl::desc("Remove <section>"),
cl::value_desc("section"));
static cl::alias ToRemoveA("R", cl::desc("Alias for remove-section"),
cl::aliasopt(ToRemove));
static cl::opt<bool> StripAll(
"strip-all",
cl::desc(
"Removes non-allocated sections other than .gnu.warning* sections"));
static cl::opt<bool>
StripAllGNU("strip-all-gnu",
cl::desc("Removes symbol, relocation, and debug information"));
static cl::list<std::string> Keep("keep", cl::desc("Keep <section>"),
cl::value_desc("section"));
static cl::list<std::string> OnlyKeep("only-keep",
cl::desc("Remove all but <section>"),
cl::value_desc("section"));
static cl::alias OnlyKeepA("j", cl::desc("Alias for only-keep"),
cl::aliasopt(OnlyKeep));
static cl::opt<bool> StripDebug("strip-debug",
cl::desc("Removes all debug information"));
static cl::opt<bool> StripSections("strip-sections",
cl::desc("Remove all section headers"));
static cl::opt<bool>
StripNonAlloc("strip-non-alloc",
cl::desc("Remove all non-allocated sections"));
static cl::opt<bool>
StripDWO("strip-dwo", cl::desc("Remove all DWARF .dwo sections from file"));
static cl::opt<bool> ExtractDWO(
"extract-dwo",
cl::desc("Remove all sections that are not DWARF .dwo sections from file"));
static cl::opt<std::string>
SplitDWO("split-dwo",
cl::desc("Equivalent to extract-dwo on the input file to "
"<dwo-file>, then strip-dwo on the input file"),
cl::value_desc("dwo-file"));
static cl::list<std::string> AddSection(
"add-section",
cl::desc("Make a section named <section> with the contents of <file>."),
cl::value_desc("section=file"));
static cl::opt<bool> LocalizeHidden(
"localize-hidden",
cl::desc(
"Mark all symbols that have hidden or internal visibility as local"));
static cl::opt<std::string>
AddGnuDebugLink("add-gnu-debuglink",
cl::desc("adds a .gnu_debuglink for <debug-file>"),
cl::value_desc("debug-file"));
using SectionPred = std::function<bool(const SectionBase &Sec)>;
bool IsDWOSection(const SectionBase &Sec) { return Sec.Name.endswith(".dwo"); }
template <class ELFT>
bool OnlyKeepDWOPred(const Object<ELFT> &Obj, const SectionBase &Sec) {
// We can't remove the section header string table.
if (&Sec == Obj.getSectionHeaderStrTab())
return false;
// Short of keeping the string table we want to keep everything that is a DWO
// section and remove everything else.
return !IsDWOSection(Sec);
}
template <class ELFT>
void WriteObjectFile(const Object<ELFT> &Obj, StringRef File) {
std::unique_ptr<FileOutputBuffer> Buffer;
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
FileOutputBuffer::create(File, Obj.totalSize(),
FileOutputBuffer::F_executable);
handleAllErrors(BufferOrErr.takeError(), [](const ErrorInfoBase &) {
error("failed to open " + OutputFilename);
});
Buffer = std::move(*BufferOrErr);
Obj.write(*Buffer);
if (auto E = Buffer->commit())
reportError(File, errorToErrorCode(std::move(E)));
}
template <class ELFT>
void SplitDWOToFile(const ELFObjectFile<ELFT> &ObjFile, StringRef File) {
// Construct a second output file for the DWO sections.
ELFObject<ELFT> DWOFile(ObjFile);
DWOFile.removeSections([&](const SectionBase &Sec) {
return OnlyKeepDWOPred<ELFT>(DWOFile, Sec);
});
DWOFile.finalize();
WriteObjectFile(DWOFile, File);
}
// This function handles the high level operations of GNU objcopy including
// handling command line options. It's important to outline certain properties
// we expect to hold of the command line operations. Any operation that "keeps"
// should keep regardless of a remove. Additionally any removal should respect
// any previous removals. Lastly whether or not something is removed shouldn't
// depend a) on the order the options occur in or b) on some opaque priority
// system. The only priority is that keeps/copies overrule removes.
template <class ELFT> void CopyBinary(const ELFObjectFile<ELFT> &ObjFile) {
std::unique_ptr<Object<ELFT>> Obj;
if (!OutputFormat.empty() && OutputFormat != "binary")
error("invalid output format '" + OutputFormat + "'");
if (!OutputFormat.empty() && OutputFormat == "binary")
Obj = llvm::make_unique<BinaryObject<ELFT>>(ObjFile);
else
Obj = llvm::make_unique<ELFObject<ELFT>>(ObjFile);
if (!SplitDWO.empty())
SplitDWOToFile<ELFT>(ObjFile, SplitDWO.getValue());
// Localize:
if (LocalizeHidden) {
Obj->getSymTab()->localize([](const Symbol &Sym) {
return Sym.Visibility == STV_HIDDEN || Sym.Visibility == STV_INTERNAL;
});
}
SectionPred RemovePred = [](const SectionBase &) { return false; };
// Removes:
if (!ToRemove.empty()) {
RemovePred = [&](const SectionBase &Sec) {
return std::find(std::begin(ToRemove), std::end(ToRemove), Sec.Name) !=
std::end(ToRemove);
};
}
if (StripDWO || !SplitDWO.empty())
RemovePred = [RemovePred](const SectionBase &Sec) {
return IsDWOSection(Sec) || RemovePred(Sec);
};
if (ExtractDWO)
RemovePred = [RemovePred, &Obj](const SectionBase &Sec) {
return OnlyKeepDWOPred(*Obj, Sec) || RemovePred(Sec);
};
if (StripAllGNU)
RemovePred = [RemovePred, &Obj](const SectionBase &Sec) {
if (RemovePred(Sec))
return true;
if ((Sec.Flags & SHF_ALLOC) != 0)
return false;
if (&Sec == Obj->getSectionHeaderStrTab())
return false;
switch (Sec.Type) {
case SHT_SYMTAB:
case SHT_REL:
case SHT_RELA:
case SHT_STRTAB:
return true;
}
return Sec.Name.startswith(".debug");
};
if (StripSections) {
RemovePred = [RemovePred](const SectionBase &Sec) {
return RemovePred(Sec) || (Sec.Flags & SHF_ALLOC) == 0;
};
Obj->WriteSectionHeaders = false;
}
if (StripDebug) {
RemovePred = [RemovePred](const SectionBase &Sec) {
return RemovePred(Sec) || Sec.Name.startswith(".debug");
};
}
if (StripNonAlloc)
RemovePred = [RemovePred, &Obj](const SectionBase &Sec) {
if (RemovePred(Sec))
return true;
if (&Sec == Obj->getSectionHeaderStrTab())
return false;
return (Sec.Flags & SHF_ALLOC) == 0;
};
if (StripAll)
RemovePred = [RemovePred, &Obj](const SectionBase &Sec) {
if (RemovePred(Sec))
return true;
if (&Sec == Obj->getSectionHeaderStrTab())
return false;
if (Sec.Name.startswith(".gnu.warning"))
return false;
return (Sec.Flags & SHF_ALLOC) == 0;
};
// Explicit copies:
if (!OnlyKeep.empty()) {
RemovePred = [RemovePred, &Obj](const SectionBase &Sec) {
// Explicitly keep these sections regardless of previous removes.
if (std::find(std::begin(OnlyKeep), std::end(OnlyKeep), Sec.Name) !=
std::end(OnlyKeep))
return false;
// Allow all implicit removes.
if (RemovePred(Sec)) {
return true;
}
// Keep special sections.
if (Obj->getSectionHeaderStrTab() == &Sec) {
return false;
}
if (Obj->getSymTab() == &Sec || Obj->getSymTab()->getStrTab() == &Sec) {
return false;
}
// Remove everything else.
return true;
};
}
if (!Keep.empty()) {
RemovePred = [RemovePred](const SectionBase &Sec) {
// Explicitly keep these sections regardless of previous removes.
if (std::find(std::begin(Keep), std::end(Keep), Sec.Name) !=
std::end(Keep))
return false;
// Otherwise defer to RemovePred.
return RemovePred(Sec);
};
}
Obj->removeSections(RemovePred);
if (!AddSection.empty()) {
for (const auto &Flag : AddSection) {
auto SecPair = StringRef(Flag).split("=");
auto SecName = SecPair.first;
auto File = SecPair.second;
auto BufOrErr = MemoryBuffer::getFile(File);
if (!BufOrErr)
reportError(File, BufOrErr.getError());
auto Buf = std::move(*BufOrErr);
auto BufPtr = reinterpret_cast<const uint8_t *>(Buf->getBufferStart());
auto BufSize = Buf->getBufferSize();
Obj->addSection(SecName, ArrayRef<uint8_t>(BufPtr, BufSize));
}
}
if (!AddGnuDebugLink.empty()) {
Obj->addGnuDebugLink(AddGnuDebugLink);
}
Obj->finalize();
WriteObjectFile(*Obj, OutputFilename.getValue());
}
static ArrayRef<uint8_t> consumeDebugMagic(ArrayRef<uint8_t> Data,
StringRef SecName) {
// First 4 bytes are section magic.
if (Data.size() < 4)
error(SecName + " too short");
if (support::endian::read32le(Data.data()) != COFF::DEBUG_SECTION_MAGIC)
error(SecName + " has an invalid magic");
return Data.slice(4);
}
const std::vector<GloballyHashedType> mergeDebugT(const COFFObjectFile &Obj, const coff_section *Sec) {
std::vector<GloballyHashedType> OwnedHashes;
ArrayRef<uint8_t> Data;
Obj.getSectionContents(Sec, Data);
Data = consumeDebugMagic(Data, ".debug$T");
if (Data.empty())
return OwnedHashes;
BinaryByteStream Stream(Data, support::little);
CVTypeArray Types;
BinaryStreamReader Reader(Stream);
if (auto EC = Reader.readArray(Types, Reader.getLength()))
error("Reader::readArray failed: " + toString(std::move(EC)));
//Types.valid();
//auto iter = Types.begin(false);
//auto end = Types.end();
//++iter;
OwnedHashes = GloballyHashedType::hashTypes(Types);
return OwnedHashes;
}
//void CodeViewDebug::emitTypeGlobalHashes() {
// if (TypeTable.empty())
// return;
//
// // Start the .debug$H section with the version and hash algorithm, currently
// // hardcoded to version 0, SHA1.
// OS.SwitchSection(Asm->getObjFileLowering().getCOFFGlobalTypeHashesSection());
//
// OS.EmitValueToAlignment(4);
// OS.AddComment("Magic");
// OS.EmitIntValue(COFF::DEBUG_HASHES_SECTION_MAGIC, 4);
// OS.AddComment("Section Version");
// OS.EmitIntValue(0, 2);
// OS.AddComment("Hash Algorithm");
// OS.EmitIntValue(uint16_t(GlobalTypeHashAlg::SHA1), 2);
//
// TypeIndex TI(TypeIndex::FirstNonSimpleIndex);
// for (const auto &GHR : TypeTable.hashes()) {
// if (OS.isVerboseAsm()) {
// // Emit an EOL-comment describing which TypeIndex this hash corresponds
// // to, as well as the stringified SHA1 hash.
// SmallString<32> Comment;
// raw_svector_ostream CommentOS(Comment);
// CommentOS << formatv("{0:X+} [{1}]", TI.getIndex(), GHR);
// OS.AddComment(Comment);
// ++TI;
// }
// assert(GHR.Hash.size() % 20 == 0);
// StringRef S(reinterpret_cast<const char *>(GHR.Hash.data()),
// GHR.Hash.size());
// OS.EmitBinaryData(S);
// }
//}
ArrayRef<uint8_t> toDebugH(const std::vector<GloballyHashedType> hashes,
BumpPtrAllocator &Alloc) {
uint32_t Size = 8 + 20 * hashes.size();
uint8_t *Data = Alloc.Allocate<uint8_t>(Size);
MutableArrayRef<uint8_t> Buffer(Data, Size);
BinaryStreamWriter Writer(Buffer, llvm::support::little);
uint32_t magic = COFF::DEBUG_HASHES_SECTION_MAGIC;
cantFail(Writer.writeInteger(magic));
cantFail(Writer.writeInteger(uint16_t(0)));
cantFail(Writer.writeInteger(uint16_t(GlobalTypeHashAlg::SHA1)));
for (const auto &H : hashes) {
Writer.writeBytes(H.Hash);
}
assert(Writer.bytesRemaining() == 0);
return Buffer;
}
// This function handles the high level operations of GNU objcopy including
// handling command line options. It's important to outline certain properties
// we expect to hold of the command line operations. Any operation that "keeps"
// should keep regardless of a remove. Additionally any removal should respect
// any previous removals. Lastly whether or not something is removed shouldn't
// depend a) on the order the options occur in or b) on some opaque priority
// system. The only priority is that keeps/copies overrule removes.
void CopyBinary(const COFFObjectFile &Obj) {
uint32_t NumSections = Obj.getNumberOfSections();
std::vector<GloballyHashedType> hashes;
uint32_t position = 0;
for (uint32_t I = 1; I < NumSections + 1; ++I) {
const coff_section *COFFSection;
StringRef SectionName;
if (auto EC = Obj.getSection(I, COFFSection))
error("getSection failed: #" + Twine(I) + ": " + EC.message());
if (auto EC = Obj.getSectionName(COFFSection, SectionName))
error("getSectionName failed: #" + Twine(I) + ": " + EC.message());;
if (SectionName == ".debug$H") {
return;
}
if (SectionName == ".debug$T") {
position = I;
hashes = mergeDebugT(Obj, COFFSection);
break;
}
}
COFFDumper Dumper(Obj);
COFFYAML::Object YAMLObj = Dumper.getYAMLObj();
NumSections++;
YAMLObj.Header.NumberOfSections = NumSections;
COFFYAML::Section NewYAMLSection;
YAMLObj.Header.TimeDateStamp = Obj.getTimeDateStamp();
//std::vector<uint8_t> myvec;
//for (const auto &GlobHash : OwnedHashes) {
// myvec.insert(myvec.end(), GlobHash.Hash.begin(), GlobHash.Hash.end());
//}
BumpPtrAllocator Allocator;
ArrayRef<uint8_t> sectionData = toDebugH(hashes, Allocator);
COFFYAML::Section last = YAMLObj.Sections.back();
NewYAMLSection.Name = ".debug$H";
NewYAMLSection.Header.Characteristics = 0x42300040; // i think this should be zero
NewYAMLSection.Header.VirtualAddress = 0;
NewYAMLSection.Header.VirtualSize = 0;
NewYAMLSection.Header.NumberOfLineNumbers = 0;
NewYAMLSection.Header.NumberOfRelocations = 0;
NewYAMLSection.Header.PointerToLineNumbers = 0;
NewYAMLSection.Header.PointerToRawData = 0; // last.Header.PointerToRawData + last.Header.SizeOfRawData + (last.Header.NumberOfRelocations * (4+4+2)) + 512; // Random jump forward, hoping it would work
NewYAMLSection.Header.PointerToRelocations = 0;
NewYAMLSection.Header.SizeOfRawData = sectionData.size();
NewYAMLSection.Alignment = 4;
NewYAMLSection.SectionData = yaml::BinaryRef(sectionData);
NewYAMLSection.DebugH = CodeViewYAML::fromDebugH(sectionData);
YAMLObj.Sections.insert(YAMLObj.Sections.begin() + position, NewYAMLSection);
COFFYAML::Symbol Sym;
++position;
Sym.Name = ".debug$H";
strcpy(Sym.Header.Name, "\0\0\0\0\0\0\0\0");
Sym.Header.Value = 0;
Sym.Header.SectionNumber = position;
Sym.Header.Type = 0;
Sym.Header.StorageClass = 3;
Sym.Header.NumberOfAuxSymbols = 1;
Sym.SimpleType = COFF::IMAGE_SYM_TYPE_NULL;
Sym.ComplexType = COFF::IMAGE_SYM_DTYPE_NULL;
COFF::AuxiliarySectionDefinition YAMLASD;
YAMLASD.Length = sectionData.size();
YAMLASD.NumberOfRelocations = 0;
YAMLASD.NumberOfLinenumbers = 0;
JamCRC JC(/*Init=*/0);
for (const auto &i : sectionData)
JC.update(i);
YAMLASD.CheckSum = JC.getCRC();
YAMLASD.Number = position;
YAMLASD.Selection = 0;
YAMLASD.unused = -52;
Sym.SectionDefinition = YAMLASD;
int i = 0;
int symbol_pos = 0;
for (auto &Symbol : YAMLObj.Symbols) {
if (Symbol.Name == ".debug$T")
symbol_pos = i;
++i;
if (Symbol.Header.SectionNumber > 0 && Symbol.Header.SectionNumber < NumSections && Symbol.Header.SectionNumber >= position) {
if (Symbol.SectionDefinition.hasValue() && Symbol.SectionDefinition.getValue().Number == Symbol.Header.SectionNumber) {
++Symbol.SectionDefinition.getValue().Number;
}
++Symbol.Header.SectionNumber;
}
}
YAMLObj.Symbols.insert(YAMLObj.Symbols.begin()+symbol_pos+1, Sym);
++YAMLObj.Header.NumberOfSymbols;
COFFParser Parser(YAMLObj);
Parser.parse();
layoutOptionalHeader(Parser);
layoutCOFF(Parser);
std::error_code EC;
std::unique_ptr<ToolOutputFile> Out(
new ToolOutputFile(OutputFilename, EC, sys::fs::F_None));
if (EC)
error("yaml2obj: Error opening '" + OutputFilename + "': " + EC.message());
writeCOFF(Parser, Out->os());
Out->keep();
Out->os().flush();
// WriteObjectFile(*Obj, OutputFilename.getValue());
}
int main(int argc, char **argv) {
// Print a stack trace if we signal out.
sys::PrintStackTraceOnErrorSignal(argv[0]);
PrettyStackTraceProgram X(argc, argv);
llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
cl::ParseCommandLineOptions(argc, argv, "llvm objcopy utility\n");
ToolName = argv[0];
if (InputFilename.empty()) {
cl::PrintHelpMessage();
return 2;
}
Expected<OwningBinary<Binary>> BinaryOrErr = createBinary(InputFilename);
if (!BinaryOrErr)
reportError(InputFilename, BinaryOrErr.takeError());
Binary &Binary = *BinaryOrErr.get().getBinary();
if (auto *o = dyn_cast<ELFObjectFile<ELF64LE>>(&Binary)) {
CopyBinary(*o);
return 0;
}
if (auto *o = dyn_cast<ELFObjectFile<ELF32LE>>(&Binary)) {
CopyBinary(*o);
return 0;
}
if (auto *o = dyn_cast<ELFObjectFile<ELF64BE>>(&Binary)) {
CopyBinary(*o);
return 0;
}
if (auto *o = dyn_cast<ELFObjectFile<ELF32BE>>(&Binary)) {
CopyBinary(*o);
return 0;
}
if (auto *o = dyn_cast<COFFObjectFile>(&Binary)) {
CopyBinary(*o);
return 0;
}
reportError(InputFilename, object_error::invalid_file_type);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment