Программист
Устраняем ошибки, связанные с SIGSEGV: ошибка сегментирования в контейнерах Linux (код возврата 139)
Сигнал SIGSEGV, применяемый в Linux, означает нарушение сегментирования в рамках работающего процесса. Ошибки сегментирования возникают из-за того, что программа пытается обратиться к участку памяти, который пока не выделен. Это может произойти из-за бага, случайно вкравшегося в код, либо из-за того, что внутри системы происходит некая вредоносная активность.Сигналы SIGSEGV возникают на уровне операционной системы, но столкнуться с ними также вполне можно и в контексте контейнерных технологий, например, Docker и Kubernetes. Когда контейнер завершает работу, выдав код возврата 139, дело именно в том, что он получил сигнал SIGSEGV. Операционная система завершает процесс контейнера, чтобы предохраниться от нарушения целостности памяти.Если ваши контейнеры то и дело завершают работу с кодом возврата, то важно исследовать, что именно вызывает ошибки сегментирования. Часто следы ведут к программным ошибкам в языках, открывающих вам прямой доступ к памяти. Если такая ошибка возникает в том контейнере, где выполняется сторонний образ, то виной тому может быть баг в стороннем софте или несовместимость образа со средой.В этой статье будет объяснено, что представляют собой сигналы SIGSEGV, как они влияют на работу ваших контейнеров с Linux в Kubernetes. Также я подскажу, как отлаживать ошибки сегментации в вашем приложении, а если они возникают – как с ними справляться.
Термин ошибка сегментирования может показаться туманным, но с технической точки зрения это очень простое явление. Вот в чём оно заключается: процесс получает сигнал SIGSEGV из-за того, что попытался прочитать информацию из такой области памяти, к которой ему не разрешено обращаться – или записать информацию в такую область. Как правило, ядро завершает такой процесс, чтобы избежать повреждения памяти. Данное поведение можно изменить, явно обрабатывая сигнал в коде программы.Ошибки сегментирования называются именно так, поскольку нарушают тот порядок деления памяти, который ранее был целенаправленно задан. В сегментах данных хранятся значения, которые могут быть определены во время компиляции, в текстовых сегментах содержатся программные инструкции, а в сегментах кучи инкапсулированы те переменные, которые создаются во время выполнения и выделяются динамически.Большинство ошибок сегментирования, встречающихся в реальной жизни, относятся именно к третьей категории. Это такие операции, как неправильное определение указателей, попытки записи в память, выделенную только для чтения, а также выход за границы при обращении к массиву и попытки обратиться к памяти, находящейся за пределами кучи.Вот тривиальный пример программы на C, в которой фигурирует ошибка сегментирования:
Сохраним программу как hello-world.c
и скомпилируем её при помощи make
:
Теперь выполним скомпилированный бинарник:
Как видите, программа немедленно завершается и выдаётся сообщение об ошибке сегментирования. Если вы проверите код возврата, то убедитесь, что он равен 139 и означает ошибку сегментирования:
Почему так происходит? В программе была создана переменная buffer
, но под неё не было выделено памяти. В результате присваивания buffer[0] = 0
происходит запись в невыделенную память. Можно исправить программу, гарантировав, что размера буфера точно хватит, чтобы в нём поместились все требуемые данные:
Если выделить буфер buffer
размером 1 байт, то этой памяти точно хватит, чтобы обработать присвоенное значение. Эта программа успешно завершается с кодом возврата 0.
Теперь давайте рассмотрим, что случится, если ошибка сегментирования произойдёт в контейнере. Вот простой файл Dockerfile
для показанного выше приложения, которое аварийно завершилось:
Соберём образ нашего контейнера при помощи следующей команды:
Теперь запустим контейнер:
Контейнер запустится, выполнит команду и сразу же завершит работу. Воспользуйтесь docker ps
с флагом -a, чтобы извлечь подробную информацию об остановленном контейнере: