| ... | ... | @@ -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 | 484 | fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version { |
| 406 | 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 | 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 | 589 | pub const AbiAndDynamicLinkerFromFileError = error{ |
| 528 | 590 | FileSystem, |
| 529 | 591 | SystemResources, |
| ... | ... | @@ -674,65 +736,65 @@ pub fn abiAndDynamicLinkerFromFile( |
| 674 | 736 | if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and |
| 675 | 737 | cross_target.glibc_version == null) |
| 676 | 738 | { |
| 677 | | if (rpath_offset) |rpoff| { |
| 678 | | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 679 | | |
| 680 | | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); |
| 681 | | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); |
| 682 | | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); |
| 683 | | |
| 684 | | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; |
| 685 | | if (sh_buf.len < shentsize) return error.InvalidElfFile; |
| 686 | | |
| 687 | | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); |
| 688 | | const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); |
| 689 | | const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf)); |
| 690 | | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); |
| 691 | | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); |
| 692 | | var strtab_buf: [4096:0]u8 = undefined; |
| 693 | | const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len); |
| 694 | | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); |
| 695 | | const shstrtab = strtab_buf[0..shstrtab_read_len]; |
| 696 | | |
| 697 | | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); |
| 698 | | var sh_i: u16 = 0; |
| 699 | | const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { |
| 700 | | // Reserve some bytes so that we can deref the 64-bit struct fields |
| 701 | | // even when the ELF file is 32-bits. |
| 702 | | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); |
| 703 | | const sh_read_byte_len = try preadMin( |
| 704 | | file, |
| 705 | | sh_buf[0 .. sh_buf.len - sh_reserve], |
| 706 | | shoff, |
| 707 | | shentsize, |
| 739 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 740 | |
| 741 | 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); |
| 743 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); |
| 744 | |
| 745 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; |
| 746 | if (sh_buf.len < shentsize) return error.InvalidElfFile; |
| 747 | |
| 748 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); |
| 749 | 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)); |
| 751 | 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); |
| 753 | var strtab_buf: [4096:0]u8 = undefined; |
| 754 | 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); |
| 756 | const shstrtab = strtab_buf[0..shstrtab_read_len]; |
| 757 | |
| 758 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); |
| 759 | var sh_i: u16 = 0; |
| 760 | 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 |
| 762 | // even when the ELF file is 32-bits. |
| 763 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); |
| 764 | const sh_read_byte_len = try preadMin( |
| 765 | file, |
| 766 | sh_buf[0 .. sh_buf.len - sh_reserve], |
| 767 | shoff, |
| 768 | 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; |
| 710 | | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ |
| 711 | | sh_i += 1; |
| 712 | | shoff += shentsize; |
| 713 | | sh_buf_i += shentsize; |
| 714 | | }) { |
| 715 | | const sh32 = @ptrCast( |
| 716 | | *elf.Elf32_Shdr, |
| 717 | | @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), |
| 718 | | ); |
| 719 | | const sh64 = @ptrCast( |
| 720 | | *elf.Elf64_Shdr, |
| 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 | | } |
| 780 | const sh64 = @ptrCast( |
| 781 | *elf.Elf64_Shdr, |
| 782 | @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), |
| 783 | ); |
| 784 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); |
| 785 | // TODO this pointer cast should not be necessary |
| 786 | const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0); |
| 787 | if (mem.eql(u8, sh_name, ".dynstr")) { |
| 788 | break :find_dyn_str .{ |
| 789 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), |
| 790 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), |
| 791 | }; |
| 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 | 798 | // TODO this pointer cast should not be necessary |
| 737 | 799 | const rpoff_usize = std.math.cast(usize, rpoff) orelse return error.InvalidElfFile; |
| 738 | 800 | if (rpoff_usize > ds.size) return error.InvalidElfFile; |
| ... | ... | @@ -746,116 +808,26 @@ pub fn abiAndDynamicLinkerFromFile( |
| 746 | 808 | const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab.ptr, 0), 0); |
| 747 | 809 | var it = mem.tokenize(u8, rpath_list, ":"); |
| 748 | 810 | while (it.next()) |rpath| { |
| 749 | | var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { |
| 750 | | error.NameTooLong => unreachable, |
| 751 | | error.InvalidUtf8 => unreachable, |
| 752 | | error.BadPathName => unreachable, |
| 753 | | error.DeviceBusy => unreachable, |
| 754 | | |
| 755 | | error.FileNotFound, |
| 756 | | error.NotDir, |
| 757 | | error.InvalidHandle, |
| 758 | | error.AccessDenied, |
| 759 | | error.NoDevice, |
| 760 | | => continue, |
| 761 | | |
| 762 | | error.ProcessFdQuotaExceeded, |
| 763 | | error.SystemFdQuotaExceeded, |
| 764 | | error.SystemResources, |
| 765 | | error.SymLinkLoop, |
| 766 | | error.Unexpected, |
| 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; |
| 811 | if (glibcVerFromRPath(rpath)) |ver| { |
| 812 | result.target.os.version_range.linux.glibc = ver; |
| 813 | break; |
| 814 | } else |err| switch (err) { |
| 815 | error.GLibCNotFound => continue, |
| 816 | else => |e| return e, |
| 817 | } |
| 818 | } |
| 819 | } else if (result.dynamic_linker.get()) |dl_path| { |
| 820 | // There is no DT_RUNPATH so we try to find libc.so.6 inside the same |
| 821 | // directory as the dynamic linker. |
| 822 | if (fs.path.dirname(dl_path)) |rpath| { |
| 823 | if (glibcVerFromRPath(rpath)) |ver| { |
| 824 | result.target.os.version_range.linux.glibc = ver; |
| 825 | } else |err| switch (err) { |
| 826 | error.GLibCNotFound => {}, |
| 827 | else => |e| return e, |
| 828 | } |
| 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 | |