stage2: function calls using the global offset table

so far they don't support parameters or return values
This commit is contained in:
Andrew Kelley 2020-05-19 13:51:46 -04:00
parent 8d812dba30
commit 8d3cca7fc2
3 changed files with 85 additions and 167 deletions

View File

@ -19,24 +19,6 @@ pub const Result = union(enum) {
fail: *Module.ErrorMsg,
};
pub fn pltEntrySize(target: Target) u16 {
return switch (target.cpu.arch) {
.i386, .x86_64 => 5,
else => @panic("TODO implement pltEntrySize for more architectures"),
};
}
pub fn writePltEntry(target: Target, buf: []u8, addr: u32) void {
switch (target.cpu.arch) {
.i386, .x86_64 => {
// 9a xx xx xx xx call addr
buf[0] = 0x9a;
mem.writeIntLittle(u32, buf[1..5], addr);
},
else => @panic("TODO implement pltEntrySize for more architectures"),
}
}
pub fn generateSymbol(
bin_file: *link.ElfFile,
src: usize,
@ -221,14 +203,14 @@ const Function = struct {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const func = func_val.func;
const plt_index = func.owner_decl.link.offset_table_index.plt;
const plt = &self.bin_file.program_headers.items[self.bin_file.phdr_got_plt_index.?];
const plt_entry_size = pltEntrySize(self.target.*);
const plt_addr = @intCast(u32, plt.p_vaddr + func.owner_decl.link.offset_table_index.plt * plt_entry_size);
// ea xx xx xx xx jmp addr
try self.code.resize(self.code.items.len + 5);
self.code.items[self.code.items.len - 5] = 0xea;
mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], plt_addr);
const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
const ptr_bits = self.target.cpu.arch.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes);
// ff 14 25 xx xx xx xx call [addr]
try self.code.resize(self.code.items.len + 7);
self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 };
mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], got_addr);
const return_type = func.fn_type.fnReturnType();
switch (return_type.zigTypeTag()) {
.Void => return MCValue{ .none = {} },
@ -606,7 +588,7 @@ const Function = struct {
if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| {
const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
const decl = payload.decl;
const got_addr = got.p_vaddr + decl.link.offset_table_index.got * ptr_bytes;
const got_addr = got.p_vaddr + decl.link.offset_table_index * ptr_bytes;
return MCValue{ .memory = got_addr };
}
return self.fail(src, "TODO codegen more kinds of const pointers", .{});

View File

@ -110,7 +110,6 @@ pub const ElfFile = struct {
/// The index into the program headers of the global offset table.
/// It needs PT_LOAD and Read flags.
phdr_got_index: ?u16 = null,
phdr_got_plt_index: ?u16 = null,
entry_addr: ?u64 = null,
shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){},
@ -119,7 +118,6 @@ pub const ElfFile = struct {
text_section_index: ?u16 = null,
symtab_section_index: ?u16 = null,
got_section_index: ?u16 = null,
got_plt_section_index: ?u16 = null,
/// The same order as in the file. ELF requires global symbols to all be after the
/// local symbols, they cannot be mixed. So we must buffer all the global symbols and
@ -132,16 +130,11 @@ pub const ElfFile = struct {
/// If the vaddr of the executable program header changes, the entire
/// offset table needs to be rewritten.
offset_table: std.ArrayListUnmanaged(u64) = std.ArrayListUnmanaged(u64){},
/// Same order as in the file. The value is the absolute vaddr value.
/// If the vaddr of the executable program header changes, the entire
/// fn trampoline table needs to be rewritten.
fn_trampoline_table: std.ArrayListUnmanaged(u64) = std.ArrayListUnmanaged(u64){},
phdr_table_dirty: bool = false,
shdr_table_dirty: bool = false,
shstrtab_dirty: bool = false,
offset_table_count_dirty: bool = false,
fn_trampoline_table_count_dirty: bool = false,
error_flags: ErrorFlags = ErrorFlags{},
@ -157,18 +150,12 @@ pub const ElfFile = struct {
/// If this field is 0, it means the codegen size = 0 and there is no symbol or
/// offset table entry.
local_sym_index: u32,
/// when size = 0 and there is no offset table index
offset_table_index: union {
unallocated: void,
/// This is an index into offset_table
got: u32,
/// This is an index into fn_trampoline_table
plt: u32,
},
/// This field is undefined for symbols with size = 0.
offset_table_index: u32,
pub const empty = Decl{
.local_sym_index = 0,
.offset_table_index = .{ .unallocated = {} },
.offset_table_index = undefined,
};
};
@ -183,7 +170,6 @@ pub const ElfFile = struct {
self.local_symbols.deinit(self.allocator);
self.global_symbols.deinit(self.allocator);
self.offset_table.deinit(self.allocator);
self.fn_trampoline_table.deinit(self.allocator);
if (self.owns_file_handle) {
if (self.file) |f| f.close();
}
@ -357,30 +343,6 @@ pub const ElfFile = struct {
});
self.phdr_table_dirty = true;
}
if (self.phdr_got_plt_index == null) {
self.phdr_got_plt_index = @intCast(u16, self.program_headers.items.len);
const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint;
// We really only need ptr alignment but since we are using PROGBITS, linux requires
// page align.
const p_align = 0x1000;
const off = self.findFreeSpace(file_size, p_align);
//std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
// TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.
// we'll need to re-use that function anyway, in case the GOT grows and overlaps something
// else in virtual memory.
const default_got_plt_addr = 0x6000000;
try self.program_headers.append(self.allocator, .{
.p_type = elf.PT_LOAD,
.p_offset = off,
.p_filesz = file_size,
.p_vaddr = default_got_plt_addr,
.p_paddr = default_got_plt_addr,
.p_memsz = file_size,
.p_align = p_align,
.p_flags = elf.PF_R,
});
self.phdr_table_dirty = true;
}
if (self.shstrtab_index == null) {
self.shstrtab_index = @intCast(u16, self.sections.items.len);
assert(self.shstrtab.items.len == 0);
@ -438,24 +400,6 @@ pub const ElfFile = struct {
});
self.shdr_table_dirty = true;
}
if (self.got_plt_section_index == null) {
self.got_plt_section_index = @intCast(u16, self.sections.items.len);
const phdr = &self.program_headers.items[self.phdr_got_plt_index.?];
try self.sections.append(self.allocator, .{
.sh_name = try self.makeString(".got.plt"),
.sh_type = elf.SHT_PROGBITS,
.sh_flags = elf.SHF_ALLOC,
.sh_addr = phdr.p_vaddr,
.sh_offset = phdr.p_offset,
.sh_size = phdr.p_filesz,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = phdr.p_align,
.sh_entsize = 0,
});
self.shdr_table_dirty = true;
}
if (self.symtab_section_index == null) {
self.symtab_section_index = @intCast(u16, self.sections.items.len);
const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);
@ -640,7 +584,6 @@ pub const ElfFile = struct {
assert(!self.shdr_table_dirty);
assert(!self.shstrtab_dirty);
assert(!self.offset_table_count_dirty);
assert(!self.fn_trampoline_table_count_dirty);
const syms_sect = &self.sections.items[self.symtab_section_index.?];
assert(syms_sect.sh_info == self.local_symbols.items.len);
}
@ -836,14 +779,10 @@ pub const ElfFile = struct {
pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void {
if (decl.link.local_sym_index != 0) return;
const is_fn = (decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1);
try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1);
try self.fn_trampoline_table.ensureCapacity(self.allocator, self.fn_trampoline_table.items.len + 1);
const local_sym_index = self.local_symbols.items.len;
const offset_table_index = self.offset_table.items.len;
const fn_trampoline_table_index = self.fn_trampoline_table.items.len;
const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
self.local_symbols.appendAssumeCapacity(.{
@ -854,20 +793,15 @@ pub const ElfFile = struct {
.st_value = phdr.p_vaddr,
.st_size = 0,
});
if (is_fn) {
self.fn_trampoline_table.appendAssumeCapacity(0);
self.fn_trampoline_table_count_dirty = true;
} else {
self.offset_table.appendAssumeCapacity(0);
self.offset_table_count_dirty = true;
}
errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1);
self.offset_table.appendAssumeCapacity(0);
errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1);
self.offset_table_count_dirty = true;
decl.link = .{
.local_sym_index = @intCast(u32, local_sym_index),
.offset_table_index = if (is_fn)
.{ .plt = @intCast(u32, fn_trampoline_table_index) }
else
.{ .got = @intCast(u32, offset_table_index) },
.offset_table_index = @intCast(u32, offset_table_index),
};
}
@ -885,7 +819,6 @@ pub const ElfFile = struct {
return;
},
};
const is_fn = (typed_value.ty.zigTypeTag() == .Fn);
const required_alignment = typed_value.ty.abiAlignment(self.options.target);
@ -905,13 +838,14 @@ pub const ElfFile = struct {
const file_offset = if (need_realloc) fo: {
const new_block = try self.allocateTextBlock(code.len, required_alignment);
local_sym.st_value = new_block.vaddr;
if (is_fn) {
self.fn_trampoline_table.items[decl.link.offset_table_index.plt] = new_block.vaddr;
try self.writeFnTrampolineEntry(decl.link.offset_table_index.plt);
} else {
self.offset_table.items[decl.link.offset_table_index.got] = new_block.vaddr;
try self.writeOffsetTableEntry(decl.link.offset_table_index.got);
}
self.offset_table.items[decl.link.offset_table_index] = new_block.vaddr;
//std.debug.warn("{}: writing got index {}=0x{x}\n", .{
// decl.name,
// decl.link.offset_table_index,
// self.offset_table.items[decl.link.offset_table_index],
//});
try self.writeOffsetTableEntry(decl.link.offset_table_index);
break :fo new_block.file_offset;
} else existing_block.file_offset;
@ -928,13 +862,11 @@ pub const ElfFile = struct {
} else {
try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1);
try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1);
try self.fn_trampoline_table.ensureCapacity(self.allocator, self.fn_trampoline_table.items.len + 1);
const decl_name = mem.spanZ(decl.name);
const name_str_index = try self.makeString(decl_name);
const new_block = try self.allocateTextBlock(code.len, required_alignment);
const local_sym_index = self.local_symbols.items.len;
const offset_table_index = self.offset_table.items.len;
const fn_trampoline_table_index = self.fn_trampoline_table.items.len;
//std.debug.warn("add symbol for {} at vaddr 0x{x}, size {}\n", .{ decl.name, new_block.vaddr, code.len });
self.local_symbols.appendAssumeCapacity(.{
@ -946,32 +878,17 @@ pub const ElfFile = struct {
.st_size = code.len,
});
errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1);
if (is_fn) {
self.fn_trampoline_table.appendAssumeCapacity(new_block.vaddr);
} else {
self.offset_table.appendAssumeCapacity(new_block.vaddr);
}
errdefer if (is_fn) {
self.fn_trampoline_table.shrink(self.allocator, self.fn_trampoline_table.items.len - 1);
} else {
self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1);
};
self.offset_table.appendAssumeCapacity(new_block.vaddr);
errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1);
self.offset_table_count_dirty = true;
try self.writeSymbol(local_sym_index);
if (is_fn) {
try self.writeFnTrampolineEntry(fn_trampoline_table_index);
self.fn_trampoline_table_count_dirty = true;
} else {
try self.writeOffsetTableEntry(offset_table_index);
self.offset_table_count_dirty = true;
}
try self.writeOffsetTableEntry(offset_table_index);
decl.link = .{
.local_sym_index = @intCast(u32, local_sym_index),
.offset_table_index = if (is_fn)
.{ .plt = @intCast(u32, fn_trampoline_table_index) }
else
.{ .got = @intCast(u32, offset_table_index) },
.offset_table_index = @intCast(u32, offset_table_index),
};
//std.debug.warn("writing new {} at vaddr 0x{x}\n", .{ decl.name, new_block.vaddr });
@ -1101,40 +1018,6 @@ pub const ElfFile = struct {
}
}
fn writeFnTrampolineEntry(self: *ElfFile, index: usize) !void {
const shdr = &self.sections.items[self.got_plt_section_index.?];
const phdr = &self.program_headers.items[self.phdr_got_plt_index.?];
const entry_size = codegen.pltEntrySize(self.options.target);
var entry_buf: [16]u8 = undefined;
assert(entry_size <= entry_buf.len);
if (self.fn_trampoline_table_count_dirty) {
// TODO Also detect virtual address collisions.
const allocated_size = self.allocatedSize(shdr.sh_offset);
const needed_size = self.local_symbols.items.len * entry_size;
if (needed_size > allocated_size) {
// Must move the entire .got.plt section.
const new_offset = self.findFreeSpace(needed_size, entry_size);
const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, shdr.sh_size);
if (amt != shdr.sh_size) return error.InputOutput;
shdr.sh_offset = new_offset;
phdr.p_offset = new_offset;
}
shdr.sh_size = needed_size;
phdr.p_memsz = needed_size;
phdr.p_filesz = needed_size;
self.shdr_table_dirty = true; // TODO look into making only the one section dirty
self.phdr_table_dirty = true; // TODO look into making only the one program header dirty
self.fn_trampoline_table_count_dirty = false;
}
const off = shdr.sh_offset + @as(u64, entry_size) * index;
const vaddr = @intCast(u32, self.fn_trampoline_table.items[index]);
codegen.writePltEntry(self.options.target, &entry_buf, vaddr);
try self.file.?.pwriteAll(entry_buf[0..entry_size], off);
}
fn writeOffsetTableEntry(self: *ElfFile, index: usize) !void {
const shdr = &self.sections.items[self.got_section_index.?];
const phdr = &self.program_headers.items[self.phdr_got_index.?];

View File

@ -209,4 +209,57 @@ pub fn addCases(ctx: *TestContext) void {
\\
},
);
ctx.addZIRCompareOutput(
"function call with no args no return value",
&[_][]const u8{
\\@noreturn = primitive(noreturn)
\\@void = primitive(void)
\\@usize = primitive(usize)
\\@0 = int(0)
\\@1 = int(1)
\\@2 = int(2)
\\@3 = int(3)
\\
\\@syscall_array = str("syscall")
\\@sysoutreg_array = str("={rax}")
\\@rax_array = str("{rax}")
\\@rdi_array = str("{rdi}")
\\@rcx_array = str("rcx")
\\@r11_array = str("r11")
\\@memory_array = str("memory")
\\
\\@exit0_fnty = fntype([], @noreturn)
\\@exit0 = fn(@exit0_fnty, {
\\ %SYS_exit_group = int(231)
\\ %exit_code = as(@usize, @0)
\\
\\ %syscall = ref(@syscall_array)
\\ %sysoutreg = ref(@sysoutreg_array)
\\ %rax = ref(@rax_array)
\\ %rdi = ref(@rdi_array)
\\ %rcx = ref(@rcx_array)
\\ %r11 = ref(@r11_array)
\\ %memory = ref(@memory_array)
\\
\\ %rc = asm(%syscall, @usize,
\\ volatile=1,
\\ output=%sysoutreg,
\\ inputs=[%rax, %rdi],
\\ clobbers=[%rcx, %r11, %memory],
\\ args=[%SYS_exit_group, %exit_code])
\\
\\ %99 = unreachable()
\\});
\\
\\@start_fnty = fntype([], @noreturn, cc=Naked)
\\@start = fn(@start_fnty, {
\\ %0 = call(@exit0, [])
\\})
\\@9 = str("_start")
\\@10 = ref(@9)
\\@11 = export(@10, @start)
},
&[_][]const u8{""},
);
}