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

相关文章

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

GO语言短变量声明的实现示例

《GO语言短变量声明的实现示例》在Go语言中,短变量声明是一种简洁的变量声明方式,使用:=运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下... 目录基本语法功能特点与var的区别适用场景注意事项基本语法variableName := value功能特点1、自动类型推

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作

Springboot项目构建时各种依赖详细介绍与依赖关系说明详解

《Springboot项目构建时各种依赖详细介绍与依赖关系说明详解》SpringBoot通过spring-boot-dependencies统一依赖版本管理,spring-boot-starter-w... 目录一、spring-boot-dependencies1.简介2. 内容概览3.核心内容结构4.

C#使用Spire.XLS快速生成多表格Excel文件

《C#使用Spire.XLS快速生成多表格Excel文件》在日常开发中,我们经常需要将业务数据导出为结构清晰的Excel文件,本文将手把手教你使用Spire.XLS这个强大的.NET组件,只需几行C#... 目录一、Spire.XLS核心优势清单1.1 性能碾压:从3秒到0.5秒的质变1.2 批量操作的优雅

Go语言使用Gin处理路由参数和查询参数

《Go语言使用Gin处理路由参数和查询参数》在WebAPI开发中,处理路由参数(PathParameter)和查询参数(QueryParameter)是非常常见的需求,下面我们就来看看Go语言... 目录一、路由参数 vs 查询参数二、Gin 获取路由参数和查询参数三、示例代码四、运行与测试1. 测试编程路

Python使用python-pptx自动化操作和生成PPT

《Python使用python-pptx自动化操作和生成PPT》这篇文章主要为大家详细介绍了如何使用python-pptx库实现PPT自动化,并提供实用的代码示例和应用场景,感兴趣的小伙伴可以跟随小编... 目录使用python-pptx操作PPT文档安装python-pptx基础概念创建新的PPT文档查看