Repository navigation
feat(relinker): support --trace-imports for Linux output - #7417
oneandonlydean merged 5 commits into
Conversation
| } | ||
|
|
||
| void ReportOnAbort(int number, siginfo_t* info, void* context) { | ||
| std::fprintf(stderr, "[libc] SIGABRT\n"); |
There was a problem hiding this comment.
The SIGABRT handler calls fprintf here, then ReportCallTrace() calls dladdr, stdio, and the trace-copy routines. These are not async-signal-safe; if abort() is raised while the thread holds a stdio/loader/trace lock, this handler can deadlock before the prior handler runs or the process terminates. Could the trace be emitted from a non-signal-context abort path (or otherwise use a signal-safe handoff) so the diagnostic cannot turn an abort into a hang?
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
3847840 to
0e0bda8
Compare
921e403 to
3a2c02f
Compare
Build the call tracer for Linux as well as Windows. The thunk area is a memfd mapped twice, writable and executable, and threads are named by gettid. Linux has no crash report, so the first traced import installs a SIGABRT handler that prints the last calls and the most called imports, then passes the signal on to the previous handler. guest_call_trace and guest_call_trace_abort now run on Linux too.
The SIGABRT handler used stdio and dladdr, so an abort raised while another thread held the stderr lock or the loader lock hung instead of ending. The report is now formatted into a stack buffer and written with write(2). Callers are named from a module list taken with dl_iterate_phdr when imports are traced, and import counts are read without a lock from a fixed record array. The abort test now holds the stderr lock in another thread, which timed out before this change.
A thread that lost the race to write the report passed SIGABRT on straight away, and the default action ended the process while the first thread was still writing. It now waits for the report to finish, polling with nanosleep for at most 2 seconds, before it chains. guest_call_trace_abort_race aborts from two threads at once and checks the whole report; it failed 35 times in 50 without the wait. Reported by jotitadev.
The Linux entry stub now passes each traced import through libc.prx's TraceImport_nid_no_patch before it calls the guest entry, and stores the returned thunk in the import's slot. The same imports are traced as on Windows: functions bound through a PLT slot or a GOT slot, without an addend, and not weak. TraceImport_nid_no_patch is bound eagerly through an added GLOB_DAT relocation, so a libc.prx without it fails at load. Each slot's page is made writable with mprotect first, so slots inside a PT_GNU_RELRO range can be replaced. --trace-imports now requires libc.prx among the libraries on Linux too, and is refused with --macos.
3a2c02f to
78634fb
Compare
Resolve docs/dev/debt/libc.md: keep main's _Throw_Cpp_error entry and this branch's call tracing entry.
What
--trace-imports(#4547) only worked for Windows output. This adds it for Linux output, as the second half of #3815's Linux support, on top of the libc runtime in #7411.With the flag,
LinuxElfPatchernow:TraceImport_nid_no_patchsymbol and aGLOB_DATrelocation for it, bound eagerly like every other import, so alibc.prxwithout it fails at load with ld.so's undefined symbol error;--linux-fixed-baseoutput alike;TraceImport_nid_no_patch(*slot, name, library)and stores the result back in the slot, then falls through to the usual stub.rdx(ld.so's fini pointer) and the stack are preserved.The same imports are traced as on Windows: functions bound through a PLT slot, or through a GOT slot whose symbol is a function, with no addend, and not weak.
Before storing, the prologue makes each slot's page readable, writable and executable with
mprotect. Today the Linux output dropsPT_GNU_RELRO, but #6406 keeps it, and real eboots have theirJUMP_SLOTtargets inside it. Without this, the store would fault once both are merged. I merged #6406 locally with a RELRO range over the slot's page and the traced output still ran correctly. The trade-off (those pages stay writable, and data pages become executable while tracing) is recorded indocs/dev/debt/relinker.md.Other changes:
--trace-importsno longer requires--windows. It is refused with--macos, and it still requireslibc.prxamong the libraries and conflicts with--lazy-binding.TRACING.mdand the debt entries now cover Linux.Tested
Linux x86-64, GCC 13. Relinker-only build: the full suite of 195 tests passes.
linux_trace_importstest. It builds a fakea.prxand a fakelibc.prxwithcc, likelinux_executable_exports, and runs the relinked fixture. Its hook replacesa.prx'ssharedwith a function that exits with 77.--linux-fixed-base.libc.prxwithout the hook fails at load naming the symbol.libc.prxdependency,--lazy-bindingand--macos.windows_trace_importsloses only its "requires--windows" assertion.libc.prxfrom feat(libc): record traced system library calls on Linux #7411:APS5_TRACE_CALLS=a ./out.elfon a fixture whose import callsabort()prints:Not tested on a real title, Windows or macOS.
python3 tools/check_conventions.py --base origin/mainpasses.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)