Commit c668396941

Andrew Kelley <andrew@ziglang.org>
2022-09-09 05:26:58
std.zig.system.NativeTargetInfo: handle missing DT_RUNPATH
This commit removes the check that takes advantage of when the dynamic linker is a symlink. Instead, it falls back on the same directory as the dynamic linker as a de facto runpath. Empirically, this gives correct results on Gentoo and NixOS. Unfortunately it is still falling short for Debian, which has libc.so.6 in a different directory as the dynamic linker.
1 parent 1b6fa19
Changed files (1)
lib
std
lib/std/zig/system/NativeTargetInfo.zig
@@ -400,7 +400,86 @@ fn detectAbiAndDynamicLinker(
     };
 }
 
-const glibc_so_basename = "libc.so.6";
+fn glibcVerFromRPath(rpath: []const u8) !std.builtin.Version {
+    var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
+        error.NameTooLong => unreachable,
+        error.InvalidUtf8 => unreachable,
+        error.BadPathName => unreachable,
+        error.DeviceBusy => unreachable,
+
+        error.FileNotFound,
+        error.NotDir,
+        error.InvalidHandle,
+        error.AccessDenied,
+        error.NoDevice,
+        => return error.GLibCNotFound,
+
+        error.ProcessFdQuotaExceeded,
+        error.SystemFdQuotaExceeded,
+        error.SystemResources,
+        error.SymLinkLoop,
+        error.Unexpected,
+        => |e| return e,
+    };
+    defer dir.close();
+
+    // Now we have a candidate for the path to libc shared object. In
+    // the past, we used readlink() here because the link name would
+    // reveal the glibc version. However, in more recent GNU/Linux
+    // installations, there is no symlink. Thus we instead use a more
+    // robust check of opening the libc shared object and looking at the
+    // .dynstr section, and finding the max version number of symbols
+    // that start with "GLIBC_2.".
+    const glibc_so_basename = "libc.so.6";
+    var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
+        error.NameTooLong => unreachable,
+        error.InvalidUtf8 => unreachable, // Windows only
+        error.BadPathName => unreachable, // Windows only
+        error.PipeBusy => unreachable, // Windows-only
+        error.SharingViolation => unreachable, // Windows-only
+        error.FileLocksNotSupported => unreachable, // No lock requested.
+        error.NoSpaceLeft => unreachable, // read-only
+        error.PathAlreadyExists => unreachable, // read-only
+        error.DeviceBusy => unreachable, // read-only
+        error.FileBusy => unreachable, // read-only
+        error.InvalidHandle => unreachable, // should not be in the error set
+        error.WouldBlock => unreachable, // not using O_NONBLOCK
+        error.NoDevice => unreachable, // not asking for a special device
+
+        error.AccessDenied,
+        error.FileNotFound,
+        error.NotDir,
+        error.IsDir,
+        => return error.GLibCNotFound,
+
+        error.FileTooBig => return error.Unexpected,
+
+        error.ProcessFdQuotaExceeded,
+        error.SystemFdQuotaExceeded,
+        error.SystemResources,
+        error.SymLinkLoop,
+        error.Unexpected,
+        => |e| return e,
+    };
+    defer f.close();
+
+    return glibcVerFromSoFile(f) catch |err| switch (err) {
+        error.InvalidElfMagic,
+        error.InvalidElfEndian,
+        error.InvalidElfClass,
+        error.InvalidElfFile,
+        error.InvalidElfVersion,
+        error.InvalidGnuLibCVersion,
+        error.UnexpectedEndOfFile,
+        => return error.GLibCNotFound,
+
+        error.SystemResources,
+        error.UnableToReadElfFile,
+        error.Unexpected,
+        error.FileSystem,
+        => |e| return e,
+    };
+}
 
 fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
     var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
@@ -507,23 +586,6 @@ fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
     return max_ver;
 }
 
-fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version {
-    // example: "libc-2.3.4.so"
-    // example: "libc-2.27.so"
-    // example: "ld-2.33.so"
-    const suffix = ".so";
-    if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
-        return error.UnrecognizedGnuLibCFileName;
-    }
-    // chop off "libc-" and ".so"
-    const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
-    return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
-        error.Overflow => return error.InvalidGnuLibCVersion,
-        error.InvalidCharacter => return error.InvalidGnuLibCVersion,
-        error.InvalidVersion => return error.InvalidGnuLibCVersion,
-    };
-}
-
 pub const AbiAndDynamicLinkerFromFileError = error{
     FileSystem,
     SystemResources,
@@ -674,65 +736,65 @@ pub fn abiAndDynamicLinkerFromFile(
     if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
         cross_target.glibc_version == null)
     {
-        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 preadMin(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 preadMin(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 preadMin(
-                    file,
-                    sh_buf[0 .. sh_buf.len - sh_reserve],
-                    shoff,
-                    shentsize,
+        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 preadMin(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 preadMin(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 preadMin(
+                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]),
                 );
-                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.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
-                    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),
-                        };
-                    }
+                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.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
+                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;
+            }
+        } else null;
 
-            if (dynstr) |ds| {
+        if (dynstr) |ds| {
+            if (rpath_offset) |rpoff| {
                 // TODO this pointer cast should not be necessary
                 const rpoff_usize = std.math.cast(usize, rpoff) orelse return error.InvalidElfFile;
                 if (rpoff_usize > ds.size) return error.InvalidElfFile;
@@ -746,116 +808,26 @@ pub fn abiAndDynamicLinkerFromFile(
                 const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab.ptr, 0), 0);
                 var it = mem.tokenize(u8, rpath_list, ":");
                 while (it.next()) |rpath| {
-                    var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
-                        error.NameTooLong => unreachable,
-                        error.InvalidUtf8 => unreachable,
-                        error.BadPathName => unreachable,
-                        error.DeviceBusy => unreachable,
-
-                        error.FileNotFound,
-                        error.NotDir,
-                        error.InvalidHandle,
-                        error.AccessDenied,
-                        error.NoDevice,
-                        => continue,
-
-                        error.ProcessFdQuotaExceeded,
-                        error.SystemFdQuotaExceeded,
-                        error.SystemResources,
-                        error.SymLinkLoop,
-                        error.Unexpected,
-                        => |e| return e,
-                    };
-                    defer dir.close();
-
-                    // Now we have a candidate for the path to libc shared object. In
-                    // the past, we used readlink() here because the link name would
-                    // reveal the glibc version. However, in more recent GNU/Linux
-                    // installations, there is no symlink. Thus we instead use a more
-                    // robust check of opening the libc shared object and looking at the
-                    // .dynstr section, and finding the max version number of symbols
-                    // that start with "GLIBC_2.".
-                    var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
-                        error.NameTooLong => unreachable,
-                        error.InvalidUtf8 => unreachable, // Windows only
-                        error.BadPathName => unreachable, // Windows only
-                        error.PipeBusy => unreachable, // Windows-only
-                        error.SharingViolation => unreachable, // Windows-only
-                        error.FileLocksNotSupported => unreachable, // No lock requested.
-                        error.NoSpaceLeft => unreachable, // read-only
-                        error.PathAlreadyExists => unreachable, // read-only
-                        error.DeviceBusy => unreachable, // read-only
-                        error.FileBusy => unreachable, // read-only
-                        error.InvalidHandle => unreachable, // should not be in the error set
-                        error.WouldBlock => unreachable, // not using O_NONBLOCK
-                        error.NoDevice => unreachable, // not asking for a special device
-
-                        error.AccessDenied,
-                        error.FileNotFound,
-                        error.NotDir,
-                        => continue,
-
-                        error.IsDir => return error.InvalidElfFile,
-                        error.FileTooBig => return error.Unexpected,
-
-                        error.ProcessFdQuotaExceeded,
-                        error.SystemFdQuotaExceeded,
-                        error.SystemResources,
-                        error.SymLinkLoop,
-                        error.Unexpected,
-                        => |e| return e,
-                    };
-                    defer f.close();
-
-                    result.target.os.version_range.linux.glibc = glibcVerFromSoFile(f) catch |err| switch (err) {
-                        error.InvalidElfMagic,
-                        error.InvalidElfEndian,
-                        error.InvalidElfClass,
-                        error.InvalidElfFile,
-                        error.InvalidElfVersion,
-                        error.InvalidGnuLibCVersion,
-                        error.UnexpectedEndOfFile,
-                        => continue,
-
-                        error.SystemResources,
-                        error.UnableToReadElfFile,
-                        error.Unexpected,
-                        error.FileSystem,
-                        => |e| return e,
-                    };
-                    break;
+                    if (glibcVerFromRPath(rpath)) |ver| {
+                        result.target.os.version_range.linux.glibc = ver;
+                        break;
+                    } else |err| switch (err) {
+                        error.GLibCNotFound => continue,
+                        else => |e| return e,
+                    }
+                }
+            } else if (result.dynamic_linker.get()) |dl_path| {
+                // There is no DT_RUNPATH so we try to find libc.so.6 inside the same
+                // directory as the dynamic linker.
+                if (fs.path.dirname(dl_path)) |rpath| {
+                    if (glibcVerFromRPath(rpath)) |ver| {
+                        result.target.os.version_range.linux.glibc = ver;
+                    } else |err| switch (err) {
+                        error.GLibCNotFound => {},
+                        else => |e| return e,
+                    }
                 }
             }
-        } else if (result.dynamic_linker.get()) |dl_path| glibc_ver: {
-            // There is no DT_RUNPATH but we can try to see if the information is
-            // present in the symlink data for the dynamic linker path.
-            var link_buf: [std.os.PATH_MAX]u8 = undefined;
-            const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
-                error.NameTooLong => unreachable,
-                error.InvalidUtf8 => unreachable, // Windows only
-                error.BadPathName => unreachable, // Windows only
-                error.UnsupportedReparsePointType => unreachable, // Windows only
-
-                error.AccessDenied,
-                error.FileNotFound,
-                error.NotLink,
-                error.NotDir,
-                => break :glibc_ver,
-
-                error.SystemResources,
-                error.FileSystem,
-                error.SymLinkLoop,
-                error.Unexpected,
-                => |e| return e,
-            };
-            result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
-                fs.path.basename(link_name),
-                "ld-",
-            ) catch |err| switch (err) {
-                error.UnrecognizedGnuLibCFileName,
-                error.InvalidGnuLibCVersion,
-                => break :glibc_ver,
-            };
         }
     }