Elf2: start implementing dynamic linking

This commit is contained in:
Jacob Young 2025-10-29 18:04:11 -04:00
parent 40901440a6
commit 0834e696f7
11 changed files with 625 additions and 226 deletions

View File

@ -596,10 +596,13 @@ pub fn appendZigProcessFlags(
"-target", try target.query.zigTriple(b.allocator),
"-mcpu", try target.query.serializeCpuAlloc(b.allocator),
});
if (target.query.dynamic_linker.get()) |dynamic_linker| {
try zig_args.append("--dynamic-linker");
try zig_args.append(dynamic_linker);
if (target.query.dynamic_linker) |dynamic_linker| {
if (dynamic_linker.get()) |dynamic_linker_path| {
try zig_args.append("--dynamic-linker");
try zig_args.append(dynamic_linker_path);
} else {
try zig_args.append("--no-dynamic-linker");
}
}
}
}

View File

@ -46,8 +46,9 @@ android_api_level: ?u32 = null,
abi: ?Target.Abi = null,
/// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path
/// based on the `os_tag`.
dynamic_linker: Target.DynamicLinker = .none,
/// based on the `os_tag`. When `dynamic_linker` is a non-`null` empty string, no dynamic
/// linker is used regardless of `os_tag`.
dynamic_linker: ?Target.DynamicLinker = null,
/// `null` means default for the cpu/arch/os combo.
ofmt: ?Target.ObjectFormat = null,
@ -213,7 +214,7 @@ pub fn parse(args: ParseOptions) !Query {
const diags = args.diagnostics orelse &dummy_diags;
var result: Query = .{
.dynamic_linker = Target.DynamicLinker.init(args.dynamic_linker),
.dynamic_linker = if (args.dynamic_linker) |dynamic_linker| .init(dynamic_linker) else null,
};
var it = mem.splitScalar(u8, args.arch_os_abi, '-');
@ -381,7 +382,7 @@ pub fn isNativeCpu(self: Query) bool {
pub fn isNativeOs(self: Query) bool {
return self.os_tag == null and self.os_version_min == null and self.os_version_max == null and
self.dynamic_linker.get() == null and self.glibc_version == null and self.android_api_level == null;
self.dynamic_linker == null and self.glibc_version == null and self.android_api_level == null;
}
pub fn isNativeAbi(self: Query) bool {
@ -599,7 +600,7 @@ pub fn eql(a: Query, b: Query) bool {
if (!versionEqualOpt(a.glibc_version, b.glibc_version)) return false;
if (a.android_api_level != b.android_api_level) return false;
if (a.abi != b.abi) return false;
if (!a.dynamic_linker.eql(b.dynamic_linker)) return false;
if (!dynamicLinkerEqualOpt(a.dynamic_linker, b.dynamic_linker)) return false;
if (a.ofmt != b.ofmt) return false;
return true;
@ -611,6 +612,12 @@ fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool {
return SemanticVersion.order(a.?, b.?) == .eq;
}
fn dynamicLinkerEqualOpt(a: ?Target.DynamicLinker, b: ?Target.DynamicLinker) bool {
if (a == null and b == null) return true;
if (a == null or b == null) return false;
return a.?.eql(b.?);
}
test parse {
const io = std.testing.io;

View File

@ -7013,7 +7013,8 @@ pub const RTLD = switch (native_os) {
LAZY: bool = false,
NOW: bool = false,
NOLOAD: bool = false,
_3: u5 = 0,
DEEPBIND: bool = false,
_4: u4 = 0,
GLOBAL: bool = false,
_9: u3 = 0,
NODELETE: bool = false,

View File

@ -562,7 +562,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
// Apply the initial relocations as early as possible in the startup process. We cannot
// make calls yet on some architectures (e.g. MIPS) *because* they haven't been applied yet,
// so this must be fully inlined.
if (builtin.position_independent_executable) {
if (builtin.link_mode == .static and builtin.position_independent_executable) {
@call(.always_inline, std.pie.relocate, .{phdrs});
}

View File

@ -585,10 +585,10 @@ fn abiAndDynamicLinkerFromFile(
.os = os,
.abi = query.abi orelse Target.Abi.default(cpu.arch, os.tag),
.ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
.dynamic_linker = query.dynamic_linker,
.dynamic_linker = query.dynamic_linker orelse .none,
};
var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
const look_for_ld = query.dynamic_linker.get() == null;
const look_for_ld = query.dynamic_linker == null;
var got_dyn_section: bool = false;
{
@ -938,7 +938,7 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
const is_linux = builtin.target.os.tag == .linux;
const is_illumos = builtin.target.os.tag == .illumos;
const is_darwin = builtin.target.os.tag.isDarwin();
const have_all_info = query.dynamic_linker.get() != null and
const have_all_info = query.dynamic_linker != null and
query.abi != null and (!is_linux or query.abi.?.isGnu());
const os_is_non_native = query.os_tag != null;
// The illumos environment is always the same.
@ -1126,10 +1126,7 @@ fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Quer
.os = os,
.abi = abi,
.ofmt = query.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch),
.dynamic_linker = if (query.dynamic_linker.get() == null)
Target.DynamicLinker.standard(cpu, os, abi)
else
query.dynamic_linker,
.dynamic_linker = query.dynamic_linker orelse .standard(cpu, os, abi),
};
}

View File

@ -123,6 +123,7 @@ pub const ResolveError = error{
WasiExecModelRequiresWasi,
SharedMemoryIsWasmOnly,
ObjectFilesCannotShareMemory,
ObjectFilesCannotSpecifyDynamicLinker,
SharedMemoryRequiresAtomicsAndBulkMemory,
ThreadsRequireSharedMemory,
EmittingLlvmModuleRequiresLlvmBackend,
@ -131,6 +132,7 @@ pub const ResolveError = error{
EmittingBinaryRequiresLlvmLibrary,
LldIncompatibleObjectFormat,
LldCannotIncrementallyLink,
LldCannotSpecifyDynamicLinkerForSharedLibraries,
LtoRequiresLld,
SanitizeThreadRequiresLibCpp,
LibCRequiresLibUnwind,
@ -142,6 +144,7 @@ pub const ResolveError = error{
TargetCannotStaticLinkExecutables,
LibCRequiresDynamicLinking,
SharedLibrariesRequireDynamicLinking,
DynamicLinkingWithLldRequiresSharedLibraries,
ExportMemoryAndDynamicIncompatible,
DynamicLibraryPrecludesPie,
TargetRequiresPie,
@ -274,16 +277,11 @@ pub fn resolve(options: Options) ResolveError!Config {
if (options.link_mode == .static) return error.LibCRequiresDynamicLinking;
break :b .dynamic;
}
// When creating a executable that links to system libraries, we
// require dynamic linking, but we must not link static libraries
// or object files dynamically!
if (options.any_dyn_libs and options.output_mode == .Exe) {
if (options.link_mode == .static) return error.SharedLibrariesRequireDynamicLinking;
break :b .dynamic;
}
if (options.link_mode) |link_mode| break :b link_mode;
if (options.any_dyn_libs) break :b .dynamic;
if (explicitly_exe_or_dyn_lib and link_libc) {
// When using the native glibc/musl ABI, dynamic linking is usually what people want.
if (options.resolved_target.is_native_abi and (target.isGnuLibC() or target.isMuslLibC())) {
@ -425,6 +423,25 @@ pub fn resolve(options: Options) ResolveError!Config {
break :b use_llvm;
};
switch (options.output_mode) {
.Exe => if (options.any_dyn_libs) {
// When creating a executable that links to system libraries, we
// require dynamic linking, but we must not link static libraries
// or object files dynamically!
if (link_mode == .static) return error.SharedLibrariesRequireDynamicLinking;
} else if (use_lld and !link_libc and !link_libcpp and !link_libunwind) {
// Lld does not support creating dynamic executables when not
// linking to any shared libraries.
if (link_mode == .dynamic) return error.DynamicLinkingWithLldRequiresSharedLibraries;
},
.Lib => if (use_lld and options.resolved_target.is_explicit_dynamic_linker) {
return error.LldCannotSpecifyDynamicLinkerForSharedLibraries;
},
.Obj => if (options.resolved_target.is_explicit_dynamic_linker) {
return error.ObjectFilesCannotSpecifyDynamicLinker;
},
}
const use_new_linker = b: {
if (use_lld) {
if (options.use_new_linker == true) return error.NewLinkerIncompatibleWithLld;

View File

@ -182,6 +182,10 @@ pub fn emitMir(emit: *Emit) Error!void {
try elf_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)
else if (emit.bin_file.cast(.elf2)) |elf| @intFromEnum(try elf.globalSymbol(.{
.name = extern_func.toSlice(&emit.lower.mir).?,
.lib_name = switch (comp.compiler_rt_strat) {
.none, .lib, .obj, .zcu => null,
.dyn_lib => "compiler_rt",
},
.type = .FUNC,
})) else if (emit.bin_file.cast(.macho)) |macho_file|
try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)
@ -320,10 +324,12 @@ pub fn emitMir(emit: *Emit) Error!void {
}, emit.lower.target), &.{.{
.op_index = 0,
.target = .{
.index = if (emit.bin_file.cast(.elf)) |elf_file|
try elf_file.getGlobalSymbol("__tls_get_addr", null)
else if (emit.bin_file.cast(.elf2)) |elf| @intFromEnum(try elf.globalSymbol(.{
.index = if (emit.bin_file.cast(.elf)) |elf_file| try elf_file.getGlobalSymbol(
"__tls_get_addr",
if (comp.config.link_libc) "c" else null,
) else if (emit.bin_file.cast(.elf2)) |elf| @intFromEnum(try elf.globalSymbol(.{
.name = "__tls_get_addr",
.lib_name = if (comp.config.link_libc) "c" else null,
.type = .FUNC,
})) else unreachable,
.is_extern = true,

View File

@ -1882,17 +1882,13 @@ fn initSyntheticSections(self: *Elf) !void {
const comp = self.base.comp;
const target = self.getTarget();
const ptr_size = self.ptrWidthBytes();
const shared_objects = self.shared_objects.values();
const is_exe_or_dyn_lib = switch (comp.config.output_mode) {
.Exe => true,
.Lib => comp.config.link_mode == .dynamic,
.Obj => false,
};
const have_dynamic_linker = comp.config.link_mode == .dynamic and is_exe_or_dyn_lib and !target.dynamic_linker.eql(.none);
const needs_interp = have_dynamic_linker and
(comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker);
const have_dynamic_linker = comp.config.link_mode == .dynamic and is_exe_or_dyn_lib;
const needs_eh_frame = blk: {
if (self.zigObjectPtr()) |zo|
@ -2004,7 +2000,15 @@ fn initSyntheticSections(self: *Elf) !void {
});
}
if (needs_interp and self.section_indexes.interp == null) {
if (needs_interp: {
if (comp.config.link_mode == .static) break :needs_interp false;
if (target.dynamic_linker.get() == null) break :needs_interp false;
break :needs_interp switch (comp.config.output_mode) {
.Exe => true,
.Lib => comp.root_mod.resolved_target.is_explicit_dynamic_linker,
.Obj => false,
};
} and self.section_indexes.interp == null) {
self.section_indexes.interp = try self.addSection(.{
.name = try self.insertShString(".interp"),
.type = elf.SHT_PROGBITS,
@ -2013,7 +2017,7 @@ fn initSyntheticSections(self: *Elf) !void {
});
}
if (self.isEffectivelyDynLib() or shared_objects.len > 0 or comp.config.pie) {
if (have_dynamic_linker or comp.config.pie or self.isEffectivelyDynLib()) {
if (self.section_indexes.dynstrtab == null) {
self.section_indexes.dynstrtab = try self.addSection(.{
.name = try self.insertShString(".dynstr"),

File diff suppressed because it is too large Load Diff

View File

@ -808,7 +808,6 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
const link_mode = comp.config.link_mode;
const is_dyn_lib = link_mode == .dynamic and is_lib;
const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe;
const have_dynamic_linker = link_mode == .dynamic and is_exe_or_dyn_lib;
const target = &comp.root_mod.resolved_target.result;
const compiler_rt_path: ?Cache.Path = blk: {
if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
@ -1070,12 +1069,12 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
}
}
if (have_dynamic_linker and
(comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker))
{
if (output_mode == .Exe and link_mode == .dynamic) {
if (target.dynamic_linker.get()) |dynamic_linker| {
try argv.append("-dynamic-linker");
try argv.append("--dynamic-linker");
try argv.append(dynamic_linker);
} else {
try argv.append("--no-dynamic-linker");
}
}

View File

@ -558,6 +558,7 @@ const usage_build_generic =
\\ --enable-new-dtags Use the new behavior for dynamic tags (RUNPATH)
\\ --disable-new-dtags Use the old behavior for dynamic tags (RPATH)
\\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so)
\\ --no-dynamic-linker Do not set any dynamic interpreter path
\\ --sysroot [path] Set the system root directory (usually /)
\\ --version [ver] Dynamic library semver
\\ -fentry Enable entry point with default symbol name
@ -1301,6 +1302,8 @@ fn buildOutputType(
mod_opts.optimize_mode = parseOptimizeMode(rest);
} else if (mem.eql(u8, arg, "--dynamic-linker")) {
create_module.dynamic_linker = args_iter.nextOrFatal();
} else if (mem.eql(u8, arg, "--no-dynamic-linker")) {
create_module.dynamic_linker = "";
} else if (mem.eql(u8, arg, "--sysroot")) {
const next_arg = args_iter.nextOrFatal();
create_module.sysroot = next_arg;
@ -2418,6 +2421,11 @@ fn buildOutputType(
mem.eql(u8, arg, "-dynamic-linker"))
{
create_module.dynamic_linker = linker_args_it.nextOrFatal();
} else if (mem.eql(u8, arg, "-I") or
mem.eql(u8, arg, "--no-dynamic-linker") or
mem.eql(u8, arg, "-no-dynamic-linker"))
{
create_module.dynamic_linker = "";
} else if (mem.eql(u8, arg, "-E") or
mem.eql(u8, arg, "--export-dynamic") or
mem.eql(u8, arg, "-export-dynamic"))
@ -3191,13 +3199,14 @@ fn buildOutputType(
const resolved_soname: ?[]const u8 = switch (soname) {
.yes => |explicit| explicit,
.no => null,
.yes_default_value => switch (target.ofmt) {
.elf => if (have_version)
.yes_default_value => if (create_module.resolved_options.output_mode == .Lib and
create_module.resolved_options.link_mode == .dynamic and target.ofmt == .elf)
if (have_version)
try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ root_name, version.major })
else
try std.fmt.allocPrint(arena, "lib{s}.so", .{root_name}),
else => null,
},
try std.fmt.allocPrint(arena, "lib{s}.so", .{root_name})
else
null,
};
const emit_bin_resolved: Compilation.CreateOptions.Emit = switch (emit_bin) {
@ -3646,7 +3655,11 @@ fn buildOutputType(
try test_exec_args.append(arena, try std.fmt.allocPrint(arena, "-mcpu={s}", .{mcpu}));
}
if (create_module.dynamic_linker) |dl| {
try test_exec_args.appendSlice(arena, &.{ "--dynamic-linker", dl });
if (dl.len > 0) {
try test_exec_args.appendSlice(arena, &.{ "--dynamic-linker", dl });
} else {
try test_exec_args.append(arena, "--no-dynamic-linker");
}
}
try test_exec_args.append(arena, null); // placeholder for the path of the emitted C source file
}
@ -3793,7 +3806,7 @@ fn createModule(
.result = target,
.is_native_os = target_query.isNativeOs(),
.is_native_abi = target_query.isNativeAbi(),
.is_explicit_dynamic_linker = !target_query.dynamic_linker.eql(.none),
.is_explicit_dynamic_linker = target_query.dynamic_linker != null,
};
};
@ -3965,6 +3978,7 @@ fn createModule(
error.WasiExecModelRequiresWasi => fatal("only WASI OS targets support execution model", .{}),
error.SharedMemoryIsWasmOnly => fatal("only WebAssembly CPU targets support shared memory", .{}),
error.ObjectFilesCannotShareMemory => fatal("object files cannot share memory", .{}),
error.ObjectFilesCannotSpecifyDynamicLinker => fatal("object files cannot specify --dynamic-linker", .{}),
error.SharedMemoryRequiresAtomicsAndBulkMemory => fatal("shared memory requires atomics and bulk_memory CPU features", .{}),
error.ThreadsRequireSharedMemory => fatal("threads require shared memory", .{}),
error.EmittingLlvmModuleRequiresLlvmBackend => fatal("emitting an LLVM module requires using the LLVM backend", .{}),
@ -3973,6 +3987,7 @@ fn createModule(
error.EmittingBinaryRequiresLlvmLibrary => fatal("producing machine code via LLVM requires using the LLVM library", .{}),
error.LldIncompatibleObjectFormat => fatal("using LLD to link {s} files is unsupported", .{@tagName(target.ofmt)}),
error.LldCannotIncrementallyLink => fatal("self-hosted backends do not support linking with LLD", .{}),
error.LldCannotSpecifyDynamicLinkerForSharedLibraries => fatal("LLD does not support --dynamic-linker on shared libraries", .{}),
error.LtoRequiresLld => fatal("LTO requires using LLD", .{}),
error.SanitizeThreadRequiresLibCpp => fatal("thread sanitization is (for now) implemented in C++, so it requires linking libc++", .{}),
error.LibCRequiresLibUnwind => fatal("libc of the specified target requires linking libunwind", .{}),
@ -3984,6 +3999,7 @@ fn createModule(
error.TargetCannotStaticLinkExecutables => fatal("static linking of executables unavailable on the specified target", .{}),
error.LibCRequiresDynamicLinking => fatal("libc of the specified target requires dynamic linking", .{}),
error.SharedLibrariesRequireDynamicLinking => fatal("using shared libraries requires dynamic linking", .{}),
error.DynamicLinkingWithLldRequiresSharedLibraries => fatal("dynamic linking with lld requires at least one shared library", .{}),
error.ExportMemoryAndDynamicIncompatible => fatal("exporting memory is incompatible with dynamic linking", .{}),
error.DynamicLibraryPrecludesPie => fatal("dynamic libraries cannot be position independent executables", .{}),
error.TargetRequiresPie => fatal("the specified target requires position independent executables", .{}),