如何使lldb忽略EXC_BAD_ACCESS异常?

9

我正在 Mac OSX 上编写一个程序,依赖于 sigaction/sa_handler 机制。运行用户的代码片段并随时准备捕获信号/异常。程序工作良好,但问题是我无法使用 lldb 进行调试。即使我设置了忽略任何异常,lldb 似乎也无法忽略。

proc hand -p true -s false SIGSEGV 
proc hand -p true -s false SIGBUS

即使尝试使用命令c,控制流程也会在触发异常的指令处停止,并且不会跳转到我之前安装的sa_handler。输出结果为:

Process 764 stopped
* thread #2: tid = 0xf140, 0x00000001000b8000, stop reason = EXC_BAD_ACCESS (code=2, address=0x1000b8000)

我该如何让lldb忽略异常/信号,使程序的sa_handler起作用?
编辑:示例代码
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>

static void handler(int signo, siginfo_t *sigaction, void *context)
{
    printf("in handler.\n");
    signal(signo, SIG_DFL);
}

static void gen_exception()
{
    printf("gen_exception in.\n");
    *(int *)0 = 0;
    printf("gen_exception out.\n");
}

void *gen_exception_thread(void *parg)
{
    gen_exception();
    return 0;
}

int main()
{
    struct sigaction sa;
    sa.sa_sigaction = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;

    if(sigaction(/*SIGBUS*/SIGSEGV, &sa, NULL) == -1) {
        printf("sigaction fails.\n");
        return 0;
    }

    pthread_t id;
    pthread_create(&id, NULL, gen_exception_thread, NULL);
    pthread_join(id, NULL);

    return 0;
}
4个回答

7

我最近在一个项目中需要用到这个,所以我自己构建了LLDB。 我在tools/debugserver/source/MacOSX/MachTask.mm 中修补了一行代码。

err = ::task_set_exception_ports (task, m_exc_port_info.mask, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);

为了

err = ::task_set_exception_ports (task, m_exc_port_info.mask & ~EXC_MASK_BAD_ACCESS, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);

由此导致debugserver无法捕获EXC_BAD_ACCESS异常。现在,我的自定义LLDB可以很好地工作:它仍然可以捕获SIGSEGV和SIGBUS异常,但面对EXC_BAD_ACCESS时不再进入愚蠢的无限循环。预先设置进程处理选项也能很好地处理前面致命的信号,现在我可以毫不顾虑地调试SEGV处理程序了。
苹果应该将这个功能添加到LLDB中...这对他们来说似乎是一个非常简单的修复方案。

“built my own LLDB”是什么意思?我假设LLDB仍然是Xcode调试器(我正在使用Xcode 6.4)。你是在谈论LLDB的设置更改吗?如果是这样,那么tools/debugserver/source/MacOSX/MachTask.mm文件在哪里? - Ash
3
LLDB 是开源的,所以我下载了源代码,在那里进行了必要的补丁,并进行了编译。 - nneonneo

5

这是Mac OS X调试器接口中长期存在的一个bug(gdb也有同样的问题...)。如果你有开发者账号,请向http://bugreport.apple.com提交bug报告。实际上使用SIGSEGV处理程序的人很少,所以这个问题从未引起内核工程师的注意,所以更多的bug是好事...


2
嗨Jim:我注意到你在这个LLDB错误报告上发表了评论,指出“直接的方法需要调试器以root身份运行”。在OS X上,我完全可以将调试器作为root运行(我理解该错误报告是关于iOS的,因此这是不可能的)。是否有任何方法可以修补LLDB,使其在调试器以root身份运行时忽略EXC_BAD_ACCESS? - nneonneo
  • 提交问题 https://bugreport.apple.com/web/?problemID=46412375
  • @jim-ingham 能否详细说明“直接的方法需要调试器以 root 用户身份运行”?假设用户允许将 gdb 作为 root 运行,那么该如何操作?
- timotheecour
真正需要以root身份运行的是debugserver。如果它以root身份运行,它可以将EXC_BAD_ACCESS转发到主机异常端口(即插入任务异常端口直到lldb取代它的那个端口)。这将把它转换为SIGSEGV,这就是你想要的。请注意,由于lldb不监听BSD信号,这意味着任何未处理的SIGSEGV都会使您的程序崩溃。因此,实际上,这种方法可能并不比完全忽略EXC_BAD_ACCESS更好,后者更容易实现。 - Jim Ingham
bugreport.apple.com/web/?problemID=46412375 没有得到任何回应,所以我提交了 https://bugs.llvm.org/show_bug.cgi?id=40669。 - timotheecour
@timotheecour,由于bugreport.apple.com已经关闭,您能否请重新在https://openradar.appspot.com/myradars/add上提交该漏洞报告? - zrhoffman
我不确定是否完全是同一个问题,但也许这个(看起来是Xcode 14.3工具集中的新问题,因为根据我的测试,它在Xcode 14.2上无法工作)表明所涉及的长期存在的错误可能已经被修复了? - swineone

3

我们可以很容易地做到。只需添加这段代码。

#include <mach/task.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>

int ret = task_set_exception_ports(
                                   mach_task_self(),
                                   EXC_MASK_BAD_ACCESS,
                                   MACH_PORT_NULL,//m_exception_port,
                                   EXCEPTION_DEFAULT,
                                   0);

不要忘记做这个。
proc hand -p true -s false SIGSEGV 
proc hand -p true -s false SIGBUS

enter image description here

完整代码:

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>

#include <mach/task.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>

static void handler(int signo, siginfo_t *sigaction, void *context)
{
    printf("in handler.\n");
    signal(signo, SIG_DFL);
}

static void gen_exception()
{
    printf("gen_exception in.\n");
    *(int *)0 = 0;
    printf("gen_exception out.\n");
}

void *gen_exception_thread(void *parg)
{
    gen_exception();
    return 0;
}

int main()
{
    task_set_exception_ports(
                             mach_task_self(),
                             EXC_MASK_BAD_ACCESS,
                             MACH_PORT_NULL,//m_exception_port,
                             EXCEPTION_DEFAULT,
                             0);
    
    
    struct sigaction sa;
    sa.sa_sigaction = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;

    if(sigaction(/*SIGBUS*/SIGSEGV, &sa, NULL) == -1) {
        printf("sigaction fails.\n");
        return 0;
    }

    pthread_t id;
    pthread_create(&id, NULL, gen_exception_thread, NULL);
    pthread_join(id, NULL);

    return 0;
}

请参考(中文文章):https://zhuanlan.zhihu.com/p/33542591


这是我唯一可行的解决方案。在x64和arm64上运行良好,但不适用于Rosetta。 - Daniel Lehmann
对我来说,在x86_64架构上通过Rosetta运行不起来。 - undefined

0
一点示例代码可以使这样的问题更容易回答...我以前从未使用过sigaction API,但我把它组合起来了-
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void segv_handler (int in)
{
    puts ("in segv_handler()");
}

void sigbus_handler (int in)
{
    puts ("in sigbus_handler()");
}

int main ()
{
    struct sigaction action;
    action.sa_mask = 0;
    action.sa_flags = 0;


    action.sa_handler = segv_handler;
    sigaction (SIGSEGV, &action, NULL);
    action.sa_handler = sigbus_handler;
    sigaction (SIGBUS, &action, NULL);

    puts ("about to send SIGSEGV signal from main()");
    kill (getpid(), SIGSEGV);

    puts ("about to send SIGBUS signal from main()");
    kill (getpid(), SIGBUS);

    puts ("exiting main()");

}


% lldb a.out
(lldb) br s -n main
(lldb) r
(lldb) pr h -p true -s false SIGSEGV SIGBUS
(lldb) c
Process 54743 resuming
about to send SIGSEGV signal from main()
Process 54743 stopped and restarted: thread 1 received signal: SIGSEGV
in segv_handler()
about to send SIGBUS signal from main()
Process 54743 stopped and restarted: thread 1 received signal: SIGBUS
in sigbus_handler()
exiting main()
Process 54743 exited with status = 0 (0x00000000) 
(lldb) 

这里看起来一切都正常。如果我在process handle参数中添加了-n false,lldb就不会打印关于Process .. stopped and restarted的行。

请注意,这些信号设置不会跨进程执行持久化。因此,如果您重新开始调试会话(一旦您已经启动了进程,就使用r),您需要重新设置这些设置。您可能希望创建一个命令别名快捷方式,并将其放入您的~/.lldbinit文件中,以便您可以使用短命令设置您喜欢的进程处理方式。


这并没有解决原始问题:你的代码并没有生成EXC_BAD_ACCESS,而是生成了SIGSEGV。正是EXC_BAD_ACCESS将lldb置于无限循环中。 - Derek Bruening

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接