| ... | @@ -400,7 +400,86 @@ fn detectAbiAndDynamicLinker( | ... | @@ -400,7 +400,86 @@ fn detectAbiAndDynamicLinker( |
| 400 | }; | 400 | }; |
| 401 | } | 401 | } |
| 402 | | 402 | |
| 403 | const glibc_so_basename = "libc.so.6"; | 403 | fn 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 | } |
| 404 | | 483 | |
| 405 | fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version { | 484 | fn 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 | } |
| 509 | | 588 | |
| 510 | fn 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 | | | |
| 527 | pub const AbiAndDynamicLinkerFromFileError = error{ | 589 | pub 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() and | 736 | 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 | |
| 679 | | 741 | 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 | |
| 683 | | 745 | 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 | |
| 686 | | 748 | _ = 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 | |
| 696 | | 758 | 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 fields | 762 | // 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; |
| 734 | | 795 | |
| 735 | if (dynstr) |ds| { | 796 | if (dynstr) |ds| { |
| | 797 | if (rpath_offset) |rpoff| { |
| 736 | // TODO this pointer cast should not be necessary | 798 | // 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, |
| 754 | | 816 | 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| { |
| 761 | | 823 | 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 | } |
| 861 | | 833 | |