Переопределить вызов функции в C

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

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

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

#include <apiheader.h>    

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

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

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

Ответ 1

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

Ответ 2

С помощью 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++.

Ответ 3

Вы можете переопределить функцию, используя трюк LD_PRELOAD - см. man ld.so. Вы компилируете shared lib с помощью своей функции и запускаете двоичный файл (вам даже не нужно изменять двоичный файл!), Например 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, без изменения/перекомпиляции программы, чтобы вы могли сделать трюк в программах, для которых у вас нет источника. Разве это не приятно?

Ответ 4

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

Ответ 5

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

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

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

http://wwwold.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf

Ответ 6

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

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

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

Ответ 7

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

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

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

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

Ответ 8

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

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

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

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

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

Ответ 9

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

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

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

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

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

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

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