From ce7168211464733e18a737c5f3ff3332f3903345 Mon Sep 17 00:00:00 2001 From: Coldwings Date: Thu, 10 Apr 2025 13:27:30 +0800 Subject: [PATCH] Enhance debug tools (#753) * Better debug tools * Fix on ARM64 asan hook * Force set CMAKE_POLICY_VERSION_MINIMUM to make third-party libs able to compile via cmake 4 * gdb eval with return type since debugger may not have source code to resolve --- CMakeLists.txt | 3 ++ io/iouring-wrapper.cpp | 8 ++++ thread/thread.cpp | 93 ++++++++++++++++++++++++++++++++++++++++++ tools/photongdb.py | 44 ++++++++++++++------ tools/ppstack | 50 +++++++++++++++++++++++ 5 files changed, 186 insertions(+), 12 deletions(-) create mode 100755 tools/ppstack diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d88d8aa..ecc6eede 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,9 @@ project( LANGUAGES C CXX ASM ) +# for CMake 4.x compatible +set(CMAKE_POLICY_VERSION_MINIMUM 3.5) + # Utility Modules and Find Modules include(FindPackageHandleStandardArgs) include(CheckCXXCompilerFlag) diff --git a/io/iouring-wrapper.cpp b/io/iouring-wrapper.cpp index 9a469379..7b717b68 100644 --- a/io/iouring-wrapper.cpp +++ b/io/iouring-wrapper.cpp @@ -231,7 +231,11 @@ class iouringEngine : public MasterEventEngine, public CascadingEventEngine, pub ioCtx timer_ctx(true, false); __kernel_timespec ts; auto usec = timeout.timeout_us(); +<<<<<<< HEAD if (usec < (uint64_t) std::numeric_limits::max()) { +======= + if (usec < (uint64_t)std::numeric_limits::max()) { +>>>>>>> 60da16c (Enhance debug tools (#753)) sqe->flags |= IOSQE_IO_LINK; ts = usec_to_timespec(usec); sqe = _get_sqe(); @@ -375,7 +379,11 @@ class iouringEngine : public MasterEventEngine, public CascadingEventEngine, pub ssize_t wait_and_fire_events(uint64_t timeout) override { // Prepare own timeout +<<<<<<< HEAD if (timeout > (uint64_t) std::numeric_limits::max()) { +======= + if (timeout > (uint64_t)std::numeric_limits::max()) { +>>>>>>> 60da16c (Enhance debug tools (#753)) timeout = std::numeric_limits::max(); } diff --git a/thread/thread.cpp b/thread/thread.cpp index 3d06790c..84bf675a 100644 --- a/thread/thread.cpp +++ b/thread/thread.cpp @@ -169,6 +169,51 @@ namespace photon void* _ptr; }; + #if defined(__has_feature) + # if __has_feature(address_sanitizer) // for clang + # define __SANITIZE_ADDRESS__ // GCC already sets this + # endif + #endif + + #ifdef __SANITIZE_ADDRESS__ + extern "C" { + // Check out sanitizer/asan-interface.h in compiler-rt for documentation. + void __sanitizer_start_switch_fiber(void** fake_stack_save, const void* bottom, + size_t size); + void __sanitizer_finish_switch_fiber(void* fake_stack_save, + const void** bottom_old, size_t* size_old); + } + + static void asan_start(void** save, thread* to) { + void* bottom = to->buf ? to->buf : to->stackful_alloc_top; + __sanitizer_start_switch_fiber(save, bottom, + to->stack_size); + } + + static void asan_finish(void* save) { + __sanitizer_finish_switch_fiber(save, nullptr, nullptr); + } + +#define ASAN_START() asan_finish((void*)nullptr); + +#define ASAN_SWITCH(to) \ + void* __save; \ + asan_start(&__save, to); \ + DEFER({ asan_finish(__save); }); + +#define ASAN_DIE_SWITCH(to) \ + asan_start(nullptr, to); + +#else +#define ASAN_START(ptr) +#define ASAN_SWITCH(to) +#define ASAN_DIE_SWITCH(to) +#endif + + static void _asan_start() asm("_asan_start"); + + __attribute__((used)) static void _asan_start() { ASAN_START(); } + struct thread_list; struct thread : public intrusive_list_node { volatile vcpu_t* vcpu; @@ -727,6 +772,7 @@ R"( ); inline void switch_context(thread* from, thread* to) { + ASAN_SWITCH(to); prepare_switch(from, to); auto _t_ = to->stack.pointer_ref(); register auto f asm("rsi") = from->stack.pointer_ref(); @@ -740,6 +786,7 @@ R"( inline void switch_context_defer(thread* from, thread* to, void (*defer)(void*), void* arg) { + ASAN_SWITCH(to); prepare_switch(from, to); auto _t_ = to->stack.pointer_ref(); register auto f asm("rcx") = from->stack.pointer_ref(); @@ -779,6 +826,7 @@ R"( DEF_ASM_FUNC(_photon_thread_stub) R"( + call _asan_start mov 0x40(%rbp), %rcx movq $0, 0x40(%rbp) call *0x48(%rbp) @@ -789,6 +837,7 @@ R"( ); inline void switch_context(thread* from, thread* to) { + ASAN_SWITCH(to); prepare_switch(from, to); auto _t_ = to->stack.pointer_ref(); register auto f asm("rdx") = from->stack.pointer_ref(); @@ -804,6 +853,7 @@ R"( inline void switch_context_defer(thread* from, thread* to, void (*defer)(void*), void* arg) { + ASAN_SWITCH(to); prepare_switch(from, to); auto _t_ = to->stack.pointer_ref(); register auto f asm("r9") = from->stack.pointer_ref(); @@ -852,6 +902,7 @@ R"( DEF_ASM_FUNC(_photon_thread_stub) R"( + bl _asan_start //; asan_start() ldp x0, x1, [x29, #0x40] //; load arg, start into x0, x1 str xzr, [x29, #0x40] //; set arg as 0 blr x1 //; start(x0) @@ -867,6 +918,7 @@ R"( #endif inline void switch_context(thread* from, thread* to) { + ASAN_SWITCH(to); prepare_switch(from, to); auto _t_ = to->stack.pointer_ref(); register auto f asm("x0") = from->stack.pointer_ref(); @@ -886,6 +938,7 @@ R"( inline void switch_context_defer(thread* from, thread* to, void (*defer)(void*), void* arg) { + ASAN_SWITCH(to); prepare_switch(from, to); auto _t_ = to->stack.pointer_ref(); register auto f asm("x3") = from->stack.pointer_ref(); @@ -933,6 +986,7 @@ R"( func = (uint64_t)&spinlock_unlock; arg = &lock; } + ASAN_DIE_SWITCH(sw.to); _photon_switch_context_defer_die( arg, func, sw.to->stack.pointer_ref()); __builtin_unreachable(); @@ -2089,4 +2143,43 @@ R"( photon_thread_alloc = _photon_thread_alloc; photon_thread_dealloc = _photon_thread_dealloc; } + + extern "C" { + [[gnu::used]] + void *gdb_get_thread_stack_ptr(void *th) { + if (!th) + return nullptr; + return ((thread *)th)->stack._ptr; + } + [[gnu::used]] + void *gdb_get_current_thread() { + return CURRENT; + } + [[gnu::used]] + void *gdb_get_next_thread(void *c) { + if (!c) + return nullptr; + return ((thread *)c)->next(); + } + [[gnu::used]] + void *gdb_get_vcpu(void *th) { + if (!th) + return nullptr; + return (void *)((thread *)th)->vcpu; + } + [[gnu::used]] + size_t gdb_get_sleepq_size(void *vcpu) { + if (!vcpu) + return 0; + return ((vcpu_t *)vcpu)->sleepq.q.size(); + } + [[gnu::used]] + void *gdb_get_sleepq_item(void *vcpu, size_t idx) { + if (!vcpu) + return nullptr; + if (((vcpu_t *)vcpu)->sleepq.q.size() <= idx) + return nullptr; + return ((vcpu_t *)vcpu)->sleepq.q[idx]; + } + } } diff --git a/tools/photongdb.py b/tools/photongdb.py index d4d3e76b..1f1f2885 100644 --- a/tools/photongdb.py +++ b/tools/photongdb.py @@ -52,28 +52,25 @@ def cprint(stat, *args): def get_next_ready(p): - return gdb.parse_and_eval("(photon::thread*)%s" % p.dereference()['__next_ptr']) + return gdb.parse_and_eval("(void*)gdb_get_next_thread((void*){})".format(p)) def get_current(): - return gdb.parse_and_eval("(photon::thread*)photon::CURRENT") + return gdb.parse_and_eval("(void*)gdb_get_current_thread()") def get_vcpu(p): - return p.dereference()['vcpu'].dereference() - - -def get_sleepq(vcpu): - return vcpu['sleepq']['q'] + return gdb.parse_and_eval("(void*)gdb_get_vcpu((void*){})".format(p)) +def get_thread_stack_ptr(p): + return gdb.parse_and_eval("(void*)gdb_get_thread_stack_ptr((void*){})".format(p)) def in_sleep(q): - size = q['_M_impl']['_M_finish'] - q['_M_impl']['_M_start'] - return [(q['_M_impl']['_M_start'][i]) for i in range(size)] + size = int(gdb.parse_and_eval("(size_t)gdb_get_sleepq_size((void*){})".format(q))) + return [gdb.parse_and_eval("(void*)gdb_get_sleepq_item((void*){}, {})".format(q, i)) for i in range(size)] def switch_to_ph(regs, rsp, rbp, rip): - cprint('SWITCH', "to {} {} {}".format(hex(rsp), hex(rbp), hex(rip))) gdb.parse_and_eval("{}={}".format(regs['sp'], rsp)) gdb.parse_and_eval("{}={}".format(regs['bp'], rbp)) gdb.parse_and_eval("{}={}".format(regs['ip'], rip)) @@ -96,7 +93,7 @@ def set_u64_reg(l, r): def get_stkregs(p): - t = get_u64_ptr(p['stack']['_ptr']) + t = get_u64_ptr(get_thread_stack_ptr(p)) rsp = t + 8 rip = get_u64_val(t + 8) rbp = get_u64_val(t) @@ -119,7 +116,7 @@ def load_photon_threads(): photon.append(('READY', p, rsp, rbp, rip)) p = get_next_ready(p) vcpu = get_vcpu(c) - for t in in_sleep(get_sleepq(vcpu)): + for t in in_sleep(vcpu): rsp, rbp, rip = get_stkregs(t) photon.append(('SLEEP', t, rsp, rbp, rip)) return @@ -163,6 +160,7 @@ def invoke(self, arg, tty): arch = get_arch() regs = get_regs(arch) + cprint('SWITCH', "to {} {} {}".format(hex(photon[i][2]), hex(photon[i][3]), hex(photon[i][4]))) switch_to_ph(regs, photon[i][2], photon[i][3], photon[i][4]) @@ -225,6 +223,27 @@ def invoke(self, arg, tty): enabling = False cprint('WARNING', "Finished photon thread lookup mode.") +from threading import Lock + +class PhotonPs(gdb.Command): + def __init__(self): + gdb.Command.__init__(self, "photon_ps", + gdb.COMMAND_STACK, gdb.COMPLETE_NONE) + self.lock = Lock() + + def invoke(self, arg, tty): + with self.lock: + photon_init() + if len(photon) > 0: + for i, (stat, pth, rsp, rbp, rbi) in enumerate(photon): + cprint( + stat, '[{}]'.format(i), pth, hex(rsp), hex(rbp), hex(rbi)) + arch = get_arch() + regs = get_regs(arch) + switch_to_ph(regs, rsp, rbp, rbi) + gdb.execute("bt") + switch_to_ph(regs, photon[0][2], photon[0][3], photon[0][4]) + photon_restore() PhotonInit() PhotonFini() @@ -232,5 +251,6 @@ def invoke(self, arg, tty): PhotonThreads() PhotonLs() PhotonFr() +PhotonPs() cprint('INFO', 'Photon-GDB-extension loaded') diff --git a/tools/ppstack b/tools/ppstack new file mode 100755 index 00000000..ad5b4d64 --- /dev/null +++ b/tools/ppstack @@ -0,0 +1,50 @@ +#!/usr/bin/sh + +if test $# -ne 1; then + echo "Usage: `basename $0 .sh` " 1>&2 + exit 1 +fi + +if test ! -r /proc/$1; then + echo "Process $1 not found." 1>&2 + exit 1 +fi + +# GDB doesn't allow "thread apply all bt" when the process isn't +# threaded; need to peek at the process to determine if that or the +# simpler "bt" should be used. + +backtrace="photon_ps" +if test -d /proc/$1/task ; then + # Newer kernel; has a task/ directory. + if test `/bin/ls /proc/$1/task | /usr/bin/wc -l` -gt 1 2>/dev/null ; then + backtrace="thread apply all photon_ps" + fi +elif test -f /proc/$1/maps ; then + # Older kernel; go by it loading libpthread. + if /bin/grep -e libpthread /proc/$1/maps > /dev/null 2>&1 ; then + backtrace="thread apply all photon_ps" + fi +fi + +GDB=${GDB:-gdb} +PHOTONDB=${PHOTONDB:-/usr/local/lib/photon/tools/photongdb.py} +PHOTONSO=${PHOTONSO:-/usr/local/lib/libphoton.so} + +# Run GDB, strip out unwanted noise. +# --readnever is no longer used since .gdb_index is now in use. +$GDB --quiet -nx $GDBARGS /proc/$1/exe $1 <&1 | +set width 0 +set height 0 +set pagination no +source $PHOTONDB +add-symbol-file $PHOTONSO +$backtrace +EOF +/bin/sed -n \ + -e 's/^\((gdb) \)*//' \ + -e '/^#/p' \ + -e '/^Thread/p' \ + -e '/CURRENT/p' \ + -e '/READY/p' \ + -e '/SLEEP/p' \ No newline at end of file