Как определить возможные/потенциальные проблемы в программе 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 -s
(в sh
, 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)
Ответ 4
В Linux библиотека Gnu libsigsegv включает в себя функцию stackoverflow_install_handler
, которая может обнаруживать (и в некоторых случаях случаи помогают вам восстановить) переполнение стека.
Ответ 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, это может обрабатываться через альтернативный стек.
- Если вы не используете его... тогда вы не сможете обработать сигнал, и ваша программа может произойти сбой без обработки/эрро-отчетности.
Ответ 8
В Visual Studio можно использовать editbin для изменения размера стека. Информацию можно найти на msdn.microsoft.com/en-us/library/35yc2tc3.aspx.
Ответ 9
Некоторые компиляторы поддерживают функцию stackavail(), которая возвращает количество оставшегося свободного пространства стека. Вы можете использовать эту функцию перед вызовом функций в ваших программах, для которых требуется много пространства стека, чтобы определить, безопасно ли их вызывать.