未定义表达式的汇编程序调试
Assembler debug of undefined expression
我试图更好地理解编译器如何为 undefined 表达式生成代码,例如对于以下代码:
int main()
{
int i = 5;
i = i++;
return 0;
}
这是gcc 4.8.2生成的汇编代码(优化关闭-O0,我插入了自己的行号以供参考):
(gdb) disassemble main
Dump of assembler code for function main:
(1) 0x0000000000000000 <+0>: push %rbp
(2) 0x0000000000000001 <+1>: mov %rsp,%rbp
(3) 0x0000000000000004 <+4>: movl [=11=]x5,-0x4(%rbp)
(4) 0x000000000000000b <+11>: mov -0x4(%rbp),%eax
(5) 0x000000000000000e <+14>: lea 0x1(%rax),%edx
(6) 0x0000000000000011 <+17>: mov %edx,-0x4(%rbp)
(7) 0x0000000000000014 <+20>: mov %eax,-0x4(%rbp)
(8) 0x0000000000000017 <+23>: mov [=11=]x0,%eax
(9) 0x000000000000001c <+28>: pop %rbp
(10) 0x000000000000001d <+29>: retq
End of assembler dump.
执行此代码导致 i
的值保持在 5 的值(使用 printf()
语句验证)即 i
似乎永远不会增加。我知道不同的编译器会以不同的方式 evaluate/compile 未定义的表达式,这可能只是 gcc 的方式,即我可以用不同的编译器得到不同的结果。
关于汇编代码,据我理解:
忽略行 - 1-2 设置 stack/base 指针等。
第 3/4 行 - 5 的值如何分配给 i
.
谁能解释第 5-6 行发生了什么?看起来好像i
最终会被重新赋值为5(第7行),但是是自增操作(post自增操作需要i++
) 只是 abandoned/skipped 编译器在这种情况下?
第5-6行,就是i++
。 lea 0x1(%rax),%edx
是 i + 1
,mov %edx,-0x4(%rbp)
将其写回到 i
。但是第 7 行,mov %eax,-0x4(%rbp)
将原始值写回 i
。代码如下:
(4) eax = i
(5) edx = i + 1
(6) i = edx
(7) i = eax
这三行包含你的答案:
lea 0x1(%rax),%edx
mov %edx,-0x4(%rbp)
mov %eax,-0x4(%rbp)
增量操作没有被跳过。 lea
是增量,从%rax
中取值并将增量值存储在%edx
中。 %edx
被存储,但随后被使用 %eax
中原始值的下一行覆盖。
理解此代码的关键是了解 lea
的工作原理。它代表 load effective address,所以虽然它看起来像一个指针解引用,但它实际上只是进行数学计算以获得 [whatever] 的最终地址,然后保留地址,而不是值 在那个地址。这意味着它可以用于任何可以使用寻址模式有效表达的数学表达式,作为数学操作码的替代方法。出于这个原因,它经常被用作获得乘法和添加到单个指令中的方法。特别是,在这种情况下,它用于在一条指令中递增值并将结果移动到不同的寄存器,其中 inc
会就地覆盖它。
我试图更好地理解编译器如何为 undefined 表达式生成代码,例如对于以下代码:
int main()
{
int i = 5;
i = i++;
return 0;
}
这是gcc 4.8.2生成的汇编代码(优化关闭-O0,我插入了自己的行号以供参考):
(gdb) disassemble main
Dump of assembler code for function main:
(1) 0x0000000000000000 <+0>: push %rbp
(2) 0x0000000000000001 <+1>: mov %rsp,%rbp
(3) 0x0000000000000004 <+4>: movl [=11=]x5,-0x4(%rbp)
(4) 0x000000000000000b <+11>: mov -0x4(%rbp),%eax
(5) 0x000000000000000e <+14>: lea 0x1(%rax),%edx
(6) 0x0000000000000011 <+17>: mov %edx,-0x4(%rbp)
(7) 0x0000000000000014 <+20>: mov %eax,-0x4(%rbp)
(8) 0x0000000000000017 <+23>: mov [=11=]x0,%eax
(9) 0x000000000000001c <+28>: pop %rbp
(10) 0x000000000000001d <+29>: retq
End of assembler dump.
执行此代码导致 i
的值保持在 5 的值(使用 printf()
语句验证)即 i
似乎永远不会增加。我知道不同的编译器会以不同的方式 evaluate/compile 未定义的表达式,这可能只是 gcc 的方式,即我可以用不同的编译器得到不同的结果。
关于汇编代码,据我理解:
忽略行 - 1-2 设置 stack/base 指针等。
第 3/4 行 - 5 的值如何分配给 i
.
谁能解释第 5-6 行发生了什么?看起来好像i
最终会被重新赋值为5(第7行),但是是自增操作(post自增操作需要i++
) 只是 abandoned/skipped 编译器在这种情况下?
第5-6行,就是i++
。 lea 0x1(%rax),%edx
是 i + 1
,mov %edx,-0x4(%rbp)
将其写回到 i
。但是第 7 行,mov %eax,-0x4(%rbp)
将原始值写回 i
。代码如下:
(4) eax = i
(5) edx = i + 1
(6) i = edx
(7) i = eax
这三行包含你的答案:
lea 0x1(%rax),%edx
mov %edx,-0x4(%rbp)
mov %eax,-0x4(%rbp)
增量操作没有被跳过。 lea
是增量,从%rax
中取值并将增量值存储在%edx
中。 %edx
被存储,但随后被使用 %eax
中原始值的下一行覆盖。
理解此代码的关键是了解 lea
的工作原理。它代表 load effective address,所以虽然它看起来像一个指针解引用,但它实际上只是进行数学计算以获得 [whatever] 的最终地址,然后保留地址,而不是值 在那个地址。这意味着它可以用于任何可以使用寻址模式有效表达的数学表达式,作为数学操作码的替代方法。出于这个原因,它经常被用作获得乘法和添加到单个指令中的方法。特别是,在这种情况下,它用于在一条指令中递增值并将结果移动到不同的寄存器,其中 inc
会就地覆盖它。