【问题标题】:Correct way to use Intel Debug Registers (GCC / inline assembly)正确使用英特尔调试寄存器的方法(GCC/内联汇编)
【发布时间】:2012-08-03 20:30:44
【问题描述】:

我一直在尝试使用英特尔调试寄存器,但我似乎做错了什么。我创建了一个非常简单的 Linux LKM 并尝试使用内联汇编来执行寄存器的琐碎操作。例如:

 __asm__ ("movl %eax, %db0");

我收到的错误消息表明我在做一些根本不正确的事情。例如:

Error: unsupported for `mov'

有人对如何使用这些寄存器有任何见解吗?

【问题讨论】:

  • 这适用于 x86 还是 x86-64 系统?如果是 64 位,则需要从 64 位寄存器 (rax) 移动。否则,请删除该标签以防止混淆。
  • 这适用于 x86-64 (rax) 和 x86 (eax)。这只是一个例子,我也尝试过自己对各种调试寄存器进行异或运算。选择这些标签是因为我将它们与低级主题相关联。
  • Xoring 永远不会起作用,因为调试寄存器只能通过mov 指令访问。问题是调试寄存器是drN,而不是dbN。在直接查看文档时,我几乎错过了这一点。
  • @ughoavgfhw,是的,寄存器是 DRN,但as 允许使用dbN 作为它们的名称,如manual 所示。在我的 32 位 OpenSUSE 12.1 机器上,GNU As 2.21.1 接受 drNdbN 作为这些寄存器的名称。

标签: gcc linux-kernel x86 x86-64


【解决方案1】:

这是一个语法错误 - gcc 的内联汇编器使用 % 作为操作数标识符,并且要在 x86 的寄存器名称中显式使用它,您必须编写:

__asm__ ("movl %%eax, %%db0\n\t");

这样就可以编译了。

更正:
这里有多个问题:

  1. __asm__ 没有clobbers 的语句是非常不寻常的(并且很少做你所期望的,因为在汇编中可以做的事情很少没有副作用)。尽管如此,当且仅当该指令实际上是无副作用且没有输入的,看起来可以省略 clobber 列表。
    这样做的一个后果是不再需要转义%。这个简单示例的编译器为__asm__("movl %eax, %db0\n\t"); 创建与为完全指定的__asm__("movl %%eax, %%db0\n\t":::); 创建相同的操作码。不过,这不一定是优势,因为 ...
  2. 第二个问题是便携性; x86_64 和 i386 相似但不相同,其中一个区别是调试 reg 的寄存器宽度。仅这一点就是真正使用内联汇编操作数而不是直接使用寄存器名称的一个特别好的理由,因为以下代码:
    __asm__("mov %0, %%db0\n\t" : : "a"((uintptr_t)0) : );
    在 64 位 (gcc -m64 ...) 和 32 位 (gcc -m32 ...) 上编译 - 它创建 相同 指令,但是在汇编中编写时使用的寄存器对于 64 位是不同的:
    208: 0f 23 c0 mov %rax,%db0
    在 32 位时,它确实:
    269: 0f 23 c0 mov %eax,%db0
    此处使用输入操作数提供了抽象寄存器宽度的能力(uintptr_t 来自 <inttypes.h> 并保证始终是完整的通用寄存器宽度),因此相同的内联汇编可用于 32 位和64 位编译。

我承认我不知道编译器将 clobber-list-less __asm__ 与“正常”此类语句区别对待。

在任何情况下,实际上您在修改调试寄存器时肯定需要一个参数/clobber 列表,因为对这些的访问是序列化指令,编译器应该隐式知道这些指令为memory破坏者。此外,您从它们那里读取/写入它们的值必须来自/来自某个地方......因此是输入/输出。

【讨论】:

  • 感谢您的快速回复。恐怕它似乎不起作用。结果是“错误:错误的寄存器名称 `%%eax'” - 我将进一步研究内联汇编,因为作为一个相对较新的人,我可能正在做一些有点雄心勃勃的事情!
  • 只有当程序集有操作数时才成立。没有它们,只有一个% 用于寄存器。
  • 感谢您的澄清。有人建议我调查 TRESOR 项目 (www1.informatik.uni-erlangen.de/tresor),将其作为读取和写入难以捉摸的调试寄存器的真实世界实现。如果/何时我可以确定他们的方法,我将审查来源并报告。
  • @JOgden,有趣的是:当我使用完整形式的内联汇编语句 (__asm__ ("movl %%eax, %%db0\n\t" : : : );) 时,它在我的 32 位机器上编译得很好。内核模块生成的二进制代码中的指令似乎是正确的:0f 23 c0。如果内联汇编语句在这里很重要,不知道为什么完整的形式。
【解决方案2】:

FrankH 的答案在 x64 模式下不起作用。可能会用到的是:

inline void setDebugReg( long v )
{
   __asm__ __volatile__ ("mov %0, %%db0\n\t" : : "r"(v) : ) ;
}

(您需要一个类似的 db7 asm 来设置观察点的标志)。

但是设置这些寄存器不能在用户空间完成,所以需要其他方法。这是使用 ptrace 执行此操作的示例代码,其中a process is forked to ptrace attach, change the registers, ptrace detach and then exit

【讨论】:

    猜你喜欢
    • 2017-07-02
    • 1970-01-01
    • 2011-07-20
    • 2021-05-20
    • 2021-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多