authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-09 12:28:25-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-09-09 12:28:25-04:00
log9e070b653c89a9216f9dd9f78ed7c78c11460ac7
tree6190d043645b0417e76164f7149679fa865bf349
parentc9f145a50b11e54b72b2fccd04384bbb856446cf
parent68e61bbc0c3b896d5d549168ff8b88fe04e2269f
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #12788 from ziglang/detect-native-glibc

std.zig.system.NativeTargetInfo: improve glibc version and dynamic linker detection

7 files changed, 333 insertions(+), 233 deletions(-)

doc/docgen.zig+1-2
...@@ -1210,7 +1210,7 @@ fn genHtml(...@@ -1210,7 +1210,7 @@ fn genHtml(
1210 var env_map = try process.getEnvMap(allocator);1210 var env_map = try process.getEnvMap(allocator);
1211 try env_map.put("ZIG_DEBUG_COLOR", "1");1211 try env_map.put("ZIG_DEBUG_COLOR", "1");
12121212
1213 const host = try std.zig.system.NativeTargetInfo.detect(allocator, .{});1213 const host = try std.zig.system.NativeTargetInfo.detect(.{});
1214 const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe);1214 const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe);
12151215
1216 for (toc.nodes) |node| {1216 for (toc.nodes) |node| {
...@@ -1474,7 +1474,6 @@ fn genHtml(...@@ -1474,7 +1474,6 @@ fn genHtml(
1474 .arch_os_abi = triple,1474 .arch_os_abi = triple,
1475 });1475 });
1476 const target_info = try std.zig.system.NativeTargetInfo.detect(1476 const target_info = try std.zig.system.NativeTargetInfo.detect(
1477 allocator,
1478 cross_target,1477 cross_target,
1479 );1478 );
1480 switch (host.getExternalExecutor(target_info, .{1479 switch (host.getExternalExecutor(target_info, .{
lib/std/build.zig+2-2
...@@ -171,7 +171,7 @@ pub const Builder = struct {...@@ -171,7 +171,7 @@ pub const Builder = struct {
171 const env_map = try allocator.create(EnvMap);171 const env_map = try allocator.create(EnvMap);
172 env_map.* = try process.getEnvMap(allocator);172 env_map.* = try process.getEnvMap(allocator);
173173
174 const host = try NativeTargetInfo.detect(allocator, .{});174 const host = try NativeTargetInfo.detect(.{});
175175
176 const self = try allocator.create(Builder);176 const self = try allocator.create(Builder);
177 self.* = Builder{177 self.* = Builder{
...@@ -1798,7 +1798,7 @@ pub const LibExeObjStep = struct {...@@ -1798,7 +1798,7 @@ pub const LibExeObjStep = struct {
1798 }1798 }
17991799
1800 fn computeOutFileNames(self: *LibExeObjStep) void {1800 fn computeOutFileNames(self: *LibExeObjStep) void {
1801 self.target_info = NativeTargetInfo.detect(self.builder.allocator, self.target) catch1801 self.target_info = NativeTargetInfo.detect(self.target) catch
1802 unreachable;1802 unreachable;
18031803
1804 const target = self.target_info.target;1804 const target = self.target_info.target;
lib/std/build/EmulatableRunStep.zig+1-1
...@@ -158,7 +158,7 @@ fn warnAboutForeignBinaries(step: *EmulatableRunStep) void {...@@ -158,7 +158,7 @@ fn warnAboutForeignBinaries(step: *EmulatableRunStep) void {
158158
159 const host_name = builder.host.target.zigTriple(builder.allocator) catch unreachable;159 const host_name = builder.host.target.zigTriple(builder.allocator) catch unreachable;
160 const foreign_name = artifact.target.zigTriple(builder.allocator) catch unreachable;160 const foreign_name = artifact.target.zigTriple(builder.allocator) catch unreachable;
161 const target_info = std.zig.system.NativeTargetInfo.detect(builder.allocator, artifact.target) catch unreachable;161 const target_info = std.zig.system.NativeTargetInfo.detect(artifact.target) catch unreachable;
162 const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc;162 const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc;
163 switch (builder.host.getExternalExecutor(target_info, .{163 switch (builder.host.getExternalExecutor(target_info, .{
164 .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,164 .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
lib/std/zig/system/NativeTargetInfo.zig+318-216
...@@ -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};
3233
33/// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected34/// 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 no38/// 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.40pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo {
40pub 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 architecture202 // 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 code16204 // * 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}
237237
238/// First we attempt to use the executable's own binary. If it is dynamically238/// 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, then240/// 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.
243fn detectAbiAndDynamicLinker(251fn 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);
280287
281 for (all_abis) |abi| {288 for (all_abis) |abi| {
282 // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and289 // 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(
300307
301 // Best case scenario: the executable is dynamically linked, and we can iterate308 // 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(
396345
397 else => |e| return e,346 else => |e| return e,
398 };347 };
348 errdefer file.close();
399349
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 before351 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,
415354
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();
428366
429 // If Zig is statically linked, such as via distributed binary static builds, the above367 // 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}
455395
456const glibc_so_basename = "libc.so.6";396fn 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,
457409
458fn 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 only429 error.InvalidUtf8 => unreachable, // Windows only
471 error.BadPathName => unreachable, // Windows only430 error.BadPathName => unreachable, // Windows only
472 error.UnsupportedReparsePointType => unreachable, // Windows only431 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
477fn 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}
476581
477fn glibcVerFromLinkName(link_name: []const u8, prefix: []const u8) !std.builtin.Version {582fn 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() and746 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
646751 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
650755 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
653758 _ = 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
663768 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 fields772 // 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;
701805
702 if (dynstr) |ds| {806 if (dynstr) |ds| {
807 if (rpath_offset) |rpoff| {
703 // TODO this pointer cast should not be necessary808 // 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,
721826 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: {
728833 // 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 is845 // 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 }
804906
src/codegen/llvm.zig+1-1
...@@ -3870,7 +3870,7 @@ pub const DeclGen = struct {...@@ -3870,7 +3870,7 @@ pub const DeclGen = struct {
3870 var b: usize = 0;3870 var b: usize = 0;
3871 for (parent_ty.structFields().values()[0..field_index]) |field| {3871 for (parent_ty.structFields().values()[0..field_index]) |field| {
3872 if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue;3872 if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue;
3873 b += field.ty.bitSize(target);3873 b += @intCast(usize, field.ty.bitSize(target));
3874 }3874 }
3875 break :b b;3875 break :b b;
3876 };3876 };
src/main.zig+8-9
...@@ -268,7 +268,7 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -268,7 +268,7 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
268 } else if (mem.eql(u8, cmd, "init-lib")) {268 } else if (mem.eql(u8, cmd, "init-lib")) {
269 return cmdInit(gpa, arena, cmd_args, .Lib);269 return cmdInit(gpa, arena, cmd_args, .Lib);
270 } else if (mem.eql(u8, cmd, "targets")) {270 } else if (mem.eql(u8, cmd, "targets")) {
271 const info = try detectNativeTargetInfo(arena, .{});271 const info = try detectNativeTargetInfo(.{});
272 const stdout = io.getStdOut().writer();272 const stdout = io.getStdOut().writer();
273 return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);273 return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
274 } else if (mem.eql(u8, cmd, "version")) {274 } else if (mem.eql(u8, cmd, "version")) {
...@@ -2267,7 +2267,7 @@ fn buildOutputType(...@@ -2267,7 +2267,7 @@ fn buildOutputType(
2267 }2267 }
22682268
2269 const cross_target = try parseCrossTargetOrReportFatalError(arena, target_parse_options);2269 const cross_target = try parseCrossTargetOrReportFatalError(arena, target_parse_options);
2270 const target_info = try detectNativeTargetInfo(gpa, cross_target);2270 const target_info = try detectNativeTargetInfo(cross_target);
22712271
2272 if (target_info.target.os.tag != .freestanding) {2272 if (target_info.target.os.tag != .freestanding) {
2273 if (ensure_libc_on_non_freestanding)2273 if (ensure_libc_on_non_freestanding)
...@@ -3283,7 +3283,7 @@ fn runOrTest(...@@ -3283,7 +3283,7 @@ fn runOrTest(
3283 if (std.process.can_execv and arg_mode == .run and !watch) {3283 if (std.process.can_execv and arg_mode == .run and !watch) {
3284 // execv releases the locks; no need to destroy the Compilation here.3284 // execv releases the locks; no need to destroy the Compilation here.
3285 const err = std.process.execv(gpa, argv.items);3285 const err = std.process.execv(gpa, argv.items);
3286 try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info, link_libc);3286 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
3287 const cmd = try std.mem.join(arena, " ", argv.items);3287 const cmd = try std.mem.join(arena, " ", argv.items);
3288 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });3288 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
3289 } else if (std.process.can_spawn) {3289 } else if (std.process.can_spawn) {
...@@ -3300,7 +3300,7 @@ fn runOrTest(...@@ -3300,7 +3300,7 @@ fn runOrTest(
3300 }3300 }
33013301
3302 const term = child.spawnAndWait() catch |err| {3302 const term = child.spawnAndWait() catch |err| {
3303 try warnAboutForeignBinaries(gpa, arena, arg_mode, target_info, link_libc);3303 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
3304 const cmd = try std.mem.join(arena, " ", argv.items);3304 const cmd = try std.mem.join(arena, " ", argv.items);
3305 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });3305 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
3306 };3306 };
...@@ -3914,7 +3914,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -3914,7 +3914,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
3914 gimmeMoreOfThoseSweetSweetFileDescriptors();3914 gimmeMoreOfThoseSweetSweetFileDescriptors();
39153915
3916 const cross_target: std.zig.CrossTarget = .{};3916 const cross_target: std.zig.CrossTarget = .{};
3917 const target_info = try detectNativeTargetInfo(gpa, cross_target);3917 const target_info = try detectNativeTargetInfo(cross_target);
39183918
3919 const exe_basename = try std.zig.binNameAlloc(arena, .{3919 const exe_basename = try std.zig.binNameAlloc(arena, .{
3920 .root_name = "build",3920 .root_name = "build",
...@@ -4956,8 +4956,8 @@ test "fds" {...@@ -4956,8 +4956,8 @@ test "fds" {
4956 gimmeMoreOfThoseSweetSweetFileDescriptors();4956 gimmeMoreOfThoseSweetSweetFileDescriptors();
4957}4957}
49584958
4959fn detectNativeTargetInfo(gpa: Allocator, cross_target: std.zig.CrossTarget) !std.zig.system.NativeTargetInfo {4959fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.NativeTargetInfo {
4960 return std.zig.system.NativeTargetInfo.detect(gpa, cross_target);4960 return std.zig.system.NativeTargetInfo.detect(cross_target);
4961}4961}
49624962
4963/// Indicate that we are now terminating with a successful exit code.4963/// Indicate that we are now terminating with a successful exit code.
...@@ -5320,14 +5320,13 @@ fn parseIntSuffix(arg: []const u8, prefix_len: usize) u64 {...@@ -5320,14 +5320,13 @@ fn parseIntSuffix(arg: []const u8, prefix_len: usize) u64 {
5320}5320}
53215321
5322fn warnAboutForeignBinaries(5322fn warnAboutForeignBinaries(
5323 gpa: Allocator,
5324 arena: Allocator,5323 arena: Allocator,
5325 arg_mode: ArgMode,5324 arg_mode: ArgMode,
5326 target_info: std.zig.system.NativeTargetInfo,5325 target_info: std.zig.system.NativeTargetInfo,
5327 link_libc: bool,5326 link_libc: bool,
5328) !void {5327) !void {
5329 const host_cross_target: std.zig.CrossTarget = .{};5328 const host_cross_target: std.zig.CrossTarget = .{};
5330 const host_target_info = try detectNativeTargetInfo(gpa, host_cross_target);5329 const host_target_info = try detectNativeTargetInfo(host_cross_target);
53315330
5332 switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {5331 switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {
5333 .native => return,5332 .native => return,
src/test.zig+2-2
...@@ -1213,7 +1213,7 @@ pub const TestContext = struct {...@@ -1213,7 +1213,7 @@ pub const TestContext = struct {
1213 }1213 }
12141214
1215 fn run(self: *TestContext) !void {1215 fn run(self: *TestContext) !void {
1216 const host = try std.zig.system.NativeTargetInfo.detect(self.gpa, .{});1216 const host = try std.zig.system.NativeTargetInfo.detect(.{});
12171217
1218 var progress = std.Progress{};1218 var progress = std.Progress{};
1219 const root_node = progress.start("compiler", self.cases.items.len);1219 const root_node = progress.start("compiler", self.cases.items.len);
...@@ -1302,7 +1302,7 @@ pub const TestContext = struct {...@@ -1302,7 +1302,7 @@ pub const TestContext = struct {
1302 global_cache_directory: Compilation.Directory,1302 global_cache_directory: Compilation.Directory,
1303 host: std.zig.system.NativeTargetInfo,1303 host: std.zig.system.NativeTargetInfo,
1304 ) !void {1304 ) !void {
1305 const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target);1305 const target_info = try std.zig.system.NativeTargetInfo.detect(case.target);
1306 const target = target_info.target;1306 const target = target_info.target;
13071307
1308 var arena_allocator = std.heap.ArenaAllocator.init(allocator);1308 var arena_allocator = std.heap.ArenaAllocator.init(allocator);