From 010f7a29fdec322cc6190e36f74ae387ed18e1eb Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 15 May 2025 14:23:19 +0200 Subject: [PATCH 1/9] Add dladdr symbolication --- .../src/core/internal/backtrace/dwarf.d | 160 +++--------------- 1 file changed, 25 insertions(+), 135 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/dwarf.d b/runtime/druntime/src/core/internal/backtrace/dwarf.d index 5da98948000..2ba4f3a11fd 100644 --- a/runtime/druntime/src/core/internal/backtrace/dwarf.d +++ b/runtime/druntime/src/core/internal/backtrace/dwarf.d @@ -244,11 +244,8 @@ int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData, { if (debugLineSectionData) resolveAddresses(debugLineSectionData, locations, baseAddress); - version (Darwin) - { - if (!debugLineSectionData) - resolveAddressesWithAtos(locations); - } + else + resolveAddressesWithDladdr(locations); TraceInfoBuffer buffer; foreach (idx, const ref loc; locations) @@ -267,138 +264,31 @@ int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData, return 0; } -version (Darwin) { - /** - * Resolve the addresses of `locations` using `atos` (executable that ships with XCode) - * - * Spawns a child process that calls `atos`. Communication is through stdin/stdout pipes. - * - * After this function successfully completes, `locations` will contain - * file / lines informations. - * - * The lifetime of the `Location` data surpases function return (strndup is used). - * - * Params: - * locations = The locations to resolve - */ - private void resolveAddressesWithAtos(Location[] locations) @nogc nothrow - { - import core.stdc.stdio : fclose, fflush, fgets, fprintf, printf, snprintf; - import core.stdc.stdlib : exit; - import core.sys.posix.stdio : fdopen; - import core.sys.posix.unistd : close, dup2, execlp, fork, getpid, pipe; - // Create in/out pipes to communicate with the forked exec - int[2] dummy_pipes; // these dummy pipes are there to prevent funny issues when stdin/stdout is closed and pipe returns id 0 or 1 - int[2] pipes_to_atos; - int[2] pipes_from_atos; - if ( pipe(dummy_pipes) < 0 || pipe(pipes_to_atos) < 0 || pipe(pipes_from_atos) < 0 ) { - printf("some pipe creation error!\n"); - return; - } - close(dummy_pipes[0]); - close(dummy_pipes[1]); - auto write_to_atos = pipes_to_atos[1]; - auto read_from_atos = pipes_from_atos[0]; - auto atos_stdin = pipes_to_atos[0]; - auto atos_stdout = pipes_from_atos[1]; - auto self_pid = cast(int) getpid(); - // Spawn a child process that calls atos, reads/writes from the pipes, and then exits. - auto child_id = fork(); - if (child_id == -1) - { - printf("some fork error!\n"); - return; - } - else if (child_id == 0) - { - // We are in the child process, spawn atos and link pipes - // Close unused read/write ends of pipes - close(write_to_atos); - close(read_from_atos); - // Link pipes to stdin/stdout - dup2(atos_stdin, 0); - close(atos_stdin); - dup2(atos_stdout, 1); - close(atos_stdout); - char[10] pid_str; - snprintf(pid_str.ptr, pid_str.sizeof, "%d", cast(int) self_pid); - const(char)* atos_executable = "atos"; - const(char)* atos_p_arg = "-p"; - const(char)* atos_fullpath_arg = "-fullPath"; - execlp(atos_executable, atos_executable, atos_fullpath_arg, atos_p_arg, pid_str.ptr, null); - // If exec returns, an error occurred, need to exit the forked process here. - printf("some exec error!\n"); - exit(0); +/** + * Resolve the addresses of `locations` using dladdr. + * + * Params: + * locations = The locations to resolve +*/ +void resolveAddressesWithDladdr(ref Location[] locations) @nogc nothrow { + import core.demangle : demangle; + import core.sys.posix.dlfcn : dladdr, Dl_info; + import core.stdc.string : strlen; + foreach(ref Location location; locations) { + + Dl_info info; + dladdr(location.address, &info); + location.line = -1; + + // Strip off path to image. + if (info.dli_fname) { + location.file = info.dli_fname[0..strlen(info.dli_fname)]; } - // Parent process just continues from here. - // Close unused pipes - close(atos_stdin); - close(atos_stdout); - auto to_atos = fdopen(write_to_atos, "w"); - auto from_atos = fdopen(read_from_atos, "r"); - // buffer for atos reading. Note that symbol names can be super large... - static char[16 * 1024] read_buffer = void; - char* status_ptr = null; - foreach (ref loc; locations) - { - fprintf(to_atos, "%p\n", loc.address); - fflush(to_atos); - read_buffer[0] = '\0'; - status_ptr = fgets(read_buffer.ptr, read_buffer.sizeof, from_atos); - if (!status_ptr) - break; - Location parsed_loc = parseAtosLine(read_buffer.ptr); - if (parsed_loc.line != -1) - { - // Only update the file:line info, keep the procedure name as found before (preserving the standard truncation). - loc.file = parsed_loc.file; - loc.line = parsed_loc.line; - } + + // Demangle D symbols. + if (info.dli_sname) { + location.procedure = info.dli_sname[0..strlen(info.dli_sname)]; } - if (!status_ptr) - printf("\nDid not succeed in using 'atos' for extra debug information.\n"); - fclose(to_atos); - fclose(from_atos); - close(write_to_atos); - close(read_from_atos); - } - private Location parseAtosLine(char* buffer) @nogc nothrow - { - // The line from `atos` is in one of these formats: - // myfunction (in library.dylib) (sourcefile.c:17) - // myfunction (in library.dylib) + 0x1fe - // myfunction (in library.dylib) + 15 - // 0xdeadbeef (in library.dylib) + 0x1fe - // 0xdeadbeef (in library.dylib) + 15 - // 0xdeadbeef (in library.dylib) - // 0xdeadbeef - import core.stdc.stdlib : atoi; - import core.stdc.string : strchr, strstr; - import core.sys.posix.string : strndup; - Location loc; - if (!buffer) - return loc; - if (buffer[0] == '0' && buffer[1] == 'x') - // no named symbol found - return loc; - const symbolname_end = strstr(buffer, " (in "); - if (!symbolname_end) - return loc; - const symbolname_size = symbolname_end - buffer; - loc.procedure = strndup(buffer, symbolname_size)[0..symbolname_size]; - const filename_start = strstr(symbolname_end, ") (") + 3; - if (cast(size_t)filename_start < 4) - return loc; - const colon_location = strchr(filename_start, ':'); - if (!colon_location) - return loc; - const filename_size = colon_location - filename_start; - loc.file = strndup(filename_start, filename_size)[0..filename_size]; - const final_paren = strchr(colon_location+1, ')'); - if (!final_paren) - return loc; - loc.line = atoi(colon_location+1); - return loc; } } From da0f49b6f3b12b0445ff5a0cec29da92122ce583 Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 15 May 2025 18:04:15 +0200 Subject: [PATCH 2/9] Completely remove atos --- .../src/core/internal/backtrace/dwarf.d | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/dwarf.d b/runtime/druntime/src/core/internal/backtrace/dwarf.d index 2ba4f3a11fd..11a839db5b0 100644 --- a/runtime/druntime/src/core/internal/backtrace/dwarf.d +++ b/runtime/druntime/src/core/internal/backtrace/dwarf.d @@ -69,6 +69,7 @@ else import core.internal.container.array; import core.stdc.string : strlen, memcpy; +import core.attribute : weak; //debug = DwarfDebugMachine; debug(DwarfDebugMachine) import core.stdc.stdio : printf; @@ -237,6 +238,24 @@ struct TraceInfoBuffer } } +/** + * A weakly linked hook which can be implemented by external libraries + * to extend the symbolication capabilites when debug info is missing. +*/ +extern(C) void rt_dwarfSymbolicate(ref Location[] locations) @weak +{ + import core.sys.posix.dlfcn : dladdr, Dl_info; + Dl_info info; + foreach(ref Location location; locations) + { + if (dladdr(location.address, &info) == 0) + continue; + + location.file = info.dli_fname[0..strlen(info.dli_fname)]; + location.procedure = info.dli_sname[0..strlen(info.dli_sname)]; + } +} + private: int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData, @@ -244,8 +263,7 @@ int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData, { if (debugLineSectionData) resolveAddresses(debugLineSectionData, locations, baseAddress); - else - resolveAddressesWithDladdr(locations); + else rt_dwarfSymbolicate(locations); TraceInfoBuffer buffer; foreach (idx, const ref loc; locations) @@ -264,34 +282,6 @@ int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData, return 0; } -/** - * Resolve the addresses of `locations` using dladdr. - * - * Params: - * locations = The locations to resolve -*/ -void resolveAddressesWithDladdr(ref Location[] locations) @nogc nothrow { - import core.demangle : demangle; - import core.sys.posix.dlfcn : dladdr, Dl_info; - import core.stdc.string : strlen; - foreach(ref Location location; locations) { - - Dl_info info; - dladdr(location.address, &info); - location.line = -1; - - // Strip off path to image. - if (info.dli_fname) { - location.file = info.dli_fname[0..strlen(info.dli_fname)]; - } - - // Demangle D symbols. - if (info.dli_sname) { - location.procedure = info.dli_sname[0..strlen(info.dli_sname)]; - } - } -} - /** * Resolve the addresses of `locations` using `debugLineSectionData` * From 642941a213135207b8a08d2cfa02b84a1df148d4 Mon Sep 17 00:00:00 2001 From: Luna Date: Fri, 16 May 2025 18:23:38 +0200 Subject: [PATCH 3/9] Added cleanup hooks --- .../src/core/internal/backtrace/dwarf.d | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/dwarf.d b/runtime/druntime/src/core/internal/backtrace/dwarf.d index 11a839db5b0..f857553c212 100644 --- a/runtime/druntime/src/core/internal/backtrace/dwarf.d +++ b/runtime/druntime/src/core/internal/backtrace/dwarf.d @@ -185,6 +185,8 @@ int traceHandlerOpApplyImpl(size_t numFrames, startIdx = idx + 1; } + // Symbolicate locations + rt_dwarfSymbolicate(locations[startIdx .. $]); if (!image.isValid()) return locations[startIdx .. $].processCallstack(null, 0, dg); @@ -192,6 +194,9 @@ int traceHandlerOpApplyImpl(size_t numFrames, // find address -> file, line mapping using dwarf debug_line return image.processDebugLineSectionData( (line) => locations[startIdx .. $].processCallstack(line, image.baseAddress, dg)); + + // Allow cleaning up after ourselves. + rt_dwarfSymbolicateCleanup(locations[startIdx .. $]); } struct TraceInfoBuffer @@ -242,18 +247,15 @@ struct TraceInfoBuffer * A weakly linked hook which can be implemented by external libraries * to extend the symbolication capabilites when debug info is missing. */ -extern(C) void rt_dwarfSymbolicate(ref Location[] locations) @weak +@weak +extern(C) void rt_dwarfSymbolicate(Location[] locations) +{ +} + +/// ditto +@weak +extern(C) void rt_dwarfSymbolicateCleanup(Location[] locations) { - import core.sys.posix.dlfcn : dladdr, Dl_info; - Dl_info info; - foreach(ref Location location; locations) - { - if (dladdr(location.address, &info) == 0) - continue; - - location.file = info.dli_fname[0..strlen(info.dli_fname)]; - location.procedure = info.dli_sname[0..strlen(info.dli_sname)]; - } } private: @@ -263,7 +265,6 @@ int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData, { if (debugLineSectionData) resolveAddresses(debugLineSectionData, locations, baseAddress); - else rt_dwarfSymbolicate(locations); TraceInfoBuffer buffer; foreach (idx, const ref loc; locations) From 0fe3c915ec718f6f9eb4f0674ab55ceca6040a29 Mon Sep 17 00:00:00 2001 From: Luna Date: Fri, 16 May 2025 18:36:05 +0200 Subject: [PATCH 4/9] Add atos removal note --- runtime/druntime/src/core/internal/backtrace/dwarf.d | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/runtime/druntime/src/core/internal/backtrace/dwarf.d b/runtime/druntime/src/core/internal/backtrace/dwarf.d index f857553c212..ac358516e29 100644 --- a/runtime/druntime/src/core/internal/backtrace/dwarf.d +++ b/runtime/druntime/src/core/internal/backtrace/dwarf.d @@ -43,7 +43,7 @@ * Reference: http://www.dwarfstd.org/ * Copyright: Copyright Digital Mars 2015 - 2015. * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Yazan Dabain, Sean Kelly + * Authors: Yazan Dabain, Sean Kelly, Luna Nielsen * Source: $(DRUNTIMESRC core/internal/backtrace/dwarf.d) */ @@ -246,6 +246,16 @@ struct TraceInfoBuffer /** * A weakly linked hook which can be implemented by external libraries * to extend the symbolication capabilites when debug info is missing. + * + * NOTE: + * There used to be an atos based symbolication implementation built in + * here, but atos is not a portable solution on darwin derived OSes. + * atos conflicts with things such as the hardened runtime, iOS releases, + * App Store certification and the like. I've removed that implementation + * to ensure that D can easily be used to publish to the App Store. + * Please avoid adding other private APIs in its place directly in to + * druntime. If it resides in PrivateFrameworks or is a dev tool, + * don't use it. - Luna */ @weak extern(C) void rt_dwarfSymbolicate(Location[] locations) From bd9cda3017e88370d3ad805b11ce69c33b00fee0 Mon Sep 17 00:00:00 2001 From: Luna Date: Sun, 6 Jul 2025 16:08:48 +0200 Subject: [PATCH 5/9] Add dSYM mapper --- .../src/core/internal/backtrace/dwarf.d | 31 --- .../src/core/internal/backtrace/macho.d | 45 ++-- runtime/druntime/src/core/internal/macho/dl.d | 228 ++++++++++++++++++ runtime/druntime/src/core/internal/macho/io.d | 77 ++++++ .../src/core/sys/darwin/mach/loader.d | 15 ++ runtime/druntime/src/rt/sections_elf_shared.d | 1 - 6 files changed, 345 insertions(+), 52 deletions(-) create mode 100644 runtime/druntime/src/core/internal/macho/dl.d create mode 100644 runtime/druntime/src/core/internal/macho/io.d diff --git a/runtime/druntime/src/core/internal/backtrace/dwarf.d b/runtime/druntime/src/core/internal/backtrace/dwarf.d index ac358516e29..635bc635749 100644 --- a/runtime/druntime/src/core/internal/backtrace/dwarf.d +++ b/runtime/druntime/src/core/internal/backtrace/dwarf.d @@ -185,18 +185,12 @@ int traceHandlerOpApplyImpl(size_t numFrames, startIdx = idx + 1; } - // Symbolicate locations - rt_dwarfSymbolicate(locations[startIdx .. $]); - if (!image.isValid()) return locations[startIdx .. $].processCallstack(null, 0, dg); // find address -> file, line mapping using dwarf debug_line return image.processDebugLineSectionData( (line) => locations[startIdx .. $].processCallstack(line, image.baseAddress, dg)); - - // Allow cleaning up after ourselves. - rt_dwarfSymbolicateCleanup(locations[startIdx .. $]); } struct TraceInfoBuffer @@ -243,31 +237,6 @@ struct TraceInfoBuffer } } -/** - * A weakly linked hook which can be implemented by external libraries - * to extend the symbolication capabilites when debug info is missing. - * - * NOTE: - * There used to be an atos based symbolication implementation built in - * here, but atos is not a portable solution on darwin derived OSes. - * atos conflicts with things such as the hardened runtime, iOS releases, - * App Store certification and the like. I've removed that implementation - * to ensure that D can easily be used to publish to the App Store. - * Please avoid adding other private APIs in its place directly in to - * druntime. If it resides in PrivateFrameworks or is a dev tool, - * don't use it. - Luna -*/ -@weak -extern(C) void rt_dwarfSymbolicate(Location[] locations) -{ -} - -/// ditto -@weak -extern(C) void rt_dwarfSymbolicateCleanup(Location[] locations) -{ -} - private: int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData, diff --git a/runtime/druntime/src/core/internal/backtrace/macho.d b/runtime/druntime/src/core/internal/backtrace/macho.d index ff6a31b7faa..e6e7809846e 100644 --- a/runtime/druntime/src/core/internal/backtrace/macho.d +++ b/runtime/druntime/src/core/internal/backtrace/macho.d @@ -20,32 +20,37 @@ else version (WatchOS) version (Darwin): import core.stdc.config : c_ulong; -import core.sys.darwin.crt_externs : _NSGetMachExecuteHeader; -import core.sys.darwin.mach.getsect : mach_header_64, getsectiondata; - -struct Image -{ - private mach_header_64* self; +import core.stdc.stdlib : free; +import core.internal.macho.dl; +import core.internal.macho.io; + +struct Image { + SharedObject self; + SharedObject debugObj; + + this(SharedObject self) { + this.self = self; + this.debugObj = self; + + if (!self.hasSection("__DWARF", "__debug_line")) { + auto dsymPath = getDsymDefaultPath(); + this.debugObj = SharedObject.fromFile(dsymPath.ptr); + } + } - static Image openSelf() - { - return Image(_NSGetMachExecuteHeader()); + T processDebugLineSectionData(T)(scope T delegate(const(ubyte)[]) processor) { + return processor(debugObj.getSection("__DWARF", "__debug_line")); } - @property bool isValid() - { - return self !is null; + static Image openSelf() { + return Image(SharedObject.thisExecutable()); } - T processDebugLineSectionData(T)(scope T delegate(const(ubyte)[]) processor) - { - c_ulong size; - auto data = getsectiondata(self, "__DWARF", "__debug_line", &size); - return processor(data[0 .. size]); + @property bool isValid() { + return self.isValid; } - @property size_t baseAddress() - { - return 0; + @property size_t baseAddress() { + return cast(size_t)self.baseAddress; } } diff --git a/runtime/druntime/src/core/internal/macho/dl.d b/runtime/druntime/src/core/internal/macho/dl.d new file mode 100644 index 00000000000..47586e2ac61 --- /dev/null +++ b/runtime/druntime/src/core/internal/macho/dl.d @@ -0,0 +1,228 @@ +/** + * Simplifies working with shared Macho-O objects of the current process. + * + * Copyright: Copyright Kitsunebi Games 2025 + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Luna (the Foxgirl) Nielsen + * Source: $(DRUNTIMESRC core/internal/macho/dl.d) + */ + +module core.internal.macho.dl; + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (Darwin): + +import core.sys.darwin.mach.getsect : mach_header_64, getsectiondata, getsectbynamefromheader_64; +import core.sys.darwin.dlfcn; +import core.sys.darwin.fcntl; +import core.sys.darwin.sys.mman; +import core.sys.posix.sys.stat; +import core.sys.darwin.mach.loader; +import core.sys.darwin.mach.dyld : + _dyld_image_count, + _dyld_get_image_name, + _dyld_get_image_header, + _dyld_get_image_vmaddr_slide; + +/** + Enables iterating over the process' currently loaded shared objects. +*/ +struct SharedObjects { +@nogc nothrow: + /// + alias Callback = int delegate(SharedObject); + + /// + static int opApply(scope Callback dg) + { + foreach(i; 0.._dyld_image_count) { + if (int result = dg(SharedObject.fromIndex(i))) + return result; + } + return 0; + } +} + +/** + A loeaded mach-o binary. +*/ +struct SharedObject { +@nogc nothrow: +private: + mach_header_64* _header; + ptrdiff_t vmaddr_slide; + const(char)* _name; + +public: + + /** + Returns the shared object with the given index. + */ + static SharedObject fromIndex(uint idx) { + return SharedObject( + cast(mach_header_64*)_dyld_get_image_header(idx), + _dyld_get_image_vmaddr_slide(idx), + _dyld_get_image_name(idx) + ); + } + + /** + Returns the shared object with the given dlopen handle. + + Params: + dlhandle = Handle returned by dlopen + + Returns: + A SharedObject instance matching the given dlhandle, + or an empty handle on failure. + */ + static SharedObject fromHandle(void* dlhandle) { + foreach(so; SharedObjects) { + if (auto hndl = dlopen(so.name, RTLD_NOLOAD)) { + dlclose(hndl); + + if (hndl is dlhandle) + return so; + } + } + return SharedObject.init; + } + + /** + Returns the shared object with the given dlopen handle. + + Params: + path = Path to the object to load. + + Returns: + A SharedObject instance matching the given path, + or an empty handle on failure. + */ + static SharedObject fromFile(const(char)* path) { + if (auto hndl = dlopen(path, 0)) + return SharedObject.fromHandle(hndl); + + mach_header_64* base_header = cast(mach_header_64*)_dyld_get_image_header(0); + + // Try opening and mapping the file. + int fd = open(path, O_RDONLY); + if (fd == -1) + return SharedObject.init; + + stat_t fdInfo; + if (fstat(fd, &fdInfo) == -1) + return SharedObject.init; + + void* data = mmap(null, fdInfo.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + return SharedObject.init; + + // Non-fat mach-o object. + mach_header* hdr = cast(mach_header*)data; + if (hdr.magic == MH_MAGIC || hdr.magic == MH_MAGIC_64) { + if (hdr.cputype != base_header.cputype || hdr.cpusubtype != base_header.cpusubtype) { + munmap(data, fdInfo.st_size); + return SharedObject.init; + } + + return SharedObject(cast(mach_header_64*)data, -1, path); + } + + // Fat binary. + fat_header* fat = cast(fat_header*)data; + if (fat.magic == [0xca, 0xfe, 0xba, 0xbe]) { + fat_entry* entry = cast(fat_entry*)(data+fat_header.sizeof); + foreach(i; 0..fat.count) { + if (entry.cputype == base_header.cputype && entry.cpusubtype == base_header.cpusubtype) { + return SharedObject(cast(mach_header_64*)(data+entry.file_offset), -1, path); + } + + entry++; + } + } + + // Remember to unmap the file. + munmap(data, fdInfo.st_size); + return SharedObject.init; + } + + /** + Returns the object of the current process' executable. + */ + static SharedObject thisExecutable() { + return SharedObject.fromIndex(0); + } + + /** + Whether the object is valid. + */ + @property bool isValid() { + return _header !is null; + } + + /** + The name of this object. + */ + @property const(char)* name() { + return _name; + } + + /** + The base address of this object. + */ + @property void* baseAddress() { + return cast(void*)_header; + } + + /** + The mach header of the image. + */ + @property mach_header_64* header() { + return _header; + } + + /** + The virtual memory slide for the image. + */ + @property ptrdiff_t slide() { + return vmaddr_slide; + } + + /** + Gets whether the given section is present. + */ + bool hasSection(const(char)* segname, const(char)* sectname) { + return getSection(segname, sectname).length > 0; + } + + /** + Gets the given section within the shared object/image. + */ + ubyte[] getSection(const(char)* segname, const(char)* sectname) { + + // mmapped mach-o + if (vmaddr_slide == -1 && _header) { + if (auto sect = getsectbynamefromheader_64(_header, segname, sectname)) { + return (cast(ubyte*)_header+sect.offset)[0..sect.size]; + } + return null; + } + + // linked mach-o + if (_header) { + size_t len; + if (auto data = getsectiondata(_header, segname, sectname, &len)) + return data[0..len]; + } + + return null; + } +} diff --git a/runtime/druntime/src/core/internal/macho/io.d b/runtime/druntime/src/core/internal/macho/io.d new file mode 100644 index 00000000000..b2afc2a9b5e --- /dev/null +++ b/runtime/druntime/src/core/internal/macho/io.d @@ -0,0 +1,77 @@ +/** + * Provides (read-only) memory-mapped I/O for Mach-O files. + * + * + * Copyright: Copyright Kitsunebi Games 2025 + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Luna (the Foxgirl) Nielsen + * Source: $(DRUNTIMESRC core/internal/macho/io.d) + */ + +module core.internal.macho.io; + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (Darwin): + +import core.stdc.stdio : snprintf; +import core.stdc.stdlib : free, malloc; +import core.sys.darwin.mach.dyld : _NSGetExecutablePath; + +/** + Returns the path to the process' executable as newly allocated slice. +*/ +char[] thisExePath() { + uint len; + if (_NSGetExecutablePath(null, &len) != -1) + return null; + + auto buffer = cast(char*) malloc(len); + if (!buffer) + return null; + + if (_NSGetExecutablePath(buffer, &len) != 0) { + free(buffer); + return null; + } + + return buffer[0..len]; +} + +/** + Gets the base file name of the given path. +*/ +char[] getBaseName(char[] path) { + size_t i; + foreach_reverse(j; 0..path.length) { + if (path[j] == '/') { + i = j; + break; + } + } + return path[i+1..$]; +} + +/** + Gets the default path of a dSYM file. +*/ +char[] getDsymDefaultPath() { + enum DSYM_SUBPATH_FMT = "%s.dSYM/Contents/Resources/DWARF/%s"; + + char[] exePath = thisExePath(); + char[] exeBase = exePath.getBaseName(); + int len = snprintf(null, 0, DSYM_SUBPATH_FMT, exePath.ptr, exeBase.ptr); + + char* fullname = cast(char*)malloc(len+1); + len = snprintf(fullname, len+1, DSYM_SUBPATH_FMT, exePath.ptr, exeBase.ptr); + + free(exePath.ptr); + return fullname[0..len]; +} \ No newline at end of file diff --git a/runtime/druntime/src/core/sys/darwin/mach/loader.d b/runtime/druntime/src/core/sys/darwin/mach/loader.d index 7713eea9785..a3b2e530730 100644 --- a/runtime/druntime/src/core/sys/darwin/mach/loader.d +++ b/runtime/druntime/src/core/sys/darwin/mach/loader.d @@ -3423,3 +3423,18 @@ struct note_command ulong offset; ulong size; } + +struct fat_header +{ + ubyte[4] magic; + uint count; +} + +struct fat_entry +{ + uint cputype; + uint cpusubtype; + uint file_offset; + uint size; + uint alignment; +} diff --git a/runtime/druntime/src/rt/sections_elf_shared.d b/runtime/druntime/src/rt/sections_elf_shared.d index 83c0f236ef2..40d7919b032 100644 --- a/runtime/druntime/src/rt/sections_elf_shared.d +++ b/runtime/druntime/src/rt/sections_elf_shared.d @@ -84,7 +84,6 @@ else version (Darwin) else static assert(0, "Not implemented for this architecture"); - extern(C) intptr_t _dyld_get_image_slide(const mach_header*) nothrow @nogc; } else version (NetBSD) { From 3c679f5bd4b9e923d70462e3e8243f6ef9d2d186 Mon Sep 17 00:00:00 2001 From: Luna Date: Sun, 6 Jul 2025 16:27:53 +0200 Subject: [PATCH 6/9] Fix image slide --- runtime/druntime/src/core/internal/backtrace/macho.d | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/druntime/src/core/internal/backtrace/macho.d b/runtime/druntime/src/core/internal/backtrace/macho.d index e6e7809846e..2f66f991f13 100644 --- a/runtime/druntime/src/core/internal/backtrace/macho.d +++ b/runtime/druntime/src/core/internal/backtrace/macho.d @@ -51,6 +51,8 @@ struct Image { } @property size_t baseAddress() { - return cast(size_t)self.baseAddress; + if (debugObj.slide == -1) + return self.slide; + return 0; } } From fa92a470ccbe25b7d9e102056216bb9f805e6abc Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 7 Jul 2025 10:57:10 +0200 Subject: [PATCH 7/9] Fix typo --- runtime/druntime/src/core/internal/macho/dl.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/druntime/src/core/internal/macho/dl.d b/runtime/druntime/src/core/internal/macho/dl.d index 47586e2ac61..66b3afeb083 100644 --- a/runtime/druntime/src/core/internal/macho/dl.d +++ b/runtime/druntime/src/core/internal/macho/dl.d @@ -52,7 +52,7 @@ struct SharedObjects { } /** - A loeaded mach-o binary. + A loaded mach-o binary. */ struct SharedObject { @nogc nothrow: From c3e7ec5eeeb91eb988430a33b41c6fc6e56844bf Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 7 Jul 2025 19:37:32 +0200 Subject: [PATCH 8/9] Add dSYM generation to exception druntime tests --- runtime/druntime/test/exceptions/Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime/druntime/test/exceptions/Makefile b/runtime/druntime/test/exceptions/Makefile index 7565e425060..b1d74b92dd8 100644 --- a/runtime/druntime/test/exceptions/Makefile +++ b/runtime/druntime/test/exceptions/Makefile @@ -67,6 +67,12 @@ ifeq ($(OS),windows) endif include ../common.mak +# Generate dSYMs on macOS. +$(OBJDIR)/%$(DOTEXE).dSYM: $(OBJDIR)/%$(DOTEXE) + @[ "$(OS)" = "osx" ] && dsymutil -o $<.dSYM $< + +$(TESTS:%=$(OBJDIR)/%.done): $(OBJDIR)/%.done: $(OBJDIR)/%$(DOTEXE) $(OBJDIR)/%$(DOTEXE).dSYM + $(ROOT)/line_trace.done: $(ROOT)/line_trace$(DOTEXE) @echo Testing line_trace $(TIMELIMIT)$(ROOT)/line_trace > $@ From 93bc61eb79b6d7c9379b3f807f591dee7d55ff09 Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 8 Jul 2025 10:36:49 +0200 Subject: [PATCH 9/9] Apply @the-horo's runtime unittest patch --- runtime/druntime/test/exceptions/Makefile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/runtime/druntime/test/exceptions/Makefile b/runtime/druntime/test/exceptions/Makefile index b1d74b92dd8..5ea164d01b0 100644 --- a/runtime/druntime/test/exceptions/Makefile +++ b/runtime/druntime/test/exceptions/Makefile @@ -69,9 +69,15 @@ include ../common.mak # Generate dSYMs on macOS. $(OBJDIR)/%$(DOTEXE).dSYM: $(OBJDIR)/%$(DOTEXE) - @[ "$(OS)" = "osx" ] && dsymutil -o $<.dSYM $< + dsymutil $< -$(TESTS:%=$(OBJDIR)/%.done): $(OBJDIR)/%.done: $(OBJDIR)/%$(DOTEXE) $(OBJDIR)/%$(DOTEXE).dSYM +ifeq ($(OS),osx) +tests_without_exe = line_trace_21656 rt_trap_exceptions_drt_gdb +exes = $(filter-out $(tests_without_exe),$(TESTS)) +$(exes:%=$(OBJDIR)/%.done): $(OBJDIR)/%.done: $(OBJDIR)/%$(DOTEXE) $(OBJDIR)/%$(DOTEXE).dSYM +$(OBJDIR)/line_trace_21656.done: $(OBJDIR)/line_trace$(DOTEXE).dSYM +$(OBJDIR)/rt_trap_exceptions_drt_gdb.done: $(OBJDIR)/rt_trap_exceptions_drt$(DOTEXE).dSYM +endif $(ROOT)/line_trace.done: $(ROOT)/line_trace$(DOTEXE) @echo Testing line_trace