装配内联AT& T型不匹配

时间:2017-07-13 19:07:18

标签: gcc x86 inline-assembly att

我正在学习集会,但我发现什么都没有帮助我做到这一点。它甚至可能吗?我无法做到这一点。

我希望此代码采用" b"将值放入%eax,然后在我的输出中移动%eax的内容并打印该ASCII字符," 0"在这种情况下。

char a;
int b=48;
__asm__ ( 
//Here's the "Error: operand type mismatch for `mov'
"movl %0, %%eax;"
"movl %%eax, %1;"

:"=r"(a)
:"r" (b)
:"%eax"
);

printf("%c\n",a);

2 个答案:

答案 0 :(得分:0)

由于a被定义为字符(char a;),:"=r"(a)将分配一个8字节的寄存器。 32字节寄存器EAX无法加载8字节寄存器 - movl %dl, %eaxmovl %0, %%eax)将导致此错误。为此目的,在AT& T语法中有符号扩展和零扩展指令movzxmovsx(英特尔语法):movs...movz...

更改

movl %0, %%eax;

movzbl %0, %%eax;

答案 1 :(得分:0)

负责错误的指令是:

movl %0, %%eax

因此,为了弄清楚它导致错误的原因,我们需要了解它的含义。它是一个32位MOV指令(AT& T语法中的l后缀表示" long",又称DWORD)。目标操作数是32位EAX寄存器。源操作数是第一个输入/输出操作数a。换句话说,这个:

"=r"(a)

表示char a;将用作仅输出寄存器。

因此,内联汇编程序想要做的是生成如下代码:

movl %dl, %eax

(假设为了论证adl寄存器中分配,但它可以很容易地在任何8位寄存器中分配)。问题是,代码无效,因为操作数大小不匹配。源操作数和目标操作数的大小不同:一个是32位,另一个是8位。这不起作用。

解决方法是movzx / movsx指令(随80386引入),它将8(或16)位源操作数移动到32位目标操作数中,或者没有扩展名或符号扩展,分别。在AT& T语法中,将8位源移动到32位目的地的形式为movzbl(对于零扩展,与无符号值一起使用)或movsbl(用于符号扩展,使用带有签名的值。)

但等等 - 这是错误的解决方法。您的代码因其他原因无效:a未初始化!并且不仅a未初始化,而且您已通过输出约束告诉内联汇编程序它是仅输出操作数(=符号)!所以你无法从中读取 - 你只能存储它。

你的操作数符号向后。你真正想要的是以下内容:

__asm__( 
        "movl %1, %%eax;"
        "movl %%eax, %0;"

        : "=r"(a)
        : "r" (b)
        : "%eax"
       );

当然,这仍然会给你一个操作数大小不匹配,但它现在在第二个汇编指令上。这告诉内联汇编器要发出的是以下代码:

    movl $48,  %edx
    movl %edx, %eax
    movl %eax, %dl

无效,因为32位源(%eax)无法移动到8位目标(%dl)。而且您无法使用movzx / movsx解决此问题,因为这用于扩展,而不是截断。写这个的方法如下:

    movl $48,  %edx
    movl %edx, %eax
    movb %al,  %dl

其中最后一条指令是8位移位,从8位源寄存器到8位目标寄存器。

在内联汇编中,这将写为:

__asm__( 
        "movl %1, %%eax;"
        "movb %%al, %0;"

        : "=r"(a)
        : "r" (b)
        : "%eax"
       );

但是,这不是使用内联汇编的正确方法。您已手动对内联汇编块内的EAX寄存器进行硬编码,这意味着您必须对其进行破坏。这样做的问题在于,当涉及寄存器分配时,它将编译器的手绑在背后。您假设要执行的操作是将所有进出内联汇编块的内容放入输入和输出操作数中。这使编译器能够以最佳方式处理所有寄存器分配。代码 应如下所示:

char a;
int  b = 48;
int temp;
__asm__( 
        "movl %2, %0\n\t"
        "movb %b0, %1"

        : "=r"(temp),
          "=r"(a)
        : "r" (b)
        :
       );

这里发生了很多变化:

  • 我介绍了另一个临时变量(适当地命名为temp)并将其添加到仅输出操作数列表中。这会导致编译器自动为它分配一个寄存器,然后我们在asm块中使用它。
  • 既然我们让编译器进行了寄存器分配,我们就不需要一个代码清单,这样就不用了。
  • b指令的源操作数需要movb修饰符,以确保使用该寄存器的字节大小部分,而不是整个32位寄存器。
  • 我没有在每个asm指令的末尾使用分号,而是使用\n\t(除了最后一个)。这是推荐用于内联汇编块的内容,它可以使您获得更好的汇编输出列表,因为它与编译器内部的匹配。

更好的是为操作数引入符号名称,使代码更具可读性:

char a;
int  b = 48;
int temp;
__asm__( 
        "movl %[input], %[temp]\n\t"
        "movb %b[temp], %[dest]"

        : [temp]  "=r"(temp),
          [dest]  "=r"(a)
        : [input] "r" (b)
        :
       );

而且,此时,如果您还没有注意到,那么您会发现此代码非常愚蠢。你不需要所有那些临时工和注册注册改组。你可以这样做:

    movl $48, %eax

且值48已在al中,因为al是32位寄存器eax的低8位。

或者,您可以这样做:

    movb $48, %al

这只是将值48明确移动到8位寄存器al中的8位移动。

但实际上,如果您正在调用printf,则参数必须作为int传递(而不是char,因为它是一种可变函数),所以你绝对想要:

    movl $48, %eax

当您开始使用内联汇编时,编译器无法通过它轻松进行优化,因此您会得到效率低下的代码。你真正需要的只是:

int a = 48;
printf("%c\n",a);

产生以下汇编代码:

    pushl   $48
    pushl   $AddressOfFormatString
    call    printf
    addl    $8, %esp

或等同于:

    movl    $48, %eax
    pushl   %eax
    pushl   $AddressOfFormatString
    call    printf
    addl    $8, %esp

现在,我想你会对自己说:"是的,但如果我这样做,那么我就不使用内联汇编!" 我的回答是:完全。你不需要在这里进行内联汇编,事实上,你应该使用它,因为它只会导致问题。编写起来比较困难,导致代码生成效率低下。

如果您想学习汇编语言编程,请获取汇编程序并使用它 - 而不是C编译器的内联汇编程序。 NASM是一种受欢迎且出色的选择,YASM也是如此。如果你想坚持使用Gnu汇编程序,那么你可以坚持使用这种曲折的AT& T语法,然后运行as