Skip to content

Commit bd9cda3

Browse files
Add dSYM mapper
1 parent 0fe3c91 commit bd9cda3

File tree

6 files changed

+345
-52
lines changed

6 files changed

+345
-52
lines changed

runtime/druntime/src/core/internal/backtrace/dwarf.d

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -185,18 +185,12 @@ int traceHandlerOpApplyImpl(size_t numFrames,
185185
startIdx = idx + 1;
186186
}
187187

188-
// Symbolicate locations
189-
rt_dwarfSymbolicate(locations[startIdx .. $]);
190-
191188
if (!image.isValid())
192189
return locations[startIdx .. $].processCallstack(null, 0, dg);
193190

194191
// find address -> file, line mapping using dwarf debug_line
195192
return image.processDebugLineSectionData(
196193
(line) => locations[startIdx .. $].processCallstack(line, image.baseAddress, dg));
197-
198-
// Allow cleaning up after ourselves.
199-
rt_dwarfSymbolicateCleanup(locations[startIdx .. $]);
200194
}
201195

202196
struct TraceInfoBuffer
@@ -243,31 +237,6 @@ struct TraceInfoBuffer
243237
}
244238
}
245239

246-
/**
247-
* A weakly linked hook which can be implemented by external libraries
248-
* to extend the symbolication capabilites when debug info is missing.
249-
*
250-
* NOTE:
251-
* There used to be an atos based symbolication implementation built in
252-
* here, but atos is not a portable solution on darwin derived OSes.
253-
* atos conflicts with things such as the hardened runtime, iOS releases,
254-
* App Store certification and the like. I've removed that implementation
255-
* to ensure that D can easily be used to publish to the App Store.
256-
* Please avoid adding other private APIs in its place directly in to
257-
* druntime. If it resides in PrivateFrameworks or is a dev tool,
258-
* don't use it. - Luna
259-
*/
260-
@weak
261-
extern(C) void rt_dwarfSymbolicate(Location[] locations)
262-
{
263-
}
264-
265-
/// ditto
266-
@weak
267-
extern(C) void rt_dwarfSymbolicateCleanup(Location[] locations)
268-
{
269-
}
270-
271240
private:
272241

273242
int processCallstack(Location[] locations, const(ubyte)[] debugLineSectionData,

runtime/druntime/src/core/internal/backtrace/macho.d

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,37 @@ else version (WatchOS)
2020
version (Darwin):
2121

2222
import core.stdc.config : c_ulong;
23-
import core.sys.darwin.crt_externs : _NSGetMachExecuteHeader;
24-
import core.sys.darwin.mach.getsect : mach_header_64, getsectiondata;
25-
26-
struct Image
27-
{
28-
private mach_header_64* self;
23+
import core.stdc.stdlib : free;
24+
import core.internal.macho.dl;
25+
import core.internal.macho.io;
26+
27+
struct Image {
28+
SharedObject self;
29+
SharedObject debugObj;
30+
31+
this(SharedObject self) {
32+
this.self = self;
33+
this.debugObj = self;
34+
35+
if (!self.hasSection("__DWARF", "__debug_line")) {
36+
auto dsymPath = getDsymDefaultPath();
37+
this.debugObj = SharedObject.fromFile(dsymPath.ptr);
38+
}
39+
}
2940

30-
static Image openSelf()
31-
{
32-
return Image(_NSGetMachExecuteHeader());
41+
T processDebugLineSectionData(T)(scope T delegate(const(ubyte)[]) processor) {
42+
return processor(debugObj.getSection("__DWARF", "__debug_line"));
3343
}
3444

35-
@property bool isValid()
36-
{
37-
return self !is null;
45+
static Image openSelf() {
46+
return Image(SharedObject.thisExecutable());
3847
}
3948

40-
T processDebugLineSectionData(T)(scope T delegate(const(ubyte)[]) processor)
41-
{
42-
c_ulong size;
43-
auto data = getsectiondata(self, "__DWARF", "__debug_line", &size);
44-
return processor(data[0 .. size]);
49+
@property bool isValid() {
50+
return self.isValid;
4551
}
4652

47-
@property size_t baseAddress()
48-
{
49-
return 0;
53+
@property size_t baseAddress() {
54+
return cast(size_t)self.baseAddress;
5055
}
5156
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/**
2+
* Simplifies working with shared Macho-O objects of the current process.
3+
*
4+
* Copyright: Copyright Kitsunebi Games 2025
5+
* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6+
* Authors: Luna (the Foxgirl) Nielsen
7+
* Source: $(DRUNTIMESRC core/internal/macho/dl.d)
8+
*/
9+
10+
module core.internal.macho.dl;
11+
12+
version (OSX)
13+
version = Darwin;
14+
else version (iOS)
15+
version = Darwin;
16+
else version (TVOS)
17+
version = Darwin;
18+
else version (WatchOS)
19+
version = Darwin;
20+
21+
version (Darwin):
22+
23+
import core.sys.darwin.mach.getsect : mach_header_64, getsectiondata, getsectbynamefromheader_64;
24+
import core.sys.darwin.dlfcn;
25+
import core.sys.darwin.fcntl;
26+
import core.sys.darwin.sys.mman;
27+
import core.sys.posix.sys.stat;
28+
import core.sys.darwin.mach.loader;
29+
import core.sys.darwin.mach.dyld :
30+
_dyld_image_count,
31+
_dyld_get_image_name,
32+
_dyld_get_image_header,
33+
_dyld_get_image_vmaddr_slide;
34+
35+
/**
36+
Enables iterating over the process' currently loaded shared objects.
37+
*/
38+
struct SharedObjects {
39+
@nogc nothrow:
40+
///
41+
alias Callback = int delegate(SharedObject);
42+
43+
///
44+
static int opApply(scope Callback dg)
45+
{
46+
foreach(i; 0.._dyld_image_count) {
47+
if (int result = dg(SharedObject.fromIndex(i)))
48+
return result;
49+
}
50+
return 0;
51+
}
52+
}
53+
54+
/**
55+
A loeaded mach-o binary.
56+
*/
57+
struct SharedObject {
58+
@nogc nothrow:
59+
private:
60+
mach_header_64* _header;
61+
ptrdiff_t vmaddr_slide;
62+
const(char)* _name;
63+
64+
public:
65+
66+
/**
67+
Returns the shared object with the given index.
68+
*/
69+
static SharedObject fromIndex(uint idx) {
70+
return SharedObject(
71+
cast(mach_header_64*)_dyld_get_image_header(idx),
72+
_dyld_get_image_vmaddr_slide(idx),
73+
_dyld_get_image_name(idx)
74+
);
75+
}
76+
77+
/**
78+
Returns the shared object with the given dlopen handle.
79+
80+
Params:
81+
dlhandle = Handle returned by dlopen
82+
83+
Returns:
84+
A SharedObject instance matching the given dlhandle,
85+
or an empty handle on failure.
86+
*/
87+
static SharedObject fromHandle(void* dlhandle) {
88+
foreach(so; SharedObjects) {
89+
if (auto hndl = dlopen(so.name, RTLD_NOLOAD)) {
90+
dlclose(hndl);
91+
92+
if (hndl is dlhandle)
93+
return so;
94+
}
95+
}
96+
return SharedObject.init;
97+
}
98+
99+
/**
100+
Returns the shared object with the given dlopen handle.
101+
102+
Params:
103+
path = Path to the object to load.
104+
105+
Returns:
106+
A SharedObject instance matching the given path,
107+
or an empty handle on failure.
108+
*/
109+
static SharedObject fromFile(const(char)* path) {
110+
if (auto hndl = dlopen(path, 0))
111+
return SharedObject.fromHandle(hndl);
112+
113+
mach_header_64* base_header = cast(mach_header_64*)_dyld_get_image_header(0);
114+
115+
// Try opening and mapping the file.
116+
int fd = open(path, O_RDONLY);
117+
if (fd == -1)
118+
return SharedObject.init;
119+
120+
stat_t fdInfo;
121+
if (fstat(fd, &fdInfo) == -1)
122+
return SharedObject.init;
123+
124+
void* data = mmap(null, fdInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
125+
if (data == MAP_FAILED)
126+
return SharedObject.init;
127+
128+
// Non-fat mach-o object.
129+
mach_header* hdr = cast(mach_header*)data;
130+
if (hdr.magic == MH_MAGIC || hdr.magic == MH_MAGIC_64) {
131+
if (hdr.cputype != base_header.cputype || hdr.cpusubtype != base_header.cpusubtype) {
132+
munmap(data, fdInfo.st_size);
133+
return SharedObject.init;
134+
}
135+
136+
return SharedObject(cast(mach_header_64*)data, -1, path);
137+
}
138+
139+
// Fat binary.
140+
fat_header* fat = cast(fat_header*)data;
141+
if (fat.magic == [0xca, 0xfe, 0xba, 0xbe]) {
142+
fat_entry* entry = cast(fat_entry*)(data+fat_header.sizeof);
143+
foreach(i; 0..fat.count) {
144+
if (entry.cputype == base_header.cputype && entry.cpusubtype == base_header.cpusubtype) {
145+
return SharedObject(cast(mach_header_64*)(data+entry.file_offset), -1, path);
146+
}
147+
148+
entry++;
149+
}
150+
}
151+
152+
// Remember to unmap the file.
153+
munmap(data, fdInfo.st_size);
154+
return SharedObject.init;
155+
}
156+
157+
/**
158+
Returns the object of the current process' executable.
159+
*/
160+
static SharedObject thisExecutable() {
161+
return SharedObject.fromIndex(0);
162+
}
163+
164+
/**
165+
Whether the object is valid.
166+
*/
167+
@property bool isValid() {
168+
return _header !is null;
169+
}
170+
171+
/**
172+
The name of this object.
173+
*/
174+
@property const(char)* name() {
175+
return _name;
176+
}
177+
178+
/**
179+
The base address of this object.
180+
*/
181+
@property void* baseAddress() {
182+
return cast(void*)_header;
183+
}
184+
185+
/**
186+
The mach header of the image.
187+
*/
188+
@property mach_header_64* header() {
189+
return _header;
190+
}
191+
192+
/**
193+
The virtual memory slide for the image.
194+
*/
195+
@property ptrdiff_t slide() {
196+
return vmaddr_slide;
197+
}
198+
199+
/**
200+
Gets whether the given section is present.
201+
*/
202+
bool hasSection(const(char)* segname, const(char)* sectname) {
203+
return getSection(segname, sectname).length > 0;
204+
}
205+
206+
/**
207+
Gets the given section within the shared object/image.
208+
*/
209+
ubyte[] getSection(const(char)* segname, const(char)* sectname) {
210+
211+
// mmapped mach-o
212+
if (vmaddr_slide == -1 && _header) {
213+
if (auto sect = getsectbynamefromheader_64(_header, segname, sectname)) {
214+
return (cast(ubyte*)_header+sect.offset)[0..sect.size];
215+
}
216+
return null;
217+
}
218+
219+
// linked mach-o
220+
if (_header) {
221+
size_t len;
222+
if (auto data = getsectiondata(_header, segname, sectname, &len))
223+
return data[0..len];
224+
}
225+
226+
return null;
227+
}
228+
}

0 commit comments

Comments
 (0)