【问题标题】:C / Assembly: how to change a single bit in a CPU register?C /汇编:如何更改CPU寄存器中的单个位?
【发布时间】:2014-09-08 11:55:28
【问题描述】:

我是软件故障注入领域的新研究人员,目前我的最终目标是编写一段能够更改 CPU 寄存器中单个位的简单代码。我正在考虑在 C 中执行此操作(代码中包含一些汇编调用)。考虑到这一点,我在 Stack Overflow 中找到了这个很棒的线程和关于如何访问 32 位 CPU 寄存器内容的简单示例:Is it possible to access 32-bit registers in C? 这样,我就可以编写这个简单的代码:

#include <stdio.h>

int main()
{
    register int value;

    register int ecx asm("ecx");
    printf("Contents of ecx: %d\n", ecx);

    asm("movl %%ecx, %0;" : "=r" (value) : ); //Assembly: this stores the ecx value into the variable value
    printf("Contents of value: %d\n", value);

    return 0;
}

这似乎是对这个主题的一个很好的介绍,那里提供的答案给了我很好的洞察力和信息来源(我已经在阅读 GCC 文档),但现在我需要更进一步,我。例如,我需要了解如何更改 CPU 寄存器中单个位的内容(或者至少,首先要了解一些更简单的内容:如何更改 CPU 寄存器的值?)。如果有人能给我提示或告诉我寻找它的最合适的来源,我将不胜感激。

一切顺利并在此先感谢您, 若昂

P.S.:不知道这是否有帮助,但我正在使用 CentOS 6.5 32 位系统(尽管 CPU 是 64 位系统,更准确地说是 Intel Pentium Dual CPU E2180 @ 2.00 GHz)。另外,我以前接触过 Assembly,但就像 10 年前一样,在一个课程单元上呆了几个月,所以目前我正在尝试复习我对这门语言的了解。

【问题讨论】:

  • 是的,大多数编译器都有将汇编程序嵌入到另一种语言中的规定,但具体如何实现取决于编译器本身。
  • 我不是软件故障注入领域的研究人员,但我不得不怀疑专注于更改寄存器中的位的决定。为什么选择寄存器而不是变量?变量有可能(如果不常见)永远不会出现在寄存器中。也就是说,gcc 允许您直接访问代码中的程序集,这样您就可以轻松地踩在寄存器上(asm("mov $0, %%eax":))。显然这种事情通常是一个非常糟糕的想法,但如果你的目标是制造错误,那么你就去吧。要踩位,请查看 i386 程序集(也许是 BTC?)。
  • 我会使用 AND eax, 0xFFFE 取消设置位 0,OR eax 0x01 设置位 0 或 XOR eax, ecx 用于位翻转(ecx 包含 eax 并屏蔽了一些位)。
  • @dureuill 为什么不只用一个常数异或?顺便说一句,and eax, 0xFFFE 重置了一半以上的位。
  • 我认为这不是模拟硬件故障的最佳方式。使用内联汇编将修改程序中固定位置的寄存器,而不是像您期望的硬件故障那样的随机位置。它还会对程序生成产生影响。编译器要么围绕内联程序集生成不同的计数,要么将其优化掉,因为它认为它没有效果。使用调试 API(例如 ptrace)修改另一个正在运行的程序中的寄存器应该会给您带来更像硬件故障的东西。

标签: c gcc assembly inline-assembly cpu-registers


【解决方案1】:

在汇编中修改寄存器位子集的常用方法是使用带有常量的逻辑运算。

AND %eax, 0xFFFFFFFE 取消设置第 0 位。

OR %eax, 0x01 设置第 0 位。

XOR %eax, 0x01 翻转第 0 位。

(感谢@harold 的更正)。

话虽如此,正如 cmets 中所述,您可能不想在程序中直接使用内联汇编来模拟硬件故障。它将与您引入修改的地方紧密绑定,并且在源代码和二进制级别混合错误引入不是我推荐的方法(您的编译器可以并且将会重新组织和优化代码,因此很可能它最终会导致意外行为。请参阅 .Balakrishnan 和代表的“你看到的不是你执行的”。

您可以使用二进制检测平台,例如英特尔的 PIN 或现有的故障注入框架。

编辑: 由于 OP 要求在 cmets 中提供内联 asm 的“hello world”示例,因此它是:

#include <stdlib.h>
#include <stdio.h>

int main()
{
    register int eax asm("%eax");
    asm("xorl %eax, %eax");
    asm("xorl $1, %eax");
    printf("Content of eax: %d\n", eax);
    return 0;
}

另存为 test.c 然后编译:

gcc test.c -o test

或更好:

gcc test.c -S

这将创建 test.s,一个包含程序的汇编输出的文件。该文件将使您了解有关代码的许多内容,并且在使用内联 asm 时应在编译之前生成程序集(至少在您不理解某些内容时)。

然后您可以从程序集中组装二进制文件:

gcc test.s -o test

以下是关于 x86 的一些注意事项:

  • 在实际指令中,我将目的寄存器放在指令的末尾。
  • 立即数以“$”为前缀,以“%”注册
  • 操作扩展寄存器(32位寄存器,以“e”为前缀)的指令以“l”为后缀

【讨论】:

  • 感谢您的洞察力,dureuill。我知道这不是模拟硬件故障的最佳方法。对于该领域的新手来说,这更像是一个起点(一种“你好世界!”)。我试图完全理解你有用的 cmets / 答案,更准确地说是你的汇编代码,我可能会在这里公开我的疑问。再次,非常感谢!
  • 我现在对您的程序集有了更好的理解(对于像我这样多年未处理按位/逻辑运算符的人来说,这可能会变得很方便:link)。事实上,下面代码的行为(“分段错误(核心转储)”)并不是我“预期的”(可能你提到的“(...)最终导致意外行为”):#include &lt;stdio.h&gt; int main() { register int eax asm("eax"); asm("XOR %eax, 0x01"); //printf("Contents of eax: %d\n", eax_value); return 0; }对此有任何提示吗?谢谢!
  • 所以,如果我理解正确,您使用第一个 XOR 将 eax 的值设置为零,第二个 XOR 在同一个寄存器上进行位翻转(0x00000000 到 0x00000001),对? (这里的输出是“eax 的内容:1”)谢谢,我找不到更好的答案:]
  • 您理解正确。很高兴我的回答对你有用。
  • 至于你的第一个问题,我只想说 printf 也在使用 eax。至于您的第二个问题,您正在寻找二进制检测程序。
【解决方案2】:

基于 Intel 的处理器提供一次操作一位的指令。这些指令最常被忽略并被ANDORXORTEST 指令取代,这些指令可以一次操作多个位。此外,较旧的处理器会真的缓慢执行它们。我不知道他们是否仍然很慢,可能没有。

对应的汇编指令为(AT&T语法)

测试一下(CF = 位值):

BT imm8, reg
BT reg, reg
BT reg, mem

测试和补码(CF = 旧位值;新位值 = ~旧位值):

BTC imm8, reg
BTC reg, reg
BTC reg, mem

测试和重置(CF = 旧位值;新位值 = 0)

BTR imm8, reg
BTR reg, reg
BTR reg, mem

测试和设置(CF = 旧位值;新位值 = 1)

BTS imm8, reg
BTS reg, reg
BTS reg, mem

当用于内存时,它可以原子地使用(只有一个 CPU 在执行读/修改/写周期时访问该内存。)因此它可以用于创建锁和信号量。

【讨论】:

  • 感谢您的评论,亚历克西斯。但是我没有完全理解您的代码,请您详细说明一下吗?
  • 您可能需要下载程序员手册参考以获得详细信息。 intel.com/content/www/us/en/processors/… 您可能想要这本书:“英特尔 64 和 IA-32 架构软件开发人员手册组合卷 2A、2B 和 2C:指令集参考,A-Z”。这些指令占用一个位(在我的示例中位于左侧)并检查一个寄存器或内存位置。
猜你喜欢
  • 2021-03-25
  • 2013-10-21
  • 2011-09-28
  • 2014-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多