MuleDebug: translate runtime PCs back to link-time addresses for bfd - #677
Conversation
Issue amule-project#676 (danim7): amule's own wxASSERT / unhandled-exception backtrace shows "??" for every frame inside the amule binary even when built -DCMAKE_BUILD_TYPE=Debug with libbfd available; only wxWidgets / libc frames symbolicate. The bfd address resolver in MuleDebug.cpp can't handle PIE binaries. Modern Linux distros default to PIE for security (executable linked as if at virtual address 0, loaded at an ASLR-randomized base), so backtrace() returns runtime PCs like 0x55e7c40e4e60 while bfd's section->vma is the link-time vma in the low range. The section-bounds check if (address > (vma + size)) { return; } short-circuits on every PIE address, bfd_find_nearest_line is never called with a valid offset, s_found stays false, and the outer loop emits "??". For ET_EXEC (non-PIE) binaries the runtime PC equals the link-time vma so the check passes; that's why this used to work on the older default and stopped working silently as distros switched defaults. Capture the executable's PIE relocation offset once at init_backtrace_info() via dl_iterate_phdr -- dlpi_addr is 0 for ET_EXEC (non-PIE) and the random load address for ET_DYN (PIE), which is exactly the value to subtract from runtime PCs. In get_file_line_info() subtract s_pie_base from the incoming address before the section comparisons. Non-PIE behaviour is unchanged (s_pie_base == 0); PIE behaviour is fixed. Library frames (libc, libwx, ...) continue to be skipped by the section-bounds check as before -- their runtime addresses minus amule's relocation are still well outside amule's vma range. Windows (wxStackWalker -> dbghelp), macOS (dladdr-per-frame), and the addr2line fallback at HAVE_BFD-off all handle PIE natively and are not touched.
|
Just a question, if on Linux addr2line works, and wxStackWalker works on Mac, why using different paths. We could eliminate the need of bfd.h completely and use wxStackWalker everywhere. |
|
@Vollstrecker -- two factual nits on the premise first:
So today wxStackWalker runs only on Windows, and Linux/macOS each have their own symbolicator. Consolidating onto wxStackWalker everywhere is plausible but worth doing as a separate refactor, not folded into this PR. A few things to verify before pulling the trigger:
On balance I don't think the migration earns its keep: the dependency stays (just relocated into wx), three working code paths get replaced by one unknown-PIE-correctness code path, and the existing MuleDebug.cpp is small and now correct. Worth keeping the three direct paths and revisiting only if wxStackWalker's Linux/macOS support materially improves. |
|
Just tested the PR (already merged), and it works nice with bfd installed. After that, I uninstalled bfd ( (btw, very funny comments in MuleDebug.cpp :D) |
|
@danim7 -- pushed the addr2line fix as #678. Same shape as #677: subtract the PIE base from each address before invoking addr2line. The dl_iterate_phdr lookup is now factored into a small idempotent helper that both paths share. Verified on an Ubuntu VM: with libbfd present, behaviour is unchanged (regression check passes); with Thanks for catching the gap. |
same PIE bug. Reported by danim7 on the amule-project#677 thread after they uninstalled libbfd to test the fallback: amule frames came back as "??", and running addr2line manually with the same arguments produced the same empty output -- the addr2line tool expects link-time addresses, but the fallback hands it runtime PCs. Hoist the s_pie_base + find_pie_base_cb + dl_iterate_phdr plumbing out of the #ifdef HAVE_BFD block into the shared Linux scope so both paths can use it. Wrap the actual lookup in init_pie_base() (idempotent, no-op on subsequent calls) so the bfd path's init_backtrace_info() and the no-bfd path of get_backtrace() both seed it at the right moment without duplicating the dl_iterate_phdr call. In the no-bfd branch, translate each runtime PC by subtracting s_pie_base before formatting into the addr2line command line. For PIE binaries this yields the link-time address addr2line expects. For non-PIE binaries s_pie_base is 0 (dlpi_addr is 0 for ET_EXEC) so the subtraction is a no-op and the addr2line invocation is byte-identical to before -- no regression possible on the non-PIE path. Symmetric to amule-project#677. The two paths now use the same offset under the same conditions and would either both work or both break on the same kind of input.
same PIE bug. Reported by danim7 on the #677 thread after they uninstalled libbfd to test the fallback: amule frames came back as "??", and running addr2line manually with the same arguments produced the same empty output -- the addr2line tool expects link-time addresses, but the fallback hands it runtime PCs. Hoist the s_pie_base + find_pie_base_cb + dl_iterate_phdr plumbing out of the #ifdef HAVE_BFD block into the shared Linux scope so both paths can use it. Wrap the actual lookup in init_pie_base() (idempotent, no-op on subsequent calls) so the bfd path's init_backtrace_info() and the no-bfd path of get_backtrace() both seed it at the right moment without duplicating the dl_iterate_phdr call. In the no-bfd branch, translate each runtime PC by subtracting s_pie_base before formatting into the addr2line command line. For PIE binaries this yields the link-time address addr2line expects. For non-PIE binaries s_pie_base is 0 (dlpi_addr is 0 for ET_EXEC) so the subtraction is a no-op and the addr2line invocation is byte-identical to before -- no regression possible on the non-PIE path. Symmetric to #677. The two paths now use the same offset under the same conditions and would either both work or both break on the same kind of input.
CamuleApp (amule.cpp) and CamuleDaemonApp both install a wxApp::OnFatalException override that prints a libbfd/addr2line- resolved in-process backtrace on SIGSEGV / SIGBUS / SIGABRT via get_backtrace(). CamuleRemoteGuiApp doesn't, so when amulegui crashes the wx default handler runs and exits without producing any amule frames -- making diagnosis of GTK-callback-into-stale- widget bugs (such as #692, search-tab close while results are streaming) a guessing game even with a saved core, because the core itself only sees gtk_main / libgtk frames and the systemd coredump trace can't see into amule's process state at crash time. Mirror the existing CamuleApp::OnFatalException body in CamuleRemoteGuiApp so amulegui's next crash dumps the same backtrace amule(d) already do -- including the libbfd PIE fixes from #677 / #678 that recently went in. Body is intentionally a copy of CamuleApp::OnFatalException (only the program name in the message differs) rather than refactored into CamuleAppCommon: the latter is not a wxApp derivative so the virtual override has to live on each wxApp-derived class anyway, and a shared helper for the 18-line message string is not worth the indirection. #if wxUSE_ON_FATAL_EXCEPTION guard matches amule.cpp.
The GNOME 49 SDK ships libbfd (in /usr/lib + bfd.h in /usr/include),
so cmake/bfd.cmake auto-detects it and links aMule against
libbfd-2.46.so. The GNOME 49 *Runtime* (the tree shipped to end
users alongside the bundle) does NOT ship libbfd — it's a
development library, not a runtime one — so installed flatpaks
abort at startup:
amule: error while loading shared libraries: libbfd-2.46.so:
cannot open shared object file
Every Linux user installing the 3.0.0 flatpak bundle hits this today
(amule-org#13).
Add an ENABLE_BFD option to amule's CMake (default ON, so distro
packagers, the AppImage build, native local builds and CI keep
using libbfd exactly as before) and gate the cmake/bfd.cmake
include on it. Pass -DENABLE_BFD=NO from the flatpak manifest to
disable libbfd linkage there.
MuleDebug.cpp's existing !HAVE_BFD fallback already handles this:
backtrace() + backtrace_symbols() still emit the stack with
function names, and the code shells out to addr2line for source
line info when available. The post-amule-project#677 PIE-base translation keeps
addresses meaningful on modern PIE binaries. Net effect on flatpak
crash reports: slightly less detailed (no in-process file:line
resolution) but still actionable.
Only the flatpak channel is affected. AppImage and native packages
keep symbol-resolved backtraces via libbfd.
…RL (amule-project#677) Follow-ups to amule-project#663: - Give the Kad tab the same full-width first row as the ED2K pane: the nodes-list URL refresher (update button, label, URL entry) with the connect/disconnect toggle right-aligned, above the stats graph and the Bootstrap box. Previously the toggle sat inside the 2-column grid, so a right-aligned toggle landed mid-pane instead of at the pane edge. - macOS rendered the server-list and nodes-list URL entries two lines tall: their rows carry an icon-bearing button (taller than a one-line field on macOS) and the entries used Expand(), which stretched them to match. Switch both to CenterVertical() (still proportion 1 horizontally) so they stay one line.
…age (amule-project#685) The Networks tab strip carried a single ConnectButton that toggled ED2K and Kad together, while each tab had its own state-blind Connect/Disconnect buttons. That mirrored neither the REST API (already symmetric via POST /networks/{connect,disconnect} with {network: "ed2k"|"kad"|"both"}) nor amulegui after amule-project#663/amule-project#677, and it meant only the global button reflected the actual connection state. Each network tab now owns one state-aware toggle for its own network. Colour and label follow the real state from the SSE status_changed event — a red/amber/green plug reading "ED2K: Connected", "Kad: Connecting…" — while the click performs the opposite action. "connecting" counts as up, so a Kad that is running-but-not-routing (the backend collapses that into "connecting") stays stoppable; this matches CKadDlg::OnBnClickedDisconnectKad. There is no confirmation dialog, matching the buttons it replaces. Disconnecting both networks at once is no longer offered. Kad's "Connect from known clients" button is not lost: it issued the same EC_OP_KAD_START the toggle does, exactly as amulegui's ID_KNOWNNODECONNECT handler is a bare StartKad() call. "Bootstrap from node" is a genuinely different operation (EC_OP_KAD_BOOTSTRAP_FROM_IP) and is untouched, as is the per-row connect that dials one specific server. The page is now a single view file. servers.js, kad.js, ed2k.js and logs.js were imported by networks.js and nothing else, so splitting them only bought a five-request, two-wave waterfall behind the lazy route import in app.js RouteView — the browser had to parse networks.js before it could discover the other four. At 410 lines the merged file sits alongside preferences.js (411) and download-detail.js (429), which are single-file multi-tab views already; Networks was the only page split up. split-detail.js stays separate, being shared with Downloads and Shared files. Merging also let some duplication and dead code go: - stat() was defined identically in kad.js and ed2k.js; one copy remains, and the two log panels now share a logBox() helper. - Three data.ensureStatus() calls in the panels were no-ops. Shell already calls it unconditionally for every route (app.js) and it is guarded by statusActive, so Ed2kInfoPanel no longer needs an effect at all. - NetworkConnectButton lives in the view rather than components.js: it is Networks-only, and keeping it in components.js would have shipped it eagerly to every page. - app.css: the button is now a .btn, so the tool-btn-derived sizing and the dead .tabs-extra .conn-btn overrides are gone. The state colours and .tabs-extra itself stay (still used by the Downloads category filters). - i18n: no new keys — networks_tab_ed2k/_kad and app_connect* already cover the label. Eight keys left with no reference are removed, and the missing networks_kad_conn_disabled is added; without it the Kad info panel printed a raw key string whenever Kad was stopped.
…ct#683) Shrinks the per-tab connect button and names its network (Connect/Disconnect/Cancel ED2K|Kad), adds top padding to both network tabs' first row, and scales the button icon to a uniform DPI-aware size with a per-(state, size) cache. Bitmap margins are wxOSX-only: wxMSW keeps its native font-derived default, wxGTK keeps the leading-space fallback since it has no margin support. Kad tab redesigned to mirror the ED2K tab: the graph spans the full width with the bootstrap-from-node row beneath it, and the four-octet IP entry collapses to a single trimmed x.x.x.x field. Drops two redundant controls: the inert global Connect toolbar button (no event binding since amule-project#663/amule-project#677) and the "Bootstrap from known clients" button, which called the same StartKad() as the Kad tab's own Connect toggle.
Fixes #676.
What
amule's own
wxASSERT/ unhandled-exception backtrace shows??for every frame inside the amule binary even when built-DCMAKE_BUILD_TYPE=Debugwithlibbfd-devavailable; only wxWidgets / libc frames symbolicate. The bfd resolver inMuleDebug.cppcan't handle PIE binaries.Modern Linux distros default to PIE for security: the executable is linked as if at virtual address
0and loaded at an ASLR-randomized base.backtrace()returns runtime PCs like0x55e7c40e4e60, while bfd'ssection->vmais the link-time vma (low range,0x1000-based on PIE). The section-bounds check at MuleDebug.cpp:278short-circuits on every PIE runtime PC,
bfd_find_nearest_lineis never reached,s_foundstays false, the outer loop emits??. For ET_EXEC (non-PIE) binaries the runtime PC equals the link-time vma so the check passed -- that's why this used to work on the older default and stopped working silently as distros switched defaults.How
Capture the executable's PIE relocation offset once at
init_backtrace_infoviadl_iterate_phdr.dlpi_addris0for ET_EXEC (non-PIE) and the random load address for ET_DYN (PIE), which is exactly the value to subtract from runtime PCs. Inget_file_line_infosubtracts_pie_basefrom the incoming address before the section comparisons. Library frames (libc, libwx, ...) continue to be skipped by the section-bounds check -- their runtime addresses minus amule's relocation are still well outside amule's vma range.Scope
addr2line -e /proc/<pid>/exefallback already handles PIE; not touched.__APPLE__block usesdladdrper-frame, already handles PIE; not touched.__WINDOWS__block useswxStackWalker-> dbghelp, already ASLR-aware; not touched.s_pie_base == 0so behaviour is unchanged. No regression possible.Status
Builds clean on macOS (the file still parses through the
__APPLE__path) and on Linux Debug + libbfd (amule-dev-vm).