Почему программа Linux, которая разыменовывает (char *) 0, не всегда segfault? - PullRequest
6 голосов
/ 14 ноября 2009

Я тестирую код, предназначенный для обнаружения, когда дочерний процесс вышел из строя. Вообразите меня удивленным, когда этот код не всегда segfault:

#include <stdio.h>

int main() {
  char *p = (char *)(unsigned long)0;
  putchar(*p);
  return 0;
}

Я работаю под ядром Debian Linux 2.6.26; моя оболочка - AT & T ksh93 из пакета Debian ksh, версия M 93s + 2008-01-31. Иногда эта программа segfault, но в противном случае она просто молча завершается с ненулевым состоянием выхода, но без сообщения. Моя программа обнаружения сигналов сообщает следующее:

segfault terminated by signal 11: Segmentation fault
segfault terminated by signal 53: Real-time signal 19
segfault terminated by signal 11: Segmentation fault
segfault terminated by signal 53: Real-time signal 19
segfault terminated by signal 53: Real-time signal 19
segfault terminated by signal 53: Real-time signal 19
segfault terminated by signal 53: Real-time signal 19

Запуск под чистым ksh показывает, что segfault также встречается редко:

Running... 
Running... 
Running... 
Running... 
Running... 
Running... Memory fault
Running... 

Интересно, что bash правильно определяет segfault каждый раз .

У меня есть два вопроса:

  1. Кто-нибудь может объяснить это поведение?

  2. Кто-нибудь может предложить простую программу на C, которая будет надежно работать с segfault при каждом выполнении? Я также пытался kill(getpid(), SIGSEGV), но я получаю похожие результаты.


РЕДАКТИРОВАТЬ: jbcreix имеет ответ : мой детектор segfault был сломан. Меня обманули, потому что у ksh такая же проблема. Я пробовал с bash и bash правильно каждый раз.

Моя ошибка заключалась в том, что я передавал WNOHANG в waitpid(), где я должен был проходить ноль. Я не знаю, о чем я мог думать! Интересно, в чем дело с ksh, но это отдельный вопрос.

Ответы [ 3 ]

12 голосов
/ 14 ноября 2009

Запись в NULL надежно приведет к ошибке сегмента или шины.

Иногда ОС сопоставляет страницу, доступную только для чтения, с нулевым адресом. Таким образом, иногда вы можете читать из NULL.

Хотя C определяет адрес NULL как специальный, «реализация» этого специального статуса фактически обрабатывается подсистемой виртуальной памяти (VM) операционной системы.

WINE и dosmu необходимо отобразить страницу на NULL для совместимости с Windows. См. mmap_min_addr в ядре Linux, чтобы перестроить ядро, которое не может этого сделать.

mmap_min_addr в настоящее время является горячей темой из-за связанной эксплойта и публичного пламени к Линусу (известности Linux, очевидно) от Тео де Раадта, из OpenBSD.

Если вы хотите написать код ребенку таким образом, вы всегда можете позвонить: raise(SIGSEGV);

Кроме того, вы можете получить гарантированный указатель на ошибку сегмента из: int *ptr_segv = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS, -1, 0);

Где PROT_NONE - это ключ к резервированию памяти, к которому нет доступа. Для 32-разрядной версии Intel Linux PAGE_SIZE - 4096.

1 голос
/ 14 ноября 2009

Ответ на вопрос номер два из Википедии :

 int main(void)
 {
     char *s = "hello world";
     *s = 'H';
 }
1 голос
/ 14 ноября 2009

Я не уверен, почему у него нет последовательного поведения. Я думаю, что это не так придирчиво к чтению. Или что-то в этом роде, хотя я, вероятно, был бы совершенно неправ.

Попробуйте написать в NULL. Кажется, это соответствует мне. Я понятия не имею, почему вы хотите использовать это, хотя. :)

int main()
{
    *(int *)0 = 0xFFFFFFFF;
    return -1;
}
...