检查变量在堆内存中的分配方式(用于调试运行时错误)

Examine how the variables are allocated in the heap memory (for debugging runtime errors)

例如,下面的代码导致munmap_chunk(): invalid pointer

#include <vector>
int main(int argc, char* argv[]) {
  std::vector<int> foo(10, 0);
  std::vector<int> bar(10, 1);
  for(int i = 0; i < 20; i++) {
    foo[i] = 42;
  }
  bar.clear(); // causes munmap_chunk(): invalid pointer
}

在这个简单的例子中,我很容易猜到bar分配在堆内存中的foo之后,所以可以猜到一些对foo的操作“破坏”了内存bar。所以修复错误相当容易。

然而在实际应用中,情况可能要复杂得多,我们不能轻易猜测堆内存分配。

所以我的问题是:

  1. 有没有办法显示变量在堆中的分配方式?
  2. 是否可以监控哪个函数不小心破坏了某些内存?

Is there way to show how variables are allocated in the heap?

是:您可以检查 vector 将在调试器中使用的位置。例如(使用你的程序)和 GDB:

(gdb) start
Temporary breakpoint 1 at 0x1185: file t.cc, line 3.
Starting program: /tmp/a.out

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffdbb8) at t.cc:3
3         std::vector<int> foo(10, 0);
(gdb) n
4         std::vector<int> bar(10, 1);
(gdb) p/r foo
 = {<std::_Vector_base<int, std::allocator<int> >> = {_M_impl = {<std::allocator<int>> = {<__gnu_cxx::new_allocator<int>> = {<No data fields>}, <No data fields>}, <std::_Vector_base<int, std::allocator<int> >::_Vector_impl_data> = {_M_start = 0x55555556aeb0, _M_finish = 0x55555556aed8, _M_end_of_storage = 0x55555556aed8}, <No data fields>}}, <No data fields>}
(gdb) n
5         for(int i = 0; i < 20; i++) {
(gdb) p/r bar
 = {<std::_Vector_base<int, std::allocator<int> >> = {_M_impl = {<std::allocator<int>> = {<__gnu_cxx::new_allocator<int>> = {<No data fields>}, <No data fields>}, <std::_Vector_base<int, std::allocator<int> >::_Vector_impl_data> = {_M_start = 0x55555556aee0, _M_finish = 0x55555556af08, _M_end_of_storage = 0x55555556af08}, <No data fields>}}, <No data fields>}

在这里您可以看到 foo 将使用 0x55555556aeb00x55555556aed8 的位置,而 bar 将使用 0x55555556aee00x55555556af08 的位置。

但是请注意,这些位置可能会从 运行 运行 改变,尤其是在多线程程序中,这使得该技术 非常 难以使用。

如果 Address Sanitizer 可以找到您的问题,那将是一种更快、更可靠的方法。

Is it possible to monitor which function break certain memory accidentally?

是的:这就是观察点的用途。比如我们不希望foo._M_impl._M_end_of_storage指向的位置发生变化,那么我们可以在上面设置一个观察点:

(gdb) start
Temporary breakpoint 1 at 0x1185: file t.cc, line 3.
Starting program: /tmp/a.out

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffdbb8) at t.cc:3
3         std::vector<int> foo(10, 0);
(gdb) n
4         std::vector<int> bar(10, 1);
(gdb) n
5         for(int i = 0; i < 20; i++) {

(gdb) watch *(int*)0x55555556aed8
Hardware watchpoint 2: *(int*)0x55555556aed8

(gdb) c
Continuing.

Hardware watchpoint 2: *(int*)0x55555556aed8

Old value = 49
New value = 42
main (argc=1, argv=0x7fffffffdbb8) at t.cc:5
5         for(int i = 0; i < 20; i++) {

(gdb) p i
 = 10     <-- voila, found the place where overflow happened.