diff --git a/llvm/tools/llvm-objcopy/CMakeLists.txt b/llvm/tools/llvm-objcopy/CMakeLists.txt --- a/llvm/tools/llvm-objcopy/CMakeLists.txt +++ b/llvm/tools/llvm-objcopy/CMakeLists.txt @@ -33,6 +33,10 @@ MachO/MachOWriter.cpp MachO/MachOLayoutBuilder.cpp MachO/Object.cpp + Wasm/Object.cpp + Wasm/Reader.cpp + Wasm/Writer.cpp + Wasm/WasmObjcopy.cpp DEPENDS ObjcopyOptsTableGen InstallNameToolOptsTableGen diff --git a/llvm/tools/llvm-objcopy/CopyConfig.h b/llvm/tools/llvm-objcopy/CopyConfig.h --- a/llvm/tools/llvm-objcopy/CopyConfig.h +++ b/llvm/tools/llvm-objcopy/CopyConfig.h @@ -171,6 +171,10 @@ DiscardType DiscardMode = DiscardType::None; Optional NewSymbolVisibility; + // WebAssembly only + StringRef WasmSplitDebug; + StringRef WasmDebugInfoUrl; + // Repeated options std::vector AddSection; std::vector DumpSection; diff --git a/llvm/tools/llvm-objcopy/CopyConfig.cpp b/llvm/tools/llvm-objcopy/CopyConfig.cpp --- a/llvm/tools/llvm-objcopy/CopyConfig.cpp +++ b/llvm/tools/llvm-objcopy/CopyConfig.cpp @@ -551,6 +551,9 @@ Config.BuildIdLinkOutput = InputArgs.getLastArgValue(OBJCOPY_build_id_link_output); Config.SplitDWO = InputArgs.getLastArgValue(OBJCOPY_split_dwo); + Config.WasmSplitDebug = InputArgs.getLastArgValue(OBJCOPY_wasm_split_debug); + Config.WasmDebugInfoUrl = + InputArgs.getLastArgValue(OBJCOPY_wasm_debug_info_url); Config.SymbolsPrefix = InputArgs.getLastArgValue(OBJCOPY_prefix_symbols); Config.AllocSectionsPrefix = InputArgs.getLastArgValue(OBJCOPY_prefix_alloc_sections); diff --git a/llvm/tools/llvm-objcopy/ObjcopyOpts.td b/llvm/tools/llvm-objcopy/ObjcopyOpts.td --- a/llvm/tools/llvm-objcopy/ObjcopyOpts.td +++ b/llvm/tools/llvm-objcopy/ObjcopyOpts.td @@ -227,3 +227,15 @@ "compatibility: debug, constructor, warning, indirect, synthetic, " "unique-object, before.">, MetaVarName<"name=[section:]value[,flags]">; + +defm wasm_split_debug + : Eq<"wasm-split-debug", "For a Wasm module, extract all debug sections " + "from the input file to , then " + "strip-debug on the input file.">, + MetaVarName<"wasm-sym-file">; + +defm wasm_debug_info_url + : Eq<"wasm-debug-info-url", "For a Wasm module, specify the locatiuon of " + "the debug info file created with " + "--wasm-split-debug.">, + MetaVarName<"dwarf-url">; diff --git a/llvm/tools/llvm-objcopy/Wasm/Object.h b/llvm/tools/llvm-objcopy/Wasm/Object.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/Wasm/Object.h @@ -0,0 +1,89 @@ +//===- Object.h -------------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_OBJCOPY_WASM_OBJECT_H +#define LLVM_TOOLS_OBJCOPY_WASM_OBJECT_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/BinaryFormat/Wasm.h" +#include "llvm/Object/Wasm.h" +#include "llvm/Support/LEB128.h" + +namespace llvm { +namespace objcopy { +namespace wasm { + +struct Section { + uint32_t Type; + StringRef Name; // (Custom sections only) + + Section(uint32_t Type); + Section(StringRef Name); + + ArrayRef getContents() const { + if (!OwnedContents.empty()) + return OwnedContents; + return ContentsRef; + } + + void setContentsRef(ArrayRef Data) { + OwnedContents.clear(); + ContentsRef = Data; + } + + void setOwnedContents(std::vector &&Data) { + ContentsRef = ArrayRef(); + OwnedContents = std::move(Data); + } + + size_t getPayloadLength() const { + size_t ContentSize = getContents().size(); + size_t NameSize = Type != llvm::wasm::WASM_SEC_CUSTOM + ? 0 + : llvm::getULEB128Size(Name.size()) + Name.size(); + return ContentSize + NameSize; + } + + size_t getSize() const { + size_t PayloadLen = getPayloadLength(); + size_t SectionSize = + sizeof(uint8_t) + llvm::getULEB128Size(PayloadLen) + PayloadLen; + return SectionSize; + } + +private: + ArrayRef ContentsRef; + std::vector OwnedContents; +}; + +struct Object { + llvm::wasm::WasmObjectHeader WasmHeader; + + Object(); + + ArrayRef
getSections() const { return Sections; } + iterator_range::iterator> getMutableSections() { + return make_range(Sections.begin(), Sections.end()); + } + + void addSections(ArrayRef
NewSections); + void removeSections(function_ref ToRemove); + + void setExternalDebugInfoSection(StringRef ExternalDebugInfoUrl); + +private: + std::vector
Sections; +}; + +} // end namespace wasm +} // end namespace objcopy +} // end namespace llvm + +#endif // LLVM_TOOLS_OBJCOPY_WASM_OBJECT_H diff --git a/llvm/tools/llvm-objcopy/Wasm/Object.cpp b/llvm/tools/llvm-objcopy/Wasm/Object.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/Wasm/Object.cpp @@ -0,0 +1,76 @@ +//===- Object.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 "Object.h" +#include "Buffer.h" +#include + +namespace llvm { +namespace objcopy { +namespace wasm { + +using namespace object; + +static const std::string WasmSectionNames[14] = { + "", "type", "import", "function", "table", "memory", "global", + "export", "start", "element", "code", "data", "datacount", "event"}; + +Section::Section(uint32_t SectionType) : Type(SectionType) { + assert(SectionType > llvm::wasm::WASM_SEC_CUSTOM && + SectionType <= llvm::wasm::WASM_SEC_EVENT); + Name = StringRef(WasmSectionNames[SectionType]); +} + +Section::Section(StringRef SectionName) + : Type(llvm::wasm::WASM_SEC_CUSTOM), Name(SectionName) {} + +Object::Object() { + WasmHeader.Magic = {llvm::wasm::WasmMagic, sizeof(llvm::wasm::WasmMagic)}; + WasmHeader.Version = llvm::wasm::WasmVersion; +} + +void Object::addSections(ArrayRef
NewSections) { + for (Section S : NewSections) { + Sections.emplace_back(S); + } +} + +void Object::removeSections(function_ref ToRemove) { + Sections.erase( + std::remove_if(std::begin(Sections), std::end(Sections), + [ToRemove](const Section &Sec) { return ToRemove(Sec); }), + std::end(Sections)); +} + +void Object::setExternalDebugInfoSection(StringRef ExternalDebugInfoUrl) { + static const StringRef ExternalDebugInfoSectionName("external_debug_info"); + + removeSections([](const Section &Section) { + return Section.Name == ExternalDebugInfoSectionName; + }); + + uint32_t UrlLen = ExternalDebugInfoUrl.size(); + uint32_t ContentSize = llvm::getULEB128Size(UrlLen) + UrlLen; + + std::vector Buf(ContentSize); + uint8_t *Ptr = &Buf[0]; + Ptr += llvm::encodeULEB128(UrlLen, Ptr); + memcpy(Ptr, reinterpret_cast(ExternalDebugInfoUrl.data()), + UrlLen); + Ptr += UrlLen; + assert(Ptr - &Buf[0] == ContentSize); + + Section S(ExternalDebugInfoSectionName); + S.setOwnedContents(std::move(Buf)); + + addSections(ArrayRef
{S}); +} + +} // end namespace wasm +} // end namespace objcopy +} // end namespace llvm diff --git a/llvm/tools/llvm-objcopy/Wasm/Reader.h b/llvm/tools/llvm-objcopy/Wasm/Reader.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/Wasm/Reader.h @@ -0,0 +1,40 @@ +//===- Reader.h -------------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_OBJCOPY_WASM_READER_H +#define LLVM_TOOLS_OBJCOPY_WASM_READER_H + +#include "Buffer.h" +#include "llvm/Object/Wasm.h" +#include "llvm/Support/Error.h" + +namespace llvm { +namespace objcopy { +namespace wasm { + +struct Object; + +using object::WasmObjectFile; + +class WasmReader { +public: + explicit WasmReader(const WasmObjectFile &O) : WasmObj(O) {} + + Expected> create() const; + +private: + Error readSections(Object &Obj) const; + + const WasmObjectFile &WasmObj; +}; + +} // end namespace wasm +} // end namespace objcopy +} // end namespace llvm + +#endif // LLVM_TOOLS_OBJCOPY_WASM_READER_H diff --git a/llvm/tools/llvm-objcopy/Wasm/Reader.cpp b/llvm/tools/llvm-objcopy/Wasm/Reader.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/Wasm/Reader.cpp @@ -0,0 +1,57 @@ +//===- Reader.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 "Reader.h" +#include "Object.h" + +namespace llvm { +namespace objcopy { +namespace wasm { + +using namespace object; + +Error WasmReader::readSections(Object &Obj) const { + std::vector
Sections; + for (section_iterator SI = WasmObj.section_begin(), + SE = WasmObj.section_end(); + SI != SE; ++SI) { + const SectionRef §ion_ref = *SI; + const WasmSection §ion = WasmObj.getWasmSection(section_ref); + + if (section.Type != llvm::wasm::WASM_SEC_CUSTOM) { + assert(section.Type <= llvm::wasm::WASM_SEC_EVENT); + Sections.push_back(Section(section.Type)); + } else + Sections.push_back(Section(section.Name)); + + DataRefImpl data_ref = section_ref.getRawDataRefImpl(); + Expected> ErrorOrContents = + WasmObj.getSectionContents(data_ref); + if (!ErrorOrContents) + return ErrorOrContents.takeError(); + + Section &S = Sections.back(); + S.setContentsRef(ErrorOrContents.get()); + } + + Obj.addSections(Sections); + return Error::success(); +} + +Expected> WasmReader::create() const { + auto Obj = std::make_unique(); + + if (Error E = readSections(*Obj)) + return std::move(E); + + return std::move(Obj); +} + +} // end namespace wasm +} // end namespace objcopy +} // end namespace llvm diff --git a/llvm/tools/llvm-objcopy/Wasm/WasmObjcopy.h b/llvm/tools/llvm-objcopy/Wasm/WasmObjcopy.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/Wasm/WasmObjcopy.h @@ -0,0 +1,31 @@ +//===- WasmObjcopy.h --------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_OBJCOPY_WASMOBJCOPY_H +#define LLVM_TOOLS_OBJCOPY_WASMOBJCOPY_H + +namespace llvm { +class Error; + +namespace object { +class WasmObjectFile; +} // end namespace object + +namespace objcopy { +struct CopyConfig; +class Buffer; + +namespace wasm { +Error executeObjcopyOnBinary(const CopyConfig &Config, + object::WasmObjectFile &In, Buffer &Out); + +} // end namespace wasm +} // end namespace objcopy +} // end namespace llvm + +#endif // LLVM_TOOLS_OBJCOPY_WASMOBJCOPY_H diff --git a/llvm/tools/llvm-objcopy/Wasm/WasmObjcopy.cpp b/llvm/tools/llvm-objcopy/Wasm/WasmObjcopy.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/Wasm/WasmObjcopy.cpp @@ -0,0 +1,127 @@ +//===- WasmObjcopy.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 "WasmObjcopy.h" +#include "CopyConfig.h" +#include "Object.h" +#include "Reader.h" +#include "Writer.h" +#include "llvm-objcopy.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" + +namespace llvm { +namespace objcopy { +namespace wasm { + +static bool isDebugSection(const Section &Sec) { + return Sec.Name.startswith(".debug"); +} + +static Error splitDwarfToFile(const CopyConfig &Config, + const WasmReader &Reader, StringRef File) { + Expected> ObjOrErr = Reader.create(); + if (!ObjOrErr) + return createFileError(File, ObjOrErr.takeError()); + + Object *Obj = ObjOrErr->get(); + Obj->removeSections([](const Section &Sec) { return !isDebugSection(Sec); }); + + FileBuffer Out(File); + WasmWriter Writer(*Obj, Out); + if (Error E = Writer.write()) + return createFileError(Config.OutputFilename, std::move(E)); + return Error::success(); +} + +// 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. +static Error handleArgs(const CopyConfig &Config, Object &Obj, + const WasmReader &Reader) { + if (!Config.WasmSplitDebug.empty()) { + if (Error E = splitDwarfToFile(Config, Reader, Config.WasmSplitDebug)) + return E; + + // Add or replace "external_debug_info" custom section + Obj.setExternalDebugInfoSection(Config.WasmDebugInfoUrl.empty() + ? Config.WasmSplitDebug + : Config.WasmDebugInfoUrl); + } + + // Perform the actual section removals. + Obj.removeSections([&Config](const Section &Sec) { + // --only-section fully removes sections that aren't mentioned. + if (!Config.OnlySection.empty() && !Config.OnlySection.matches(Sec.Name)) + return true; + + if (Config.StripDebug || Config.StripAll || Config.StripAllGNU || + !Config.WasmSplitDebug.empty() || Config.StripUnneeded || + Config.DiscardMode == DiscardType::All) { + if (isDebugSection(Sec)) + return true; + } + + if (Config.ToRemove.matches(Sec.Name)) + return true; + + return false; + }); + + if (!Config.AddGnuDebugLink.empty() || !Config.AddSection.empty() || + !Config.AllocSectionsPrefix.empty() || Config.AllowBrokenLinks || + !Config.BuildIdLinkDir.empty() || Config.BuildIdLinkInput || + Config.BuildIdLinkOutput || Config.DecompressDebugSections || + Config.DiscardMode == DiscardType::Locals || + !Config.DumpSection.empty() || Config.EntryExpr || Config.ExtractDWO || + Config.KeepFileSymbols || Config.LocalizeHidden || + !Config.KeepSection.empty() || Config.NewSymbolVisibility || + Config.OnlyKeepDebug || Config.PreserveDates || + !Config.SectionsToRename.empty() || !Config.SetSectionAlignment.empty() || + !Config.SetSectionFlags.empty() || !Config.SplitDWO.empty() || + Config.StripDWO || Config.StripNonAlloc || Config.StripSections || + !Config.SymbolsPrefix.empty() || !Config.SymbolsToAdd.empty() || + !Config.SymbolsToGlobalize.empty() || !Config.SymbolsToKeep.empty() || + !Config.SymbolsToKeepGlobal.empty() || + !Config.SymbolsToLocalize.empty() || !Config.SymbolsToRemove.empty() || + !Config.SymbolsToWeaken.empty() || + !Config.UnneededSymbolsToRemove.empty() || Config.Weaken) { + return createStringError(llvm::errc::invalid_argument, + "option not supported by llvm-objcopy for WASM"); + } + + return Error::success(); +} + +Error executeObjcopyOnBinary(const CopyConfig &Config, + object::WasmObjectFile &In, Buffer &Out) { + WasmReader Reader(In); + Expected> ObjOrErr = Reader.create(); + if (!ObjOrErr) + return createFileError(Config.InputFilename, ObjOrErr.takeError()); + + Object *Obj = ObjOrErr->get(); + assert(Obj && "Unable to deserialize Wasm object"); + + if (Error E = handleArgs(Config, *Obj, Reader)) + return createFileError(Config.InputFilename, std::move(E)); + + WasmWriter Writer(*Obj, Out); + if (Error E = Writer.write()) + return createFileError(Config.OutputFilename, std::move(E)); + + return Error::success(); +} + +} // end namespace wasm +} // end namespace objcopy +} // end namespace llvm diff --git a/llvm/tools/llvm-objcopy/Wasm/Writer.h b/llvm/tools/llvm-objcopy/Wasm/Writer.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/Wasm/Writer.h @@ -0,0 +1,41 @@ +//===- Writer.h -------------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_OBJCOPY_WASM_WRITER_H +#define LLVM_TOOLS_OBJCOPY_WASM_WRITER_H + +#include "Buffer.h" +#include "llvm/Support/Error.h" + +namespace llvm { +namespace objcopy { +namespace wasm { + +struct Object; + +class WasmWriter { +public: + WasmWriter(const Object &Obj, Buffer &Buf) : Obj(Obj), Buf(Buf) {} + + Error write(); + +private: + size_t calculateFileSize() const; + size_t headerSize() const; + void writeHeader(); + void writeSections(); + + const Object &Obj; + Buffer &Buf; +}; + +} // end namespace wasm +} // end namespace objcopy +} // end namespace llvm + +#endif // LLVM_TOOLS_OBJCOPY_WASM_WRITER_H diff --git a/llvm/tools/llvm-objcopy/Wasm/Writer.cpp b/llvm/tools/llvm-objcopy/Wasm/Writer.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/Wasm/Writer.cpp @@ -0,0 +1,69 @@ +//===- Writer.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 "Writer.h" +#include "Object.h" +#include "llvm/Object/Wasm.h" +#include "llvm/Support/LEB128.h" + +namespace llvm { +namespace objcopy { +namespace wasm { + +size_t WasmWriter::headerSize() const { + return Obj.WasmHeader.Magic.size() + sizeof(Obj.WasmHeader.Version); +} + +void WasmWriter::writeHeader() { + uint8_t *Ptr = Buf.getBufferStart(); + + memcpy(Ptr, Obj.WasmHeader.Magic.data(), Obj.WasmHeader.Magic.size()); + Ptr += Obj.WasmHeader.Magic.size(); + + memcpy(Ptr, &Obj.WasmHeader.Version, sizeof(Obj.WasmHeader.Version)); + Ptr += sizeof(Obj.WasmHeader.Version); +} + +void WasmWriter::writeSections() { + uint8_t *Ptr = Buf.getBufferStart() + headerSize(); + for (const auto §ion : Obj.getSections()) { + *Ptr++ = section.Type; + Ptr += llvm::encodeULEB128(section.getPayloadLength(), Ptr); + if (section.Type == llvm::wasm::WASM_SEC_CUSTOM) { + Ptr += llvm::encodeULEB128(section.Name.size(), Ptr); + memcpy(Ptr, section.Name.data(), section.Name.size()); + Ptr += section.Name.size(); + } + memcpy(Ptr, section.getContents().data(), section.getContents().size()); + Ptr += section.getContents().size(); + } + assert(Ptr - Buf.getBufferStart() == calculateFileSize()); +} + +size_t WasmWriter::calculateFileSize() const { + size_t FileSize = headerSize(); + for (const Section §ion : Obj.getSections()) { + FileSize += section.getSize(); + } + return FileSize; +} + +Error WasmWriter::write() { + size_t FileSize = calculateFileSize(); + if (Error E = Buf.allocate(FileSize)) + return E; + + writeHeader(); + writeSections(); + + return Buf.commit(); +} + +} // end namespace wasm +} // end namespace objcopy +} // end namespace llvm diff --git a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp --- a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp +++ b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp @@ -8,10 +8,11 @@ #include "llvm-objcopy.h" #include "Buffer.h" +#include "COFF/COFFObjcopy.h" #include "CopyConfig.h" #include "ELF/ELFObjcopy.h" -#include "COFF/COFFObjcopy.h" #include "MachO/MachOObjcopy.h" +#include "Wasm/WasmObjcopy.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" @@ -25,6 +26,7 @@ #include "llvm/Object/ELFTypes.h" #include "llvm/Object/Error.h" #include "llvm/Object/MachO.h" +#include "llvm/Object/Wasm.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" @@ -172,6 +174,8 @@ return coff::executeObjcopyOnBinary(Config, *COFFBinary, Out); else if (auto *MachOBinary = dyn_cast(&In)) return macho::executeObjcopyOnBinary(Config, *MachOBinary, Out); + else if (auto *WasmBinary = dyn_cast(&In)) + return objcopy::wasm::executeObjcopyOnBinary(Config, *WasmBinary, Out); else return createStringError(object_error::invalid_file_type, "unsupported object file format");