大家好,欢迎来到IT知识分享网。
大纲
在《静态分析C语言生成函数调用关系的利器——cflow》和《静态分析C语言生成函数调用关系的利器——cflow(二)》中,我们介绍了使用cflow直接分析c语言源码导出调用栈的方法。在做实验的过程中,我一直在思考一个问题:cflow能解释C语言?看了下源码后,发现它的确有解析的模块。大家可以看下它的部分代码。
// parser.c typedef struct {
char *name; int type_end; int parmcnt; int line; enum storage storage; } Ident; void parse_declaration(Ident*, int); void parse_variable_declaration(Ident*, int); void parse_function_declaration(Ident*, int); …… static void print_token(TOKSTK *tokptr) {
switch (tokptr->type) {
case IDENTIFIER: case TYPE: case WORD: case MODIFIER: case STRUCT: case PARM_WRAPPER: case QUALIFIER: case OP: fprintf(stderr, "`%s'", tokptr->token); break; case LBRACE0: case LBRACE: fprintf(stderr, "`{'"); break; case RBRACE0: case RBRACE: fprintf(stderr, "`}'"); break; case EXTERN: fprintf(stderr, "`extern'"); break; case STATIC: fprintf(stderr, "`static'"); break; case TYPEDEF: fprintf(stderr, "`typedef'"); break; case STRING: fprintf(stderr, "\"%s\"", tokptr->token); break; default: fprintf(stderr, "`%c'", tokptr->type); } }
可以发现它是纯纯的文本解析。这就引发了我的一个担忧:如果C语言的编译器对文件的解释和cflow的解释器对同一份文件的结果解析不同怎么办?这个可能性还是存在的。
本文介绍的cally和egypt就很好的避开了这个问题,因为对文件的解析交给了GCC编译器。它们只是对编译器产生的中间结构化内容(Register transfer language)进行解释和整理,这个难度就比解析C语言源码要简单。产出的DOT (graph description language)文件交给dot程序生成调用栈的图。
我们还是以《静态分析C语言生成函数调用关系的利器——cflow(二)》中的libevent库为例。
准备工作
安装graphviz
sudo apt install graphviz
安装cally
cally就是一个python脚本,我们只要把工程代码下载下来即可。
git clone https://github.com/chaudron/cally.git
安装egypt
wget https://www.gson.org/egypt/download/egypt-1.11.tar.gz . tar xzf egypt-1.11.tar.gz rm egypt-1.11.tar.gz cd egypt-1.11 perl Makefile.PL make sudo make install cd -
简单分析
我们使用的是libevent库作为示例,就先将其依赖的库安装好。
sudo apt-get install libssl-dev
否则会报如下错误
./bufferevent_openssl.c:36:10: fatal error: openssl/ssl.h: No such file or directory
GCC产生RTL(Register transfer language)文件
libevent库中的test-time程序是通过链接编译完的libevent.a和libevent_core.a生成的。现在我们不能依赖原工程中的cmake来生成,需要自己编写编译指令。(还是需要先把整个工程编译一遍,具体见《静态分析C语言生成函数调用关系的利器——cflow(二)》中坑3:缺失编译时产生的文件)。
gcc ./test/test-time.c \ -I./build/include/ -I./include -I./ \ -L./build/lib/ -Wl,-Bstatic -levent -levent_core -Wl,-Bdynamic \ -o test-time-main
gcc ./test/test-time.c \ -I./build/include/ -I./include -I./ \ -L./build/lib/ -Wl,-Bstatic -levent -levent_core -Wl,-Bdynamic \ -fdump-rtl-expand
这样就产生了一个名字叫a-test-time.c.245r.expand的RTL文件。
cally
将上一步生成的文件拷贝到cally.py所在的目录,然后执行
python3 ./cally.py a-test-time.c.245r.expand \ | dot -Grankdir=LR -Tpng -o cally_test_time_call_graph.png
egypt
egypt a-test-time.c.245r.expand --include-external \ |dot -Grankdir=LR -Tpng -o egypt_test_time_call_graph.png
总结
我们看下test-time.c的部分源码。可以看到egypt的展现更加准确,因为它将time_cb和main进行了关联,而cally则没展现出来这层关系。
static int rand_int(int n) {
return evutil_weakrand_(&weakrand_state) % n; } static void time_cb(evutil_socket_t fd, short event, void *arg) {
struct timeval tv; int i, j; called++; if (called < 10*NEVENT) {
for (i = 0; i < 10; i++) {
j = rand_int(NEVENT); tv.tv_sec = 0; tv.tv_usec = rand_int(50000); if (tv.tv_usec % 2 || called < NEVENT) evtimer_add(ev[j], &tv); else evtimer_del(ev[j]); } } } int main(int argc, char **argv) {
struct event_base *base; struct timeval tv; int i; #ifdef _WIN32 WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(2, 2); (void) WSAStartup(wVersionRequested, &wsaData); #endif evutil_weakrand_seed_(&weakrand_state, 0); if (getenv("EVENT_DEBUG_LOGGING_ALL")) {
event_enable_debug_logging(EVENT_DBG_ALL); } base = event_base_new(); for (i = 0; i < NEVENT; i++) {
ev[i] = evtimer_new(base, time_cb, event_self_cbarg()); tv.tv_sec = 0; tv.tv_usec = rand_int(50000); evtimer_add(ev[i], &tv); } i = event_base_dispatch(base); printf("event_base_dispatch=%d, called=%d, EVENT=%d\n", i, called, NEVENT); if (i == 1 && called >= NEVENT) {
return EXIT_SUCCESS; } else {
return EXIT_FAILURE; } }
高级分析
gcc `find . -regextype posix-extended -regex '^./[^/]*\.c$' ! -name 'wepoll.c' ! -name 'win32select.c' ! -name 'evthread_win32.c' ! -name 'buffer_iocp.c' ! -name 'bufferevent_async.c' ! -name 'arc4random.c' ! -name 'event_iocp.c' ! -name 'bufferevent_mbedtls.c'` \ ./test/test-time.c \ -I./build/include/ -I./include -I./ \ -L./build/lib/ -lcrypto -lssl \ -DLITTLE_ENDIAN -D__clang__ \ -UD_WIN32 -UDMBEDTLS_SSL_RENEGOTIATION \ -fdump-rtl-expand
这样我们得到一堆RTL文件。这些文件我都将其拷贝到cally和egypt测试工程的sample目录下。
cally
python3 ./cally.py ../sample/*.expand --caller main | dot -Grankdir=LR -Tsvg -o cally_full_test_time_call_graph.svg
生成文件非常大,就不展示了。(见https://github.com/f/tools/tree/main/cally)
只展示event_add函数的调用栈。
egypt
egypt sample/*.expand --include-external --callees main | dot -Grankdir=LR -Tsvg -o egypt_full_test_time_call_graph.svg
生成文件非常大,就不展示了。(见https://github.com/f/tools/tree/main/egypt)
只展示event_add函数的调用栈。
总结
egypt比cally优秀,可以分析出更加复杂的调用关系。
参考资料
- https://www.gson.org/egypt/
- https://www.gson.org/egypt/egypt.html
- https://github.com/chaudron/cally
- https://ftp.gnu.org/gnu/cflow/
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/149380.html