Я пишу программу на Mac OSX в зависимости от механизма sigaction / sa_handler. Запустите фрагмент кода от пользователя и будьте готовы перехватывать сигналы / исключения в любое время. Программа работает нормально, но проблема в том, что я не могу отладить ее с помощью lldb. lldb, похоже, не может игнорировать любые исключения, даже если я установил

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

Поток управления останавливается на инструкции, которая запускает исключение, и не переходит к sa_handler, который я установил ранее, даже если я попробовал команду c. Результат был:

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;
}
9
jay 9 Ноя 2014 в 17:14

4 ответа

Лучший ответ

Мне это было нужно в недавнем проекте, поэтому я только что построил свой собственный 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);

Что приводит к тому, что сервер отладки не может перехватывать исключения EXC_BAD_ACCESS. Теперь мой собственный LLDB работает нормально: он по-прежнему улавливает SIGSEGV и SIGBUS, но больше не входит в глупый бесконечный цикл при столкновении с EXC_BAD_ACCESS. Установка параметров process handle для ранее фатальных сигналов тоже работает нормально, и теперь я могу безнаказанно отлаживать обработчики SEGV.

Apple действительно должна сделать эту опцию в LLDB ... кажется, для них это действительно простое решение.

5
nneonneo 22 Сен 2015 в 21:16
Что вы подразумеваете под «создал свою собственную LLDB»? Я предполагаю, что LLDB по-прежнему является отладчиком Xcode (я использую Xcode 6.4). Это просто изменение настроек для LLDB, о котором вы говорите? И если да, то где этот файл tools/debugserver/source/MacOSX/MachTask.mm?
 – 
Ash
3 Мар 2016 в 09:08
3
LLDB имеет открытый исходный код, поэтому я скачал исходный код, сделал там необходимые исправления и скомпилировал Это.
 – 
nneonneo
3 Мар 2016 в 09:42

Это давняя ошибка в интерфейсе отладчика в Mac OS X (у gdb была такая же проблема ...). Если у вас есть учетная запись разработчика, сообщите об ошибке с помощью http://bugreport.apple.com. Так мало людей на самом деле используют обработчики SIGSEGV, что проблема никогда не привлекает внимания со стороны разработчиков ядра, поэтому больше ошибок - это хорошо ...

5
Jim Ingham 11 Ноя 2014 в 01:11
2
Привет, Джим! Я заметил, что вы прокомментировали отчет об ошибке LLDB, что " прямой способ сделать это требует, чтобы отладчик работал от имени пользователя root». В OS X я бы отлично запустил отладчик от имени пользователя root (я понимаю, что отчет об ошибке касается iOS, где это невозможно). Есть ли способ исправить LLDB, чтобы он мог игнорировать EXC_BAD_ACCESS, если отладчик работает от имени пользователя root?
 – 
nneonneo
22 Сен 2015 в 19:09
* подано bugreport.apple.com/web/?problemID=46412375 * @jim -ingham, не могли бы вы уточнить «Простой способ сделать это требует, чтобы отладчик запускался от имени пользователя root»? Предполагая, что пользователь может запускать gdb от имени пользователя root, как это будет работать?
 – 
timotheecour
3 Дек 2018 в 12:24
Это действительно отладочный сервер, который должен работать от имени пользователя root. Если он работает от имени пользователя root, он может перенаправить EXC_BAD_ACCESS на порт исключения хоста (тот, который был подключен к порту исключения задачи, пока lldb не вытеснил его). Это превратит его в SIGSEGV, что вам и нужно. Обратите внимание: поскольку lldb не прослушивает сигналы BSD, это будет означать, что любые необработанные SIGSEGV приведут к сбою вашей программы. Так что на самом деле этот метод, вероятно, не лучше, чем полное игнорирование EXC_BAD_ACCESS, что было бы проще реализовать.
 – 
Jim Ingham
4 Дек 2018 в 01:52
Bugreport.apple.com/web/?problemID=46412375 получил 0 ответов, поэтому я зарегистрировал ошибки. llvm.org/show_bug.cgi?id=40669
 – 
timotheecour
9 Фев 2019 в 00:42
Поскольку bugreport.apple.com больше не существует, не могли бы вы повторно отправить отчет об ошибке на https://openradar.appspot.com/myradars/add?
 – 
zrhoffman
14 Сен 2020 в 20:39

Мы легко справимся. Просто добавьте этот код.

#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

2
Yanni 21 Сен 2020 в 06:30
Это единственное решение, которое работает для меня. Отлично работает на x64 и arm64, правда не через Розетту.
 – 
Daniel Lehmann
8 Дек 2021 в 08:09

Небольшой пример кода может упростить ответ на такой вопрос ... Я никогда раньше не использовал 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) 

Похоже, здесь все работает правильно. Если бы я добавил -n false к аргументам process handle, lldb не напечатал бы строки о Process .. stopped and restarted.

Обратите внимание, что эти параметры сигнала не сохраняются при выполнении процесса. Поэтому, если вы начинаете сеанс отладки заново (r после того, как вы уже запустили процесс один раз), вам нужно будет заново установить их. Вы можете создать ярлык для псевдонима команды и поместить его в файл ~/.lldbinit, чтобы вы могли настроить обработку процесса так, как вы предпочитаете, с помощью короткого cmd.

0
Jason Molenda 10 Ноя 2014 в 06:48
Это не решает исходную проблему: ваш код не генерирует EXC_BAD_ACCESS, а генерирует SIGSEGV. EXC_BAD_ACCESS переводит lldb в бесконечный цикл.
 – 
Derek Bruening
28 Окт 2018 в 18:57