authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-22 18:18:24-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-02-22 18:18:24-05:00
log61b69a418db2f525c4be4e19029487f61fb3234e
tree8612f2d44ceac2de68f0493476e2bbbfb0c112cb
parent6ef2384c07ef6817f134ffc61e34bcea6e87df92
parent0499c731eabd2c88928ef740b488706c51a8a6a7
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #22659 from ifreund/linker-script-fix

link: fix ambiguous names in linker scripts

2 files changed, 188 insertions(+), 153 deletions(-)

src/link.zig+169-145
......@@ -1891,144 +1891,184 @@ pub fn resolveInputs(
18911891 mem.reverse(UnresolvedInput, unresolved_inputs.items);
18921892
18931893 syslib: while (unresolved_inputs.pop()) |unresolved_input| {
1894 const name_query: UnresolvedInput.NameQuery = switch (unresolved_input) {
1895 .name_query => |nq| nq,
1896 .ambiguous_name => |an| an: {
1897 const lib_name, const link_mode = stripLibPrefixAndSuffix(an.name, target) orelse {
1898 try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, .{
1899 .path = Path.initCwd(an.name),
1900 .query = an.query,
1901 }, color);
1902 continue;
1903 };
1904 break :an .{
1905 .name = lib_name,
1906 .query = .{
1907 .needed = an.query.needed,
1908 .weak = an.query.weak,
1909 .reexport = an.query.reexport,
1910 .must_link = an.query.must_link,
1911 .hidden = an.query.hidden,
1912 .allow_so_scripts = an.query.allow_so_scripts,
1913 .preferred_mode = link_mode,
1914 .search_strategy = .no_fallback,
1894 switch (unresolved_input) {
1895 .name_query => |name_query| {
1896 const query = name_query.query;
1897
1898 // Checked in the first pass above while looking for libc libraries.
1899 assert(!fs.path.isAbsolute(name_query.name));
1900
1901 checked_paths.clearRetainingCapacity();
1902
1903 switch (query.search_strategy) {
1904 .mode_first, .no_fallback => {
1905 // check for preferred mode
1906 for (lib_directories) |lib_directory| switch (try resolveLibInput(
1907 gpa,
1908 arena,
1909 unresolved_inputs,
1910 resolved_inputs,
1911 &checked_paths,
1912 &ld_script_bytes,
1913 lib_directory,
1914 name_query,
1915 target,
1916 query.preferred_mode,
1917 color,
1918 )) {
1919 .ok => continue :syslib,
1920 .no_match => {},
1921 };
1922 // check for fallback mode
1923 if (query.search_strategy == .no_fallback) {
1924 try failed_libs.append(arena, .{
1925 .name = name_query.name,
1926 .strategy = query.search_strategy,
1927 .checked_paths = try arena.dupe(u8, checked_paths.items),
1928 .preferred_mode = query.preferred_mode,
1929 });
1930 continue :syslib;
1931 }
1932 for (lib_directories) |lib_directory| switch (try resolveLibInput(
1933 gpa,
1934 arena,
1935 unresolved_inputs,
1936 resolved_inputs,
1937 &checked_paths,
1938 &ld_script_bytes,
1939 lib_directory,
1940 name_query,
1941 target,
1942 query.fallbackMode(),
1943 color,
1944 )) {
1945 .ok => continue :syslib,
1946 .no_match => {},
1947 };
1948 try failed_libs.append(arena, .{
1949 .name = name_query.name,
1950 .strategy = query.search_strategy,
1951 .checked_paths = try arena.dupe(u8, checked_paths.items),
1952 .preferred_mode = query.preferred_mode,
1953 });
1954 continue :syslib;
19151955 },
1916 };
1917 },
1918 .path_query => |pq| {
1919 try resolvePathInput(gpa, arena, unresolved_inputs, resolved_inputs, &ld_script_bytes, target, pq, color);
1920 continue;
1921 },
1922 .dso_exact => |dso_exact| {
1923 try resolved_inputs.append(gpa, .{ .dso_exact = dso_exact });
1924 continue;
1925 },
1926 };
1927 const query = name_query.query;
1928
1929 // Checked in the first pass above while looking for libc libraries.
1930 assert(!fs.path.isAbsolute(name_query.name));
1931
1932 checked_paths.clearRetainingCapacity();
1956 .paths_first => {
1957 for (lib_directories) |lib_directory| {
1958 // check for preferred mode
1959 switch (try resolveLibInput(
1960 gpa,
1961 arena,
1962 unresolved_inputs,
1963 resolved_inputs,
1964 &checked_paths,
1965 &ld_script_bytes,
1966 lib_directory,
1967 name_query,
1968 target,
1969 query.preferred_mode,
1970 color,
1971 )) {
1972 .ok => continue :syslib,
1973 .no_match => {},
1974 }
19331975
1934 switch (query.search_strategy) {
1935 .mode_first, .no_fallback => {
1936 // check for preferred mode
1937 for (lib_directories) |lib_directory| switch (try resolveLibInput(
1976 // check for fallback mode
1977 switch (try resolveLibInput(
1978 gpa,
1979 arena,
1980 unresolved_inputs,
1981 resolved_inputs,
1982 &checked_paths,
1983 &ld_script_bytes,
1984 lib_directory,
1985 name_query,
1986 target,
1987 query.fallbackMode(),
1988 color,
1989 )) {
1990 .ok => continue :syslib,
1991 .no_match => {},
1992 }
1993 }
1994 try failed_libs.append(arena, .{
1995 .name = name_query.name,
1996 .strategy = query.search_strategy,
1997 .checked_paths = try arena.dupe(u8, checked_paths.items),
1998 .preferred_mode = query.preferred_mode,
1999 });
2000 continue :syslib;
2001 },
2002 }
2003 },
2004 .ambiguous_name => |an| {
2005 // First check the path relative to the current working directory.
2006 // If the file is a library and is not found there, check the library search paths as well.
2007 // This is consistent with the behavior of GNU ld.
2008 if (try resolvePathInput(
19382009 gpa,
19392010 arena,
19402011 unresolved_inputs,
19412012 resolved_inputs,
1942 &checked_paths,
19432013 &ld_script_bytes,
1944 lib_directory,
1945 name_query,
19462014 target,
1947 query.preferred_mode,
2015 .{
2016 .path = Path.initCwd(an.name),
2017 .query = an.query,
2018 },
19482019 color,
1949 )) {
1950 .ok => continue :syslib,
1951 .no_match => {},
1952 };
1953 // check for fallback mode
1954 if (query.search_strategy == .no_fallback) {
1955 try failed_libs.append(arena, .{
1956 .name = name_query.name,
1957 .strategy = query.search_strategy,
1958 .checked_paths = try arena.dupe(u8, checked_paths.items),
1959 .preferred_mode = query.preferred_mode,
1960 });
1961 continue :syslib;
2020 )) |lib_result| {
2021 switch (lib_result) {
2022 .ok => continue :syslib,
2023 .no_match => {
2024 for (lib_directories) |lib_directory| {
2025 switch ((try resolvePathInput(
2026 gpa,
2027 arena,
2028 unresolved_inputs,
2029 resolved_inputs,
2030 &ld_script_bytes,
2031 target,
2032 .{
2033 .path = .{
2034 .root_dir = lib_directory,
2035 .sub_path = an.name,
2036 },
2037 .query = an.query,
2038 },
2039 color,
2040 )).?) {
2041 .ok => continue :syslib,
2042 .no_match => {},
2043 }
2044 }
2045 fatal("{s}: file listed in linker script not found", .{an.name});
2046 },
2047 }
19622048 }
1963 for (lib_directories) |lib_directory| switch (try resolveLibInput(
2049 continue;
2050 },
2051 .path_query => |pq| {
2052 if (try resolvePathInput(
19642053 gpa,
19652054 arena,
19662055 unresolved_inputs,
19672056 resolved_inputs,
1968 &checked_paths,
19692057 &ld_script_bytes,
1970 lib_directory,
1971 name_query,
19722058 target,
1973 query.fallbackMode(),
2059 pq,
19742060 color,
1975 )) {
1976 .ok => continue :syslib,
1977 .no_match => {},
1978 };
1979 try failed_libs.append(arena, .{
1980 .name = name_query.name,
1981 .strategy = query.search_strategy,
1982 .checked_paths = try arena.dupe(u8, checked_paths.items),
1983 .preferred_mode = query.preferred_mode,
1984 });
1985 continue :syslib;
1986 },
1987 .paths_first => {
1988 for (lib_directories) |lib_directory| {
1989 // check for preferred mode
1990 switch (try resolveLibInput(
1991 gpa,
1992 arena,
1993 unresolved_inputs,
1994 resolved_inputs,
1995 &checked_paths,
1996 &ld_script_bytes,
1997 lib_directory,
1998 name_query,
1999 target,
2000 query.preferred_mode,
2001 color,
2002 )) {
2003 .ok => continue :syslib,
2004 .no_match => {},
2005 }
2006
2007 // check for fallback mode
2008 switch (try resolveLibInput(
2009 gpa,
2010 arena,
2011 unresolved_inputs,
2012 resolved_inputs,
2013 &checked_paths,
2014 &ld_script_bytes,
2015 lib_directory,
2016 name_query,
2017 target,
2018 query.fallbackMode(),
2019 color,
2020 )) {
2021 .ok => continue :syslib,
2022 .no_match => {},
2061 )) |lib_result| {
2062 switch (lib_result) {
2063 .ok => {},
2064 .no_match => fatal("{}: file not found", .{pq.path}),
20232065 }
20242066 }
2025 try failed_libs.append(arena, .{
2026 .name = name_query.name,
2027 .strategy = query.search_strategy,
2028 .checked_paths = try arena.dupe(u8, checked_paths.items),
2029 .preferred_mode = query.preferred_mode,
2030 });
2031 continue :syslib;
2067 continue;
2068 },
2069 .dso_exact => |dso_exact| {
2070 try resolved_inputs.append(gpa, .{ .dso_exact = dso_exact });
2071 continue;
20322072 },
20332073 }
20342074 @compileError("unreachable");
......@@ -2178,10 +2218,10 @@ fn resolvePathInput(
21782218 target: std.Target,
21792219 pq: UnresolvedInput.PathQuery,
21802220 color: std.zig.Color,
2181) Allocator.Error!void {
2182 switch (switch (Compilation.classifyFileExt(pq.path.sub_path)) {
2183 .static_library => try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color),
2184 .shared_library => try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
2221) Allocator.Error!?ResolveLibInputResult {
2222 switch (Compilation.classifyFileExt(pq.path.sub_path)) {
2223 .static_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color),
2224 .shared_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
21852225 .object => {
21862226 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
21872227 fatal("failed to open object {}: {s}", .{ pq.path, @errorName(err) });
......@@ -2192,7 +2232,7 @@ fn resolvePathInput(
21922232 .must_link = pq.query.must_link,
21932233 .hidden = pq.query.hidden,
21942234 } });
2195 return;
2235 return null;
21962236 },
21972237 .res => {
21982238 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
......@@ -2202,12 +2242,9 @@ fn resolvePathInput(
22022242 .path = pq.path,
22032243 .file = file,
22042244 } });
2205 return;
2245 return null;
22062246 },
22072247 else => fatal("{}: unrecognized file extension", .{pq.path}),
2208 }) {
2209 .ok => {},
2210 .no_match => fatal("{}: file not found", .{pq.path}),
22112248 }
22122249}
22132250
......@@ -2228,9 +2265,11 @@ fn resolvePathInputLib(
22282265 try resolved_inputs.ensureUnusedCapacity(gpa, 1);
22292266
22302267 const test_path: Path = pq.path;
2231 // In the case of .so files, they might actually be "linker scripts"
2268 // In the case of shared libraries, they might actually be "linker scripts"
22322269 // that contain references to other libraries.
2233 if (pq.query.allow_so_scripts and target.ofmt == .elf and mem.endsWith(u8, test_path.sub_path, ".so")) {
2270 if (pq.query.allow_so_scripts and target.ofmt == .elf and
2271 Compilation.classifyFileExt(test_path.sub_path) == .shared_library)
2272 {
22342273 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
22352274 error.FileNotFound => return .no_match,
22362275 else => |e| fatal("unable to search for {s} library '{'}': {s}", .{
......@@ -2356,21 +2395,6 @@ pub fn openDsoInput(diags: *Diags, path: Path, needed: bool, weak: bool, reexpor
23562395 } };
23572396}
23582397
2359fn stripLibPrefixAndSuffix(path: []const u8, target: std.Target) ?struct { []const u8, std.builtin.LinkMode } {
2360 const prefix = target.libPrefix();
2361 const static_suffix = target.staticLibSuffix();
2362 const dynamic_suffix = target.dynamicLibSuffix();
2363 const basename = fs.path.basename(path);
2364 const unlibbed = if (mem.startsWith(u8, basename, prefix)) basename[prefix.len..] else return null;
2365 if (mem.endsWith(u8, unlibbed, static_suffix)) return .{
2366 unlibbed[0 .. unlibbed.len - static_suffix.len], .static,
2367 };
2368 if (mem.endsWith(u8, unlibbed, dynamic_suffix)) return .{
2369 unlibbed[0 .. unlibbed.len - dynamic_suffix.len], .dynamic,
2370 };
2371 return null;
2372}
2373
23742398/// Returns true if and only if there is at least one input of type object,
23752399/// archive, or Windows resource file.
23762400pub 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;