4

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

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呢?

4

3 回答 3

6

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

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为正,则“有符号”或“无符号”无关紧要,因此无符号比较给出正确答案。
  2. 如果idx为负数,则将其解释为无符号数将产生一个非常大的数(大于 2 31 -1),因此在这种情况下,无符号比较也给出了正确答案。

所以一个无符号比较完成了两个有符号比较的工作。这仅在limit有符号且非负数时有效。如果编译器可以证明它是非负的,它将生成这样的优化代码。


另一种可能性是,如果最初的 C 代码有问题,并且比较有符号和无符号。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
}
于 2018-05-07T21:49:54.287 回答
2

anatolyg 答案的附录:

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

计算机中的信息以位编码(一位=零或一),ecx是32位信息,仅此而已。

无论您是否将最高位解释为符号,这取决于以下代码,即在汇编级别使用movsx扩展值(以类似符号的方式)是完全合法的,即使您稍后将其解释为位掩码或无符号诠释。

是否存在逻辑层面的冲突取决于作者计划的功能。如果作者确实希望该测试arg_0不分支 ifvar_8是“负”值并且arg_0< 2 31,那么代码是正确的。

顺便说一句,反汇编在 first 中缺少有关参数大小的信息movsx,因此产生此参数的反汇编工具令人困惑(否则是否很好?要小心)。

那么,var_8 是有符号的还是无符号的?那么arg_0呢?

var_8首先是内存地址,并且从那里使用 8 位或 16 位信息(从您的反汇编中不清楚,哪个) - 以“签名”方式。但是如果不探索完整的代码就很难说出更多信息var_8,它甚至可能var_8是 32 位无符号整数“变量”,但出于某种原因,作者决定在第一个movsx. arg_0然后用作cmp指令的无符号 32 位整数。

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

这比 C 或其他高级编程语言有更多的自由,例如,如果你在内存中有 4 字节计数器,你知道每个计数器都小于 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它们被用作单独的字节计数器,这是速度优化,可以在单个指令中增加其中两个。

于 2018-05-08T06:09:26.093 回答
2

这可能是这样的范围检查的结果,下限不仅限于 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)

uint32_t arg_0 = UPPER_BOUND - LOWER_BOUND

这是确定整数是否介于两个具有已知值集的整数(包括)之间的技巧。

当边界是这样的常量时,大多数现代编译器已经知道如何进行这种优化。例如 gcc 将为上面的第一个片段发出以下指令

    add     edi, -LOWER_BOUND 
    cmp     dil, UPPER_BOUND - LOWER_BOUND
    jbe     .L5
于 2018-05-08T17:22:55.287 回答