Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
B
bcc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
bcc
Commits
33522d7b
Commit
33522d7b
authored
Feb 08, 2016
by
Sasha Goldshtein
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixed indentation and Python style issues from pep
parent
dda47697
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
267 additions
and
199 deletions
+267
-199
tools/memleak.c
tools/memleak.c
+39
-39
tools/memleak.py
tools/memleak.py
+188
-160
tools/memleak_examples.txt
tools/memleak_examples.txt
+40
-0
No files found.
tools/memleak.c
View file @
33522d7b
...
@@ -3,10 +3,10 @@
...
@@ -3,10 +3,10 @@
#define MAX_STACK_SIZE 10
#define MAX_STACK_SIZE 10
struct
alloc_info_t
{
struct
alloc_info_t
{
u64
size
;
u64
size
;
u64
timestamp_ns
;
u64
timestamp_ns
;
int
num_frames
;
int
num_frames
;
u64
callstack
[
MAX_STACK_SIZE
];
u64
callstack
[
MAX_STACK_SIZE
];
};
};
BPF_HASH
(
sizes
,
u64
);
BPF_HASH
(
sizes
,
u64
);
...
@@ -14,23 +14,23 @@ BPF_HASH(allocs, u64, struct alloc_info_t);
...
@@ -14,23 +14,23 @@ BPF_HASH(allocs, u64, struct alloc_info_t);
// Adapted from https://github.com/iovisor/bcc/tools/offcputime.py
// Adapted from https://github.com/iovisor/bcc/tools/offcputime.py
static
u64
get_frame
(
u64
*
bp
)
{
static
u64
get_frame
(
u64
*
bp
)
{
if
(
*
bp
)
{
if
(
*
bp
)
{
// The following stack walker is x86_64 specific
// The following stack walker is x86_64 specific
u64
ret
=
0
;
u64
ret
=
0
;
if
(
bpf_probe_read
(
&
ret
,
sizeof
(
ret
),
(
void
*
)(
*
bp
+
8
)))
if
(
bpf_probe_read
(
&
ret
,
sizeof
(
ret
),
(
void
*
)(
*
bp
+
8
)))
return
0
;
return
0
;
if
(
bpf_probe_read
(
bp
,
sizeof
(
*
bp
),
(
void
*
)
*
bp
))
if
(
bpf_probe_read
(
bp
,
sizeof
(
*
bp
),
(
void
*
)
*
bp
))
*
bp
=
0
;
*
bp
=
0
;
return
ret
;
return
ret
;
}
}
return
0
;
return
0
;
}
}
static
int
grab_stack
(
struct
pt_regs
*
ctx
,
struct
alloc_info_t
*
info
)
static
int
grab_stack
(
struct
pt_regs
*
ctx
,
struct
alloc_info_t
*
info
)
{
{
int
depth
=
0
;
int
depth
=
0
;
u64
bp
=
ctx
->
bp
;
u64
bp
=
ctx
->
bp
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
...
@@ -39,14 +39,14 @@ static int grab_stack(struct pt_regs *ctx, struct alloc_info_t *info)
...
@@ -39,14 +39,14 @@ static int grab_stack(struct pt_regs *ctx, struct alloc_info_t *info)
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
if
(
!
(
info
->
callstack
[
depth
++
]
=
get_frame
(
&
bp
)))
return
depth
;
return
depth
;
return
depth
;
}
}
int
alloc_enter
(
struct
pt_regs
*
ctx
,
size_t
size
)
int
alloc_enter
(
struct
pt_regs
*
ctx
,
size_t
size
)
{
{
u64
pid
=
bpf_get_current_pid_tgid
();
u64
pid
=
bpf_get_current_pid_tgid
();
u64
size64
=
size
;
u64
size64
=
size
;
sizes
.
update
(
&
pid
,
&
size64
);
sizes
.
update
(
&
pid
,
&
size64
);
if
(
SHOULD_PRINT
)
if
(
SHOULD_PRINT
)
bpf_trace_printk
(
"alloc entered, size = %u
\n
"
,
size
);
bpf_trace_printk
(
"alloc entered, size = %u
\n
"
,
size
);
...
@@ -55,21 +55,21 @@ int alloc_enter(struct pt_regs *ctx, size_t size)
...
@@ -55,21 +55,21 @@ int alloc_enter(struct pt_regs *ctx, size_t size)
int
alloc_exit
(
struct
pt_regs
*
ctx
)
int
alloc_exit
(
struct
pt_regs
*
ctx
)
{
{
u64
address
=
ctx
->
ax
;
u64
address
=
ctx
->
ax
;
u64
pid
=
bpf_get_current_pid_tgid
();
u64
pid
=
bpf_get_current_pid_tgid
();
u64
*
size64
=
sizes
.
lookup
(
&
pid
);
u64
*
size64
=
sizes
.
lookup
(
&
pid
);
struct
alloc_info_t
info
=
{
0
};
struct
alloc_info_t
info
=
{
0
};
if
(
size64
==
0
)
if
(
size64
==
0
)
return
0
;
// missed alloc entry
return
0
;
// missed alloc entry
info
.
size
=
*
size64
;
info
.
size
=
*
size64
;
sizes
.
delete
(
&
pid
);
sizes
.
delete
(
&
pid
);
info
.
timestamp_ns
=
bpf_ktime_get_ns
();
info
.
timestamp_ns
=
bpf_ktime_get_ns
();
info
.
num_frames
=
grab_stack
(
ctx
,
&
info
)
-
2
;
info
.
num_frames
=
grab_stack
(
ctx
,
&
info
)
-
2
;
allocs
.
update
(
&
address
,
&
info
);
allocs
.
update
(
&
address
,
&
info
);
if
(
SHOULD_PRINT
)
if
(
SHOULD_PRINT
)
bpf_trace_printk
(
"alloc exited, size = %lu, result = %lx, frames = %d
\n
"
,
info
.
size
,
address
,
info
.
num_frames
);
bpf_trace_printk
(
"alloc exited, size = %lu, result = %lx, frames = %d
\n
"
,
info
.
size
,
address
,
info
.
num_frames
);
return
0
;
return
0
;
...
@@ -77,12 +77,12 @@ int alloc_exit(struct pt_regs *ctx)
...
@@ -77,12 +77,12 @@ int alloc_exit(struct pt_regs *ctx)
int
free_enter
(
struct
pt_regs
*
ctx
,
void
*
address
)
int
free_enter
(
struct
pt_regs
*
ctx
,
void
*
address
)
{
{
u64
addr
=
(
u64
)
address
;
u64
addr
=
(
u64
)
address
;
struct
alloc_info_t
*
info
=
allocs
.
lookup
(
&
addr
);
struct
alloc_info_t
*
info
=
allocs
.
lookup
(
&
addr
);
if
(
info
==
0
)
if
(
info
==
0
)
return
0
;
return
0
;
allocs
.
delete
(
&
addr
);
allocs
.
delete
(
&
addr
);
if
(
SHOULD_PRINT
)
if
(
SHOULD_PRINT
)
bpf_trace_printk
(
"free entered, address = %lx, size = %lu
\n
"
,
address
,
info
->
size
);
bpf_trace_printk
(
"free entered, address = %lx, size = %lu
\n
"
,
address
,
info
->
size
);
...
...
tools/memleak.py
View file @
33522d7b
...
@@ -8,139 +8,159 @@ import ctypes
...
@@ -8,139 +8,159 @@ import ctypes
import
os
import
os
class
Time
(
object
):
class
Time
(
object
):
# BPF timestamps come from the monotonic clock. To be able to filter
# BPF timestamps come from the monotonic clock. To be able to filter
# and compare them from Python, we need to invoke clock_gettime from librt.
# and compare them from Python, we need to invoke clock_gettime.
# Adapted from http://stackoverflow.com/a/1205762
# Adapted from http://stackoverflow.com/a/1205762
CLOCK_MONOTONIC_RAW
=
4
# see <linux/time.h>
CLOCK_MONOTONIC_RAW
=
4
# see <linux/time.h>
class
timespec
(
ctypes
.
Structure
):
class
timespec
(
ctypes
.
Structure
):
_fields_
=
[
_fields_
=
[
(
'tv_sec'
,
ctypes
.
c_long
),
(
'tv_sec'
,
ctypes
.
c_long
),
(
'tv_nsec'
,
ctypes
.
c_long
)
(
'tv_nsec'
,
ctypes
.
c_long
)
]
]
librt
=
ctypes
.
CDLL
(
'librt.so.1'
,
use_errno
=
True
)
librt
=
ctypes
.
CDLL
(
'librt.so.1'
,
use_errno
=
True
)
clock_gettime
=
librt
.
clock_gettime
clock_gettime
=
librt
.
clock_gettime
clock_gettime
.
argtypes
=
[
ctypes
.
c_int
,
ctypes
.
POINTER
(
timespec
)]
clock_gettime
.
argtypes
=
[
ctypes
.
c_int
,
ctypes
.
POINTER
(
timespec
)]
@
staticmethod
@
staticmethod
def
monotonic_time
():
def
monotonic_time
():
t
=
Time
.
timespec
()
t
=
Time
.
timespec
()
if
Time
.
clock_gettime
(
Time
.
CLOCK_MONOTONIC_RAW
,
ctypes
.
pointer
(
t
))
!=
0
:
if
Time
.
clock_gettime
(
errno_
=
ctypes
.
get_errno
()
Time
.
CLOCK_MONOTONIC_RAW
,
ctypes
.
pointer
(
t
))
!=
0
:
raise
OSError
(
errno_
,
os
.
strerror
(
errno_
))
errno_
=
ctypes
.
get_errno
()
return
t
.
tv_sec
*
1e9
+
t
.
tv_nsec
raise
OSError
(
errno_
,
os
.
strerror
(
errno_
))
return
t
.
tv_sec
*
1e9
+
t
.
tv_nsec
class
StackDecoder
(
object
):
class
StackDecoder
(
object
):
def
__init__
(
self
,
pid
,
bpf
):
def
__init__
(
self
,
pid
,
bpf
):
self
.
pid
=
pid
self
.
pid
=
pid
self
.
bpf
=
bpf
self
.
bpf
=
bpf
self
.
ranges_cache
=
{}
self
.
ranges_cache
=
{}
self
.
refresh_code_ranges
()
self
.
refresh_code_ranges
()
def
refresh_code_ranges
(
self
):
def
refresh_code_ranges
(
self
):
if
self
.
pid
==
-
1
:
if
self
.
pid
==
-
1
:
return
return
self
.
code_ranges
=
self
.
_get_code_ranges
()
self
.
code_ranges
=
self
.
_get_code_ranges
()
def
_get_code_ranges
(
self
):
@
staticmethod
ranges
=
{}
def
_is_binary_segment
(
parts
):
raw_ranges
=
open
(
"/proc/%d/maps"
%
self
.
pid
).
readlines
()
return
len
(
parts
)
==
6
and
\
for
raw_range
in
raw_ranges
:
parts
[
5
][
0
]
==
'['
and
'x'
in
parts
[
1
]
# A typical line from /proc/PID/maps looks like this:
# 7f21b6635000-7f21b67eb000 r-xp 00000000 fd:00 1442606 /usr/lib64/libc-2.21.so
def
_get_code_ranges
(
self
):
# We are looking for executable segments that have a binary (.so
ranges
=
{}
# or the main executable). The first two lines are the range of
raw_ranges
=
open
(
"/proc/%d/maps"
%
self
.
pid
).
readlines
()
# that memory segment, which we index by binary name.
# A typical line from /proc/PID/maps looks like this:
parts
=
raw_range
.
split
()
# 7f21b6635000-7f21b67eb000 r-xp ... /usr/lib64/libc-2.21.so
if
len
(
parts
)
<
6
or
parts
[
5
][
0
]
==
'['
or
not
'x'
in
parts
[
1
]:
# We are looking for executable segments that have a .so file
continue
# or the main executable. The first two lines are the range of
binary
=
parts
[
5
]
# that memory segment, which we index by binary name.
range_parts
=
parts
[
0
].
split
(
'-'
)
for
raw_range
in
raw_ranges
:
addr_range
=
(
int
(
range_parts
[
0
],
16
),
int
(
range_parts
[
1
],
16
))
parts
=
raw_range
.
split
()
ranges
[
binary
]
=
addr_range
if
not
StackDecoder
.
_is_binary_segment
(
parts
):
return
ranges
continue
binary
=
parts
[
5
]
def
_get_sym_ranges
(
self
,
binary
):
range_parts
=
parts
[
0
].
split
(
'-'
)
if
binary
in
self
.
ranges_cache
:
addr_range
=
(
int
(
range_parts
[
0
],
16
),
return
self
.
ranges_cache
[
binary
]
int
(
range_parts
[
1
],
16
))
sym_ranges
=
{}
ranges
[
binary
]
=
addr_range
raw_symbols
=
run_command_get_output
(
"objdump -t %s"
%
binary
)
return
ranges
for
raw_symbol
in
raw_symbols
:
# A typical line from objdump -t looks like this:
@
staticmethod
# 00000000004007f5 g F .text 000000000000010e main
def
_is_function_symbol
(
parts
):
# We only care about functions (F) in the .text segment. The first
return
len
(
parts
)
==
6
and
parts
[
3
]
==
".text"
\
# number is the start address, and the second number is the length.
and
parts
[
2
]
==
"F"
parts
=
raw_symbol
.
split
()
if
len
(
parts
)
<
6
or
parts
[
3
]
!=
".text"
or
parts
[
2
]
!=
"F"
:
def
_get_sym_ranges
(
self
,
binary
):
continue
if
binary
in
self
.
ranges_cache
:
sym_start
=
int
(
parts
[
0
],
16
)
return
self
.
ranges_cache
[
binary
]
sym_len
=
int
(
parts
[
4
],
16
)
sym_ranges
=
{}
sym_name
=
parts
[
5
]
raw_symbols
=
run_command_get_output
(
"objdump -t %s"
%
binary
)
sym_ranges
[
sym_name
]
=
(
sym_start
,
sym_len
)
for
raw_symbol
in
raw_symbols
:
self
.
ranges_cache
[
binary
]
=
sym_ranges
# A typical line from objdump -t looks like this:
return
sym_ranges
# 00000000004007f5 g F .text 000000000000010e main
# We only care about functions in the .text segment.
def
_decode_sym
(
self
,
binary
,
offset
):
# The first number is the start address, and the second
sym_ranges
=
self
.
_get_sym_ranges
(
binary
)
# number is the length.
# Find the symbol that contains the specified offset. There might not be one.
parts
=
raw_symbol
.
split
()
for
name
,
(
start
,
length
)
in
sym_ranges
.
items
():
if
not
StackDecoder
.
_is_function_symbol
(
parts
):
if
offset
>=
start
and
offset
<=
(
start
+
length
):
continue
return
"%s+0x%x"
%
(
name
,
offset
-
start
)
sym_start
=
int
(
parts
[
0
],
16
)
return
"%x"
%
offset
sym_len
=
int
(
parts
[
4
],
16
)
sym_name
=
parts
[
5
]
def
_decode_addr
(
self
,
addr
):
sym_ranges
[
sym_name
]
=
(
sym_start
,
sym_len
)
code_ranges
=
self
.
_get_code_ranges
()
self
.
ranges_cache
[
binary
]
=
sym_ranges
# Find the binary that contains the specified address. For .so files, look
return
sym_ranges
# at the relative address; for the main executable, look at the absolute
# address.
def
_decode_sym
(
self
,
binary
,
offset
):
for
binary
,
(
start
,
end
)
in
code_ranges
.
items
():
sym_ranges
=
self
.
_get_sym_ranges
(
binary
)
if
addr
>=
start
and
addr
<=
end
:
# Find the symbol that contains the specified offset.
offset
=
addr
-
start
if
binary
.
endswith
(
".so"
)
else
addr
# There might not be one.
return
"%s [%s]"
%
(
self
.
_decode_sym
(
binary
,
offset
),
binary
)
for
name
,
(
start
,
length
)
in
sym_ranges
.
items
():
return
"%x"
%
addr
if
offset
>=
start
and
offset
<=
(
start
+
length
):
return
"%s+0x%x"
%
(
name
,
offset
-
start
)
def
decode_stack
(
self
,
info
,
is_kernel_trace
):
return
"%x"
%
offset
stack
=
""
if
info
.
num_frames
<=
0
:
def
_decode_addr
(
self
,
addr
):
return
"???"
code_ranges
=
self
.
_get_code_ranges
()
for
i
in
range
(
0
,
info
.
num_frames
):
# Find the binary that contains the specified address.
addr
=
info
.
callstack
[
i
]
# For .so files, look at the relative address; for the main
if
is_kernel_trace
:
# executable, look at the absolute address.
stack
+=
" %s [kernel] (%x) ;"
%
(
self
.
bpf
.
ksym
(
addr
),
addr
)
for
binary
,
(
start
,
end
)
in
code_ranges
.
items
():
else
:
if
addr
>=
start
and
addr
<=
end
:
# At some point, we hope to have native BPF user-mode symbol
offset
=
addr
-
start
\
# decoding, but for now we have to use our own
if
binary
.
endswith
(
".so"
)
else
addr
stack
+=
" %s (%x) ;"
%
(
self
.
_decode_addr
(
addr
),
addr
)
return
"%s [%s]"
%
(
self
.
_decode_sym
(
binary
,
return
stack
offset
),
binary
)
return
"%x"
%
addr
def
decode_stack
(
self
,
info
,
is_kernel_trace
):
stack
=
""
if
info
.
num_frames
<=
0
:
return
"???"
for
i
in
range
(
0
,
info
.
num_frames
):
addr
=
info
.
callstack
[
i
]
if
is_kernel_trace
:
stack
+=
" %s [kernel] (%x) ;"
%
\
(
self
.
bpf
.
ksym
(
addr
),
addr
)
else
:
# At some point, we hope to have native BPF
# user-mode symbol decoding, but for now we
# have to use our own.
stack
+=
" %s (%x) ;"
%
\
(
self
.
_decode_addr
(
addr
),
addr
)
return
stack
def
run_command_get_output
(
command
):
def
run_command_get_output
(
command
):
p
=
subprocess
.
Popen
(
command
.
split
(),
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
)
p
=
subprocess
.
Popen
(
command
.
split
(),
return
iter
(
p
.
stdout
.
readline
,
b''
)
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
)
return
iter
(
p
.
stdout
.
readline
,
b''
)
def
run_command_get_pid
(
command
):
def
run_command_get_pid
(
command
):
p
=
subprocess
.
Popen
(
command
.
split
())
p
=
subprocess
.
Popen
(
command
.
split
())
return
p
.
pid
return
p
.
pid
examples
=
"""
examples
=
"""
EXAMPLES:
EXAMPLES:
./memleak.py -p $(pidof allocs)
./memleak.py -p $(pidof allocs)
Trace allocations and display a summary of "leaked" (outstanding)
Trace allocations and display a summary of "leaked" (outstanding)
allocations every 5 seconds
allocations every 5 seconds
./memleak.py -p $(pidof allocs) -t
./memleak.py -p $(pidof allocs) -t
Trace allocations and display each individual call to malloc/free
Trace allocations and display each individual call to malloc/free
./memleak.py -p $(pidof allocs) -a -i 10
./memleak.py -p $(pidof allocs) -a -i 10
Trace allocations and display allocated addresses, sizes, and stacks
Trace allocations and display allocated addresses, sizes, and stacks
every 10 seconds for outstanding allocations
every 10 seconds for outstanding allocations
./memleak.py -c "./allocs"
./memleak.py -c "./allocs"
Run the specified command and trace its allocations
Run the specified command and trace its allocations
./memleak.py
./memleak.py
Trace allocations in kernel mode and display a summary of outstanding
Trace allocations in kernel mode and display a summary of outstanding
allocations every 5 seconds
allocations every 5 seconds
./memleak.py -o 60000
./memleak.py -o 60000
Trace allocations in kernel mode and display a summary of outstanding
Trace allocations in kernel mode and display a summary of outstanding
allocations that are at least one minute (60 seconds) old
allocations that are at least one minute (60 seconds) old
"""
"""
description
=
"""
description
=
"""
...
@@ -150,20 +170,20 @@ allocations made with kmalloc/kfree.
...
@@ -150,20 +170,20 @@ allocations made with kmalloc/kfree.
"""
"""
parser
=
argparse
.
ArgumentParser
(
description
=
description
,
parser
=
argparse
.
ArgumentParser
(
description
=
description
,
formatter_class
=
argparse
.
RawDescriptionHelpFormatter
,
formatter_class
=
argparse
.
RawDescriptionHelpFormatter
,
epilog
=
examples
)
epilog
=
examples
)
parser
.
add_argument
(
"-p"
,
"--pid"
,
parser
.
add_argument
(
"-p"
,
"--pid"
,
help
=
"the PID to trace; if not specified, trace kernel allocs"
)
help
=
"the PID to trace; if not specified, trace kernel allocs"
)
parser
.
add_argument
(
"-t"
,
"--trace"
,
action
=
"store_true"
,
parser
.
add_argument
(
"-t"
,
"--trace"
,
action
=
"store_true"
,
help
=
"print trace messages for each alloc/free call"
)
help
=
"print trace messages for each alloc/free call"
)
parser
.
add_argument
(
"-i"
,
"--interval"
,
default
=
5
,
parser
.
add_argument
(
"-i"
,
"--interval"
,
default
=
5
,
help
=
"interval in seconds to print outstanding allocations"
)
help
=
"interval in seconds to print outstanding allocations"
)
parser
.
add_argument
(
"-a"
,
"--show-allocs"
,
default
=
False
,
action
=
"store_true"
,
parser
.
add_argument
(
"-a"
,
"--show-allocs"
,
default
=
False
,
action
=
"store_true"
,
help
=
"show allocation addresses and sizes as well as call stacks"
)
help
=
"show allocation addresses and sizes as well as call stacks"
)
parser
.
add_argument
(
"-o"
,
"--older"
,
default
=
500
,
parser
.
add_argument
(
"-o"
,
"--older"
,
default
=
500
,
help
=
"prune allocations younger than this age in milliseconds"
)
help
=
"prune allocations younger than this age in milliseconds"
)
parser
.
add_argument
(
"-c"
,
"--command"
,
parser
.
add_argument
(
"-c"
,
"--command"
,
help
=
"execute and trace the specified command"
)
help
=
"execute and trace the specified command"
)
args
=
parser
.
parse_args
()
args
=
parser
.
parse_args
()
...
@@ -172,11 +192,11 @@ command = args.command
...
@@ -172,11 +192,11 @@ command = args.command
kernel_trace
=
(
pid
==
-
1
and
command
is
None
)
kernel_trace
=
(
pid
==
-
1
and
command
is
None
)
trace_all
=
args
.
trace
trace_all
=
args
.
trace
interval
=
int
(
args
.
interval
)
interval
=
int
(
args
.
interval
)
min_age_ns
=
1e6
*
int
(
args
.
older
)
min_age_ns
=
1e6
*
int
(
args
.
older
)
if
not
command
is
None
:
if
command
is
not
None
:
print
(
"Executing '%s' and tracing the resulting process."
%
command
)
print
(
"Executing '%s' and tracing the resulting process."
%
command
)
pid
=
run_command_get_pid
(
command
)
pid
=
run_command_get_pid
(
command
)
bpf_source
=
open
(
"memleak.c"
).
read
()
bpf_source
=
open
(
"memleak.c"
).
read
()
bpf_source
=
bpf_source
.
replace
(
"SHOULD_PRINT"
,
"1"
if
trace_all
else
"0"
)
bpf_source
=
bpf_source
.
replace
(
"SHOULD_PRINT"
,
"1"
if
trace_all
else
"0"
)
...
@@ -184,41 +204,49 @@ bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
...
@@ -184,41 +204,49 @@ bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
bpf_program
=
BPF
(
text
=
bpf_source
)
bpf_program
=
BPF
(
text
=
bpf_source
)
if
not
kernel_trace
:
if
not
kernel_trace
:
print
(
"Attaching to malloc and free in pid %d, Ctrl+C to quit."
%
pid
)
print
(
"Attaching to malloc and free in pid %d, Ctrl+C to quit."
%
pid
)
bpf_program
.
attach_uprobe
(
name
=
"c"
,
sym
=
"malloc"
,
fn_name
=
"alloc_enter"
,
pid
=
pid
)
bpf_program
.
attach_uprobe
(
name
=
"c"
,
sym
=
"malloc"
,
bpf_program
.
attach_uretprobe
(
name
=
"c"
,
sym
=
"malloc"
,
fn_name
=
"alloc_exit"
,
pid
=
pid
)
fn_name
=
"alloc_enter"
,
pid
=
pid
)
bpf_program
.
attach_uprobe
(
name
=
"c"
,
sym
=
"free"
,
fn_name
=
"free_enter"
,
pid
=
pid
)
bpf_program
.
attach_uretprobe
(
name
=
"c"
,
sym
=
"malloc"
,
fn_name
=
"alloc_exit"
,
pid
=
pid
)
bpf_program
.
attach_uprobe
(
name
=
"c"
,
sym
=
"free"
,
fn_name
=
"free_enter"
,
pid
=
pid
)
else
:
else
:
print
(
"Attaching to kmalloc and kfree, Ctrl+C to quit."
)
print
(
"Attaching to kmalloc and kfree, Ctrl+C to quit."
)
bpf_program
.
attach_kprobe
(
event
=
"__kmalloc"
,
fn_name
=
"alloc_enter"
)
bpf_program
.
attach_kprobe
(
event
=
"__kmalloc"
,
fn_name
=
"alloc_enter"
)
bpf_program
.
attach_kretprobe
(
event
=
"__kmalloc"
,
fn_name
=
"alloc_exit"
)
bpf_program
.
attach_kretprobe
(
event
=
"__kmalloc"
,
fn_name
=
"alloc_exit"
)
bpf_program
.
attach_kprobe
(
event
=
"kfree"
,
fn_name
=
"free_enter"
)
bpf_program
.
attach_kprobe
(
event
=
"kfree"
,
fn_name
=
"free_enter"
)
decoder
=
StackDecoder
(
pid
,
bpf_program
)
decoder
=
StackDecoder
(
pid
,
bpf_program
)
def
print_outstanding
():
def
print_outstanding
():
stacks
=
{}
stacks
=
{}
print
(
"*** Outstanding allocations:"
)
print
(
"*** Outstanding allocations:"
)
allocs
=
bpf_program
.
get_table
(
"allocs"
)
allocs
=
bpf_program
.
get_table
(
"allocs"
)
for
address
,
info
in
sorted
(
allocs
.
items
(),
key
=
lambda
a
:
a
[
1
].
size
):
for
address
,
info
in
sorted
(
allocs
.
items
(),
key
=
lambda
a
:
a
[
1
].
size
):
if
Time
.
monotonic_time
()
-
min_age_ns
<
info
.
timestamp_ns
:
if
Time
.
monotonic_time
()
-
min_age_ns
<
info
.
timestamp_ns
:
continue
continue
stack
=
decoder
.
decode_stack
(
info
,
kernel_trace
)
stack
=
decoder
.
decode_stack
(
info
,
kernel_trace
)
if
stack
in
stacks
:
stacks
[
stack
]
=
(
stacks
[
stack
][
0
]
+
1
,
stacks
[
stack
][
1
]
+
info
.
size
)
if
stack
in
stacks
:
else
:
stacks
[
stack
]
=
(
1
,
info
.
size
)
stacks
[
stack
]
=
(
stacks
[
stack
][
0
]
+
1
,
if
args
.
show_allocs
:
stacks
[
stack
][
1
]
+
info
.
size
)
print
(
"
\
t
addr = %x size = %s"
%
(
address
.
value
,
info
.
size
))
else
:
for
stack
,
(
count
,
size
)
in
sorted
(
stacks
.
items
(),
key
=
lambda
s
:
s
[
1
][
1
]):
stacks
[
stack
]
=
(
1
,
info
.
size
)
print
(
"
\
t
%d bytes in %d allocations from stack
\
n
\
t
\
t
%s"
%
(
size
,
count
,
stack
.
replace
(
";"
,
"
\
n
\
t
\
t
"
)))
if
args
.
show_allocs
:
print
(
"
\
t
addr = %x size = %s"
%
(
address
.
value
,
info
.
size
))
for
stack
,
(
count
,
size
)
in
sorted
(
stacks
.
items
(),
key
=
lambda
s
:
s
[
1
][
1
]):
print
(
"
\
t
%d bytes in %d allocations from stack
\
n
\
t
\
t
%s"
%
(
size
,
count
,
stack
.
replace
(
";"
,
"
\
n
\
t
\
t
"
)))
while
True
:
while
True
:
if
trace_all
:
if
trace_all
:
print
bpf_program
.
trace_fields
()
print
bpf_program
.
trace_fields
()
else
:
else
:
try
:
try
:
sleep
(
interval
)
sleep
(
interval
)
except
KeyboardInterrupt
:
except
KeyboardInterrupt
:
exit
()
exit
()
decoder
.
refresh_code_ranges
()
decoder
.
refresh_code_ranges
()
print_outstanding
()
print_outstanding
()
tools/memleak_examples.txt
View file @
33522d7b
...
@@ -19,6 +19,42 @@ Attaching to malloc and free in pid 5193, Ctrl+C to quit.
...
@@ -19,6 +19,42 @@ Attaching to malloc and free in pid 5193, Ctrl+C to quit.
__libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fd460ac2790)
__libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fd460ac2790)
As time goes on, it becomes apparent that the main function in the allocs
process is leaking memory, 16 bytes at a time. Fortunately, you don't have to
inspect each allocation individually -- you get a nice summary of which stack
is responsible for a large leak.
Occasionally, you do want the individual allocation details. Perhaps the same
stack is allocating various sizes and you want to confirm which sizes are
prevalent. Use the -a switch:
# ./memleak.py -p $(pidof allocs) -a
Attaching to malloc and free in pid 5193, Ctrl+C to quit.
*** Outstanding allocations:
addr = 948cd0 size = 16
addr = 948d10 size = 16
addr = 948d30 size = 16
addr = 948cf0 size = 16
64 bytes in 4 allocations from stack
main+0x6d [/home/vagrant/allocs] (400862)
__libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fd460ac2790)
*** Outstanding allocations:
addr = 948d50 size = 16
addr = 948cd0 size = 16
addr = 948d10 size = 16
addr = 948d30 size = 16
addr = 948cf0 size = 16
addr = 948dd0 size = 16
addr = 948d90 size = 16
addr = 948db0 size = 16
addr = 948d70 size = 16
addr = 948df0 size = 16
160 bytes in 10 allocations from stack
main+0x6d [/home/vagrant/allocs] (400862)
__libc_start_main+0xf0 [/usr/lib64/libc-2.21.so] (7fd460ac2790)
When using the -p switch, memleak traces the allocations of a particular
When using the -p switch, memleak traces the allocations of a particular
process. Without this switch, kernel allocations (kmalloc) are traced instead.
process. Without this switch, kernel allocations (kmalloc) are traced instead.
For example:
For example:
...
@@ -58,6 +94,10 @@ Attaching to kmalloc and kfree, Ctrl+C to quit.
...
@@ -58,6 +94,10 @@ Attaching to kmalloc and kfree, Ctrl+C to quit.
perf_tp_event_init [kernel] (ffffffff81192479)
perf_tp_event_init [kernel] (ffffffff81192479)
Here you can see that arming the kprobe to which our eBPF program is attached
consumed 8KB of memory. Loading the BPF program also consumed a couple hundred
bytes (in bpf_prog_load).
memleak stores each allocated block along with its size, timestamp, and the
memleak stores each allocated block along with its size, timestamp, and the
stack that allocated it. When the block is deleted, this information is freed
stack that allocated it. When the block is deleted, this information is freed
to reduce the memory overhead.
to reduce the memory overhead.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment