循环 pintool 的通用模式

Generic pattern for a loop- pintool

我正在尝试找到一个能够 运行 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   [=10=]x1,-0x4(%rbp)
  40c3b7:   81 7d fc ff 00 00 00    cmpl   [=10=]xff,-0x4(%rbp)
  40c3be:   7e b4                   jle    40c374 <makeMaps_e+0x1c>

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

有没有办法找出 index 值和总迭代次数?

Is there a way of finding out the index value and total number of iterations?

没有

某些循环可能具有非索引逻辑,例如以非平凡的方式推进两个以上的组件键,例如链表遍历(您必须实际遍历树才能告诉total count),其中主要终止条件是(leaf_ptr == nullptr),加上一些循环可能有几个不同的终止条件(break;),那么你会选择哪个作为你的迭代次数?实际触发循环退出的最大值,或者更早的那个 运行?

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

一般来说,每个字节可以包含256个不同的值。如果你有一段 200 字节长的代码(这就像一些非平凡的循环实际上在做某事),你有 256200 种可能的机器代码。其中只有一小部分是有意义的机器代码构造一个循环,但其中只有一小部分仍然是非常多的选项,代码如何运行和操作。寻找一种乐观的通用模式。

同样在纯 theory/abstract 级别上,这听起来非常接近原始 "halting problem",即您有一些代码,并且您希望能够判断它是否会停止,何时停止执行(有一个额外的请求,可以实际告诉它在停止之前将执行多少次迭代)。所以你可以研究一下,为什么这实际上无法解决。

你仍然可以产生某种循环 pintool 启发式,它可能像最常见的那样捕获,如果你将针对一些不太高级的机器代码(比如一些旧编译器的未优化的 C),你可能会检测到令人惊讶的大量循环,但这完全是 "non-generic" 解决方案,即使是这种不可靠的启发式方法也需要大量的努力和思考。

正如@Ped7g 所说,并非所有循环都具有在第一次迭代运行之前已知的行程计数。

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

  • 找到循环分支。

    如果循环内部有多个条件分支,其中一个离开循环不再返回,或者跳过计数器递增,则这不是一个简单的循环。

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

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

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

  • 检查这些寄存器中只有一个在循环中实际被修改。

  • 检查修改后的寄存器(循环计数器)是否每次迭代只修改一个常数

给定循环计数器的起始值和与之比较的结束值,您可以计算行程计数。如果这些检查中的任何一个失败,根据这个启发式,它不是 "simple loop"。

如果你想让它在未优化的代码上工作,你必须通过 store/reload 跟随循环计数器到内存。即使是优化编译器有时也会发出多条指令来触及循环计数器,例如 lea ecx, [rdi+1] 然后使用 ecx 一段时间,然后 mov edi, ecx。通常这看起来像是一个优化失误(循环中的更多指令在编译器需要的地方有循环外的东西)但它确实发生了。