了解反汇编的 C 代码:dec %eax 和 movl $0x0,-0x8(%ebp)
Understanding disassembled C code: dec %eax and movl $0x0,-0x8(%ebp)
我正在尝试理解一段反汇编代码中的行,如下所示。我想知道以下内容:
- dec %eax : 为什么 eax 寄存器递减? eax寄存器的初始值是多少?
- movl $0x0,-0x8(%ebp) :为什么我们要将值 0x0 移入堆栈? movl 不是存储 32 位值(4 个字节)吗?如果是这样,为什么值存储在基指针下方 8 个字节而不是 4 个字节?
这是反汇编的二进制文件:
Contents of section .text:
0000 554889e5 48c745f8 00000000 905dc3 UH..H.E......].
Contents of section .rodata:
0000 48656c6c 6f00 Hello.
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520352e .GCC: (Ubuntu 5.
0010 342e302d 36756275 6e747531 7e31362e 4.0-6ubuntu1~16.
0020 30342e31 30292035 2e342e30 20323031 04.10) 5.4.0 201
0030 36303630 3900 60609.
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 0f000000 00410e10 8602430d .........A....C.
0030 064a0c07 08000000 .J......
Disassembly of section .text:
0000000000000000 <my_function>:
0: 55 push %ebp
1: 48 dec %eax
2: 89 e5 mov %esp,%ebp
4: 48 dec %eax
5: c7 45 f8 00 00 00 00 movl [=10=]x0,-0x8(%ebp)
c: 90 nop
d: 5d pop %ebp
e: c3 ret
这是 C 代码:
void my_function () {
char* my_string = "Hello";
}
您正在像反汇编 32 位代码一样反汇编 64 位代码。 REX.W前缀只存在于64位代码中,在32位代码中是DEC EAX指令。
您正在反汇编 64 位代码,就好像它是 32 位代码一样。这通常是不可能的,除非你专门覆盖你的反汇编程序或使用 objcopy
或其他东西将 64 位 machine-code 复制到 32 位 ELF 目标文件中。
x86-64 将 0x40..f 字节重新用作 REX 前缀,而不是 inc/dec 的 1 字节编码。 DEC EAX 实际上是一个 REX.W 前缀,所以对于正常的 frame-pointer 设置,指令是 mov %rsp, %rbp
。
这也解释了堆栈指针下方red-zone的前8个字节的使用。 (x86-64 系统 V 有一个 red-zone,i386 系统 V 没有;它会在存储在它下面之前移动 ESP。)它解释了指针 -8
而不是 -4
,因为 x86-64 有 8 字节指针。
0
字节是因为您正在反汇编未链接的 .o
。链接器将用字符串的绝对地址填充这 4 个字节的零。
GCC 在此处使用 mov r/m64, sign_extended_imm32
来存储一个 8 字节的内存指针,使用 32 位绝对地址作为立即数。
要将其放入寄存器,我们会得到 non-PIE 可执行文件的正常 mov r32, imm32
(隐式零扩展到 64 位)。但是此代码(默认为 -O0
"debug mode")需要内存中的整个 8 字节指针。它仍然可以使用 32 位绝对地址而不是 RIP-relative LEA 到寄存器 + 单独存储中,但它必须显式 sign-extension 到 64 位。
我正在尝试理解一段反汇编代码中的行,如下所示。我想知道以下内容:
- dec %eax : 为什么 eax 寄存器递减? eax寄存器的初始值是多少?
- movl $0x0,-0x8(%ebp) :为什么我们要将值 0x0 移入堆栈? movl 不是存储 32 位值(4 个字节)吗?如果是这样,为什么值存储在基指针下方 8 个字节而不是 4 个字节?
这是反汇编的二进制文件:
Contents of section .text:
0000 554889e5 48c745f8 00000000 905dc3 UH..H.E......].
Contents of section .rodata:
0000 48656c6c 6f00 Hello.
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520352e .GCC: (Ubuntu 5.
0010 342e302d 36756275 6e747531 7e31362e 4.0-6ubuntu1~16.
0020 30342e31 30292035 2e342e30 20323031 04.10) 5.4.0 201
0030 36303630 3900 60609.
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 0f000000 00410e10 8602430d .........A....C.
0030 064a0c07 08000000 .J......
Disassembly of section .text:
0000000000000000 <my_function>:
0: 55 push %ebp
1: 48 dec %eax
2: 89 e5 mov %esp,%ebp
4: 48 dec %eax
5: c7 45 f8 00 00 00 00 movl [=10=]x0,-0x8(%ebp)
c: 90 nop
d: 5d pop %ebp
e: c3 ret
这是 C 代码:
void my_function () {
char* my_string = "Hello";
}
您正在像反汇编 32 位代码一样反汇编 64 位代码。 REX.W前缀只存在于64位代码中,在32位代码中是DEC EAX指令。
您正在反汇编 64 位代码,就好像它是 32 位代码一样。这通常是不可能的,除非你专门覆盖你的反汇编程序或使用 objcopy
或其他东西将 64 位 machine-code 复制到 32 位 ELF 目标文件中。
x86-64 将 0x40..f 字节重新用作 REX 前缀,而不是 inc/dec 的 1 字节编码。 DEC EAX 实际上是一个 REX.W 前缀,所以对于正常的 frame-pointer 设置,指令是 mov %rsp, %rbp
。
这也解释了堆栈指针下方red-zone的前8个字节的使用。 (x86-64 系统 V 有一个 red-zone,i386 系统 V 没有;它会在存储在它下面之前移动 ESP。)它解释了指针 -8
而不是 -4
,因为 x86-64 有 8 字节指针。
0
字节是因为您正在反汇编未链接的 .o
。链接器将用字符串的绝对地址填充这 4 个字节的零。
GCC 在此处使用 mov r/m64, sign_extended_imm32
来存储一个 8 字节的内存指针,使用 32 位绝对地址作为立即数。
要将其放入寄存器,我们会得到 non-PIE 可执行文件的正常 mov r32, imm32
(隐式零扩展到 64 位)。但是此代码(默认为 -O0
"debug mode")需要内存中的整个 8 字节指针。它仍然可以使用 32 位绝对地址而不是 RIP-relative LEA 到寄存器 + 单独存储中,但它必须显式 sign-extension 到 64 位。