静态分析C语言生成函数调用关系的利器——cflow(二)

news2024/12/23 23:45:59

大纲

  • 环境准备
  • 选择项目
  • 分析代码
    • 简单分析
    • 高级分析
      • 坑:不能显示main函数所有调用函数的调用栈
      • 坑2:重定义错误
      • 坑3:缺失编译时产生的文件
      • 坑4:缺失工程的头文件包含路径指定
      • 坑5:操作系统的坑
        • 只存在于windows操作系统上的文件
      • 坑6:大小顶问题
  • 最终展示
  • 参考资料

从最开始写《IT项目研发过程中的利器》这系列博文已经过去6年。最近几年,相关软件有所迭代,也出现很多其他有意思的“利器”。最近准备把这系列做个修补,同时新增其他语言(比如Golang和Python)品类的“利器”供大家把玩。
在《静态分析C语言生成函数调用关系的利器——cflow》一文中,我们介绍了如何使用cflow查看C语言代码中函数的调用关系。其中指出cflow(老版本)不能直接导出dot文件,需要使用其他工具来做辅助。但是最新版的cflow(v1.7)已经支持导出dot了
目前市面上介绍cflow的例子都比较简单(包括我写的那篇《静态分析C语言生成函数调用关系的利器——cflow》),比如函数都在一个文件里的,且调用关系也不复杂。但是现实工作中,我们的代码工程结构可能很复杂,导致看了类似博文的同学也不知道在实际生产中怎么应用。
于是本文就开始上难度,不仅要分析多层调用,还要结构复杂。这篇可能是全网目前能找到的最复杂使用cflow去做大型项目源码分析的例子了。

环境准备

我的测试环境是Ubuntu 12。

uname -a

Linux fangliang 5.15.0-91-generic #101-Ubuntu SMP Tue Nov 14 13:30:08 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

我们可以直接使用apt安装cflow。graphviz则是用于在最后一步将dot文件转换成图片,我们先提前将其安装好。

sudo apt-get install cflow
sudo apt-get install graphviz

选择项目

我挑选的分析项目是libevent,它是很多著名项目的底层库,比如Google Chrome、Memcached、Transmission。
我们可以从https://github.com/libevent/libevent.git获取其代码。它的代码结构还是蛮正规的。
在这里插入图片描述
它有很多代码都是在根目录,而我们这次要分析的是test目录下test-time.c文件中的main函数调用栈。

/*
 * Copyright (c) 2002-2007 Niels Provos <provos@citi.umich.edu>
 * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "event2/event-config.h"
#include "util-internal.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/time.h>
#endif
#include <errno.h>

#include "event2/event.h"
#include "event2/event_compat.h"
#include "event2/event_struct.h"

int called = 0;

#define NEVENT	20000

struct event *ev[NEVENT];

struct evutil_weakrand_state weakrand_state;

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;
	}
}

分析代码

简单分析

进入libevent目录,执行下面指令

cflow ./test/test-time.c --format=dot > test_time.dot
dot -T gif test_time.dot -o test_time.gif  

请添加图片描述
可以看到我们只能看到定义在test-time.c中的函数的调用栈,而像右下角的event_add则没有显示更深的调用栈。这个在现实工作中肯定是不能满足需求的。

高级分析

高级分析可以将main函数所有调用的函数的底层调用栈也会显示出来。但是整个过程还是蛮曲折的。本文主要讲解如何挖坑和填坑。

坑:不能显示main函数所有调用函数的调用栈

我们可以给cflow指定一个文件,分析出其调用栈。于是这个问题的根本原因是我们没有给它提供足够多的文件,比如上例中event_add的实现在哪个文件里是需要提供给cflow的。
最简单办法就是我们把所有的基础c文件(跟目录下的c文件)都给cflow来分析。

cflow  ./test/test-time.c ./*.c --format=dot > test_time.dot

但是会报一系列问题,我们挨个解决。
在这里插入图片描述

比较多的是XXX redefined,this is the place of previous definition,即重定义。

坑2:重定义错误

这类错误主要是符号类型错误,我们只要加入相关指令即可,修改如下

cflow ./test/test-time.c ./*.c \
 -i^s --brief \
 --define '__attribute__\(c\)'\
 --define '__typeof\(c\)=int' \
 --symbol __inline:=inline\
 --symbol __inline__:=inline\
 --symbol __const__:=const\
 --symbol __const:=const\
 --symbol __restrict:=restrict\
 --symbol __extension__:qualifier\
 --symbol __asm__:wrapper\
 --symbol __nonnull:wrapper\
 --symbol __wur:wrapper \
 --format=dot > test_time.dot

执行完会报这个错:找不到event2这个文件夹下的event-config.h。
在这里插入图片描述
经过寻找,这个文件并不存在。这说明该文件是在编译时生成的。

坑3:缺失编译时产生的文件

解决办法也就是编译libevent了。

mkdir build && cd build
cmake ..     # Default to Unix Makefiles.
make

这个时候event-config.h生成了,它的位置是libevent/build/include/event2/event-config.h。

find -name "event-config.h" 

./build/include/event2/event-config.h

然后我们要把这个目录加入到cflow的检索路径下,即加入

–include-dir=./build/include/

cflow ./test/test-time.c ./*.c \
 -i^s --brief \
 --define '__attribute__\(c\)'\
 --define '__typeof\(c\)=int' \
 --symbol __inline:=inline\
 --symbol __inline__:=inline\
 --symbol __const__:=const\
 --symbol __const:=const\
 --symbol __restrict:=restrict\
 --symbol __extension__:qualifier\
 --symbol __asm__:wrapper\
 --symbol __nonnull:wrapper\
 --symbol __wur:wrapper \
 --include-dir=./build/include/ \
 --format=dot > test_time.dot

但是这次又报下列错误,即部分文件找不到。
在这里插入图片描述

坑4:缺失工程的头文件包含路径指定

解决办法就是找到这些文件所在的目录,然后在指令中指定即可。

–include-dir=./include
–include-dir=./ \

cflow ./test/test-time.c ./*.c \
 -i^s --brief \
 --define '__attribute__\(c\)'\
 --define '__typeof\(c\)=int' \
 --symbol __inline:=inline\
 --symbol __inline__:=inline\
 --symbol __const__:=const\
 --symbol __const:=const\
 --symbol __restrict:=restrict\
 --symbol __extension__:qualifier\
 --symbol __asm__:wrapper\
 --symbol __nonnull:wrapper\
 --symbol __wur:wrapper \
 --include-dir=./build/include/ \
 --include-dir=./include \
 --include-dir=./ \
 --format=dot > test_time.dot

继续报错。这次错误主要集中在Window相关的文件上。
在这里插入图片描述

坑5:操作系统的坑

libevent是支持在多种操作系统上编译的,其中就包括windows。而我们这次是在linux上编译,而cflow是不区分系统的,于是我们需要手工解决这个问题。

只存在于windows操作系统上的文件

wepoll.c是只服务于windows操作系统。针对这个文件,我直接将其后缀修改成cw,这样就可以避开cflow的检索(因为我们在指令中指定了*.c)。
在这里插入图片描述
类似的文件还有event_iocp.c和buffer_iocp.c,我们都对它们进行后缀名修改处理。
这个时候只剩下下面这个错了。#error “Endianness not defined!”。
在这里插入图片描述

坑6:大小顶问题

这个问题一般不会遇到,因为操作系统基本确定了大小顶。但是cflow是代码分析工具,它不关心操作系统是什么。于是这个问题我们也要手工处理。先看下代码

/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
#if defined(LITTLE_ENDIAN)
#define blk0(i)                                                                \
    (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |                       \
                   (rol(block->l[i], 8) & 0x00FF00FF))
#elif defined(BIG_ENDIAN)
#define blk0(i) block->l[i]
#else
#error "Endianness not defined!"
#endif

解决方案也很简单,我们在cflow的指令中指定一个宏——LITTLE_ENDIAN。

-D LITTLE_ENDIAN

题外话,可能通过下面指令确定是大小顶。小顶是1,大顶是0。

echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 

修改后的指令是

cflow ./test/test-time.c ./*.c \
 -i^s --brief \
 --define '__attribute__\(c\)'\
 --define '__typeof\(c\)=int' \
 --symbol __inline:=inline\
 --symbol __inline__:=inline\
 --symbol __const__:=const\
 --symbol __const:=const\
 --symbol __restrict:=restrict\
 --symbol __extension__:qualifier\
 --symbol __asm__:wrapper\
 --symbol __nonnull:wrapper\
 --symbol __wur:wrapper \
 --include-dir=./build/include/ \
 --include-dir=./include \
 --include-dir=./ \
 -D LITTLE_ENDIAN \
 --format=dot > test_time.dot

最终展示

经过上面处理,就没有错误出现了。我们可以使用下面指令生成图片。

dot -T gif test_time.dot -o test_time.gif  

请添加图片描述
局部图如下
在这里插入图片描述

如果图片看不行,可以通过下面指令生成svg文件。

dot -T svg test_time.dot -o test_time.svg

可以从https://github.com/f304646673/tools/blob/main/cflow/images/test_time.svg下载查看。

参考资料

  • https://www.gnu.org/software/cflow/manual/cflow.html
  • https://libevent.org/
  • https://zh.wikipedia.org/wiki/Libevent

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1406531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大型语言模型 (LLM)全解读

一、大型语言模型&#xff08;Large Language Model&#xff09;定义 大型语言模型 是一种深度学习算法&#xff0c;可以执行各种自然语言处理 (NLP) 任务。 大型语言模型底层使用多个转换器模型&#xff0c; 底层转换器是一组神经网络。 大型语言模型是使用海量数据集进行训练…

服务器数据恢复—EVA存储raid5硬盘离线的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌EVA某型号存储&#xff0c;底层是RAID5阵列&#xff0c;划分了若干lun。 服务器故障&分析&#xff1a; 该存储设备中raid5阵列有两块硬盘掉线&#xff0c;存储中的lun丢失。 将故障服务器存储中的所有磁盘编号后取出&#xff0c;硬件…

web安全思维导图(白帽子)

web安全思维导图(白帽子) 客户端脚本安全 服务端应用安全 白帽子讲web安全 安全运营体系建设

外网ssh远程连接服务器

文章目录 外网ssh远程连接服务器一、前言二、配置流程1. 在服务器上安装[cpolar](https://www.cpolar.com/)客户端2. 查看版本号&#xff0c;有正常显示版本号即为安装成功3. token认证4. 简单穿透测试5. 向系统添加服务6. 启动cpolar服务7. 查看服务状态8. 登录后台&#xff0…

Unity之Cinemachine教程

前言 Cinemachine是Unity引擎的一个高级相机系统&#xff0c;旨在简化和改善游戏中的相机管理。Cinemachine提供了一组强大而灵活的工具&#xff0c;可用于创建令人印象深刻的视觉效果&#xff0c;使开发人员能够更轻松地掌控游戏中的摄像机行为。 主要功能和特性包括&#x…

JAVA算法—排序

目录 *冒泡排序&#xff1a; *选择排序&#xff1a; 插入排序&#xff1a; 快速排序&#xff1a; 总结&#xff1a; 以下全部以升序为例 *冒泡排序&#xff1a; 引用&#xff1a; 在完成升序排序时&#xff0c;最大的元素会经过一轮轮的遍历逐渐被交换到数列的末尾&#…

网络安全的使命:守护数字世界的稳定和信任

在数字化时代&#xff0c;网络安全的角色不仅仅是技术系统的守护者&#xff0c;更是数字社会的信任保卫者。网络安全的使命是保护、维护和巩固数字世界的稳定性、可靠性以及人们对互联网的信任。本文将深入探讨网络安全是如何履行这一使命的。 第一部分&#xff1a;信息资产的…

Flink编程——最小程序MiniProgram

最小程序MiniProgram 前面我们已经搭建起了Flink 的基础环境&#xff0c;这一节我们就在上一节的基础上&#xff0c;进行编写我们的第一个Flink 程序&#xff0c;开始之前我们先看一下一个完整的Flink 程序是什么样的 Flink 程序结构 为了演示Flink 程序结构&#xff0c;我们…

【TEE论文】Confidential Serverless Made Efficient with Plug-In Enclaves (2021 ISCA)

Confidential Serverless Made Efficient with Plug-In Enclaves ipads.se.sjtu.edu.cn/chinasys21/vedios/Confidential Serverless Made Efficient with Plug-In Enclaves-李明煜.mp4 问题&#xff1a;在SGX飞地中运行现有的无服务器应用程序&#xff0c;并观察到性能下降可…

【ASOC全解析(一)】ASOC架构简介和欲解决的问题

【ASOC全解析&#xff08;一&#xff09;】ASOC架构简介和欲解决的问题 一、什么是ASOC以及ASOC解决的三个问题二、ASOC的组成与功能解决第一个问题解决第二个问题解决第三个问题 三、ASOC基本工作原理 /********************************************************************…

使用Sobel算子把视频转换为只剩边缘部分

效果展示 原始视频 修改后的视频 整体代码 import cv2vc cv2.VideoCapture(test.mp4)if vc.isOpened():open, frame vc.read() else:open Falsei 0 while open:ret, frame vc.read()if frame is None:breakif ret True:i 1# 转换为灰度图gray cv2.cvtColor(frame, cv…

RabbitMQ进阶篇【理解➕应用】

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于RabbitMQ的相关操作吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 一.什么是交换机 1.概念释义 2.例…

聚观早报 | 苹果将开放第三方NFC支付;华为P70系列参数曝光

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 1月23日消息 苹果将开放第三方NFC支付 华为P70系列参数曝光 Celestiq已正式开始量产 岚图汽车官宣与华为合作 美…

LLM + RecSys 初体验(上)

最近在逛小红书的时候&#xff0c;发现了一个新的GPU算力租赁平台&#xff0c;与AutoDL和恒源云等平台类似。正巧&#xff0c;官网有活动&#xff0c;注册即送RTX 4090三个小时&#xff0c;CPU 5 小时。正巧最近在测试 LLM推荐系统的 OpenP5 平台&#xff0c;果断入手测试! 用…

力扣精选算法100道——x的平方根(二分查找专题)

x的平方根 首先看到这个题目的时候&#xff0c;我们需要对上一个二分查找专题的题目进行深度理解&#xff0c;然后了解模板&#xff0c;这题是完全利用的上一题的模板知识进行&#xff0c;如果直接看这个题目可能是有点懵的&#xff0c;因为我这里直接利用模板进行解题。力扣…

nexus清理docker私库

下载nexus-cli客户端&#xff0c;并非必须下载到服务器&#xff0c;理论上只要能访问到nexus就行 wget https://s3.eu-west-2.amazonaws.com/nexus-cli/1.0.0-beta/linux/nexus-cli这个链接下载不了了&#xff0c;末尾有资源下载&#xff0c;里面包含了完整包和脚本&#xff0…

Mysql主从复制、读写分离、分库分表

大数据处理 1.主从复制1.1 概述1.2 原理1.3 搭建 1.主从复制 主从复制 1.1 概述 主从复制指: 将主数据库的DDL和DML操作通过二进制日志传递到从库服务器中, 然后从库根据日志重新执行(也叫重做), 从而使从库和主库的数据保存同步 MYSQL支持一台主库同时向多台从库进行复制,…

Kafka-服务端-KafkaController

Broker能够处理来自KafkaController的LeaderAndIsrRequest、StopReplicaRequest、UpdateMetadataRequest等请求。 在Kafka集群的多个Broker中&#xff0c;有一个Broker会被选举为Controller Leader,负责管理整个集群中所有的分区和副本的状态。 例如&#xff1a;当某分区的Le…

解密.dataru被困的数据:如何应对.dataru勒索病毒威胁

导言&#xff1a; 在数字时代&#xff0c;勒索病毒如.dataru正在不断演变&#xff0c;威胁着用户的数据安全。本文91数据恢复将深入介绍.dataru勒索病毒的特点、被加密数据的恢复方法&#xff0c;以及预防措施&#xff0c;帮助您更好地了解并对抗这一数字威胁。当面对被勒索病…

Armv8-M的TrustZone技术之SAU寄存器总结

每个SAU寄存器是32位宽。下表显示了SAU寄存器概要。 5.1 SAU_CTRL register SAU_CTRL寄存器的特征如下图和表所示&#xff1a; 5.2 SAU_TYPE register 5.3 SAU_RNR register 5.4 SAU_RBAR register 5.5 SAU_RLAR register 5.6 SAU区域配置 当SAU启用时&#xff0c;未由已启用…