| ... | ... | @@ -28,6 +28,7 @@ pub const DetectError = error{ |
| 28 | 28 | SystemFdQuotaExceeded, |
| 29 | 29 | DeviceBusy, |
| 30 | 30 | OSVersionDetectionFail, |
| 31 | Unexpected, |
| 31 | 32 | }; |
| 32 | 33 | |
| 33 | 34 | /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected |
| ... | ... | @@ -36,8 +37,7 @@ pub const DetectError = error{ |
| 36 | 37 | /// relative to that. |
| 37 | 38 | /// Any resources this function allocates are released before returning, and so there is no |
| 38 | 39 | /// deinitialization method. |
| 39 | | /// TODO Remove the Allocator requirement from this function. |
| 40 | | pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo { |
| 40 | pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo { |
| 41 | 41 | var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch()); |
| 42 | 42 | if (cross_target.os_tag == null) { |
| 43 | 43 | switch (builtin.target.os.tag) { |
| ... | ... | @@ -198,7 +198,7 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ |
| 198 | 198 | } orelse backup_cpu_detection: { |
| 199 | 199 | break :backup_cpu_detection Target.Cpu.baseline(cpu_arch); |
| 200 | 200 | }; |
| 201 | | var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target); |
| 201 | var result = try detectAbiAndDynamicLinker(cpu, os, cross_target); |
| 202 | 202 | // For x86, we need to populate some CPU feature flags depending on architecture |
| 203 | 203 | // and mode: |
| 204 | 204 | // * 16bit_mode => if the abi is code16 |
| ... | ... | @@ -235,13 +235,20 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ |
| 235 | 235 | return result; |
| 236 | 236 | } |
| 237 | 237 | |
| 238 | | /// First we attempt to use the executable's own binary. If it is dynamically |
| 239 | | /// linked, then it should answer both the C ABI question and the dynamic linker question. |
| 240 | | /// If it is statically linked, then we try /usr/bin/env (or the file it references in shebang). If that does not provide the answer, then |
| 241 | | /// we fall back to the defaults. |
| 242 | | /// TODO Remove the Allocator requirement from this function. |
| 238 | /// In the past, this function attempted to use the executable's own binary if it was dynamically |
| 239 | /// linked to answer both the C ABI question and the dynamic linker question. However, this |
| 240 | /// could be problematic on a system that uses a RUNPATH for the compiler binary, locking |
| 241 | /// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc |
| 242 | /// version. The problem is that libc.so.6 glibc version will match that of the system while |
| 243 | /// the dynamic linker will match that of the compiler binary. Executables with these versions |
| 244 | /// mismatching will fail to run. |
| 245 | /// |
| 246 | /// Therefore, this function works the same regardless of whether the compiler binary is |
| 247 | /// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the |
| 248 | /// answer to these questions, or if there is a shebang line, then it chases the referenced |
| 249 | /// file recursively. If that does not provide the answer, then the function falls back to |
| 250 | /// defaults. |
| 243 | 251 | fn detectAbiAndDynamicLinker( |
| 244 | | allocator: Allocator, |
| 245 | 252 | cpu: Target.Cpu, |
| 246 | 253 | os: Target.Os, |
| 247 | 254 | cross_target: CrossTarget, |
| ... | ... | @@ -279,8 +286,8 @@ fn detectAbiAndDynamicLinker( |
| 279 | 286 | const ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch); |
| 280 | 287 | |
| 281 | 288 | for (all_abis) |abi| { |
| 282 | | // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and |
| 283 | | // skip adding it to `ld_info_list`. |
| 289 | // This may be a nonsensical parameter. We detect this with |
| 290 | // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`. |
| 284 | 291 | const target: Target = .{ |
| 285 | 292 | .cpu = cpu, |
| 286 | 293 | .os = os, |
| ... | ... | @@ -300,64 +307,6 @@ fn detectAbiAndDynamicLinker( |
| 300 | 307 | |
| 301 | 308 | // Best case scenario: the executable is dynamically linked, and we can iterate |
| 302 | 309 | // over our own shared objects and find a dynamic linker. |
| 303 | | self_exe: { |
| 304 | | const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator); |
| 305 | | defer { |
| 306 | | for (lib_paths) |lib_path| { |
| 307 | | allocator.free(lib_path); |
| 308 | | } |
| 309 | | allocator.free(lib_paths); |
| 310 | | } |
| 311 | | |
| 312 | | var found_ld_info: LdInfo = undefined; |
| 313 | | var found_ld_path: [:0]const u8 = undefined; |
| 314 | | |
| 315 | | // Look for dynamic linker. |
| 316 | | // This is O(N^M) but typical case here is N=2 and M=10. |
| 317 | | find_ld: for (lib_paths) |lib_path| { |
| 318 | | for (ld_info_list) |ld_info| { |
| 319 | | const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); |
| 320 | | if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) { |
| 321 | | found_ld_info = ld_info; |
| 322 | | found_ld_path = lib_path; |
| 323 | | break :find_ld; |
| 324 | | } |
| 325 | | } |
| 326 | | } else break :self_exe; |
| 327 | | |
| 328 | | // Look for glibc version. |
| 329 | | var os_adjusted = os; |
| 330 | | if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and |
| 331 | | cross_target.glibc_version == null) |
| 332 | | { |
| 333 | | for (lib_paths) |lib_path| { |
| 334 | | if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) { |
| 335 | | os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) { |
| 336 | | error.UnrecognizedGnuLibCFileName => continue, |
| 337 | | error.InvalidGnuLibCVersion => continue, |
| 338 | | error.GnuLibCVersionUnavailable => continue, |
| 339 | | else => |e| return e, |
| 340 | | }; |
| 341 | | break; |
| 342 | | } |
| 343 | | } |
| 344 | | } |
| 345 | | |
| 346 | | var result: NativeTargetInfo = .{ |
| 347 | | .target = .{ |
| 348 | | .cpu = cpu, |
| 349 | | .os = os_adjusted, |
| 350 | | .abi = cross_target.abi orelse found_ld_info.abi, |
| 351 | | .ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os_adjusted.tag, cpu.arch), |
| 352 | | }, |
| 353 | | .dynamic_linker = if (cross_target.dynamic_linker.get() == null) |
| 354 | | DynamicLinker.init(found_ld_path) |
| 355 | | else |
| 356 | | cross_target.dynamic_linker, |
| 357 | | }; |
| 358 | | return result; |
| 359 | | } |
| 360 | | |
| 361 | 310 | const elf_file = blk: { |
| 362 | 311 | // This block looks for a shebang line in /usr/bin/env, |
| 363 | 312 | // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead, |
| ... | ... | @@ -369,7 +318,7 @@ fn detectAbiAndDynamicLinker( |
| 369 | 318 | // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) |
| 370 | 319 | var buffer: [258]u8 = undefined; |
| 371 | 320 | while (true) { |
| 372 | | const file = std.fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { |
| 321 | const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) { |
| 373 | 322 | error.NoSpaceLeft => unreachable, |
| 374 | 323 | error.NameTooLong => unreachable, |
| 375 | 324 | error.PathAlreadyExists => unreachable, |
| ... | ... | @@ -396,38 +345,29 @@ fn detectAbiAndDynamicLinker( |
| 396 | 345 | |
| 397 | 346 | else => |e| return e, |
| 398 | 347 | }; |
| 348 | errdefer file.close(); |
| 399 | 349 | |
| 400 | | const line = file.reader().readUntilDelimiter(&buffer, '\n') catch |err| switch (err) { |
| 401 | | error.IsDir => unreachable, // Handled before |
| 402 | | error.AccessDenied => unreachable, |
| 403 | | error.WouldBlock => unreachable, // Did not request blocking mode |
| 404 | | error.OperationAborted => unreachable, // Windows-only |
| 405 | | error.BrokenPipe => unreachable, |
| 406 | | error.ConnectionResetByPeer => unreachable, |
| 407 | | error.ConnectionTimedOut => unreachable, |
| 408 | | error.InputOutput => unreachable, |
| 409 | | error.Unexpected => unreachable, |
| 410 | | |
| 411 | | error.StreamTooLong, |
| 412 | | error.EndOfStream, |
| 413 | | error.NotOpenForReading, |
| 350 | const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) { |
| 351 | error.UnexpectedEndOfFile, |
| 352 | error.UnableToReadElfFile, |
| 414 | 353 | => break :blk file, |
| 415 | 354 | |
| 416 | | else => |e| { |
| 417 | | file.close(); |
| 418 | | return e; |
| 419 | | }, |
| 355 | else => |e| return e, |
| 420 | 356 | }; |
| 357 | const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file; |
| 358 | const line = buffer[0..newline]; |
| 421 | 359 | if (!mem.startsWith(u8, line, "#!")) break :blk file; |
| 422 | | var it = std.mem.tokenize(u8, line[2..], " "); |
| 423 | | file.close(); |
| 360 | var it = mem.tokenize(u8, line[2..], " "); |
| 424 | 361 | file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target); |
| 362 | file.close(); |
| 425 | 363 | } |
| 426 | 364 | }; |
| 427 | 365 | defer elf_file.close(); |
| 428 | 366 | |
| 429 | 367 | // If Zig is statically linked, such as via distributed binary static builds, the above |
| 430 | 368 | // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file. |
| 369 | // TODO: inline this function and combine the buffer we already read above to find |
| 370 | // the possible shebang line with the buffer we use for the ELF header. |
| 431 | 371 | return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) { |
| 432 | 372 | error.FileSystem, |
| 433 | 373 | error.SystemResources, |
| ... | ... | @@ -453,25 +393,190 @@ fn detectAbiAndDynamicLinker( |
| 453 | 393 | }; |
| 454 | 394 | } |
| 455 | 395 | |
| 456 | | const glibc_so_basename = "libc.so.6"; |
| 396 | fn glibcVerFromRPath(rpath: []const u8) !std.builtin.Version { |
| 397 | var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { |
| 398 | error.NameTooLong => unreachable, |
| 399 | error.InvalidUtf8 => unreachable, |
| 400 | error.BadPathName => unreachable, |
| 401 | error.DeviceBusy => unreachable, |
| 402 | |
| 403 | error.FileNotFound, |
| 404 | error.NotDir, |
| 405 | error.InvalidHandle, |
| 406 | error.AccessDenied, |
| 407 | error.NoDevice, |
| 408 | => return error.GLibCNotFound, |
| 457 | 409 | |
| 458 | | fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version { |
| 459 | | var link_buf: [std.os.PATH_MAX]u8 = undefined; |
| 460 | | const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) { |
| 461 | | error.AccessDenied => return error.GnuLibCVersionUnavailable, |
| 462 | | error.FileSystem => return error.FileSystem, |
| 463 | | error.SymLinkLoop => return error.SymLinkLoop, |
| 410 | error.ProcessFdQuotaExceeded, |
| 411 | error.SystemFdQuotaExceeded, |
| 412 | error.SystemResources, |
| 413 | error.SymLinkLoop, |
| 414 | error.Unexpected, |
| 415 | => |e| return e, |
| 416 | }; |
| 417 | defer dir.close(); |
| 418 | |
| 419 | // Now we have a candidate for the path to libc shared object. In |
| 420 | // the past, we used readlink() here because the link name would |
| 421 | // reveal the glibc version. However, in more recent GNU/Linux |
| 422 | // installations, there is no symlink. Thus we instead use a more |
| 423 | // robust check of opening the libc shared object and looking at the |
| 424 | // .dynstr section, and finding the max version number of symbols |
| 425 | // that start with "GLIBC_2.". |
| 426 | const glibc_so_basename = "libc.so.6"; |
| 427 | var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { |
| 464 | 428 | error.NameTooLong => unreachable, |
| 465 | | error.NotLink => return error.GnuLibCVersionUnavailable, |
| 466 | | error.FileNotFound => return error.GnuLibCVersionUnavailable, |
| 467 | | error.SystemResources => return error.SystemResources, |
| 468 | | error.NotDir => return error.GnuLibCVersionUnavailable, |
| 469 | | error.Unexpected => return error.GnuLibCVersionUnavailable, |
| 470 | 429 | error.InvalidUtf8 => unreachable, // Windows only |
| 471 | 430 | error.BadPathName => unreachable, // Windows only |
| 472 | | error.UnsupportedReparsePointType => unreachable, // Windows only |
| 431 | error.PipeBusy => unreachable, // Windows-only |
| 432 | error.SharingViolation => unreachable, // Windows-only |
| 433 | error.FileLocksNotSupported => unreachable, // No lock requested. |
| 434 | error.NoSpaceLeft => unreachable, // read-only |
| 435 | error.PathAlreadyExists => unreachable, // read-only |
| 436 | error.DeviceBusy => unreachable, // read-only |
| 437 | error.FileBusy => unreachable, // read-only |
| 438 | error.InvalidHandle => unreachable, // should not be in the error set |
| 439 | error.WouldBlock => unreachable, // not using O_NONBLOCK |
| 440 | error.NoDevice => unreachable, // not asking for a special device |
| 441 | |
| 442 | error.AccessDenied, |
| 443 | error.FileNotFound, |
| 444 | error.NotDir, |
| 445 | error.IsDir, |
| 446 | => return error.GLibCNotFound, |
| 447 | |
| 448 | error.FileTooBig => return error.Unexpected, |
| 449 | |
| 450 | error.ProcessFdQuotaExceeded, |
| 451 | error.SystemFdQuotaExceeded, |
| 452 | error.SystemResources, |
| 453 | error.SymLinkLoop, |
| 454 | error.Unexpected, |
| 455 | => |e| return e, |
| 456 | }; |
| 457 | defer f.close(); |
| 458 | |
| 459 | return glibcVerFromSoFile(f) catch |err| switch (err) { |
| 460 | error.InvalidElfMagic, |
| 461 | error.InvalidElfEndian, |
| 462 | error.InvalidElfClass, |
| 463 | error.InvalidElfFile, |
| 464 | error.InvalidElfVersion, |
| 465 | error.InvalidGnuLibCVersion, |
| 466 | error.UnexpectedEndOfFile, |
| 467 | => return error.GLibCNotFound, |
| 468 | |
| 469 | error.SystemResources, |
| 470 | error.UnableToReadElfFile, |
| 471 | error.Unexpected, |
| 472 | error.FileSystem, |
| 473 | => |e| return e, |
| 473 | 474 | }; |
| 474 | | return glibcVerFromLinkName(link_name, "libc-"); |
| 475 | } |
| 476 | |
| 477 | fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version { |
| 478 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; |
| 479 | _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); |
| 480 | const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf); |
| 481 | const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf); |
| 482 | if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; |
| 483 | const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { |
| 484 | elf.ELFDATA2LSB => .Little, |
| 485 | elf.ELFDATA2MSB => .Big, |
| 486 | else => return error.InvalidElfEndian, |
| 487 | }; |
| 488 | const need_bswap = elf_endian != native_endian; |
| 489 | if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; |
| 490 | |
| 491 | const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { |
| 492 | elf.ELFCLASS32 => false, |
| 493 | elf.ELFCLASS64 => true, |
| 494 | else => return error.InvalidElfClass, |
| 495 | }; |
| 496 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 497 | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); |
| 498 | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); |
| 499 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); |
| 500 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; |
| 501 | if (sh_buf.len < shentsize) return error.InvalidElfFile; |
| 502 | |
| 503 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); |
| 504 | const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); |
| 505 | const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf)); |
| 506 | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); |
| 507 | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); |
| 508 | var strtab_buf: [4096:0]u8 = undefined; |
| 509 | const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len); |
| 510 | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); |
| 511 | const shstrtab = strtab_buf[0..shstrtab_read_len]; |
| 512 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); |
| 513 | var sh_i: u16 = 0; |
| 514 | const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { |
| 515 | // Reserve some bytes so that we can deref the 64-bit struct fields |
| 516 | // even when the ELF file is 32-bits. |
| 517 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); |
| 518 | const sh_read_byte_len = try preadMin( |
| 519 | file, |
| 520 | sh_buf[0 .. sh_buf.len - sh_reserve], |
| 521 | shoff, |
| 522 | shentsize, |
| 523 | ); |
| 524 | var sh_buf_i: usize = 0; |
| 525 | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ |
| 526 | sh_i += 1; |
| 527 | shoff += shentsize; |
| 528 | sh_buf_i += shentsize; |
| 529 | }) { |
| 530 | const sh32 = @ptrCast( |
| 531 | *elf.Elf32_Shdr, |
| 532 | @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), |
| 533 | ); |
| 534 | const sh64 = @ptrCast( |
| 535 | *elf.Elf64_Shdr, |
| 536 | @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), |
| 537 | ); |
| 538 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); |
| 539 | // TODO this pointer cast should not be necessary |
| 540 | const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0); |
| 541 | if (mem.eql(u8, sh_name, ".dynstr")) { |
| 542 | break :find_dyn_str .{ |
| 543 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), |
| 544 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), |
| 545 | }; |
| 546 | } |
| 547 | } |
| 548 | } else return error.InvalidGnuLibCVersion; |
| 549 | |
| 550 | // Here we loop over all the strings in the dynstr string table, assuming that any |
| 551 | // strings that start with "GLIBC_2." indicate the existence of such a glibc version, |
| 552 | // and furthermore, that the system-installed glibc is at minimum that version. |
| 553 | |
| 554 | // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system. |
| 555 | // Here I use double this value plus some headroom. This makes it only need |
| 556 | // a single read syscall here. |
| 557 | var buf: [80000]u8 = undefined; |
| 558 | if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion; |
| 559 | |
| 560 | const dynstr_size = @intCast(usize, dynstr.size); |
| 561 | const dynstr_bytes = buf[0..dynstr_size]; |
| 562 | _ = try preadMin(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len); |
| 563 | var it = mem.split(u8, dynstr_bytes, &.{0}); |
| 564 | var max_ver: std.builtin.Version = .{ .major = 2, .minor = 2, .patch = 5 }; |
| 565 | while (it.next()) |s| { |
| 566 | if (mem.startsWith(u8, s, "GLIBC_2.")) { |
| 567 | const chopped = s["GLIBC_".len..]; |
| 568 | const ver = std.builtin.Version.parse(chopped) catch |err| switch (err) { |
| 569 | error.Overflow => return error.InvalidGnuLibCVersion, |
| 570 | error.InvalidCharacter => return error.InvalidGnuLibCVersion, |
| 571 | error.InvalidVersion => return error.InvalidGnuLibCVersion, |
| 572 | }; |
| 573 | switch (ver.order(max_ver)) { |
| 574 | .gt => max_ver = ver, |
| 575 | .lt, .eq => continue, |
| 576 | } |
| 577 | } |
| 578 | } |
| 579 | return max_ver; |
| 475 | 580 | } |
| 476 | 581 | |
| 477 | 582 | fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version { |
| ... | ... | @@ -641,65 +746,65 @@ pub fn abiAndDynamicLinkerFromFile( |
| 641 | 746 | if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and |
| 642 | 747 | cross_target.glibc_version == null) |
| 643 | 748 | { |
| 644 | | if (rpath_offset) |rpoff| { |
| 645 | | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 646 | | |
| 647 | | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); |
| 648 | | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); |
| 649 | | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); |
| 650 | | |
| 651 | | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; |
| 652 | | if (sh_buf.len < shentsize) return error.InvalidElfFile; |
| 653 | | |
| 654 | | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); |
| 655 | | const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); |
| 656 | | const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf)); |
| 657 | | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); |
| 658 | | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); |
| 659 | | var strtab_buf: [4096:0]u8 = undefined; |
| 660 | | const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len); |
| 661 | | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); |
| 662 | | const shstrtab = strtab_buf[0..shstrtab_read_len]; |
| 663 | | |
| 664 | | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); |
| 665 | | var sh_i: u16 = 0; |
| 666 | | const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { |
| 667 | | // Reserve some bytes so that we can deref the 64-bit struct fields |
| 668 | | // even when the ELF file is 32-bits. |
| 669 | | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); |
| 670 | | const sh_read_byte_len = try preadMin( |
| 671 | | file, |
| 672 | | sh_buf[0 .. sh_buf.len - sh_reserve], |
| 673 | | shoff, |
| 674 | | shentsize, |
| 749 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 750 | |
| 751 | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); |
| 752 | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); |
| 753 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); |
| 754 | |
| 755 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; |
| 756 | if (sh_buf.len < shentsize) return error.InvalidElfFile; |
| 757 | |
| 758 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); |
| 759 | const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); |
| 760 | const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf)); |
| 761 | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); |
| 762 | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); |
| 763 | var strtab_buf: [4096:0]u8 = undefined; |
| 764 | const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len); |
| 765 | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); |
| 766 | const shstrtab = strtab_buf[0..shstrtab_read_len]; |
| 767 | |
| 768 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); |
| 769 | var sh_i: u16 = 0; |
| 770 | const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { |
| 771 | // Reserve some bytes so that we can deref the 64-bit struct fields |
| 772 | // even when the ELF file is 32-bits. |
| 773 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); |
| 774 | const sh_read_byte_len = try preadMin( |
| 775 | file, |
| 776 | sh_buf[0 .. sh_buf.len - sh_reserve], |
| 777 | shoff, |
| 778 | shentsize, |
| 779 | ); |
| 780 | var sh_buf_i: usize = 0; |
| 781 | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ |
| 782 | sh_i += 1; |
| 783 | shoff += shentsize; |
| 784 | sh_buf_i += shentsize; |
| 785 | }) { |
| 786 | const sh32 = @ptrCast( |
| 787 | *elf.Elf32_Shdr, |
| 788 | @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), |
| 675 | 789 | ); |
| 676 | | var sh_buf_i: usize = 0; |
| 677 | | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ |
| 678 | | sh_i += 1; |
| 679 | | shoff += shentsize; |
| 680 | | sh_buf_i += shentsize; |
| 681 | | }) { |
| 682 | | const sh32 = @ptrCast( |
| 683 | | *elf.Elf32_Shdr, |
| 684 | | @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), |
| 685 | | ); |
| 686 | | const sh64 = @ptrCast( |
| 687 | | *elf.Elf64_Shdr, |
| 688 | | @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), |
| 689 | | ); |
| 690 | | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); |
| 691 | | // TODO this pointer cast should not be necessary |
| 692 | | const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0); |
| 693 | | if (mem.eql(u8, sh_name, ".dynstr")) { |
| 694 | | break :find_dyn_str .{ |
| 695 | | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), |
| 696 | | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), |
| 697 | | }; |
| 698 | | } |
| 790 | const sh64 = @ptrCast( |
| 791 | *elf.Elf64_Shdr, |
| 792 | @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), |
| 793 | ); |
| 794 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); |
| 795 | // TODO this pointer cast should not be necessary |
| 796 | const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0); |
| 797 | if (mem.eql(u8, sh_name, ".dynstr")) { |
| 798 | break :find_dyn_str .{ |
| 799 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), |
| 800 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), |
| 801 | }; |
| 699 | 802 | } |
| 700 | | } else null; |
| 803 | } |
| 804 | } else null; |
| 701 | 805 | |
| 702 | | if (dynstr) |ds| { |
| 806 | if (dynstr) |ds| { |
| 807 | if (rpath_offset) |rpoff| { |
| 703 | 808 | // TODO this pointer cast should not be necessary |
| 704 | 809 | const rpoff_usize = std.math.cast(usize, rpoff) orelse return error.InvalidElfFile; |
| 705 | 810 | if (rpoff_usize > ds.size) return error.InvalidElfFile; |
| ... | ... | @@ -713,64 +818,31 @@ pub fn abiAndDynamicLinkerFromFile( |
| 713 | 818 | const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab.ptr, 0), 0); |
| 714 | 819 | var it = mem.tokenize(u8, rpath_list, ":"); |
| 715 | 820 | while (it.next()) |rpath| { |
| 716 | | var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { |
| 717 | | error.NameTooLong => unreachable, |
| 718 | | error.InvalidUtf8 => unreachable, |
| 719 | | error.BadPathName => unreachable, |
| 720 | | error.DeviceBusy => unreachable, |
| 721 | | |
| 722 | | error.FileNotFound, |
| 723 | | error.NotDir, |
| 724 | | error.InvalidHandle, |
| 725 | | error.AccessDenied, |
| 726 | | error.NoDevice, |
| 727 | | => continue, |
| 728 | | |
| 729 | | error.ProcessFdQuotaExceeded, |
| 730 | | error.SystemFdQuotaExceeded, |
| 731 | | error.SystemResources, |
| 732 | | error.SymLinkLoop, |
| 733 | | error.Unexpected, |
| 734 | | => |e| return e, |
| 735 | | }; |
| 736 | | defer dir.close(); |
| 737 | | |
| 738 | | var link_buf: [std.os.PATH_MAX]u8 = undefined; |
| 739 | | const link_name = std.os.readlinkatZ( |
| 740 | | dir.fd, |
| 741 | | glibc_so_basename, |
| 742 | | &link_buf, |
| 743 | | ) catch |err| switch (err) { |
| 744 | | error.NameTooLong => unreachable, |
| 745 | | error.InvalidUtf8 => unreachable, // Windows only |
| 746 | | error.BadPathName => unreachable, // Windows only |
| 747 | | error.UnsupportedReparsePointType => unreachable, // Windows only |
| 748 | | |
| 749 | | error.AccessDenied, |
| 750 | | error.FileNotFound, |
| 751 | | error.NotLink, |
| 752 | | error.NotDir, |
| 753 | | => continue, |
| 754 | | |
| 755 | | error.SystemResources, |
| 756 | | error.FileSystem, |
| 757 | | error.SymLinkLoop, |
| 758 | | error.Unexpected, |
| 759 | | => |e| return e, |
| 760 | | }; |
| 761 | | result.target.os.version_range.linux.glibc = glibcVerFromLinkName( |
| 762 | | link_name, |
| 763 | | "libc-", |
| 764 | | ) catch |err| switch (err) { |
| 765 | | error.UnrecognizedGnuLibCFileName, |
| 766 | | error.InvalidGnuLibCVersion, |
| 767 | | => continue, |
| 768 | | }; |
| 769 | | break; |
| 821 | if (glibcVerFromRPath(rpath)) |ver| { |
| 822 | result.target.os.version_range.linux.glibc = ver; |
| 823 | return result; |
| 824 | } else |err| switch (err) { |
| 825 | error.GLibCNotFound => continue, |
| 826 | else => |e| return e, |
| 827 | } |
| 828 | } |
| 829 | } |
| 830 | } |
| 831 | |
| 832 | if (result.dynamic_linker.get()) |dl_path| glibc_ver: { |
| 833 | // There is no DT_RUNPATH so we try to find libc.so.6 inside the same |
| 834 | // directory as the dynamic linker. |
| 835 | if (fs.path.dirname(dl_path)) |rpath| { |
| 836 | if (glibcVerFromRPath(rpath)) |ver| { |
| 837 | result.target.os.version_range.linux.glibc = ver; |
| 838 | return result; |
| 839 | } else |err| switch (err) { |
| 840 | error.GLibCNotFound => {}, |
| 841 | else => |e| return e, |
| 770 | 842 | } |
| 771 | 843 | } |
| 772 | | } else if (result.dynamic_linker.get()) |dl_path| glibc_ver: { |
| 773 | | // There is no DT_RUNPATH but we can try to see if the information is |
| 844 | |
| 845 | // So far, no luck. Next we try to see if the information is |
| 774 | 846 | // present in the symlink data for the dynamic linker path. |
| 775 | 847 | var link_buf: [std.os.PATH_MAX]u8 = undefined; |
| 776 | 848 | const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { |
| ... | ... | @@ -799,6 +871,36 @@ pub fn abiAndDynamicLinkerFromFile( |
| 799 | 871 | error.InvalidGnuLibCVersion, |
| 800 | 872 | => break :glibc_ver, |
| 801 | 873 | }; |
| 874 | return result; |
| 875 | } |
| 876 | |
| 877 | // Nothing worked so far. Finally we fall back to hard-coded search paths. |
| 878 | // Some distros such as Debian keep their libc.so.6 in `/lib/$triple/`. |
| 879 | var path_buf: [std.os.PATH_MAX]u8 = undefined; |
| 880 | var index: usize = 0; |
| 881 | const prefix = "/lib/"; |
| 882 | const cpu_arch = @tagName(result.target.cpu.arch); |
| 883 | const os_tag = @tagName(result.target.os.tag); |
| 884 | const abi = @tagName(result.target.abi); |
| 885 | mem.copy(u8, path_buf[index..], prefix); |
| 886 | index += prefix.len; |
| 887 | mem.copy(u8, path_buf[index..], cpu_arch); |
| 888 | index += cpu_arch.len; |
| 889 | path_buf[index] = '-'; |
| 890 | index += 1; |
| 891 | mem.copy(u8, path_buf[index..], os_tag); |
| 892 | index += os_tag.len; |
| 893 | path_buf[index] = '-'; |
| 894 | index += 1; |
| 895 | mem.copy(u8, path_buf[index..], abi); |
| 896 | index += abi.len; |
| 897 | const rpath = path_buf[0..index]; |
| 898 | if (glibcVerFromRPath(rpath)) |ver| { |
| 899 | result.target.os.version_range.linux.glibc = ver; |
| 900 | return result; |
| 901 | } else |err| switch (err) { |
| 902 | error.GLibCNotFound => {}, |
| 903 | else => |e| return e, |
| 802 | 904 | } |
| 803 | 905 | } |
| 804 | 906 | |