Skip to content

Commit 58cfc66

Browse files
committed
pstack: add C++ name demangling
I noticed today that the real pstack script (i.e. GDB) was printing fully-qualified, demangled C++ names. It occurred to me that we could probably do this in our script too. The result works quite well. Signed-off-by: Stephen Brennan <[email protected]>
1 parent e47a548 commit 58cfc66

File tree

1 file changed

+80
-3
lines changed

1 file changed

+80
-3
lines changed

drgn_tools/pstack.py

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,20 @@
2424
"""
2525
import argparse
2626
import base64
27+
import ctypes.util
2728
import fnmatch
2829
import gzip
2930
import json
3031
import logging
3132
import os
3233
import struct
3334
import sys
35+
import warnings
3436
from bisect import bisect_left
37+
from functools import lru_cache
3538
from pathlib import Path
3639
from typing import Any
40+
from typing import Callable
3741
from typing import Dict
3842
from typing import List
3943
from typing import Tuple
@@ -534,6 +538,62 @@ def read_fn(_, count, offset, __):
534538
return prog
535539

536540

541+
@lru_cache(maxsize=1)
542+
def _load_demangler() -> Callable[[str], str]:
543+
# The GNU C++ standard library contains a function called __cxa_demangle
544+
# which we can use to translate C++ function names.
545+
#
546+
# https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
547+
# https://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/a00026.html#aaf2180d3f67420d4e937e85b281b94a0
548+
#
549+
# Signature:
550+
# char * __cxxabiv1::__cxa_demangle ( const char *__mangled_name,
551+
# char *__output_buffer,
552+
# size_t *__length,
553+
# int *__status
554+
# )
555+
# The output buffer, when not provided, is allocated and must be freed via
556+
# free().
557+
stdcxx_name = ctypes.util.find_library("stdc++")
558+
stdc_name = ctypes.util.find_library("c")
559+
if not stdcxx_name or not stdc_name:
560+
warnings.warn("Cannot import C++ demangling")
561+
return lambda s: s
562+
stdcxx = ctypes.CDLL(stdcxx_name)
563+
stdc = ctypes.CDLL(stdc_name)
564+
demangle_func_p = stdcxx.__cxa_demangle
565+
demangle_func_p.restype = ctypes.POINTER(ctypes.c_char)
566+
567+
# The status variable has the following return codes:
568+
status_codes = {
569+
0: "The demangling operation succeeded.",
570+
-1: "A memory allocation failure occurred.",
571+
-2: "mangled_name is not a valid name under the C++ ABI mangling rules.",
572+
-3: "One of the arguments is invalid.",
573+
}
574+
575+
def demanglefn(mangled: str) -> str:
576+
in_buffer = ctypes.c_char_p(mangled.encode("utf-8"))
577+
status = ctypes.c_int()
578+
result = demangle_func_p(in_buffer, None, None, ctypes.pointer(status))
579+
if status.value != 0:
580+
msg = status_codes.get(status.value, "unknown error")
581+
warnings.warn(f"Unable to demangle '{mangled}': {msg}")
582+
return mangled
583+
strval = ctypes.cast(result, ctypes.c_char_p).value.decode("utf-8") # type: ignore
584+
stdc.free(result)
585+
return strval
586+
587+
return demanglefn
588+
589+
590+
def demangle(mangled: str) -> str:
591+
if mangled.startswith("_Z"):
592+
return _load_demangler()(mangled)
593+
else:
594+
return mangled
595+
596+
537597
def print_user_stack_trace(regs: Object) -> None:
538598
"""
539599
Prints the userspace stack trace for regs, with the module name included
@@ -542,15 +602,32 @@ def print_user_stack_trace(regs: Object) -> None:
542602
prog = regs.prog_
543603
trace = prog.stack_trace(regs)
544604
print(" ------ userspace ---------")
545-
for frame, line in zip(trace, str(trace).split("\n")):
605+
for i, frame in enumerate(trace):
606+
name = demangle(frame.name)
607+
608+
offset = ""
609+
try:
610+
sym = frame.symbol()
611+
offset = f"+0x{frame.pc - sym.address:x}/0x{sym.size:x}"
612+
except LookupError:
613+
pass
614+
546615
mod_text = ""
547616
try:
548617
mod = prog.module(frame.pc)
549618
off = frame.pc - mod.address_range[0]
550-
mod_text = f" (in {mod.name} +0x{off:x})"
619+
mod_text = f" (from {mod.name} +0x{off:x})"
551620
except LookupError:
552621
pass
553-
print(" " + line.rstrip() + mod_text)
622+
623+
source_text = ""
624+
try:
625+
source_info = ":".join(map(str, frame.source()))
626+
source_text = f" ({source_info})"
627+
except LookupError:
628+
pass
629+
630+
print(f" #{i:<2d} {name}{offset}{source_text}{mod_text}")
554631

555632

556633
def dump_print_process(

0 commit comments

Comments
 (0)