Compare commits

...

2 Commits

Author SHA1 Message Date
hodlinator
fd341f25b0
Merge a1da3bfc12f77aacae9fb409df40d5151465dea4 into cd8089c20baaaee329cbf7951195953a9f86d7cf 2025-03-16 17:18:11 +01:00
Hodlinator
a1da3bfc12
qa debug: Add --debug_runs/-waitfordebugger
Makes bitcoind spin during startup, waiting for a debugger to be attached.

Useful for debugging one or several bitcoind nodes running in the context of a functional test.

TODO: Confirm code works on Mac.
2025-02-12 22:58:00 +01:00
5 changed files with 98 additions and 1 deletions

View File

@ -132,6 +132,7 @@ option(ENABLE_HARDENING "Attempt to harden the resulting executables." ON)
option(REDUCE_EXPORTS "Attempt to reduce exported symbols in the resulting executables." OFF) option(REDUCE_EXPORTS "Attempt to reduce exported symbols in the resulting executables." OFF)
option(WERROR "Treat compiler warnings as errors." OFF) option(WERROR "Treat compiler warnings as errors." OFF)
option(WITH_CCACHE "Attempt to use ccache for compiling." ON) option(WITH_CCACHE "Attempt to use ccache for compiling." ON)
option(WAIT_FOR_DEBUGGER "Support for waiting during startup for debugger to be attached." OFF)
option(WITH_ZMQ "Enable ZMQ notifications." OFF) option(WITH_ZMQ "Enable ZMQ notifications." OFF)
if(WITH_ZMQ) if(WITH_ZMQ)
@ -246,6 +247,10 @@ target_compile_definitions(core_interface_debug INTERFACE
ABORT_ON_FAILED_ASSUME ABORT_ON_FAILED_ASSUME
) )
if(WAIT_FOR_DEBUGGER)
add_compile_definitions(WAIT_FOR_DEBUGGER=1)
endif()
if(WIN32) if(WIN32)
#[=[ #[=[
This build system supports two ways to build binaries for Windows. This build system supports two ways to build binaries for Windows.

View File

@ -28,7 +28,19 @@
#include <util/tokenpipe.h> #include <util/tokenpipe.h>
#include <util/translation.h> #include <util/translation.h>
#ifdef WIN32
#include <windows.h>
#include <debugapi.h>
#elif defined(__APPLE__)
#include <sys/sysctl.h>
#elif defined(__linux__)
#include <linux/prctl.h>
#include <sys/prctl.h>
#endif
#include <any> #include <any>
#include <cstdlib>
#include <fstream>
#include <functional> #include <functional>
#include <optional> #include <optional>
@ -159,6 +171,60 @@ static bool ProcessInitCommands(ArgsManager& args)
return false; return false;
} }
#ifdef WAIT_FOR_DEBUGGER
static int HandleWaitForDebugger(int argc, char* argv[])
{
for (int i = 0; i < argc; ++i) {
if (strcmp(argv[i], "-waitfordebugger") == 0) {
while (true) {
bool attached{false};
# if defined(__linux__)
// Allow any process to attach to us.
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
std::ifstream sf{"/proc/self/status", std::ios::in};
if (!sf.good()) {
return EXIT_FAILURE;
}
std::string s;
uint pid{0};
while (sf >> s) {
if (s == "TracerPid:") {
sf >> pid;
break;
}
std::getline(sf, s);
}
attached = pid > 0;
# elif defined(__APPLE__)
const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
kinfo_proc info;
size_t size{sizeof(info)};
const int ret{sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0)};
if (ret != EXIT_SUCCESS) {
return ret;
}
attached = info.kp_proc.p_flag & P_TRACED;
# elif defined(WIN32)
attached = IsDebuggerPresent();
# else
# error "Platform doesn't support -waitfordebugger.";
# endif // platform
if (attached) {
break;
} else {
std::this_thread::sleep_for(100ms);
}
}
}
}
return EXIT_SUCCESS;
}
#endif // WAIT_FOR_DEBUGGER
static bool AppInit(NodeContext& node) static bool AppInit(NodeContext& node)
{ {
bool fRet = false; bool fRet = false;
@ -254,6 +320,10 @@ static bool AppInit(NodeContext& node)
MAIN_FUNCTION MAIN_FUNCTION
{ {
#ifdef WAIT_FOR_DEBUGGER
if (int ret{HandleWaitForDebugger(argc, argv)}; ret != EXIT_SUCCESS) return ret;
#endif
#ifdef WIN32 #ifdef WIN32
common::WinCmdLineArgs winArgs; common::WinCmdLineArgs winArgs;
std::tie(argc, argv) = winArgs.get(); std::tie(argc, argv) = winArgs.get();

View File

@ -681,6 +681,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
hidden_args.emplace_back("-daemon"); hidden_args.emplace_back("-daemon");
hidden_args.emplace_back("-daemonwait"); hidden_args.emplace_back("-daemonwait");
#endif #endif
argsman.AddArg("-waitfordebugger", "Spin until a debugger is attached", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
// Add the hidden options // Add the hidden options
argsman.AddHiddenArgs(hidden_args); argsman.AddHiddenArgs(hidden_args);
@ -1119,6 +1120,12 @@ bool AppInitParameterInteraction(const ArgsManager& args)
} }
} }
#ifndef WAIT_FOR_DEBUGGER
if (args.GetBoolArg("-waitfordebugger", false)) {
return InitError(Untranslated("-waitfordebugger support was not included as WAIT_FOR_DEBUGGER is not #defined."));
}
#endif
return true; return true;
} }

View File

@ -205,6 +205,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
help="Explicitly use v1 transport (can be used to overwrite global --v2transport option)") help="Explicitly use v1 transport (can be used to overwrite global --v2transport option)")
parser.add_argument("--test_methods", dest="test_methods", nargs='*', parser.add_argument("--test_methods", dest="test_methods", nargs='*',
help="Run specified test methods sequentially instead of the full test. Use only for methods that do not depend on any context set up in run_test or other methods.") help="Run specified test methods sequentially instead of the full test. Use only for methods that do not depend on any context set up in run_test or other methods.")
parser.add_argument("--debug_runs", dest="debug_runs", nargs="*", type=int, default=[],
help="Node executions in the test to stall and wait for debugger, 0-based.")
self.add_options(parser) self.add_options(parser)
# Running TestShell in a Jupyter notebook causes an additional -f argument # Running TestShell in a Jupyter notebook causes an additional -f argument
@ -239,6 +241,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
PortSeed.n = self.options.port_seed PortSeed.n = self.options.port_seed
TestNode.debug_runs = self.options.debug_runs
def set_binary_paths(self): def set_binary_paths(self):
"""Update self.options with the paths of all binaries from environment variables or their default values""" """Update self.options with the paths of all binaries from environment variables or their default values"""

View File

@ -76,6 +76,9 @@ class TestNode():
To make things easier for the test writer, any unrecognised messages will To make things easier for the test writer, any unrecognised messages will
be dispatched to the RPC connection.""" be dispatched to the RPC connection."""
runs = 0
debug_runs = None
def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False, v2transport=False): def __init__(self, i, datadir_path, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False, v2transport=False):
""" """
Kwargs: Kwargs:
@ -253,10 +256,18 @@ class TestNode():
if env is not None: if env is not None:
subp_env.update(env) subp_env.update(env)
wait_for_debugger = TestNode.debug_runs is not None and TestNode.runs in TestNode.debug_runs
if wait_for_debugger:
extra_args.append("-waitfordebugger")
self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, cwd=cwd, **kwargs) self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, cwd=cwd, **kwargs)
self.running = True self.running = True
self.log.debug("bitcoind started, waiting for RPC to come up") if wait_for_debugger:
self.log.info(f"bitcoind started (run #{TestNode.runs}, node #{self.index}), waiting for debugger, PID: {self.process.pid}")
else:
self.log.debug(f"bitcoind started (run #{TestNode.runs}, node #{self.index}), waiting for RPC to come up")
TestNode.runs += 1
if self.start_perf: if self.start_perf:
self._start_perf() self._start_perf()