From 27012c0f75c2e4891277d0d14f9f97a9f564d596 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 17 Nov 2020 09:26:20 -0800 Subject: [PATCH] [debugserver] Add option to propagate SIGSEGV to target process Adds a command line option that makes debugserver propagate the SIGSEGV signal to the target process. Motivation: I'm one of the maintainers of Delve [1] a debugger for Go. We use debugserver as our backend on macOS and one of the most often reported bugs is that, on macOS, we don't propagate SIGSEGV back to the target process [2]. Sometimes some programs will actually cause a SIGSEGV, by design, and then handle it. Those programs can not be debugged at all. Since catching signals isn't very important for a Go debugger I'd much rather have a command line option in debugserver that causes it to let SIGSEGV go directly to the target process. [1] https://github.com/go-delve/delve/ [2] https://github.com/go-delve/delve/issues/852 Differential revision: https://reviews.llvm.org/D89315 --- lldb/tools/debugserver/source/DNB.cpp | 56 ++++++++++--------- lldb/tools/debugserver/source/DNB.h | 30 +++++----- .../debugserver/source/MacOSX/MachProcess.h | 10 ++-- .../debugserver/source/MacOSX/MachProcess.mm | 19 ++++--- .../debugserver/source/MacOSX/MachTask.h | 2 +- .../debugserver/source/MacOSX/MachTask.mm | 8 ++- lldb/tools/debugserver/source/RNBContext.h | 6 ++ lldb/tools/debugserver/source/RNBRemote.cpp | 13 +++-- lldb/tools/debugserver/source/debugserver.cpp | 22 ++++++-- 9 files changed, 102 insertions(+), 64 deletions(-) diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp index afafe0d0474a..2d6516e8c654 100644 --- a/lldb/tools/debugserver/source/DNB.cpp +++ b/lldb/tools/debugserver/source/DNB.cpp @@ -319,20 +319,21 @@ static bool spawn_waitpid_thread(pid_t pid) { } nub_process_t DNBProcessLaunch( - const char *path, char const *argv[], const char *envp[], + RNBContext *ctx, const char *path, char const *argv[], const char *envp[], const char *working_directory, // NULL => don't change, non-NULL => set // working directory for inferior to this const char *stdin_path, const char *stdout_path, const char *stderr_path, - bool no_stdio, nub_launch_flavor_t launch_flavor, int disable_aslr, - const char *event_data, char *err_str, size_t err_len) { - DNBLogThreadedIf(LOG_PROCESS, "%s ( path='%s', argv = %p, envp = %p, " - "working_dir=%s, stdin=%s, stdout=%s, " - "stderr=%s, no-stdio=%i, launch_flavor = %u, " - "disable_aslr = %d, err = %p, err_len = " - "%llu) called...", + bool no_stdio, int disable_aslr, const char *event_data, char *err_str, + size_t err_len) { + DNBLogThreadedIf(LOG_PROCESS, + "%s ( path='%s', argv = %p, envp = %p, " + "working_dir=%s, stdin=%s, stdout=%s, " + "stderr=%s, no-stdio=%i, launch_flavor = %u, " + "disable_aslr = %d, err = %p, err_len = " + "%llu) called...", __FUNCTION__, path, static_cast(argv), static_cast(envp), working_directory, stdin_path, - stdout_path, stderr_path, no_stdio, launch_flavor, + stdout_path, stderr_path, no_stdio, ctx->LaunchFlavor(), disable_aslr, static_cast(err_str), static_cast(err_len)); @@ -349,10 +350,10 @@ nub_process_t DNBProcessLaunch( MachProcessSP processSP(new MachProcess); if (processSP.get()) { DNBError launch_err; - pid_t pid = processSP->LaunchForDebug(path, argv, envp, working_directory, - stdin_path, stdout_path, stderr_path, - no_stdio, launch_flavor, disable_aslr, - event_data, launch_err); + pid_t pid = processSP->LaunchForDebug( + path, argv, envp, working_directory, stdin_path, stdout_path, + stderr_path, no_stdio, ctx->LaunchFlavor(), disable_aslr, event_data, + ctx->GetUnmaskSignals(), launch_err); if (err_str) { *err_str = '\0'; if (launch_err.Fail()) { @@ -412,7 +413,8 @@ nub_process_t DNBProcessGetPIDByName(const char *name) { } nub_process_t DNBProcessAttachByName(const char *name, struct timespec *timeout, - char *err_str, size_t err_len) { + bool unmask_signals, char *err_str, + size_t err_len) { if (err_str && err_len > 0) err_str[0] = '\0'; std::vector matching_proc_infos; @@ -433,12 +435,12 @@ nub_process_t DNBProcessAttachByName(const char *name, struct timespec *timeout, } return DNBProcessAttach(matching_proc_infos[0].kp_proc.p_pid, timeout, - err_str, err_len); + unmask_signals, err_str, err_len); } nub_process_t DNBProcessAttach(nub_process_t attach_pid, - struct timespec *timeout, char *err_str, - size_t err_len) { + struct timespec *timeout, bool unmask_signals, + char *err_str, size_t err_len) { if (err_str && err_len > 0) err_str[0] = '\0'; @@ -480,7 +482,8 @@ nub_process_t DNBProcessAttach(nub_process_t attach_pid, if (processSP.get()) { DNBLogThreadedIf(LOG_PROCESS, "(DebugNub) attaching to pid %d...", attach_pid); - pid = processSP->AttachForDebug(attach_pid, err_str, err_len); + pid = + processSP->AttachForDebug(attach_pid, unmask_signals, err_str, err_len); if (pid != INVALID_NUB_PROCESS) { bool res = AddProcessToMap(pid, processSP); @@ -667,15 +670,18 @@ GetAllInfosMatchingName(const char *full_process_name, return matching_proc_infos.size(); } -nub_process_t DNBProcessAttachWait( - const char *waitfor_process_name, nub_launch_flavor_t launch_flavor, - bool ignore_existing, struct timespec *timeout_abstime, - useconds_t waitfor_interval, char *err_str, size_t err_len, - DNBShouldCancelCallback should_cancel_callback, void *callback_data) { +nub_process_t +DNBProcessAttachWait(RNBContext *ctx, const char *waitfor_process_name, + bool ignore_existing, struct timespec *timeout_abstime, + useconds_t waitfor_interval, char *err_str, size_t err_len, + DNBShouldCancelCallback should_cancel_callback, + void *callback_data) { DNBError prepare_error; std::vector exclude_proc_infos; size_t num_exclude_proc_infos; + nub_launch_flavor_t launch_flavor = ctx->LaunchFlavor(); + // If the PrepareForAttach returns a valid token, use MachProcess to check // for the process, otherwise scan the process table. @@ -771,8 +777,8 @@ nub_process_t DNBProcessAttachWait( if (waitfor_pid != INVALID_NUB_PROCESS) { DNBLogThreadedIf(LOG_PROCESS, "Attaching to %s with pid %i...\n", waitfor_process_name, waitfor_pid); - waitfor_pid = - DNBProcessAttach(waitfor_pid, timeout_abstime, err_str, err_len); + waitfor_pid = DNBProcessAttach(waitfor_pid, timeout_abstime, + ctx->GetUnmaskSignals(), err_str, err_len); } bool success = waitfor_pid != INVALID_NUB_PROCESS; diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h index 8364ec0c1162..069c62dc41d8 100644 --- a/lldb/tools/debugserver/source/DNB.h +++ b/lldb/tools/debugserver/source/DNB.h @@ -18,10 +18,11 @@ #include "MacOSX/DarwinLog/DarwinLogEvent.h" #include "MacOSX/Genealogy.h" #include "MacOSX/ThreadInfo.h" -#include -#include +#include "RNBContext.h" #include #include +#include +#include #define DNB_EXPORT __attribute__((visibility("default"))) @@ -42,24 +43,27 @@ nub_bool_t DNBSetArchitecture(const char *arch); // Process control nub_process_t DNBProcessLaunch( - const char *path, char const *argv[], const char *envp[], + RNBContext *ctx, const char *path, char const *argv[], const char *envp[], const char *working_directory, // NULL => don't change, non-NULL => set // working directory for inferior to this const char *stdin_path, const char *stdout_path, const char *stderr_path, - bool no_stdio, nub_launch_flavor_t launch_flavor, int disable_aslr, - const char *event_data, char *err_str, size_t err_len); + bool no_stdio, int disable_aslr, const char *event_data, char *err_str, + size_t err_len); nub_process_t DNBProcessGetPIDByName(const char *name); nub_process_t DNBProcessAttach(nub_process_t pid, struct timespec *timeout, - char *err_str, size_t err_len); + bool unmask_signals, char *err_str, + size_t err_len); nub_process_t DNBProcessAttachByName(const char *name, struct timespec *timeout, - char *err_str, size_t err_len); -nub_process_t -DNBProcessAttachWait(const char *wait_name, nub_launch_flavor_t launch_flavor, - bool ignore_existing, struct timespec *timeout, - useconds_t interval, char *err_str, size_t err_len, - DNBShouldCancelCallback should_cancel = NULL, - void *callback_data = NULL); + bool unmask_signals, char *err_str, + size_t err_len); +nub_process_t DNBProcessAttachWait(RNBContext *ctx, const char *wait_name, + bool ignore_existing, + struct timespec *timeout, + useconds_t interval, char *err_str, + size_t err_len, + DNBShouldCancelCallback should_cancel = NULL, + void *callback_data = NULL); // Resume a process with exact instructions on what to do with each thread: // - If no thread actions are supplied (actions is NULL or num_actions is zero), // then all threads are continued. diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.h b/lldb/tools/debugserver/source/MacOSX/MachProcess.h index 7eb663cc2d51..a41b137c3fd0 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.h +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.h @@ -78,12 +78,14 @@ public: }; // Child process control - pid_t AttachForDebug(pid_t pid, char *err_str, size_t err_len); + pid_t AttachForDebug(pid_t pid, bool unmask_signals, char *err_str, + size_t err_len); pid_t LaunchForDebug(const char *path, char const *argv[], char const *envp[], const char *working_directory, const char *stdin_path, const char *stdout_path, const char *stderr_path, bool no_stdio, nub_launch_flavor_t launch_flavor, - int disable_aslr, const char *event_data, DNBError &err); + int disable_aslr, const char *event_data, + bool unmask_signals, DNBError &err); static uint32_t GetCPUTypeForLocalProcess(pid_t pid); static pid_t ForkChildForPTraceDebugging(const char *path, char const *argv[], @@ -107,7 +109,7 @@ public: pid_t BoardServiceLaunchForDebug(const char *app_bundle_path, char const *argv[], char const *envp[], bool no_stdio, bool disable_aslr, - const char *event_data, + const char *event_data, bool unmask_signals, DNBError &launch_err); pid_t BoardServiceForkChildForPTraceDebugging( const char *path, char const *argv[], char const *envp[], bool no_stdio, @@ -128,7 +130,7 @@ public: #ifdef WITH_SPRINGBOARD pid_t SBLaunchForDebug(const char *app_bundle_path, char const *argv[], char const *envp[], bool no_stdio, bool disable_aslr, - DNBError &launch_err); + bool unmask_signals, DNBError &launch_err); static pid_t SBForkChildForPTraceDebugging(const char *path, char const *argv[], char const *envp[], bool no_stdio, diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm index 80792f07214b..0206fa636e11 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm @@ -2600,7 +2600,8 @@ void *MachProcess::ProfileThread(void *arg) { return NULL; } -pid_t MachProcess::AttachForDebug(pid_t pid, char *err_str, size_t err_len) { +pid_t MachProcess::AttachForDebug(pid_t pid, bool unmask_signals, char *err_str, + size_t err_len) { // Clear out and clean up from any current state Clear(); if (pid != 0) { @@ -2617,7 +2618,7 @@ pid_t MachProcess::AttachForDebug(pid_t pid, char *err_str, size_t err_len) { SetState(eStateAttaching); m_pid = pid; - if (!m_task.StartExceptionThread(err)) { + if (!m_task.StartExceptionThread(unmask_signals, err)) { const char *err_cstr = err.AsString(); ::snprintf(err_str, err_len, "%s", err_cstr ? err_cstr : "unable to start the exception thread"); @@ -3077,7 +3078,7 @@ pid_t MachProcess::LaunchForDebug( // working directory for inferior to this const char *stdin_path, const char *stdout_path, const char *stderr_path, bool no_stdio, nub_launch_flavor_t launch_flavor, int disable_aslr, - const char *event_data, DNBError &launch_err) { + const char *event_data, bool unmask_signals, DNBError &launch_err) { // Clear out and clean up from any current state Clear(); @@ -3182,7 +3183,7 @@ pid_t MachProcess::LaunchForDebug( for (i = 0; (arg = argv[i]) != NULL; i++) m_args.push_back(arg); - m_task.StartExceptionThread(launch_err); + m_task.StartExceptionThread(unmask_signals, launch_err); if (launch_err.Fail()) { if (launch_err.AsString() == NULL) launch_err.SetErrorString("unable to start the exception thread"); @@ -3525,7 +3526,8 @@ static CFStringRef CopyBundleIDForPath(const char *app_bundle_path, pid_t MachProcess::SBLaunchForDebug(const char *path, char const *argv[], char const *envp[], bool no_stdio, - bool disable_aslr, DNBError &launch_err) { + bool disable_aslr, bool unmask_signals, + DNBError &launch_err) { // Clear out and clean up from any current state Clear(); @@ -3541,7 +3543,7 @@ pid_t MachProcess::SBLaunchForDebug(const char *path, char const *argv[], char const *arg; for (i = 0; (arg = argv[i]) != NULL; i++) m_args.push_back(arg); - m_task.StartExceptionThread(launch_err); + m_task.StartExceptionThread(unmask_signals, launch_err); if (launch_err.Fail()) { if (launch_err.AsString() == NULL) @@ -3738,7 +3740,8 @@ pid_t MachProcess::SBForkChildForPTraceDebugging( #if defined(WITH_BKS) || defined(WITH_FBS) pid_t MachProcess::BoardServiceLaunchForDebug( const char *path, char const *argv[], char const *envp[], bool no_stdio, - bool disable_aslr, const char *event_data, DNBError &launch_err) { + bool disable_aslr, const char *event_data, bool unmask_signals, + DNBError &launch_err) { DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path); // Fork a child process for debugging @@ -3751,7 +3754,7 @@ pid_t MachProcess::BoardServiceLaunchForDebug( char const *arg; for (i = 0; (arg = argv[i]) != NULL; i++) m_args.push_back(arg); - m_task.StartExceptionThread(launch_err); + m_task.StartExceptionThread(unmask_signals, launch_err); if (launch_err.Fail()) { if (launch_err.AsString() == NULL) diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.h b/lldb/tools/debugserver/source/MacOSX/MachTask.h index 36e31dddd455..e9cb885bfdb9 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.h +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.h @@ -67,7 +67,7 @@ public: kern_return_t RestoreExceptionPortInfo(); kern_return_t ShutDownExcecptionThread(); - bool StartExceptionThread(DNBError &err); + bool StartExceptionThread(bool unmask_signals, DNBError &err); nub_addr_t GetDYLDAllImageInfosAddress(DNBError &err); kern_return_t BasicInfo(struct task_basic_info *info); static kern_return_t BasicInfo(task_t task, struct task_basic_info *info); diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.mm b/lldb/tools/debugserver/source/MacOSX/MachTask.mm index fcbe6e71389e..1d977191c009 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.mm @@ -595,7 +595,7 @@ bool MachTask::IsValid(task_t task) { return false; } -bool MachTask::StartExceptionThread(DNBError &err) { +bool MachTask::StartExceptionThread(bool unmask_signals, DNBError &err) { DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__); task_t task = TaskPortForProcessID(err); @@ -624,6 +624,12 @@ bool MachTask::StartExceptionThread(DNBError &err) { return false; } + if (unmask_signals) { + m_exc_port_info.mask = m_exc_port_info.mask & + ~(EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | + EXC_MASK_ARITHMETIC); + } + // Set the ability to get all exceptions on this port err = ::task_set_exception_ports( task, m_exc_port_info.mask, m_exception_port, diff --git a/lldb/tools/debugserver/source/RNBContext.h b/lldb/tools/debugserver/source/RNBContext.h index 7d8f458ce0f9..0b46151e4785 100644 --- a/lldb/tools/debugserver/source/RNBContext.h +++ b/lldb/tools/debugserver/source/RNBContext.h @@ -124,6 +124,11 @@ public: void SetDetachOnError(bool detach) { m_detach_on_error = detach; } bool GetDetachOnError() { return m_detach_on_error; } + void SetUnmaskSignals(bool unmask_signals) { + m_unmask_signals = unmask_signals; + } + bool GetUnmaskSignals() { return m_unmask_signals; } + protected: // Classes that inherit from RNBContext can see and modify these nub_process_t m_pid; @@ -147,6 +152,7 @@ protected: void StartProcessStatusThread(); void StopProcessStatusThread(); static void *ThreadFunctionProcessStatus(void *arg); + bool m_unmask_signals; private: RNBContext(const RNBContext &rhs) = delete; diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index aef41a5dba43..1f3a830a1dce 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -3927,8 +3927,8 @@ rnb_err_t RNBRemote::HandlePacket_v(const char *p) { } const bool ignore_existing = true; attach_pid = DNBProcessAttachWait( - attach_name.c_str(), m_ctx.LaunchFlavor(), ignore_existing, NULL, - 1000, err_str, sizeof(err_str), RNBRemoteShouldCancelCallback); + &m_ctx, attach_name.c_str(), ignore_existing, NULL, 1000, err_str, + sizeof(err_str), RNBRemoteShouldCancelCallback); } else if (strstr(p, "vAttachOrWait;") == p) { p += strlen("vAttachOrWait;"); @@ -3939,8 +3939,8 @@ rnb_err_t RNBRemote::HandlePacket_v(const char *p) { } const bool ignore_existing = false; attach_pid = DNBProcessAttachWait( - attach_name.c_str(), m_ctx.LaunchFlavor(), ignore_existing, NULL, - 1000, err_str, sizeof(err_str), RNBRemoteShouldCancelCallback); + &m_ctx, attach_name.c_str(), ignore_existing, NULL, 1000, err_str, + sizeof(err_str), RNBRemoteShouldCancelCallback); } else if (strstr(p, "vAttachName;") == p) { p += strlen("vAttachName;"); if (!GetProcessNameFrom_vAttach(p, attach_name)) { @@ -3948,7 +3948,8 @@ rnb_err_t RNBRemote::HandlePacket_v(const char *p) { __FILE__, __LINE__, p, "non-hex char in arg on 'vAttachName' pkt"); } - attach_pid = DNBProcessAttachByName(attach_name.c_str(), NULL, err_str, + attach_pid = DNBProcessAttachByName(attach_name.c_str(), NULL, + Context().GetUnmaskSignals(), err_str, sizeof(err_str)); } else if (strstr(p, "vAttach;") == p) { @@ -3961,7 +3962,7 @@ rnb_err_t RNBRemote::HandlePacket_v(const char *p) { struct timespec attach_timeout_abstime; DNBTimer::OffsetTimeOfDay(&attach_timeout_abstime, 30, 0); attach_pid = DNBProcessAttach(pid_attaching_to, &attach_timeout_abstime, - err_str, sizeof(err_str)); + false, err_str, sizeof(err_str)); } } else { return HandlePacket_UNIMPLEMENTED(p); diff --git a/lldb/tools/debugserver/source/debugserver.cpp b/lldb/tools/debugserver/source/debugserver.cpp index feb65eb6d3fb..fed24fe0648a 100644 --- a/lldb/tools/debugserver/source/debugserver.cpp +++ b/lldb/tools/debugserver/source/debugserver.cpp @@ -245,8 +245,8 @@ RNBRunLoopMode RNBRunLoopLaunchInferior(RNBRemote *remote, : ctx.GetWorkingDirectory()); const char *process_event = ctx.GetProcessEvent(); nub_process_t pid = DNBProcessLaunch( - resolved_path, &inferior_argv[0], &inferior_envp[0], cwd, stdin_path, - stdout_path, stderr_path, no_stdio, launch_flavor, g_disable_aslr, + &ctx, resolved_path, &inferior_argv[0], &inferior_envp[0], cwd, + stdin_path, stdout_path, stderr_path, no_stdio, g_disable_aslr, process_event, launch_err_str, sizeof(launch_err_str)); g_pid = pid; @@ -368,7 +368,8 @@ RNBRunLoopMode RNBRunLoopLaunchAttaching(RNBRemote *remote, DNBLogThreadedIf(LOG_RNB_MINIMAL, "%s Attaching to pid %i...", __FUNCTION__, attach_pid); char err_str[1024]; - pid = DNBProcessAttach(attach_pid, NULL, err_str, sizeof(err_str)); + pid = DNBProcessAttach(attach_pid, NULL, ctx.GetUnmaskSignals(), err_str, + sizeof(err_str)); g_pid = pid; if (pid == INVALID_NUB_PROCESS) { @@ -889,6 +890,10 @@ static struct option g_long_options[] = { 'F'}, // When debugserver launches the process, forward debugserver's // current environment variables to the child process ("./debugserver // -F localhost:1234 -- /bin/ls" + {"unmask-signals", no_argument, NULL, + 'U'}, // debugserver will ignore EXC_MASK_BAD_ACCESS, + // EXC_MASK_BAD_INSTRUCTION and EXC_MASK_ARITHMETIC, which results in + // SIGSEGV, SIGILL and SIGFPE being propagated to the target process. {NULL, 0, NULL, 0}}; int communication_fd = -1; @@ -1260,6 +1265,10 @@ int main(int argc, char *argv[]) { forward_env = true; break; + case 'U': + ctx.SetUnmaskSignals(true); + break; + case '2': // File descriptor passed to this process during fork/exec and is already // open and ready for communication. @@ -1514,8 +1523,8 @@ int main(int argc, char *argv[]) { RNBLogSTDOUT("Waiting to attach to process %s...\n", waitfor_pid_name.c_str()); nub_process_t pid = DNBProcessAttachWait( - waitfor_pid_name.c_str(), launch_flavor, ignore_existing, - timeout_ptr, waitfor_interval, err_str, sizeof(err_str)); + &ctx, waitfor_pid_name.c_str(), ignore_existing, timeout_ptr, + waitfor_interval, err_str, sizeof(err_str)); g_pid = pid; if (pid == INVALID_NUB_PROCESS) { @@ -1550,7 +1559,8 @@ int main(int argc, char *argv[]) { RNBLogSTDOUT("Attaching to process %s...\n", attach_pid_name.c_str()); nub_process_t pid = DNBProcessAttachByName( - attach_pid_name.c_str(), timeout_ptr, err_str, sizeof(err_str)); + attach_pid_name.c_str(), timeout_ptr, ctx.GetUnmaskSignals(), + err_str, sizeof(err_str)); g_pid = pid; if (pid == INVALID_NUB_PROCESS) { ctx.LaunchStatus().SetError(-1, DNBError::Generic);