# 4.窥孔优化(以除法还原示例)

窥孔优化是反编译器一个很重要的还原技术。而且这个技术没有捷径可言，大部分情况，都是特定规则的集合。

这一章将以最经典的“整数除法”为例，展示编译器和反编译器的对于整数除法的优化与还原技术。

## 编译器对乘法与除法优化

对于很多算数表达式，为了提升效率编译器会进行等价的替换。

典型案例之一就是使用左移替代2的倍数的乘法：

```c
//源码示例，这里a为无符号整数
a = a * 4

//编译器优化后
a = a << 2
```

这一优化就是”窥孔优化“的一种，因为在现代CPU中除法和乘法运算是十分昂贵的，尤其是除法。（据说以前除法比加法慢50倍以上，比乘法慢10倍以上，直到Intel的Cannon Lake架构出现，Cannon Lake将64位整数除法的最大延时从96个周期降为了18个周期。这样除法就只比加法慢20倍，比乘法慢5倍）

实际的情况比想象中更加的复杂，单说加减乘除就有各种优化方法，下面是几个常见的典型除法类的优化示例：

1. 无符号除法优化 <https://godbolt.org/z/acm19_div3>

```c
//除法优化示例，这里edi为无符号整数
edi = edi / 3

//优化后编译成汇编 即 edi = (edi * 0xaaaaaaab) >> 33
mov eax, edi          ; eax = edi
mov edi, 2863311531   ; edi = 0xaaaaaaab
imul rax, rdi         ; rax = rax * 0xaaaaaaab
shr rax, 33           ; rax >>= 33
```

2.有符号除法优化

```c
//除法优化示例，这里edi为有符号整数
edi  = edi / 2;

//优化后编译成汇编 即 edi =  (edi + (edi >> 33)) >> 1
//其中所有的右移都为有符号右移
mov     eax, edi         ; eax = edi
shr     eax, 31          ; eax = eax >> 31
add     eax, edi         ; eax = eax + edi
sar     eax              ; eax = eax >> 1
```

3.无符号取余优化

```c
//取余优化示例，这里edi为无符号整数
edi % 3 == 0

//优化后编译成汇编
imul edi, edi, -1431655765    ; edi = edi * 0xaaaaaaab
cmp edi, 1431655765           ; compare with 0x55555555
setbe al                      ; return 1 if edi <= 0x55555555

```

在GCC中，有专门的规则实现这一类优化，叫做**match.pd**的文件专门描述这一类优化。他是一种类LISP规则语言，反正我是看不懂，可以看上面的注释，看看具体实现了哪种优化。

{% embed url="<https://github.com/gcc-mirror/gcc/blob/master/gcc/match.pd>" %}

<figure><img src="https://2050902342-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFIjush9EwySt5d8KqOoI%2Fuploads%2Ff1F4RESNPY2ocVeGRerx%2Fimage.png?alt=media&#x26;token=502848b2-4f82-412d-ab83-ebbec9cb731b" alt=""><figcaption></figcaption></figure>

## 反编译器的反窥孔优化：

为了将优化后的算式“反向优化”为人类可理解的算法，反编译器和编译器一样，都有这一类的匹配规则，称之为“反窥孔优化”。但是，编译器的窥孔优化不一定是绝对能还原的，所以

我这里以Ghidra为例（IDA不开源，有空再研究这玩意咋实现），Ghidra提供了一些列的规则处理这种特殊情况

<https://www.lockshaw.io/static/ghidra/decompiler/doc/classRule.html#details>

其中的除法优化示例：RuleDivOpt 类，他会将优化后乘法+右移的功能反编译成除法

公式为：

* `sub( (zext(V)*c)>>n, 0) => V / (2^n/(c-1))`
* `sub( (sext(V)*c)s>>n, 0) => V s/ (2^n/(c-1))`

看上面这个公式不太直观。还是举个例子后模拟ghidra的运算方法

```c
# 源码
unsigned divideByFive(unsigned x)
{
    return x / 5;
}
# 编译器优化后的汇编实现
mov     eax, edi
mov     edi, 3435973837
imul    rax, rdi
shr     rax, 34

```

参考汇编，代入公式 ( 2^n / (c-1) ) 其中 n=34, c=3435973837

```jsx
Python 3.10.6
>>> n=34
>>> c=3435973837
>>> ( 2**n // (c-1) )
5
```

换成整数，约为5，因此可以反编译优化为 x / 5

## 反窥孔优化简单汇总

下面简单汇总了一些反窥孔优化的项目，都是从ghidra抄的

其中 V、W、X代表变量

c、d代表常量

```jsx

-V => V * -1
V + -W ==> V - W
((V + c) + d) => V + (c+d)
((V * c) * d) => V * (c*d)
((V + (W + c)) + d) => (W + (c+d)) + V
V + 0xff... => V - 0x00...
(V << W) & d => (V & (W >> c)) << c
(V & c) & d => V & (c & d)
(V >> X) | (W >> X) => (V | W) >> X
!!V => V
!(V == W) => V != W
!(V < W) => W <= V
!(V <= W) => W < V
!(V != W) => V == W
V ^^ W => V != W
V * c + V * d => V * (c + d)
sub( (zext(V)*c)>>n, 0) => V / (2^n/(c-1))
sub( (sext(V)*c)s>>n, 0) => V s/ (2^n/(c-1))
sub(ext(V)*c,b)>>d + V -> sub( (ext(V)*(c+2^n))>>n,0)
W+((V-W)>>1) =>sub( (zext(V)*(c+2^n))>>(n+1), 0)
(V << c) << d => V << (c+d)
(V << c) >> c => V & 0xff
sub( concat(V,W), 0) => W
sub( concat(V,W), c) => V
sub( concat(V,W), c) => sub(V,c)
V * -1 == c => V == -c
V + c == d => V == (d-c)
~V == c => V == ~c
((V + c) + W) & 0xfff0 => (V + (c & 0xfff0)) + W
```

## 对抗：不透明谓词混淆

ref:<https://www.zhihu.com/question/46259412>

ref:<https://bbs.kanxue.com/thread-270019.htm>

不透明谓词是一套很有趣且实用的静态反编译对抗技术。

它和窥孔优化很像，只不过是反向。窥孔优化从复杂到简单，它从简单到复杂。

通过添加反编译器无法计算的条件进行混淆。

todo：介绍对抗不透明谓词的反编译技术

### reference

<https://medium.com/@prathamesh1615/adding-peephole-optimization-to-gcc-89c329dd27b3>

<https://fuzhe1989.github.io/2020/01/22/optimizations-in-cpp-compilers/>

<https://www.computer.org/csdl/proceedings-article/arith/2005/23660131/12OmNyQYt7U>

<https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide/>
