gcc内联汇编-howto-栗子栗子

2020-04-23

本文是GCC-Inline-Assembly-HOWTO的翻译,尽量翻译正确,如有疑惑,或参看原文。

7. 一些有用的代码

如何设置/清除寄存器中的一个位?

__asm__ __volatile__(   "btsl %1,%0"
                      : "=m" (ADDR)
                      : "Ir" (pos)
                      : "cc"
                      );

此处,ADDR(一个内存变量)中的pos变量对应的比特位将设为1.我们可以用btrl替代btsl来清除一个位。限制符Ir指出,pos是一个寄存器,且它的值介于0-31(x86限制符)。即我们可以设置/清除ADDR变量中任意0~31位值。因为条件码将被改变,我们增加cc到受影响列表。

现在我们看一些复杂但有用的函数。字符串复制。

static inline char * strcpy(char * dest,const char *src)
{
int d0, d1, d2;
__asm__ __volatile__(  "1:\tlodsb\n\t"
                       "stosb\n\t"
                       "testb %%al,%%al\n\t"
                       "jne 1b"
                     : "=&S" (d0), "=&D" (d1), "=&a" (d2)
                     : "0" (src),"1" (dest) 
                     : "memory");
return dest;
}

源地址保存在esi中,目标地址在edi中,然后开始复制,当我们到达0时,复制结束。限制符&S,&D,&a说明寄存器esi, edi, eax是早期受影响寄存器。即,它们的内容将在函数完成前被改变。此处明显memory也在受影响之列。

我们看一个类似的函数,移动一块双字(double words)。注意函数声明为一个宏。

#define mov_blk(src, dest, numwords) \
__asm__ __volatile__ (                                          \
                       "cld\n\t"                                \
                       "rep\n\t"                                \
                       "movsl"                                  \
                       :                                        \
                       : "S" (src), "D" (dest), "c" (numwords)  \
                       : "%ecx", "%esi", "%edi"                 \
                       )

这里我们没有输出,所以改变发生在寄存器ecx, esiedi上,是块移动的副作用。所以我们将它们加在受影响列表上。

Linux中,系统调用是由GCC内联汇编实现的。让我们看一些一个系统调用是如何实现的。所有的系统调用被写作一个宏(linux/unistd.h)。如,一个有3个参数的系统调用被写作如下的宏:

#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile (  "int $0x80" \
                  : "=a" (__res) \
                  : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
                    "d" ((long)(arg3))); \
__syscall_return(type,__res); \
}

无论任何的3个参数的系统调用,都使用以上宏进行。syscall数字放在eax中,然后每个参数放在ebx, ecx, edx。最终int 0x80指令真正的执行调用。返回码可以在eax中获得。

每个系统调用实现方法类似。退出是一个单参数系统调用,让我们看看它的代码是什么样的,如下:

{
        asm("movl $1,%%eax;         /* SYS_exit is 1 */
             xorl %%ebx,%%ebx;      /* Argument is in ebx, it is 0 */
             int  $0x80"            /* Enter kernel mode */
             );
}

退出数字是1,参数(parameter)是0。所以我们安排eax包含1,ebx包含0,通过int $0x80执行exit(0)。这就是exit的工作原理。