Skip to content

Commit 333eec5

Browse files
committed
Initial support for static PIE executables
1 parent 4d54e9a commit 333eec5

12 files changed

+296
-43
lines changed

lib/std/dynamic_library.zig

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub const DynLib = switch (builtin.os) {
2424
// fashion.
2525
const LinkMap = extern struct {
2626
l_addr: usize,
27-
l_name: [*]const u8,
27+
l_name: [*:0]const u8,
2828
l_ld: ?*elf.Dyn,
2929
l_next: ?*LinkMap,
3030
l_prev: ?*LinkMap,
@@ -53,48 +53,48 @@ const RDebug = extern struct {
5353
r_ldbase: usize,
5454
};
5555

56-
fn elf_get_va_offset(phdrs: []elf.Phdr) !usize {
57-
for (phdrs) |*phdr| {
58-
if (phdr.p_type == elf.PT_LOAD) {
59-
return @ptrToInt(phdr) - phdr.p_vaddr;
60-
}
56+
// XXX: This should be weak (#1917)
57+
extern var _DYNAMIC: [128]elf.Dyn;
58+
59+
comptime {
60+
if (builtin.os == .linux) {
61+
asm (
62+
\\ .weak _DYNAMIC
63+
\\ .hidden _DYNAMIC
64+
);
6165
}
62-
return error.InvalidExe;
6366
}
6467

6568
pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator {
66-
const va_offset = try elf_get_va_offset(phdrs);
67-
68-
const dyn_table = init: {
69-
for (phdrs) |*phdr| {
70-
if (phdr.p_type == elf.PT_DYNAMIC) {
71-
const ptr = @intToPtr([*]elf.Dyn, va_offset + phdr.p_vaddr);
72-
break :init ptr[0 .. phdr.p_memsz / @sizeOf(elf.Dyn)];
73-
}
74-
}
69+
if (@ptrToInt(&_DYNAMIC[0]) == 0) {
7570
// No PT_DYNAMIC means this is either a statically-linked program or a
7671
// badly corrupted one
7772
return LinkMap.Iterator{ .current = null };
78-
};
73+
}
7974

8075
const link_map_ptr = init: {
81-
for (dyn_table) |*dyn| {
82-
switch (dyn.d_tag) {
76+
var i: usize = 0;
77+
while (_DYNAMIC[i].d_tag != elf.DT_NULL) : (i += 1) {
78+
switch (_DYNAMIC[i].d_tag) {
8379
elf.DT_DEBUG => {
84-
const r_debug = @intToPtr(*RDebug, dyn.d_un.d_ptr);
85-
if (r_debug.r_version != 1) return error.InvalidExe;
86-
break :init r_debug.r_map;
80+
const ptr = @intToPtr(?*RDebug, _DYNAMIC[i].d_un.d_ptr);
81+
if (ptr) |r_debug| {
82+
if (r_debug.r_version != 1) return error.InvalidExe;
83+
break :init r_debug.r_map;
84+
}
8785
},
8886
elf.DT_PLTGOT => {
89-
const got_table = @intToPtr([*]usize, dyn.d_un.d_ptr);
90-
// The address to the link_map structure is stored in the
91-
// second slot
92-
break :init @intToPtr(?*LinkMap, got_table[1]);
87+
const ptr = @intToPtr(?[*]usize, _DYNAMIC[i].d_un.d_ptr);
88+
if (ptr) |got_table| {
89+
// The address to the link_map structure is stored in
90+
// the second slot
91+
break :init @intToPtr(?*LinkMap, got_table[1]);
92+
}
9393
},
9494
else => {},
9595
}
9696
}
97-
return error.InvalidExe;
97+
return LinkMap.Iterator{ .current = null };
9898
};
9999

100100
return LinkMap.Iterator{ .current = link_map_ptr };

lib/std/elf.zig

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,20 +640,48 @@ pub const Elf64_Syminfo = extern struct {
640640
pub const Elf32_Rel = extern struct {
641641
r_offset: Elf32_Addr,
642642
r_info: Elf32_Word,
643+
644+
pub inline fn r_sym(self: @This()) u24 {
645+
return @truncate(u24, self.r_info >> 8);
646+
}
647+
pub inline fn r_type(self: @This()) u8 {
648+
return @truncate(u8, self.r_info & 0xff);
649+
}
643650
};
644651
pub const Elf64_Rel = extern struct {
645652
r_offset: Elf64_Addr,
646653
r_info: Elf64_Xword,
654+
655+
pub inline fn r_sym(self: @This()) u32 {
656+
return @truncate(u32, self.r_info >> 32);
657+
}
658+
pub inline fn r_type(self: @This()) u32 {
659+
return @truncate(u32, self.r_info & 0xffffffff);
660+
}
647661
};
648662
pub const Elf32_Rela = extern struct {
649663
r_offset: Elf32_Addr,
650664
r_info: Elf32_Word,
651665
r_addend: Elf32_Sword,
666+
667+
pub inline fn r_sym(self: @This()) u24 {
668+
return @truncate(u24, self.r_info >> 8);
669+
}
670+
pub inline fn r_type(self: @This()) u8 {
671+
return @truncate(u8, self.r_info & 0xff);
672+
}
652673
};
653674
pub const Elf64_Rela = extern struct {
654675
r_offset: Elf64_Addr,
655676
r_info: Elf64_Xword,
656677
r_addend: Elf64_Sxword,
678+
679+
pub inline fn r_sym(self: @This()) u32 {
680+
return @truncate(u32, self.r_info >> 32);
681+
}
682+
pub inline fn r_type(self: @This()) u32 {
683+
return @truncate(u32, self.r_info & 0xffffffff);
684+
}
657685
};
658686
pub const Elf32_Phdr = extern struct {
659687
p_type: Elf32_Word,
@@ -853,6 +881,16 @@ pub const Dyn = switch (@sizeOf(usize)) {
853881
8 => Elf64_Dyn,
854882
else => @compileError("expected pointer size of 32 or 64"),
855883
};
884+
pub const Rel = switch (@sizeOf(usize)) {
885+
4 => Elf32_Rel,
886+
8 => Elf64_Rel,
887+
else => @compileError("expected pointer size of 32 or 64"),
888+
};
889+
pub const Rela = switch (@sizeOf(usize)) {
890+
4 => Elf32_Rela,
891+
8 => Elf64_Rela,
892+
else => @compileError("expected pointer size of 32 or 64"),
893+
};
856894
pub const Shdr = switch (@sizeOf(usize)) {
857895
4 => Elf32_Shdr,
858896
8 => Elf64_Shdr,

lib/std/os/bits/linux.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,7 @@ pub const dirent64 = extern struct {
996996

997997
pub const dl_phdr_info = extern struct {
998998
dlpi_addr: usize,
999-
dlpi_name: ?[*]const u8,
999+
dlpi_name: ?[*:0]const u8,
10001000
dlpi_phdr: [*]std.elf.Phdr,
10011001
dlpi_phnum: u16,
10021002
};

lib/std/os/linux/start_pie.zig

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
const std = @import("std");
2+
const elf = std.elf;
3+
const builtin = @import("builtin");
4+
const assert = std.debug.assert;
5+
6+
const R_AMD64_RELATIVE = 8;
7+
const R_386_RELATIVE = 8;
8+
const R_ARM_RELATIVE = 23;
9+
const R_AARCH64_RELATIVE = 1027;
10+
const R_RISCV_RELATIVE = 3;
11+
12+
const ARCH_RELATIVE_RELOC = switch (builtin.arch) {
13+
.i386 => R_386_RELATIVE,
14+
.x86_64 => R_AMD64_RELATIVE,
15+
.arm => R_ARM_RELATIVE,
16+
.aarch64 => R_AARCH64_RELATIVE,
17+
.riscv64 => R_RISCV_RELATIVE,
18+
else => @compileError("unsupported architecture"),
19+
};
20+
21+
// Just a convoluted (but necessary) way to obtain the address of the _DYNAMIC[]
22+
// vector as PC-relative so that we can use it before any relocation is applied
23+
fn getDynamicSymbol() [*]elf.Dyn {
24+
const addr = switch (builtin.arch) {
25+
.i386 => asm volatile (
26+
\\ .weak _DYNAMIC
27+
\\ .hidden _DYNAMIC
28+
\\ call 1f
29+
\\ 1: pop %[ret]
30+
\\ lea _DYNAMIC-1b(%[ret]), %[ret]
31+
: [ret] "=r" (-> usize)
32+
),
33+
.x86_64 => asm volatile (
34+
\\ .weak _DYNAMIC
35+
\\ .hidden _DYNAMIC
36+
\\ lea _DYNAMIC(%%rip), %[ret]
37+
: [ret] "=r" (-> usize)
38+
),
39+
// Work around the limited offset range of `ldr`
40+
.arm => asm volatile (
41+
\\ .weak _DYNAMIC
42+
\\ .hidden _DYNAMIC
43+
\\ ldr %[ret], 1f
44+
\\ add %[ret], pc
45+
\\ b 2f
46+
\\ 1: .word _DYNAMIC-1b
47+
\\ 2:
48+
: [ret] "=r" (-> usize)
49+
),
50+
// A simple `adr` is not enough as it has a limited offset range
51+
.aarch64 => asm volatile (
52+
\\ .weak _DYNAMIC
53+
\\ .hidden _DYNAMIC
54+
\\ adrp %[ret], _DYNAMIC
55+
\\ add %[ret], %[ret], #:lo12:_DYNAMIC
56+
: [ret] "=r" (-> usize)
57+
),
58+
.riscv64 => asm volatile (
59+
\\ lla %[ret], _DYNAMIC
60+
: [ret] "=r" (-> usize)
61+
),
62+
else => @compileError("???"),
63+
};
64+
if (addr == 0) unreachable;
65+
return @intToPtr([*]elf.Dyn, addr);
66+
}
67+
68+
pub fn apply_relocations() void {
69+
@setRuntimeSafety(false);
70+
71+
const dynv = getDynamicSymbol();
72+
const auxv = std.os.linux.elf_aux_maybe.?;
73+
var at_phent: usize = undefined;
74+
var at_phnum: usize = undefined;
75+
var at_phdr: usize = undefined;
76+
var at_hwcap: usize = undefined;
77+
78+
{
79+
var i: usize = 0;
80+
while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) {
81+
switch (auxv[i].a_type) {
82+
elf.AT_PHENT => at_phent = auxv[i].a_un.a_val,
83+
elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val,
84+
elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val,
85+
else => continue,
86+
}
87+
}
88+
}
89+
90+
// Sanity check
91+
assert(at_phent == @sizeOf(elf.Phdr));
92+
93+
// Search the TLS section
94+
const phdrs = (@intToPtr([*]elf.Phdr, at_phdr))[0..at_phnum];
95+
96+
const base_addr = blk: {
97+
for (phdrs) |*phdr| {
98+
if (phdr.p_type == elf.PT_DYNAMIC) {
99+
break :blk @ptrToInt(&dynv[0]) - phdr.p_vaddr;
100+
}
101+
}
102+
unreachable;
103+
};
104+
105+
var rel_addr: usize = 0;
106+
var rela_addr: usize = 0;
107+
var rel_size: usize = 0;
108+
var rela_size: usize = 0;
109+
110+
{
111+
var i: usize = 0;
112+
while (dynv[i].d_tag != elf.DT_NULL) : (i += 1) {
113+
switch (dynv[i].d_tag) {
114+
elf.DT_REL => rel_addr = base_addr + dynv[i].d_un.d_ptr,
115+
elf.DT_RELA => rela_addr = base_addr + dynv[i].d_un.d_ptr,
116+
elf.DT_RELSZ => rel_size = dynv[i].d_un.d_val,
117+
elf.DT_RELASZ => rela_size = dynv[i].d_un.d_val,
118+
else => {},
119+
}
120+
}
121+
}
122+
123+
// Perform the relocations
124+
if (rel_addr != 0) {
125+
const rel = @bytesToSlice(elf.Rel, @intToPtr([*]u8, rel_addr)[0..rel_size]);
126+
for (rel) |r| {
127+
if (r.r_type() != ARCH_RELATIVE_RELOC) continue;
128+
@intToPtr(*usize, base_addr + r.r_offset).* += base_addr;
129+
}
130+
}
131+
if (rela_addr != 0) {
132+
const rela = @bytesToSlice(elf.Rela, @intToPtr([*]u8, rela_addr)[0..rela_size]);
133+
for (rela) |r| {
134+
if (r.r_type() != ARCH_RELATIVE_RELOC) continue;
135+
@intToPtr(*usize, base_addr + r.r_offset).* += base_addr + @bitCast(usize, r.r_addend);
136+
}
137+
}
138+
}

lib/std/os/test.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ export fn iter_fn(info: *dl_phdr_info, size: usize, data: ?*usize) i32 {
205205
test "dl_iterate_phdr" {
206206
if (builtin.os == .windows or builtin.os == .wasi or builtin.os == .macosx)
207207
return error.SkipZigTest;
208+
if (builtin.position_independent_executable)
209+
return error.SkipZigTest;
208210

209211
var counter: usize = 0;
210212
expect(os.dl_iterate_phdr(usize, iter_fn, &counter) != 0);

lib/std/start.zig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ fn posixCallMainAndExit() noreturn {
155155
// Find the beginning of the auxiliary vector
156156
const auxv = @ptrCast([*]std.elf.Auxv, envp.ptr + envp_count + 1);
157157
std.os.linux.elf_aux_maybe = auxv;
158+
159+
// Do this as early as possible, the aux vector is needed
160+
if (builtin.position_independent_executable) {
161+
@import("os/linux/start_pie.zig").apply_relocations();
162+
}
163+
158164
// Initialize the TLS area
159165
const gnu_stack_phdr = std.os.linux.tls.initTLS() orelse @panic("ELF missing stack size");
160166

src/all_types.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,6 +1922,12 @@ enum WantPIC {
19221922
WantPICEnabled,
19231923
};
19241924

1925+
enum WantPIE {
1926+
WantPIEAuto,
1927+
WantPIEDisabled,
1928+
WantPIEEnabled,
1929+
};
1930+
19251931
enum WantStackCheck {
19261932
WantStackCheckAuto,
19271933
WantStackCheckDisabled,
@@ -2101,6 +2107,7 @@ struct CodeGen {
21012107
Stage2ProgressNode *sub_progress_node;
21022108

21032109
WantPIC want_pic;
2110+
WantPIE want_pie;
21042111
WantStackCheck want_stack_check;
21052112
WantCSanitize want_sanitize_c;
21062113
CacheHash cache_hash;
@@ -2175,6 +2182,7 @@ struct CodeGen {
21752182
bool disable_gen_h;
21762183
bool bundle_compiler_rt;
21772184
bool have_pic;
2185+
bool have_pie;
21782186
bool have_dynamic_link; // this is whether the final thing will be dynamically linked. see also is_dynamic
21792187
bool have_stack_probing;
21802188
bool have_sanitize_c;

0 commit comments

Comments
 (0)