jingham created this revision. jingham added reviewers: JDevlieghere, jasonmolenda, labath. Herald added a project: All. jingham requested review of this revision. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
For relatively uninteresting reasons, it is not possible when running under the debugger to invoke the mechanism that converts a Mach Exception into its BSD signal equivalent. The Mach Exceptions contain more information than the signal does, so we prefer to stop at the mach exception raise for most purposes. But if your application relies on a SIGBUS, SIGSEGV, etc. handler, then your application won't run properly in the debugger, it will just get stuck on the exception. The was a workaround for this added a while back - by passing -U to the debugger, EXC_BAD_ACCESS, EXC_BAD_INSTRUCTION and EXC_ARITHMETIC are masked out. But this isn't terribly convenient, because you have to invoke debugserver specially so you have to do this every time you debug, you can't use the feature when you don't control how debugserver is launched, and you can't control which exception you want to mask out. I made this more convenient by adding a QSetIgnoredExceptions packet to debugserver which will set the exceptions not to include, so we can direct this from lldb. Then I added a way for Platform to provide "extra startup commands" to the remote startup sequence - there was already a way for the user to supply extra startup commands, and I could have had users set the QSetIgnoredExceptions in the "target.extra-startup-commands", but that would be pretty undiscoverable. Instead, I added a "platform.plugin.darwin.ignored-exceptions" property that users can set with just the exception mask. Then PlatformDarwin gathers the results of this setting, and conses up the appropriate packet and returns that from PlatformDarwin::ExtraStartupCommands. I also wanted to add a validator for the property. Since I don't control the property creation (that all happens through processing the .td file) I couldn't put my validator in the OptionValueString made for the setting on construction, so I added an API to set it after the fact. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D125434 Files: lldb/include/lldb/Interpreter/OptionValueString.h lldb/include/lldb/Target/Platform.h lldb/include/lldb/Utility/StringExtractorGDBRemote.h lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h lldb/source/Plugins/Platform/MacOSX/PlatformMacOSXProperties.td lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp lldb/source/Target/Platform.cpp lldb/source/Utility/StringExtractorGDBRemote.cpp lldb/test/API/macosx/ignore_exceptions/Makefile lldb/test/API/macosx/ignore_exceptions/TestIgnoredExceptions.py lldb/test/API/macosx/ignore_exceptions/main.c lldb/tools/debugserver/source/DNB.cpp lldb/tools/debugserver/source/DNB.h lldb/tools/debugserver/source/MacOSX/MachException.cpp lldb/tools/debugserver/source/MacOSX/MachException.h lldb/tools/debugserver/source/MacOSX/MachProcess.h lldb/tools/debugserver/source/MacOSX/MachProcess.mm lldb/tools/debugserver/source/MacOSX/MachTask.h lldb/tools/debugserver/source/MacOSX/MachTask.mm lldb/tools/debugserver/source/RNBContext.cpp lldb/tools/debugserver/source/RNBContext.h lldb/tools/debugserver/source/RNBRemote.cpp lldb/tools/debugserver/source/RNBRemote.h lldb/tools/debugserver/source/debugserver.cpp
Index: lldb/tools/debugserver/source/debugserver.cpp =================================================================== --- lldb/tools/debugserver/source/debugserver.cpp +++ lldb/tools/debugserver/source/debugserver.cpp @@ -368,7 +368,7 @@ DNBLogThreadedIf(LOG_RNB_MINIMAL, "%s Attaching to pid %i...", __FUNCTION__, attach_pid); char err_str[1024]; - pid = DNBProcessAttach(attach_pid, NULL, ctx.GetUnmaskSignals(), err_str, + pid = DNBProcessAttach(attach_pid, NULL, ctx.GetIgnoredExceptions(), err_str, sizeof(err_str)); g_pid = pid; @@ -1275,7 +1275,7 @@ break; case 'U': - ctx.SetUnmaskSignals(true); + ctx.AddDefaultIgnoredExceptions(); break; case '2': @@ -1574,7 +1574,7 @@ RNBLogSTDOUT("Attaching to process %s...\n", attach_pid_name.c_str()); nub_process_t pid = DNBProcessAttachByName( - attach_pid_name.c_str(), timeout_ptr, ctx.GetUnmaskSignals(), + attach_pid_name.c_str(), timeout_ptr, ctx.GetIgnoredExceptions(), err_str, sizeof(err_str)); g_pid = pid; if (pid == INVALID_NUB_PROCESS) { Index: lldb/tools/debugserver/source/RNBRemote.h =================================================================== --- lldb/tools/debugserver/source/RNBRemote.h +++ lldb/tools/debugserver/source/RNBRemote.h @@ -110,6 +110,7 @@ start_noack_mode, // 'QStartNoAckMode' prefix_reg_packets_with_tid, // 'QPrefixRegisterPacketsWithThreadID set_logging_mode, // 'QSetLogging:' + set_ignored_exceptions, // 'QSetIgnoredExceptions' set_max_packet_size, // 'QSetMaxPacketSize:' set_max_payload_size, // 'QSetMaxPayloadSize:' set_environment_variable, // 'QEnvironment:' @@ -197,6 +198,7 @@ rnb_err_t HandlePacket_QStartNoAckMode(const char *p); rnb_err_t HandlePacket_QThreadSuffixSupported(const char *p); rnb_err_t HandlePacket_QSetLogging(const char *p); + rnb_err_t HandlePacket_QSetIgnoredExceptions(const char *p); rnb_err_t HandlePacket_QSetDisableASLR(const char *p); rnb_err_t HandlePacket_QSetSTDIO(const char *p); rnb_err_t HandlePacket_QSetWorkingDir(const char *p); Index: lldb/tools/debugserver/source/RNBRemote.cpp =================================================================== --- lldb/tools/debugserver/source/RNBRemote.cpp +++ lldb/tools/debugserver/source/RNBRemote.cpp @@ -394,9 +394,12 @@ "'G', 'p', and 'P') support having the thread ID appended " "to the end of the command")); t.push_back(Packet(set_logging_mode, &RNBRemote::HandlePacket_QSetLogging, - NULL, "QSetLogging:", "Check if register packets ('g', " - "'G', 'p', and 'P' support having " - "the thread ID prefix")); + NULL, "QSetLogging:", "Turn on log channels in debugserver")); + t.push_back(Packet(set_ignored_exceptions, &RNBRemote::HandlePacket_QSetIgnoredExceptions, + NULL, "QSetIgnoredExceptions:", "Set the exception types " + "debugserver won't wait for, allowing " + "them to be turned into the equivalent " + "BSD signals by the normal means.")); t.push_back(Packet( set_max_packet_size, &RNBRemote::HandlePacket_QSetMaxPacketSize, NULL, "QSetMaxPacketSize:", @@ -2210,6 +2213,37 @@ return rnb_success; } +rnb_err_t RNBRemote::HandlePacket_QSetIgnoredExceptions(const char *p) { + // We can't set the ignored exceptions if we have a running process: + if (m_ctx.HasValidProcessID()) + return SendPacket("E35"); + + p += sizeof("QSetIgnoredExceptions:") - 1; + bool success = true; + while(1) { + const char *bar = strchr(p, '|'); + if (bar == nullptr) { + success = m_ctx.AddIgnoredException(p); + break; + } else { + std::string exc_str(p, bar - p); + if (exc_str.empty()) { + success = false; + break; + } + + success = m_ctx.AddIgnoredException(exc_str.c_str()); + if (!success) + break; + p = bar + 1; + } + } + if (success) + return SendPacket("OK"); + else + return SendPacket("E36"); +} + rnb_err_t RNBRemote::HandlePacket_QThreadSuffixSupported(const char *p) { m_thread_suffix_supported = true; return SendPacket("OK"); @@ -3791,8 +3825,8 @@ "'%s'", getpid(), attach_name.c_str()); attach_pid = DNBProcessAttachByName(attach_name.c_str(), NULL, - Context().GetUnmaskSignals(), err_str, - sizeof(err_str)); + Context().GetIgnoredExceptions(), + err_str, sizeof(err_str)); } else if (strstr(p, "vAttach;") == p) { p += strlen("vAttach;"); @@ -3806,7 +3840,8 @@ DNBLog("[LaunchAttach] START %d vAttach to pid %d", getpid(), pid_attaching_to); attach_pid = DNBProcessAttach(pid_attaching_to, &attach_timeout_abstime, - false, err_str, sizeof(err_str)); + m_ctx.GetIgnoredExceptions(), + err_str, sizeof(err_str)); } } else { return HandlePacket_UNIMPLEMENTED(p); Index: lldb/tools/debugserver/source/RNBContext.h =================================================================== --- lldb/tools/debugserver/source/RNBContext.h +++ lldb/tools/debugserver/source/RNBContext.h @@ -21,6 +21,7 @@ class RNBContext { public: + using IgnoredExceptions = std::vector<exception_mask_t>; enum { event_proc_state_changed = 0x001, event_proc_thread_running = 0x002, // Sticky @@ -118,10 +119,13 @@ 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 AddIgnoredException(const char *exception_name); + + void AddDefaultIgnoredExceptions(); + + const IgnoredExceptions &GetIgnoredExceptions() { + return m_ignored_exceptions; } - bool GetUnmaskSignals() { return m_unmask_signals; } protected: // Classes that inherit from RNBContext can see and modify these @@ -144,7 +148,7 @@ std::string m_working_directory; std::string m_process_event; bool m_detach_on_error = false; - bool m_unmask_signals = false; + IgnoredExceptions m_ignored_exceptions; void StartProcessStatusThread(); void StopProcessStatusThread(); Index: lldb/tools/debugserver/source/RNBContext.cpp =================================================================== --- lldb/tools/debugserver/source/RNBContext.cpp +++ lldb/tools/debugserver/source/RNBContext.cpp @@ -24,6 +24,7 @@ #include "DNB.h" #include "DNBLog.h" #include "RNBRemote.h" +#include "MacOSX/MachException.h" // Destructor RNBContext::~RNBContext() { SetProcessID(INVALID_NUB_PROCESS); } @@ -286,3 +287,17 @@ nub_state_t pid_state = DNBProcessGetState(m_pid); return pid_state == eStateRunning || pid_state == eStateStepping; } + +bool RNBContext::AddIgnoredException(const char *exception_name) { + exception_mask_t exc_mask = MachException::ExceptionMask(exception_name); + if (exc_mask == 0) + return false; + m_ignored_exceptions.push_back(exc_mask); + return true; +} + +void RNBContext::AddDefaultIgnoredExceptions() { + m_ignored_exceptions.push_back(EXC_MASK_BAD_ACCESS); + m_ignored_exceptions.push_back(EXC_MASK_BAD_INSTRUCTION); + m_ignored_exceptions.push_back(EXC_MASK_ARITHMETIC); +} Index: lldb/tools/debugserver/source/MacOSX/MachTask.mm =================================================================== --- lldb/tools/debugserver/source/MacOSX/MachTask.mm +++ lldb/tools/debugserver/source/MacOSX/MachTask.mm @@ -602,7 +602,9 @@ return false; } -bool MachTask::StartExceptionThread(bool unmask_signals, DNBError &err) { +bool MachTask::StartExceptionThread( + const RNBContext::IgnoredExceptions &ignored_exceptions, + DNBError &err) { DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__); task_t task = TaskPortForProcessID(err); @@ -631,10 +633,9 @@ 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); + if (!ignored_exceptions.empty()) { + for (exception_mask_t mask : ignored_exceptions) + m_exc_port_info.mask = m_exc_port_info.mask & ~mask; } // Set the ability to get all exceptions on this port Index: lldb/tools/debugserver/source/MacOSX/MachTask.h =================================================================== --- lldb/tools/debugserver/source/MacOSX/MachTask.h +++ lldb/tools/debugserver/source/MacOSX/MachTask.h @@ -21,6 +21,7 @@ #include <map> #include <string> #include "DNBDefs.h" +#include "RNBContext.h" #include "MachException.h" #include "MachVMMemory.h" #include "PThreadMutex.h" @@ -67,7 +68,8 @@ kern_return_t RestoreExceptionPortInfo(); kern_return_t ShutDownExcecptionThread(); - bool StartExceptionThread(bool unmask_signals, DNBError &err); + bool StartExceptionThread( + const RNBContext::IgnoredExceptions &ignored_exceptions, 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); Index: lldb/tools/debugserver/source/MacOSX/MachProcess.mm =================================================================== --- lldb/tools/debugserver/source/MacOSX/MachProcess.mm +++ lldb/tools/debugserver/source/MacOSX/MachProcess.mm @@ -2590,8 +2590,11 @@ return NULL; } -pid_t MachProcess::AttachForDebug(pid_t pid, bool unmask_signals, char *err_str, - size_t err_len) { +pid_t MachProcess::AttachForDebug( + pid_t pid, + const RNBContext::IgnoredExceptions &ignored_exceptions, + char *err_str, + size_t err_len) { // Clear out and clean up from any current state Clear(); if (pid != 0) { @@ -2608,7 +2611,7 @@ SetState(eStateAttaching); m_pid = pid; - if (!m_task.StartExceptionThread(unmask_signals, err)) { + if (!m_task.StartExceptionThread(ignored_exceptions, err)) { const char *err_cstr = err.AsString(); ::snprintf(err_str, err_len, "%s", err_cstr ? err_cstr : "unable to start the exception thread"); @@ -3112,7 +3115,9 @@ // 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, bool unmask_signals, DNBError &launch_err) { + const char *event_data, + const RNBContext::IgnoredExceptions &ignored_exceptions, + DNBError &launch_err) { // Clear out and clean up from any current state Clear(); @@ -3138,7 +3143,7 @@ m_flags |= (eMachProcessFlagsUsingFBS | eMachProcessFlagsBoardCalculated); if (BoardServiceLaunchForDebug(app_bundle_path.c_str(), argv, envp, no_stdio, disable_aslr, event_data, - unmask_signals, launch_err) != 0) + ignored_exceptions, launch_err) != 0) return m_pid; // A successful SBLaunchForDebug() returns and assigns a // non-zero m_pid. } @@ -3152,7 +3157,7 @@ m_flags |= (eMachProcessFlagsUsingBKS | eMachProcessFlagsBoardCalculated); if (BoardServiceLaunchForDebug(app_bundle_path.c_str(), argv, envp, no_stdio, disable_aslr, event_data, - unmask_signals, launch_err) != 0) + ignored_exceptions, launch_err) != 0) return m_pid; // A successful SBLaunchForDebug() returns and assigns a // non-zero m_pid. } @@ -3164,7 +3169,7 @@ std::string app_bundle_path = GetAppBundle(path); if (!app_bundle_path.empty()) { if (SBLaunchForDebug(app_bundle_path.c_str(), argv, envp, no_stdio, - disable_aslr, unmask_signals, launch_err) != 0) + disable_aslr, ignored_exceptions, launch_err) != 0) return m_pid; // A successful SBLaunchForDebug() returns and assigns a // non-zero m_pid. } @@ -3198,7 +3203,7 @@ for (i = 0; (arg = argv[i]) != NULL; i++) m_args.push_back(arg); - m_task.StartExceptionThread(unmask_signals, launch_err); + m_task.StartExceptionThread(ignored_exceptions, launch_err); if (launch_err.Fail()) { if (launch_err.AsString() == NULL) launch_err.SetErrorString("unable to start the exception thread"); @@ -3566,7 +3571,9 @@ pid_t MachProcess::SBLaunchForDebug(const char *path, char const *argv[], char const *envp[], bool no_stdio, - bool disable_aslr, bool unmask_signals, + bool disable_aslr, + const RNBContext::IgnoredExceptions + &ignored_exceptions, DNBError &launch_err) { // Clear out and clean up from any current state Clear(); @@ -3583,7 +3590,7 @@ char const *arg; for (i = 0; (arg = argv[i]) != NULL; i++) m_args.push_back(arg); - m_task.StartExceptionThread(unmask_signals, launch_err); + m_task.StartExceptionThread(ignored_exceptions, launch_err); if (launch_err.Fail()) { if (launch_err.AsString() == NULL) @@ -3784,7 +3791,8 @@ #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, bool unmask_signals, + bool disable_aslr, const char *event_data, + const RNBContext::IgnoredExceptions &ignored_exceptions, DNBError &launch_err) { DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path); @@ -3798,7 +3806,7 @@ char const *arg; for (i = 0; (arg = argv[i]) != NULL; i++) m_args.push_back(arg); - m_task.StartExceptionThread(unmask_signals, launch_err); + m_task.StartExceptionThread(ignored_exceptions, launch_err); if (launch_err.Fail()) { if (launch_err.AsString() == NULL) Index: lldb/tools/debugserver/source/MacOSX/MachProcess.h =================================================================== --- lldb/tools/debugserver/source/MacOSX/MachProcess.h +++ lldb/tools/debugserver/source/MacOSX/MachProcess.h @@ -34,6 +34,7 @@ #include "PThreadCondition.h" #include "PThreadEvent.h" #include "PThreadMutex.h" +#include "RNBContext.h" #include "ThreadInfo.h" class DNBThreadResumeActions; @@ -78,14 +79,17 @@ }; // Child process control - pid_t AttachForDebug(pid_t pid, bool unmask_signals, char *err_str, + pid_t AttachForDebug(pid_t pid, + const RNBContext::IgnoredExceptions &ignored_exceptions, + 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, - bool unmask_signals, DNBError &err); + const RNBContext::IgnoredExceptions &ignored_exceptions, + DNBError &err); static uint32_t GetCPUTypeForLocalProcess(pid_t pid); static pid_t ForkChildForPTraceDebugging(const char *path, char const *argv[], @@ -109,7 +113,8 @@ pid_t BoardServiceLaunchForDebug(const char *app_bundle_path, char const *argv[], char const *envp[], bool no_stdio, bool disable_aslr, - const char *event_data, bool unmask_signals, + const char *event_data, + const RNBContext::IgnoredExceptions &ignored_exceptions, DNBError &launch_err); pid_t BoardServiceForkChildForPTraceDebugging( const char *path, char const *argv[], char const *envp[], bool no_stdio, Index: lldb/tools/debugserver/source/MacOSX/MachException.h =================================================================== --- lldb/tools/debugserver/source/MacOSX/MachException.h +++ lldb/tools/debugserver/source/MacOSX/MachException.h @@ -127,6 +127,7 @@ uint8_t flags; // Action flags describing what to do with the exception }; static const char *Name(exception_type_t exc_type); + static exception_mask_t ExceptionMask(const char *name); }; #endif Index: lldb/tools/debugserver/source/MacOSX/MachException.cpp =================================================================== --- lldb/tools/debugserver/source/MacOSX/MachException.cpp +++ lldb/tools/debugserver/source/MacOSX/MachException.cpp @@ -512,3 +512,49 @@ } return NULL; } + +// Returns the exception mask for a given exception name. +// 0 is not a legit mask, so we return that in the case of an error. +exception_mask_t MachException::ExceptionMask(const char *name) { + static const char *exception_prefix = "EXC_"; + static const int prefix_len = strlen(exception_prefix); + + // All mach exceptions start with this prefix: + if (strstr(name, exception_prefix) != name) + return 0; + + name += prefix_len; + if (strcmp(name, "BAD_ACCESS") == 0) + return EXC_MASK_BAD_ACCESS; + if (strcmp(name, "BAD_INSTRUCTION") == 0) + return EXC_MASK_BAD_INSTRUCTION; + if (strcmp(name, "ARITHMETIC") == 0) + return EXC_MASK_ARITHMETIC; + if (strcmp(name, "EMULATION") == 0) + return EXC_MASK_EMULATION; + if (strcmp(name, "SOFTWARE") == 0) + return EXC_MASK_SOFTWARE; + if (strcmp(name, "BREAKPOINT") == 0) + return EXC_MASK_BREAKPOINT; + if (strcmp(name, "SYSCALL") == 0) + return EXC_MASK_SYSCALL; + if (strcmp(name, "MACH_SYSCALL") == 0) + return EXC_MASK_MACH_SYSCALL; + if (strcmp(name, "RPC_ALERT") == 0) + return EXC_MASK_RPC_ALERT; +#ifdef EXC_CRASH + if (strcmp(name, "CRASH") == 0) + return EXC_MASK_CRASH; +#endif + if (strcmp(name, "RESOURCE") == 0) + return EXC_MASK_RESOURCE; +#ifdef EXC_GUARD + if (strcmp(name, "GUARD") == 0) + return EXC_MASK_GUARD; +#endif +#ifdef EXC_CORPSE_NOTIFY + if (strcmp(name, "CORPSE_NOTIFY") == 0) + return EXC_MASK_CORPSE_NOTIFY; +#endif + return 0; +} Index: lldb/tools/debugserver/source/DNB.h =================================================================== --- lldb/tools/debugserver/source/DNB.h +++ lldb/tools/debugserver/source/DNB.h @@ -51,10 +51,14 @@ nub_process_t DNBProcessGetPIDByName(const char *name); nub_process_t DNBProcessAttach(nub_process_t pid, struct timespec *timeout, - bool unmask_signals, char *err_str, + const RNBContext::IgnoredExceptions + &ignored_exceptions, + char *err_str, size_t err_len); nub_process_t DNBProcessAttachByName(const char *name, struct timespec *timeout, - bool unmask_signals, char *err_str, + const RNBContext::IgnoredExceptions + &ignored_exceptions, + char *err_str, size_t err_len); nub_process_t DNBProcessAttachWait(RNBContext *ctx, const char *wait_name, bool ignore_existing, Index: lldb/tools/debugserver/source/DNB.cpp =================================================================== --- lldb/tools/debugserver/source/DNB.cpp +++ lldb/tools/debugserver/source/DNB.cpp @@ -352,7 +352,7 @@ 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); + ctx->GetIgnoredExceptions(), launch_err); if (err_str) { *err_str = '\0'; if (launch_err.Fail()) { @@ -412,7 +412,8 @@ } nub_process_t DNBProcessAttachByName(const char *name, struct timespec *timeout, - bool unmask_signals, char *err_str, + const RNBContext::IgnoredExceptions + &ignored_exceptions, char *err_str, size_t err_len) { if (err_str && err_len > 0) err_str[0] = '\0'; @@ -434,11 +435,13 @@ } return DNBProcessAttach(matching_proc_infos[0].kp_proc.p_pid, timeout, - unmask_signals, err_str, err_len); + ignored_exceptions, err_str, err_len); } nub_process_t DNBProcessAttach(nub_process_t attach_pid, - struct timespec *timeout, bool unmask_signals, + struct timespec *timeout, + const RNBContext::IgnoredExceptions + &ignored_exceptions, char *err_str, size_t err_len) { if (err_str && err_len > 0) err_str[0] = '\0'; @@ -487,7 +490,8 @@ DNBLogThreadedIf(LOG_PROCESS, "(DebugNub) attaching to pid %d...", attach_pid); pid = - processSP->AttachForDebug(attach_pid, unmask_signals, err_str, err_len); + processSP->AttachForDebug(attach_pid, ignored_exceptions, err_str, + err_len); if (pid != INVALID_NUB_PROCESS) { bool res = AddProcessToMap(pid, processSP); @@ -782,7 +786,8 @@ DNBLogThreadedIf(LOG_PROCESS, "Attaching to %s with pid %i...\n", waitfor_process_name, waitfor_pid); waitfor_pid = DNBProcessAttach(waitfor_pid, timeout_abstime, - ctx->GetUnmaskSignals(), err_str, err_len); + ctx->GetIgnoredExceptions(), err_str, + err_len); } bool success = waitfor_pid != INVALID_NUB_PROCESS; Index: lldb/test/API/macosx/ignore_exceptions/main.c =================================================================== --- /dev/null +++ lldb/test/API/macosx/ignore_exceptions/main.c @@ -0,0 +1,27 @@ +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <stdio.h> + +int g_ints[] = {10, 20, 30, 40, 50, 60}; + +void +saction_handler(int signo, siginfo_t info, void *baton) { + printf("Got into handler.\n"); + mprotect(g_ints, sizeof(g_ints), PROT_READ|PROT_WRITE); // stop here in the signal handler + g_ints[0] = 20; +} +int +main() +{ + mprotect(g_ints, 10*sizeof(int) , PROT_NONE); + struct sigaction my_action; + sigemptyset(&my_action.sa_mask); + my_action.sa_handler = (void (*)(int)) saction_handler; + my_action.sa_flags = SA_SIGINFO; + + sigaction(SIGBUS, &my_action, NULL); // Stop here to get things going. + int local_value = g_ints[1]; + return local_value; // Break here to make sure we got past the signal handler +} Index: lldb/test/API/macosx/ignore_exceptions/TestIgnoredExceptions.py =================================================================== --- /dev/null +++ lldb/test/API/macosx/ignore_exceptions/TestIgnoredExceptions.py @@ -0,0 +1,60 @@ +""" +Test that by turning off EXC_BAD_ACCESS catching, we can +debug into and out of a signal handler. +""" + +import lldb +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.lldbtest import * + +class TestDarwinSignalHandlers(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + @skipUnlessDarwin + def test_ignored_thread(self): + """It isn't possible to convert an EXC_BAD_ACCESS to a signal when + running under the debugger, which makes debugging SIGBUS handlers + and so forth difficult. This test sends QIgnoreExceptions and that + should get us into the signal handler and out again. """ + self.build() + self.main_source_file = lldb.SBFileSpec("main.c") + self.suspended_thread_test() + + def suspended_thread_test(self): + # Make sure that we don't accept bad values: + self.match("settings set platform.plugin.darwin.ignored-exceptions EXC_BAD_AXESS", "EXC_BAD_AXESS", error=True) + # Now set ourselves to ignore some exceptions. The test depends on ignoring EXC_BAD_ACCESS, but I passed a couple + # to make sure they parse: + self.runCmd("settings set platform.plugin.darwin.ignored-exceptions EXC_BAD_ACCESS|EXC_ARITHMETIC") + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self, + "Stop here to get things going", self.main_source_file) + + sig_bkpt = target.BreakpointCreateBySourceRegex("stop here in the signal handler", + self.main_source_file) + self.assertEqual(sig_bkpt.GetNumLocations(), 1, "Found sig handler breakpoint") + return_bkpt = target.BreakpointCreateBySourceRegex("Break here to make sure we got past the signal handler", + self.main_source_file) + self.assertEqual(return_bkpt.GetNumLocations(), 1, "Found return breakpoint") + # Now continue, and we should stop with a stop reason of SIGBUS: + process.Continue() + self.assertEqual(process.state, lldb.eStateStopped, "Stopped after continue to SIGBUS") + self.assertEqual(thread.stop_reason, lldb.eStopReasonSignal) + self.assertEqual(thread.GetStopReasonDataAtIndex(0), 10, "Got a SIGBUS") + + # Now when we continue, we'll find our way into the signal handler: + threads = lldbutil.continue_to_breakpoint(process, sig_bkpt) + self.assertEqual(len(threads), 1, "Stopped at sig breakpoint") + + threads = lldbutil.continue_to_breakpoint(process, return_bkpt) + self.assertEqual(len(threads), 1, "Stopped at return breakpoint") + + # Make sure we really changed the value: + + process.Continue() + self.assertEqual(process.state, lldb.eStateExited, "Process exited") + self.assertEqual(process.exit_state, 20, "Got the right exit status") + Index: lldb/test/API/macosx/ignore_exceptions/Makefile =================================================================== --- /dev/null +++ lldb/test/API/macosx/ignore_exceptions/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c +CFLAGS_EXTRAS := -std=c99 + +include Makefile.rules Index: lldb/source/Utility/StringExtractorGDBRemote.cpp =================================================================== --- lldb/source/Utility/StringExtractorGDBRemote.cpp +++ lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -126,6 +126,8 @@ return eServerPacketType_QSetWorkingDir; if (PACKET_STARTS_WITH("QSetLogging:")) return eServerPacketType_QSetLogging; + if (PACKET_STARTS_WITH("QSetIgnoredExceptions")) + return eServerPacketType_QSetIgnoredExceptions; if (PACKET_STARTS_WITH("QSetMaxPacketSize:")) return eServerPacketType_QSetMaxPacketSize; if (PACKET_STARTS_WITH("QSetMaxPayloadSize:")) Index: lldb/source/Target/Platform.cpp =================================================================== --- lldb/source/Target/Platform.cpp +++ lldb/source/Target/Platform.cpp @@ -1945,6 +1945,10 @@ return CompilerType(); } +Args Platform::GetExtraStartupCommands() { + return {}; +} + PlatformSP PlatformList::GetOrCreate(llvm::StringRef name) { std::lock_guard<std::recursive_mutex> guard(m_mutex); for (const PlatformSP &platform_sp : m_platforms) { Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -954,12 +954,24 @@ m_gdb_comm.GetVAttachOrWaitSupported(); m_gdb_comm.EnableErrorStringInPacket(); - size_t num_cmds = GetExtraStartupCommands().GetArgumentCount(); - for (size_t idx = 0; idx < num_cmds; idx++) { - StringExtractorGDBRemote response; - m_gdb_comm.SendPacketAndWaitForResponse( - GetExtraStartupCommands().GetArgumentAtIndex(idx), response); + // First dispatch any commands from the platform: + auto handle_cmds = [&] (const Args &args) -> void { + size_t num_cmds = args.GetArgumentCount(); + for (size_t idx = 0; idx < num_cmds; idx++) { + StringExtractorGDBRemote response; + m_gdb_comm.SendPacketAndWaitForResponse( + args.GetArgumentAtIndex(idx), response); + } + }; + + PlatformSP platform_sp = GetTarget().GetPlatform(); + if (platform_sp) { + handle_cmds(platform_sp->GetExtraStartupCommands()); } + + // Then dispatch any process commands: + handle_cmds(GetExtraStartupCommands()); + return error; } Index: lldb/source/Plugins/Platform/MacOSX/PlatformMacOSXProperties.td =================================================================== --- lldb/source/Plugins/Platform/MacOSX/PlatformMacOSXProperties.td +++ lldb/source/Plugins/Platform/MacOSX/PlatformMacOSXProperties.td @@ -5,3 +5,12 @@ DefaultStringValue<"">, Desc<"Directories/KDKs to search for kexts in when starting a kernel debug session.">; } + +let Definition = "platformdarwin" in { + def IgnoredExceptions: Property<"ignored-exceptions", "String">, + DefaultStringValue<"">, + Desc<"List the mach exceptions to ignore, separated by '|' " + "(e.g. 'EXC_BAD_ACCESS|EXC_BAD_INSTRUCTION'). " + "lldb will instead stop on the BSD signal the exception was converted" + " into, if there is one.">; +} Index: lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h =================================================================== --- lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h +++ lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h @@ -48,6 +48,18 @@ ~PlatformDarwin() override; + static lldb::PlatformSP CreateInstance(bool force, const ArchSpec *arch); + + static void DebuggerInitialize(lldb_private::Debugger &debugger); + + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "darwin"; } + + static llvm::StringRef GetDescriptionStatic(); + Status PutFile(const FileSpec &source, const FileSpec &destination, uint32_t uid = UINT32_MAX, uint32_t gid = UINT32_MAX) override; @@ -96,6 +108,8 @@ FileSpec LocateExecutable(const char *basename) override; Status LaunchProcess(ProcessLaunchInfo &launch_info) override; + + Args GetExtraStartupCommands() override; static std::tuple<llvm::VersionTuple, llvm::StringRef> ParseVersionBuildDir(llvm::StringRef str); Index: lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp =================================================================== --- lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp +++ lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp @@ -19,11 +19,15 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" #include "lldb/Core/Section.h" #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" #include "lldb/Host/XML.h" #include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/Options.h" #include "lldb/Symbol/LocateSymbolFile.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/SymbolFile.h" @@ -48,12 +52,136 @@ using namespace lldb; using namespace lldb_private; +static Status ExceptionMaskValidator(const char *string, void *unused) { + Status error; + llvm::StringRef str_ref(string); + llvm::SmallVector<llvm::StringRef> candidates; + str_ref.split(candidates, '|'); + for (auto candidate : candidates) { + if (!(candidate == "EXC_BAD_ACCESS" + || candidate == "EXC_BAD_INSTRUCTION" + || candidate == "EXC_ARITHMETIC" + || candidate == "EXC_RESOURCE" + || candidate == "EXC_GUARD")) { + error.SetErrorStringWithFormat("invalid exception type: '%s'", + candidate.str().c_str()); + return error; + } + } + return {}; +} + /// Destructor. /// /// The destructor is virtual since this class is designed to be /// inherited from by the plug-in instance. PlatformDarwin::~PlatformDarwin() = default; +// Static Variables +static uint32_t g_initialize_count = 0; + +void PlatformDarwin::Initialize() { + Platform::Initialize(); + + if (g_initialize_count++ == 0) { + PluginManager::RegisterPlugin(PlatformDarwin::GetPluginNameStatic(), + PlatformDarwin::GetDescriptionStatic(), + PlatformDarwin::CreateInstance, + PlatformDarwin::DebuggerInitialize); + } +} + +void PlatformDarwin::Terminate() { + if (g_initialize_count > 0) { + if (--g_initialize_count == 0) { + PluginManager::UnregisterPlugin(PlatformDarwin::CreateInstance); + } + } + + Platform::Terminate(); +} + +llvm::StringRef PlatformDarwin::GetDescriptionStatic() { + return "Darwin platform plug-in."; +} + +PlatformSP PlatformDarwin::CreateInstance(bool force, const ArchSpec *arch) { + // We only create subclasses of the PlatformDarwin plugin. + return PlatformSP(); +} + +#define LLDB_PROPERTIES_platformdarwin +#include "PlatformMacOSXProperties.inc" + +#define LLDB_PROPERTIES_platformdarwin +enum { +#include "PlatformMacOSXPropertiesEnum.inc" +}; + +class PlatformDarwinProperties : public Properties { +public: + static ConstString &GetSettingName() { + static ConstString g_setting_name("darwin"); + return g_setting_name; + } + + PlatformDarwinProperties() : Properties() { + m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName()); + m_collection_sp->Initialize(g_platformdarwin_properties); + } + + ~PlatformDarwinProperties() override = default; + + const char *GetIgnoredExceptions() const { + const uint32_t idx = ePropertyIgnoredExceptions; + const OptionValueString *option_value = + m_collection_sp->GetPropertyAtIndexAsOptionValueString( + NULL, false, idx); + assert(option_value); + return option_value->GetCurrentValue(); + } + + OptionValueString *GetIgnoredExceptionValue() { + const uint32_t idx = ePropertyIgnoredExceptions; + OptionValueString *option_value = + m_collection_sp->GetPropertyAtIndexAsOptionValueString( + NULL, false, idx); + assert(option_value); + return option_value; + } +}; + +static PlatformDarwinProperties &GetGlobalProperties() { + static PlatformDarwinProperties g_settings; + return g_settings; +} + +void PlatformDarwin::DebuggerInitialize( + lldb_private::Debugger &debugger) { + if (!PluginManager::GetSettingForPlatformPlugin( + debugger, PlatformDarwinProperties::GetSettingName())) { + const bool is_global_setting = false; + PluginManager::CreateSettingForPlatformPlugin( + debugger, GetGlobalProperties().GetValueProperties(), + ConstString("Properties for the PlatformDarwin plug-in."), + is_global_setting); + OptionValueString *value = GetGlobalProperties().GetIgnoredExceptionValue(); + value->SetValidator(ExceptionMaskValidator); + } +} + +Args +PlatformDarwin::GetExtraStartupCommands() { + const char *ignored_exceptions = GetGlobalProperties().GetIgnoredExceptions(); + if (!ignored_exceptions || ignored_exceptions[0] == '\0' ) + return {}; + Args ret_args; + std::string packet = "QSetIgnoredExceptions:"; + packet.append(ignored_exceptions); + ret_args.AppendArgument(packet); + return ret_args; +} + lldb_private::Status PlatformDarwin::PutFile(const lldb_private::FileSpec &source, const lldb_private::FileSpec &destination, uint32_t uid, Index: lldb/include/lldb/Utility/StringExtractorGDBRemote.h =================================================================== --- lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -174,6 +174,7 @@ eServerPacketType_QMemTags, // write memory tags eServerPacketType_qLLDBSaveCore, + eServerPacketType_QSetIgnoredExceptions }; ServerPacketType GetServerPacketType() const; Index: lldb/include/lldb/Target/Platform.h =================================================================== --- lldb/include/lldb/Target/Platform.h +++ lldb/include/lldb/Target/Platform.h @@ -847,6 +847,8 @@ } virtual CompilerType GetSiginfoType(const llvm::Triple &triple); + + virtual Args GetExtraStartupCommands(); protected: /// Create a list of ArchSpecs with the given OS and a architectures. The Index: lldb/include/lldb/Interpreter/OptionValueString.h =================================================================== --- lldb/include/lldb/Interpreter/OptionValueString.h +++ lldb/include/lldb/Interpreter/OptionValueString.h @@ -109,6 +109,11 @@ bool IsCurrentValueEmpty() const { return m_current_value.empty(); } bool IsDefaultValueEmpty() const { return m_default_value.empty(); } + + void SetValidator(ValidatorCallback validator, void *baton = nullptr) { + m_validator = validator; + m_validator_baton = baton; + } protected: std::string m_current_value;
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits