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:
- Is there way to show how variables are allocated in the heap?
- 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.