x86汇编中的冲突符号:movsx然后是unsigned compare / branch?

时间:2018-05-07 21:15:01

标签: assembly x86 masm micro-optimization signedness

我对以下代码段感到困惑:

movsx   ecx, [ebp+var_8] ; signed move
cmp     ecx, [ebp+arg_0]
jnb     short loc_401027 ; unsigned jump

这似乎有冲突。 Var_8似乎是在签名扩展帐户上签名的。然而,jnb暗示var_8未在帐户上签名,它是无符号的比较。

那么,var_8是签名还是未签名?那么arg_0呢?

3 个答案:

答案 0 :(得分:5)

如Jester所述,无符号比较可用于对签名号码进行范围检查。例如,一个常见的C表达式,用于检查索引是否介于0和某个限制之间:

short idx = ...;
int limit = ...; // actually, it's called "arg_0" - is it a function's argument?
if (idx >= 0 && idx < limit)
{
    // do stuff
}

此符号扩展后的idx是带符号的32位数字(int)。这个想法是,在将它与limit进行比较时,好像它是无符号的,它会同时进行两次比较。

  1. 如果idx为正,那么&#34;签署&#34;或&#34;未签名&#34;没关系,所以没有签名 比较给出了正确的答案。
  2. 如果idx为负数,则将其解释为无符号数将产生一个非常大的数字(大于2 31 -1),因此在这种情况下,无符号比较也会给出正确答案。
  3. 因此,一个无符号比较会完成两次签名比较的工作。这仅在limit签名且非否定时有效。如果编译器可以证明它是非负的,那么它将生成这样的优化代码。

    另一种可能性是,如果初始C代码是错误的,并且它使用unsigned进行比较。 C的一个有点令人惊讶的特性是当有符号变量与无符号变量进行比较时,效果是无符号比较。

    short x = ...;
    unsigned y = ...;
    
    // Buggy code!
    if (x < y) // has surprising behavior for e.g. x = -1
    {
        // do stuff
    }
    
    if (x < (int)y) // better; still buggy if the casting could overflow
    {
        // do stuff
    }
    

答案 1 :(得分:2)

解剖学答案附录:

原则上,组装层面没有冲突。

计算机中的信息以位(一位=零或一位)编码,ecx是32位信息,没有别的。

无论您是否将最高位解释为符号,都可以使用以下代码,即在汇编级别上使用movsx来扩展值是完全合法的(签名中)即使您稍后将其解释为位掩码或无符号整数。

逻辑级别是否存在冲突取决于作者计划的功能。如果作者确实希望针对arg_0的测试不分支var_8是&#34;否定&#34;值和arg_0&lt; 2 31 ,那么代码是正确的。

BTW反汇编缺少有关第一个movsx中参数大小的信息,因此生成此参数的反汇编工具令人困惑(其他方面是否合适?小心)。

  

那么,var_8是签名还是未签名?那么arg_0呢?

var_8首先是内存地址,并且从那里使用8位或16位信息(从您的反汇编中看不清楚,哪一个) - 在&#34;签署&#34;办法。但是在没有探索完整代码的情况下很难说出var_8,甚至可能是var_8是32位无符号整数&#34;变量&#34;,但由于某种原因作者决定在第一个movsx中仅使用其内容的单一扩展低16位。然后将arg_0用作cmp指令的无符号32位整数。

在汇编中,问题不在于var_8是有符号还是无符号,汇编中的问题是你有多少信息,在哪里,以及那些对这些比特的解释是什么?以下代码。

在C或其他高级编程语言中有更多的自由,例如,如果你在内存中有四个字节计数器,你知道它们每个都小于200,并且你想要增加第一个也是最后一个,你可以这样做:

.data
counter1: db 13
counter2: db 6
counter3: db 34
counter4: db 17

.text
    ...
    ; increment first and last counter in one instruction
    ; overflow not-expected/handled, counters should to be < 200
    add  dword [counter1],0x01000001

现在(想象)在拆解此类代码时,您将如何理解这一点,而不是上述来源的原始评论?会变得棘手,如果你不理解其他代码,counter1-4被用作单独的字节计数器,这就是速度优化,可以在单个指令中递增其中两个。

答案 2 :(得分:2)

这可能是像这样的范围检查的结果,下限不仅限于0,而是任何整数值

let g1:[Int] = [2,0,0,0,0]
let g2:[Int] = [2,0,0,0,0]
let g3:[Int] = [2,0,0,0,0]
let g4:[Int] = [3,0,0,0,0]
let g5:[Int] = [3,0,0,0,0]
let g6:[Int] = [4,0,0,0,0]
let g7:[Int] = [5,0,0,0,0]

public static var groids:[[Int]] = [g1, g2, g3, g4, g5, g6, g7]

//In the place a is assigned
var a:[Int] = groids[0]

上述表达式可以优化为

int8_t var_8 = ...;
if (LOWER_BOUND <= var_8 && var_8 <= UPPER_BOUND)

unsigned arg_0 = UPPER_BOUND - LOWER_BOUND; if ((unsigned)(var_8 - LOWER_BOUND) <= arg_0)

这是determine if an integer is between two integers (inclusive) with known sets of values的一招。

当边界是常量like this时,大多数现代编译器都知道如何进行优化。例如,gcc将为上面的第一个片段发出以下说明

uint32_t arg_0 = UPPER_BOUND - LOWER_BOUND