有没有办法在 GCC 或 cl.exe 的预处理和编译之间插入一个步骤?
Posted
技术标签:
【中文标题】有没有办法在 GCC 或 cl.exe 的预处理和编译之间插入一个步骤?【英文标题】:Is there a way to insert a step between pre-processing and compilation for GCC or cl.exe? 【发布时间】:2018-03-01 06:18:12 【问题描述】:我正在尝试检测大型 C 代码库。代码库可以用 GCC 和 MS cl.exe 构建。代码库包含数百万行。我正在尝试对其进行代码覆盖。因为运行时环境比较特殊,所以我不得不用特殊的方式来做instrumentation。
我编写了一个可以进行检测的转换工具。但它不能处理宏扩展、头文件包含等。换句话说,它必须在预处理阶段之后工作。
我可能没有足够的时间来编写 C 预处理器。由于代码库是用 GCC/cl.exe 构建的,我想知道是否可以将我的转换步骤注入到 GCC 或 cl.exe 编译过程中。像这样:
GCC/cl.exe pre-process -> (My transformation) -> GCC/cl.exe compilation
这可能吗?
添加 1
到目前为止,所有答案都围绕着 GCC。 Microsoft cl.exe 怎么样?我尝试了/P 选项,它将预处理结果发送到文件。但结果包含许多行,如下所示:
#line 1306 "<some file path>"
我正在尝试解决它。
好的,我解决了。同时指定/P
和/EP
可以抑制#line
指令。
添加 2
同时为cl.exe
指定"/P /EP"
的输出是一个没有#line
指令的*.i
文件。这是一个有效的 C 源文件。所以它可以直接输入到cl.exe
。我只是重命名原始 C 文件并使用*.i
文件进行检测,然后使用构建过程。
(注意避免通过/FI
包含一些头文件。这可能会导致一些重复的定义错误。应该删除它们,因为它们的内容已经包含在*.i
文件中。)
添加 3
我可以使用/P
开关。 #line
指令不会危及编译,并且可以被 C 解析器识别。正如 Jonathan Leffler 指出的那样,如果没有这些信息,就很难从检测代码回溯到原始 c
源代码。
加 4
仪器并不容易。例如,根据here(注意第 4 块),基于块的代码覆盖的块分离是棘手的。
【问题讨论】:
是的,有可能;不,这不是特别容易。通常有一个单独的 C 预处理器,通常称为cpp
。您可以在原始源上使用适当的参数运行它,然后检测输出,然后使用完整的编译器完成编译(除非您的检测添加了需要进一步预处理的额外材料,否则第二个预处理器不会做任何重要的事情. 编译器有一些选项(通常是-E
和/或-P
)来运行预处理器——您可以对输出进行后处理并将结果再次输入编译器。
使用 makefile 管理 Jonathan 提到的 3 个步骤:仅在预处理器模式下运行编译器/运行您的工具/编译
@Jonathan 在这里告诉您的基本内容是 gcc 只是一个“编译器驱动程序”,它评估命令行参数,然后让其他程序通过使用临时文件的特定参数调用它们来完成实际工作由早期阶段(或通过管道)产生。您可以单独调用它们,或者指示 gcc 停止并稍后在给定阶段恢复。
代码库有多大(百万行)?您可以花多少时间?
我的回答现在确实概括了 MSVC(它类似于 GCC)。
【参考方案1】:
是的,这是可能的;不,这不是特别容易。
通常有一个单独的 C 预处理器,通常称为 cpp
。您可以在原始源代码上使用适当的参数运行它,然后检测输出,然后使用完整的编译器完成编译 - 除了第二个预处理器阶段没有什么重要的事情要做,除非您的检测添加了需要进一步的额外材料预处理。
同样,编译器也有一些选项(通常是 -E
和/或 -P
)来仅运行预处理器 - 您可以对其中的输出进行后处理并将结果再次输入编译器。
例如,给定一个起始文件file1.pp
,您可以使用 GCC (gcc
):
gcc -E file1.pp …other-options-as-needed… -o file1.i
transformer file1.i file1.c
gcc -c file1.c …more-options-as-needed…
gcc -o instrumented-program file1.o …other-object-files-and-options…
我假设您的程序名为transformer
,它采用任意输入文件名 (file1.i
) 并写入任意输出文件 (file1.c
)。当然,您可以根据需要添加其他选项。
然后您在makefile
中调整构建过程以自动处理此问题。在旧 (POSIX) 规则下,您将添加一个后缀 .pp
到 .SUFFIXES
,然后提供将 .pp
编译到 .o
的规则(可能编译到 .c
文件,也可能直接编译到可执行文件)。大多数情况下,您希望自动移动中间 file1.i
文件,但您可能需要偶尔保留它。
考虑是否创建一个“编译器”shell 脚本,一举从.pp
文件生成检测的.c
文件。请注意,处理此类程序可能会变得非常复杂——但如果你能保持简单,它会非常有帮助。此类脚本的一个优点是您可以使其在 Windows 和 Unix 上呈现相同的外部(命令行)界面,并且只需安排内部处理 GCC、Clang、MSVC 和任何其他编译器。
您可以从.c
文件开始(而不是我假设的.pp
文件),但您需要一种系统化的方式来处理名称——您不会破坏原始的.c
文件。同样,使用 shell 脚本从 C 源代码创建一个经过检测的.o
(或.obj
)文件可能会更容易——它可以处理文件命名的复杂性。
请记住,#line
指令允许您为 C 编译器指定行号和文件名;它旨在协助预处理文件(例如,Yacc/Bison 的输出包含 #line
指令,用于识别代码在原始语法 (.y
) 文件中的来源)。
当 GCC 预处理文件时,其输出包含 #line
指令的变体。当我预处理一个名为 alloc3d19.c
的文件时,它的前 4 行:
/* SO 4885-6272 */
#include <stdlib.h>
#include <stdio.h>
然后 GCC 开始生成输出:
# 1 "alloc3d19.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "alloc3d19.c"
# 1 "/usr/include/stdlib.h" 1 3 4
# 61 "/usr/include/stdlib.h" 3 4
# 1 "/usr/include/Availability.h" 1 3 4
# 202 "/usr/include/Availability.h" 3 4
# 1 "/opt/gcc/v7.3.0/lib/gcc/x86_64-apple-darwin17.4.0/7.3.0/include-fixed/AvailabilityInternal.h" 1 3 4
# 203 "/usr/include/Availability.h" 2 3 4
# 62 "/usr/include/stdlib.h" 2 3 4
在#
后面没有line
但意思基本相同,除了文件名后面的数字。 (两个空白行是注释和源代码中的空白行;直到输出文件的第 1638 行才到达stdio.h
。使用 73 行源代码,输出为 2091 行,其中 292 行#line
指令。)您的转换器需要处理(可能通过忽略)这些行。您可能会忽略它们,但是很难回溯到源头。您可能需要添加一些 #line
指令来掩饰您的代码添加的位置。您可能需要临时更改文件名,以便与您的仪器相关的任何消息都与与原始源代码相关的消息分开。
【讨论】:
【参考方案2】:对于GCC(特别是),您可以考虑编写自己的GCC plugin(它不会转换文本文件,而是转换内部 GCC 表示)。你也可以考虑libclang。但这并不容易(您可能会花费数周或数月的时间)。
考虑到:GCC 是一个复杂的软件(大约一千万行代码),您需要大量工作来学习它的内部表示(Generic/TREE 和GIMPLE)。此外,插件 API 并不完全稳定,因此在从 GCC 7 升级到 GCC 8(将于 2018 年春季发布)时,您可能需要更改插件代码。
我在旧的GCC MELT documentation 页面上收集并写了一些关于 GCC 插件的资料(有点旧)。
另一种可能性可能是使用一些 other 预处理器(可能是GPP 或m4)并从其他一些文件生成一些经过检测的C 或C++ 代码。请注意,生成 C 或 C++ 代码是一种常见的习惯(查看 Qt moc,查看 bison 的示例......)。
无论您采用什么方法,这都不容易(除非您的特定代码库遵循一些一致的约定)。在某些情况下(只有十万行的小型代码库)手动转换代码可能更简单。
顺便说一句,如果您使用编译器生成预处理文件,您可以(轻松)删除发出的 #line
或 #
行,例如一些grep -v '^#'
(但您可能还想保留它们和/或解析它们)。
请注意,自动检测代码比您想象的要难......(主要问题是不要忽略 #
行)。
【讨论】:
是的,检测并不容易。对于基于块的代码覆盖,块分离是棘手的。如此处所述:blogs.msdn.microsoft.com/phuene/2007/05/03/…以上是关于有没有办法在 GCC 或 cl.exe 的预处理和编译之间插入一个步骤?的主要内容,如果未能解决你的问题,请参考以下文章
error MSB6006: “CL.exe”已退出,代码为X —— 的解决办法