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(...@@ -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+17-117
...@@ -37,8 +37,7 @@ pub const DetectError = error{...@@ -37,8 +37,7 @@ pub const DetectError = error{
37/// relative to that.37/// relative to that.
38/// 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
39/// deinitialization method.39/// deinitialization method.
40/// TODO Remove the Allocator requirement from this function.40pub fn detect(cross_target: CrossTarget) DetectError!NativeTargetInfo {
41pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo {
42 var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());41 var os = cross_target.getOsTag().defaultVersionRange(cross_target.getCpuArch());
43 if (cross_target.os_tag == null) {42 if (cross_target.os_tag == null) {
44 switch (builtin.target.os.tag) {43 switch (builtin.target.os.tag) {
...@@ -199,7 +198,7 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ...@@ -199,7 +198,7 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ
199 } orelse backup_cpu_detection: {198 } orelse backup_cpu_detection: {
200 break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);199 break :backup_cpu_detection Target.Cpu.baseline(cpu_arch);
201 };200 };
202 var result = try detectAbiAndDynamicLinker(allocator, cpu, os, cross_target);201 var result = try detectAbiAndDynamicLinker(cpu, os, cross_target);
203 // 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
204 // and mode:203 // and mode:
205 // * 16bit_mode => if the abi is code16204 // * 16bit_mode => if the abi is code16
...@@ -236,13 +235,20 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ...@@ -236,13 +235,20 @@ pub fn detect(allocator: Allocator, cross_target: CrossTarget) DetectError!Nativ
236 return result;235 return result;
237}236}
238237
239/// 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
240/// 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
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, then240/// could be problematic on a system that uses a RUNPATH for the compiler binary, locking
242/// 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
243/// 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.
244fn detectAbiAndDynamicLinker(251fn detectAbiAndDynamicLinker(
245 allocator: Allocator,
246 cpu: Target.Cpu,252 cpu: Target.Cpu,
247 os: Target.Os,253 os: Target.Os,
248 cross_target: CrossTarget,254 cross_target: CrossTarget,
...@@ -280,8 +286,8 @@ fn detectAbiAndDynamicLinker(...@@ -280,8 +286,8 @@ fn detectAbiAndDynamicLinker(
280 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);
281287
282 for (all_abis) |abi| {288 for (all_abis) |abi| {
283 // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and289 // This may be a nonsensical parameter. We detect this with
284 // skip adding it to `ld_info_list`.290 // error.UnknownDynamicLinkerPath and skip adding it to `ld_info_list`.
285 const target: Target = .{291 const target: Target = .{
286 .cpu = cpu,292 .cpu = cpu,
287 .os = os,293 .os = os,
...@@ -301,62 +307,6 @@ fn detectAbiAndDynamicLinker(...@@ -301,62 +307,6 @@ fn detectAbiAndDynamicLinker(
301307
302 // Best case scenario: the executable is dynamically linked, and we can iterate308 // Best case scenario: the executable is dynamically linked, and we can iterate
303 // over our own shared objects and find a dynamic linker.309 // 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
360 const elf_file = blk: {310 const elf_file = blk: {
361 // This block looks for a shebang line in /usr/bin/env,311 // This block looks for a shebang line in /usr/bin/env,
362 // 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,
...@@ -452,56 +402,6 @@ fn detectAbiAndDynamicLinker(...@@ -452,56 +402,6 @@ fn detectAbiAndDynamicLinker(
452402
453const glibc_so_basename = "libc.so.6";403const 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
505fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {405fn glibcVerFromSoFile(file: fs.File) !std.builtin.Version {
506 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;406 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
507 _ = try preadMin(file, &hdr_buf, 0, hdr_buf.len);407 _ = 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...@@ -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
...@@ -1211,7 +1211,7 @@ pub const TestContext = struct {...@@ -1211,7 +1211,7 @@ pub const TestContext = struct {
1211 }1211 }
12121212
1213 fn run(self: *TestContext) !void {1213 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
1216 var progress = std.Progress{};1216 var progress = std.Progress{};
1217 const root_node = progress.start("compiler", self.cases.items.len);1217 const root_node = progress.start("compiler", self.cases.items.len);
...@@ -1300,7 +1300,7 @@ pub const TestContext = struct {...@@ -1300,7 +1300,7 @@ pub const TestContext = struct {
1300 global_cache_directory: Compilation.Directory,1300 global_cache_directory: Compilation.Directory,
1301 host: std.zig.system.NativeTargetInfo,1301 host: std.zig.system.NativeTargetInfo,
1302 ) !void {1302 ) !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);
1304 const target = target_info.target;1304 const target = target_info.target;
13051305
1306 var arena_allocator = std.heap.ArenaAllocator.init(allocator);1306 var arena_allocator = std.heap.ArenaAllocator.init(allocator);