memleak: the symbol of the function that calls `new` disappeared
- 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