Refix: Fast, Debuggable, Reproducible Builds
yosefk.com
yosefk.com
so we've never had the problem the author set to resolve -- despite each of us (and the ci) having different source tree locations, gdb picks up the source code just fine.
i guess build systems people use love canonicalising source paths?
Note that in general, relative paths create 2 problems:
1. You [and tools] don't know where the code is just by inspecting the binary;
2. There may be more than one answer to the question relative to what?
In this sense, a relative path is potentially even worse than "an absolute path to a directory created during the build that was since deleted", since there's substitute-path [in gdb, not necessarily any other DWARF consumer, but at least in gdb] and you could find currently existing paths corresponding to the absolute paths in the build and remap them (unless of course they were all the same absolute path like /tmp/xxx but they were directories on different build hosts or created at different build phases - but you hope it won't get this bad.) With relative paths, you have no reasonable way of remapping different meanings of "./" to different absolute paths since "./" is always "./" (though you might remap differently based on what follows "./" in the path; but it's getting really painful at this point.)
This "relative to what"? question is not a problem if you have a monolithic build (which is awesome, everyone should!) with all the source kept under the same root directory and then "relative to what?" has the sane single answer "relatively to that root directory." This can be a big problem if there are multiple build systems building multiple binaries in different root directories and a linker then links those libraries or they're shared objects loaded at runtime and ending up in the same process or core dump.
Your bug reporting tool should obviously report the version of the software the dump is associated with. It's common to just put the git hash in the binary.
> 2. There may be more than one answer to the question relative to what?
The directory CI runs in. If you're not sure, check your CI scripts.
2. As discussed in GP, there can be more than one such directory if your build isn't monolithic (which it should be, but it often isn't)
How is knowing the filenames going to help with that?
> What if the code is compiled from multiple repositories? I should now clone them all?
Presumably you already have a CI script that does this. If not, invest a few minutes into making one.
I can't imagine it's really that slow to edit the paths properly. It's probably just accidental N^2 or something.
Also it feels like there should be a proper field in ELF/DWARF for this if people are wanting to do it a lot (and it seems like a reasonable thing to do).
Regarding replacing the paths "properly": it's not accidentally quadratic, it's predictably linear but with large constants because there's a lot of data to move and a lot of offsets to update. An example of such a problem (linear complexity, large constants) is the linker. It took more than 40 years for C++ linkers to get fast; mold is the linker that I always said was possible (a very fast one) but that I didn't actually implement. Now it's implemented and it takes 5-10 seconds to link that large binary. This is roughly the speed that you can hope for if you not only postulate the possibility of fast DWARF editing like I used to do with fast linking but actually implement it; this will still be an order of magnitude at least slower than refix but it will match the speed of sed. Of course since we had to wait for more than 40 years for a fast linker and a linker is something nobody can do without and everyone wants to be fast, we will have to wait for more like 400 years for anyone to actually optimize debugedit to that level. And I much prefer the small annoyance of having leading ////// in the file paths to waiting for 400 years for a less buggy and faster debugedit and then waiting 5-10 seconds per rebuild.
I agree that it would have been great if the DWARF format as well the actual DWARF producer made it easier to change path prefixes, and it'd be especially great if it worked with compressed debug sections without having to uncompress and recompress them.
https://github.com/TLeonardUK/ds3os/blob/main/default.nix#L4...
Also: if you really, really want to never go looking for the sources ― just store them in the finished executable/debug info file. Seriously. For some reason, they're (when gzip'ed) somehow smaller than the resulting executable with debug info.
Regarding debugger path remapping - there are a lot of tools consuming debug info, not just "the" debugger, and not all have the setting; assert line info printout doesn't have a setting like that. It's also extra hassle to remap even if you can.
As to storing the sources in the binary - it can be nice but it's going to be slower for various reasons. How big they are depends in part on how good you are at putting just the code you actually compiled there which is not always easy. And when assert fails you need to extract it before looking at it; everything now has this step.
The point of refix is to make sources easier / trivial to find with very minor build flow changes - just a compiler flag and a tens of ms long post build step.
In my case at work (and we actually use Linux), we sadly don't have that luxury: the CI is walled of and its disk is purged every midnight, so knowing what folder the project was built in is pointless since it doesn't even contains the commit hash, it has the CI's unique job number instead but even it is also useless since the CI for some reason stores info only about 3 days of build jobs so... yeah. We resort to baking in the git info as a recognizable string into the binary, and then checking out the sources manually.
P.S. IIRC, PDB's are matched to the EXE's by the EXE's checksum so generally if the PDB is checked in into a symbol server, then retrieving it back is simple, if you know what source server to query, of course.
> Why not fix the binary coming out of the build cache, so it points to the absolute path of the source files?
What is the absolute path? If you had a virtual file system that allowed you to construct a path to any file at a given commit, this would work great. But who does that other than Google? Or if you agree that every developer will check out the same source code repo at the same path, but the you have to have the right commit checked out.
Ideally you would want your binary to point back to your code repo, like SourceLink does.
If it's a CI build, I recommend exporting source files to NAS, and define a retention policy. If the org is averse to NAS despite this use case being fine and not exposing the weaknesses of NAS, you could define the absolute path as /local/commit-id/whatever and have a system where the source gets closed there relatively easily. Note that either way CI needn't build with source at the path you refix to at the post link step, and with NAS it definitely shouldn't since for this use case NAS is very bad.
Some issues with debuginfod you won't have with "refixed" binaries:
* If an assert fails, you still don't get an absolute path you can open in an editor in the error message
* This assumes the debug info including the source code was uploaded to the server. This can work for "external releases" but do you want this to happen with every build done by every user? This would be notably slower than refixing the binary. If you don't do this, then for every build you didn't do this for, you still have the problem
* Not all tools consuming DWARF support this. gdb and Valgrind do; does VTune or the sanitizers which emit call stacks with source lines you then want to open in an editor?
* Implementing this reasonably is just much more work than passing 2 flags to gcc and running one post-link pass on every binary coming out of the build system
(1) Well, "much worse" in this specific sense. debuginfod does solve a different problem of distributing debug information to people who have a binary installation without debug info on demand. At this it's infinitely better than refix which doesn't help here at all. However this is not how I'd like to work on my employer's software as a developer.
Not only slower than the rust version, also slower than most other >200 string search functions. Whilst musl has the fastest string search function for less than 32 substring chars.
See https://rurban.github.io/smart/results/all/rand2.html with libc1 being GNU libc memmem() and musl1 being musl's memmem().
> "Well you're not supposed to get core dumps from versions with uncommitted changes, unless it's your local version that you haven't given to anyone but are testing locally, so you know which version it is. You should only release versions externally thru CI" - so giving anything to anyone to test is now considered "releasing externally" and must necessarily go thru CI, and having trouble finding the source code is now a punishment for straying from proper procedure? How did this discussion, which started at how build caches speed up the build, deteriorate to the point where we're telling developers to change how they work, in ways which will slow them down?
Hey, you brought it up. Don't put this on the reader.
This is about bad situations like... here are some examples:
* If you read the description of Wheel format (Python packages), the name of the archive allows one to include eg. build number in it, but states such packages shouldn't be released publicly. (I've never tested this with PyPI.org, but wouldn't be surprised if they actually allow such packages, because see next).
* PyPI allows uploading packages with non-release versions, i.e. X.Y.Za1 or X.Y.Zb2 or X.Y.Zrc3. That is the public index intended for production use allows non-production versions. This usually doesn't bother Python programmers because they rarely use anything other than pip to install packages, whose crafty defaults allow to filter out non-released versions. This, of course, breaks when easy_install gets into the picture (and that happens when developers run something like "setup.py install").
Now, where this is all going: the protocol for working with publicly released software should include the release versions (if you want to debug it, send bug reports etc.) This is to prevent from overloading maintainers with investigation of custom configurations and the infinite number of things users could do with the software when not following the explicit usage / build instructions.
You, among friends, don't have to follow the protocol, as long as your friends are OK with it. But, as a general advise, OP is right, this is how you behave nicely, and if you don't do that the users will be upset, and you'll get a lot of extra work with a ton of criticism on top of it.
Great article!
> Not sure about the unrestricted voice, it might get me in trouble
None of your recent tweets (on teams/managers, on tests, on tooling etc) have got you in to any lasting trouble... have they? ;)
If I had just read your comment I would have thought that he was a rookie/newcomer writer.
[1] https://rizin.re