在x86里,循环指令有:
LOOP:ECX不为零时循环 LOOPE/LOOPZ:ECX不为零且标志Z=1时循环 LOOPNE/LOOPNZ:ECX不为零且标志Z=0时循环
但实质上,由于上面指令适用范围太窄,只能用于循环体非常简单的。所以往往是用条件跳转指令来实际循环。用例子来体现一下:
#include <stdio.h> int loop_for( int n ) { int sum = 0; for ( int i = 0; i < n; i++ ) { sum += i; } return sum; } int loop_while( int n ) { int sum = 0; int i = 0; while ( i < n ) { sum += i; i++; } return sum; } int loop_do( int n ) { int sum = 0; int i = 0; do { sum += i; i++; } while ( i < n ); return sum; } int main() { int n = 0; scanf( "%d", &n ); return loop_for( n ) + loop_while( n ) + loop_do( n ); }
看一下loop_do,loop_for, loop_while的汇编:
(gdb) disassemble loop_do Dump of assembler code for function _Z7loop_doi: 0x080485d4 <+0>: push %ebp 0x080485d5 <+1>: mov %esp,%ebp 0x080485d7 <+3>: sub $0x10,%esp 0x080485da <+6>: movl $0x0,-0x4(%ebp) 0x080485e1 <+13>: movl $0x0,-0x8(%ebp) 0x080485e8 <+20>: mov -0x8(%ebp),%eax 0x080485eb <+23>: add %eax,-0x4(%ebp) 0x080485ee <+26>: addl $0x1,-0x8(%ebp) 0x080485f2 <+30>: mov -0x8(%ebp),%eax 0x080485f5 <+33>: cmp 0x8(%ebp),%eax 0x080485f8 <+36>: setl %al 0x080485fb <+39>: test %al,%al 0x080485fd <+41>: jne 0x80485e8 <_Z7loop_doi+20> 0x080485ff <+43>: mov -0x4(%ebp),%eax 0x08048602 <+46>: leave 0x08048603 <+47>: ret End of assembler dump.
(gdb) disassemble loop_for Dump of assembler code for function _Z8loop_fori: 0x08048570 <+0>: push %ebp 0x08048571 <+1>: mov %esp,%ebp 0x08048573 <+3>: sub $0x10,%esp 0x08048576 <+6>: movl $0x0,-0x4(%ebp) 0x0804857d <+13>: movl $0x0,-0x8(%ebp) 0x08048584 <+20>: jmp 0x8048590 <_Z8loop_fori+32> 0x08048586 <+22>: mov -0x8(%ebp),%eax 0x08048589 <+25>: add %eax,-0x4(%ebp) 0x0804858c <+28>: addl $0x1,-0x8(%ebp) 0x08048590 <+32>: mov -0x8(%ebp),%eax 0x08048593 <+35>: cmp 0x8(%ebp),%eax 0x08048596 <+38>: setl %al 0x08048599 <+41>: test %al,%al 0x0804859b <+43>: jne 0x8048586 <_Z8loop_fori+22> 0x0804859d <+45>: mov -0x4(%ebp),%eax 0x080485a0 <+48>: leave 0x080485a1 <+49>: ret End of assembler dump.
(gdb) disassemble loop_while Dump of assembler code for function _Z10loop_whilei: 0x080485a2 <+0>: push %ebp 0x080485a3 <+1>: mov %esp,%ebp 0x080485a5 <+3>: sub $0x10,%esp 0x080485a8 <+6>: movl $0x0,-0x4(%ebp) 0x080485af <+13>: movl $0x0,-0x8(%ebp) 0x080485b6 <+20>: jmp 0x80485c2 <_Z10loop_whilei+32> 0x080485b8 <+22>: mov -0x8(%ebp),%eax 0x080485bb <+25>: add %eax,-0x4(%ebp) 0x080485be <+28>: addl $0x1,-0x8(%ebp) 0x080485c2 <+32>: mov -0x8(%ebp),%eax 0x080485c5 <+35>: cmp 0x8(%ebp),%eax 0x080485c8 <+38>: setl %al 0x080485cb <+41>: test %al,%al 0x080485cd <+43>: jne 0x80485b8 <_Z10loop_whilei+22> 0x080485cf <+45>: mov -0x4(%ebp),%eax 0x080485d2 <+48>: leave 0x080485d3 <+49>: ret End of assembler dump.
由于loop_for,loop_while的代码逻辑一样,连生成的汇编都是一样。loop_do和两者不大一样,所以,汇编不一样。现在只以loo_do进行分析:
(gdb) disassemble loop_do Dump of assembler code for function _Z7loop_doi: 0x080485d4 <+0>: push %ebp 0x080485d5 <+1>: mov %esp,%ebp 0x080485d7 <+3>: sub $0x10,%esp 0x080485da <+6>: movl $0x0,-0x4(%ebp) 0x080485e1 <+13>: movl $0x0,-0x8(%ebp) 0x080485e8 <+20>: mov -0x8(%ebp),%eax 0x080485eb <+23>: add %eax,-0x4(%ebp) 0x080485ee <+26>: addl $0x1,-0x8(%ebp) 0x080485f2 <+30>: mov -0x8(%ebp),%eax 0x080485f5 <+33>: cmp 0x8(%ebp),%eax 0x080485f8 <+36>: setl %al 0x080485fb <+39>: test %al,%al 0x080485fd <+41>: jne 0x80485e8 <_Z7loop_doi+20> 0x080485ff <+43>: mov -0x4(%ebp),%eax 0x08048602 <+46>: leave 0x08048603 <+47>: ret End of assembler dump.
由指令地址0x080485fd这一条指令可知,执行流程会回到0x080485e8,这个地址是比跳转指令地址更小,直到满足eax的值和ebp+8的内容相等才会停止。也就是说,从0x080485e8到0x080485fd就构成了一个循环。由于
0x080485f5 <+33>: cmp 0x8(%ebp),%eax
是用来设置标志位的,所以应该是对应于i < n子句
也就是说,
0x080485e8 <+20>: mov -0x8(%ebp),%eax 0x080485eb <+23>: add %eax,-0x4(%ebp) 0x080485ee <+26>: addl $0x1,-0x8(%ebp)
对应于
36 sum += i; 37 i++;
如果同样分析loop_for,loop_while也会发现,跳转指令是跳到比当前指令地址更小的地址来执行。
从这里可以看出,如果在分析函数的汇编时,遇到跳转指令,如果它并不是跳到比当前指令地址更大的地址执行,那么它有可能是一个循环,否则就是一个普通的条件跳转结构