Skip to content

Commit 3785a41

Browse files
committed
Reapply [LLD] [COFF] Implement a GNU/ELF like -wrap option
Add a simple forwarding option in the MinGW frontend, and implement the private -wrap option in the COFF linker. The feature in lld-link isn't gated by the -lldmingw option, but the option is left as a private, undocumented option primarily used by the MinGW driver. The implementation is significantly based on the support for --wrap in the ELF linker, but many small nuance details are different between the ELF and COFF linkers, ending up with more than a few implementation differences. This fixes https://bugs.llvm.org/show_bug.cgi?id=47384. Differential Revision: https://reviews.llvm.org/D89004 Reapplied with the bitfield member canInline fixed so it doesn't break builds targeting windows.
1 parent 77fb8cb commit 3785a41

18 files changed

+444
-3
lines changed

lld/COFF/Driver.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,6 +2008,12 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) {
20082008
while (run());
20092009
}
20102010

2011+
// Create wrapped symbols for -wrap option.
2012+
std::vector<WrappedSymbol> wrapped = addWrappedSymbols(args);
2013+
// Load more object files that might be needed for wrapped symbols.
2014+
if (!wrapped.empty())
2015+
while (run());
2016+
20112017
if (config->autoImport) {
20122018
// MinGW specific.
20132019
// Load any further object files that might be needed for doing automatic
@@ -2051,6 +2057,10 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) {
20512057
// references to the symbols we use from them.
20522058
run();
20532059

2060+
// Apply symbol renames for -wrap.
2061+
if (!wrapped.empty())
2062+
wrapSymbols(wrapped);
2063+
20542064
// Resolve remaining undefined symbols and warn about imported locals.
20552065
symtab->resolveRemainingUndefines();
20562066
if (errorCount())

lld/COFF/InputFiles.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ class ObjFile : public InputFile {
148148
ArrayRef<SectionChunk *> getGuardLJmpChunks() { return guardLJmpChunks; }
149149
ArrayRef<Symbol *> getSymbols() { return symbols; }
150150

151+
MutableArrayRef<Symbol *> getMutableSymbols() { return symbols; }
152+
151153
ArrayRef<uint8_t> getDebugSection(StringRef secName);
152154

153155
// Returns a Symbol object for the symbolIndex'th symbol in the

lld/COFF/LTO.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ void BitcodeCompiler::add(BitcodeFile &f) {
139139
r.VisibleToRegularObj = sym->isUsedInRegularObj;
140140
if (r.Prevailing)
141141
undefine(sym);
142+
143+
// We tell LTO to not apply interprocedural optimization for wrapped
144+
// (with -wrap) symbols because otherwise LTO would inline them while
145+
// their values are still not final.
146+
r.LinkerRedefined = !sym->canInline;
142147
}
143148
checkError(ltoObj->add(std::move(f.obj), resols));
144149
}

lld/COFF/MinGW.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "MinGW.h"
10+
#include "Driver.h"
11+
#include "InputFiles.h"
1012
#include "SymbolTable.h"
1113
#include "lld/Common/ErrorHandler.h"
14+
#include "llvm/ADT/DenseMap.h"
15+
#include "llvm/ADT/DenseSet.h"
1216
#include "llvm/Object/COFF.h"
17+
#include "llvm/Support/Parallel.h"
1318
#include "llvm/Support/Path.h"
1419
#include "llvm/Support/raw_ostream.h"
1520

@@ -173,3 +178,73 @@ void lld::coff::writeDefFile(StringRef name) {
173178
os << "\n";
174179
}
175180
}
181+
182+
static StringRef mangle(Twine sym) {
183+
assert(config->machine != IMAGE_FILE_MACHINE_UNKNOWN);
184+
if (config->machine == I386)
185+
return saver.save("_" + sym);
186+
return saver.save(sym);
187+
}
188+
189+
// Handles -wrap option.
190+
//
191+
// This function instantiates wrapper symbols. At this point, they seem
192+
// like they are not being used at all, so we explicitly set some flags so
193+
// that LTO won't eliminate them.
194+
std::vector<WrappedSymbol>
195+
lld::coff::addWrappedSymbols(opt::InputArgList &args) {
196+
std::vector<WrappedSymbol> v;
197+
DenseSet<StringRef> seen;
198+
199+
for (auto *arg : args.filtered(OPT_wrap)) {
200+
StringRef name = arg->getValue();
201+
if (!seen.insert(name).second)
202+
continue;
203+
204+
Symbol *sym = symtab->findUnderscore(name);
205+
if (!sym)
206+
continue;
207+
208+
Symbol *real = symtab->addUndefined(mangle("__real_" + name));
209+
Symbol *wrap = symtab->addUndefined(mangle("__wrap_" + name));
210+
v.push_back({sym, real, wrap});
211+
212+
// These symbols may seem undefined initially, but don't bail out
213+
// at symtab->reportUnresolvable() due to them, but let wrapSymbols
214+
// below sort things out before checking finally with
215+
// symtab->resolveRemainingUndefines().
216+
sym->deferUndefined = true;
217+
real->deferUndefined = true;
218+
// We want to tell LTO not to inline symbols to be overwritten
219+
// because LTO doesn't know the final symbol contents after renaming.
220+
real->canInline = false;
221+
sym->canInline = false;
222+
223+
// Tell LTO not to eliminate these symbols.
224+
sym->isUsedInRegularObj = true;
225+
if (!isa<Undefined>(wrap))
226+
wrap->isUsedInRegularObj = true;
227+
}
228+
return v;
229+
}
230+
231+
// Do renaming for -wrap by updating pointers to symbols.
232+
//
233+
// When this function is executed, only InputFiles and symbol table
234+
// contain pointers to symbol objects. We visit them to replace pointers,
235+
// so that wrapped symbols are swapped as instructed by the command line.
236+
void lld::coff::wrapSymbols(ArrayRef<WrappedSymbol> wrapped) {
237+
DenseMap<Symbol *, Symbol *> map;
238+
for (const WrappedSymbol &w : wrapped) {
239+
map[w.sym] = w.wrap;
240+
map[w.real] = w.sym;
241+
}
242+
243+
// Update pointers in input files.
244+
parallelForEach(ObjFile::instances, [&](ObjFile *file) {
245+
MutableArrayRef<Symbol *> syms = file->getMutableSymbols();
246+
for (size_t i = 0, e = syms.size(); i != e; ++i)
247+
if (Symbol *s = map.lookup(syms[i]))
248+
syms[i] = s;
249+
});
250+
}

lld/COFF/MinGW.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
#include "Config.h"
1313
#include "Symbols.h"
1414
#include "lld/Common/LLVM.h"
15+
#include "llvm/ADT/ArrayRef.h"
1516
#include "llvm/ADT/StringSet.h"
17+
#include "llvm/Option/ArgList.h"
18+
#include <vector>
1619

1720
namespace lld {
1821
namespace coff {
@@ -36,6 +39,24 @@ class AutoExporter {
3639

3740
void writeDefFile(StringRef name);
3841

42+
// The -wrap option is a feature to rename symbols so that you can write
43+
// wrappers for existing functions. If you pass `-wrap:foo`, all
44+
// occurrences of symbol `foo` are resolved to `__wrap_foo` (so, you are
45+
// expected to write `__wrap_foo` function as a wrapper). The original
46+
// symbol becomes accessible as `__real_foo`, so you can call that from your
47+
// wrapper.
48+
//
49+
// This data structure is instantiated for each -wrap option.
50+
struct WrappedSymbol {
51+
Symbol *sym;
52+
Symbol *real;
53+
Symbol *wrap;
54+
};
55+
56+
std::vector<WrappedSymbol> addWrappedSymbols(llvm::opt::InputArgList &args);
57+
58+
void wrapSymbols(ArrayRef<WrappedSymbol> wrapped);
59+
3960
} // namespace coff
4061
} // namespace lld
4162

lld/COFF/Options.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ def print_symbol_order: P<
252252
"print-symbol-order",
253253
"Print a symbol order specified by /call-graph-ordering-file and "
254254
"/call-graph-profile-sort into the specified file">;
255+
def wrap : P_priv<"wrap">;
255256

256257
// Flags for debugging
257258
def lldmap : F<"lldmap">;

lld/COFF/SymbolTable.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ void SymbolTable::reportUnresolvable() {
390390
for (auto &i : symMap) {
391391
Symbol *sym = i.second;
392392
auto *undef = dyn_cast<Undefined>(sym);
393-
if (!undef)
393+
if (!undef || sym->deferUndefined)
394394
continue;
395395
if (undef->getWeakAlias())
396396
continue;
@@ -482,6 +482,7 @@ std::pair<Symbol *, bool> SymbolTable::insert(StringRef name) {
482482
sym = reinterpret_cast<Symbol *>(make<SymbolUnion>());
483483
sym->isUsedInRegularObj = false;
484484
sym->pendingArchiveLoad = false;
485+
sym->canInline = true;
485486
inserted = true;
486487
}
487488
return {sym, inserted};

lld/COFF/Symbols.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ class Symbol {
103103
explicit Symbol(Kind k, StringRef n = "")
104104
: symbolKind(k), isExternal(true), isCOMDAT(false),
105105
writtenToSymtab(false), pendingArchiveLoad(false), isGCRoot(false),
106-
isRuntimePseudoReloc(false), nameSize(n.size()),
107-
nameData(n.empty() ? nullptr : n.data()) {}
106+
isRuntimePseudoReloc(false), deferUndefined(false), canInline(true),
107+
nameSize(n.size()), nameData(n.empty() ? nullptr : n.data()) {}
108108

109109
const unsigned symbolKind : 8;
110110
unsigned isExternal : 1;
@@ -130,6 +130,16 @@ class Symbol {
130130

131131
unsigned isRuntimePseudoReloc : 1;
132132

133+
// True if we want to allow this symbol to be undefined in the early
134+
// undefined check pass in SymbolTable::reportUnresolvable(), as it
135+
// might be fixed up later.
136+
unsigned deferUndefined : 1;
137+
138+
// False if LTO shouldn't inline whatever this symbol points to. If a symbol
139+
// is overwritten after LTO, LTO shouldn't inline the symbol because it
140+
// doesn't know the final contents of the symbol.
141+
unsigned canInline : 1;
142+
133143
protected:
134144
// Symbol name length. Assume symbol lengths fit in a 32-bit integer.
135145
uint32_t nameSize;
@@ -468,7 +478,9 @@ void replaceSymbol(Symbol *s, ArgT &&... arg) {
468478
"SymbolUnion not aligned enough");
469479
assert(static_cast<Symbol *>(static_cast<T *>(nullptr)) == nullptr &&
470480
"Not a Symbol");
481+
bool canInline = s->canInline;
471482
new (s) T(std::forward<ArgT>(arg)...);
483+
s->canInline = canInline;
472484
}
473485
} // namespace coff
474486

lld/MinGW/Driver.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ bool mingw::link(ArrayRef<const char *> argsArr, bool canExitEarly,
377377
add("-includeoptional:" + StringRef(a->getValue()));
378378
for (auto *a : args.filtered(OPT_delayload))
379379
add("-delayload:" + StringRef(a->getValue()));
380+
for (auto *a : args.filtered(OPT_wrap))
381+
add("-wrap:" + StringRef(a->getValue()));
380382

381383
std::vector<StringRef> searchPaths;
382384
for (auto *a : args.filtered(OPT_L)) {

lld/MinGW/Options.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ defm whole_archive: B<"whole-archive",
9191
def v: Flag<["-"], "v">, HelpText<"Display the version number">;
9292
def verbose: F<"verbose">, HelpText<"Verbose mode">;
9393
def version: F<"version">, HelpText<"Display the version number and exit">;
94+
defm wrap: Eq<"wrap", "Use wrapper functions for symbol">,
95+
MetaVarName<"<symbol>">;
9496

9597
// LLD specific options
9698
def _HASH_HASH_HASH : Flag<["-"], "###">,

lld/test/COFF/wrap-i386.s

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// REQUIRES: x86
2+
// RUN: split-file %s %t.dir
3+
// RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/main.s -o %t.main.obj
4+
// RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/other.s -o %t.other.obj
5+
6+
// RUN: lld-link -out:%t.exe %t.main.obj %t.other.obj -entry:entry -subsystem:console -debug:symtab -safeseh:no -wrap:foo -wrap:nosuchsym
7+
// RUN: llvm-objdump -d --print-imm-hex %t.exe | FileCheck %s
8+
9+
// CHECK: <_entry>:
10+
// CHECK-NEXT: movl $0x11010, %edx
11+
// CHECK-NEXT: movl $0x11010, %edx
12+
// CHECK-NEXT: movl $0x11000, %edx
13+
14+
// RUN: llvm-readobj --symbols %t.exe > %t.dump
15+
// RUN: FileCheck --check-prefix=SYM1 %s < %t.dump
16+
// RUN: FileCheck --check-prefix=SYM2 %s < %t.dump
17+
// RUN: FileCheck --check-prefix=SYM3 %s < %t.dump
18+
19+
// _foo = 0xffc11000 = 4290842624
20+
// ___wrap_foo = ffc11010 = 4290842640
21+
// SYM1: Name: _foo
22+
// SYM1-NEXT: Value: 4290842624
23+
// SYM1-NEXT: Section: IMAGE_SYM_ABSOLUTE
24+
// SYM1-NEXT: BaseType: Null
25+
// SYM1-NEXT: ComplexType: Null
26+
// SYM1-NEXT: StorageClass: External
27+
// SYM2: Name: ___wrap_foo
28+
// SYM2-NEXT: Value: 4290842640
29+
// SYM2-NEXT: Section: IMAGE_SYM_ABSOLUTE
30+
// SYM2-NEXT: BaseType: Null
31+
// SYM2-NEXT: ComplexType: Null
32+
// SYM2-NEXT: StorageClass: External
33+
// SYM3-NOT: Name: ___real_foo
34+
35+
#--- main.s
36+
.global _entry
37+
_entry:
38+
movl $_foo, %edx
39+
movl $___wrap_foo, %edx
40+
movl $___real_foo, %edx
41+
42+
#--- other.s
43+
.global _foo
44+
.global ___wrap_foo
45+
.global ___real_foo
46+
47+
_foo = 0x11000
48+
___wrap_foo = 0x11010
49+
___real_foo = 0x11020

lld/test/COFF/wrap-import.ll

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// REQUIRES: x86
2+
3+
// Check that wrapping works when the wrapped symbol is imported from a
4+
// different DLL.
5+
6+
// RUN: split-file %s %t.dir
7+
// RUN: llc %t.dir/main.ll -o %t.main.obj --filetype=obj
8+
// RUN: llvm-as %t.dir/main.ll -o %t.main.bc
9+
// RUN: llvm-mc -filetype=obj -triple=x86_64-win32-gnu %t.dir/lib.s -o %t.lib.obj
10+
11+
// RUN: lld-link -dll -out:%t.lib.dll %t.lib.obj -noentry -export:func -implib:%t.lib.lib
12+
// RUN: lld-link -out:%t.exe %t.main.obj %t.lib.lib -entry:entry -subsystem:console -wrap:func
13+
// RUN: lld-link -out:%t.exe %t.main.bc %t.lib.lib -entry:entry -subsystem:console -wrap:func
14+
15+
#--- main.ll
16+
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
17+
target triple = "x86_64-w64-windows-gnu"
18+
19+
declare void @func()
20+
21+
define void @entry() {
22+
call void @func()
23+
ret void
24+
}
25+
26+
declare void @__real_func()
27+
28+
define void @__wrap_func() {
29+
call void @__real_func()
30+
ret void
31+
}
32+
33+
#--- lib.s
34+
.global func
35+
func:
36+
ret

lld/test/COFF/wrap-lto-1.ll

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
; REQUIRES: x86
2+
; LTO
3+
; RUN: llvm-as %s -o %t.obj
4+
; RUN: lld-link -out:%t.exe %t.obj -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps
5+
; RUN: cat %t.exe.resolution.txt | FileCheck -check-prefix=RESOLS %s
6+
7+
; ThinLTO
8+
; RUN: opt -module-summary %s -o %t.obj
9+
; RUN: lld-link -out:%t.exe %t.obj -entry:entry -subsystem:console -wrap:bar -debug:symtab -lldsavetemps
10+
; RUN: cat %t.exe.resolution.txt | FileCheck -check-prefix=RESOLS %s
11+
12+
; Make sure that the 'r' (linker redefined) bit is set for bar and __real_bar
13+
; in the resolutions file. The calls to bar and __real_bar will be routed to
14+
; __wrap_bar and bar, respectively. So they cannot be inlined.
15+
; RESOLS: ,bar,pxr{{$}}
16+
; RESOLS: ,__real_bar,xr{{$}}
17+
; RESOLS: ,__wrap_bar,px{{$}}
18+
19+
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
20+
target triple = "x86_64-w64-windows-gnu"
21+
22+
define void @bar() {
23+
ret void
24+
}
25+
26+
define void @entry() {
27+
call void @bar()
28+
ret void
29+
}
30+
31+
declare void @__real_bar()
32+
33+
define void @__wrap_bar() {
34+
call void @__real_bar()
35+
ret void
36+
}

0 commit comments

Comments
 (0)