| ... | @@ -28,6 +28,7 @@ pub const DetectError = error{ | ... | @@ -28,6 +28,7 @@ pub const DetectError = error{ |
| 28 | SystemFdQuotaExceeded, | 28 | SystemFdQuotaExceeded, |
| 29 | DeviceBusy, | 29 | DeviceBusy, |
| 30 | OSVersionDetectionFail, | 30 | OSVersionDetectionFail, |
| | 31 | Unexpected, |
| 31 | }; | 32 | }; |
| 32 | | 33 | |
| 33 | /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected | 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,8 +37,7 @@ pub const DetectError = error{ |
| 36 | /// relative to that. | 37 | /// relative to that. |
| 37 | /// Any resources this function allocates are released before returning, and so there is no | 38 | /// Any resources this function allocates are released before returning, and so there is no |
| 38 | /// deinitialization method. | 39 | /// deinitialization method. |
| 39 | /// TODO Remove the Allocator requirement from this function. | 40 | pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo { |
| 40 | pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo { | | |
| 41 | var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch()); | 41 | var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch()); |
| 42 | if (cross_target.os_tag == null) { | 42 | if (cross_target.os_tag == null) { |
| 43 | switch (builtin.target.os.tag) { | 43 | switch (builtin.target.os.tag) { |
| ... | @@ -198,7 +198,7 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ | ... | @@ -198,7 +198,7 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ |
| 198 | } orelse backup_cpu_detection: { | 198 | } orelse backup_cpu_detection: { |
| 199 | break :backup_cpu_detection Target.Cpu.baseline(cpu_arch); | 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 | // For x86, we need to populate some CPU feature flags depending on architecture | 202 | // For x86, we need to populate some CPU feature flags depending on architecture |
| 203 | // and mode: | 203 | // and mode: |
| 204 | // * 16bit_mode => if the abi is code16 | 204 | // * 16bit_mode => if the abi is code16 |
| ... | @@ -235,13 +235,20 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ | ... | @@ -235,13 +235,20 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ |
| 235 | return result; | 235 | return result; |
| 236 | } | 236 | } |
| 237 | | 237 | |
| 238 | /// First we attempt to use the executable's own binary. If it is dynamically | 238 | /// In the past, this function attempted to use the executable's own binary if it was dynamically |
| 239 | /// linked, then it should answer both the C ABI question and the dynamic linker question. | 239 | /// linked to answer both the C ABI question and the dynamic linker question. However, this |
| 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 | 240 | /// could be problematic on a system that uses a RUNPATH for the compiler binary, locking |
| 241 | /// we fall back to the defaults. | 241 | /// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc |
| 242 | /// TODO Remove the Allocator requirement from this function. | 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 | fn detectAbiAndDynamicLinker( | 251 | fn detectAbiAndDynamicLinker( |
| 244 | allocator: Allocator, | | |
| 245 | cpu: Target.Cpu, | 252 | cpu: Target.Cpu, |
| 246 | os: Target.Os, | 253 | os: Target.Os, |
| 247 | cross_target: CrossTarget, | 254 | cross_target: CrossTarget, |
| ... | @@ -279,8 +286,8 @@ fn detectAbiAndDynamicLinker( | ... | @@ -279,8 +286,8 @@ fn detectAbiAndDynamicLinker( |
| 279 | const ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch); | 286 | const ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch); |
| 280 | | 287 | |
| 281 | for (all_abis) |abi| { | 288 | for (all_abis) |abi| { |
| 282 | // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and | 289 | // This may be a nonsensical parameter. We detect this with |
| 283 | // skip adding it to `ld_info_list`. | 290 | // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`. |
| 284 | const target: Target = .{ | 291 | const target: Target = .{ |
| 285 | .cpu = cpu, | 292 | .cpu = cpu, |
| 286 | .os = os, | 293 | .os = os, |
| ... | @@ -300,64 +307,6 @@ fn detectAbiAndDynamicLinker( | ... | @@ -300,64 +307,6 @@ fn detectAbiAndDynamicLinker( |
| 300 | | 307 | |
| 301 | // Best case scenario: the executable is dynamically linked, and we can iterate | 308 | // Best case scenario: the executable is dynamically linked, and we can iterate |
| 302 | // over our own shared objects and find a dynamic linker. | 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 | const elf_file = blk: { | 310 | const elf_file = blk: { |
| 362 | // This block looks for a shebang line in /usr/bin/env, | 311 | // This block looks for a shebang line in /usr/bin/env, |
| 363 | // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead, | 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,7 +318,7 @@ fn detectAbiAndDynamicLinker( |
| 369 | // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) | 318 | // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1) |
| 370 | var buffer: [258]u8 = undefined; | 319 | var buffer: [258]u8 = undefined; |
| 371 | while (true) { | 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 | error.NoSpaceLeft => unreachable, | 322 | error.NoSpaceLeft => unreachable, |
| 374 | error.NameTooLong => unreachable, | 323 | error.NameTooLong => unreachable, |
| 375 | error.PathAlreadyExists => unreachable, | 324 | error.PathAlreadyExists => unreachable, |
| ... | @@ -396,38 +345,29 @@ fn detectAbiAndDynamicLinker( | ... | @@ -396,38 +345,29 @@ fn detectAbiAndDynamicLinker( |
| 396 | | 345 | |
| 397 | else => |e| return e, | 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) { | 350 | const len = preadMin(file, &buffer, 0, buffer.len) catch |err| switch (err) { |
| 401 | error.IsDir => unreachable, // Handled before | 351 | error.UnexpectedEndOfFile, |
| 402 | error.AccessDenied => unreachable, | 352 | error.UnableToReadElfFile, |
| 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, | | |
| 414 | => break :blk file, | 353 | => break :blk file, |
| 415 | | 354 | |
| 416 | else => |e| { | 355 | else => |e| return e, |
| 417 | file.close(); | | |
| 418 | return e; | | |
| 419 | }, | | |
| 420 | }; | 356 | }; |
| | 357 | const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file; |
| | 358 | const line = buffer[0..newline]; |
| 421 | if (!mem.startsWith(u8, line, "#!")) break :blk file; | 359 | if (!mem.startsWith(u8, line, "#!")) break :blk file; |
| 422 | var it = std.mem.tokenize(u8, line[2..], " "); | 360 | var it = mem.tokenize(u8, line[2..], " "); |
| 423 | file.close(); | | |
| 424 | file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target); | 361 | file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, cross_target); |
| | 362 | file.close(); |
| 425 | } | 363 | } |
| 426 | }; | 364 | }; |
| 427 | defer elf_file.close(); | 365 | defer elf_file.close(); |
| 428 | | 366 | |
| 429 | // If Zig is statically linked, such as via distributed binary static builds, the above | 367 | // If Zig is statically linked, such as via distributed binary static builds, the above |
| 430 | // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file. | 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 | return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) { | 371 | return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) { |
| 432 | error.FileSystem, | 372 | error.FileSystem, |
| 433 | error.SystemResources, | 373 | error.SystemResources, |
| ... | @@ -453,25 +393,190 @@ fn detectAbiAndDynamicLinker( | ... | @@ -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 { | 410 | error.ProcessFdQuotaExceeded, |
| 459 | var link_buf: [std.os.PATH_MAX]u8 = undefined; | 411 | error.SystemFdQuotaExceeded, |
| 460 | const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) { | 412 | error.SystemResources, |
| 461 | error.AccessDenied => return error.GnuLibCVersionUnavailable, | 413 | error.SymLinkLoop, |
| 462 | error.FileSystem => return error.FileSystem, | 414 | error.Unexpected, |
| 463 | error.SymLinkLoop => return error.SymLinkLoop, | 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 | error.NameTooLong => unreachable, | 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 | error.InvalidUtf8 => unreachable, // Windows only | 429 | error.InvalidUtf8 => unreachable, // Windows only |
| 471 | error.BadPathName => unreachable, // Windows only | 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 | fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version { | 582 | fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version { |
| ... | @@ -641,65 +746,65 @@ pub fn abiAndDynamicLinkerFromFile( | ... | @@ -641,65 +746,65 @@ pub fn abiAndDynamicLinkerFromFile( |
| 641 | if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and | 746 | if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and |
| 642 | cross_target.glibc_version == null) | 747 | cross_target.glibc_version == null) |
| 643 | { | 748 | { |
| 644 | if (rpath_offset) |rpoff| { | 749 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 645 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); | 750 | |
| 646 | | 751 | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); |
| 647 | 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); |
| 648 | 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); |
| 649 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); | 754 | |
| 650 | | 755 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; |
| 651 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; | 756 | if (sh_buf.len < shentsize) return error.InvalidElfFile; |
| 652 | if (sh_buf.len < shentsize) return error.InvalidElfFile; | 757 | |
| 653 | | 758 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); |
| 654 | _ = try preadMin(file, &sh_buf, str_section_off, shentsize); | 759 | const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); |
| 655 | 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)); |
| 656 | 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); |
| 657 | 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); |
| 658 | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); | 763 | var strtab_buf: [4096:0]u8 = undefined; |
| 659 | var strtab_buf: [4096:0]u8 = undefined; | 764 | const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len); |
| 660 | 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); |
| 661 | const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); | 766 | const shstrtab = strtab_buf[0..shstrtab_read_len]; |
| 662 | const shstrtab = strtab_buf[0..shstrtab_read_len]; | 767 | |
| 663 | | 768 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); |
| 664 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); | 769 | var sh_i: u16 = 0; |
| 665 | var sh_i: u16 = 0; | 770 | const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { |
| 666 | 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 |
| 667 | // Reserve some bytes so that we can deref the 64-bit struct fields | 772 | // even when the ELF file is 32-bits. |
| 668 | // even when the ELF file is 32-bits. | 773 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); |
| 669 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); | 774 | const sh_read_byte_len = try preadMin( |
| 670 | const sh_read_byte_len = try preadMin( | 775 | file, |
| 671 | file, | 776 | sh_buf[0 .. sh_buf.len - sh_reserve], |
| 672 | sh_buf[0 .. sh_buf.len - sh_reserve], | 777 | shoff, |
| 673 | shoff, | 778 | shentsize, |
| 674 | 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; | 790 | const sh64 = @ptrCast( |
| 677 | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ | 791 | *elf.Elf64_Shdr, |
| 678 | sh_i += 1; | 792 | @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), |
| 679 | shoff += shentsize; | 793 | ); |
| 680 | sh_buf_i += shentsize; | 794 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); |
| 681 | }) { | 795 | // TODO this pointer cast should not be necessary |
| 682 | const sh32 = @ptrCast( | 796 | const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0); |
| 683 | *elf.Elf32_Shdr, | 797 | if (mem.eql(u8, sh_name, ".dynstr")) { |
| 684 | @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), | 798 | break :find_dyn_str .{ |
| 685 | ); | 799 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), |
| 686 | const sh64 = @ptrCast( | 800 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), |
| 687 | *elf.Elf64_Shdr, | 801 | }; |
| 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 | } | | |
| 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 | // TODO this pointer cast should not be necessary | 808 | // TODO this pointer cast should not be necessary |
| 704 | const rpoff_usize = std.math.cast(usize, rpoff) orelse return error.InvalidElfFile; | 809 | const rpoff_usize = std.math.cast(usize, rpoff) orelse return error.InvalidElfFile; |
| 705 | if (rpoff_usize > ds.size) return error.InvalidElfFile; | 810 | if (rpoff_usize > ds.size) return error.InvalidElfFile; |
| ... | @@ -713,64 +818,31 @@ pub fn abiAndDynamicLinkerFromFile( | ... | @@ -713,64 +818,31 @@ pub fn abiAndDynamicLinkerFromFile( |
| 713 | const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab.ptr, 0), 0); | 818 | const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab.ptr, 0), 0); |
| 714 | var it = mem.tokenize(u8, rpath_list, ":"); | 819 | var it = mem.tokenize(u8, rpath_list, ":"); |
| 715 | while (it.next()) |rpath| { | 820 | while (it.next()) |rpath| { |
| 716 | var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { | 821 | if (glibcVerFromRPath(rpath)) |ver| { |
| 717 | error.NameTooLong => unreachable, | 822 | result.target.os.version_range.linux.glibc = ver; |
| 718 | error.InvalidUtf8 => unreachable, | 823 | return result; |
| 719 | error.BadPathName => unreachable, | 824 | } else |err| switch (err) { |
| 720 | error.DeviceBusy => unreachable, | 825 | error.GLibCNotFound => continue, |
| 721 | | 826 | else => |e| return e, |
| 722 | error.FileNotFound, | 827 | } |
| 723 | error.NotDir, | 828 | } |
| 724 | error.InvalidHandle, | 829 | } |
| 725 | error.AccessDenied, | 830 | } |
| 726 | error.NoDevice, | 831 | |
| 727 | => continue, | 832 | if (result.dynamic_linker.get()) |dl_path| glibc_ver: { |
| 728 | | 833 | // There is no DT_RUNPATH so we try to find libc.so.6 inside the same |
| 729 | error.ProcessFdQuotaExceeded, | 834 | // directory as the dynamic linker. |
| 730 | error.SystemFdQuotaExceeded, | 835 | if (fs.path.dirname(dl_path)) |rpath| { |
| 731 | error.SystemResources, | 836 | if (glibcVerFromRPath(rpath)) |ver| { |
| 732 | error.SymLinkLoop, | 837 | result.target.os.version_range.linux.glibc = ver; |
| 733 | error.Unexpected, | 838 | return result; |
| 734 | => |e| return e, | 839 | } else |err| switch (err) { |
| 735 | }; | 840 | error.GLibCNotFound => {}, |
| 736 | defer dir.close(); | 841 | else => |e| return e, |
| 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; | | |
| 770 | } | 842 | } |
| 771 | } | 843 | } |
| 772 | } else if (result.dynamic_linker.get()) |dl_path| glibc_ver: { | 844 | |
| 773 | // There is no DT_RUNPATH but we can try to see if the information is | 845 | // So far, no luck. Next we try to see if the information is |
| 774 | // present in the symlink data for the dynamic linker path. | 846 | // present in the symlink data for the dynamic linker path. |
| 775 | var link_buf: [std.os.PATH_MAX]u8 = undefined; | 847 | var link_buf: [std.os.PATH_MAX]u8 = undefined; |
| 776 | const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { | 848 | const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { |
| ... | @@ -799,6 +871,36 @@ pub fn abiAndDynamicLinkerFromFile( | ... | @@ -799,6 +871,36 @@ pub fn abiAndDynamicLinkerFromFile( |
| 799 | error.InvalidGnuLibCVersion, | 871 | error.InvalidGnuLibCVersion, |
| 800 | => break :glibc_ver, | 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 | |