From 60f2f3457dd73772e7cd60bf5d90358079361d11 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 27 Feb 2020 16:38:10 -0500 Subject: [PATCH] getStandardDynamicLinkerPath renamed and no allocator * `std.Target.getStandardDynamicLinkerPath` => `std.Target.standardDynamicLinkerPath` * it now takes a pointer to fixed size array rather than an allocator * `std.zig.system.NativeTargetInfo.detect` now supports reading PT_INTERP from /usr/bin/env --- lib/std/target.zig | 142 +++++++++++++++++++------------------ lib/std/zig/system.zig | 115 +++++++++++++++++++++--------- src-self-hosted/stage2.zig | 16 ++--- 3 files changed, 159 insertions(+), 114 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 9cd65e7661..33019b6bbe 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1084,65 +1084,59 @@ pub const Target = struct { } } - /// Caller owns returned memory. - pub fn getStandardDynamicLinkerPath( - self: Target, - allocator: *mem.Allocator, - ) error{ - OutOfMemory, - UnknownDynamicLinkerPath, - TargetHasNoDynamicLinker, - }![:0]u8 { - const a = allocator; + /// The result will be a slice of `buffer`, pointing at position 0. + /// A return value of `null` means the concept of a dynamic linker is not meaningful for that target. + pub fn standardDynamicLinkerPath(self: Target, buffer: *[255]u8) ?[]u8 { + const S = struct { + fn print(b: *[255]u8, comptime fmt: []const u8, args: var) []u8 { + return std.fmt.bufPrint(b, fmt, args) catch unreachable; + } + fn copy(b: *[255]u8, s: []const u8) []u8 { + mem.copy(u8, b, s); + return b[0..s.len]; + } + }; + const print = S.print; + const copy = S.copy; + if (self.isAndroid()) { - return mem.dupeZ(a, u8, if (self.cpu.arch.ptrBitWidth() == 64) - "/system/bin/linker64" - else - "/system/bin/linker"); + const suffix = if (self.cpu.arch.ptrBitWidth() == 64) "64" else ""; + return print(buffer, "/system/bin/linker{}", .{suffix}); } if (self.isMusl()) { - var result = try std.Buffer.init(allocator, "/lib/ld-musl-"); - defer result.deinit(); - - var is_arm = false; - switch (self.cpu.arch) { - .arm, .thumb => { - try result.append("arm"); - is_arm = true; - }, - .armeb, .thumbeb => { - try result.append("armeb"); - is_arm = true; - }, - else => |arch| try result.append(@tagName(arch)), - } - if (is_arm and self.getFloatAbi() == .hard) { - try result.append("hf"); - } - try result.append(".so.1"); - return result.toOwnedSlice(); + const is_arm = switch (self.cpu.arch) { + .arm, .armeb, .thumb, .thumbeb => true, + else => false, + }; + const arch_part = switch (self.cpu.arch) { + .arm, .thumb => "arm", + .armeb, .thumbeb => "armeb", + else => |arch| @tagName(arch), + }; + const arch_suffix = if (is_arm and self.getFloatAbi() == .hard) "hf" else ""; + return print(buffer, "/lib/ld-musl-{}{}.so.1", .{ arch_part, arch_suffix }); } switch (self.os.tag) { - .freebsd => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.1"), - .netbsd => return mem.dupeZ(a, u8, "/libexec/ld.elf_so"), - .dragonfly => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.2"), + .freebsd => return copy(buffer, "/libexec/ld-elf.so.1"), + .netbsd => return copy(buffer, "/libexec/ld.elf_so"), + .dragonfly => return copy(buffer, "/libexec/ld-elf.so.2"), .linux => switch (self.cpu.arch) { .i386, .sparc, .sparcel, - => return mem.dupeZ(a, u8, "/lib/ld-linux.so.2"), + => return copy(buffer, "/lib/ld-linux.so.2"), - .aarch64 => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64.so.1"), - .aarch64_be => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64_be.so.1"), - .aarch64_32 => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64_32.so.1"), + .aarch64 => return copy(buffer, "/lib/ld-linux-aarch64.so.1"), + .aarch64_be => return copy(buffer, "/lib/ld-linux-aarch64_be.so.1"), + .aarch64_32 => return copy(buffer, "/lib/ld-linux-aarch64_32.so.1"), .arm, .armeb, .thumb, .thumbeb, - => return mem.dupeZ(a, u8, switch (self.getFloatAbi()) { + => return copy(buffer, switch (self.getFloatAbi()) { .hard => "/lib/ld-linux-armhf.so.3", else => "/lib/ld-linux.so.3", }), @@ -1159,29 +1153,35 @@ pub const Target = struct { }; const is_nan_2008 = mips.featureSetHas(self.cpu.features, .nan2008); const loader = if (is_nan_2008) "ld-linux-mipsn8.so.1" else "ld.so.1"; - return std.fmt.allocPrint0(a, "/lib{}/{}", .{ lib_suffix, loader }); + return print(buffer, "/lib{}/{}", .{ lib_suffix, loader }); }, - .powerpc => return mem.dupeZ(a, u8, "/lib/ld.so.1"), - .powerpc64, .powerpc64le => return mem.dupeZ(a, u8, "/lib64/ld64.so.2"), - .s390x => return mem.dupeZ(a, u8, "/lib64/ld64.so.1"), - .sparcv9 => return mem.dupeZ(a, u8, "/lib64/ld-linux.so.2"), - .x86_64 => return mem.dupeZ(a, u8, switch (self.abi) { + .powerpc => return copy(buffer, "/lib/ld.so.1"), + .powerpc64, .powerpc64le => return copy(buffer, "/lib64/ld64.so.2"), + .s390x => return copy(buffer, "/lib64/ld64.so.1"), + .sparcv9 => return copy(buffer, "/lib64/ld-linux.so.2"), + .x86_64 => return copy(buffer, switch (self.abi) { .gnux32 => "/libx32/ld-linux-x32.so.2", else => "/lib64/ld-linux-x86-64.so.2", }), - .riscv32 => return mem.dupeZ(a, u8, "/lib/ld-linux-riscv32-ilp32.so.1"), - .riscv64 => return mem.dupeZ(a, u8, "/lib/ld-linux-riscv64-lp64.so.1"), + .riscv32 => return copy(buffer, "/lib/ld-linux-riscv32-ilp32.so.1"), + .riscv64 => return copy(buffer, "/lib/ld-linux-riscv64-lp64.so.1"), + // Architectures in this list have been verified as not having a standard + // dynamic linker path. .wasm32, .wasm64, - => return error.TargetHasNoDynamicLinker, - - .arc, - .avr, .bpfel, .bpfeb, + .nvptx, + .nvptx64, + => return null, + + // TODO go over each item in this list and either move it to the above list, or + // implement the standard dynamic linker path code for it. + .arc, + .avr, .hexagon, .msp430, .r600, @@ -1189,8 +1189,6 @@ pub const Target = struct { .tce, .tcele, .xcore, - .nvptx, - .nvptx64, .le32, .le64, .amdil, @@ -1204,9 +1202,25 @@ pub const Target = struct { .lanai, .renderscript32, .renderscript64, - => return error.UnknownDynamicLinkerPath, + => return null, }, + // Operating systems in this list have been verified as not having a standard + // dynamic linker path. + .freestanding, + .ios, + .tvos, + .watchos, + .macosx, + .uefi, + .windows, + .emscripten, + .other, + .wasi, + => return null, + + // TODO go over each item in this list and either move it to the above list, or + // implement the standard dynamic linker path code for it. .ananas, .cloudabi, .fuchsia, @@ -1230,19 +1244,7 @@ pub const Target = struct { .amdpal, .hermit, .hurd, - => return error.UnknownDynamicLinkerPath, - - .freestanding, - .ios, - .tvos, - .watchos, - .macosx, - .uefi, - .windows, - .emscripten, - .other, - .wasi, - => return error.TargetHasNoDynamicLinker, + => return null, } } }; diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 5c9b71001b..d2828a88a1 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -167,7 +167,15 @@ pub const NativePaths = struct { pub const NativeTargetInfo = struct { target: Target, - dynamic_linker: ?[:0]u8, + + /// Contains the memory used to store the dynamic linker path. This field should + /// not be used directly. See `dynamicLinker` and `setDynamicLinker`. This field + /// exists so that this API requires no allocator. + dynamic_linker_buffer: [255]u8 = undefined, + + /// Used to construct the dynamic linker path. This field should not be used + /// directly. See `dynamicLinker` and `setDynamicLinker`. + dynamic_linker_max: ?u8 = null, pub const DetectError = error{ OutOfMemory, @@ -181,6 +189,7 @@ pub const NativeTargetInfo = struct { /// Detects the native CPU model & features, operating system & version, and C ABI & dynamic linker. /// On Linux, this is additionally responsible for detecting the native glibc version when applicable. + /// TODO Remove the allocator requirement from this. pub fn detect(allocator: *Allocator) DetectError!NativeTargetInfo { const arch = Target.current.cpu.arch; const os_tag = Target.current.os.tag; @@ -188,8 +197,7 @@ pub const NativeTargetInfo = struct { // TODO Detect native CPU model & features. Until that is implemented we hard code baseline. const cpu = Target.Cpu.baseline(arch); - // TODO Detect native operating system version. Until that is implemented we use the minimum version - // of the default range. + // TODO Detect native operating system version. Until that is implemented we use the default range. const os = Target.Os.defaultVersionRange(os_tag); return detectAbiAndDynamicLinker(allocator, cpu, os); @@ -201,6 +209,21 @@ pub const NativeTargetInfo = struct { self.* = undefined; } + /// The returned memory has the same lifetime as the `NativeTargetInfo`. + pub fn dynamicLinker(self: *const NativeTargetInfo) ?[]const u8 { + const m = self.dynamic_linker_max orelse return null; + return self.dynamic_linker_buffer[0 .. m + 1]; + } + + pub fn setDynamicLinker(self: *NativeTargetInfo, dl_or_null: ?[]const u8) void { + if (dl_or_null) |dl| { + mem.copy(u8, &self.dynamic_linker_buffer, dl); + self.dynamic_linker_max = @intCast(u8, dl.len - 1); + } else { + self.dynamic_linker_max = null; + } + } + /// First we attempt to use the executable's own binary. If it is dynamically /// linked, then it should answer both the C ABI question and the dynamic linker question. /// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then @@ -245,10 +268,11 @@ pub const NativeTargetInfo = struct { .os = os, .abi = abi, }; - const standard_ld_path = target.getStandardDynamicLinkerPath(allocator) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.UnknownDynamicLinkerPath, error.TargetHasNoDynamicLinker => continue, - }; + var buf: [255]u8 = undefined; + const standard_ld_path = if (target.standardDynamicLinkerPath(&buf)) |s| + try mem.dupe(allocator, u8, s) + else + continue; errdefer allocator.free(standard_ld_path); try ld_info_list.append(.{ .ld_path = standard_ld_path, @@ -294,28 +318,29 @@ pub const NativeTargetInfo = struct { } } - return NativeTargetInfo{ + var result: NativeTargetInfo = .{ .target = .{ .cpu = cpu, .os = os_adjusted, .abi = found_ld_info.abi, }, - .dynamic_linker = try mem.dupeZ(allocator, u8, found_ld_path), }; + result.setDynamicLinker(found_ld_path); + return result; } // If Zig is statically linked, such as via distributed binary static builds, the above // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env. // Since that path is hard-coded into the shebang line of many portable scripts, it's a // reasonably reliable path to check for. - return abiAndDynamicLinkerFromUsrBinEnv(allocator, cpu, os) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.FileSystem => return error.FileSystem, - error.SystemResources => return error.SystemResources, - error.SymLinkLoop => return error.SymLinkLoop, - error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded, - error.SystemFdQuotaExceeded => return error.SystemFdQuotaExceeded, - error.DeviceBusy => return error.DeviceBusy, + return abiAndDynamicLinkerFromUsrBinEnv(cpu, os) catch |err| switch (err) { + error.FileSystem, + error.SystemResources, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + error.DeviceBusy, + => |e| return e, error.UnableToReadElfFile, error.ElfNotADynamicExecutable, @@ -327,6 +352,8 @@ pub const NativeTargetInfo = struct { error.InvalidElfMagic, error.UsrBinEnvNotAvailable, error.Unexpected, + error.UnexpectedEndOfFile, + error.NameTooLong, // Finally, we fall back on the standard path. => defaultAbiAndDynamicLinker(allocator, cpu, os), }; @@ -362,11 +389,7 @@ pub const NativeTargetInfo = struct { }; } - fn abiAndDynamicLinkerFromUsrBinEnv( - allocator: *Allocator, - cpu: Target.Cpu, - os: Target.Os, - ) !NativeTargetInfo { + fn abiAndDynamicLinkerFromUsrBinEnv(cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo { const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, error.NameTooLong => unreachable, @@ -409,6 +432,14 @@ pub const NativeTargetInfo = struct { const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); + var result: NativeTargetInfo = .{ + .target = .{ + .cpu = cpu, + .os = os, + .abi = Target.Abi.default(cpu.arch, os), + }, + }; + const ph_total_size = std.math.mul(u32, phentsize, phnum) catch |err| switch (err) { error.Overflow => return error.InvalidElfProgramHeaders, }; @@ -430,7 +461,22 @@ pub const NativeTargetInfo = struct { const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); switch (p_type) { elf.PT_INTERP => { - std.debug.warn("found PT_INTERP\n", .{}); + const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); + const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); + var interp_buf: [255]u8 = undefined; + if (p_filesz > interp_buf.len) return error.NameTooLong; + var read_offset: usize = 0; + while (true) { + const len = try wrapRead(env_file.pread( + interp_buf[read_offset .. p_filesz - read_offset], + p_offset + read_offset, + )); + if (len == 0) return error.UnexpectedEndOfFile; + read_offset += len; + if (read_offset == p_filesz) break; + } + // PT_INTERP includes a null byte in p_filesz. + result.setDynamicLinker(interp_buf[0 .. p_filesz - 1]); }, elf.PT_DYNAMIC => { std.debug.warn("found PT_DYNAMIC\n", .{}); @@ -440,7 +486,7 @@ pub const NativeTargetInfo = struct { } } - return error.OutOfMemory; // TODO + return result; } fn wrapRead(res: std.os.ReadError!usize) !usize { @@ -457,18 +503,17 @@ pub const NativeTargetInfo = struct { } fn defaultAbiAndDynamicLinker(allocator: *Allocator, cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo { - const target: Target = .{ - .cpu = cpu, - .os = os, - .abi = Target.Abi.default(cpu.arch, os), - }; - return @as(NativeTargetInfo, .{ - .target = target, - .dynamic_linker = target.getStandardDynamicLinkerPath(allocator) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.UnknownDynamicLinkerPath, error.TargetHasNoDynamicLinker => null, + var result: NativeTargetInfo = .{ + .target = .{ + .cpu = cpu, + .os = os, + .abi = Target.Abi.default(cpu.arch, os), }, - }); + }; + if (result.target.standardDynamicLinkerPath(&result.dynamic_linker_buffer)) |s| { + result.dynamic_linker_max = @intCast(u8, s.len - 1); + } + return result; } }; diff --git a/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig index 14673b0e98..5358c4fedc 100644 --- a/src-self-hosted/stage2.zig +++ b/src-self-hosted/stage2.zig @@ -1157,9 +1157,9 @@ fn crossTargetToTarget(cross_target: CrossTarget, dynamic_linker_ptr: *?[*:0]u8) if (cross_target.os_tag == null) { adjusted_target.os = detected_info.target.os; - if (detected_info.dynamic_linker) |dl| { + if (detected_info.dynamicLinker()) |dl| { have_native_dl = true; - dynamic_linker_ptr.* = dl.ptr; + dynamic_linker_ptr.* = try mem.dupeZ(std.heap.c_allocator, u8, dl); } if (cross_target.abi == null) { adjusted_target.abi = detected_info.target.abi; @@ -1169,13 +1169,11 @@ fn crossTargetToTarget(cross_target: CrossTarget, dynamic_linker_ptr: *?[*:0]u8) } } if (!have_native_dl) { - dynamic_linker_ptr.* = adjusted_target.getStandardDynamicLinkerPath( - std.heap.c_allocator, - ) catch |err| switch (err) { - error.TargetHasNoDynamicLinker => null, - error.UnknownDynamicLinkerPath => null, - else => |e| return e, - }; + var buf: [255]u8 = undefined; + dynamic_linker_ptr.* = if (adjusted_target.standardDynamicLinkerPath(&buf)) |s| + try mem.dupeZ(std.heap.c_allocator, u8, s) + else + null; } return adjusted_target; }