2014-02-01 4 views
13

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

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

+2

'(char *) __ builtin_frame_address (1) - (char *) __ builtin_frame_address (0)'? –

+0

@ H2CO3 Позвольте мне попробовать, но я не уверен, как будет обрабатываться EIP (x86, RIP для x64). – alexandernst

+0

Это хороший вопрос, и я не гуру низкого уровня, так что не верьте мне на слово :) –

ответ

16

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

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

Из documentation:

адрес кадра, как правило, адрес первого слова толкнул в стек с помощью функции.

На 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; 

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

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

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

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

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, проанализировав строку формата), вы должны позволить вызывающему абоненту передать эту информацию вызываемому.

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; 
} 
+0

Спасибо! Этот ответ достаточно подробный, но редактирование разочаровало меня ... Я должен думать о другом способе * делать сумасшедшие вещи * со стеком :) – alexandernst

+0

@alexandernst: Я вижу одну небольшую возможность ... см. Мое последнее редактирование (абзац «однако» внизу). –

+0

Я думал о еще одном варианте (держись, сумасшедшие вещи идут). Получите начало звонящего, а затем используйте udis86, чтобы увидеть, сколько было зарезервировано. – alexandernst

Смежные вопросы