Как обнаружить возможные / потенциальные проблемы переполнения стека в программе на языке c / c ++? - PullRequest
40 голосов
/ 14 октября 2008

Существует ли стандартный способ узнать, сколько стекового пространства имеет ваше приложение и какой самый высокий уровень водяного знака для использования стека во время выполнения?

Также, в случае страшного фактического переполнения, что происходит?

Это происходит сбой, вызывает исключение или сигнал? Есть ли стандарт или он отличается во всех системах и компиляторах?

Я ищу специально для Windows, Linux и Macintosh.

Ответы [ 9 ]

15 голосов
/ 14 октября 2008

В Windows переполнение стека исключение .

Следующий код Windows иллюстрирует это:

#include <stdio.h>
#include <windows.h>

void StackOverFlow()
{
  CONTEXT context;

  // we are interested control registers
  context.ContextFlags = CONTEXT_CONTROL;

  // get the details
  GetThreadContext(GetCurrentThread(), &context);

  // print the stack pointer
  printf("Esp: %X\n", context.Esp);

  // this will eventually overflow the stack
  StackOverFlow();
}

DWORD ExceptionFilter(EXCEPTION_POINTERS *pointers, DWORD dwException)
{
  return EXCEPTION_EXECUTE_HANDLER;
}

void main()
{
  CONTEXT context;

  // we are interested control registers
  context.ContextFlags = CONTEXT_CONTROL;

  // get the details
  GetThreadContext(GetCurrentThread(), &context);

  // print the stack pointer
  printf("Esp: %X\n", context.Esp);

  __try
  {
    // cause a stack overflow
    StackOverFlow();
  }
  __except(ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
  {
    printf("\n****** ExceptionFilter fired ******\n");
  }
}

Когда этот exe-файл запускается, генерируется следующий вывод:

Esp: 12FC4C
Esp: 12F96C
Esp: 12F68C
.....
Esp: 33D8C
Esp: 33AAC
Esp: 337CC

****** ExceptionFilter fired ******
12 голосов
/ 14 октября 2008

В Linux вы получаете ошибку сегментации, если ваш код пытается выполнить запись за стеком.

Размер стека является свойством, унаследованным между процессами. Если вы можете прочитать или изменить его в оболочке, используя команды типа ulimit -ssh, ksh, zsh) или limit stacksize (tcsh, zsh).

Из программы можно узнать размер стека с помощью

#include <sys/resource.h>
#include <stdio.h>

struct rlimit l;
getrlimit(RLIMIT_STACK, &l);
printf("stack_size = %d\n", l.rlim_cur);

Я не знаю стандартного способа получения размера доступного стека.

Стек начинается с argc, за которым следует содержимое argv и копия окружения, а затем ваши переменные. Однако, поскольку ядро ​​может рандомизировать местоположение начала стека, и могут быть некоторые фиктивные значения выше argc, было бы неправильно предполагать, что у вас есть l.rlim_cur байтов, доступных ниже &argc.

Одним из способов получения точного местоположения стека является просмотр файла /proc/1234/maps (где 1234 - идентификатор процесса вашей программы). Когда вы знаете эти границы, вы можете вычислить, какая часть вашего стека используется, посмотрев адрес последней локальной переменной.

10 голосов
/ 14 октября 2008

gcc помещает дополнительный блок памяти между адресом возврата и обычными переменными в вызовах «небезопасных» функций, например (в этом примере функция void test () {char a [10]; b [20]}:

call stack:
-----------
return address
dummy
char b[10]
char a[20]

Если функция записывает 36 байтов в указатель «a», переполнение «повредит» адрес возврата (возможное нарушение безопасности). Но это также изменит значение «пустышки», то есть между указателем и адресом возврата, поэтому программа будет аварийно завершать работу с предупреждением (вы можете отключить это с помощью -fno-stack-protector)

7 голосов
/ 17 октября 2009

В Linux библиотека Gnu libsigsegv включает функцию stackoverflow_install_handler, которая может обнаружить (а в некоторых случаях помочь вам восстановиться) переполнение стека.

6 голосов
/ 19 декабря 2008

В окнах стек (для определенного потока) увеличивается по требованию, пока не будет достигнут размер стека, указанный для этого потока до его создания.

Увеличение по требованию осуществляется с использованием защитных страниц, поскольку изначально имеется только фрагмент стека, за которым следует защитная страница, которая при попадании вызывает исключение - это исключение является специальным и обрабатывается система для вас - обработка увеличивает доступное пространство стека (также проверяется, достигнут ли предел!), и операция чтения повторяется.

Как только предел достигнут, больше нет роста, что приводит к исключению переполнения стека. Текущее основание стека и лимит хранятся в блоке среды потока в структуре, называемой _NT_TIB (блок информации о потоке). Если у вас есть под рукой отладчик, вот что вы видите:

0:000> dt ntdll!_teb @$teb nttib.
   +0x000 NtTib  : 
      +0x000 ExceptionList : 0x0012e030 _EXCEPTION_REGISTRATION_RECORD
      +0x004 StackBase : 0x00130000 
      +0x008 StackLimit : 0x0011e000 
      +0x00c SubSystemTib : (null) 
      +0x010 FiberData : 0x00001e00 
      +0x010 Version : 0x1e00
      +0x014 ArbitraryUserPointer : (null) 
      +0x018 Self   : 0x7ffdf000 _NT_TIB

Атрибут StackLimit будет обновляться по требованию. Если вы проверите атрибуты в этом блоке памяти, вы увидите нечто похожее на это:

0:000> !address 0x0011e000 
    00030000 : 0011e000 - 00012000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  abc.560

И проверка страницы рядом с ней выявляет атрибут охраны:

0:000> !address 0x0011e000-1000
    00030000 : 0011d000 - 00001000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000104 PAGE_READWRITE | PAGE_GUARD
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  abc.560

Надеюсь, это поможет.

4 голосов
/ 14 октября 2008

Переполнение стека, вероятно, самый неприятный тип исключений для обработки - потому что ваш обработчик исключений должен иметь дело с минимальным количеством стека (обычно для этой цели резервируется только одна страница).

Интересное обсуждение трудностей, связанных с обработкой исключений этого типа, см. В следующих сообщениях в блоге: 1 и 2 от Криса Брамма, которые фокусируются на проблеме с точки зрения .NET, особенно хостинг CLR.

1 голос
/ 11 августа 2011

В Visual Studio можно использовать editbin для изменения размера стека. Информация может быть найдена в msdn.microsoft.com / en-us / library / 35yc2tc3.aspx .

1 голос
/ 19 декабря 2008

Я бы предложил вам использовать стек альтернативных сигналов, если вы используете Linux.

  1. В этом случае весь сигнал будет обрабатываться через альтернативный стек.
  2. В случае переполнения стека система генерирует сигнал SEGV, это может быть обработано через альтернативный стек.
  3. Если вы не используете его ... тогда, возможно, вы не сможете обработать сигнал, и ваша программа может произойти сбой без какой-либо обработки / сообщения об ошибке.
0 голосов
/ 14 октября 2008

Некоторые компиляторы поддерживают функцию stackavail (), которая возвращает количество оставшегося свободного места в стеке. Вы можете использовать эту функцию перед вызовом функций в ваших программах, которые требуют много места в стеке, чтобы определить, безопасно ли их вызывать

...