谷歌之春 发表于 2010-04-28 22:46

关于gcc 嵌入式汇编的一个问题(current在x86_64下的实现)

在ULK3.0中,作者描述了current的实现(使用esp寄存器的值),查看X86_64下的实现代码,发现有较大差别:
static inline struct task_struct *get_current(void)
{
        struct task_struct *t;
      asm volatile("movq %%gs:%P1,%0"
                     :"=r" (t)
                     :"i"(offsetof(struct x8664_pda, pcurrent))
                     :"memory");      
        return t;
}
不明白两个问题:
1、为什么对X86_64结构不使用esp寄存器的值去获取?
2、这段嵌入式汇编中的movq %%gs:%P1,% 中有两个不明白的地方,一是%%gs:%P1 算是什么意思?段前缀?但是貌似linux汇编没这个方式啊;而是%%gs:%P1中%p1是什么意思?1应该表示是第二个参数,offsetof(struct x8664_pda, pcurrent),P表示什么呢?我查了下GCC手册,没有查到。。。

wangzhen11aaa 发表于 2011-10-09 22:52

有时侯我们会想这么写一条汇编语句:
                  
                /* 错误写法 */
                asm volatile ("movl %0(%%esp), %%eax" : /* no outputs */ : "i" (4) );
         
         但这样子gcc会报错:
                  
                Error: junk `(%esp)' after expression
         
         原因是,%0是立即数(用"i"约束的),于是gcc会把这条汇编指令变成:
                  
                movl $4(%esp), %eax
         
         于是它就会报错,因为多了一个$符号 -- 这在GNU as的寄存器间接寻址中是不允许的。需要改成:
                  
                asm volatile ("movl %c0(%%esp), %%eax" : /* no outputs */ : "i" (4) );
         

          例如下面的程序:
                  
             /* WARNING: WRONG! */
             $ cat -n dollar.c
             1#include <stdio.h>
             2
             3int main()
             4{
             5          long var = 0;
             6
             7          asm volatile
             8                  ("pushl $0x11223344\n\t"
             9                   "pushl $0x55667788\n\t"
            10                   "movl%1(%%esp), %0\n\t"
            11                   "addl$8, %%esp "   /* 消减前面两条push指令导致的esp值改变 */
            12
            13                   : "=r" (var)
            14                   : "i" (4)
            15                   : "cc", "esp"
            16                  );
            17
            18          printf("var is 0x%x\n", var);
            19          return 0;
            20}

         
          使用gcc编译:
               
                $ gcc dollar.c -m32 -S
         
          查看一下生成的dollar.s文件,我们看到inline asm被gcc转换成:
               
                pushl   $0x11223344
                pushl   $0x55667788
                movl    $4(%esp), %eax
                addl    $8, %esp
      
         
          如上所说,"$4(%esp)"这种寻址方式是无法通过GNU as的,因为多了一个$符号。 GCC为我们提供了一些方法,
          允许我们消除那个无用的$符号:
               
               
                %c0         消除%0的$符号
                %n0         消除%0的$符号,并添加负号。 (例如%0是4,那么%n0就是-4)
                %l0         和%0相同,但是只用于跳转指令的目标
                %a0         %0本来是一个地址,例如0x8048000,放在%eax中。 现在把它转换为寄存器间接寻址,亦即: (%eax)
                %P0         %c0相同,消除%0的$符号
         
          让我们再回头来看上面的dollar.c程序。 我们把第10行改成:
               
                "movl %c1(%%esp), %0\n\t"
         
          然后再次用gcc dollar.c -m32 -S编译,查看dollar.s中的相关代码,发现这条汇编语句变成了:
               
                movl    4(%esp), %eax
         
          这正是我们期待的结果。 至于%n0、%l0和%P0,留给读者自己试验一下。

          看一下%a0的例子:

                $ cat a.c
                #include <stdio.h>

                int main(void)
                {
                        char c = 0;

                        asm volatile ("movb %a1, %0"
                                    : "=r" (c)
                                    /* GNU ld 为IA32连接程序时,默认在从0x8048001开始的地方保存'E'、'L'、'F'三个字母 */
                                    : "r" (0x8048001)
                                     );

                        printf("c is %c\n", c);
                        return 0;
                }
         
          编译运行:
                  $ gcc -m32 a.c
                $ ./a.out
                c is E
         
          成功的打印出了字母'E'。 倘若我们用-S生成*.s文件,再查看其中的相关汇编语句:
               
                movb (%eax), %al
         
          这说明gcc为0x8048001接合了eax寄存器,这就是%1;而%a1,则把它转换为间接寻址,也就是『(%%eax)』。

lhuoshan 发表于 2011-10-24 22:32

沙发很给力啊。。。:em08:
页: [1]
查看完整版本: 关于gcc 嵌入式汇编的一个问题(current在x86_64下的实现)