diff options
| author | Niklas Cassel <cassel@kernel.org> | 2026-09-18 16:06:42 +0200 |
|---|---|---|
| committer | Jens Axboe <axboe@kernel.dk> | 2026-09-29 12:54:47 -0600 |
| commit | 684b413b5483f57c890c171b9400076a0143b918 (patch) | |
| tree | f167daddad3dce9af44773541373eafbccc9ac61 /scripts/decode_stacktrace.sh | |
| download | linux-stable-684b413b5483f57c890c171b9400076a0143b918.tar.gz linux-stable-684b413b5483f57c890c171b9400076a0143b918.zip | |
virtio_blk: set the zone write granularitygrafted
virtblk_read_zoned_limits() reads the write granularity that the device
reports in virtio_blk_zoned_characteristics and assigns it to the
physical block size and to io_min, but never to the limit that is named
after it. queue_limits.zone_write_granularity is left at zero, so
blk_validate_zoned_limits() raises it to the logical block size:
if (lim->zone_write_granularity < lim->logical_block_size)
lim->zone_write_granularity = lim->logical_block_size;
A device that reports a granularity coarser than its logical block size,
which is what the field exists to express, therefore has it silently
reduced. A 512e host managed disk passed through to a guest reports a
logical block size of 512 and a write granularity of 4096, and the guest
ends up with a zone write granularity of 512.
bio_split_alignment() returns lim->zone_write_granularity if it is non-zero
and bio_split_io_at() may split a bio with as per bio_split_alignment().
This can real to the write getting rejected by the host drive, as the write
is not aligned to the physical block size.
zonefs also takes its block size from bdev_zone_write_granularity(), so it
would incorrectly use 512 on a disk that requires 4096.
sd_zbc_read_zones() sets the limit from the physical block size for the
same reason. NVMe ZNS and null_blk leave it unset, but the fallback
gives the right answer for them, as their write granularity is the
logical block size. virtio carries a separate value that may exceed it.
Set the zone write granularity from the value that the device reports.
Fixes: 95bfec41bd3d ("virtio-blk: add support for zoned block devices")
Signed-off-by: Niklas Cassel <cassel@kernel.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Link: https://patch.msgid.link/20260918140641.2031075-2-cassel@kernel.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Diffstat (limited to 'scripts/decode_stacktrace.sh')
| -rwxr-xr-x | scripts/decode_stacktrace.sh | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/scripts/decode_stacktrace.sh b/scripts/decode_stacktrace.sh new file mode 100755 index 000000000..39d60d477 --- /dev/null +++ b/scripts/decode_stacktrace.sh @@ -0,0 +1,368 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 +# (c) 2014, Sasha Levin <sasha.levin@oracle.com> +#set -x + +usage() { + echo "Usage:" + echo " $0 [-R] -r <release>" + echo " $0 [-R] [<vmlinux> [<base_path>|auto [<modules_path>]]]" + echo " $0 -h" + echo "Options:" + echo " -R: decode return address instead of caller address." +} + +# Try to find a Rust demangler +if type llvm-cxxfilt >/dev/null 2>&1 ; then + cppfilt=llvm-cxxfilt +elif type c++filt >/dev/null 2>&1 ; then + cppfilt=c++filt + cppfilt_opts=-i +fi + +UTIL_SUFFIX= +if [[ -z ${LLVM:-} ]]; then + UTIL_PREFIX=${CROSS_COMPILE:-} +else + UTIL_PREFIX=llvm- + if [[ ${LLVM} == */ ]]; then + UTIL_PREFIX=${LLVM}${UTIL_PREFIX} + elif [[ ${LLVM} == -* ]]; then + UTIL_SUFFIX=${LLVM} + fi +fi + +READELF=${UTIL_PREFIX}readelf${UTIL_SUFFIX} +ADDR2LINE=${UTIL_PREFIX}addr2line${UTIL_SUFFIX} +NM=${UTIL_PREFIX}nm${UTIL_SUFFIX} +decode_retaddr=false + +if [[ $1 == "-h" ]] ; then + usage + exit 0 +elif [[ $1 == "-R" ]] ; then + decode_retaddr=true + shift 1 +fi + +if [[ $1 == "-r" ]] ; then + vmlinux="" + basepath="auto" + modpath="" + release=$2 + + for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do + if [ -e "$fn" ] ; then + vmlinux=$fn + break + fi + done + + if [[ $vmlinux == "" ]] ; then + echo "ERROR! vmlinux image for release $release is not found" >&2 + usage + exit 2 + fi +else + vmlinux=$1 + basepath=${2-auto} + modpath=$3 + release="" + debuginfod= + + # Can we use debuginfod-find? + if type debuginfod-find >/dev/null 2>&1 ; then + debuginfod=${1-only} + fi + + if [[ $vmlinux == "" && -z $debuginfod ]] ; then + echo "ERROR! vmlinux image must be specified" >&2 + usage + exit 1 + fi +fi + +declare aarray_support=true +declare -A cache 2>/dev/null +if [[ $? != 0 ]]; then + aarray_support=false +else + declare -A modcache +fi + +find_module() { + if [[ -n $debuginfod ]] ; then + if [[ -n $modbuildid ]] ; then + debuginfod-find debuginfo $modbuildid && return + fi + + # Only using debuginfod so don't try to find vmlinux module path + if [[ $debuginfod == "only" ]] ; then + return + fi + fi + + if [ -z $release ] ; then + release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p') + fi + if [ -n "${release}" ] ; then + release_dirs="/usr/lib/debug/lib/modules/$release /lib/modules/$release" + fi + + found_without_debug_info=false + for dir in "$modpath" "$(dirname "$vmlinux")" ${release_dirs}; do + if [ -n "${dir}" ] && [ -e "${dir}" ]; then + for fn in $(find "$dir" -name "${module//_/[-_]}.ko*") ; do + if ${READELF} -WS "$fn" | grep -qwF .debug_line ; then + echo $fn + return + fi + found_without_debug_info=true + done + fi + done + + if [[ ${found_without_debug_info} == true ]]; then + echo "WARNING! No debugging info in module ${module}, rebuild with DEBUG_KERNEL and DEBUG_INFO" >&2 + else + echo "WARNING! Cannot find .ko for module ${module}, please pass a valid module path" >&2 + fi + + return 1 +} + +parse_symbol() { + # The structure of symbol at this point is: + # ([name]+[offset]/[total length]) + # + # For example: + # do_basic_setup+0x9c/0xbf + + if [[ $module == "" ]] ; then + local objfile=$vmlinux + elif [[ $aarray_support == true && "${modcache[$module]+isset}" == "isset" ]]; then + local objfile=${modcache[$module]} + else + local objfile=$(find_module) + if [[ $objfile == "" ]] ; then + return + fi + if [[ $aarray_support == true ]]; then + modcache[$module]=$objfile + fi + fi + + # Remove the englobing parenthesis + symbol=${symbol#\(} + symbol=${symbol%\)} + + # Strip segment + local segment + if [[ $symbol == *:* ]] ; then + segment=${symbol%%:*}: + symbol=${symbol#*:} + fi + + # Strip the symbol name so that we could look it up + local name=${symbol%+*} + + # Use 'nm vmlinux' to figure out the base address of said symbol. + # It's actually faster to call it every time than to load it + # all into bash. + if [[ $aarray_support == true && "${cache[$module,$name]+isset}" == "isset" ]]; then + local base_addr=${cache[$module,$name]} + else + local base_addr=$(${NM} "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') + if [[ $base_addr == "" ]] ; then + # address not found + return + fi + if [[ $aarray_support == true ]]; then + cache[$module,$name]="$base_addr" + fi + fi + # Let's start doing the math to get the exact address into the + # symbol. First, strip out the symbol total length. + local expr=${symbol%/*} + # Also parse the offset from symbol. + local offset=${expr#*+} + offset=$((offset)) + + # Now, replace the symbol name with the base address we found + # before. + expr=${expr/$name/0x$base_addr} + + # Evaluate it to find the actual address + # The stack trace shows the return address, which is the next + # instruction after the actual call, so as long as it's in the same + # symbol, subtract one from that to point the call instruction. + if [[ $decode_retaddr == false && $offset != 0 ]]; then + expr=$((expr-1)) + else + expr=$((expr)) + fi + local address=$(printf "%x\n" "$expr") + + # Pass it to addr2line to get filename and line number + # Could get more than one result + if [[ $aarray_support == true && "${cache[$module,$address]+isset}" == "isset" ]]; then + local code=${cache[$module,$address]} + else + local code=$(${ADDR2LINE} -i -e "$objfile" "$address" 2>/dev/null) + if [[ $aarray_support == true ]]; then + cache[$module,$address]=$code + fi + fi + + # addr2line doesn't return a proper error code if it fails, so + # we detect it using the value it prints so that we could preserve + # the offset/size into the function and bail out + if [[ $code == "??:0" ]]; then + return + fi + + # Strip out the base of the path on each line + code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") + + # In the case of inlines, move everything to same line + code=${code//$'\n'/' '} + + # Demangle if the name looks like a Rust symbol and if + # we got a Rust demangler + if [[ $name =~ ^_R && $cppfilt != "" ]] ; then + name=$("$cppfilt" "$cppfilt_opts" "$name") + fi + + # Replace old address with pretty line numbers + symbol="$segment$name ($code)" +} + +debuginfod_get_vmlinux() { + local vmlinux_buildid=${1##* } + + if [[ $vmlinux != "" ]]; then + return + fi + + if [[ $vmlinux_buildid =~ ^[0-9a-f]+ ]]; then + vmlinux=$(debuginfod-find debuginfo $vmlinux_buildid) + if [[ $? -ne 0 ]] ; then + echo "ERROR! vmlinux image not found via debuginfod-find" >&2 + usage + exit 2 + fi + return + fi + echo "ERROR! Build ID for vmlinux not found. Try passing -r or specifying vmlinux" >&2 + usage + exit 2 +} + +decode_code() { + local scripts=`dirname "${BASH_SOURCE[0]}"` + local lim="Code: " + + echo -n "${1%%${lim}*}" + echo "${lim}${1##*${lim}}" | $scripts/decodecode +} + +handle_line() { + if [[ $basepath == "auto" && $vmlinux != "" ]] ; then + module="" + symbol="kernel_init+0x0/0x0" + parse_symbol + basepath=${symbol#kernel_init (} + basepath=${basepath%/init/main.c:*)} + fi + + local words spaces + + # Tokenize: words and spaces to preserve the alignment + read -ra words <<<"$1" + IFS='#' read -ra spaces <<<"$(shopt -s extglob; echo "${1//+([^[:space:]])/#}")" + + # Remove hex numbers. Do it ourselves until it happens in the + # kernel + + # We need to know the index of the last element before we + # remove elements because arrays are sparse + local last=$(( ${#words[@]} - 1 )) + + for i in "${!words[@]}"; do + # Remove the address + if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then + unset words[$i] spaces[$i] + fi + done + + # Extract info after the symbol if present. E.g.: + # func_name+0x54/0x80 (P) + # ^^^ + # The regex assumes only uppercase letters will be used. To be + # extended if needed. + local info_str="" + if [[ ${words[$last]} =~ \([A-Z]*\) ]]; then + info_str=${words[$last]} + unset words[$last] spaces[$last] + last=$(( $last - 1 )) + fi + + # Join module name with its build id if present, as these were + # split during tokenization (e.g. "[module" and "modbuildid]"). + if [[ ${words[$last]} =~ ^[0-9a-f]+\] ]]; then + words[$last-1]="${words[$last-1]} ${words[$last]}" + unset words[$last] spaces[$last] + last=$(( $last - 1 )) + fi + + if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then + module=${words[$last]} + # some traces format is "(%pS)", which like "(foo+0x0/0x1 [bar])" + # so $module may like "[bar])". Strip the right parenthesis firstly + module=${module%\)} + module=${module#\[} + module=${module%\]} + modbuildid=${module#* } + module=${module% *} + if [[ $modbuildid == $module ]]; then + modbuildid= + fi + symbol=${words[$last-1]} + unset words[$last-1] spaces[$last-1] + else + # The symbol is the last element, process it + symbol=${words[$last]} + module= + modbuildid= + fi + + unset words[$last] + parse_symbol # modifies $symbol + + # Add up the line number to the symbol + for i in "${!words[@]}"; do + echo -n "${spaces[i]}${words[i]}" + done + echo "${spaces[$last]}${symbol}${module:+ ${module}}${info_str:+ ${info_str}}" +} + +while read line; do + # Strip unexpected carriage return at end of line + line=${line%$'\r'} + + # Let's see if we have an address in the line + if [[ $line =~ \[\<([^]]+)\>\] ]] || + [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then + # Translate address to line numbers + handle_line "$line" + # Is it a code line? + elif [[ $line == *Code:* ]]; then + decode_code "$line" + # Is it a version line? + elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then + debuginfod_get_vmlinux "$line" + else + # Nothing special in this line, show it as is + echo "$line" + fi +done |
