diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index aca6cf958..c565a53c5 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -59,48 +59,48 @@ const RDebug = extern struct { r_ldbase: usize, }; -fn elf_get_va_offset(phdrs: []elf.Phdr) !usize { - for (phdrs) |*phdr| { - if (phdr.p_type == elf.PT_LOAD) { - return @ptrToInt(phdr) - phdr.p_vaddr; - } +// TODO: This should be weak (#1917) +extern var _DYNAMIC: [128]elf.Dyn; + +comptime { + if (builtin.os == .linux) { + asm ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + ); } - return error.InvalidExe; } pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { - const va_offset = try elf_get_va_offset(phdrs); - - const dyn_table = init: { - for (phdrs) |*phdr| { - if (phdr.p_type == elf.PT_DYNAMIC) { - const ptr = @intToPtr([*]elf.Dyn, va_offset + phdr.p_vaddr); - break :init ptr[0 .. phdr.p_memsz / @sizeOf(elf.Dyn)]; - } - } + if (@ptrToInt(&_DYNAMIC[0]) == 0) { // No PT_DYNAMIC means this is either a statically-linked program or a // badly corrupted one return LinkMap.Iterator{ .current = null }; - }; + } const link_map_ptr = init: { - for (dyn_table) |*dyn| { - switch (dyn.d_tag) { + var i: usize = 0; + while (_DYNAMIC[i].d_tag != elf.DT_NULL) : (i += 1) { + switch (_DYNAMIC[i].d_tag) { elf.DT_DEBUG => { - const r_debug = @intToPtr(*RDebug, dyn.d_val); - if (r_debug.r_version != 1) return error.InvalidExe; - break :init r_debug.r_map; + const ptr = @intToPtr(?*RDebug, _DYNAMIC[i].d_val); + if (ptr) |r_debug| { + if (r_debug.r_version != 1) return error.InvalidExe; + break :init r_debug.r_map; + } }, elf.DT_PLTGOT => { - const got_table = @intToPtr([*]usize, dyn.d_val); - // The address to the link_map structure is stored in the - // second slot - break :init @intToPtr(?*LinkMap, got_table[1]); + const ptr = @intToPtr(?[*]usize, _DYNAMIC[i].d_val); + if (ptr) |got_table| { + // The address to the link_map structure is stored in + // the second slot + break :init @intToPtr(?*LinkMap, got_table[1]); + } }, else => {}, } } - return error.InvalidExe; + return LinkMap.Iterator{ .current = null }; }; return LinkMap.Iterator{ .current = link_map_ptr }; diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 983dc6f9f..a28780fd0 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -719,20 +719,48 @@ pub const Elf64_Syminfo = extern struct { pub const Elf32_Rel = extern struct { r_offset: Elf32_Addr, r_info: Elf32_Word, + + pub inline fn r_sym(self: @This()) u24 { + return @truncate(u24, self.r_info >> 8); + } + pub inline fn r_type(self: @This()) u8 { + return @truncate(u8, self.r_info & 0xff); + } }; pub const Elf64_Rel = extern struct { r_offset: Elf64_Addr, r_info: Elf64_Xword, + + pub inline fn r_sym(self: @This()) u32 { + return @truncate(u32, self.r_info >> 32); + } + pub inline fn r_type(self: @This()) u32 { + return @truncate(u32, self.r_info & 0xffffffff); + } }; pub const Elf32_Rela = extern struct { r_offset: Elf32_Addr, r_info: Elf32_Word, r_addend: Elf32_Sword, + + pub inline fn r_sym(self: @This()) u24 { + return @truncate(u24, self.r_info >> 8); + } + pub inline fn r_type(self: @This()) u8 { + return @truncate(u8, self.r_info & 0xff); + } }; pub const Elf64_Rela = extern struct { r_offset: Elf64_Addr, r_info: Elf64_Xword, r_addend: Elf64_Sxword, + + pub inline fn r_sym(self: @This()) u32 { + return @truncate(u32, self.r_info >> 32); + } + pub inline fn r_type(self: @This()) u32 { + return @truncate(u32, self.r_info & 0xffffffff); + } }; pub const Elf32_Dyn = extern struct { d_tag: Elf32_Sword, @@ -917,6 +945,16 @@ pub const Dyn = switch (@sizeOf(usize)) { 8 => Elf64_Dyn, else => @compileError("expected pointer size of 32 or 64"), }; +pub const Rel = switch (@sizeOf(usize)) { + 4 => Elf32_Rel, + 8 => Elf64_Rel, + else => @compileError("expected pointer size of 32 or 64"), +}; +pub const Rela = switch (@sizeOf(usize)) { + 4 => Elf32_Rela, + 8 => Elf64_Rela, + else => @compileError("expected pointer size of 32 or 64"), +}; pub const Shdr = switch (@sizeOf(usize)) { 4 => Elf32_Shdr, 8 => Elf64_Shdr, diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig new file mode 100644 index 000000000..d0ef79e1f --- /dev/null +++ b/lib/std/os/linux/start_pie.zig @@ -0,0 +1,138 @@ +const std = @import("std"); +const elf = std.elf; +const builtin = @import("builtin"); +const assert = std.debug.assert; + +const R_AMD64_RELATIVE = 8; +const R_386_RELATIVE = 8; +const R_ARM_RELATIVE = 23; +const R_AARCH64_RELATIVE = 1027; +const R_RISCV_RELATIVE = 3; + +const ARCH_RELATIVE_RELOC = switch (builtin.arch) { + .i386 => R_386_RELATIVE, + .x86_64 => R_AMD64_RELATIVE, + .arm => R_ARM_RELATIVE, + .aarch64 => R_AARCH64_RELATIVE, + .riscv64 => R_RISCV_RELATIVE, + else => @compileError("unsupported architecture"), +}; + +// Just a convoluted (but necessary) way to obtain the address of the _DYNAMIC[] +// vector as PC-relative so that we can use it before any relocation is applied +fn getDynamicSymbol() [*]elf.Dyn { + const addr = switch (builtin.arch) { + .i386 => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ call 1f + \\ 1: pop %[ret] + \\ lea _DYNAMIC-1b(%[ret]), %[ret] + : [ret] "=r" (-> usize) + ), + .x86_64 => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ lea _DYNAMIC(%%rip), %[ret] + : [ret] "=r" (-> usize) + ), + // Work around the limited offset range of `ldr` + .arm => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ ldr %[ret], 1f + \\ add %[ret], pc + \\ b 2f + \\ 1: .word _DYNAMIC-1b + \\ 2: + : [ret] "=r" (-> usize) + ), + // A simple `adr` is not enough as it has a limited offset range + .aarch64 => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ adrp %[ret], _DYNAMIC + \\ add %[ret], %[ret], #:lo12:_DYNAMIC + : [ret] "=r" (-> usize) + ), + .riscv64 => asm volatile ( + \\ lla %[ret], _DYNAMIC + : [ret] "=r" (-> usize) + ), + else => @compileError("???"), + }; + if (addr == 0) unreachable; + return @intToPtr([*]elf.Dyn, addr); +} + +pub fn apply_relocations() void { + @setRuntimeSafety(false); + + const dynv = getDynamicSymbol(); + const auxv = std.os.linux.elf_aux_maybe.?; + var at_phent: usize = undefined; + var at_phnum: usize = undefined; + var at_phdr: usize = undefined; + var at_hwcap: usize = undefined; + + { + var i: usize = 0; + while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) { + switch (auxv[i].a_type) { + elf.AT_PHENT => at_phent = auxv[i].a_un.a_val, + elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val, + elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val, + else => continue, + } + } + } + + // Sanity check + assert(at_phent == @sizeOf(elf.Phdr)); + + // Search the TLS section + const phdrs = (@intToPtr([*]elf.Phdr, at_phdr))[0..at_phnum]; + + const base_addr = blk: { + for (phdrs) |*phdr| { + if (phdr.p_type == elf.PT_DYNAMIC) { + break :blk @ptrToInt(&dynv[0]) - phdr.p_vaddr; + } + } + unreachable; + }; + + var rel_addr: usize = 0; + var rela_addr: usize = 0; + var rel_size: usize = 0; + var rela_size: usize = 0; + + { + var i: usize = 0; + while (dynv[i].d_tag != elf.DT_NULL) : (i += 1) { + switch (dynv[i].d_tag) { + elf.DT_REL => rel_addr = base_addr + dynv[i].d_un.d_ptr, + elf.DT_RELA => rela_addr = base_addr + dynv[i].d_un.d_ptr, + elf.DT_RELSZ => rel_size = dynv[i].d_un.d_val, + elf.DT_RELASZ => rela_size = dynv[i].d_un.d_val, + else => {}, + } + } + } + + // Perform the relocations + if (rel_addr != 0) { + const rel = @bytesToSlice(elf.Rel, @intToPtr([*]u8, rel_addr)[0..rel_size]); + for (rel) |r| { + if (r.r_type() != ARCH_RELATIVE_RELOC) continue; + @intToPtr(*usize, base_addr + r.r_offset).* += base_addr; + } + } + if (rela_addr != 0) { + const rela = @bytesToSlice(elf.Rela, @intToPtr([*]u8, rela_addr)[0..rela_size]); + for (rela) |r| { + if (r.r_type() != ARCH_RELATIVE_RELOC) continue; + @intToPtr(*usize, base_addr + r.r_offset).* += base_addr + @bitCast(usize, r.r_addend); + } + } +} diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index a7123f9d8..5a6f3bddf 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -386,6 +386,8 @@ fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void { test "dl_iterate_phdr" { if (builtin.os.tag == .windows or builtin.os.tag == .wasi or builtin.os.tag == .macos) return error.SkipZigTest; + if (builtin.position_independent_executable) + return error.SkipZigTest; var counter: usize = 0; try os.dl_iterate_phdr(&counter, IterFnError, iter_fn); diff --git a/lib/std/start.zig b/lib/std/start.zig index de8d03531..6e15dad84 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -202,6 +202,12 @@ fn posixCallMainAndExit() noreturn { // Find the beginning of the auxiliary vector const auxv = @ptrCast([*]std.elf.Auxv, @alignCast(@alignOf(usize), envp.ptr + envp_count + 1)); std.os.linux.elf_aux_maybe = auxv; + + // Do this as early as possible, the aux vector is needed + if (builtin.position_independent_executable) { + @import("os/linux/start_pie.zig").apply_relocations(); + } + // Initialize the TLS area std.os.linux.tls.initStaticTLS(); diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index cc02c4c02..d8eeaf7fa 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -855,6 +855,14 @@ void ZigLLVMAddModuleCodeViewFlag(LLVMModuleRef module) { unwrap(module)->addModuleFlag(Module::Warning, "CodeView", 1); } +void ZigLLVMSetModulePICLevel(LLVMModuleRef module) { + unwrap(module)->setPICLevel(PICLevel::Level::BigPIC); +} + +void ZigLLVMSetModulePIELevel(LLVMModuleRef module) { + unwrap(module)->setPIELevel(PIELevel::Level::Large); +} + static AtomicOrdering mapFromLLVMOrdering(LLVMAtomicOrdering Ordering) { switch (Ordering) { case LLVMAtomicOrderingNotAtomic: return AtomicOrdering::NotAtomic; diff --git a/src/zig_llvm.h b/src/zig_llvm.h index 2500c162e..24ea53395 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -206,6 +206,8 @@ ZIG_EXTERN_C struct ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef modul ZIG_EXTERN_C void ZigLLVMDisposeDIBuilder(struct ZigLLVMDIBuilder *dbuilder); ZIG_EXTERN_C void ZigLLVMAddModuleDebugInfoFlag(LLVMModuleRef module); ZIG_EXTERN_C void ZigLLVMAddModuleCodeViewFlag(LLVMModuleRef module); +ZIG_EXTERN_C void ZigLLVMSetModulePICLevel(LLVMModuleRef module); +ZIG_EXTERN_C void ZigLLVMSetModulePIELevel(LLVMModuleRef module); ZIG_EXTERN_C void ZigLLVMSetCurrentDebugLocation(LLVMBuilderRef builder, int line, int column, struct ZigLLVMDIScope *scope);