Commit 3683ba87ac

Andrew Kelley <andrew@ziglang.org>
2020-02-28 01:49:00
complete the native target detection based on /usr/bin/env
1 parent fd006c1
Changed files (4)
lib/std/zig/system.zig
@@ -238,16 +238,6 @@ pub const NativeTargetInfo = struct {
         // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined
         // and supported by Zig. But that means that we must detect the system ABI here rather than
         // relying on `Target.current`.
-        const LdInfo = struct {
-            ld_path_buffer: [255]u8,
-            ld_path_max: u8,
-            abi: Target.Abi,
-
-            pub fn ldPath(self: *const @This()) []const u8 {
-                const m: usize = self.ld_path_max;
-                return self.ld_path_buffer[0 .. m + 1];
-            }
-        };
         const all_abis = comptime blk: {
             assert(@enumToInt(Target.Abi.none) == 0);
             const fields = std.meta.fields(Target.Abi)[1..];
@@ -333,7 +323,7 @@ pub const NativeTargetInfo = struct {
         // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env.
         // Since that path is hard-coded into the shebang line of many portable scripts, it's a
         // reasonably reliable path to check for.
-        return abiAndDynamicLinkerFromUsrBinEnv(cpu, os) catch |err| switch (err) {
+        return abiAndDynamicLinkerFromUsrBinEnv(cpu, os, ld_info_list) catch |err| switch (err) {
             error.FileSystem,
             error.SystemResources,
             error.SymLinkLoop,
@@ -343,8 +333,6 @@ pub const NativeTargetInfo = struct {
             => |e| return e,
 
             error.UnableToReadElfFile,
-            error.ElfNotADynamicExecutable,
-            error.InvalidElfProgramHeaders,
             error.InvalidElfClass,
             error.InvalidElfVersion,
             error.InvalidElfEndian,
@@ -373,6 +361,10 @@ pub const NativeTargetInfo = struct {
             error.NotDir => return error.GnuLibCVersionUnavailable,
             error.Unexpected => return error.GnuLibCVersionUnavailable,
         };
+        return glibcVerFromLinkName(link_name);
+    }
+
+    fn glibcVerFromLinkName(link_name: []const u8) !std.builtin.Version {
         // example: "libc-2.3.4.so"
         // example: "libc-2.27.so"
         const prefix = "libc-";
@@ -389,7 +381,11 @@ pub const NativeTargetInfo = struct {
         };
     }
 
-    fn abiAndDynamicLinkerFromUsrBinEnv(cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo {
+    fn abiAndDynamicLinkerFromUsrBinEnv(
+        cpu: Target.Cpu,
+        os: Target.Os,
+        ld_info_list: []const LdInfo,
+    ) !NativeTargetInfo {
         const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) {
             error.NoSpaceLeft => unreachable,
             error.NameTooLong => unreachable,
@@ -409,8 +405,7 @@ pub const NativeTargetInfo = struct {
             else => |e| return e,
         };
         var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
-        const hdr_bytes_len = try wrapRead(env_file.pread(&hdr_buf, 0));
-        if (hdr_bytes_len < @sizeOf(elf.Elf32_Ehdr)) return error.InvalidElfFile;
+        _ = try preadFull(env_file, &hdr_buf, 0, hdr_buf.len);
         const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf);
         const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf);
         if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic;
@@ -430,7 +425,6 @@ pub const NativeTargetInfo = struct {
         var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
         const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
         const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
-        const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
 
         var result: NativeTargetInfo = .{
             .target = .{
@@ -439,67 +433,234 @@ pub const NativeTargetInfo = struct {
                 .abi = Target.Abi.default(cpu.arch, os),
             },
         };
+        var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
 
-        const ph_total_size = std.math.mul(u32, phentsize, phnum) catch |err| switch (err) {
-            error.Overflow => return error.InvalidElfProgramHeaders,
-        };
         var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
+        if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
+
         var ph_i: u16 = 0;
         while (ph_i < phnum) {
-            // Reserve some bytes so that we can deref the 64-bit struct fields even when the ELF file is 32-bits.
-            const reserve = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
-            const read_byte_len = try wrapRead(env_file.pread(ph_buf[0 .. ph_buf.len - reserve], phoff));
-            if (read_byte_len < phentsize) return error.ElfNotADynamicExecutable;
-            var buf_i: usize = 0;
-            while (buf_i < read_byte_len and ph_i < phnum) : ({
+            // Reserve some bytes so that we can deref the 64-bit struct fields
+            // even when the ELF file is 32-bits.
+            const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
+            const ph_read_byte_len = try preadFull(env_file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
+            var ph_buf_i: usize = 0;
+            while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
                 ph_i += 1;
                 phoff += phentsize;
-                buf_i += phentsize;
+                ph_buf_i += phentsize;
             }) {
-                const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[buf_i]));
-                const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[buf_i]));
+                const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i]));
+                const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i]));
                 const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
                 switch (p_type) {
                     elf.PT_INTERP => {
                         const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
                         const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
-                        var interp_buf: [255]u8 = undefined;
-                        if (p_filesz > interp_buf.len) return error.NameTooLong;
-                        var read_offset: usize = 0;
-                        while (true) {
-                            const len = try wrapRead(env_file.pread(
-                                interp_buf[read_offset .. p_filesz - read_offset],
-                                p_offset + read_offset,
-                            ));
-                            if (len == 0) return error.UnexpectedEndOfFile;
-                            read_offset += len;
-                            if (read_offset == p_filesz) break;
-                        }
+                        if (p_filesz > result.dynamic_linker_buffer.len) return error.NameTooLong;
+                        _ = try preadFull(env_file, result.dynamic_linker_buffer[0..p_filesz], p_offset, p_filesz);
                         // PT_INTERP includes a null byte in p_filesz.
-                        result.setDynamicLinker(interp_buf[0 .. p_filesz - 1]);
+                        const len = p_filesz - 1;
+                        // dynamic_linker_max is "max", not "len".
+                        // We know it will fit in u8 because we check against dynamic_linker_buffer.len above.
+                        result.dynamic_linker_max = @intCast(u8, len - 1);
+
+                        // Use it to determine ABI.
+                        const full_ld_path = result.dynamic_linker_buffer[0..len];
+                        for (ld_info_list) |ld_info| {
+                            const standard_ld_basename = fs.path.basename(ld_info.ldPath());
+                            if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
+                                result.target.abi = ld_info.abi;
+                                break;
+                            }
+                        }
                     },
-                    elf.PT_DYNAMIC => {
-                        std.debug.warn("found PT_DYNAMIC\n", .{});
+                    // We only need this for detecting glibc version.
+                    elf.PT_DYNAMIC => if (Target.current.os.tag == .linux and result.target.isGnuLibC()) {
+                        var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
+                        const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
+                        const dyn_size: u64 = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
+                        const dyn_num = p_filesz / dyn_size;
+                        var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
+                        var dyn_i: usize = 0;
+                        dyn: while (dyn_i < dyn_num) {
+                            // Reserve some bytes so that we can deref the 64-bit struct fields
+                            // even when the ELF file is 32-bits.
+                            const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
+                            const dyn_read_byte_len = try preadFull(
+                                env_file,
+                                dyn_buf[0 .. dyn_buf.len - dyn_reserve],
+                                dyn_off,
+                                dyn_size,
+                            );
+                            var dyn_buf_i: usize = 0;
+                            while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
+                                dyn_i += 1;
+                                dyn_off += dyn_size;
+                                dyn_buf_i += dyn_size;
+                            }) {
+                                const dyn32 = @ptrCast(
+                                    *elf.Elf32_Dyn,
+                                    @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]),
+                                );
+                                const dyn64 = @ptrCast(
+                                    *elf.Elf64_Dyn,
+                                    @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]),
+                                );
+                                const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
+                                const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
+                                if (tag == elf.DT_RUNPATH) {
+                                    rpath_offset = val;
+                                    break :dyn;
+                                }
+                            }
+                        }
                     },
                     else => continue,
                 }
             }
         }
 
+        if (Target.current.os.tag == .linux and result.target.isGnuLibC()) {
+            if (rpath_offset) |rpoff| {
+                const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
+
+                var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
+                const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
+                const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
+
+                var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
+                if (sh_buf.len < shentsize) return error.InvalidElfFile;
+
+                _ = try preadFull(env_file, &sh_buf, str_section_off, shentsize);
+                const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
+                const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
+                const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
+                const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
+                var strtab_buf: [4096:0]u8 = undefined;
+                const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
+                const shstrtab_read_len = try preadFull(env_file, &strtab_buf, shstrtab_off, shstrtab_len);
+                const shstrtab = strtab_buf[0..shstrtab_read_len];
+
+                const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
+                var sh_i: u16 = 0;
+                const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
+                    // Reserve some bytes so that we can deref the 64-bit struct fields
+                    // even when the ELF file is 32-bits.
+                    const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
+                    const sh_read_byte_len = try preadFull(
+                        env_file,
+                        sh_buf[0 .. sh_buf.len - sh_reserve],
+                        shoff,
+                        shentsize,
+                    );
+                    var sh_buf_i: usize = 0;
+                    while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
+                        sh_i += 1;
+                        shoff += shentsize;
+                        sh_buf_i += shentsize;
+                    }) {
+                        const sh32 = @ptrCast(
+                            *elf.Elf32_Shdr,
+                            @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
+                        );
+                        const sh64 = @ptrCast(
+                            *elf.Elf64_Shdr,
+                            @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
+                        );
+                        const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
+                        // TODO this pointer cast should not be necessary
+                        const sh_name = mem.toSliceConst(u8, @ptrCast([*:0]u8, shstrtab[sh_name_off..].ptr));
+                        if (mem.eql(u8, sh_name, ".dynstr")) {
+                            break :find_dyn_str .{
+                                .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
+                                .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
+                            };
+                        }
+                    }
+                } else null;
+
+                if (dynstr) |ds| {
+                    const strtab_len = std.math.min(ds.size, strtab_buf.len);
+                    const strtab_read_len = try preadFull(env_file, &strtab_buf, ds.offset, shstrtab_len);
+                    const strtab = strtab_buf[0..strtab_read_len];
+                    // TODO this pointer cast should not be necessary
+                    const rpath_list = mem.toSliceConst(u8, @ptrCast([*:0]u8, strtab[rpoff..].ptr));
+                    var it = mem.tokenize(rpath_list, ":");
+                    while (it.next()) |rpath| {
+                        var dir = fs.cwd().openDirList(rpath) catch |err| switch (err) {
+                            error.NameTooLong => unreachable,
+                            error.InvalidUtf8 => unreachable,
+                            error.BadPathName => unreachable,
+                            error.DeviceBusy => unreachable,
+
+                            error.FileNotFound,
+                            error.NotDir,
+                            error.AccessDenied,
+                            error.NoDevice,
+                            => continue,
+
+                            error.ProcessFdQuotaExceeded,
+                            error.SystemFdQuotaExceeded,
+                            error.SystemResources,
+                            error.SymLinkLoop,
+                            error.Unexpected,
+                            => |e| return e,
+                        };
+                        defer dir.close();
+
+                        var link_buf: [std.os.PATH_MAX]u8 = undefined;
+                        const link_name = std.os.readlinkatC(
+                            dir.fd,
+                            glibc_so_basename,
+                            &link_buf,
+                        ) catch |err| switch (err) {
+                            error.NameTooLong => unreachable,
+
+                            error.AccessDenied,
+                            error.FileNotFound,
+                            error.NotDir,
+                            => continue,
+
+                            error.SystemResources,
+                            error.FileSystem,
+                            error.SymLinkLoop,
+                            error.Unexpected,
+                            => |e| return e,
+                        };
+                        result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
+                            link_name,
+                        ) catch |err| switch (err) {
+                            error.UnrecognizedGnuLibCFileName,
+                            error.InvalidGnuLibCVersion,
+                            => continue,
+                        };
+                        break;
+                    }
+                }
+            }
+        }
+
         return result;
     }
 
-    fn wrapRead(res: std.os.ReadError!usize) !usize {
-        return res catch |err| switch (err) {
-            error.OperationAborted => unreachable, // Windows-only
-            error.WouldBlock => unreachable, // Did not request blocking mode
-            error.SystemResources => return error.SystemResources,
-            error.IsDir => return error.UnableToReadElfFile,
-            error.BrokenPipe => return error.UnableToReadElfFile,
-            error.ConnectionResetByPeer => return error.UnableToReadElfFile,
-            error.Unexpected => return error.Unexpected,
-            error.InputOutput => return error.FileSystem,
-        };
+    fn preadFull(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
+        var i: u64 = 0;
+        while (i < min_read_len) {
+            const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) {
+                error.OperationAborted => unreachable, // Windows-only
+                error.WouldBlock => unreachable, // Did not request blocking mode
+                error.SystemResources => return error.SystemResources,
+                error.IsDir => return error.UnableToReadElfFile,
+                error.BrokenPipe => return error.UnableToReadElfFile,
+                error.ConnectionResetByPeer => return error.UnableToReadElfFile,
+                error.Unexpected => return error.Unexpected,
+                error.InputOutput => return error.FileSystem,
+            };
+            if (len == 0) return error.UnexpectedEndOfFile;
+            i += len;
+        }
+        return i;
     }
 
     fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo {
@@ -513,20 +674,31 @@ pub const NativeTargetInfo = struct {
         result.dynamic_linker_max = result.target.standardDynamicLinkerPath(&result.dynamic_linker_buffer);
         return result;
     }
-};
 
-fn elfInt(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_64) {
-    if (is_64) {
-        if (need_bswap) {
-            return @byteSwap(@TypeOf(int_64), int_64);
-        } else {
-            return int_64;
+    const LdInfo = struct {
+        ld_path_buffer: [255]u8,
+        ld_path_max: u8,
+        abi: Target.Abi,
+
+        pub fn ldPath(self: *const LdInfo) []const u8 {
+            const m: usize = self.ld_path_max;
+            return self.ld_path_buffer[0 .. m + 1];
         }
-    } else {
-        if (need_bswap) {
-            return @byteSwap(@TypeOf(int_32), int_32);
+    };
+
+    fn elfInt(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_64) {
+        if (is_64) {
+            if (need_bswap) {
+                return @byteSwap(@TypeOf(int_64), int_64);
+            } else {
+                return int_64;
+            }
         } else {
-            return int_32;
+            if (need_bswap) {
+                return @byteSwap(@TypeOf(int_32), int_32);
+            } else {
+                return int_32;
+            }
         }
     }
-}
+};
lib/std/c.zig
@@ -108,6 +108,7 @@ pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8
 pub extern "c" fn dup(fd: fd_t) c_int;
 pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int;
 pub extern "c" fn readlink(noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize;
+pub extern "c" fn readlinkat(dirfd: fd_t, noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize;
 pub extern "c" fn realpath(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8;
 pub extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
 pub extern "c" fn gettimeofday(noalias tv: ?*timeval, noalias tz: ?*timezone) c_int;
lib/std/dynamic_library.zig
@@ -82,12 +82,12 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator {
         for (dyn_table) |*dyn| {
             switch (dyn.d_tag) {
                 elf.DT_DEBUG => {
-                    const r_debug = @intToPtr(*RDebug, dyn.d_un.d_ptr);
+                    const r_debug = @intToPtr(*RDebug, dyn.d_val);
                     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);
+                    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]);
lib/std/elf.zig
@@ -708,17 +708,11 @@ pub const Elf64_Rela = extern struct {
 };
 pub const Elf32_Dyn = extern struct {
     d_tag: Elf32_Sword,
-    d_un: extern union {
-        d_val: Elf32_Word,
-        d_ptr: Elf32_Addr,
-    },
+    d_val: Elf32_Addr,
 };
 pub const Elf64_Dyn = extern struct {
     d_tag: Elf64_Sxword,
-    d_un: extern union {
-        d_val: Elf64_Xword,
-        d_ptr: Elf64_Addr,
-    },
+    d_val: Elf64_Addr,
 };
 pub const Elf32_Verdef = extern struct {
     vd_version: Elf32_Half,