C语言中如何获取动态库的调用方信息
在Java中,很方便可以通过Thread的StackTrackElement来获取到方法的调用方信息。那么在C中如何获取类似的信息呢?
答案是使用backtrace系列函数。
#include <execinfo.h>
//backtrace() 返回调用程序的回溯(跟踪)信息,存储在由
//buffer指向的数组中。
int backtrace(void **buffer, int size);
//backtrace() 返回一组地址,backtrace_symbols()象征性地
//翻译这些地址为一个描述地址的字符串数组。
char **backtrace_symbols(void *const *buffer, int size);
//backtrace_symbols_fd()的参数buffer、size同backtrace_symbos(),
//不同之处在于,backtrace_symbols_fd()并不会返回一个
//字符串数组给调用者,而是将字符串写入fd对应文件。
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
示例:
准备三个源文件:libB.cpp
,libA.cpp
,main.cpp
。
关系如下:
-
libB.cpp
这个方法是最关键的方法,使用backtrace函数打印了调用关系。
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
int funB(){
printf("====>>>> in funB(), printing backtrace ...\n");
void *buffer[100];
char **strings;
int nptrs = backtrace(buffer, 100);
printf("\nbacktrace() returned %d addresses\n", nptrs);
strings = backtrace_symbols(buffer, nptrs);
int j;
for (j = 0; j < nptrs; j++) {
printf("%s\n", strings[j]);
}
free(strings);
return 0;
}
-
libA.cpp
这个方法只是为了验证调用关系的,没有什么实际的用处。
include <stdio.h>
int funB();
int funA(){
printf("====>>>> in funA(), calling funB\n");
funB();
return 0;
}
-
main.cpp
主函数,1:通过A间接调用B;2:直接调用B;
int funA();
int funB();
#include<stdio.h>
int main(){
printf("====>>>> in main(), calling funA()\n");
funA();
printf("\n\n---------------------------------------------------------\n\n\n");
printf("====>>>> in main(), calling funB()\n");
funB();
return 0;
}
编译运行
- 简单编译并运行
#!/bin/bash
# 清理
rm lib/*.so -f
rm main_* -f
# 编译
gcc libB.cpp -fPIC -shared -o lib/libB.so
gcc libA.cpp -fPIC -shared -o lib/libA.so -Wl,-rpath=./lib -L./lib -lB
gcc main.cpp -o main_plain -L./lib -Wl,--copy-dt-needed-entries -lA
# 运行
export LD_LIBRARY_PATH=./lib
./main_plain
输出为:
调用输出
- 使用
-O3
优化编译并运行
#!/bin/bash
# 清理
rm lib/*.so -f
rm main_* -f
# 编译
gcc libB.cpp -fPIC -shared -o lib/libB.so -O3
gcc libA.cpp -fPIC -shared -o lib/libA.so -Wl,-rpath=./lib -L./lib -lB -O3
gcc main.cpp -o main_o3 -L./lib -Wl,--copy-dt-needed-entries -lA -O3
# 运行
export LD_LIBRARY_PATH=./lib
./main_o3
输出为:
调用输出
- 使用
-O3
以及-fomit-frame-pointer
进行编译
-fomit-frame-pointer
Don't keep the frame pointer in a register for functions that don't need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register
available in many functions. It also makes debugging impossible on some machines.On some machines, such as the VAX, this flag has no effect, because the standard calling sequence automatically handles the frame pointer and nothing is saved by pretending it doesn't exist.
The machine-description macro "FRAME_POINTER_REQUIRED" controls whether a target machine supports this flag.The default setting (when not optimizing for size) for 32-bit GNU/Linux x86 and 32-bit Darwin x86 targets is -fomit-frame-pointer. You can configure GCC with the --enable-frame-pointer
configure option to change the default.Enabled at levels -O, -O2, -O3, -Os.
#!/bin/bash
# 清理
rm lib/*.so -f
rm main_* -f
# 编译
gcc -fomit-frame-pointer libB.cpp -fPIC -shared -o lib/libB.so -O3
gcc -fomit-frame-pointer libA.cpp -fPIC -shared -o lib/libA.so -Wl,-rpath=./lib -L./lib -lB -O3
gcc -fomit-frame-pointer main.cpp -o main_o3_fomit -L./lib -Wl,--copy-dt-needed-entries -lA -O3
# 运行
export LD_LIBRARY_PATH=./lib
./main_o3_fomit
输出为:
调用输出
结论:获取的信息还是挺清晰的
如果用来保障libB.so只能被认证过的程序调用,那么可以在libB中对上一层的程序进行校验。