ASM中的非法指令:锁定cmpxchg dest,src

时间:2009-11-17 03:11:12

标签: assembly x86

我一直在搞乱一些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

5 个答案:

答案 0 :(得分:7)

您需要cmpxchgl %edx, (%ecx)

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

我尝试过,你的代码使用内存操作数。我不知道你是否意识到这一点,但这个序列(带寄存器目的地)有一个流行的名字:“f00fc7c8 bug”或“the F00F bug”。在Pentium时代,这是一个“HCF”(停止和着火)或“杀手捅”指令,因为它会产生一个例外,由于总线被锁定而无法服务,并且它可以从用户调用模式。我认为可能存在操作系统级别的软件解决方法。

答案 1 :(得分:3)

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

  1. 是的,如果您想要原子性,则需要LOCK前缀。唯一的例外是XCHG(非CMPXCHG)指令,默认情况下会被锁定,正如asveikau指出的那样。
  2. 是的,从用户模式代码中使用LOCK是完全合法的。
  3. 是的,将CMPXCHG与注册目标操作数一起使用是完全合法的。
  4. 也就是说,将LOCK CMPXCHG 一起使用与注册目的地操作数一起 是合法的。引用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。

答案 2 :(得分:1)

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

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

  

该指令可与a一起使用   LOCK前缀允许指令   以原子方式执行。简化   处理器总线的接口,   目标操作数接收到   写循环而不考虑   比较结果。该   目标操作数将被写回   比较失败;否则,   源操作数写入   目的地。 (处理器永远不会   不会产生锁定的读取   产生一个锁定的写。)

答案 3 :(得分:1)

好奇,这个最终代码仍然正确吗?从我所看到的,你正在进行相反的比较,即你正在比较指针的值(即指针所指的实际地址)与用作更新的整数...目标设置为临时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)

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

答案 4 :(得分:0)

这似乎不太可能是问题的根源,但official documentation也表明该指令不早于486架构支持。这是在实模式下发生的,保护模式覆盖吗?