From 89f86e46d278a35a613bbc662cdd3f65ffc76ed7 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Thu, 30 Apr 2026 10:08:25 +0100 Subject: [PATCH 1/2] std: don't use dyld functions on non-macOS Darwin 65922a2d4 started using some (deprecated but still available) dyld functions for stack unwinding and debug information on Darwin targets, because they are significantly faster than `dladdr` (which is the "correct" thing to use). However, these functions are unavailable on Darwin targets other than macOS, for instance on iOS. Therefore, on those targets, we must fall back to the slow `dladdr` path. --- lib/std/debug/SelfInfo/MachO.zig | 57 +++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index c35ae03e9b24a5f3bfb4649a56620f15c487cf05..431108cd3f6164769359902f1abccdec720516df 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -93,12 +93,28 @@ pub fn getSymbols( pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { _ = si; _ = io; - // This function is marked as deprecated; however, it is significantly more - // performant than `dladdr` (since the latter also does a very slow symbol - // lookup), so let's use it since it's still available. - return std.mem.span(std.c.dyld_image_path_containing_address( - @ptrFromInt(address), - ) orelse return error.MissingDebugInfo); + return getModuleNameInner(address) orelse return error.MissingDebugInfo; +} +fn getModuleNameInner(address: usize) ?[]const u8 { + switch (builtin.target.os.tag) { + .macos => { + // This function is marked as deprecated; however, it is significantly more performant + // than `dladdr` (since the latter also does a very slow symbol lookup), so let's just + // use it for the better performance since it's still available. + return std.mem.span(std.c.dyld_image_path_containing_address( + @ptrFromInt(address), + ) orelse return null); + }, + else => { + // On other Darwin systems, the function used above is entirely unavailable, so we have + // no choice but to use the slow `dladdr`. + var info: std.c.dl_info = undefined; + if (std.c.dladdr(@ptrFromInt(address), &info) == 0) { + return null; + } + return std.mem.span(info.fname); + }, + } } pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize { const gpa = std.debug.getDebugInfoAllocator(); @@ -446,12 +462,25 @@ fn unwindFrameInner(si: *SelfInfo, io: Io, context: *UnwindContext) !usize { /// Acquires the mutex on success. fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!*Module { - // This function is marked as deprecated; however, it is significantly more - // performant than `dladdr` (since the latter also does a very slow symbol - // lookup), so let's use it since it's still available. - const text_base = std.c._dyld_get_image_header_containing_address( - @ptrFromInt(address), - ) orelse return error.MissingDebugInfo; + const text_base: *anyopaque = switch (builtin.target.os.tag) { + .macos => base: { + // This function is marked as deprecated; however, it is significantly more performant + // than `dladdr` (since the latter also does a very slow symbol lookup), so let's just + // use it for the better performance since it's still available. + break :base std.c._dyld_get_image_header_containing_address( + @ptrFromInt(address), + ) orelse return error.MissingDebugInfo; + }, + else => base: { + // On other Darwin systems, the function used above is entirely unavailable, so we have + // no choice but to use the slow `dladdr`. + var info: std.c.dl_info = undefined; + if (std.c.dladdr(@ptrFromInt(address), &info) == 0) { + return error.MissingDebugInfo; + } + break :base info.fbase; + }, + }; try si.mutex.lock(io); errdefer si.mutex.unlock(io); const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(text_base), Module.Adapter{}); @@ -563,9 +592,7 @@ const Module = struct { fn getFile(module: *Module, gpa: Allocator, io: Io) Error!*MachOFile { if (module.file == null) { - const path = std.mem.span( - std.c.dyld_image_path_containing_address(@ptrFromInt(module.text_base)).?, - ); + const path = getModuleNameInner(module.text_base).?; module.file = MachOFile.load(gpa, io, path, builtin.cpu.arch) catch |err| switch (err) { error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e, -- 2.54.0 From 2b8a05b8d9125b7465f0d3bd44e90d82a06dd5fa Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Thu, 30 Apr 2026 09:57:20 +0100 Subject: [PATCH 2/2] tests: restore standalone/ios, add Zig code to it This was deleted in beef9fdf42726d8cfe3017ff3017767fb615ef08 because it was setting `b.sysroot`. However, I frankly have no idea why it was doing that---the test works just fine without touching `b.sysroot`! Even before it was removed, this test was never actually running, because the executable was not added as a dependency of `test_step`. I made it run, and updated it to work (it was previously failing due to a missing framework path). I also added some Zig source code, the goal being to test that the panic handler compiles okay on iOS. I confirmed that the updated test passes as-is, but fails if the fix in the previous commit is reverted. Because this test requires the iOS SDK to be installed, it is only run if `-Denable-ios-sdk` is passed to `zig build`, which is currently not the case in CI. For now, I am not changing this, because the aarch64-macos CI runner currently does not have the iOS SDK installed. It would be good to enable this coverage on CI at some point, though. --- test/standalone/build.zig.zon | 3 +++ test/standalone/ios/build.zig | 41 +++++++++++++++++++++++++++++++++++ test/standalone/ios/main.m | 38 ++++++++++++++++++++++++++++++++ test/standalone/ios/panic.zig | 4 ++++ 4 files changed, 86 insertions(+) create mode 100644 test/standalone/ios/build.zig create mode 100644 test/standalone/ios/main.m create mode 100644 test/standalone/ios/panic.zig diff --git a/test/standalone/build.zig.zon b/test/standalone/build.zig.zon index 175a106f3e544374e88a8aa1ab76d3a2abfc4895..072bcd0309cf4decce854badf096994606a1e550 100644 --- a/test/standalone/build.zig.zon +++ b/test/standalone/build.zig.zon @@ -153,6 +153,9 @@ .compiler_rt_panic = .{ .path = "compiler_rt_panic", }, + .ios = .{ + .path = "ios", + }, .depend_on_main_mod = .{ .path = "depend_on_main_mod", }, diff --git a/test/standalone/ios/build.zig b/test/standalone/ios/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..cd1d52aec973ab44c8c4f30d0224c6e0f34b5268 --- /dev/null +++ b/test/standalone/ios/build.zig @@ -0,0 +1,41 @@ +const std = @import("std"); + +pub const requires_symlinks = true; +pub const requires_ios_sdk = true; + +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + const target = b.resolveTargetQuery(.{ + .cpu_arch = .aarch64, + .os_tag = .ios, + }); + + const exe = b.addExecutable(.{ + .name = "main", + .root_module = b.createModule(.{ + .root_source_file = b.path("panic.zig"), + .optimize = .Debug, + .target = target, + .link_libc = true, + }), + }); + + const io = b.graph.io; + + if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| { + exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); + exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) }); + exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/SubFrameworks" }) }); + exe.root_module.addLibraryPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/lib" }) }); + } else { + exe.step.dependOn(&b.addFail("no iOS SDK found").step); + } + + exe.root_module.addCSourceFile(.{ .file = b.path("main.m"), .flags = &.{} }); + exe.root_module.linkFramework("Foundation", .{}); + exe.root_module.linkFramework("UIKit", .{}); + + test_step.dependOn(&b.addInstallArtifact(exe, .{}).step); +} diff --git a/test/standalone/ios/main.m b/test/standalone/ios/main.m new file mode 100644 index 0000000000000000000000000000000000000000..82b9a8c20fcd49a4c447178a841c84d366377e30 --- /dev/null +++ b/test/standalone/ios/main.m @@ -0,0 +1,38 @@ +#import + +@interface AppDelegate : UIResponder +@property (strong, nonatomic) UIWindow *window; +@end + +extern void zig_panic(); + +int main() { + @autoreleasepool { + return UIApplicationMain(0, nil, nil, NSStringFromClass([AppDelegate class])); + } +} + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(id)options { + CGRect mainScreenBounds = [[UIScreen mainScreen] bounds]; + self.window = [[UIWindow alloc] initWithFrame:mainScreenBounds]; + UIViewController *viewController = [[UIViewController alloc] init]; + viewController.view.frame = mainScreenBounds; + + NSString* msg = @"Hello world"; + + UILabel *label = [[UILabel alloc] initWithFrame:mainScreenBounds]; + [label setText:msg]; + [viewController.view addSubview: label]; + + self.window.rootViewController = viewController; + + [self.window makeKeyAndVisible]; + + zig_panic(); + + return YES; +} + +@end diff --git a/test/standalone/ios/panic.zig b/test/standalone/ios/panic.zig new file mode 100644 index 0000000000000000000000000000000000000000..5acb60da2dd9d9cb4dfcf8800b70a7dcea977e95 --- /dev/null +++ b/test/standalone/ios/panic.zig @@ -0,0 +1,4 @@ +export fn zig_panic() void { + @panic("called zig_panic"); +} +pub const _start = {}; // entry point is in main.m -- 2.54.0