forked from OSchip/llvm-project
COFF: Support delay-load import tables.
DLLs are usually resolved at process startup, but you can delay-load them by passing /delayload option to the linker. If a /delayload is specified, the linker has to create data which is similar to regular import table. One notable difference is that the pointers in a delay-load import table are originally pointing to thunks that resolves themselves. Each thunk loads a DLL, resolve its name, and then overwrites the pointer with the result so that subsequent function calls directly call a desired function. The linker has to emit thunks. llvm-svn: 240250
This commit is contained in:
parent
5055f6dfe0
commit
a77336bd5d
|
@ -60,6 +60,7 @@ struct Configuration {
|
||||||
bool DLL = false;
|
bool DLL = false;
|
||||||
StringRef Implib;
|
StringRef Implib;
|
||||||
std::vector<Export> Exports;
|
std::vector<Export> Exports;
|
||||||
|
std::set<StringRef> DelayLoads;
|
||||||
|
|
||||||
// Options for manifest files.
|
// Options for manifest files.
|
||||||
ManifestKind Manifest = SideBySide;
|
ManifestKind Manifest = SideBySide;
|
||||||
|
|
175
lld/COFF/DLL.cpp
175
lld/COFF/DLL.cpp
|
@ -7,8 +7,8 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
//
|
//
|
||||||
// This file defines various types of chunks for the DLL import
|
// This file defines various types of chunks for the DLL import or export
|
||||||
// descriptor table. They are inherently Windows-specific.
|
// descriptor tables. They are inherently Windows-specific.
|
||||||
// You need to read Microsoft PE/COFF spec to understand details
|
// You need to read Microsoft PE/COFF spec to understand details
|
||||||
// about the data structures.
|
// about the data structures.
|
||||||
//
|
//
|
||||||
|
@ -114,6 +114,7 @@ public:
|
||||||
explicit NullChunk(size_t N) : Size(N) {}
|
explicit NullChunk(size_t N) : Size(N) {}
|
||||||
bool hasData() const override { return false; }
|
bool hasData() const override { return false; }
|
||||||
size_t getSize() const override { return Size; }
|
size_t getSize() const override { return Size; }
|
||||||
|
void setAlign(size_t N) { Align = N; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t Size;
|
size_t Size;
|
||||||
|
@ -149,24 +150,34 @@ std::vector<Chunk *> IdataContents::getChunks() {
|
||||||
return V;
|
return V;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IdataContents::create() {
|
static std::map<StringRef, std::vector<DefinedImportData *>>
|
||||||
|
binImports(const std::vector<DefinedImportData *> &Imports) {
|
||||||
// Group DLL-imported symbols by DLL name because that's how
|
// Group DLL-imported symbols by DLL name because that's how
|
||||||
// symbols are layed out in the import descriptor table.
|
// symbols are layed out in the import descriptor table.
|
||||||
std::map<StringRef, std::vector<DefinedImportData *>> Map;
|
std::map<StringRef, std::vector<DefinedImportData *>> M;
|
||||||
for (DefinedImportData *Sym : Imports)
|
for (DefinedImportData *Sym : Imports)
|
||||||
Map[Sym->getDLLName()].push_back(Sym);
|
M[Sym->getDLLName()].push_back(Sym);
|
||||||
|
|
||||||
|
for (auto &P : M) {
|
||||||
|
// Sort symbols by name for each group.
|
||||||
|
std::vector<DefinedImportData *> &Syms = P.second;
|
||||||
|
std::sort(Syms.begin(), Syms.end(),
|
||||||
|
[](DefinedImportData *A, DefinedImportData *B) {
|
||||||
|
return A->getName() < B->getName();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return M;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdataContents::create() {
|
||||||
|
std::map<StringRef, std::vector<DefinedImportData *>> Map =
|
||||||
|
binImports(Imports);
|
||||||
|
|
||||||
// Create .idata contents for each DLL.
|
// Create .idata contents for each DLL.
|
||||||
for (auto &P : Map) {
|
for (auto &P : Map) {
|
||||||
StringRef Name = P.first;
|
StringRef Name = P.first;
|
||||||
std::vector<DefinedImportData *> &Syms = P.second;
|
std::vector<DefinedImportData *> &Syms = P.second;
|
||||||
|
|
||||||
// Sort symbols by name for each group.
|
|
||||||
std::sort(Syms.begin(), Syms.end(),
|
|
||||||
[](DefinedImportData *A, DefinedImportData *B) {
|
|
||||||
return A->getName() < B->getName();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create lookup and address tables. If they have external names,
|
// Create lookup and address tables. If they have external names,
|
||||||
// we need to create HintName chunks to store the names.
|
// we need to create HintName chunks to store the names.
|
||||||
// If they don't (if they are import-by-ordinals), we store only
|
// If they don't (if they are import-by-ordinals), we store only
|
||||||
|
@ -203,6 +214,148 @@ void IdataContents::create() {
|
||||||
Dirs.push_back(make_unique<NullChunk>(sizeof(ImportDirectoryTableEntry)));
|
Dirs.push_back(make_unique<NullChunk>(sizeof(ImportDirectoryTableEntry)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export table
|
||||||
|
// See Microsoft PE/COFF spec 4.3 for details.
|
||||||
|
|
||||||
|
// A chunk for the delay import descriptor table etnry.
|
||||||
|
class DelayDirectoryChunk : public Chunk {
|
||||||
|
public:
|
||||||
|
explicit DelayDirectoryChunk(Chunk *N) : DLLName(N) {}
|
||||||
|
|
||||||
|
size_t getSize() const override {
|
||||||
|
return sizeof(delay_import_directory_table_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeTo(uint8_t *Buf) override {
|
||||||
|
auto *E = (delay_import_directory_table_entry *)(Buf + FileOff);
|
||||||
|
E->Name = DLLName->getRVA();
|
||||||
|
E->ModuleHandle = ModuleHandle->getRVA();
|
||||||
|
E->DelayImportAddressTable = AddressTab->getRVA();
|
||||||
|
E->DelayImportNameTable = NameTab->getRVA();
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk *DLLName;
|
||||||
|
Chunk *ModuleHandle;
|
||||||
|
Chunk *AddressTab;
|
||||||
|
Chunk *NameTab;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial contents for delay-loaded functions.
|
||||||
|
// This code calls __delayLoadHerper2 function to resolve a symbol
|
||||||
|
// and then overwrites its jump table slot with the result
|
||||||
|
// for subsequent function calls.
|
||||||
|
static const uint8_t Thunk[] = {
|
||||||
|
0x51, // push rcx
|
||||||
|
0x52, // push rdx
|
||||||
|
0x41, 0x50, // push r8
|
||||||
|
0x41, 0x51, // push r9
|
||||||
|
0x48, 0x83, 0xEC, 0x48, // sub rsp, 48h
|
||||||
|
0x66, 0x0F, 0x7F, 0x04, 0x24, // movdqa xmmword ptr [rsp], xmm0
|
||||||
|
0x66, 0x0F, 0x7F, 0x4C, 0x24, 0x10, // movdqa xmmword ptr [rsp+10h], xmm1
|
||||||
|
0x66, 0x0F, 0x7F, 0x54, 0x24, 0x20, // movdqa xmmword ptr [rsp+20h], xmm2
|
||||||
|
0x66, 0x0F, 0x7F, 0x5C, 0x24, 0x30, // movdqa xmmword ptr [rsp+30h], xmm3
|
||||||
|
0x48, 0x8D, 0x15, 0, 0, 0, 0, // lea rdx, [__imp_<FUNCNAME>]
|
||||||
|
0x48, 0x8D, 0x0D, 0, 0, 0, 0, // lea rcx, [___DELAY_IMPORT_...]
|
||||||
|
0xE8, 0, 0, 0, 0, // call __delayLoadHelper2
|
||||||
|
0x66, 0x0F, 0x6F, 0x04, 0x24, // movdqa xmm0, xmmword ptr [rsp]
|
||||||
|
0x66, 0x0F, 0x6F, 0x4C, 0x24, 0x10, // movdqa xmm1, xmmword ptr [rsp+10h]
|
||||||
|
0x66, 0x0F, 0x6F, 0x54, 0x24, 0x20, // movdqa xmm2, xmmword ptr [rsp+20h]
|
||||||
|
0x66, 0x0F, 0x6F, 0x5C, 0x24, 0x30, // movdqa xmm3, xmmword ptr [rsp+30h]
|
||||||
|
0x48, 0x83, 0xC4, 0x48, // add rsp, 48h
|
||||||
|
0x41, 0x59, // pop r9
|
||||||
|
0x41, 0x58, // pop r8
|
||||||
|
0x5A, // pop rdx
|
||||||
|
0x59, // pop rcx
|
||||||
|
0xFF, 0xE0, // jmp rax
|
||||||
|
};
|
||||||
|
|
||||||
|
// A chunk for the delay import thunk.
|
||||||
|
class ThunkChunk : public Chunk {
|
||||||
|
public:
|
||||||
|
ThunkChunk(Defined *I, Defined *H) : Imp(I), Helper(H) {}
|
||||||
|
|
||||||
|
size_t getSize() const override { return sizeof(Thunk); }
|
||||||
|
|
||||||
|
void writeTo(uint8_t *Buf) override {
|
||||||
|
memcpy(Buf + FileOff, Thunk, sizeof(Thunk));
|
||||||
|
write32le(Buf + FileOff + 36, Imp->getRVA());
|
||||||
|
write32le(Buf + FileOff + 43, Desc->getRVA());
|
||||||
|
write32le(Buf + FileOff + 48, Helper->getRVA());
|
||||||
|
}
|
||||||
|
|
||||||
|
Defined *Imp = nullptr;
|
||||||
|
Chunk *Desc = nullptr;
|
||||||
|
Defined *Helper = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Chunk *> DelayLoadContents::getChunks(Defined *H) {
|
||||||
|
Helper = H;
|
||||||
|
create();
|
||||||
|
std::vector<Chunk *> V;
|
||||||
|
for (std::unique_ptr<Chunk> &C : Dirs)
|
||||||
|
V.push_back(C.get());
|
||||||
|
for (std::unique_ptr<Chunk> &C : Addresses)
|
||||||
|
V.push_back(C.get());
|
||||||
|
for (std::unique_ptr<Chunk> &C : Names)
|
||||||
|
V.push_back(C.get());
|
||||||
|
for (std::unique_ptr<Chunk> &C : HintNames)
|
||||||
|
V.push_back(C.get());
|
||||||
|
for (auto &P : DLLNames) {
|
||||||
|
std::unique_ptr<Chunk> &C = P.second;
|
||||||
|
V.push_back(C.get());
|
||||||
|
}
|
||||||
|
return V;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t DelayLoadContents::getDirSize() {
|
||||||
|
return Dirs.size() * sizeof(delay_import_directory_table_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DelayLoadContents::create() {
|
||||||
|
std::map<StringRef, std::vector<DefinedImportData *>> Map =
|
||||||
|
binImports(Imports);
|
||||||
|
|
||||||
|
// Create .didat contents for each DLL.
|
||||||
|
for (auto &P : Map) {
|
||||||
|
StringRef Name = P.first;
|
||||||
|
std::vector<DefinedImportData *> &Syms = P.second;
|
||||||
|
|
||||||
|
size_t Base = Addresses.size();
|
||||||
|
for (DefinedImportData *S : Syms) {
|
||||||
|
auto T = make_unique<ThunkChunk>(S, Helper);
|
||||||
|
auto A = make_unique<LookupChunk>(T.get());
|
||||||
|
T->Desc = A.get();
|
||||||
|
Addresses.push_back(std::move(A));
|
||||||
|
Thunks.push_back(std::move(T));
|
||||||
|
auto C =
|
||||||
|
make_unique<HintNameChunk>(S->getExternalName(), S->getOrdinal());
|
||||||
|
Names.push_back(make_unique<LookupChunk>(C.get()));
|
||||||
|
HintNames.push_back(std::move(C));
|
||||||
|
}
|
||||||
|
// Terminate with null values.
|
||||||
|
Addresses.push_back(make_unique<NullChunk>(LookupChunkSize));
|
||||||
|
Names.push_back(make_unique<NullChunk>(LookupChunkSize));
|
||||||
|
|
||||||
|
for (int I = 0, E = Syms.size(); I < E; ++I)
|
||||||
|
Syms[I]->setLocation(Addresses[Base + I].get());
|
||||||
|
auto *MH = new NullChunk(8);
|
||||||
|
MH->setAlign(8);
|
||||||
|
ModuleHandles.push_back(std::unique_ptr<Chunk>(MH));
|
||||||
|
|
||||||
|
// Create the delay import table header.
|
||||||
|
if (!DLLNames.count(Name))
|
||||||
|
DLLNames[Name] = make_unique<StringChunk>(Name);
|
||||||
|
auto Dir = make_unique<DelayDirectoryChunk>(DLLNames[Name].get());
|
||||||
|
Dir->ModuleHandle = MH;
|
||||||
|
Dir->AddressTab = Addresses[Base].get();
|
||||||
|
Dir->NameTab = Names[Base].get();
|
||||||
|
Dirs.push_back(std::move(Dir));
|
||||||
|
}
|
||||||
|
// Add null terminator.
|
||||||
|
Dirs.push_back(
|
||||||
|
make_unique<NullChunk>(sizeof(delay_import_directory_table_entry)));
|
||||||
|
}
|
||||||
|
|
||||||
// Export table
|
// Export table
|
||||||
// Read Microsoft PE/COFF spec 5.3 for details.
|
// Read Microsoft PE/COFF spec 5.3 for details.
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace coff {
|
||||||
class IdataContents {
|
class IdataContents {
|
||||||
public:
|
public:
|
||||||
void add(DefinedImportData *Sym) { Imports.push_back(Sym); }
|
void add(DefinedImportData *Sym) { Imports.push_back(Sym); }
|
||||||
|
bool empty() { return Imports.empty(); }
|
||||||
std::vector<Chunk *> getChunks();
|
std::vector<Chunk *> getChunks();
|
||||||
|
|
||||||
uint64_t getDirRVA() { return Dirs[0]->getRVA(); }
|
uint64_t getDirRVA() { return Dirs[0]->getRVA(); }
|
||||||
|
@ -41,6 +42,32 @@ private:
|
||||||
std::map<StringRef, std::unique_ptr<Chunk>> DLLNames;
|
std::map<StringRef, std::unique_ptr<Chunk>> DLLNames;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Windows-specific.
|
||||||
|
// DelayLoadContents creates all chunks for the delay-load DLL import table.
|
||||||
|
class DelayLoadContents {
|
||||||
|
public:
|
||||||
|
void add(DefinedImportData *Sym) { Imports.push_back(Sym); }
|
||||||
|
bool empty() { return Imports.empty(); }
|
||||||
|
std::vector<Chunk *> getChunks(Defined *Helper);
|
||||||
|
std::vector<std::unique_ptr<Chunk>> &getDataChunks() { return ModuleHandles; }
|
||||||
|
std::vector<std::unique_ptr<Chunk>> &getCodeChunks() { return Thunks; }
|
||||||
|
|
||||||
|
uint64_t getDirRVA() { return Dirs[0]->getRVA(); }
|
||||||
|
uint64_t getDirSize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void create();
|
||||||
|
Defined *Helper;
|
||||||
|
std::vector<DefinedImportData *> Imports;
|
||||||
|
std::vector<std::unique_ptr<Chunk>> Dirs;
|
||||||
|
std::vector<std::unique_ptr<Chunk>> ModuleHandles;
|
||||||
|
std::vector<std::unique_ptr<Chunk>> Addresses;
|
||||||
|
std::vector<std::unique_ptr<Chunk>> Names;
|
||||||
|
std::vector<std::unique_ptr<Chunk>> HintNames;
|
||||||
|
std::vector<std::unique_ptr<Chunk>> Thunks;
|
||||||
|
std::map<StringRef, std::unique_ptr<Chunk>> DLLNames;
|
||||||
|
};
|
||||||
|
|
||||||
// Windows-specific.
|
// Windows-specific.
|
||||||
// EdataContents creates all chunks for the DLL export table.
|
// EdataContents creates all chunks for the DLL export table.
|
||||||
class EdataContents {
|
class EdataContents {
|
||||||
|
|
|
@ -378,6 +378,12 @@ bool LinkerDriver::link(llvm::ArrayRef<const char*> ArgsArr) {
|
||||||
Config->Exports.push_back(E.get());
|
Config->Exports.push_back(E.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle /delayload
|
||||||
|
for (auto *Arg : Args->filtered(OPT_delayload)) {
|
||||||
|
Config->DelayLoads.insert(Arg->getValue());
|
||||||
|
Config->Includes.insert("__delayLoadHelper2");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle /failifmismatch
|
// Handle /failifmismatch
|
||||||
for (auto *Arg : Args->filtered(OPT_failifmismatch))
|
for (auto *Arg : Args->filtered(OPT_failifmismatch))
|
||||||
if (checkFailIfMismatch(Arg->getValue()))
|
if (checkFailIfMismatch(Arg->getValue()))
|
||||||
|
|
|
@ -159,29 +159,45 @@ void Writer::createImportTables() {
|
||||||
if (Symtab->ImportFiles.empty())
|
if (Symtab->ImportFiles.empty())
|
||||||
return;
|
return;
|
||||||
OutputSection *Text = createSection(".text");
|
OutputSection *Text = createSection(".text");
|
||||||
Idata.reset(new IdataContents());
|
|
||||||
for (std::unique_ptr<ImportFile> &File : Symtab->ImportFiles) {
|
for (std::unique_ptr<ImportFile> &File : Symtab->ImportFiles) {
|
||||||
for (SymbolBody *Body : File->getSymbols()) {
|
for (SymbolBody *B : File->getSymbols()) {
|
||||||
if (auto *Import = dyn_cast<DefinedImportData>(Body)) {
|
auto *Import = dyn_cast<DefinedImportData>(B);
|
||||||
Idata->add(Import);
|
if (!Import) {
|
||||||
|
// Linker-created function thunks for DLL symbols are added to
|
||||||
|
// .text section.
|
||||||
|
Text->addChunk(cast<DefinedImportThunk>(B)->getChunk());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Linker-created function thunks for DLL symbols are added to
|
if (Config->DelayLoads.count(Import->getDLLName())) {
|
||||||
// .text section.
|
DelayIdata.add(Import);
|
||||||
Text->addChunk(cast<DefinedImportThunk>(Body)->getChunk());
|
} else {
|
||||||
|
Idata.add(Import);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OutputSection *Sec = createSection(".idata");
|
if (!Idata.empty()) {
|
||||||
for (Chunk *C : Idata->getChunks())
|
OutputSection *Sec = createSection(".idata");
|
||||||
Sec->addChunk(C);
|
for (Chunk *C : Idata.getChunks())
|
||||||
|
Sec->addChunk(C);
|
||||||
|
}
|
||||||
|
if (!DelayIdata.empty()) {
|
||||||
|
OutputSection *Sec = createSection(".didat");
|
||||||
|
for (Chunk *C : DelayIdata.getChunks(Symtab->find("__delayLoadHelper2")))
|
||||||
|
Sec->addChunk(C);
|
||||||
|
Sec = createSection(".text");
|
||||||
|
for (std::unique_ptr<Chunk> &C : DelayIdata.getCodeChunks())
|
||||||
|
Sec->addChunk(C.get());
|
||||||
|
Sec = createSection(".data");
|
||||||
|
for (std::unique_ptr<Chunk> &C : DelayIdata.getDataChunks())
|
||||||
|
Sec->addChunk(C.get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Writer::createExportTable() {
|
void Writer::createExportTable() {
|
||||||
if (Config->Exports.empty())
|
if (Config->Exports.empty())
|
||||||
return;
|
return;
|
||||||
Edata.reset(new EdataContents());
|
|
||||||
OutputSection *Sec = createSection(".edata");
|
OutputSection *Sec = createSection(".edata");
|
||||||
for (std::unique_ptr<Chunk> &C : Edata->Chunks)
|
for (std::unique_ptr<Chunk> &C : Edata.Chunks)
|
||||||
Sec->addChunk(C.get());
|
Sec->addChunk(C.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,11 +325,16 @@ void Writer::writeHeader() {
|
||||||
Dir[EXPORT_TABLE].RelativeVirtualAddress = Sec->getRVA();
|
Dir[EXPORT_TABLE].RelativeVirtualAddress = Sec->getRVA();
|
||||||
Dir[EXPORT_TABLE].Size = Sec->getVirtualSize();
|
Dir[EXPORT_TABLE].Size = Sec->getVirtualSize();
|
||||||
}
|
}
|
||||||
if (Idata) {
|
if (!Idata.empty()) {
|
||||||
Dir[IMPORT_TABLE].RelativeVirtualAddress = Idata->getDirRVA();
|
Dir[IMPORT_TABLE].RelativeVirtualAddress = Idata.getDirRVA();
|
||||||
Dir[IMPORT_TABLE].Size = Idata->getDirSize();
|
Dir[IMPORT_TABLE].Size = Idata.getDirSize();
|
||||||
Dir[IAT].RelativeVirtualAddress = Idata->getIATRVA();
|
Dir[IAT].RelativeVirtualAddress = Idata.getIATRVA();
|
||||||
Dir[IAT].Size = Idata->getIATSize();
|
Dir[IAT].Size = Idata.getIATSize();
|
||||||
|
}
|
||||||
|
if (!DelayIdata.empty()) {
|
||||||
|
Dir[DELAY_IMPORT_DESCRIPTOR].RelativeVirtualAddress =
|
||||||
|
DelayIdata.getDirRVA();
|
||||||
|
Dir[DELAY_IMPORT_DESCRIPTOR].Size = DelayIdata.getDirSize();
|
||||||
}
|
}
|
||||||
if (OutputSection *Sec = findSection(".rsrc")) {
|
if (OutputSection *Sec = findSection(".rsrc")) {
|
||||||
Dir[RESOURCE_TABLE].RelativeVirtualAddress = Sec->getRVA();
|
Dir[RESOURCE_TABLE].RelativeVirtualAddress = Sec->getRVA();
|
||||||
|
|
|
@ -100,8 +100,9 @@ private:
|
||||||
llvm::SpecificBumpPtrAllocator<OutputSection> CAlloc;
|
llvm::SpecificBumpPtrAllocator<OutputSection> CAlloc;
|
||||||
llvm::SpecificBumpPtrAllocator<BaserelChunk> BAlloc;
|
llvm::SpecificBumpPtrAllocator<BaserelChunk> BAlloc;
|
||||||
std::vector<OutputSection *> OutputSections;
|
std::vector<OutputSection *> OutputSections;
|
||||||
std::unique_ptr<IdataContents> Idata;
|
IdataContents Idata;
|
||||||
std::unique_ptr<EdataContents> Edata;
|
DelayLoadContents DelayIdata;
|
||||||
|
EdataContents Edata;
|
||||||
|
|
||||||
uint64_t FileSize;
|
uint64_t FileSize;
|
||||||
uint64_t SizeOfImage;
|
uint64_t SizeOfImage;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# RUN: lld -flavor link2 /out:%t.exe /entry:main /subsystem:console \
|
||||||
|
# RUN: %p/Inputs/hello64.obj %p/Inputs/std64.lib /delayload:std64.dll \
|
||||||
|
# RUN: /alternatename:__delayLoadHelper2=main
|
||||||
|
# RUN: llvm-readobj -coff-imports %t.exe | FileCheck %s
|
||||||
|
|
||||||
|
CHECK: DelayImport {
|
||||||
|
CHECK-NEXT: Name: std64.dll
|
||||||
|
CHECK-NEXT: Attributes: 0x0
|
||||||
|
CHECK-NEXT: ModuleHandle: 0x1018
|
||||||
|
CHECK-NEXT: ImportAddressTable: 0x3040
|
||||||
|
CHECK-NEXT: ImportNameTable: 0x3060
|
||||||
|
CHECK-NEXT: BoundDelayImportTable: 0x0
|
||||||
|
CHECK-NEXT: UnloadDelayImportTable: 0x0
|
||||||
|
CHECK-NEXT: Import {
|
||||||
|
CHECK-NEXT: Symbol: ExitProcess (0)
|
||||||
|
CHECK-NEXT: Address: 0x2045
|
||||||
|
CHECK-NEXT: }
|
||||||
|
CHECK-NEXT: Import {
|
||||||
|
CHECK-NEXT: Symbol: (50)
|
||||||
|
CHECK-NEXT: Address: 0x209C
|
||||||
|
CHECK-NEXT: }
|
||||||
|
CHECK-NEXT: Import {
|
||||||
|
CHECK-NEXT: Symbol: MessageBoxA (1)
|
||||||
|
CHECK-NEXT: Address: 0x20F3
|
||||||
|
CHECK-NEXT: }
|
||||||
|
CHECK-NEXT: }
|
Loading…
Reference in New Issue