wasm-linker: basic TLS support

Linker now parses segments with regards to TLS segments. If the name
represents a TLS segment but does not contain the TLS flag, we set it
manually as the object file is created using an older compiler (LLVM).

For now we panic when we find a TLS relocation and implement those
later.
This commit is contained in:
Luuk de Gram
2023-03-12 15:23:58 +01:00
parent 49d37e2d17
commit b0024c4884
5 changed files with 57 additions and 8 deletions

View File

@@ -894,6 +894,13 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
_ = wasm.resolved_symbols.swapRemove(loc);
}
if (!wasm.base.options.shared_memory) {
if (wasm.undefs.fetchSwapRemove("__tls_base")) |kv| {
const loc = try wasm.createSyntheticSymbol("__tls_base", .global);
try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
}
}
}
// Tries to find a global symbol by its name. Returns null when not found,
@@ -2224,6 +2231,18 @@ fn setupMemory(wasm: *Wasm) !void {
while (data_seg_it.next()) |entry| {
const segment = &wasm.segments.items[entry.value_ptr.*];
memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);
// set TLS-related symbols
if (mem.eql(u8, entry.key_ptr.*, ".tdata")) {
if (wasm.findGlobalSymbol("__tls_base")) |loc| {
const sym = loc.getSymbol(wasm);
sym.index = try wasm.globals.append(wasm.base.allocator, wasm.imports.globalCount, .{
.global_type = .{ .valtype = .i32_const, .mutable = false },
.init = .{ .i32_const = @intCast(i32, memory_ptr) },
});
}
}
memory_ptr += segment.size;
segment.offset = offset;
offset += segment.size;

View File

@@ -126,10 +126,12 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
.R_WASM_TABLE_INDEX_SLEB,
.R_WASM_TABLE_NUMBER_LEB,
.R_WASM_TYPE_INDEX_LEB,
.R_WASM_MEMORY_ADDR_TLS_SLEB,
=> leb.writeUnsignedFixed(5, atom.code.items[reloc.offset..][0..5], @intCast(u32, value)),
.R_WASM_MEMORY_ADDR_LEB64,
.R_WASM_MEMORY_ADDR_SLEB64,
.R_WASM_TABLE_INDEX_SLEB64,
.R_WASM_MEMORY_ADDR_TLS_SLEB64,
=> leb.writeUnsignedFixed(10, atom.code.items[reloc.offset..][0..10], value),
}
}
@@ -190,5 +192,10 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
const rel_value = @intCast(i32, target_atom.offset + offset) + relocation.addend;
return @intCast(u32, rel_value);
},
.R_WASM_MEMORY_ADDR_TLS_SLEB,
.R_WASM_MEMORY_ADDR_TLS_SLEB64,
=> {
@panic("TODO: Implement TLS relocations");
},
}
}

View File

@@ -674,6 +674,12 @@ fn Parser(comptime ReaderType: type) type {
segment.alignment,
segment.flags,
});
// support legacy object files that specified being TLS by the name instead of the TLS flag.
if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) {
// set the flag so we can simply check for the flag in the rest of the linker.
segment.flags |= @enumToInt(types.Segment.Flags.WASM_SEG_FLAG_TLS);
}
}
parser.object.segment_info = segments;
},

View File

@@ -90,6 +90,10 @@ pub fn requiresImport(symbol: Symbol) bool {
return true;
}
pub fn isTLS(symbol: Symbol) bool {
return symbol.flags & @enumToInt(Flag.WASM_SYM_TLS) != 0;
}
pub fn hasFlag(symbol: Symbol, flag: Flag) bool {
return symbol.flags & @enumToInt(flag) != 0;
}

View File

@@ -38,6 +38,8 @@ pub const Relocation = struct {
R_WASM_TABLE_INDEX_SLEB64 = 18,
R_WASM_TABLE_INDEX_I64 = 19,
R_WASM_TABLE_NUMBER_LEB = 20,
R_WASM_MEMORY_ADDR_TLS_SLEB = 21,
R_WASM_MEMORY_ADDR_TLS_SLEB64 = 25,
/// Returns true for relocation types where the `addend` field is present.
pub fn addendIsPresent(self: RelocationType) bool {
@@ -125,23 +127,34 @@ pub const Segment = struct {
/// Bitfield containing flags for a segment
flags: u32,
pub fn isTLS(segment: Segment) bool {
return segment.flags & @enumToInt(Flags.WASM_SEG_FLAG_TLS) != 0;
}
/// Returns the name as how it will be output into the final object
/// file or binary. When `merge_segments` is true, this will return the
/// short name. i.e. ".rodata". When false, it returns the entire name instead.
pub fn outputName(self: Segment, merge_segments: bool) []const u8 {
if (std.mem.startsWith(u8, self.name, ".synthetic")) return ".synthetic"; // always merge
if (!merge_segments) return self.name;
if (std.mem.startsWith(u8, self.name, ".rodata.")) {
pub fn outputName(segment: Segment, merge_segments: bool) []const u8 {
if (segment.isTLS()) {
return ".tdata";
} else if (!merge_segments) {
return segment.name;
} else if (std.mem.startsWith(u8, segment.name, ".rodata.")) {
return ".rodata";
} else if (std.mem.startsWith(u8, self.name, ".text.")) {
} else if (std.mem.startsWith(u8, segment.name, ".text.")) {
return ".text";
} else if (std.mem.startsWith(u8, self.name, ".data.")) {
} else if (std.mem.startsWith(u8, segment.name, ".data.")) {
return ".data";
} else if (std.mem.startsWith(u8, self.name, ".bss.")) {
} else if (std.mem.startsWith(u8, segment.name, ".bss.")) {
return ".bss";
}
return self.name;
return segment.name;
}
pub const Flags = enum(u32) {
WASM_SEG_FLAG_STRINGS = 0x1,
WASM_SEG_FLAG_TLS = 0x2,
};
};
pub const InitFunc = struct {