Skip to content

Commit 31fd9d0

Browse files
committed
[PECOFF] Invoke cvtres.exe in the driver.
Previously we invoked cvtres.exe for each compiled Windows resource file. The generated files were then concatenated and embedded to the executable. That was not the correct way to merge compiled Windows resource files. If you just concatenate generated files, only the first file would be recognized and the rest would be ignored as trailing garbage. The right way to merge them is to call cvtres.exe with multiple input files. In this patch we do that in the Windows driver. llvm-svn: 212763
1 parent 57cacb0 commit 31fd9d0

File tree

5 files changed

+106
-168
lines changed

5 files changed

+106
-168
lines changed

lld/include/lld/ReaderWriter/Reader.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ class Registry {
122122
void addSupportNativeObjects();
123123
void addSupportCOFFObjects(PECOFFLinkingContext &);
124124
void addSupportCOFFImportLibraries();
125-
void addSupportWindowsResourceFiles();
126125
void addSupportMachOObjects(StringRef archName);
127126
void addSupportELFObjects(bool atomizeStrings, TargetHandlerBase *handler);
128127
void addSupportELFDynamicSharedObjects(bool useShlibUndefines,

lld/lib/Driver/WinLinkDriver.cpp

Lines changed: 99 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,53 @@ static bool parseManifest(StringRef option, bool &enable, bool &embed,
282282
return true;
283283
}
284284

285+
// Returns true if the given file is a Windows resource file.
286+
static bool isResoruceFile(StringRef path) {
287+
llvm::sys::fs::file_magic fileType;
288+
if (llvm::sys::fs::identify_magic(path, fileType)) {
289+
// If we cannot read the file, assume it's not a resource file.
290+
// The further stage will raise an error on this unreadable file.
291+
return false;
292+
}
293+
return fileType == llvm::sys::fs::file_magic::windows_resource;
294+
}
295+
296+
// Merge Windows resource files and convert them to a single COFF file.
297+
// The temporary file path is set to result.
298+
static bool convertResourceFiles(std::vector<std::string> inFiles,
299+
std::string &result) {
300+
// Create an output file path.
301+
SmallString<128> outFile;
302+
if (llvm::sys::fs::createTemporaryFile("resource", "obj", outFile))
303+
return false;
304+
std::string outFileArg = ("/out:" + outFile).str();
305+
306+
// Construct CVTRES.EXE command line and execute it.
307+
std::string program = "cvtres.exe";
308+
std::string programPath = llvm::sys::FindProgramByName(program);
309+
if (programPath.empty()) {
310+
llvm::errs() << "Unable to find " << program << " in PATH\n";
311+
return false;
312+
}
313+
314+
std::vector<const char *> args;
315+
args.push_back(programPath.c_str());
316+
args.push_back("/machine:x86");
317+
args.push_back("/readonly");
318+
args.push_back("/nologo");
319+
args.push_back(outFileArg.c_str());
320+
for (const std::string &path : inFiles)
321+
args.push_back(path.c_str());
322+
args.push_back(nullptr);
323+
324+
if (llvm::sys::ExecuteAndWait(programPath.c_str(), &args[0]) != 0) {
325+
llvm::errs() << program << " failed\n";
326+
return false;
327+
}
328+
result = outFile.str();
329+
return true;
330+
}
331+
285332
// Parse /manifestuac:(level=<string>|uiAccess=<string>).
286333
//
287334
// The arguments will be embedded to the manifest XML file with no error check,
@@ -503,43 +550,37 @@ static bool createManifestResourceFile(PECOFFLinkingContext &ctx,
503550
return true;
504551
}
505552

506-
// Create a side-by-side manifest file. The side-by-side manifest file is a
507-
// separate XML file having ".manifest" extension. It will be created in the
508-
// same directory as the resulting executable.
553+
554+
// Create the a side-by-side manifest file.
555+
//
556+
// The manifest file will convey some information to the linker, such as whether
557+
// the binary needs to run as Administrator or not. Instead of being placed in
558+
// the PE/COFF header, it's in XML format for some reason -- I guess it's
559+
// probably because it's invented in the early dot-com era.
560+
//
561+
// The side-by-side manifest file is a separate XML file having ".manifest"
562+
// extension. It will be created in the same directory as the resulting
563+
// executable.
509564
static bool createSideBySideManifestFile(PECOFFLinkingContext &ctx,
510565
raw_ostream &diag) {
566+
std::string path = ctx.getManifestOutputPath();
567+
if (path.empty()) {
568+
// Default name of the manifest file is "foo.exe.manifest" where "foo.exe" is
569+
// the output path.
570+
path = ctx.outputPath();
571+
path.append(".manifest");
572+
}
573+
511574
std::string errorInfo;
512-
llvm::raw_fd_ostream out(ctx.getManifestOutputPath().data(), errorInfo,
513-
llvm::sys::fs::F_Text);
575+
llvm::raw_fd_ostream out(path.c_str(), errorInfo, llvm::sys::fs::F_Text);
514576
if (!errorInfo.empty()) {
515-
diag << "Failed to open " << ctx.getManifestOutputPath() << ": "
516-
<< errorInfo << "\n";
577+
diag << errorInfo << "\n";
517578
return false;
518579
}
519580
out << createManifestXml(ctx);
520581
return true;
521582
}
522583

523-
// Create the a side-by-side manifest file, or create a resource file for the
524-
// manifest file and add it to the input graph.
525-
//
526-
// The manifest file will convey some information to the linker, such as whether
527-
// the binary needs to run as Administrator or not. Instead of being placed in
528-
// the PE/COFF header, it's in XML format for some reason -- I guess it's
529-
// probably because it's invented in the early dot-com era.
530-
static bool createManifest(PECOFFLinkingContext &ctx, raw_ostream &diag) {
531-
if (ctx.getEmbedManifest()) {
532-
std::string resourceFilePath;
533-
if (!createManifestResourceFile(ctx, diag, resourceFilePath))
534-
return false;
535-
std::unique_ptr<InputElement> inputElement(
536-
new PECOFFFileNode(ctx, ctx.allocate(resourceFilePath)));
537-
ctx.getInputGraph().addInputElement(std::move(inputElement));
538-
return true;
539-
}
540-
return createSideBySideManifestFile(ctx, diag);
541-
}
542-
543584
// Handle /failifmismatch option.
544585
static bool
545586
handleFailIfMismatchOption(StringRef option,
@@ -770,14 +811,13 @@ bool WinLinkDriver::linkPECOFF(int argc, const char **argv, raw_ostream &diag) {
770811
return false;
771812

772813
// Create the file if needed.
773-
if (context.getCreateManifest())
774-
if (!createManifest(context, diag))
814+
if (context.getCreateManifest() && !context.getEmbedManifest())
815+
if (!createSideBySideManifestFile(context, diag))
775816
return false;
776817

777818
// Register possible input file parsers.
778819
context.registry().addSupportCOFFObjects(context);
779820
context.registry().addSupportCOFFImportLibraries();
780-
context.registry().addSupportWindowsResourceFiles();
781821
context.registry().addSupportArchives(context.logInputFiles());
782822
context.registry().addSupportNativeObjects();
783823
context.registry().addSupportYamlFiles();
@@ -1202,6 +1242,35 @@ bool WinLinkDriver::parse(int argc, const char *argv[],
12021242
for (const StringRef value : dashdash->getValues())
12031243
inputFiles.push_back(value);
12041244

1245+
// Compile Windows resource files to compiled resource file.
1246+
if (ctx.getCreateManifest() && ctx.getEmbedManifest() &&
1247+
!isReadingDirectiveSection) {
1248+
std::string resFile;
1249+
if (!createManifestResourceFile(ctx, diag, resFile))
1250+
return false;
1251+
inputFiles.push_back(ctx.allocate(resFile));
1252+
}
1253+
1254+
// A Windows Resource file is not an object file. It contains data,
1255+
// such as an icon image, and is not in COFF file format. If resource
1256+
// files are given, the linker merge them into one COFF file using
1257+
// CVTRES.EXE and then link the resulting file.
1258+
{
1259+
auto it = std::partition(inputFiles.begin(), inputFiles.end(),
1260+
isResoruceFile);
1261+
if (it != inputFiles.begin()) {
1262+
std::vector<std::string> resFiles(inputFiles.begin(), it);
1263+
std::string resObj;
1264+
if (!convertResourceFiles(resFiles, resObj)) {
1265+
diag << "Failed to convert resource files\n";
1266+
return false;
1267+
}
1268+
inputFiles = std::vector<StringRef>(it, inputFiles.end());
1269+
inputFiles.push_back(ctx.allocate(resObj));
1270+
ctx.registerTemporaryFile(resObj);
1271+
}
1272+
}
1273+
12051274
// Prepare objects to add them to input graph.
12061275
for (StringRef path : inputFiles) {
12071276
path = ctx.allocate(path);
@@ -1253,14 +1322,6 @@ bool WinLinkDriver::parse(int argc, const char *argv[],
12531322
ctx.setOutputPath(replaceExtension(ctx, path, ".exe"));
12541323
}
12551324

1256-
// Default name of the manifest file is "foo.exe.manifest" where "foo.exe" is
1257-
// the output path.
1258-
if (ctx.getManifestOutputPath().empty()) {
1259-
std::string path = ctx.outputPath();
1260-
path.append(".manifest");
1261-
ctx.setManifestOutputPath(ctx.allocate(path));
1262-
}
1263-
12641325
// Add the input files to the input graph.
12651326
if (!ctx.hasInputGraph())
12661327
ctx.setInputGraph(std::unique_ptr<InputGraph>(new InputGraph()));

lld/lib/ReaderWriter/PECOFF/ReaderCOFF.cpp

Lines changed: 0 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -935,112 +935,6 @@ StringRef FileCOFF::ArrayRefToString(ArrayRef<uint8_t> array) {
935935
return StringRef(*contents).trim();
936936
}
937937

938-
// Convert .res file to .coff file and then parse it. Resource file is a file
939-
// containing various types of data, such as icons, translation texts,
940-
// etc. "cvtres.exe" command reads an RC file to create a COFF file which
941-
// encapsulates resource data into rsrc$N sections, where N is an integer.
942-
//
943-
// The linker is not capable to handle RC files directly. Instead, it runs
944-
// cvtres.exe on RC files and then then link its outputs.
945-
class ResourceFileReader : public Reader {
946-
public:
947-
bool canParse(file_magic magic, StringRef ext,
948-
const MemoryBuffer &) const override {
949-
return (magic == llvm::sys::fs::file_magic::windows_resource);
950-
}
951-
952-
std::error_code
953-
parseFile(std::unique_ptr<MemoryBuffer> &mb, const class Registry &,
954-
std::vector<std::unique_ptr<File>> &result) const override {
955-
// Convert RC file to COFF
956-
ErrorOr<std::string> coffPath = convertResourceFileToCOFF(std::move(mb));
957-
if (std::error_code ec = coffPath.getError())
958-
return ec;
959-
llvm::FileRemover coffFileRemover(*coffPath);
960-
961-
// Read and parse the COFF
962-
ErrorOr<std::unique_ptr<MemoryBuffer>> newmb =
963-
MemoryBuffer::getFile(*coffPath);
964-
if (std::error_code ec = newmb.getError())
965-
return ec;
966-
std::error_code ec;
967-
std::unique_ptr<FileCOFF> file(new FileCOFF(std::move(newmb.get()), ec));
968-
if (ec)
969-
return ec;
970-
if (std::error_code ec = file->parse())
971-
return ec;
972-
result.push_back(std::move(file));
973-
return std::error_code();
974-
}
975-
976-
private:
977-
static ErrorOr<std::string>
978-
writeResToTemporaryFile(std::unique_ptr<MemoryBuffer> mb) {
979-
// Get a temporary file path for .res file.
980-
SmallString<128> tempFilePath;
981-
if (std::error_code ec =
982-
llvm::sys::fs::createTemporaryFile("tmp", "res", tempFilePath))
983-
return ec;
984-
985-
// Write the memory buffer contents to .res file, so that we can run
986-
// cvtres.exe on it.
987-
std::unique_ptr<llvm::FileOutputBuffer> buffer;
988-
if (std::error_code ec = llvm::FileOutputBuffer::create(
989-
tempFilePath.str(), mb->getBufferSize(), buffer))
990-
return ec;
991-
memcpy(buffer->getBufferStart(), mb->getBufferStart(), mb->getBufferSize());
992-
if (std::error_code ec = buffer->commit())
993-
return ec;
994-
995-
// Convert SmallString -> StringRef -> std::string.
996-
return tempFilePath.str().str();
997-
}
998-
999-
static ErrorOr<std::string>
1000-
convertResourceFileToCOFF(std::unique_ptr<MemoryBuffer> mb) {
1001-
// Write the resource file to a temporary file.
1002-
ErrorOr<std::string> inFilePath = writeResToTemporaryFile(std::move(mb));
1003-
if (std::error_code ec = inFilePath.getError())
1004-
return ec;
1005-
llvm::FileRemover inFileRemover(*inFilePath);
1006-
1007-
// Create an output file path.
1008-
SmallString<128> outFilePath;
1009-
if (std::error_code ec =
1010-
llvm::sys::fs::createTemporaryFile("tmp", "obj", outFilePath))
1011-
return ec;
1012-
std::string outFileArg = ("/out:" + outFilePath).str();
1013-
1014-
// Construct CVTRES.EXE command line and execute it.
1015-
std::string program = "cvtres.exe";
1016-
std::string programPath = llvm::sys::FindProgramByName(program);
1017-
if (programPath.empty()) {
1018-
llvm::errs() << "Unable to find " << program << " in PATH\n";
1019-
return llvm::errc::broken_pipe;
1020-
}
1021-
std::vector<const char *> args;
1022-
args.push_back(programPath.c_str());
1023-
args.push_back("/machine:x86");
1024-
args.push_back("/readonly");
1025-
args.push_back("/nologo");
1026-
args.push_back(outFileArg.c_str());
1027-
args.push_back(inFilePath->c_str());
1028-
args.push_back(nullptr);
1029-
1030-
DEBUG({
1031-
for (const char **p = &args[0]; *p; ++p)
1032-
llvm::dbgs() << *p << " ";
1033-
llvm::dbgs() << "\n";
1034-
});
1035-
1036-
if (llvm::sys::ExecuteAndWait(programPath.c_str(), &args[0]) != 0) {
1037-
llvm::errs() << program << " failed\n";
1038-
return llvm::errc::broken_pipe;
1039-
}
1040-
return outFilePath.str().str();
1041-
}
1042-
};
1043-
1044938
class COFFObjectReader : public Reader {
1045939
public:
1046940
COFFObjectReader(PECOFFLinkingContext &ctx) : _ctx(ctx) {}
@@ -1204,7 +1098,4 @@ void Registry::addSupportCOFFObjects(PECOFFLinkingContext &ctx) {
12041098
kindStringsAMD64);
12051099
}
12061100

1207-
void Registry::addSupportWindowsResourceFiles() {
1208-
add(std::unique_ptr<Reader>(new ResourceFileReader()));
1209-
}
12101101
}

lld/test/pecoff/resource.test

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
# REQUIRES: winres
22

33
# RUN: yaml2obj %p/Inputs/nop.obj.yaml > %t.obj
4-
#
4+
55
# RUN: lld -flavor link /out:%t.exe /subsystem:console /entry:start /opt:noref \
66
# RUN: -- %t.obj %p/Inputs/resource.res
77

88
# Check if the binary contains UTF-16LE string "Hello" copied from resource.res.
99
# RUN: cat %t.exe | grep 'H.e.l.l.o'
10+
11+
# RUN: lld -flavor link /out:%t.exe /subsystem:console /entry:start /opt:noref \
12+
# RUN: -- %t.obj %p/Inputs/resource.res %p/Inputs/resource.res
13+
14+
# Multiple resource files are concatenated.
15+
# RUN: cat %t.exe | grep 'H.e.l.l.o.H.e.l.l.o.'

lld/unittests/DriverTests/WinLinkDriverTest.cpp

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ TEST_F(WinLinkParserTest, Basic) {
6464
EXPECT_TRUE(_context.isTerminalServerAware());
6565
EXPECT_TRUE(_context.getDynamicBaseEnabled());
6666
EXPECT_TRUE(_context.getCreateManifest());
67-
EXPECT_EQ("a.exe.manifest", _context.getManifestOutputPath());
6867
EXPECT_EQ("", _context.getManifestDependency());
6968
EXPECT_FALSE(_context.getEmbedManifest());
7069
EXPECT_EQ(1, _context.getManifestId());
@@ -595,24 +594,6 @@ TEST_F(WinLinkParserTest, Manifest_No) {
595594
EXPECT_FALSE(_context.getCreateManifest());
596595
}
597596

598-
TEST_F(WinLinkParserTest, Manifest_Embed) {
599-
EXPECT_TRUE(parse("link.exe", "/manifest:embed", "a.out", nullptr));
600-
EXPECT_TRUE(_context.getCreateManifest());
601-
EXPECT_TRUE(_context.getEmbedManifest());
602-
EXPECT_EQ(1, _context.getManifestId());
603-
EXPECT_EQ("'asInvoker'", _context.getManifestLevel());
604-
EXPECT_EQ("'false'", _context.getManifestUiAccess());
605-
}
606-
607-
TEST_F(WinLinkParserTest, Manifest_Embed_ID42) {
608-
EXPECT_TRUE(parse("link.exe", "/manifest:embed,id=42", "a.out", nullptr));
609-
EXPECT_TRUE(_context.getCreateManifest());
610-
EXPECT_TRUE(_context.getEmbedManifest());
611-
EXPECT_EQ(42, _context.getManifestId());
612-
EXPECT_EQ("'asInvoker'", _context.getManifestLevel());
613-
EXPECT_EQ("'false'", _context.getManifestUiAccess());
614-
}
615-
616597
TEST_F(WinLinkParserTest, Manifestuac_no) {
617598
EXPECT_TRUE(parse("link.exe", "/manifestuac:NO", "a.out", nullptr));
618599
EXPECT_FALSE(_context.getManifestUAC());

0 commit comments

Comments
 (0)