32 位机器上的 64 位整数

逆向工程 x86 C x64 堆栈变量
2021-06-29 08:11:43

假设我用 C 语言创建了一个这样的程序,并在普通的 64 位台式机上编译它:

#include <stdint.h>

int main(void)
{
    uint64_t a = 0x12345679abcdefULL;
    uint64_t b = 0xfdcba987654321ULL;
    uint64_t c = a + b;
    return 0;
}

我刚刚使用gcc -O0 -S. 这似乎在栈上分配两个32位值,然后用添加addladcl分开。即使我在 64 位机器上,这也会发生在-m32-m64switch 上,而且似乎确实有一个用于 64 位整数的专用指令集,包括诸如movq和 之类的操作addq

  1. 为什么gcc农产品代码像一个32位的机器,即使我告诉它使用64位运算用-m64

    >uname -a
    CYGWIN_NT-6.2-WOW work 1.7.35(0.287/5/3) 2015-03-04 12:07 i686 Cygwin
    
    >gcc --version
    gcc (GCC) 4.9.2
    

现在,假设我在 32 位机器上运行 64 位整数。

  1. 程序在堆栈上分配了两个 32 位变量这是 C/C++ 标准的一部分,还是在某些编译器预期的堆上分配它们(因为它们试图以不同的方式将 64 位整数放入 32 位堆栈)?

  2. 如果我放入movq为 32 位机器设计的程序,是否会模拟所需的行为,或者指令是否会被误解?

1个回答

1:

请参阅此 64 位程序集参考中的“立即数”部分

指令中的立即数保持 32 位,并且它们的值被符号扩展到 64 位

只是没有将 64 位立即数移动到内存(*)的指令并且因为可以通过移动两个 32 位块来很好地模拟它,所以没有理由引入新的 64 位指令。(他们本可以将立即数更改mov为始终使用 64 位。但考虑到您将使用的大多数常量 <= 2^31,使用 32 位只会为高位零字节节省大量空间,并且成本有点高当您实际使用更大的常量时,这样可以节省内存)。

(*) 但是,有一些指令可以将 64 位立即数移动到 64 位寄存器,因为您无法访问寄存器中的高 32 位,而不是内存。

我不知道为什么你的程序产生了单独的 addl/adcl 指令;这是我从你的程序中得到的:

    pushq   %rbp
    movq    %rsp, %rbp
    movl    $2041302511, -24(%rbp)
    movl    $1193046, -20(%rbp)
    movl    $-2023406815, -16(%rbp)
    movl    $16632745, -12(%rbp)
    movq    -16(%rbp), %rax
    movq    -24(%rbp), %rdx
    leaq    (%rdx,%rax), %rax
    movq    %rax, -8(%rbp)
    movl    $0, %eax
    leave
    ret

如您所见,leaq (%rdx,%rax), %rax添加 64 位数字没问题。这是 RHEL 6.6 64 位系统上的 gcc 4.4.7。请始终说明您的编译器和操作系统版本,因为输出可能非常依赖于这些。

2:

只要您处理的是 x86/amd64 架构,您就可能依赖于放置在堆栈中的局部变量和不在堆栈中的全局变量。但请注意,“堆栈”和“堆”的概念并不像看起来那么明确。brk/sbrk不推荐使用分配内存机制;现代实现使用mmap. 这可能意味着您在地址空间的不同部分有几个小堆。在 ARM 和 MIPS 上,根本没有堆栈指针 - 只有一个特定寄存器用作堆栈指针的约定,但推送/弹出指令也可以与其他寄存器一起使用(*)。理论上,编译器可以mmap()在每个函数开始时自由地做一个来分配本地内存,并且munmap()它在函数的末尾。编译器必须的唯一一件事就是在函数退出后不保留已分配的内存(对于已分配的合理定义)。

(*) 这有点过于简单化,但演示了这个概念。

当然,mmap()用于为局部变量腾出空间的想法是一个极端的例子,可能没有人使用。但是许多编译器将局部变量放入处理器寄存器中,并且从不为它们保留堆栈空间(如果您从不使用指向它们的指针,并且在不像 x86 那样缺乏寄存器的体系结构上)。许多架构也将处理器寄存器用于函数参数。而且我已经看到微控制器 C 编译器允许您将函数的所有局部变量放入静态区域,如果您使用某个关键字,则编译器知道该函数不是递归调用的。因此,虽然大多数情况下,局部变量将被放置在堆栈中,但您不应认为这是一成不变的。

3.

指令会被误解。处理器可以是32位或64位模式,相同的指令(含义:指令字节序列相同)在每条指令中都有不同的含义。例如,48 89 43 ecmov [rbx-20],rax在64位模式,但dec eax; mov DWORD PTR [ebx-0x14],eax在32位模式。