x86 汇编中的冲突符号:movsx 然后未签名 compare/branch?
Conflicting signs in x86 assembly: movsx then unsigned compare/branch?
我对以下片段感到困惑:
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 呢?
正如 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
进行比较时,就好像它是无符号的一样,它会同时进行两个比较。
- 如果
idx
为正数,则"signed"或"unsigned"无所谓,所以unsigned
比较给出正确答案。
- 如果
idx
是负数,那么将其解释为无符号数会产生一个非常大的数(大于231-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
}
分析答案的附录:
原则上在汇编层面上没有冲突。
计算机中的信息是以位编码的(一位=零或一),ecx
是32位信息,没有别的。
无论您是否将最高位解释为符号,这取决于以下代码,即在汇编级别,使用 movsx
扩展值(以类似符号的方式)是完全合法的,即使如果您稍后将其解释为位掩码或无符号整数。
是否有逻辑层面的冲突取决于作者规划的功能。如果作者确实希望针对 arg_0
的测试在 var_8
为 "negative" 值且 arg_0
< 231 时不分支,则代码是正确的。
顺便说一句,反汇编缺少有关第一个参数大小的信息 movsx
,因此生成此信息的反汇编工具令人困惑(否则它会好吗?请小心)。
So, is var_8 signed or unsigned? And what about arg_0?
var_8
是第一个也是最重要的内存地址,从那里使用 8 位或 16 位信息(从你的反汇编中不清楚,是哪一个) - 在 "signed" 方式。但是如果不探索完整的代码就很难说出更多关于 var_8
的信息,它甚至可能是 var_8
是 32 位无符号整数 "variable",但出于某种原因作者决定只使用 sing-在第一个 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
用作单独的字节计数器,这是速度优化以在单个指令中递增其中两个。
这可能是这样的范围检查的结果,下限不仅限于 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
这是 determine if an integer is between two integers (inclusive) with known sets of values 的技巧。
大多数现代编译器已经知道如何在边界为常量时进行此优化like this。例如,gcc 将为上面的第一个片段发出以下指令
add edi, -LOWER_BOUND
cmp dil, UPPER_BOUND - LOWER_BOUND
jbe .L5
我对以下片段感到困惑:
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 呢?
正如 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
进行比较时,就好像它是无符号的一样,它会同时进行两个比较。
- 如果
idx
为正数,则"signed"或"unsigned"无所谓,所以unsigned 比较给出正确答案。 - 如果
idx
是负数,那么将其解释为无符号数会产生一个非常大的数(大于231-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
}
分析答案的附录:
原则上在汇编层面上没有冲突。
计算机中的信息是以位编码的(一位=零或一),ecx
是32位信息,没有别的。
无论您是否将最高位解释为符号,这取决于以下代码,即在汇编级别,使用 movsx
扩展值(以类似符号的方式)是完全合法的,即使如果您稍后将其解释为位掩码或无符号整数。
是否有逻辑层面的冲突取决于作者规划的功能。如果作者确实希望针对 arg_0
的测试在 var_8
为 "negative" 值且 arg_0
< 231 时不分支,则代码是正确的。
顺便说一句,反汇编缺少有关第一个参数大小的信息 movsx
,因此生成此信息的反汇编工具令人困惑(否则它会好吗?请小心)。
So, is var_8 signed or unsigned? And what about arg_0?
var_8
是第一个也是最重要的内存地址,从那里使用 8 位或 16 位信息(从你的反汇编中不清楚,是哪一个) - 在 "signed" 方式。但是如果不探索完整的代码就很难说出更多关于 var_8
的信息,它甚至可能是 var_8
是 32 位无符号整数 "variable",但出于某种原因作者决定只使用 sing-在第一个 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
用作单独的字节计数器,这是速度优化以在单个指令中递增其中两个。
这可能是这样的范围检查的结果,下限不仅限于 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
这是 determine if an integer is between two integers (inclusive) with known sets of values 的技巧。
大多数现代编译器已经知道如何在边界为常量时进行此优化like this。例如,gcc 将为上面的第一个片段发出以下指令
add edi, -LOWER_BOUND
cmp dil, UPPER_BOUND - LOWER_BOUND
jbe .L5