diff --git a/kdump-lib.sh b/kdump-lib.sh index 1c68e6a..88fea64 100755 --- a/kdump-lib.sh +++ b/kdump-lib.sh @@ -977,3 +977,74 @@ get_all_kdump_crypt_dev() [[ -n "$_crypt" ]] && echo $_crypt done } + +check_vmlinux() +{ + # Use readelf to check if it's a valid ELF + readelf -h $1 &>/dev/null || return 1 +} + +get_vmlinux_size() +{ + local size=0 + + while read _type _offset _virtaddr _physaddr _fsize _msize _flg _aln; do + size=$(( $size + $_msize )) + done <<< $(readelf -l -W $1 | grep "^ LOAD" 2>/dev/stderr) + + echo $size +} + +try_decompress() +{ + # The obscure use of the "tr" filter is to work around older versions of + # "grep" that report the byte offset of the line instead of the pattern. + + # Try to find the header ($1) and decompress from here + for pos in `tr "$1\n$2" "\n$2=" < "$4" | grep -abo "^$2"` + do + if ! type -P $3 > /dev/null; then + ddebug "Signiature detected but '$3' is missing, skip this decompressor" + break + fi + + pos=${pos%%:*} + tail -c+$pos "$img" | $3 > $5 2> /dev/null + if check_vmlinux $5; then + ddebug "Kernel is extracted with '$3'" + return 0 + fi + done + + return 1 +} + +# Borrowed from linux/scripts/extract-vmlinux +get_kernel_size() +{ + # Prepare temp files: + local img=$1 tmp=$(mktemp /tmp/vmlinux-XXX) + trap "rm -f $tmp" 0 + + # Try to check if it's a vmlinux already + check_vmlinux $img && get_vmlinux_size $img && return 0 + + # That didn't work, so retry after decompression. + try_decompress '\037\213\010' xy gunzip $img $tmp || \ + try_decompress '\3757zXZ\000' abcde unxz $img $tmp || \ + try_decompress 'BZh' xy bunzip2 $img $tmp || \ + try_decompress '\135\0\0\0' xxx unlzma $img $tmp || \ + try_decompress '\211\114\132' xy 'lzop -d' $img $tmp || \ + try_decompress '\002!L\030' xxx 'lz4 -d' $img $tmp || \ + try_decompress '(\265/\375' xxx unzstd $img $tmp + + # Finally check for uncompressed images or objects: + [[ $? -eq 0 ]] && get_vmlinux_size $tmp && return 0 + + # Fallback to use iomem + local _size=0 + for _seg in $(cat /proc/iomem | grep -E "Kernel (code|rodata|data|bss)" | cut -d ":" -f 1); do + _size=$(( $_size + 0x${_seg#*-} - 0x${_seg%-*} )) + done + echo $_size +} diff --git a/kdumpctl b/kdumpctl index 24f5cf7..37d8c51 100755 --- a/kdumpctl +++ b/kdumpctl @@ -1214,6 +1214,97 @@ rebuild() { return $? } +do_estimate() { + local kdump_mods + local -A large_mods + local baseline + local kernel_size mod_size initrd_size baseline_size runtime_size reserved_size estimated_size recommanded_size + local size_mb=$(( 1024 * 1024 )) + + setup_initrd + if [ ! -f "$TARGET_INITRD" ]; then + derror "kdumpctl estimate: kdump initramfs is not built yet." + exit 1 + fi + + kdump_mods="$(lsinitrd "$TARGET_INITRD" -f /usr/lib/dracut/hostonly-kernel-modules.txt | tr '\n' ' ')" + baseline=$(kdump_get_arch_recommend_size) + if [[ "${baseline: -1}" == "M" ]]; then + baseline=${baseline%M} + elif [[ "${baseline: -1}" == "G" ]]; then + baseline=$(( ${baseline%G} * 1024 )) + elif [[ "${baseline: -1}" == "T" ]]; then + baseline=$(( ${baseline%Y} * 1048576 )) + fi + + # The default value when using crashkernel=auto + baseline_size=$((baseline * size_mb)) + # Current reserved crashkernel size + reserved_size=$(cat /sys/kernel/kexec_crash_size) + # A pre-estimated value for userspace usage and kernel + # runtime allocation, 64M should good for most cases + runtime_size=$((64 * size_mb)) + # Kernel image size + kernel_size=$(get_kernel_size "$KDUMP_KERNEL") + # Kdump initramfs size + initrd_size=$(du -b "$TARGET_INITRD" | awk '{print $1}') + # Kernel modules static size after loaded + mod_size=0 + while read -r _name _size _; do + if [[ ! " $kdump_mods " == *" $_name "* ]]; then + continue + fi + mod_size=$((mod_size + _size)) + + # Mark module with static size larger than 2M as large module + if [[ $((_size / size_mb)) -ge 1 ]]; then + large_mods[$_name]=$_size + fi + done <<< "$(< /proc/modules)" + + # Extra memory usage required for LUKS2 decryption + crypt_size=0 + for _dev in $(get_all_kdump_crypt_dev); do + _crypt_info=$(cryptsetup luksDump "/dev/block/$_dev") + [[ $(echo "$_crypt_info" | sed -n "s/^Version:\s*\(.*\)/\1/p" ) == "2" ]] || continue + for _mem in $(echo "$_crypt_info" | sed -n "s/\sMemory:\s*\(.*\)/\1/p" | sort -n ); do + crypt_size=$((crypt_size + _mem * 1024)) + break + done + done + [[ $crypt_size -ne 0 ]] && echo -e "Encrypted kdump target requires extra memory, assuming using the keyslot with minimun memory requirement\n" + + estimated_size=$((kernel_size + mod_size + initrd_size + runtime_size + crypt_size)) + if [[ $baseline_size -gt $estimated_size ]]; then + recommanded_size=$baseline_size + else + recommanded_size=$estimated_size + fi + + echo "Reserved crashkernel: $((reserved_size / size_mb))M" + echo "Recommanded crashkernel: $((recommanded_size / size_mb))M" + echo + echo "Kernel image size: $((kernel_size / size_mb))M" + echo "Kernel modules size: $((mod_size / size_mb))M" + echo "Initramfs size: $((initrd_size / size_mb))M" + echo "Runtime reservation: $((runtime_size / size_mb))M" + [[ $crypt_size -ne 0 ]] && \ + echo "LUKS required size: $((crypt_size / size_mb))M" + echo -n "Large modules:" + if [[ "${#large_mods[@]}" -eq 0 ]]; then + echo " " + else + echo "" + for _mod in "${!large_mods[@]}"; do + echo " $_mod: ${large_mods[$_mod]}" + done + fi + + if [[ $reserved_size -lt $recommanded_size ]]; then + echo "WARNING: Current crashkernel size is lower than recommanded size $((recommanded_size / size_mb))M." + fi +} + if [ ! -f "$KDUMP_CONFIG_FILE" ]; then derror "Error: No kdump config file found!" exit 1 @@ -1269,8 +1360,11 @@ main () showmem) show_reserved_mem ;; + estimate) + do_estimate + ;; *) - dinfo $"Usage: $0 {start|stop|status|restart|reload|rebuild|propagate|showmem}" + dinfo $"Usage: $0 {estimate|start|stop|status|restart|reload|rebuild|propagate|showmem}" exit 1 esac } diff --git a/kdumpctl.8 b/kdumpctl.8 index ae97af7..a32a972 100644 --- a/kdumpctl.8 +++ b/kdumpctl.8 @@ -44,6 +44,11 @@ impossible to use password authentication during kdump. .TP .I showmem Prints the size of reserved memory for crash kernel in megabytes. +.TP +.I estimate +Estimate a suitable crashkernel value for current machine. This is a +best-effort estimate. It will print a recommanded crashkernel value +based on current kdump setup, and list some details of memory usage. .SH "SEE ALSO" .BR kdump.conf (5),