根据GCC的Extended ASM and Assembler Template,要保持指令连续,它们必须位于同一ASM块中。我很难理解是什么提供了对具有多个语句的块中的操作数进行读写的调度或计时。
例如,使用EBX
时必须保留RBX
或CPUID
,因为根据ABI,调用者拥有它。关于EBX
和RBX
的使用,还有一些悬而未决的问题,因此我们希望无条件地保留它(这是一个要求)。因此,需要将三个指令编码到单个ASM块中,以确保指令的连续性(例如:第一段中讨论的汇编模板):
unsigned int __FUNC = 1, __SUBFUNC = 0;
unsigned int __EAX, __EBX, __ECX, __EDX;
__asm__ __volatile__ (
"push %ebx;"
"cpuid;"
"pop %ebx"
: "=a"(__EAX), "=b"(__EBX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC)
);
如果表示操作数的表达式在错误的时间点被解释,那么
__EBX
将是保存的EBX
(而不是CPUID
的EBX
),这很可能是指向全局偏移量的指针表(GOT)(如果启用了PIC)。该表达式确切地在何处指定将
CPUID
的%EBX
存储到__EBX
中(1)在PUSH %EBX
之后; (2)在CPUID
之后;但是(3)在POP %EBX
之前? 最佳答案
在您的问题中,您需要提供执行push
和pop
为ebx
的一些代码。在使用ebx
(位置无关代码)用gcc进行编译的情况下保存-fPIC
的想法是正确的。在这种情况下,返回时不要破坏ebx
是我们的职责。不幸的是,您使用显式使用ebx
定义约束的方式。通常,如果您使用的是PIC代码并且将=b
指定为输出约束,则编译器会警告您(错误:“ asm”中的操作数约束不一致)。为什么它不会为您发出警告,这很不寻常。
要解决此问题,您可以让汇编器模板为您选择一个寄存器。不用压入和弹出,我们只需将%ebx
与编译器选择的未使用寄存器交换,然后通过将其交换回来来恢复它。由于我们不希望在交换期间让编译器破坏我们的输入寄存器,因此我们指定了早期的clobber修饰符,因此最终以=&r
约束(而不是OPs代码中的=b
)结束。可以在here上找到更多有关修饰符的信息。您的代码(32位)如下所示:
unsigned int __FUNC = 1, __SUBFUNC = 0;
unsigned int __EAX, __EBX, __ECX, __EDX;
__asm__ __volatile__ (
"xchgl\t%%ebx, %k1\n\t" \
"cpuid\n\t" \
"xchgl\t%%ebx, %k1\n\t"
: "=a"(__EAX), "=&r"(__EBX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC));
如果打算针对X86_64(64位)进行编译,则需要保存
%rbx
的全部内容。上面的代码将无法正常工作。您将必须使用类似:uint32_t __FUNC = 1, __SUBFUNC = 0;
uint32_t __EAX, __ECX, __EDX;
uint64_t __BX; /* Big enough to hold a 64 bit value */
__asm__ __volatile__ (
"xchgq\t%%rbx, %q1\n\t" \
"cpuid\n\t" \
"xchgq\t%%rbx, %q1\n\t"
: "=a"(__EAX), "=&r"(__BX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC));
您可以使用条件编译来处理X86_64和i386:
uint32_t __FUNC = 1, __SUBFUNC = 0;
uint32_t __EAX, __ECX, __EDX;
uint64_t __BX; /* Big enough to hold a 64 bit value */
#if defined(__i386__)
__asm__ __volatile__ (
"xchgl\t%%ebx, %k1\n\t" \
"cpuid\n\t" \
"xchgl\t%%ebx, %k1\n\t"
: "=a"(__EAX), "=&r"(__BX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC));
#elif defined(__x86_64__)
__asm__ __volatile__ (
"xchgq\t%%rbx, %q1\n\t" \
"cpuid\n\t" \
"xchgq\t%%rbx, %q1\n\t"
: "=a"(__EAX), "=&r"(__BX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC));
#else
#error "Unknown architecture."
#endif
GCC具有在
__cpuid
中定义的cpuid.h
宏。它定义了宏,以便仅在需要时保存ebx
和rbx
寄存器。您可以在此处找到GCC 4.8.1宏定义,以了解它们如何处理cpuid.h中的cpuid
。精明的读者可能会问这个问题-是什么阻止编译器选择
ebx
或rbx
作为暂存寄存器进行交换。编译器在PIC上下文中了解ebx
和rbx
,并且不允许将其用作暂存器。这是基于我多年来的个人观察并回顾了从C代码生成的汇编器(.s)文件。我不能肯定地说更古老的gcc版本如何处理它,所以这可能是一个问题。关于c - 如何确保对扩展ASM的操作数进行读写操作?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32099922/