Winapi 如何提高代码覆盖率工具的速度?

Winapi 如何提高代码覆盖率工具的速度?,winapi,testing,x86,code-coverage,Winapi,Testing,X86,Code Coverage,我已经编写了一个小的代码覆盖实用程序来记录x86可执行文件中命中的基本块。它运行时没有目标的源代码或调试符号,只需要一些它监视的基本块 然而,它正在成为我的应用程序中的瓶颈,它涉及到单个可执行映像的重复覆盖快照 它已经经历了几个阶段,因为我试图加快它。我一开始只是在每个基本块的开头放一个INT3,作为调试器附加,并记录命中。然后,我尝试通过在计数器中修补任何大于5字节(JMP REL32大小)的块来提高性能。我在进程内存空间中编写了一个小存根('mov[blah],1/jmp backToThe

我已经编写了一个小的代码覆盖实用程序来记录x86可执行文件中命中的基本块。它运行时没有目标的源代码或调试符号,只需要一些它监视的基本块

然而,它正在成为我的应用程序中的瓶颈,它涉及到单个可执行映像的重复覆盖快照

它已经经历了几个阶段,因为我试图加快它。我一开始只是在每个基本块的开头放一个INT3,作为调试器附加,并记录命中。然后,我尝试通过在计数器中修补任何大于5字节(JMP REL32大小)的块来提高性能。我在进程内存空间中编写了一个小存根('mov[blah],1/jmp backToTheBasicBlockWeCameFrom'),并为其修补了一个jmp。这大大加快了速度,因为没有异常,也没有调试器中断,但我希望加快速度

我想到了以下其中一个:

1) 使用我的补丁计数器预先插入目标二进制文件(目前我在运行时这样做)。我可以在PE中创建一个新的部分,在其中抛出我的计数器,修补我需要的所有钩子,然后在每次执行后用我的调试器从同一部分读取数据。这将使我获得一些速度(据我估计大约16%),但在较小的块中仍然需要那些讨厌的INT3,这将严重影响性能

2) 检测二进制文件,使其包含自己未处理的ExceptionFilter,并结合上述内容处理自己的int3。这意味着在每个int3上都没有从调试对象到我的覆盖工具的进程切换,但仍然会引发断点异常和随后的内核转换——我认为这实际上不会获得太多性能,对吗

3) 尝试使用Intel的硬件分支评测指令做一些聪明的事情。这听起来非常棒,但我不清楚该怎么做——这在windows用户模式应用程序中可能吗?如果很简单的话,我可能会写一个内核模式驱动程序,但我不是一个内核程序员(我涉猎了一点),可能会给自己带来很多麻烦。是否有其他项目使用此方法?我看到Linux内核可以监控内核本身,这让我觉得监控一个特定的用户模式应用程序会很困难

4) 使用现成的应用程序。它需要在没有任何源代码或调试符号的情况下工作,可以编写脚本(这样我就可以成批运行),最好是免费的(我很吝啬)。但是,付费工具并不是不可能的(如果我可以在工具上花费更少,并提高性能,以避免购买新硬件,这将是一个很好的理由)

5) 还有别的。我在Windows XP上的VMWare上运行,在相当旧的硬件(奔腾4-ish)上运行-我有什么遗漏,或者我应该阅读的线索吗?我可以将JMP REL32降到小于5字节(并且捕获更小的块而不需要int3)吗


谢谢。

如果你坚持检测二进制文件,最快的覆盖范围就是5字节跳出跳回技巧。(您将介绍二进制检测工具的标准基础。)

INT3解决方案将始终包含一个陷阱。是的,您可以在您的空间而不是调试器空间中处理陷阱,这将加快它的速度,但它永远不会接近跳出/返回补丁的竞争对手。如果您正在检测的函数恰好小于5个字节(例如,“inc eax/ret”),那么您可能需要它作为备份,因为您没有5个字节可以修补

要稍微优化一些东西,您可能需要检查修补过的代码。未经审查,原代码:

         instrn 1
         instrn 2
         instrn N
  next:
经过修补,大体上如下所示:

         jmp patch
         xxx 
  next:
通常必须有一个补丁:

   patch: pushf
          inc   count
          popf
          instrn1
          instrn2
          instrnN
          jmp   back
如果只需要覆盖率,则不需要增加,也不需要保存标志:

   patch: mov    byte ptr covered,1
          instrn1
          instrn2
          instrnN
          jmp   back
您应该使用字节而不是单词来减小补丁大小。您应该在缓存线上对齐修补程序,这样处理器就不会有两条缓存线来执行修补程序

如果您坚持计数,您可以分析instrn1/2/N,看看它们是否关心“inc”愚弄的标志,如果需要,只关心pushf/popf,或者您可以在补丁中不关心的两条指令之间插入增量。您必须在一定程度上分析这些问题,以处理诸如instnret等复杂问题;您可以生成一个更好的补丁(例如,不要“jmp back”)

您可能会发现使用add count,1inc count更快,因为这样可以避免部分条件代码更新和随后的管道联锁。这将稍微影响您的cc影响分析,因为inc不设置进位,而add设置进位

另一种可能性是PC采样。根本不要插入代码;只需定期中断线程并获取样本PC值。如果您知道基本块的位置,则基本块中任何位置的PC样本都是整个块执行的证据。这不一定能提供精确的覆盖率数据(您可能会错过关键的PC值),但开销相当低

如果您愿意修补源代码,您可以做得更好:只需在第i个基本块的开头插入“covered[i]=true;”,并让编译器处理所有各种优化。不需要补丁。这其中最酷的部分是,如果在嵌套循环中有基本块,并且像这样插入源探测,编译器会注意到探测赋值相对于循环是幂等的,并将探测从循环中取出。维奥拉,环路内的零探头。你还想要什么?

投票以“不具建设性”结束?我很惊讶有这么多人投票支持完美的结束