[WebAssembly] Apply data relocations at runtime in shared objects

See: https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md

Data section relocations in wasm shared libraries are applied by the
library itself at static constructor time.  This change adds a new
synthetic function that applies relocations to relevant memory locations
on startup.

Differential Revision: https://reviews.llvm.org/D59278

llvm-svn: 357715
This commit is contained in:
Sam Clegg 2019-04-04 18:40:51 +00:00
parent 41fe3a54c2
commit 09137be7f8
11 changed files with 192 additions and 43 deletions

View File

@ -9,6 +9,9 @@ target triple = "wasm32-unknown-unknown"
@indirect_func = local_unnamed_addr global i32 ()* @foo, align 4
@indirect_func_external = local_unnamed_addr global void ()* @func_external, align 4
@data_addr = local_unnamed_addr global i32* @data, align 4
@data_addr_external = local_unnamed_addr global i32* @data_external, align 4
define default i32 @foo() {
entry:
; To ensure we use __stack_pointer
@ -36,7 +39,7 @@ declare void @func_external()
; CHECK: Sections:
; CHECK-NEXT: - Type: CUSTOM
; CHECK-NEXT: Name: dylink
; CHECK-NEXT: MemorySize: 12
; CHECK-NEXT: MemorySize: 20
; CHECK-NEXT: MemoryAlignment: 2
; CHECK-NEXT: TableSize: 2
; CHECK-NEXT: TableAlignment: 0
@ -90,6 +93,12 @@ declare void @func_external()
; CHECK-NEXT: GlobalMutable: true
; CHECK-NEXT: - Type: FUNCTION
; CHECK: - Type: EXPORT
; CHECK-NEXT: Exports:
; CHECK-NEXT: - Name: __wasm_call_ctors
; CHECK-NEXT: Kind: FUNCTION
; CHECK-NEXT: Index: 1
; check for elem segment initialized with __table_base global as offset
; CHECK: - Type: ELEM
@ -97,7 +106,19 @@ declare void @func_external()
; CHECK-NEXT: - Offset:
; CHECK-NEXT: Opcode: GLOBAL_GET
; CHECK-NEXT: Index: 2
; CHECK-NEXT: Functions: [ 1, 0 ]
; CHECK-NEXT: Functions: [ 3, 0 ]
; check the generated code in __wasm_call_ctors and __wasm_apply_relocs functions
; TODO(sbc): Disassemble and verify instructions.
; CHECK: - Type: CODE
; CHECK-NEXT: Functions:
; CHECK-NEXT: - Index: 1
; CHECK-NEXT: Locals: []
; CHECK-NEXT: Body: 10020B
; CHECK-NEXT: - Index: 2
; CHECK-NEXT: Locals: []
; CHECK-NEXT: Body: 230141046A230241006A360200230141086A230241016A3602002301410C6A230141006A360200230141106A23033602000B
; check the data segment initialized with __memory_base global as offset
@ -108,4 +129,4 @@ declare void @func_external()
; CHECK-NEXT: Offset:
; CHECK-NEXT: Opcode: GLOBAL_GET
; CHECK-NEXT: Index: 1
; CHECK-NEXT: Content: '020000000000000001000000'
; CHECK-NEXT: Content: '0200000000000000010000000000000000000000'

View File

@ -1,6 +1,6 @@
; RUN: llc -filetype=obj %s -o %t.o
; RUN: not wasm-ld -o %t.wasm %t.o 2>&1 | FileCheck %s -check-prefix=UNDEF
; RUN: not wasm-ld --allow-undefined -o %t.wasm %t.o 2>&1 | FileCheck %s -check-prefix=BADRELOC
; RUN: not wasm-ld --shared -o %t.wasm %t.o 2>&1 | FileCheck %s -check-prefix=BADRELOC
target triple = "wasm32-unknown-unknown"

View File

@ -278,10 +278,15 @@ void LinkerDriver::createFiles(opt::InputArgList &Args) {
}
}
static StringRef getEntry(opt::InputArgList &Args, StringRef Default) {
static StringRef getEntry(opt::InputArgList &Args) {
auto *Arg = Args.getLastArg(OPT_entry, OPT_no_entry);
if (!Arg)
return Default;
if (!Arg) {
if (Args.hasArg(OPT_relocatable))
return "";
if (Args.hasArg(OPT_shared))
return "__wasm_call_ctors";
return "_start";
}
if (Arg->getOption().getID() == OPT_no_entry)
return "";
return Arg->getValue();
@ -298,7 +303,7 @@ static void setConfigs(opt::InputArgList &Args) {
Config->CompressRelocations = Args.hasArg(OPT_compress_relocations);
Config->Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true);
Config->DisableVerify = Args.hasArg(OPT_disable_verify);
Config->Entry = getEntry(Args, Args.hasArg(OPT_relocatable) ? "" : "_start");
Config->Entry = getEntry(Args);
Config->ExportAll = Args.hasArg(OPT_export_all);
Config->ExportDynamic = Args.hasFlag(OPT_export_dynamic,
OPT_no_export_dynamic, false);
@ -422,11 +427,21 @@ static void createSyntheticSymbols() {
static llvm::wasm::WasmGlobalType MutableGlobalTypeI32 = {WASM_TYPE_I32,
true};
if (!Config->Relocatable)
if (!Config->Relocatable) {
WasmSym::CallCtors = Symtab->addSyntheticFunction(
"__wasm_call_ctors", WASM_SYMBOL_VISIBILITY_HIDDEN,
make<SyntheticFunction>(NullSignature, "__wasm_call_ctors"));
if (Config->Pic) {
// For PIC code we create a synthetic function call __wasm_apply_relocs
// and add this as the first call in __wasm_call_ctors.
// We also unconditionally export
WasmSym::ApplyRelocs = Symtab->addSyntheticFunction(
"__wasm_apply_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
make<SyntheticFunction>(NullSignature, "__wasm_apply_relocs"));
}
}
// The __stack_pointer is imported in the shared library case, and exported
// in the non-shared (executable) case.
if (Config->Shared) {
@ -518,12 +533,6 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
Config->ImportTable = true;
}
if (Config->Shared) {
Config->ImportMemory = true;
Config->ExportDynamic = true;
Config->AllowUndefined = true;
}
// Handle --trace-symbol.
for (auto *Arg : Args.filtered(OPT_trace_symbol))
Symtab->trace(Arg->getValue());
@ -531,6 +540,12 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
if (!Config->Relocatable)
createSyntheticSymbols();
if (Config->Shared) {
Config->ImportMemory = true;
Config->ExportDynamic = true;
Config->AllowUndefined = true;
}
createFiles(Args);
if (errorCount())
return;
@ -547,15 +562,13 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
handleUndefined(Arg->getValue());
Symbol *EntrySym = nullptr;
if (!Config->Relocatable) {
if (!Config->Shared && !Config->Entry.empty()) {
EntrySym = handleUndefined(Config->Entry);
if (EntrySym && EntrySym->isDefined())
EntrySym->ForceExport = true;
else
error("entry symbol not defined (pass --no-entry to supress): " +
Config->Entry);
}
if (!Config->Relocatable && !Config->Entry.empty()) {
EntrySym = handleUndefined(Config->Entry);
if (EntrySym && EntrySym->isDefined())
EntrySym->ForceExport = true;
else
error("entry symbol not defined (pass --no-entry to supress): " +
Config->Entry);
}
if (errorCount())

View File

@ -296,3 +296,58 @@ void InputFunction::writeTo(uint8_t *Buf) const {
memcpy(Buf, LastRelocEnd, ChunkSize);
LLVM_DEBUG(dbgs() << " total: " << (Buf + ChunkSize - Orig) << "\n");
}
// Generate code to apply relocations to the data section at runtime.
// This is only called when generating shared libaries (PIC) where address are
// not known at static link time.
void InputSegment::generateRelocationCode(raw_ostream &OS) const {
uint32_t SegmentVA = OutputSeg->StartVA + OutputSegmentOffset;
for (const WasmRelocation &Rel : Relocations) {
uint32_t Offset = Rel.Offset - getInputSectionOffset();
uint32_t OutputVA = SegmentVA + Offset;
// Get __memory_base
writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
writeUleb128(OS, WasmSym::MemoryBase->getGlobalIndex(), "memory_base");
// Add the offset of the relocation
writeU8(OS, WASM_OPCODE_I32_CONST, "I32_CONST");
writeSleb128(OS, OutputVA, "offset");
writeU8(OS, WASM_OPCODE_I32_ADD, "ADD");
// Now figure out what we want to store
switch (Rel.Type) {
case R_WASM_TABLE_INDEX_I32:
// Add the table index to the __table_base
writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
writeUleb128(OS, WasmSym::TableBase->getGlobalIndex(), "table_base");
writeU8(OS, WASM_OPCODE_I32_CONST, "CONST");
writeSleb128(OS, File->calcNewValue(Rel), "new table index");
writeU8(OS, WASM_OPCODE_I32_ADD, "ADD");
break;
case R_WASM_MEMORY_ADDR_I32: {
Symbol *Sym = File->getSymbol(Rel);
if (Sym->isUndefined()) {
// Undefined addresses are accessed via imported GOT globals
writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
writeUleb128(OS, Sym->getGOTIndex(), "global index");
} else {
// Defined global data is accessed via known offset from __memory_base
writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
writeUleb128(OS, WasmSym::MemoryBase->getGlobalIndex(), "memory_base");
writeU8(OS, WASM_OPCODE_I32_CONST, "CONST");
writeSleb128(OS, File->calcNewValue(Rel), "new memory offset");
writeU8(OS, WASM_OPCODE_I32_ADD, "ADD");
}
break;
}
default:
llvm_unreachable("unexpected relocation type in data segment");
}
// Store that value at the virtual address
writeU8(OS, WASM_OPCODE_I32_STORE, "I32_STORE");
writeUleb128(OS, 2, "align");
writeUleb128(OS, 0, "offset");
}
}

View File

@ -92,6 +92,8 @@ public:
static bool classof(const InputChunk *C) { return C->kind() == DataSegment; }
void generateRelocationCode(raw_ostream &OS) const;
uint32_t getAlignment() const { return Segment.Data.Alignment; }
StringRef getName() const override { return Segment.Data.Name; }
StringRef getDebugName() const override { return StringRef(); }

View File

@ -96,6 +96,9 @@ public:
uint32_t calcNewValue(const WasmRelocation &Reloc) const;
uint32_t calcNewAddend(const WasmRelocation &Reloc) const;
uint32_t calcExpectedValue(const WasmRelocation &Reloc) const;
Symbol *getSymbol(const WasmRelocation &Reloc) const {
return Symbols[Reloc.Index];
};
const WasmSection *CodeSection = nullptr;
const WasmSection *DataSection = nullptr;

View File

@ -76,6 +76,11 @@ void lld::wasm::markLive() {
}
}
if (Config->Pic) {
Enqueue(WasmSym::CallCtors);
Enqueue(WasmSym::ApplyRelocs);
}
// Follow relocations to mark all reachable chunks.
while (!Q.empty()) {
InputChunk *C = Q.pop_back_val();

View File

@ -24,6 +24,7 @@ using namespace lld;
using namespace lld::wasm;
DefinedFunction *WasmSym::CallCtors;
DefinedFunction *WasmSym::ApplyRelocs;
DefinedData *WasmSym::DsoHandle;
DefinedData *WasmSym::DataEnd;
DefinedData *WasmSym::HeapBase;

View File

@ -406,6 +406,10 @@ struct WasmSym {
// Function that directly calls all ctors in priority order.
static DefinedFunction *CallCtors;
// __wasm_apply_relocs
// Function that applies relocations to data segment post-instantiation.
static DefinedFunction *ApplyRelocs;
// __dso_handle
// Symbol used in calls to __cxa_atexit to determine current DLL
static DefinedData *DsoHandle;

View File

@ -65,7 +65,9 @@ private:
uint32_t lookupType(const WasmSignature &Sig);
uint32_t registerType(const WasmSignature &Sig);
void createCtorFunction();
void createApplyRelocationsFunction();
void createCallCtorsFunction();
void calculateInitFunctions();
void processRelocations(InputChunk *Chunk);
void assignIndexes();
@ -1149,17 +1151,6 @@ void Writer::processRelocations(InputChunk *Chunk) {
File->TypeMap[Reloc.Index] = registerType(Types[Reloc.Index]);
File->TypeIsUsed[Reloc.Index] = true;
break;
case R_WASM_MEMORY_ADDR_SLEB:
case R_WASM_MEMORY_ADDR_I32:
case R_WASM_MEMORY_ADDR_LEB: {
DataSymbol *Sym = File->getDataSymbol(Reloc.Index);
if (!Config->Relocatable && !isa<DefinedData>(Sym) && !Sym->isWeak())
error(File->getName() + ": relocation " +
relocTypeToString(Reloc.Type) + " cannot be used againt symbol " +
Sym->getName() + "; recompile with -fPIC");
break;
}
case R_WASM_GLOBAL_INDEX_LEB: {
auto* Sym = File->getSymbols()[Reloc.Index];
if (!isa<GlobalSymbol>(Sym) && !Sym->isInGOT()) {
@ -1168,7 +1159,24 @@ void Writer::processRelocations(InputChunk *Chunk) {
}
}
}
if (Config->Pic) {
// Certain relocation types can't be used when building PIC output, since
// they would require absolute symbol addresses at link time.
switch (Reloc.Type) {
case R_WASM_TABLE_INDEX_SLEB:
case R_WASM_MEMORY_ADDR_SLEB:
case R_WASM_MEMORY_ADDR_LEB: {
Symbol *Sym = File->getSymbols()[Reloc.Index];
error(toString(File) + ": relocation " +
relocTypeToString(Reloc.Type) + " cannot be used againt symbol " +
toString(*Sym) + "; recompile with -fPIC");
break;
}
}
}
}
}
void Writer::assignIndexes() {
@ -1272,12 +1280,38 @@ void Writer::createOutputSegments() {
}
}
static const int OPCODE_CALL = 0x10;
static const int OPCODE_END = 0xb;
// For -shared (PIC) output, we create create a synthetic function which will
// apply any relocations to the data segments on startup. This function is
// called __wasm_apply_relocs and is added at the very beginning of
// __wasm_call_ctors before any of the constructors run.
void Writer::createApplyRelocationsFunction() {
LLVM_DEBUG(dbgs() << "createApplyRelocationsFunction\n");
// First write the body's contents to a string.
std::string BodyContent;
{
raw_string_ostream OS(BodyContent);
writeUleb128(OS, 0, "num locals");
for (const OutputSegment *Seg : Segments)
for (const InputSegment *InSeg : Seg->InputSegments)
InSeg->generateRelocationCode(OS);
writeU8(OS, WASM_OPCODE_END, "END");
}
// Once we know the size of the body we can create the final function body
std::string FunctionBody;
{
raw_string_ostream OS(FunctionBody);
writeUleb128(OS, BodyContent.size(), "function size");
OS << BodyContent;
}
ArrayRef<uint8_t> Body = arrayRefFromStringRef(Saver.save(FunctionBody));
cast<SyntheticFunction>(WasmSym::ApplyRelocs->Function)->setBody(Body);
}
// Create synthetic "__wasm_call_ctors" function based on ctor functions
// in input object.
void Writer::createCtorFunction() {
void Writer::createCallCtorsFunction() {
if (!WasmSym::CallCtors->isLive())
return;
@ -1286,11 +1320,16 @@ void Writer::createCtorFunction() {
{
raw_string_ostream OS(BodyContent);
writeUleb128(OS, 0, "num locals");
if (Config->Pic) {
writeU8(OS, WASM_OPCODE_CALL, "CALL");
writeUleb128(OS, WasmSym::ApplyRelocs->getFunctionIndex(),
"function index");
}
for (const WasmInitEntry &F : InitFunctions) {
writeU8(OS, OPCODE_CALL, "CALL");
writeU8(OS, WASM_OPCODE_CALL, "CALL");
writeUleb128(OS, F.Sym->getFunctionIndex(), "function index");
}
writeU8(OS, OPCODE_END, "END");
writeU8(OS, WASM_OPCODE_END, "END");
}
// Once we know the size of the body we can create the final function body
@ -1348,12 +1387,15 @@ void Writer::run() {
assignIndexes();
log("-- calculateInitFunctions");
calculateInitFunctions();
if (!Config->Relocatable)
createCtorFunction();
log("-- calculateTypes");
calculateTypes();
log("-- layoutMemory");
layoutMemory();
if (!Config->Relocatable) {
if (Config->Pic)
createApplyRelocationsFunction();
createCallCtorsFunction();
}
log("-- calculateExports");
calculateExports();
log("-- calculateCustomSections");

View File

@ -241,11 +241,14 @@ enum : unsigned {
// Opcodes used in initializer expressions.
enum : unsigned {
WASM_OPCODE_END = 0x0b,
WASM_OPCODE_CALL = 0x10,
WASM_OPCODE_GLOBAL_GET = 0x23,
WASM_OPCODE_I32_STORE = 0x36,
WASM_OPCODE_I32_CONST = 0x41,
WASM_OPCODE_I64_CONST = 0x42,
WASM_OPCODE_F32_CONST = 0x43,
WASM_OPCODE_F64_CONST = 0x44,
WASM_OPCODE_I32_ADD = 0x6a,
};
enum : unsigned {