有没有办法在 GCC 或 cl.exe 的预处理和编译之间插入一个步骤?
Is there a way to insert a step between pre-processing and compilation for GCC or cl.exe?
我正在尝试检测大型 C 代码库。可以使用 GCC 和 MS cl.exe 构建代码库。代码库包含数百万行。我正在尝试对其进行代码覆盖。因为运行时环境比较特殊,所以我必须用特殊的方式进行检测。
我已经编写了一个可以进行检测的转换工具。但是它不能处理宏扩展,头文件包含等。换句话说,它必须在预处理阶段之后工作。
我可能没有足够的时间编写 C 预处理器。由于代码库是用 GCC/cl.exe 构建的,我想知道是否可以将我的转换步骤注入 GCC 或 cl.exe 编译过程。像这样:
GCC/cl.exe pre-process -> (My transformation) -> GCC/cl.exe compilation
这可能吗?
加 1
到目前为止,所有的答案都围绕着GCC。微软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 解析器识别。如果没有此类信息,就很难从经过检测的代码回溯到原始 c
源代码,正如 Jonathan Leffler 所指出的。
添加 4
检测并不容易。例如,根据 here(注意块 4),基于块的代码覆盖的块分离很棘手。
是的,有可能;不,这不是特别容易。
通常有一个单独的 C 预处理器,通常称为 cpp
。您可以 运行 在原始源上使用适当的参数,然后检测输出,然后使用完整的编译器完成编译——除了第二个预处理器阶段没有什么重要的事情要做,除非您的检测添加额外的material 需要进一步预处理。
类似地,编译器有一些选项(通常是 -E
and/or -P
)到 运行 预处理器——你可以 post-process 输出然后将结果再次输入编译器。
例如,给定一个起始文件 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) 规则下,您将向 .SUFFIXES
添加一个后缀 .pp
,然后提供将 .pp
编译为 .o
的规则(也许是.c
文件,并且可能直接指向一个可执行文件)。大多数时候,您希望自动移动中间 file1.i
文件,但偶尔可能需要保留它。
考虑是否创建一个 'compiler' shell 脚本,从 .pp
文件中一次性生成经过检测的 .c
文件。请注意,处理此类程序可能会变得相当复杂——但如果您能保持简单,就会非常有帮助。这样一个脚本的一个优点是你可以让它在 Windows 和 Unix 上呈现相同的外部(命令行)界面,并且只安排内部处理 GCC vs Clang vs MSVC vs 任何其他编译器。
您可以从 .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
指令。)你的转换器需要处理——可能是忽略——这样的行。您可能会忽略它们,但是 back-tracking 很难找到源代码。您可能需要添加一些 #line
指令来伪装代码的添加位置。您可能需要临时更改文件名,以便与您的检测相关的任何消息与与原始源代码相关的消息分开。
与GCC (specifically) you might consider writing your own GCC plugin (which will transform not textual files, but internal GCC representations). You might also consider libclang。但这并不容易(您可能需要花费数周或数月的时间)。
请注意:GCC 是一个复杂的软件(大约一千万行代码),您需要做大量工作才能了解其内部表示形式 (Generic/TREE & GIMPLE)。此外,插件 API 并不完全稳定,因此从 GCC 7 到 GCC 8(将于 spring 2018 年发布)时,您可能需要更改插件代码。
我在我的旧 GCC MELT documentation 页面上收集并写了一些 material(有点旧)关于 GCC 插件。
另一种可能是使用一些 other 预处理器(例如 GPP or m4) and generate some instrumented C or C++ code from some other files. Be aware that generating C or C++ code is a common habit (look into Qt moc, into bison ...)。
无论您采用何种方法,通常都不会很容易(除非您的特定代码库遵循一些一致的约定)。在某些情况下(只有十万行的小代码库)手动转换代码可能更简单。
顺便说一句,如果您使用编译器生成预处理文件,您可以(轻松地)删除发出的 #line
或 #
行,例如一些 grep -v '^#'
(但您可能还想保留它们 and/or 解析它们)。
请注意,自动检测代码比您想的要难....(主要问题是不要忽略 #
行)。
我正在尝试检测大型 C 代码库。可以使用 GCC 和 MS cl.exe 构建代码库。代码库包含数百万行。我正在尝试对其进行代码覆盖。因为运行时环境比较特殊,所以我必须用特殊的方式进行检测。
我已经编写了一个可以进行检测的转换工具。但是它不能处理宏扩展,头文件包含等。换句话说,它必须在预处理阶段之后工作。
我可能没有足够的时间编写 C 预处理器。由于代码库是用 GCC/cl.exe 构建的,我想知道是否可以将我的转换步骤注入 GCC 或 cl.exe 编译过程。像这样:
GCC/cl.exe pre-process -> (My transformation) -> GCC/cl.exe compilation
这可能吗?
加 1
到目前为止,所有的答案都围绕着GCC。微软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 解析器识别。如果没有此类信息,就很难从经过检测的代码回溯到原始 c
源代码,正如 Jonathan Leffler 所指出的。
添加 4
检测并不容易。例如,根据 here(注意块 4),基于块的代码覆盖的块分离很棘手。
是的,有可能;不,这不是特别容易。
通常有一个单独的 C 预处理器,通常称为 cpp
。您可以 运行 在原始源上使用适当的参数,然后检测输出,然后使用完整的编译器完成编译——除了第二个预处理器阶段没有什么重要的事情要做,除非您的检测添加额外的material 需要进一步预处理。
类似地,编译器有一些选项(通常是 -E
and/or -P
)到 运行 预处理器——你可以 post-process 输出然后将结果再次输入编译器。
例如,给定一个起始文件 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) 规则下,您将向 .SUFFIXES
添加一个后缀 .pp
,然后提供将 .pp
编译为 .o
的规则(也许是.c
文件,并且可能直接指向一个可执行文件)。大多数时候,您希望自动移动中间 file1.i
文件,但偶尔可能需要保留它。
考虑是否创建一个 'compiler' shell 脚本,从 .pp
文件中一次性生成经过检测的 .c
文件。请注意,处理此类程序可能会变得相当复杂——但如果您能保持简单,就会非常有帮助。这样一个脚本的一个优点是你可以让它在 Windows 和 Unix 上呈现相同的外部(命令行)界面,并且只安排内部处理 GCC vs Clang vs MSVC vs 任何其他编译器。
您可以从 .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
指令。)你的转换器需要处理——可能是忽略——这样的行。您可能会忽略它们,但是 back-tracking 很难找到源代码。您可能需要添加一些 #line
指令来伪装代码的添加位置。您可能需要临时更改文件名,以便与您的检测相关的任何消息与与原始源代码相关的消息分开。
与GCC (specifically) you might consider writing your own GCC plugin (which will transform not textual files, but internal GCC representations). You might also consider libclang。但这并不容易(您可能需要花费数周或数月的时间)。
请注意:GCC 是一个复杂的软件(大约一千万行代码),您需要做大量工作才能了解其内部表示形式 (Generic/TREE & GIMPLE)。此外,插件 API 并不完全稳定,因此从 GCC 7 到 GCC 8(将于 spring 2018 年发布)时,您可能需要更改插件代码。
我在我的旧 GCC MELT documentation 页面上收集并写了一些 material(有点旧)关于 GCC 插件。
另一种可能是使用一些 other 预处理器(例如 GPP or m4) and generate some instrumented C or C++ code from some other files. Be aware that generating C or C++ code is a common habit (look into Qt moc, into bison ...)。
无论您采用何种方法,通常都不会很容易(除非您的特定代码库遵循一些一致的约定)。在某些情况下(只有十万行的小代码库)手动转换代码可能更简单。
顺便说一句,如果您使用编译器生成预处理文件,您可以(轻松地)删除发出的 #line
或 #
行,例如一些 grep -v '^#'
(但您可能还想保留它们 and/or 解析它们)。
请注意,自动检测代码比您想的要难....(主要问题是不要忽略 #
行)。