GCC内联-推送地址,而不是堆栈的值

GCC内联-推送地址,而不是堆栈的值,c,gcc,inline-assembly,att,C,Gcc,Inline Assembly,Att,我正在试验GCC的内联汇编程序(我使用MinGW,我的操作系统是Win7)。 现在我只需要一些基本的C stdlib函数就可以了。我通常熟悉英特尔的语法,但对AT&T来说是新手 下面的代码工作得很好: char localmsg[] = "my local message"; asm("leal %0, %%eax" : "=m" (localmsg)); asm("push %eax"); asm("call %0" : : "m" (puts)); asm("add $4,%esp");

我正在试验GCC的内联汇编程序(我使用MinGW,我的操作系统是Win7)。 现在我只需要一些基本的C stdlib函数就可以了。我通常熟悉英特尔的语法,但对AT&T来说是新手

下面的代码工作得很好:

char localmsg[] = "my local message";
asm("leal %0, %%eax" : "=m" (localmsg));
asm("push %eax");
asm("call %0" : : "m" (puts));
asm("add $4,%esp");
然而,LEA似乎是多余的,因为我可以直接将值推到堆栈上。好吧,由于我认为AT&T的一个特点,这样做:

asm("push %0" : "=m" (localmsg));
将在最终可执行文件中生成以下汇编代码:

PUSH DWORD PTR SS:[ESP+1F]
因此,不是将地址推送到我的字符串,而是推送到它的内容,因为“指针”在C术语中是“未引用的”。这显然会导致崩溃

我相信这只是GAS的正常行为,但我找不到任何关于如何克服这一问题的信息。我非常感谢你的帮助

另外,我知道对于那些有经验的人来说,这是一个微不足道的问题。我预计会被否决,但我只是花了45分钟寻找解决方案,却一无所获


p.p.S.我意识到正确的方法是在C代码中调用
put()
。这纯粹是出于教育/实验原因。

虽然内联asm总是有点棘手,但从中调用函数尤其具有挑战性。这不是我对“了解内联asm”项目的建议。如果您还没有,我建议查看最新的内联asm。为了解释内联asm是如何工作的,已经做了很多工作

也就是说,这里有一些想法:

1) 像这样使用多个asm语句是个坏主意。正如上面所说:不要期望asm语句序列在编译后保持完全连续。如果某些指令需要在输出中保持连续,请将它们放在单个多指令asm语句中

2) 直接修改寄存器(就像您正在使用eax)而不让gcc知道您正在这样做也是一个坏主意。您应该使用寄存器约束(这样gcc就可以选择自己的寄存器)或者使用clobbers让gcc知道您正在踩它们

3) 当调用函数(如puts)时,虽然某些寄存器必须在返回前恢复其值,但被调用函数可以将某些寄存器视为暂存寄存器(即在返回前修改和不恢复)。正如我在#2中提到的,让asm在不通知gcc的情况下修改寄存器是一个非常糟糕的主意。如果知道所调用函数的ABI,可以将其暂存寄存器添加到asm的clobber列表中

4) 在这个特定的示例中,您使用的是常量字符串,作为一般规则,在向字符串、结构、数组等传递asm指针时,您可能需要“内存”缓冲区,以确保在开始执行asm之前执行任何挂起的内存写入

5) 实际上,
lea
正在做一些非常重要的事情。esp的值在编译时是未知的,因此您不能执行
推送$12345
。在将其推送到堆栈上之前,需要有人计算(esp+localmsg的偏移量)。另外,请参见下面的第二个示例

6) 如果您更喜欢英特尔格式(哪个思维正常的人不会?),可以使用-masm=intel

考虑到所有这些,我对这段代码的第一次剪切看起来是这样的。请注意,这不会破坏“暂存器”寄存器。这只是一个练习

#include <stdio.h>

int main()
{
  const char localmsg[] = "my local message";

  int result;

  /* Use 'volatile' since 'result' is usually not going to get used,
     which might tempt gcc to discard this asm statement as unneeded. */

  asm volatile ("push %[msg] \n\t"   /* Push the address of the string. */
                "call %[puts] \n \t" /* Call the print function. */
                "add $4,%%esp"       /* Clean up the stack. */

                : "=a" (result) /* The result code from puts. */
                : [puts] "m" (puts), [msg] "r" (localmsg)
                : "memory", "esp");

   printf("%d\n", result);
}
作为一个全局函数,localmsg的地址现在在编译时是已知的(好的,我简化了一点),生成的asm如下所示:

push $__ZL8localmsg
call _puts
add $4,%esp

塔达。

执行
asm(“推送%0”:“=m”(&localmsg))工作?@markgz否,它抱怨“asm语句中需要左值”。无论如何,我认为这没有多大意义,因为localmsg已经是一个指针。为什么要将localmsg标记为输出操作数?@marglisse我错了,我不明白第一个冒号总是用于输出,而不管修饰符是什么。不过,这对我的问题没有影响。回答得很好。我确实非常喜欢英特尔的语法,但我不确定如何让它与扩展的语法和变量一起工作。我给你的内联asm文档的文档链接谈到了如何使用变量(有许多示例)。如果您来自MS的编译器,您需要忘记在asm中使用符号名的想法。(实际上)所有变量都需要指定为输入或输出。至于“让英特尔发挥作用”,那没什么。考虑这个(无用的)ASM语句:<代码> ASM(“MOVL $ 0,%EAX”);<代码>。使用
gcc foo.cpp
编译,它将编译无误。使用
gcc-masm=intel foo.cpp编译,除非将其更改为英特尔语法:
asm(“mov eax,0”)是的,但我的意思是“参数”“传递”到内联ASM代码,就像在您帮助我的示例中一样。ATT中的[name]或%0,我应该如何使其在Intel中工作?完全相同([name]或%0)。这些不是att的工件,它们只是gcc将参数放入asm中适当位置的方式。将第一个参数中的字符串视为fprintf的模板。当编译器编译asm语句时,它只接受所有%0和%[name],替换约束中的内容并将结果字符串输出到汇编程序。使用
。英特尔语法
不会将c编译器“切换”为使用英特尔格式。就c而言,这只是另一个向汇编程序输出的任意字符串(这确实改变了汇编程序解释代码的方式)。如果要更改编译器输出的代码的格式,需要使用-masm。理论上,您可以使用
.intel\u syntax/.att\u syntax
包装所有asm字符串,但这可能会带来其他问题。使用-masm有什么问题?或者你在写公共标题?一个asm语句可以同时支持这两个语句:asm({mov$0,%0 | mov%0,0})。。。
push $__ZL8localmsg
call _puts
add $4,%esp