From 074ddf1ac6cefaff21d8b49a126556158ec5bc6b Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 24 Apr 2019 20:53:46 +0200 Subject: [PATCH 1/5] Implementation of dl_phdr_info --- std/c/linux.zig | 2 ++ std/os/linux.zig | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/std/c/linux.zig b/std/c/linux.zig index b0dadf071d..1f7139965b 100644 --- a/std/c/linux.zig +++ b/std/c/linux.zig @@ -11,3 +11,5 @@ pub const pthread_attr_t = extern struct { /// See std.elf for constants for this pub extern fn getauxval(__type: c_ulong) c_ulong; + +pub extern fn dl_iterate_phdr(callback: *const c_void, data: ?*c_void) i32; diff --git a/std/os/linux.zig b/std/os/linux.zig index e7f822185b..1dae263ede 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -2,6 +2,7 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; +const elf = std.elf; const vdso = @import("linux/vdso.zig"); pub use switch (builtin.arch) { builtin.Arch.x86_64 => @import("linux/x86_64.zig"), @@ -1534,6 +1535,68 @@ pub const dirent64 = extern struct { d_name: u8, // field address is the address of first byte of name https://github.com/ziglang/zig/issues/173 }; +pub const dl_phdr_info = extern struct { + dlpi_addr: usize, + dlpi_name: [*c]const u8, + dlpi_phdr: [*c]*c_void, + dlpi_phnum: u16, +}; + +const DynLib = @import("../dynamic_library.zig"); + +// XXX: This should be weak +extern const __ehdr_start: elf.Ehdr = undefined; + +pub fn dl_iterate_phdr(callback: extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) i32, data: ?*c_void) isize { + if (builtin.link_libc) { + return std.c.dl_iterate_phdr(@ptrCast(*const c_void, callback), data); + } + + const elf_base = @ptrToInt(&__ehdr_start); + const n_phdr = __ehdr_start.e_phnum; + const phdrs = (@intToPtr([*]elf.Phdr, elf_base + __ehdr_start.e_phoff))[0..n_phdr]; + + var it = DynLib.linkmap_iterator(phdrs) catch return 0; + if (it.end()) { + // The executable has no dynamic link infos, create a single info + // struct for the whole ELF image + var info = dl_phdr_info{ + .dlpi_addr = elf_base, + .dlpi_name = c"/proc/self/exe", + .dlpi_phdr = @intToPtr([*c]*c_void, elf_base + __ehdr_start.e_phoff), + .dlpi_phnum = __ehdr_start.e_phnum, + }; + + return callback(&info, @sizeOf(dl_phdr_info), data); + } + + // Last return value from the callback function + var last_r: isize = 0; + while (it.next()) |entry| { + var info = dl_phdr_info{ + .dlpi_addr = entry.l_addr, + .dlpi_name = entry.l_name, + .dlpi_phdr = 0, + .dlpi_phnum = 0, + }; + + if (entry.l_addr != 0) { + const elf_header = @intToPtr(*elf.Ehdr, entry.l_addr); + info.dlpi_phdr = @intToPtr([*c]*c_void, entry.l_addr + elf_header.e_phoff); + info.dlpi_phnum = elf_header.e_phnum; + } else { + // This is the running ELF image + info.dlpi_phdr = @intToPtr([*c]*c_void, elf_base + __ehdr_start.e_phoff); + info.dlpi_phnum = __ehdr_start.e_phnum; + } + + last_r = callback(&info, @sizeOf(dl_phdr_info), data); + if (last_r != 0) break; + } + + return last_r; +} + test "import" { if (builtin.os == builtin.Os.linux) { _ = @import("linux/test.zig"); From 12eff09ff486e80ed073edb54074c7d3afbd6fc0 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 24 Apr 2019 20:54:05 +0200 Subject: [PATCH 2/5] Expose Elf32_Dyn and Elf64_Dyn --- std/elf.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/std/elf.zig b/std/elf.zig index d0ec5fb2ae..49f2f7d137 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -877,6 +877,11 @@ pub const Phdr = switch (@sizeOf(usize)) { 8 => Elf64_Phdr, else => @compileError("expected pointer size of 32 or 64"), }; +pub const Dyn = switch (@sizeOf(usize)) { + 4 => Elf32_Dyn, + 8 => Elf64_Dyn, + else => @compileError("expected pointer size of 32 or 64"), +}; pub const Shdr = switch (@sizeOf(usize)) { 4 => Elf32_Shdr, 8 => Elf64_Shdr, From e4825dbd771906722508933bb355e342b992869b Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 24 Apr 2019 20:54:17 +0200 Subject: [PATCH 3/5] Other --- std/dynamic_library.zig | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/std/dynamic_library.zig b/std/dynamic_library.zig index 698b0eb216..c116a8de53 100644 --- a/std/dynamic_library.zig +++ b/std/dynamic_library.zig @@ -19,6 +19,85 @@ pub const DynLib = switch (builtin.os) { else => void, }; +const LinkMap = extern struct { + l_addr: usize, + l_name: [*]u8, + l_ld: [*c]elf.Dyn, + l_next: [*c]LinkMap, + l_prev: [*c]LinkMap, + + pub const Iterator = struct { + lm_ptr: [*c]LinkMap, + + fn end(self: *const Iterator) bool { + return self.lm_ptr == 0; + } + + fn next(self: *Iterator) ?*LinkMap { + if (self.lm_ptr != 0) { + const ptr = self.lm_ptr; + self.lm_ptr = self.lm_ptr.*.l_next; + return ptr; + } + return null; + } + }; +}; + +const RDebug = extern struct { + r_version: i32, + r_map: [*c]LinkMap, + r_brk: usize, + 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; + } + } + 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)]; + } + } + // No PT_DYNAMIC means this is either a statically-linked program or a + // badly corrupted one + return LinkMap.Iterator{.lm_ptr = 0}; + }; + + const link_map_ptr = init: { + for (dyn_table) |*dyn| { + switch (dyn.d_tag) { + elf.DT_DEBUG => { + const r_debug = @intToPtr(*RDebug, dyn.d_un.d_ptr); + 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_un.d_ptr); + // The address to the link_map structure is stored in the + // second slot + break :init @intToPtr([*c]LinkMap, got_table[1]); + }, + else => { } + } + } + return error.InvalidExe; + }; + + return LinkMap.Iterator{.lm_ptr = link_map_ptr}; +} + pub const LinuxDynLib = struct { elf_lib: ElfLib, fd: i32, From 07dfccf9674c2f7196c31b25813ea9e3e7e1dad6 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 5 May 2019 12:14:53 +0200 Subject: [PATCH 4/5] Review --- std/c/linux.zig | 4 +++- std/dynamic_library.zig | 34 ++++++++++++++++++-------------- std/os/linux.zig | 43 ++++++++++++++++++++++------------------- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/std/c/linux.zig b/std/c/linux.zig index 1f7139965b..48e359f361 100644 --- a/std/c/linux.zig +++ b/std/c/linux.zig @@ -1,3 +1,4 @@ +const linux = @import("../os/linux.zig"); pub use @import("../os/linux/errno.zig"); pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) c_int; @@ -12,4 +13,5 @@ pub const pthread_attr_t = extern struct { /// See std.elf for constants for this pub extern fn getauxval(__type: c_ulong) c_ulong; -pub extern fn dl_iterate_phdr(callback: *const c_void, data: ?*c_void) i32; +pub const dl_iterate_phdr_callback = extern fn (info: *linux.dl_phdr_info, size: usize, data: ?*c_void) c_int; +pub extern fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; diff --git a/std/dynamic_library.zig b/std/dynamic_library.zig index c116a8de53..b2064311db 100644 --- a/std/dynamic_library.zig +++ b/std/dynamic_library.zig @@ -19,25 +19,29 @@ pub const DynLib = switch (builtin.os) { else => void, }; +// The link_map structure is not completely specified beside the fields +// reported below, any libc is free to store additional data in the remaining +// space. +// An iterator is provided in order to traverse the linked list in a idiomatic +// fashion. const LinkMap = extern struct { l_addr: usize, - l_name: [*]u8, - l_ld: [*c]elf.Dyn, - l_next: [*c]LinkMap, - l_prev: [*c]LinkMap, + l_name: [*]const u8, + l_ld: ?*elf.Dyn, + l_next: ?*LinkMap, + l_prev: ?*LinkMap, pub const Iterator = struct { - lm_ptr: [*c]LinkMap, + current: ?*LinkMap, - fn end(self: *const Iterator) bool { - return self.lm_ptr == 0; + fn end(self: *Iterator) bool { + return self.current == null; } fn next(self: *Iterator) ?*LinkMap { - if (self.lm_ptr != 0) { - const ptr = self.lm_ptr; - self.lm_ptr = self.lm_ptr.*.l_next; - return ptr; + if (self.current) |it| { + self.current = it.l_next; + return it; } return null; } @@ -46,7 +50,7 @@ const LinkMap = extern struct { const RDebug = extern struct { r_version: i32, - r_map: [*c]LinkMap, + r_map: ?*LinkMap, r_brk: usize, r_ldbase: usize, }; @@ -72,7 +76,7 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { } // No PT_DYNAMIC means this is either a statically-linked program or a // badly corrupted one - return LinkMap.Iterator{.lm_ptr = 0}; + return LinkMap.Iterator{.current = null}; }; const link_map_ptr = init: { @@ -87,7 +91,7 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { const got_table = @intToPtr([*]usize, dyn.d_un.d_ptr); // The address to the link_map structure is stored in the // second slot - break :init @intToPtr([*c]LinkMap, got_table[1]); + break :init @intToPtr(?*LinkMap, got_table[1]); }, else => { } } @@ -95,7 +99,7 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { return error.InvalidExe; }; - return LinkMap.Iterator{.lm_ptr = link_map_ptr}; + return LinkMap.Iterator{.current = link_map_ptr}; } pub const LinuxDynLib = struct { diff --git a/std/os/linux.zig b/std/os/linux.zig index 1dae263ede..739342df60 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -4,6 +4,7 @@ const builtin = @import("builtin"); const maxInt = std.math.maxInt; const elf = std.elf; const vdso = @import("linux/vdso.zig"); +const dl = @import("../dynamic_library.zig"); pub use switch (builtin.arch) { builtin.Arch.x86_64 => @import("linux/x86_64.zig"), builtin.Arch.i386 => @import("linux/i386.zig"), @@ -1537,33 +1538,32 @@ pub const dirent64 = extern struct { pub const dl_phdr_info = extern struct { dlpi_addr: usize, - dlpi_name: [*c]const u8, - dlpi_phdr: [*c]*c_void, + dlpi_name: ?[*]const u8, + dlpi_phdr: [*]elf.Phdr, dlpi_phnum: u16, }; -const DynLib = @import("../dynamic_library.zig"); - // XXX: This should be weak extern const __ehdr_start: elf.Ehdr = undefined; -pub fn dl_iterate_phdr(callback: extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) i32, data: ?*c_void) isize { +pub fn dl_iterate_phdr(comptime T: type, callback: extern fn (info: *dl_phdr_info, size: usize, data: ?*T) i32, data: ?*T) isize { if (builtin.link_libc) { - return std.c.dl_iterate_phdr(@ptrCast(*const c_void, callback), data); + return std.c.dl_iterate_phdr(@ptrCast(std.c.dl_iterate_phdr_callback, callback), @ptrCast(?*c_void, data)); } const elf_base = @ptrToInt(&__ehdr_start); const n_phdr = __ehdr_start.e_phnum; const phdrs = (@intToPtr([*]elf.Phdr, elf_base + __ehdr_start.e_phoff))[0..n_phdr]; - var it = DynLib.linkmap_iterator(phdrs) catch return 0; + var it = dl.linkmap_iterator(phdrs) catch return 0; + + // The executable has no dynamic link segment, create a single entry for + // the whole ELF image if (it.end()) { - // The executable has no dynamic link infos, create a single info - // struct for the whole ELF image var info = dl_phdr_info{ .dlpi_addr = elf_base, .dlpi_name = c"/proc/self/exe", - .dlpi_phdr = @intToPtr([*c]*c_void, elf_base + __ehdr_start.e_phoff), + .dlpi_phdr = @intToPtr([*]elf.Phdr, elf_base + __ehdr_start.e_phoff), .dlpi_phnum = __ehdr_start.e_phnum, }; @@ -1573,23 +1573,26 @@ pub fn dl_iterate_phdr(callback: extern fn (info: *dl_phdr_info, size: usize, da // Last return value from the callback function var last_r: isize = 0; while (it.next()) |entry| { - var info = dl_phdr_info{ - .dlpi_addr = entry.l_addr, - .dlpi_name = entry.l_name, - .dlpi_phdr = 0, - .dlpi_phnum = 0, - }; + var dlpi_phdr: usize = undefined; + var dlpi_phnum: u16 = undefined; if (entry.l_addr != 0) { const elf_header = @intToPtr(*elf.Ehdr, entry.l_addr); - info.dlpi_phdr = @intToPtr([*c]*c_void, entry.l_addr + elf_header.e_phoff); - info.dlpi_phnum = elf_header.e_phnum; + dlpi_phdr = entry.l_addr + elf_header.e_phoff; + dlpi_phnum = elf_header.e_phnum; } else { // This is the running ELF image - info.dlpi_phdr = @intToPtr([*c]*c_void, elf_base + __ehdr_start.e_phoff); - info.dlpi_phnum = __ehdr_start.e_phnum; + dlpi_phdr = elf_base + __ehdr_start.e_phoff; + dlpi_phnum = __ehdr_start.e_phnum; } + var info = dl_phdr_info{ + .dlpi_addr = entry.l_addr, + .dlpi_name = entry.l_name, + .dlpi_phdr = @intToPtr([*]elf.Phdr, dlpi_phdr), + .dlpi_phnum = dlpi_phnum, + }; + last_r = callback(&info, @sizeOf(dl_phdr_info), data); if (last_r != 0) break; } From a095db0df71127b1fe9b595679cffb4a072b0a35 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 5 May 2019 13:00:32 +0200 Subject: [PATCH 5/5] Add a test case --- std/os/linux/test.zig | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/std/os/linux/test.zig b/std/os/linux/test.zig index 286748b70c..1949d00298 100644 --- a/std/os/linux/test.zig +++ b/std/os/linux/test.zig @@ -1,6 +1,8 @@ const std = @import("../../std.zig"); const builtin = @import("builtin"); const linux = std.os.linux; +const mem = std.mem; +const elf = std.elf; const expect = std.testing.expect; test "getpid" { @@ -42,3 +44,42 @@ test "timer" { // TODO implicit cast from *[N]T to [*]T err = linux.epoll_wait(@intCast(i32, epoll_fd), @ptrCast([*]linux.epoll_event, &events), 8, -1); } + +export fn iter_fn(info: *linux.dl_phdr_info, size: usize, data: ?*usize) i32 { + var counter = data.?; + // Count how many libraries are loaded + counter.* += usize(1); + + // The image should contain at least a PT_LOAD segment + if (info.dlpi_phnum < 1) return -1; + + // Quick & dirty validation of the phdr pointers, make sure we're not + // pointing to some random gibberish + var i: usize = 0; + var found_load = false; + while (i < info.dlpi_phnum) : (i += 1) { + const phdr = info.dlpi_phdr[i]; + + if (phdr.p_type != elf.PT_LOAD) continue; + + // Find the ELF header + const elf_header = @intToPtr(*elf.Ehdr, phdr.p_vaddr - phdr.p_offset); + // Validate the magic + if (!mem.eql(u8, elf_header.e_ident[0..], "\x7fELF")) return -1; + // Consistency check + if (elf_header.e_phnum != info.dlpi_phnum) return -1; + + found_load = true; + break; + } + + if (!found_load) return -1; + + return 42; +} + +test "dl_iterate_phdr" { + var counter: usize = 0; + expect(linux.dl_iterate_phdr(usize, iter_fn, &counter) != 0); + expect(counter != 0); +}