【问题标题】:Illegal instruction in ASM: lock cmpxchg dest, srcASM 中的非法指令:lock cmpxchg dest, src
【发布时间】:2009-11-17 03:11:12
【问题描述】:

我一直在搞乱一些 x86 程序集,因为它出现在我的许多课程中。特别是,我想将比较和交换 (CAS) 公开为用户函数。这是为了让我可以实现自己的锁。

我在 Intel CPU 上使用 Linux 2.6.31 和 GCC 4.1.1。

我有以下几点:

// int cmpxchg(int *dest, int expected, int update)
.globl cmpxchg
cmpxchg:
  pushl %ebp
  movl  %esp, %ebp

  // edx holds dest
  movl 8(%ebp), %edx
  // eax holds expected value
  movl 12(%ebp), %eax
  // ecx holds the new value
  movl 16(%ebp), %ecx

  // cmpxchg dest_addr, exp_value
  // compare to %eax is implicit
  lock cmpxchgl %edx, %ecx

  leave
  ret

这是一个 *.s 文件,我用我的驱动程序编译。当我包含该行时

  lock cmpxchgl %edx, %ecx

并执行,我收到“非法指令”错误。 当我用

替换该行时
  cmpxchgl %edx, %ecx

我的代码似乎运行良好。

首先,lock 有必要吗?我不确定cmpxchgl 是否自然是原子的,所以我使用lock 来确定。作为用户级程序,我什至可以使用lock 吗?

谢谢

================================================ =================

我的最终代码(对于那些将来可能会在这里徘徊的人):

// int cmpxchg(int *dest, int expected, int update)
.globl cmpxchg
cmpxchg:
  pushl %ebp
  movl  %esp, %ebp

  // edx holds dest, use eDx for Destination ;-)
  movl 8(%ebp), %edx
  // eax holds expected value implicitly
  movl 12(%ebp), %eax

  // cmpxchg dest_add, src_value
  lock cmpxchgl %edx, 16(%ebp)

  leave
  ret

【问题讨论】:

标签: assembly x86


【解决方案1】:

你需要cmpxchgl %edx, (%ecx)

除非目标是内存操作数,否则此操作没有意义,但是该指令允许寄存器目标。如果指令使用寄存器模式,CPU 会出错。

我试过了,你的代码可以使用内存操作数。我不知道你是否意识到这一点,但这个序列(带有注册目标)有一个流行的名称:“f00fc7c8 错误”或“the F00F bug”。在 Pentium 时代,这是一个“HCF”(停止并着火)或“杀手戳”指令,因为它会生成一个异常,然后由于总线被锁定而无法处理该异常,并且可以从用户调用模式。我认为可能存在操作系统级别的软件解决方法。

【讨论】:

  • 嗯,现在一个地址(通过指针)被加载到 %edx,一个值(整数)被加载到 %ecx。你是这个意思吗?原谅我的无知,我只有 6 小时的组装时间......
  • 你必须在 %ecx 中放一个地址,并为目标使用内存寻址模式,例如(%ecx)offset(%ecx)
  • @Ross:f00f 错误与CMPXCHG8B 指令有关,这与OP 使用的CMPXCHG 指令不同。
  • 哦,对了。好吧,他接近了,他的操作码确实F00F开头。
  • 是的。我不得不查看它以仔细检查。 :)
【解决方案2】:

Ross 的回答已经说明了大部分内容,但我会尝试澄清一些事情。

  1. 是的,如果你想要原子性,LOCK 前缀是必需的。唯一的例外是 XCHG(不是 CMPXCHG)指令,正如 asveikau 指出的那样,默认情况下它是锁定的。
  2. 是的,在用户模式代码中使用 LOCK 是完全合法的。
  3. 是的,将CMPXCHG 与寄存器目标操作数一起使用是完全合法的。

也就是说,将LOCK CMPXCHG together 与寄存器目标操作数一起使用是合法的。引用the IA-32 manual的第2A卷(我的副本中的第3-538页):

LOCK 前缀只能添加到以下指令之前,并且只能添加到目标操作数是内存操作数的那些指令形式:ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCHG8B、DEC、INC 、NEG、NOT、OR、SBB、SUB、XOR、XADD 和 XCHG。

【讨论】:

  • 有趣的花絮:您不需要 LOCK 的一个原子原语是 XCHG(原子交换)。我认为这是因为该指令早于 LOCK 前缀。
  • 哦,是的,这是一个很好的观点。我将编辑我的答案以澄清。
  • XCHG 指令不早于 LOCK 前缀;两者都在原始 8086 上可用。
【解决方案3】:

您的程序在这里编译得很好(GNU as 2.20)(我将它粘贴到 test.s 并运行 as -o test.o test.s

至于锁,英特尔的文档说:

该指令可以与 LOCK 前缀以允许指令 以原子方式执行。简化 处理器总线的接口, 目标操作数收到一个 写周期不考虑 比较的结果。这 如果目标操作数被写回 比较失败;否则, 源操作数被写入 目的地。 (处理器从不 产生一个锁定的读取,也没有 产生锁定写入。)

【讨论】:

    【解决方案4】:

    很好奇,这个最终代码仍然正确吗?从我所见,您正在反向进行比较,也就是说,您正在将指针的值(即指针所指的实际地址)与用作更新的整数进行比较......此外目标设置为用作更新值的临时 int。换句话说,而不是:

    lock cmpxchgl %edx, 16(%ebp)
    

    我想你会想要这样的东西:

    //move the update value into ecx register
    movl 0x16(%ebp), %ecx
    
    //do the comparison between the value at the address pointed to by edx and eax,
    //and if they are the same, copy ecx into the address being pointed to by edx
    lock cmpxchgl %ecx, (%edx)
    

    原始代码是否真的按计划工作(不仅仅是编译),如果没有,您是否最终重新组织了代码,使其看起来更像上面的?

    【讨论】:

      【解决方案5】:

      这似乎不太可能是问题的根源,但official documentation 还指出该指令不支持早于 486 架构。这是否发生在实模式中,具有保护模式覆盖?

      【讨论】:

      • 不幸的是,我还不熟悉您刚刚使用的所有术语:-/ 我在 Q6600 四核 Intel 上运行,所以我确信处理器足够新。实模式和保护模式覆盖是什么意思?解释的链接很好。
      • 是的,CPU 非常现代。实模式是 x86 CPU 的启动状态。在这种模式下,它几乎就像 1980 年代的 8086。 32 位和 64 位操作系统在初始化期间启用“保护模式”,提供现代功能,如大于 64K 的地址空间、虚拟内存等。
      猜你喜欢
      • 2014-10-12
      • 2011-01-24
      • 1970-01-01
      • 2020-03-20
      • 2012-02-02
      • 2020-04-13
      • 2017-11-16
      相关资源
      最近更新 更多