如何在ARM汇编中仅设定溢出标志(overflow flag)?

5
我在学习Raspberry PI上的ARM汇编时,一直在尝试修改标志位。我已经想出了单独设置零标志、负数标志和进位标志的方法。但是我无法想到如何仅设置溢出标志。是否有可能?任何帮助都将不胜感激!
挑战在于不写入cpsr(由于各种原因,我不被允许这样做,否则那将是最好的解决方案,因为它是最好的解决方案)。
编辑:仅使用算术或移位,在其他标志位清零的情况下设置溢出标志。NZCV = 0001
编辑2:进一步澄清,我认为需要多个指令才能实现这一点。

@Peter Cordes 是的,对于十六进制是这样,但我会说任何算术或移位都是如此。 - yosmo78
1
@PeterCordes ARM不像其他架构那样反转进位,因此您同时获得有符号和无符号溢出。在ARM上,Carry out不是借位。 - old_timer
哦,是哪个ARM呢?我认为ARM不会改变aarch64这种类型的东西。 - old_timer
你是否受限于单个指令,还是可以使用任意数量的指令? - old_timer
你可以直接写入 PSR 吗? - old_timer
显示剩余2条评论
4个回答

4

我没有看到仅使用一个指令的明显方法,但您可以通过组合来完成。例如:

mov  r0, #0x80000000
mov  r1, #0x00000001
subs r2, r0, r1  ; C and V set
mov  r3, #0x10
asrs r3, #1      ; C cleared, V not changed

1
如果你想的话,你可以把它变得更紧凑,例如使用 mov r1, #0x10 / subs r2, r0, r1, lsr #4 来重复使用一个常量。或者也许使用旋转移位器,这样你就不需要两个单独的输入寄存器来开始了。像 mov r0, #0x80000002 应该可以用旋转的8位常量进行编码,而 adds r0, r0, r0 则会产生 r0 = 4,并设置 C 和 V,准备好进行 asrs - Peter Cordes

2
abc cr
000 00 
001 01  x
010 01 
011 10 
100 01 
101 10 
110 10  x
111 11 

有符号溢出是指进位不等于进位。如果第一列是操作数a、b和最高有效位(对于有符号或无符号溢出,其他位都不重要)的进位,则右列为进位和结果。如果结果为1,则会得到N位。因此,它与操作数的最高有效位为1且进位为0有关。

0xxx (carrys)
1xxx (operand a)
1xxx (operand b)

0x80 + 0x80 = 0x00 (zero flag)
0x81 + 0x81 = 0x02 (need some other ones)

  100000010
   10000001
+  10000001
============
   00000010

-127 + -127 = -254。最大的负数是-128,0x80,因此这是一种有符号溢出。

但是有一个进位存在。

所以也许减法可以解决问题。-127 - 127。

  100000011
   10000001
+  10000000
============
   00000010

但是,作为一个减法运算器,它是否会将进位输出反转为借位,并在进位位上留下一个0?这不是ARM的工作方式,其他处理器/内核可能会这样做。
因此,为了能够实现这一点,您需要具备将进位输出定义为减法借位(在加法结束时反转进位输出)的处理器。
您在撰写本文时编辑了您的问题,移位操作如何修改有符号溢出?需要进行加法或减法(需要使用加法器)。

请注意,有符号溢出是指进位不等于进位(对于符号位即最高位),而不是整个计算过程的进位。 - cooperised

2
我对汇编语言比较新,通过实验和研究,我找到了以下设置单个标志位的方法。请注意,我使用的是基于ARM7TDMI-S的32位RISC微控制器架构。有所谓的MRSMSR指令。MRS用于读取标志位,MSR用于写入标志位。
下面是如何设置每个标志位:
msr cpsr_cxsf, #0x80000000 ; N Flag
    
msr cpsr_cxsf, #0x40000000 ; Z flag
    
msr cpsr_cxsf, #0x20000000 ; C Flag
    
msr cpsr_cxsf, #0x10000000 ; V Flag

0
我认为在ARM上,无论寄存器输入如何,通过单个算术指令写入所有四个标志位,都无法得到V=1 C=0且结果为正的情况(Z=0 N=0)。因此,@domen的答案可能是除了直接使用msr设置CPSR之外,我们能做的最好的方法,它使用移位来清除C和其他标志位,同时保持V不变。
一般情况下,问题中允许使用算术运算,但是mul不会修改V,而mls/mla/smull等指令也不会设置标志位。类似lsl的移位指令以及and等位操作指令也不会修改V。当sdiv溢出时,例如INT_MIN/-1,不会留下任何痕迹:sdiv根本不会设置标志位。

加法和减法会设置所有四个标志位,但是如果你将减法看作是实际的二进制减法,那么减法会将C设置为非借位输出。(ARM的减法x - y会将Carry标志位设置为从带进位加法x + ~y中得到的,其中carry-in = 1。)

  • 我们需要结果非零,所以要清除Zero标志位。
  • 我们需要结果非负,所以要清除Negative标志位,因此我们需要结果为正数。
  • 我们需要Carry标志位清除,所以我们不能进行负数 + 负数 => 正数的加法运算;它们的符号位会进位到C。而正数 + 正数溢出为负数会设置N。
    所以我们不能使用加法。
减法可以带符号溢出,例如正数减去负数得到负数,这是我们不想要的。
或者负数减去正数得到正数。但是负数比正数更大,所以这些减法不会产生借位。因此它们确实设置了C。(当C被设置且Z被清除时,bhi会跳转。) 因此,减法的溢出条件都无法避免设置C。(至少在不考虑adc / sbc的情况下,不确定它们是否有帮助。)
以下是一种相当简洁高效的方法:首先使用subs进行减法,它会设置除了N以外的所有标志位,然后进行一次移位,覆盖除了V以外的所有标志位。
.syntax unified

  movs  r0, #0x80                  // fits in a Thumb 16-bit mov reg, imm8 (zero-extended)
  subs  r0, r0, r0, lsl #24        // NZCV=1001 from 0x80 - INT_MIN (0x80000000) overflowing to negative result 0x80000080
  lsrs  r0, #1                     // R0 = 0x40000040,  NZCV=0001  (checked with QEMU + LLDB)

在Thumb-2机器码中,有2个指令是16位的。我还没有想到一种方法可以使所有3个指令都具有16位的Thumb编码,无论是使用这种策略还是其他策略。
       0: 2080          movs    r0, #0x80
       2: ebb0 6000     subs.w  r0, r0, r0, lsl #24
       6: 0840          lsrs    r0, r0, #0x1

使用借位/进位输入到sbc/adc?不,没有帮助。 0 - (1<<31) - 1等于0x7fffffff,就像0 - INT_MIN - 1一样。将其作为一个操作来执行就像0 - (INT_MIN + 1),从0中减去一个负数并得到一个正数而不溢出。使用ARM指令,例如rscs r0, #0,可以清除C和V标志。

0 - 0x7fffffff - 1 产生 0x80000000 = INT_MIN,所以这甚至不是有符号溢出。从一个正数中减去也不会有,但是从一个负数中减去会有。但是负数比正数更高,所以减法不会有借位,所以 -3 - 0x7fffffff - 1 结果为 C=0。如果我们从小于 -1 的负数开始,我们甚至不需要 sbc,所以这并没有帮助。

0 + 0x80000000 + 1 使用 adc 也没有帮助,这是 INT_MIN + 1,它没有有符号溢出并且是负数。任何涉及加法的有符号溢出都会有进位或者负结果,即使使用 adc

所以在这里,adcsbc 或者仅限于 ARM 模式的 rsc 都没有帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接