Управление конфигурациями программного обеспечения C во время компиляции [дубликат]

61
задан Tim Post 6 March 2009 в 04:04
поделиться

9 ответов

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

64
ответ дан paxdiablo 31 August 2018 в 10:37
поделиться
  • 1
    Использование определения не является истинным полиморфным ответом, но я соглашусь на использование изобретательности: P – Suroot 6 March 2009 в 03:50
  • 2
    Используйте флаг времени компиляции для управления перехватом (см. Обновленный ответ). Опять же, это можно сделать и во время выполнения, вам просто нужно обнаружить его в myGetObjectName () и всегда вызывать getObjectName, если установлен флаг времени выполнения (т. Е. Все же перехватывать, но изменять поведение). – paxdiablo 6 March 2009 в 03:54
  • 3
    Вы можете использовать параметр & quot; -ключить файл & quot; чтобы GCC включал файл автоматически. вам даже не придется прикасаться к файлу :) – Johannes Schaub - litb 6 March 2009 в 05:40
  • 4
    хотя автоматическое включение может вызвать проблемы при включении исходного файла API. его заявления также будут заменены. не слишком легко :) – Johannes Schaub - litb 6 March 2009 в 05:43
  • 5
    Это отлично подходит для модульных тестовых наборов (например, с использованием CGreen), где вы хотите заглушить некоторые зависимости внутри функции. Был отличным ответом для моих целей. – nrjohnstone 10 July 2013 в 03:13

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

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

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

35
ответ дан Benjamin 31 August 2018 в 10:37
поделиться
  • 1
    Нет, вы можете только переопределить функцию, предоставленную общей библиотекой таким образом. – Chris Stratton 20 December 2012 в 00:30
  • 2
    @ChrisStratton Вы правы, syscall не может быть переоценена таким образом, я отредактировал свой ответ. – qrdl 20 December 2012 в 08:24

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

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

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
ответ дан Chris Peterson 31 August 2018 в 10:37
поделиться
  • 1
    Я рассмотрел этот метод, но он требует модификации исходного кода, чего я действительно не хочу делать, если только не придется. Хотя это имеет дополнительное преимущество при переключении во время выполнения. – dreamlax 6 March 2009 в 04:08

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

71
ответ дан codelogic 31 August 2018 в 10:37
поделиться
  • 1
    это хороший способ. не знал об этом. но если я правильно читаю man-страницу, это должно быть «__ real_getObjectName (anObject); & quot; который маршрутизируется в getObjectName компоновщиком. в противном случае вы снова вызовите __wrap_getObjectName снова. или я что-то пропустил? – Johannes Schaub - litb 6 March 2009 в 04:41
  • 2
    Вы правы, это должно быть __real_getObjectName, спасибо. Я должен был дважды проверить на странице man :) – codelogic 6 March 2009 в 04:46
  • 3
    Я разочарован тем, что ld на Mac OS X не поддерживает флаг --wrap. – Daryl Spitzer 6 January 2011 в 21:04
  • 4
    ld в Mac OS X - это печальное зверь во многих отношениях. :-( – Carl Norum 28 January 2013 в 20:45
  • 5
    Кстати, компилятор не предоставляет объявления символа __real *. Вы должны объявить это с помощью extern char *__real_getObjectName(object *anObject). – Brendan 13 May 2016 в 03:13

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

24
ответ дан Johannes Schaub - litb 31 August 2018 в 10:37
поделиться
  • 1
    Это потребует изменения API, не так ли? – dreamlax 6 March 2009 в 04:20
  • 2
    ваше имя функции будет одинаковым. также не изменит ABI или API каким-либо образом. просто включите переопределяющий файл при связывании, и вызовы будут сделаны для не-слабой функции. libc / pthread делают этот трюк: когда pthread связан, его поточные функции используются вместо слабого libc – Johannes Schaub - litb 6 March 2009 в 04:22
  • 3
    – Johannes Schaub - litb 6 March 2009 в 04:26

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

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

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

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

3
ответ дан Joshua 31 August 2018 в 10:37
поделиться
  • 1
    Звучит сложно, но он не позволяет изменять источник. Очень приятное предложение. – dreamlax 6 March 2009 в 04:01
  • 2
    Я не думаю, что вы можете заставить getObjectName перейти к определенной библиотеке без трюков dlopen / dlsym. – paxdiablo 6 March 2009 в 04:28
  • 3
    Любая операция времени привязки, которая перетаскивается в библиотеке хоста, приведет к многозначному символу. – paxdiablo 6 March 2009 в 04:30
  • 4
    Пример: вы ссылаетесь на lib2, для компоновщика требуется l1getObj (переопределенное имя). Но l1getObj требует getObj (который уже находится в l2, поэтому компоновщик не вмещает объекты хоста) - это приводит к бесконечной рекурсии. – paxdiablo 6 March 2009 в 04:32
  • 5
    Что-то особенно странное в отношении поведения, когда библиотеки являются динамическими, а не статическими, что делает эту работу. Я обнаружил это случайно. – Joshua 6 March 2009 в 05:00

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

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

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

Перехват произвольных функции на платформах Windows, UNIX и Macintosh OS X (2004), Daniel S. Myers и Adam L. Bazinet .

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

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

10
ответ дан Kuba Ober 31 August 2018 в 10:37
поделиться

Вы могли бы использовать общую библиотеку (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, поскольку он является наиболее гибким.

0
ответ дан TofuBeer 31 August 2018 в 10:37
поделиться

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

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

override.h

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

foo.c

function foo() { return 1234; }

override.c

function foo() { return 5678; }

Используйте pattern-

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

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

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

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

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

  • Это позволяет вам переопределить одну функцию из любого исходного файла без изменения кода.
  • Недостатком является то, что вы должны использовать отдельный файл заголовка для каждого файла, который вы хотите переопределить.
3
ответ дан vaughan 31 August 2018 в 10:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: