Подтвердить что ты не робот

Определение размера фрейма стека

Фрейм стека функции вызывающего абонента можно легко получить через __builtin_frame_address(1), но как насчет размера фрейма стека?

Есть ли функция, которая даст мне знать, насколько велик стек стека функции звонящего?

4b9b3361

Ответ 1

Моя первая реакция была бы, зачем кому-то это нужно? Сложной практикой для функции C является динамическое определение размера фрейма стека. Вся точка cdecl (классическое соглашение о вызове C) заключается в том, что сама функция ( "вызываемая" ) не знает размер фрейма стека. Любое отклонение от этой философии может привести к разрыву кода при переключении на другую платформу, другой размер адреса (например, с 32-битного на 64-разрядный), другой компилятор или даже разные настройки компилятора (в частности, оптимизации).

С другой стороны, поскольку gcc уже предлагает эту функцию __builtin_frame_address, будет интересно посмотреть, сколько информации может быть получено там.

Из документации:

Адрес кадра, как правило, является адресом первого слова, вложенного в стек функцией.

В x86 функция обычно начинается с:

push ebp       ; bp for 16-bit, ebp for 32-bit, rbp for 64-bit

Другими словами, __builtin_frame_address возвращает базовый указатель кадра стека вызывающего. К сожалению, базовый указатель почти ничего не говорит о том, где начинается или заканчивается кадр стека; базовый указатель указывает на местоположение, которое находится где-то посередине кадра стека (между параметрами и локальными переменными).

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

register char * const basepointer  asm("ebp");
register char * const stackpointer asm("esp");

size_localvars = basepointer - stackpointer;

Пожалуйста, имейте в виду, что gcc, похоже, выделяет пространство в стеке с самого начала, которое используется для хранения параметров для других функций, вызванных внутри вызываемого абонента. Строго говоря, это пространство принадлежит кадрам стека этих других функций, но граница неясна. Является ли это проблемой, зависит от вашей цели; что вы собираетесь делать с вычисленным размером фрейма стека?

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

void callee(int a, int b, int c, int d)
{
    size_params = sizeof d + (char *)&d - (char *)&a;
}

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

register char * const stackpointer asm("esp");

void callee(int a, int b, int c, int d)
{
    total_size = sizeof d + (char *)&d - stackpointer;
}

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

EDIT: Обратите внимание, что это работает только для того, чтобы функция измеряла его собственный стек стека. Вызов не может рассчитать размер кадра стека его вызывающего абонента; вызывающий вызов должен будет запросить у абонента эту информацию.

Тем не менее, вызываемый может сделать обоснованное предположение о размере локальных переменных вызывающего абонента. Этот блок начинается с того, где заканчиваются параметры вызываемого абонента (sizeof d + (char *)&d), и заканчивается указателем базовой линии (__builtin_frame_address(1)). Начальный адрес может быть немного неточным из-за выравнивания адреса, налагаемого компилятором; вычисленный размер может включать кусок неиспользуемого пространства стека.

void callee(int a, int b, int c, int d)
{
   size_localvars_of_caller = __builtin_frame_address(1) - sizeof d - (char *)&d;
}