Вопрос

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

Например, допустим, я использую функцию, вызываемую getObjectName тысячи раз в моем исходном коде.Иногда я хочу временно переопределить эту функцию, потому что я хочу изменить поведение этой функции, чтобы увидеть другой результат.

Я создаю новый исходный файл следующим образом:

#include <apiheader.h>    

const char *getObjectName (object *anObject)
{
    if (anObject == NULL)
        return "(null)";
    else
        return "name should be here";
}

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

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

Это было полезно?

Решение

Если вы хотите перехватывать / изменять вызовы только для вашего источника, самое простое решение - создать файл заголовка (intercept.h) с:

#ifdef INTERCEPT
    #define getObjectName(x) myGetObectName(x)
#endif

и реализуем функцию следующим образом (в intercept.c который не делает включать в себя intercept.h):

const char *myGetObjectName (object *anObject) {
    if (anObject == NULL)
        return "(null)";
    else
        return getObjectName(anObject);
}

Затем убедитесь, что в каждом исходном файле, в котором вы хотите перехватить вызов, есть:

#include "intercept.h"

на самом верху.

Затем, когда вы скомпилируете с помощью "-DINTERCEPT", все файлы будут вызывать вашу функцию, а не реальную, и ваша функция все равно может вызывать реальную.

Компиляция без использования "-DINTERCEPT" предотвратит перехват.

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

Другие советы

С помощью gcc под Linux вы можете использовать --wrap флаг компоновщика выглядит следующим образом:

gcc program.c -Wl,-wrap,getObjectName -o program

и определите свою функцию следующим образом:

const char *__wrap_getObjectName (object *anObject)
{
    if (anObject == NULL)
        return "(null)";
    else
        return __real_getObjectName( anObject ); // call the real function
}

Это гарантирует, что все вызовы в getObjectName() перенаправляются на вашу функцию-оболочку (во время соединения).Однако этот очень полезный флаг отсутствует в gcc под Mac OS X.

Не забудьте объявить функцию-оболочку с помощью extern "C" однако, если вы компилируете с помощью g ++.

Вы можете переопределить функцию с помощью LD_PRELOAD хитрость - смотрите man ld.so.Вы компилируете общую библиотеку с помощью своей функции и запускаете двоичный файл (вам даже не нужно изменять двоичный файл!). Нравится LD_PRELOAD=mylib.so myprog.

В теле вашей функции (в общей библиотеке) вы пишете примерно так:

const char *getObjectName (object *anObject) {
  static char * (*func)();

  if(!func)
    func = (char *(*)()) dlsym(RTLD_NEXT, "getObjectName");
  printf("Overridden!\n");     
  return(func(anObject));    // call original function
}

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

Если вы используете GCC, вы можете создать свою функцию weak.Эти может быть переопределен по неслабым функциям:

тест.c:

#include <stdio.h>

__attribute__((weak)) void test(void) { 
    printf("not overridden!\n"); 
}

int main() {
    test();
}

Что он делает?

$ gcc test.c
$ ./a.out
not overridden!

тест1.c:

#include <stdio.h>

void test(void) {
    printf("overridden!\n");
}

Что он делает?

$ gcc test1.c test.c
$ ./a.out
overridden!

К сожалению, это не сработает для других компиляторов.Но у вас могут быть слабые объявления, которые содержат переопределяемые функции в их собственном файле, помещая просто include в файлы реализации API, если вы компилируете с использованием GCC:

слабые стороны.h:

__attribute__((weak)) void test(void);
... other weak function declarations ...

функции.c:

/* for GCC, these will become weak definitions */
#ifdef __GNUC__
#include "weakdecls.h"
#endif

void test(void) { 
    ...
}

... other functions ...

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

Часто бывает желательно изменить поведение существующих баз кода путем переноса или замены функций.Когда редактирование исходного кода этих функций является жизнеспособным вариантом, это может быть простым процессом.Когда исходный код функций не может быть отредактирован (например, если функции предоставляются системной библиотекой C), тогда требуются альтернативные методы.Здесь мы представляем такие методы для платформ UNIX, Windows и Macintosh OS X.

Это отличный PDF-файл, описывающий, как это было сделано в OS X, Linux и Windows.

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

Перехват произвольных функций на платформах Windows, UNIX и Macintosh OS X (2004), автор Дэниел С.Майерс и Адам Л.Базинет.

Ты можешь загрузите PDF-файл непосредственно из другого места (для резервирования)..

И, наконец, должны ли предыдущие два источника каким-то образом сгореть, вот результат поиска в Google по этому запросу.

Вы можете определить указатель на функцию как глобальную переменную.Синтаксис вызывающих устройств не изменился бы.Когда ваша программа запускается, она может проверить, установлен ли какой-либо флаг командной строки или переменная среды для включения ведения журнала, затем сохранить исходное значение указателя функции и заменить его вашей функцией ведения журнала.Вам не понадобится специальная сборка с включенным ведением журнала.Пользователи могли бы включить ведение журнала "в полевых условиях".

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

фу.х:

typedef const char* (*GetObjectNameFuncPtr)(object *anObject);
extern GetObjectNameFuncPtr GetObjectName;

foo.cpp:

const char* GetObjectName_real(object *anObject)
{
    return "object name";
}

const char* GetObjectName_logging(object *anObject)
{
    if (anObject == null)
        return "(null)";
    else
        return GetObjectName_real(anObject);
}

GetObjectNameFuncPtr GetObjectName = GetObjectName_real;

void main()
{
    GetObjectName(NULL); // calls GetObjectName_real();

    if (isLoggingEnabled)
        GetObjectName = GetObjectName_logging;

    GetObjectName(NULL); // calls GetObjectName_logging();
}

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

Библиотека # 1 связана с основной библиотекой и предоставляет переопределяемый символ под другим именем.

Библиотека # 2 связана с библиотекой # 1, перехватывая вызов и вызывая переопределенную версию в библиотеке # 1.

Будьте очень осторожны с заказами ссылок здесь, иначе это не сработает.

Основываясь на ответе @Johannes Schaub, предложите решение, подходящее для кода, которым вы не владеете.

Измените псевдоним функции, которую вы хотите переопределить, на слабо определенную функцию, а затем переопределите ее самостоятельно.

переопределение.h

#define foo(x) __attribute__((weak))foo(x)

foo.c

function foo() { return 1234; }

переопределение.c

function foo() { return 5678; }

Использование значения переменных, зависящих от шаблона в вашем Makefile добавьте флаг компилятора -include override.h.

%foo.o: ALL_CFLAGS += -include override.h

В сторону:Возможно, вы могли бы также использовать -D 'foo(x) __attribute__((weak))foo(x)' чтобы определить ваши макросы.

Скомпилируйте и свяжите файл с вашей повторной реализацией (override.c).

  • Это позволяет вам переопределить одну функцию из любого исходного файла без необходимости изменять код.

  • Недостатком является то, что вы должны использовать отдельный заголовочный файл для каждого файла, который вы хотите переопределить.

Вы также могли бы использовать общую библиотеку (Unix) или DLL (Windows) для этого (это немного снизило бы производительность).Затем вы можете изменить DLL /, чтобы она загружалась (одна версия для отладки, одна версия для не-отладки).

Я делал нечто подобное в прошлом (не для достижения того, чего пытаетесь достичь вы, но основная предпосылка та же), и это сработало хорошо.

[Правка на основе комментария к ОП]

Фактически, одна из причин, по которой я хочу переопределить функции, заключается в том, что я подозреваю, что они ведут себя по-разному в разных операционных системах.

Есть два распространенных способа (о которых я знаю) справиться с этим: общий способ lib / dll или написание различных реализаций, на которые вы ссылаетесь.

Для обоих решений (общие библиотеки или разные ссылки) у вас должны быть foo_linux.c, foo_osx.c, foo_win32.c (или лучший способ - linux / foo.c, osx / foo.c и win32 / foo.c), а затем скомпилировать и связать с соответствующим.

Если вы ищете как разный код для разных платформ, так и debug -vs- release, я, вероятно, был бы склонен использовать решение shared lib / DLL, поскольку оно является наиболее гибким.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top