我正在爲在Linux機器上運行的C語言編寫的應用程序實現一些有限的遠程調試功能。目標是與應用程序進行通信並查找任意變量的值或運行任意函數。是否可以確定符號是否是C中的變量或函數?
我能夠通過dlsym()
調用查找符號,但我無法確定返回的地址是指的是函數還是變量。有沒有辦法通過這個符號表來確定輸入信息?
我正在爲在Linux機器上運行的C語言編寫的應用程序實現一些有限的遠程調試功能。目標是與應用程序進行通信並查找任意變量的值或運行任意函數。是否可以確定符號是否是C中的變量或函數?
我能夠通過dlsym()
調用查找符號,但我無法確定返回的地址是指的是函數還是變量。有沒有辦法通過這個符號表來確定輸入信息?
您可以讀取該文件/proc/self/maps
和分析每一行的前三個字段:
<begin-addr>-<end-addr> rwxp ...
然後您搜索包含您正在尋找的地址線和檢查權限:
r-x
:它是代碼;rw-
:它是可寫數據;r--
:它是隻讀數據;rwxp
:生成的代碼,...)。例如下面的程序:
#include <stdio.h>
void foo() {}
int x;
int main()
{
int y;
printf("%p\n%p\n%p\n", foo, &x, &y);
scanf("%*s");
return 0;
}
...在我的系統給出了這樣的輸出:
0x400570
0x6009e4
0x7fff4c9b4e2c
......這些都是相關的線路從/proc/<pid>/maps
:
00400000-00401000 r-xp 00000000 00:1d 641656 /tmp/a.out
00600000-00601000 rw-p 00000000 00:1d 641656 /tmp/a.out
....
7fff4c996000-7fff4c9b7000 rw-p 00000000 00:00 0 [stack]
....
所以地址是:代碼,數據和數據。
很好的答案!爲了澄清其他讀者,'/ proc/
@rodrigo你能告訴我'%* s'的功能嗎? – phyrrus9
@ phyrrus9:它從標準輸入('%s')中讀取一個字符串,但隨後丟棄它而不保存在任何地方('*')。請注意,對'scanf()'的調用沒有任何額外的參數。我編寫了這個程序,爲了停止程序,直到按下ENTER鍵才能讀取文件'/ proc/
在x86平臺上,如果您可以查看它的地址空間,可以檢查用於爲函數設置堆棧的指令。它通常是:
push ebp
mov ebp, esp
我不看好x64平臺,但是我認爲這是類似的:
push rbp
mov rbp, rsp
This介紹C調用約定
但是請記住,編譯器優化可能會優化這些說明。如果你希望這個工作,你可能需要添加一個標誌來禁用這個優化。我相信對於GCC來說,-fno-omit-frame-pointer將會做到這一點。
除非代碼未經優化編譯,否則在可能的情況下可能會忽略幀指針。所以這不可靠。 –
哦,這是真的。我相信他可以禁用這一優化。我會編輯我的答案,謝謝 – chbaker0
一種可能的解決方案是通過解析nm utility的輸出來爲應用程序提取符號表。 nm包含符號類型的信息。 T(全局文本)類型的符號是函數。
這個解決方案的麻煩是你必須確保你的符號表匹配目標(特別是如果你打算使用它來提取地址,儘管與dlsym()結合使用會更安全)。我用來確保的方法是將生成過程的符號表生成部分作爲後處理步驟。
我想這是不是一個非常可靠的方法,但它可能工作:
以一個衆所周知的功能,如main()
地址,一個衆所周知的全局變量的地址。
現在取未知符號的地址並計算這個地址和另外兩個地址之間差值的絕對值。最小的差異將表明未知地址更接近函數或全局變量,這意味着它可能是另一個函數或另一個全局變量。
此方法假定編譯器/鏈接器將所有全局變量打包到特定內存塊,並將所有函數打包到另一個內存塊。例如,Microsoft編譯器將所有全局變量放入(虛擬內存中的較低地址)函數之前。
我假設你也不會願意來檢查局部變量,其地址不能由函數返回(一旦函數結束時,局部變量被丟失)
它可以通過組合dlsym()
和dladdr1()
來完成。
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
#include <stdio.h>
int symbolType(void *sym) {
ElfW(Sym) *pElfSym;
Dl_info i;
if (dladdr1(sym, &i, (void **)&pElfSym, RTLD_DL_SYMENT))
return ELF32_ST_TYPE(pElfSym->st_info);
return 0;
}
int main(int argc, char *argv[]) {
for (int i=1; i < argc; ++i) {
printf("Symbol [%s]: ", argv[i]);
void *mySym = dlsym(RTLD_DEFAULT, argv[i]);
// This will not work with symbols that have a 0 value, but that's not going to be very common
if (!mySym)
puts("not found!");
else {
int type = symbolType(mySym);
switch (type) {
case STT_FUNC: puts("Function"); break;
case STT_OBJECT: puts("Data"); break;
case STT_COMMON: puts("Common data"); break;
/* get all the other types from the elf.h header file */
default: printf("Dunno! [%d]\n", type);
}
}
}
return 0;
}
平臺相關的,但你可以通過尋找一些特殊功能開始代碼(蹦牀等) – 2013-11-20 22:01:26
或3.拉出來的信息與1.檢查地址(空間),或2脫身的DWARF調試信息(這是非平凡的) – nos
調試信息不適用於此應用程序;該應用程序非常大,以至於嘗試使用調試信息進行編譯會導致嘗試讀取它的任何內容崩潰(gdb) – dykeag