Substituir uma chamada de function em C

Eu quero replace certas chamadas de function para várias APIs por causa do log de chamadas, mas eu também pode querer manipular os dados antes de serem enviados para a function real.

Por exemplo, digamos que eu use uma function chamada getObjectName milhares de vezes no meu código-fonte. Eu quero replace temporariamente esta function, por vezes, porque eu quero mudar o comportamento desta function para ver o resultado diferente.

Eu crio um novo arquivo de origem como este:

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

Compilo todas as minhas outras fonts como normalmente faria, mas eu as vinculo a essa function antes de vincular à biblioteca da API. Isso funciona bem, exceto que obviamente não posso chamar a function real dentro da minha function de substituição.

Existe uma maneira mais fácil de “replace” uma function sem vincular / compilar erros / avisos? Idealmente, eu quero ser capaz de replace a function apenas compilando e vinculando um ou dois arquivos extras em vez de mexer nas opções de vinculação ou alterar o código-fonte real do meu programa.

    Se for apenas para sua origem que você deseja capturar / modificar as chamadas, a solução mais simples é montar um arquivo de header ( intercept.h ) com:

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

    e implementar a function da seguinte maneira (em intercept.c que não inclui intercept.h ):

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

    Em seguida, certifique-se de que cada arquivo de origem em que você deseja interceptar a chamada tenha:

     #include "intercept.h" 

    no topo.

    Então, quando você compilar com ” -DINTERCEPT “, todos os arquivos chamarão sua function em vez da real e sua function ainda poderá chamar a real.

    A compilation sem o ” -DINTERCEPT ” impedirá a interceptação.

    É um pouco mais complicado se você quiser interceptar todas as chamadas (não apenas aquelas da sua fonte) – isso geralmente pode ser feito com carregamento dynamic e resolução da function real (com chamadas do tipo dlsym- e dlsym- ), mas eu não acho é necessário no seu caso.

    Com o gcc, no Linux, você pode usar o --wrap vinculador --wrap assim:

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

    e defina sua function como:

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

    Isso garantirá que todas as chamadas para getObjectName() sejam redirecionadas para sua function de wrapper (no momento do link). Este sinalizador muito útil está, no entanto, ausente no gcc no Mac OS X.

    Lembre-se de declarar a function wrapper com extern "C" se você estiver compilando com g ++.

    Você pode sobrescrever uma function usando o truque LD_PRELOAD – veja man ld.so Você compila a biblioteca compartilhada com sua function e inicia o binário (você nem precisa modificar o binário!) Como LD_PRELOAD=mylib.so myprog .

    No corpo da sua function (em lib compartilhada) você escreve assim:

     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 } 

    Você pode replace qualquer function da biblioteca compartilhada, mesmo do stdlib, sem modificar / recompilar o programa, para que você possa fazer o truque em programas para os quais você não possui uma fonte. Não é legal?

    Se você usar o GCC, poderá tornar sua function weak . Aqueles podem ser substituídos por funções não fracas:

    test.c :

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

    O que isso faz?

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

    test1.c :

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

    O que isso faz?

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

    Infelizmente, isso não funcionará para outros compiladores. Mas você pode ter as declarações fracas que contêm funções substituíveis em seu próprio arquivo, colocando apenas uma inclusão nos arquivos de implementação da API se você estiver compilando usando o GCC:

    weakdecls.h :

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

    funções.c :

     /* for GCC, these will become weak definitions */ #ifdef __GNUC__ #include "weakdecls.h" #endif void test(void) { ... } ... other functions ... 

    O lado negativo disso é que ele não funciona inteiramente sem fazer alguma coisa com os arquivos da API (precisando dessas três linhas e dos weakdecls). Mas uma vez que você fez essa mudança, as funções podem ser facilmente anuladas, escrevendo uma definição global em um arquivo e vinculando isso.

    Frequentemente, é desejável modificar o comportamento de bases de código existentes agrupando ou substituindo funções. Ao editar o código-fonte dessas funções é uma opção viável, isso pode ser um processo direto. Quando a fonte das funções não puder ser editada (por exemplo, se as funções forem fornecidas pela biblioteca C do sistema), serão necessárias técnicas alternativas. Aqui, apresentamos essas técnicas para plataformas UNIX, Windows e Macintosh OS X.

    Este é um ótimo PDF sobre como isso foi feito no OS X, Linux e Windows.

    Não tem nenhum truque incrível que não tenha sido documentado aqui (este é um incrível conjunto de respostas BTW) … mas é uma boa leitura.

    Interceptando funções arbitrárias nas plataformas Windows, UNIX e Macintosh OS X (2004), por Daniel S. Myers e Adam L. Bazinet .

    Você pode baixar o PDF diretamente de um local alternativo (para redundância) .

    E, finalmente, se as duas fonts anteriores de alguma forma ficarem em chamas, aqui está um resultado de pesquisa do Google .

    Você pode definir um ponteiro de function como uma variável global. A syntax dos chamadores não mudaria. Quando o programa for iniciado, ele poderá verificar se algum sinalizador de linha de comando ou variável de ambiente está definido para habilitar o log, em seguida, salve o valor original do ponteiro da function e substitua-o pela sua function de log. Você não precisaria de um build especial “logging enabled”. Os usuários podem ativar o registro “no campo”.

    Você precisará modificar o código-fonte dos chamadores, mas não o chamado (para que isso funcione ao chamar bibliotecas de terceiros).

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

    Há também um método complicado de fazer isso no vinculador envolvendo duas bibliotecas stub.

    A biblioteca nº 1 está vinculada à biblioteca do host e expõe o símbolo que está sendo redefinido com outro nome.

    A Biblioteca # 2 está vinculada à biblioteca # 1, interceptando a chamada e chamando a versão redefinida na biblioteca # 1.

    Tenha muito cuidado com os pedidos de link aqui ou não vai funcionar.

    Com base na resposta do @Johannes Schaub com uma solução adequada para o código que você não possui.

    Alias ​​a function que você deseja replace para uma function definida fracamente e, em seguida, reimplementá-la você mesmo.

    override.h

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

    foo.c

     function foo() { return 1234; } 

    override.c

     function foo() { return 5678; } 

    Use valores variables ​​específicos de padrões em seu Makefile para adicionar o sinalizador de compilador -include override.h .

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

    Aparte: Talvez você também possa usar -D 'foo(x) __attribute__((weak))foo(x)' para definir suas macros.

    Compile e vincule o arquivo à sua reimplementação ( override.c ).

    • Isso permite que você substitua uma única function de qualquer arquivo de origem, sem precisar modificar o código.

    • A desvantagem é que você deve usar um arquivo de header separado para cada arquivo que deseja replace.

    Você poderia usar uma biblioteca compartilhada (Unix) ou uma DLL (Windows) para fazer isso também (seria um pouco de uma penalidade de desempenho). Você pode então alterar a DLL / para que seja carregado (uma versão para debugging, uma versão para não-debugging).

    Eu fiz uma coisa semelhante no passado (não para alcançar o que você está tentando alcançar, mas a premissa básica é a mesma) e funcionou bem.

    [Editar com base no comentário do OP]

    Na verdade, uma das razões pelas quais desejo replace as funções é porque suspeito que elas se comportam de maneira diferente em sistemas operacionais diferentes.

    Existem duas maneiras comuns (que eu conheço) de lidar com isso, a maneira compartilhada de lib / dll ou escrever diferentes implementações com as quais você faz o link.

    Para ambas as soluções (libs compartilhadas ou links diferentes) você teria foo_linux.c, foo_osx.c, foo_win32.c (ou uma maneira melhor é linux / foo.c, osx / foo.c e win32 / foo.c) e então compilar e vincular com o apropriado.

    Se você estiver procurando por código diferente para plataformas diferentes e debug -vs- release eu provavelmente estaria inclinado a ir com a solução lib / DLL compartilhada, pois é o mais flexível.