authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-08 20:26:58-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-08 20:52:49-07:00
logc668396941f19a5c015014822d980bbe9f2bc585
tree659e3bbd754cdf453f1e716de667474275265853
parent1b6fa1965a5472d9bc9e3140d052bde0fb949fe1

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 files changed, 154 insertions(+), 182 deletions(-)

lib/std/zig/system/NativeTargetInfo.zig+154-182
...@@ -400,7 +400,86 @@ fn detectAbiAndDynamicLinker(...@@ -400,7 +400,86 @@ fn detectAbiAndDynamicLinker(
400 };400 };
401}401}
402402
403const glibc_so_basename = "libc.so.6";403fn glibcVerFromRPath(rpath: []const u8) !std.builtin.Version {
404 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
405 error.NameTooLong => unreachable,
406 error.InvalidUtf8 => unreachable,
407 error.BadPathName => unreachable,
408 error.DeviceBusy => unreachable,
409
410 error.FileNotFound,
411 error.NotDir,
412 error.InvalidHandle,
413 error.AccessDenied,
414 error.NoDevice,
415 => return error.GLibCNotFound,
416
417 error.ProcessFdQuotaExceeded,
418 error.SystemFdQuotaExceeded,
419 error.SystemResources,
420 error.SymLinkLoop,
421 error.Unexpected,
422 => |e| return e,
423 };
424 defer dir.close();
425
426 // Now we have a candidate for the path to libc shared object. In
427 // the past, we used readlink() here because the link name would
428 // reveal the glibc version. However, in more recent GNU/Linux
429 // installations, there is no symlink. Thus we instead use a more
430 // robust check of opening the libc shared object and looking at the
431 // .dynstr section, and finding the max version number of symbols
432 // that start with "GLIBC_2.".
433 const glibc_so_basename = "libc.so.6";
434 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
435 error.NameTooLong => unreachable,
436 error.InvalidUtf8 => unreachable, // Windows only
437 error.BadPathName => unreachable, // Windows only
438 error.PipeBusy => unreachable, // Windows-only
439 error.SharingViolation => unreachable, // Windows-only
440 error.FileLocksNotSupported => unreachable, // No lock requested.
441 error.NoSpaceLeft => unreachable, // read-only
442 error.PathAlreadyExists => unreachable, // read-only
443 error.DeviceBusy => unreachable, // read-only
444 error.FileBusy => unreachable, // read-only
445 error.InvalidHandle => unreachable, // should not be in the error set
446 error.WouldBlock => unreachable, // not using O_NONBLOCK
447 error.NoDevice => unreachable, // not asking for a special device
448
449 error.AccessDenied,
450 error.FileNotFound,
451 error.NotDir,
452 error.IsDir,
453 => return error.GLibCNotFound,
454
455 error.FileTooBig => return error.Unexpected,
456
457 error.ProcessFdQuotaExceeded,
458 error.SystemFdQuotaExceeded,
459 error.SystemResources,
460 error.SymLinkLoop,
461 error.Unexpected,
462 => |e| return e,
463 };
464 defer f.close();
465
466 return glibcVerFromSoFile(f) catch |err| switch (err) {
467 error.InvalidElfMagic,
468 error.InvalidElfEndian,
469 error.InvalidElfClass,
470 error.InvalidElfFile,
471 error.InvalidElfVersion,
472 error.InvalidGnuLibCVersion,
473 error.UnexpectedEndOfFile,
474 => return error.GLibCNotFound,
475
476 error.SystemResources,
477 error.UnableToReadElfFile,
478 error.Unexpected,
479 error.FileSystem,
480 => |e| return e,
481 };
482}
404483
405fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {484fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
406 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;485 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 {...@@ -507,23 +586,6 @@ fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
507 return max_ver;586 return max_ver;
508}587}
509588
510fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version {
511 // example: "libc-2.3.4.so"
512 // example: "libc-2.27.so"
513 // example: "ld-2.33.so"
514 const suffix = ".so";
515 if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) {
516 return error.UnrecognizedGnuLibCFileName;
517 }
518 // chop off "libc-" and ".so"
519 const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len];
520 return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) {
521 error.Overflow => return error.InvalidGnuLibCVersion,
522 error.InvalidCharacter => return error.InvalidGnuLibCVersion,
523 error.InvalidVersion => return error.InvalidGnuLibCVersion,
524 };
525}
526
527pub const AbiAndDynamicLinkerFromFileError = error{589pub const AbiAndDynamicLinkerFromFileError = error{
528 FileSystem,590 FileSystem,
529 SystemResources,591 SystemResources,
...@@ -674,65 +736,65 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -674,65 +736,65 @@ pub fn abiAndDynamicLinkerFromFile(
674 if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and736 if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and
675 cross_target.glibc_version == null)737 cross_target.glibc_version == null)
676 {738 {
677 if (rpath_offset) |rpoff| {739 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
678 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);740
679741 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
680 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);742 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
681 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);743 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
682 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);744
683745 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
684 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;746 if (sh_buf.len < shentsize) return error.InvalidElfFile;
685 if (sh_buf.len < shentsize) return error.InvalidElfFile;747
686748 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);
687 _ = try preadMin(file, &sh_buf, str_section_off, shentsize);749 const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));
688 const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf));750 const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));
689 const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf));751 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
690 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);752 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
691 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);753 var strtab_buf: [4096:0]u8 = undefined;
692 var strtab_buf: [4096:0]u8 = undefined;754 const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);
693 const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len);755 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);
694 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len);756 const shstrtab = strtab_buf[0..shstrtab_read_len];
695 const shstrtab = strtab_buf[0..shstrtab_read_len];757
696758 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
697 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);759 var sh_i: u16 = 0;
698 var sh_i: u16 = 0;760 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
699 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {761 // Reserve some bytes so that we can deref the 64-bit struct fields
700 // Reserve some bytes so that we can deref the 64-bit struct fields762 // even when the ELF file is 32-bits.
701 // even when the ELF file is 32-bits.763 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
702 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);764 const sh_read_byte_len = try preadMin(
703 const sh_read_byte_len = try preadMin(765 file,
704 file,766 sh_buf[0 .. sh_buf.len - sh_reserve],
705 sh_buf[0 .. sh_buf.len - sh_reserve],767 shoff,
706 shoff,768 shentsize,
707 shentsize,769 );
770 var sh_buf_i: usize = 0;
771 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
772 sh_i += 1;
773 shoff += shentsize;
774 sh_buf_i += shentsize;
775 }) {
776 const sh32 = @ptrCast(
777 *elf.Elf32_Shdr,
778 @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),
708 );779 );
709 var sh_buf_i: usize = 0;780 const sh64 = @ptrCast(
710 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({781 *elf.Elf64_Shdr,
711 sh_i += 1;782 @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
712 shoff += shentsize;783 );
713 sh_buf_i += shentsize;784 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
714 }) {785 // TODO this pointer cast should not be necessary
715 const sh32 = @ptrCast(786 const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
716 *elf.Elf32_Shdr,787 if (mem.eql(u8, sh_name, ".dynstr")) {
717 @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]),788 break :find_dyn_str .{
718 );789 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
719 const sh64 = @ptrCast(790 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
720 *elf.Elf64_Shdr,791 };
721 @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]),
722 );
723 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
724 // TODO this pointer cast should not be necessary
725 const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0);
726 if (mem.eql(u8, sh_name, ".dynstr")) {
727 break :find_dyn_str .{
728 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
729 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
730 };
731 }
732 }792 }
733 } else null;793 }
794 } else null;
734795
735 if (dynstr) |ds| {796 if (dynstr) |ds| {
797 if (rpath_offset) |rpoff| {
736 // TODO this pointer cast should not be necessary798 // TODO this pointer cast should not be necessary
737 const rpoff_usize = std.math.cast(usize, rpoff) orelse return error.InvalidElfFile;799 const rpoff_usize = std.math.cast(usize, rpoff) orelse return error.InvalidElfFile;
738 if (rpoff_usize > ds.size) return error.InvalidElfFile;800 if (rpoff_usize > ds.size) return error.InvalidElfFile;
...@@ -746,116 +808,26 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -746,116 +808,26 @@ pub fn abiAndDynamicLinkerFromFile(
746 const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab.ptr, 0), 0);808 const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab.ptr, 0), 0);
747 var it = mem.tokenize(u8, rpath_list, ":");809 var it = mem.tokenize(u8, rpath_list, ":");
748 while (it.next()) |rpath| {810 while (it.next()) |rpath| {
749 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {811 if (glibcVerFromRPath(rpath)) |ver| {
750 error.NameTooLong => unreachable,812 result.target.os.version_range.linux.glibc = ver;
751 error.InvalidUtf8 => unreachable,813 break;
752 error.BadPathName => unreachable,814 } else |err| switch (err) {
753 error.DeviceBusy => unreachable,815 error.GLibCNotFound => continue,
754816 else => |e| return e,
755 error.FileNotFound,817 }
756 error.NotDir,818 }
757 error.InvalidHandle,819 } else if (result.dynamic_linker.get()) |dl_path| {
758 error.AccessDenied,820 // There is no DT_RUNPATH so we try to find libc.so.6 inside the same
759 error.NoDevice,821 // directory as the dynamic linker.
760 => continue,822 if (fs.path.dirname(dl_path)) |rpath| {
761823 if (glibcVerFromRPath(rpath)) |ver| {
762 error.ProcessFdQuotaExceeded,824 result.target.os.version_range.linux.glibc = ver;
763 error.SystemFdQuotaExceeded,825 } else |err| switch (err) {
764 error.SystemResources,826 error.GLibCNotFound => {},
765 error.SymLinkLoop,827 else => |e| return e,
766 error.Unexpected,828 }
767 => |e| return e,
768 };
769 defer dir.close();
770
771 // Now we have a candidate for the path to libc shared object. In
772 // the past, we used readlink() here because the link name would
773 // reveal the glibc version. However, in more recent GNU/Linux
774 // installations, there is no symlink. Thus we instead use a more
775 // robust check of opening the libc shared object and looking at the
776 // .dynstr section, and finding the max version number of symbols
777 // that start with "GLIBC_2.".
778 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
779 error.NameTooLong => unreachable,
780 error.InvalidUtf8 => unreachable, // Windows only
781 error.BadPathName => unreachable, // Windows only
782 error.PipeBusy => unreachable, // Windows-only
783 error.SharingViolation => unreachable, // Windows-only
784 error.FileLocksNotSupported => unreachable, // No lock requested.
785 error.NoSpaceLeft => unreachable, // read-only
786 error.PathAlreadyExists => unreachable, // read-only
787 error.DeviceBusy => unreachable, // read-only
788 error.FileBusy => unreachable, // read-only
789 error.InvalidHandle => unreachable, // should not be in the error set
790 error.WouldBlock => unreachable, // not using O_NONBLOCK
791 error.NoDevice => unreachable, // not asking for a special device
792
793 error.AccessDenied,
794 error.FileNotFound,
795 error.NotDir,
796 => continue,
797
798 error.IsDir => return error.InvalidElfFile,
799 error.FileTooBig => return error.Unexpected,
800
801 error.ProcessFdQuotaExceeded,
802 error.SystemFdQuotaExceeded,
803 error.SystemResources,
804 error.SymLinkLoop,
805 error.Unexpected,
806 => |e| return e,
807 };
808 defer f.close();
809
810 result.target.os.version_range.linux.glibc = glibcVerFromSoFile(f) catch |err| switch (err) {
811 error.InvalidElfMagic,
812 error.InvalidElfEndian,
813 error.InvalidElfClass,
814 error.InvalidElfFile,
815 error.InvalidElfVersion,
816 error.InvalidGnuLibCVersion,
817 error.UnexpectedEndOfFile,
818 => continue,
819
820 error.SystemResources,
821 error.UnableToReadElfFile,
822 error.Unexpected,
823 error.FileSystem,
824 => |e| return e,
825 };
826 break;
827 }829 }
828 }830 }
829 } else if (result.dynamic_linker.get()) |dl_path| glibc_ver: {
830 // There is no DT_RUNPATH but we can try to see if the information is
831 // present in the symlink data for the dynamic linker path.
832 var link_buf: [std.os.PATH_MAX]u8 = undefined;
833 const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) {
834 error.NameTooLong => unreachable,
835 error.InvalidUtf8 => unreachable, // Windows only
836 error.BadPathName => unreachable, // Windows only
837 error.UnsupportedReparsePointType => unreachable, // Windows only
838
839 error.AccessDenied,
840 error.FileNotFound,
841 error.NotLink,
842 error.NotDir,
843 => break :glibc_ver,
844
845 error.SystemResources,
846 error.FileSystem,
847 error.SymLinkLoop,
848 error.Unexpected,
849 => |e| return e,
850 };
851 result.target.os.version_range.linux.glibc = glibcVerFromLinkName(
852 fs.path.basename(link_name),
853 "ld-",
854 ) catch |err| switch (err) {
855 error.UnrecognizedGnuLibCFileName,
856 error.InvalidGnuLibCVersion,
857 => break :glibc_ver,
858 };
859 }831 }
860 }832 }
861833