Skip to content

Commit

Permalink
[lld][WebAssembly] Ensure stub symbols always get address 0
Browse files Browse the repository at this point in the history
Without this extra flag we can't distingish between stub functions and
functions that happen to have address 0 (relative to __table_base).

Adding this flag bit the base symbol class actually avoids growing the
SymbolUnion struct which would not be true if we added it to the
FunctionSymbol subclass (due to bitbacking).

The previous approach of setting it's table index to zero worked for
normal static relocations but not for `-fPIC` code.

See emscripten-core/emscripten#12819

Differential Revision: https://reviews.llvm.org/D92038
  • Loading branch information
sbc100 committed Nov 26, 2020
1 parent 43afba0 commit 48ddf5e
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 10 deletions.
90 changes: 90 additions & 0 deletions lld/test/wasm/weak-undefined-pic.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Checks handling of undefined weak external functions. When the
# static linker decides they are undefined, check GOT relocations
# resolve to zero (i.e. a global that contains zero.).
#
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
# RUN: wasm-ld %t.o -o %t1.wasm
# RUN: obj2yaml %t1.wasm | FileCheck %s
#
# With `--unresolved-symbols=ignore-all` the behaviour should be the same
# as the default.>
#
# RUN: wasm-ld --unresolved-symbols=ignore-all %t.o -o %t2.wasm
# RUN: obj2yaml %t2.wasm | FileCheck %s

.globl get_foo_addr
get_foo_addr:
.functype get_foo_addr () -> (i32)
global.get foo@GOT
end_function

.globl _start
_start:
.functype _start () -> ()
call get_foo_addr
end_function

.weak foo
.functype foo () -> (i32)

# Verify that we do not generate dynamnic relocations for the GOT entry.

# CHECK-NOT: __wasm_apply_relocs

# Verify that we do not generate an import for foo

# CHECK-NOT: - Type: IMPORT

# CHECK: - Type: GLOBAL
# CHECK-NEXT: Globals:
# CHECK-NEXT: - Index: 0
# CHECK-NEXT: Type: I32
# CHECK-NEXT: Mutable: true
# CHECK-NEXT: InitExpr:
# CHECK-NEXT: Opcode: I32_CONST
# CHECK-NEXT: Value: 66560
# Global 'undefined_weak:foo' representing the GOT entry for foo
# Unlike other internal GOT entries that need to be mutable this one
# is immutable and not updated by `__wasm_apply_relocs`
# CHECK-NEXT: - Index: 1
# CHECK-NEXT: Type: I32
# CHECK-NEXT: Mutable: false
# CHECK-NEXT: InitExpr:
# CHECK-NEXT: Opcode: I32_CONST
# CHECK-NEXT: Value: 0

# CHECK: - Type: CUSTOM
# CHECK-NEXT: Name: name
# CHECK-NEXT: FunctionNames:
# CHECK-NEXT: - Index: 0
# CHECK-NEXT: Name: 'undefined_weak:foo'
# CHECK-NEXT: - Index: 1
# CHECK-NEXT: Name: get_foo_addr
# CHECK-NEXT: - Index: 2
# CHECK-NEXT: Name: _start
# CHECK-NEXT: GlobalNames:
# CHECK-NEXT: - Index: 0
# CHECK-NEXT: Name: __stack_pointer
# CHECK-NEXT: - Index: 1
# CHECK-NEXT: Name: 'undefined_weak:foo'

# With `-pie` or `-shared` the resolution should is defered to the dynamic
# linker and the function address should be imported as GOT.func.foo.
#
# RUN: wasm-ld --experimental-pic -pie %t.o -o %t3.wasm
# RUN: obj2yaml %t3.wasm | FileCheck %s --check-prefix=IMPORT

# IMPORT: - Type: IMPORT
# IMPORT: - Module: GOT.func
# IMPORT-NEXT: Field: foo
# IMPORT-NEXT: Kind: GLOBAL
# IMPORT-NEXT: GlobalType: I32
# IMPORT-NEXT: GlobalMutable: true

# IMPORT: GlobalNames:
# IMPORT-NEXT: - Index: 0
# IMPORT-NEXT: Name: __memory_base
# IMPORT-NEXT: - Index: 1
# IMPORT-NEXT: Name: __table_base
# IMPORT-NEXT: - Index: 2
# IMPORT-NEXT: Name: foo
2 changes: 1 addition & 1 deletion lld/wasm/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,7 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) {
warn(Twine("symbol exported via --export not found: ") + arg->getValue());
}

if (!config->relocatable) {
if (!config->relocatable && !config->isPic) {
// Add synthetic dummies for weak undefined functions. Must happen
// after LTO otherwise functions may not yet have signatures.
symtab->handleWeakUndefines();
Expand Down
2 changes: 1 addition & 1 deletion lld/wasm/MarkLive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ void MarkLive::mark() {
reloc.Type == R_WASM_TABLE_INDEX_I32 ||
reloc.Type == R_WASM_TABLE_INDEX_I64) {
auto *funcSym = cast<FunctionSymbol>(sym);
if (funcSym->hasTableIndex() && funcSym->getTableIndex() == 0)
if (funcSym->isStub)
continue;
}

Expand Down
4 changes: 3 additions & 1 deletion lld/wasm/Relocations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ static void reportUndefined(Symbol *sym) {
<< "ignoring undefined symbol: " + toString(*sym) + "\n");
f->stubFunction = symtab->createUndefinedStub(*f->getSignature());
f->stubFunction->markLive();
f->setTableIndex(0);
// Mark the function itself as a stub which prevents it from being
// assigned a table entry.
f->isStub = true;
}
}
break;
Expand Down
6 changes: 3 additions & 3 deletions lld/wasm/SymbolTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -673,9 +673,9 @@ InputFunction *SymbolTable::replaceWithUnreachable(Symbol *sym,
// to be exported outside the object file.
replaceSymbol<DefinedFunction>(sym, debugName, WASM_SYMBOL_BINDING_LOCAL,
nullptr, func);
// Ensure it compares equal to the null pointer, and so that table relocs
// don't pull in the stub body (only call-operand relocs should do that).
func->setTableIndex(0);
// Ensure the stub function doesn't get a table entry. Its address
// should always compare equal to the null pointer.
sym->isStub = true;
return func;
}

Expand Down
7 changes: 6 additions & 1 deletion lld/wasm/Symbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class Symbol {
Symbol(StringRef name, Kind k, uint32_t flags, InputFile *f)
: name(name), file(f), symbolKind(k), referenced(!config->gcSections),
requiresGOT(false), isUsedInRegularObj(false), forceExport(false),
canInline(false), traced(false), flags(flags) {}
canInline(false), traced(false), isStub(false), flags(flags) {}

StringRef name;
InputFile *file;
Expand Down Expand Up @@ -157,6 +157,11 @@ class Symbol {
// True if this symbol is specified by --trace-symbol option.
bool traced : 1;

// True if this symbol is a linker-synthesized stub function (traps when
// called) and should otherwise be treated as missing/undefined. See
// SymbolTable::replaceWithUndefined.
bool isStub : 1;

uint32_t flags;
};

Expand Down
15 changes: 12 additions & 3 deletions lld/wasm/SyntheticSections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ void GlobalSection::generateRelocationCode(raw_ostream &os) const {
writeU8(os, opcode_ptr_const, "CONST");
writeSleb128(os, d->getVirtualAddress(), "offset");
} else if (auto *f = dyn_cast<FunctionSymbol>(sym)) {
if (f->isStub)
continue;
// Get __table_base
writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
writeUleb128(os, WasmSym::tableBase->getGlobalIndex(), "__table_base");
Expand All @@ -329,12 +331,16 @@ void GlobalSection::writeBody() {
// TODO(wvo): when do these need I64_CONST?
for (const Symbol *sym : internalGotSymbols) {
WasmGlobal global;
global.Type = {WASM_TYPE_I32, config->isPic};
// In the case of dynamic linking, internal GOT entries
// need to be mutable since they get updated to the correct
// runtime value during `__wasm_apply_relocs`.
bool mutable_ = config->isPic & !sym->isStub;
global.Type = {WASM_TYPE_I32, mutable_};
global.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
if (auto *d = dyn_cast<DefinedData>(sym))
global.InitExpr.Value.Int32 = d->getVirtualAddress();
else if (auto *f = dyn_cast<FunctionSymbol>(sym))
global.InitExpr.Value.Int32 = f->getTableIndex();
global.InitExpr.Value.Int32 = f->isStub ? 0 : f->getTableIndex();
else {
assert(isa<UndefinedData>(sym));
global.InitExpr.Value.Int32 = 0;
Expand Down Expand Up @@ -375,7 +381,10 @@ void StartSection::writeBody() {
}

void ElemSection::addEntry(FunctionSymbol *sym) {
if (sym->hasTableIndex())
// Don't add stub functions to the wasm table. The address of all stub
// functions should be zero and they should they don't appear in the table.
// They only exist so that the calls to missing functions can validate.
if (sym->hasTableIndex() || sym->isStub)
return;
sym->setTableIndex(config->tableBase + indirectFunctions.size());
indirectFunctions.emplace_back(sym);
Expand Down

0 comments on commit 48ddf5e

Please sign in to comment.