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(...@@ -1891,30 +1891,69 @@ pub fn resolveInputs(
1891 syslib: while (unresolved_inputs.pop()) |unresolved_input| {1891 syslib: while (unresolved_inputs.pop()) |unresolved_input| {
1892 const name_query: UnresolvedInput.NameQuery = switch (unresolved_input) {1892 const name_query: UnresolvedInput.NameQuery = switch (unresolved_input) {
1893 .name_query => |nq| nq,1893 .name_query => |nq| nq,
1894 .ambiguous_name => |an| an: {1894 .ambiguous_name => |an| {
1895 const lib_name, const link_mode = stripLibPrefixAndSuffix(an.name, target) orelse {1895 // First check the path relative to the current working directory.
1896 try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, .{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 .{
1897 .path = Path.initCwd(an.name),1906 .path = Path.initCwd(an.name),
1898 .query = an.query,1907 .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,
1913 },1908 },
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;
1915 },1940 },
1916 .path_query => |pq| {1941 .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 }
1918 continue;1957 continue;
1919 },1958 },
1920 .dso_exact => |dso_exact| {1959 .dso_exact => |dso_exact| {
...@@ -2176,10 +2215,10 @@ fn resolvePathInput(...@@ -2176,10 +2215,10 @@ fn resolvePathInput(
2176 target: std.Target,2215 target: std.Target,
2177 pq: UnresolvedInput.PathQuery,2216 pq: UnresolvedInput.PathQuery,
2178 color: std.zig.Color,2217 color: std.zig.Color,
2179) Allocator.Error!void {2218) Allocator.Error!?ResolveLibInputResult {
2180 switch (switch (Compilation.classifyFileExt(pq.path.sub_path)) {2219 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),2220 .static_library => return 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),2221 .shared_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
2183 .object => {2222 .object => {
2184 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|2223 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
2185 fatal("failed to open object {}: {s}", .{ pq.path, @errorName(err) });2224 fatal("failed to open object {}: {s}", .{ pq.path, @errorName(err) });
...@@ -2190,7 +2229,7 @@ fn resolvePathInput(...@@ -2190,7 +2229,7 @@ fn resolvePathInput(
2190 .must_link = pq.query.must_link,2229 .must_link = pq.query.must_link,
2191 .hidden = pq.query.hidden,2230 .hidden = pq.query.hidden,
2192 } });2231 } });
2193 return;2232 return null;
2194 },2233 },
2195 .res => {2234 .res => {
2196 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|2235 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
...@@ -2200,12 +2239,9 @@ fn resolvePathInput(...@@ -2200,12 +2239,9 @@ fn resolvePathInput(
2200 .path = pq.path,2239 .path = pq.path,
2201 .file = file,2240 .file = file,
2202 } });2241 } });
2203 return;2242 return null;
2204 },2243 },
2205 else => fatal("{}: unrecognized file extension", .{pq.path}),2244 else => fatal("{}: unrecognized file extension", .{pq.path}),
2206 }) {
2207 .ok => {},
2208 .no_match => fatal("{}: file not found", .{pq.path}),
2209 }2245 }
2210}2246}
22112247
...@@ -2226,9 +2262,11 @@ fn resolvePathInputLib(...@@ -2226,9 +2262,11 @@ fn resolvePathInputLib(
2226 try resolved_inputs.ensureUnusedCapacity(gpa, 1);2262 try resolved_inputs.ensureUnusedCapacity(gpa, 1);
22272263
2228 const test_path: Path = pq.path;2264 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"
2230 // that contain references to other libraries.2266 // 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 {
2232 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {2270 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
2233 error.FileNotFound => return .no_match,2271 error.FileNotFound => return .no_match,
2234 else => |e| fatal("unable to search for {s} library '{'}': {s}", .{2272 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...@@ -2354,21 +2392,6 @@ pub fn openDsoInput(diags: *Diags, path: Path, needed: bool, weak: bool, reexpor
2354 } };2392 } };
2355}2393}
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
2372/// Returns true if and only if there is at least one input of type object,2395/// Returns true if and only if there is at least one input of type object,
2373/// archive, or Windows resource file.2396/// archive, or Windows resource file.
2374pub fn anyObjectInputs(inputs: []const Input) bool {2397pub fn anyObjectInputs(inputs: []const Input) bool {
test/link/elf.zig+19-8
...@@ -2123,24 +2123,35 @@ fn testLinkOrder(b: *Build, opts: Options) *Step {...@@ -2123,24 +2123,35 @@ fn testLinkOrder(b: *Build, opts: Options) *Step {
2123fn testLdScript(b: *Build, opts: Options) *Step {2123fn testLdScript(b: *Build, opts: Options) *Step {
2124 const test_step = addTestStep(b, "ld-script", opts);2124 const test_step = addTestStep(b, "ld-script", opts);
21252125
2126 const dso = addSharedLibrary(b, opts, .{ .name = "bar" });2126 const bar = addSharedLibrary(b, opts, .{ .name = "bar" });
2127 addCSourceBytes(dso, "int foo() { return 42; }", &.{});2127 addCSourceBytes(bar, "int bar() { return 42; }", &.{});
2128
2129 const baz = addSharedLibrary(b, opts, .{ .name = "baz" });
2130 addCSourceBytes(baz, "int baz() { return 42; }", &.{});
21282131
2129 const scripts = WriteFile.create(b);2132 const scripts = WriteFile.create(b);
2130 _ = scripts.add("liba.so", "INPUT(libfoo.so)");2133 _ = scripts.add("liba.so", "INPUT(libfoo.so libfoo2.so.1)");
2131 _ = scripts.add("libfoo.so", "GROUP(AS_NEEDED(-lbar))");2134 _ = 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
2133 const exe = addExecutable(b, opts, .{ .name = "main" });2140 const exe = addExecutable(b, opts, .{ .name = "main" });
2134 addCSourceBytes(exe,2141 addCSourceBytes(exe,
2135 \\int foo();2142 \\int bar();
2143 \\int baz();
2136 \\int main() {2144 \\int main() {
2137 \\ return foo() - 42;2145 \\ return bar() - baz();
2138 \\}2146 \\}
2139 , &.{});2147 , &.{});
2140 exe.linkSystemLibrary2("a", .{});2148 exe.linkSystemLibrary2("a", .{});
2141 exe.addLibraryPath(scripts.getDirectory());2149 exe.addLibraryPath(scripts.getDirectory());
2142 exe.addLibraryPath(dso.getEmittedBinDirectory());2150 exe.addLibraryPath(scripts2.getDirectory());
2143 exe.addRPath(dso.getEmittedBinDirectory());2151 exe.addLibraryPath(bar.getEmittedBinDirectory());
2152 exe.addLibraryPath(baz.getEmittedBinDirectory());
2153 exe.addRPath(bar.getEmittedBinDirectory());
2154 exe.addRPath(baz.getEmittedBinDirectory());
2144 exe.linkLibC();2155 exe.linkLibC();
2145 exe.allow_so_scripts = true;2156 exe.allow_so_scripts = true;
21462157
...@@ -2167,7 +2178,7 @@ fn testLdScriptPathError(b: *Build, opts: Options) *Step {...@@ -2167,7 +2178,7 @@ fn testLdScriptPathError(b: *Build, opts: Options) *Step {
2167 // TODO: A future enhancement could make this error message also mention2178 // TODO: A future enhancement could make this error message also mention
2168 // the file that references the missing library.2179 // the file that references the missing library.
2169 expectLinkErrors(exe, test_step, .{2180 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",
2171 });2182 });
21722183
2173 return test_step;2184 return test_step;