catch 发表于 2014-1-18 21:11
1. 嵌入式汇编的一般形式: __asm__ __volatile__ ("" : output : input : modify); 其中,__asm__表示 ...
2. Linux源代码中嵌入式汇编举例 Linux源代码中,在arch目录下的.h和.c文件中,很多文件都涉及嵌入式汇编,下面以system.h中的C函数为例,说明嵌入式汇编的应用。 (1)简单应用 #define __save_flags(x) __asm__ __volatile__("pushfl ; popl %0":"=g" (x): /* no input */) #define __restore_flags(x) __asm__ __volatile__("pushl %0 ; popfl": /* no output */ :"g" (x):"memory", "cc") 第一个宏是保存标志寄存器的值,第二个宏是恢复标志寄存器的值。第一个宏中的pushfl指令是把标志寄存器的值压栈。而popl是把栈顶的值(刚压入栈的flags)弹出到x变量中,这个变量可以存放在一个寄存器或内存中。这样,你可以很容易地读懂第二个宏。 (2) 较复杂应用 static inline unsigned long get_limit(unsigned long segment) { unsigned long __limit; __asm__("lsll %1,%0" :"=r" (__limit):"r" (segment)); return __limit+1; } 这是一个设置段界限的函数,汇编代码段中的输出参数为__limit(即%0),输入参数为segment(即%1)。Lsll是加载段界限的指令,即把segment段描述符中的段界限字段装入某个寄存器(这个寄存器与__limit结合),函数返回__limit加1,即段长。 (3)复杂应用 在Linux内核代码中,有关字符串操作的函数都是通过嵌入式汇编完成的,因为内核及用户程序对字符串函数的调用非常频繁,因此,用汇编代码实现主要是为了提高效率(当然是以牺牲可读性和可维护性为代价的)。在此,我们仅列举一个字符串比较函数strcmp,其代码在arch/i386/string.h中。 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 //装入串操作数,即从[esi]传送到al寄存器,然后esi指向串中下一个元素 scasb //扫描串操作数,即从al中减去es:[edi],不保留结果,只改变标志 jne2f //如果两个字符不相等,则转到标号2 testb %al %al jne 1b xorl %eax %eax jmp 3f 2: sbbl %eax %eax orb $1 %al 3: 这段代码看起来非常熟悉,读起来也不困难。其中1f 表示往前(forword)找到第一个标号为1的那一行,相应地,1b表示往后找。其中嵌入式汇编代码中输出和输入部分的结合情况为: · 返回值__res,放在al寄存器中,与%0相结合; · 局部变量d0,与%1相结合,也与输入部分的cs参数相对应,也存放在寄存器ESI中,即ESI中存放源字符串的起始地址。 · 局部变量d1, 与%2相结合,也与输入部分的ct参数相对应,也存放在寄存器EDI中,即EDI中存放目的字符串的起始地址。 通过对这段代码的分析我们应当体会到,万变不利其本,嵌入式汇编与一般汇编的区别仅仅是形式,本质依然不变。因此,全面掌握Intel 386 汇编指令乃突破阅读低层代码之根本。
|