静态分析C语言生成函数调用关系的利器——cally和egypt

静态分析C语言生成函数调用关系的利器——cally和egypt本文探讨了使用 cally 和 egypt 工具解析 C 语言源码 通过 GCC 产生的 Registertran RTL 文件来避免与编译器解释差异的问题 实现了更准确的函数调用关系分

大家好,欢迎来到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

(0)
上一篇 2025-03-24 15:00
下一篇 2025-03-24 15:05

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信