File indexing completed on 2024-05-12 17:15:54
0001 #!/bin/sh 0002 0003 # 0004 # SPDX-FileCopyrightText: 2014-2021 Milian Wolff <mail@milianw.de> 0005 # 0006 # SPDX-License-Identifier: LGPL-2.1-or-later 0007 # 0008 0009 usage() { 0010 echo "Usage: $0 [--debug|-d] [--use-inject] [--record-only] DEBUGGEE [ARGUMENT]..." 0011 echo "or: $0 [--debug|-d] -p PID" 0012 echo "or: $0 -a FILE" 0013 echo 0014 echo "A heap memory usage profiler. It uses LD_PRELOAD to track all" 0015 echo "calls to the core memory allocation functions and logs these" 0016 echo "occurrences. Additionally, backtraces are obtained and logged." 0017 echo "Combined this can give interesting answers to questions such as:" 0018 echo 0019 echo " * How much heap memory is my application using?" 0020 echo " * Where is heap memory being allocated, and how often?" 0021 echo " * How much space are heap individual allocations requesting?" 0022 echo 0023 echo "To evaluate the generated heaptrack data, use heaptrack_print or heaptrack_gui." 0024 echo 0025 echo "Mandatory arguments to heaptrack:" 0026 echo " DEBUGGEE The name or path to the application that should" 0027 echo " be run with heaptrack analyzation enabled." 0028 echo 0029 echo "Alternatively, to attach to a running process:" 0030 echo " -p, --pid PID The process ID of a running process into which" 0031 echo " heaptrack will be injected. This only works with" 0032 echo " applications that already link against libdl." 0033 echo " WARNING: Runtime-attaching heaptrack is UNSTABLE and can lead to CRASHES" 0034 echo " in your application, especially after you detach heaptrack again." 0035 echo " You are hereby warned, use it at your own risk!" 0036 echo 0037 echo "Optional arguments to heaptrack:" 0038 echo " -r, --raw Only record raw data, do not interpret it." 0039 echo " -d, --debug Run the debuggee in GDB and heaptrack." 0040 echo " --use-inject Use the same heaptrack_inject symbol interception mechanism instead of relying on" 0041 echo " the dynamic linker and LD_PRELOAD. This is an experimental flag for now." 0042 echo " --record-only Only record and interpret the data, do not attempt to analyze it." 0043 echo " ARGUMENT Any number of arguments that will be passed verbatim" 0044 echo " to the debuggee." 0045 echo " -h, --help Show this help message and exit." 0046 echo " -v, --version Displays version information." 0047 echo " -o, --output Specifies the data-file for the captured data." 0048 echo " %h in the file name string is replaced with the hostname of the system." 0049 echo " %p in the file name string is replaced with the pid of the application being profiled." 0050 echo " Parent directories will be created if output files are under non-existing directories." 0051 echo " e.g.," 0052 echo " ./%h/%p/outdat will be translated into ./<hostname>/<pid>/outdat." 0053 echo " The directory ./<hostname>/<pid> will be created if it doesn't exist." 0054 echo 0055 echo "Alternatively, to analyze a recorded heaptrack data file:" 0056 echo " -a, --analyze FILE Open the heaptrack data file in heaptrack_gui, if available," 0057 echo " or fallback to heaptrack_print otherwise." 0058 echo " Any options passed after --analyze will be passed along." 0059 echo 0060 exit 0 0061 } 0062 0063 debug= 0064 pid= 0065 client= 0066 use_inject_lib= 0067 write_raw_data= 0068 record_only= 0069 0070 # path to current heaptrack.sh executable 0071 SCRIPT_PATH=$(readlink -f "$0") 0072 SCRIPT_DIR=$(dirname "$SCRIPT_PATH") 0073 EXE_PATH=$(readlink -f "$SCRIPT_DIR") 0074 0075 openHeaptrackDataFiles() { 0076 if [ -x "$EXE_PATH/heaptrack_gui" ]; then 0077 "$EXE_PATH/heaptrack_gui" "$@" 0078 else 0079 "$EXE_PATH/heaptrack_print" "$@" 0080 fi 0081 } 0082 0083 ORIG_CMDLINE=$@ 0084 0085 while true; do 0086 case "$1" in 0087 "-d" | "--debug") 0088 if [ -z "$(command -v gdb 2> /dev/null)" ]; then 0089 echo "GDB is not installed, cannot debug heaptrack." 0090 exit 1 0091 fi 0092 debug=1 0093 shift 1 0094 ;; 0095 "--use-inject") 0096 use_inject_lib=1 0097 shift 1 0098 ;; 0099 "-r" | "--raw") 0100 write_raw_data=1 0101 shift 1 0102 ;; 0103 "--record-only") 0104 record_only=1 0105 shift 1 0106 ;; 0107 "-h" | "--help") 0108 usage 0109 exit 0 0110 ;; 0111 "-o" | "--output" | "--output-file") 0112 if [ -z "$2" ]; then 0113 echo "Missing output argument." 0114 exit 1 0115 fi 0116 output=$(echo $2 | sed "s/%h/$(hostname)/g" | sed "s/%p/$$/g") 0117 if [ -d "$output" ]; then 0118 echo "Please specify a file-name or a full path-name for output." 0119 exit 1 0120 fi 0121 if [ ! -d $(dirname $output) ]; then 0122 mkdir -p $(dirname $output) 0123 fi 0124 output=$(readlink -f $output) 0125 shift 2 0126 ;; 0127 "-p" | "--pid") 0128 if [ -z "$(command -v gdb 2> /dev/null)" ]; then 0129 echo "GDB is not installed, cannot attach to running process." 0130 exit 1 0131 fi 0132 if [ -f "/proc/sys/kernel/yama/ptrace_scope" ] && [ "$(cat "/proc/sys/kernel/yama/ptrace_scope")" -gt "0" ]; then 0133 echo "Cannot runtime-attach, you need to set /proc/sys/kernel/yama/ptrace_scope to 0" 0134 exit 1 0135 fi 0136 pid=$2 0137 if [ -z "$pid" ]; then 0138 echo "Missing PID argument." 0139 exit 1 0140 fi 0141 case $(uname) in 0142 Linux*) 0143 client=$(cat "/proc/$pid/comm") 0144 ;; 0145 FreeBSD*) 0146 client=$(awk '{print $1}' < "/proc/$pid/cmdline") 0147 ;; 0148 esac 0149 if [ -z "$client" ]; then 0150 echo "Cannot attach to unknown process with PID $pid." 0151 exit 1 0152 fi 0153 shift 2 0154 echo $@ 0155 if [ ! -z "$@" ]; then 0156 echo "You cannot specify a debuggee and a pid at the same time." 0157 exit 1 0158 fi 0159 break 0160 ;; 0161 "-v" | "--version") 0162 echo "heaptrack @HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@" 0163 exit 0 0164 ;; 0165 "-a" | "--analyze") 0166 shift 1 0167 openHeaptrackDataFiles "$@" 0168 exit 0169 ;; 0170 *) 0171 if [ "$1" = "--" ]; then 0172 shift 1 0173 fi 0174 if [ ! -x "$(command -v "$1" 2> /dev/null)" ]; then 0175 if [ -z "$1" ] && [ -x "$EXE_PATH/heaptrack_gui" ]; then 0176 "$EXE_PATH/heaptrack_gui" 0177 exit 0178 fi 0179 if [ -f "$1" ] && echo "$1" | grep -q "heaptrack."; then 0180 openHeaptrackDataFiles "$ORIG_CMDLINE" 0181 exit 0182 fi 0183 0184 if [ ! -e "$1" ]; then 0185 echo "Error: Debuggee \"$1\" was not found." 0186 else 0187 echo "Error: Debuggee \"$1\" is not an executable." 0188 fi 0189 0190 echo 0191 echo "Usage: $0 [--debug|-d] [--help|-h] DEBUGGEE [ARGS...]" 0192 exit 1 0193 fi 0194 client="$1" 0195 shift 1 0196 break 0197 ;; 0198 esac 0199 done 0200 0201 # put output into current pwd 0202 if [ -z "$output" ]; then 0203 output=$(pwd)/heaptrack.$(basename "$client").$$ 0204 fi 0205 0206 # find preload library and interpreter executable using relative paths 0207 LIB_REL_PATH="@LIB_REL_PATH@" 0208 LIBEXEC_REL_PATH="@LIBEXEC_REL_PATH@" 0209 0210 ENVCHECKER="$EXE_PATH/$LIBEXEC_REL_PATH/heaptrack_env" 0211 if [ ! -f "$ENVCHECKER" ]; then 0212 echo "Could not find heaptrack_env: $ENVCHECKER" 0213 exit 1 0214 fi 0215 ENVCHECKER=$(readlink -f "$ENVCHECKER") 0216 0217 INTERPRETER="$EXE_PATH/$LIBEXEC_REL_PATH/heaptrack_interpret" 0218 if [ -z "$write_raw_data" ] && [ ! -f "$INTERPRETER" ]; then 0219 echo "Could not find heaptrack interpreter executable: $INTERPRETER" 0220 exit 1 0221 fi 0222 INTERPRETER=$(readlink -f "$INTERPRETER") 0223 0224 if [ -z "$use_inject_lib" ]; then 0225 LIBHEAPTRACK_PRELOAD="$EXE_PATH/$LIB_REL_PATH/libheaptrack_preload.so" 0226 else 0227 LIBHEAPTRACK_PRELOAD="$EXE_PATH/$LIB_REL_PATH/libheaptrack_inject.so" 0228 fi 0229 if [ ! -f "$LIBHEAPTRACK_PRELOAD" ]; then 0230 echo "Could not find heaptrack preload library $LIBHEAPTRACK_PRELOAD" 0231 exit 1 0232 fi 0233 LIBHEAPTRACK_PRELOAD=$(readlink -f "$LIBHEAPTRACK_PRELOAD") 0234 0235 LIBHEAPTRACK_INJECT="$EXE_PATH/$LIB_REL_PATH/libheaptrack_inject.so" 0236 if [ ! -f "$LIBHEAPTRACK_INJECT" ]; then 0237 echo "Could not find heaptrack inject library $LIBHEAPTRACK_INJECT" 0238 exit 1 0239 fi 0240 LIBHEAPTRACK_INJECT=$(readlink -f "$LIBHEAPTRACK_INJECT") 0241 0242 # setup named pipe to read data from 0243 pipe=/tmp/heaptrack_fifo$$ 0244 mkfifo $pipe 0245 0246 # if root is profiling a process for non root 0247 # give profiled process write access to the pipe 0248 if [ ! -z "$pid" ]; then 0249 case $(uname) in 0250 Linux*) 0251 pid_user=$(stat -c %U "/proc/$pid") 0252 ;; 0253 FreeBSD*) 0254 pid_user=$(stat -f %Su "/proc/$pid") 0255 ;; 0256 esac 0257 if [ -z "$pid_user" ]; then 0258 exit 1 0259 fi 0260 chown "$pid_user" "$pipe" || exit 1 0261 fi 0262 0263 output_suffix="gz" 0264 COMPRESSOR="gzip -c" 0265 UNCOMPRESSOR="gzip -dc" 0266 0267 if [ "@ZSTD_FOUND@" = "TRUE" ] && [ ! -z "$(command -v zstd 2> /dev/null)" ]; then 0268 output_suffix="zst" 0269 COMPRESSOR="zstd -c" 0270 UNCOMPRESSOR="zstd -dc" 0271 fi 0272 0273 output_non_raw="$output.$output_suffix" 0274 0275 if [ ! -z "$write_raw_data" ]; then 0276 output_suffix="raw.$output_suffix" 0277 fi 0278 0279 # interpret the data and compress the output on the fly 0280 output="$output.$output_suffix" 0281 if [ -z "$write_raw_data" ]; then 0282 "$INTERPRETER" < $pipe | $COMPRESSOR > "$output" & 0283 else 0284 $COMPRESSOR < $pipe > "$output" & 0285 fi 0286 debuggee=$! 0287 0288 cleanup() { 0289 if [ ! -z "$pid" ] && [ -d "/proc/$pid" ]; then 0290 echo "removing heaptrack injection via GDB, this might take some time..." 0291 gdb --batch-silent -n -iex="set auto-solib-add off" \ 0292 -iex="set language c" -p $pid \ 0293 --eval-command="sharedlibrary libheaptrack_inject" \ 0294 --eval-command="call (void) heaptrack_stop()" \ 0295 --eval-command="detach" 0296 # NOTE: we do not call dlclose here, as that has the tendency to trigger 0297 # crashes in the debuggee. So instead, we keep heaptrack loaded. 0298 fi 0299 rm -f "$pipe" 0300 case $(uname) in 0301 FreeBSD*) 0302 rm -f "$pipe.lock" 0303 ;; 0304 esac 0305 kill "$debuggee" 2> /dev/null 0306 0307 echo "Heaptrack finished! Now run the following to investigate the data:" 0308 echo 0309 0310 if [ ! -z "$write_raw_data" ]; then 0311 echo " $UNCOMPRESSOR < \"$output\" | $INTERPRETER | $COMPRESSOR > \"$output_non_raw\"" 0312 else 0313 echo " heaptrack --analyze \"$output\"" 0314 fi 0315 0316 if [ -z "$record_only" ] && [ -z "$write_raw_data" ] && [ -x "$EXE_PATH/heaptrack_gui" ]; then 0317 echo "" 0318 echo "heaptrack_gui detected, automatically opening the file..." 0319 "$EXE_PATH/heaptrack_gui" "$output" 0320 fi 0321 } 0322 trap cleanup EXIT 0323 0324 echo "heaptrack output will be written to \"$output\"" 0325 0326 if [ -z "$debug" ] && [ -z "$pid" ]; then 0327 echo "starting application, this might take some time..." 0328 LD_PRELOAD="$LIBHEAPTRACK_PRELOAD${LD_PRELOAD:+:$LD_PRELOAD}" DUMP_HEAPTRACK_OUTPUT="$pipe" "$client" "$@" 0329 EXIT_CODE=$? 0330 else 0331 if [ -z "$pid" ]; then 0332 echo "starting application in GDB, this might take some time..." 0333 gdb --quiet --eval-command="set environment LD_PRELOAD=$LIBHEAPTRACK_PRELOAD" \ 0334 --eval-command="set environment DUMP_HEAPTRACK_OUTPUT=$pipe" \ 0335 --eval-command="set startup-with-shell off" \ 0336 --eval-command="run" --args "$client" "$@" 0337 EXIT_CODE=$? 0338 else 0339 echo "injecting heaptrack into application via GDB, this might take some time..." 0340 dlopen=$($ENVCHECKER dlopen "$LIBHEAPTRACK_INJECT") 0341 if [ -z "$debug" ]; then 0342 unset DEBUGINFOD_URLS 0343 gdb --batch-silent -n -iex="set auto-solib-add off" \ 0344 -iex="set language c" -p $pid \ 0345 --eval-command="sharedlibrary libc.so" \ 0346 --eval-command="call (void) $dlopen" \ 0347 --eval-command="sharedlibrary libheaptrack_inject" \ 0348 --eval-command="call (void) heaptrack_inject(\"$pipe\")" \ 0349 --eval-command="detach" 0350 else 0351 echo $dlopen 0352 gdb --quiet -iex="set language c" -p $pid \ 0353 --eval-command="sharedlibrary libc.so" \ 0354 --eval-command="print (void*) $dlopen" \ 0355 --eval-command="sharedlibrary libheaptrack_inject" \ 0356 --eval-command="call (void) heaptrack_inject(\"$pipe\")" 0357 fi 0358 EXIT_CODE=$? 0359 echo "injection finished" 0360 fi 0361 fi 0362 0363 wait $debuggee 0364 exit $EXIT_CODE 0365 0366 # kate: hl Bash