Launch QEMU with gdb and connect locally with gdb client

This guide is for cases where QEMU counters very early failures and it is hard to synchronize it in a later point in time.

Image creation and PVC population

This scenario is a slight variation of the guide about starting strace, hence some of the details on the image build and the PVC population are simply skipped and explained in the other section.

In this example, QEMU will be launched with gdbserver and later we will connect to it using a local gdb client.

The wrapping script looks like:

  1. #!/bin/bash
  2. LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/var/run/debug/usr/lib64 /var/run/debug/usr/bin/gdbserver \
  3. localhost:1234 \
  4. /usr/libexec/qemu-kvm $@ &
  5. printf "%d" $(pgrep gdbserver) > /run/libvirt/qemu/run/default_vmi-debug-tools.pid

First, we need to build and push the image with the wrapping script and the gdbserver:

  1. FROM quay.io/centos/centos:stream9 as build
  2. ENV DIR /debug-tools
  3. ENV DEBUGINFOD_URLS https://debuginfod.centos.org/
  4. RUN mkdir -p ${DIR}/logs
  5. RUN yum install --installroot=${DIR} -y gdb-gdbserver && yum clean all
  6. COPY ./wrap_qemu_gdb.sh $DIR/wrap_qemu_gdb.sh
  7. RUN chmod 0755 ${DIR}/wrap_qemu_gdb.sh
  8. RUN chown 107:107 ${DIR}/wrap_qemu_gdb.sh
  9. RUN chown 107:107 ${DIR}/logs

Then, we can create and populate the debug-tools PVC as with did in the strace example:

  1. $ k apply -f debug-tools-pvc.yaml
  2. persistentvolumeclaim/debug-tools created
  3. $ kubectl apply -f populate-job-pvc.yaml
  4. job.batch/populate-pvc created
  5. $ $ kubectl get jobs
  6. NAME COMPLETIONS DURATION AGE
  7. populate-pvc 1/1 7s 2m12s

Configmap:

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: my-config-map
  5. data:
  6. my_script.sh: |
  7. #!/bin/sh
  8. tempFile=`mktemp --dry-run`
  9. echo $4 > $tempFile
  10. sed -i "s|<emulator>/usr/libexec/qemu-kvm</emulator>|<emulator>/var/run/debug/wrap_qemu_gdb.sh</emulator>|" $tempFile
  11. cat $tempFile

As last step, we need to create the configmaps to modify the VM XML:

  1. $ kubectl apply -f configmap.yaml
  2. configmap/my-config-map created

Build client image

In this scenario, we use an additional container image containing gdb and the same qemu binary as the target process to debug. This image will be run locally with podman.

In order to build this image, we need to identify the image of the virt-launcher container we want to debug. Based on the KubeVirt installation, the namespace and the name of the KubeVirt CR could vary. In this example, we’ll assume that KubeVirt CR is called kubevirt and installed in the kubevirt namespace.

You can easily find out the right names in your cluster by searching with:

  1. $ kubectl get kubevirt -A
  2. NAMESPACE NAME AGE PHASE
  3. kubevirt kubevirt 3h11m Deployed

The steps to build the image are:

  1. Get the registry of the images of the KubeVirt installation:

    1. $ export registry=$(kubectl get kubevirt kubevirt -n kubevirt -o jsonpath='{.status.observedDeploymentConfig}' |jq '.registry'|tr -d "\"")
    2. $ echo $registry
    3. "registry:5000/kubevirt"
  2. Get the shasum of the virt-launcher image:

    1. $ export tag=$(kubectl get kubevirt kubevirt -n kubevirt -o jsonpath='{.status.observedDeploymentConfig}' |jq '.virtLauncherSha'|tr -d "\"")
    2. $ echo $tag
    3. "sha256:6c8b85eed8e83a4c70779836b246c057d3e882eb513f3ded0a02e0a4c4bda837"

Example of Dockerfile:

  1. ARG registry
  2. ARG tag
  3. FROM ${registry}/kubevirt/virt-launcher${tag} AS launcher
  4. FROM quay.io/centos/centos:stream9 as build
  5. RUN yum install -y gdb && yum clean all
  6. COPY --from=launcher /usr/libexec/qemu-kvm /usr/libexec/qemu-kvm
  1. Build the image by using the registry and the tag retrieved in the previous steps:

    1. $ podman build \
    2. -t gdb-client \
    3. --build-arg registry=$registry \
    4. --build-arg tag=@$tag \
    5. -f Dockerfile.client .

Podman will replace the registry and tag arguments provided on the command line. In this way, we can specify the image registry and shasum for the KubeVirt version to debug.

Run the VM to troubleshoot

For this example, we add an annotation to keep the virt-launcher pod running even if any errors occur:

  1. metadata:
  2. annotations:
  3. kubevirt.io/keep-launcher-alive-after-failure: "true"

Then, we can launch the VM:

  1. $ kubectl apply -f debug-vmi.yaml
  2. virtualmachineinstance.kubevirt.io/vmi-debug-tools created
  3. $ kubectl get vmi
  4. NAME AGE PHASE IP NODENAME READY
  5. vmi-debug-tools 28s Scheduled node01 False
  6. $ kubectl get po
  7. NAME READY STATUS RESTARTS AGE
  8. populate-pvc-dnxld 0/1 Completed 0 4m17s
  9. virt-launcher-vmi-debug-tools-tfh28 4/4 Running 0 25s

The wrapping script starts the gdbserver and expose in the port 1234 inside the container. In order to be able to connect remotely to the gdbserver, we can use the command kubectl port-forward to expose the gdb port on our machine.

  1. $ kubectl port-forward virt-launcher-vmi-debug-tools-tfh28 1234
  2. Forwarding from 127.0.0.1:1234 -> 1234
  3. Forwarding from [::1]:1234 -> 1234

Finally, we can start the gbd client in the container:

  1. $ podman run -ti --network host gdb-client:latest
  2. $ gdb /usr/libexec/qemu-kvm -ex 'target remote localhost:1234'
  3. GNU gdb (GDB) Red Hat Enterprise Linux 10.2-12.el9
  4. Copyright (C) 2021 Free Software Foundation, Inc.
  5. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  6. This is free software: you are free to change and redistribute it.
  7. There is NO WARRANTY, to the extent permitted by law.
  8. Type "show copying" and "show warranty" for details.
  9. This GDB was configured as "x86_64-redhat-linux-gnu".
  10. Type "show configuration" for configuration details.
  11. For bug reporting instructions, please see:
  12. <https://www.gnu.org/software/gdb/bugs/>.
  13. Find the GDB manual and other documentation resources online at:
  14. <http://www.gnu.org/software/gdb/documentation/>.
  15. For help, type "help".
  16. --Type <RET> for more, q to quit, c to continue without paging--
  17. Type "apropos word" to search for commands related to "word"...
  18. Reading symbols from /usr/libexec/qemu-kvm...
  19. Reading symbols from /root/.cache/debuginfod_client/26221a84fabd219a68445ad0cc87283e881fda15/debuginfo...
  20. Remote debugging using localhost:1234
  21. Reading /lib64/ld-linux-x86-64.so.2 from remote target...
  22. warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
  23. Reading /lib64/ld-linux-x86-64.so.2 from remote target...
  24. Reading symbols from target:/lib64/ld-linux-x86-64.so.2...
  25. Downloading separate debug info for /system-supplied DSO at 0x7ffc10eff000...
  26. 0x00007f1a70225e70 in _start () from target:/lib64/ld-linux-x86-64.so.2

For simplicity, we started podman with the option --network host in this way, the container is able to access any port mapped on the host.