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