简单 g++ 内联汇编程序中的错误
Posted
技术标签:
【中文标题】简单 g++ 内联汇编程序中的错误【英文标题】:Error in simple g++ inline assembler 【发布时间】:2017-01-25 12:49:10 【问题描述】:我正在尝试编写一个“hello world”程序来测试 g++ 中的内联汇编程序。 (仍然倾向于 AT&T 语法)
代码是:
#include <stdlib.h>
#include <stdio.h>
# include <iostream>
using namespace std;
int main()
int c,d;
__asm__ __volatile__ (
"mov %eax,1; \n\t"
"cpuid; \n\t"
"mov %edx, $d; \n\t"
"mov %ecx, $c; \n\t"
);
cout << c << " " << d << "\n";
return 0;
我收到以下错误:
inline1.cpp: Assembler messages:
inline1.cpp:18: Error: unsupported instruction `mov'
inline1.cpp:19: Error: unsupported instruction `mov'
你能帮我完成吗?
Tks
【问题讨论】:
AT&T 语法几乎所有操作数都颠倒了。不是dst, src
,而是src, dst
。您的代码破坏者注册但不使用扩展的汇编器模板来告诉 GCC。您是否考虑过 CPUID 内在函数?
如果您read more about the GAS AT&T syntax,您很快就会了解到大多数指令都有一个后缀来说明操作的大小。例如movb
移动一个字节。当然(如前所述)颠倒操作数的顺序。
如果你想学习汇编,我强烈建议不要使用内联汇编。 GCC 很难做到正确,并且充满了gotchyas。您总是可以编写一个单独的汇编文件并将其汇编并与您的 C 代码链接。 gcc.gnu.org/wiki/DontUseInlineAsm
int c,d; int a = 1; __asm__ __volatile__ ( "cpuid" : "=c"(c), "=d"(d), "+a"(a) :: "ebx" );
gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
【参考方案1】:
您的汇编代码无效。请仔细阅读Extended Asm。这是另一个good overview。 这是一个CPUID示例代码from here:
static inline void cpuid(int code, uint32_t* a, uint32_t* d)
asm volatile ( "cpuid" : "=a"(*a), "=d"(*d) : "0"(code) : "ebx", "ecx" );
注意格式:
首先是:
,然后是输出操作数:: "=a"(*a), "=d"(*d)
; "=a"
是 eax
和 "=b
是 ebx
第二个:
后跟输入操作数:: "0"(code)
; "0"
表示code
应该与输出操作数0
占据相同的位置(在这种情况下为eax
)
第三个:
后跟被破坏的寄存器列表:: "ebx", "ecx"
我收到以下错误:
...
...错误:不支持的指令“mov”
在mov
指令的情况下,由于您没有指定后缀,GAS 将尝试推导出一个。看起来 GAS 无法这样做,因此出现错误。
This GAS 语法指南说:
如果没有指定后缀,并且没有内存操作数 指令,GAS 从 目标寄存器操作数(最终操作数)。
【讨论】:
movl 和 -m32 不起作用。仍在尝试了解代码的作用:-) @Chocksmith 再次重写了答案,希望它现在更有帮助:) “您的目标是 64 位,而 mov 需要 64 位类型。” 不,在 GAS 语法中,mov
需要指令后缀指示的大小预计。我想你可能知道这一点,但你的答案的底部要么令人困惑,要么是错误的。
是的,好多了。不幸的是,downvote 不是我的,所以我不能删除它!据推测,它无法推断出 movl
的原因是因为 (A) 操作数的顺序错误,并且 (B) 寄存器必须用 double @ 转义987654343@.
非常有用!感谢您的回答!【参考方案2】:
我将@AMA 的答案保留为已接受的答案,因为它已经足够完整了。但我考虑了一下,得出的结论是它不是 100% 正确的。
我试图在 GCC 中实现的代码是下面的代码(Microsoft Visual Studio 版本)。
int c,d;
_asm
mov eax, 1;
cpuid;
mov d, edx;
mov c, ecx;
当eax设置为1的cpuid执行时,在ecx和edx中返回特征信息。
建议的代码返回来自 eax ("=a") 和 edx (="d") 的值。 这可以在 gdb 上轻松看到:
(gdb) disassemble cpuid
Dump of assembler code for function cpuid(int, uint32_t*, uint32_t*):
0x0000000000000a2a <+0>: push %rbp
0x0000000000000a2b <+1>: mov %rsp,%rbp
0x0000000000000a2e <+4>: push %rbx
0x0000000000000a2f <+5>: mov %edi,-0xc(%rbp)
0x0000000000000a32 <+8>: mov %rsi,-0x18(%rbp)
0x0000000000000a36 <+12>: mov %rdx,-0x20(%rbp)
0x0000000000000a3a <+16>: mov -0xc(%rbp),%eax
0x0000000000000a3d <+19>: cpuid
0x0000000000000a3f <+21>: mov -0x18(%rbp),%rcx
0x0000000000000a43 <+25>: mov %eax,(%rcx) <== HERE
0x0000000000000a45 <+27>: mov -0x20(%rbp),%rax
0x0000000000000a49 <+31>: mov %edx,(%rax) <== HERE
0x0000000000000a4b <+33>: nop
0x0000000000000a4c <+34>: pop %rbx
0x0000000000000a4d <+35>: pop %rbp
0x0000000000000a4e <+36>: retq
End of assembler dump.
生成更接近我想要的东西的代码是(根据对 cmets 的反馈进行编辑):
static inline void cpuid2(uint32_t* d, uint32_t* c)
int a = 1;
asm volatile ( "cpuid" : "=d"(*d), "=c"(*c), "+a"(a) :: "ebx" );
结果是:
(gdb) disassemble cpuid2
Dump of assembler code for function cpuid2(uint32_t*, uint32_t*):
0x00000000000009b0 <+0>: push %rbp
0x00000000000009b1 <+1>: mov %rsp,%rbp
0x00000000000009b4 <+4>: push %rbx
0x00000000000009b5 <+5>: mov %rdi,-0x20(%rbp)
0x00000000000009b9 <+9>: mov %rsi,-0x28(%rbp)
0x00000000000009bd <+13>: movl $0x1,-0xc(%rbp)
0x00000000000009c4 <+20>: mov -0xc(%rbp),%eax
0x00000000000009c7 <+23>: cpuid
0x00000000000009c9 <+25>: mov %edx,%esi
0x00000000000009cb <+27>: mov -0x20(%rbp),%rdx
0x00000000000009cf <+31>: mov %esi,(%rdx)
0x00000000000009d1 <+33>: mov -0x28(%rbp),%rdx
0x00000000000009d5 <+37>: mov %ecx,(%rdx)
0x00000000000009d7 <+39>: mov %eax,-0xc(%rbp)
0x00000000000009da <+42>: nop
0x00000000000009db <+43>: pop %rbx
0x00000000000009dc <+44>: pop %rbp
0x00000000000009dd <+45>: retq
End of assembler dump.
为了清楚起见...我知道有更好的方法来做到这一点。但这里的目的纯粹是教育。只是想了解它是如何工作的;-)
--已编辑(删除个人意见)--
【讨论】:
这段代码只是碰巧生成你想要的东西,完全靠运气。最后一个参数上的"0"(1)
意味着它将使用与第 0 个相同的约束,即 "=d"(*d)
这意味着 1 将通过寄存器 EDX 传递到汇编程序模板中,但你真的需要通过 EAX 传递值 1!你很幸运值 1 已经在 EAX 中,然后复制到 EDX
任何寄存器(或内存)被修改但不为 GCC 所知(通过输出约束)必须在 clobber 列表中指定。 CPUID 会破坏 EAX、EBX、ECX、EDX 中的值。你还没有告诉 GCC 它们都被覆盖了。
根据我在您原始问题下的评论中的代码并将其调整为在您在此答案中创建的函数内工作,以下代码将正常工作static inline void cpuid2(uint32_t* d, uint32_t* c) int a = 1; __asm__ __volatile__ ( "cpuid" : "=c"(*c), "=d"(*d), "+a"(a) :: "ebx" );
在这种情况下,ECX 和 EDX 被标记为输出,这很好。所以编译器知道不能依赖这两个寄存器具有与输入汇编模板之前相同的值。 "+a"
表示 EAX 中的值将用作输入和输出。输入的值将是 1(我们将它放在变量 a 中),因为它也被列为输出,编译器知道它不能依赖 EAX 中的值作为相同的。问题是 GCC 不知道 EBX 已被修改。您需要在clobber列表中单独指定。
你是对的。我不知道当 EAX=1 时 CPUID 也会覆盖 EAX。它记录在这里:x86.renejeschke.de/html/file_module_x86_id_45.html我将再次编辑答案以上是关于简单 g++ 内联汇编程序中的错误的主要内容,如果未能解决你的问题,请参考以下文章
GNU g++ 内联汇编块,如 Apple g++/Visual C++?