[llvm] [llvm-objcopy][llvm-mt] Support updating the resources of PE images (PR #225163)
Fabian Meumertzheim via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 22 05:27:53 PDT 2026
https://github.com/fmeum updated https://github.com/llvm/llvm-project/pull/225163
>From 422d8ef475a7e5ebfa04ce66a404669623e14c83 Mon Sep 17 00:00:00 2001
From: Fabian Meumertzheim <fabian at meumertzhe.im>
Date: Tue, 22 Sep 2026 14:26:32 +0200
Subject: [PATCH 1/3] [Object] Add resource tree editing and .rsrc section
writing
Extend WindowsResourceParser with findResource, addResource and
removeResource to look up, add, replace and remove individual resources
of a resource tree, and add writeWindowsResourceSection, which writes a
resource tree in the format of the .rsrc section of a PE image, i.e.
with the resource data entries holding RVAs instead of relocations. The
serialization of the resource directory is shared with
writeWindowsResourceCOFF.
Also make ResourceSectionRef::load locate the resource directory of a PE
image through the resource table data directory entry rather than by
section name. The directory may start at an offset within its section,
since the loader resolves the offsets in it relative to the directory.
The parser now also preserves the code page of data entries.
This is in preparation for llvm-objcopy and llvm-mt gaining support for
updating the resources of PE images.
Assisted-by: Claude Code
Assisted-by: OpenAI Codex
Claude-Session: https://claude.ai/code/session_01M9VKWRFkU4dMqPLUkAY9Lu
---
llvm/include/llvm/Object/COFF.h | 2 +
llvm/include/llvm/Object/WindowsResource.h | 74 +++-
llvm/lib/Object/COFFObjectFile.cpp | 33 +-
llvm/lib/Object/WindowsResource.cpp | 401 +++++++++++++++------
4 files changed, 388 insertions(+), 122 deletions(-)
diff --git a/llvm/include/llvm/Object/COFF.h b/llvm/include/llvm/Object/COFF.h
index 02efa1ea55612..541ebf9d08c59 100644
--- a/llvm/include/llvm/Object/COFF.h
+++ b/llvm/include/llvm/Object/COFF.h
@@ -1440,6 +1440,7 @@ class ResourceSectionRef {
BinaryByteStream BBS;
SectionRef Section;
+ uint32_t SectionOffset = 0;
const COFFObjectFile *Obj = nullptr;
std::vector<const coff_relocation *> Relocs;
@@ -1450,6 +1451,7 @@ class ResourceSectionRef {
Expected<const coff_resource_data_entry &>
getDataEntryAtOffset(uint32_t Offset);
Expected<ArrayRef<UTF16>> getDirStringAtOffset(uint32_t Offset);
+ Error load(const COFFObjectFile *O, const SectionRef &S, uint32_t Offset);
};
// Corresponds to `_FPO_DATA` structure in the PE/COFF spec.
diff --git a/llvm/include/llvm/Object/WindowsResource.h b/llvm/include/llvm/Object/WindowsResource.h
index 230de3a06f778..945fe0442bc07 100644
--- a/llvm/include/llvm/Object/WindowsResource.h
+++ b/llvm/include/llvm/Object/WindowsResource.h
@@ -40,6 +40,7 @@
#include "llvm/Support/Error.h"
#include <map>
+#include <optional>
namespace llvm {
@@ -154,6 +155,16 @@ class WindowsResource : public Binary {
class WindowsResourceParser {
public:
class TreeNode;
+
+ struct StringOrID {
+ bool IsString;
+ ArrayRef<UTF16> String;
+ uint32_t ID = ~0u;
+
+ StringOrID(uint32_t ID) : IsString(false), ID(ID) {}
+ StringOrID(ArrayRef<UTF16> String) : IsString(true), String(String) {}
+ };
+
LLVM_ABI WindowsResourceParser(bool MinGW = false);
LLVM_ABI Error parse(WindowsResource *WR,
std::vector<std::string> &Duplicates);
@@ -165,6 +176,23 @@ class WindowsResourceParser {
ArrayRef<std::vector<uint8_t>> getData() const { return Data; }
ArrayRef<std::vector<UTF16>> getStringTable() const { return StringTable; }
+ /// Returns the node of the resource with the given type and name, whose
+ /// children are the data nodes of the resource's languages, or nullptr if
+ /// there is no such resource.
+ LLVM_ABI const TreeNode *findResource(const StringOrID &Type,
+ const StringOrID &Name) const;
+ /// Adds a resource with the given type, name and language and the given
+ /// data, replacing an existing resource with the same type, name and
+ /// language. \p Origin describes the source of the resource for diagnostics.
+ LLVM_ABI void addResource(const StringOrID &Type, const StringOrID &Name,
+ uint16_t Language, ArrayRef<uint8_t> ResourceData,
+ StringRef Origin);
+ /// Removes the resources with the given type and name, either of all
+ /// languages or, if \p Language is set, only of that language. Returns
+ /// whether any resource was removed.
+ LLVM_ABI bool removeResource(const StringOrID &Type, const StringOrID &Name,
+ std::optional<uint16_t> Language = std::nullopt);
+
class TreeNode {
public:
template <typename T>
@@ -177,6 +205,8 @@ class WindowsResourceParser {
uint16_t getMajorVersion() const { return MajorVersion; }
uint16_t getMinorVersion() const { return MinorVersion; }
uint32_t getCharacteristics() const { return Characteristics; }
+ uint32_t getCodePage() const { return CodePage; }
+ uint32_t getReserved() const { return Reserved; }
bool checkIsDataNode() const { return IsDataNode; }
const Children<uint32_t> &getIDChildren() const { return IDChildren; }
const Children<std::string> &getStringChildren() const {
@@ -190,15 +220,15 @@ class WindowsResourceParser {
static std::unique_ptr<TreeNode> createStringNode(uint32_t Index);
static std::unique_ptr<TreeNode> createIDNode();
// DataIndex is the Data vector index that the data node points at.
- static std::unique_ptr<TreeNode> createDataNode(uint16_t MajorVersion,
- uint16_t MinorVersion,
- uint32_t Characteristics,
- uint32_t Origin,
- uint32_t DataIndex);
+ static std::unique_ptr<TreeNode>
+ createDataNode(uint16_t MajorVersion, uint16_t MinorVersion,
+ uint32_t Characteristics, uint32_t CodePage,
+ uint32_t Reserved, uint32_t Origin, uint32_t DataIndex);
explicit TreeNode(uint32_t StringIndex);
TreeNode(uint16_t MajorVersion, uint16_t MinorVersion,
- uint32_t Characteristics, uint32_t Origin, uint32_t DataIndex);
+ uint32_t Characteristics, uint32_t CodePage, uint32_t Reserved,
+ uint32_t Origin, uint32_t DataIndex);
bool addEntry(const ResourceEntryRef &Entry, uint32_t Origin,
std::vector<std::vector<uint8_t>> &Data,
@@ -212,12 +242,20 @@ class WindowsResourceParser {
std::vector<std::vector<uint8_t>> &Data,
TreeNode *&Result);
bool addDataChild(uint32_t ID, uint16_t MajorVersion, uint16_t MinorVersion,
- uint32_t Characteristics, uint32_t Origin,
- uint32_t DataIndex, TreeNode *&Result);
+ uint32_t Characteristics, uint32_t CodePage,
+ uint32_t Reserved, uint32_t Origin, uint32_t DataIndex,
+ TreeNode *&Result);
TreeNode &addIDChild(uint32_t ID);
TreeNode &addNameChild(ArrayRef<UTF16> NameRef,
std::vector<std::vector<UTF16>> &StringTable);
+ TreeNode &addChild(const StringOrID &Key,
+ std::vector<std::vector<UTF16>> &StringTable);
+ const TreeNode *findChild(const StringOrID &Key) const;
+ TreeNode *findChild(const StringOrID &Key) {
+ return const_cast<TreeNode *>(std::as_const(*this).findChild(Key));
+ }
void shiftDataIndexDown(uint32_t Index);
+ void shiftStringIndexDown(uint32_t Index);
bool IsDataNode = false;
uint32_t StringIndex;
@@ -227,21 +265,14 @@ class WindowsResourceParser {
uint16_t MajorVersion = 0;
uint16_t MinorVersion = 0;
uint32_t Characteristics = 0;
+ uint32_t CodePage = 0;
+ uint32_t Reserved = 0;
// The .res file that defined this TreeNode, for diagnostics.
// Index into InputFilenames.
uint32_t Origin;
};
- struct StringOrID {
- bool IsString;
- ArrayRef<UTF16> String;
- uint32_t ID = ~0u;
-
- StringOrID(uint32_t ID) : IsString(false), ID(ID) {}
- StringOrID(ArrayRef<UTF16> String) : IsString(true), String(String) {}
- };
-
private:
Error addChildren(TreeNode &Node, ResourceSectionRef &RSR,
const coff_resource_dir_table &Table, uint32_t Origin,
@@ -249,6 +280,7 @@ class WindowsResourceParser {
std::vector<std::string> &Duplicates);
bool shouldIgnoreDuplicate(const ResourceEntryRef &Entry) const;
bool shouldIgnoreDuplicate(const std::vector<StringOrID> &Context) const;
+ void removeChild(TreeNode &Parent, const StringOrID &Key);
TreeNode Root;
std::vector<std::vector<uint8_t>> Data;
@@ -264,6 +296,14 @@ writeWindowsResourceCOFF(llvm::COFF::MachineTypes MachineType,
const WindowsResourceParser &Parser,
uint32_t TimeDateStamp);
+/// Writes the resources of \p Parser in the format of the .rsrc section of a
+/// PE image, i.e. the resource directory followed by the resource data.
+/// \p SectionRVA is the RVA at which the section will be loaded, which the
+/// resource data entries refer to the data by.
+LLVM_ABI std::vector<uint8_t>
+writeWindowsResourceSection(const WindowsResourceParser &Parser,
+ uint32_t SectionRVA);
+
LLVM_ABI void printResourceTypeName(uint16_t TypeID, raw_ostream &OS);
} // namespace object
} // namespace llvm
diff --git a/llvm/lib/Object/COFFObjectFile.cpp b/llvm/lib/Object/COFFObjectFile.cpp
index 08458f8345209..482da5b731858 100644
--- a/llvm/lib/Object/COFFObjectFile.cpp
+++ b/llvm/lib/Object/COFFObjectFile.cpp
@@ -2371,6 +2371,22 @@ ResourceSectionRef::getTableEntry(const coff_resource_dir_table &Table,
}
Error ResourceSectionRef::load(const COFFObjectFile *O) {
+ // In images, the resource directory is located by the resource table data
+ // directory entry. Offsets within the directory are relative to that entry,
+ // rather than to the section containing it.
+ const data_directory *Dir = O->getDataDirectory(COFF::RESOURCE_TABLE);
+ if (Dir && Dir->RelativeVirtualAddress != 0 && Dir->Size != 0) {
+ for (const SectionRef &S : O->sections()) {
+ const coff_section *Sec = O->getCOFFSection(S);
+ if (Dir->RelativeVirtualAddress >= Sec->VirtualAddress &&
+ uint64_t(Dir->RelativeVirtualAddress) <
+ uint64_t(Sec->VirtualAddress) + O->getSectionSize(Sec))
+ return load(O, S, Dir->RelativeVirtualAddress - Sec->VirtualAddress);
+ }
+ return createStringError(object_error::parse_failed,
+ "resource directory not found in any section");
+ }
+
for (const SectionRef &S : O->sections()) {
Expected<StringRef> Name = S.getName();
if (!Name)
@@ -2384,14 +2400,25 @@ Error ResourceSectionRef::load(const COFFObjectFile *O) {
}
Error ResourceSectionRef::load(const COFFObjectFile *O, const SectionRef &S) {
+ return load(O, S, 0);
+}
+
+Error ResourceSectionRef::load(const COFFObjectFile *O, const SectionRef &S,
+ uint32_t Offset) {
Obj = O;
Section = S;
Expected<StringRef> Contents = Section.getContents();
if (!Contents)
return Contents.takeError();
- BBS = BinaryByteStream(*Contents, llvm::endianness::little);
+ if (Offset >= Contents->size())
+ return createStringError(object_error::parse_failed,
+ "resource directory extends past end of section");
+ SectionOffset = Offset;
+ BBS =
+ BinaryByteStream(Contents->drop_front(Offset), llvm::endianness::little);
const coff_section *COFFSect = Obj->getCOFFSection(Section);
ArrayRef<coff_relocation> OrigRelocs = Obj->getRelocations(COFFSect);
+ Relocs.clear();
Relocs.reserve(OrigRelocs.size());
for (const coff_relocation &R : OrigRelocs)
Relocs.push_back(&R);
@@ -2410,8 +2437,8 @@ ResourceSectionRef::getContents(const coff_resource_data_entry &Entry) {
// the coff_resource_data_entry struct).
const uint8_t *EntryPtr = reinterpret_cast<const uint8_t *>(&Entry);
ptrdiff_t EntryOffset = EntryPtr - BBS.data().data();
- coff_relocation RelocTarget{ulittle32_t(EntryOffset), ulittle32_t(0),
- ulittle16_t(0)};
+ coff_relocation RelocTarget{ulittle32_t(SectionOffset + EntryOffset),
+ ulittle32_t(0), ulittle16_t(0)};
auto RelocsForOffset =
std::equal_range(Relocs.begin(), Relocs.end(), &RelocTarget,
[](const coff_relocation *A, const coff_relocation *B) {
diff --git a/llvm/lib/Object/WindowsResource.cpp b/llvm/lib/Object/WindowsResource.cpp
index 211e581a7da6a..08986ee28c132 100644
--- a/llvm/lib/Object/WindowsResource.cpp
+++ b/llvm/lib/Object/WindowsResource.cpp
@@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "llvm/Object/WindowsResource.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/Object/COFF.h"
#include "llvm/Object/WindowsMachineFlag.h"
#include "llvm/Support/FormatVariadic.h"
@@ -430,6 +431,7 @@ Error WindowsResourceParser::addChildren(TreeNode &Node,
Context.push_back(StringOrID(Entry.Identifier.ID));
bool Added = Node.addDataChild(Entry.Identifier.ID, Table.MajorVersion,
Table.MinorVersion, Table.Characteristics,
+ DataEntry.Codepage, DataEntry.Reserved,
Origin, Data.size(), Child);
if (Added) {
UNWRAP_OR_RETURN(Contents, RSR.getContents(DataEntry));
@@ -454,10 +456,11 @@ WindowsResourceParser::TreeNode::TreeNode(uint32_t StringIndex)
WindowsResourceParser::TreeNode::TreeNode(uint16_t MajorVersion,
uint16_t MinorVersion,
uint32_t Characteristics,
+ uint32_t CodePage, uint32_t Reserved,
uint32_t Origin, uint32_t DataIndex)
: IsDataNode(true), DataIndex(DataIndex), MajorVersion(MajorVersion),
MinorVersion(MinorVersion), Characteristics(Characteristics),
- Origin(Origin) {}
+ CodePage(CodePage), Reserved(Reserved), Origin(Origin) {}
std::unique_ptr<WindowsResourceParser::TreeNode>
WindowsResourceParser::TreeNode::createStringNode(uint32_t Index) {
@@ -470,13 +473,12 @@ WindowsResourceParser::TreeNode::createIDNode() {
}
std::unique_ptr<WindowsResourceParser::TreeNode>
-WindowsResourceParser::TreeNode::createDataNode(uint16_t MajorVersion,
- uint16_t MinorVersion,
- uint32_t Characteristics,
- uint32_t Origin,
- uint32_t DataIndex) {
- return std::unique_ptr<TreeNode>(new TreeNode(
- MajorVersion, MinorVersion, Characteristics, Origin, DataIndex));
+WindowsResourceParser::TreeNode::createDataNode(
+ uint16_t MajorVersion, uint16_t MinorVersion, uint32_t Characteristics,
+ uint32_t CodePage, uint32_t Reserved, uint32_t Origin, uint32_t DataIndex) {
+ return std::unique_ptr<TreeNode>(new TreeNode(MajorVersion, MinorVersion,
+ Characteristics, CodePage,
+ Reserved, Origin, DataIndex));
}
WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addTypeNode(
@@ -502,7 +504,7 @@ bool WindowsResourceParser::TreeNode::addLanguageNode(
std::vector<std::vector<uint8_t>> &Data, TreeNode *&Result) {
bool Added = addDataChild(Entry.getLanguage(), Entry.getMajorVersion(),
Entry.getMinorVersion(), Entry.getCharacteristics(),
- Origin, Data.size(), Result);
+ 0, 0, Origin, Data.size(), Result);
if (Added)
Data.push_back(Entry.getData());
return Added;
@@ -510,10 +512,10 @@ bool WindowsResourceParser::TreeNode::addLanguageNode(
bool WindowsResourceParser::TreeNode::addDataChild(
uint32_t ID, uint16_t MajorVersion, uint16_t MinorVersion,
- uint32_t Characteristics, uint32_t Origin, uint32_t DataIndex,
- TreeNode *&Result) {
+ uint32_t Characteristics, uint32_t CodePage, uint32_t Reserved,
+ uint32_t Origin, uint32_t DataIndex, TreeNode *&Result) {
auto NewChild = createDataNode(MajorVersion, MinorVersion, Characteristics,
- Origin, DataIndex);
+ CodePage, Reserved, Origin, DataIndex);
auto ElementInserted = IDChildren.emplace(ID, std::move(NewChild));
Result = ElementInserted.first->second.get();
return ElementInserted.second;
@@ -597,6 +599,187 @@ void WindowsResourceParser::TreeNode::shiftDataIndexDown(uint32_t Index) {
}
}
+// Shift StringIndex of all string children with an Index greater or equal to
+// the given one, to fill a gap from removing an entry from the StringTable
+// vector.
+void WindowsResourceParser::TreeNode::shiftStringIndexDown(uint32_t Index) {
+ for (auto &Child : StringChildren) {
+ if (Child.second->StringIndex >= Index)
+ Child.second->StringIndex--;
+ Child.second->shiftStringIndexDown(Index);
+ }
+ for (auto &Child : IDChildren)
+ Child.second->shiftStringIndexDown(Index);
+}
+
+WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addChild(
+ const StringOrID &Key, std::vector<std::vector<UTF16>> &StringTable) {
+ if (Key.IsString)
+ return addNameChild(Key.String, StringTable);
+ return addIDChild(Key.ID);
+}
+
+const WindowsResourceParser::TreeNode *
+WindowsResourceParser::TreeNode::findChild(const StringOrID &Key) const {
+ if (!Key.IsString) {
+ auto Child = IDChildren.find(Key.ID);
+ return Child == IDChildren.end() ? nullptr : Child->second.get();
+ }
+ std::string KeyString;
+ convertUTF16LEToUTF8String(Key.String, KeyString);
+ auto Child = StringChildren.find(KeyString);
+ return Child == StringChildren.end() ? nullptr : Child->second.get();
+}
+
+const WindowsResourceParser::TreeNode *
+WindowsResourceParser::findResource(const StringOrID &Type,
+ const StringOrID &Name) const {
+ const TreeNode *TypeNode = Root.findChild(Type);
+ return TypeNode ? TypeNode->findChild(Name) : nullptr;
+}
+
+namespace {
+
+// Owns a copy of a StringOrID's string, if any. This keeps keys valid while
+// resource removal mutates the parser's string table.
+struct StableStringOrID {
+ std::vector<UTF16> Storage;
+ WindowsResourceParser::StringOrID Value;
+
+ explicit StableStringOrID(const WindowsResourceParser::StringOrID &Key)
+ : Storage(Key.IsString ? Key.String.vec() : std::vector<UTF16>()),
+ Value(Key.IsString ? WindowsResourceParser::StringOrID(Storage)
+ : WindowsResourceParser::StringOrID(Key.ID)) {}
+};
+
+} // namespace
+
+void WindowsResourceParser::addResource(const StringOrID &Type,
+ const StringOrID &Name,
+ uint16_t Language,
+ ArrayRef<uint8_t> ResourceData,
+ StringRef Origin) {
+ StableStringOrID StableType(Type);
+ StableStringOrID StableName(Name);
+ std::vector<uint8_t> NewData = ResourceData.vec();
+
+ uint32_t CodePage = 0;
+ uint32_t Reserved = 0;
+ if (const TreeNode *NameNode =
+ findResource(StableType.Value, StableName.Value)) {
+ auto LanguageNode = NameNode->IDChildren.find(Language);
+ if (LanguageNode != NameNode->IDChildren.end() &&
+ LanguageNode->second->IsDataNode) {
+ CodePage = LanguageNode->second->CodePage;
+ Reserved = LanguageNode->second->Reserved;
+ }
+ }
+
+ // Replace any existing resource with the same type, name and language.
+ removeResource(StableType.Value, StableName.Value, Language);
+
+ uint32_t OriginIndex = InputFilenames.size();
+ InputFilenames.push_back(std::string(Origin));
+ TreeNode &TypeNode = Root.addChild(StableType.Value, StringTable);
+ TreeNode &NameNode = TypeNode.addChild(StableName.Value, StringTable);
+ TreeNode *LanguageNode;
+ bool Added = NameNode.addDataChild(Language, 0, 0, 0, CodePage, Reserved,
+ OriginIndex, Data.size(), LanguageNode);
+ assert(Added && "existing resource was not removed");
+ (void)Added;
+ Data.push_back(std::move(NewData));
+}
+
+bool WindowsResourceParser::removeResource(const StringOrID &Type,
+ const StringOrID &Name,
+ std::optional<uint16_t> Language) {
+ StableStringOrID StableType(Type);
+ StableStringOrID StableName(Name);
+ TreeNode *TypeNode = Root.findChild(StableType.Value);
+ if (!TypeNode)
+ return false;
+ TreeNode *NameNode = TypeNode->findChild(StableName.Value);
+ if (!NameNode)
+ return false;
+
+ bool Removed = false;
+ std::vector<uint32_t> RemovedDataIndices;
+ for (auto Child = NameNode->IDChildren.begin();
+ Child != NameNode->IDChildren.end();) {
+ if (Language && Child->first != *Language) {
+ ++Child;
+ continue;
+ }
+ if (Child->second->IsDataNode)
+ RemovedDataIndices.push_back(Child->second->DataIndex);
+ Child = NameNode->IDChildren.erase(Child);
+ Removed = true;
+ }
+
+ // Remove directories that have become empty.
+ if (NameNode->IDChildren.empty() && NameNode->StringChildren.empty()) {
+ removeChild(*TypeNode, StableName.Value);
+ if (TypeNode->IDChildren.empty() && TypeNode->StringChildren.empty())
+ removeChild(Root, StableType.Value);
+ }
+
+ // Remove the data of the removed resources, starting with the highest index
+ // so that the indices of the remaining ones stay valid while shifting.
+ llvm::sort(RemovedDataIndices, std::greater<>());
+ for (uint32_t Index : RemovedDataIndices) {
+ Data.erase(Data.begin() + Index);
+ Root.shiftDataIndexDown(Index);
+ }
+ return Removed;
+}
+
+void WindowsResourceParser::removeChild(TreeNode &Parent,
+ const StringOrID &Key) {
+ if (!Key.IsString) {
+ Parent.IDChildren.erase(Key.ID);
+ return;
+ }
+ std::string KeyString;
+ convertUTF16LEToUTF8String(Key.String, KeyString);
+ auto Child = Parent.StringChildren.find(KeyString);
+ if (Child == Parent.StringChildren.end())
+ return;
+ uint32_t StringIndex = Child->second->StringIndex;
+ Parent.StringChildren.erase(Child);
+ StringTable.erase(StringTable.begin() + StringIndex);
+ Root.shiftStringIndexDown(StringIndex);
+}
+
+namespace {
+
+// Writes the resource directory of a resource tree: the directory tables and
+// their entries, the data entries and the directory string table, in the layout
+// used by both the .rsrc$01 section of an object file and the start of the
+// .rsrc section of an image.
+class ResourceDirectoryWriter {
+public:
+ explicit ResourceDirectoryWriter(const WindowsResourceParser &Parser);
+
+ // The size of the directory, including the string table.
+ uint32_t getSize() const { return Size; }
+
+ // Writes the directory to Buf, which must be zero-initialized and at least
+ // getSize() bytes large. The DataRVA field of the data entry of the resource
+ // with data index I is set to GetDataRVA(I) and the offset of the data entry
+ // in Buf is stored in DataEntryOffsets[I].
+ void write(char *Buf, function_ref<uint32_t(uint32_t)> GetDataRVA,
+ std::vector<uint32_t> &DataEntryOffsets) const;
+
+private:
+ const WindowsResourceParser::TreeNode &Root;
+ const ArrayRef<std::vector<uint8_t>> Data;
+ const ArrayRef<std::vector<UTF16>> StringTable;
+ std::vector<uint32_t> StringTableOffsets;
+ uint32_t Size;
+};
+
+} // namespace
+
class WindowsResourceCOFFWriter {
public:
WindowsResourceCOFFWriter(COFF::MachineTypes MachineType,
@@ -614,15 +797,13 @@ class WindowsResourceCOFFWriter {
void writeSecondSection();
void writeSymbolTable();
void writeStringTable();
- void writeDirectoryTree();
- void writeDirectoryStringTable();
void writeFirstSectionRelocations();
std::unique_ptr<WritableMemoryBuffer> OutputBuffer;
char *BufferStart;
uint64_t CurrentOffset = 0;
COFF::MachineTypes MachineType;
- const WindowsResourceParser::TreeNode &Resources;
const ArrayRef<std::vector<uint8_t>> Data;
+ ResourceDirectoryWriter Directory;
uint64_t FileSize;
uint32_t SymbolTableOffset;
uint32_t SectionOneSize;
@@ -630,8 +811,6 @@ class WindowsResourceCOFFWriter {
uint32_t SectionOneRelocations;
uint32_t SectionTwoSize;
uint32_t SectionTwoOffset;
- const ArrayRef<std::vector<UTF16>> StringTable;
- std::vector<uint32_t> StringTableOffsets;
std::vector<uint32_t> DataOffsets;
std::vector<uint32_t> RelocationAddresses;
};
@@ -639,8 +818,7 @@ class WindowsResourceCOFFWriter {
WindowsResourceCOFFWriter::WindowsResourceCOFFWriter(
COFF::MachineTypes MachineType, const WindowsResourceParser &Parser,
Error &E)
- : MachineType(MachineType), Resources(Parser.getTree()),
- Data(Parser.getData()), StringTable(Parser.getStringTable()) {
+ : MachineType(MachineType), Data(Parser.getData()), Directory(Parser) {
performFileLayout();
OutputBuffer = WritableMemoryBuffer::getNewMemBuffer(
@@ -669,16 +847,7 @@ void WindowsResourceCOFFWriter::performFileLayout() {
void WindowsResourceCOFFWriter::performSectionOneLayout() {
SectionOneOffset = FileSize;
- SectionOneSize = Resources.getTreeSize();
- uint32_t CurrentStringOffset = SectionOneSize;
- uint32_t TotalStringTableSize = 0;
- for (auto const &String : StringTable) {
- StringTableOffsets.push_back(CurrentStringOffset);
- uint32_t StringSize = String.size() * sizeof(UTF16) + sizeof(uint16_t);
- CurrentStringOffset += StringSize;
- TotalStringTableSize += StringSize;
- }
- SectionOneSize += alignTo(TotalStringTableSize, sizeof(uint32_t));
+ SectionOneSize = Directory.getSize();
// account for the relocations of section one.
SectionOneRelocations = FileSize + SectionOneSize;
@@ -780,8 +949,11 @@ void WindowsResourceCOFFWriter::writeFirstSection() {
// Write section one.
CurrentOffset += sizeof(coff_section);
- writeDirectoryTree();
- writeDirectoryStringTable();
+ // The DataRVA fields are set to zero because they are relocated.
+ Directory.write(
+ BufferStart + CurrentOffset, [](uint32_t) { return 0; },
+ RelocationAddresses);
+ CurrentOffset += SectionOneSize;
writeFirstSectionRelocations();
CurrentOffset = alignTo(CurrentOffset, SECTION_ALIGNMENT);
@@ -867,23 +1039,39 @@ void WindowsResourceCOFFWriter::writeStringTable() {
memset(COFFStringTable, 0, 4);
}
-void WindowsResourceCOFFWriter::writeDirectoryTree() {
+ResourceDirectoryWriter::ResourceDirectoryWriter(
+ const WindowsResourceParser &Parser)
+ : Root(Parser.getTree()), Data(Parser.getData()),
+ StringTable(Parser.getStringTable()) {
+ // The string table follows the directory tree and the data entries.
+ uint32_t StringOffset = Root.getTreeSize();
+ for (auto const &String : StringTable) {
+ StringTableOffsets.push_back(StringOffset);
+ StringOffset += String.size() * sizeof(UTF16) + sizeof(uint16_t);
+ }
+ Size = alignTo(StringOffset, sizeof(uint32_t));
+}
+
+void ResourceDirectoryWriter::write(
+ char *Buf, function_ref<uint32_t(uint32_t)> GetDataRVA,
+ std::vector<uint32_t> &DataEntryOffsets) const {
+ using TreeNode = WindowsResourceParser::TreeNode;
+
// Traverse parsed resource tree breadth-first and write the corresponding
// COFF objects.
- std::queue<const WindowsResourceParser::TreeNode *> Queue;
- Queue.push(&Resources);
+ std::queue<const TreeNode *> Queue;
+ Queue.push(&Root);
uint32_t NextLevelOffset =
- sizeof(coff_resource_dir_table) + (Resources.getStringChildren().size() +
- Resources.getIDChildren().size()) *
- sizeof(coff_resource_dir_entry);
- std::vector<const WindowsResourceParser::TreeNode *> DataEntriesTreeOrder;
- uint32_t CurrentRelativeOffset = 0;
+ sizeof(coff_resource_dir_table) +
+ (Root.getStringChildren().size() + Root.getIDChildren().size()) *
+ sizeof(coff_resource_dir_entry);
+ std::vector<const TreeNode *> DataEntriesTreeOrder;
+ uint32_t Offset = 0;
while (!Queue.empty()) {
- auto CurrentNode = Queue.front();
+ const TreeNode *CurrentNode = Queue.front();
Queue.pop();
- auto *Table = reinterpret_cast<coff_resource_dir_table *>(BufferStart +
- CurrentOffset);
+ auto *Table = reinterpret_cast<coff_resource_dir_table *>(Buf + Offset);
Table->Characteristics = CurrentNode->getCharacteristics();
Table->TimeDateStamp = 0;
Table->MajorVersion = CurrentNode->getMajorVersion();
@@ -892,80 +1080,61 @@ void WindowsResourceCOFFWriter::writeDirectoryTree() {
auto &StringChildren = CurrentNode->getStringChildren();
Table->NumberOfNameEntries = StringChildren.size();
Table->NumberOfIDEntries = IDChildren.size();
- CurrentOffset += sizeof(coff_resource_dir_table);
- CurrentRelativeOffset += sizeof(coff_resource_dir_table);
+ Offset += sizeof(coff_resource_dir_table);
// Write the directory entries immediately following each directory table.
- for (auto const &Child : StringChildren) {
- auto *Entry = reinterpret_cast<coff_resource_dir_entry *>(BufferStart +
- CurrentOffset);
- Entry->Identifier.setNameOffset(
- StringTableOffsets[Child.second->getStringIndex()]);
- if (Child.second->checkIsDataNode()) {
+ auto WriteEntry = [&](const TreeNode &Child, auto SetIdentifier) {
+ auto *Entry = reinterpret_cast<coff_resource_dir_entry *>(Buf + Offset);
+ SetIdentifier(*Entry);
+ if (Child.checkIsDataNode()) {
Entry->Offset.DataEntryOffset = NextLevelOffset;
NextLevelOffset += sizeof(coff_resource_data_entry);
- DataEntriesTreeOrder.push_back(Child.second.get());
+ DataEntriesTreeOrder.push_back(&Child);
} else {
Entry->Offset.SubdirOffset = NextLevelOffset + (1 << 31);
- NextLevelOffset += sizeof(coff_resource_dir_table) +
- (Child.second->getStringChildren().size() +
- Child.second->getIDChildren().size()) *
- sizeof(coff_resource_dir_entry);
- Queue.push(Child.second.get());
+ NextLevelOffset +=
+ sizeof(coff_resource_dir_table) +
+ (Child.getStringChildren().size() + Child.getIDChildren().size()) *
+ sizeof(coff_resource_dir_entry);
+ Queue.push(&Child);
}
- CurrentOffset += sizeof(coff_resource_dir_entry);
- CurrentRelativeOffset += sizeof(coff_resource_dir_entry);
- }
- for (auto const &Child : IDChildren) {
- auto *Entry = reinterpret_cast<coff_resource_dir_entry *>(BufferStart +
- CurrentOffset);
- Entry->Identifier.ID = Child.first;
- if (Child.second->checkIsDataNode()) {
- Entry->Offset.DataEntryOffset = NextLevelOffset;
- NextLevelOffset += sizeof(coff_resource_data_entry);
- DataEntriesTreeOrder.push_back(Child.second.get());
- } else {
- Entry->Offset.SubdirOffset = NextLevelOffset + (1 << 31);
- NextLevelOffset += sizeof(coff_resource_dir_table) +
- (Child.second->getStringChildren().size() +
- Child.second->getIDChildren().size()) *
- sizeof(coff_resource_dir_entry);
- Queue.push(Child.second.get());
- }
- CurrentOffset += sizeof(coff_resource_dir_entry);
- CurrentRelativeOffset += sizeof(coff_resource_dir_entry);
- }
+ Offset += sizeof(coff_resource_dir_entry);
+ };
+ for (auto const &Child : StringChildren)
+ WriteEntry(*Child.second, [&](coff_resource_dir_entry &Entry) {
+ Entry.Identifier.setNameOffset(
+ StringTableOffsets[Child.second->getStringIndex()]);
+ });
+ for (auto const &Child : IDChildren)
+ WriteEntry(*Child.second, [&](coff_resource_dir_entry &Entry) {
+ Entry.Identifier.ID = Child.first;
+ });
}
- RelocationAddresses.resize(Data.size());
+ DataEntryOffsets.resize(Data.size());
// Now write all the resource data entries.
- for (const auto *DataNodes : DataEntriesTreeOrder) {
- auto *Entry = reinterpret_cast<coff_resource_data_entry *>(BufferStart +
- CurrentOffset);
- RelocationAddresses[DataNodes->getDataIndex()] = CurrentRelativeOffset;
- Entry->DataRVA = 0; // Set to zero because it is a relocation.
- Entry->DataSize = Data[DataNodes->getDataIndex()].size();
- Entry->Codepage = 0;
- Entry->Reserved = 0;
- CurrentOffset += sizeof(coff_resource_data_entry);
- CurrentRelativeOffset += sizeof(coff_resource_data_entry);
- }
-}
-
-void WindowsResourceCOFFWriter::writeDirectoryStringTable() {
- // Now write the directory string table for .rsrc$01
- uint32_t TotalStringTableSize = 0;
+ for (const TreeNode *DataNode : DataEntriesTreeOrder) {
+ auto *Entry = reinterpret_cast<coff_resource_data_entry *>(Buf + Offset);
+ uint32_t DataIndex = DataNode->getDataIndex();
+ DataEntryOffsets[DataIndex] = Offset;
+ Entry->DataRVA = GetDataRVA(DataIndex);
+ Entry->DataSize = Data[DataIndex].size();
+ Entry->Codepage = DataNode->getCodePage();
+ Entry->Reserved = DataNode->getReserved();
+ Offset += sizeof(coff_resource_data_entry);
+ }
+
+ // Now write the directory string table.
for (auto &String : StringTable) {
uint16_t Length = String.size();
- support::endian::write16le(BufferStart + CurrentOffset, Length);
- CurrentOffset += sizeof(uint16_t);
- auto *Start = reinterpret_cast<UTF16 *>(BufferStart + CurrentOffset);
+ support::endian::write16le(Buf + Offset, Length);
+ Offset += sizeof(uint16_t);
+ auto *Start = reinterpret_cast<UTF16 *>(Buf + Offset);
llvm::copy(String, Start);
- CurrentOffset += Length * sizeof(UTF16);
- TotalStringTableSize += Length * sizeof(UTF16) + sizeof(uint16_t);
+ Offset += Length * sizeof(UTF16);
}
- CurrentOffset +=
- alignTo(TotalStringTableSize, sizeof(uint32_t)) - TotalStringTableSize;
+ assert(alignTo(Offset, sizeof(uint32_t)) == Size &&
+ "directory layout mismatch");
}
void WindowsResourceCOFFWriter::writeFirstSectionRelocations() {
@@ -1013,5 +1182,33 @@ writeWindowsResourceCOFF(COFF::MachineTypes MachineType,
return Writer.write(TimeDateStamp);
}
+std::vector<uint8_t>
+writeWindowsResourceSection(const WindowsResourceParser &Parser,
+ uint32_t SectionRVA) {
+ ResourceDirectoryWriter Directory(Parser);
+ ArrayRef<std::vector<uint8_t>> Data = Parser.getData();
+
+ // The resource data follows the directory, with each entry aligned to 8
+ // bytes like in the .rsrc$02 section created by cvtres.
+ std::vector<uint32_t> DataOffsets;
+ DataOffsets.reserve(Data.size());
+ uint32_t Size = Directory.getSize();
+ for (auto const &Entry : Data) {
+ Size = alignTo(Size, sizeof(uint64_t));
+ DataOffsets.push_back(Size);
+ Size += Entry.size();
+ }
+
+ std::vector<uint8_t> Buf(Size);
+ std::vector<uint32_t> DataEntryOffsets;
+ Directory.write(
+ reinterpret_cast<char *>(Buf.data()),
+ [&](uint32_t DataIndex) { return SectionRVA + DataOffsets[DataIndex]; },
+ DataEntryOffsets);
+ for (size_t I = 0; I < Data.size(); ++I)
+ llvm::copy(Data[I], Buf.begin() + DataOffsets[I]);
+ return Buf;
+}
+
} // namespace object
} // namespace llvm
>From 0bf3e267ba8802a41326f0a1c88a5d9d0fb0b860 Mon Sep 17 00:00:00 2001
From: Fabian Meumertzheim <fabian at meumertzhe.im>
Date: Tue, 22 Sep 2026 14:26:32 +0200
Subject: [PATCH 2/3] [llvm-objcopy] Add --dump-resource and --update-resource
for PE images
Add two COFF-specific options that operate on the resources of PE
images, identified by their integer type, name and (optionally)
language IDs:
--dump-resource=<type>/<name>[/<language>]=<file> writes the contents
of a resource to a file.
--update-resource=<type>/<name>[/<language>]=<file> replaces the
contents of a resource with those of a file, adding the resource if it
doesn't exist. Without a language, the resources of all languages are
replaced by a single one, which keeps the language if there was
exactly one and is language-neutral otherwise.
The updated resource section is written in place if it fits into its
current address range. If it doesn't, the sections following it are
moved, which is only possible if they are discardable (like .reloc)
since only the references to them from the data directories, the debug
directory and the base relocations are updated. Data preceding the
resource directory in its section is kept, data following the resources
has to be zero padding. If the image has no resource section, one is
added after the last non-discardable section.
Also clear the attribute certificate table data directory entry when
writing PE images. The table is located by a file offset and isn't part
of any section, so it isn't copied and the entry would otherwise point
at unrelated data. Any Authenticode signature is invalidated by
modifying the image anyway.
This makes it possible to embed and extract application manifests
without Windows-only tools such as mt.exe or the UpdateResource API.
Assisted-by: Claude Code
Assisted-by: OpenAI Codex
Claude-Session: https://claude.ai/code/session_01M9VKWRFkU4dMqPLUkAY9Lu
---
llvm/docs/CommandGuide/llvm-objcopy.md | 23 +
llvm/docs/ReleaseNotes.md | 5 +
llvm/include/llvm/ObjCopy/COFF/COFFConfig.h | 27 +
llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp | 339 ++++++++++-
llvm/lib/ObjCopy/COFF/COFFObject.cpp | 6 +
llvm/lib/ObjCopy/COFF/COFFObject.h | 2 +
llvm/lib/ObjCopy/COFF/COFFWriter.cpp | 10 +
llvm/lib/ObjCopy/ConfigManager.cpp | 15 +-
.../llvm-objcopy/COFF/certificate-table.test | 46 ++
.../llvm-objcopy/COFF/dump-resource.test | 192 +++++++
.../llvm-objcopy/COFF/update-resource.test | 525 ++++++++++++++++++
.../llvm-objcopy/DXContainer/copy-basic.yaml | 4 +
llvm/tools/llvm-objcopy/ObjcopyOptions.cpp | 63 +++
llvm/tools/llvm-objcopy/ObjcopyOpts.td | 12 +
14 files changed, 1252 insertions(+), 17 deletions(-)
create mode 100644 llvm/test/tools/llvm-objcopy/COFF/certificate-table.test
create mode 100644 llvm/test/tools/llvm-objcopy/COFF/dump-resource.test
create mode 100644 llvm/test/tools/llvm-objcopy/COFF/update-resource.test
diff --git a/llvm/docs/CommandGuide/llvm-objcopy.md b/llvm/docs/CommandGuide/llvm-objcopy.md
index c26cd1e9be883..4cde46583ceb8 100644
--- a/llvm/docs/CommandGuide/llvm-objcopy.md
+++ b/llvm/docs/CommandGuide/llvm-objcopy.md
@@ -574,10 +574,33 @@ Keep undefined symbols, even if they would otherwise be stripped.
## COFF-SPECIFIC OPTIONS
+:::{option} --dump-resource <type>/<name>[/<language>]=<file>
+Dump the contents of the resource of a PE image with the given integer type,
+name and language IDs into the file `<file>`. If `<language>` is omitted, the
+resource's first language is used.
+:::
+
:::{option} --subsystem <name>[:<version>]
Set the PE subsystem, and optionally subsystem version.
:::
+:::{option} --update-resource <type>/<name>[/<language>]=<file>
+Replace the contents of the resource of a PE image with the given integer type,
+name and language IDs with the contents of the file `<file>`, adding the
+resource if it does not exist. If `<language>` is omitted, the new resource
+replaces the resources with the given type and name of all languages. It keeps
+their language if there is exactly one, and is language-neutral otherwise.
+
+If the resource section has to grow beyond its current address range, the
+sections following it are moved, which is only possible if they are
+discardable (like `.reloc`). A resource section is added after the last
+non-discardable section if the image does not have one.
+:::
+
+Note that modifying a PE image invalidates its Authenticode signature. The
+attribute certificate table holding the signature is not copied and its data
+directory entry is cleared.
+
## SUPPORTED FORMATS
The following values are currently supported by {program}`llvm-objcopy` for the
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 11d86f9dd5db7..a4521845f490f 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -311,6 +311,11 @@ Makes programs 10x faster by doing Special New Thing.
* llvm-rc now supports `/showIncludes` to report header and resource-file
dependencies in a format compatible with Ninja's `deps = msvc` mode.
+* llvm-objcopy now supports dumping and updating the resources of PE images
+ with the `--dump-resource` and `--update-resource` options. It also no longer
+ writes PE images with a dangling attribute certificate table (Authenticode
+ signature) entry, but clears the entry instead.
+
### Changes to LLDB
* `platform.plugin.wasm.runtime-args` now precede the port argument on the Wasm
diff --git a/llvm/include/llvm/ObjCopy/COFF/COFFConfig.h b/llvm/include/llvm/ObjCopy/COFF/COFFConfig.h
index d69285d7b5b3c..f55d081cccfac 100644
--- a/llvm/include/llvm/ObjCopy/COFF/COFFConfig.h
+++ b/llvm/include/llvm/ObjCopy/COFF/COFFConfig.h
@@ -9,16 +9,43 @@
#ifndef LLVM_OBJCOPY_COFF_COFFCONFIG_H
#define LLVM_OBJCOPY_COFF_COFFCONFIG_H
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include <cstdint>
+#include <memory>
#include <optional>
namespace llvm {
namespace objcopy {
+// Identifies a resource of a PE image by its integer type and name IDs and,
+// optionally, its language ID.
+struct COFFResourceIdentifier {
+ uint32_t Type = 0;
+ uint32_t Name = 0;
+ std::optional<uint16_t> Language;
+};
+
+// A resource to dump from a PE image into a file.
+struct COFFResourceDump {
+ COFFResourceIdentifier Resource;
+ StringRef FileName;
+};
+
+// A resource to add to a PE image or to replace in it.
+struct COFFResourceUpdate {
+ COFFResourceIdentifier Resource;
+ std::shared_ptr<MemoryBuffer> Data;
+};
+
// Coff specific configuration for copying/stripping a single file.
struct COFFConfig {
std::optional<unsigned> Subsystem;
std::optional<unsigned> MajorSubsystemVersion;
std::optional<unsigned> MinorSubsystemVersion;
+ SmallVector<COFFResourceDump, 0> DumpResource;
+ SmallVector<COFFResourceUpdate, 0> UpdateResource;
};
} // namespace objcopy
diff --git a/llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp b/llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp
index 48c488826c47d..004617dcce826 100644
--- a/llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp
+++ b/llvm/lib/ObjCopy/COFF/COFFObjcopy.cpp
@@ -13,11 +13,15 @@
#include "llvm/ObjCopy/COFF/COFFConfig.h"
#include "llvm/ObjCopy/CommonConfig.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Object/Binary.h"
#include "llvm/Object/COFF.h"
+#include "llvm/Object/WindowsResource.h"
#include "llvm/Support/CRC.h"
#include "llvm/Support/Errc.h"
+#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/Path.h"
#include <cassert>
@@ -131,31 +135,339 @@ static uint32_t flagsToCharacteristics(SectionFlag AllFlags, uint32_t OldChar) {
return NewCharacteristics;
}
+static Error writeFile(StringRef FileName, ArrayRef<uint8_t> Contents) {
+ std::unique_ptr<FileOutputBuffer> Buffer;
+ if (auto B = FileOutputBuffer::create(FileName, Contents.size()))
+ Buffer = std::move(*B);
+ else
+ return B.takeError();
+
+ llvm::copy(Contents, Buffer->getBufferStart());
+ return Buffer->commit();
+}
+
static Error dumpSection(Object &O, StringRef SectionName, StringRef FileName) {
for (const coff::Section &Section : O.getSections()) {
if (Section.Name != SectionName)
continue;
+ return writeFile(FileName, Section.getContents());
+ }
+ return createStringError(object_error::parse_failed, "section '%s' not found",
+ SectionName.str().c_str());
+}
- ArrayRef<uint8_t> Contents = Section.getContents();
+// Returns the size of the address range that a section of a PE image occupies
+// when loaded.
+static uint64_t getMappedSize(const Section &Sec) {
+ return std::max<uint64_t>(Sec.Header.VirtualSize, Sec.Header.SizeOfRawData);
+}
- std::unique_ptr<FileOutputBuffer> Buffer;
- if (auto B = FileOutputBuffer::create(FileName, Contents.size()))
- Buffer = std::move(*B);
- else
- return B.takeError();
+static bool containsRVA(const Section &Sec, uint32_t RVA) {
+ return RVA >= Sec.Header.VirtualAddress &&
+ RVA < Sec.Header.VirtualAddress + getMappedSize(Sec);
+}
- llvm::copy(Contents, Buffer->getBufferStart());
- if (Error E = Buffer->commit())
+// Applies Patch to the contents of the data directory with the given index of
+// a PE image, if the image has it.
+static Error
+patchDataDirectory(Object &Obj, unsigned Index,
+ function_ref<Error(MutableArrayRef<uint8_t>)> Patch) {
+ if (Obj.DataDirectories.size() <= Index)
+ return Error::success();
+ const data_directory &Dir = Obj.DataDirectories[Index];
+ if (Dir.RelativeVirtualAddress == 0 || Dir.Size == 0)
+ return Error::success();
+ for (Section &Sec : Obj.getMutableSections()) {
+ if (!containsRVA(Sec, Dir.RelativeVirtualAddress))
+ continue;
+ size_t Offset = Dir.RelativeVirtualAddress - Sec.Header.VirtualAddress;
+ if (Offset + Dir.Size > Sec.getContents().size())
+ return createStringError(object_error::parse_failed,
+ "data directory extends past end of section");
+ std::vector<uint8_t> Contents = Sec.getContents().vec();
+ if (Error E =
+ Patch(MutableArrayRef<uint8_t>(Contents).slice(Offset, Dir.Size)))
return E;
+ // setOwnedContents sets the raw data size to the size of the contents,
+ // which is not necessarily aligned to the file alignment.
+ uint32_t SizeOfRawData = Sec.Header.SizeOfRawData;
+ Sec.setOwnedContents(std::move(Contents));
+ Sec.Header.SizeOfRawData = SizeOfRawData;
+ return Error::success();
+ }
+ return createStringError(object_error::parse_failed,
+ "data directory not found in any section");
+}
+// Moves the sections of a PE image starting at the given index up by Delta
+// bytes in the address space, updating the references to them from the data
+// directories, the debug directory and the base relocations. References from
+// other places, such as the code, are not updated, so only sections that are
+// not referenced from there (such as discardable sections) can be moved.
+static Error shiftSections(Object &Obj, size_t FirstIndex, uint32_t Delta) {
+ ArrayRef<Section> Sections = Obj.getSections();
+ if (FirstIndex >= Sections.size())
return Error::success();
+ uint32_t MovedStart = Sections[FirstIndex].Header.VirtualAddress;
+ auto IsMoved = [MovedStart](uint32_t RVA) {
+ return RVA != 0 && RVA >= MovedStart;
+ };
+
+ size_t Index = 0;
+ for (Section &Sec : Obj.getMutableSections())
+ if (Index++ >= FirstIndex)
+ Sec.Header.VirtualAddress += Delta;
+
+ // The certificate table's entry holds a file offset rather than an RVA.
+ for (size_t I = 0; I < Obj.DataDirectories.size(); ++I) {
+ data_directory &Dir = Obj.DataDirectories[I];
+ if (I != CERTIFICATE_TABLE && IsMoved(Dir.RelativeVirtualAddress))
+ Dir.RelativeVirtualAddress += Delta;
}
- return createStringError(object_error::parse_failed, "section '%s' not found",
- SectionName.str().c_str());
+
+ // The debug directory holds the RVAs of the payloads of its entries.
+ if (Error E = patchDataDirectory(
+ Obj, DEBUG_DIRECTORY,
+ [&](MutableArrayRef<uint8_t> Contents) -> Error {
+ for (size_t Offset = 0;
+ Offset + sizeof(debug_directory) <= Contents.size();
+ Offset += sizeof(debug_directory)) {
+ auto *Entry =
+ reinterpret_cast<debug_directory *>(Contents.data() + Offset);
+ if (IsMoved(Entry->AddressOfRawData))
+ Entry->AddressOfRawData += Delta;
+ }
+ return Error::success();
+ }))
+ return E;
+
+ // The base relocation blocks hold the RVAs of the pages they apply to.
+ return patchDataDirectory(
+ Obj, BASE_RELOCATION_TABLE,
+ [&](MutableArrayRef<uint8_t> Contents) -> Error {
+ size_t Offset = 0;
+ while (Offset + sizeof(coff_base_reloc_block_header) <=
+ Contents.size()) {
+ auto *Block = reinterpret_cast<coff_base_reloc_block_header *>(
+ Contents.data() + Offset);
+ if (Block->BlockSize == 0)
+ break;
+ if (Block->BlockSize < sizeof(coff_base_reloc_block_header))
+ return createStringError(object_error::parse_failed,
+ "invalid base relocation block size");
+ if (IsMoved(Block->PageRVA))
+ Block->PageRVA += Delta;
+ Offset += Block->BlockSize;
+ }
+ return Error::success();
+ });
+}
+
+// Replaces the contents of a section of a PE image, resizing it as needed. If
+// the section grows beyond its current address range, the sections following
+// it are moved up, which requires them to be discardable.
+static Error setSectionContents(Object &Obj, Section &Sec,
+ std::vector<uint8_t> Contents) {
+ uint32_t SectionAlignment = Obj.PeHeader.SectionAlignment;
+ uint64_t OldEnd = alignTo(getMappedSize(Sec), SectionAlignment);
+ uint64_t NewEnd = alignTo(Contents.size(), SectionAlignment);
+ if (NewEnd > OldEnd) {
+ // The section index is one-based, so this skips the section itself.
+ for (const Section &Next : Obj.getSections().drop_front(Sec.Index))
+ if (!(Next.Header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE))
+ return createStringError(
+ errc::invalid_argument,
+ "section '%s' cannot grow past section '%s', which is not "
+ "discardable and thus cannot be moved",
+ Sec.Name.str().c_str(), Next.Name.str().c_str());
+ if (Error E = shiftSections(Obj, Sec.Index, NewEnd - OldEnd))
+ return E;
+ }
+ size_t Size = Contents.size();
+ Sec.setOwnedContents(std::move(Contents));
+ Sec.Header.VirtualSize = Size;
+ Sec.Header.SizeOfRawData = alignTo(Size, Obj.PeHeader.FileAlignment);
+ return Error::success();
+}
+
+struct ResourceSectionLocation {
+ Section *Sec = nullptr;
+ uint32_t Offset = 0;
+};
+
+// Returns the section and offset containing the resource directory of a PE
+// image, or a null section if the image has no resources.
+static Expected<ResourceSectionLocation> findResourceSection(Object &Obj) {
+ if (Obj.DataDirectories.size() <= RESOURCE_TABLE)
+ return ResourceSectionLocation();
+ const data_directory &Dir = Obj.DataDirectories[RESOURCE_TABLE];
+ if (Dir.RelativeVirtualAddress == 0 || Dir.Size == 0)
+ return ResourceSectionLocation();
+ for (Section &Sec : Obj.getMutableSections()) {
+ if (containsRVA(Sec, Dir.RelativeVirtualAddress))
+ return ResourceSectionLocation{&Sec, Dir.RelativeVirtualAddress -
+ Sec.Header.VirtualAddress};
+ }
+ return createStringError(object_error::parse_failed,
+ "resource directory not found in any section");
+}
+
+static Expected<ArrayRef<uint8_t>>
+findResourceData(const WindowsResourceParser &Parser,
+ const COFFResourceIdentifier &Resource) {
+ if (const WindowsResourceParser::TreeNode *Node =
+ Parser.findResource(Resource.Type, Resource.Name)) {
+ const auto &Languages = Node->getIDChildren();
+ auto Language = Resource.Language ? Languages.find(*Resource.Language)
+ : Languages.begin();
+ if (Language != Languages.end() && Language->second->checkIsDataNode())
+ return ArrayRef<uint8_t>(
+ Parser.getData()[Language->second->getDataIndex()]);
+ }
+ if (Resource.Language)
+ return createStringError(
+ errc::invalid_argument,
+ "resource with type %u, name %u and language %u not found",
+ Resource.Type, Resource.Name, unsigned(*Resource.Language));
+ return createStringError(errc::invalid_argument,
+ "resource with type %u and name %u not found",
+ Resource.Type, Resource.Name);
+}
+
+static Error handleResources(const COFFConfig &COFFConfig,
+ const COFFObjectFile &In, Object &Obj) {
+ if (COFFConfig.DumpResource.empty() && COFFConfig.UpdateResource.empty())
+ return Error::success();
+ if (!Obj.IsPE)
+ return createStringError(
+ errc::invalid_argument,
+ "resources can only be dumped from or updated in PE images");
+
+ WindowsResourceParser Parser;
+ const data_directory *InputResourceDir = In.getDataDirectory(RESOURCE_TABLE);
+ if (InputResourceDir && InputResourceDir->RelativeVirtualAddress != 0 &&
+ InputResourceDir->Size != 0) {
+ ResourceSectionRef RSR;
+ if (Error E = RSR.load(&In))
+ return E;
+ std::vector<std::string> Duplicates;
+ if (Error E = Parser.parse(RSR, In.getFileName(), Duplicates))
+ return E;
+ if (!Duplicates.empty())
+ return createStringError(object_error::parse_failed, "%s",
+ Duplicates.front().c_str());
+ }
+
+ for (const COFFResourceDump &Dump : COFFConfig.DumpResource) {
+ Expected<ArrayRef<uint8_t>> Data = findResourceData(Parser, Dump.Resource);
+ if (!Data)
+ return Data.takeError();
+ if (Error E = writeFile(Dump.FileName, *Data))
+ return E;
+ }
+
+ if (COFFConfig.UpdateResource.empty())
+ return Error::success();
+
+ Expected<ResourceSectionLocation> ResourceSection = findResourceSection(Obj);
+ if (!ResourceSection)
+ return ResourceSection.takeError();
+
+ for (const COFFResourceUpdate &Update : COFFConfig.UpdateResource) {
+ const COFFResourceIdentifier &Resource = Update.Resource;
+ uint16_t Language = 0;
+ if (Resource.Language) {
+ Language = *Resource.Language;
+ } else if (const WindowsResourceParser::TreeNode *Node =
+ Parser.findResource(Resource.Type, Resource.Name)) {
+ // Replace the resource for all languages, keeping the language if there
+ // is exactly one. Otherwise, the new resource is language-neutral.
+ if (Node->getIDChildren().size() == 1)
+ Language = Node->getIDChildren().begin()->first;
+ else
+ Parser.removeResource(Resource.Type, Resource.Name);
+ }
+ Parser.addResource(Resource.Type, Resource.Name, Language,
+ arrayRefFromStringRef(Update.Data->getBuffer()),
+ Update.Data->getBufferIdentifier());
+ }
+
+ if (Section *Sec = ResourceSection->Sec) {
+ uint32_t Offset = ResourceSection->Offset;
+ ArrayRef<uint8_t> OldContents = Sec->getContents();
+ if (Offset > OldContents.size())
+ return createStringError(
+ object_error::parse_failed,
+ "resource directory extends past end of section '%s'",
+ Sec->Name.str().c_str());
+ // Data following the resources cannot be preserved, as it would have to
+ // move when the resources grow. Only allow zero padding there.
+ uint64_t ResourcesEnd =
+ uint64_t(Offset) + Obj.DataDirectories[RESOURCE_TABLE].Size;
+ if (ResourcesEnd < OldContents.size() &&
+ !llvm::all_of(OldContents.drop_front(ResourcesEnd),
+ [](uint8_t Byte) { return Byte == 0; }))
+ return createStringError(errc::invalid_argument,
+ "section '%s' contains data after its "
+ "resources that cannot be preserved",
+ Sec->Name.str().c_str());
+
+ std::vector<uint8_t> ResourceContents = writeWindowsResourceSection(
+ Parser, Sec->Header.VirtualAddress + Offset);
+ std::vector<uint8_t> Contents(OldContents.begin(),
+ OldContents.begin() + Offset);
+ llvm::append_range(Contents, ResourceContents);
+ Obj.DataDirectories[RESOURCE_TABLE].Size = ResourceContents.size();
+ return setSectionContents(Obj, *Sec, std::move(Contents));
+ }
+
+ // Add a resource section after the last non-discardable section, so that
+ // discardable sections such as .reloc stay at the end of the image.
+ ArrayRef<Section> Sections = Obj.getSections();
+ size_t Index = Sections.size();
+ while (Index > 0 && (Sections[Index - 1].Header.Characteristics &
+ IMAGE_SCN_MEM_DISCARDABLE))
+ --Index;
+ if (Index == 0)
+ Index = Sections.size();
+ uint32_t SectionAlignment = Obj.PeHeader.SectionAlignment;
+ uint32_t RVA = Index == 0
+ ? alignTo(Obj.PeHeader.SizeOfHeaders, SectionAlignment)
+ : alignTo(Sections[Index - 1].Header.VirtualAddress +
+ getMappedSize(Sections[Index - 1]),
+ SectionAlignment);
+ std::vector<uint8_t> Contents = writeWindowsResourceSection(Parser, RVA);
+ if (Error E =
+ shiftSections(Obj, Index, alignTo(Contents.size(), SectionAlignment)))
+ return E;
+
+ Section Sec;
+ Sec.Name = ".rsrc";
+ Sec.Header.VirtualSize = Contents.size();
+ Sec.Header.VirtualAddress = RVA;
+ Sec.setOwnedContents(std::move(Contents));
+ Sec.Header.SizeOfRawData =
+ alignTo(Sec.Header.VirtualSize, Obj.PeHeader.FileAlignment);
+ // Sec.Header.PointerToRawData is filled in by the writer.
+ Sec.Header.PointerToRelocations = 0;
+ Sec.Header.PointerToLinenumbers = 0;
+ Sec.Header.NumberOfRelocations = 0;
+ Sec.Header.NumberOfLinenumbers = 0;
+ Sec.Header.Characteristics =
+ IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;
+ Obj.insertSection(Index, std::move(Sec));
+
+ if (Obj.DataDirectories.size() <= RESOURCE_TABLE)
+ Obj.DataDirectories.resize(RESOURCE_TABLE + 1);
+ Obj.DataDirectories[RESOURCE_TABLE].RelativeVirtualAddress = RVA;
+ Obj.DataDirectories[RESOURCE_TABLE].Size =
+ Obj.getSections()[Index].Header.VirtualSize;
+ return Error::success();
}
static Error handleArgs(const CommonConfig &Config,
- const COFFConfig &COFFConfig, Object &Obj) {
+ const COFFConfig &COFFConfig, const COFFObjectFile &In,
+ Object &Obj) {
for (StringRef Op : Config.DumpSection) {
auto [Section, File] = Op.split('=');
if (Error E = dumpSection(Obj, Section, File))
@@ -305,6 +617,9 @@ static Error handleArgs(const CommonConfig &Config,
NewSection.SectionData->getBufferEnd()});
}
+ if (Error E = handleResources(COFFConfig, In, Obj))
+ return E;
+
if (!Config.AddGnuDebugLink.empty())
if (Error E = addGnuDebugLink(Obj, Config.AddGnuDebugLink))
return E;
@@ -336,7 +651,7 @@ Error executeObjcopyOnBinary(const CommonConfig &Config,
return createFileError(Config.InputFilename, ObjOrErr.takeError());
Object *Obj = ObjOrErr->get();
assert(Obj && "Unable to deserialize COFF object");
- if (Error E = handleArgs(Config, COFFConfig, *Obj))
+ if (Error E = handleArgs(Config, COFFConfig, In, *Obj))
return createFileError(Config.InputFilename, std::move(E));
COFFWriter Writer(*Obj, Out);
if (Error E = Writer.write())
diff --git a/llvm/lib/ObjCopy/COFF/COFFObject.cpp b/llvm/lib/ObjCopy/COFF/COFFObject.cpp
index 91cf7e32a7396..99dc0adda2aea 100644
--- a/llvm/lib/ObjCopy/COFF/COFFObject.cpp
+++ b/llvm/lib/ObjCopy/COFF/COFFObject.cpp
@@ -74,6 +74,12 @@ void Object::addSections(ArrayRef<Section> NewSections) {
updateSections();
}
+void Object::insertSection(size_t Index, Section NewSection) {
+ NewSection.UniqueId = NextSectionUniqueId++;
+ Sections.insert(Sections.begin() + Index, std::move(NewSection));
+ updateSections();
+}
+
void Object::updateSections() {
SectionMap = DenseMap<ssize_t, Section *>(Sections.size());
size_t Index = 1;
diff --git a/llvm/lib/ObjCopy/COFF/COFFObject.h b/llvm/lib/ObjCopy/COFF/COFFObject.h
index 1c04b7f64f3a0..42447d05f4dcf 100644
--- a/llvm/lib/ObjCopy/COFF/COFFObject.h
+++ b/llvm/lib/ObjCopy/COFF/COFFObject.h
@@ -133,6 +133,8 @@ struct Object {
const Section *findSection(ssize_t UniqueId) const;
void addSections(ArrayRef<Section> NewSections);
+ // Insert a section before the section with the given (zero-based) index.
+ void insertSection(size_t Index, Section NewSection);
void removeSections(function_ref<bool(const Section &)> ToRemove);
void truncateSections(function_ref<bool(const Section &)> ToTruncate);
diff --git a/llvm/lib/ObjCopy/COFF/COFFWriter.cpp b/llvm/lib/ObjCopy/COFF/COFFWriter.cpp
index fed67d67f13a7..60350abb02b03 100644
--- a/llvm/lib/ObjCopy/COFF/COFFWriter.cpp
+++ b/llvm/lib/ObjCopy/COFF/COFFWriter.cpp
@@ -302,6 +302,16 @@ Error COFFWriter::finalize(bool IsBigObj) {
// If the PE header had a checksum, clear it, since it isn't valid
// any longer. (We don't calculate a new one.)
Obj.PeHeader.CheckSum = 0;
+
+ // The attribute certificate table (which holds Authenticode signatures)
+ // isn't part of any section and is located by a file offset rather than
+ // an RVA, so it isn't copied. Clear its entry, which would otherwise point
+ // at unrelated data. (Any signature would be invalidated by modifying
+ // the image anyway.)
+ if (Obj.DataDirectories.size() > CERTIFICATE_TABLE) {
+ Obj.DataDirectories[CERTIFICATE_TABLE].RelativeVirtualAddress = 0;
+ Obj.DataDirectories[CERTIFICATE_TABLE].Size = 0;
+ }
}
Expected<size_t> StrTabSizeOrErr = finalizeStringTable();
diff --git a/llvm/lib/ObjCopy/ConfigManager.cpp b/llvm/lib/ObjCopy/ConfigManager.cpp
index 403b9d1bb922e..1ae5f5fd363c3 100644
--- a/llvm/lib/ObjCopy/ConfigManager.cpp
+++ b/llvm/lib/ObjCopy/ConfigManager.cpp
@@ -14,7 +14,8 @@ using namespace llvm;
using namespace llvm::objcopy;
Expected<const ELFConfig &> ConfigManager::getELFConfig() const {
- if (!Common.ExtractSection.empty())
+ if (!Common.ExtractSection.empty() || !COFF.DumpResource.empty() ||
+ !COFF.UpdateResource.empty())
return createStringError(llvm::errc::invalid_argument,
"option is not supported for ELF");
return ELF;
@@ -57,7 +58,8 @@ Expected<const MachOConfig &> ConfigManager::getMachOConfig() const {
Common.StripUnneeded || Common.DiscardMode == DiscardType::Locals ||
!Common.SymbolsToAdd.empty() || Common.GapFill != 0 ||
Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0 ||
- !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty())
+ !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty() ||
+ !COFF.DumpResource.empty() || !COFF.UpdateResource.empty())
return createStringError(llvm::errc::invalid_argument,
"option is not supported for MachO");
@@ -81,7 +83,8 @@ Expected<const WasmConfig &> ConfigManager::getWasmConfig() const {
!Common.compressSections.empty() || Common.DecompressDebugSections ||
Common.GapFill != 0 || Common.PadTo != 0 ||
Common.ChangeSectionLMAValAll != 0 ||
- !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty())
+ !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty() ||
+ !COFF.DumpResource.empty() || !COFF.UpdateResource.empty())
return createStringError(llvm::errc::invalid_argument,
"only flags for section dumping, removal, and "
"addition are supported");
@@ -113,7 +116,8 @@ Expected<const XCOFFConfig &> ConfigManager::getXCOFFConfig() const {
!Common.compressSections.empty() || Common.DecompressDebugSections ||
Common.GapFill != 0 || Common.PadTo != 0 ||
Common.ChangeSectionLMAValAll != 0 ||
- !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty()) {
+ !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty() ||
+ !COFF.DumpResource.empty() || !COFF.UpdateResource.empty()) {
return createStringError(
llvm::errc::invalid_argument,
"no flags are supported yet, only basic copying is allowed");
@@ -139,7 +143,8 @@ ConfigManager::getDXContainerConfig() const {
!Common.compressSections.empty() || Common.DecompressDebugSections ||
Common.GapFill != 0 || Common.PadTo != 0 ||
Common.ChangeSectionLMAValAll != 0 ||
- !Common.ChangeSectionAddress.empty()) {
+ !Common.ChangeSectionAddress.empty() || !COFF.DumpResource.empty() ||
+ !COFF.UpdateResource.empty()) {
return createStringError(llvm::errc::invalid_argument,
"option is not supported for DXContainer");
}
diff --git a/llvm/test/tools/llvm-objcopy/COFF/certificate-table.test b/llvm/test/tools/llvm-objcopy/COFF/certificate-table.test
new file mode 100644
index 0000000000000..11898d74597d3
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/COFF/certificate-table.test
@@ -0,0 +1,46 @@
+## The attribute certificate table is located by a file offset and is not part
+## of any section, so it is not copied. Its data directory entry is cleared
+## instead of being left dangling.
+
+# RUN: yaml2obj %s -o %t.exe
+# RUN: llvm-readobj --file-headers %t.exe | FileCheck %s --check-prefix=BEFORE
+# RUN: llvm-objcopy %t.exe %t2.exe
+# RUN: llvm-readobj --file-headers %t2.exe | FileCheck %s --check-prefix=AFTER
+
+# BEFORE: CertificateTableRVA: 0x400
+# BEFORE-NEXT: CertificateTableSize: 0x100
+# AFTER: CertificateTableRVA: 0x0
+# AFTER-NEXT: CertificateTableSize: 0x0
+
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ CertificateTable:
+ RelativeVirtualAddress: 1024
+ Size: 256
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+symbols: []
+...
diff --git a/llvm/test/tools/llvm-objcopy/COFF/dump-resource.test b/llvm/test/tools/llvm-objcopy/COFF/dump-resource.test
new file mode 100644
index 0000000000000..6db6432bf290e
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/COFF/dump-resource.test
@@ -0,0 +1,192 @@
+## Test --dump-resource, which dumps a resource of a PE image into a file.
+
+# RUN: split-file %s %t
+# RUN: yaml2obj %t/resources.yaml -o %t/resources.exe
+# RUN: yaml2obj %t/offset.yaml -o %t/offset.exe
+# RUN: yaml2obj %t/noresources.yaml -o %t/noresources.exe
+
+# RUN: llvm-objcopy --dump-resource=24/1=%t/dumped.txt %t/resources.exe %t/copy.exe
+# RUN: FileCheck %s --check-prefix=DATA --input-file=%t/dumped.txt --match-full-lines
+# RUN: llvm-objcopy --dump-resource=24/1/0=%t/dumped2.txt %t/resources.exe %t/copy.exe
+# RUN: cmp %t/dumped.txt %t/dumped2.txt
+
+## Resource directory offsets are relative to the resource table data directory,
+## which does not have to be at the start of its section.
+# RUN: llvm-objcopy --dump-resource=24/1=%t/offset.txt %t/offset.exe %t/copy.exe
+# RUN: cmp %t/dumped.txt %t/offset.txt
+
+## Updating such a resource preserves the bytes preceding the directory.
+# RUN: llvm-objcopy --update-resource=24/1=%t/updated.txt %t/offset.exe %t/updated.exe
+# RUN: llvm-objcopy --dump-resource=24/1=%t/updated.dump %t/updated.exe %t/copy.exe
+# RUN: cmp %t/updated.txt %t/updated.dump
+# RUN: llvm-readobj --file-headers --sections --hex-dump=.rsrc %t/updated.exe | \
+# RUN: FileCheck %s --check-prefix=OFFSET
+
+# OFFSET: ResourceTableRVA: 0x2004
+# OFFSET-NEXT: ResourceTableSize: 0x67
+# OFFSET: Name: .rsrc
+# OFFSET-NEXT: VirtualSize: 0x6B
+# OFFSET: Hex dump of section '.rsrc':
+# OFFSET-NEXT: 0x40002000 deadbeef
+# OFFSET: 0x40002050 0f000000 b0040000 78563412
+
+# DATA: ABCD
+
+## Dumping a resource that does not exist fails.
+# RUN: not llvm-objcopy --dump-resource=24/2=%t/out.txt %t/resources.exe %t/copy.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NO-NAME
+# RUN: not llvm-objcopy --dump-resource=25/1=%t/out.txt %t/resources.exe %t/copy.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NO-TYPE
+# RUN: not llvm-objcopy --dump-resource=24/1/1033=%t/out.txt %t/resources.exe %t/copy.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NO-LANGUAGE
+# RUN: not llvm-objcopy --dump-resource=24/1=%t/out.txt %t/noresources.exe %t/copy.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NO-RESOURCES
+
+# NO-NAME: error: '{{.*}}.exe': resource with type 24 and name 2 not found
+# NO-TYPE: error: '{{.*}}.exe': resource with type 25 and name 1 not found
+# NO-LANGUAGE: error: '{{.*}}.exe': resource with type 24, name 1 and language 1033 not found
+# NO-RESOURCES: error: '{{.*}}noresources.exe': resource with type 24 and name 1 not found
+
+## Resources can only be dumped from PE images.
+# RUN: yaml2obj %t/object.yaml -o %t/object.o
+# RUN: not llvm-objcopy --dump-resource=24/1=%t/out.txt %t/object.o %t/object2.o 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NOT-PE
+
+# NOT-PE: error: '{{.*}}object.o': resources can only be dumped from or updated in PE images
+
+## Invalid option values.
+# RUN: not llvm-objcopy --dump-resource=24/1 %t/resources.exe %t/copy.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NO-FILE
+# RUN: not llvm-objcopy --dump-resource=24=%t/out.txt %t/resources.exe %t/copy.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=BAD-FORMAT
+# RUN: not llvm-objcopy --dump-resource=24/1/x=%t/out.txt %t/resources.exe %t/copy.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=BAD-LANGUAGE
+
+# NO-FILE: error: bad format for --dump-resource: missing file name
+# BAD-FORMAT: error: bad format for --dump-resource: expected <type>/<name>[/<language>]=<file>
+# BAD-LANGUAGE: error: bad format for --dump-resource: 'x' is not a valid resource language ID
+
+## An image with a resource section holding a single resource of type 24,
+## name 1 and language 0 with the data "ABCD" (at RVA 0x2058).
+#--- resources.yaml
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ ResourceTable:
+ RelativeVirtualAddress: 8192
+ Size: 92
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+ - Name: .rsrc
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 92
+ SectionData: 000000000000000000000000000001001800000018000080000000000000000000000000000001000100000030000080000000000000000000000000000001000000000048000000582000000400000000000000000000004142434400000000
+symbols: []
+...
+#--- offset.yaml
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ ResourceTable:
+ RelativeVirtualAddress: 8196
+ Size: 92
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+ - Name: .rsrc
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 96
+ ## Four unrelated bytes precede the resource directory. The resource data
+ ## is at RVA 0x205c.
+ SectionData: DEADBEEF0000000000000000000000000000010018000000180000800000000000000000000000000000010001000000300000800000000000000000000000000000010000000000480000005C20000004000000B0040000785634124142434400000000
+symbols: []
+...
+#--- updated.txt
+0123456789ABCD
+#--- noresources.yaml
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+symbols: []
+...
+#--- object.yaml
+--- !COFF
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ Alignment: 4
+ SectionData: C3C3C3C3
+symbols: []
+...
diff --git a/llvm/test/tools/llvm-objcopy/COFF/update-resource.test b/llvm/test/tools/llvm-objcopy/COFF/update-resource.test
new file mode 100644
index 0000000000000..2d61d5457d3bb
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/COFF/update-resource.test
@@ -0,0 +1,525 @@
+## Test --update-resource, which adds resources to PE images or replaces them.
+
+# RUN: split-file %s %t
+# RUN: yaml2obj %t/noresources.yaml -o %t/noresources.exe
+# RUN: %python -c "open(r'%t/large.bin', 'wb').write(b'A' * 5000)"
+
+## Add a resource to an image without resources. The resource section is added
+## before the discardable .reloc section, which is moved along with the base
+## relocation table entry and the base relocation block that refers to it.
+# RUN: llvm-objcopy --update-resource=24/1=%t/small.txt %t/noresources.exe %t/added.exe
+# RUN: llvm-readobj --file-headers --sections --coff-basereloc --coff-resources %t/added.exe | \
+# RUN: FileCheck %s --check-prefix=ADDED
+
+# ADDED: SizeOfImage: 16384
+# ADDED: ResourceTableRVA: 0x2000
+# ADDED-NEXT: ResourceTableSize: 0x5D
+# ADDED: BaseRelocationTableRVA: 0x3000
+# ADDED-NEXT: BaseRelocationTableSize: 0x18
+# ADDED: Name: .text
+# ADDED-NEXT: VirtualSize: 0x4
+# ADDED-NEXT: VirtualAddress: 0x1000
+# ADDED: Name: .rsrc
+# ADDED-NEXT: VirtualSize: 0x5D
+# ADDED-NEXT: VirtualAddress: 0x2000
+# ADDED-NEXT: RawDataSize: 0x200
+# ADDED-NEXT: PointerToRawData: 0x400
+# ADDED: Characteristics [
+# ADDED-NEXT: IMAGE_SCN_CNT_INITIALIZED_DATA
+# ADDED-NEXT: IMAGE_SCN_MEM_READ
+# ADDED-NEXT: ]
+# ADDED: Name: .reloc
+# ADDED-NEXT: VirtualSize: 0x18
+# ADDED-NEXT: VirtualAddress: 0x3000
+# ADDED-NEXT: RawDataSize: 0x200
+# ADDED-NEXT: PointerToRawData: 0x600
+# ADDED: BaseReloc [
+# ADDED-NEXT: Entry {
+# ADDED-NEXT: Type: DIR64
+# ADDED-NEXT: Address: 0x1000
+# ADDED-NEXT: }
+# ADDED-NEXT: Entry {
+# ADDED-NEXT: Type: ABSOLUTE
+# ADDED-NEXT: Address: 0x1000
+# ADDED-NEXT: }
+# ADDED-NEXT: Entry {
+# ADDED-NEXT: Type: DIR64
+# ADDED-NEXT: Address: 0x3000
+# ADDED-NEXT: }
+# ADDED-NEXT: Entry {
+# ADDED-NEXT: Type: ABSOLUTE
+# ADDED-NEXT: Address: 0x3000
+# ADDED-NEXT: }
+# ADDED-NEXT: ]
+# ADDED: Resources [
+# ADDED-NEXT: Total Number of Resources: 1
+# ADDED-NEXT: Base Table Address: 0x400
+# ADDED: Number of String Entries: 0
+# ADDED-NEXT: Number of ID Entries: 1
+# ADDED-NEXT: Type: MANIFEST (ID 24) [
+# ADDED-NEXT: Table Offset: 0x18
+# ADDED-NEXT: Number of String Entries: 0
+# ADDED-NEXT: Number of ID Entries: 1
+# ADDED-NEXT: Name: (ID 1) [
+# ADDED-NEXT: Table Offset: 0x30
+# ADDED-NEXT: Number of String Entries: 0
+# ADDED-NEXT: Number of ID Entries: 1
+# ADDED-NEXT: Language: (ID 0) [
+# ADDED-NEXT: Entry Offset: 0x48
+# ADDED-NEXT: Time/Date Stamp: 1970-01-01 00:00:00 (0x0)
+# ADDED-NEXT: Major Version: 0
+# ADDED-NEXT: Minor Version: 0
+# ADDED-NEXT: Characteristics: 0
+# ADDED-NEXT: Data [
+# ADDED-NEXT: DataRVA: 0x2058
+# ADDED-NEXT: DataSize: 5
+# ADDED-NEXT: Codepage: 0
+# ADDED-NEXT: Reserved: 0
+# ADDED-NEXT: Data (
+# ADDED-NEXT: 0000: 41424344 0A |ABCD.|
+# ADDED-NEXT: )
+# ADDED-NEXT: ]
+# ADDED-NEXT: ]
+# ADDED-NEXT: ]
+# ADDED-NEXT: ]
+# ADDED-NEXT: ]
+
+## Replace the resource with a larger one that still fits into the address
+## range of the resource section, which keeps the other sections in place, and
+## add a resource of another type.
+# RUN: llvm-objcopy --update-resource=24/1=%t/medium.txt --update-resource=10/100=%t/small.txt \
+# RUN: %t/added.exe %t/replaced.exe
+# RUN: llvm-readobj --file-headers --sections --coff-resources %t/replaced.exe | \
+# RUN: FileCheck %s --check-prefix=REPLACED
+# RUN: llvm-objcopy --dump-resource=24/1=%t/dumped.txt --dump-resource=10/100=%t/dumped2.txt \
+# RUN: %t/replaced.exe %t/copy.exe
+# RUN: cmp %t/dumped.txt %t/medium.txt
+# RUN: cmp %t/dumped2.txt %t/small.txt
+
+# REPLACED: ResourceTableRVA: 0x2000
+# REPLACED-NEXT: ResourceTableSize: 0xB5
+# REPLACED: BaseRelocationTableRVA: 0x3000
+# REPLACED: Name: .rsrc
+# REPLACED-NEXT: VirtualSize: 0xB5
+# REPLACED-NEXT: VirtualAddress: 0x2000
+# REPLACED-NEXT: RawDataSize: 0x200
+# REPLACED: Name: .reloc
+# REPLACED-NEXT: VirtualSize: 0x18
+# REPLACED-NEXT: VirtualAddress: 0x3000
+# REPLACED: Resources [
+# REPLACED-NEXT: Total Number of Resources: 2
+# REPLACED: Type: RCDATA (ID 10) [
+# REPLACED: Name: (ID 100) [
+# REPLACED: Language: (ID 0) [
+# REPLACED: DataRVA: 0x20B0
+# REPLACED-NEXT: DataSize: 5
+# REPLACED: Type: MANIFEST (ID 24) [
+# REPLACED: Name: (ID 1) [
+# REPLACED: Language: (ID 0) [
+# REPLACED: DataRVA: 0x20A0
+# REPLACED-NEXT: DataSize: 15
+
+## Grow the resource beyond the address range of the resource section, which
+## moves the following sections again.
+# RUN: llvm-objcopy --update-resource=24/1=%t/large.bin %t/replaced.exe %t/grown.exe
+# RUN: llvm-readobj --file-headers --sections --coff-basereloc --coff-resources %t/grown.exe | \
+# RUN: FileCheck %s --check-prefix=GROWN
+# RUN: llvm-objcopy --dump-resource=24/1=%t/dumped.bin %t/grown.exe %t/copy.exe
+# RUN: cmp %t/dumped.bin %t/large.bin
+
+# GROWN: SizeOfImage: 20480
+# GROWN: ResourceTableRVA: 0x2000
+# GROWN-NEXT: ResourceTableSize: 0x1430
+# GROWN: BaseRelocationTableRVA: 0x4000
+# GROWN-NEXT: BaseRelocationTableSize: 0x18
+# GROWN: Name: .rsrc
+# GROWN-NEXT: VirtualSize: 0x1430
+# GROWN-NEXT: VirtualAddress: 0x2000
+# GROWN-NEXT: RawDataSize: 0x1600
+# GROWN-NEXT: PointerToRawData: 0x400
+# GROWN: Name: .reloc
+# GROWN-NEXT: VirtualSize: 0x18
+# GROWN-NEXT: VirtualAddress: 0x4000
+# GROWN-NEXT: RawDataSize: 0x200
+# GROWN-NEXT: PointerToRawData: 0x1A00
+# GROWN: BaseReloc [
+# GROWN-NEXT: Entry {
+# GROWN-NEXT: Type: DIR64
+# GROWN-NEXT: Address: 0x1000
+# GROWN-NEXT: }
+# GROWN-NEXT: Entry {
+# GROWN-NEXT: Type: ABSOLUTE
+# GROWN-NEXT: Address: 0x1000
+# GROWN-NEXT: }
+# GROWN-NEXT: Entry {
+# GROWN-NEXT: Type: DIR64
+# GROWN-NEXT: Address: 0x4000
+# GROWN-NEXT: }
+# GROWN-NEXT: Entry {
+# GROWN-NEXT: Type: ABSOLUTE
+# GROWN-NEXT: Address: 0x4000
+# GROWN-NEXT: }
+# GROWN-NEXT: ]
+# GROWN: Type: MANIFEST (ID 24) [
+# GROWN: Name: (ID 1) [
+# GROWN: Language: (ID 0) [
+# GROWN: DataRVA: 0x20A8
+# GROWN-NEXT: DataSize: 5000
+
+## With a language, only the resource of that language is added or replaced.
+# RUN: llvm-objcopy --update-resource=24/1/1033=%t/small.txt --update-resource=24/1/1034=%t/medium.txt \
+# RUN: %t/noresources.exe %t/languages.exe
+# RUN: llvm-objcopy --update-resource=24/1/1034=%t/large.bin %t/languages.exe %t/languages2.exe
+# RUN: llvm-readobj --coff-resources %t/languages.exe | FileCheck %s --check-prefix=LANGUAGES
+# RUN: llvm-readobj --coff-resources %t/languages2.exe | FileCheck %s --check-prefix=LANGUAGES2
+
+# LANGUAGES: Language: (ID 1033) [
+# LANGUAGES: DataSize: 5
+# LANGUAGES: Language: (ID 1034) [
+# LANGUAGES: DataSize: 15
+# LANGUAGES2: Language: (ID 1033) [
+# LANGUAGES2: DataSize: 5
+# LANGUAGES2: Language: (ID 1034) [
+# LANGUAGES2: DataSize: 5000
+
+## Without a language, the resources of all languages are replaced by a single
+## language-neutral one, unless there is only one, whose language is kept.
+# RUN: llvm-objcopy --update-resource=24/1=%t/large.bin %t/languages.exe %t/neutral.exe
+# RUN: llvm-readobj --coff-resources %t/neutral.exe | FileCheck %s --check-prefix=NEUTRAL
+# RUN: llvm-objcopy --update-resource=24/1/1033=%t/small.txt %t/noresources.exe %t/single.exe
+# RUN: llvm-objcopy --update-resource=24/1=%t/medium.txt %t/single.exe %t/kept.exe
+# RUN: llvm-readobj --coff-resources %t/kept.exe | FileCheck %s --check-prefix=KEPT
+
+# NEUTRAL: Name: (ID 1) [
+# NEUTRAL: Number of ID Entries: 1
+# NEUTRAL-NEXT: Language: (ID 0) [
+# NEUTRAL: DataSize: 5000
+# KEPT: Name: (ID 1) [
+# KEPT: Number of ID Entries: 1
+# KEPT-NEXT: Language: (ID 1033) [
+# KEPT: DataSize: 15
+
+## The resource section cannot grow beyond its address range if it is followed
+## by a section that is not discardable.
+# RUN: yaml2obj %t/followed.yaml -o %t/followed.exe
+# RUN: llvm-objcopy --update-resource=24/1=%t/medium.txt %t/followed.exe %t/followed2.exe
+# RUN: llvm-readobj --sections --coff-resources %t/followed2.exe | FileCheck %s --check-prefix=FOLLOWED
+# RUN: not llvm-objcopy --update-resource=24/1=%t/large.bin %t/followed.exe %t/followed3.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=CANNOT-MOVE
+
+# FOLLOWED: Name: .rsrc
+# FOLLOWED-NEXT: VirtualSize: 0x67
+# FOLLOWED-NEXT: VirtualAddress: 0x2000
+# FOLLOWED: Name: .data
+# FOLLOWED-NEXT: VirtualSize: 0x4
+# FOLLOWED-NEXT: VirtualAddress: 0x3000
+# FOLLOWED: Language: (ID 0) [
+# FOLLOWED: DataRVA: 0x2058
+# FOLLOWED-NEXT: DataSize: 15
+# CANNOT-MOVE: error: '{{.*}}followed.exe': section '.rsrc' cannot grow past section '.data', which is not discardable and thus cannot be moved
+
+## Data following the resources in their section cannot be preserved since it
+## would have to move when the resources grow, so only zero padding is allowed.
+# RUN: yaml2obj %t/padded.yaml -o %t/padded.exe
+# RUN: llvm-objcopy --update-resource=24/1=%t/medium.txt %t/padded.exe %t/padded2.exe
+# RUN: llvm-readobj --sections --coff-resources %t/padded2.exe | FileCheck %s --check-prefix=PADDED
+# RUN: yaml2obj %t/trailing.yaml -o %t/trailing.exe
+# RUN: not llvm-objcopy --update-resource=24/1=%t/medium.txt %t/trailing.exe %t/trailing2.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=TRAILING
+
+# PADDED: Name: .rsrc
+# PADDED-NEXT: VirtualSize: 0x67
+# PADDED: Language: (ID 0) [
+# PADDED: DataRVA: 0x2058
+# PADDED-NEXT: DataSize: 15
+# TRAILING: error: '{{.*}}trailing.exe': section '.rsrc' contains data after its resources that cannot be preserved
+
+## The entries of the debug directory are updated if their payloads are moved.
+# RUN: yaml2obj %t/debug.yaml -o %t/debug.exe
+# RUN: llvm-objcopy --update-resource=24/1=%t/large.bin %t/debug.exe %t/debug2.exe
+# RUN: llvm-readobj --file-headers --sections --coff-debug-directory %t/debug2.exe | \
+# RUN: FileCheck %s --check-prefix=DEBUG
+
+# DEBUG: DebugRVA: 0x4000
+# DEBUG-NEXT: DebugSize: 0x1C
+# DEBUG: Name: .debug
+# DEBUG-NEXT: VirtualSize: 0x20
+# DEBUG-NEXT: VirtualAddress: 0x4000
+# DEBUG-NEXT: RawDataSize: 0x200
+# DEBUG-NEXT: PointerToRawData: 0x1800
+# DEBUG: DebugEntry {
+# DEBUG: AddressOfRawData: 0x401C
+# DEBUG-NEXT: PointerToRawData: 0x181C
+# DEBUG-NEXT: RawData (
+# DEBUG-NEXT: 0000: DEADBEEF
+
+## Resources can only be updated in PE images.
+# RUN: yaml2obj %t/object.yaml -o %t/object.o
+# RUN: not llvm-objcopy --update-resource=24/1=%t/small.txt %t/object.o %t/object2.o 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NOT-PE
+# RUN: yaml2obj %t/elf.yaml -o %t/elf.o
+# RUN: not llvm-objcopy --update-resource=24/1=%t/small.txt %t/elf.o %t/elf2.o 2>&1 | \
+# RUN: FileCheck %s --check-prefix=ELF
+
+# NOT-PE: error: '{{.*}}object.o': resources can only be dumped from or updated in PE images
+# ELF: error: option is not supported for ELF
+
+## Invalid option values.
+# RUN: not llvm-objcopy --update-resource=24/1 %t/noresources.exe %t/out.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NO-FILE
+# RUN: not llvm-objcopy --update-resource=24=%t/small.txt %t/noresources.exe %t/out.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=BAD-FORMAT
+# RUN: not llvm-objcopy --update-resource=24/1/0/0=%t/small.txt %t/noresources.exe %t/out.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=BAD-FORMAT
+# RUN: not llvm-objcopy --update-resource=x/1=%t/small.txt %t/noresources.exe %t/out.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=BAD-TYPE
+# RUN: not llvm-objcopy --update-resource=24/y=%t/small.txt %t/noresources.exe %t/out.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=BAD-NAME
+# RUN: not llvm-objcopy --update-resource=24/1/65536=%t/small.txt %t/noresources.exe %t/out.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=BAD-LANGUAGE
+# RUN: not llvm-objcopy --update-resource=24/1=%t/missing.txt %t/noresources.exe %t/out.exe 2>&1 | \
+# RUN: FileCheck %s --check-prefix=MISSING -DENOENT=%errc_ENOENT
+
+# NO-FILE: error: bad format for --update-resource: missing file name
+# BAD-FORMAT: error: bad format for --update-resource: expected <type>/<name>[/<language>]=<file>
+# BAD-TYPE: error: bad format for --update-resource: 'x' is not a valid resource type ID
+# BAD-NAME: error: bad format for --update-resource: 'y' is not a valid resource name ID
+# BAD-LANGUAGE: error: bad format for --update-resource: '65536' is not a valid resource language ID
+# MISSING: error: '{{.*}}missing.txt': [[ENOENT]]
+
+#--- small.txt
+ABCD
+#--- medium.txt
+0123456789ABCD
+#--- noresources.yaml
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ BaseRelocationTable:
+ RelativeVirtualAddress: 8192
+ Size: 24
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+ ## Two base relocation blocks with one DIR64 entry each: one for the .text
+ ## section and one for the .reloc section itself.
+ - Name: .reloc
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_DISCARDABLE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 24
+ SectionData: 001000000C00000000A00000002000000C00000000A00000
+symbols: []
+...
+## An image with a resource section holding a single resource of type 24,
+## name 1 and language 0 with the data "ABCD" (at RVA 0x2058), followed by a
+## section that is not discardable.
+#--- followed.yaml
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ ResourceTable:
+ RelativeVirtualAddress: 8192
+ Size: 92
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+ - Name: .rsrc
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 92
+ SectionData: 000000000000000000000000000001001800000018000080000000000000000000000000000001000100000030000080000000000000000000000000000001000000000048000000582000000400000000000000000000004142434400000000
+ - Name: .data
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE ]
+ VirtualAddress: 12288
+ VirtualSize: 4
+ SectionData: 01020304
+symbols: []
+...
+## The same image, but followed by a discardable section that holds the debug
+## directory with a single entry whose payload is in the same section.
+#--- debug.yaml
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ ResourceTable:
+ RelativeVirtualAddress: 8192
+ Size: 92
+ Debug:
+ RelativeVirtualAddress: 12288
+ Size: 28
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+ - Name: .rsrc
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 92
+ SectionData: 000000000000000000000000000001001800000018000080000000000000000000000000000001000100000030000080000000000000000000000000000001000000000048000000582000000400000000000000000000004142434400000000
+ - Name: .debug
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_DISCARDABLE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 12288
+ VirtualSize: 32
+ SectionData: 00000000000000000000000000000000040000001C30000000100000DEADBEEF
+symbols: []
+...
+## The same resources as in followed.yaml, followed by zero padding.
+#--- padded.yaml
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ ResourceTable:
+ RelativeVirtualAddress: 8192
+ Size: 92
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+ - Name: .rsrc
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 100
+ SectionData: 0000000000000000000000000000010018000000180000800000000000000000000000000000010001000000300000800000000000000000000000000000010000000000480000005820000004000000000000000000000041424344000000000000000000000000
+symbols: []
+...
+## The same resources as in followed.yaml, followed by other data.
+#--- trailing.yaml
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ ResourceTable:
+ RelativeVirtualAddress: 8192
+ Size: 92
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+ - Name: .rsrc
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 100
+ SectionData: 000000000000000000000000000001001800000018000080000000000000000000000000000001000100000030000080000000000000000000000000000001000000000048000000582000000400000000000000000000004142434400000000DEADBEEFDEADBEEF
+symbols: []
+...
+#--- object.yaml
+--- !COFF
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ Alignment: 4
+ SectionData: C3C3C3C3
+symbols: []
+...
+#--- elf.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_REL
+ Machine: EM_X86_64
+...
diff --git a/llvm/test/tools/llvm-objcopy/DXContainer/copy-basic.yaml b/llvm/test/tools/llvm-objcopy/DXContainer/copy-basic.yaml
index b63bc09c370df..b5182e5e14c03 100644
--- a/llvm/test/tools/llvm-objcopy/DXContainer/copy-basic.yaml
+++ b/llvm/test/tools/llvm-objcopy/DXContainer/copy-basic.yaml
@@ -3,6 +3,10 @@
# RUN: yaml2obj %s -o %t
# RUN: llvm-objcopy %t %t.out
# RUN: cmp %t %t.out
+# RUN: not llvm-objcopy --update-resource=24/1=%s %t %t.resource 2>&1 | \
+# RUN: FileCheck %s --check-prefix=RESOURCE
+
+# RESOURCE: error: option is not supported for DXContainer
## The DXContainer described below was generated with:
diff --git a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
index 4c0f9d1d09daf..6914e8c7af05a 100644
--- a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
+++ b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
@@ -574,6 +574,37 @@ static Error loadNewSectionData(StringRef ArgValue, StringRef OptionName,
return Error::success();
}
+// Parses the "<type>/<name>[/<language>]" part of the value of a resource
+// option.
+static Error parseResourceIdentifier(StringRef Value, StringRef OptionName,
+ COFFResourceIdentifier &Resource) {
+ SmallVector<StringRef, 3> Parts;
+ Value.split(Parts, '/');
+ if (Parts.size() < 2 || Parts.size() > 3)
+ return createStringError(
+ errc::invalid_argument,
+ "bad format for " + OptionName +
+ ": expected <type>/<name>[/<language>]=<file>");
+ if (Parts[0].getAsInteger(0, Resource.Type))
+ return createStringError(errc::invalid_argument,
+ "bad format for " + OptionName + ": '" + Parts[0] +
+ "' is not a valid resource type ID");
+ if (Parts[1].getAsInteger(0, Resource.Name))
+ return createStringError(errc::invalid_argument,
+ "bad format for " + OptionName + ": '" + Parts[1] +
+ "' is not a valid resource name ID");
+ if (Parts.size() == 3) {
+ uint16_t Language;
+ if (Parts[2].getAsInteger(0, Language))
+ return createStringError(errc::invalid_argument,
+ "bad format for " + OptionName + ": '" +
+ Parts[2] +
+ "' is not a valid resource language ID");
+ Resource.Language = Language;
+ }
+ return Error::success();
+}
+
static Expected<int64_t> parseChangeSectionLMA(StringRef ArgValue,
StringRef OptionName) {
StringRef StringValue;
@@ -821,6 +852,38 @@ objcopy::parseObjcopyOptions(ArrayRef<const char *> ArgsArr,
}
}
+ for (auto *Arg : InputArgs.filtered(OBJCOPY_dump_resource)) {
+ auto [Spec, FileName] = StringRef(Arg->getValue()).split('=');
+ if (FileName.empty())
+ return createStringError(
+ errc::invalid_argument,
+ "bad format for --dump-resource: missing file name");
+ COFFResourceDump Dump;
+ if (Error E =
+ parseResourceIdentifier(Spec, "--dump-resource", Dump.Resource))
+ return std::move(E);
+ Dump.FileName = FileName;
+ COFFConfig.DumpResource.push_back(Dump);
+ }
+
+ for (auto *Arg : InputArgs.filtered(OBJCOPY_update_resource)) {
+ auto [Spec, FileName] = StringRef(Arg->getValue()).split('=');
+ if (FileName.empty())
+ return createStringError(
+ errc::invalid_argument,
+ "bad format for --update-resource: missing file name");
+ COFFResourceUpdate Update;
+ if (Error E =
+ parseResourceIdentifier(Spec, "--update-resource", Update.Resource))
+ return std::move(E);
+ ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
+ MemoryBuffer::getFile(FileName);
+ if (!BufOrErr)
+ return createFileError(FileName, errorCodeToError(BufOrErr.getError()));
+ Update.Data = std::move(*BufOrErr);
+ COFFConfig.UpdateResource.push_back(std::move(Update));
+ }
+
Config.OutputFormat = StringSwitch<FileFormat>(OutputFormat)
.Case("binary", FileFormat::Binary)
.Case("ihex", FileFormat::IHex)
diff --git a/llvm/tools/llvm-objcopy/ObjcopyOpts.td b/llvm/tools/llvm-objcopy/ObjcopyOpts.td
index fbc6a59d9461e..ea95b20b49d74 100644
--- a/llvm/tools/llvm-objcopy/ObjcopyOpts.td
+++ b/llvm/tools/llvm-objcopy/ObjcopyOpts.td
@@ -134,6 +134,18 @@ defm subsystem
"Set the PE subsystem, and optionally subsystem version">,
MetaVarName<"name[:version]">, Group<grp_coff>;
+defm dump_resource
+ : Eq<"dump-resource",
+ "Dump the contents of the resource with the given type, name and "
+ "(optionally) language IDs into <file>">,
+ MetaVarName<"type/name[/language]=file">, Group<grp_coff>;
+
+defm update_resource
+ : Eq<"update-resource",
+ "Add the resource with the given type, name and (optionally) language "
+ "IDs with the contents of <file>, or replace it">,
+ MetaVarName<"type/name[/language]=file">, Group<grp_coff>;
+
def extract_dwo
: Flag<["--"], "extract-dwo">,
HelpText<
>From 85bb2e2f81ef3a2ad62a1016d56320b33c41a851 Mon Sep 17 00:00:00 2001
From: Fabian Meumertzheim <fabian at meumertzhe.im>
Date: Tue, 22 Sep 2026 14:26:33 +0200
Subject: [PATCH 3/3] [llvm-mt] Implement /inputresource, /outputresource and
/updateresource
mt.exe reads manifests from and writes them to the RT_MANIFEST resource
of PE images with these options, which llvm-mt so far ignored as
unsupported. Implement them on top of the resource support of the
ObjCopy library:
/inputresource:<file>[;[#]<id>] merges the manifest embedded in the
image as the resource with the given ID (default: 1) like a /manifest
input.
/outputresource:<file>[;[#]<id>] writes the merged manifest to that
resource, replacing an existing one.
/updateresource:<file>[;[#]<id>] is equivalent to both.
/notify_update also takes manifests written to resources into account.
Assisted-by: Claude Code
Assisted-by: OpenAI Codex
Claude-Session: https://claude.ai/code/session_01M9VKWRFkU4dMqPLUkAY9Lu
---
llvm/docs/ReleaseNotes.md | 4 +
llvm/test/tools/llvm-mt/Inputs/pe.yaml | 38 ++++
llvm/test/tools/llvm-mt/inputresource.test | 95 ++++++++++
llvm/test/tools/llvm-mt/outputresource.test | 47 +++++
llvm/test/tools/llvm-mt/updateresource.test | 25 +++
llvm/test/tools/llvm-mt/xml_error.test | 6 +-
llvm/tools/llvm-mt/CMakeLists.txt | 2 +
llvm/tools/llvm-mt/Opts.td | 7 +-
llvm/tools/llvm-mt/llvm-mt.cpp | 191 +++++++++++++++++---
9 files changed, 384 insertions(+), 31 deletions(-)
create mode 100644 llvm/test/tools/llvm-mt/Inputs/pe.yaml
create mode 100644 llvm/test/tools/llvm-mt/inputresource.test
create mode 100644 llvm/test/tools/llvm-mt/outputresource.test
create mode 100644 llvm/test/tools/llvm-mt/updateresource.test
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index a4521845f490f..b8d27f16806c5 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -316,6 +316,10 @@ Makes programs 10x faster by doing Special New Thing.
writes PE images with a dangling attribute certificate table (Authenticode
signature) entry, but clears the entry instead.
+* llvm-mt now supports the `/inputresource`, `/outputresource` and
+ `/updateresource` options, which read and write manifests embedded in PE
+ images as resources.
+
### Changes to LLDB
* `platform.plugin.wasm.runtime-args` now precede the port argument on the Wasm
diff --git a/llvm/test/tools/llvm-mt/Inputs/pe.yaml b/llvm/test/tools/llvm-mt/Inputs/pe.yaml
new file mode 100644
index 0000000000000..7cd89a0c5214d
--- /dev/null
+++ b/llvm/test/tools/llvm-mt/Inputs/pe.yaml
@@ -0,0 +1,38 @@
+## A minimal PE image without resources, followed by a discardable section.
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 1073741824
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ BaseRelocationTable:
+ RelativeVirtualAddress: 8192
+ Size: 12
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 4
+ SectionData: C3C3C3C3
+ - Name: .reloc
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_DISCARDABLE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 12
+ SectionData: 001000000C00000000A00000
+symbols: []
+...
diff --git a/llvm/test/tools/llvm-mt/inputresource.test b/llvm/test/tools/llvm-mt/inputresource.test
new file mode 100644
index 0000000000000..410e6e5fc2424
--- /dev/null
+++ b/llvm/test/tools/llvm-mt/inputresource.test
@@ -0,0 +1,95 @@
+REQUIRES: libxml2
+UNSUPPORTED: system-windows
+
+Read the manifest from the RT_MANIFEST resource of a PE image.
+RUN: yaml2obj %p/Inputs/pe.yaml -o %t.exe
+RUN: llvm-objcopy --update-resource=24/1=%p/Inputs/test_manifest.manifest %t.exe
+RUN: llvm-mt /inputresource:%t.exe /out:%t.manifest
+RUN: FileCheck %s --input-file=%t.manifest
+
+The resource ID defaults to 1 and can be given with or without a '#'.
+RUN: llvm-mt "/inputresource:%t.exe;#1" /out:%t.1.manifest
+RUN: diff %t.manifest %t.1.manifest
+RUN: llvm-mt "/inputresource:%t.exe;1" /out:%t.2.manifest
+RUN: diff %t.manifest %t.2.manifest
+
+Other resource IDs work as well.
+RUN: llvm-objcopy --update-resource=24/2=%p/Inputs/additional.manifest %t.exe
+RUN: llvm-mt "/inputresource:%t.exe;#2" /out:%t.3.manifest
+RUN: FileCheck %s --input-file=%t.3.manifest --check-prefix=ADDITIONAL
+
+The manifest read from the resource is merged with the other input manifests.
+RUN: llvm-mt /inputresource:%t.exe /manifest %p/Inputs/additional.manifest /out:%t.4.manifest
+RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /manifest %p/Inputs/additional.manifest /out:%t.5.manifest
+RUN: diff %t.4.manifest %t.5.manifest
+
+CHECK: <?xml version="1.0" encoding="UTF-8"?>
+CHECK-NEXT: <assembly xmlns="urn:schemas-microsoft-com:asm.v1">
+CHECK-NEXT: <trustInfo>
+CHECK-NEXT: <security>
+CHECK-NEXT: <requestedPrivileges>
+CHECK-NEXT: <requestedExecutionLevel level="3" uiAccess="1"/>
+CHECK-NEXT: </requestedPrivileges>
+CHECK-NEXT: </security>
+CHECK-NEXT: </trustInfo>
+CHECK-NEXT: <dependency>
+CHECK-NEXT: <dependentAssembly>
+CHECK-NEXT: <assemblyIdentity program="displayDriver"/>
+CHECK-NEXT: </dependentAssembly>
+CHECK-NEXT: </dependency>
+CHECK-NEXT: <compatibility>
+CHECK-NEXT: <dependency>
+CHECK-NEXT: <assemblyIdentity program="compatibilityCheck1"/>
+CHECK-NEXT: </dependency>
+CHECK-NEXT: <application>
+CHECK-NEXT: <supportedOS Id="FooOS"/>
+CHECK-NEXT: </application>
+CHECK-NEXT: </compatibility>
+CHECK-NEXT: </assembly>
+
+ADDITIONAL: <?xml version="1.0" encoding="UTF-8"?>
+ADDITIONAL-NEXT: <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ADDITIONAL-NEXT: <trustInfo>
+ADDITIONAL-NEXT: <security>
+ADDITIONAL-NEXT: <requestedPrivileges>
+ADDITIONAL-NEXT: <requestedExecutionLevel level="3" admin="false"/>
+ADDITIONAL-NEXT: </requestedPrivileges>
+ADDITIONAL-NEXT: </security>
+ADDITIONAL-NEXT: </trustInfo>
+ADDITIONAL-NEXT: <dependency>
+ADDITIONAL-NEXT: <dependentAssembly>
+ADDITIONAL-NEXT: <assemblyIdentity program="logDisplay"/>
+ADDITIONAL-NEXT: </dependentAssembly>
+ADDITIONAL-NEXT: </dependency>
+ADDITIONAL-NEXT: <compatibility>
+ADDITIONAL-NEXT: <dependency>
+ADDITIONAL-NEXT: <assemblyIdentity program="compatibilityCheck2"/>
+ADDITIONAL-NEXT: </dependency>
+ADDITIONAL-NEXT: <application>
+ADDITIONAL-NEXT: <supportedOS Id="BarOS"/>
+ADDITIONAL-NEXT: </application>
+ADDITIONAL-NEXT: </compatibility>
+ADDITIONAL-NEXT: </assembly>
+
+Errors.
+RUN: not llvm-mt "/inputresource:%t.exe;#3" /out:%t.out 2>&1 | FileCheck %s --check-prefix=NOT-FOUND
+NOT-FOUND: llvm-mt: error: {{.*}}.exe: manifest resource with ID 3 not found
+
+RUN: yaml2obj %p/Inputs/pe.yaml -o %t.noresources.exe
+RUN: not llvm-mt /inputresource:%t.noresources.exe /out:%t.out 2>&1 | FileCheck %s --check-prefix=NO-RESOURCES
+NO-RESOURCES: llvm-mt: error: {{.*}}.noresources.exe: manifest resource with ID 1 not found
+
+RUN: not llvm-mt /inputresource:%p/Inputs/test_manifest.manifest /out:%t.out 2>&1 | FileCheck %s --check-prefix=NOT-OBJECT
+NOT-OBJECT: llvm-mt: error: '{{.*}}test_manifest.manifest': The file was not recognized as a valid object file
+
+RUN: not llvm-mt "/inputresource:%t.exe;#x" /out:%t.out 2>&1 | FileCheck %s --check-prefix=INVALID-ID
+INVALID-ID: llvm-mt: error: invalid resource ID in '{{.*}}.exe;#x'
+
+RUN: not llvm-mt "/inputresource:%t.exe;" /out:%t.out 2>&1 | FileCheck %s --check-prefix=EMPTY-ID
+EMPTY-ID: llvm-mt: error: invalid resource ID in '{{.*}}.exe;'
+
+RUN: not llvm-mt "/inputresource:;#1" /out:%t.out 2>&1 | FileCheck %s --check-prefix=NO-FILE
+NO-FILE: llvm-mt: error: missing file name in ';#1'
+
+RUN: not llvm-mt /inputresource:%t.exe 2>&1 | FileCheck %s --check-prefix=NO-OUTPUT
+NO-OUTPUT: llvm-mt: error: no output file specified
diff --git a/llvm/test/tools/llvm-mt/outputresource.test b/llvm/test/tools/llvm-mt/outputresource.test
new file mode 100644
index 0000000000000..405ed25205cd4
--- /dev/null
+++ b/llvm/test/tools/llvm-mt/outputresource.test
@@ -0,0 +1,47 @@
+REQUIRES: libxml2
+UNSUPPORTED: system-windows
+
+Write the merged manifest to the RT_MANIFEST resource of a PE image.
+RUN: yaml2obj %p/Inputs/pe.yaml -o %t.exe
+RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /manifest %p/Inputs/additional.manifest /outputresource:%t.exe
+RUN: llvm-readobj --coff-resources %t.exe | FileCheck %s --check-prefix=RESOURCES
+RUN: llvm-mt /inputresource:%t.exe /out:%t.manifest
+RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /manifest %p/Inputs/additional.manifest /out:%t.expected.manifest
+RUN: diff %t.expected.manifest %t.manifest
+
+RESOURCES: Type: MANIFEST (ID 24) [
+RESOURCES: Name: (ID 1) [
+RESOURCES: Language: (ID 0) [
+
+The resource ID defaults to 1 and can be given with or without a '#'. Existing
+resources are replaced.
+RUN: llvm-mt /manifest %p/Inputs/additional.manifest "/outputresource:%t.exe;#1"
+RUN: llvm-mt /manifest %p/Inputs/additional.manifest "/outputresource:%t.exe;2"
+RUN: llvm-readobj --coff-resources %t.exe | FileCheck %s --check-prefix=RESOURCES2
+RUN: llvm-mt "/inputresource:%t.exe;#1" /out:%t.1.manifest
+RUN: llvm-mt "/inputresource:%t.exe;#2" /out:%t.2.manifest
+RUN: llvm-mt /manifest %p/Inputs/additional.manifest /out:%t.expected2.manifest
+RUN: diff %t.expected2.manifest %t.1.manifest
+RUN: diff %t.expected2.manifest %t.2.manifest
+
+RESOURCES2: Type: MANIFEST (ID 24) [
+RESOURCES2: Name: (ID 1) [
+RESOURCES2: Language: (ID 0) [
+RESOURCES2: Name: (ID 2) [
+RESOURCES2: Language: (ID 0) [
+
+The manifest can be written to a file and a resource at the same time.
+RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /out:%t.3.manifest /outputresource:%t.exe
+RUN: llvm-mt /inputresource:%t.exe /out:%t.4.manifest
+RUN: diff %t.3.manifest %t.4.manifest
+
+With /notify_update, the exit code indicates whether the resource changed.
+RUN: not llvm-mt /manifest %p/Inputs/additional.manifest /outputresource:%t.exe /notify_update
+RUN: llvm-mt /manifest %p/Inputs/additional.manifest /outputresource:%t.exe /notify_update
+
+Errors.
+RUN: not llvm-mt /manifest %p/Inputs/test_manifest.manifest /outputresource:%p/Inputs/test_manifest.manifest 2>&1 | FileCheck %s --check-prefix=NOT-OBJECT
+NOT-OBJECT: llvm-mt: error: '{{.*}}test_manifest.manifest': The file was not recognized as a valid object file
+
+RUN: not llvm-mt /manifest %p/Inputs/test_manifest.manifest "/outputresource:%t.exe;#" 2>&1 | FileCheck %s --check-prefix=INVALID-ID
+INVALID-ID: llvm-mt: error: invalid resource ID in '{{.*}}.exe;#'
diff --git a/llvm/test/tools/llvm-mt/updateresource.test b/llvm/test/tools/llvm-mt/updateresource.test
new file mode 100644
index 0000000000000..3f4787d14a666
--- /dev/null
+++ b/llvm/test/tools/llvm-mt/updateresource.test
@@ -0,0 +1,25 @@
+REQUIRES: libxml2
+UNSUPPORTED: system-windows
+
+Update the manifest embedded in a PE image by merging other manifests into it.
+RUN: yaml2obj %p/Inputs/pe.yaml -o %t.exe
+RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /outputresource:%t.exe
+RUN: llvm-mt /manifest %p/Inputs/additional.manifest /updateresource:%t.exe
+RUN: llvm-mt /inputresource:%t.exe /out:%t.manifest
+RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /manifest %p/Inputs/additional.manifest /out:%t.expected.manifest
+RUN: diff %t.expected.manifest %t.manifest
+
+With /notify_update, the exit code indicates whether the resource changed.
+Without other manifests, the embedded manifest is only canonicalized, which does
+not change it if it is already canonical.
+RUN: llvm-mt /updateresource:%t.exe /notify_update
+RUN: not llvm-mt /manifest %p/Inputs/windows_settings.manifest /updateresource:%t.exe /notify_update
+RUN: llvm-mt /inputresource:%t.exe /out:%t.2.manifest
+RUN: FileCheck %s --input-file=%t.2.manifest --check-prefix=SETTINGS
+
+SETTINGS: <dpiAware>true/pm</dpiAware>
+
+Errors.
+RUN: yaml2obj %p/Inputs/pe.yaml -o %t.noresources.exe
+RUN: not llvm-mt /updateresource:%t.noresources.exe 2>&1 | FileCheck %s --check-prefix=NOT-FOUND
+NOT-FOUND: llvm-mt: error: {{.*}}.noresources.exe: manifest resource with ID 1 not found
diff --git a/llvm/test/tools/llvm-mt/xml_error.test b/llvm/test/tools/llvm-mt/xml_error.test
index 4d0a8ece43bf5..4e2dfac4ba1c4 100644
--- a/llvm/test/tools/llvm-mt/xml_error.test
+++ b/llvm/test/tools/llvm-mt/xml_error.test
@@ -11,8 +11,8 @@ RUN: FileCheck %s -check-prefix=EMPTY
EMPTY: llvm-mt: error: attempted to merge empty manifest
-RUN: llvm-mt /inputresource:foo.res \
+RUN: not llvm-mt /inputresource:foo.res \
RUN: /manifest %p/Inputs/test_manifest.manifest \
-RUN: /out:%t | FileCheck %s -check-prefix=NOT_SUPPORTED
+RUN: /out:%t 2>&1 | FileCheck %s -check-prefix=NOT_FOUND -DENOENT=%errc_ENOENT
-NOT_SUPPORTED: llvm-mt: ignoring unsupported 'inputresource:' option
+NOT_FOUND: llvm-mt: error: 'foo.res': [[ENOENT]]
diff --git a/llvm/tools/llvm-mt/CMakeLists.txt b/llvm/tools/llvm-mt/CMakeLists.txt
index baa9f986a85b3..f5350c319e972 100644
--- a/llvm/tools/llvm-mt/CMakeLists.txt
+++ b/llvm/tools/llvm-mt/CMakeLists.txt
@@ -4,6 +4,8 @@ if (NOT LLVM_ENABLE_LIBXML2)
endif ()
set(LLVM_LINK_COMPONENTS
+ ObjCopy
+ Object
Option
Support
WindowsManifest
diff --git a/llvm/tools/llvm-mt/Opts.td b/llvm/tools/llvm-mt/Opts.td
index c4f7375c6d8fa..1bc1c2074297f 100644
--- a/llvm/tools/llvm-mt/Opts.td
+++ b/llvm/tools/llvm-mt/Opts.td
@@ -12,10 +12,9 @@ def no_dependency : Flag<["/", "-"], "nodependency">, HelpText<"Not supported">,
def category : Flag<["/", "-"], "category">, HelpText<"Not supported">, Group<unsupported>;
def no_logo : Flag<["/", "-"], "nologo">, HelpText<"No effect as this tool never writes copyright data. Included for parity">;
def out : Joined<["/", "-"], "out:">, HelpText<"Name of the output manifest. If this is skipped and only one manifest is being operated upon by the tool, that manifest is modified in place">, MetaVarName<"manifest">;
-def input_resource : Joined<["/", "-"], "inputresource:">, HelpText<"Not supported">, MetaVarName<"file">, Group<unsupported>;
-def output_resource : Joined<["/", "-"], "outputresource:">, HelpText<"Not supported">, MetaVarName<"file">, Group<unsupported>;
-def output_resource_flag : Flag<["/", "-"], "outputresource">, Alias<output_resource>, HelpText<"Not supported">, Group<unsupported>;
-def update_resource : Joined<["/", "-"], "updateresource:">, HelpText<"Not supported">, MetaVarName<"file">, Group<unsupported>;
+def input_resource : Joined<["/", "-"], "inputresource:">, HelpText<"Read the manifest from the RT_MANIFEST resource with the given ID (default: 1) of a PE image">, MetaVarName<"file[;[#]id]">;
+def output_resource : Joined<["/", "-"], "outputresource:">, HelpText<"Write the merged manifest to the RT_MANIFEST resource with the given ID (default: 1) of a PE image">, MetaVarName<"file[;[#]id]">;
+def update_resource : Joined<["/", "-"], "updateresource:">, HelpText<"Equivalent to specifying both /inputresource and /outputresource with the same argument">, MetaVarName<"file[;[#]id]">;
def hash_update : Joined<["/", "-"], "hashupdate:">, HelpText<"Not supported">, MetaVarName<"file">, Group<unsupported>;
def hash_update_flag : Flag<["/", "-"], "hashupdate">, Alias<hash_update>, HelpText<"Not supported">, Group<unsupported>;
def validate_manifest : Flag<["/", "-"], "validate_manifest">, HelpText<"Not supported">, Group<unsupported>;
diff --git a/llvm/tools/llvm-mt/llvm-mt.cpp b/llvm/tools/llvm-mt/llvm-mt.cpp
index 9e781ad2fedb3..a8042ce59c14e 100644
--- a/llvm/tools/llvm-mt/llvm-mt.cpp
+++ b/llvm/tools/llvm-mt/llvm-mt.cpp
@@ -12,12 +12,20 @@
//===---------------------------------------------------------------------===//
#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
+#include "llvm/ObjCopy/COFF/COFFConfig.h"
+#include "llvm/ObjCopy/COFF/COFFObjcopy.h"
+#include "llvm/ObjCopy/CommonConfig.h"
+#include "llvm/Object/Binary.h"
+#include "llvm/Object/COFF.h"
+#include "llvm/Object/WindowsResource.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/Driver.h"
+#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileOutputBuffer.h"
+#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
@@ -27,6 +35,7 @@
#include "llvm/Support/raw_ostream.h"
#include "llvm/WindowsManifest/WindowsManifestMerger.h"
+#include <optional>
#include <system_error>
using namespace llvm;
@@ -48,6 +57,18 @@ class CvtResOptTable : public opt::OptTable {
public:
CvtResOptTable() : opt::OptTable(optionTables(), true) {}
};
+
+// The type of manifest resources.
+constexpr uint32_t RT_MANIFEST = 24;
+
+// A manifest embedded in a PE image as a resource, as specified by the
+// "<file>[;[#]<id>]" argument of the /inputresource, /outputresource and
+// /updateresource options.
+struct ManifestResource {
+ std::string File;
+ uint32_t ID = 1; // CREATEPROCESS_MANIFEST_RESOURCE_ID
+};
+
} // namespace
[[noreturn]] static void reportError(Twine Msg) {
@@ -66,6 +87,100 @@ static void error(Error EC) {
});
}
+static ManifestResource parseManifestResource(StringRef Arg) {
+ auto [File, ID] = Arg.rsplit(';');
+ if (File.empty())
+ reportError("missing file name in '" + Arg + "'");
+ ManifestResource Resource;
+ Resource.File = std::string(File);
+ if (Arg.contains(';')) {
+ ID.consume_front("#");
+ if (ID.getAsInteger(10, Resource.ID))
+ reportError("invalid resource ID in '" + Arg + "'");
+ }
+ return Resource;
+}
+
+static Expected<object::OwningBinary<object::Binary>>
+openImage(StringRef File) {
+ Expected<object::OwningBinary<object::Binary>> BinaryOrErr =
+ object::createBinary(File);
+ if (!BinaryOrErr)
+ return createFileError(File, BinaryOrErr.takeError());
+ auto *Obj = dyn_cast<object::COFFObjectFile>(BinaryOrErr->getBinary());
+ if (!Obj || !(Obj->getPE32Header() || Obj->getPE32PlusHeader()))
+ return createFileError(
+ File, createStringError(errc::invalid_argument, "not a PE image"));
+ return BinaryOrErr;
+}
+
+// Returns the manifest embedded in a PE image as the given resource, or
+// std::nullopt if the image does not contain it.
+static Expected<std::optional<std::string>>
+readManifestResource(const ManifestResource &Resource) {
+ Expected<object::OwningBinary<object::Binary>> BinaryOrErr =
+ openImage(Resource.File);
+ if (!BinaryOrErr)
+ return BinaryOrErr.takeError();
+ auto *Obj = cast<object::COFFObjectFile>(BinaryOrErr->getBinary());
+ const object::data_directory *Dir =
+ Obj->getDataDirectory(COFF::RESOURCE_TABLE);
+ if (!Dir || Dir->RelativeVirtualAddress == 0 || Dir->Size == 0)
+ return std::nullopt;
+
+ object::ResourceSectionRef RSR;
+ if (Error E = RSR.load(Obj))
+ return createFileError(Resource.File, std::move(E));
+ object::WindowsResourceParser Parser;
+ std::vector<std::string> Duplicates;
+ if (Error E = Parser.parse(RSR, Resource.File, Duplicates))
+ return createFileError(Resource.File, std::move(E));
+ if (!Duplicates.empty())
+ return createFileError(Resource.File,
+ createStringError(object::object_error::parse_failed,
+ "%s", Duplicates.front().c_str()));
+
+ const object::WindowsResourceParser::TreeNode *Node =
+ Parser.findResource(RT_MANIFEST, Resource.ID);
+ if (!Node || Node->getIDChildren().empty())
+ return std::nullopt;
+ // Manifests are language-neutral in practice, so use the first language.
+ const object::WindowsResourceParser::TreeNode &Language =
+ *Node->getIDChildren().begin()->second;
+ if (!Language.checkIsDataNode())
+ return std::nullopt;
+ ArrayRef<uint8_t> Data = Parser.getData()[Language.getDataIndex()];
+ return std::string(Data.begin(), Data.end());
+}
+
+// Embeds a manifest in a PE image as the given resource.
+static Error writeManifestResource(const ManifestResource &Resource,
+ StringRef Manifest) {
+ Expected<FilePermissionsApplier> PermsApplierOrErr =
+ FilePermissionsApplier::create(Resource.File);
+ if (!PermsApplierOrErr)
+ return PermsApplierOrErr.takeError();
+ Expected<object::OwningBinary<object::Binary>> BinaryOrErr =
+ openImage(Resource.File);
+ if (!BinaryOrErr)
+ return BinaryOrErr.takeError();
+ auto *Obj = cast<object::COFFObjectFile>(BinaryOrErr->getBinary());
+
+ objcopy::CommonConfig Config;
+ Config.InputFilename = Resource.File;
+ Config.OutputFilename = Resource.File;
+ objcopy::COFFConfig COFFConfig;
+ COFFConfig.UpdateResource.push_back(
+ {{RT_MANIFEST, Resource.ID, std::nullopt},
+ MemoryBuffer::getMemBufferCopy(Manifest, "manifest")});
+ if (Error E = writeToOutput(Resource.File, [&](raw_ostream &OS) {
+ return objcopy::coff::executeObjcopyOnBinary(Config, COFFConfig, *Obj,
+ OS);
+ }))
+ return E;
+ return PermsApplierOrErr->apply(Resource.File);
+}
+
int llvm_mt_main(int Argc, char **Argv, const llvm::ToolContext &) {
CvtResOptTable T;
unsigned MAI, MAC;
@@ -98,22 +213,39 @@ int llvm_mt_main(int Argc, char **Argv, const llvm::ToolContext &) {
}
std::vector<std::string> InputFiles = InputArgs.getAllArgValues(OPT_manifest);
+ std::vector<ManifestResource> InputResources;
+ for (auto *Arg : InputArgs.filtered(OPT_input_resource, OPT_update_resource))
+ InputResources.push_back(parseManifestResource(Arg->getValue()));
+ std::vector<ManifestResource> OutputResources;
+ for (auto *Arg : InputArgs.filtered(OPT_output_resource, OPT_update_resource))
+ OutputResources.push_back(parseManifestResource(Arg->getValue()));
- if (InputFiles.size() == 0) {
+ if (InputFiles.empty() && InputResources.empty())
reportError("no input file specified");
- }
StringRef OutputFile;
if (InputArgs.hasArg(OPT_out)) {
OutputFile = InputArgs.getLastArgValue(OPT_out);
- } else if (InputFiles.size() == 1) {
- OutputFile = InputFiles[0];
- } else {
- reportError("no output file specified");
+ } else if (OutputResources.empty()) {
+ if (InputFiles.size() == 1 && InputResources.empty())
+ OutputFile = InputFiles[0];
+ else
+ reportError("no output file specified");
}
windows_manifest::WindowsManifestMerger Merger;
+ for (const ManifestResource &Resource : InputResources) {
+ Expected<std::optional<std::string>> ManifestOrErr =
+ readManifestResource(Resource);
+ if (!ManifestOrErr)
+ error(ManifestOrErr.takeError());
+ if (!*ManifestOrErr)
+ reportError(Twine(Resource.File) + ": manifest resource with ID " +
+ Twine(Resource.ID) + " not found");
+ error(Merger.merge(MemoryBufferRef(**ManifestOrErr, Resource.File)));
+ }
+
for (const auto &File : InputFiles) {
ErrorOr<std::unique_ptr<MemoryBuffer>> ManifestOrErr =
MemoryBuffer::getFile(File);
@@ -125,19 +257,25 @@ int llvm_mt_main(int Argc, char **Argv, const llvm::ToolContext &) {
std::unique_ptr<MemoryBuffer> OutputBuffer = Merger.getMergedManifest();
if (!OutputBuffer)
reportError("empty manifest not written");
+ StringRef Output = OutputBuffer->getBuffer();
int ExitCode = 0;
if (InputArgs.hasArg(OPT_notify_update)) {
- ErrorOr<std::unique_ptr<MemoryBuffer>> OutBuffOrErr =
- MemoryBuffer::getFile(OutputFile);
- // Assume if we couldn't open the output file then it doesn't exist meaning
- // there was a change.
- bool Same = false;
- if (OutBuffOrErr) {
- const std::unique_ptr<MemoryBuffer> &FileBuffer = *OutBuffOrErr;
- Same = std::equal(
- OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(),
- FileBuffer->getBufferStart(), FileBuffer->getBufferEnd());
+ bool Same = true;
+ if (!OutputFile.empty()) {
+ ErrorOr<std::unique_ptr<MemoryBuffer>> OutBuffOrErr =
+ MemoryBuffer::getFile(OutputFile);
+ // Assume if we couldn't open the output file then it doesn't exist
+ // meaning there was a change.
+ Same = OutBuffOrErr && (*OutBuffOrErr)->getBuffer() == Output;
+ }
+ for (const ManifestResource &Resource : OutputResources) {
+ Expected<std::optional<std::string>> ExistingOrErr =
+ readManifestResource(Resource);
+ if (!ExistingOrErr)
+ error(ExistingOrErr.takeError());
+ if (!*ExistingOrErr || **ExistingOrErr != Output)
+ Same = false;
}
if (!Same) {
#if LLVM_ON_UNIX
@@ -148,13 +286,18 @@ int llvm_mt_main(int Argc, char **Argv, const llvm::ToolContext &) {
}
}
- Expected<std::unique_ptr<FileOutputBuffer>> FileOrErr =
- FileOutputBuffer::create(OutputFile, OutputBuffer->getBufferSize());
- if (!FileOrErr)
- reportError(OutputFile, errorToErrorCode(FileOrErr.takeError()));
- std::unique_ptr<FileOutputBuffer> FileBuffer = std::move(*FileOrErr);
- std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(),
- FileBuffer->getBufferStart());
- error(FileBuffer->commit());
+ if (!OutputFile.empty()) {
+ Expected<std::unique_ptr<FileOutputBuffer>> FileOrErr =
+ FileOutputBuffer::create(OutputFile, Output.size());
+ if (!FileOrErr)
+ reportError(OutputFile, errorToErrorCode(FileOrErr.takeError()));
+ std::unique_ptr<FileOutputBuffer> FileBuffer = std::move(*FileOrErr);
+ llvm::copy(Output, FileBuffer->getBufferStart());
+ error(FileBuffer->commit());
+ }
+
+ for (const ManifestResource &Resource : OutputResources)
+ error(writeManifestResource(Resource, Output));
+
return ExitCode;
}
More information about the llvm-commits
mailing list