Home > Software design >  Examine how the variables are allocated in the heap memory (for debugging runtime errors)
Examine how the variables are allocated in the heap memory (for debugging runtime errors)

Time:10-08

For example, the following code causes 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
}

In this simple example, I can easily guess that bar is allocated after foo in the heap memory, so can guess some operation on foo "break" the memory of bar. so fixing the bug is fairly easy.

However in a real application, the situation could be much more complex and we can't easily guess the heap memory allocation.

So my question is:

  1. Is there way to show how variables are allocated in the heap?
  2. Is it possible to monitor which function break certain memory accidentally?

CodePudding user response:

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

Yes: you can examine locations that vector will use in a debugger. For example (using your program) and 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
$1 = {<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
$2 = {<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>}

Here you can see that foo will use locations 0x55555556aeb0 through 0x55555556aed8, and bar will use 0x55555556aee0 through 0x55555556af08.

Note however that these locations may change from run to run, especially in multi-threaded programs, making this technique very difficult to use.

If your problem can be found by the Address Sanitizer, that would be significantly faster and more reliable approach.

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

Yes: that's what watchpoints are for. For example, we don't expect the location pointed to by foo._M_impl._M_end_of_storage to be changed, so we can set a watchpoint on it:

(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
$1 = 10     <-- voila, found the place where overflow happened.
  • Related