Core dump deduplication#193
Conversation
mplegendre
left a comment
There was a problem hiding this comment.
One big change needed around PHDR lookups from link maps. Otherwise it's small changes.
I didn't in-depth examine the server and network code. I expect we'll be adjusting that to fit in the crashing logging, and I'll take more of a look then.
| static int crash_write_fd = -1; | ||
| static int crash_installed = 0; | ||
|
|
||
| static char crash_altstack_buf[CRASH_ALTSTACK_SIZE]; |
There was a problem hiding this comment.
That's a decent size static buffer, and I'm expecting we'll default run with the ALTSTACK off (once we get the configuration options to make that disable-able). How about making this a pointer instead of a buffer, and doing an anonymous mmap on it if we're using ALTSTACK.
There was a problem hiding this comment.
I've replaced the static buffer as requested
| #include "client.h" | ||
| #include "client_heap.h" | ||
| #include "client_api.h" | ||
| #if defined(CRASH_HANDLER_ENABLED) |
There was a problem hiding this comment.
Can we remove the CRASH_HANDLER_ENABLED define in general? I'd prefer to keep ifdefs to the minimum. Otherwise it's easy to get compilation errors that can slip in with rarely tested define combinations.
There are, of course, necessary defines such as the architecture specific code below. I've no objections to those. And there are occasional places where you absolutely need the define to compile, such as library/header existance.
As an alternative you can use the define in the FE config parsing to decide of crash handling is on/off by default (or make the define hardcode to off there). Then use a dynamic test throughout the rest of the code base.
There was a problem hiding this comment.
I've removed CRASH_HANDLER_ENABLED; the crash handler is now always compiled.
| const ElfW(Phdr) *phdrs; | ||
| unsigned int i; | ||
|
|
||
| if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) |
There was a problem hiding this comment.
This test won't work for libraries with compile-time-determined load addresses. You don't see as much of them anymore, but for a while RHEL was shipping system libraries that were pre-compiled to always load at the same address in every process, which produced faster load times but were more vulnerable to injection attacks. It's still legal to make libraries that do that, even if not common.
For these libraries the load_base will be 0. You calculated symbol/table/etc address in memory by adding the binary's 'offset' (which was the compile-time memory address) to the 0 load base.
This code would fault reading from 0 if it found one of those libraries.
It's absolutely obnoxious to get phdrs from link_maps (no idea why they didn't just give us a pointer). We'd previously talked about using dl_iterate_phdr at library load time to maintain a mapping of link maps to phdrs (which might be useful to other parts of spindle). I think that's still the best bet.
There was a problem hiding this comment.
dl_iterate_phdr turned out not to work because it only iterates over the objects in the current namespace, so instead we read the program headers from the file. New tests have been added with an executable and a library with a fixed load address.
| return 0; | ||
| } | ||
|
|
||
| static int find_pc_et_exec(unsigned long pc) |
There was a problem hiding this comment.
And this needs the load_base passed to it in order to handle PIE executables.
If you get the above find_pc_et_dyn function fixed up correctly, you should be able to use the same code for executables and libraries. Each has corner cases where they behave like the other, so you essentially need the same code to handle both.
There was a problem hiding this comment.
We now handle PIE executables, and I've added a test with a PIE executable.
| break; | ||
| rd = (const struct r_debug *) | ||
| ((const struct r_debug_ext_mirror *) rd)->r_next; | ||
| } |
There was a problem hiding this comment.
This is exciting. The r_debug extended is new enough I've never used it or run with it. But I also can't easily glance to know if this is right. And I don't see a test that would trigger this in the testsuite.
Have you run any spindle client crashes through this system? That would exercise this.
There was a problem hiding this comment.
There was a test that crashes in a dlmopen'ed library, but it actually happened to pass even without this because the fallback was still able to deduplicate. I've changed the test so that it now checks for both correct deduplication, and that the crash site key contained the correct library.
| #ifdef PROCMAP_QUERY | ||
| /* Linux 6.11 and later let us query /proc/pid/maps without textual | ||
| parsing. This stores whether we can do that. */ | ||
| static int crash_maps_query_supported = 0; |
There was a problem hiding this comment.
I didn't know about PROCMAP_QUERY. Generally looks fine for this.
As a tangent... this is a new kernel API that could reveal spindle's changes to an application. We may need to start intercepting IOCTL and looking for/fixing PROCMAP_QUERY. But that doesn't need doing here.
|
|
||
| /* The remaining possible fixes only apply to faulting on an | ||
| addresss (SIGSEGV, SIGBUS). */ | ||
| if (sig != SIGSEGV && sig != SIGBUS) |
There was a problem hiding this comment.
I'm unsure, but are you confident SIGBUS should be here? Are there situations/applications that hit a SIGBUS and take corrective action?
There was a problem hiding this comment.
There is a case where the application can fix SIGBUS: reading past the end of a memory-mapped file produces SIGBUS, and the application's handler can fix it by extending the file. I've added two tests for this: one where the application handler extends the file (and Spindle shouldn't and doesn't handle it as a crash), and one where the application handler doesn't (and Spindle should and does handle it).
Adds server-side components of crash handling. Client sends LDCS_MSG_CRASH_REPORT to sever. Server propagates request. When a single rank is chosen for a given crashsite, replies with LDCS_MSG_CRASH_RESPONSE.
Add the client-side crash handler. On a fatal signal, it chains to an application-registered signal handler, if any, and, if the application handler did not fix the fault, sends LDCS_MSG_CRASH_REPORT to the server which selects a single process per crashsite. Non-selected crashsites set their coredump limit to zero.
Adds --crash-dedup option to control whether crash deduplication is enabled.
Adds --enable-crash-handler to control whether the crash handler is compiled. Adds --enable-crash-dedup to control whether crash deduplication is enabled by default at runtime.
Adds a new script run_crash_tests_template.sh which runs various configurations of the new crash_test.c testsuite. This tests various kinds of crashes, numberes of distinct crashsites, etc. to validate that the crash handler deduplicates them correctly. It also tests chaining to application signal handlers that use the safepoint pattern to fix faults and retry.
Run the new crash handler testsuite in CI. Configure CI runner to generate coredumps, add shared filesystem to aggregate coredumps across nodes in mutli-node tests. Grants CAP_SYS_RESOURCE so ulimit can be set in containers.
autotools-generated files re-generated
c5a3b61 to
46fa81e
Compare
This PR adds a feature to Spindle to deduplicate core dumps by unique crash site, so that only one representative core dump is produced for a given crash site.
The operation is as follows:
In order to work with application-registered signal handlers, Spindle intercepts
sigaction,signal,bsd_signalandsysv_signaland stores handlers registered through these. To remain invisible to the application, queries for registered signal handlers returns the application's handler rather than Spindle's, orSIG_DFLif no application handler has been registered.There is a new set of tests which test various crash patterns (for example, all same crash sites; all different crash sites; two sets; mixed abort and segfault). There are also tests which verify that we get the correct faulting page when an access spans two pages with different permissions. There are a set of tests of application handlers which test the safepoint pattern as used by Java and Julia. A test driver script runs the tests and verifies that they crash or exit cleanly as required; that the correct number of core dumps was produced; and that the top frames of the core dumps match the expected crash site.
Known Limitations
I have observed intermittent failures in GitHub Actions where the Flux tests or the Slurm plugin tests hang. I believe these are existing issues and not due to the code in this PR, as we have seen the same failures in the existing devel branch, and, in the case of the Slurm plugin hang, the logs indicate a problem in cachepath consensus.