【问题标题】:Detecting recursion in a C file with Python使用 Python 检测 C 文件中的递归
【发布时间】:2018-08-16 22:54:50
【问题描述】:

我需要在相当大的 (5-15,000) 组 C(不是 C++)文件中检测直接和间接递归。

文件已经过预处理。

出于安全原因,该代码非常“老派”,因此其中没有函数指针之类的花哨的东西,只有传递变量的函数和一些执行相同操作的函数宏。

检测递归最自然的方法是制作有向调用图,将每个函数视为一个节点,其边缘连接到它调用的所有其他函数。如果图有任何循环,那么我们就有递归。

查找函数调用的正则表达式很简单,但我还需要知道调用的是哪个函数。

PyCParser 很好,但它抱怨很多事情,例如未定义的变量或未定义源类型或在不同文件中定义的 typedef,这与我的用例完全无关。该项目使用自定义的依赖管理系统,所以一些包含和这些是自动添加的,所以我需要 PyCParser 不关心 anything 除了FuncCallFuncDef 节点,我不认为有一种方法可以限制解析过程本身。

我宁愿不实现解析器,因为我没有时间学习如何在 python 中执行此操作然后实现解决方案。

回到问题,我将如何解析 C 文件中的函数?基本上得到一个以字符串(文件中定义的函数的名称)作为键,字符串列表(每个函数调用的函数)作为值的字典?正则表达式似乎是最自然的解决方案。

遗憾的是,使用 python 不是可选的。

【问题讨论】:

  • 您要查找的输出是什么?将写入stdout 之类的函数Warning: <function_name> is recursive !
  • 创建一个图表,指示哪个函数调用了哪些函数并搜索循环。
  • 在不知道哪些标识符是类型的情况下无法解析 C,因此您的“自定义依赖管理系统”是一个硬块。
  • 下次我会阅读您的帖子:) 当文件完全预处理时,pycparser 工作正常。我记得它试图预处理文件,但应该跳过那部分,因为你必须提供所有包含位置......最好提供一个已经预处理的文件,在这种情况下,你会得到所有信息。我听说clang 也很好。似乎有一个 python 绑定。
  • 要详细说明我的评论,请考虑A ( B ); ( C ) ( D );,这是多少个函数调用?

标签: python c regex parsing


【解决方案1】:

为什么不在编译后的代码上使用objdump,然后解析生成的程序集来构建图表?

test1.c 文件:

extern void test2();

void test1()
{
   test2();
}

test2.c 文件:

extern void test1();

void test2()
{
   test1();
}


int main()
{
   test2();
}

现在构建它:

gcc -g test1.c test2.c -o myprog

现在反汇编

objdump -d myprog > myprog.asm

使用几个简单的正则表达式查找所有函数调用,同时记住您所在的上下文。反汇编示例向您展示了它应该是多么容易:

00401630 <_test1>:
  401630:   55                      push   %ebp
  401631:   89 e5                   mov    %esp,%ebp
  401633:   83 ec 08                sub    $0x8,%esp
  401636:   e8 05 00 00 00          call   401640 <_test2>
  40163b:   c9                      leave  
  40163c:   c3                      ret    
  40163d:   90                      nop
  40163e:   90                      nop
  40163f:   90                      nop

00401640 <_test2>:
  401640:   55                      push   %ebp
  401641:   89 e5                   mov    %esp,%ebp
  401643:   83 ec 08                sub    $0x8,%esp
  401646:   e8 e5 ff ff ff          call   401630 <_test1>
  40164b:   c9                      leave  
  40164c:   c3                      ret    

然后使用 python 对您的反汇编进行后处理并构建一个函数字典=>调用:

import re
import collections

calldict = collections.defaultdict(set)

callre = re.compile(".*\scall\s+.*<(.*)>")
funcre = re.compile("[0-9a-f]+\s<(.*)>:")

current_function = ""

with open("myprog.asm") as f:
    for l in f:
        m = funcre.match(l)
        if m:
            current_function = m.group(1)
        else:
            m = callre.search(l)
            if m:
                called = m.group(1)
                calldict[current_function].add(called)

我没有编写完整的图形搜索,但您可以使用以下简单代码检测“乒乓”递归:

for function,called_set in calldict.items():
    for called in called_set:
        callset = calldict.get(called)
        if callset and function in callset:
            print(function,called)

这给了我:

_test2 _test1
_test1 _test2

callcatcher 也使用此符号/asm 分析技术来检测未使用的 C 函数(这也可以通过检查不在任何集合中的键来非常容易地完成,并对编译器符号进行一些过滤)

【讨论】:

  • OP 需要使用 Python。我引用:Using python is not optional sadly..
  • 当然后处理必须在 python 中完成 :)
  • 我很欣赏你解决这个问题的方法。 :)(尽管我们不知道文件是否可以通过这种方式进行预处理)
  • 谢谢。将编译器工具和 python 结合起来通常非常有效。
  • 一开始我会尝试一些不同的东西。手动解析 c 文件,因为它是“基本 C 代码”。但这可能需要一些时间来处理超过 15.000 个 c 文件以读取 OP 所说的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多