developer tip

C에서 함수 호출 재정의

optionbox 2020. 11. 6. 08:06
반응형

C에서 함수 호출 재정의


호출을 로깅하기 위해 다양한 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()링크 타임에 래퍼 함수로 다시 라우팅됩니다. 그러나이 매우 유용한 플래그는 Mac OS X의 gcc에는 없습니다.

extern "C"그래도 g ++로 컴파일하는 경우 래퍼 함수를 ​​선언해야합니다 .


LD_PRELOADtrick-see를 사용하여 함수를 재정의 할 수 있습니다 man ld.so. 함수로 공유 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에서도 공유 라이브러리의 모든 함수를 재정의 할 수 있으므로 소스가없는 프로그램에서 트릭을 수행 할 수 있습니다. 멋지지 않나요?


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!

슬프게도 다른 컴파일러에서는 작동하지 않습니다. 그러나 GCC를 사용하여 컴파일하는 경우 API 구현 파일에 포함 만 배치하여 자체 파일에 재정의 가능한 함수를 포함하는 약한 선언을 가질 수 있습니다.

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 파일에 무언가를하지 않고는 완전히 작동하지 않는다는 것입니다 (이 세 줄과 weakdecls 필요). 그러나 일단 변경하면 하나의 파일에 전역 정의를 작성하고 연결하여 함수를 쉽게 재정의 할 수 있습니다.


It is often desirable to modify the behavior of existing code bases by wrapping or replacing functions. When editing the source code of those functions is a viable option, this can be a straight-forward process. When the source of the functions cannot be edited (e.g., if the functions are provided by the system C library), then alternative techniques are required. Here, we present such techniques for UNIX, Windows, and Macintosh OS X platforms.

This is a great PDF covering how this was done on OS X, Linux and Windows.

It doesn't have any amazing tricks that haven't been documented here (this is an amazing set of responses BTW)... but it is a nice read.

Intercepting arbitrary functions on Windows, UNIX, and Macintosh OS X platforms (2004), by Daniel S. Myers and Adam L. Bazinet.

You can download the PDF directly from an alternate location (for redundancy).

And finally, should the previous two sources somehow go down in flames, here's a Google search result for it.


You can define a function pointer as a global variable. The callers syntax would not change. When your program starts, it could check if some command-line flag or environment variable is set to enable logging, then save the function pointer's original value and replace it with your logging function. You would not need a special "logging enabled" build. Users could enable logging "in the field".

You will need to be able to modify the callers' source code, but not the callee (so this would work when calling third-party libraries).

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

There's also a tricky method of doing it in the linker involving two stub libraries.

Library #1 is linked against the host library and exposes the symbol being redefined under another name.

Library #2 is linked against library #1, interecepting the call and calling the redefined version in library #1.

Be very careful with link orders here or it won't work.


Building on @Johannes Schaub's answer with a solution suitable for code you don't own.

Alias the function you want to override to a weakly-defined function, and then reimplement it yourself.

override.h

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

foo.c

function foo() { return 1234; }

override.c

function foo() { return 5678; }

Use pattern-specific variable values in your Makefile to add the compiler flag -include override.h.

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

Aside: Perhaps you could also use -D 'foo(x) __attribute__((weak))foo(x)' to define your macros.

Compile and link the file with your reimplementation (override.c).

  • This allows you to override a single function from any source file, without having to modify the code.

  • The downside is that you must use a separate header file for each file you want to override.


You could use a shared library (Unix) or a DLL (Windows) to do this as well (would be a bit of a performance penalty). You can then change the DLL/so that gets loaded (one version for debug, one version for non-debug).

I have done a similar thing in the past (not to achieve what you are trying to achieve, but the basic premise is the same) and it worked out well.

[Edit based on OP comment]

In fact one of the reasons I want to override functions is because I suspect they behave differently on different operating systems.

There are two common ways (that I know of) of dealing with that, the shared lib/dll way or writing different implementations that you link against.

For both solutions (shared libs or different linking) you would have foo_linux.c, foo_osx.c, foo_win32.c (or a better way is linux/foo.c, osx/foo.c and win32/foo.c) and then compile and link with the appropriate one.

If you are looking for both different code for different platforms AND debug -vs- release I would probably be inclined to go with the shared lib/DLL solution as it is the most flexible.

참고URL : https://stackoverflow.com/questions/617554/override-a-function-call-in-c

반응형