authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2025-01-29 14:11:23+01:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2025-02-10 23:19:48+01:00
log819716b59f941253ae98405bb4b30049aea5f8de
tree0e501e960750e089393647b4bf74a518b18cda61
parent34644511bb6d83aa6575641ef79e5eefb495e3e1
signaturelock-open Commit is signed but in an unrecognized format.

link: fix ambiguous names in linker scripts

Currently zig fails to build while linking the system LLVM/C++ libraries on my Chimera Linux system due to the fact that libc++.so is a linker script with the following contents: INPUT(libc++.so.1 -lc++abi -lunwind) Prior to this commit, zig would try to convert "ambiguous names" in linker scripts such as libc++.so.1 in this example into -lfoo style flags. This fails in this case due to the so version number as zig checks for exactly the .so suffix. Furthermore, I do not think that this conversion is semantically correct since converting libfoo.so to -lfoo could theoretically end up resulting in libfoo.a getting linked which seems wrong when a different file is specified in the linker script. With this patch, this attempted conversion is removed. Instead, zig always first checks if the exact file/path in the linker script exists relative to the current working directory. If the file is classified as a library (including versioned shared objects such as libfoo.so.1), zig then falls back to checking if the exact file/path in the linker script exists relative to each directory in the library search path, selecting the first match or erroring out if none is found. This behavior fixes the regression that prevents building zig while linking the system LLVM/C++ libraries on Chimera Linux.

2 files changed, 87 insertions(+), 53 deletions(-)

src/link.zig+68-45
......@@ -1891,30 +1891,69 @@ pub fn resolveInputs(
18911891 syslib: while (unresolved_inputs.pop()) |unresolved_input| {
18921892 const name_query: UnresolvedInput.NameQuery = switch (unresolved_input) {
18931893 .name_query => |nq| nq,
1894 .ambiguous_name => |an| an: {
1895 const lib_name, const link_mode = stripLibPrefixAndSuffix(an.name, target) orelse {
1896 try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, .{
1894 .ambiguous_name => |an| {
1895 // First check the path relative to the current working directory.
1896 // If the file is a library and is not found there, check the library search paths as well.
1897 // This is consistent with the behavior of GNU ld.
1898 if (try resolvePathInput(
1899 gpa,
1900 arena,
1901 unresolved_inputs,
1902 resolved_inputs,
1903 &ld_script_bytes,
1904 target,
1905 .{
18971906 .path = Path.initCwd(an.name),
18981907 .query = an.query,
1899 }, color);
1900 continue;
1901 };
1902 break :an .{
1903 .name = lib_name,
1904 .query = .{
1905 .needed = an.query.needed,
1906 .weak = an.query.weak,
1907 .reexport = an.query.reexport,
1908 .must_link = an.query.must_link,
1909 .hidden = an.query.hidden,
1910 .allow_so_scripts = an.query.allow_so_scripts,
1911 .preferred_mode = link_mode,
1912 .search_strategy = .no_fallback,
19131908 },
1914 };
1909 color,
1910 )) |lib_result| {
1911 switch (lib_result) {
1912 .ok => continue :syslib,
1913 .no_match => {
1914 for (lib_directories) |lib_directory| {
1915 switch ((try resolvePathInput(
1916 gpa,
1917 arena,
1918 unresolved_inputs,
1919 resolved_inputs,
1920 &ld_script_bytes,
1921 target,
1922 .{
1923 .path = .{
1924 .root_dir = lib_directory,
1925 .sub_path = an.name,
1926 },
1927 .query = an.query,
1928 },
1929 color,
1930 )).?) {
1931 .ok => continue :syslib,
1932 .no_match => {},
1933 }
1934 }
1935 fatal("{s}: file listed in linker script not found", .{an.name});
1936 },
1937 }
1938 }
1939 continue;
19151940 },
19161941 .path_query => |pq| {
1917 try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, pq, color);
1942 if (try resolvePathInput(
1943 gpa,
1944 arena,
1945 unresolved_inputs,
1946 resolved_inputs,
1947 &ld_script_bytes,
1948 target,
1949 pq,
1950 color,
1951 )) |lib_result| {
1952 switch (lib_result) {
1953 .ok => {},
1954 .no_match => fatal("{}: file not found", .{pq.path}),
1955 }
1956 }
19181957 continue;
19191958 },
19201959 .dso_exact => |dso_exact| {
......@@ -2176,10 +2215,10 @@ fn resolvePathInput(
21762215 target: std.Target,
21772216 pq: UnresolvedInput.PathQuery,
21782217 color: std.zig.Color,
2179) Allocator.Error!void {
2180 switch (switch (Compilation.classifyFileExt(pq.path.sub_path)) {
2181 .static_library => try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color),
2182 .shared_library => try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
2218) Allocator.Error!?ResolveLibInputResult {
2219 switch (Compilation.classifyFileExt(pq.path.sub_path)) {
2220 .static_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color),
2221 .shared_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
21832222 .object => {
21842223 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
21852224 fatal("failed to open object {}: {s}", .{ pq.path, @errorName(err) });
......@@ -2190,7 +2229,7 @@ fn resolvePathInput(
21902229 .must_link = pq.query.must_link,
21912230 .hidden = pq.query.hidden,
21922231 } });
2193 return;
2232 return null;
21942233 },
21952234 .res => {
21962235 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
......@@ -2200,12 +2239,9 @@ fn resolvePathInput(
22002239 .path = pq.path,
22012240 .file = file,
22022241 } });
2203 return;
2242 return null;
22042243 },
22052244 else => fatal("{}: unrecognized file extension", .{pq.path}),
2206 }) {
2207 .ok => {},
2208 .no_match => fatal("{}: file not found", .{pq.path}),
22092245 }
22102246}
22112247
......@@ -2226,9 +2262,11 @@ fn resolvePathInputLib(
22262262 try resolved_inputs.ensureUnusedCapacity(gpa, 1);
22272263
22282264 const test_path: Path = pq.path;
2229 // In the case of .so files, they might actually be "linker scripts"
2265 // In the case of shared libraries, they might actually be "linker scripts"
22302266 // that contain references to other libraries.
2231 if (pq.query.allow_so_scripts and target.ofmt == .elf and mem.endsWith(u8, test_path.sub_path, ".so")) {
2267 if (pq.query.allow_so_scripts and target.ofmt == .elf and
2268 Compilation.classifyFileExt(test_path.sub_path) == .shared_library)
2269 {
22322270 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
22332271 error.FileNotFound => return .no_match,
22342272 else => |e| fatal("unable to search for {s} library '{'}': {s}", .{
......@@ -2354,21 +2392,6 @@ pub fn openDsoInput(diags: *Diags, path: Path, needed: bool, weak: bool, reexpor
23542392 } };
23552393}
23562394
2357fn stripLibPrefixAndSuffix(path: []const u8, target: std.Target) ?struct { []const u8, std.builtin.LinkMode } {
2358 const prefix = target.libPrefix();
2359 const static_suffix = target.staticLibSuffix();
2360 const dynamic_suffix = target.dynamicLibSuffix();
2361 const basename = fs.path.basename(path);
2362 const unlibbed = if (mem.startsWith(u8, basename, prefix)) basename[prefix.len..] else return null;
2363 if (mem.endsWith(u8, unlibbed, static_suffix)) return .{
2364 unlibbed[0 .. unlibbed.len - static_suffix.len], .static,
2365 };
2366 if (mem.endsWith(u8, unlibbed, dynamic_suffix)) return .{
2367 unlibbed[0 .. unlibbed.len - dynamic_suffix.len], .dynamic,
2368 };
2369 return null;
2370}
2371
23722395/// Returns true if and only if there is at least one input of type object,
23732396/// archive, or Windows resource file.
23742397pub fn anyObjectInputs(inputs: []const Input) bool {
test/link/elf.zig+19-8
......@@ -2123,24 +2123,35 @@ fn testLinkOrder(b: *Build, opts: Options) *Step {
21232123fn testLdScript(b: *Build, opts: Options) *Step {
21242124 const test_step = addTestStep(b, "ld-script", opts);
21252125
2126 const dso = addSharedLibrary(b, opts, .{ .name = "bar" });
2127 addCSourceBytes(dso, "int foo() { return 42; }", &.{});
2126 const bar = addSharedLibrary(b, opts, .{ .name = "bar" });
2127 addCSourceBytes(bar, "int bar() { return 42; }", &.{});
2128
2129 const baz = addSharedLibrary(b, opts, .{ .name = "baz" });
2130 addCSourceBytes(baz, "int baz() { return 42; }", &.{});
21282131
21292132 const scripts = WriteFile.create(b);
2130 _ = scripts.add("liba.so", "INPUT(libfoo.so)");
2133 _ = scripts.add("liba.so", "INPUT(libfoo.so libfoo2.so.1)");
21312134 _ = scripts.add("libfoo.so", "GROUP(AS_NEEDED(-lbar))");
21322135
2136 // Check finding a versioned .so file that is elsewhere in the library search paths.
2137 const scripts2 = WriteFile.create(b);
2138 _ = scripts2.add("libfoo2.so.1", "GROUP(AS_NEEDED(-lbaz))");
2139
21332140 const exe = addExecutable(b, opts, .{ .name = "main" });
21342141 addCSourceBytes(exe,
2135 \\int foo();
2142 \\int bar();
2143 \\int baz();
21362144 \\int main() {
2137 \\ return foo() - 42;
2145 \\ return bar() - baz();
21382146 \\}
21392147 , &.{});
21402148 exe.linkSystemLibrary2("a", .{});
21412149 exe.addLibraryPath(scripts.getDirectory());
2142 exe.addLibraryPath(dso.getEmittedBinDirectory());
2143 exe.addRPath(dso.getEmittedBinDirectory());
2150 exe.addLibraryPath(scripts2.getDirectory());
2151 exe.addLibraryPath(bar.getEmittedBinDirectory());
2152 exe.addLibraryPath(baz.getEmittedBinDirectory());
2153 exe.addRPath(bar.getEmittedBinDirectory());
2154 exe.addRPath(baz.getEmittedBinDirectory());
21442155 exe.linkLibC();
21452156 exe.allow_so_scripts = true;
21462157
......@@ -2167,7 +2178,7 @@ fn testLdScriptPathError(b: *Build, opts: Options) *Step {
21672178 // TODO: A future enhancement could make this error message also mention
21682179 // the file that references the missing library.
21692180 expectLinkErrors(exe, test_step, .{
2170 .stderr_contains = "error: unable to find dynamic system library 'foo' using strategy 'no_fallback'. searched paths:",
2181 .stderr_contains = "error: libfoo.so: file listed in linker script not found",
21712182 });
21722183
21732184 return test_step;