Repository: kaiwan/procmap Branch: master Commit: fe662ad7d6da Files: 18 Total size: 152.6 KB Directory structure: gitextract_2as3zfr2/ ├── .gitignore ├── LICENSE ├── README.md ├── color.sh ├── common.sh ├── config ├── do_kernelseg.sh ├── do_vgraph.sh ├── err_common.sh ├── install_procmap ├── lib_procmap.sh ├── mapsfile_prep.sh ├── procmap ├── procmap_kernel/ │ ├── Makefile │ ├── convenient.h │ └── procmap.c └── test/ ├── runtests.sh └── shellcheck_run ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # .gitignore # Ignore these files/dirs # ref: https://www.atlassian.com/git/tutorials/saving-changes/gitignore hello* log* colours_256_test.png # **/logs => a dir named 'logs' **/bkp* **/.tmp* ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Kaiwan N Billimoria Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # procmap ***procmap* is designed to be a console/CLI utility to visualize the complete memory map of a Linux process, in effect, to visualize the memory mappings of both the kernel and usermode Virtual Address Space (VAS).** It outputs a simple visualization of the complete memory map of a given process in a vertically-tiled format **ordered by descending virtual address** (see **screenshots** below). The script has the intelligence to show kernel and userspace mappings as well as calculate and show the sparse memory regions that will be present. Also, each segment or mapping is (very approximately) scaled by relative size and color-coded for readability. On 64-bit systems, it also shows the so-called non-canonical sparse region or 'hole' (typically close to a whopping 16,384 PB on the x86_64). ***Hey, be sure to see the [Examples](https://github.com/kaiwan/procmap#examples) section !*** ***Usage:*** $ ./procmap Usage: procmap [options] --pid=PID-of-process-to-show-memory-map-of Options: --only-user : show ONLY the usermode mappings or segments (not kernel VAS) --only-kernel : show ONLY the kernel-space mappings or segments (not user VAS) [default is to show BOTH] --locate=, : locate a given region within the process VAS start-vaddr : a virtual address in hexadecimal length : length of the region to locate in KB --export-maps=filename write all map information gleaned to the file you specify in CSV (note that it overwrites the file) --export-kernel=filename write kernel information gleaned to the file you specify in CSV (note that it overwrites the file) --verbose : verbose mode (try it! see below for details) --debug : run in debug mode --version|--ver : display version info. ... $ ## Platforms that procmap has been tested upon: - x86_64 (Ubuntu, Fedora distros) - AArch32 - Raspberry Pi Zero W - TI BBB (BeagleBone Black) [some work's pending here though] - AArch64 - Raspberry Pi 3B, 4 Model B - TI BGP (BeaglePlay) ## IMPORTANT: Running procmap on systems other than x86_64 On systems other than x86_64 (like AArch32/AArch64/etc), we don't know for sure if the *kernel module component* can be compiled and built while executing on the target system; it may be possible to, it may not. Technically, to build a kernel module on the target system, you will require it to have a kernel development environment setup; this boils down to having the compiler, make and - key here - the 'kernel headers' package installed *for the kernel version it's currently running upon*. This can be a big ask... f.e., am running a *custom* 5.4 kernel on my Raspberry Pi; everything works fine, but as the kernel source tree for 5.4 isn't present (nor is there any kernel headers package), building kernel modules on it fails (while it works with the stock Raspbian kernel). So: **you will have to cross-compile the kernel module**; to do so: 1. On your x86_64 *host* system: 2. Ensure you have an appropriate x86_64-to-ARM (or whatever) cross compiler installed. Then: 3. `git clone` the procmap project 4. `cd procmap/procmap_kernel` 5. `make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-` (assuming the cross-compiler prefix is `arm-linux-gnueabihf-` (as is the case for the Raspberry Pi 32-bit) 6. Now verify that the procmap.ko kernel module is successfully built 7. If all okay, transfer it (scp or otherwise) to your target; place it (within the procmap source tree on the target device) in the *procmap/procmap_kernel* directory 8. run *procmap* - it should now work. ## How does procmap work? ### In a nutshell, in kernel-space: The kernel memory map is garnered via the kernel component of this project - a *Loadable Kernel Module*. It collates all required information and makes that info available to userspace via a common interfacing technique - a debugfs (pseudo) file. Particulars: Assuming the debugfs filesystem is mounted at /sys/kernel/debug, the kernel module sets up a debugfs file here: ` /sys/kernel/debug/procmap/disp_kernelseg_details` Reading this file generates the required kernel information, which the scripts interpret and display. ### In a nutshell, in userspace: The userspace memory map is collated and displayed by iterating over the `/proc/PID/maps` pseudo-file of the given process. For both kernel and userspace, the procmap script color-codes and shows the following details (comma separated) for each segment (or mapping): * the start user virtual address (uva) to the right extreme of the line seperator - the segment name - it's size (appropriately, in KB/MB/GB/TB) - it's mode (permissions; highlights if null or .WX for security) - the type of mapping (p=private, s=shared) (only userspace) - if a file mapping, the offset from the beginning of the file (0x0 for anonymous or starts at BOF) (only userspace) To aid with visualization of the process VAS, we show the relative vertical "length" of a segment or mapping via it's height (of course, it's a highly approximate measure). The script works on both 32 and 64-bit Linux OS (lightly tested, I request more testing, on more platforms, and bug/issue reports please!). ## Requirements: Kernel: - Linux kernel 3.0 or later (technically, >= 2.6.12 should work) - debugfs must be supported and mounted - proc fs must be supported and mounted - You should have the rights to build and insmod(8) a third-party (us!) kernel module on your box; in effect, you must have root (sudo) User utils: - bash(1) - bc(1) - smem(8) - build system (make, gcc, binutils, etc) - common utils typically always installed on a Linux system (grep, ps, cut, cat, getopts, etc) - dtc (device tree compiler) on ARM-based platforms Also of course, you require *root* access (to install the kernel module (or the CAP_SYS_MODULE capability), and get the details of any process from /proc/PID/<...>). ## Examples ### 1. Running procmap on an x86_64 Ubuntu 24.04 LTS box (with PID set to 1) [![asciicast](https://asciinema.org/a/700116.svg)](https://asciinema.org/a/700116) In case you can't see or play the above (quite fantastic!) '*asciinema*' widget, pl visit this link to see the ASCII screencast: https://asciinema.org/a/700116 ### 2. Running procmap on the TI BeagleBone Black (BBB) ARM-32 board (with PID set to a 'bash' process) [![asciicast](https://asciinema.org/a/700143.svg)](https://asciinema.org/a/700143) ## A Note on the (optional) statistics display We optionally show some **statistics** when done. The stats display is set to Off by default; to turn them On, set the `config:SHOW_STATS` variable to `1`. Once on, these stats show up: - Total sizes of kernel and user VAS's (bytes to TB range) - Total RAM reported by the system - If the process user virtual address space (VAS) memory is displayed, the stats also show, for that process: - The total number of VMA (Virtual Memory Area) objects the kernel currently maintains for it, and how many are 'sparse' regions - The amount and percentage of memory in it's userspace VAS that is just 'sparse' (empty; on 64-bit systems it can be very high!) vs the actually used memory amount and percentage - Memory usage statistics for this process via: - ps(1) - smem(8) As a bonus, the output is logged - appended - to the file `log_procmap.txt`. Look it up when done. ### Exporting the output ### - Use the --export-maps=filename option to write all map information gleaned to the file filename (writes in CSV format). - If you just want the output (with color info), simply use output redirection: `procmap -p 12345 > procmap_saved.txt` It contains the ANSI color sequence codes within it; this is good, as to see it with color, simply 'cat' the file! - If you want to strip out the ANSI color sequence, do this: `sed -r 's/\x1B\[[0-9;]*[a-zA-Z]//g' procmap_saved.txt` [End doc] ================================================ FILE: color.sh ================================================ #!/bin/bash #------------------------------------------------------------------ # color.sh # # Common convenience routines for color support in bash. # # (c) Kaiwan N Billimoria # kaiwan -at- kaiwantech -dot- com # MIT / GPL v2 #------------------------------------------------------------------ # The SEALS Opensource Project # SEALS : Simple Embedded Arm Linux System # Maintainer : Kaiwan N Billimoria # kaiwan -at- kaiwantech -dot- com # Project URL: # https://github.com/kaiwan/seals [ -z "${PDIR}" ] && { PDIR="$(which $0)" PDIR="$(dirname $0)" # true if procmap isn't in PATH PFX="$(dirname ${PDIR})" # dir in which this script and tools reside source ${PFX}/../err_common.sh || { echo "${name}: fatal: could not source file '${PFX}/../err_common.sh', aborting..." exit 1 } } #------------------- Colors!! Yay :-) ----------------------------------------- # Ref: https://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux # [Ans by Drew Noakes] # Useful Ref! https://i.stack.imgur.com/a2S4s.png # Always fail gracefully (via the tput <...> || true ) # This is required on consoles that don't support colour! #--- Foreground Colors fg_black() { tput setaf 0 || true } fg_darkgrey() { tput setaf 232 || true } fg_red() { tput setaf 1 || true } fg_purple() { tput setaf 125 || true } fg_orange() { tput setaf 166 || true } fg_green() { tput setaf 2 || true } fg_darkgreen() { tput setaf 22 || true } fg_yellow() { tput setaf 3 || true } fg_blue() { tput setaf 4 || true } fg_navyblue() { tput setaf 17 || true } fg_magenta() { tput setaf 5 || true } fg_cyan() { tput setaf 6 || true } fg_white() { tput setaf 7 || true } fg_grey() { tput setaf 8 || true } #--- Background Colors bg_white() { tput setab 7 || true } bg_gray() { tput setab 250 || true } bg_red() { tput setab 1 || true } bg_green() { tput setab 2 || true } bg_yellow() { tput setab 3 || true } bg_blue() { tput setab 4 || true } bg_cyan() { tput setab 6 || true } #--- Text Attributes <-- NOK! #tb=$(tput bold) # bold #tsb=$(tput smso) # enter standout bold mode #trb=$(tput rmso) # exit standout bold mode #trev=$(tput rev) # reverse video #tdim=$(tput dim) # half-brightness #tBell=$(tput bel) # sound bell! #--- Composite text attribs [ta] <-- NOK! #taErr="${tb}${fg_red}${bg_white}${tBell}" #taTitle="${tb}${fg_black}${bg_yellow}" #taReg="" # 'regular' msgs #taBold="$(tput bold)" #taBold="${tb}" #taAbnormal="${fg_white}${bg_blue}" # 'Abnormal' msgs - error msgs,... #taDebug="${tdim}" # Reset text attributes to normal without clearing screen. color_reset() { tput sgr0 || true } #--------------------- E c h o ---------------------------------------- # The _base_ echo/logging function. # Parameters: # $1 : a tag that speicifies the logging level # $2 ... $n : message to echo (to stdout and logfile) # # Logging Levels (from low->high 'criticality') are: # -------- -------- # LogLevel Function # -------- -------- # DDEBUG decho # INFO iecho # ALERT aecho [bold] # WARN wecho # CRIT cecho # TITL techo <-- exception: this is NOT really a loglevel, # it's a special display attribute # !WARNING! # Ensure you don't call any of the x[Ee]cho functions from here, as they # call this func and it becomes infinitely recursive. Echo() { local SEP=" " # echo "# = $# : params: $@" [ $# -eq 0 ] && return 1 local numparams=$# local tag="${1}" [ ${numparams} -gt 1 ] && shift # get rid of the tag, so that we can access the txt msg # Prefix the logging level : debug/info/warn/critical local loglevel # maintaining 4-char strings for 'loglevel' alleviates the need for more # code with printf etc case "${tag}" in DDEBUG) loglevel="dbug" ;; INFO) loglevel="info" ;; ALERT) loglevel="alrt" ;; WARN) loglevel="warn" ;; CRIT) loglevel="crit" ;; TITL) loglevel="titl" ;; *) loglevel=" " ;; esac local dt="[$(date +%a_%d%b%Y_%T.%N)]" local dt_log="[$(date +%a_%d%b%Y_%T.%N)]" local dt_disp [ ${VERBOSE_MSG} -eq 1 ] && dt_disp=${dt} local msgpfx1_log="[${loglevel}]${SEP}${dt_log}" local msgpfx1_disp="${dt}" [ ${VERBOSE_MSG} -eq 1 ] && msgpfx1_disp="${msgpfx1_log}" local msgpfx2_log="${SEP}${name}:${FUNCNAME[ 1 ]}()${SEP}" local msgpfx2_disp="" [ ${VERBOSE_MSG} -eq 1 ] && msgpfx2_disp="${msgpfx2_log}" local msgtxt="$*" local msgfull_log="${msgpfx1_log}${msgpfx2_log}${msgtxt}" local msg_disp="${msgpfx1_disp}${SEP}${msgtxt}" [ ${VERBOSE_MSG} -eq 1 ] && msg_disp="${msgfull_log}" # lets log it first anyhow [ -f ${LOGFILE_COMMON} ] && echo "${msgfull_log}" >> ${LOGFILE_COMMON} if [ ${numparams} -eq 1 -o ${COLOR} -eq 0 ]; then # no color/text attribute [ ${DEBUG} -eq 1 ] && echo "${msgfull_log}" || echo "${msg_disp}" return 0 fi #--- 'color' or text attrib present! fg_green echo -n "${msgpfx1_disp}${SEP}" [ ${DEBUG} -eq 1 -o ${VERBOSE_MSG} -eq 1 ] && { fg_blue echo -n "${msgpfx2_disp}" } color_reset # Reset to normal. case "${tag}" in DDEBUG) tput dim || true ; fg_cyan #fg_magenta ;; INFO) #tput # Deliberate: no special attribs for 'info' ;; ALERT) tput bold ;; WARN) fg_red ; bg_yellow ; tput bold ;; CRIT) fg_white ; bg_red ; tput bold ;; TITL) fg_black ; bg_yellow ; tput bold ;; esac echo "${msgtxt}" color_reset # Reset to normal. return 0 } # end Echo() #--- Wrappers over Echo follow --- # Parameters: # $1 : message to echo (to stdout and logfile) #--------------------- d e c h o -------------------------------------- # DEBUG-level echo :-) decho() { [ ${DEBUG} -eq 1 ] && Echo DDEBUG "$1" true } #--------------------- i e c h o --------------------------------------- # INFO-level / regular Color-echo. iecho () { Echo INFO "$1" } #--------------------- a e c h o --------------------------------------- # ALERT-level Color-echo. aecho () { Echo ALERT "$1" } #--------------------- w e c h o --------------------------------------- # WARN-level Color-echo. wecho () { Echo WARN "$1" } #--------------------- c e c h o --------------------------------------- # CRITical-level Color-echo. cecho () { Echo CRIT "$1" } #--------------------- t e c h o --------------------------------------- # Title Color-echo. techo () { Echo TITL "$1" } #--- # ShowTitle # Display a string in "title" form # Parameter(s): # $1 : String to display [required] ShowTitle() { techo "$1" } test_256() { for i in $(seq 0 255) do tput setab $i printf '%03d ' $i done color_reset } ================================================ FILE: common.sh ================================================ #!/bin/bash #------------------------------------------------------------------ # common.sh # # Common convenience routines # # (c) Kaiwan N Billimoria # kaiwan -at- kaiwantech -dot- com # License: MIT #------------------------------------------------------------------ export TOPDIR="$(pwd)" #ON=1 #OFF=0 name=$(basename $0) PFX=$(dirname "$(which $0 2>/dev/null)") # dir in which 'procmap' and tools reside source ${PFX}/err_common.sh || { echo "$name: could not source ${PFX}/err_common.sh, aborting..." exit 1 } source ${PFX}/color.sh || { echo "$name: could not source ${PFX}/color.sh, aborting..." exit 1 } prompt() { [ $# -gt 0 ] && echo "$@" || printf "[Enter] to continue... " read } # runcmd # Parameters # $1 ... : params are the command to run runcmd() { [ $# -eq 0 ] && return echo "$*" eval "$*" } # ref: https://stackoverflow.com/questions/369758/how-to-trim-whitespace-from-a-bash-variable trim() { local var="$*" # remove leading whitespace characters var="${var#"${var%%[![:space:]]*}"}" # remove trailing whitespace characters var="${var%"${var##*[![:space:]]}"}" printf '%s' "$var" } # If we're not in a GUI (X Windows) display, abort (reqd for yad) check_gui() { which xdpyinfo > /dev/null 2>&1 || { FatalError "xdpyinfo (package x11-utils) does not seem to be installed. Aborting..." } xdpyinfo >/dev/null 2>&1 || { FatalError "Sorry, we're not running in a GUI display environment. Aborting..." } which xrandr > /dev/null 2>&1 || { FatalError "xrandr (package x11-server-utils) does not seem to be installed. Aborting..." } #--- Screen Resolution stuff res_w=$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f1) res_h=$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2) centre_x=$((($res_w/3)+0)) centre_y=$((($res_h/3)-100)) CAL_WIDTH=$((($res_w/3)+200)) CAL_HT=$(($res_h/3)) } # genLogFilename # Generates a logfile name that includes the date/timestamp # Format: # ddMmmYYYY[_HHMMSS] # Parameter(s) # #$1 : String to prefix to log filename, null okay as well [required] # $1 : Include time component or not [required] # $1 = 0 : Don't include the time component (only date) in the log filename # $1 = 1 : include the time component in the log filename genLogFilename() { [ $1 -eq 0 ] && log_filename=$(date +%d%b%Y) [ $1 -eq 1 ] && log_filename=$(date +%d%b%Y_%H%M%S) echo ${log_filename} } vecho() { [ ${VERBOSE} -eq 0 ] && return echo "[v] $*" } #---------- c h e c k _ d e p s --------------------------------------- # Checks passed packages - are they installed? (just using 'which'; # using the pkg management utils (apt/dnf/etc) would be too time consuming) # Parameters: # $1 : 1 => fatal error, exit # 0 => warn only # [.. $@ ..] : space-sep string of all packages to check # Eg. check_deps "make perf spatch xterm" check_deps() { local util needinstall=0 #report_progress local severity=$1 shift for util in $@ do #echo "util = $util" which ${util} > /dev/null 2>&1 || { [ ${needinstall} -eq 0 ] && wecho "The following utilit[y|ies] or package(s) do NOT seem to be installed:" iecho "[!] ${util}" needinstall=1 continue } done if [ ${needinstall} -eq 1 ] ; then [ ${severity} -eq 1 ] && { FatalError "You must first install the required package(s) or utilities shown above \ (check console and log output too) and then retry, thanks. Aborting..." } || { wecho "WARNING! The package(s) shown above are not present" } fi } # end check_deps() # Simple wrappers over check_deps(); # Recall, the fundamental theorem of software engineering FTSE: # "We can solve any problem by introducing an extra level of indirection." # -D Wheeler # ;-) check_deps_fatal() { check_deps 1 "$@" } check_deps_warn() { check_deps 0 "$@" } verify_utils_present() { [ ! -d /proc ] && FatalError "proc fs not available or not mounted? Aborting..." || true check_deps_fatal "getconf bc make gcc kmod grep awk sed kill readlink head tail \ cut cat tac sort wc ldd file" check_deps_warn "sudo tput ps smem" # need yad? GUI env? which xdpyinfo > /dev/null 2>&1 && check_deps_warn "yad" || true # need dtc? -only on systems that use the DT [[ -d /proc/device-tree ]] && check_deps_fatal "dtc" || true } ## isathread # Param: PID # Returns: # 1 if $1 is a (worker/child) thread of some process, 0 if it's a process by itself, 127 on failure. isathread() { [ $# -ne 1 ] && { aecho "isathread: parameter missing!" 1>&2 return 127 } export PARENT_PROCESS=${PID} local t1=$(ps -LA|grep -w "${PID}" |head -n1) local pid=$(echo ${t1} |cut -d' ' -f1) local lwp=$(echo ${t1} |cut -d' ' -f2) [[ ${pid} -eq ${lwp} ]] && return 0 || { PARENT_PROCESS=${pid} return 1 } } ================================================ FILE: config ================================================ #!/bin/bash # config # https://github.com/kaiwan/procmap # # Configuration file for the procmap project. set -a # export all # Still, shellcheck likes them all explicitly 'export'ed export LC_ALL=C # locale #------------- One-time install of kernel-detail file, vars export KMOD=procmap export DBGFS_LOC=$(mount |grep debugfs |awk '{print $3}') export DBGFS_FILENAME=disp_kernelseg_details export REPORT_DIR=/etc/procmap export KSEGFILE=${REPORT_DIR}/kseg_dtl #------------ export name=procmap export gDELIM="," export VERBOSE=0 export DEBUG=0 export WRITELOG=0 export LOCATE_SPEC="" export LIMIT_SCALE_SZ=20 export LARGE_SPACE=12 # userspace VAS display configs export SHOW_USERSPACE=1 export EMB=0 # set to 1 for 'embedded' systems; simpler [no float point, etc] export SPARSE_SHOW=1 export SHOW_VSYSCALL_PAGE=0 export SHOW_STATS=1 # kernel seg display configs export SHOW_KERNELSEG=1 #export KSEGFILE=/tmp/${name}/kseg_dtl #NAME=procmap_kernel_setup.sh #export KSEGFILE=/tmp/${NAME}/kseg_dtl export ARCHFILE=/tmp/${name}/arch_dtl export KERNELDIR=${PFX}/procmap_kernel export KMOD=procmap #export DBGFS_LOC=$(mount |grep debugfs |awk '{print $3}') #export DBGFS_FILENAME=disp_kernelseg_details export KSPARSE_SHOW=1 export SHOW_KSTATS=1 # Common sizes export GB_1=$(bc <<< "scale=6; 1.0*1024.0*1024.0*1024.0") export GB_2=$(bc <<< "scale=6; 2.0*1024.0*1024.0*1024.0") export GB_3=$(bc <<< "scale=6; 3.0*1024.0*1024.0*1024.0") export GB_4=$(bc <<< "scale=6; 4.0*1024.0*1024.0*1024.0") export TB_1=$(bc <<< "scale=0; 1*1024*1024*1024*1024") export TB_128=$(bc <<< "scale=6; 128.0*1024.0*1024.0*1024.0*1024.0") export TB_256=$(bc <<< "scale=6; 256.0*1024.0*1024.0*1024.0*1024.0") # Arch-specific config setup is in the 'lib_procmap.sh' file export IS_X86_64=0 export IS_Aarch32=0 export IS_Aarch64=0 export IS_X86_32=0 # Colors # Find the curr encoded color functions (fg_xxx, bg_yyy) in the color.sh script # FG = foreground color export FG_MAPNAME=fg_navyblue export FG_KVAR=fg_darkgreen export FG_LOC=fg_red ================================================ FILE: do_kernelseg.sh ================================================ #!/bin/bash # do_kernelseg.sh # Part of the procmap project: # https://github.com/kaiwan/procmap # # (c) Kaiwan NB # All arch-specific vars are here! source ${ARCHFILE} || { echo "${name}: fatal: could not source ${ARCHFILE} , aborting..." exit 1 } KSPARSE_ENTRY="<... K sparse region ...>" VAS_NONCANONICAL_HOLE="<... 64-bit: non-canonical hole ...>" export MAPFLAG_WITHIN_REGION=10 export RAM_START_PHYADDR=0x0 #-----------------------s h o w A r r a y ----------------------------- # Parameters: # $1 : debug print; if 1, it's just a debug print, if 0 we're writing it's # data to a file in order to process further (sort/etc) show_gkArray() { local i k DIM=6 [ $1 -eq 1 ] && { echo decho "gkRow = ${gkRow}" echo "show_gkArray(): [segname,size,start_kva,end_kva,mode,flags]" } for ((i=0; i<${gkRow}; i+=${DIM})) do printf "%s," "${gkArray[${i}]}" # segname let k=i+1 printf "%llu," "${gkArray[${k}]}" # seg size let k=i+2 printf "%llx," "${gkArray[${k}]}" # start kva let k=i+3 printf "%llx," "${gkArray[${k}]}" # end kva let k=i+4 printf "%s," "${gkArray[${k}]}" # mode let k=i+5 printf "%s" "${gkArray[${k}]}" # flags printf "\n" done } # end show_gkArray() # Setup the kernel Sparse region at the very top (high) end of the VAS # in the gkArray[] # ARCH SPECIFIC ! See the arch-specific config setup func in lib_procmap.sh # to see the actual values specified; it's sourced here via the # 'source ${ARCHFILE}' done at the beginning! setup_ksparse_top() { #set -x gkRow=0 # Require the topmost valid kernel va, query it from the o/p of our # kernel component, the procmap LKM local top_kva=0x$(head -n1 ${KSEGFILE} |awk -F"${gDELIM}" '{print $2}') #locate_region ${top_kva} ${HIGHEST_KVA} local gap_dec=$((HIGHEST_KVA-top_kva)) if [ ${gap_dec} -gt ${PAGE_SIZE} ]; then append_kernel_mapping "${KSPARSE_ENTRY}" "${gap_dec}" ${top_kva} \ "${HIGHEST_KVA}" "---" 0 fi } # end setup_ksparse_top() # pa2va # Convert the given phy addr (pa), to a kernel va (kva) # CAREFUL! # We do so by exploiting the fact that the kernel direct-maps all platform # RAM into the kernel segment starting at PAGE_OFFSET (i.e. into the lowmem # region). So, # kva = pa + PAGE_OFFSET # HOWEVER, this ONLY holds true for direct-mapped kernel RAM not for ANYTHING # else! # We EXPECT to ONLY be passed a physical addr that maps to the kernel direct- # mapped addresses - lowmem addr. # # NOTE NOTE NOTE # Wrt the kernel code/data/bss, realized that converting the physical addr # found in /proc/iomem to a KVA is NOT a simple matter of adding the PA to # the PAGE_OFFSET value. It's platform-specific! F.e. on the TI BBB AArch32 (AM35xx # SoC), the Device Tree shows that RAM's mapped at 0x8000 0000 physical addr. We # have to take this into a/c else we'll calculate the KVA wrongly... So # kva = (pa - RAM_START_PHYADDR) + PAGE_OFFSET # If we're not on a DT-based platform (like x86), then RAM_START_PHYADDR is 0... # # Parameters: # $1 : phy addr (pa) pa2va() { # TIP : for bash arithmetic w/ large #s, first calculate in *decimal* base using # bc(1), then convert it to hex as required (via printf) local pgoff_dec=$(printf "%llu" 0x${PAGE_OFFSET} 2>/dev/null) local pa_dec=$(printf "%llu" 0x${1}) local RAM_START_PHYADDR_DEC=$(printf "%llu" ${RAM_START_PHYADDR}) local kva=$(bc <<< "(${pa_dec}-${RAM_START_PHYADDR_DEC})+${pgoff_dec}") printf "${FMTSPC_VA}" ${kva} } # end pa2va get_ram_phyaddr_from_dt() { # ... Eg. DT snippet for RAM bank (for the TI BBB) ... # memory@80000000 { # device_type = "memory"; # reg = < 0x80000000 0x20000000 >; # ^^^^^^^^^^ len # $1 $2 $3 _$4_ $5 # this is the start phy addr of RAM ! # }; local dt_ram=0 [[ ! -d /proc/device-tree ]] && return # no DT, np # Get it from the token after the @ symbol; easier... dt_ram=$(dtc -I fs /proc/device-tree/ 2>/dev/null |grep -A3 "memory@"|grep "reg *= *<") # fetch only the first hex number (following the '<') RAM_START_PHYADDR=$(echo $dt_ram |awk -F"<" '{print $2}' |awk '{print $1}') decho "RAM_START_PHYADDR = ${RAM_START_PHYADDR}" } # setup_kernelimg_mappings # Setup mappings for the kernel image itself; this usually consists of (could # be fewer entries on some arch's): # sudo grep -w "Kernel" /proc/iomem # 297c00000-2988031d0 : Kernel code # 2988031d1-29926c5bf : Kernel data # 2994ea000-29978ffff : Kernel bss # BUT, Carefully NOTE - the above are PHYSICAL ADDR, not kva's; # So, we'll have to convert them to kva's -SEE NOTE BELOW! - and then insert # them (in order by descending kva) into our gkArray[] data structure. setup_kernelimg_mappings() { local TMPF=/tmp/${name}/kimgpa local start_pa end_pa mapname local start_kva end_kva ###--- This appears to be the ONE place where we still require sudo ! ---### sudo grep -w "Kernel" /proc/iomem > ${TMPF} #--- loop over the kernel image recs IFS=$'\n' local i=1 local REC local prev_startkva=0 #${TB_256} #0 ###--- NOTE NOTE NOTE # Wrt the kernel code/data/bss, realized that converting the physical addr # found in /proc/iomem to a KVA is NOT a simple matter of adding the PA to # the PAGE_OFFSET value. It's platform-specifc! F.e. on the TI BBB AArch32 (AM35xx # SoC), the Device Tree shows that RAM's mapped at 0x8000 0000 physical addr. We # have to take this into a/c else we'll calculate the KVA wrongly... # BBB DTS: # ... # memory@80000000 { # device_type = "memory"; # reg = < 0x80000000 0x20000000 >; # ^^^^^^^^^^ len # this is the start phy addr of RAM ! # }; # ... ###--- get_ram_phyaddr_from_dt for REC in $(cat ${TMPF}) do #decho "REC: $REC" start_pa=$(echo "${REC}" |cut -d"-" -f1) start_pa=$(trim ${start_pa}) end_pa=$(echo "${REC}" |cut -d"-" -f2 |cut -d":" -f1) end_pa=$(trim ${end_pa}) mapname=$(echo "${REC}" |cut -d":" -f2) mapname=$(trim ${mapname}) # Convert pa to kva start_kva=$(pa2va ${start_pa}) #echo "start_kva = ${start_kva}" end_kva=$(pa2va ${end_pa}) # Write to 'kernel seg' file # ksegfile record fmt: # start-kva,end-kva,perms,name local ekva_dec=$(printf "%llu" 0x${end_kva}) local skva_dec=$(printf "%llu" 0x${start_kva}) local gap=$(bc <<< "(${ekva_dec}-${skva_dec})") #decho "k img: ${mapname},${gap},${start_kva},${end_kva}" append_kernel_mapping "${mapname}" ${gap} 0x${start_kva} \ 0x${end_kva} "..." ${MAPFLAG_WITHIN_REGION} # sparse region? gap=$(bc <<< "(${prev_startkva}-${ekva_dec})") #gap=$(bc <<< "(${ekva_dec}-${prev_startkva})") # ? #decho "prev_startkva = ${prev_startkva} ; ekva_dec = ${ekva_dec}" decho "gap = ${gap}" # RELOOK ! # the redirection to null dev gets rid of this: # "./do_kernelseg.sh: line 153: [: -18446635713769246384: integer expression expected" if [ ${gap} -gt ${PAGE_SIZE} 2>/dev/null ] ; then local start_kva_sparse=$(printf "0x%llx" ${skva_dec}) local prev_startkva_hex=$(printf "0x%llx" ${prev_startkva}) append_kernel_mapping "${KSPARSE_ENTRY}" "${gap}" ${start_kva_sparse} \ ${prev_startkva_hex} "---" 0 fi let i=i+1 prev_startkva=${skva_dec} done 1>&2 #---------- # Sort by descending kva! [ ${DEBUG} -eq 0 ] && rm -f ${TMPF} || true } # end setup_kernelimg_mappings #----------- i n t e r p r e t _ k e r n e l _ r e c ------------------- # Interpret record (a CSV 'line' passed as $1) and populate the gkArray[] # n-dim array. # Format: # start_kva,end_kva,mode,name_of_region # ; kva = kernel virtual address # eg. $1 = # 0xffff9be100000000,0xffff9be542800000,rwx,lowmem region # # Parameters: # $1 : the above CSV format string of 4 fields {start_kva,end_kva,mode,region-name} # $2 : loop index (starts @ 1) # Populate the global 'n-dim' (n=5) array gkArr. interpret_kernel_rec() { local numcol=$(echo "$1" | tr ',' ' ' | wc -w) #echo "p1 = $1; nc=$numcol" [[ ${numcol} -lt 4 ]] && return # req 5 fields in the record (4 as segname could be 1 word) local gap=0 # size (in bytes, decimal) of the kernel region, # i.e., end_kva - start_kva local start_kva=0x$(echo "${1}" |cut -d"${gDELIM}" -f1) local end_kva=0x$(echo "${1}" |cut -d"${gDELIM}" -f2) #echo "numcol = $numcol; skva=${start_kva} ekva=${end_kva}" # Skip comment lines echo "${start_kva}" | grep -q "^#" && return local mode=$(echo "${1}" |cut -d"${gDELIM}" -f3) local name=$(echo "${1}" |cut -d"${gDELIM}" -f4) [ -z "${name}" ] && segment=" [-unnamed-] " # Convert hex to dec local start_dec=$(printf "%llu" ${start_kva}) local end_dec=$(printf "%llu" ${end_kva}) local seg_sz=$(printf "%llu" $((end_dec-start_dec))) # in bytes # The global 5d-array's format is: # col0 col1 col2 col3 col4 # row'n' [regname],[size],[start_kva],[end_kva],[mode] # TODO # vsyscall: manually place detail into gkArray[] #------------ Sparse Detection if [ ${KSPARSE_SHOW} -eq 1 ]; then DetectedSparse=0 decho "$2: seg=${name} prevseg_name=${prevseg_name} , gkRow=${gkRow} " # Detect sparse region, and if present, insert into the gArr[]. # Sparse region detected by condition: # gap = prev_seg_start - this-segment-end > 1 page if [ $2 -eq 1 ] ; then # ignore the first kernel region decho "k sparse check: skipping first kernel region" else local end_hex=$(printf "0x%llx" ${end_dec}) prevseg_start_kva_hex=$(printf "0x%llx" ${prevseg_start_kva}) decho "@@ gap = prevseg_start_kva_hex: ${prevseg_start_kva_hex} - end_hex: ${end_hex}" gap=$(bc <<< "(${prevseg_start_kva}-${end_dec})") #local gap_hex=$(printf "0x%llx" ${gap}) decho "gap = ${gap}" [ ${gap} -gt ${PAGE_SIZE} ] && DetectedSparse=1 fi if [ ${DetectedSparse} -eq 1 ]; then local start_kva_dec=$(bc <<< "(${prevseg_start_kva}-${gap})") local start_kva_sparse=$(printf "0x%llx" ${start_kva_dec}) append_kernel_mapping "${KSPARSE_ENTRY}" "${gap}" ${start_kva_sparse} \ ${prevseg_start_kva_hex} "---" 0 # TODO : count k sparse and u sparse regions seperately! # Stats #[ ${SHOW_KSTATS} -eq 1 ] && { # let gNumSparse=gNumSparse+1 # let gTotalSparseSize=gTotalSparseSize+gap #} fi prevseg_start_kva=${start_dec} fi #-------------- #--- Populate the global array append_kernel_mapping "${name}" ${seg_sz} ${start_kva} ${end_kva} ${mode} 0 [ ${SHOW_KSTATS} -eq 1 ] && { let gTotalSegSize=${gTotalSegSize}+${seg_sz} } prevseg_name=${name} decho "prevseg_name = ${prevseg_name} " } # end interpret_kernel_rec() # Insert k sparse region from last (lowest) valid k mapping (often, lowmem) # to first valid kernel va # Parameters # $1 : prev segment/mapping start va (in hex) setup_ksparse_lowest() { # The highest uva: # On 32-bit = it can be the modules region on Aarch32 # On 64-bit = it varies with the arch # x86_64: Ref: /Documentation/x86/x86_64/mm.rst # Start addr | Offset | End addr | Size | VM area description # 0000000000000000 | 0 | 00007fffffffffff | 128 TB | user-space virtual memory, different per mm # # NOTE- this info is encoded into the arch-specific config setup code in # lib_procmap.sh, pl refer to it. # The way to perform arithmetic in bash on large #s is to use bc(1); # AND to to decimal arithmetic and then convert to hex if required! # calculation: $1 - START_KVA # ;the START_KVA value is in the ARCHFILE local kva_dec=$(printf "%llu" ${1}) #local START_KVA_DEC=$(printf "%llu" ${START_KVA}) local gap_dec=$(bc <<< "(${kva_dec}-${START_KVA_DEC})") #decho "p1 = $1 , START_KVA = ${START_KVA} ; gap_dec=${gap_dec}" if [ ${gap_dec} -gt ${PAGE_SIZE} ]; then append_kernel_mapping "${KSPARSE_ENTRY}" "${gap_dec}" 0x${START_KVA} \ ${1} "---" 0 fi } # end setup_ksparse_lowest() setup_noncanonical_sparse_region() { # this is ARCH SPECIFIC and ONLY for 64-bit # the noncanonical 'hole' spans from 'start kva' down to 'end uva' if [ ${IS_64_BIT} -eq 1 ]; then append_kernel_mapping "${VAS_NONCANONICAL_HOLE}" "${NONCANONICAL_REG_SIZE}" \ 0x${END_UVA} 0x${START_KVA} "---" 0 fi } # populate_kernel_segment_mappings() populate_kernel_segment_mappings() { setup_ksparse_top setup_kernelimg_mappings #---------- Loop over the kernel segment data records export IFS=$'\n' local i=1 local REC prevseg_start_kva=0 for REC in $(cat ${KSEGFILE}) do decho "REC: $REC" interpret_kernel_rec ${REC} ${i} #printf "=== %06d / %06d\r" ${i} ${gFileLines} let i=i+1 done 1>&2 #---------- #exit # TODO - ins k sparse region from last (lowest) valid k mapping to top end uva prevseg_start_kva_hex=$(printf "0x%llx" ${prevseg_start_kva}) decho "prevseg_start_kva_hex = ${prevseg_start_kva_hex}" setup_ksparse_lowest ${prevseg_start_kva_hex} # Non-canonical sparse region for 64-bit if [ ${IS_64_BIT} -eq 1 ]; then #-a ${SHOW_USERSPACE} -eq 1 ] ; then setup_noncanonical_sparse_region fi [ ${DEBUG} -eq 1 ] && show_gkArray 1 ################## # Get all the kernel mapping data into a file: # Reverse sort by 4th field, the hexadecimal end va; simple ASCII sort works # because numbers 0-9a-f are anyway in alphabetical order show_gkArray 0 > /tmp/${name}/pmk sort -t"," -k4 -r /tmp/${name}/pmk > /tmp/${name}/pmkfinal ################## [ ${DEBUG} -eq 1 ] && cat /tmp/${name}/pmkfinal } # end populate_kernel_segment_mappings() ================================================ FILE: do_vgraph.sh ================================================ #!/bin/bash # do_vgraph.sh # # Quick Description: # Support script for the procmap project. Handles the user VAS population # into our array data structure. # Don't invoke this directly, run the 'procmap' wrapper instead. # "Draw" out, (somewhat) to scale, ranges of numbers in a vertically tiled # format. For eg.: the output of /proc/iomem, /proc/vmalloc, # /proc//maps, etc etc # # We EXPECT as input a file (the job of the mapsfile_prep.sh script is to # generate this file); the file must be in CSV format with 3 columns; # col 1 and col 2 are ASSuMEd to be in hexadecimal. # (as of this very early ver at least). # FORMAT :: # [...] # field1,field2,field3 # field1,field2,field3 # [...] # # field1: integer value (often an address of some sort) # field2: integer value (often an address of some sort) # field3: string: descriptive # # Our mapsfile_prep.sh script is invoked via the procmap wrapper to do # precisely this. # # Created : 17Apr2020 # Author: # Kaiwan N Billimoria # kaiwan -at- kaiwantech -dot- com # kaiwan -dot- billimoria -at- gmail -dot- com # kaiwanTECH # License: MIT. source ${PFX}/do_kernelseg.sh || { echo "${name}: fatal: could not source ${PFX}/do_kernelseg.sh , aborting..." exit 1 } # Titles, etc... NULLTRAP_STR="< NULL trap >" SPARSE_ENTRY="<... Sparse Region ...>" ########### Functions follow ####################### #-------------------- p r e p _ f i l e ------------------------------- prep_file() { # Get rid of comment lines sed --in-place '/^#/d' ${gINFILE} } # end prep_file() #------------------- g e t _ r a n g e _ i n f o ---------------------- get_range_info() { # Get the process user VAS (virtual addr space) range: start - end # -the first and last numbers! local int_start=$(head -n1 ${gINFILE} |cut -d"${gDELIM}" -f1 |sed 's/ //') # also trim local int_end=$(tail -n2 ${gINFILE} |head -n1 |cut -d"${gDELIM}" -f2 |sed 's/ //') #decho "int_start = $int_start int_end $int_end" #local int_end=$(tail -n1 ${gINFILE} |cut -d"${gDELIM}" -f2 |sed 's/ //') # RELOOK : int value overflows here w/ large 64-bit # as input # Fixed: use printf w/ %llu fmt local start_dec=$(printf "%llu" 0x${int_start}) #$(echo $((16#${int_start}))) local end_dec=$(printf "%llu" 0x${int_end}) gTotalLen=$(printf "%llu" $((end_dec-start_dec))) gFileLines=$(wc -l ${gINFILE} |awk '{print $1}') # = # of VMAs decho "range: [${start_dec} to ${end_dec}]: total size=${gTotalLen}" } # end get_range_info() #--- # Userspace array: # We require a 6d array: each 'row' will hold these values: # # col0 col1 col2 col3 col4 col5 # row'n' [segname],[size],[start_uva],[end_uva],[mode],[offset] # # HOWEVER, bash only actually supports 1d array; we thus treat a simple 1d # array as an 'n'd (n=6) array! # So we just populate a 1d array like this: # [val1] [val2] [val3] [val4] [val5] [val6] [val7] [val8] [...] # but INTERPRET it as 6d like so: # ([val1],[val2],[val3],[val4],[val5],[val6]),([val7],[val8],[val9],val[10],...) [...] declare -a gArray gRow=0 #--- # Kernel-space array: (see kseg file) declare -a gkArray gkRow=0 #-----------------------s h o w A r r a y ----------------------------- # Parameters: # $1 : debug print; if 1, it's just a debug print, if 0 we're writing it's # data to a file in order to process further (sort/etc) showArray() { local i k DIM=6 if [ $1 -eq 1 ] ; then echo decho "gRow = ${gRow}" # gArray :: [segname],[size],[start_va],[end_va],[mode],[offset] echo "showArray(): [segname,size,start_uva,end_uva,mode,offset]" fi for ((i=0; i<${gRow}; i+=${DIM})) do printf "%s," "${gArray[${i}]}" # segname let k=i+1 printf "%d," "${gArray[${k}]}" # seg size let k=i+2 #--- Nice bugfix here! # On the TI BBB 32-bit, i noticed that the 'heap' would appear at or near # the very top of the user VAS! that's just wrong... Investigating, i found # that its as the /tmp/procmap/pmu file records were'nt being correctly # sorted.. That was as running sort(1) on hex numbers does work *as long as # they're seen as numbers and not strings*. To do that, we have to ensure # that #s - like the start and end UVAs here - are left-padded with 0s! # Then sort works correctly and all's well! # (Interestingly, the %0zx printf format has it work portably for 32 and 64 # bit - avoiding the need to explicitly do %08x / %016x for 32/64 bit!) printf "%0zx," "0x${gArray[${k}]}" # start va let k=i+3 printf "%0zx," "0x${gArray[${k}]}" # end va #--- let k=i+4 # UNSURE / RELOOK # error here ?? only on console device (no color) # 'environment: line 131: printf: : invalid number' ?? printf "%s," "${gArray[${k}]}" # mode+flag let k=i+5 printf "%x\n" "0x${gArray[${k}]}" # file offset done } # end showArray() gNumSparse=0 gTotalSparseSize=0 gTotalSegSize=0 setup_nulltrap_page() { local pgsz_hex=$(printf "%x" ${PAGE_SIZE}) #local pgsz_hex=$(printf "%x" 4096) # ${PAGE_SIZE}) # TODO : why is PAGE_SIZE == 0 ??? # Have temporarily patched PAGE_SIZE to 0x1000 in lib_procmap.sh:parse_ksegfile_write_archfile() append_userspace_mapping "${NULLTRAP_STR}" ${PAGE_SIZE} 0 \ ${pgsz_hex} "----" 0 # RELOOK? Treat the NULL trap page as a sparse region?? inc_sparse ${PAGE_SIZE} } # end setup_nulltrap_page() #------------- i n t e r p r e t _ u s e r _ r e c --------------------- # Interpret record (a CSV 'line' from the input stream) and populate the # gArr[] n-dim array. # Format: # start_uva,end_uva,mode/p|s,offset,image_file # ; uva = user virtual address # eg. # 7f1827411000,7f1827412000,rw-p,00028000,/lib/x86_64-linux-gnu/ld-2.27.so # - 7f3390031000,7f3390053000,/lib/x86_64-linux-gnu/libc-2.28.so # Parameters: # $1 : the above CSV format string of 5 fields {start_uva,end_uva,mode,off,segname} # $2 : loop index # Populate the global 'n-dim' (n=6) array gArray. # Arch-independent. interpret_user_rec() { local gap=0 local start_uva=$(echo "${1}" |cut -d"${gDELIM}" -f1) local end_uva=$(echo "${1}" |cut -d"${gDELIM}" -f2) # Skip comment lines echo "${start_uva}" | grep -q "^#" && return local mode=$(echo "${1}" |cut -d"${gDELIM}" -f3) local offset=$(echo "${1}" |cut -d"${gDELIM}" -f4) local segment=$(echo "${1}" |cut -d"${gDELIM}" -f5) [ -z "${segment}" ] && segment=" [-unnamed-] " # Remove any leading zeroes from the offset offset=$(echo ${offset} |sed 's/^0*//') [ -z "${offset}" ] && offset=0 # Convert hex to dec local start_dec=$(printf "%llu" 0x${start_uva}) local end_dec=$(printf "%llu" 0x${end_uva}) local seg_sz=$(printf "%llu" $((end_dec-start_dec))) # in bytes local DetectedSparse=0 # The global 6d-array's format is: # col0 col1 col2 col3 col4 col5 # row'n' [segname],[size],[start_uva],[end_uva],[mode],[offset] if [ "${offset}" = "00000000" ]; then offset="0" fi # NOTE- # The '[vsyscall]' page is in kernel-space; hence, we only show it if # our config requires us to...; default is No if [ "${segment}" = "[vsyscall]" -a ${SHOW_VSYSCALL_PAGE} -eq 0 ]; then decho "skipping [vsyscall] page..." prevseg_start_uva=${start_dec} prevseg_name="[vsyscall]" return fi #------------ Sparse Detection if [ ${SPARSE_SHOW} -eq 1 ]; then decho " $2: seg=${segment} prevseg_name=${prevseg_name} , gRow=${gRow} " # Detect sparse region, and if present, insert into the gArr[]. # Sparse region detected by condition: # gap = this-segment-start - prev-segment-end > 1 page # Wait! With order by Descending va, we should take the prev segment's # start uva (not the end uva)! # gap = prev_seg_start - this-segment-end > 1 page if [ "${segment}" != "[vsyscall]" ]; then #decho "end_dec=${end_dec} prevseg_start_uva=${prevseg_start_uva}" gap=$((${prevseg_start_uva}-${end_dec})) local gap_hex=$(printf "0x%llx" ${gap}) decho "gap = ${gap}" [ ${gap} -gt ${PAGE_SIZE} ] && DetectedSparse=1 fi if [ ${DetectedSparse} -eq 1 -a "${prevseg_name}" != "[vsyscall]" ]; then local prevseg_start_uva_hex=$(printf "%llx" ${prevseg_start_uva}) local sparse_start_uva_dec=$((${prevseg_start_uva}-${gap})) # local sparse_start_uva=$((0x${prevseg_start_uva_hex}-${gap_hex})) local sparse_start_uva=$(printf "%llx" ${sparse_start_uva_dec}) decho "prevseg_start_uva_hex=${prevseg_start_uva_hex} gap = ${gap_hex} sparse_start_uva=${sparse_start_uva}" #prompt append_userspace_mapping "${SPARSE_ENTRY}" ${gap} ${sparse_start_uva} \ ${prevseg_start_uva_hex} "----" 0 inc_sparse ${gap} fi prevseg_start_uva=${start_dec} fi #-------------- #if [ ${DetectedSparse} -eq 0 ]; then #--- Populate the global array append_userspace_mapping "${segment}" ${seg_sz} ${start_uva} \ ${end_uva} "${mode}" ${offset} prevseg_name=${segment} #decho "prevseg_name = ${prevseg_name} #" } # end interpret_user_rec() # query_highest_valid_uva() # Require the topmost valid userspace va, query it from the o/p of our # prep_mapfile.sh script # TODO : ARCH SPECIFIC !! query_highest_valid_uva() { local TMPF=/tmp/${name}/qhva awk -F"${gDELIM}" '{print $2}' ${gINFILE} > ${TMPF} [ ! -s ${TMPF} ] && { warn "couldn't fetch highest valid uva, aborting..." return } #set -x local va for va in $(cat ${TMPF}) do decho "va: $va" local va_dec=$(printf "%llu" 0x${va}) if (( $(echo "${va_dec} < ${END_UVA_DEC}" |bc -l) )); then HIGHEST_VALID_UVA=${va} rm -f ${TMPF} return fi done HIGHEST_VALID_UVA=0x0 #set +x rm -f ${TMPF} } # end query_highest_valid_uva() # Setup the userspace Sparse region at the very top (high) end of the VAS # in the gArray[] # TODO : ARCH SPECIFIC !! setup_usparse_top() { gRow=0 query_highest_valid_uva local HIGHEST_VALID_UVA_DEC=$(printf "%llu" 0x${HIGHEST_VALID_UVA}) decho "HIGHEST_VALID_UVA = ${HIGHEST_VALID_UVA}" [ ${HIGHEST_VALID_UVA_DEC} -eq 0 ] && { echo "Warning! couldn't fetch highest valid uva, aborting..." return } local gap_dec=$(bc <<< "(${END_UVA_DEC}-${HIGHEST_VALID_UVA_DEC})") if [ ${gap_dec} -gt ${PAGE_SIZE} ]; then append_userspace_mapping "${SPARSE_ENTRY}" "${gap_dec}" ${HIGHEST_VALID_UVA} \ "${END_UVA}" "----" 0 inc_sparse ${gap_dec} fi } # end setup_usparse_top() disp_fmt() { if [ ${VERBOSE} -eq 1 ] ; then tput bold ; fg_red #; bg_gray printf "VAS mappings: name [ size,perms,u:maptype,u:0xfile-offset]\n" color_reset fi } total_size_userspc() { local TMPF=/tmp/${name}/pmutmp showArray 1 > ${TMPF} # rm first header line and lines with 'Sparse' in them.. sed --in-place '1,3d' ${TMPF} sed --in-place '/Sparse/d' ${TMPF} # rm last line, it has the null trap page sed --in-place '$d' ${TMPF} # cumulatively total the 2nd field, the size gTotalSegSize=$(awk -F, 'total+=$2 ; END {print total}' ${TMPF} |tail -n1) #echo "gTotalSegSize = ${gTotalSegSize} bytes" [ ${DEBUG} -eq 0 ] && rm -f ${TMPF} } # footer_stats_etc() # Write a footer, addn details if in verbose mode, show the 'statistics' as # required. Also, check for work like --export-map= ... footer_stats_etc() { disp_fmt #--- Footer tput bold [[ ${ITS_A_THREAD} -eq 0 ]] && { printf "[=====--- End memory map for process %d:%s ---=====]\n" ${PID} ${PRCS_NAME} } || { printf "[=====--- End memory map for thread %d:%s of process %d:%s ---=====]\n" \ ${PID} ${THRD_NAME} ${PARENT_PROCESS} ${PRCS_NAME} } color_reset if [ ${VERBOSE} -eq 1 -a ${SHOW_USERSPACE} -eq 1 ]; then printf "[Pathname: %s ]\n" ${PRCS_PATHNAME} printf "\n[v] " runcmd "ls -l ${PRCS_PATHNAME}" printf "\n[v] " runcmd "file ${PRCS_PATHNAME}" which ldd >/dev/null 2>&1 && { # arch-specific? printf "\n[v] " runcmd "ldd ${PRCS_PATHNAME}" #printf "\n" } fi stats ${PID} ${PRCS_NAME} if [ ! -z "${XMAP_FILE}" ]; then touch /tmp/${name}/.kusep if [ -s /tmp/${name}/pmkfinal -a -s /tmp/${name}/pmufinal ] ; then cat > /tmp/${name}/.kusep << @EOF@ +++-------------- Kernel-User boundary --------------+++ @EOF@ fi cat /tmp/${name}/pmkfinal /tmp/${name}/.kusep /tmp/${name}/pmufinal > ${XMAP_FILE} #2>/dev/null if [ -s /tmp/${name}/pmkfinal -o -s /tmp/${name}/pmufinal ] ; then # Perform multiple ops w/ sed on the file; 1i inserts a line at the top sed --in-place -e "1i# Generated by procmap (c) kaiwanTECH\n\ # ${PRJ_URL}\n\ # Via the --export-maps= option\n\ # CSV format:\n# name,size,start_va,end_va,perms[u:maptype],[u:0xfile-offset]\n" ${XMAP_FILE} echo "[i] Maps info written to file ${XMAP_FILE} (as CSV)." fi fi } # end footer_stats_etc() #--------------------------- m a i n _ w r a p p e r ------------------- # Parameters: # $1 : PID of process main_wrapper() { local PID=$1 local szKB szMB szGB prep_file get_range_info export IFS=$'\n' #--- Header tput bold printf "\n[==================--- P R O C M A P ---==================]\n" color_reset printf "Process Virtual Address Space (VAS) Visualization utility\n" printf "https://github.com/kaiwan/procmap\n\n" date # If we can't get the real name via /proc/pid/exe, fallback to using /proc/pid/status and 'which' [[ -z "${PRCS_PATHNAME}" ]] && { PRCS_PATHNAME=$(realpath /proc/${PID}/exe 2>/dev/null) || { local name=$(grep "^Name" /proc/${PID}/status |cut -d: -f2|xargs) #|xargs to trim whitespace! PRCS_PATHNAME=$(which ${name}) } } # PARENT_PROCESS is the orig PID PRCS_NAME=$(cat /proc/${PARENT_PROCESS}/comm 2>/dev/null) || \ PRCS_NAME=$(grep "^Name" /proc/${PID}/status |cut -d: -f2|xargs) #|xargs to trim whitespace! #PRCS_PPID=$(ps -o ppid= -p ${PID}) PRCS_PPID=$(ps -LA |awk -v pid=${PID} '$2==pid {print $1}') THRD_NAME=$(cat /proc/${PRCS_PPID}/task/${PID}/comm 2>/dev/null) || \ THRD_NAME=$(ps -o comm= -p ${PID}) tput bold [[ ${ITS_A_THREAD} -eq 0 ]] && { printf "[=====--- Start memory map for process %d:%s ---=====]\n" ${PID} ${PRCS_NAME} } || { printf "[=====--- Start memory map for thread %d:%s of process %d:%s ---=====]\n" \ ${PID} ${THRD_NAME} ${PARENT_PROCESS} ${PRCS_NAME} } printf "[Pathname: %s ]\n" ${PRCS_PATHNAME} color_reset disp_fmt LOC_LEN=0 #decho "LOCATE_SPEC = ${LOCATE_SPEC}" # Any specific region to locate in the memory map? if [ ! -z "${LOCATE_SPEC}" ]; then LOC_STARTADDR=$(echo "${LOCATE_SPEC}" |cut -d, -f1) LOC_STARTADDR_DEC=$(printf "%llu" ${LOC_STARTADDR}) LOC_LEN=$(echo "${LOCATE_SPEC}" |cut -d, -f2) fi #----------- KERNEL-SPACE VAS calculation and drawing # Requires root (sudo) # Show kernelspace? Yes by default! if [ ${SHOW_KERNELSEG} -eq 1 ] ; then #init_kernel_lkm_get_details |tee -a ${LOG} || true #get_machine_set_arch_config |tee -a ${LOG} || true populate_kernel_segment_mappings graphit -k else decho "Skipping kernel segment display..." fi # Show userspace? Yes by default! [ ${SHOW_USERSPACE} -eq 0 ] && { decho "Skipping userspace display..." footer_stats_etc return } #----------- USERSPACE VAS calculation and drawing # Redirect to stderr what we don't want in the log #printf "\n%s: Processing, pl wait ...\n" "${name}" 1>&2 color_reset setup_usparse_top # Loop over the 'infile', populating the global 'n-d' array gArray local i=0 REC for REC in $(cat ${gINFILE}) do decho "REC: $REC" interpret_user_rec ${REC} ${i} printf "=== %06d / %06d\r" ${i} $((${gFileLines}-1)) let i=i+1 done 1>&2 # By now, we've populated the gArr[] ; # Order is by descending va's, so check for and insert the last two entries: # a conditional/possible sparse region and the NULL trap page # Setup the Sparse region just before the NULL trap page: #decho "prevseg_start_uva = ${prevseg_start_uva}" local gap_dec=$((prevseg_start_uva-PAGE_SIZE)) local gap=$(printf "0x%llx" ${gap_dec}) local prevseg_start_uva_hex=$(printf "%llx" ${prevseg_start_uva}) if [ ${gap_dec} -gt ${PAGE_SIZE} ]; then append_userspace_mapping "${SPARSE_ENTRY}" ${gap_dec} ${PAGE_SIZE} \ ${prevseg_start_uva_hex} "----" 0 inc_sparse ${gap} fi # Setup the NULL trap page: the very last entry setup_nulltrap_page [ ${DEBUG} -eq 1 ] && showArray 1 total_size_userspc ################## # Get all the user mapping data into a file: # Reverse sort by 4th field, the hexadecimal end va; simple ASCII sort works # because numbers 0-9a-f are anyway in alphabetical order showArray 0 > /tmp/${name}/pmu sort -f -t"," -k4 -r /tmp/${name}/pmu > /tmp/${name}/pmufinal ################## # draw it! [ ${SHOW_USERSPACE} -eq 1 ] && graphit -u footer_stats_etc } # end main_wrapper() # stats() stats() { if [ ${SHOW_STATS} -eq 0 ]; then echo "[!] stats display being skipped (see the config file)" return fi printf "\n=== Statistics ===\n" printf "\nTotal Kernel VAS (Virtual Address Space):\n" # Here the KERNEL_VAS_SIZE is, f.e., the number 549755813888.0000 # Need to convert it to an integer first (else a runtime err occurs) local kvsize=$(printf "%d\n" ${KERNEL_VAS_SIZE} 2>/dev/null) largenum_display ${kvsize} local uvsize=$(printf "%d\n" ${USER_VAS_SIZE} 2>/dev/null) printf "\nTotal User VAS (Virtual Address Space):\n" largenum_display ${uvsize} local PID=$1 local name="$2" local numvmas=0 #local numvmas=$(sudo wc -l /proc/${PID}/maps |awk '{print $1}') wc -l /proc/${PID}/maps >/dev/null 2>&1 && local numvmas=$(wc -l /proc/${PID}/maps |awk '{print $1}') || \ echo "*Whoops, getting num VMAs requires you to run procmap as root*" #[ ${gFileLines} -ne ${numvmas} ] && printf " [!] Warning! # VMAs does not match /proc/${PID}/maps\n" # The [vsyscall] VMA shows up but the NULL trap doesn't [[ ${numvmas} -ne 0 && ${SHOW_VSYSCALL_PAGE} -eq 1 ]] && let numvmas=numvmas+1 # for the NULL trap page #--- Total reported memory (RAM) on the system local totalram_kb=$(grep "^MemTotal" /proc/meminfo |cut -d: -f2|awk '{print $1}') local totalram=$(bc <<< "${totalram_kb}*1024") printf "\nTotal reported memory (RAM) on this system:\n" largenum_display ${totalram} if [ ${SHOW_USERSPACE} -eq 1 ] ; then printf "\n\n=== Statistics for Userspace: ===\n" printf "For PID %d:%s\n" ${PID} ${name} [[ ${numvmas} -ne 0 ]] && printf " %d VMAs (segments or mappings)" ${numvmas} || true [ ${SPARSE_SHOW} -eq 1 ] && { printf " %d sparse regions (includes NULL trap page)\n" ${gNumSparse} printf "Total User VAS that is Sparse memory:\n" largenum_display ${gTotalSparseSize} ${USER_VAS_SIZE} } # Valid regions (segments) total size printf "\nTotal User VAS that's valid (mapped) memory:\n" largenum_display ${gTotalSegSize} ${USER_VAS_SIZE} printf "\n===\n" # Show ps and smem stats only if it's a process and not a worker/child thread of some process [[ ${ITS_A_THREAD} -eq 1 ]] && { echo ; return } printf "\nMemory Usage stats for process PID %d:%s\n" ${PID} ${name} printf "Via ps(1):\n" # ps aux|head -n1 # USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ps aux |awk -v pid=${PID} '$2==pid {printf(" %%MEM=%u VSZ=%lu KB \ RSS=%lu KB\n", $4,$5,$6)}' which smem >/dev/null 2>&1 && { # This requires root for processes you don't own [[ ${PROCESS_OWNER} -eq 1 ]] && { printf "Via smem(8) [might take a while, pl wait ...] :\n" # smem|head -n1 # PID User Command Swap USS PSS RSS smem |awk -v pid=${PID} '$1==pid {printf(" swap=%u USS=%lu KB \ PSS=%lu KB RSS=%lu KB\n", $4,$5,$6,$7)}' || echo "OOPS, requires root" } || vecho "smem(8) requires root, skipping..." } #else # echo fi # if ${SHOW_USERSPACE} -eq 1 } # end stats() usage() { echo "Usage: ${name} -u -k [-d] -p PID-of-process -f input-CSV-filename(5 column format) -f : input CSV file -k : show only kernel-space -u : show only userspace [default: show BOTH] -d : run in debug mode -v : run in verbose mode" } ##### 'main' : execution starts here ##### #echo "test_256" #test_256 #exit 0 which bc >/dev/null 2>&1 || { echo "${name}: bc(1) package missing, pl install. Aborting..." exit 1 } [ $# -lt 4 ] && { usage exit 1 } SHOW_KERNELSEG=0 SHOW_USERSPACE=0 while getopts "p:f:l:h?kudv" opt; do case "${opt}" in h|\?) usage ; exit 0 ;; p) PID=${OPTARG} ;; f) gINFILE=${OPTARG} ;; l) LOCATE_SPEC=${OPTARG} #echo "LOCATE_SPEC=${LOCATE_SPEC}" ;; k) SHOW_KERNELSEG=1 ;; u) SHOW_USERSPACE=1 ;; d) DEBUG=1 ;; v) VERBOSE=1 ;; *) usage ;; esac done shift $((OPTIND-1)) decho "gINFILE=${gINFILE}" main_wrapper ${PID} exit 0 ================================================ FILE: err_common.sh ================================================ #!/bin/bash #------------------------------------------------------------------ # err_common.sh # # Common error handling routines. # # (c) Kaiwan N Billimoria # kaiwan -at- kaiwantech -dot- com # MIT / GPL v2 #------------------------------------------------------------------ export TOPDIR="$(pwd)" export VERBOSE_MSG=0 export DEBUG=0 # Replace with log filename export LOGFILE_COMMON=/dev/null export COLOR=1 #--- Icons # src: /usr/share/icons/Humanity/actions/ #ICON_NEXT=go-next #ICON_BACK=go-previous #ICON_YES=add #go-next ICON_NO=remove #gtk-remove #ICON_ADD=add #gtk-add #ICON_REGISTER=player_record #ICON_SIGNIN=format-text-direction-ltr #ICON_EXIT=stock_mark #system-log-out # QP # QuickPrint ;-) # Print timestamp, script name, line#. Useful for debugging. QP() { _ERR_HDR_FMT="%.23s %s[%s]: " _ERR_MSG_FMT="${_ERR_HDR_FMT}%s\n" [ ${COLOR} -eq 1 ] && fg_blue printf " QP: $_ERR_MSG_FMT" "$(date +%F.%T.%N)" " ${BASH_SOURCE[1]##*/}:${FUNCNAME[2]}" |tee -a ${LOGFILE_COMMON} dumpstack #printf " QP: $_ERR_MSG_FMT" $(date +%F.%T.%N) " ${BASH_SOURCE[1]##*/}:${BASH_LINENO[0]}" |tee -a ${LOGFILE_COMMON} [ ${COLOR} -eq 1 ] && color_reset unset _ERR_HDR_FMT unset _ERR_MSG_FMT true } STACK_MAXDEPTH=32 # arbit? dumpstack() { #for frame in $(seq 1 $1) local frame=1 local funcname ShowTitle " Stack Call-trace:" [ ${COLOR} -eq 1 ] && fg_blue while [ true ] do funcname=${FUNCNAME[${frame}]} printf " [frame #${frame}] ${BASH_SOURCE[${frame}]}:${funcname}:${BASH_LINENO[${frame}]}" #printf " [frame #${frame}] ${funcname}" [ ${frame} -ne 1 ] && printf "\n" || { [ ${COLOR} -eq 1 ] && fg_magenta printf " <-- top of stack\n" [ ${COLOR} -eq 1 ] && fg_blue } [ "${funcname}" = "main" ] && break # stop, reached 'main' [ ${frame} -ge ${STACK_MAXDEPTH} ] && break # just in case ... let frame=frame+1 done |tee -a ${LOGFILE_COMMON} [ ${COLOR} -eq 1 ] && color_reset true } # params: the error message cli_handle_error() { #QP if [ $# -lt 1 ] ; then cecho "FatalError :: " else cecho "FatalError :: $*" fi dumpstack [ ${COLOR} -eq 1 ] && color_reset exit 1 } #--------------------- F a t a l E r r o r ---------------------------- # Exits with exit status 1 ! # Parameters: # $1 : error message [optional] FatalError() { #set -x SEALS_REPORT_ERROR_URL="" local msgpre="Sorry, we've encountered a fatal error.\n\n" local errmsg="Details:\n$(date):${name}:${FUNCNAME[ 1 ]}()" local msgpost="\n\ If you feel this is a bug / issue, kindly report it here: ${SEALS_REPORT_ERROR_URL}\n Many thanks. " [ $# -ne 1 ] && { local msg="${msgpre}${errmsg}\n${msgpost}" } || { local msg="${msgpre}${errmsg}\n ${1}\n${msgpost}" } #cecho "Fatal Error! Details: ${errmsg} ${1}" #local LN=$(echo "${MSG}" |wc -l) #local calht=$(($LN*10)) local title=" FATAL ERROR!" yad --title="${title}" --image=dialog-warning --text="${msg}" \ --button="Close!${ICON_NO}:0" \ --wrap --text-align=center --button-layout=center --center \ --selectable-labels --no-escape --dialog-sep --sticky --on-top --skip-taskbar 2>/dev/null || true cli_handle_error "$@" exit 1 } # end FatalError() # $1 = warning msg warn() { [ $# -eq 0 ] && return fg_yellow ShowTitle "!WARNING! $*" #QP color_reset } # Prompt # Interactive: prompt the user to continue by pressing ENTER or # abort by pressing Ctrl-C # Parameter(s): # $1 : string to display (string) # $2 : string to display on signal trap [optional] Prompt() { local msg="*** User Abort detected! ***" trap 'wecho "${msg}" ; dumpstack ; color_reset ; exit 3' INT QUIT [ ${COLOR} -eq 1 ] && fg_magenta echo "Press ENTER to continue, or Ctrl-C to abort now..." read [ ${COLOR} -eq 1 ] && color_reset } # end Prompt() ================================================ FILE: install_procmap ================================================ #!/bin/bash # Part of the 'procmap' project, a utility to show the kernel and/or user # virtual address space of any given process. # This is the initial 'install' script. # Major advantages of this approach include: # 1. Regular users can browse their own process's user VAS (no root access reqd) # 2. The procmap kernel module needs to be run and collect details only once, # at install time. # # Author: # Kaiwan N Billimoria # kaiwan -at- kaiwantech -dot- com # kaiwan -dot- billimoria -at- gmail -dot- com # kaiwanTECH # # License: MIT. # Turn on unofficial Bash 'strict mode'! V useful # "Convert many kinds of hidden, intermittent, or subtle bugs into immediate, glaringly obvious errors" # ref: http://redsymbol.net/articles/unofficial-bash-strict-mode/ set -euo pipefail name=$(basename $0) # set the common paths PATH=${PATH}:/sbin/:/usr/sbin/:/usr/bin/:/bin/ export TERM=xterm-256color # sanitize the environment before run [[ "$(env | sed -r -e '/^(PWD|SHLVL|_)=/d')" ]] && { export TERM=xterm-256color # exec -c "$0" "$@" } PDIR=$(which $0) [ -z "${PDIR}" ] && PDIR=$(dirname $0) # true if procmap isn't in PATH export PFX=$(dirname ${PDIR}) # dir in which this script and tools reside source config || { echo "${name}: fatal: could not source file 'config', aborting..." exit 1 } VERBOSE=1 DEBUG=0 vecho() { [[ ${VERBOSE} -eq 1 ]] && echo "[v] $*" } decho() { [[ ${DEBUG} -eq 1 ]] && echo "[d] $*" } die() { echo >&2 "${name}: ***FATAL ERROR*** Error detail follows: $*" ; exit 1 } # build_lkm() # (Re)build the LKM - Loadable Kernel Module for this project build_lkm() { echo "[i] kernel: building the procmap kernel module now..." # kernel headers? [ ! -e /lib/modules/"$(uname -r)"/build ] && \ die " Suitable build env for kernel modules is missing! Pl install the Linux kernel headers (via the appropriate package). If you cannot install a 'kernel headers' package (perhaps you're running a custom built kernel), then you will need to cross-compile the procmap kernel module on your host and copy it across to the target device. Pl see this project's README.md file for details: https://github.com/kaiwan/procmap#procmap (Plus the section 'IMPORTANT: Running procmap on systems other than x86_64' https://github.com/kaiwan/procmap#important-running-procmap-on-systems-other-than-x86_64)." cd procmap_kernel || die "cd procmap_kernel failed" make clean >/dev/null 2>&1 || true make >/dev/null 2>&1 || die "kernel module \"${KMOD}\" build failed, aborting..." if [ ! -s ${KMOD}.ko ] ; then die "kernel module \"${KMOD}\" not generated? aborting..." fi vecho " kernel: LKM built" } # end build_lkm() # init_kernel_lkm_get_details() init_kernel_lkm_get_details() { #set +x vecho "kernel: init kernel LKM and get details:" if [ ! -d ${DBGFS_LOC} ] ; then die "kernel debugfs not supported or mounted? aborting..." else vecho " debugfs location verfied" fi #TOP=$(pwd) if [ ! -s ${KMOD}.ko -o ${KMOD}.c -nt ${KMOD}.ko ] ; then build_lkm fi # Ok, the kernel module is there, lets insert it! #ls -l ${KMOD}.ko rmmod ${KMOD} 2>/dev/null || true # rm any stale instance insmod ./${KMOD}.ko || { echo "${name}: insmod(8) on kernel module \"${KMOD}\" failed, will rebuild and retry now..." build_lkm insmod ./${KMOD}.ko || return } # Whoa! with set -o pipefail enabled AND using grep -q, all kinds of crazy s*it # Practical sol: do NOT use -q, redirect stdout & stderr to null dev! # ref: https://stackoverflow.com/questions/19120263/why-exit-code-141-with-grep-q lsmod | egrep -w "^${KMOD}" >/dev/null 2>&1 [ $? -ne 0 ] && die "insmod(8) on kernel module \"${KMOD}\" failed? aborting..." vecho " LKM inserted into kernel" ls ${DBGFS_LOC}/${KMOD}/${DBGFS_FILENAME} >/dev/null 2>&1 || { rmmod ${KMOD} die "required debugfs file not present? aborting..." } vecho " debugfs file present" } # end init_kernel_lkm_get_details() #--- Setup the one-time kernel-details file setup_kernel_dtl_file() { # Finally! generate the kernel seg details [[ ! -d ${REPORT_DIR} ]] && mkdir -p ${REPORT_DIR} #|| die "failed creating procmap report dir" TEMP_KSEGFILE=${REPORT_DIR}/$(basename ${KSEGFILE}).$$ #echo "TEMP_KSEGFILE = $TEMP_KSEGFILE" cat ${DBGFS_LOC}/${KMOD}/${DBGFS_FILENAME} > ${TEMP_KSEGFILE} # CSV fmt of ${KSEGFILE}.$$ # start_kva,end_kva,mode,name-of-region # Must numerically sort the kernel mappings by descending kva (2nd field) sort -t"," -k2n -r ${TEMP_KSEGFILE} > ${KSEGFILE} chmod 0444 ${KSEGFILE} rm -f ${TEMP_KSEGFILE} || true echo "[i] procmap: one-time kernel details placed here:${KSEGFILE} TIP: This kernel-detail file - ${KSEGFILE} - is ALWAYS required by procmap, even when viewing only the user virtual address space of any given process. Thus, pl back it up, so that you can restore it in this location if required in future." decho "kseg dtl: $(cat ${KSEGFILE})" || true } # end setup_kernel_dtl_file() #--- 'main' [[ -f ${KSEGFILE} ]] && { echo "WARNING! procmap seems to be already be installed on this system! Would you like to re-install it (overwriting the original kernel-detail file (${KSEGFILE})? [y/n]" read re [[ "${re}" = "y" || "${re}" = "Y" ]] || exit 0 } [[ $(id -u) -ne 0 ]] && die "requires root" DIR=$(pwd) init_kernel_lkm_get_details || true setup_kernel_dtl_file rmmod ${KMOD} || true cd ${DIR} exit 0 ================================================ FILE: lib_procmap.sh ================================================ #!/bin/bash # lib_procmap.sh # # Support script for the procmap project. # Has several 'library' routines. # (c) 2020 Kaiwan N Billimoria # kaiwanTECH # License: MIT #--- # Putting 'true' at the last line of a function is often reqd when 'bash # (unofficial) strict mode' is enabled, as we do... # set -euo pipefail # Ref: http://redsymbol.net/articles/unofficial-bash-strict-mode/ #--- PFX=$(dirname "$(which $0 2>/dev/null)") # dir in which 'procmap' and tools reside source ${PFX}/common.sh || { echo "${name}: fatal: could not source ${PFX}/common.sh , aborting..." exit 1 } source ${PFX}/.scratchfile LOCATED_REGION_ENTRY="<--LOCATED-->" # locate_region() # Insert a 'locate region'? (passed via -l) # Parameters: # $1 = -u|-k; -u => from userspace, -k => from kernel-space # $2 = start virtual addr of the region to check for intersection (hex) # $3 = end virtual addr of the region to check for intersection (hex) locate_region() { local lr_start_va=$2 lr_end_va=$3 [ "${lr_start_va:0:2}" != "0x" ] && lr_start_va=0x$2 [ "${lr_end_va:0:2}" != "0x" ] && lr_end_va=0x$3 #set -x # TODO / RELOOK # Error: #/home/kaiwan/gitLinux_repos/procmap/lib_procmap.sh: line 32: printf: 558ba488c000: invalid number #/home/kaiwan/gitLinux_repos/procmap/lib_procmap.sh: line 32: printf: 558ba488b000: invalid number local start_va_dec=$(printf "%llu" ${lr_start_va} 2>/dev/null) local end_va_dec=$(printf "%llu" ${lr_end_va} 2>/dev/null) #local start_va_dec=$(printf "%llu" 0x${2}) #local end_va_dec=$(printf "%llu" 0x${3}) #set +x if (( $(echo "${LOC_STARTADDR_DEC} >= ${start_va_dec}" |bc -l) )) ; then if (( $(echo "${LOC_STARTADDR_DEC} <= ${end_va_dec}" |bc -l) )) ; then decho " <-------- located :: start = ${LOC_STARTADDR} of length ${LOC_LEN} KB -------->" local loc_len_bytes=$((LOC_LEN*1024)) local loc_end_va_dec=$(bc <<< "${LOC_STARTADDR_DEC}+${loc_len_bytes}") LOC_END_VA=0x$(printf "%llx" ${loc_end_va_dec}) if [ "$1" = "-k" ]; then do_append_kernel_mapping "${LOCATED_REGION_ENTRY}" "${loc_len_bytes}" ${LOC_STARTADDR} \ ${LOC_END_VA} "..." elif [ "$1" = "-u" ]; then do_append_userspace_mapping "${LOCATED_REGION_ENTRY}" "${loc_len_bytes}" ${LOC_STARTADDR} \ ${LOC_END_VA} "..." 0 fi fi fi } # end locate_region() # inc_sparse() # Parameters: # $1 : size of the sparse region (decimal, bytes) inc_sparse() { [ ${SPARSE_SHOW} -eq 1 ] && { let gNumSparse=gNumSparse+1 let gTotalSparseSize=gTotalSparseSize+$1 } } # end inc_sparse() # Display the number passed in a human-readable fashion # As appropriate, also in KB, MB, GB, TB # $1 : the (large) number to display # $2 : OPTIONAL PARAM: # the total space size 'out of' (for percentage calculation) # percent = ($1/$2)*100 largenum_display() { local szKB=0 szMB=0 szGB=0 szTB=0 # !EMB: if we try and use simple bash arithmetic comparison, we get a # "integer expression expected" err; hence, use bc(1): [ ${1} -ge 1024 ] && szKB=$(bc <<< "scale=6; ${1}/1024.0") || szKB=0 if (( $(echo "${szKB} > 1024" |bc -l) )); then szMB=$(bc <<< "scale=6; ${szKB}/1024.0") fi if (( $(echo "${szMB} > 1024" |bc -l) )); then szGB=$(bc <<< "scale=6; ${szMB}/1024.0") fi if (( $(echo "${szGB} > 1024" |bc -l) )); then szTB=$(bc <<< "scale=6; ${szGB}/1024.0") fi #printf "%s: %llu bytes = %9.6f KB" ${3} ${1} ${szKB} printf " %llu bytes = %9.6f KB" ${1} ${szKB} if (( $(echo "${szKB} > 1024" |bc -l) )); then printf " = %9.6f MB" ${szMB} if (( $(echo "${szMB} > 1024" |bc -l) )); then printf " = %9.6f GB" ${szGB} fi if (( $(echo "${szGB} > 1024" |bc -l) )); then printf " = %9.6f TB" ${szTB} fi fi if [ $# -eq 2 ] ; then local pcntg=$(bc <<< "scale=12; (${1}/${2})*100.0") printf "\n i.e. %2.6f%%" ${pcntg} fi } # end largenum_display() # parse_ksegfile_write_archfile() # Here, we parse information obtained via procmap's kernel component - the # procmap LKM (loadable kernel module); it's already been written into the # file ${KSEGFILE} (via it's debugfs file from the # init_kernel_lkm_get_details() function) parse_ksegfile_write_archfile() { vecho "Parsing in various kernel variables as required" decho "KSEGFILE=${KSEGFILE}" VECTORS_BASE=$(grep -w "vector" ${KSEGFILE} |cut -d"${gDELIM}" -f1) || VECTORS_BASE="" FIXADDR_START=$(grep -w "fixmap" ${KSEGFILE} |cut -d"${gDELIM}" -f1) || FIXADDR_START="" MODULES_VADDR=$(grep -w "module" ${KSEGFILE} |cut -d"${gDELIM}" -f1) || MODULES_VADDR="" MODULES_END=$(grep -w "module" ${KSEGFILE} |cut -d"${gDELIM}" -f2) || MODULES_END="" KASAN_SHADOW_START=$(grep -w "KASAN" ${KSEGFILE} |cut -d"${gDELIM}" -f1) || KASAN_SHADOW_START="" KASAN_SHADOW_END=$(grep -w "KASAN" ${KSEGFILE} |cut -d"${gDELIM}" -f2) || KASAN_SHADOW_END="" VMALLOC_START=$(grep -w "vmalloc" ${KSEGFILE} |cut -d"${gDELIM}" -f1) || VMALLOC_START="" VMALLOC_END=$(grep -w "vmalloc" ${KSEGFILE} |cut -d"${gDELIM}" -f2) || VMALLOC_END="" PAGE_OFFSET=$(grep -w "lowmem" ${KSEGFILE} |cut -d"${gDELIM}" -f1) || PAGE_OFFSET="" # RELOOK: The 'high_memory' location seems to be an issue; let's skip it for now at least... #high_memory=$(grep -w "^high_memory" ${KSEGFILE} |cut -d"${gDELIM}" -f2) || high_memory="" #high_memory=$(grep -w "lowmem" ${KSEGFILE} |cut -d"${gDELIM}" -f2) || high_memory="" PKMAP_BASE=$(grep -w "HIGHMEM" ${KSEGFILE} |cut -d"${gDELIM}" -f1) || PKMAP_BASE="" PAGE_SIZE=$(grep -w "PAGE_SIZE" ${KSEGFILE} |cut -d"${gDELIM}" -f2) || PAGE_SIZE=1000 TASK_SIZE=$(grep -w "TASK_SIZE" ${KSEGFILE} |cut -d"${gDELIM}" -f2) || TASK_SIZE="" # Delete the PAGE_SIZE and TASK_SIZE lines from the KSEGFILE file # as we don't want it in the kernel map processing loop that follows sed --in-place '/^PAGE_SIZE/d' ${KSEGFILE} 2>/dev/null || true sed --in-place '/^TASK_SIZE/d' ${KSEGFILE} 2>/dev/null || true # We *require* these 'globals' again later in the script; # So we place them into an 'arch' file (we try and keep a 'descending order # by kva' ordering) and source this file in the scripts that require it. # It's arch-dependent, some vars may be NULL; that's okay. rm -f ${ARCHFILE} 2>/dev/null cat > ${ARCHFILE} << @EOF@ VECTORS_BASE=${VECTORS_BASE} FIXADDR_START=${FIXADDR_START} MODULES_VADDR=${MODULES_VADDR} MODULES_END=${MODULES_END} KASAN_SHADOW_START=${KASAN_SHADOW_START} KASAN_SHADOW_END=${KASAN_SHADOW_END} VMALLOC_START=${VMALLOC_START} VMALLOC_END=${VMALLOC_END} PAGE_OFFSET=${PAGE_OFFSET} PKMAP_BASE=${PKMAP_BASE} PAGE_SIZE=${PAGE_SIZE} TASK_SIZE=${TASK_SIZE} @EOF@ #high_memory=${high_memory} echo true } # end parse_ksegfile_write_archfile() ####################################################################### # Arch-specific details ####################################################################### # To calculate stuff (like the kernel start va), we require: # end_uva ; highest user va # size of the sparse non-canonical region #---------------------------------------------------------------------- # For x86_64, 4-level paging, 4k page : the typical default #---------------------------------------------------------------------- set_config_x86_64() { vecho "set config for x86_64:" ARCH=x86_64 PAGE_SIZE=$(printf "%llu" 0x${PAGE_SIZE}) USER_VAS_SIZE_TB=128 KERNEL_VAS_SIZE_TB=128 # bash debugging TIP: # set -x : turn tracing ON # set +x : turn tracing OFF #set -x # TIP : for bash arithmetic w/ large #s, first calculate in *decimal* base using # bc(1), then convert it to hex as required (via printf) # start kva = end uva + sparse non-canonical region size # For hex, bc(1) Requires the #s to be in UPPERCASE; so we use the ^^ op to # achieve this (bash ver>=4) # sparse non-canonical region size = 2^64 - (user VAS + kernel VAS) NONCANONICAL_REG_SIZE=$(bc <<< "2^64-(${USER_VAS_SIZE_TB}*${TB_1}+${KERNEL_VAS_SIZE_TB}*${TB_1})") NONCANONICAL_REG_SIZE_HEX=$(printf "0x%llx" ${NONCANONICAL_REG_SIZE}) END_UVA_DEC=$(bc <<< "(${USER_VAS_SIZE_TB}*${TB_1}-1)") END_UVA=$(printf "%llx" ${END_UVA_DEC}) START_KVA_DEC=$(bc <<< "(${END_UVA_DEC}+${NONCANONICAL_REG_SIZE}+1)") START_KVA=$(printf "%llx" ${START_KVA_DEC}) HIGHEST_KVA=ffffffffffffffff HIGHEST_KVA_DEC=$(printf "%llu" 0x${HIGHEST_KVA}) START_UVA=0 START_UVA_DEC=0 # Calculate size of K and U VAS's KERNEL_VAS_SIZE=$(bc <<< "(${HIGHEST_KVA_DEC}-${START_KVA_DEC}+1)") # user VAS size is the kernel macro TASK_SIZE (?) USER_VAS_SIZE=$(bc <<< "(${END_UVA_DEC}-${START_UVA_DEC}+1)") # We *require* these 'globals' in the other scripts # So we place all of them into a file and source this file in the # scripts that require it # Delete earier values that we'll replace with the proper ones now... sed --in-place '/^PAGE_SIZE/d' ${ARCHFILE} 2>/dev/null cat >> ${ARCHFILE} << @EOF@ ARCH=x86_64 IS_64_BIT=1 PAGE_SIZE=${PAGE_SIZE} USER_VAS_SIZE_TB=128 KERNEL_VAS_SIZE_TB=128 KERNEL_VAS_SIZE=${KERNEL_VAS_SIZE} USER_VAS_SIZE=${USER_VAS_SIZE} HIGHEST_KVA=0xffffffffffffffff START_KVA=${START_KVA} START_KVA_DEC=${START_KVA_DEC} NONCANONICAL_REG_SIZE_HEX=${NONCANONICAL_REG_SIZE_HEX} NONCANONICAL_REG_SIZE=${NONCANONICAL_REG_SIZE} END_UVA=${END_UVA} END_UVA_DEC=${END_UVA_DEC} START_UVA=0x0 FMTSPC_VA=${FMTSPC_VA} @EOF@ } # end set_config_x86_64() #---------------------------------------------------------------------- # For Aarch32 (ARM-32), 2-level paging, 4k page #---------------------------------------------------------------------- set_config_aarch32() { vecho "set config for Aarch32:" ARCH=Aarch32 PAGE_SIZE=$(printf "%lu" 0x${PAGE_SIZE}) # 32-bit, so no sparse non-canonical region. # Retrieve the PAGE_OFFSET and HIGHMEM lines from the ARCHFILE file PAGE_OFFSET=$(grep "^PAGE_OFFSET" ${ARCHFILE} |cut -d"=" -f2) HIGHMEM=$(grep "^HIGHMEM" ${ARCHFILE} |cut -d"=" -f2 || true) decho "PAGE_OFFSET = ${PAGE_OFFSET} , HIGHMEM = ${HIGHMEM}" [ -z "${PAGE_OFFSET}" ] && { echo "ERROR: Aarch32: couldn't fetch the PAGE_OFFSET value, aborting..." exit 1 } HIGHEST_KVA=ffffffff HIGHEST_KVA_DEC=$(printf "%lu" 0x${HIGHEST_KVA}) # For Aarch32 (and possibly other arch's as well), we cannot simply assume # that the 'start kva' is PAGE_OFFSET; very often it's the start of the kernel # module region which is *below* PAGE_OFFSET; check for this and update.. #START_KVA=$(tail -n1 ${KSEGFILE} |cut -d"${gDELIM}" -f1) START_KVA=$(grep -w "module" ${KSEGFILE} |cut -d"${gDELIM}" -f1) if (( $(echo "0x${START_KVA^^} > 0x${PAGE_OFFSET^^}" |bc -l "obase=16" 2>/dev/null) )); then START_KVA=${PAGE_OFFSET} fi START_KVA_DEC=$(printf "%lu" 0x${START_KVA}) END_UVA_DEC=$((0x${START_KVA}-1)) END_UVA=$(printf "%lx" ${END_UVA_DEC}) START_UVA=0x0 START_UVA_DEC=0 # Calculate size of K and U VAS's KERNEL_VAS_SIZE=$(bc <<< "(${HIGHEST_KVA_DEC}-${START_KVA_DEC}+1)") USER_VAS_SIZE=$(bc <<< "(${END_UVA_DEC}-${START_UVA_DEC}+1)") # We *require* these 'globals' in the other scripts # So we place all of them into a file and source this file in the # scripts that require it # Delete earier values that we'll replace with the proper ones now... sed --in-place '/^PAGE_SIZE/d' ${ARCHFILE} 2>/dev/null cat >> ${ARCHFILE} << @EOF@ ARCH=Aarch32 IS_64_BIT=0 PAGE_SIZE=${PAGE_SIZE} KERNEL_VAS_SIZE=${KERNEL_VAS_SIZE} USER_VAS_SIZE=${USER_VAS_SIZE} HIGHEST_KVA=0xffffffff START_KVA=${START_KVA} START_KVA_DEC=${START_KVA_DEC} END_UVA=${END_UVA} END_UVA_DEC=${END_UVA_DEC} START_UVA=0x0 FMTSPC_VA=${FMTSPC_VA} @EOF@ } # end set_config_aarch32() #---------------------------------------------------------------------- # For Aarch64 4-level paging, 4k page : the typical default #---------------------------------------------------------------------- set_config_aarch64() { #set -x #------------ # ISSUES ??? # runtime values- #ARCH=Aarch64 #IS_64_BIT=1 #PAGE_SIZE=4096 #KERNEL_VAS_SIZE_TB=0 #USER_VAS_SIZE_TB=0 #KERNEL_VAS_SIZE=0 #USER_VAS_SIZE=549755813888 #HIGHEST_KVA=0xffffffffffffffff #START_KVA=ffffffffffffffff #START_KVA_DEC=18446744073709551616 #NONCANONICAL_REG_SIZE_HEX=0xffffffffffffffff #NONCANONICAL_REG_SIZE=18446744073709551616 #END_UVA=ffffffffffffffff # #---------------- It IS indeed a BUG! # The issue- the K and U VAS size was < 1 TB (0.5TB)! # So, have now converted the Aarch64 config to use GB instead of TB # and it seems good... #------------ vecho "set config for Aarch64:" ARCH=Aarch64 PAGE_SIZE=$(printf "%llu" 0x${PAGE_SIZE}) # get the user VAS size via TASK_SIZE macro TASK_SIZE_DEC=$(printf "%llu" 0x${TASK_SIZE}) USER_VAS_SIZE=${TASK_SIZE_DEC} USER_VAS_SIZE_GB=$(bc <<< "scale=2; (${TASK_SIZE_DEC}/${GB_1})") # TODO : check this ASSUMPTION! [seems ok] # We assume the kernel VAS size == user VAS size KERNEL_VAS_SIZE_GB=${USER_VAS_SIZE_GB} KERNEL_VAS_SIZE=$(bc <<< "(${KERNEL_VAS_SIZE_GB}*${GB_1})") # sparse non-canonical region size = 2^64 - (user VAS + kernel VAS) NONCANONICAL_REG_SIZE=$(bc <<< "scale=0; 2^64-(${USER_VAS_SIZE_GB}*${GB_1}+${KERNEL_VAS_SIZE_GB}*${GB_1})") NONCANONICAL_REG_SIZE=${NONCANONICAL_REG_SIZE::-5} # hack- next printf fails if ends with .0000 NONCANONICAL_REG_SIZE_HEX=$(printf "0x%llx" ${NONCANONICAL_REG_SIZE}) # Tend to get these warnings on aarch64 ? # ./lib_procmap.sh: line 426: printf: warning: 18446744073709551616: Numerical result out of range # ./lib_procmap.sh: line 432: printf: warning: 18446744073709551616: Numerical result out of range # ...but it continues ok END_UVA_DEC=$(bc <<< "(${USER_VAS_SIZE_GB}*${GB_1}-1)") END_UVA_DEC=${END_UVA_DEC::-5} # hack- next printf fails if ends with .0000 END_UVA=$(printf "%llx" ${END_UVA_DEC}) START_KVA_DEC=$(bc <<< "(${END_UVA_DEC}+${NONCANONICAL_REG_SIZE}+1)") START_KVA=$(printf "%llx" ${START_KVA_DEC}) HIGHEST_KVA=ffffffffffffffff HIGHEST_KVA_DEC=$(printf "%llu" 0x${HIGHEST_KVA}) START_UVA=0 START_UVA_DEC=0 # We *require* these 'globals' in the other scripts # So we place all of them into a file and source this file in the # scripts that require it # Delete earier values that we'll replace with the proper ones now... sed --in-place '/^PAGE_SIZE/d' ${ARCHFILE} 2>/dev/null cat >> ${ARCHFILE} << @EOF@ ARCH=Aarch64 IS_64_BIT=1 PAGE_SIZE=${PAGE_SIZE} KERNEL_VAS_SIZE_GB=${KERNEL_VAS_SIZE_GB} USER_VAS_SIZE_GB=${USER_VAS_SIZE_GB} KERNEL_VAS_SIZE=${KERNEL_VAS_SIZE} USER_VAS_SIZE=${USER_VAS_SIZE} HIGHEST_KVA=0xffffffffffffffff START_KVA=${START_KVA} START_KVA_DEC=${START_KVA_DEC} NONCANONICAL_REG_SIZE_HEX=${NONCANONICAL_REG_SIZE_HEX} NONCANONICAL_REG_SIZE=${NONCANONICAL_REG_SIZE} END_UVA=${END_UVA} END_UVA_DEC=${END_UVA_DEC} START_UVA=0x0 FMTSPC_VA=${FMTSPC_VA} @EOF@ } # end set_config_aarch64() # human_readable_kernel_arch() human_readable_kernel_arch() { if [ ${VERBOSE} -eq 0 -a ${DEBUG} -eq 0 ] ; then return fi local TMPF1=/tmp/${name}/karch1 TMPF2=/tmp/${name}/karch2 awk -F= 'NF > 1 {print $1, "=", $2}' ${ARCHFILE} > ${TMPF1} awk 'NF == 3 {print $0}' ${TMPF1} > ${TMPF2} sed --in-place '/FMTSPC_VA/ d' ${TMPF2} local LIN="--------------------------------------------------" echo "${LIN} [v] System details detected :: ${LIN} $(cat ${TMPF2}) ${LIN}" rm -f ${TMPF1} ${TMPF2} } # end human_readable_kernel_arch() # export_kernel_info() # (Over)write collected kernel info into the XKERN_INFO file # Arrange it to be in CSV format (for easy import) export_kernel_info() { #prompt "XKERN_FILE = ${XKERN_FILE}" [ -z "${XKERN_FILE}" ] && { echo "Warning! kernel info export file unset" return } cp ${ARCHFILE} ${XKERN_FILE} # Perform multiple ops w/ sed on the file; 1i inserts a line at the top sed --in-place -e "1i# Generated by procmap (c) kaiwanTECH\n\ # ${PRJ_URL}\n\ # Via the --export-kernel= option\n\ # CSV format:\n\ # kernel-var/macro,value\n\ #\n\ # System Type : ${MACH_DESC}\n"\ -e 's/=/,/' -e '/FMTSPC_VA/d' ${XKERN_FILE} echo "[i] Kernel info written to file ${XKERN_FILE} (as CSV)." } # end export_kernel_info() # show_machine_kernel_dtl() show_machine_kernel_dtl() { printf "Detected machine type: " tput bold echo "${MACH_DESC}" color_reset human_readable_kernel_arch # --export-kernel= option? [ ! -z "${XKERN_FILE}" ] && export_kernel_info true } # end show_machine_kernel_dtl() #---------------------------------------------------------------------- # get_machine_set_arch_config() get_machine_set_arch_config() { # 32 or 64 bit OS? IS_64_BIT=1 local bitw=$(getconf LONG_BIT) [ ${bitw} -eq 32 ] && IS_64_BIT=0 # implies 32-bit # Portable printing if [ ${IS_64_BIT} -eq 1 ] ; then FMTSPC_VA="%016lx" else # 32-bit FMTSPC_VA="%08lx" fi # By now the the kernel 'archfile' has been generated; source it in... source ${ARCHFILE} 2>/dev/null || { #FatalError "${name}: could not source ${ARCHFILE} , aborting..." echo "${name}: could not source ${ARCHFILE} ..." } local mach=$(uname -m) local cpu=${mach:0:3} if [ "${mach}" = "x86_64" ]; then IS_X86_64=1 MACH_DESC="x86_64" set_config_x86_64 elif [[ "${cpu}" =~ "arm" ]]; then IS_Aarch32=1 MACH_DESC="ARM-32" set_config_aarch32 elif [[ "${mach}" =~ "aarch64" ]]; then IS_Aarch64=1 MACH_DESC="ARM-64" set_config_aarch64 elif [ "${cpu}" = "x86" ]; then if [ ${IS_64_BIT} -eq 0 ] ; then IS_X86_32=1 MACH_DESC="x86-32" set_config_x86_32 fi else printf "\n\nSorry, your CPU (\"$(uname -m)\") isn't supported...\n" # TODO - 'pl report this' exit 1 fi [ ${IS_64_BIT} -eq 1 ] && { MACH_DESC="${MACH_DESC}, 64-bit system & OS" } || { MACH_DESC="${MACH_DESC}, 32-bit OS" } show_machine_kernel_dtl } # end get_machine_set_arch_config() # do_append_kernel_mapping() # Append a new n-dim entry in the gkArray[] data structure, # creating, in effect, a new mapping # Parameters: # $1 : name of mapping/segment # $2 : size (in bytes, decimal) of mapping/segment # $3 : start va of mapping/segment # $4 : end va of mapping/segment # $5 : mode (perms) of mapping/segment # $6 : flags (special attributes if any) do_append_kernel_mapping() { # row'n' [segname],[size],[start_uva],[end_uva],[mode],[offset] gkArray[${gkRow}]="${1}" let gkRow=gkRow+1 gkArray[${gkRow}]="${2}" let gkRow=gkRow+1 gkArray[${gkRow}]=${3} # start kva let gkRow=gkRow+1 gkArray[${gkRow}]="${4}" # end (higher) kva let gkRow=gkRow+1 gkArray[${gkRow}]="${5}" let gkRow=gkRow+1 gkArray[${gkRow}]="${6}" let gkRow=gkRow+1 } # end do_append_kernel_mapping() # append_kernel_mapping() # Append a new n-dim entry in the gkArray[] data structure, # creating, in effect, a new mapping. This is merely a wrapper over the # actual func - do_append_kernel_mapping(). # # Parameters: # $1 : name of mapping/segment # $2 : size (in bytes, decimal) of mapping/segment # $3 : start va of mapping/segment # $4 : end va of mapping/segment # $5 : mode (perms) of mapping/segment # $6 : flags (special attributes if any) append_kernel_mapping() { do_append_kernel_mapping "$1" $2 $3 $4 $5 $6 [ ${LOC_LEN} -ne 0 ] && locate_region -k $3 $4 || true } # do_append_userspace_mapping() # Append a new n-dim entry in the gArray[] data structure, # creating, in effect, a new mapping # Parameters: # $1 : name of mapping/segment # $2 : size (in bytes, decimal) of mapping/segment # $3 : start va of mapping/segment # $4 : end va of mapping/segment # $5 : mode (perms) + mapping type (p|s) of mapping/segment # $6 : file offset (hex) of mapping/segment do_append_userspace_mapping() { # row'n' [segname],[size],[start_uva],[end_uva],[mode],[offset] gArray[${gRow}]="${1}" let gRow=gRow+1 gArray[${gRow}]=${2} let gRow=gRow+1 gArray[${gRow}]=${3} # start kva let gRow=gRow+1 gArray[${gRow}]=${4} # end (higher) kva let gRow=gRow+1 gArray[${gRow}]="${5}" let gRow=gRow+1 gArray[${gRow}]=${6} let gRow=gRow+1 } # end do_append_userspace_mapping() # append_userspace_mapping() append_userspace_mapping() { # $3 = start va # $4 = end va do_append_userspace_mapping "$1" $2 $3 $4 $5 $6 [ ${LOC_LEN} -ne 0 ] && locate_region -u $3 $4 } show_located_region_in_map() { # TODO: BUG: if LOC_STARTADDR is same as a segment addr, it's printed twice tput bold; fg_red if [ ${IS_64_BIT} -eq 1 ] ; then printf "| %s ${FMTSPC_VA} |\n" \ "${MARK_LOCATION}" ${LOC_STARTADDR} else printf "| %s ${FMTSPC_VA} |\n" \ "${MARK_LOCATION}" ${LOC_STARTADDR} fi color_reset oversized=0 } # Kernel-only: # Check, if the currently printed 'end_va' matches an entry in our ARCHFILE; # If so, print the entry 'label' (name); f.e. 0x.... <-- PAGE_OFFSET # TODO: x86_64: buggy when -k option passed, ok when both VAS's are displayed insert_arch_label() { local end_va=$1 archfile_entry archfile_entry_label if [ "${end_va:0:2}" = "0x" ]; then archfile_entry=$(grep "${end_va:2}" ${ARCHFILE}) # ${end_va:2} => leave out the '0x' part else archfile_entry=$(grep "${end_va}" ${ARCHFILE}) fi [ -z "${archfile_entry}" ] && { printf "\n" return } # Ok, we have a kernel arch entry; get it's name archfile_entry_label=$(echo "${archfile_entry}" |cut -d"=" -f1) # The values can overlap! F.e on some Aarch32 with a 2:2 (or 3:1) VM split, # both PAGE_OFFSET and MODULES_END coincide at 0x80000000 (or 0xc0000000); # Ditto for MODULES_VADDR and START_KVA local entrylen=$(echo "${archfile_entry_label}" |wc -l) if [ ${entrylen} -ge 2 ]; then archfile_entry_label=$(tr '\n' '/' <<<"${archfile_entry_label}") archfile_entry_label=${archfile_entry_label::-1} # rm last '/' fi tput bold printf "%s <-- %s\n" $(${FG_KVAR}) "${archfile_entry_label}" color_reset } #---------------------- g r a p h i t --------------------------------- # Iterates over the global n-dim arrays 'drawing' the vgraph. # when invoked with -k, it iterates over the gkArray[] ds # when invoked with -u, it iterates over the gArray[] ds # Data driven tech! # # Parameters: # $1 : -u|-k ; -u => userspace , -k = kernel-space graphit() { local i k local segname seg_sz start_va end_va mode offset flags local szKB=0 szMB=0 szGB=0 szTB=0 szPB=0 local LIN_HIGHEST_K="+------------------ K E R N E L V A S end kva ------------------+" local LIN_LOWEST_K="+------------------ K E R N E L V A S start kva ------------------+" local LIN_HIGHEST_U="+------------------ U S E R V A S end uva ------------------+" local LIN_LOWEST_U="+------------------ U S E R V A S start uva ------------------+" local LIN="+----------------------------------------------------------------------+" local LIN_WITHIN_REGION="| [------------------------------------------------------------] |" local ELLIPSE_LIN="~ . . . . . . . . . ~" local BOX_SIDES="| |" local LIN_LOCATED_REGION=" +------------------------------------------------------+" local MARK_LOCATION="X" local linelen=$((${#LIN}-2)) local oversized=0 decho "+++ graphit(): param = $1" color_reset if [ "$1" = "-u" ] ; then local DIM=6 local rows=${gRow} local FILE_TO_PARSE=/tmp/${name}/pmufinal elif [ "$1" = "-k" ] ; then local DIM=5 local rows=${gkRow} local FILE_TO_PARSE=/tmp/${name}/pmkfinal fi ### # Converting from using the in-memory n-dim array to using a CSV file ### IFS=$'\n' #for ((i=0; i<${rows}; i+=${DIM})) local REC rownum=1 totalrows=$(wc -l ${FILE_TO_PARSE} |awk '{print $1}') for REC in $(cat ${FILE_TO_PARSE}) do local tlen=0 len_perms len_maptype len_offset local tmp1="" tmp2="" tmp3="" tmp4="" tmp5="" local tmp5a="" tmp5b="" tmp5c="" tmp6="" local segname_nocolor tmp1_nocolor tmp2_nocolor tmp3_nocolor local tmp4_nocolor tmp5a_nocolor tmp5b_nocolor tmp5c_nocolor local tmp5 tmp5_nocolor local tmp7 tmp7_nocolor local archfile_entry archfile_entry_label #--- Retrieve values from the file #set -x segname=$(echo "${REC}" | cut -d"," -f1) seg_sz=$(echo "${REC}" | cut -d"," -f2) start_va=$(echo "${REC}" | cut -d"," -f3) end_va=$(echo "${REC}" | cut -d"," -f4) mode=$(echo "${REC}" | cut -d"," -f5) flags=$(echo "${REC}" | cut -d"," -f6) #set +x if [ "$1" = "-u" ] ; then offset=$(echo "${REC}" | cut -d"," -f6) flags=0 fi #segname=${gArray[${i}]} # col 1 [str: the label/segment name] #let k=i+1 #seg_sz=${gArray[${k}]} # col 2 [int: the segment size] #let k=i+2 #start_va=0x${gArray[${k}]} # col 3 [int: the first number, start_va] #let k=i+3 #end_va=0x${gArray[${k}]} # col 4 [int: the second number, end_va] #let k=i+4 #mode=${gArray[${k}]} # col 5 [str: the mode+flag] #let k=i+5 #offset=${gArray[${k}]} # col 6 [int: the file offset] #elif [ "$1" = "-k" ] ; then #segname=${gkArray[${i}]} # col 1 [str: the label/segment name] #let k=i+1 #seg_sz=${gkArray[${k}]} # col 2 [int: the segment size] #let k=i+2 #start_va=${gkArray[${k}]} # col 3 [int: the first number, start_va] #let k=i+3 #end_va=${gkArray[${k}]} # col 4 [int: the second number, end_va] #let k=i+4 #mode=${gkArray[${k}]} # col 5 [str: the mode+flag] # Calculate segment size in diff units as required if [ -z "${seg_sz}" ] ; then decho "-- invalid mapping size, skipping..." return fi szKB=$(bc <<< "${seg_sz}/1024") [ ${szKB} -ge 1024 ] && szMB=$(bc <<< "scale=2; ${szKB}/1024.0") || szMB=0 # !EMB: if we try and use simple bash arithmetic comparison, we get a # "integer expression expected" err; hence, use bc(1): szGB=0 if (( $(echo "${szMB} > 1024" |bc -l) )); then szGB=$(bc <<< "scale=2; ${szMB}/1024.0") fi szTB=0 if (( $(echo "${szGB} > 1024" |bc -l) )); then szTB=$(bc <<< "scale=2; ${szGB}/1024.0") fi szPB=0 if (( $(echo "${szTB} > 1024" |bc -l) )); then szPB=$(bc <<< "scale=2; ${szTB}/1024.0") fi #decho "@@@ i=$i/${rows} , seg_sz = ${seg_sz}" decho "nm = ${segname} , end_va = ${end_va} , start_va = ${start_va}" #--- Drawing :-p ! # the horizontal line with the end uva at the end of it #===================================== # the first actual print emitted! # Eg. # +----------------------------------------------------------------------+ 000055681263b000 # Changed to end_va @EOL as we now always print in descending order #if [ "$1" = "-k" -a ${i} -eq $((${rows}-${DIM})) ] ; then # last loop iteration # if [ ${IS_64_BIT} -eq 1 ] ; then #if [ ${i} -eq $((${rows}-${DIM})) ] ; then # last loop iteration #elif [ ${i} -ne 0 ] ; then # ** normal case ** #else # very first line if [ ${rownum} -eq 1 ] ; then # first row decho "%%%%%%%%%%%%%%%%%% FIRST LOOP" tput bold || true if [ "${1}" = "-k" ] ; then printf "%s ${FMTSPC_VA}\n" "${LIN_HIGHEST_K}" 0x"${end_va}" elif [ "${1}" = "-u" ] ; then printf "%s ${FMTSPC_VA}\n" "${LIN_HIGHEST_U}" 0x${END_UVA} fi color_reset elif [ ${rownum} -eq ${totalrows} ] ; then # last loop iteration decho "%%%%%%%%%%%%%%%%%% LAST LOOP" if [ "$1" = "-k" -a ${IS_64_BIT} -eq 1 ] ; then tput bold || true printf "%s ${FMTSPC_VA}" "${LIN_LOWEST_K}" 0x${START_KVA} insert_arch_label ${START_KVA} color_reset else printf "%s ${FMTSPC_VA}" "${LIN}" 0x${end_va} insert_arch_label ${end_va} fi #elif [ ${rownum} -gt 0 ] ; then # ** normal case ** else # ** normal case ** #decho "%%%%%%%%%%%%%%%%%% NORMAL LOOP" #============ -l option: LOCATE region ! ====================== if [ ${LOC_LEN} -ne 0 -a "${segname}" = "${LOCATED_REGION_ENTRY}" ]; then show_located_region_in_map let rownum=rownum+1 continue fi #=== ** normal case ** === #set -x if [ "${segname}" != "${LOCATED_REGION_ENTRY}" ]; then if [ ${flags} -eq 0 ] ; then printf "%s ${FMTSPC_VA}" "${LIN}" 0x"${end_va}" elif [ ${flags} -eq ${MAPFLAG_WITHIN_REGION} ] ; then printf "%s ${FMTSPC_VA}" "${LIN_WITHIN_REGION}" 0x"${end_va}" fi fi if [ "$1" = "-k" ] ; then insert_arch_label ${end_va} else printf "\n" fi #set +x fi #--- Collate and print the details of the current mapping (segment) # Eg. # |<... Sparse Region ...> [ 14.73 MB] [----,0x0] | # Print segment name tmp1=$(printf "%s|%20s " $(${FG_MAPNAME}) ${segname}) local segname_nocolor=$(printf "|%20s " ${segname}) # Colour and Print segment *size* according to scale; in KB or MB or GB or TB or PB tlen=0 if (( $(echo "${szKB} < 1024" |bc -l) )); then # print KB only tmp2=$(printf "%s [%4d KB" $(fg_darkgreen) ${szKB}) tmp2_nocolor=$(printf " [%4d KB" ${szKB}) tlen=${#tmp2_nocolor} elif (( $(echo "${szKB} > 1024" |bc -l) )); then if (( $(echo "${szMB} < 1024" |bc -l) )); then # print MB only tmp3=$(printf "%s[%7.2f MB" $(fg_navyblue) ${szMB}) tmp3_nocolor=$(printf "[%7.2f MB" ${szMB}) tlen=${#tmp3_nocolor} elif (( $(echo "${szMB} > 1024" |bc -l) )); then #set -x if (( $(echo "${szGB} < 1024" |bc -l) )); then # print GB only #-- TODO / RELOOK !!! bug here! # ++ printf '%s%s[%7.2f GB%s' '' 8.03 '' # environment: line 132: printf: : invalid number # + tmp4='8.03[ 0.00 GB' #-- #tmp4=$(printf "%s%s[%7.2f GB%s" $(tput bold) $(fg_purple) ${szGB} $(color_reset)) tmp4=$(printf "[%7.2f GB" ${szGB}) tmp4_nocolor=$(printf "[%7.2f GB" ${szGB}) tlen=${#tmp4_nocolor} #set +x elif (( $(echo "${szGB} > 1024" |bc -l) )); then if (( $(echo "${szTB} < 1024" |bc -l) )); then # print TB only tmp5=$(printf "%s%s[%7.2f TB%s" $(tput bold) $(fg_red) ${szTB} $(color_reset)) tmp5_nocolor=$(printf "[%7.2f TB" ${szTB}) tlen=${#tmp5_nocolor} else # print PB only tmp7=$(printf "%s%s[%9.2f PB%s" $(tput bold) $(fg_red) ${szPB} $(color_reset)) tmp7_nocolor=$(printf "[%9.2f PB" ${szPB}) tlen=${#tmp7_nocolor} fi fi fi fi # Mode field: # Userspace: # 'mode' xxxy has two pieces of info: # - xxx is the mode (octal permissions / rwx style) # - y is either p or s, private or shared mapping # seperate them out in order to print them in diff colors, etc # (substr op: ${string:position:length} ; position starts @ 0) # kernel: it's just 'mode' if [ "$1" = "-u" ] ; then local perms=$(echo ${mode:0:3}) local maptype=$(echo ${mode:3:1}) else local perms=${mode} fi # mode (perms) + mapping type # print in bold red fg if: # mode == --- # mode violates the W^X principle, i.e., w and x set local flag_null_perms=0 flag_wx_perms=0 if [ "${perms}" = "---" ]; then flag_null_perms=1 fi echo "${perms}" | grep -q ".wx" && flag_wx_perms=1 if [ ${flag_null_perms} -eq 1 -o ${flag_wx_perms} -eq 1 ] ; then tmp5a=$(printf "%s%s,%s%s" $(tput bold) $(fg_red) "${perms}" $(color_reset)) if [ "$1" = "-u" ] ; then # addn comma only for userspace tmp5a="${tmp5a}," else # to compensate, addn space for kernel tmp5a="${tmp5a} " fi else tmp5a=$(printf "%s,%s%s" $(fg_black) "${perms}" $(color_reset)) if [ "$1" = "-u" ] ; then # addn comma only for userspace tmp5a="${tmp5a}," else # to compensate, addn space for kernel tmp5a="${tmp5a} " fi fi tmp5a_nocolor=$(printf ",%s," "${perms}") len_perms=${#tmp5a_nocolor} # userspace: mapping type and file offset if [ "$1" = "-u" ] ; then tmp5b=$(printf "%s%s%s,%s" $(fg_blue) "${maptype}" $(fg_black) $(color_reset)) tmp5b_nocolor=$(printf "%s," "${maptype}") len_maptype=${#tmp5b_nocolor} # file offset tmp5c=$(printf "%s0x%s%s" $(fg_black) "${offset}" $(color_reset)) tmp5c_nocolor=$(printf "0x%s" "${offset}") len_offset=${#tmp5c_nocolor} fi # Calculate the strlen of the printed string, and thus calculate and print # the appropriate number of spaces after it until the "|" close-box symbol. # Final strlen value: local segnmlen=${#segname_nocolor} if [ ${segnmlen} -lt 20 ]; then segnmlen=20 # as we do printf "|%20s"... fi if [ "$1" = "-u" ] ; then let tlen=${segnmlen}+${tlen}+${len_perms}+${len_maptype}+${len_offset} else let tlen=${segnmlen}+${tlen}+${len_perms} fi if [ ${tlen} -lt ${#LIN} ] ; then local spc_reqd=$((${linelen}-${tlen})) tmp6=$(printf "]%${spc_reqd}s|\n" " ") # print the required # of spaces and then the '|' else tmp6=$(printf "]") fi #decho "tlen=${tlen} spc_reqd=${spc_reqd}" #================ The second actual print emitted! # f.e: # "| [heap] [ 132 KB,rw-,p,0x0] |" echo "${tmp1}${tmp2}${tmp3}${tmp4}${tmp5}${tmp7}${tmp5a}${tmp5b}${tmp5c}${tmp6}" #--- NEW CALC for SCALING # Simplify: We base the 'height' of each segment on the number of digits # in the segment size (in bytes)! segscale=${#seg_sz} # strlen(seg_sz) # Exceptions to this check: # 1. Null trap if [ "${segname}" = "< NULL trap >" ] ; then segscale=1 # assign a segscale of 1 for the null trap, so that it gets printed as a single line box # 2. if we're in a 'located region' (via -l option) elif [ "${segname}" != "${LOCATED_REGION_ENTRY}" -a ${segscale} -lt 4 ] ; then # && { # min seg size is 4096 bytes echo "procmap:graphit(): fatal error, seg size < 4096 [segscale (# digits) <= 4]. segname = ${segname}, segscale = ${segscale}. Aborting..." echo "Kindly report this as a bug, thanks!" exit 1 fi decho "seg_sz = ${seg_sz} segscale=${segscale}" local box_height=0 # for segscale range [1-4] # i.e. from 1-4 digits, i.e., 0 to 9999 bytes (ie. ~ 0 to 9.8 KB, single line if [ ${segscale} -ge 1 -a ${segscale} -le 4 ]; then box_height=0 # for segscale range [5-7] # i.e. for 5 digits, i.e., ~ 10 KB to 99 KB, 1 line box # i.e. for 6 digits, i.e., ~ 100 KB to 999 KB ~= 1 MB, 2 line box # i.e. for 7 digits, i.e., ~ 1 MB to 9.9 MB, 3 line box elif [ ${segscale} -ge 5 -a ${segscale} -le 7 ]; then let box_height=segscale-4 elif [ ${segscale} -ge 8 -a ${segscale} -le 13 ]; then # for segscale >= 8 digits let box_height=segscale-3 elif [ ${segscale} -ge 14 -a ${segscale} -le 16 ]; then # for segscale >= 14 digits # i.e. for 14 digits, i.e., from ~ 1 TB onwards, show an oversized ellipse box box_height=16 else # for segscale >= 16 digits # i.e. for 16 digits, i.e., realistically, for the noncanonical 'hole' on 64-bit # spanning close to 16 EB ! on x86_64 box_height=20 fi #--- # draw the sides of the 'box' [ ${box_height} -ge ${LARGE_SPACE} ] && { oversized=1 } #decho "box_height = ${box_height} oversized=${oversized}" local x for ((x=1; x<${box_height}; x++)) do printf "%s\n" "${BOX_SIDES}" if [ ${oversized} -eq 1 ] ; then [ ${x} -eq $(((LIMIT_SCALE_SZ-4)/2)) ] && printf "%s\n" "${ELLIPSE_LIN}" fi done oversized=0 let rownum=rownum+1 done # address space: the K-U boundary! on 32-bit, display both the start kva and # the 'end uva' virt addresses; on 64-bit, the noncanonical sparse region code # takes care of printing it correctly... tput bold if [ "${1}" = "-k" ] ; then [ ${IS_64_BIT} -eq 0 ] && { printf "%s ${FMTSPC_VA}" "${LIN_LOWEST_K}" 0x${START_KVA} echo } [ ${SHOW_USERSPACE} -eq 0 ] && { if [ ${IS_64_BIT} -eq 0 ]; then color_reset; return fi printf "%s ${FMTSPC_VA}\n" "${LIN_HIGHEST_U}" 0x${END_UVA} } fi color_reset # userspace: last line, the zero-th virt address; always: #+----------------------------------------------------------------------+ 0000000000000000 if [ "$1" = "-u" ] ; then tput bold printf "%s ${FMTSPC_VA}\n" "${LIN_LOWEST_U}" ${start_va} color_reset fi } # end graphit() ================================================ FILE: mapsfile_prep.sh ================================================ #!/bin/bash # mapsfile_prep.sh # # Quick Description: # Support script for the procmap project. # Don't invoke this directly, run the 'procmap' wrapper instead. # # Author: # Kaiwan N Billimoria # kaiwan -at- kaiwantech -dot- com # kaiwanTECH # License: MIT TMPF=/tmp/${name}/prep.$$ TMPF_R=${TMPF}.reversed gencsv() { # CSV format for the foll fields: # start_uva,end_uva,mode+p|s,offset,image_file awk '{printf("%s,%s,%s,%s\n", $1,$2,$3,$6)}' ${infile} | tee ${TMPF} >/dev/null #shellcheck: ^-- SC2024 (warning): sudo doesn't affect redirects. Use ..| sudo tee file sed --in-place 's/-/,/' ${TMPF} sed --in-place 's/ /,/' ${TMPF} # del comment lines sed --in-place '/^#/d' ${TMPF} # REVERSE the order of lines, thus ordering the VAS by descending va !! tac ${TMPF} > ${TMPF_R} || { echo "tac(1) failed? aborting...(pl report as bug)" exit 1 } cp ${TMPF_R} ${outfile} rm -f ${TMPF} ${TMPF_R} 2>/dev/null } ##### 'main' : execution starts here ##### [ $# -lt 2 ] && { echo "Usage: ${name} PID-of-process-for-maps-file output-filename.csv" exit 1 } infile=/proc/$1/maps outfile=$2 [ ! -r ${infile} ] && { echo "${name}: \"$1\" not readable (permissions issue)? aborting..." exit 1 } [ -f ${outfile} ] && { decho "${name}: !WARNING! \"${outfile}\" exists, will be overwritten!" } gencsv exit 0 ================================================ FILE: procmap ================================================ #!/usr/bin/env bash # #!/bin/bash # procmap # https://github.com/kaiwan/procmap # # Part of the 'procmap' project. # This bash script forms the userspace component of the 'procmap' project. # # The procmap project's intention is simply this: given a process's PID, it will # display (in a CLI/console output format only for now) a complete 'memory map' # of the process VAS (virtual address space). # The memory map will consist of two major parts, ordered by descending virtual # address: # # Kernel VAS / kernel segment # Userspace mappings (or segments) # # The kernel segment details will be realized by inserting (insmod(8)) the kernel # component of this project, the LKM (Loadable Kernel Module) named procmap. It's # output will then be parsed in and 'drawn' first. # # The user mode mappings (or 'segments') will be realized and displayed by the # majority of the code of this bash script. # # Thus, we obtain a full 'picture', a COMPLETE MEMORY MAP of the given process's # VAS (Virtual Address Space)! # # Common terms: # kva = kernel virtual address # uva = user virtual address # # Note:- BSD has a utility by the same name: procmap(1), this project isn't # the same, though (quite obviously) some aspects are similar. # (it's man page: https://man.openbsd.org/procmap.1) # # Project URL: # https://github.com/kaiwan/procmap # # Run this program; it invokes the other scripts as required. # Author: # Kaiwan N Billimoria # kaiwan -at- kaiwantech -dot- com # kaiwan -dot- billimoria -at- gmail -dot- com # kaiwanTECH # # License: MIT. #set -x # set the common paths PATH=${PATH}:/sbin/:/usr/sbin/:/usr/bin/:/bin/ export TERM=xterm-256color # sanitize the environment before run [[ "$(env | sed -r -e '/^(PWD|SHLVL|_)=/d')" ]] && { export TERM=xterm-256color # exec -c "$0" "$@" } # Turn on unofficial Bash 'strict mode'! V useful # "Convert many kinds of hidden, intermittent, or subtle bugs into immediate, glaringly obvious errors" # ref: http://redsymbol.net/articles/unofficial-bash-strict-mode/ set -euo pipefail export name=procmap PDIR=$(which $0) [ -z "${PDIR}" ] && PDIR=$(dirname $0) # true if procmap isn't in PATH export PFX=$(dirname ${PDIR}) # dir in which this script and tools reside export PAGE_SIZE=$(getconf PAGE_SIZE) # for the --only-user case SCRATCHFILE=${PFX}/.scratchfile rm -f ${SCRATCHFILE} touch ${SCRATCHFILE} mkdir -p /tmp/${name} 2>/dev/null source ${PFX}/common.sh || { echo "${name}: fatal: could not source file '${PFX}/common.sh', aborting..." exit 1 } verify_utils_present source ${PFX}/config || { echo "${name}: fatal: could not source configuration in file '${PFX}/config', aborting..." exit 1 } # leverage PS4 to show func, line#, etc when debugging! [ ${DEBUG} -eq 1 ] && export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' source ${PFX}/lib_procmap.sh || { echo "${name}: fatal: could not source ${PFX}/lib_procmap.sh , aborting..." exit 1 } logfile_post_process() { sed -i.bkp "/###\:\:\:/d" ${1} # take a backup & get rid of the signature line sed -i "s/\x1b....//g" ${1} # get rid of the ^[(B^[[m characters ! # '\x1b' is ESC ! Find it, and then delete it and the following 4 chars # (the 4 .'s that follow specify this) sed -i "s/\x1m.//g" ${1} # get rid of the color characters ! sed -i "s/^\;166m//g" ${1} sed -i "s/^mm//g" ${1} sed -i "s/^5\;166m//g" ${1} sed -i "s/47m//g" ${1} #[ ${DEBUG} -eq 0 ] && rm -f sed* } usage() { cat >/tmp/.ph << @EOF@ Usage: ${name} [options] -p PID (OR --pid=PID-of-process-to-show-memory-map-of) The only *required* option switch is: -p PID|--pid=PID : PID of the process whose virtual memory map's to be displayed The following option switches are all optional: -u|--only-user : show ONLY the user mode mappings or segments (not kernel VAS) -k|--only-kernel : show ONLY the kernel-space mappings or segments (not user VAS) [-u and -k are mutually exclusive and the default is to show BOTH] --export-maps=filename : write all map information gleaned to the file you specify in CSV --export-kernel=filename : write kernel information gleaned to the file you specify in CSV -v|--verbose : verbose mode (try it! see below for details) -d|--debug : run in debug mode --ver|--version : display version info -h|--help : display this help screen. See the config file as well. *Verbose* mode (--verbose): Most verbose messages are prepended with '[v]' to help you identify them In addition to the 'usual' stuff, the output now includes: - kernel segment details (prior to showing the kernel VAS); useful for system developers/BSP - progress messages - a header/footer legend for the mappings - at the end, if userspace view included, upon the process executable in question, we perform an ls -l, file, ldd commands. ----------------------------- Caveats / Limitations: ----------------------------- - This utility is in development and is continually evolving. As of now, in terms of the precise arch-specific kernel memory layout specifics, it's almost certainly incomplete. For example, as of now we don't (can't?) show every kernel mapping as documented in the kernel (for the x86_64, here: Documentation/x86/x86_64/mm.rst); f.e., the kernel vmemmap region, guard hole, etc are not shown explcitly - they are simply within one of the sparse regions on the map. - procmap currently supports the following CPU arch's: x86_64, AArch32, AArch64; other archs - could use help - running procmap on arch's other than x86_64: TL;DR: you might need to cross-compile the procmap kernel module (under procmap/procmap_kernel dir) yourself and copy it over to the target device under the same directory (details: see the README.md pl) - requires root (sudo) access - kernel mappings: the precise 'mode' (perms) of each mapping being unclear (to me), I simply put in what seems logical (but am unsure of it's accuracy). Knowledgeable persons, pl help! ----------------------------- Installation ----------------------------- - From v0.6 onward, an 'install' script's been provided (install_procmap); obviously, it must be run the very first time - as root - to install procmap. Major advantages of this approach include: - Regular users can browse their own process's user VAS (no root access reqd) - The procmap kernel module needs to be run and collect details only once, at install time. @EOF@ cat /tmp/.ph 1>&2 rm -f /tmp/.ph procmap_version } ### To FIX: # --locate=, : locate a given region within the process VAS # start-vaddr : a virtual address in hexadecimal # length : length of the region to locate in KB VER_MAJOR=0 VER_MINOR=6 PRJ_URL="https://github.com/kaiwan/procmap" AUTHORS="Kaiwan N Billimoria" ORG="kaiwanTECH" LIC="MIT" procmap_version() { printf "${name} ver ${VER_MAJOR}.${VER_MINOR}\n" printf "(c) ${AUTHORS}, ${ORG}\n" printf "${PRJ_URL}\n" printf "License: ${LIC}\n" } # Parameters: # $1 : if 1 show the 'usage' text # $2... : error message err() { [ $# -lt 2 ] && return local show_usage=$1 shift tput bold; fg_red printf "%s\n" "$@" >&2 color_reset [ ${show_usage} -eq 1 ] && { echo; usage } exit 1 } show_selected_opt() { [ $# -eq 0 ] && return fg_yellow printf "%s\n" "$@" color_reset } # Parameter: PID of the process validity_checks() { [[ $# -ne 1 ]] && return # validity checks on PID rgx='^[0-9]+$' if ! [[ ${PID} =~ ${rgx} ]] ; then err 0 "Error: Invalid PID (must be a positive integer)" fi [ ${PID} -eq 0 ] && { err 0 "Specifying a valid PID with -p|--pid= is mandatory" } [ ! -f ${PFX}/do_vgraph.sh ] && { err 0 "${name}: ${PFX}/do_vgraph.sh missing? Aborting..." } [ ! -f ${PFX}/mapsfile_prep.sh ] && { err 0 "${name}: ${PFX}/mapsfile_prep.sh missing? Aborting..." } #--- # Musn't use sudo as far as is possible, thus allowing regular users to view # their own processes's user VAS #--- # Check for process existence ps -LA|grep -w ${PID} >/dev/null 2>&1 || { err 0 "${name}: process/thread with PID \"${PID}\" is invalid (or dead now?) Aborting..." } # Verify it's not a kernel thread local pname pname=$(grep "^Name" /proc/${PID}/status |cut -d: -f2|xargs) #|xargs to trim whitespace! ps aux|grep -w "\[${pname}\]" >/dev/null 2>&1 && { # name in square brackets? it's a kthread err 0 "${name}: process with PID \"${PID}\" name \"[${pname}]\" is invalid; I believe it's a kernel thread. Aborting..." } || true # Do we own the process (or thread) PID? If we can send it signal 0, yes, else no PROCESS_OWNER=0 kill -0 ${PID} 2>/dev/null && PROCESS_OWNER=1 || true # If we don't own the process, we simply can't proceed... [[ ${PROCESS_OWNER} -eq 0 ]] && err 0 "FATAL: you don't own the process (or thread) with PID ${PID} Either run this util as root or only upon processes (or threads) you own" || true } # Parameter: PID of the process validate_pid() { [[ $# -ne 1 ]] && return validity_checks ${PID} #echo "1 PID=${PID}" # Is it a thread of a process? set +e isathread ${PID} [[ $? -eq 1 ]] && { ITS_A_THREAD=1 show_selected_opt "[i] will display memory map for thread ${PID} of process ${PARENT_PROCESS}" } || { show_selected_opt "[i] will display memory map for process PID=${PID}" } set -e #echo "-p passed; PID=${PID}" } #--- 'main' here [ $# -lt 1 ] && { usage exit 0 } #---------------------------------------------------------- # This work's now taken over by the new design - to avoid root when seeing userspace only #[[ ${SHOW_KERNELSEG} -eq 1 ]] && init_kernel_lkm_get_details |tee -a ${LOG} || true # this does req root # So now, just gain access to the procmap kernel report, here: ${KSEGFILE} [[ ! -f ${KSEGFILE} ]] && { echo "${name}: *FATAL* : procmap's kernel detail report file (${KSEGFILE}) seems to be unavailable? If this is the first run, plese first install procmap by running sudo ./install_procmap Else, (re)check: has procmap been correctly installed (with it's kernel detail report) the first time? (You can rerun the installer - sudo ./install_procmap - if required.) Aborting..." exit 1 } #---------------------------------------------------------- PID=0 export ITS_A_THREAD=0 XKERN_FILE="" # ref: https://www.geeksforgeeks.org/getopts-command-in-linux-with-examples/ optspec=":p:ukvdh?-:" while getopts "${optspec}" opt do #echo "opt = ${opt}" case "${opt}" in p) #echo "-p passed; pid=${OPTARG}" PID=${OPTARG} validate_pid ${PID} ;; u) show_selected_opt "[i] will display ONLY user VAS" export SHOW_KERNELSEG=0 [[ ${SHOW_USERSPACE} -eq 0 ]] && { echo "The -u and -k options are mutually exclusive" ; exit 1 } ;; k) show_selected_opt "[i] will display ONLY kernel VAS" export SHOW_USERSPACE=0 [[ ${SHOW_KERNELSEG} -eq 0 ]] && { echo "The -u and -k options are mutually exclusive" ; exit 1 } ;; v) export VERBOSE=1 show_selected_opt "[i] running in VERBOSE mode" ;; d) export DEBUG=1 show_selected_opt "[i] running in DEBUG mode" ;; h) usage ; exit 0 ;; -) # 'long' opts '--xxx' style, ala checksec! #echo "optarg = ${OPTARG}" #prompt case "${OPTARG}" in pid=*) PID=$(echo "${OPTARG}" |cut -d"=" -f2) validate_pid ${PID} ;; only-user) show_selected_opt "[i] will display ONLY user VAS" SHOW_KERNELSEG=0 ;; only-kernel) show_selected_opt "[i] will display ONLY kernel VAS" SHOW_USERSPACE=0 ;; export-maps=*) XMAP_FILE=$(echo "${OPTARG}" |cut -d"=" -f2) [ -z "${XMAP_FILE}" ] && { err 0 "${name}: pl specify the filename for the --export-maps= option" } [ -f ${XMAP_FILE} ] && { err 0 "${name}: specified filename for --export-maps= option already exists, aborting..." } touch ${XMAP_FILE} || { err 0 "${name}: cannot create/write to specified file \"${XMAP_FILE}\", pl re-specify it or adjust permissions" } show_selected_opt "[i] will write all map info to ${XMAP_FILE} (CSV)" # Write 'scratch' data that we require in other support scripts here.. cat >> ${SCRATCHFILE} << @EOF@ XMAP_FILE=${XMAP_FILE} @EOF@ ;; export-kernel=*) XKERN_FILE=$(echo "${OPTARG}" |cut -d"=" -f2) [ -z "${XKERN_FILE}" ] && { err 0 "${name}: pl specify the filename for the --export-kernel= option" } [ -f ${XKERN_FILE} ] && { err 0 "${name}: specified filename for --export-kernel= option already exists, aborting..." } touch ${XKERN_FILE} || { err 0 "${name}: cannot create/write to specified file \"${XKERN_FILE}\", pl re-specify it or adjust permissions" } show_selected_opt "[i] will write kernel info to ${XKERN_FILE} (CSV)" # Write 'scratch' data that we require in other support scripts here.. cat >> ${SCRATCHFILE} << @EOF@ XKERN_FILE=${XKERN_FILE} @EOF@ ;; locate=*) LOCATE_SPEC=${OPTARG:7} # cut out the 'locate=' beginning LOC_STARTADDR=$(echo "${LOCATE_SPEC}" |cut -d, -f1) # Validity check; expect the vaddr to be in hex (0x....) [ "${LOC_STARTADDR:0:2}" != "0x" ] && { err 0 "${name}: the start virtual address Must be specified as a hexadecimal qty (0x....)" } # validate that a length has actually been passed LOC_LEN=$(echo "${LOCATE_SPEC}" |cut -d, -f2) decho "LOC_LEN = ${LOC_LEN}" [[ ( -z "${LOC_LEN}" ) || ( ${LOC_LEN} -le 0 ) ]] && { err 0 "${name}: the length of the virtual address region to locate must be positive (KB)" } show_selected_opt "[i] locate region from (${LOCATE_SPEC}) (start-vaddr,len-in-Kb)" ;; verbose) export VERBOSE=1 show_selected_opt "[i] running in VERBOSE mode" ;; debug) export DEBUG=1 show_selected_opt "[i] running in DEBUG mode" ;; ver|version) procmap_version exit 0 ;; help) usage exit 0 ;; *) err 1 "Unknown option '${OPTARG}'" ;; esac esac done shift $((OPTIND-1)) [[ ${PID} -eq 0 ]] && err 0 "Error: Invalid PID (must be a positive integer)" LOG=log_procmap.txt TMPCSV=/tmp/${name}/vgrph.csv parse_ksegfile_write_archfile get_machine_set_arch_config |tee -a ${LOG} || true # this does not req root # Invoke the prep_mapsfile script to prep the memory map file ${PFX}/mapsfile_prep.sh ${PID} ${TMPCSV} || exit 1 source ${ARCHFILE} if [ "${ARCH}" = "Aarch32" ]; then sed --in-place '1d' ${TMPCSV} # rm 1st line [vectors] mapping fi cat >> ${SCRATCHFILE} << @EOF@ SHOW_KERNELSEG=${SHOW_KERNELSEG} SHOW_USERSPACE=${SHOW_USERSPACE} @EOF@ # Invoke the worker script to 'draw' the memory map # Usage: do_vgraph [-d] -p PID-of-process -f input-CSV-filename(5 column format) # -v : run in verbose mode # -d : run in debug mode" dovg_cmdline="-p ${PID} -f ${TMPCSV}" [ ${DEBUG} -eq 1 ] && { dovg_cmdline="${dovg_cmdline} -d" export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' } [ ${VERBOSE} -eq 1 ] && dovg_cmdline="${dovg_cmdline} -v" [ ${SHOW_KERNELSEG} -eq 1 ] && dovg_cmdline="${dovg_cmdline} -k" [ ${SHOW_USERSPACE} -eq 1 ] && dovg_cmdline="${dovg_cmdline} -u" [ ! -z "${LOCATE_SPEC}" ] && dovg_cmdline="${dovg_cmdline} -l ${LOCATE_SPEC}" ${PFX}/do_vgraph.sh ${dovg_cmdline} | tee -a ${LOG} || true [ ${DEBUG} -eq 0 ] && rm -f ${TMPCSV} if [ ${WRITELOG} -eq 1 ]; then logfile_post_process ${LOG} echo "$(date): output logged (appended) here :" ls -lh ${LOG} fi [ ${DEBUG} -eq 0 ] && { rm -f ${SCRATCHFILE} rm -rf /tmp/${name} } exit 0 ================================================ FILE: procmap_kernel/Makefile ================================================ # ch5/lkm_template/Makefile # *************************************************************** # This program is part of the source code released for the book # "Linux Kernel Programming" 2E # (c) Author: Kaiwan N Billimoria # Publisher: Packt # GitHub repository: # https://github.com/PacktPublishing/Linux-Kernel-Programming_2E # # From: Ch 5 : Writing Your First Kernel Module LKMs, Part 2 # *************************************************************** # Brief Description: # A 'better' Makefile template for Linux LKMs (Loadable Kernel Modules); besides # the 'usual' targets (the build, install and clean), we incorporate targets to # do useful (and indeed required) stuff like: # - adhering to kernel coding style (indent+checkpatch) # - several static analysis targets (via sparse, gcc, flawfinder, cppcheck) # - two _dummy_ dynamic analysis targets (KASAN, LOCKDEP); just to remind you! # - a packaging (.tar.xz) target and # - a help target. # # To get started, just type: # make help # # For details, please refer the book, Ch 5, section # 'A "better" Makefile template for your kernel modules'. # # AUTHOR : Kaiwan N Billimoria # DESCRIPTION : A simple kernel module 'better' Makefile template # LICENSE : Dual MIT/GPL # VERSION : 0.2 #------------------------------------------------------------------ # IMPORTANT : Set FNAME_C to the kernel module name source filename (without .c). # This enables you to use this Makefile as a template; just update this variable! # As well, the KEEP_DEBUG_INFO variable (see it below) can be set to 'y' or 'n' (no being # the default) FNAME_C ?= procmap ifeq ($(FNAME_C),) $(error ERROR: you Must pass the C file like this: \ make FNAME_C=csrc-filename-without-.c target-name) else ifneq ("$(origin FNAME_C)", "command line") $(info FNAME_C=$(FNAME_C)) endif #------------------------------------------------------------------ #--- To support cross-compiling for kernel modules # For architecture (cpu) 'arch', invoke make as: # make ARCH= CROSS_COMPILE= # F.e. to cross-compile for the AArch64: # make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- # # Alternately: # export ARCH= # export CROSS_COMPILE= # make # # The KDIR var is set to a sample path below; you're expected to update it on # your box to the appropriate path to the kernel source tree for that arch. ifeq ($(ARCH),arm) # *UPDATE* 'KDIR' below to point to the ARM Linux kernel source tree on your box KDIR ?= ~/arm_prj/kernel/linux else ifeq ($(ARCH),arm64) # *UPDATE* 'KDIR' below to point to the ARM64 (AArch64) Linux kernel source # tree on your box KDIR ?= /xtra/rpi_work/rpi_64bit/kernel_rpi/linux/ else ifeq ($(ARCH),powerpc) # *UPDATE* 'KDIR' below to point to the PPC64 Linux kernel source tree on your box KDIR ?= ~/ppc_prj/kernel/linux-5.4 else # 'KDIR' is the Linux 'kernel headers' package on your host system; this is # usually an x86_64, but could be anything, really (f.e. building directly # on a Raspberry Pi implies that it's the host) KDIR ?= /lib/modules/$(shell uname -r)/build endif #--- # Compiler CC := $(CROSS_COMPILE)gcc #CC := clang STRIP := ${CROSS_COMPILE}strip PWD := $(shell pwd) obj-m += ${FNAME_C}.o #--- Debug or production mode? # Set the KEEP_DEBUG_INFO variable accordingly to y/n resp. We keep it off (n) by default. # (Actually, debug info is always going to be generated when you build the # module on a debug kernel, where CONFIG_DEBUG_INFO is defined, making this # setting of the ccflags-y (or the older EXTRA_CFLAGS) variable mostly redundant # (besides the still useful -DDEBUG). # This simply helps us influence the build on a production kernel, forcing # generation of debug symbols, if so required. Also, realize that the DEBUG # macro is turned on by many CONFIG_*DEBUG* options; hence, we use a different # macro var name, KEEP_DEBUG_INFO). KEEP_DEBUG_INFO := n DBG_STRIP := y ifeq (${KEEP_DEBUG_INFO}, y) # https://www.kernel.org/doc/html/latest/kbuild/makefiles.html#compilation-flags # EXTRA_CFLAGS deprecated; use ccflags-y ccflags-y += -DDEBUG -g -ggdb -gdwarf-4 -Og -Wall -fno-omit-frame-pointer -fvar-tracking-assignments DBG_STRIP := n else ccflags-y += -UDEBUG -Wall endif # We always keep the dynamic debug facility enabled; this allows us to dynamically # turn on/off debug printk's later... To disable it simply comment out the following line ccflags-y += -DDYNAMIC_DEBUG_MODULE KMODDIR ?= /lib/modules/$(shell uname -r) # Gain access to kernel configs (the '-' says 'continue on error') -include $(KDIR)/.config # Strip the module? Note: # a) Only strip debug symbols else it won't load correctly # b) WARNING! Don't strip modules when using CONFIG_MODULE_SIG* crytographic security ifdef CONFIG_MODULE_SIG DBG_STRIP := n endif ifdef CONFIG_MODULE_SIG_ALL DBG_STRIP := n endif ifdef CONFIG_MODULE_SIG_FORCE DBG_STRIP := n endif all: @echo @echo -n "\e[1m\e[41m" @echo '--- Building : KDIR=${KDIR} ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} ccflags-y="${ccflags-y}" KEEP_DEBUG_INFO=${KEEP_DEBUG_INFO} DBG_STRIP=${DBG_STRIP} ---' @echo -n "\e[0m" @${CC} --version|head -n1 @echo make -C $(KDIR) M=$(PWD) modules if [ "${DBG_STRIP}" = "y" ]; then \ ${STRIP} --strip-debug ${FNAME_C}.ko ; \ fi install: @echo @echo -n "\e[1m\e[41m" @echo "--- installing ---" @echo -n "\e[0m" @echo " [First, invoking the 'make' ]" make @echo @echo -n "\e[1m\e[34m" @echo " [Now for the 'sudo make install' ]" @echo -n "\e[0m" sudo make -C $(KDIR) M=$(PWD) modules_install sudo depmod @echo " [If !debug and !(module signing), stripping debug info from ${KMODDIR}/extra/${FNAME_C}.ko]" if [ "${DBG_STRIP}" = "y" ]; then \ sudo ${STRIP} --strip-debug ${KMODDIR}/extra/${FNAME_C}.ko ; \ fi dt: ifeq (,$(shell which dtc 2>/dev/null)) $(error ERROR: install the DTC compiler first) endif @echo -n "\e[1m\e[41m" @echo "--- compiles the Device Tree Blob (DTB) from the DTS (ARM/PPC/Risc-V/etc) ---" @echo -n "\e[0m" dtc -@ -I dts -O dtb -o $(FNAME_C).dtb $(FNAME_C).dts # DTBO - Device Tree Blob Overlay nsdeps: @echo -n "\e[1m\e[41m" @echo "--- nsdeps (namespace dependencies resolution; for possibly importing ns's) ---" @echo -n "\e[0m" make -C $(KDIR) M=$(PWD) nsdeps clean: @echo @echo -n "\e[1m\e[41m" @echo "--- cleaning ---" @echo -n "\e[0m" @echo make -C $(KDIR) M=$(PWD) clean # from 'indent'; comment out if you want the backup kept rm -f *.dtb *.dtbo # Any usermode programs to build? Insert the build target(s) below #--------------- More (useful) targets! ------------------------------- INDENT := indent # code-style : "wrapper" target over the following kernel code style targets code-style: make indent make checkpatch # indent- "beautifies" C code - to conform to the the Linux kernel # coding style guidelines. # Note! original source file(s) is overwritten, so we back it up. indent: ifeq (,$(shell which indent 2>/dev/null)) $(error ERROR: install indent first) endif @echo @echo -n "\e[1m\e[41m" @echo "--- applying kernel code style indentation with indent ---" @echo -n "\e[0m" @echo mkdir bkp 2> /dev/null; cp -f *.[chsS] bkp/ # Prefixing a hyphen before a command has make continue even if the cmd fails -${INDENT} -linux --line-length95 *.[chsS] # add source files as required # Detailed check on the source code styling / etc checkpatch: make clean @echo @echo -n "\e[1m\e[41m" @echo "--- kernel code style check with checkpatch.pl ---" @echo -n "\e[0m" @echo -$(KDIR)/scripts/checkpatch.pl --no-tree -f --max-line-length=95 *.[ch] # add source files as required #--- Static Analysis # sa : "wrapper" target over the following kernel static analyzer targets sa: make sa_clangtidy make sa_sparse make sa_gcc make sa_flawfinder make sa_cppcheck # static analysis with clang-tidy sa_clangtidy: ifeq (,$(shell which clang-tidy 2>/dev/null)) $(error ERROR: install clang-tidy first) endif make clean @echo @echo -n "\e[1m\e[41m" @echo "--- static analysis with clang-tidy ---" @echo -n "\e[0m" @echo -CHECKS_ON="-*,clang-analyzer-*,bugprone-*,cert-*,concurrency-*,performance-*,portability-*,linuxkernel-*,readability-*,misc-*"; CHECKS_OFF="-clang-analyzer-cplusplus*,-misc-include-cleaner,-readability-identifier-length,-readability-braces-around-statements" ; clang-tidy -header-filter=.* --use-color *.[ch] -checks=$$CHECKS_ON,$$CHECKS_OFF # static analysis with sparse sa_sparse: ifeq (,$(shell which sparse 2>/dev/null)) $(error ERROR: install sparse first) endif make clean @echo @echo -n "\e[1m\e[41m" @echo "--- static analysis with sparse ---" @echo -n "\e[0m" @echo # If you feel it's too much, use C=1 instead # NOTE: deliberately IGNORING warnings from kernel headers! -make -Wsparse-all C=2 CHECK="/usr/bin/sparse --os=linux --arch=$(ARCH)" -C $(KDIR) M=$(PWD) modules 2>&1 |egrep -v "^\./include/.*\.h|^\./arch/.*\.h" # static analysis with gcc sa_gcc: make clean @echo @echo -n "\e[1m\e[41m" @echo "--- static analysis with gcc ---" @echo -n "\e[0m" @echo -make W=1 -C $(KDIR) M=$(PWD) modules # static analysis with flawfinder sa_flawfinder: ifeq (,$(shell which flawfinder 2>/dev/null)) $(error ERROR: install flawfinder first) endif make clean @echo @echo -n "\e[1m\e[41m" @echo "--- static analysis with flawfinder ---" @echo -n "\e[0m" @echo -flawfinder *.[ch] # static analysis with cppcheck sa_cppcheck: ifeq (,$(shell which cppcheck 2>/dev/null)) $(error ERROR: install cppcheck first) endif make clean @echo @echo -n "\e[1m\e[41m" @echo "--- static analysis with cppcheck ---" @echo -n "\e[0m" @echo -cppcheck -v --force --enable=all -i .tmp_versions/ -i *.mod.c -i bkp/ --suppress=missingIncludeSystem . # Packaging: just generates a tar.xz of the source as of now tarxz-pkg: rm -f ../${FNAME_C}.tar.xz 2>/dev/null make clean @echo @echo -n "\e[1m\e[41m" @echo "--- packaging ---" @echo -n "\e[0m" @echo tar caf ../${FNAME_C}.tar.xz * ls -l ../${FNAME_C}.tar.xz @echo '=== package created: ../$(FNAME_C).tar.xz ===' @echo ' TIP: When extracting, to extract into a directory with the same name as the tar file, do this:' @echo ' tar -xvf ${FNAME_C}.tar.xz --one-top-level' help: @echo -n "\e[1m\e[41m" @echo '=== Makefile Help : additional targets available ===' @echo -n "\e[0m" @echo @echo 'TIP: Type make to show all valid targets' @echo 'FYI: KDIR=${KDIR} ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} ccflags-y="${ccflags-y}" KEEP_DEBUG_INFO=${KEEP_DEBUG_INFO} DBG_STRIP=${DBG_STRIP}' @echo @echo -n "\e[1m\e[34m" @echo '--- 'usual' kernel LKM targets ---' @echo -n "\e[0m" @echo 'typing "make" or "all" target : builds the kernel module object (the .ko)' @echo 'install : installs the kernel module(s) to INSTALL_MOD_PATH (default here: /lib/modules/$(shell uname -r)/).' @echo ' : Takes care of performing debug-only symbols stripping iff KEEP_DEBUG_INFO=n and not using module signature' @echo 'dt : compiles the Device Tree Blob (DTB) from the DTS file (applicable to ARM, PPC, RISC-V, etc)' @echo 'nsdeps : namespace dependencies resolution; for possibly importing namespaces' @echo 'clean : cleanup - remove all kernel objects, temp files/dirs, etc' @echo @echo -n "\e[1m\e[34m" @echo '--- kernel code style targets ---' @echo -n "\e[0m" @echo 'code-style : "wrapper" target over the following kernel code style targets' @echo ' indent : run the $(INDENT) utility on source file(s) to indent them as per the kernel code style' @echo ' checkpatch : run the kernel code style checker tool on source file(s)' @echo @echo -n "\e[1m\e[34m" @echo '--- kernel static analyzer targets ---' @echo -n "\e[0m" @echo 'sa : "wrapper" target over the following kernel static analyzer targets' @echo ' sa_clangtidy : run the static analysis clang-tidy tool on the source file(s)' @echo ' sa_sparse : run the static analysis sparse tool on the source file(s)' @echo ' sa_gcc : run gcc with option -W1 ("Generally useful warnings") on the source file(s)' @echo ' sa_flawfinder : run the static analysis flawfinder tool on the source file(s)' @echo ' sa_cppcheck : run the static analysis cppcheck tool on the source file(s)' @echo 'TIP: use Coccinelle as well: https://www.kernel.org/doc/html/v6.1/dev-tools/coccinelle.html' @echo @echo -n "\e[1m\e[34m" @echo '--- kernel dynamic analysis targets ---' @echo -n "\e[0m" @echo 'da_kasan : DUMMY target: this is to remind you to run your code with the dynamic analysis KASAN tool enabled; requires configuring the kernel with CONFIG_KASAN On, rebuild and boot it' @echo 'da_lockdep : DUMMY target: this is to remind you to run your code with the dynamic analysis LOCKDEP tool (for deep locking issues analysis) enabled; requires configuring the kernel with CONFIG_PROVE_LOCKING On, rebuild and boot it' @echo 'TIP: Best to build a debug kernel with several kernel debug config options turned On, boot via it and run all your test cases' @echo @echo -n "\e[1m\e[34m" @echo '--- misc targets ---' @echo -n "\e[0m" @echo 'tarxz-pkg : tar and compress the LKM source files as a tar.xz into the dir above; allows one to transfer and build the module on another system' @echo ' TIP: When extracting, to extract into a directory with the same name as the tar file, do this:' @echo ' tar -xvf ${FNAME_C}.tar.xz --one-top-level' @echo -n "\e[1m\e[31m" @echo '--- Tips ---' @echo -n "\e[0m" @echo ' If the build fails with a (GCC compiler failure) message like' @echo ' unrecognized command-line option ‘-ftrivial-auto-var-init=zero’' @echo ' it's likely that you're using an older GCC. Try installing gcc-12 (or later),' @echo ' change this Makefile CC variable to "gcc-12" (or whatever) and retry' @echo @echo 'help : this help target' ================================================ FILE: procmap_kernel/convenient.h ================================================ /* * convenient.h * Brief Description: * A few convenience macros and routines.. * Mostly for kernel-space usage, some for user-space as well. */ #ifndef __CONVENIENT_H__ #define __CONVENIENT_H__ #include /* HZ */ #include #ifdef __KERNEL__ #include /* *** PLEASE READ this first *** * * We can reduce the load, and increase readability, by using the trace_printk * instead of printk. To see the trace_printk() output do: * cat /sys/kernel/debug/tracing/trace * * If we insist on using the regular printk, lets at least rate-limit it. * For the programmers' convenience, this too is programatically controlled * (by an integer var USE_RATELIMITING [default: On]). * *** Kernel module authors Note: *** * To use the trace_printk(), pl #define the symbol USE_FTRACE_PRINT in your * Makefile: * EXTRA_CFLAGS += -DUSE_FTRACE_PRINT * If you do not do this, we will use the usual printk() . * * To view : * printk's : dmesg * trace_printk's : cat /sys/kernel/debug/tracing/trace * * Default: printk (with rate-limiting) */ /* Keep this defined to use the FTRACE-style trace_printk(), else will use * regular printk() */ //#define USE_FTRACE_BUFFER #undef USE_FTRACE_BUFFER #ifdef USE_FTRACE_BUFFER #define DBGPRINT(string, args...) \ trace_printk(string, ##args); #else #define DBGPRINT(string, args...) do { \ int USE_RATELIMITING = 1; \ if (USE_RATELIMITING) { \ pr_info_ratelimited(string, ##args); \ } \ else \ pr_info(string, ##args); \ } while (0) #endif #endif /* #ifdef __KERNEL__ */ /*------------------------ MSG, QP ------------------------------------*/ #ifdef DEBUG #ifdef __KERNEL__ #define MSG(string, args...) do { \ DBGPRINT("%s:%d : " string, __func__, __LINE__, ##args); \ } while (0) #else #define MSG(string, args...) do { \ fprintf(stderr, "%s:%d : " string, __func__, __LINE__, ##args); \ } while (0) #endif #ifdef __KERNEL__ #define MSG_SHORT(string, args...) do { \ DBGPRINT(string, ##args); \ } while (0) #else #define MSG_SHORT(string, args...) do { \ fprintf(stderr, string, ##args); \ } while (0) #endif // QP = Quick Print #define QP MSG("\n") #ifdef __KERNEL__ #ifndef USE_FTRACE_BUFFER #define QPDS do { \ MSG("\n"); \ dump_stack(); \ } while (0) #else #define QPDS do { \ MSG("\n"); \ trace_dump_stack(); \ } while (0) #endif #endif #ifdef __KERNEL__ #define HexDump(from_addr, len) do { \ print_hex_dump_bytes(" ", DUMP_PREFIX_ADDRESS, from_addr, len); \ } while (0) #endif #else /* #ifdef DEBUG */ #define MSG(string, args...) #define MSG_SHORT(string, args...) #define QP #define QPDS #endif /* SHOW_DELTA_*(low, hi) : * Show the low val, high val and the delta (hi-low) in either bytes/KB/MB/GB, * as required. * Inspired from raspberry pi kernel src: arch/arm/mm/init.c:MLM() */ #define SHOW_DELTA_b(low, hi) (low), (hi), ((hi) - (low)) #define SHOW_DELTA_K(low, hi) (low), (hi), (((hi) - (low)) >> 10) #define SHOW_DELTA_M(low, hi) (low), (hi), (((hi) - (low)) >> 20) #define SHOW_DELTA_G(low, hi) (low), (hi), (((hi) - (low)) >> 30) #define SHOW_DELTA_MG(low, hi) (low), (hi), (((hi) - (low)) >> 20), (((hi) - (low)) >> 30) #ifdef __KERNEL__ /*------------------------ PRINT_CTX ---------------------------------*/ /* * An interesting way to print the context info; we mimic the kernel * Ftrace 'latency-format' : * _-----=> irqs-off [d] * / _----=> need-resched [N] * | / _---=> hardirq/softirq [H|h|s] [1] * || / _--=> preempt-depth [#] * ||| / * CPU TASK/PID |||| DURATION FUNCTION CALLS * | | | |||| | | | | | | * * [1] 'h' = hard irq is running ; 'H' = hard irq occurred inside a softirq] * * Sample output (via 'normal' printk method; in this comment, we make / * into \* ...) * CPU) task_name:PID | irqs,need-resched,hard/softirq,preempt-depth \* func_name() *\ * 001) rdwr_drv_secret -4857 | ...0 \* read_miscdrv_rdwr() *\ * * (of course, above, we don't display the 'Duration' and 'Function Calls' fields) */ #include #include #define PRINT_CTX() do { \ int PRINTCTX_SHOWHDR = 0; \ char intr = '.'; \ if (!in_task()) { \ if (in_irq() && in_softirq()) \ intr = 'H'; /* hardirq occurred inside a softirq */ \ else if (in_irq()) \ intr = 'h'; /* hardirq is running */ \ else if (in_softirq()) \ intr = 's'; \ } \ else \ intr = '.'; \ \ if (PRINTCTX_SHOWHDR == 1) \ pr_debug("CPU) task_name:PID | irqs,need-resched,hard/softirq,preempt-depth /* func_name() */\n"); \ pr_debug( \ "%03d) %c%s%c:%d | " \ "%c%c%c%u " \ "/* %s() */\n" \ , smp_processor_id(), \ (!current->mm?'[':' '), current->comm, (!current->mm?']':' '), current->pid, \ (irqs_disabled()?'d':'.'), \ (need_resched()?'N':'.'), \ intr, \ (preempt_count() && 0xff), \ __func__ \ ); \ } while (0) #endif /*------------------------ assert --------------------------------------- * Hey, careful! * Using assertions is great *but* be aware of traps & pitfalls: * http://blog.regehr.org/archives/1096 * * The closest equivalent perhaps, to assert() in the kernel are the BUG() * or BUG_ON() and WARN() or WARN_ON() macros. Using BUG*() is _only_ for those * cases where recovery is impossible. WARN*() is usally considered a better * option. Pl see for details. * * Here, we just trivially emit a noisy [trace_]printk() to "warn" the dev/user. */ #ifdef __KERNEL__ #define assert(expr) do { \ if (!(expr)) { \ pr_warn("********** Assertion [%s] failed! : %s:%s:%d **********\n", \ #expr, __FILE__, __func__, __LINE__); \ } \ } while (0) #endif /*------------------------ DELAY_LOOP --------------------------------*/ static inline void beep(int what) { #ifdef __KERNEL__ pr_info("%c", (char)what); #else #include char buf = (char)what; (void)write(STDOUT_FILENO, &buf, 1); #endif } /* * DELAY_LOOP macro * (Mostly) mindlessly loop, then print a char (via our beep() routine, * to emulate 'work' :-) * @val : ASCII value to print * @loop_count : times to loop around */ #define DELAY_LOOP(val, loop_count) \ { \ int c = 0, m; \ unsigned int for_index, inner_index, x; \ \ for (for_index = 0; for_index < loop_count; for_index++) { \ beep((val)); \ c++; \ for (inner_index = 0; inner_index < HZ; inner_index++) { \ for (m = 0; m < 50; m++); \ x = inner_index / 2; \ } \ } \ /*printf("c=%d\n",c);*/ \ } /*------------------------------------------------------------------------*/ #ifdef __KERNEL__ void delay_sec(long); /*------------ delay_sec -------------------------------------------------- * Delays execution for @val seconds. * If @val is -1, we sleep forever! * MUST be called from process context. * (We deliberately do not inline this function; this way, we can see it's * entry within a kernel stack call trace). */ void delay_sec(long val) { asm(""); // force the compiler to not inline it! if (in_task()) { set_current_state(TASK_INTERRUPTIBLE); if (-1 == val) schedule_timeout(MAX_SCHEDULE_TIMEOUT); else schedule_timeout(val * HZ); } } #endif /* #ifdef __KERNEL__ */ #endif /* #ifndef __CONVENIENT_H__ */ ================================================ FILE: procmap_kernel/procmap.c ================================================ /* * procmap.c *************************************************************** * Brief Description: * This kernel module forms the kernel component of the 'procmap' project. * * The procmap project's intention is, given a process's PID, it will display * (in a CLI/console output format only for now) a complete 'memory map' of the * process VAS (virtual address space). * * Note:- BSD has a utility by the same name: procmap(1), this project isn't * the same, though (quite obviously) some aspects are similar. * * Works on both 32 and 64-bit systems of differing architectures (note: only * lightly tested on ARM and x86 32 and 64-bit systems). *************************************************************** * (c) Kaiwan N Billimoria, 2020 * (c) kaiwanTECH * License: MIT */ #define pr_fmt(fmt) "%s:%s(): " fmt, KBUILD_MODNAME, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include "convenient.h" MODULE_AUTHOR("Kaiwan N Billimoria"); MODULE_DESCRIPTION("procmap: an LKM, the kernel component of the procmap project"); MODULE_LICENSE("Dual MIT/GPL"); MODULE_VERSION("0.2"); // For portability between 32 and 64-bit platforms #if (BITS_PER_LONG == 32) #define FMTSPC "%08x" #define FMTSPC_DEC "%7d" #define TYPECST unsigned int #elif(BITS_PER_LONG == 64) #define FMTSPC "%016lx" #define FMTSPC_DEC "%9ld" #define TYPECST unsigned long #endif #define MAXLEN 2048 #define ELLPS "| [ . . . ] |\n" static struct dentry *gparent; DEFINE_MUTEX(mtx); #include /* * Try to use Red Hat’s version header if present. * Rocky/Alma sometimes don’t ship it, so we fall back. */ #ifdef CONFIG_RHEL_VERSION #include #endif /* * Unified helper to determine “effective RHEL release code”. * - On upstream kernels, returns 0 (unused). * - On RHEL/Rocky with rh_rhel_version.h, returns RHEL_RELEASE_CODE. * - Otherwise, tries to parse UTS_RELEASE string (e.g. "el8_10" → 810). */ static int using_rhel; #include static inline int effective_rhel_release_code(void) { #ifdef CONFIG_RHEL_VERSION return RHEL_RELEASE_CODE; #else /* Fallback: check for "elX_Y" in the release string */ if (strstr(UTS_RELEASE, "el8_10")) return 810; if (strstr(UTS_RELEASE, "el8_9")) return 809; if (strstr(UTS_RELEASE, "el8_8")) return 808; /* extend as needed for other releases */ return 0; /* unknown or not RHEL-ish */ #endif } /* * query_kernelseg_details * Display kernel segment details as applicable to the architecture we're * currently running upon. * Format (for most of the details): * start_kva,end_kva,, * * CAREFUL: An ABI: * We depend on the field (in the usermode scripts); * do NOT arbitrarily change it; if you Must, you'll need to update the * usermode scripts that depend on it. * * We try to order it by descending address (here, kva's) but this doesn't * always work out as ordering of regions differs by arch. * * An enhancement: this module is now part of our 'procmap' project. * Here, we display the kernel segment values in CSV format as follows: * start_kva,end_kva,, * f.e. on an x86_64 VM w/ 2047 MB RAM * 0xffff92dac0000000,0xffff92db3fff0000,rwx,lowmem region */ static void query_kernelseg_details(char *buf) { #define TMPMAX 256 char tmpbuf[TMPMAX]; unsigned long ram_size; // RHEL: Rocky/Alma/... if (using_rhel) ram_size = totalram_pages() * PAGE_SIZE; else { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) ram_size = totalram_pages() * PAGE_SIZE; #else // totalram_pages() undefined on the BeagleBone running an older 4.19 kernel.. #if defined(CONFIG_ARM) // TODO: test on ARM ram_size = totalram_pages * PAGE_SIZE; #endif #endif } #if defined(CONFIG_ARM64) pr_info("VA_BITS (CONFIG_ARM64_VA_BITS) = %d\n", VA_BITS); if (VA_BITS > 48 && PAGE_SIZE == (64*1024)) // typically 52 bits and 64K pages pr_warn("*** >= ARMv8.2 with LPA? (YMMV, not supported here) ***\n"); #endif #ifdef ARM /* On ARM, the definition of VECTORS_BASE turns up only in kernels >= 4.11 */ #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 11, 0) memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, "%08x,%08lx,r--,vector table\n", //FMTSPC "," FMTSPC ",r--,vector table\n", (TYPECST) VECTORS_BASE, (TYPECST) VECTORS_BASE + PAGE_SIZE); strlcat(buf, tmpbuf, MAXLEN); #endif #endif /* kernel fixmap region */ memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, FMTSPC "," FMTSPC ",r--,fixmap region\n", #ifdef CONFIG_ARM /* On ARM, the FIXADDR_START macro's only defined from 5.11! * For earlier kernels, as a really silly and ugly workaround am simply * copying it in here... */ #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0) #define FIXADDR_START 0xffc00000UL #endif (TYPECST) FIXADDR_START, (TYPECST) FIXADDR_END #else (TYPECST) FIXADDR_START, (TYPECST) FIXADDR_START + FIXADDR_SIZE #endif ); strlcat(buf, tmpbuf, MAXLEN); /* kernel module region * For the modules region, it's high in the kernel segment on typical 64-bit * systems, but the other way around on many 32-bit systems (particularly * ARM-32); so we rearrange the order in which it's shown depending on the * arch, thus trying to maintain a 'by descending address' ordering. */ #if (BITS_PER_LONG == 64) memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, FMTSPC "," FMTSPC ",rwx,module region\n", (TYPECST) MODULES_VADDR, (TYPECST) MODULES_END); strlcat(buf, tmpbuf, MAXLEN); #endif #ifdef CONFIG_KASAN // KASAN region: Kernel Address SANitizer memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, FMTSPC "," FMTSPC ",rw-,KASAN shadow\n", (TYPECST) KASAN_SHADOW_START, (TYPECST) KASAN_SHADOW_END); strlcat(buf, tmpbuf, MAXLEN); #endif /* TODO - sparsemem model; vmemmap region */ /* vmalloc region */ memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, FMTSPC "," FMTSPC ",rw-,vmalloc region\n", (TYPECST) VMALLOC_START, (TYPECST) VMALLOC_END); strlcat(buf, tmpbuf, MAXLEN); /* lowmem region: spans from PAGE_OFFSET for size of platform RAM */ memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, FMTSPC "," FMTSPC ",rwx,lowmem region\n", (TYPECST)PAGE_OFFSET, (TYPECST)(PAGE_OFFSET + ram_size)); strlcat(buf, tmpbuf, MAXLEN); pr_debug("high_memory = 0x%016lx\n", high_memory); /* (possible) highmem region; may be present on some 32-bit systems */ #if defined(CONFIG_HIGHMEM) && (BITS_PER_LONG==32) memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, FMTSPC "," FMTSPC ",rwx,HIGHMEM region\n", (TYPECST) PKMAP_BASE, (TYPECST) (PKMAP_BASE) + (LAST_PKMAP * PAGE_SIZE)); strlcat(buf, tmpbuf, MAXLEN); #endif /* * Symbols for kernel: * text begin/end (_text/_etext) * init begin/end (__init_begin, __init_end) * data begin/end (_sdata, _edata) * bss begin/end (__bss_start, __bss_stop) * are only defined *within* the kernel (in-tree) and aren't available * for modules; thus we don't attempt to print them. */ #if (BITS_PER_LONG == 32) /* modules region: see the comment above reg this */ memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, FMTSPC "," FMTSPC ",rwx,module region:\n", (TYPECST)MODULES_VADDR, (TYPECST)MODULES_END); strlcat(buf, tmpbuf, MAXLEN); #endif #include /* Enhancement: also pass along other key kernel vars */ memset(tmpbuf, 0, TMPMAX); snprintf(tmpbuf, TMPMAX, "PAGE_SIZE," FMTSPC "\n" "TASK_SIZE," FMTSPC "\n", // "high_memory," FMTSPC "\n", (TYPECST) PAGE_SIZE, (TYPECST) TASK_SIZE); //, high_memory); strlcat(buf, tmpbuf, MAXLEN); } /* Our debugfs file 1's read callback function */ static ssize_t dbgfs_show_kernelseg(struct file *filp, char __user *ubuf, size_t count, loff_t *fpos) { char *kbuf; ssize_t ret = 0; if (mutex_lock_interruptible(&mtx)) return -ERESTARTSYS; kbuf = kzalloc(MAXLEN, GFP_KERNEL); if (unlikely(!kbuf)) { mutex_unlock(&mtx); return -ENOMEM; } query_kernelseg_details(kbuf); ret = simple_read_from_buffer(ubuf, MAXLEN, fpos, kbuf, strlen(kbuf)); //MSG("ret = %ld\n", ret); kfree(kbuf); mutex_unlock(&mtx); return ret; } static const struct file_operations dbgfs_fops = { .read = dbgfs_show_kernelseg, }; static int setup_debugfs_file(void) { struct dentry *file1; int stat = 0; if (!IS_ENABLED(CONFIG_DEBUG_FS)) { pr_warn("debugfs unsupported! Aborting ...\n"); return -EINVAL; } /* Create a dir under the debugfs mount point, whose name is the module name */ gparent = debugfs_create_dir(KBUILD_MODNAME, NULL); if (!gparent) { pr_info("debugfs_create_dir failed, aborting...\n"); stat = PTR_ERR(gparent); goto out_fail_1; } /* Create a generic debugfs file */ #define DBGFS_FILE1 "disp_kernelseg_details" file1 = debugfs_create_file(DBGFS_FILE1, 0444, gparent, (void *)NULL, &dbgfs_fops); if (!file1) { pr_info("debugfs_create_file failed, aborting...\n"); stat = PTR_ERR(file1); goto out_fail_2; } pr_debug("debugfs file 1 /%s/%s created\n", KBUILD_MODNAME, DBGFS_FILE1); return 0; /* success */ out_fail_2: debugfs_remove_recursive(gparent); out_fail_1: return stat; } static int __init procmap_init(void) { int ret = 0; pr_info("inserted\n"); if (effective_rhel_release_code() >= 808) { using_rhel = 1; pr_info("fyi, RHEL release = %d\n", effective_rhel_release_code()); } ret = setup_debugfs_file(); return ret; } static void __exit procmap_exit(void) { debugfs_remove_recursive(gparent); pr_info("removed\n"); } module_init(procmap_init); module_exit(procmap_exit); ================================================ FILE: test/runtests.sh ================================================ #!/bin/bash # Test cases PROCMAP=../procmap die() { echo >&2 "FATAL: $*" exit 1 } # runcmd # Parameters # $1 ... : params are the command to run runcmd() { [ $# -eq 0 ] && return echo "$@" eval "$@" } # PUT = Prg Under Test gen_and_run_put() { cat > /tmp/h.c << @EOF@ #include #include int main() { int x=42; printf("Hello, world! The ans is %d\n", x); pause(); } @EOF@ gcc /tmp/h.c -o /tmp/put -Wall pkill put /tmp/put >/dev/null & } runtest() { echo -n " ******************* " if [[ "$1" = "p" ]] ; then echo "Postive testcase $2 *******************" elif [[ "$1" = "n" ]] ; then echo "Negative testcase $2 *******************" fi echo [[ "$1" = "p" ]] && let PTC=PTC+1 [[ "$1" = "n" ]] && let NTC=NTC+1 shift ; shift runcmd "$*" } source ../color.sh || { echo "couldn't source ../color.sh"; exit 1 } Echo_tests() { echo "Echo*() tests:" DEBUG=1 ; decho "decho test: DEBUG == 1" # should show DEBUG=0 ; decho "decho test: DEBUG == 0" # shouldn't show iecho "iecho test" aecho "aecho test" wecho "wecho test" cecho "cecho test" techo "techo test" } #--- 'main' [[ ! -x ${PROCMAP} ]] && die "procmap script not located correctly? (value = ${PROCMAP})" Echo_tests echo "[Enter] to proceed, ^C to abort ..."; read gen_and_run_put PID=$(pgrep --newest put) [[ -z "${PID}" ]] && PID=1 # Positive Test Cases (PTCs) PTC=1 runtest p ${PTC} "${PROCMAP} --pid=${PID}" runtest p ${PTC} "${PROCMAP} -p ${PID}" pkill put # Negative Test Cases (NTCs) NTC=1 runtest n ${NTC} "${PROCMAP} --pid=-100" runtest n ${NTC} "${PROCMAP} -p -9" runtest n ${NTC} "${PROCMAP} -p abc0" runtest n ${NTC} "${PROCMAP} -p 1234567890" runtest p ${PTC} "${PROCMAP} -p 1" runtest p ${PTC} "${PROCMAP} -p 1 --only-user" # TODO : test case w/ v large user VAS (eg. python..) # and the 'heap' (still) shows up too high in the u VAS ! # test cases with all/any options passed # test cases with all/any 'config' file variations exit 0 ================================================ FILE: test/shellcheck_run ================================================ #!/bin/bash # shellcheck_run # Part of the procmap project. echo "*** Test - shellcheck *** " shellcheck -e SC2155,SC2063,SC2166,SC1090 -S warning ../procmap ../install_procmap ../*.sh ../config # https://www.shellcheck.net/wiki/ # -e : exclude warning # SC2155 : Declare and assign separately to avoid masking return values. # SC2063 : (warning): Prefer [ p ] || [ q ] as [ p -o q ] is not well defined. # SC2166 : (warning): Prefer [ p ] && [ q ] as [ p -a q ] is not well defined. # SC1090 (warning): ShellCheck can't follow non-constant source. Use a directive to specify location # -S SEVERITY --severity=SEVERITY Minimum severity of errors to consider (error, warning, info, style)