Chinaunix首页 | 论坛 | 博客
  • 博客访问: 57964
  • 博文数量: 33
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 0
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-10 16:26
文章分类

全部博文(33)

文章存档

2014年(33)

我的朋友

分类: LINUX

2014-03-12 10:59:41

关于linux内嵌汇编语言,
内核之旅的第六讲:已经讲得很清楚了。
这里结合几个实例主要讲解几个需要留意的地方。

实例一:变量修饰符"+"
asm.c

#include <stdio.h>

unsigned long input = 0x12345678;
unsigned long output = 0x10000000;

int main(void)
{
    asm volatile ("addl %1,%0 \n\t"
            :"=r"(output)
            :"r"(input));
    printf("output:0x%x\n",output);

    return 0;
}

编译、运行:
gcc -o asm asm.c -g
./asm
结果为:
output:0x2468acf0

怎么回事呢?我们的意图是让input+output,结果为0x22345678才对啊。
没关系,objdump分析一下:
objdump -j .text -S asm
我们只关心内嵌汇编语言的反汇编的结果:

asm volatile ("addl %1,%0 \n\t"
 8048385:    a1 ac 95 04 08     mov 0x80495ac,%eax
 804838a:    01 c0              add %eax,%eax
 804838c:    a3 b0 95 04 08     mov %eax,0x80495b0
            :"=r"(output)
            :"r"(input));

很明显可以猜到,0x80495ac是input的地址,0x80495b0是output的地址,
那么反汇编的结果就是将input+input的结果送给output,
所以结果才为0x12345678+0x12345678 = 0x2468acf0;

原因是这样的,老版的gcc将操作符严格的分为输入和输出两种,分别放在输入和输出部分,
那么我们知道这个output是一个先读后写型操作数,也就是说,要先将output读出来,和input相加,然后再写回去,
所以output既属于输入部分也属于输出部分。那么问题的症结也就在此。我们在输入部分也加上output试试。

#include <stdio.h>

unsigned long input = 0x12345678;
unsigned long output = 0x10000000;

int main(void)
{
    asm volatile ("addl %1,%0 \n\t"
            :"=r"(output)
            :"r"(input)
            "0"(output));//新添加上去的

    printf("output:0x%x\n",output);

    return 0;
}

编译、运行:
gcc -o asm asm.c -g
./asm
结果为:
output:0x22345678
反汇编:objdump -j .text -S asm
对应的汇编指令为;

    asm volatile ("addl %1,%0 \n\t"
 8048385:    8b 15 bc 95 04 08     mov 0x80495bc,%edx
 804838b:    a1 c0 95 04 08        mov 0x80495c0,%eax
 8048390:    01 d0                 add %edx,%eax
 8048392:    a3 c0 95 04 08        mov %eax,0x80495c0
            :"=r"(output)
            :"r"(input)),
            "0"(output));

先将input(0x80495bc)送给edx,
然后再把output(0x80495c0)送给eax,
edx+eax->eax,
eax->output


其实有一个输出变量修饰符"+",就是专门干这个事情的。
我们上面的例子中用的输出变量修饰符"="(这一句:"=r"(output)中的=号)只是表示这是一个输出操作数,
而"+"则表示这是一个先读后写型操作数。
所以将这里的=改为+,一样可以解决问题,读者可以自己尝试。

实例二:变量修饰符"&"
上个实例讲解了变量修饰符+,这个实例主要说明变量修饰符&。
先看两段代码:

asm volatile ("movl %1,%0 \n\t"
            :"=r"(output)
            :"r"(input));

 

asm volatile ("movl %1,%0 \n\t"
            :"=&r"(output)
            :"r"(input));

这两段代码主要的区别就在于这个&。
变量修饰符&表示:输出变量和输入变量不能共用同一个寄存器,这样据说可以避免很多麻烦。
反汇编这两段代码看看是不是这样:

    asm volatile ("movl %1,%0 \n\t"
 8048385:    a1 ac 95 04 08     mov 0x80495ac,%eax
 804838a:    89 c0              mov %eax,%eax
 804838c:    a3 b0 95 04 08     mov %eax,0x80495b0
            :"=r"(output)
            :"r"(input));

 

asm volatile ("movl %1,%0 \n\t"
 8048385:    a1 ac 95 04 08     mov 0x80495ac,%eax
 804838a:    89 c2     mov %eax,%edx
 804838c:    89 d0     mov %edx,%eax
 804838e:    a3 b0 95 04 08     mov %eax,0x80495b0
            :"=&r"(output)
            :"r"(input));

实例三: 破坏描述
什么是破坏描述呢?当我们用纯C编写代码时,寄存器的分配和使用是完全由编译器来控制的。
因为编译器对C语法和语义相当的了解,所以在编译器自己优化的时候,会协调好寄存器的使用。
但是当C中有内嵌的汇编语言时,寄存器也可以由程序员来使用和控制,所以就得有一种机制通知编译器
内嵌汇编都修改了哪些寄存器,编译器对这些寄存器再次使用可能会导致错误。这种机制就是破坏描述。
先看一段代码:
 

#include <stdio.h>

unsigned long input = 0x12345678;
unsigned long output,zero;

int main(void)
{
    asm volatile ("movl $0,%%eax \n\t"
            "movl %%eax,%1 \n\t"
            "movl %2,%%eax \n\t"
            "movl %%eax,%0 \n\t"
            :"=m"(output),"=m"(zero)
            :"r"(input));
    printf("output:0x%x\t zero:0x%x\t\n",output,zero);

    return 0;
}

这段代码想把zero置为0,然后再把input赋值给output。
那么结果是不是这样的呢?
编译、运行:
gcc -o asm asm.c -g
./asm 
结果为:
output:0x0	 zero:0x0

同样看看反汇编的结果吧:objdump -j .text -S asm

asm volatile ("movl $0,%%eax \n\t"
 8048385:    a1 bc 95 04 08     mov 0x80495bc,%eax
 804838a:    b8 00 00 00 00     mov $0x0,%eax
 804838f:    a3 c8 95 04 08     mov %eax,0x80495c8
 8048394:    89 c0              mov %eax,%eax
 8048396:    a3 c4 95 04 08     mov %eax,0x80495c4
            "movl %%eax,%1 \n\t"
            "movl %2,%%eax \n\t"
            "movl %%eax,%0 \n\t"
            :"=m"(output),"=m"(zero)
            :"r"(input));

先将input(0x80495bc)送给eax,
然后将eax清零
再将eax送给zero(x80495c8)
将eax送给output(0x80495c4)

这就是编译器在优化的时候造成的麻烦。
那么我们可以用破坏描述,告诉编译器eax已经使用,不能再对它进行更改了。代码修改如下:

#include <stdio.h>

unsigned long input = 0x12345678;
unsigned long output,zero;

int main(void)
{
    asm volatile ("movl $0,%%eax \n\t"
            "movl %%eax,%1 \n\t"
            "movl %2,%%eax \n\t"
            "movl %%eax,%0 \n\t"
            :"=m"(output),"=m"(zero)
            :"r"(input)
            :"eax");//新加的破坏描述

    printf("output:0x%x\t zero:0x%x\t\n",output,zero);

    return 0;
}

结果为:./asm
output:0x12345678	 zero:0x0

反汇编的结果为:objdump -j .text -S asm

asm volatile ("movl $0,%%eax \n\t"
 8048385:    8b 15 bc 95 04 08     mov 0x80495bc,%edx
 804838b:    b8 00 00 00 00        mov $0x0,%eax
 8048390:    a3 c8 95 04 08        mov %eax,0x80495c8
 8048395:    89 d0                 mov %edx,%eax
 8048397:    a3 c4 95 04 08        mov %eax,0x80495c4
            "movl %2,%%eax \n\t"
            "movl %%eax,%0 \n\t"
            :"=m"(output),"=m"(zero)
            :"r"(input)
            :"eax");

另外还有内存破坏描述:"memory",同样的道理,这里就不再多举例了.
参考资料:《》人民邮电出版社 作者:华清远见嵌入式培训中心 河秦 王洪涛 2008-11-1 第一版
阅读(986) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~