Skip to content

Commit 2f9c5c0

Browse files
committed
self-host dynamic linker detection
1 parent c784c52 commit 2f9c5c0

17 files changed

+430
-532
lines changed

lib/std/os.zig

+18-14
Original file line numberDiff line numberDiff line change
@@ -2974,18 +2974,26 @@ pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
29742974
}
29752975

29762976
pub fn dl_iterate_phdr(
2977-
comptime T: type,
2978-
callback: extern fn (info: *dl_phdr_info, size: usize, data: ?*T) i32,
2979-
data: ?*T,
2980-
) isize {
2977+
context: var,
2978+
comptime Error: type,
2979+
comptime callback: fn (info: *dl_phdr_info, size: usize, context: @TypeOf(context)) Error!void,
2980+
) Error!void {
2981+
const Context = @TypeOf(context);
2982+
29812983
if (builtin.object_format != .elf)
29822984
@compileError("dl_iterate_phdr is not available for this target");
29832985

29842986
if (builtin.link_libc) {
2985-
return system.dl_iterate_phdr(
2986-
@ptrCast(std.c.dl_iterate_phdr_callback, callback),
2987-
@ptrCast(?*c_void, data),
2988-
);
2987+
switch (system.dl_iterate_phdr(struct {
2988+
fn callbackC(info: *dl_phdr_info, size: usize, data: ?*c_void) callconv(.C) c_int {
2989+
const context_ptr = @ptrCast(*const Context, @alignCast(@alignOf(*const Context), data));
2990+
callback(info, size, context_ptr.*) catch |err| return @errorToInt(err);
2991+
return 0;
2992+
}
2993+
}.callbackC, @intToPtr(?*c_void, @ptrToInt(&context)))) {
2994+
0 => return,
2995+
else => |err| return @errSetCast(Error, @intToError(@intCast(u16, err))), // TODO don't hardcode u16
2996+
}
29892997
}
29902998

29912999
const elf_base = std.process.getBaseAddress();
@@ -3007,11 +3015,10 @@ pub fn dl_iterate_phdr(
30073015
.dlpi_phnum = ehdr.e_phnum,
30083016
};
30093017

3010-
return callback(&info, @sizeOf(dl_phdr_info), data);
3018+
return callback(&info, @sizeOf(dl_phdr_info), context);
30113019
}
30123020

30133021
// Last return value from the callback function
3014-
var last_r: isize = 0;
30153022
while (it.next()) |entry| {
30163023
var dlpi_phdr: [*]elf.Phdr = undefined;
30173024
var dlpi_phnum: u16 = undefined;
@@ -3033,11 +3040,8 @@ pub fn dl_iterate_phdr(
30333040
.dlpi_phnum = dlpi_phnum,
30343041
};
30353042

3036-
last_r = callback(&info, @sizeOf(dl_phdr_info), data);
3037-
if (last_r != 0) break;
3043+
try callback(&info, @sizeOf(dl_phdr_info), context);
30383044
}
3039-
3040-
return last_r;
30413045
}
30423046

30433047
pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError;

lib/std/process.zig

+56
Original file line numberDiff line numberDiff line change
@@ -613,3 +613,59 @@ pub fn getBaseAddress() usize {
613613
else => @compileError("Unsupported OS"),
614614
}
615615
}
616+
617+
/// Caller owns the result value and each inner slice.
618+
pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0]u8 {
619+
switch (builtin.link_mode) {
620+
.Static => return &[_][:0]u8{},
621+
.Dynamic => {},
622+
}
623+
const List = std.ArrayList([:0]u8);
624+
switch (builtin.os) {
625+
.linux,
626+
.freebsd,
627+
.netbsd,
628+
.dragonfly,
629+
=> {
630+
var paths = List.init(allocator);
631+
errdefer {
632+
const slice = paths.toOwnedSlice();
633+
for (slice) |item| {
634+
allocator.free(item);
635+
}
636+
allocator.free(slice);
637+
}
638+
try os.dl_iterate_phdr(&paths, error{OutOfMemory}, struct {
639+
fn callback(info: *os.dl_phdr_info, size: usize, list: *List) !void {
640+
const name = info.dlpi_name orelse return;
641+
if (name[0] == '/') {
642+
const item = try mem.dupeZ(list.allocator, u8, mem.toSliceConst(u8, name));
643+
errdefer list.allocator.free(item);
644+
try list.append(item);
645+
}
646+
}
647+
}.callback);
648+
return paths.toOwnedSlice();
649+
},
650+
.macosx, .ios, .watchos, .tvos => {
651+
var paths = List.init(allocator);
652+
errdefer {
653+
const slice = paths.toOwnedSlice();
654+
for (slice) |item| {
655+
allocator.free(item);
656+
}
657+
allocator.free(slice);
658+
}
659+
const img_count = std.c._dyld_image_count();
660+
var i: u32 = 0;
661+
while (i < img_count) : (i += 1) {
662+
const name = std.c._dyld_get_image_name(i);
663+
const item = try mem.dupeZ(allocator, u8, mem.toSliceConst(u8, name));
664+
errdefer allocator.free(item);
665+
try paths.append(item);
666+
}
667+
return paths.toOwnedSlice();
668+
},
669+
else => return error.UnimplementedSelfExeSharedPaths,
670+
}
671+
}

lib/std/target.zig

+137
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,13 @@ pub const Target = union(enum) {
10371037
};
10381038
}
10391039

1040+
pub fn isAndroid(self: Target) bool {
1041+
return switch (self.getAbi()) {
1042+
.android => true,
1043+
else => false,
1044+
};
1045+
}
1046+
10401047
pub fn isDragonFlyBSD(self: Target) bool {
10411048
return switch (self.getOs()) {
10421049
.dragonfly => true,
@@ -1196,6 +1203,136 @@ pub const Target = union(enum) {
11961203

11971204
return .unavailable;
11981205
}
1206+
1207+
pub const FloatAbi = enum {
1208+
hard,
1209+
soft,
1210+
soft_fp,
1211+
};
1212+
1213+
pub fn getFloatAbi(self: Target) FloatAbi {
1214+
return switch (self.getAbi()) {
1215+
.gnueabihf,
1216+
.eabihf,
1217+
.musleabihf,
1218+
=> .hard,
1219+
else => .soft,
1220+
};
1221+
}
1222+
1223+
/// Caller owns returned memory.
1224+
pub fn getStandardDynamicLinkerPath(
1225+
self: Target,
1226+
allocator: *mem.Allocator,
1227+
) error{
1228+
OutOfMemory,
1229+
UnknownDynamicLinkerPath,
1230+
}![:0]u8 {
1231+
const a = allocator;
1232+
if (self.isAndroid()) {
1233+
return mem.dupeZ(a, u8, if (self.getArchPtrBitWidth() == 64)
1234+
"/system/bin/linker64"
1235+
else
1236+
"/system/bin/linker");
1237+
}
1238+
1239+
if (self.isMusl()) {
1240+
var result = try std.Buffer.init(allocator, "/lib/ld-musl-");
1241+
defer result.deinit();
1242+
1243+
var is_arm = false;
1244+
switch (self.getArch()) {
1245+
.arm, .thumb => {
1246+
try result.append("arm");
1247+
is_arm = true;
1248+
},
1249+
.armeb, .thumbeb => {
1250+
try result.append("armeb");
1251+
is_arm = true;
1252+
},
1253+
else => |arch| try result.append(@tagName(arch)),
1254+
}
1255+
if (is_arm and self.getFloatAbi() == .hard) {
1256+
try result.append("hf");
1257+
}
1258+
try result.append(".so.1");
1259+
return result.toOwnedSlice();
1260+
}
1261+
1262+
switch (self.getOs()) {
1263+
.freebsd => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.1"),
1264+
.netbsd => return mem.dupeZ(a, u8, "/libexec/ld.elf_so"),
1265+
.dragonfly => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.2"),
1266+
.linux => switch (self.getArch()) {
1267+
.i386,
1268+
.sparc,
1269+
.sparcel,
1270+
=> return mem.dupeZ(a, u8, "/lib/ld-linux.so.2"),
1271+
1272+
.aarch64 => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64.so.1"),
1273+
.aarch64_be => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64_be.so.1"),
1274+
.aarch64_32 => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64_32.so.1"),
1275+
1276+
.arm,
1277+
.armeb,
1278+
.thumb,
1279+
.thumbeb,
1280+
=> return mem.dupeZ(a, u8, switch (self.getFloatAbi()) {
1281+
.hard => "/lib/ld-linux-armhf.so.3",
1282+
else => "/lib/ld-linux.so.3",
1283+
}),
1284+
1285+
.mips,
1286+
.mipsel,
1287+
.mips64,
1288+
.mips64el,
1289+
=> return error.UnknownDynamicLinkerPath,
1290+
1291+
.powerpc => return mem.dupeZ(a, u8, "/lib/ld.so.1"),
1292+
.powerpc64, .powerpc64le => return mem.dupeZ(a, u8, "/lib64/ld64.so.2"),
1293+
.s390x => return mem.dupeZ(a, u8, "/lib64/ld64.so.1"),
1294+
.sparcv9 => return mem.dupeZ(a, u8, "/lib64/ld-linux.so.2"),
1295+
.x86_64 => return mem.dupeZ(a, u8, switch (self.getAbi()) {
1296+
.gnux32 => "/libx32/ld-linux-x32.so.2",
1297+
else => "/lib64/ld-linux-x86-64.so.2",
1298+
}),
1299+
1300+
.riscv32 => return mem.dupeZ(a, u8, "/lib/ld-linux-riscv32-ilp32.so.1"),
1301+
.riscv64 => return mem.dupeZ(a, u8, "/lib/ld-linux-riscv64-lp64.so.1"),
1302+
1303+
.arc,
1304+
.avr,
1305+
.bpfel,
1306+
.bpfeb,
1307+
.hexagon,
1308+
.msp430,
1309+
.r600,
1310+
.amdgcn,
1311+
.tce,
1312+
.tcele,
1313+
.xcore,
1314+
.nvptx,
1315+
.nvptx64,
1316+
.le32,
1317+
.le64,
1318+
.amdil,
1319+
.amdil64,
1320+
.hsail,
1321+
.hsail64,
1322+
.spir,
1323+
.spir64,
1324+
.kalimba,
1325+
.shave,
1326+
.lanai,
1327+
.wasm32,
1328+
.wasm64,
1329+
.renderscript32,
1330+
.renderscript64,
1331+
=> return error.UnknownDynamicLinkerPath,
1332+
},
1333+
else => return error.UnknownDynamicLinkerPath,
1334+
}
1335+
}
11991336
};
12001337

12011338
test "parseCpuFeatureSet" {

src-self-hosted/introspect.zig

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ const fs = std.fs;
66

77
const warn = std.debug.warn;
88

9+
pub fn detectDynamicLinker(allocator: *mem.Allocator, target: std.Target) ![:0]u8 {
10+
if (target == .Native) {
11+
return @import("libc_installation.zig").detectNativeDynamicLinker(allocator);
12+
} else {
13+
return target.getStandardDynamicLinkerPath(allocator);
14+
}
15+
}
16+
917
/// Caller must free result
1018
pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![]u8 {
1119
const test_zig_dir = try fs.path.join(allocator, &[_][]const u8{ test_path, "lib", "zig" });

src-self-hosted/libc_installation.zig

+38-1
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ pub const LibCInstallation = struct {
492492
const default_cc_exe = if (is_windows) "cc.exe" else "cc";
493493

494494
/// caller owns returned memory
495-
pub fn ccPrintFileName(
495+
fn ccPrintFileName(
496496
allocator: *Allocator,
497497
o_file: []const u8,
498498
want_dirname: enum { full_path, only_dir },
@@ -535,6 +535,43 @@ pub fn ccPrintFileName(
535535
}
536536
}
537537

538+
/// Caller owns returned memory.
539+
pub fn detectNativeDynamicLinker(allocator: *Allocator) ![:0]u8 {
540+
const standard_ld_path = try std.Target.current.getStandardDynamicLinkerPath(allocator);
541+
var standard_ld_path_resource: ?[:0]u8 = standard_ld_path; // Set to null to avoid freeing it.
542+
defer if (standard_ld_path_resource) |s| allocator.free(s);
543+
544+
const standard_ld_basename = fs.path.basename(standard_ld_path);
545+
546+
{
547+
// Best case scenario: the current executable is dynamically linked, and we can iterate
548+
// over our own shared objects and find a dynamic linker.
549+
const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
550+
defer allocator.free(lib_paths);
551+
552+
for (lib_paths) |lib_path| {
553+
if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
554+
return std.mem.dupeZ(allocator, u8, lib_path);
555+
}
556+
}
557+
}
558+
559+
// If Zig is statically linked, such as via distributed binary static builds, the above
560+
// trick won't work. What are we left with? Try to run the system C compiler and get
561+
// it to tell us the dynamic linker path.
562+
return ccPrintFileName(allocator, standard_ld_basename, .full_path) catch |err| switch (err) {
563+
error.OutOfMemory => return error.OutOfMemory,
564+
error.LibCRuntimeNotFound,
565+
error.CCompilerExitCode,
566+
error.CCompilerCrashed,
567+
error.UnableToSpawnCCompiler,
568+
=> {
569+
standard_ld_path_resource = null; // Prevent freeing standard_ld_path.
570+
return standard_ld_path;
571+
},
572+
};
573+
}
574+
538575
const Search = struct {
539576
path: []const u8,
540577
version: []const u8,

0 commit comments

Comments
 (0)