В настоящее время я работаю над платформой модульного тестирования, где пользователи могут создавать тестовые случаи и регистрироваться в платформе.
Я также хотел бы убедиться, что если какой-либо код пользовательского теста вызывает сбой, он не должен разбивать всю инфраструктуру, а должен помечаться как сбойный. Чтобы заставить это работать, я написал следующий Код, чтобы я мог запустить Код Пользователя в функции Песочницы
bool SandBox(void *(*fn)(void *),void *arg, void *rc)
{
#ifdef WIN32
__try
{
if (rc)
rc = fn(arg);
else
fn(arg);
return true;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
#else
#endif
}
Это прекрасно работает в Windows, но я хотел бы, чтобы мой фреймворк был переносимым, и для этого я хотел бы обеспечить аналогичную функциональность для сред posix.
Я знаю, что обработчики сигналов C могут перехватывать сигнал ОС, но для перевода механизма обработки сигналов в среду SEH есть определенные проблемы, которые я не могу решить
Еще одна возможность, о которой я подумал, — запустить тестовый код пользователя в отдельном потоке с собственным обработчиком сигналов и завершить поток из обработчика сигналов, но опять же не уверен, что это может сработать.
Поэтому, прежде чем думать дальше, я бы хотел помощи сообщества, если они знают о лучшем решении для решения этой проблемы / ситуации.
Как вы сказали, вы можете поймать SIGSEGV через signal()
или же sigaction()
,
Продолжать не рекомендуется, так как это будет неопределенное поведение, т. Е. Ваша память может быть повреждена, что может привести к сбою в других тестовых примерах (или даже преждевременно завершить весь процесс).
Можно ли было запускать тестовые примеры один за другим как подпроцесс? Таким образом, вы можете проверить состояние выхода и определить, завершилось ли оно корректно, с ошибкой или из-за сигнала.
Запуск тестовых случаев в отдельном потоке будет иметь ту же проблему: у вас нет защиты памяти между вашими тестовыми примерами и кодом, управляющим тестовыми примерами.
Предлагаемый подход будет следующим:
fork()
создать дочерний процесс.
В дочернем процессе вы execve()
ваш тестовый случай. Это может быть один и тот же двоичный файл с разными аргументами для выбора определенного теста).
В родительском процессе вы вызываете waitpid()
дождаться окончания тестового случая. Вы получили пид от fork()
вызов в родительском процессе.
Оцените состояние подпроцесса с помощью макросов WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG.
Если вам нужны тайм-ауты для ваших тестовых случаев, вы также можете установить обработчик для SIGCHLD. Если тайм-аут истекает первым, kill()
дочерний процесс. Помните, что вы можете вызывать только определенные функции из обработчиков сигналов.
Просто еще одно примечание: execve()
на самом деле не требуется. Вы можете просто продолжить и позвонить по указанному вами тест-кейсу напрямую.
Дополнять ответ SSTN, в Linux вы могли бы иметь специфичные для процессора и системы C код, который:
SA_SIGINFO
ucontext_t*
указательанализировать специфическое для машины контекстное состояние (то есть машинные регистры mcontext_t*
От этого ucontext_t*
) — увидеть getcontext (3) для деталей; «разобрав» указатель кода, вы сможете узнать, какая операция завершилась неудачно, и вы можете получить ошибочный адрес.
изменить и восстановить это состояние машины, это означает изменение адресного пространства процесса путем вызова ММАП (2) и / или изменить некоторые машинные регистры через это mcontext_t*
Это, конечно, непереносимо и болезненно для кода и отладки. Возможно, вам придется отключить некоторые оптимизации компилятора, используйте asm
инструкции или volatile
указатели и т.д …
На Debian или Ubuntu смотрите /usr/include/x86_64-linux-gnu/sys/ucontext.h
заголовок фл.
IIRC какая-то старая версия SML / NJ играла такие трюки.
Читай очень внимательно Сигнал (7) и изучать ABI спецификация для вашего процессора, например x86-64 ABI спецификация
На практике вы также можете использовать (более легко) siglongjmp (3) из обработчика сигнала. Вы также можете сознательно нарушать signal(7)
правила. Вы могли бы использовать Ян Тейлор (работает над НКУ в гугле) libbacktrace библиотека, она работает лучше, если ваши приложения и ее библиотеки имеют отладочную информацию (например, скомпилированы с g++ -O1 -g2
). Смотрите также GNU libc трассировка (3) а также dladdr (3)
обращение SIGEGV
по слухам, не очень эффективен в Linux. На GNU / Hurd вы бы использовали его механизм внешнего пейджера.
Другая возможность — запустить протестированную программу из gdb
отладчик. Последние версии gdb
может быть написано в Python, так что вы можете автоматизировать много вещей. Это может быть практически самый переносимый подход (так как в последнее время gdb
был портирован на многие системы).
Последние (июнь 2016 г.) версии 4.6 или будущие или исправленные ядра могут быть в состоянии обработать ошибки страницы в пространстве пользователя и особенно userfaultfd
; но я не знаю подробностей. Смотрите также этот вопрос.