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

Получение размера функции С++

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

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

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

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

4b9b3361

Ответ 1

Нет, это не сработает:

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

В целом, это невозможно сделать это со 100% -ной точностью, потому что вы должны предсказать все пути кода, что похоже на решение проблемы

Ответ 5

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

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

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

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

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

Ответ 6

Это может работать в очень ограниченных сценариях. Я использую его в части программы ввода кода, которую я написал. Я не помню, где я нашел информацию, но у меня есть следующее (С++ в 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;

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

Ответ 7

Что значит "размер функции"?

Если вы имеете в виду указатель на функцию, то это всегда 4 байта для 32-битных систем.

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

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

Ответ 8

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

Ответ 9

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

Я не удивлюсь, если есть случаи, которые этого не поймают.

Ответ 10

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

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

Ответ 11

Просто установите PAGE_EXECUTE_READWRITE по адресу, где вы получили свою функцию. Затем читайте каждый байт. Когда вы получили байт "0xCC", это означает, что конец функции actual_reading_address - 1.

Ответ 12

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

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

FARPROC pfn = (FARPROC)some_function_with_possibility_to_get_its_size_at_runtime;

pfn Я получаю (например: 0x7FF724241893) указывает на это, это всего лишь инструкция JMP:

введите описание изображения здесь

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

Вы можете делать все это на языке ассемблера, но не на 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 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;
    }
}

Ответ 13

Использование GCC, не так сложно.

void do_something(void) { 
   printf("%s!", "Hello your name is Cemetech"); 
   do_something_END: 
} 

... 

   printf("size of function do_something: %i", (int)(&&do_something_END - (int)do_something));

Ответ 14

ниже код получить точный размер функционального блока, он отлично работает с моим тестом runtime_checks отключить _RTC_CheckEsp в режиме отладки

    #pragma runtime_checks("", off)
DWORD __stdcall loadDll(char* pDllFullPath)
{  
    OutputDebugStringA(pDllFullPath);
    //OutputDebugStringA("loadDll...................\r\n");
    return 0;
    //return test(pDllFullPath);
}
#pragma runtime_checks("", restore)

DWORD __stdcall getFuncSize_loadDll()
{
    DWORD maxSize=(PBYTE)getFuncSize_loadDll-(PBYTE)loadDll;
    PBYTE pTail=(PBYTE)getFuncSize_loadDll-1;
    while(*pTail != 0xC2 && *pTail != 0xC3) --pTail;
    if (*pTail==0xC2)
    {   //0xC3          : ret
        //0xC2 04 00    : ret 4
        pTail +=3;
    }

    return pTail-(PBYTE)loadDll;
};

Ответ 15

Непереносимый, но основанный на API и правильно работающий подход заключается в использовании программ чтения программных баз данных - таких как dbghelp.dll в Windows или readelf в Linux. Их использование возможно только в том случае, если отладочная информация включена/присутствует вместе с программой. Вот пример того, как это работает в Windows:

SYMBOL_INFO symbol = { };

symbol.SizeOfStruct = sizeof(SYMBOL_INFO);

// Implies, that the module is loaded into _dbg_session_handle, see ::SymInitialize & ::SymLoadModule64
::SymFromAddr(_dbg_session_handle, address, 0, &symbol);

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

Я полагаю, что нечто подобное можно сделать через readelf в Linux, но, возможно, вам придется придумать библиотеку поверх ее исходного кода...

Вы должны иметь в виду, что, хотя подход, основанный на разборке, возможен, вам, в основном, придется анализировать ориентированный граф с конечными точками в ret, halt, jmp (ДОКАЗАНО, что включено инкрементное связывание, и вы можете читать jmp-таблицу для определить, является ли jmp, с которым вы сталкиваетесь в функции, внутренним по отношению к этой функции (отсутствует в jmp-таблице изображений) или внешним (присутствует в этой таблице; такие jmp часто встречаются как часть оптимизации хвостового вызова на x64, как я знаю)) любые вызовы, которые должны быть nonret (например, помощник, генерирующий исключение) и т.д.

Ответ 16

Это старый вопрос, но все же...

Для Windows x64 все функции имеют таблицу функций, которая содержит смещение и размер функции. https://docs.microsoft.com/en-us/windows/win32/debug/pe-format Эта таблица функций используется для разматывания при возникновении исключения.

Тем не менее, это не содержит информацию, как встраивание, и все другие вопросы, которые люди уже отметили...