forked from OSchip/llvm-project
COFF: Support /manifest{,uac,dependency,file} options.
The linker has to create an XML file for each executable. This patch supports that feature. You can optionally embed an XML file to an executable as .rsrc section. If you choose to do that (by passing /manifest:embed option), the linker has to create a textual resource file containing an XML file, compile that using rc.exe to a binary resource file, conver that resource file to a COFF file using cvtres.exe, and then link that COFF file. This patch implements that feature too. llvm-svn: 239978
This commit is contained in:
parent
c6e8bfc41d
commit
24c5fd0419
|
@ -37,6 +37,8 @@ struct Export {
|
|||
|
||||
// Global configuration.
|
||||
struct Configuration {
|
||||
enum ManifestKind { SideBySide, Embed, No };
|
||||
|
||||
llvm::COFF::MachineTypes MachineType = llvm::COFF::IMAGE_FILE_MACHINE_AMD64;
|
||||
bool Verbose = false;
|
||||
WindowsSubsystem Subsystem = llvm::COFF::IMAGE_SUBSYSTEM_UNKNOWN;
|
||||
|
@ -55,6 +57,15 @@ struct Configuration {
|
|||
bool DLL = false;
|
||||
std::vector<Export> Exports;
|
||||
|
||||
// Options for manifest files.
|
||||
ManifestKind Manifest = SideBySide;
|
||||
int ManifestID = 1;
|
||||
StringRef ManifestDependency;
|
||||
bool ManifestUAC = true;
|
||||
StringRef ManifestLevel = "'asInvoker'";
|
||||
StringRef ManifestUIAccess = "'false'";
|
||||
StringRef ManifestFile;
|
||||
|
||||
// Used by /failifmismatch option.
|
||||
std::map<StringRef, StringRef> MustMatch;
|
||||
|
||||
|
|
|
@ -241,8 +241,10 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
|
|||
Config->Verbose = true;
|
||||
|
||||
// Handle /dll
|
||||
if (Args->hasArg(OPT_dll))
|
||||
if (Args->hasArg(OPT_dll)) {
|
||||
Config->DLL = true;
|
||||
Config->ManifestID = 2;
|
||||
}
|
||||
|
||||
// Handle /entry
|
||||
if (auto *Arg = Args->getLastArg(OPT_entry))
|
||||
|
@ -367,6 +369,30 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Handle /manifest
|
||||
if (auto *Arg = Args->getLastArg(OPT_manifest_colon)) {
|
||||
if (auto EC = parseManifest(Arg->getValue())) {
|
||||
llvm::errs() << "/manifest: " << EC.message() << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle /manifestuac
|
||||
if (auto *Arg = Args->getLastArg(OPT_manifestuac)) {
|
||||
if (auto EC = parseManifestUAC(Arg->getValue())) {
|
||||
llvm::errs() << "/manifestuac: " << EC.message() << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle /manifestdependency
|
||||
if (auto *Arg = Args->getLastArg(OPT_manifestdependency))
|
||||
Config->ManifestDependency = Arg->getValue();
|
||||
|
||||
// Handle /manifestfile
|
||||
if (auto *Arg = Args->getLastArg(OPT_manifestfile))
|
||||
Config->ManifestFile = Arg->getValue();
|
||||
|
||||
// Handle miscellaneous boolean flags.
|
||||
if (Args->hasArg(OPT_allowbind_no)) Config->AllowBind = false;
|
||||
if (Args->hasArg(OPT_allowisolation_no)) Config->AllowIsolation = false;
|
||||
|
@ -405,6 +431,16 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
|
|||
Config->GCRoots.insert(Sym);
|
||||
}
|
||||
|
||||
// Windows specific -- Create a resource file containing a manifest file.
|
||||
if (Config->Manifest == Configuration::Embed) {
|
||||
auto MBOrErr = createManifestRes();
|
||||
if (MBOrErr.getError())
|
||||
return false;
|
||||
std::unique_ptr<MemoryBuffer> MB = std::move(MBOrErr.get());
|
||||
Inputs.push_back(MB->getMemBufferRef());
|
||||
OwningMBs.push_back(std::move(MB)); // take ownership
|
||||
}
|
||||
|
||||
// Windows specific -- Input files can be Windows resource files (.res files).
|
||||
// We invoke cvtres.exe to convert resource files to a regular COFF file
|
||||
// then link the result file normally.
|
||||
|
@ -513,6 +549,11 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Windows specific -- Create a side-by-side manifest file.
|
||||
if (Config->Manifest == Configuration::SideBySide)
|
||||
if (createSideBySideManifest())
|
||||
return false;
|
||||
|
||||
// Write the result.
|
||||
Writer Out(&Symtab);
|
||||
if (auto EC = Out.write(Config->OutputFile)) {
|
||||
|
|
|
@ -119,6 +119,16 @@ std::error_code parseVersion(StringRef Arg, uint32_t *Major, uint32_t *Minor);
|
|||
std::error_code parseSubsystem(StringRef Arg, WindowsSubsystem *Sys,
|
||||
uint32_t *Major, uint32_t *Minor);
|
||||
|
||||
// Parses a string in the form of "EMBED[,=<integer>]|NO".
|
||||
std::error_code parseManifest(StringRef Arg);
|
||||
|
||||
// Parses a string in the form of "level=<string>|uiAccess=<string>"
|
||||
std::error_code parseManifestUAC(StringRef Arg);
|
||||
|
||||
// Create a resource file containing a manifest XML.
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> createManifestRes();
|
||||
std::error_code createSideBySideManifest();
|
||||
|
||||
// Used for dllexported symbols.
|
||||
ErrorOr<Export> parseExport(StringRef Arg);
|
||||
std::error_code fixupExports();
|
||||
|
|
|
@ -60,7 +60,10 @@ public:
|
|||
Args.insert(Args.begin(), Exe);
|
||||
Args.push_back(nullptr);
|
||||
if (llvm::sys::ExecuteAndWait(Args[0], Args.data()) != 0) {
|
||||
llvm::errs() << Exe << " failed\n";
|
||||
for (const char *S : Args)
|
||||
if (S)
|
||||
llvm::errs() << S << " ";
|
||||
llvm::errs() << "failed\n";
|
||||
return make_error_code(LLDError::InvalidOption);
|
||||
}
|
||||
return std::error_code();
|
||||
|
@ -151,6 +154,163 @@ std::error_code parseSubsystem(StringRef Arg, WindowsSubsystem *Sys,
|
|||
return std::error_code();
|
||||
}
|
||||
|
||||
// Parses a string in the form of "EMBED[,=<integer>]|NO".
|
||||
// Results are directly written to Config.
|
||||
std::error_code parseManifest(StringRef Arg) {
|
||||
if (Arg.equals_lower("no")) {
|
||||
Config->Manifest = Configuration::No;
|
||||
return std::error_code();
|
||||
}
|
||||
if (!Arg.startswith_lower("embed"))
|
||||
return make_error_code(LLDError::InvalidOption);
|
||||
Config->Manifest = Configuration::Embed;
|
||||
Arg = Arg.substr(strlen("embed"));
|
||||
if (Arg.empty())
|
||||
return std::error_code();
|
||||
if (!Arg.startswith_lower(",id="))
|
||||
return make_error_code(LLDError::InvalidOption);
|
||||
Arg = Arg.substr(strlen(",id="));
|
||||
if (Arg.getAsInteger(0, Config->ManifestID))
|
||||
return make_error_code(LLDError::InvalidOption);
|
||||
return std::error_code();
|
||||
}
|
||||
|
||||
// Parses a string in the form of "level=<string>|uiAccess=<string>|NO".
|
||||
// Results are directly written to Config.
|
||||
std::error_code parseManifestUAC(StringRef Arg) {
|
||||
if (Arg.equals_lower("no")) {
|
||||
Config->ManifestUAC = false;
|
||||
return std::error_code();
|
||||
}
|
||||
for (;;) {
|
||||
Arg = Arg.ltrim();
|
||||
if (Arg.empty())
|
||||
return std::error_code();
|
||||
if (Arg.startswith_lower("level=")) {
|
||||
Arg = Arg.substr(strlen("level="));
|
||||
std::tie(Config->ManifestLevel, Arg) = Arg.split(" ");
|
||||
continue;
|
||||
}
|
||||
if (Arg.startswith_lower("uiaccess=")) {
|
||||
Arg = Arg.substr(strlen("uiaccess="));
|
||||
std::tie(Config->ManifestUIAccess, Arg) = Arg.split(" ");
|
||||
continue;
|
||||
}
|
||||
return make_error_code(LLDError::InvalidOption);
|
||||
}
|
||||
}
|
||||
|
||||
// Quote each line with "". Existing double-quote is converted
|
||||
// to two double-quotes.
|
||||
static void quoteAndPrint(raw_ostream &Out, StringRef S) {
|
||||
while (!S.empty()) {
|
||||
StringRef Line;
|
||||
std::tie(Line, S) = S.split("\n");
|
||||
if (Line.empty())
|
||||
continue;
|
||||
Out << '\"';
|
||||
for (int I = 0, E = Line.size(); I != E; ++I) {
|
||||
if (Line[I] == '\"') {
|
||||
Out << "\"\"";
|
||||
} else {
|
||||
Out << Line[I];
|
||||
}
|
||||
}
|
||||
Out << "\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Create a manifest file contents.
|
||||
static std::string createManifestXml() {
|
||||
std::string S;
|
||||
llvm::raw_string_ostream OS(S);
|
||||
// Emit the XML. Note that we do *not* verify that the XML attributes are
|
||||
// syntactically correct. This is intentional for link.exe compatibility.
|
||||
OS << "<?xml version=\"1.0\" standalone=\"yes\"?>\n"
|
||||
<< "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\"\n"
|
||||
<< " manifestVersion=\"1.0\">\n";
|
||||
if (Config->ManifestUAC) {
|
||||
OS << " <trustInfo>\n"
|
||||
<< " <security>\n"
|
||||
<< " <requestedPrivileges>\n"
|
||||
<< " <requestedExecutionLevel level=" << Config->ManifestLevel
|
||||
<< " uiAccess=" << Config->ManifestUIAccess << "/>\n"
|
||||
<< " </requestedPrivileges>\n"
|
||||
<< " </security>\n"
|
||||
<< " </trustInfo>\n";
|
||||
if (!Config->ManifestDependency.empty()) {
|
||||
OS << " <dependency>\n"
|
||||
<< " <dependentAssembly>\n"
|
||||
<< " <assemblyIdentity " << Config->ManifestDependency << " />\n"
|
||||
<< " </dependentAssembly>\n"
|
||||
<< " </dependency>\n";
|
||||
}
|
||||
}
|
||||
OS << "</assembly>\n";
|
||||
OS.flush();
|
||||
return S;
|
||||
}
|
||||
|
||||
// Create a resource file containing a manifest XML.
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> createManifestRes() {
|
||||
// Create a temporary file for the resource script file.
|
||||
SmallString<128> RCPath;
|
||||
if (sys::fs::createTemporaryFile("tmp", "rc", RCPath)) {
|
||||
llvm::errs() << "cannot create a temporary file\n";
|
||||
return make_error_code(LLDError::InvalidOption);
|
||||
}
|
||||
FileRemover RCRemover(RCPath);
|
||||
|
||||
// Open the temporary file for writing.
|
||||
std::error_code EC;
|
||||
llvm::raw_fd_ostream Out(RCPath, EC, sys::fs::F_Text);
|
||||
if (EC) {
|
||||
llvm::errs() << "failed to open " << RCPath << ": " << EC.message() << "\n";
|
||||
return make_error_code(LLDError::InvalidOption);
|
||||
}
|
||||
|
||||
// Write resource script to the RC file.
|
||||
Out << "#define LANG_ENGLISH 9\n"
|
||||
<< "#define SUBLANG_DEFAULT 1\n"
|
||||
<< "#define APP_MANIFEST " << Config->ManifestID << "\n"
|
||||
<< "#define RT_MANIFEST 24\n"
|
||||
<< "LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT\n"
|
||||
<< "APP_MANIFEST RT_MANIFEST {\n";
|
||||
quoteAndPrint(Out, createManifestXml());
|
||||
Out << "}\n";
|
||||
Out.close();
|
||||
|
||||
// Create output resource file.
|
||||
SmallString<128> ResPath;
|
||||
if (sys::fs::createTemporaryFile("tmp", "res", ResPath)) {
|
||||
llvm::errs() << "cannot create a temporary file\n";
|
||||
return make_error_code(LLDError::InvalidOption);
|
||||
}
|
||||
|
||||
Executor E("rc.exe");
|
||||
E.add("/fo");
|
||||
E.add(ResPath.str());
|
||||
E.add("/nologo");
|
||||
E.add(RCPath.str());
|
||||
if (auto EC = E.run())
|
||||
return EC;
|
||||
return MemoryBuffer::getFile(ResPath);
|
||||
}
|
||||
|
||||
std::error_code createSideBySideManifest() {
|
||||
std::string Path = Config->ManifestFile;
|
||||
if (Path == "")
|
||||
Path = (Twine(Config->OutputFile) + ".manifest").str();
|
||||
std::error_code EC;
|
||||
llvm::raw_fd_ostream Out(Path, EC, llvm::sys::fs::F_Text);
|
||||
if (EC) {
|
||||
llvm::errs() << EC.message() << "\n";
|
||||
return EC;
|
||||
}
|
||||
Out << createManifestXml();
|
||||
return std::error_code();
|
||||
}
|
||||
|
||||
// Parse a string in the form of
|
||||
// "<name>[=<internalname>][,@ordinal[,NONAME]][,DATA][,PRIVATE]".
|
||||
// Used for parsing /export arguments.
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
# RUN: yaml2obj %p/Inputs/ret42.yaml > %t.obj
|
||||
|
||||
# RUN: lld -flavor link2 /out:%t.exe %t.obj
|
||||
# RUN: FileCheck -check-prefix=MANIFEST %s < %t.exe.manifest
|
||||
|
||||
MANIFEST: <?xml version="1.0" standalone="yes"?>
|
||||
MANIFEST: <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
MANIFEST: manifestVersion="1.0">
|
||||
MANIFEST: <trustInfo>
|
||||
MANIFEST: <security>
|
||||
MANIFEST: <requestedPrivileges>
|
||||
MANIFEST: <requestedExecutionLevel level='asInvoker' uiAccess='false'/>
|
||||
MANIFEST: </requestedPrivileges>
|
||||
MANIFEST: </security>
|
||||
MANIFEST: </trustInfo>
|
||||
MANIFEST: </assembly>
|
||||
|
||||
# RUN: lld -flavor link2 /out:%t.exe /manifestuac:"level='requireAdministrator' uiAccess='true'" %t.obj
|
||||
# RUN: FileCheck -check-prefix=UAC %s < %t.exe.manifest
|
||||
|
||||
UAC: <?xml version="1.0" standalone="yes"?>
|
||||
UAC: <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
UAC: manifestVersion="1.0">
|
||||
UAC: <trustInfo>
|
||||
UAC: <security>
|
||||
UAC: <requestedPrivileges>
|
||||
UAC: <requestedExecutionLevel level='requireAdministrator' uiAccess='true'/>
|
||||
UAC: </requestedPrivileges>
|
||||
UAC: </security>
|
||||
UAC: </trustInfo>
|
||||
UAC: </assembly>
|
||||
|
||||
# RUN: lld -flavor link2 /out:%t.exe /manifestdependency:"foo='bar'" %t.obj
|
||||
# RUN: FileCheck -check-prefix=DEPENDENCY %s < %t.exe.manifest
|
||||
|
||||
DEPENDENCY: <?xml version="1.0" standalone="yes"?>
|
||||
DEPENDENCY: <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
DEPENDENCY: manifestVersion="1.0">
|
||||
DEPENDENCY: <trustInfo>
|
||||
DEPENDENCY: <security>
|
||||
DEPENDENCY: <requestedPrivileges>
|
||||
DEPENDENCY: <requestedExecutionLevel level='asInvoker' uiAccess='false'/>
|
||||
DEPENDENCY: </requestedPrivileges>
|
||||
DEPENDENCY: </security>
|
||||
DEPENDENCY: </trustInfo>
|
||||
DEPENDENCY: <dependency>
|
||||
DEPENDENCY: <dependentAssembly>
|
||||
DEPENDENCY: <assemblyIdentity foo='bar' />
|
||||
DEPENDENCY: </dependentAssembly>
|
||||
DEPENDENCY: </dependency>
|
||||
DEPENDENCY: </assembly>
|
||||
|
||||
# RUN: lld -flavor link2 /out:%t.exe /manifestuac:no %t.obj
|
||||
# RUN: FileCheck -check-prefix=NOUAC %s < %t.exe.manifest
|
||||
|
||||
NOUAC: <?xml version="1.0" standalone="yes"?>
|
||||
NOUAC: <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
NOUAC: manifestVersion="1.0">
|
||||
NOUAC: </assembly>
|
Loading…
Reference in New Issue