From 8d3cca7fc2e7b34486cc56951bb0dadb75e498a0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 May 2020 13:51:46 -0400 Subject: [PATCH] stage2: function calls using the global offset table so far they don't support parameters or return values --- src-self-hosted/codegen.zig | 36 ++------ src-self-hosted/link.zig | 163 +++++------------------------------- test/stage2/zir.zig | 53 ++++++++++++ 3 files changed, 85 insertions(+), 167 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 6d0c0cee7e..ca029cab80 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -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", .{}); diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 9f376252d3..7dfe2ed5e1 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -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.?]; diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index afee3e7895..d06f38d0ad 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -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{""}, + ); }