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标记“热门”/“冷门”基本块。
以逆向工程的角度来看,这些信息可用来辨别函数的核心部分和异常处理部分。