Как определить возможные/потенциальные проблемы в программе c/С++?

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

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

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

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

Ответ 1

В 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 ******

Ответ 2

В 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 - это идентификатор процесса вашей программы). Как только вы узнаете эти границы, вы можете вычислить, сколько вашего стека используется, если посмотреть на адрес последней локальной переменной.

Ответ 3

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

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

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

Ответ 5

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

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

Ответ 6

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

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

Как только предел достигнут, больше не растет, что приводит к исключению. Текущая база стека и предел хранятся в блоке среды потока, в структуре, называемой _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

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

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

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

Ответ 7

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

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

Ответ 9

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