1const std = @import("../../std.zig"); 2const builtin = @import("builtin"); 3const mem = std.mem; 4const assert = std.debug.assert; 5const fs = std.fs; 6const elf = std.elf; 7const native_endian = builtin.cpu.arch.endian(); 8 9const NativeTargetInfo = @This(); 10const Target = std.Target; 11const Allocator = std.mem.Allocator; 12const CrossTarget = std.zig.CrossTarget; 13const windows = std.zig.system.windows; 14const darwin = std.zig.system.darwin; 15const linux = std.zig.system.linux; 16 17target: Target, 18dynamic_linker: DynamicLinker = DynamicLinker{}, 19 20pub const DynamicLinker = Target.DynamicLinker; 21 22pub const DetectError = error{ 23 OutOfMemory, 24 FileSystem, 25 SystemResources, 26 SymLinkLoop, 27 ProcessFdQuotaExceeded, 28 SystemFdQuotaExceeded, 29 DeviceBusy, 30 OSVersionDetectionFail, 31}; 32 33/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected 34/// natively, which should be standard or default, and which are provided explicitly, this function 35/// resolves the native components by detecting the native system, and then resolves standard/default parts 36/// relative to that. 37/// Any resources this function allocates are released before returning, and so there is no 38/// deinitialization method. 39/// TODO Remove the Allocator requirement from this function. 40pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo { 41 var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch()); 42 if (cross_target.os_tag == null) { 43 switch (builtin.target.os.tag) { 44 .linux => { 45 const uts = std.os.uname(); 46 const release = mem.sliceTo(&uts.release, 0); 47 // The release field sometimes has a weird format, 48 // `Version.parse` will attempt to find some meaningful interpretation. 49 if (std.builtin.Version.parse(release)) |ver| { 50 os.version_range.linux.range.min = ver; 51 os.version_range.linux.range.max = ver; 52 } else |err| switch (err) { 53 error.Overflow => {}, 54 error.InvalidCharacter => {}, 55 error.InvalidVersion => {}, 56 } 57 }, 58 .solaris => { 59 const uts = std.os.uname(); 60 const release = mem.sliceTo(&uts.release, 0); 61 if (std.builtin.Version.parse(release)) |ver| { 62 os.version_range.semver.min = ver; 63 os.version_range.semver.max = ver; 64 } else |err| switch (err) { 65 error.Overflow => {}, 66 error.InvalidCharacter => {}, 67 error.InvalidVersion => {}, 68 } 69 }, 70 .windows => { 71 const detected_version = windows.detectRuntimeVersion(); 72 os.version_range.windows.min = detected_version; 73 os.version_range.windows.max = detected_version; 74 }, 75 .macos => try darwin.macos.detect(&os), 76 .freebsd, .netbsd, .dragonfly => { 77 const key = switch (builtin.target.os.tag) { 78 .freebsd => "kern.osreldate", 79 .netbsd, .dragonfly => "kern.osrevision", 80 else => unreachable, 81 }; 82 var value: u32 = undefined; 83 var len: usize = @sizeOf(@TypeOf(value)); 84 85 std.os.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { 86 error.NameTooLong => unreachable, // constant, known good value 87 error.PermissionDenied => unreachable, // only when setting values, 88 error.SystemResources => unreachable, // memory already on the stack 89 error.UnknownName => unreachable, // constant, known good value 90 error.Unexpected => return error.OSVersionDetectionFail, 91 }; 92 93 switch (builtin.target.os.tag) { 94 .freebsd => { 95 // https://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/versions.html 96 // Major * 100,000 has been convention since FreeBSD 2.2 (1997) 97 // Minor * 1(0),000 summed has been convention since FreeBSD 2.2 (1997) 98 // e.g. 492101 = 4.11-STABLE = 4.(9+2) 99 const major = value / 100_000; 100 const minor1 = value % 100_000 / 10_000; // usually 0 since 5.1 101 const minor2 = value % 10_000 / 1_000; // 0 before 5.1, minor version since 102 const patch = value % 1_000; 103 os.version_range.semver.min = .{ .major = major, .minor = minor1 + minor2, .patch = patch }; 104 os.version_range.semver.max = os.version_range.semver.min; 105 }, 106 .netbsd => { 107 // #define __NetBSD_Version__ MMmmrrpp00 108 // 109 // M = major version 110 // m = minor version; a minor number of 99 indicates current. 111 // r = 0 (*) 112 // p = patchlevel 113 const major = value / 100_000_000; 114 const minor = value % 100_000_000 / 1_000_000; 115 const patch = value % 10_000 / 100; 116 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; 117 os.version_range.semver.max = os.version_range.semver.min; 118 }, 119 .dragonfly => { 120 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cb2cde83771754aeef9bb3251ee48959138dec87/Makefile.inc1#L15-L17 121 // flat base10 format: Mmmmpp 122 // M = major 123 // m = minor; odd-numbers indicate current dev branch 124 // p = patch 125 const major = value / 100_000; 126 const minor = value % 100_000 / 100; 127 const patch = value % 100; 128 os.version_range.semver.min = .{ .major = major, .minor = minor, .patch = patch }; 129 os.version_range.semver.max = os.version_range.semver.min; 130 }, 131 else => unreachable, 132 } 133 }, 134 .openbsd => { 135 const mib: [2]c_int = [_]c_int{ 136 std.os.CTL.KERN, 137 std.os.KERN.OSRELEASE, 138 }; 139 var buf: [64]u8 = undefined; 140 var len: usize = buf.len; 141 142 std.os.sysctl(&mib, &buf, &len, null, 0) catch |err| switch (err) { 143 error.NameTooLong => unreachable, // constant, known good value 144 error.PermissionDenied => unreachable, // only when setting values, 145 error.SystemResources => unreachable, // memory already on the stack 146 error.UnknownName => unreachable, // constant, known good value 147 error.Unexpected => return error.OSVersionDetectionFail, 148 }; 149 150 if (std.builtin.Version.parse(buf[0 .. len - 1])) |ver| { 151 os.version_range.semver.min = ver; 152 os.version_range.semver.max = ver; 153 } else |_| { 154 return error.OSVersionDetectionFail; 155 } 156 }, 157 else => { 158 // Unimplemented, fall back to default version range. 159 }, 160 } 161 } 162 163 if (cross_target.os_version_min) |min| switch (min) { 164 .none => {}, 165 .semver => |semver| switch (cross_target.getOsTag()) { 166 .linux => os.version_range.linux.range.min = semver, 167 else => os.version_range.semver.min = semver, 168 }, 169 .windows => |win_ver| os.version_range.windows.min = win_ver, 170 }; 171 172 if (cross_target.os_version_max) |max| switch (max) { 173 .none => {}, 174 .semver => |semver| switch (cross_target.getOsTag()) { 175 .linux => os.version_range.linux.range.max = semver, 176 else => os.version_range.semver.max = semver, 177 }, 178 .windows => |win_ver| os.version_range.windows.max = win_ver, 179 }; 180 181 if (cross_target.glibc_version) |glibc| { 182 assert(cross_target.isGnuLibC()); 183 os.version_range.linux.glibc = glibc; 184 } 185 186 // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the 187 // native CPU architecture as being different than the current target), we use this: 188 const cpu_arch = cross_target.getCpuArch(); 189 190 var cpu = switch (cross_target.cpu_model) { 191 .native => detectNativeCpuAndFeatures(cpu_arch, os, cross_target), 192 .baseline => Target.Cpu.baseline(cpu_arch), 193 .determined_by_cpu_arch => if (cross_target.cpu_arch == null) 194 detectNativeCpuAndFeatures(cpu_arch, os, cross_target) 195 else 196 Target.Cpu.baseline(cpu_arch), 197 .explicit => |model| model.toCpu(cpu_arch), 198 } orelse backup_cpu_detection: { 199 break :backup_cpu_detection Target.Cpu.baseline(cpu_arch); 200 }; 201 var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target); 202 // For x86, we need to populate some CPU feature flags depending on architecture 203 // and mode: 204 // * 16bit_mode => if the abi is code16 205 // * 32bit_mode => if the arch is i386 206 // However, the "mode" flags can be used as overrides, so if the user explicitly 207 // sets one of them, that takes precedence. 208 switch (cpu_arch) { 209 .i386 => { 210 if (!std.Target.x86.featureSetHasAny(cross_target.cpu_features_add, .{ 211 .@"16bit_mode", .@"32bit_mode", 212 })) { 213 switch (result.target.abi) { 214 .code16 => result.target.cpu.features.addFeature( 215 @enumToInt(std.Target.x86.Feature.@"16bit_mode"), 216 ), 217 else => result.target.cpu.features.addFeature( 218 @enumToInt(std.Target.x86.Feature.@"32bit_mode"), 219 ), 220 } 221 } 222 }, 223 .arm, .armeb => { 224 // XXX What do we do if the target has the noarm feature? 225 // What do we do if the user specifies +thumb_mode? 226 }, 227 .thumb, .thumbeb => { 228 result.target.cpu.features.addFeature( 229 @enumToInt(std.Target.arm.Feature.thumb_mode), 230 ); 231 }, 232 else => {}, 233 } 234 cross_target.updateCpuFeatures(&result.target.cpu.features); 235 return result; 236} 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. If that does not provide the answer, then 241/// we fall back to the defaults. 242/// TODO Remove the Allocator requirement from this function. 243fn detectAbiAndDynamicLinker( 244 allocator: Allocator, 245 cpu: Target.Cpu, 246 os: Target.Os, 247 cross_target: CrossTarget, 248) DetectError!NativeTargetInfo { 249 const native_target_has_ld = comptime builtin.target.hasDynamicLinker(); 250 const is_linux = builtin.target.os.tag == .linux; 251 const have_all_info = cross_target.dynamic_linker.get() != null and 252 cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu()); 253 const os_is_non_native = cross_target.os_tag != null; 254 if (!native_target_has_ld or have_all_info or os_is_non_native) { 255 return defaultAbiAndDynamicLinker(cpu, os, cross_target); 256 } 257 if (cross_target.abi) |abi| { 258 if (abi.isMusl()) { 259 // musl implies static linking. 260 return defaultAbiAndDynamicLinker(cpu, os, cross_target); 261 } 262 } 263 // The current target's ABI cannot be relied on for this. For example, we may build the zig 264 // compiler for target riscv64-linux-musl and provide a tarball for users to download. 265 // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined 266 // and supported by Zig. But that means that we must detect the system ABI here rather than 267 // relying on `builtin.target`. 268 const all_abis = comptime blk: { 269 assert(@enumToInt(Target.Abi.none) == 0); 270 const fields = std.meta.fields(Target.Abi)[1..]; 271 var array: [fields.len]Target.Abi = undefined; 272 inline for (fields) |field, i| { 273 array[i] = @field(Target.Abi, field.name); 274 } 275 break :blk array; 276 }; 277 var ld_info_list_buffer: [all_abis.len]LdInfo = undefined; 278 var ld_info_list_len: usize = 0; 279 280 for (all_abis) |abi| { 281 // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and 282 // skip adding it to `ld_info_list`. 283 const target: Target = .{ 284 .cpu = cpu, 285 .os = os, 286 .abi = abi, 287 }; 288 const ld = target.standardDynamicLinkerPath(); 289 if (ld.get() == null) continue; 290 291 ld_info_list_buffer[ld_info_list_len] = .{ 292 .ld = ld, 293 .abi = abi, 294 }; 295 ld_info_list_len += 1; 296 } 297 const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; 298 299 // Best case scenario: the executable is dynamically linked, and we can iterate 300 // over our own shared objects and find a dynamic linker. 301 self_exe: { 302 const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator); 303 defer { 304 for (lib_paths) |lib_path| { 305 allocator.free(lib_path); 306 } 307 allocator.free(lib_paths); 308 } 309 310 var found_ld_info: LdInfo = undefined; 311 var found_ld_path: [:0]const u8 = undefined; 312 313 // Look for dynamic linker. 314 // This is O(N^M) but typical case here is N=2 and M=10. 315 find_ld: for (lib_paths) |lib_path| { 316 for (ld_info_list) |ld_info| { 317 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); 318 if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) { 319 found_ld_info = ld_info; 320 found_ld_path = lib_path; 321 break :find_ld; 322 } 323 } 324 } else break :self_exe; 325 326 // Look for glibc version. 327 var os_adjusted = os; 328 if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and 329 cross_target.glibc_version == null) 330 { 331 for (lib_paths) |lib_path| { 332 if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) { 333 os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) { 334 error.UnrecognizedGnuLibCFileName => continue, 335 error.InvalidGnuLibCVersion => continue, 336 error.GnuLibCVersionUnavailable => continue, 337 else => |e| return e, 338 }; 339 break; 340 } 341 } 342 } 343 344 var result: NativeTargetInfo = .{ 345 .target = .{ 346 .cpu = cpu, 347 .os = os_adjusted, 348 .abi = cross_target.abi orelse found_ld_info.abi, 349 }, 350 .dynamic_linker = if (cross_target.dynamic_linker.get() == null) 351 DynamicLinker.init(found_ld_path) 352 else 353 cross_target.dynamic_linker, 354 }; 355 return result; 356 } 357 358 const env_file = std.fs.openFileAbsoluteZ("/usr/bin/env", .{}) catch |err| switch (err) { 359 error.NoSpaceLeft => unreachable, 360 error.NameTooLong => unreachable, 361 error.PathAlreadyExists => unreachable, 362 error.SharingViolation => unreachable, 363 error.InvalidUtf8 => unreachable, 364 error.BadPathName => unreachable, 365 error.PipeBusy => unreachable, 366 error.FileLocksNotSupported => unreachable, 367 error.WouldBlock => unreachable, 368 error.FileBusy => unreachable, // opened without write permissions 369 370 error.IsDir, 371 error.NotDir, 372 error.AccessDenied, 373 error.NoDevice, 374 error.FileNotFound, 375 error.FileTooBig, 376 error.Unexpected, 377 => return defaultAbiAndDynamicLinker(cpu, os, cross_target), 378 379 else => |e| return e, 380 }; 381 defer env_file.close(); 382 383 // If Zig is statically linked, such as via distributed binary static builds, the above 384 // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env. 385 // Since that path is hard-coded into the shebang line of many portable scripts, it's a 386 // reasonably reliable path to check for. 387 return abiAndDynamicLinkerFromFile(env_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) { 388 error.FileSystem, 389 error.SystemResources, 390 error.SymLinkLoop, 391 error.ProcessFdQuotaExceeded, 392 error.SystemFdQuotaExceeded, 393 => |e| return e, 394 395 error.UnableToReadElfFile, 396 error.InvalidElfClass, 397 error.InvalidElfVersion, 398 error.InvalidElfEndian, 399 error.InvalidElfFile, 400 error.InvalidElfMagic, 401 error.Unexpected, 402 error.UnexpectedEndOfFile, 403 error.NameTooLong, 404 // Finally, we fall back on the standard path. 405 => defaultAbiAndDynamicLinker(cpu, os, cross_target), 406 }; 407} 408 409const glibc_so_basename = "libc.so.6"; 410 411fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version { 412 var link_buf: [std.os.PATH_MAX]u8 = undefined; 413 const link_name = std.os.readlinkZ(so_path.ptr, &link_buf) catch |err| switch (err) { 414 error.AccessDenied => return error.GnuLibCVersionUnavailable, 415 error.FileSystem => return error.FileSystem, 416 error.SymLinkLoop => return error.SymLinkLoop, 417 error.NameTooLong => unreachable, 418 error.NotLink => return error.GnuLibCVersionUnavailable, 419 error.FileNotFound => return error.GnuLibCVersionUnavailable, 420 error.SystemResources => return error.SystemResources, 421 error.NotDir => return error.GnuLibCVersionUnavailable, 422 error.Unexpected => return error.GnuLibCVersionUnavailable, 423 error.InvalidUtf8 => unreachable, // Windows only 424 error.BadPathName => unreachable, // Windows only 425 error.UnsupportedReparsePointType => unreachable, // Windows only 426 }; 427 return glibcVerFromLinkName(link_name, "libc-"); 428} 429 430fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version { 431 // example: "libc-2.3.4.so" 432 // example: "libc-2.27.so" 433 // example: "ld-2.33.so" 434 const suffix = ".so"; 435 if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) { 436 return error.UnrecognizedGnuLibCFileName; 437 } 438 // chop off "libc-" and ".so" 439 const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len]; 440 return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) { 441 error.Overflow => return error.InvalidGnuLibCVersion, 442 error.InvalidCharacter => return error.InvalidGnuLibCVersion, 443 error.InvalidVersion => return error.InvalidGnuLibCVersion, 444 }; 445} 446 447pub const AbiAndDynamicLinkerFromFileError = error{ 448 FileSystem, 449 SystemResources, 450 SymLinkLoop, 451 ProcessFdQuotaExceeded, 452 SystemFdQuotaExceeded, 453 UnableToReadElfFile, 454 InvalidElfClass, 455 InvalidElfVersion, 456 InvalidElfEndian, 457 InvalidElfFile, 458 InvalidElfMagic, 459 Unexpected, 460 UnexpectedEndOfFile, 461 NameTooLong, 462}; 463 464pub fn abiAndDynamicLinkerFromFile( 465 file: fs.File, 466 cpu: Target.Cpu, 467 os: Target.Os, 468 ld_info_list: []const LdInfo, 469 cross_target: CrossTarget, 470) AbiAndDynamicLinkerFromFileError!NativeTargetInfo { 471 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; 472 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len); 473 const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf); 474 const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf); 475 if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic; 476 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { 477 elf.ELFDATA2LSB => .Little, 478 elf.ELFDATA2MSB => .Big, 479 else => return error.InvalidElfEndian, 480 }; 481 const need_bswap = elf_endian != native_endian; 482 if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; 483 484 const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { 485 elf.ELFCLASS32 => false, 486 elf.ELFCLASS64 => true, 487 else => return error.InvalidElfClass, 488 }; 489 var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff); 490 const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize); 491 const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); 492 493 var result: NativeTargetInfo = .{ 494 .target = .{ 495 .cpu = cpu, 496 .os = os, 497 .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os), 498 }, 499 .dynamic_linker = cross_target.dynamic_linker, 500 }; 501 var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC 502 const look_for_ld = cross_target.dynamic_linker.get() == null; 503 504 var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; 505 if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; 506 507 var ph_i: u16 = 0; 508 while (ph_i < phnum) { 509 // Reserve some bytes so that we can deref the 64-bit struct fields 510 // even when the ELF file is 32-bits. 511 const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); 512 const ph_read_byte_len = try preadMin(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize); 513 var ph_buf_i: usize = 0; 514 while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({ 515 ph_i += 1; 516 phoff += phentsize; 517 ph_buf_i += phentsize; 518 }) { 519 const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i])); 520 const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i])); 521 const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); 522 switch (p_type) { 523 elf.PT_INTERP => if (look_for_ld) { 524 const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); 525 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); 526 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; 527 const filesz = @intCast(usize, p_filesz); 528 _ = try preadMin(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz); 529 // PT_INTERP includes a null byte in filesz. 530 const len = filesz - 1; 531 // dynamic_linker.max_byte is "max", not "len". 532 // We know it will fit in u8 because we check against dynamic_linker.buffer.len above. 533 result.dynamic_linker.max_byte = @intCast(u8, len - 1); 534 535 // Use it to determine ABI. 536 const full_ld_path = result.dynamic_linker.buffer[0..len]; 537 for (ld_info_list) |ld_info| { 538 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); 539 if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { 540 result.target.abi = ld_info.abi; 541 break; 542 } 543 } 544 }, 545 // We only need this for detecting glibc version. 546 elf.PT_DYNAMIC => if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and 547 cross_target.glibc_version == null) 548 { 549 var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); 550 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); 551 const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); 552 const dyn_num = p_filesz / dyn_size; 553 var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined; 554 var dyn_i: usize = 0; 555 dyn: while (dyn_i < dyn_num) { 556 // Reserve some bytes so that we can deref the 64-bit struct fields 557 // even when the ELF file is 32-bits. 558 const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn); 559 const dyn_read_byte_len = try preadMin( 560 file, 561 dyn_buf[0 .. dyn_buf.len - dyn_reserve], 562 dyn_off, 563 dyn_size, 564 ); 565 var dyn_buf_i: usize = 0; 566 while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({ 567 dyn_i += 1; 568 dyn_off += dyn_size; 569 dyn_buf_i += dyn_size; 570 }) { 571 const dyn32 = @ptrCast( 572 *elf.Elf32_Dyn, 573 @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]), 574 ); 575 const dyn64 = @ptrCast( 576 *elf.Elf64_Dyn, 577 @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]), 578 ); 579 const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag); 580 const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val); 581 if (tag == elf.DT_RUNPATH) { 582 rpath_offset = val; 583 break :dyn; 584 } 585 } 586 } 587 }, 588 else => continue, 589 } 590 } 591 } 592 593 if (builtin.target.os.tag == .linux and result.target.isGnuLibC() and 594 cross_target.glibc_version == null) 595 { 596 if (rpath_offset) |rpoff| { 597 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); 598 599 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); 600 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); 601 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); 602 603 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; 604 if (sh_buf.len < shentsize) return error.InvalidElfFile; 605 606 _ = try preadMin(file, &sh_buf, str_section_off, shentsize); 607 const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); 608 const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf)); 609 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); 610 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); 611 var strtab_buf: [4096:0]u8 = undefined; 612 const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len); 613 const shstrtab_read_len = try preadMin(file, &strtab_buf, shstrtab_off, shstrtab_len); 614 const shstrtab = strtab_buf[0..shstrtab_read_len]; 615 616 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); 617 var sh_i: u16 = 0; 618 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { 619 // Reserve some bytes so that we can deref the 64-bit struct fields 620 // even when the ELF file is 32-bits. 621 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); 622 const sh_read_byte_len = try preadMin( 623 file, 624 sh_buf[0 .. sh_buf.len - sh_reserve], 625 shoff, 626 shentsize, 627 ); 628 var sh_buf_i: usize = 0; 629 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ 630 sh_i += 1; 631 shoff += shentsize; 632 sh_buf_i += shentsize; 633 }) { 634 const sh32 = @ptrCast( 635 *elf.Elf32_Shdr, 636 @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), 637 ); 638 const sh64 = @ptrCast( 639 *elf.Elf64_Shdr, 640 @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), 641 ); 642 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); 643 // TODO this pointer cast should not be necessary 644 const sh_name = mem.sliceTo(std.meta.assumeSentinel(shstrtab[sh_name_off..].ptr, 0), 0); 645 if (mem.eql(u8, sh_name, ".dynstr")) { 646 break :find_dyn_str .{ 647 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), 648 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), 649 }; 650 } 651 } 652 } else null; 653 654 if (dynstr) |ds| { 655 const strtab_len = std.math.min(ds.size, strtab_buf.len); 656 const strtab_read_len = try preadMin(file, &strtab_buf, ds.offset, strtab_len); 657 const strtab = strtab_buf[0..strtab_read_len]; 658 // TODO this pointer cast should not be necessary 659 const rpoff_usize = std.math.cast(usize, rpoff) catch |err| switch (err) { 660 error.Overflow => return error.InvalidElfFile, 661 }; 662 const rpath_list = mem.sliceTo(std.meta.assumeSentinel(strtab[rpoff_usize..].ptr, 0), 0); 663 var it = mem.tokenize(u8, rpath_list, ":"); 664 while (it.next()) |rpath| { 665 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { 666 error.NameTooLong => unreachable, 667 error.InvalidUtf8 => unreachable, 668 error.BadPathName => unreachable, 669 error.DeviceBusy => unreachable, 670 671 error.FileNotFound, 672 error.NotDir, 673 error.AccessDenied, 674 error.NoDevice, 675 => continue, 676 677 error.ProcessFdQuotaExceeded, 678 error.SystemFdQuotaExceeded, 679 error.SystemResources, 680 error.SymLinkLoop, 681 error.Unexpected, 682 => |e| return e, 683 }; 684 defer dir.close(); 685 686 var link_buf: [std.os.PATH_MAX]u8 = undefined; 687 const link_name = std.os.readlinkatZ( 688 dir.fd, 689 glibc_so_basename, 690 &link_buf, 691 ) catch |err| switch (err) { 692 error.NameTooLong => unreachable, 693 error.InvalidUtf8 => unreachable, // Windows only 694 error.BadPathName => unreachable, // Windows only 695 error.UnsupportedReparsePointType => unreachable, // Windows only 696 697 error.AccessDenied, 698 error.FileNotFound, 699 error.NotLink, 700 error.NotDir, 701 => continue, 702 703 error.SystemResources, 704 error.FileSystem, 705 error.SymLinkLoop, 706 error.Unexpected, 707 => |e| return e, 708 }; 709 result.target.os.version_range.linux.glibc = glibcVerFromLinkName( 710 link_name, 711 "libc-", 712 ) catch |err| switch (err) { 713 error.UnrecognizedGnuLibCFileName, 714 error.InvalidGnuLibCVersion, 715 => continue, 716 }; 717 break; 718 } 719 } 720 } else if (result.dynamic_linker.get()) |dl_path| glibc_ver: { 721 // There is no DT_RUNPATH but we can try to see if the information is 722 // present in the symlink data for the dynamic linker path. 723 var link_buf: [std.os.PATH_MAX]u8 = undefined; 724 const link_name = std.os.readlink(dl_path, &link_buf) catch |err| switch (err) { 725 error.NameTooLong => unreachable, 726 error.InvalidUtf8 => unreachable, // Windows only 727 error.BadPathName => unreachable, // Windows only 728 error.UnsupportedReparsePointType => unreachable, // Windows only 729 730 error.AccessDenied, 731 error.FileNotFound, 732 error.NotLink, 733 error.NotDir, 734 => break :glibc_ver, 735 736 error.SystemResources, 737 error.FileSystem, 738 error.SymLinkLoop, 739 error.Unexpected, 740 => |e| return e, 741 }; 742 result.target.os.version_range.linux.glibc = glibcVerFromLinkName( 743 fs.path.basename(link_name), 744 "ld-", 745 ) catch |err| switch (err) { 746 error.UnrecognizedGnuLibCFileName, 747 error.InvalidGnuLibCVersion, 748 => break :glibc_ver, 749 }; 750 } 751 } 752 753 return result; 754} 755 756fn preadMin(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize { 757 var i: usize = 0; 758 while (i < min_read_len) { 759 const len = file.pread(buf[i..], offset + i) catch |err| switch (err) { 760 error.OperationAborted => unreachable, // Windows-only 761 error.WouldBlock => unreachable, // Did not request blocking mode 762 error.NotOpenForReading => unreachable, 763 error.SystemResources => return error.SystemResources, 764 error.IsDir => return error.UnableToReadElfFile, 765 error.BrokenPipe => return error.UnableToReadElfFile, 766 error.Unseekable => return error.UnableToReadElfFile, 767 error.ConnectionResetByPeer => return error.UnableToReadElfFile, 768 error.ConnectionTimedOut => return error.UnableToReadElfFile, 769 error.Unexpected => return error.Unexpected, 770 error.InputOutput => return error.FileSystem, 771 error.AccessDenied => return error.Unexpected, 772 }; 773 if (len == 0) return error.UnexpectedEndOfFile; 774 i += len; 775 } 776 return i; 777} 778 779fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo { 780 const target: Target = .{ 781 .cpu = cpu, 782 .os = os, 783 .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os), 784 }; 785 return NativeTargetInfo{ 786 .target = target, 787 .dynamic_linker = if (cross_target.dynamic_linker.get() == null) 788 target.standardDynamicLinkerPath() 789 else 790 cross_target.dynamic_linker, 791 }; 792} 793 794pub const LdInfo = struct { 795 ld: DynamicLinker, 796 abi: Target.Abi, 797}; 798 799pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { 800 if (is_64) { 801 if (need_bswap) { 802 return @byteSwap(@TypeOf(int_64), int_64); 803 } else { 804 return int_64; 805 } 806 } else { 807 if (need_bswap) { 808 return @byteSwap(@TypeOf(int_32), int_32); 809 } else { 810 return int_32; 811 } 812 } 813} 814 815fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, cross_target: CrossTarget) ?Target.Cpu { 816 // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`, 817 // although it is a runtime value, is guaranteed to be one of the architectures in the set 818 // of the respective switch prong. 819 switch (builtin.cpu.arch) { 820 .x86_64, .i386 => { 821 return @import("x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target); 822 }, 823 else => {}, 824 } 825 826 switch (builtin.os.tag) { 827 .linux => return linux.detectNativeCpuAndFeatures(), 828 .macos => return darwin.macos.detectNativeCpuAndFeatures(), 829 else => {}, 830 } 831 832 // This architecture does not have CPU model & feature detection yet. 833 // See https://github.com/ziglang/zig/issues/4591 834 return null; 835} 836 837pub const Executor = union(enum) { 838 native, 839 rosetta, 840 qemu: []const u8, 841 wine: []const u8, 842 wasmtime: []const u8, 843 darling: []const u8, 844 bad_dl: []const u8, 845 bad_os_or_cpu, 846}; 847 848pub const GetExternalExecutorOptions = struct { 849 allow_darling: bool = true, 850 allow_qemu: bool = true, 851 allow_rosetta: bool = true, 852 allow_wasmtime: bool = true, 853 allow_wine: bool = true, 854 qemu_fixes_dl: bool = false, 855 link_libc: bool = false, 856}; 857 858/// Return whether or not the given host target is capable of executing natively executables 859/// of the other target. 860pub fn getExternalExecutor( 861 host: NativeTargetInfo, 862 candidate: NativeTargetInfo, 863 options: GetExternalExecutorOptions, 864) Executor { 865 const os_match = host.target.os.tag == candidate.target.os.tag; 866 const cpu_ok = cpu_ok: { 867 if (host.target.cpu.arch == candidate.target.cpu.arch) 868 break :cpu_ok true; 869 870 if (host.target.cpu.arch == .x86_64 and candidate.target.cpu.arch == .i386) 871 break :cpu_ok true; 872 873 if (host.target.cpu.arch == .aarch64 and candidate.target.cpu.arch == .arm) 874 break :cpu_ok true; 875 876 if (host.target.cpu.arch == .aarch64_be and candidate.target.cpu.arch == .armeb) 877 break :cpu_ok true; 878 879 // TODO additionally detect incompatible CPU features. 880 // Note that in some cases the OS kernel will emulate missing CPU features 881 // when an illegal instruction is encountered. 882 883 break :cpu_ok false; 884 }; 885 886 var bad_result: Executor = .bad_os_or_cpu; 887 888 if (os_match and cpu_ok) native: { 889 if (options.link_libc) { 890 if (candidate.dynamic_linker.get()) |candidate_dl| { 891 fs.cwd().access(candidate_dl, .{}) catch { 892 bad_result = .{ .bad_dl = candidate_dl }; 893 break :native; 894 }; 895 } 896 } 897 return .native; 898 } 899 900 // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 901 // to emulate the foreign architecture. 902 if (options.allow_rosetta and os_match and 903 host.target.os.tag == .macos and host.target.cpu.arch == .aarch64) 904 { 905 switch (candidate.target.cpu.arch) { 906 .x86_64 => return .rosetta, 907 else => return bad_result, 908 } 909 } 910 911 // If the OS matches, we can use QEMU to emulate a foreign architecture. 912 if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) { 913 return switch (candidate.target.cpu.arch) { 914 .aarch64 => Executor{ .qemu = "qemu-aarch64" }, 915 .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, 916 .arm => Executor{ .qemu = "qemu-arm" }, 917 .armeb => Executor{ .qemu = "qemu-armeb" }, 918 .hexagon => Executor{ .qemu = "qemu-hexagon" }, 919 .i386 => Executor{ .qemu = "qemu-i386" }, 920 .m68k => Executor{ .qemu = "qemu-m68k" }, 921 .mips => Executor{ .qemu = "qemu-mips" }, 922 .mipsel => Executor{ .qemu = "qemu-mipsel" }, 923 .mips64 => Executor{ .qemu = "qemu-mips64" }, 924 .mips64el => Executor{ .qemu = "qemu-mips64el" }, 925 .powerpc => Executor{ .qemu = "qemu-ppc" }, 926 .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, 927 .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, 928 .riscv32 => Executor{ .qemu = "qemu-riscv32" }, 929 .riscv64 => Executor{ .qemu = "qemu-riscv64" }, 930 .s390x => Executor{ .qemu = "qemu-s390x" }, 931 .sparc => Executor{ .qemu = "qemu-sparc" }, 932 .x86_64 => Executor{ .qemu = "qemu-x86_64" }, 933 else => return bad_result, 934 }; 935 } 936 937 switch (candidate.target.os.tag) { 938 .windows => { 939 if (options.allow_wine) { 940 switch (candidate.target.cpu.arch.ptrBitWidth()) { 941 32 => return Executor{ .wine = "wine" }, 942 64 => return Executor{ .wine = "wine64" }, 943 else => return bad_result, 944 } 945 } 946 return bad_result; 947 }, 948 .wasi => { 949 if (options.allow_wasmtime) { 950 switch (candidate.target.cpu.arch.ptrBitWidth()) { 951 32 => return Executor{ .wasmtime = "wasmtime" }, 952 else => return bad_result, 953 } 954 } 955 return bad_result; 956 }, 957 .macos => { 958 if (options.allow_darling) { 959 // This check can be loosened once darling adds a QEMU-based emulation 960 // layer for non-host architectures: 961 // https://github.com/darlinghq/darling/issues/863 962 if (candidate.target.cpu.arch != builtin.cpu.arch) { 963 return bad_result; 964 } 965 return Executor{ .darling = "darling" }; 966 } 967 return bad_result; 968 }, 969 else => return bad_result, 970 } 971} 972