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