std :: bind和范围后使用堆栈 [英] std::bind and stack-use-after-scope

查看:75
本文介绍了std :: bind和范围后使用堆栈的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,今天,我正在运行一些使用Address Sanitizer构建的代码,并且偶然发现了一个奇怪的栈后使用范围错误。
我有一个简化的示例:

So, today I was running some code built with Address Sanitizer and have stumbled upon a strange stack-use-after-scope bug. I have this simplified example:

#include <functional>
class k
{
public: operator int(){return 5;}
};

const int& n(const int& a)
{
  return a;
}

int main()
{
  k l;
  return std::bind(n, l)();
}

ASAN抱怨最后一个代码行:

ASAN complains about the last code line:

==27575==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffeab375210 at pc 0x000000400a01 bp 0x7ffeab3750e0 sp 0x7ffeab3750d8
READ of size 4 at 0x7ffeab375210 thread T0
    #0 0x400a00  (/root/tstb.exe+0x400a00)
    #1 0x7f97ce699730 in __libc_start_main (/lib64/libc.so.6+0x20730)
    #2 0x400a99  (/root/tstb.exe+0x400a99)

Address 0x7ffeab375210 is located in stack of thread T0 at offset 288 in frame
    #0 0x40080f  (/root/tstb.exe+0x40080f)

  This frame has 6 object(s):
    [32, 33) '<unknown>'
    [96, 97) '<unknown>'
    [160, 161) '<unknown>'
    [224, 225) '<unknown>'
    [288, 292) '<unknown>' <== Memory access at offset 288 is inside this variable
    [352, 368) '<unknown>'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope (/root/tstb.exe+0x400a00)
Shadow bytes around the buggy address:
  0x1000556669f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100055666a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100055666a10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
  0x100055666a20: f1 f1 f8 f2 f2 f2 f2 f2 f2 f2 f8 f2 f2 f2 f2 f2
  0x100055666a30: f2 f2 f8 f2 f2 f2 f2 f2 f2 f2 f8 f2 f2 f2 f2 f2
=>0x100055666a40: f2 f2[f8]f2 f2 f2 f2 f2 f2 f2 00 00 f2 f2 f3 f3
  0x100055666a50: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100055666a60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100055666a70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100055666a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100055666a90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==27575==ABORTING

如果我理解正确,它表示在超出范围后我们正在访问堆栈变量。
看着未经仪器化和未经优化的反汇编,我确实看到它发生在实例化的 __ invoke_impl 内部:

If I understand correctly, it says that we are accessing a stack variable after it has already gone out of scope. Looking at the uninstrumented and unoptimized disassembly I indeed see that it happens inside instantiated __invoke_impl :

Dump of assembler code for function std::__invoke_impl<int const&, int const& (*&)(int const&), k&>(std::__invoke_other, int const& (*&)(int const&), k&):
   0x0000000000400847 <+0>:     push   %rbp
   0x0000000000400848 <+1>:     mov    %rsp,%rbp
   0x000000000040084b <+4>:     push   %rbx
   0x000000000040084c <+5>:     sub    $0x28,%rsp
   0x0000000000400850 <+9>:     mov    %rdi,-0x28(%rbp)
   0x0000000000400854 <+13>:    mov    %rsi,-0x30(%rbp)
   0x0000000000400858 <+17>:    mov    -0x28(%rbp),%rax
   0x000000000040085c <+21>:    mov    %rax,%rdi
   0x000000000040085f <+24>:    callq  0x4007a2 <std::forward<int const& (*&)(int const&)>(std::remove_reference<int const& (*&)(int const&)>::type&)>
   0x0000000000400864 <+29>:    mov    (%rax),%rbx
   0x0000000000400867 <+32>:    mov    -0x30(%rbp),%rax
   0x000000000040086b <+36>:    mov    %rax,%rdi
   0x000000000040086e <+39>:    callq  0x4005c4 <std::forward<k&>(std::remove_reference<k&>::type&)>
   0x0000000000400873 <+44>:    mov    %rax,%rdi
   0x0000000000400876 <+47>:    callq  0x40056a <k::operator int()>
   0x000000000040087b <+52>:    mov    %eax,-0x14(%rbp)
   0x000000000040087e <+55>:    lea    -0x14(%rbp),%rax
   0x0000000000400882 <+59>:    mov    %rax,%rdi
   0x0000000000400885 <+62>:    callq  *%rbx
=> 0x0000000000400887 <+64>:    add    $0x28,%rsp
   0x000000000040088b <+68>:    pop    %rbx
   0x000000000040088c <+69>:    pop    %rbp
   0x000000000040088d <+70>:    retq
End of assembler dump.

在调用 k :: operator int()它将返回的值放在堆栈上,并将其地址传递给 n(),后者立即将其返回,然后从 __ invoke_impl返回本身(一直到main的返回)。

After calling k::operator int() it places the returned value on the stack and passes its address to the n(), which immediately returns it, and then it is returned from __invoke_impl itself (and goes all the way up to main's return).

所以,就好像ASAN一样,我们真的有一个堆栈-use-after-scope访问。

So, it looks like ASAN it right here and we really have an stack-use-after-scope access.

问题是:我的代码有什么问题?

The question is: What is wrong with my code?

我尝试使用gcc,clang和icc构建它,它们都产生相似的汇编输出。

I have tried building it with gcc, clang and icc and they all produce similar assembler outputs.

推荐答案

std :: bind 本质上会生成一个实现函数对象,该函数对象使用所需的参数调用绑定的函数。在您的情况下,此实现函数对象大约等于

std::bind essentially generates an implementation function object that calls the bound function with the desired arguments. In your case, this implementation function object is about equivalent to

struct Impl
{
    const int &operator()() const
    {
        int tmp = k_;
        return n(tmp);
    }

private:
    k k_;

    Impl(/*unspecified*/);
};

由于 n 返回其参数为const引用, Impl 的调用运算符将​​返回对局部变量的引用,该引用是一个悬挂的引用,然后从 main 读取。因此,范围错误后的堆栈使用。

Since n returns its argument as a const reference, the call operator of Impl will return a reference to a local variable, which is a dangling reference, which is then read from in main. Hence the stack use after scope error.

您的困惑可能源于返回n(l); 没有 bind 的应用在这里可以正常工作。但是,在后一种情况下,临时 int 是在 main 的堆栈框架中创建的,生存期为组成 return 的参数的完整表达式,其计算结果为 int

Your confusion may stem from the fact that return n(l); without the bind is expected to work fine here. However, in the latter case, the temporary int is created in the stack frame of main, lives for the duration of the full expression that makes up the argument to return, which is evaluated to int.

换句话说,虽然一个临时生存期一直到创建它的完整表达式的结尾,但在该完整表达式中调用的函数内部生成的临时对象却不是这种情况。这些被视为差异完整表达式的一部分,并在对该表达式进行求值时销毁。

In other words, while a temporary lives until the end of the full expression in which it was created, this is not the case for temporaries generated inside functions called within that full expression. These are considered part of a different full expression and are destroyed when that expression has been evaluated.

PS:因此,绑定任何函数签名 R(Args ...)的对象(对象)到 std :: function< const R&(Args ...)> 导致在调用时保证返回悬挂的引用,这是IMO在编译时应拒绝的构造。

PS: For this reason, binding any function (object) of signature R(Args...) to a std::function<const R&(Args...)> results in a guaranteed return of a dangling reference when called – a construct that IMO the library should reject at compile time.

这篇关于std :: bind和范围后使用堆栈的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆