iovisor / iovisor/bcc

memleak: the symbol of the function that calls `new` disappeared

Open
#4,958 37 comments 0 reactions 0 assignees View on GitHub
Dominant language
C
Stars
22.7k
Forks
4.1k
Avg merge
10d 4h
Merged PRs (30d)
3

Description

The symbol of the function that calls `new` disappeared on `memleak` tool.

Test file `leak_loop_new.cpp`
```c++
#include
#include
int main(int argc, char* argv[]) {
while (true) {
auto p = new int;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
return 0;
}
```

Compile and run the file `leak_loop_new.cpp` and run `memleak`
```
$ g++ leak_loop_new.cpp
$ ./a.out &
[1] 322617
$ sudo ./memleak -p 322617
using default object: libc.so.6
using page size: 4096
tracing kernel: false
Tracing outstanding memory allocs... Hit Ctrl-C to end
[9:52:1] Top 1 stacks with outstanding allocations:
4 bytes in 1 allocations from stack
0 [<00007f0bb90ae98c>] _Znwm+0x1c
1 [<00007f0bb8c29d90>] __libc_init_first+0x90
[9:52:6] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
0 [<00007f0bb90ae98c>] _Znwm+0x1c
1 [<00007f0bb8c29d90>] __libc_init_first+0x90
^C[9:52:6] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
0 [<00007f0bb90ae98c>] _Znwm+0x1c
1 [<00007f0bb8c29d90>] __libc_init_first+0x90
done
```

It is obvious that `new` is called in `main` function but it is just disappeared.
`_Znwm` is a mangled name of `operator new` by the way.
The result of python version `memleak` is same, except demangle.

```
$ sudo python3 memleak.py -p 322617
Attaching to pid 322617, Ctrl+C to quit.
[09:55:28] Top 10 stacks with outstanding allocations:
4 bytes in 1 allocations from stack
0x00007f0bb90ae98c operator new(unsigned long)+0x1c [libstdc++.so.6.0.30]
0x00007f0bb8c29d90 __libc_start_call_main+0x80 [libc.so.6]
[09:55:33] Top 10 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
0x00007f0bb90ae98c operator new(unsigned long)+0x1c [libstdc++.so.6.0.30]
0x00007f0bb8c29d90 __libc_start_call_main+0x80 [libc.so.6]
```

You can get normal symbol(backtrace) if you use `malloc` instead of `new`.
See following test.

Test file `leak_loop_malloc.cpp`
```c++
$ cat leak_loop_malloc.cpp
#include
#include
#include
int main(int argc, char* argv[]) {
while (true) {
auto p = static_cast(malloc(sizeof(int)));
std::this_thread::sleep_for(std::chrono::seconds(5));
}
return 0;
}
```

Compile and run the file `leak_loop_malloc.cpp` and run `memleak`
```
$ g++ leak_loop_malloc.cpp
$ ./a.out &
[1] 322727
$ sudo ./memleak -p 322727
using default object: libc.so.6
using page size: 4096
tracing kernel: false
Tracing outstanding memory allocs... Hit Ctrl-C to end
[10:14:43] Top 1 stacks with outstanding allocations:
4 bytes in 1 allocations from stack
0 [<000055bddfee31d5>] main+0x2c
1 [<00007f7298e29d90>] __libc_init_first+0x90
[10:14:48] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
0 [<000055bddfee31d5>] main+0x2c
1 [<00007f7298e29d90>] __libc_init_first+0x90
^C[10:14:49] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
0 [<000055bddfee31d5>] main+0x2c
1 [<00007f7298e29d90>] __libc_init_first+0x90
done
```

Some may think that it can happened only on special `main` function.
So I added `foo` function and you can check that `foo` is disappeared in this time.

Test file `leak_loop_foo.cpp`
```c++
#include
#include
int* foo() { return new int; }
int main(int argc, char* argv[]) {
while (true) {
auto p = foo();
std::this_thread::sleep_for(std::chrono::seconds(5));
}
return 0;
}
```

Compile and run the file `leak_loop_foo.cpp` and run `memleak`
```
$ sudo ./memleak -p 322693
using default object: libc.so.6
using page size: 4096
tracing kernel: false
Tracing outstanding memory allocs... Hit Ctrl-C to end
[9:59:28] Top 1 stacks with outstanding allocations:
4 bytes in 1 allocations from stack
0 [<00007f88376ae98c>] _Znwm+0x1c
1 [<0000562fdbf6a1e4>] main+0x27
2 [<00007f8837229d90>] __libc_init_first+0x90
[9:59:33] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
0 [<00007f88376ae98c>] _Znwm+0x1c
1 [<0000562fdbf6a1e4>] main+0x27
2 [<00007f8837229d90>] __libc_init_first+0x90
^C[9:59:33] Top 1 stacks with outstanding allocations:
8 bytes in 2 allocations from stack
0 [<00007f88376ae98c>] _Znwm+0x1c
1 [<0000562fdbf6a1e4>] main+0x27
2 [<00007f8837229d90>] __libc_init_first+0x90
done
```

You can find `main` but cannot find `foo`.
Some may think that maybe compiler optimize out the `foo` function.
But no. It is not optimized out. It exists inside the binary.
And it is exactly pointing out the return address of `foo` function(`0x11e4`).
`main` function start address(`0x11bd`) + offset(`0x27`) = `0x11e4`

```
$ objdump -D a.out
... snip ...
00000000000011bd :
11bd: f3 0f 1e fa endbr64
11c1: 55 push %rbp
11c2: 48 89 e5 mov %rsp,%rbp
11c5: 48 83 ec 30 sub $0x30,%rsp
11c9: 89 7d dc mov %edi,-0x24(%rbp)
11cc: 48 89 75 d0 mov %rsi,-0x30(%rbp)
11d0: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
11d7: 00 00
11d9: 48 89 45 f8 mov %rax,-0x8(%rbp)
11dd: 31 c0 xor %eax,%eax
11df: e8 c5 ff ff ff call 11a9 <_Z3foov>
11e4: 48 89 45 f0 mov %rax,-0x10(%rbp)
11e8: c7 45 e4 05 00 00 00 movl $0x5,-0x1c(%rbp)
11ef: 48 8d 55 e4 lea -0x1c(%rbp),%rdx
11f3: 48 8d 45 e8 lea -0x18(%rbp),%rax
11f7: 48 89 d6 mov %rdx,%rsi
11fa: 48 89 c7 mov %rax,%rdi
11fd: e8 8c 00 00 00 call 128e <_ZNSt6chrono8durationIlSt5ratioILl1ELl1EEEC1IivEERKT_>
1202: 48 8d 45 e8 lea -0x18(%rbp),%rax
1206: 48 89 c7 mov %rax,%rdi
1209: e8 ef 01 00 00 call 13fd <_ZNSt11this_thread9sleep_forIlSt5ratioILl1ELl1EEEEvRKNSt6chrono8durationIT_T0_EE>
120e: eb cf jmp 11df
```

Environment
```
x86_64

$ cat /etc/issue.net
Ubuntu 22.04.3 LTS
$ g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Reproduce the report with leak_loop_new.cpp, leak_loop_malloc.cpp, and leak_loop_foo.cpp using the native memleak tool and memleak.py. Compare the captured stacks for malloc and new, then inspect how memleak records and symbolizes allocation return addresses. Done means the C++ new cases retain the expected caller frames, including foo or main, with symbolized output.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, linux, python
Domain
devtools, observability, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.