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

2024-01-25 06:52

本文主要是介绍静态分析C语言生成函数调用关系的利器——cally和egypt,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大纲

  • 准备工作
  • 安装graphviz
  • 安装cally
  • 安装egypt
  • 简单分析
    • GCC产生RTL(Register transfer language)文件
    • cally
    • egypt
    • 总结
  • 高级分析
    • cally
    • egypt
  • 总结
  • 参考资料

在《静态分析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 -

简单分析

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

上面的脚本可以正确将test-time.c编译成可执行文件。
现在我们只要让它产出RTL文件即可。

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.pn
g

请添加图片描述

总结

我们看下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);elseevtimer_del(ev[j]);}}
}int
main(int argc, char **argv)
{struct event_base *base;struct timeval tv;int i;#ifdef _WIN32WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(2, 2);(void) WSAStartup(wVersionRequested, &wsaData);
#endifevutil_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;}
}

我们看到上面图片并没有展现诸如event_add这类外部函数的底层调用栈。这是因为这些函数是作为静态库提供给test-time进行链接的。且我们并没有生成它们的RTL文件,所以不能分析出完整的调用关系。
为了展现更加具体的调用关系,我们将进行一些改造,以获得更多RTL进行分析。

高级分析

上面问题的根源在于test-time编译依赖于静态库,我们首先要解决这个问题,就是要手撸一条可用的编译指令。
这个实验的主要难点也是在这个指令的正确书写,中间修正的过程我就不表了,直接贴出结果。

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/f304646673/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/f304646673/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/

这篇关于静态分析C语言生成函数调用关系的利器——cally和egypt的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/642435

相关文章

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

浅析如何使用Swagger生成带权限控制的API文档

《浅析如何使用Swagger生成带权限控制的API文档》当涉及到权限控制时,如何生成既安全又详细的API文档就成了一个关键问题,所以这篇文章小编就来和大家好好聊聊如何用Swagger来生成带有... 目录准备工作配置 Swagger权限控制给 API 加上权限注解查看文档注意事项在咱们的开发工作里,API

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Java使用POI-TL和JFreeChart动态生成Word报告

《Java使用POI-TL和JFreeChart动态生成Word报告》本文介绍了使用POI-TL和JFreeChart生成包含动态数据和图表的Word报告的方法,并分享了实际开发中的踩坑经验,通过代码... 目录前言一、需求背景二、方案分析三、 POI-TL + JFreeChart 实现3.1 Maven

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

MYSQL关联关系查询方式

《MYSQL关联关系查询方式》文章详细介绍了MySQL中如何使用内连接和左外连接进行表的关联查询,并展示了如何选择列和使用别名,文章还提供了一些关于查询优化的建议,并鼓励读者参考和支持脚本之家... 目录mysql关联关系查询关联关系查询这个查询做了以下几件事MySQL自关联查询总结MYSQL关联关系查询