呵,好久没写CSDN文章了,来凑个热闹。最近我阅读了楚狂人,wowocock写的《天书夜读》试读本,对C反汇编感触颇深,书中有一例算法反汇编,其对汇编的阅读确实富有挑战,而该书中也未详解,在此,我谨将此例详细分析如下,帮助大家更好理解C反汇编代码,若有任何错误,请大家批评指正!
该例的要求是求两个3x3矩阵的乘积,其C源代码如下:
int myfunction(int a[3][3], int b[3][3], int c[3][3])
{
int i, j;
for (i = 0; i < 3; ++i)
{
for (j = 0; j < 3; ++j)
c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
}
return 0;
}
相当简单吧?呵呵,那么希望你看过了下面的反汇编代码后,还能这样乐观。
如下的反汇编代码,据原书作者,在VC2003,debug模式下得到,同时我在VC2008PRO下得到汇编代码完全相同,可以通用,但此处引用原书中的代码。
00411A3E mov dword ptr [i],0
00411A45 jmp myfunction+30h (411A50h)
00411A47 mov eax,dword ptr [i]
00411A4A add eax,1
00411A4D mov dword ptr [i],eax
00411A50 cmp dword ptr [i],3
00411A54 jge myfunction+0AEh (411ACEh)
00411A56 mov dword ptr [j],0
00411A5D jmp myfunction+48h (411A68h)
00411A5F mov eax,dword ptr [j]
00411A62 add eax,1
00411A65 mov dword ptr [j],eax
00411A68 cmp dword ptr [j],3
00411A6C jge myfunction+0A9h (411AC9h)
00411A6E mov eax,dword ptr [i]
00411A71 imul eax,eax,0Ch
00411A74 mov ecx,dword ptr [a]
00411A77 mov edx,dword ptr [j]
00411A7A mov esi,dword ptr [b]
00411A7D mov eax,dword ptr [ecx+eax]
00411A80 imul eax,dword ptr [esi+edx*4]
00411A84 mov ecx,dword ptr [i]
00411A87 imul ecx,ecx,0Ch
00411A8A mov edx,dword ptr [a]
00411A8D mov esi,dword ptr [j]
00411A90 mov edi,dword ptr [b]
00411A93 mov ecx,dword ptr [edx+ecx+4]
00411A97 imul ecx,dword ptr [edi+esi*4+0Ch]
00411A9C add eax,ecx
00411A9E mov edx,dword ptr [i]
00411AA1 imul edx,edx,0Ch
00411AA4 mov ecx,dword ptr [a]
00411AA7 mov esi,dword ptr [j]
00411AAA mov edi,dword ptr [b]
00411AAD mov edx,dword ptr [ecx+edx+8]
00411AB1 imul edx,dword ptr [edi+esi*4+18h]
00411AB6 add eax,edx
00411AB8 mov ecx,dword ptr [i]
00411ABB imul ecx,ecx,0Ch
00411ABE add ecx,dword ptr [c]
00411AC1 mov edx,dword ptr [j]
00411AC4 mov dword ptr [ecx+edx*4],eax
00411AC7 jmp myfunction+3Fh (411A5Fh)
00411AC9 jmp myfunction+27h (411A47h)
晕了?呵呵,如果你第一遍看就可以完全读通,那我只好对您Orz了= =,希望你们这些天才别来砸我场子啊^_^
首先先简单提一下,C语言中多维数组的存储方式。其存储的规则是,列优先于行,也就是:若有a[3][3],则顺序是a[0][0], a[0][1], a[0][2], a[1][0], a[1][1], a[1][2], a[2][0], a[2][1], a[2][2]。其实所谓的多维数组这样高级语言才有的数据结构,在底层实现中,无非就是一片连续分配的一维内存空间而已,大家千万别看得太过神秘了。
接着要提一下C语言中指针在汇编实现时的情况。汇编可以说把高级语言中几乎所有现象的本质都暴露了出来。现观察一下以下情况:对于int a[3][3]定义的多维数组变量a,a本身表示的是该矩阵首行的地址,而a[0](或者*a)是首行首列元素的地址,两者是不同的指针类型,然而很容易知道在地址数值上两者是相等的,我们引申开来,比如我们要访问a[m][n],我们还可以写作*(*(a + m) + n),但由上段所述,实际上就是把a所含的地址再往后移m * 3 * 4(int占4个字节) + n * 4个字节(此注:a + m不是a所在处后移m个字节!可以把a理解为int[3]这种类型的指针,所以加了m,其实是加了12* m个bytes)就是所要访问的内存,换句话说任何多维数组总可以用一个一维普通指针(比如使用指针类型强制转换)完全访问到,尽管在高级语言中这样的做法是非常不明智且危险的。但汇编却正是这么做的!比如我们有C语句:a[i][j] = 2;,用C还可以写作*(*(a + i) + j) = 2,而用汇编则可能是这样,
mov eax, dword ptr [i] ; 把i的值读入eax
imul eax, eax, 0Ch ; 把eax乘以12,因为一行有三个int,3 * 4 = 12
lea ecx, a[eax] ; 相当于将a所含的地址与eax相加后存入ecx
mov edx, dword ptr [j] ; 把j的值读入edx
mov dword ptr [ecx + edx * 4], 2 ; ecx + edx就是a[i][j]的地址
我们尝试省略寄存器的中间步骤、并逐式代入的话,就直接可以推出这个式子:
mov dword ptr [a + i * 12 + j * 4], 2 ;这个式子和上述的完全一致。
最后提到的就是C中for语句的汇编实现。
for (init; condition; expr)
loop-body
大家都应当很清楚,执行的顺序是:先init初始化循环变量,接着判断condition,若满足则执行循环体(loop-body),再执行expr,判断条件是否满足,满足则执行loop-body……汇编代码是非常机械地对应着上述过程,例:
for (i = 0; i < 5; ++i)
loop-body
则相应汇编代码:
; 初始化代码
mov dword ptr [i], 0
jmp (*) ; 无条件跳转到(*)所在代码,这里不列出具体的代码地址了
; 这段就是expr,类似用一个中间变量(只不过这里是寄存器)的办法来给i加一
(#)mov eax, dword ptr [i]
add eax, 1
mov dword ptr [i], eax
; 这段则是判断条件,condition
(*) cmp dword ptr [i], 5 ; 比较i和5
jge (**) ; jge表示jump to (**) when i is greater than or equal to 5,大于等于5
; 跳转,(**)指向的是for循环后面接着的代码,也就是跳出循环
; 以下段省略,是循环体loop-body
…
jmp (#) ;无条件跳转到expr,并进而判断条件
; 以下是for循环以外的代码
(**)…