╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ link.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
2266 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2267 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{ 2268 .path = test_path, 2269 .query = name_query.query, 2270 }, link_mode, color)) { 2271 .no_match => {}, 2272 .ok => return .ok, 2273 } 2274 } 2275 2276 // In the case of Darwin, the main check will be .dylib, so here we 2277 // additionally check for .so files. 2278 if (target.os.tag.isDarwin() and link_mode == .dynamic) so: { 2279 const test_path: Path = .{ 2280 .root_dir = lib_directory, 2281 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}), 2282 }; 2283 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2284 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2285 error.FileNotFound => break :so, 2286 else => |e| fatal("unable to search for so library '{f}': {s}", .{ 2287 test_path, @errorName(e), 2288 }), 2289 }; 2290 errdefer file.close(io); 2291 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query); 2292 } 2293 2294 // In the case of MinGW, the main check will be .lib but we also need to 2295 // look for `libfoo.a`. 2296 if (target.isMinGW() and link_mode == .static) mingw: { 2297 const test_path: Path = .{ 2298 .root_dir = lib_directory, 2299 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}), 2300 }; 2301 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2302 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2303 error.FileNotFound => break :mingw, 2304 else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }), 2305 }; 2306 errdefer file.close(io); 2307 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query); 2308 } 2309 2310 // In the case of OpenBSD, dynamic libraries are always versioned, without 2311 // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned 2312 // shared library, and this code is intended to match that upstream behavior. 2313 if (target.isOpenBSDLibC() and link_mode == .dynamic) versioned: { 2314 const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name}); 2315 2316 var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) { 2317 error.NotDir, error.FileNotFound => break :versioned, 2318 else => |e| fatal("unable to search for shared library '{s}.*': {s}", .{ prefix, @errorName(e) }), 2319 }; 2320 defer dir.close(io); 2321 2322 var best_match_major: u32 = 0; 2323 var best_match_minor: u32 = 0; 2324 var best_match: ?[]const u8 = null; 2325 2326 var iter = dir.iterate(); 2327 while (iter.next(io) catch |err| { 2328 fatal("unable to scan library directory '{s}'", .{@errorName(err)}); 2329 }) |entry| { 2330 if (entry.kind != .file) continue; 2331 if (!std.mem.startsWith(u8, entry.name, prefix)) continue; 2332 2333 const rest = entry.name[prefix.len..]; 2334 var sit = std.mem.splitScalar(u8, rest, '.'); 2335 const major_str = sit.next() orelse continue; 2336 const minor_str = sit.next() orelse continue; 2337 if (sit.next() != null) continue; 2338 const major = std.fmt.parseInt(u32, major_str, 10) catch continue; 2339 const minor = std.fmt.parseInt(u32, minor_str, 10) catch continue; 2340 2341 if (major > best_match_major or (major == best_match_major and minor >= best_match_minor)) { 2342 best_match_major = major; 2343 best_match_minor = minor; 2344 best_match = try arena.dupe(u8, entry.name); 2345 } 2346 } 2347 2348 if (best_match) |found| { 2349 const test_path: Path = .{ 2350 .root_dir = lib_directory, 2351 .sub_path = found, 2352 }; 2353 try checked_paths.print(gpa, "\n {f}", .{test_path}); 2354 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{ 2355 .path = test_path, 2356 .query = name_query.query, 2357 }, link_mode, color)) { 2358 .no_match => {}, 2359 .ok => return .ok, 2360 } 2361 } 2362 } 2363 2364 return .no_match; 2365 } 2366 2367 /// Deduplicates static archive link inputs based on their path. This is done for efficiency, so 2368 /// that linker implementations do not need to open and scan the archive just to determine that they 2369 /// need not extract any objects. At the time of writing, it also helps avoid "multiple definitions 2370 /// of symbol" errors in incomplete linker implementations. 2371 /// 2372 /// Key is index into `resolved_inputs` of an `Input.archive`. 2373 /// 2374 /// Accessed through `ArchiveDedupAdapter`. 2375 /// 2376 const ArchiveDedupMap = std.array_hash_map.Custom(u32, void, void, true); 2377 /// Adapter for accessing `ArchiveDedupMap` with an effective key type of `Path`. 2378 const ArchiveDedupAdapter = struct { 2379 resolved_inputs: []const Input, 2380 pub fn hash(ctx: ArchiveDedupAdapter, path: Path) u32 { 2381 _ = ctx; 2382 return Path.TableAdapter.hash(.{}, path); 2383 } 2384 pub fn eql(ctx: ArchiveDedupAdapter, a_path: Path, b_input_index: u32, _: usize) bool { 2385 const b_path = ctx.resolved_inputs[b_input_index].archive.path; 2386 return a_path.eql(b_path); 2387 } 2388 }; 2389 2390 fn finishResolveLibInput( 2391 io: Io, 2392 resolved_inputs: *std.ArrayList(Input), 2393 archive_dedup: *ArchiveDedupMap, 2394 path: Path, 2395 file: Io.File, 2396 link_mode: std.lang.LinkMode, 2397 query: UnresolvedInput.Query, 2398 ) ResolveLibInputResult { 2399 switch (link_mode) { 2400 .static => { 2401 const ctx: ArchiveDedupAdapter = .{ .resolved_inputs = resolved_inputs.items }; 2402 const gop = archive_dedup.getOrPutAssumeCapacityAdapted(path, ctx); 2403 if (gop.found_existing) { 2404 // Ignore duplicate archive input 2405 file.close(io); 2406 return .ok; 2407 } 2408 gop.key_ptr.* = @intCast(resolved_inputs.items.len); 2409 resolved_inputs.appendAssumeCapacity(.{ .archive = .{ 2410 .path = path, 2411 .file = file, 2412 .must_link = query.must_link, 2413 .hidden = query.hidden, 2414 } }); 2415 }, 2416 .dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{ 2417 .path = path, 2418 .file = file, 2419 .needed = query.needed, 2420 .weak = query.weak, 2421 .reexport = query.reexport, 2422 } }), 2423 } 2424 return .ok; 2425 } 2426 2427 fn resolvePathInput( 2428 gpa: Allocator, 2429 arena: Allocator, 2430 io: Io, 2431 /// Allocated with `gpa`. 2432 unresolved_inputs: *std.ArrayList(UnresolvedInput), 2433 /// Allocated with `gpa`. 2434 resolved_inputs: *std.ArrayList(Input), 2435 /// Allocated via `gpa`. 2436 ld_script_bytes: *std.ArrayList(u8), 2437 /// Allocated via `gpa`. 2438 archive_dedup: *ArchiveDedupMap, 2439 target: *const std.Target, 2440 pq: UnresolvedInput.PathQuery, 2441 color: std.zig.Color, 2442 ) Allocator.Error!?ResolveLibInputResult { 2443 switch (Compilation.classifyFileExt(pq.path.sub_path)) { 2444 .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .static, color), 2445 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .dynamic, color), 2446 .object => { 2447 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err| 2448 fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) }); 2449 errdefer file.close(io); 2450 try resolved_inputs.append(gpa, .{ .object = .{ 2451 .path = pq.path, 2452 .file = file, 2453 .must_link = pq.query.must_link, 2454 .hidden = pq.query.hidden, 2455 } }); 2456 return null; 2457 }, 2458 .res => { 2459 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err| 2460 fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) }); 2461 errdefer file.close(io); 2462 try resolved_inputs.append(gpa, .{ .res = .{ 2463 .path = pq.path, 2464 .file = file, 2465 } }); 2466 return null; 2467 }, 2468 else => fatal("{f}: unrecognized file extension", .{pq.path}), 2469 } 2470 } 2471 2472 fn resolvePathInputLib( 2473 gpa: Allocator, 2474 arena: Allocator, 2475 io: Io, 2476 /// Allocated with `gpa`. 2477 unresolved_inputs: *std.ArrayList(UnresolvedInput), 2478 /// Allocated with `gpa`. 2479 resolved_inputs: *std.ArrayList(Input), 2480 /// Allocated via `gpa`. 2481 ld_script_bytes: *std.ArrayList(u8), 2482 /// Allocated via `gpa`. 2483 archive_dedup: *ArchiveDedupMap, 2484 target: *const std.Target, 2485 pq: UnresolvedInput.PathQuery, 2486 link_mode: std.lang.LinkMode, 2487 color: std.zig.Color, 2488 ) Allocator.Error!ResolveLibInputResult { 2489 try resolved_inputs.ensureUnusedCapacity(gpa, 1); 2490 try archive_dedup.ensureUnusedCapacity(gpa, 1); 2491 2492 const test_path: Path = pq.path; 2493 // In the case of shared libraries, they might actually be "linker scripts" 2494 // that contain references to other libraries. 2495 if (pq.query.allow_so_scripts and target.ofmt == .elf and switch (Compilation.classifyFileExt(test_path.sub_path)) { 2496 .static_library, .shared_library => true, 2497 else => false, 2498 }) { 2499 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2500 error.FileNotFound => return .no_match, 2501 else => |e| fatal("unable to search for {t} library '{f}': {t}", .{ 2502 link_mode, std.fmt.alt(test_path, .formatEscapeChar), e, 2503 }), 2504 }; 2505 errdefer file.close(io); 2506 try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len)); 2507 const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err| 2508 fatal("failed to read '{f}': {t}", .{ std.fmt.alt(test_path, .formatEscapeChar), err }); 2509 const buf = ld_script_bytes.items[0..n]; 2510 if (mem.startsWith(u8, buf, std.elf.MAGIC) or 2511 mem.startsWith(u8, buf, std.elf.ARMAG) or 2512 mem.startsWith(u8, buf, std.elf.ARMAG_THIN)) 2513 { 2514 // Appears to be an ELF or archive file. 2515 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query); 2516 } 2517 const stat = file.stat(io) catch |err| 2518 fatal("failed to stat {f}: {t}", .{ test_path, err }); 2519 const size = std.math.cast(u32, stat.size) orelse 2520 fatal("{f}: linker script too big", .{test_path}); 2521 try ld_script_bytes.resize(gpa, size); 2522 const buf2 = ld_script_bytes.items[n..]; 2523 const n2 = file.readPositionalAll(io, buf2, n) catch |err| 2524 fatal("failed to read {f}: {t}", .{ test_path, err }); 2525 if (n2 != buf2.len) fatal("failed to read {f}: unexpected end of file", .{test_path}); 2526 2527 // This `Io` is only used for a mutex, and we know we aren't doing anything async/concurrent. 2528 var threaded: Io.Threaded = .init_single_threaded; 2529 defer threaded.deinit(); 2530 var diags: Diags = .init(gpa, threaded.io()); 2531 defer diags.deinit(); 2532 2533 const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items); 2534 if (diags.hasErrors()) { 2535 var wip_errors: std.zig.ErrorBundle.Wip = undefined; 2536 try wip_errors.init(gpa); 2537 defer wip_errors.deinit(); 2538 2539 try diags.addMessagesToBundle(&wip_errors, null); 2540 2541 var error_bundle = try wip_errors.toOwnedBundle(""); 2542 defer error_bundle.deinit(gpa); 2543 2544 error_bundle.renderToStderr(io, .{}, color) catch {}; 2545 std.process.exit(1); 2546 } 2547 2548 var ld_script = ld_script_result catch |err| 2549 fatal("{f}: failed to parse linker script: {t}", .{ test_path, err }); 2550 defer ld_script.deinit(gpa); 2551 2552 try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len); 2553 for (ld_script.args) |arg| { 2554 const query: UnresolvedInput.Query = .{ 2555 .needed = arg.needed or pq.query.needed, 2556 .weak = pq.query.weak, 2557 .reexport = pq.query.reexport, 2558 .preferred_mode = pq.query.preferred_mode, 2559 .search_strategy = pq.query.search_strategy, 2560 .allow_so_scripts = pq.query.allow_so_scripts, 2561 }; 2562 if (mem.startsWith(u8, arg.path, "-l")) { 2563 unresolved_inputs.appendAssumeCapacity(.{ .name_query = .{ 2564 .name = try arena.dupe(u8, arg.path["-l".len..]), 2565 .query = query, 2566 } }); 2567 } else { 2568 unresolved_inputs.appendAssumeCapacity(.{ .ambiguous_name = .{ 2569 .name = try arena.dupe(u8, arg.path), 2570 .query = query, 2571 } }); 2572 } 2573 } 2574 file.close(io); 2575 return .ok; 2576 } 2577 2578 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { 2579 error.FileNotFound => return .no_match, 2580 else => |e| fatal("unable to search for {s} library {f}: {s}", .{ 2581 @tagName(link_mode), test_path, @errorName(e), 2582 }), 2583 }; 2584 errdefer file.close(io); 2585 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query); 2586 } 2587 2588 pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object { 2589 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); 2590 errdefer file.close(io); 2591 return .{ 2592 .path = path, 2593 .file = file, 2594 .must_link = must_link, 2595 .hidden = hidden, 2596 }; 2597 } 2598 2599 pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso { 2600 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{}); 2601 errdefer file.close(io); 2602 return .{ 2603 .path = path, 2604 .file = file, 2605 .needed = needed, 2606 .weak = weak, 2607 .reexport = reexport, 2608 }; 2609 } 2610 2611 pub fn openObjectInput(io: Io, diags: *Diags, path: Path) error{AlreadyReported}!Input { 2612 return .{ .object = openObject(io, path, false, false) catch |err| { 2613 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); 2614 } }; 2615 } 2616 2617 pub fn openArchiveInput(io: Io, diags: *Diags, path: Path, must_link: bool, hidden: bool) error{AlreadyReported}!Input { 2618 return .{ .archive = openObject(io, path, must_link, hidden) catch |err| { 2619 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); 2620 } }; 2621 } 2622 2623 pub fn openDsoInput(io: Io, diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{AlreadyReported}!Input { 2624 return .{ .dso = openDso(io, path, needed, weak, reexport) catch |err| { 2625 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) }); 2626 } }; 2627 } 2628 2629 /// Returns true if and only if there is at least one input of type object, 2630 /// archive, or Windows resource file. 2631 pub fn anyObjectInputs(inputs: []const Input) bool { 2632 return countObjectInputs(inputs) != 0; 2633 } 2634 2635 /// Returns the number of inputs of type object, archive, or Windows resource file. 2636 pub fn countObjectInputs(inputs: []const Input) usize { 2637 var count: usize = 0; 2638 for (inputs) |input| switch (input) { 2639 .dso, .dso_exact => continue, 2640 .res, .object, .archive => count += 1, 2641 }; 2642 return count; 2643 } 2644 2645 /// Returns the first input of type object or archive. 2646 pub fn firstObjectInput(inputs: []const Input) ?Input.Object { 2647 for (inputs) |input| switch (input) { 2648 .object, .archive => |obj| return obj, 2649 .res, .dso, .dso_exact => continue, 2650 }; 2651 return null; 2652 } 2653