将c代码的中间表示解析回c

时间:2015-02-28 10:46:29

标签: c++ c gcc clang llvm

我有一个用c编程语言编写的文件,并使用CIL进行预处理。现在有一个函数调用foo()在这个文件中。我想修改此文件中的c代码,以便对foo()的所有调用都在#ifdef防护下。我只想要保护呼叫而不是功能体,这样我就可以更好地控制呼叫。调用可以在if条件或while循环中。宏名称的规则:name以MACRO_开头,以原始代码中函数调用foo()的行号结束。

这是在工具中自动化的,我正在寻找一个可以解析c代码的编译器。

示例:

输入源文件

void foo(int x){
 // do something
}

int main(){
 int a;
 printf("doing something");
 foo(a);
 printf("doing something again");
 foo(a);
 return 0;
}

所需的输出

void foo(int x){
 // do something
}

int main(){
 int a;
 printf("doing something");
#ifdef MACRO_1
 foo(a);
#endif
 printf("doing something again");
#ifdef MACRO_2
 foo(a);
#endif
 return 0;
}

5 个答案:

答案 0 :(得分:1)

您可以自定义一些免费软件编译器。如果使用最近的GCC,您可以使用MELT(一种Lispy域特定语言来扩展gcc& g++等来自定义它。)。

您可能不想生成惯用的C代码。定制编译器(例如GCC - 或许Clang/LLVM ...)以获得所需的行为会更加简单。

转换一些内部编译器表示(例如,Gimple用于GCC)比输出C代码要简单一些。它可能仍然意味着数周的工作(因为C和C ++是非常复杂的语言,编译器具有相当复杂的内部表示)。

请注意,您的问题不考虑在foo内部调用 某些宏(或某些C ++模板扩展内部,或者甚至是某些内联函数)时会发生什么。这表明为什么处理编译器的中间表示是值得的。

顺便说一下,您可能会对coccinelle感兴趣,Clang是自由软件转换器的来源。

您原则上也可以使用LLVM(将您的C或C ++代码编译为llvm-cbe)然后{{3}}(实验性LLVM到C后端)

答案 1 :(得分:1)

对于SIMPLE源代码,显然你可以使用一个简单的脚本和一些你最喜欢的脚本语言(perl,php,awk,python等)的regexp。但是如果你开始决定支持if语句,成员函数调用等内部的函数调用[并希望最终得到实际编译为正确程序的输出代码],它确实变得越来越困难。

在这种情况下,您需要能够阅读(和#34;理解")C或C ++并生成一些中间形式的内容,然后您可以通过修改处理和重新发布源代码。无论从哪里开始,编写此类代码都远非易事。一种解决方案可能是使用Clang作为库。它具有从它的抽象语法树(AST)形式重写C或C ++代码的功能。此链接显示了此类重写器的示例:http://eli.thegreenplace.net/2012/06/08/basic-source-to-source-transformation-with-clang

如果你有以下代码,我不确定你想要做什么:

 if (x) 
    foo();
 bar();

显然,只需插入#if来调用foo();,只会在bar()为真时调用x,这可能不是您想要的。 。

答案 2 :(得分:0)

如果代码的结构是这样的,那么用foo调用来保护行只能被注释掉,并且不需要处理更复杂的表达式,例如bar(), foo(a),你可以像使用awk一样使用awk这样:

awk '/^\s*foo\(/ { print "#ifdef MACRO_" NR; print; print "#endif"; next } 1' filename.c

这将

/^\s*foo\(/ {                  # handle lines that begin with foo( preceded
                               # optionally by whitespaces specially by:
  print "#ifdef MACRO_" NR     # printing #ifdef MACRO_linenumber before
  print
  print "#endif"               # and #endif after the line.
  next
}
1                              # all other lines are printed unchanged.

请注意这是一个脏的,脏的黑客,不会尝试正确解析C代码。有很多方法可以解决这个问题,其中包括

if(something)
  foo(a);

foo(
  a
);

那就是

if(something)
#ifdef MACRO_foo
  foo(a);
#endif

#ifdef MACRO_foo
foo(
#endif
  a
);

分别。它可能适用于您的特定情况,但它不是一般的C代码处理工具。

答案 3 :(得分:0)

如果任务是在某些宏未定义(或定义)时从代码中排除调用foo(int),则以下方法可能会更好:

void foo(int x){
#ifdef MACRO_foo
 // do something
#endif
}

int main(){
 int a;
 printf("doing something");
 foo(a);
 printf("doing something again");
 foo(a);
 return 0;
}

因此,您可以在整个程序中排除函数体并保留函数调用。

答案 4 :(得分:0)

我认为你要求CIL做CIL不能做的事情。由于它对预处理的源代码进行操作,因此它不代表预处理器指令,因此您无法将它们放入CIL表示中。要再生。你可能能够破解CIL实现本身在遇到你的特殊情况时吐出你的指令,但是很难相信这样的hack会以任何方式通用。

你说你正在寻找一个可以解析c代码来实现这个目标的编译器"。如果你坚持"这个"特别是涉及CIL,我认为你运气不好;只有CIL才能做到这一点。

如果你放弃CIL并考虑使用不同的工具,那么我认为我有一个答案,就像CIL一样,可以在表示中保留预处理器指令(和/或允许)您可以根据自定义规则插入它们,并可以重新生成有效的C源代码文本。

该工具是我们的DMS Software Reengineering Toolkit,一个通用程序转换引擎及其C Front End。 DMS将C代码解析为AST,并将它们解析回有效的源代码,包括保留注释。 它可以用于在AST操作库上使用过程调用的混合进行源到源转换,和/或表面语法源到源重写。

DMS将捕获该AST中的预处理器指令(它们只是#34;更多语法!)在大多数情况下没有问题;有时你需要稍微(永久地)修改源代码以使预处理器指令变得可口。 DMS为C提供符号表,以及控制和数据流分析;这些将需要一些修改来处理预处理器条件。

为了匹配您在CIL上所做的事情,您可以要求DMS进行预处理;现在你最终获得了一个免费预处理器的AST。现在,DMS的现有符号表,CF和DF机器直接处理这种情况。 因此,您可以使用其他信息以不同于CIL的方式对AST执行复杂的操作。此外,您仍然可以修改AST以插入预处理程序指令,这似乎是您的关键问题。

要实现特定于呼叫站点的条件的特定效果,您可以利用DMS的surface syntax source-to-source transformation功能。 以下DMS转换可以执行您想要的操作:

rule wrap_function_call(i: Identifier, a:arguments ):statement -> statement
"  \i(\a); "
 ->
 "  #ifdef \generate_macro_name\(\i\)
      \i(\a);
    #endif
 "
 if want_to_wrap(i);

此规则查找与函数调用对应的任何语法树作为语句,并将其包装在条件中。 (如果函数调用是表达式的一部分,那么你没有说出你想要做什么;这种情况需要更多的转换,但也可以处理)。自定义辅助函数 generated_macro_name 使用与该函数名称匹配的标识符AST节点相关联的源位置信息来制造宏名称。转换以另一个自定义帮助函数 want_to_wrap 为条件,它检查每个匹配的名称以确定它是否应该被包装。

完成转换代码后,您可以调用DMS的prettyprinter机器将AST打印为源文本。