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) myGetObjectName(x)
#endif
그런 다음 다음과 같이 함수를 실행한다(에서).intercept.c
포함하지 않는 것intercept.h
):
const char *myGetObjectName (object *anObject) {
if (anObject == NULL) return "(null)";
return getObjectName(anObject);
그런 다음 통화를 가로채려는 각 소스 파일의 상단에 다음이 있는지 확인하십시오.
#include "intercept.h"
으로 컴파일할 때-DINTERCEPT
", 모든 파일은 실제보다 당신의 기능을 호출하는 반면, 당신의 기능은 여전히 실제 기능을 호출할 것이다.
이'일' 한다.-DINTERCEPT
"는 가로채기가 일어나는 것을 막을 것이다.
만약 당신이 당신의 출처로부터의 통화만이 아니라 모든 통화를 가로채고 싶다면, 그것은 좀 더 까다롭다 - 이것은 일반적으로 실제 기능의 동적 로딩과 분해능으로 이루어질 수 있다.dlload-
그리고dlsym-
타이프 콜) 하지만 네 경우에는 필요하지 않을 것 같아.
에서는 gcc로스를 할 수 .--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_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에서도 공유 라이브러리의 어떤 기능도 재정의할 수 있으므로, 당신은 출처가 없는 프로그램에서 트릭을 할 수 있다.멋지지
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 구현 파일에 포함만 배치하여 재정의 가능한 함수를 포함하는 취약한 선언이 있을 수 있다.
약소 선언h:
__attribute__((weak)) void test(void);
... other weak function declarations ...
함수.c:
/* for GCC, these will become weak definitions */
#ifdef __GNUC__
#include "weakdecls.h"
#endif
void test(void) {
...
}
... other functions ...
이것의 단점은 api파일에 대해 (그 세 줄과 약한 선언이 필요) 무엇인가를 하지 않고서는 완전히 작동하지 않는다는 것이다.그러나 일단 변경하면, 한 파일에 전역 정의를 작성하고 그 정의를 연결함으로써 기능이 쉽게 재정의될 수 있다.
@Johannes Shaub의 대답을 바탕으로 당신이 소유하지 않은 코드에 적합한 솔루션을 구축한다.
재정의할 함수를 약하게 정의한 함수에 별칭으로 지정한 다음 직접 다시 생성하십시오.
무효화하다.h
#define foo(x) __attribute__((weak))foo(x)
foo.c.
function foo() { return 1234; }
무효화하다.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
).
이렇게 하면 코드를 수정할 필요 없이 모든 원본 파일에서 단일 함수를 재정의할 수 있다.
단점은 재정의할 각 파일에 대해 별도의 헤더 파일을 사용해야 한다는 것이다.
함수를 포장하거나 교체하여 기존 코드베이스의 동작을 수정하는 것이 바람직할 때가 많다.이러한 함수의 소스 코드를 편집하는 것이 실행 가능한 옵션인 경우, 이것은 직진 과정이 될 수 있다.기능의 출처를 편집할 수 없는 경우(예: 시스템 C 라이브러리에 의해 기능이 제공되는 경우), 대체 기법이 필요하다.여기서는 UNIX, Windows 및 Macintosh OS X 플랫폼에 대한 이러한 기술을 설명한다.
이것은 OS X, 리눅스 및 Windows에서 어떻게 이 작업이 수행되었는지에 대한 훌륭한 PDF 입니다.
여기에 기록되지 않은 놀라운 묘기는 없지만(이것은 놀라운 반응의 BTW이다) 좋은 읽을거리다.
Daniel S에 의한 Windows, UNIX 및 Macintosh OS X 플랫폼(2004)의 임의 기능 가로채기. 마이어스와 아담 L. 바지넷.
PDF는 대체 위치(중복을 위해)에서 직접 다운로드할 수 있다.
그리고 마지막으로, 이전의 두 출처가 어떻게 해서든 불길에 휩싸이게 된다면, 여기에 구글 검색 결과가 있다.
함수 포인터를 전역 변수로 정의할 수 있다.호출자 구문은 변경되지 않을 것이다.프로그램이 시작되면 일부 명령줄 플래그 또는 환경 변수가 로깅을 사용하도록 설정되어 있는지 확인한 다음 함수 포인터 원래 값을 저장하고 로깅 기능으로 대체할 수 있다.특별한 "로그 작성 가능" 빌드가 필요하지 않을 것이다.사용자는 "필드에서" 로깅을 사용할 수 있다.
발신자의 소스 코드를 수정할 수 있어야 하지만, 캘리어는 수정할 수 있어야 한다(따로 제3자 라이브러리를 호출할 때 사용할 수 있음).
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();
}
두 개의 스터브 라이브러리를 포함하는 링커에서 그것을 하는 까다로운 방법도 있다.
라이브러리 #1은 호스트 라이브러리에 대해 연결되며 재정의되는 기호를 다른 이름으로 노출한다.
라이브러리 #2는 라이브러리 #1에 대해 연결되며, 호출을 통합하고 라이브러리 #1에서 다시 정의된 버전을 호출한다.
여기 링크 주문 조심해, 안 그러면 안 돼.
공유 라이브러리(Unix) 또는 DLL(Windows)을 사용하여 이 작업을 수행하면 성능 저하가 발생할 수 있다.그런 다음 로드되는 DLL/so(디버깅용 한 버전, 비디버그용 한 버전)를 변경할 수 있다.
나는 과거에 비슷한 일을 한 적이 있는데(당신이 이루고자 하는 것을 성취하기 위해서가 아니라 기본적인 전제가 같다) 잘 되었다.
[OP 코멘트를 기반으로 편집]
사실 내가 기능을 재정의하고 싶은 이유 중 하나는 그들이 다른 운영 체제에서 다르게 행동한다고 의심하기 때문이다.
그것에 대처하는 두 가지 일반적인 방법, 즉 공유 lib/dll 방식과 당신이 링크하는 다른 구현을 쓰는 방법이 있다.
두 솔루션(공유 libs 또는 다른 연결)의 경우 foo_linux.c, foo_osx.c, foo_win32.c(또는 더 나은 방법이 Linux/foo.c, osx/foo.c 및 win32/foo.c)를 가진 다음 컴파일하여 적절한 솔루션과 연결하십시오.
플랫폼마다 다른 코드와 디버그 -vs- 릴리즈를 모두 찾는다면 가장 유연한 공유 lib/DLL 솔루션으로 가고 싶을 것이다.
아래는 나의 실험이다.본문과 종국에는 네 가지 결론이 있다.
쇼트 버전
일반적으로 기능을 성공적으로 재정의하려면 다음을 고려해야 한다.
- 약한 속성
- 번역 단위 배열
롱 버전
난 이 원본 파일들을 가지고 있다.
.
├── decl.h
├── func3.c
├── main.c
├── Makefile1
├── Makefile2
├── override.c
├── test_target.c
└── weak_decl.h
본시
#include <stdio.h>
void main (void)
{
func1();
}
test_target.c.c
#include <stdio.h>
void func3(void);
void func2 (void)
{
printf("in original func2()\n");
}
void func1 (void)
{
printf("in original func1()\n");
func2();
func3();
}
펑크3.c
#include <stdio.h>
void func3 (void)
{
printf("in original func3()\n");
}
decl.h
void func1 (void);
void func2 (void);
void func3 (void);
약한_decl.h
void func1 (void);
__attribute__((weak))
void func2 (void);
__attribute__((weak))
void func3 (void);
무효화하다.c
#include <stdio.h>
void func2 (void)
{
printf("in mock func2()\n");
}
void func3 (void)
{
printf("in mock func3()\n");
}
Makefile1:
ALL:
rm -f *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_weak.o -include weak_decl.h
ar cr all_weak.a test_target_weak.o func3.o
gcc main.c all_weak.a override.o -o main -include decl.h
Makefile2:
ALL:
rm -f *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_strong.o -include decl.h # HERE -include differs!!
ar cr all_strong.a test_target_strong.o func3.o
gcc main.c all_strong.a override.o -o main -include decl.h
Makefile1 결과 출력:
in original func1()
in mock func2()
in mock func3()
Makefile2의 출력:
rm *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_strong.o -include decl.h # -include differs!!
ar cr all_strong.a test_target_strong.o func3.o
gcc main.c all_strong.a override.o -o main -include decl.h
override.o: In function `func2':
override.c:(.text+0x0): multiple definition of `func2' <===== HERE!!!
all_strong.a(test_target_strong.o):test_target.c:(.text+0x0): first defined here
override.o: In function `func3':
override.c:(.text+0x13): multiple definition of `func3' <===== HERE!!!
all_strong.a(func3.o):func3.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
Makefile4:2: recipe for target 'ALL' failed
make: *** [ALL] Error 1
기호 표:
all_messagea:
test_target_weak.o:
0000000000000013 T func1 <=== 13 is the offset of func1 in test_target_weak.o, see below disassembly
0000000000000000 W func2 <=== func2 is [W]eak symbol with default value assigned
w func3 <=== func3 is [w]eak symbol without default value
U _GLOBAL_OFFSET_TABLE_
U puts
func3.o:
0000000000000000 T func3 <==== func3 is a strong symbol
U _GLOBAL_OFFSET_TABLE_
U puts
전부_강력한a:
test_target_strong.o:
0000000000000013 T func1
0000000000000000 T func2 <=== func2 is strong symbol
U func3 <=== func3 is undefined symbol, there's no address value on the left-most column because func3 is not defined in test_target_strong.c
U _GLOBAL_OFFSET_TABLE_
U puts
func3.o:
0000000000000000 T func3 <=== func3 is strong symbol
U _GLOBAL_OFFSET_TABLE_
U puts
두 가지 경우 모두 더.override.o
기호:
0000000000000000 T func2 <=== func2 is strong symbol
0000000000000013 T func3 <=== func3 is strong symbol
U _GLOBAL_OFFSET_TABLE_
U puts
분해:
test_target_weak.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <func2>: <===== HERE func2 offset is 0
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <func2+0xb>
b: e8 00 00 00 00 callq 10 <func2+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
0000000000000013 <func1>: <====== HERE func1 offset is 13
13: 55 push %rbp
14: 48 89 e5 mov %rsp,%rbp
17: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1e <func1+0xb>
1e: e8 00 00 00 00 callq 23 <func1+0x10>
23: e8 00 00 00 00 callq 28 <func1+0x15>
28: e8 00 00 00 00 callq 2d <func1+0x1a>
2d: 90 nop
2e: 5d pop %rbp
2f: c3 retq
결론은 다음과 같다.
에 정의된 함수
.o
파일이 에 정의된 동일한 함수를 재정의할 수 있음.a
파일. 위의 Makefile1에서func2()
그리고func3()
에override.o
의 상대를 압도하다.all_weak.a
나는 둘 다로 시도했다..o
파일들이지만 작동하지 않는다.GCC의 경우, 기능을 따로 분리할 필요가 없다.
.o
Visual Studio 툴체인 파일.위 예에서 볼 수 있다.func2()
(과 같은 파일에서)func1()
) 및func3()
(별도의 파일에서) 재정의할 수 있다.함수를 재정의하려면 해당 소비자의 변환 단위를 컴파일할 때 해당 함수를 약한 함수로 지정해야 한다.그것은 그 기능을 약하게 기록하게 될 것이다.
consumer.o
에서 . 위 에 할 때 일일일 때때.test_target.c
, 소비하는 것.func2()
그리고func3()
, 추가해야 한다.-include weak_decl.h
한다.func2()
그리고func3()
나약한그func2()
또한 에서 정의된다.test_target.c
하지만 괜찮아
추가 실험
위의 원본 파일에도 계속 적용됨.그러나 바꿔라override.c
약간:
무효화하다.c
#include <stdio.h>
void func2 (void)
{
printf("in mock func2()\n");
}
// void func3 (void)
// {
// printf("in mock func3()\n");
// }
여기서 의 재정의 버전을 제거했다.func3()
. 에서 원래의 시행으로 되돌아가고 싶어서 이렇게 한 것이다.
나는 여전히 사용한다.Makefile1
짓다. 체격이 괜찮다.그러나 런타임 오류는 다음과 같이 발생한다.
xxx@xxx-host:~/source/override$ ./main
in original func1()
in mock func2()
Segmentation fault (core dumped)
그래서 나는 결승전의 기호를 확인했다.main
:
0000000000000696 T func1
00000000000006b3 T func2
w func3
그래서 우리는 그것을 볼 수 있다.func3
유효한 주소가 없다.세그먼트 결함이 발생하는 이유다.
그래서 왜? 내가 그 말을 덧붙이지 않았니?func3.o
의 안으로all_weak.a
보관 파일?
ar cr all_messages.a func3.o test_target_messages.o
나도 같은 것을 시도했다.func2
, 내가 제거했던 곳func2
로부터의 이행.ovrride.c
하지만 이번에는 세그먼트 결함이 없다.
무효화하다.c
#include <stdio.h>
// void func2 (void)
// {
// printf("in mock func2()\n");
// }
void func3 (void)
{
printf("in mock func3()\n");
}
출력:
xxx@xxx-host:~/source/override$ ./main
in original func1()
in original func2() <====== the original func2() is invoked as a fall back
in mock func3()
내 추측으로는, 왜냐하면func2
동일한 파일/파일 단위에서 정의됨func1
그래서.func2
항상 와 함께 있다.func1
. 그래서 링커는 항상 해결할 수 있다.func2
, ~에서 온 것이라도.test_target.c
또는override.c
.
그러나 을 위해func3
, 별도의 파일/파일 단위(func3.c)로 정의된다.약하다고 선언하면 소비자는test_target.o
여전히 기록될 것이다func3()
나약한그러나 불행히도 GCC 링커는 의 구현을 찾기 위해 동일한 파일의 다른 파일을 확인하지 않을 것이다.비록 그것이 실제로 거기에 있지만.
all_messagea:
func3.o:
0000000000000000 T func3 <========= func3 is indeed here!
U _GLOBAL_OFFSET_TABLE_
U puts
test_target_weak.o:
0000000000000013 T func1
0000000000000000 W func2
w func3
U _GLOBAL_OFFSET_TABLE_
U puts
따라서 다음에서 재정의 버전을 제공해야 함override.c
그렇지 않으면func3()
해결이 안 되다
하지만 나는 GCC가 왜 이렇게 행동하는지 아직도 모르겠어.누가 설명해줄 수 있으면 부탁해.
(업데이트 9:01 AM 8/8/2021: 이 스레드가 이 동작을 설명할 수 있기를 바란다.)
따라서 추가 결론은 다음과 같다.
- 일부 기호가 약하다고 선언하면 모든 약함수의 재정의 버전을 제공하는 것이 좋다.그렇지 않으면, 발신자/소비자의 동일한 파일/번역 단위 내에 있지 않는 한, 원본을 해결할 수 없다.
참조URL: https://stackoverflow.com/questions/617554/override-a-function-call-in-c
'programing' 카테고리의 다른 글
상태 항목의 값을 증가시키는 방법 (0) | 2022.05.26 |
---|---|
렌더링된 vue에서 html 자리 표시자 특성을 이스케이프하는 방법 (0) | 2022.05.26 |
Flex/lex의 문자열 리터럴에 대한 정규식 (0) | 2022.05.26 |
C의 크기가 "int"가 2바이트인가 4바이트인가? (0) | 2022.05.26 |
분할("|")을 사용하여 파이프 기호를 기준으로 Java 문자열 분할 (0) | 2022.05.26 |