从 C 访问 CPU 寄存器
Accessing CPU Registers from C
最近我一直在研究 C 中的内联汇编,想知道我是否可以直接从变量访问寄存器
像这样:
volatile uint64_t* flags = RFLAGS;
其中 RFLAGS 是 CPU 标志寄存器。很明显,上面的代码无法通过编译,但我想知道是否有类似的方法可以达到预期的效果。
使用 gcc
为 Ubuntu x86_64 编译
是的,当然。您可以 PUSHF
、PUSHFD
或 PUSHFQ
标志并将它们弹出到另一个寄存器中。例如:
unsigned int flags;
__asm{
pushfd
pop edx
mov flags, edx
}
对于 Ubuntu 下使用 AT&T 语法的 gcc,您可能会发现以下内容更直接可用:
unsigned int flags:
__asm__("pushf\n\t"
"pop edx\n\t"
"movl edx, flags");
您可以在那里随意查看它们!
您可以通过内联汇编获取标志寄存器的值,但此操作没有用,因为您无法控制相对于其他操作的访问顺序。特别是,您可能希望某些特定算术运算产生的标志在 asm 块的开头可用,但无法向编译器表达该约束。例如,假设您写道:
z = x + y;
__asm__ ( "pushf ; pop %0" : "=r"(flags) );
您可能希望通过添加产生的标志可用。但是,编译器可能选择了:
- 在 asm 之后重新排序算术,因为两者都没有依赖于另一个的结果。
- 用
add
/sub
调整堆栈指针,破坏标志。
- 使用
lea
而不是 add
来实现加法,不产生标志。
- 完全根据未使用结果的确定省略加法。
- 等等
同样的原则适用于访问任何可能被编译器生成的代码修改的寄存器。 是一种语法(在GCC/"GNU C"中)用于访问不受此问题影响的寄存器;它看起来像:
register int var __asm__("regname");
其中 regname
替换为寄存器的名称。这在大多数目标上基本上没有用,但它可以让您控制寄存器使用 input/output 对 asm 的约束,并且一些目标具有永久保存在通用寄存器中的特殊值(线程本地存储指针是最common) 这在某些情况下可能很有用。
最近我一直在研究 C 中的内联汇编,想知道我是否可以直接从变量访问寄存器
像这样:
volatile uint64_t* flags = RFLAGS;
其中 RFLAGS 是 CPU 标志寄存器。很明显,上面的代码无法通过编译,但我想知道是否有类似的方法可以达到预期的效果。
使用 gcc
为 Ubuntu x86_64 编译是的,当然。您可以 PUSHF
、PUSHFD
或 PUSHFQ
标志并将它们弹出到另一个寄存器中。例如:
unsigned int flags;
__asm{
pushfd
pop edx
mov flags, edx
}
对于 Ubuntu 下使用 AT&T 语法的 gcc,您可能会发现以下内容更直接可用:
unsigned int flags:
__asm__("pushf\n\t"
"pop edx\n\t"
"movl edx, flags");
您可以在那里随意查看它们!
您可以通过内联汇编获取标志寄存器的值,但此操作没有用,因为您无法控制相对于其他操作的访问顺序。特别是,您可能希望某些特定算术运算产生的标志在 asm 块的开头可用,但无法向编译器表达该约束。例如,假设您写道:
z = x + y;
__asm__ ( "pushf ; pop %0" : "=r"(flags) );
您可能希望通过添加产生的标志可用。但是,编译器可能选择了:
- 在 asm 之后重新排序算术,因为两者都没有依赖于另一个的结果。
- 用
add
/sub
调整堆栈指针,破坏标志。 - 使用
lea
而不是add
来实现加法,不产生标志。 - 完全根据未使用结果的确定省略加法。
- 等等
同样的原则适用于访问任何可能被编译器生成的代码修改的寄存器。 是一种语法(在GCC/"GNU C"中)用于访问不受此问题影响的寄存器;它看起来像:
register int var __asm__("regname");
其中 regname
替换为寄存器的名称。这在大多数目标上基本上没有用,但它可以让您控制寄存器使用 input/output 对 asm 的约束,并且一些目标具有永久保存在通用寄存器中的特殊值(线程本地存储指针是最common) 这在某些情况下可能很有用。