Add 'run_gdb.sh' and a GDB script for aarch64
Debugging the EL2 portion of PKVM is hard because ELF addresses
do not match hyp VA. Add a GDB script with helper commands that
make it easier to compute hyp VAs of ELF symbols at runtime.
The script boots the kernel up to a point when memory layout has
been determined and reads the relevant constants from kernel memory.
Vmlinux ELF is loaded twice: once under kernel VA and once under
hyp VA. This way debug info is available when stepping through
hyp code as well, enabling showing C source and dumping the stack.
However, bringing in C symbols means that symbols are displayed
without the '__kvm_nvhe_' ELF prefix.
Add 'run_gdb.sh' wrapper which inserts the ELF path into the GDB
script and invokes GDB with it.
Change-Id: Icd4f648c22285ca0e7a65cba8630b19d2041c2fc
diff --git a/aarch64/aarch64.gdb b/aarch64/aarch64.gdb
new file mode 100644
index 0000000..5ac4d9d
--- /dev/null
+++ b/aarch64/aarch64.gdb
@@ -0,0 +1,117 @@
+# Copyright 2020 The Android KVM Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+##
+## This is a GDB script with commands to easier debug KVM on arm64.
+## Do not use this script directly. The calling script first replaces
+## variables in the form "##VAR##" with real values.
+##
+
+set architecture aarch64
+
+define virt_to_phys
+ set $__addr = $arg0
+ if ($__addr & $kern_va_lm_bit) == 0
+ p/x ($__addr + $physvirt_offset)
+ else
+ p/x ($__addr - $kimage_voffset)
+ end
+end
+document virt_to_phys
+ Convert kernel VA to IPA and print the result.
+ Usage: virt_to_phy <kernel_va>
+end
+
+define phys_to_virt
+ set $__addr = $arg0
+ p/x ($__addr - $physvirt_offset)
+end
+document phys_to_virt
+ Convert kernel IPA to linear-map VA and print the result.
+ Usage: phys_to_virt <kernel_ipa>
+end
+
+define lm_alias
+ virt_to_phys $arg0
+ phys_to_virt $
+end
+document lm_alias
+ Convert kernel VA to linear-map VA and print the result.
+ Usage: lm_alias <kernel_va>
+end
+
+define kern_hyp_va
+ set $__addr = $arg0
+ p/x (($__addr & $hyp_va_mask) | $hyp_va_tag)
+end
+document kern_hyp_va
+ Convert kernel VA to hyp VA and print the result.
+ Usage: kern_hyp_va <kernel_va>
+end
+
+define sym_hyp_va
+ set $__addr = (unsigned long)&$arg0
+ lm_alias $__addr
+ kern_hyp_va $
+end
+document sym_hyp_va
+ Get hyp VA of an ELF symbol and print the result.
+ Usage: sym_hyp_va <sym>
+end
+
+define break_nvhe
+ sym_hyp_va __kvm_nvhe_$arg0
+ b *$
+end
+define bn
+ break_nvhe $arg0
+end
+document break_nvhe
+ Set breakpoint at hyp VA of a given nVHE symbol name.
+ Symbol name is provided without the "__kvm_nvhe_" prefix.
+ Usage: break_nvhe <sym>
+ bn <sym>
+end
+
+file "##ELF_PATH##"
+
+# Break after memory layout constants have been computed.
+break apply_alternatives_all
+commands
+ # Cache kernel constants so they are available regardless
+ # of CurrentEL.
+ set $hyp_va_tag = (tag_val << tag_lsb)
+ set $hyp_va_mask = va_mask
+ set $kern_va_lm_bit = (1ul << (vabits_actual - 1))
+ set $physvirt_offset = physvirt_offset
+ set $kimage_voffset = kimage_voffset
+
+ # Load the ELF again under hyp VA.
+ # We use '_stext' here because 'add-symbol-file' expects address
+ # of the '.text' section. '_text' corresponds to '.head.text'.
+ sym_hyp_va _stext
+ add-symbol-file "##ELF_PATH##" $
+
+ # Unset this breakpoint.
+ # Note: this deletes *all* breakpoints.
+ delete breakpoint
+
+ echo Protected KVM debug mode initialized\n
+end
+
+# Connect to QEMU emulating the kernel.
+target remote :1234
+
+# Boot the kernel up to the breakpoint above.
+continue
diff --git a/aarch64/run_gdb.sh b/aarch64/run_gdb.sh
new file mode 100755
index 0000000..2555d13
--- /dev/null
+++ b/aarch64/run_gdb.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+
+# Copyright 2020 The Android KVM Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+source "$(dirname "${BASH_SOURCE[0]}")/../common.inc"
+
+default_var GDB "${TOOLCHAINS_GDB_BIN}"
+default_var KERNEL_ELF "${LINUX_OUT_ELF}"
+default_var VERBOSE 0
+
+function usage() {
+ cat <<EOF
+
+Usage: $0 [-h] [-v] [-e GDB] [-k KERNEL_ELF]
+
+ -h output this help text
+ -v print invoked command
+ -e GDB binary
+ -k kernel ELF file (vmlinux for Linux)
+EOF
+}
+
+while getopts ":e:k:vh" OPT; do
+ case "${OPT}" in
+ e) GDB="${OPTARG}" ;;
+ k) KERNEL_ELF="${OPTARG}" ;;
+ v) VERBOSE=1 ;;
+ h)
+ usage
+ exit 0
+ ;;
+ \?)
+ echo "Invalid option: ${!OPTIND}" 1>&2
+ usage 1>&2
+ exit 1
+ ;;
+ :)
+ echo "Invalid option: -${OPTARG} requires an argument" 1>&2
+ usage 1>&2
+ exit 1
+ ;;
+ esac
+done
+shift $((OPTIND -1))
+if [ $# -ne 0 ]; then
+ echo "Unrecognized options: $@" 1>&2
+ usage 1>&2
+ exit 1
+fi
+
+INPUT="$(dirname "${BASH_SOURCE[0]}")/aarch64.gdb"
+OUTPUT=$(mktemp)
+
+[ "${VERBOSE}" -eq 1 ] && set -x
+sed "s|##ELF_PATH##|${KERNEL_ELF}|g" "${INPUT}" > "${OUTPUT}"
+exec "${GDB}" -ex "source ${OUTPUT}"
diff --git a/common.inc b/common.inc
index c2443bb..cb88053 100644
--- a/common.inc
+++ b/common.inc
@@ -30,10 +30,14 @@
PREBUILTS_KUT_ROOTFS="${PREBUILTS_IMG_DIR}/rootfs.ext4"
+TOOLCHAINS_DIR="${ROOT_DIR}/toolchains"
+TOOLCHAINS_GDB_BIN="${TOOLCHAINS_DIR}/linux-x86/gdb/bin/gdb"
+
OUT_DIR="${ROOT_DIR}/out"
LINUX_OUT="${OUT_DIR}/linux"
LINUX_OUT_IMAGE="${LINUX_OUT}/arch/arm64/boot/Image.gz"
+LINUX_OUT_ELF="${LINUX_OUT}/vmlinux"
KUT_OUT="${OUT_DIR}/kvm-unit-tests"
KUT_TEST_DIR="${KUT_OUT}/tests"
diff --git a/kvm-unit-tests/gen_makefile.sh b/kvm-unit-tests/gen_makefile.sh
index aafc2db..e484ebb 100755
--- a/kvm-unit-tests/gen_makefile.sh
+++ b/kvm-unit-tests/gen_makefile.sh
@@ -45,6 +45,7 @@
function group_target_name {
local group="$1"
local vhe="$2"
+
group="$(target_name kut "${group}")"
if [ "${vhe}" -eq 1 ]; then
group="$(target_name vhe "${group}")"
@@ -82,15 +83,20 @@
# 1) Target name
# 2) Test binary path
# 3) is VHE (0/1)
+# 4) is GDB (0/1)
function test_target {
local target="$1"
local binary="$2"
local vhe="$3"
+ local gdb="$4"
local extra_args=()
if [ "${vhe}" -eq 1 ]; then
extra_args+=(-V)
fi
+ if [ "${gdb}" -eq 1 ]; then
+ extra_args+=(-G)
+ fi
cat <<EOF
.PHONY: ${target}
@@ -140,9 +146,12 @@
# Emit a target for this test case.
TARGET="$(target_name ${NAME} ${GROUP})"
- test_target "${TARGET}" "${TEST_PATH}" "${VHE}"
+ GDB_TARGET="$(target_name gdb ${TARGET})"
+ TARGET_LIST+=("${TARGET}" "${GDB_TARGET}")
+
+ test_target "${TARGET}" "${TEST_PATH}" "${VHE}" 0
+ test_target "${GDB_TARGET}" "${TEST_PATH}" "${VHE}" 1
dependency_target "${TARGET}" "${GROUP}"
- TARGET_LIST+=("${TARGET}")
done
done