authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-08 18:27:26-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-08 20:52:49-07:00
log3ee01c14ee7ba42b484f15daeacb67da90a81c9e
tree667a4b57b8bfc1a8b6413d014a72b113d5ec2833
parentfa940bafa2720f49ee249eda1ee4cf26a247172a

std.zig.system.NativeTargetInfo: detection ignores self exe

Before, native glibc and dynamic linker detection attempted to use the executable's own binary if it was dynamically linked to answer both the C ABI question and the dynamic linker question. However, this could be problematic on a system that uses a RUNPATH for the compiler binary, locking it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc version. The problem is that libc.so.6 glibc version will match that of the system while the dynamic linker will match that of the compiler binary. Executables with these versions mismatching will fail to run. Therefore, this commit changes the logic to be the same regardless of whether the compiler binary is dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the answer to these questions, or if there is a shebang line, then it chases the referenced file recursively. If that does not provide the answer, then the function falls back to defaults. This commit also solves a TODO to remove an Allocator parameter to the detect() function.

6 files changed, 31 insertions(+), 133 deletions(-)

doc/docgen.zig+1-2
......@@ -1210,7 +1210,7 @@ fn genHtml(
12101210 var env_map = try process.getEnvMap(allocator);
12111211 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(.{});
12141214 const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe);
12151215
12161216 for (toc.nodes) |node| {
......@@ -1474,7 +1474,6 @@ fn genHtml(
14741474 .arch_os_abi = triple,
14751475 });
14761476 const target_info = try std.zig.system.NativeTargetInfo.detect(
1477 allocator,
14781477 cross_target,
14791478 );
14801479 switch (host.getExternalExecutor(target_info, .{
lib/std/build.zig+2-2
......@@ -171,7 +171,7 @@ pub const Builder = struct {
171171 const env_map = try allocator.create(EnvMap);
172172 env_map.* = try process.getEnvMap(allocator);
173173
174 const host = try NativeTargetInfo.detect(allocator, .{});
174 const host = try NativeTargetInfo.detect(.{});
175175
176176 const self = try allocator.create(Builder);
177177 self.* = Builder{
......@@ -1798,7 +1798,7 @@ pub const LibExeObjStep = struct {
17981798 }
17991799
18001800 fn computeOutFileNames(self: *LibExeObjStep) void {
1801 self.target_info = NativeTargetInfo.detect(self.builder.allocator, self.target) catch
1801 self.target_info = NativeTargetInfo.detect(self.target) catch
18021802 unreachable;
18031803
18041804 const target = self.target_info.target;
lib/std/build/EmulatableRunStep.zig+1-1
......@@ -158,7 +158,7 @@ fn warnAboutForeignBinaries(step: *EmulatableRunStep) void {
158158
159159 const host_name = builder.host.target.zigTriple(builder.allocator) catch unreachable;
160160 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;
162162 const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc;
163163 switch (builder.host.getExternalExecutor(target_info, .{
164164 .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
lib/std/zig/system/NativeTargetInfo.zig+17-117
......@@ -37,8 +37,7 @@ pub const DetectError = error{
3737/// relative to that.
3838/// Any resources this function allocates are released before returning, and so there is no
3939/// deinitialization method.
40/// TODO Remove the Allocator requirement from this function.
41pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
40pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo {
4241 var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());
4342 if (cross_target.os_tag == null) {
4443 switch (builtin.target.os.tag) {
......@@ -199,7 +198,7 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ
199198 } orelse backup_cpu_detection: {
200199 break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
201200 };
202 var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);
201 var result = try detectAbiAndDynamicLinker(cpu, os, cross_target);
203202 // For x86, we need to populate some CPU feature flags depending on architecture
204203 // and mode:
205204 // * 16bit_mode => if the abi is code16
......@@ -236,13 +235,20 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ
236235 return result;
237236}
238237
239/// First we attempt to use the executable's own binary. If it is dynamically
240/// linked, then it should answer both the C ABI question and the dynamic linker question.
241/// 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
242/// we fall back to the defaults.
243/// TODO Remove the Allocator requirement from this function.
238/// In the past, this function attempted to use the executable's own binary if it was dynamically
239/// linked to answer both the C ABI question and the dynamic linker question. However, this
240/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
241/// it to an older glibc version, while system binaries such as /usr/bin/env use a newer glibc
242/// version. The problem is that libc.so.6 glibc version will match that of the system while
243/// the dynamic linker will match that of the compiler binary. Executables with these versions
244/// mismatching will fail to run.
245///
246/// Therefore, this function works the same regardless of whether the compiler binary is
247/// dynamically or statically linked. It inspects `/usr/bin/env` as an ELF file to find the
248/// answer to these questions, or if there is a shebang line, then it chases the referenced
249/// file recursively. If that does not provide the answer, then the function falls back to
250/// defaults.
244251fn detectAbiAndDynamicLinker(
245 allocator: Allocator,
246252 cpu: Target.Cpu,
247253 os: Target.Os,
248254 cross_target: CrossTarget,
......@@ -280,8 +286,8 @@ fn detectAbiAndDynamicLinker(
280286 const ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch);
281287
282288 for (all_abis) |abi| {
283 // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and
284 // skip adding it to `ld_info_list`.
289 // This may be a nonsensical parameter. We detect this with
290 // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`.
285291 const target: Target = .{
286292 .cpu = cpu,
287293 .os = os,
......@@ -301,62 +307,6 @@ fn detectAbiAndDynamicLinker(
301307
302308 // Best case scenario: the executable is dynamically linked, and we can iterate
303309 // over our own shared objects and find a dynamic linker.
304 self_exe: {
305 const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
306 defer {
307 for (lib_paths) |lib_path| {
308 allocator.free(lib_path);
309 }
310 allocator.free(lib_paths);
311 }
312
313 var found_ld_info: LdInfo = undefined;
314 var found_ld_path: [:0]const u8 = undefined;
315
316 // Look for dynamic linker.
317 // This is O(N^M) but typical case here is N=2 and M=10.
318 find_ld: for (lib_paths) |lib_path| {
319 for (ld_info_list) |ld_info| {
320 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
321 if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
322 found_ld_info = ld_info;
323 found_ld_path = lib_path;
324 break :find_ld;
325 }
326 }
327 } else break :self_exe;
328
329 // Look for glibc version.
330 var os_adjusted = os;
331 if (builtin.target.os.tag == .linux and found_ld_info.abi.isGnu() and
332 cross_target.glibc_version == null)
333 {
334 for (lib_paths) |lib_path| {
335 if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) {
336 os_adjusted.version_range.linux.glibc = glibcVerFromSo(lib_path) catch |err| switch (err) {
337 error.GnuLibCVersionUnavailable => continue,
338 else => |e| return e,
339 };
340 break;
341 }
342 }
343 }
344
345 var result: NativeTargetInfo = .{
346 .target = .{
347 .cpu = cpu,
348 .os = os_adjusted,
349 .abi = cross_target.abi orelse found_ld_info.abi,
350 .ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os_adjusted.tag, cpu.arch),
351 },
352 .dynamic_linker = if (cross_target.dynamic_linker.get() == null)
353 DynamicLinker.init(found_ld_path)
354 else
355 cross_target.dynamic_linker,
356 };
357 return result;
358 }
359
360310 const elf_file = blk: {
361311 // This block looks for a shebang line in /usr/bin/env,
362312 // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
......@@ -452,56 +402,6 @@ fn detectAbiAndDynamicLinker(
452402
453403const glibc_so_basename = "libc.so.6";
454404
455fn glibcVerFromSo(so_path: [:0]const u8) !std.builtin.Version {
456 const file = fs.openFileAbsolute(so_path, .{}) catch |err| switch (err) {
457 // Contextually impossible errors.
458 error.NoSpaceLeft => unreachable,
459 error.NameTooLong => unreachable,
460 error.PathAlreadyExists => unreachable,
461 error.SharingViolation => unreachable,
462 error.InvalidUtf8 => unreachable,
463 error.BadPathName => unreachable,
464 error.PipeBusy => unreachable,
465 error.FileLocksNotSupported => unreachable,
466 error.WouldBlock => unreachable,
467 error.FileBusy => unreachable, // opened without write permissions
468 error.NoDevice => unreachable, // not accessing special device
469 error.InvalidHandle => unreachable, // should not be in the error set
470 error.DeviceBusy => unreachable, // read-only
471
472 // Errors that indicate a false negative may occur if we treat this as
473 // not a libc shared object.
474 error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded,
475 error.SystemFdQuotaExceeded => return error.SystemFdQuotaExceeded,
476 error.SystemResources => return error.SystemResources,
477 error.Unexpected => return error.Unexpected,
478
479 // Errors that indicate this file is not a libc shared object.
480 error.SymLinkLoop => return error.GnuLibCVersionUnavailable,
481 error.IsDir => return error.GnuLibCVersionUnavailable,
482 error.AccessDenied => return error.GnuLibCVersionUnavailable,
483 error.FileNotFound => return error.GnuLibCVersionUnavailable,
484 error.FileTooBig => return error.GnuLibCVersionUnavailable,
485 error.NotDir => return error.GnuLibCVersionUnavailable,
486 };
487 defer file.close();
488
489 return glibcVerFromSoFile(file) catch |err| switch (err) {
490 error.InvalidElfMagic => return error.GnuLibCVersionUnavailable,
491 error.InvalidElfEndian => return error.GnuLibCVersionUnavailable,
492 error.InvalidElfClass => return error.GnuLibCVersionUnavailable,
493 error.InvalidElfFile => return error.GnuLibCVersionUnavailable,
494 error.InvalidElfVersion => return error.GnuLibCVersionUnavailable,
495 error.InvalidGnuLibCVersion => return error.GnuLibCVersionUnavailable,
496 error.UnexpectedEndOfFile => return error.GnuLibCVersionUnavailable,
497 error.UnableToReadElfFile => return error.GnuLibCVersionUnavailable,
498
499 error.SystemResources => return error.SystemResources,
500 error.FileSystem => return error.FileSystem,
501 error.Unexpected => return error.Unexpected,
502 };
503}
504
505405fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
506406 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
507407 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);
src/main.zig+8-9
......@@ -268,7 +268,7 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
268268 } else if (mem.eql(u8, cmd, "init-lib")) {
269269 return cmdInit(gpa, arena, cmd_args, .Lib);
270270 } else if (mem.eql(u8, cmd, "targets")) {
271 const info = try detectNativeTargetInfo(arena, .{});
271 const info = try detectNativeTargetInfo(.{});
272272 const stdout = io.getStdOut().writer();
273273 return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
274274 } else if (mem.eql(u8, cmd, "version")) {
......@@ -2267,7 +2267,7 @@ fn buildOutputType(
22672267 }
22682268
22692269 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
22722272 if (target_info.target.os.tag != .freestanding) {
22732273 if (ensure_libc_on_non_freestanding)
......@@ -3283,7 +3283,7 @@ fn runOrTest(
32833283 if (std.process.can_execv and arg_mode == .run and !watch) {
32843284 // execv releases the locks; no need to destroy the Compilation here.
32853285 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);
32873287 const cmd = try std.mem.join(arena, " ", argv.items);
32883288 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
32893289 } else if (std.process.can_spawn) {
......@@ -3300,7 +3300,7 @@ fn runOrTest(
33003300 }
33013301
33023302 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);
33043304 const cmd = try std.mem.join(arena, " ", argv.items);
33053305 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
33063306 };
......@@ -3914,7 +3914,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
39143914 gimmeMoreOfThoseSweetSweetFileDescriptors();
39153915
39163916 const cross_target: std.zig.CrossTarget = .{};
3917 const target_info = try detectNativeTargetInfo(gpa, cross_target);
3917 const target_info = try detectNativeTargetInfo(cross_target);
39183918
39193919 const exe_basename = try std.zig.binNameAlloc(arena, .{
39203920 .root_name = "build",
......@@ -4956,8 +4956,8 @@ test "fds" {
49564956 gimmeMoreOfThoseSweetSweetFileDescriptors();
49574957}
49584958
4959fn detectNativeTargetInfo(gpa: Allocator, cross_target: std.zig.CrossTarget) !std.zig.system.NativeTargetInfo {
4960 return std.zig.system.NativeTargetInfo.detect(gpa, cross_target);
4959fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.NativeTargetInfo {
4960 return std.zig.system.NativeTargetInfo.detect(cross_target);
49614961}
49624962
49634963/// Indicate that we are now terminating with a successful exit code.
......@@ -5320,14 +5320,13 @@ fn parseIntSuffix(arg: []const u8, prefix_len: usize) u64 {
53205320}
53215321
53225322fn warnAboutForeignBinaries(
5323 gpa: Allocator,
53245323 arena: Allocator,
53255324 arg_mode: ArgMode,
53265325 target_info: std.zig.system.NativeTargetInfo,
53275326 link_libc: bool,
53285327) !void {
53295328 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
53325331 switch (host_target_info.getExternalExecutor(target_info, .{ .link_libc = link_libc })) {
53335332 .native => return,
src/test.zig+2-2
......@@ -1211,7 +1211,7 @@ pub const TestContext = struct {
12111211 }
12121212
12131213 fn run(self: *TestContext) !void {
1214 const host = try std.zig.system.NativeTargetInfo.detect(self.gpa, .{});
1214 const host = try std.zig.system.NativeTargetInfo.detect(.{});
12151215
12161216 var progress = std.Progress{};
12171217 const root_node = progress.start("compiler", self.cases.items.len);
......@@ -1300,7 +1300,7 @@ pub const TestContext = struct {
13001300 global_cache_directory: Compilation.Directory,
13011301 host: std.zig.system.NativeTargetInfo,
13021302 ) !void {
1303 const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target);
1303 const target_info = try std.zig.system.NativeTargetInfo.detect(case.target);
13041304 const target = target_info.target;
13051305
13061306 var arena_allocator = std.heap.ArenaAllocator.init(allocator);