forked from OSchip/llvm-project
[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:
parent
41fe3a54c2
commit
09137be7f8
|
@ -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'
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue