Переопределить вызов функции в C - PullRequest
65 голосов
/ 06 марта 2009

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

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

Я создаю новый исходный файл, подобный этому:

#include <apiheader.h>    

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

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

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

Ответы [ 9 ]

76 голосов
/ 06 марта 2009

С 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 ++.

67 голосов
/ 06 марта 2009

Если вы хотите захватывать / изменять вызовы только для своего источника, самое простое решение - собрать файл заголовка (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-), но Я не думаю, что это необходимо в вашем случае.

36 голосов
/ 06 марта 2009

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

В теле вашей функции (в разделяемой lib) вы пишете так:

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, без изменения / перекомпиляции программы, так что вы можете добиться цели с программами, для которых у вас нет исходного кода. Разве это не приятно?

24 голосов
/ 06 марта 2009

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

test.c

#include <stdio.h>

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

int main() {
    test();
}

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

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

test1.c

#include <stdio.h>

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

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

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

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

weakdecls.h

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

functions.c

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

void test(void) { 
    ...
}

... other functions ...

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

10 голосов
/ 23 марта 2009

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

Это отличный PDF-файл, в котором рассказывается, как это было сделано в OS X, Linux и Windows.

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

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

Вы можете загрузить PDF напрямую из другого места (для резервирования) .

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

9 голосов
/ 06 марта 2009

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

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

foo.h:

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();
}
3 голосов
/ 11 июля 2017

Опираясь на ответ @ Йоханнеса Шауба, мы предлагаем решение, подходящее для кода, который вам не принадлежит.

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

override.h

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

foo.c

function foo() { return 1234; }

override.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).

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

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

3 голосов
/ 06 марта 2009

Есть также хитрый способ сделать это в компоновщике с использованием двух библиотек-заглушек.

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

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

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

0 голосов
/ 06 марта 2009

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...