【问题标题】:How to set/clear TF flag on x86 IA32 Intel CPU in user-mode如何在用户模式下设置/清除 x86 IA32 Intel CPU 上的 TF 标志
【发布时间】:2018-07-19 06:23:14
【问题描述】:

我想知道在 x86 IA32 Intel CPU 的用户模式下设置/清除 EFLAGS.TF 的步骤

在下面尝试清除 TF 标志,但出现错误 ***** Unhandled interrupt vector *****

__asm__ volatile("pushl %eax\n\t"
                        "pushfl\n\t"
                        "popl %eax\n\t"
                        "xorl $0x0100, %eax\n\t"
                        "pushl %eax\n\t"
                        "popfl\n\t"
                        "popl %eax\n\t");

【问题讨论】:

  • 对于清除,您需要确保它已设置或在位掩码的未标记版本上使用 OR。
  • 谢谢 WIZZ 最初我尝试使用“和”如下
  • 谢谢 WIZZ 最初我尝试使用“and”,如下所示,它不起作用“and $0xFEFF, %eax\n\t”;但是对于 EFlags,我们需要正确使用“andl”,在这种情况下,我猜下面可能是正确的,如果我错了,请纠正我“andl $0xFFFFFEFF,%eax;”有了这个改变,我没有得到例外,谢谢
  • 你是对的;我不知道为什么我建议对它进行 ORing ......这样,下次有人遇到这个问题时,他们会找到这个问题并立即得到答案。

标签: x86 inline-assembly eflags


【解决方案1】:

XOR 会翻转一点,而不是总是清除它。 AND 是一种选择,BTR (bit-test-reset) 是另一种选择。带有内存目标的 BTR 使用寄存器源确实很慢,但立即数一点也不差(Has​​well 上只有 2 微指令,Skylake 上只有 3 微指令。不过,AMD 上最多 4 微指令,即使 @987654323 也需要 2 微指令@.)

popf 非常慢(9 uop,在 Skylake 上每 20 个周期 1 个)。或者在 Ryzen 上,35 微指令,每 13 个周期一个。 (http://agner.org/optimize)。所以优化周围的代码不会有很大的不同,但是找到一种方法来保持代码大小的紧凑是很有趣的。

您不需要自己保存/恢复 EAX,只需告诉编译器您要使用 : "eax" 作为破坏列表来破坏它,或者使用虚拟输出操作数(请注意,我使用的是 GNU C 扩展 asm ,不是基本的)。

static inline
void clear_tf(void) {
    long dummy;       // there's no type that's always 32-bit on 32-bit, and always 64 on 64-bit.  x32 uses 32-bit pointers in long mode so uintptr_t or size_t doesn't work.
   // if porting to x86-64 System V user-space: beware that push clobbers the red-zone
    __asm__ volatile("pushf \n\t"
                     "pop   %[tmp] \n\t"
                     "btr   $9, %[tmp]\n\t"   // reset bit 9
                     "push  %[tmp] \n\t"
                     "popf"
                    : [tmp] "=r"(dummy)
                    : // no inputs
                    : // no clobbers.  // "memory" // would block reordering with loads/stores.
                );
}

或者干脆不碰任何寄存器:这也非常有效,尤其是在没有堆栈同步微指令和内存目标并且是单微指令的 AMD Ryzen 上。

static inline
void clear_tf(void) {
   // if porting to x86-64 System V user-space: beware that push clobbers the red-zone
    __asm__ volatile("pushf \n\t"
                     "andl $0xFFFFFEFF, (%esp) \n\t"  // 1 byte larger than the pop/btr/push version
                     "popf"
                );
    // Basic asm syntax: no clobbers.
}

对于较小的代码大小,btrl $9, (%esp) 可能很好。 Haswell 上仍然只有 2 微秒(Skylake 上 3 微秒),但比 andl 小 2 个字节。 andb $0xfe, 1(%esp) 的大小也相同,但在 push 之后使用时会导致存储转发停止,并且在 Intel 上为 2 微指令 + 堆栈同步微指令。 pop %%eax; and $0xfe, %ah; push %eax 的大小也相同,还有 3 个微指令(加上一个部分寄存器合并微指令,它在 Haswell / SKL 上循环发出)。但它在 AMD 上很好。


便携性

顺便说一句,在 x86-64 System V 用户空间代码中,如果不破坏编译器的红色区域,您将无法安全地推送/弹出,因此您可能希望在 push 之前将 add $-128, %rsp 输入,然后再将其恢复。

在内核代码中没有红色区域,因此内联 asm 中的 push/pop 很好。

Windows 使用不同的 ABI,没有红色区域。

【讨论】:

  • @MichaelPetch:x86-64 System V ABI 指定了一个红色区域。我没有意识到许多使用它的非 Linux 操作系统不支持红色区域,感谢您指出这一点。我想在代码的注释中添加一些内容,但没有足够的空间提供完整的细节。如果要“提防”它只需要确认您的操作系统不使用它,并且您的代码永远不会被移植到用户空间 Linux,那很好。
【解决方案2】:

使用下面的代码可以正常工作。谢谢

  __asm__ volatile("pushl %eax;\
                    pushfl;\
                    popl %eax;\
                    andl $0xFFFFFEFF, %eax;\
                    pushl %eax;\
                    popfl;\
                    popl %eax;"
                    );

【讨论】:

  • 如果你在 EAX 上声明了一个clobber,你不必在你的 asm 语句周围保存/恢复它。 asm("blah blah using %%eax extended-asm" ::: "%eax"); 或者更好的是,使用虚拟输出操作数让编译器选择一个寄存器供您弹出。或者不要碰eax,使用andl $0xFFFFFEFF, (%%esp)。或者对于较小的代码大小但存储转发停止,andb $0xFE, 1(%esp)。或者btrl $9, (%esp) 是代码大小和微指令之间的有用权衡,尽管popf 相当慢(因此它决定了成本)而且你可能不经常这样做。
猜你喜欢
  • 2019-06-27
  • 1970-01-01
  • 1970-01-01
  • 2021-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多