第95章 基本块重排

95.1 PGO的优化方式

PGO是Profile-guided optimization的缩写,中文有“配置文件引导的优化”等译法。经PGO方式优化以后,程序中的某些基本块(basic block)(所谓基本块,指的是程序里顺序执行的语句序列。基本块由第一个语句构成入口,由最后一个语句构成出口。在执行程序时,不可从入口以外进入该基本块(被跳入),也不可从出口以外的地址跳出该基本块。)可能会被调整到可执行文件的任意位置。

很明显,函数中的程序代码存在执行频率的差异。例如,循环语句一类代码的执行频率必然很高,而错误报告、异常处理之类代码的执行频率较低。

在使用PGO时,编译器首先会生成一种可记录运行细节的特殊程序。而后,研发人员通过试运行的手段收集该程序的各项统计信息。最后,编译器根据这些统计信息对可执行文件进行调整和优化,把执行几率较小的基本块挪到其他地方。

在经PGO优化后的程序里,频繁执行的函数代码会被调整得更为紧凑。PGO优化了条件跳转的性能,提高了CPU分支预测的准确率。这些特性均有助于提升程序性能。

Oracle是由Intel C++编译器生成的程序。本文收录了Oracle中orageneric11.dll(Win32)的部分代码。

指令清单95.1 orageneric11.dll(Win32)

                  public _skgfsync
_skgfsync         proc near
 
; address 0x6030D86A
 
                  db       66h
                  nop
                  push     ebp
                  mov      ebp,  esp
                  mov      edx, [ebp+0Ch]
                  test     edx, edx
                  jz       short loc_6030D884
                  mov      eax, [edx+30h]
                  test     eax, 400h
                  jnz      __VInfreq__skgfsync  ; write to log
continue: 
                  mov      eax, [ebp+8]
                  mov      edx, [ebp+10h]
                  mov      dword ptr [eax], 0
                  lea      eax, [edx+0Fh]
                  and      eax, 0FFFFFFFCh
                  mov      ecx, [eax]
                  cmp      ecx, 45726963h
                  jnz      error               ; exit with error
                  mov      esp, ebp
                  pop      ebp
                  retn
_skgfsync         endp
 
...
 
; address 0x60B953F0
__VInfreq__skgfsync:
                  mov      eax, [edx]
                  test     eax, eax
                  jz       continue
                  mov      ecx, [ebp+10h]
                  push     ecx
                  mov      ecx, [ebp+8]
                  push     edx
                  push     ecx
                  push     offset ... ; "skgfsync(se=0x%x, ctx=0x%x, iov=0x%x)\n"
                  push     dword ptr [edx+4]
                  call     dword ptr [eax] ; write to log
                  add      esp, 14h
                  jmp      continue
error:
                  mov      edx, [ebp+8]
                  mov      dword ptr [edx], 69AAh ; 27050 "function called with invalid FIB/IOV structure"
                  mov      eax, [eax]
                  mov      [edx+4], eax
                  mov      dword ptr [edx+8], 0FA4h ; 4004
                  mov      esp, ebp
                  pop      ebp
                  retn
; END OF FUNCTION CHUNK FOR _skgfsync

上述两个基本块的地址相距9MB左右。

在这个文件中,所有的不常用函数都位于DLL文件的尾部。这部分不常用函数都被Intel C++编译器打上了VInfreq前缀。例如,我们看到函数尾部的部分代码用于记录log文件(大概用于错误、警告和异常处理)。因为Oracle开发人员在试运行期间收集统计信息时,它的执行概率较低(甚至没被执行过),所以它们被标注上了__VInfreq前缀。最终,这个日志基本块把控制流返回给位于“热门地区”的函数代码。

程序里另外一处“不常用”的区间是返回错误代码27050的基本块。

在Linux ELF环境下,Intel C++编译器会在ELF文件里通过.hot/.unlikely标记“热门”/“冷门”基本块。

以逆向工程的角度来看,这些信息可用来辨别函数的核心部分和异常处理部分。