1. 什么是回调函数?
其实回调函数就是一种利用函数指针进行函数调用的过程。
一般使用场景是用户使用底层API时,需要在API运行时再使用自己的函数,这时就会用到回调函数,即将自己的函数作为函数指针参数调用底层API,API运行中根据函数指针回调用户自己的函数。
比如:用户利用一个加密机制的API进行加密,但是用户想自定义加密算法,这时就可以用回调函数,把自定义加密算法封装成函数,函数指针作为参数传入API,用户调用API,API运行时再根据指针回调用户的回调函数。
2.以上是引子,很多文章都对回调函数有更清晰深刻的解读,有兴趣的可以搜索查看,本文重在进行CPU上汇编指令分析,下面以一个简单的回调函数demo为例进行运行指令的分析。
3.C语言源码如下:
14 bool test_func(int a, int b);
15 void test_callback(int c, bool (t_func)(int a, int b), int d);
16
17 int main(void)
18 {
19 int aa = 1;
20 int bb = 2;
21 test_callback(aa, test_func, bb);
...
212 bool test_func(int a, int b){
213 return true;
214 }
215
216 void test_callback(int c, bool (t_func)(int a, int b), int d){
217 t_func(c,d);
218 }
4. 到bochs虚拟机上单步调试运行,结果如下:
(0) [0x00000000a02c] 0010:c000a02c (unk. ctxt): mov dword ptr ss:[ebp-76], 0x00000001 ; c745b401000000
<bochs:17> n
Next at t=7979555
(0) [0x00000000a033] 0010:c000a033 (unk. ctxt): mov dword ptr ss:[ebp-72], 0x00000002 ; c745b802000000
<bochs:18> n
Next at t=7979556
(0) [0x00000000a03a] 0010:c000a03a (unk. ctxt): sub esp, 0x00000004 ; 83ec04
<bochs:19> n
Next at t=7979557
(0) [0x00000000a03d] 0010:c000a03d (unk. ctxt): push dword ptr ss:[ebp-72] ; ff75b8
<bochs:20> n
Next at t=7979558
(0) [0x00000000a040] 0010:c000a040 (unk. ctxt): lea eax, ds:[ebx-28057] ; 8d836792ffff
<bochs:21> r
eax: 0x00000000 0
ebx: 0xc0011000 -1073672192
ecx: 0xc0007004 -1073713148
edx: 0x00000020 32
esp: 0xc0006f98 -1073713256
ebp: 0xc0006ff8 -1073713160
esi: 0xc0100000 -1072693248
edi: 0x00000000 0
eip: 0xc000a040
eflags 0x00000096: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf AF PF cf
<bochs:22> n
Next at t=7979559
(0) [0x00000000a046] 0010:c000a046 (unk. ctxt): push eax ; 50
<bochs:23> r
eax: 0xc000a267 -1073700249
ebx: 0xc0011000 -1073672192
ecx: 0xc0007004 -1073713148
edx: 0x00000020 32
esp: 0xc0006f98 -1073713256
ebp: 0xc0006ff8 -1073713160
esi: 0xc0100000 -1072693248
edi: 0x00000000 0
eip: 0xc000a046
eflags 0x00000096: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf AF PF cf
<bochs:24> x 0xa267
[bochs]:
0x0000a267 <bogus+ 0>: 0xfb1e0ff3
<bochs:25> n
Next at t=7979560
(0) [0x00000000a047] 0010:c000a047 (unk. ctxt): push dword ptr ss:[ebp-76] ; ff75b4
<bochs:26> n
Next at t=7979561
(0) [0x00000000a04a] 0010:c000a04a (unk. ctxt): call .+560 (0xc000a27f) ; e830020000
<bochs:27> s
Next at t=7979562
(0) [0x00000000a27f] 0010:c000a27f (unk. ctxt): nop ; f30f1efb
<bochs:28> n
Next at t=7979563
(0) [0x00000000a283] 0010:c000a283 (unk. ctxt): push ebp ; 55
<bochs:29> n
Next at t=7979564
(0) [0x00000000a284] 0010:c000a284 (unk. ctxt): mov ebp, esp ; 89e5
<bochs:30> n
Next at t=7979565
(0) [0x00000000a286] 0010:c000a286 (unk. ctxt): sub esp, 0x00000008 ; 83ec08
<bochs:31> n
Next at t=7979566
(0) [0x00000000a289] 0010:c000a289 (unk. ctxt): call .+25 (0xc000a2a7) ; e819000000
<bochs:32> n
Next at t=7979569
(0) [0x00000000a28e] 0010:c000a28e (unk. ctxt): add eax, 0x00006d72 ; 05726d0000
<bochs:33> n
Next at t=7979570
(0) [0x00000000a293] 0010:c000a293 (unk. ctxt): sub esp, 0x00000008 ; 83ec08
<bochs:34> n
Next at t=7979571
(0) [0x00000000a296] 0010:c000a296 (unk. ctxt): push dword ptr ss:[ebp+16] ; ff7510
<bochs:35> n
Next at t=7979572
(0) [0x00000000a299] 0010:c000a299 (unk. ctxt): push dword ptr ss:[ebp+8] ; ff7508
<bochs:36> n
Next at t=7979573
(0) [0x00000000a29c] 0010:c000a29c (unk. ctxt): mov eax, dword ptr ss:[ebp+12] ; 8b450c
<bochs:37> n
Next at t=7979574
(0) [0x00000000a29f] 0010:c000a29f (unk. ctxt): call eax ; ffd0
<bochs:38> r
eax: 0xc000a267 -1073700249
ebx: 0xc0011000 -1073672192
ecx: 0xc0007004 -1073713148
edx: 0x00000020 32
esp: 0xc0006f70 -1073713296
ebp: 0xc0006f88 -1073713272
esi: 0xc0100000 -1072693248
edi: 0x00000000 0
eip: 0xc000a29f
eflags 0x00000096: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf AF PF cf
<bochs:39> n
Next at t=7979585
(0) [0x00000000a2a1] 0010:c000a2a1 (unk. ctxt): add esp, 0x00000010 ; 83c410
<bochs:40> r
eax: 0x00000001 1
ebx: 0xc0011000 -1073672192
ecx: 0xc0007004 -1073713148
edx: 0x00000020 32
esp: 0xc0006f70 -1073713296
ebp: 0xc0006f88 -1073713272
esi: 0xc0100000 -1072693248
edi: 0x00000000 0
eip: 0xc000a2a1
eflags 0x00000096: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf AF PF cf
<bochs:41> n
Next at t=7979586
(0) [0x00000000a2a4] 0010:c000a2a4 (unk. ctxt): nop ; 90
<bochs:42> n
Next at t=7979587
(0) [0x00000000a2a5] 0010:c000a2a5 (unk. ctxt): leave ; c9
<bochs:43> n
Next at t=7979588
(0) [0x00000000a2a6] 0010:c000a2a6 (unk. ctxt): ret ; c3
5. 逐行分析汇编指令
(0) [0x00000000a02c] 0010:c000a02c (unk. ctxt): mov dword ptr ss:[ebp-76], 0x00000001 ; c745b401000000
对应C语句:int aa =1;
(0) [0x00000000a033] 0010:c000a033 (unk. ctxt): mov dword ptr ss:[ebp-72], 0x00000002 ; c745b802000000
对应C语句: int bb =2;
(0) [0x00000000a03a] 0010:c000a03a (unk. ctxt): sub esp, 0x00000004 ; 83ec04
栈指针减4,预留空间
(0) [0x00000000a03d] 0010:c000a03d (unk. ctxt): push dword ptr ss:[ebp-72] ; ff75b8
为调用test_callback传参数3,即传入bb
(0) [0x00000000a040] 0010:c000a040 (unk. ctxt): lea eax, ds:[ebx-28057] ; 8d836792ffff
将参数2(回调函数)的地址赋给eax
(0) [0x00000000a046] 0010:c000a046 (unk. ctxt): push eax ; 50
传入参数2,对照readelf看到的回调函数地址和寄存器eax值,可以看到eax的值(0xc000a267)确实就是bool test_func(int a, int b)这个函数的地址 r eax: 0xc000a267 -1073700249 450: c000a267 24 FUNC GLOBAL DEFAULT 1 test_func
(0) [0x00000000a047] 0010:c000a047 (unk. ctxt): push dword ptr ss:[ebp-76] ; ff75b4
为调用test_callback传参数1,即传入aa
(0) [0x00000000a04a] 0010:c000a04a (unk. ctxt): call .+560 (0xc000a27f) ; e830020000
调用test_callback, readelf看到: 479: c000a27f 40 FUNC GLOBAL DEFAULT 1 test_callback (0) [0x00000000a27f] 0010:c000a27f (unk. ctxt): nop ; f30f1efb
(0) [0x00000000a283] 0010:c000a283 (unk. ctxt): push ebp ; 55
(0) [0x00000000a284] 0010:c000a284 (unk. ctxt): mov ebp, esp ; 89e5
(0) [0x00000000a286] 0010:c000a286 (unk. ctxt): sub esp, 0x00000008 ; 83ec08
(0) [0x00000000a289] 0010:c000a289 (unk. ctxt): call .+25 (0xc000a2a7) ; e819000000
(0) [0x00000000a28e] 0010:c000a28e (unk. ctxt): add eax, 0x00006d72 ; 05726d0000
(0) [0x00000000a293] 0010:c000a293 (unk. ctxt): sub esp, 0x00000008 ; 83ec08
以上几句是进函数的保护和调用__x86_pc_thunk,与主题无关。
以下对应test_callback中的语句t_func(c,d);, t_func是形参,参数c和d也是形参,实际运行时替换为实参,即实际等于执行test_func(aa,bb); ,后面就是简单的函数调用了,不再详述
(0) [0x00000000a296] 0010:c000a296 (unk. ctxt): push dword ptr ss:[ebp+16] ; ff7510
(0) [0x00000000a299] 0010:c000a299 (unk. ctxt): push dword ptr ss:[ebp+8] ; ff7508
(0) [0x00000000a29c] 0010:c000a29c (unk. ctxt): mov eax, dword ptr ss:[ebp+12] ; 8b450c
(0) [0x00000000a29f] 0010:c000a29f (unk. ctxt): call eax ; ffd0