static inline int strcmp(const char * cs,const char * ct) { int d0, d1; register int __res; __asm__ __volatile__( "1:\tlodsb\n\t" "scasb\n\t" "jne 2f\n\t" "testb %%al,%%al\n\t" "jne 1b\n\t" "xorl %%eax,%%eax\n\t" "jmp 3f\n" "2:\tsbbl %%eax,%%eax\n\t" "orb $1,%%al\n" "3:" :"=a" (__res), "=&S" (d0), "=&D" (d1) :"1" (cs),"2" (ct)); return __res; }
其中的“\n”是换行符,“\t”是 tab 符,在每条命令的结束加这两个符号,是为了让
gcc 把嵌入式汇编代码翻译成一般的汇编代码时能够保证换行和留有一定的空格。
例如,上 面的嵌入式汇编会被翻译成:
1: lodsb | //装入串操作数,即从DS段中esi位置的字符传送到 al 寄存器,然后 esi 根据DF指向串中下一个元素 ,DF=0,增加;DF=1,减少 |
Scasb | //扫描串操作数,即从 al 中减去ES段中edi位置的字符,不保留结果,只改变标志 CF,AF,PF,SF,OF,ZF,若字符相等,ZF=1,否则ZF=0。若DS段中字符小于ES段中串,则CF=1,后面sbb运算会出现-1 |
Jne2f | //如果两个字符不相等,则转到标号 |
testb %al %al | //如果al中全是0,则ZF=1(逻辑与结果为0),如果字符串结束遇到null零 |
jne 1b | //如果ZF=0(逻辑与结果不为0)即字符串未结束,则继续比较 |
xorl %eax %eax | //自身异或,结果为0,CF=0,eax清空 |
jmp 3f | //向前跳至3:,退出,返回值为0 |
2: sbbl %eax %eax | //32位sbb src,dest;dest-src-CF,存入dest,如果CF=1,则结果为-1(全是1);否则为0。(结果为-1时标志位:CF=1,SF=1,OF=1,ZF=0,PF=1) |
orb $1 %al | //对0位或1,保证结果为-1或者1。如果结果为0,或1之后为1,即字符大,返回1,字符小,返回-1 |
3: |
|
参考《深入分析linux内核》
这段代码看起来非常熟悉,读起来也不困难。其中 3f 表示往前(forword)找到第一个
标号为 3 的那一行,相应地,1b 表示往后找。其中嵌入式汇编代码中输出和输入部分的结合 情况为:
• 返回值__res,放在 al 寄存器中,与%0相结合;
• 局部变量 d0,与%1 相结合,也与输入部分的 cs 参数相对应,也存放在寄存器 ESI
中,即 ESI 中存放源字符串的起始地址。
• 局部变量 d1,与%2 相结合,也与输入部分的 ct 参数相对应,也存放在寄存器 EDI
中,即 EDI 中存放目的字符串的起始地址。
通过对这段代码的分析我们应当体会到,万变不利其本,嵌入式汇编与一般汇编的区别 仅仅是形式,本质依然不变。因此,全面掌握 Intel 386 汇编指令乃突破阅读低层代码之根 本
大部分指令对于AT&T使用以下后缀:
b 字节(8位),对应于Intel的byte ptr
w 字(16位),对应于Intel的word ptr
l 双字(实际是表示long,32位),对应于Intel的dword ptr
q 四字(64位),对应于Intel的qword ptr