From b5d0dbaa6bba0f01e24634886fbd06264aad016f Mon Sep 17 00:00:00 2001
From: wangshuo <wangshuo47(a)huawei.com>
Date: Mon, 15 Mar 2021 11:37:13 +0000
Subject: [PATCH] memory: add memtool.sh for glibc memory debug
glibc memory debug
The memory management of the glibc is widely used, but the methods for fault locating are limited. A command-line tool is provided to detect memory leakage of user-mode processes and collect memory distribution information about user-mode processes.
Obtains the memory distribution information of user-mode processes.
The implementation principle is to use the gcore to save the memory copy of the process, and then analyze the memory allocation of the memory copy.
memtool show -p -e -f
Detects memory leaks of user-mode processes within a period of time.
The implementation principle is to enable the memory trace mechanism of the glibc through the gdb, and then disable the trace function after a period of time.
memtool trace -p -t -f
---
doc/memtool.en.md | 13 ++
doc/memtool.md | 13 ++
src/memory/memtool.sh | 415 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 441 insertions(+)
create mode 100644 doc/memtool.en.md
create mode 100644 doc/memtool.md
create mode 100755 src/memory/memtool.sh
diff --git a/doc/memtool.en.md b/doc/memtool.en.md
new file mode 100644
index 0000000..ab6eb43
--- /dev/null
+++ b/doc/memtool.en.md
@@ -0,0 +1,13 @@
+# memtool
+
+glibc memory debug
+The memory management of the glibc is widely used, but the methods for fault locating are limited. A command-line tool is provided to detect memory leakage of user-mode processes and collect memory distribution information about user-mode processes.
+
+Obtains the memory distribution information of user-mode processes.
+The implementation principle is to use the gcore to save the memory copy of the process, and then analyze the memory allocation of the memory copy.
+memtool show -p -e -f
+
+Detects memory leaks of user-mode processes within a period of time.
+The implementation principle is to enable the memory trace mechanism of the glibc through the gdb, and then disable the trace function after a period of time.
+memtool trace -p -t -f
+
diff --git a/doc/memtool.md b/doc/memtool.md
new file mode 100644
index 0000000..992ede6
--- /dev/null
+++ b/doc/memtool.md
@@ -0,0 +1,13 @@
+# memtool
+
+glibc内存调测
+glibc的内存管理被广泛使用,但调测定位手段有限。现提供一个命令行工具,工具可以检测用户态进程内存泄漏,收集用户态进程内存分布信息。
+
+获取用户态进程内存分布信息。
+实现原理是通过gcore保存进程的内存副本, 然后分析内存副本的内存分配情况;
+memtool show -p -e -f
+
+检测用户态进程内存一段时间内的内存泄漏情况。
+实现原理是通过gdb开启glibc的内存trace机制, 然后间隔一段时间后关闭trace;
+memtool trace -p -t -f
+
diff --git a/src/memory/memtool.sh b/src/memory/memtool.sh
new file mode 100755
index 0000000..12070b3
--- /dev/null
+++ b/src/memory/memtool.sh
@@ -0,0 +1,415 @@
+#!/bin/bash
+
+#Print the usage.
+function usage()
+{
+ echo "This is a debug tool for glibc memory manage, Usage:"
+ echo " memtool show [options] Show the running process memory distribution"
+ echo " memtool trace [options] Trace the running process memory leaks"
+ echo "Selection of options:"
+ echo " --process, -p Process ID"
+ echo " --files, -f Path of record files"
+ echo " --exe, -e Exe filename with its path"
+ echo " --time, -t Time of trace (sec)"
+ echo " --help, -h Print this message and then exit"
+ echo "Examples:"
+ echo " memtool show -p PID -f path -e filename"
+ echo " memtool trace -p PID -f path -t time"
+}
+
+function trace()
+{
+ local process_id=$1
+ local limit_time=$2
+ local file=$3
+
+ gdb attach $process_id &> /dev/null << EOF
+call setenv("MALLOC_TRACE","$file",1)
+call mtrace()
+EOF
+
+ if [ $? -eq 0 ];then
+ echo "start trace sucessed"
+ else
+ echo "start trace failed"
+ return 1
+ fi
+
+ while true
+ do
+ if [ $limit_time -gt 0 ];then
+ echo -n "$limit_time sec remaining"
+ sleep 1
+ tput rc
+ tput ed
+ let limit_time--
+ if [ ! -d /proc/${process_id} ];then
+ echo "Process does not exist."
+ return 1
+ fi
+ else
+ break
+ fi
+ done
+
+ gdb attach $process_id &> /dev/null << EOF
+call muntrace()
+call unsetenv("MALLOC_TRACE")
+EOF
+
+ if [ $? -eq 0 ];then
+ echo "stop trace sucessed"
+ return 0
+ else
+ echo "stop trace failed"
+ return 1
+ fi
+}
+
+function mm_trace()
+{
+ local process_id=-1
+ local limit_time=-1
+ local file=""
+
+ while [ $# -gt 0 ]
+ do
+ case "$1" in
+ -p|--process)
+ shift
+ if [ $# -gt 0 ];then
+ if [ -d /proc/$1 ];then
+ process_id=$1
+ shift
+ else
+ echo "Invalid process id"
+ usage
+ exit 1
+ fi
+ else
+ echo "Missing parameters"
+ usage
+ exit 1
+ fi
+ ;;
+ -t|--time)
+ shift
+ if [ $# -gt 0 ];then
+ if [ "$1" -gt 0 ] 2>/dev/null ;then
+ limit_time=$1
+ shift
+ else
+ echo "Invalid trace time"
+ usage
+ exit 1
+ fi
+ else
+ echo "Missing parameters"
+ usage
+ exit 1
+ fi
+ ;;
+ -f|--file)
+ shift
+ if [ $# -gt 0 ];then
+ file=$1
+ shift
+ else
+ echo "Missing parameters"
+ usage
+ exit 1
+ fi
+ ;;
+
+ *)
+ echo "Unrecognized parameters!"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+
+ if [ $process_id -eq -1 ];then
+ echo "Missing Process ID"
+ usage
+ exit 1
+ fi
+
+ if [ $limit_time -eq -1 ];then
+ echo "Missing trace time"
+ usage
+ exit 1
+ fi
+
+ if [ -z $file ];then
+ echo "Missing trace file"
+ usage
+ exit 1
+ else
+ if [ ! -d ${file%/*} ];then
+ echo "invalid file path"
+ exit 1
+ fi
+ fi
+
+ trace $process_id $limit_time ${file}-tmp
+
+ if [ -f ${file}-tmp ];then
+ touch ${file}
+ chmod 600 ${file}
+ mtrace ${file}-tmp > $file
+ rm -rf ${file}-tmp
+ fi
+
+ return 0
+}
+
+#Show the running process memory distribution
+function show_mm_distribution
+{
+ local process_id=$1
+ local record_path=$2
+ local exe_file=$3
+
+ gcore -o $record_path/memtool_show_core $process_id &> /dev/null
+ gdb -q $exe_file $record_path/memtool_show_core* >> $record_path/mm_show_result.txt << EOF
+set pagination off
+set \$gdb_size_bits = 7
+set \$gdb_malloc_align_mask = 15
+set \$gdb_heap_max_size = 2 * 4 * 1024 * 1024 * sizeof(long)
+set \$gdb_loop = 1
+set \$gdb_prev_inuse = 0x0000000000000001
+define print_arena_distribution
+ if \$argc != 1
+ help print_arena_distribution
+ else
+ set \$gdb_arena_addr = (mstate)(\$arg0 + 1)
+ if \$gdb_arena_addr == &main_arena
+ set \$gdb_top = \$gdb_arena_addr->top
+ set \$gdb_top_size = \$gdb_arena_addr->top->mchunk_size &~ \$gdb_size_bits
+ set \$gdb_ptr = (unsigned long)\$gdb_top - \$gdb_arena_addr->system_mem + (unsigned long)\$gdb_top_size
+ else
+ set \$gdb_top = \$arg0->ar_ptr->top
+ if \$arg0->ar_ptr != (mstate) (\$arg0 + 1)
+ set \$gdb_ptr = (unsigned long)(\$arg0 + 1)
+ else
+ set \$gdb_ptr = (unsigned long)((mstate)(\$arg0 + 1) + 1)
+ end
+ end
+ set \$gdb_p = (mchunkptr)((\$gdb_ptr + \$gdb_malloc_align_mask) &~ \$gdb_malloc_align_mask)
+ set \$loop_stop_flag = 0
+ while \$gdb_loop == 1
+ if \$loop_stop_flag == 1
+ loop_break
+ end
+ set \$gdb_start_addr = \$gdb_p
+ set \$gdb_next_addr = (mchunkptr)((unsigned long)\$gdb_start_addr + \$gdb_start_addr->mchunk_size &~ \$gdb_size_bits)
+ set \$gdb_end_addr = \$gdb_start_addr
+ set \$gdb_original_use_flag = \$gdb_next_addr->mchunk_size & \$gdb_prev_inuse
+ set \$gdb_now_use_flag = \$gdb_next_addr->mchunk_size & \$gdb_prev_inuse
+ set \$gdb_chunk_num = 0
+ set \$gdb_all_chunk_size = 0
+ while \$gdb_now_use_flag == \$gdb_original_use_flag
+ set \$gdb_chunk_num = \$gdb_chunk_num + 1
+ set \$gdb_all_chunk_size = \$gdb_all_chunk_size + \$gdb_end_addr->mchunk_size &~ \$gdb_size_bits
+ set \$gdb_end_addr = \$gdb_next_addr
+ if \$gdb_end_addr == \$gdb_top
+ set \$loop_stop_flag = 1
+ loop_break
+ end
+ if \$gdb_end_addr->mchunk_size == \$gdb_prev_inuse
+ set \$loop_stop_flag = 1
+ loop_break
+ end
+ set \$gdb_next_addr = (mchunkptr)((unsigned long)\$gdb_next_addr + \$gdb_next_addr->mchunk_size &~ \$gdb_size_bits)
+ set \$gdb_now_use_flag = \$gdb_next_addr->mchunk_size & \$gdb_prev_inuse
+ end
+ printf "%#lx%#20lx%20ld%20ld%10d\n", \$gdb_start_addr, \$gdb_end_addr, \$gdb_chunk_num, \$gdb_all_chunk_size, \$gdb_original_use_flag
+ set \$gdb_p = \$gdb_end_addr
+ end
+ end
+end
+document print_arena_distribution
+Syntax: print_arena_distribution arena_addr
+| Print the arena memory distribution.
+end
+
+define print_mm_distribution
+ if \$argc != 0
+ help print_mm_distribution
+ else
+ set \$gdb_heap = (heap_info*)&main_arena - 1
+ set \$gdb_thread_num = 1
+ printf "\n"
+ printf "\n"
+ printf "start addr"
+ printf " end addr"
+ printf " chunk num"
+ printf " all chunk size"
+ printf " is use\n"
+ print_arena_distribution \$gdb_heap
+ set \$gdb_ar_ptr = main_arena.next
+ while \$gdb_ar_ptr != &main_arena
+ set \$gdb_thread_num = \$gdb_thread_num + 1
+ set \$gdb_heap_ptr = (heap_info *)((unsigned long)\$gdb_ar_ptr->top &~ (\$gdb_heap_max_size - 1))
+ while \$gdb_heap_ptr->ar_ptr != (mstate) (\$gdb_heap_ptr + 1)
+ print_arena_distribution \$gdb_heap_ptr
+ set \$gdb_heap_ptr = \$gdb_heap_ptr->prev
+ end
+ print_arena_distribution \$gdb_heap_ptr
+ set \$gdb_ar_ptr = \$gdb_ar_ptr->next
+ end
+ printf "Printf memory distribution Successfully !"
+ end
+end
+document print_mm_distribution
+Syntax: print_mm_distribution option
+ 0 -- Print the help message
+ nothing -- Print the distribution of memory
+end
+print_mm_distribution
+EOF
+ rm -f $record_path/memtool_show_core*
+}
+
+function mm_show
+{
+ local process_id=-1
+ local record_path=""
+ local exe_file=""
+
+ while [ $# -gt 0 ]
+ do
+ case "$1" in
+ -p|--process)
+ shift
+ if [ $# -eq 0 ]; then
+ echo "Missing process id"
+ usage
+ exit 1
+ else
+ if [ "$1" -gt 0 ] 2>/dev/null; then
+ process_id=$1
+ shift
+ else
+ echo "Invalid process id!"
+ usage
+ exit 1
+ fi
+ if [ ! -d /proc/$process_id ]; then
+ echo "Process do not exist!"
+ exit 1
+ fi
+ fi
+ ;;
+ -f|--files)
+ shift
+ if [ $# -eq 0 ]; then
+ echo "Missing record path"
+ exit 1
+ else
+ record_path=$1
+ if [ ! -d $record_path ]; then
+ echo "$record_path do not exists, please create it"
+ usage
+ exit 1
+ fi
+ record_path=`echo $record_path | sed "s/\/$//g"`
+ shift
+ fi
+ ;;
+ -e|--exe)
+ shift
+ if [ $# -eq 0 ]; then
+ echo "Missing exe filename"
+ usage
+ exit 1
+ else
+ exe_file=$1
+ if [ ! -f $exe_file ]; then
+ echo "Invalid exe filename"
+ usage
+ exit 1
+ fi
+ shift
+ fi
+ ;;
+ *)
+ echo "Unrecognized parameters!"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+
+ if [ $process_id -eq -1 ];then
+ echo "Missing Process ID"
+ usage
+ exit 1
+ fi
+
+ if [ "$record_path" == "" ];then
+ echo "Record path can not be empty!"
+ usage
+ exit 1
+ fi
+
+ if [ "$exe_file" == "" ];then
+ echo "Exe filename can not be empty!"
+ usage
+ exit 1
+ fi
+
+ show_mm_distribution $process_id $record_path $exe_file
+
+ return 0
+}
+
+#judge whether gdb is installed
+which gdb &> /dev/null
+if [ $? -ne 0 ]; then
+ echo "Can not find gdb on this environment"
+ exit 1
+fi
+
+#judge whether glibc-debuginfo is installed
+ls /usr/lib/debug/sbin/ldconfig.debug &> /dev/null
+if [ $? -ne 0 ]; then
+ echo "Can not find glibc-debuginfo on this environment"
+ exit 1
+fi
+
+#judge whether glibc-debugutils is installed
+ls /usr/bin/mtrace &> /dev/null
+if [ $? -ne 0 ]; then
+ echo "Can not find glibc-utils on this environment"
+ exit 1
+fi
+
+if [ $# -lt 1 ] || [ $# -gt 7 ]; then
+ usage
+ exit 1
+fi
+
+case "$1" in
+ show)
+ shift
+ mm_show "$@"
+ exit 0
+ ;;
+ trace)
+ shift
+ mm_trace "$@"
+ exit 0
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "Unrecognized options!"
+ usage
+ exit 1
+ ;;
+esac
+
--
2.29.2