Получение размера функции C ++ - PullRequest
22 голосов
/ 14 апреля 2011

Я читал этот вопрос, потому что я пытаюсь найти размер функции в программе на C ++. Намекнули, что может быть способ, который зависит от платформы. Моя целевая платформа - Windows

Метод, который у меня сейчас в голове, следующий:
1. Получить указатель на функцию
2. Увеличивайте указатель (и счетчик), пока я не достигну значения машинного кода для ret
3. Счетчик будет размером функции?

Edit1: Чтобы пояснить, что я имею в виду под «размером», я имею в виду количество байтов (машинный код), составляющих функцию.
Edit2: Было несколько комментариев, спрашивающих, почему или что я планирую делать с этим. Честный ответ: у меня нет намерений, и я не могу увидеть преимущества знания времени до компиляции длины функции. (хотя я уверен, что они есть)

Мне кажется, это правильный метод, сработает ли это?

Ответы [ 15 ]

13 голосов
/ 14 апреля 2011

Можно получить все блоки функции, но это неестественный вопрос, чтобы спросить, каков «размер» функции.Оптимизированный код переставит кодовые блоки в порядке выполнения и переместит редко используемые блоки (пути исключений) во внешние части модуля.Для получения дополнительной информации см. Оптимизация по профилю , например, как Visual C ++ достигает этого при генерации кода времени ссылки.Таким образом, функция может начинаться с адреса 0x00001000, переходить по адресу 0x00001100 в переход с 0x20001000 и ret, и иметь некоторый код обработки исключений 0x20001000.В 0x00001110 запускается другая функция.Каков «размер» вашей функции?Он охватывает диапазон от 0x00001000 до + 0x20001000, но ему «принадлежит» только несколько блоков в этом диапазоне.Таким образом, ваш вопрос должен быть не задан.

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

Погоня за указателями в памяти и поиск ret ни к чему не приведут, я боюсь.Современный код намного сложнее.

12 голосов
/ 14 апреля 2011

Нет, это не будет работать:

  1. Нет гарантии, что ваша функция содержит только одну ret инструкцию.
  2. Даже если она содержит только одну ret, вы не можете просто смотреть на отдельные байты - потому что соответствующее значение может выглядеть как просто значение, а не как инструкция.

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

10 голосов
/ 09 апреля 2012

Ух, я все время использую функцию подсчета размеров, и она имеет много и много применений.Это надежно?Ни за что.Это стандарт с ++?Ни за что.Но именно поэтому вам нужно проверить его в дизассемблере, чтобы убедиться, что он работает, каждый раз, когда вы выпускаете новую версию.Флаги компилятора могут испортить порядок.

static void funcIwantToCount()
{
   // do stuff
}
static void funcToDelimitMyOtherFunc()
{
   __asm _emit 0xCC
   __asm _emit 0xCC
   __asm _emit 0xCC
   __asm _emit 0xCC
}

int getlength( void *funcaddress )
{
   int length = 0;
   for(length = 0; *((UINT32 *)(&((unsigned char *)funcaddress)[length])) != 0xCCCCCCCC; ++length);
   return length;
}

Кажется, он лучше работает со статическими функциями.Глобальные оптимизации могут убить его.

PS Я ненавижу людей, спрашиваю, почему вы хотите сделать это, а это невозможно и т. Д. Прекратите задавать эти вопросы, пожалуйста.Звучит глупо.Программистов часто просят делать нестандартные вещи, потому что новые продукты почти всегда выходят за пределы того, что доступно.Если они этого не делают, ваш продукт, вероятно, переосмысление того, что уже было сделано.Скучный !!!

7 голосов
/ 14 апреля 2011

Это не сработает ... что если есть прыжок, манекен ret, а затем цель прыжка?Ваш код будет одурачен.

В общем случае невозможно сделать это со 100% точностью, потому что вы должны предсказать все пути кода, что похоже на решение проблемы остановки .Вы можете получить «довольно хорошую» точность, если внедрите свой собственный дизассемблер, но ни одно решение не будет таким легким, как вы себе представляете.

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


Редактировать 1:

Что если функция недаже не заканчивается ret инструкция на всех ?Он вполне может просто jmp вернуться к своему абоненту (хотя это маловероятно).


Edit 2:

Не забывайте, что, по крайней мере, x86 имеет переменную длинуинструкции ...


Обновление:

Для тех, кто говорит, что анализ потока - это не то же самое, что решение проблемы остановки:

Подумайте, что происходит, когда у вас естькод вроде:

foo:
    ....
    jmp foo

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

Разве это не похоже на проблему остановки?

2 голосов
/ 14 апреля 2011

может работать в очень ограниченных сценариях.Я использую его как часть написанной мной утилиты внедрения кода.Я не помню, где я нашел информацию, но у меня есть следующее (C ++ в VS2005):

#pragma runtime_checks("", off)

static DWORD WINAPI InjectionProc(LPVOID lpvParameter)
{
    // do something
    return 0;
}

static DWORD WINAPI InjectionProcEnd()
{
    return 0;
}

#pragma runtime_checks("", on)

А затем в какой-то другой функции у меня есть:

size_t cbInjectionProc = (size_t)InjectionProcEnd - (size_t)InjectionProc;

Вычтобы заставить это работать, нужно отключить некоторые оптимизации и объявить функции статическими;Я не помню специфику.Я не знаю, точное ли это количество байтов, но оно достаточно близко.Размер - это только размер непосредственной функции;он не включает никаких других функций, которые могут быть вызваны этой функцией.За исключением крайних случаев, подобных этому, «размер функции» не имеет смысла и бесполезен.

2 голосов
/ 14 апреля 2011

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

Я не уверен, какие инструменты вам понадобятся для этой цели с Windows. Похоже, что инструменты, перечисленные в ответе на , этот вопрос может быть тем, что вы ищете.

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

  • Целевая функция находится в другом объекте
  • Система сборки обучена зависимостям
  • Вы точно знаете, что компилятор будет создавать эти объектные файлы

Обратите внимание, что я не совсем уверен, ПОЧЕМУ вы хотите знать эту информацию. В прошлом я нуждался в этом, чтобы быть уверенным, что смогу разместить определенный кусок кода в определенном месте в памяти. Должен признаться, мне любопытно, к чему это приведет в более общей цели для настольных ОС.

1 голос
/ 10 февраля 2018

Я публикую это, чтобы сказать две вещи:

1) Большинство ответов, приведенных здесь, действительно плохи и легко сломаются .Если вы используете указатель на функцию C (используя имя функции), в debug сборке вашего исполняемого файла и, возможно, в других обстоятельствах, он может указывать на JMP shim , который не будет иметьфункционировать само тело.Вот пример.Если я сделаю следующее для функции, которую я определил ниже:

FARPROC pfn = (FARPROC)some_function_with_possibility_to_get_its_size_at_runtime;

pfn, которое я получу (например: 0x7FF724241893), укажет на это, что является просто JMP инструкцией:

enter image description here

Кроме того, компилятор может вкладывать несколько таких оболочек или ветвить код вашей функции, чтобы иметь несколько эпилогов ,или ret инструкции.Черт возьми, он может даже не использовать ret инструкцию.Тогда нет никакой гарантии, что сами функции будут скомпилированы и скомпонованы в том порядке, в котором вы определяете их в исходном коде.

Все это можно делать на языке Assembly , но не на языке Cили C ++.

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

  • Работает в 64-битных исполняемых файлах только в Windows.

  • Это, очевидно, специфично для Microsoft и не переносимо.

  • Вы должны сделать это во время выполнения.

Концепция проста - использовать способ SEH в x64 Windowsдвоичные файлы.Компилятор добавляет сведения о каждой функции в заголовок PE32 + (в каталог IMAGE_DIRECTORY_ENTRY_EXCEPTION необязательного заголовка), который можно использовать для получения точного размера функции.(Если вам интересно, эта информация используется для перехвата, обработки и раскручивания исключений в блоках __try/__except/__finally.)

Вот краткий пример:

//You will have to call this when your app initializes and then
//cache the size somewhere in the global variable because it will not
//change after the executable image is built.

size_t fn_size; //Will receive function size in bytes, or 0 if error
some_function_with_possibility_to_get_its_size_at_runtime(&fn_size);

, а затем:

#include <Windows.h>

//The function itself has to be defined for two types of a call:
// 1) when you call it just to get its size, and
// 2) for its normal operation
bool some_function_with_possibility_to_get_its_size_at_runtime(size_t* p_getSizeOnly = NULL)
{
    //This input parameter will define what we want to do:
    if(!p_getSizeOnly)
    {
        //Do this function's normal work
        //...

        return true;
    }
    else
    {
        //Get this function size
        //INFO: Works only in 64-bit builds on Windows!
        size_t nFnSz = 0;

        //One of the reasons why we have to do this at run-time is
        //so that we can get the address of a byte inside 
        //the function body... we'll get it as this thread context:
        CONTEXT context = {0};
        RtlCaptureContext(&context);

        DWORD64 ImgBase = 0;
        RUNTIME_FUNCTION* pRTFn = RtlLookupFunctionEntry(context.Rip, &ImgBase, NULL);
        if(pRTFn)
        {
            nFnSz = pRTFn->EndAddress - pRTFn->BeginAddress;
        }

        *p_getSizeOnly = nFnSz;
        return false;
    }
}
1 голос
/ 14 апреля 2011

В стандарте C ++ нет средств для получения размера или длины функции.
См. Мой ответ здесь: Можно ли загрузить функцию в некоторую выделенную память и запустить ее оттуда?

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

1 голос
/ 14 апреля 2011

Я думаю, что это будет работать на программах Windows, созданных с помощью msvc, так как для ветвей 'ret', кажется, всегда приходит в конце (даже если есть ветви, которые возвращаются рано, это делает jne, чтобы идти в конец). Однако вам понадобится какая-нибудь библиотека дизассемблера, чтобы определить текущую длину кода операции, поскольку они являются переменной длиной для x86. Если вы этого не сделаете, вы столкнетесь с ложными срабатываниями.

Я не удивлюсь, если будут случаи, когда это не уловит.

1 голос
/ 14 апреля 2011

В C ++ понятие размера функции отсутствует.В дополнение ко всему остальному, макросы препроцессора также имеют неопределенный размер.Если вы хотите посчитать количество командных слов, вы не можете сделать это в C ++, потому что он не существует, пока не скомпилирован.

...