我正在运行下面的代码,并遇到两个问题:
1]当我将movl(从寄存器中复制值)更改为movq时,我遇到gcc错误:Error: operand size mismatch for movq
。在普通程序集中,我看到可以通过添加qword前缀或点赞来实现,但这也无法满足gcc
uint64_t cpuid_0(uint64_t* _rax, uint64_t* _rbx, uint64_t* _rcx, uint64_t* _rdx){
int a, b, c, d;
*_rax = 0x0;
__asm__
__volatile__
(
"movq $0, %%rax\n"
"cpuid\n"
"movl %%eax, %0\n"
"movl %%ebx, %1\n"
"movl %%ecx, %2\n"
"movl %%edx, %3\n"
: "=r" (a), "=r" (b), "=r" (c), "=r" (d)
: "0" (a)
);
*_rax=a;*_rbx=b;*_rcx=c;*_rdx=d;
return *_rax;
}
2)我想消除多余的复制操作,所以我在约束规范中修改了代码:
uint64_t cpuid_0(uint64_t* _rax, uint64_t* _rbx, uint64_t* _rcx, uint64_t* _rdx){
int a, b, c, d;
*_rax = 0x0;
__asm__
__volatile__
(
"movq $0, %%rax\n"
"cpuid\n"
"movl %%eax, %0\n"
"movl %%ebx, %1\n"
"movl %%ecx, %2\n"
"movl %%edx, %3\n"
: "+m" (*_rax), "=m" (*_rbx), "=m" (*_rcx), "=m" (_rdx)
: "0" (*_rax)
);
*_rax=a;*_rbx=b;*_rcx=c;*_rdx=d;
return *_rax;
}
这给了我很多类似以下的错误:
warning: matching constraint does not allow a register
error: inconsistent operand constraints in an ‘asm’
而且,我认为可以在此小代码中删除__volatile__
。
这是输入"0" (*_rax)
,正在修正它……看来"0"
不适用于"=m"
内存约束,也不适用于"+m"
。 (我不知道为什么。)
更改您的第二个函数进行编译和工作:
uint32_t cpuid_0(uint32_t* _eax, uint32_t* _ebx, uint32_t* _ecx, uint32_t* _edx) { __asm__ ( "mov $0, %%eax\n" "cpuid\n" "mov %%eax, %0\n" "mov %%ebx, %1\n" "mov %%ecx, %2\n" "mov %%edx, %3\n" : "=m" (*_eax), "=m" (*_ebx), "=m" (*_ecx), "=m" (*_edx) : //"0" (*_eax) -- not required and throws errors !! : "%rax", "%rbx", "%rcx", "%rdx" // ESSENTIAL "clobbers" ) ; return *_eax ; }
其中:
为了保持一致,将所有内容都设为uint32_t。
丢弃冗余int a, b, c, d;
省略"0"
输入,在任何情况下都不会使用。
声明(*_eax)
的简单“ = m”输出
“ clobbers”全部为“%rax”,“%rbx”,“%rcx”,“%rdx”
丢弃冗余volatile
最后一个是基本
以上编译为:
push %rbx # compiler (now) knows %rbx is "clobbered" mov %rdx,%r8 # likewise %rdx mov %rcx,%r9 # ditto %rcx mov $0x0,%eax # the __asm__(.... cpuid mov %eax,(%rdi) mov %ebx,(%rsi) mov %ecx,(%r8) mov %edx,(%r9) # ....) ; mov (%rdi),%eax pop %rbx retq
注意:没有“ clobbers”编译为:
mov $0x0,%eax cpuid mov %eax,(%rdi) mov %ebx,(%rsi) mov %ecx,(%rdx) mov %edx,(%rcx) mov (%rdi),%eax retq
较短,但可惜不起作用!
您也可以(第2版):
struct cpuid { uint32_t eax ; uint32_t ebx ; uint32_t ecx ; uint32_t edx ; }; uint32_t cpuid_0(struct cpuid* cid) { uint32_t eax ; __asm__ ( "mov $0, %%eax\n" "cpuid\n" "mov %%ebx, %1\n" "mov %%ecx, %2\n" "mov %%edx, %3\n" : "=a" (eax), "=m" (cid->ebx), "=m" (cid->ecx), "=m" (cid->edx) :: "%ebx", "%ecx", "%edx" ) ; return cid->eax = eax ; }
编译为稍微短一点的东西:
push %rbx mov $0x0,%eax cpuid mov %ebx,0x4(%rdi) mov %ecx,0x8(%rdi) mov %edx,0xc(%rdi) pop %rbx mov %eax,(%rdi) retq
或者您可以做一些更像您的第一个版本(版本3:):
uint32_t cpuid_0(struct cpuid* cid) { uint32_t eax, ebx, ecx, edx ; eax = 0 ; __asm__(" cpuid\n" : "+a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)); cid->edx = edx ; cid->ecx = ecx ; cid->ebx = ebx ; return cid->eax = eax ; }
编译为:
push %rbx xor %eax,%eax cpuid mov %ebx,0x4(%rdi) mov %edx,0xc(%rdi) pop %rbx mov %ecx,0x8(%rdi) mov %eax,(%rdi) retq
此版本使用
"+a"
,"=b"
等魔术来告诉编译器将特定的寄存器分配给各个变量。这将汇编程序的数量减少到最低限度,这通常是一件好事。 [请注意,编译器知道xor %eax,%eax
比mov $0,%eax
更好(或更短),并且认为更早地进行pop %rbx
有一些好处。]
更好-@Peter Cordes发表评论(第4版):
uint32_t cpuid_1(struct cpuid* cid) { __asm__ ( "xor %%eax, %%eax\n" "cpuid\n" : "=a" (cid->eax), "=b" (cid->ebx), "=c" (cid->ecx), "=d" (cid->edx) ) ; return cid->eax ; }
编译器指出
cid->eax
已经在%eax
中,因此编译为:
push %rbx xor %eax,%eax cpuid mov %ebx,0x4(%rdi) mov %eax,(%rdi) pop %rbx mov %ecx,0x8(%rdi) mov %edx,0xc(%rdi) retq
与版本3相同,除了指令顺序上的细微差别。
FWIW:__asm__()
定义为:
asm
asm限定词
(
AssemblerTemplate:
OutputOperands [:
InputOperands [:
Clobbers]] [C0 ]内联汇编程序的关键是要了解编译器:
具有不知道
AssemblerTemplate部分的含义。它确实扩展了)
占位符,但不理解nothing
does
了解OutputOperands...这些告诉编译器汇编器需要什么参数,以及如何扩展各种%xx
。
...但是这些also
告诉编译器AssemblerTemplate does的含义,用编译器可以理解的术语。因此,编译器理解的是一种“数据流”。据了解,汇编器接受多个输入,返回多个输出,并且(可能)作为副作用“浪费”一些寄存器和/或内存量。有了这些信息,编译器可以将“黑匣子”汇编程序序列与其周围生成的代码集成在一起。编译器将执行以下操作:
为输出和输入操作数分配寄存器
并且将输入安排在所需的寄存器中(根据需要)。>>
NB:编译器将汇编程序视为单个操作,在此操作中,所有输入都在生成任何输出之前被消耗。如果在%xx
之后不使用输入,则编译器可以将给定的寄存器分配为输入和输出。因此需要所谓的“早期破坏者”。
在周围的代码周围移动“黑匣子”,保持汇编程序对其输入源的依赖以及后续代码对汇编程序输出的依赖。
如果似乎什么都不依赖于其输出,则完全丢弃“黑匣子”!