HOWTO определяет, работает ли код в контексте обработчика сигналов?
Я только узнал, что кто-то звонит - из обработчика сигнала - определенно не асинхронно-сигнальная функция, которую я написал. И, конечно же, я получаю вину (несмотря на предупреждения в моей документации). (Тот же кодер вызывает всевозможные функции, не содержащие асинхронного сигнала, из своего обработчика сигнала. Вздох.)
Итак, теперь мне любопытно: как обходить эту ситуацию от повторения? Я хотел бы иметь возможность легко определить, работает ли мой код в контексте обработчика сигнала (язык C, но не применимо ли решение к любому языку?):
int myfunc( void ) {
if( in_signal_handler_context() ) { return(-1) }
// rest of function goes here
return( 0 );
}
Это под Linux.
Надеюсь, это нелегкий ответ, иначе я буду чувствовать себя идиотом.
Ответы
Ответ 1
По-видимому, новый Linux/x86 (возможно, так как некоторое ядро 2.6.x) вызывает обработчики сигналов из vdso
. Вы можете использовать этот факт, чтобы нанести следующий ужасный взлом в ничего не подозревающем мире:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
uintmax_t vdso_start = 0;
uintmax_t vdso_end = 0; /* actually, next byte */
int check_stack_for_vdso(uint32_t *esp, size_t len)
{
size_t i;
for (i = 0; i < len; i++, esp++)
if (*esp >= vdso_start && *esp < vdso_end)
return 1;
return 0;
}
void handler(int signo)
{
uint32_t *esp;
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
/* XXX only for demonstration, don't call printf from a signal handler */
printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
}
void parse_maps()
{
FILE *maps;
char buf[256];
char path[7];
uintmax_t start, end, offset, inode;
char r, w, x, p;
unsigned major, minor;
maps = fopen("/proc/self/maps", "rt");
if (maps == NULL)
return;
while (!feof(maps) && !ferror(maps)) {
if (fgets(buf, 256, maps) != NULL) {
if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s",
&start, &end, &r, &w, &x, &p, &offset,
&major, &minor, &inode, path) == 11) {
if (!strcmp(path, "[vdso]")) {
vdso_start = start;
vdso_end = end;
break;
}
}
}
}
fclose(maps);
printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end);
}
int main()
{
struct sigaction sa;
uint32_t *esp;
parse_maps();
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART;
if (sigaction(SIGUSR1, &sa, NULL) < 0) {
perror("sigaction");
exit(1);
}
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
kill(getpid(), SIGUSR1);
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
return 0;
}
SCNR.
Ответ 2
Если мы можем предположить, что ваше приложение не блокирует сигналы вручную с помощью sigprocmask()
или pthread_sigmask()
, то это довольно просто: получите текущий идентификатор потока (tid
). Откройте /proc/tid/status
и получите значения для SigBlk
и SigCgt
. AND
эти два значения. Если результат этого AND
отличен от нуля, то этот поток в настоящее время выполняется изнутри обработчика сигнала. Я сам это испытал, и он работает.
Ответ 3
Есть два способа справиться с этим:
-
Пусть ваши сотрудники перестанут поступать неправильно. Удачи, вытащив это с боссом, хотя...
-
Сделайте свою функцию повторной и асинхронной. Если необходимо, укажите функцию с другой подписью (например, используя широко используемое соглашение об именах *_r
) с дополнительными аргументами, необходимыми для сохранения состояния.
Что касается неправильного способа сделать это, то в Linux с GNU libc вы можете использовать backtrace()
и друзей, чтобы пройти через список вызывающих абонентов вашей функции. Нелегко получить право, безопасно или переносимо, но это может сделать какое-то время:
/*
* *** Warning ***
*
* Black, fragile and unportable magic ahead
*
* Do not use this, lest the daemons of hell be unleashed upon you
*/
int in_signal_handler_context() {
int i, n;
void *bt[1000];
char **bts = NULL;
n = backtrace(bt, 1000);
bts = backtrace_symbols(bt, n);
for (i = 0; i < n; ++i)
printf("%i - %s\n", i, bts[i]);
/* Have a look at the caller chain */
for (i = 0; i < n; ++i) {
/* Far more checks are needed here to avoid misfires */
if (strstr(bts[i], "(__libc_start_main+") != NULL)
return 0;
if (strstr(bts[i], "libc.so.6(+") != NULL)
return 1;
}
return 0;
}
void unsafe() {
if (in_signal_handler_context())
printf("John, you know you are an idiot, right?\n");
}
На мой взгляд, лучше просто выйти, а не заставить писать такой код.
Ответ 4
Вы можете что-то придумать, используя sigaltstack. Настройте альтернативный стек сигналов, получите указатель стека каким-то безопасным способом, если внутри альтернативного стека, иначе abort().
Ответ 5
Думаю, вам нужно сделать следующее. Это сложное решение, которое сочетает лучшие практики не только с кодированием, но и с программным обеспечением!
- Убедите своего босса в том, что соглашение об именах в обработчиках сигналов - это хорошо. Предложите, например, венгерскую нотацию, и скажите, что она использовалась в Microsoft с большим успехом.
Итак, все обработчики сигналов начинаются с
sighnd
, например sighndInterrupt
.
- Ваша функция, которая обнаруживает контекст обработки сигналов, сделает следующее:
- Получить
backtrace()
.
- Посмотрите, начинается ли какая-либо из функций в нем с
sighnd...
. Если это так, то поздравления, вы находитесь в обработчике сигналов!
- В противном случае это не так.
- Старайтесь избегать работы с Jimmy в той же компании. "Значит, может быть только один".
Ответ 6
для кода, оптимизированного на -O2 или выше (istr), обнаружено, что нужно добавить -fno-omit-frame-pointer
else gcc оптимизирует информацию о контексте стека