Почему программа Linux, которая разыменовывает (char*)0, не всегда выполняет segfault?
-
20-09-2019 - |
Вопрос
Я тестирую код, который предназначен для обнаружения сбоя дочернего процесса.Представьте мое удивление, когда этот код не всегда выполняет 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 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
показывает, что ошибка сегмента также встречается редко:
Running...
Running...
Running...
Running...
Running...
Running... Memory fault
Running...
Интересно, bash
каждый раз корректно обнаруживает ошибку сегмента.
У меня есть два вопроса:
Кто-нибудь может объяснить такое поведение?
Кто-нибудь может предложить простую программу на языке Си, которая будет надежно выполнять сегментацию при каждом выполнении?Я тоже пробовал
kill(getpid(), SIGSEGV)
, но я получаю похожие результаты.
Редактировать: у jbcreix есть ответ:мой детектор ошибок был сломан.Я был одурачен , потому что ksh
у него та же проблема.Я пытался с bash
и bash
каждый раз делает это правильно.
Моя ошибка заключалась в том, что я проходил мимо WNOHANG
Для waitpid()
, где я должен был проходить мимо нуля.Я не знаю, о чем я могла думать!Возникает вопрос, в чем дело с ksh
, но это отдельный вопрос.
Решение
Написание Для NULL
надежно приведет к сбою сегмента или ошибке шины.
Иногда операционная система сопоставляет страницу, доступную только для чтения, с нулевым адресом.Таким образом, иногда вы можете читать из NULL
.
Хотя C определяет NULL
если адрес является специальным, "реализация" этого специального статуса фактически обрабатывается подсистемой виртуальной памяти (VM) операционной системы.
WINE и dosemu необходимо отобразить на странице по адресу NULL
для обеспечения совместимости с Windows.Видишь mmap_min_addr
в ядре Linux перестроить ядро, которое не может этого сделать.
mmap_min_addr
в настоящее время это горячая тема из-за связанного с ней эксплойта и публичного обращения к Linus (очевидно, прославившему Linux) со стороны Тео де Раадта из OpenBSD.
Если вы готовы закодировать дочерний элемент таким образом, вы всегда можете вызвать: raise(SIGSEGV);
Кроме того, вы можете получить указатель guaranteed-to-segfault из:
int *ptr_segv = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS, -1, 0);
Где PROT_NONE
это ключ к резервированию памяти, к которой невозможно получить доступ.Для 32-разрядной версии Intel Linux размер СТРАНИЦЫ равен 4096.
Другие советы
Я не уверен, почему у него нет последовательного поведения.Я бы подумал, что он не так придирчив к чтению.Или что-то в этом роде, хотя я, вероятно, был бы совершенно неправ.
Попробуйте записать в NULL.Мне кажется, это вполне логично.Хотя я понятия не имею, зачем вам понадобилось это использовать.:)
int main()
{
*(int *)0 = 0xFFFFFFFF;
return -1;
}
Ответ на вопрос номер два из Википедия :
int main(void)
{
char *s = "hello world";
*s = 'H';
}