【问题标题】:x86-64 assembly: why offset 25 bytes?x86-64 程序集:为什么要偏移 25 个字节?
【发布时间】:2018-03-10 16:32:53
【问题描述】:

我今天开始通过分析与此 c++ 示例对应的汇编代码来学习 x86 汇编(我知道存在类似 atoi 但我想尽量减少示例):

#include <vector>

std::vector<int> range(int N) {
    std::vector<int> v(N);
    for (unsigned int i = 0; i < N; ++i)
        v[i] = i;
    return v;
}

int main() {
    return range(100).back();
}

如果使用g++ -O0 -S -fno-stack-protector return_by_value.cpp 编译,则会产生以下摘录:

... <snip>
_Z5rangei:
.LFB509:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    .cfi_lsda 0x3,.LLSDA509
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    pushq   %rbx
    subq    $40, %rsp
    .cfi_offset 3, -24
    movq    %rdi, -40(%rbp)
    movl    %esi, -44(%rbp)
    leaq    -25(%rbp), %rax
    movq    %rax, %rdi
    call    _ZNSaIiEC1Ev
    movl    $0, -24(%rbp)
    movl    -44(%rbp), %eax
    movslq  %eax, %rsi
    leaq    -25(%rbp), %rcx
    leaq    -24(%rbp), %rdx
    ... <snip>

我很惊讶地看到一个奇数(即不是 8 的倍数)偏移量:leaq -25(%rbp), %rax,尤其是因为它是一个q 指令,而且我们还有-24(%rbp)编译器读取跨 8 字节边界的原因是什么?

【问题讨论】:

  • godbolt.org 是探索编译器生成的代码的绝佳资源
  • 虽然这里实际上并没有发生,但未对齐的访问在 x86 中通常是合法的

标签: c++ assembly


【解决方案1】:

看看这个片段:

leaq    -25(%rbp), %rax
movq    %rax, %rdi
call    _ZNSaIiEC1Ev

_ZNSaIiEC1Ev 被分解为std::allocator&lt;int&gt;::allocator(),因此-25(%rbp) 是传递给构造函数的allocator&lt;int&gt; 对象的地址。如果我们在 GCC 中打印这个对象的sizeof,我们会得到 1。由于对象的大小是 1,所以不需要将它对齐到 8 个字节,它可以放在任何内存地址中。

您稍后看到的-24(%rbp) 是不同对象的地址,编译器没有跨 8 字节边界读取。

请注意,lea 指令实际上并不访问内存——它只计算一个地址。因此,它有一个q 后缀并不意味着它访问了8 个字节。

【讨论】:

    猜你喜欢
    • 2014-08-07
    • 2015-09-03
    • 2012-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 2018-03-30
    相关资源
    最近更新 更多