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