Skip to content

Replace atos backtrace generation with a dSYM loader. #4930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

LunaTheFoxgirl
Copy link
Contributor

@LunaTheFoxgirl LunaTheFoxgirl commented May 15, 2025

This pull-request replaces the atos based backtrace generation with a small dSYM loader that maps the default dSYM location of a executable into the address space of the application.

This loader looks for a .dSYM bundle in the executable's directory, maps it into memory and passes it on to the DWARF state machine. It will not, however, map any dSYMs of loaded shared libraries currently; this is planned to be addressed in a future PR. Symbolicating OS libraries and symbolication by UUID are non-goals.

Outdated Info

Using atos is not portable, given that:
1. It's a developer tool that may or may not exist on a given system
2. Said tool relies on being able to execute external applications at runtime; something the hardened runtime heavily restricts
3. Apple does not support this usecase.

As an alternative a weak `rt_dwarfSymbolicate` symbol has been added, allowing dub libraries to implement this functionality instead so that the developer can opt-in to this non-portable behaviour.

Down the line I can investigate making a naïve dSYM lookup system, but without OS help -- that Apple will reject on AppStore review -- full coverage will be unlikely.

@rikkimax
Copy link
Contributor

Feedback that I gave on Discord, replicating here as requested.

The reasons for using dladdr and not using atos ext. should be documented as a comment in the code, so future people know why it was done this way, and when to trigger a replacement.

location.file = info.dli_fname[0..strlen(info.dli_fname)];
location.procedure = info.dli_sname[0..strlen(info.dli_sname)];
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more or less already handled earlier:

// https://code.woboq.org/userspace/glibc/debug/backtracesyms.c.html
// The logic that glibc's backtrace use is to check for for `dli_fname`,
// the file name, and error if not present, then check for `dli_sname`.
// In case `dli_fname` is present but not `dli_sname`, the address is
// printed related to the file. We just print the file.
static const(char)[] getFrameName (const(void)* ptr)
{
import core.sys.posix.dlfcn;
Dl_info info = void;
// Note: See the module documentation about `-L--export-dynamic`
if (dladdr(ptr, &info))
{
// Return symbol name if possible
if (info.dli_sname !is null && info.dli_sname[0] != '\0')
return info.dli_sname[0 .. strlen(info.dli_sname)];
// Fall back to file name
if (info.dli_fname !is null && info.dli_fname[0] != '\0')
return info.dli_fname[0 .. strlen(info.dli_fname)];
}
// `dladdr` failed
return "<ERROR: Unable to retrieve function name>";
}

Copy link
Contributor Author

@LunaTheFoxgirl LunaTheFoxgirl May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good to know, then this can just be a no-op stub. I am calling it for the day (spent 6 hours getting ldc2 compiling due to fighting with llvm and cmake for some time. Did get it working eventually)

Copy link
Member

@kinke kinke May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. The hook's job is to populate the file and line fields of the Locations, if possible. So the name could reflect that (resolveSourceLocs() or so).

Edit: Well, that's the primary job at least. There's nothing standing in the way of improving/adding the symbol names too. E.g., to make it work without -L--export-dynamic too.

Copy link
Contributor Author

@LunaTheFoxgirl LunaTheFoxgirl May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've called it symbolicate since it may, on macOS, also end up handling symbolication of Objective-C which requires another kind of roundtrip not related to DWARF. So in general it's supposed to be a hook that pre-symbolicates stuff (including the name of the procedure if relevant). Then letting the default implementation handle anything left over.

@schveiguy
Copy link
Contributor

Needs a cleanup function in case you need to allocate and free.

Do we need to have a function that processes the entire array at once? Why not a function that works on a single stack frame?

@kinke
Copy link
Member

kinke commented May 15, 2025

Do we need to have a function that processes the entire array at once? Why not a function that works on a single stack frame?

That gives greatest flexibility and reduces the need for some state/cache somewhere. The implementation will most likely have to read files etc., so processing the whole batch at once makes sense.

Edit: See e.g. the default implementation, processing the debug_line section until all addresses have been resolved:

/**
* Resolve the addresses of `locations` using `debugLineSectionData`
*
* Runs the DWARF state machine on `debugLineSectionData`,
* assuming it represents a debugging program describing the addresses
* in a continous and increasing manner.
*
* After this function successfully completes, `locations` will contains
* file / lines informations.
*
* Note that the lifetime of the `Location` data is bound to the lifetime
* of `debugLineSectionData`.
*
* Params:
* debugLineSectionData = A DWARF program to feed the state machine
* locations = The locations to resolve
* baseAddress = The offset to apply to every address
*/
void resolveAddresses(const(ubyte)[] debugLineSectionData, Location[] locations, size_t baseAddress) @nogc nothrow
{
size_t numberOfLocationsFound = 0;
const(ubyte)[] dbg = debugLineSectionData;
while (dbg.length > 0)
{
debug(DwarfDebugMachine) printf("new debug program\n");
const lp = readLineNumberProgram(dbg);
LocationInfo lastLoc = LocationInfo(-1, -1);
const(void)* lastAddress;
debug(DwarfDebugMachine) printf("program:\n");
runStateMachine(lp,
(const(void)* address, LocationInfo locInfo, bool isEndSequence)
{
// adjust to ASLR offset
address += baseAddress;
debug (DwarfDebugMachine)
printf("-- offsetting %p to %p\n", address - baseAddress, address);
foreach (ref loc; locations)
{
// If loc.line != -1, then it has been set previously.
// Some implementations (eg. dmd) write an address to
// the debug data multiple times, but so far I have found
// that the first occurrence to be the correct one.
if (loc.line != -1)
continue;
// Can be called with either `locInfo` or `lastLoc`
void update(const ref LocationInfo match)
{
// File indices are 1-based for DWARF < 5
const fileIndex = match.file - (lp.dwarfVersion < 5 ? 1 : 0);
const sourceFile = lp.sourceFiles[fileIndex];
debug (DwarfDebugMachine)
{
printf("-- found for [%p]:\n", loc.address);
printf("-- file: %.*s\n",
cast(int) sourceFile.file.length, sourceFile.file.ptr);
printf("-- line: %d\n", match.line);
}
// DMD emits entries with FQN, but other implementations
// (e.g. LDC) make use of directories
// See https://github.com/dlang/druntime/pull/2945
if (sourceFile.dirIndex != 0)
loc.directory = lp.includeDirectories[sourceFile.dirIndex - 1];
loc.file = sourceFile.file;
loc.line = match.line;
numberOfLocationsFound++;
}
// The state machine will not contain an entry for each
// address, as consecutive addresses with the same file/line
// are merged together to save on space, so we need to
// check if our address is within two addresses we get
// called with.
//
// Specs (DWARF v4, Section 6.2, PDF p.109) says:
// "We shrink it with two techniques. First, we delete from
// the matrix each row whose file, line, source column and
// discriminator information is identical with that of its
// predecessors.
if (loc.address == address)
update(locInfo);
else if (lastAddress &&
loc.address > lastAddress && loc.address < address)
update(lastLoc);
}
if (isEndSequence)
{
lastAddress = null;
}
else
{
lastAddress = address;
lastLoc = locInfo;
}
return numberOfLocationsFound < locations.length;
}
);
if (numberOfLocationsFound == locations.length) return;
}
}

@JohanEngelen
Copy link
Member

I did not test this, but it looks like a regression to me (no more line info). I'm OK with making the atos thing opt-in, but I don't understand why it must be removed. Basically again we are left with backtraces without line info? Having to resort to a (yet non-existing) external implementations is very bad imo.

@LunaTheFoxgirl
Copy link
Contributor Author

dladdr has been removed and a cleanup hook added. Also moved them be run before and after the rest of the symbol resolution.

@LunaTheFoxgirl
Copy link
Contributor Author

LunaTheFoxgirl commented May 16, 2025

I did not test this, but it looks like a regression to me (no more line info). I'm OK with making the atos thing opt-in, but I don't understand why it must be removed. Basically again we are left with backtraces without line info? Having to resort to a (yet non-existing) external implementations is very bad imo.

As said in #4895, having good stack traces is important, but those stack traces should not come at the cost of it being practically infeasible to use D to release stuff on the AppStore. At this point D is already capable of being used for that, and at least in my business it's a part of my future strategy to release i(Pad)OS versions of my software.

While yes, we could make the runtime only compile in atos stuff in debug mode, that does also mean there's extra friction for developers using DLang to develop apps; and also that it's likely to be useless anyways on Darwin derived OSes that don't have atos.

@JohanEngelen
Copy link
Member

While yes, we could make the runtime only compile in atos stuff in debug mode, that does also mean there's extra friction for developers using DLang to develop apps;

What is that extra friction? Do people put debug builds in the app store?

and also that it's likely to be useless anyways on Darwin derived OSes that don't have atos.

This is not a valid argument to make things worse for macOS.

How about providing a separate small library with rt_dwarfSymbolicate overrides that call into atos, so that people can at least have the old (better ;)) behavior by adding -lsomelib to the compiler invoke? (something similar to the ldc_rt.dso.obj lib on windows)

@LunaTheFoxgirl
Copy link
Contributor Author

LunaTheFoxgirl commented May 16, 2025

While yes, we could make the runtime only compile in atos stuff in debug mode, that does also mean there's extra friction for developers using DLang to develop apps;

What is that extra friction? Do people put debug builds in the app store?

and also that it's likely to be useless anyways on Darwin derived OSes that don't have atos.

This is not a valid argument to make things worse for macOS.

How about providing a separate small library with rt_dwarfSymbolicate overrides that call into atos, so that people can at least have the old (better ;)) behavior by adding -lsomelib to the compiler invoke? (something similar to the ldc_rt.dso.obj lib on windows)

This is not only about app store, it also affects things such as making builds with the hardened runtime. Which can end up making your life a headache if you want to sign and notarize your apps as well (including for macOS). Over all, I don't agree that this is the correct approach here.

And yeah, some may want to put release builds with debug info, for example, onto the app store. and also may not know to pass in extra LDC specific flags to link to the non-debug version of druntime, etc. While those can be somewhat solved by making dub more intelligent on the matter, it's just overall a massive ugly hack and your application freezing for a couple of seconds while atos runs every time you generate a stack trace is a little excessive.

Also if you only care about macOS, just use lldb, it'll do the symbolication for you when something crashes. It comes with the dev tools.

@LunaTheFoxgirl
Copy link
Contributor Author

Also as a side-note, it's the default behaviour to generate debug symbols for releases with i(Pad)OS apps and the like, xcode does it for you on compile when using swift or Objective-C; so it is over-all expected that you will in fact have the debug symbols there even if it's technically in release mode. So if we for example, just detected whether the auto-generated dSYMs were there (which apple also forcefully does for you once you publish an app) instead of tying it to release-debug; then atos would still possibly be run. Additionally having strings in druntime relating to atos might be enough to trigger a review rejection.

@schveiguy
Copy link
Contributor

My opinion is that the easy experience for D should have stack traces with file/line. You need them when you are learning D, so that is what should be the default.

I'm OK with making D easier to release on the app store, and I'm hoping there's a way we can make it easy to do (it's OK to require some extra effort for this, with documentation). But it would be bad for the first experience with D to be unreadable stack traces.

@LunaTheFoxgirl
Copy link
Contributor Author

LunaTheFoxgirl commented May 16, 2025

My opinion is that the easy experience for D should have stack traces with file/line. You need them when you are learning D, so that is what should be the default.

I'm OK with making D easier to release on the app store, and I'm hoping there's a way we can make it easy to do (it's OK to require some extra effort for this, with documentation). But it would be bad for the first experience with D to be unreadable stack traces.

As I said in the other thread. Besides providing these hooks. Making a new utility that embeds dSYMs into the dwarf section might be the way to go. Means LDC avoids ugly hacks in its source tree that just makes it more difficult for existing D users to get their work done. My main goal right now is to ensure we get the ugly hacks rooted out that breaks production software for businesses like mine or Auburn Sounds who rely on D and LDC to make a living.

Once that's done it'll be easier to take a wholistic look at how best to approach the shortcomings removing these hacks create, through better tooling or writing custom implementations where needed.

@kinke
Copy link
Member

kinke commented May 16, 2025

My opinion is that the easy experience for D should have stack traces with file/line. You need them when you are learning D, so that is what should be the default.

Yeah my main concern is exactly that - the default experience on a dev box at least should be stack traces with resolved source Locs. Ideally with an acceptable runtime overhead etc., but that's secondary.

So a solution that depends on libatos (dynamically, i.e., copes with it not being available) would be fine to me as well - we depend on poorly documented stuff on Darwin already, that whole TLS disaster with macOS 15.4 was caused by Apple removing an API in macOS 10.15, and Jacob having to re-implement those finicky details in upstream druntime again. This stuff breaking was just a question of time I guess, and might happen again anytime.

@rikkimax
Copy link
Contributor

One option here might be to add a dedicated object file that is linked automatically for executables on Posix. Pass a switch and it won't do this.

That object file can contain the atos stuff, giving the desired default.

However, this is unnecessary if dlopen approach can ship.

@LunaTheFoxgirl
Copy link
Contributor Author

One option here might be to add a dedicated object file that is linked automatically for executables on Posix. Pass a switch and it won't do this.

That object file can contain the atos stuff, giving the desired default.

However, this is unnecessary if dlopen approach can ship.

I'm not sure it can, Apple also scans strings. They'd easily find out that you are trying to load atos or that you are at some point in the application's life cycle.

If you try to hide this kind of stuff from them, they might revoke your dev and signing access.

@LunaTheFoxgirl
Copy link
Contributor Author

On a second thought. Im currently down with a bit of a cold; I think I should have a harder think about what kind of tooling could be made to make things work out once I'm feeling better. So do hold off on merging this.

@dnadlinger
Copy link
Member

dnadlinger commented May 16, 2025

Note that compiler-rt itself, in the sanitizer runtime, also shells out to atos on macOS for symbolizing. Has this caused any concrete issues with D projects being submitted to the App Store? Without any receipts to that end, I'm not sure whether there is something to fix here (though of course the implementation might have issues; #4895).

Using private frameworks directy is another issue, but we are not doing that. Of course, a more elegant solution could always be nice, but might be quite a bit of engineering effort.

@LunaTheFoxgirl
Copy link
Contributor Author

Note that compiler-rt itself, in the sanitizer runtime, also shells out to atos on macOS for symbolizing. Has this caused any concrete issues with D projects being submitted to the App Store? Without any receipts to that end, I'm not sure whether there is something to fix here (though of course the implementation might have issues; #4895).

Using private frameworks directy is another issue, but we are not doing that. Of course, a more elegant solution could always be nice, but might be quite a bit of engineering effort.

compiler-rt and the ASAN are not compiled in when you make release versions, so there it's irrelevant. The problem is that private APIs shouldn't make their way into production software; while you're developing and testing locally with self signed certs, it doesn't matter as much, even if you may end up with your application crashing due to using these things with no backtrace. At least you know what you're getting into there.

@kinke
Copy link
Member

kinke commented Jun 3, 2025

v1.41.0 final won't take too long anymore, I wanna release it in the next 2 weeks or so.

How about making the existing resolveAddressesWithAtos() weak (incl. renaming + extern(C)), and allowing the user to override it with a one-line empty dummy function to disable atos? [I guess that could be wrapped in a dub sourceLibrary project - the only requirement is that the object file with the custom strong symbol is linked.]

@LunaTheFoxgirl
Copy link
Contributor Author

I'll get back to this soonish; have some other stuff happening which means I need to focus on some other things.
Instead of going the atos route I'm thinking of adding so that a dSYM file is attempted to be located and loaded instead; since dSYM files are just mach-o files that only contains DWARF sections.

@LunaTheFoxgirl
Copy link
Contributor Author

LunaTheFoxgirl commented Jul 6, 2025

I've rewritten this PR to now include a subsystem that can mmap dSYM files when the line sections aren't in the file.
This does mean dsymutil will need to be run to get backtraces.

If someone more experienced with the DWARF format could give me some hints on how to make this properly work, that'd be great. The file is read and I get no crashes, but so far it seems I don't get any line info.

@LunaTheFoxgirl LunaTheFoxgirl changed the title Remove atos for backtrace generation. Replace atos backtrace generation with a dSYM loader. Jul 6, 2025
@LunaTheFoxgirl
Copy link
Contributor Author

Update: I got it working!

luna@feixiao n_test % cat test.d
import std.stdio;
import core.sys.darwin.dlfcn;

void main() {
	myFunction();
}


void myFunction() {
	throw new Exception("Test");
}
luna@feixiao n_test % rm test test.o; ldc2-dev -g test.d; dsymutil test; ./test
object.Exception@test.d(10): Test
----------------
test.d:10 void test.myFunction() [0x10057c993]
test.d:5 _Dmain [0x10057c8fb]

Comment on lines +69 to +75
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)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the _dyld_get_image_ functions are discouraged from being used. Can we use dladdr to get the mach_header instead? (not sure how to get the slide).

Copy link
Contributor Author

@LunaTheFoxgirl LunaTheFoxgirl Jul 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These specific functions are still documented as usable by Apple in their man pages in Tahoe; so I don't think that's a problem? Alternatively yeah; we'd have to calculate the slide ourselves to account for address space randomisation; which would require a lot more work.

The functions which Apple does not support are the get_image functions that take a mach header, to be specific. The ones that take an image index are still available.

Copy link
Contributor

@jacob-carlborg jacob-carlborg Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jacob-carlborg
Copy link
Contributor

Perhaps we could get some tests?

@LunaTheFoxgirl
Copy link
Contributor Author

Perhaps we could get some tests?

I want to, but I have no idea where to slot in dsymutil in the makefiles in the runtime tests. They're quite confusingly set up.

@the-horo
Copy link
Contributor

the-horo commented Jul 7, 2025

Perhaps we could get some tests?

I want to, but I have no idea where to slot in dsymutil in the makefiles in the runtime tests. They're quite confusingly set up.

The closest example I can think of is

$(ROOT)/%.done: $(ROOT)/%$(DOTEXE)
@echo Testing $*
$(TIMELIMIT)$< $(run_args) 2>$(ROOT)/$*.stderr || true
@if $(negate) grep -qF $(stderr_exp) $(ROOT)/$*.stderr ; then true ; else \
echo 'Searched for pattern $(stderr_exp), NEGATE = $(negate)' ;\
tail --bytes=5000 $(ROOT)/$*.stderr ;\
exit 1 ;\
fi
@if [ ! -z $(stderr_exp2) ] ; then \
if $(negate) grep -qF $(stderr_exp2) $(ROOT)/$*.stderr ; then true ; else \
echo 'Searched for '$(stderr_exp2)' NEGATE = $(negate)' ;\
tail --bytes=5000 $(ROOT)/$*.stderr ;\
exit 1 ;\
fi \
fi
@touch $@

If it helps you your example can be roughly translated as:

diff --git a/runtime/druntime/test/dsymutil/Makefile b/runtime/druntime/test/dsymutil/Makefile
new file mode 100644
index 0000000000..9059819c2b
--- /dev/null
+++ b/runtime/druntime/test/dsymutil/Makefile
@@ -0,0 +1,49 @@
+ifdef IN_LDC
+# Include this for OS
+include ../../../../dmd/osmodel.mak
+endif
+
+ifeq ($(OS),osx)
+# Only define the tests on macos
+endif
+TESTS := my_example
+
+
+# common.mak picks up TESTS and defines default build rules
+include ../common.mak
+
+# Adds -g to all built executables
+$(OBJDIR)/%$(DOTEXE): private extra_dflags += -g
+
+# Tell make how to produce the .dSYM from an object file
+$(OBJDIR)/%$(DOTEXE).dSYM: $(OBJDIR)/%$(DOTEXE)
+	dsymutil $<
+
+# Defines how to run the tests, they require the executable to be build and dsymutil to have been run.
+# And force use this rule instead of the one created by common.mak
+$(TESTS:%=$(OBJDIR)/%.done): $(OBJDIR)/%.done: $(OBJDIR)/%$(DOTEXE) $(OBJDIR)/%$(DOTEXE).dSYM
+	@echo Testing $*
+
+# Run the program and capture stderr
+# Print an error or something if the program didn't fail
+	if $(TIMELIMIT)$< 2>$(OBJDIR)/$*.stderr; then \
+		echo "Program completed unexpectedly. It should have failed" ; \
+		exit 1 ; \
+	fi
+
+# Check the stderr for a pattern
+	if ! grep -q "^object.Exception@src/my_example.d(10): Test$$" $(OBJDIR)/$*.stderr; then \
+		echo "The stderr is not alright" ; \
+		cat $(OBJDIR)/$*.stderr ; \
+		exit 1 ; \
+	fi
+
+# Or check for an exact file match, using sed to remove unportable address and other stuff
+# If you do this add `%.expected` to the target for make to properly track the dependency
+	if ! sed \
+		"s|^.*/src/|src/|g; s/\[0x[0-9a-f]*\]/\[ADDR\]/g; s/scope //g; s/Nl//g" $(OBJDIR)/$*.stderr \
+		| diff -q $*.expected -; then \
+		echo "The stderr did not match $*.expected exactly" ; \
+		cat $(OBJDIR)/$*.stderr ; \
+		exit 1 ; \
+	fi
diff --git a/runtime/druntime/test/dsymutil/my_example.expected b/runtime/druntime/test/dsymutil/my_example.expected
new file mode 100644
index 0000000000..acc96ac820
--- /dev/null
+++ b/runtime/druntime/test/dsymutil/my_example.expected
@@ -0,0 +1,4 @@
+object.Exception@src/my_example.d(10): Test
+----------------
+src/my_example.d:10 void test.myFunction() [ADDR]
+src/my_example.d:5 _Dmain [ADDR]
\ No newline at end of file
diff --git a/runtime/druntime/test/dsymutil/src/my_example.d b/runtime/druntime/test/dsymutil/src/my_example.d
new file mode 100644
index 0000000000..30e8600295
--- /dev/null
+++ b/runtime/druntime/test/dsymutil/src/my_example.d
@@ -0,0 +1,11 @@
+import std.stdio;
+import core.sys.darwin.dlfcn;
+
+void main() {
+	myFunction();
+}
+
+
+void myFunction() {
+	throw new Exception("Test");
+}

I'm not on a mac so I couldn't test it at all but you can try using this template.

@LunaTheFoxgirl
Copy link
Contributor Author

Tried adapting the suggestion to the exception tests; seems it just broke them instead.

@the-horo
Copy link
Contributor

the-horo commented Jul 7, 2025

Can you try:

$(OBJDIR)/%$(DOTEXE).dSYM: $(OBJDIR)/%$(DOTEXE)
	dsymutil $<

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

edit: ifeq ($(OS),osx) instead of ifeq ($(OS),"osx")

@the-horo
Copy link
Contributor

the-horo commented Jul 8, 2025

@LunaTheFoxgirl
Copy link
Contributor Author

ok the failing tests there are unrelated to the PR and related to dynamic compile

Copy link
Contributor

@jacob-carlborg jacob-carlborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The style guide is not entirely followed, but I'll let someone else worry about that.

@jacob-carlborg
Copy link
Contributor

Are there any plans to support reading the debug info from object files as well?

@LunaTheFoxgirl
Copy link
Contributor Author

Are there any plans to support reading the debug info from object files as well?

This would be far more involved work. For now I think just using dSYM is the most sensible way.
Especially if we add support in dub for automating this step.

@JohanEngelen
Copy link
Member

Does this detect stale dSYM files? (I don't see where it does that check)

@JohanEngelen
Copy link
Member

I still think we should let other tools do all the heavy lifting, rather than implement something (far inferior?) ourselves and pray it won't break with Apple's next OS release...

https://wiki.dwarfstd.org/Apple%27s_%22Lazy%22_DWARF_Scheme.md is a nice read by the way, it mentions the many things that are needed to make this work well.

@LunaTheFoxgirl
Copy link
Contributor Author

Does this detect stale dSYM files? (I don't see where it does that check)

It does not; but would be relatively trivial to add.

@LunaTheFoxgirl
Copy link
Contributor Author

LunaTheFoxgirl commented Jul 8, 2025

I still think we should let other tools do all the heavy lifting, rather than implement something (far inferior?) ourselves and pray it won't break with Apple's next OS release...

https://wiki.dwarfstd.org/Apple%27s_%22Lazy%22_DWARF_Scheme.md is a nice read by the way, it mentions the many things that are needed to make this work well.

Those tools are only available on macOS. This is the only portable way we can do this.

Additionally due to the sanity checks I've put in place; if Apple breaks something it shouldn't lead to crashes.

This is far more preferable than backtraces only working on macOS, and the backtraces causing the application to freeze for a few seconds; which in debugging situations like on iPadOS can cause the application to be killed by the os for being unresponsive.

@JohanEngelen
Copy link
Member

Does this detect stale dSYM files? (I don't see where it does that check)

It does not; but would be relatively trivial to add.

please do. It is very easy to forget to run dsymutil and run into weird issues.

Another thing to add is to copy clang's behavior of automatically running dsymutil when building full executable (non-separate compilation); that would achieve (almost, not for separate compilation case) parity with the current state.

@JohanEngelen
Copy link
Member

I still think we should let other tools do all the heavy lifting, rather than implement something (far inferior?) ourselves and pray it won't break with Apple's next OS release...
https://wiki.dwarfstd.org/Apple%27s_%22Lazy%22_DWARF_Scheme.md is a nice read by the way, it mentions the many things that are needed to make this work well.

Those tools are only available on macOS. This is the only portable way we can do this.

It's not the only way, alternatives debated before.

Additionally due to the sanity checks I've put in place; if Apple breaks something it shouldn't lead to crashes.

Be aware of what you are signing up to. If things break, who is going to fix it? Will we again have years (!) of no debug info on macOS?

This is far more preferable than backtraces only working on macOS, and the backtraces causing the application to freeze for a few seconds; which in debugging situations like on iPadOS can cause the application to be killed by the os for being unresponsive.

Preference is subjective, and I expect the vast majority of Apple LDC users to use it for macOS, not on iPadOS etc. I expect there to again be reports of "debug info not working", time will tell.

@LunaTheFoxgirl
Copy link
Contributor Author

I still think we should let other tools do all the heavy lifting, rather than implement something (far inferior?) ourselves and pray it won't break with Apple's next OS release...
https://wiki.dwarfstd.org/Apple%27s_%22Lazy%22_DWARF_Scheme.md is a nice read by the way, it mentions the many things that are needed to make this work well.

Those tools are only available on macOS. This is the only portable way we can do this.

It's not the only way, alternatives debated before.

Additionally due to the sanity checks I've put in place; if Apple breaks something it shouldn't lead to crashes.

Be aware of what you are signing up to. If things break, who is going to fix it? Will we again have years (!) of no debug info on macOS?

This is far more preferable than backtraces only working on macOS, and the backtraces causing the application to freeze for a few seconds; which in debugging situations like on iPadOS can cause the application to be killed by the os for being unresponsive.

Preference is subjective, and I expect the vast majority of Apple LDC users to use it for macOS, not on iPadOS etc. I expect there to again be reports of "debug info not working", time will tell.

Mainly me, my business is gearing up to release software for macOS, iOS, etc.
I practically volunteered to help maintain this stuff; so if it breaks again do ping me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants