循环pintool的通用模式

时间:2018-08-22 10:39:18

标签: loops assembly loop-unrolling

我试图找到一种通用的模式来运行pintool程序,以便它始终为我提供index的位置或含义,以及循环的值。

例如,这是某个循环的组合:

 40c374:    48 8b 55 e8             mov    -0x18(%rbp),%rdx
  40c378:   8b 45 fc                mov    -0x4(%rbp),%eax
  40c37b:   48 98                   cltq   
  40c37d:   0f b6 84 02 80 00 00    movzbl 0x80(%rdx,%rax,1),%eax
  40c384:   00 
  40c385:   84 c0                   test   %al,%al
  40c387:   74 2a                   je     40c3b3 <makeMaps_e+0x5b>
  40c389:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  40c38d:   8b 40 7c                mov    0x7c(%rax),%eax
  40c390:   89 c1                   mov    %eax,%ecx
  40c392:   48 8b 55 e8             mov    -0x18(%rbp),%rdx
  40c396:   8b 45 fc                mov    -0x4(%rbp),%eax
  40c399:   48 98                   cltq   
  40c39b:   88 8c 02 80 01 00 00    mov    %cl,0x180(%rdx,%rax,1)
  40c3a2:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  40c3a6:   8b 40 7c                mov    0x7c(%rax),%eax
  40c3a9:   8d 50 01                lea    0x1(%rax),%edx
  40c3ac:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  40c3b0:   89 50 7c                mov    %edx,0x7c(%rax)
  40c3b3:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
  40c3b7:   81 7d fc ff 00 00 00    cmpl   $0xff,-0x4(%rbp)
  40c3be:   7e b4                   jle    40c374 <makeMaps_e+0x1c>

现在我注意到Check CMD并不总是CMP ...

是否可以找出index的值和迭代总数?

2 个答案:

答案 0 :(得分:1)

  

有没有一种方法可以找出索引值和迭代总数?

否。

某些循环可能具有非索引逻辑,例如以非平凡的方式推进了两个+组件键,例如,链表遍历(实际上,您必须遍历树以甚至能够算出总数) ),其中主要终止条件为(leaf_ptr == nullptr),加上某些循环可能具有几种不同的终止条件(break;),那么您将为哪个迭代次数选择一个?在实际运行中会触发循环退出的最大次数还是较早的次数?

对于经典的for (i=...)循环,您可以在代码中手动找到它,但是“索引”几乎可以隐藏在任何东西中,没有通用模式。某些循环例如在机器码中从-10-1(而C ++源为for(i=0; i <10; ++i))通过使用简单的jnz使终止条件更便宜,其他循环可能使用地址比较而不是整数计数器(而C ++源代码正在使用数组索引)。

通常,每个字节可以包含256个不同的值。如果您有一段200字节长的代码(就像某些非平凡的循环实际上在做某件事),那么您就有256 200 个可能的机器代码。其中只有一小部分是有意义的机器代码,它构成了一个循环,但其中只有一小部分仍然有很多选择,包括代码的行为方式和操作方式。寻找一种通用的模式是乐观的。

在纯理论/抽象水平上,这听起来也非常接近原始的"halting problem",即您有一些代码,并且希望能够知道它是否会停止执行,何时执行(使用奖金请求,以便实际上能够知道它将停止多少次迭代)。因此,您可以研究为什么它实际上无法解决。

您仍然可以产生某种类似于最常见的循环pintool启发式算法,并且如果您要针对一些不太高级的机器代码(例如某些旧编译器的未优化C语言),则可能会发现出乎意料的高大量的循环,但这完全是“非通用”解决方案,即使是这种不可靠的启发式方法也需要大量的努力和思考。

答案 1 :(得分:1)

正如@ Ped7g所说,并不是所有循环都具有在第一次迭代运行之前就知道的跳闸计数。

但是对于这样的循环,通常只在循环内只有一个条件分支(通常在底部)编译它们。这样的启发式方法可能会检测到它们。

  • 找到循环分支。

    如果循环内有多个条件分支,而其中一个条件分支离开循环却不返回或跳过计数器增量,那么这不是简单的循环。

    如果可以验证它们没有针对您正在查看的(外部)循环修改循环计数器,则循环体内的嵌套循环或条件语句不是问题。

  • 找到它依赖的寄存器。例如cmp p, end_pointer / jnedec eax / jnz

    例如从循环分支向后执行第一条标志设置指令。 (如果编译器针对宏融合进行了优化,那么它们将是背靠背的,但不幸的是,在通用调优中并不总是启用该功能。)编译器通常避免使用部分标志的恶作剧(例如sub ecx, 1 / {{1 }} / dec eax),但是如果您想更加安全,请确保标志设置指令实际上设置了分支读取的标志。

  • 检查循环中是否只有这些寄存器中的一个实际上被修改了。

  • 检查修改后的寄存器(循环计数器)在每次迭代中仅被修改为恒定量

给出循环计数器的起始值和与之比较的终止值,就可以计算出行程数。如果这些检查中的任何一项失败,那么根据这种启发式方法,它就不是“简单循环”。

如果希望它在未优化的代码上运行,则必须跟踪循环计数器,直到存储/重新加载到内存。即使是优化的编译器,有时也会发出涉及循环计数器的多个指令,例如jnc,然后使用ecx一段时间,然后是lea ecx, [rdi+1]。通常,这看起来像是优化遗漏(循环中有更多指令,使编译器希望在循环外找到某些东西),但是它确实发生了。