Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- # Written 2020 by Axel Norstedt
- ################################################################################
- # Memtest Parser (early test version) #
- ################################################################################
- # Use in conjunction with a memtest enabled kernel and the memtest=N kernel #
- # cmdline. Typical usage should be memtest=17 since it's a very trivial test #
- # otherwise and would only catch flat out dead memory cells. #
- # Depending on your distribution dmesg might require root access to access the #
- # kernel boot log. In such case you can either invoke the script with sudo or #
- # with the path to a user readable kernel log file. If you go the file route, #
- # be careful not to include more than one memtest run as that will lead to #
- # undefined behavior. #
- ################################################################################
- # Exit status map: #
- # 0 Test completed, no errors found #
- # 1 Test output could not be found #
- # 2 Test completed but encountered errors #
- # 3 Specified file containing kernel log not found #
- # 4 Dependency error
- ################################################################################
- # Check dependencies
- if [[ ! -x $(command -v dmesg) && -z $1 ]]; then
- echo "Fatal: This script requires dmesg unless a log file is specified"
- exit 4
- fi
- if [[ ! -x $(command -v sed) ]]; then
- echo "Fatal: This script requires sed"
- exit 4
- fi
- #TODO
- # Investigate posibility of a graphical representation (SVG or bitmap?)
- # ASCII-art?
- # Neat trick found on "https://stackoverflow.com/questions/263890/
- # how-do-i-find-the-width-height-of-a-terminal-window" to get the terminal size
- # without having to use external tools like tput
- # The terminal size will be available through $LINES and $COLUMNS after this
- # shopt -s checkwinsize; (:); echo $LINES $COLUMNS
- # Get the kernel log
- if [[ -z $1 ]]; then
- BOOTLOG="$(dmesg)"
- elif [[ -f $1 ]]; then
- BOOTLOG="$(cat $1)"
- else
- echo "Fatal: File \"$1\" not found"
- exit 3
- fi
- ################################################################################
- # What this inline sed script does #
- ################################################################################
- # Match from the line containing "early_memtest" to the next line not #
- # containing "] ", at which point the test is over (end of indented block) #
- # Remove leading timestamp string "[ 0.000000] " (or similar) #
- # Print the pattern space (the current line after processing) #
- ################################################################################
- MT_BLOCK="$(sed -n -e '/early_memtest/,/\] [^ ]/ {
- s/\[ *[0-9]*\.[0-9]*\] // p
- }' <<< "$BOOTLOG")"
- if [[ -z $MT_BLOCK ]]; then
- echo "No memtest output found in kernel log, wrong boot option?"
- exit 1
- fi
- #echo "$MT_BLOCK"
- ################################################################################
- # Extract number of test patterns used for testing #
- ################################################################################
- # The line looks like this: #
- # "early_memtest: # of tests: 17" #
- # We want the "17" #
- ################################################################################
- MT_TESTS="$(sed '1 s/.*early_memtest: # of tests: \([0-9]*\).*/\1/ ; q' \
- <<< "$MT_BLOCK")"
- #echo "$MT_TESTS"
- ################################################################################
- # Extract test patterns used for testing #
- ################################################################################
- # Each line looks like this: #
- # " 0x0000000000200000 - 0x0000000002080000 pattern 4c494e5558726c7a" #
- # We want the "4c494e5558726c7a" #
- ################################################################################
- MT_PATTERNS="$(sed -n 's/..* pattern \([0-9a-f]*\).*/\1/p' \
- <<< "$MT_BLOCK" | uniq)"
- #echo "$MT_PATTERNS"
- ################################################################################
- # Extract test range #
- ################################################################################
- # Each line looks like this: #
- # " 0x0000000000200000 - 0x0000000002080000 pattern 4c494e5558726c7a" #
- # Then a few lines further down: #
- # " 0x000000003ffff000 - 0x0000000040000000 pattern 4c494e5558726c7a" #
- # We want the "0x0000000000200000" and the "0x0000000040000000" #
- ################################################################################
- # Extract the first pattern from the list
- MT_PATTERN="$(sed q <<< "$MT_PATTERNS")"
- # Expression to match the start
- # (first hexadecimal on a line containing the word "pattern")
- SED_EXP_S='.*0x\([0-9a-f]*\) ..* pattern ..*'
- # Expression to match the end
- # (last hexadecimal before the word "pattern", if it exists)
- SED_EXP_E='..* 0x\([0-9a-f]*\) pattern ..*'
- # Initialize variables
- MT_START="0"
- MT_END="0"
- MT_CHUNKS=""
- # Loop through the tests and extract both start and end adresses
- while read -r MT_TEST
- do
- # Basic sanity check to make sure the line looks like we expecit it to
- if [[ "$MT_TEST" == *" pattern "* ]]; then
- LOOP_S="$(sed -n 's/'"$SED_EXP_S"'/\1/p' <<< "$MT_TEST")"
- LOOP_E="$(sed -n 's/'"$SED_EXP_E"'/\1/p' <<< "$MT_TEST")"
- # Check if this is the lowest adress so far
- if [[ "$LOOP_S" < "$MT_START" || "$MT_START" == "0" ]]; then
- MT_START="$LOOP_S"
- fi
- # Check if this is the highest adress so far
- if [[ "$LOOP_E" > "$MT_END" || "$MT_END" == "0" ]]; then
- MT_END="$LOOP_E"
- fi
- # Finally, add the memory chunk to the list for later use
- MT_CHUNKS="$MT_CHUNKS"$'\n'"$LOOP_S $LOOP_E"
- fi
- # Only read through one pattern, all tests should encompass the same range
- done <<< $(sed -n "/$MT_PATTERN/p" <<< "$MT_BLOCK")
- # How many bytes of memory the test pattern was written to (HEX to DEC)
- MT_SIZE="$((16#$MT_END - 16#$MT_START))"
- #echo "$MT_START"
- #echo "$MT_END"
- #echo "$MT_SIZE"
- ################################################################################
- # Extract any error mesages #
- ################################################################################
- # Each line looks like this: #
- # " 4c494e5558726c7a bad mem addr 0x0000000000200000 - 0x0000000002080000 #
- # We want the "0x0000000000200000" and the "0x0000000002080000" #
- ################################################################################
- MT_ERRORS=$(sed -n 's/.*bad mem addr 0x\([0-9a-f]*\).*0x\([0-9a-f]*\)/\1 \2/p' \
- <<< "$MT_BLOCK")
- #echo "$MT_ERRORS"
- # Initialize variables
- MT_ASCIIMAP=""
- while read -r MT_CHUNK
- do
- # Reset for each chunk, for interoperability reasons this is a string
- MT_CHUNK_BAD="0"
- # First line of MT_CHUNK is always empty
- if [[ ! -z "$MT_CHUNK" ]]; then
- # Loop through each error for further investigation
- while read -r MT_ERROR
- do
- # Check if the error location inside the chunk
- if [[ "16#${MT_ERROR%% *}" -ge "16#${MT_CHUNK%% *}" && \
- "16#${MT_ERROR##* }" -le "16#${MT_CHUNK##* }" ]]; then
- MT_CHUNK_BAD="1"
- fi
- done <<< "$MT_ERRORS"
- # This is where we update the error map
- MT_ASCIIMAP="$MT_ASCIIMAP"$'\n'"0x${MT_CHUNK%% *} - 0x${MT_CHUNK##* } "
- if [[ "$MT_CHUNK_BAD" == "1" ]]; then
- MT_ASCIIMAP="$MT_ASCIIMAP BAD"
- else
- MT_ASCIIMAP="$MT_ASCIIMAP GOOD"
- fi
- fi
- done <<< "$MT_CHUNKS"
- ################################################################################
- # Print out the collected statistics #
- ################################################################################
- echo "$MT_TESTS tests completed with these patterns:"
- echo ""
- echo "$MT_PATTERNS"
- echo ""
- echo "Tested memory region 0x$MT_START to 0x$MT_END" \
- "($(($MT_SIZE / 1048576))MB)"
- # No empty echo here because the ASCII map already contains a leading newline
- echo "$MT_ASCIIMAP"
- echo ""
- # Print conclusion and exit
- if [[ ! -z "$MT_ERRORS" ]]; then
- echo "Error(s) found"
- exit 2
- else
- echo "No errors found"
- exit 0
- fi
Add Comment
Please, Sign In to add comment