![](/img/trans.png)
[英]How can I pass function pointer of a non static member function in a static member function in another class
[英]How can I hook a static function?
我試圖在不修改源代碼的情況下模擬 static function 。 這是因為我們有大量的遺留代碼庫,我們想添加測試代碼而不需要開發人員通過 go 並更改一堆原始代碼。
使用 objcopy,我可以玩 object 文件之間的函數,但我不能影響內部鏈接。 換句話說,在下面的代碼中,我可以讓 main.cpp 從 bar.c 調用模擬的 foo(),但我不能讓 UsesFoo() 從 bar.c 調用模擬的 foo()。
我知道這是因為 foo() 已經在 foo.c 中定義。 除了更改源代碼之外,還有什么方法可以使用 ld 或其他工具來刪除 foo() 以便最終鏈接將其從我的 bar.c 中拉出來?
foo.c
#include <stdio.h>
static void foo()
{
printf("static foo\n");
}
void UsesFoo()
{
printf("UsesFoo(). Calling foo()\n");
foo();
}
酒吧.c
#include <stdio.h>
void foo()
{
printf("I am the foo from bar.c\n");
}
主文件
#include <iostream>
extern "C" void UsesFoo();
extern "C" void foo();
using namespace std;
int main()
{
cout << "Calling UsesFoo()\n\n";
UsesFoo();
cout << "Calling foo() directly\n";
foo();
return 0;
}
編譯:
gcc -c foo.c
gcc -c bar.c
g++ -c main.c
(Below simulates how we consume code in the final output)
ar cr libfoo.a foo.o
ar cr libbar.a bar.o
g++ -o prog main.o -L. -lbar -lfoo
This works because the foo() from libbar.a gets included first, but doesn't affect the internal foo() in foo.o
我也試過:
gcc -c foo.c
gcc -c bar.c
g++ -c main.c
(Below simulates how we consume code in the final output)
ar cr libfoo.a foo.o
ar cr libbar.a bar.o
objcopy --redefine-sym foo=_redefinedFoo libfoo.a libfoo-mine.a
g++ -o prog main.o -L. -lbar -lfoo-mine
This produces the same effect. main will call foo() from bar, but UsesFoo() still calls foo() from within foo.o
通常的技巧是將它們編譯為共享庫,然后使用 LD_PRELOAD 預加載將替換您所針對的功能的版本。
這是一個不錯的教程:
我想你可以試試 gcc 中的 --wrap 標志。 使用標志的示例: 如何正確使用 `--wrap` 選項包裝函數?
如果您願意更改源代碼,long.kl 的答案將有效。 不幸的是,因為我們希望盡可能地保持源代碼的原始狀態,所以這對我們來說是不可用的。
盡管 AndrewHenle 在他的回復中認為,我們可以重寫 object 文件以允許我們覆蓋 static function。 這需要理解和解析 object 文件寫入的 ELF 格式。
主要問題是 object 文件中的函數將使用相對跳轉/分支/調用文本段中的地址。 換句話說,假設我們有以下代碼:
#include <stdio.h>
static void foo()
{
printf("static foo\n");
}
void UsesFoo()
{
printf("UsesFoo(). Calling foo()\n");
foo();
}
在這種情況下,沒有優化(“gcc -c foo.c”),這將生成一個 object 文件 foo.o,它具有以下反匯編:
objdump -d foo.o
foo.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <foo>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <foo+0xb>
b: e8 00 00 00 00 callq 10 <foo+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
0000000000000013 <UsesFoo>:
13: 55 push %rbp
14: 48 89 e5 mov %rsp,%rbp
17: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1e <UsesFoo+0xb>
1e: e8 00 00 00 00 callq 23 <UsesFoo+0x10>
23: b8 00 00 00 00 mov $0x0,%eax
28: e8 d3 ff ff ff callq 0 <foo>
2d: 90 nop
2e: 5d pop %rbp
2f: c3 retq
查看指令 0xb 和 0x1e。 這些是 c 代碼中的 printf() 被翻譯成的調用。 您會注意到在操作碼 0xe8 之后,字節的 rest 為 0x00。 這是因為在最終編譯到 puts 地址的過程中,它們將被 linker 替換(假設這是一個 static 鏈接)。
現在注意 0x28 處的調用指令正在使用 0xd3 ff ff ff 的地址進行調用。 如果這是一個非靜態 function,我們會在操作碼之后看到相同的 0x00 字節,但在這種情況下,我們會看到 0xd3ffffff。 這是一個 32 位相對調用,對應於 2 的補碼中的 -1(最終地址將在指令指針中變為 0)。 這意味着我們的文本段(代碼)已被硬編碼為使用該地址。
為了解決這個問題,我們將不得不重新編寫 ELF 以更改對 foo() 的調用的處理方式。 有幾個選擇:
我們將另一個.text.[somename] 部分添加到我們的文件中,其中包含充當蹦床的代碼,即:FakeFoo()。 然后我們重寫 foo() 的第一條指令,立即跳轉到 FakeFoo()。 Hacky,但可能會丟失調試信息。
.rela.text 部分包含 function 重定位。 這些用於告訴 linker 我們需要用最終位置替換調用的字節。 當 linker 看到此部分時,它將用最終二進制中的實際計算地址替換“偏移”字段中的地址。 對於我們的二進制文件,我們看到:
readelf -r foo.o
Relocation section '.rela.text' at offset 0x280 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000007 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
00000000000c 000b00000004 R_X86_64_PLT32 0000000000000000 puts - 4
00000000001a 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 7
00000000001f 000b00000004 R_X86_64_PLT32 0000000000000000 puts - 4
偏移量 0xc 和 0x14 是 foo() 和 UsesFoo() 中的調用指令尋找 puts() function 的位置(注意:編譯器將我們對“printf()”的調用轉換為使用“puts()”)。
因此,我們可以在此處為指令 0x28 處的調用添加另一個條目,並讓 linker 在未聲明的代碼中某處查找另一個名為“foo()”的 function。
這也需要修復 ELF 文件的 .symtab 條目,因為它將包含對本地 function foo() 的引用:
readelf -s foo.o
Symbol table '.symtab' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 19 FUNC LOCAL DEFAULT 1 foo
7: 0000000000000000 0 SECTION LOCAL DEFAULT 7
8: 0000000000000000 0 SECTION LOCAL DEFAULT 8
9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
12: 0000000000000013 29 FUNC GLOBAL DEFAULT 1 UsesFoo
為了使 linker 在此 object 文件之外查找 foo(),我們必須將 foo 的條目更改為“NOTYPE GLOBAL”“UND”類型,因此 Z3175B426046787EECE2Z777 中不存在這個文件。
還有另一個部分,.rela.eh_frame,用於調試,您也需要注意。
最后,此方法要求您通過二進制文件訪問 go,搜索與跳轉/調用/分支對應的操作碼,並創建/修復條目,以便 linker 將在其他 ZA8CFDE6331B4B666AC 文件中查找“foo()”。
所有這些只是為了讓 linker 在不同的文件中查找 foo(),以便您可以用您編寫的文件替換原始的 foo()。 如果你想在所有這些之后調用原始的 foo(),你可能想要將 foo() 重命名為其他名稱,即:_real_foo(),並設置符號表 (.symtab) 以便你的假 foo( ) 可以執行以下操作:
bar.c:
void foo()
{
printf("I am the fake foo! Calling the real foo!\n");
__real_foo();
}
最終,如果您的開發人員將他們的大部分功能從 static 方法轉移到全局方法,那會更好(並且更容易)。 但是,如果您想在 object 文件創建后重新編寫它,在適當的情況下,可以付出相當大的努力。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.