Debugging C/C++ with LLDB in VS Code with Docker and Kubernetes


  • video walkthrough

Docker

Prerequisites

  1. Dev Containers Extension
  2. CodeLLDB Extension
  3. Remote Explorer Extension

Setup

  1. Have the Docker daemon running on your machine.
  2. Install Dev Containers extension locally
  3. Jump into repo root and hit cmd + shift + p and select Reopen in Container.
  4. Install CodeLLDB in remote VS Code instance inside container (if not done automatically by the devcontainer.json config file in the root of this repo).
  5. Open any c or c++ file and go to the debugger window cmd + shift + d.
  6. Click on the green play button to run the launch configuration lldb.

Notes

C++ is a superset of C, just extern C functions in C++ files due to the way C++ handles overloading.

extern "C" {
        void my_c_function();
    }
WARNING

You will be prompted to install the C++ extension in the container, but it is not strictly necessary with this setup.

Debugging with LLDB

  • lldb means llvm debugger; it hooks into the compiler architecture of llvm which is the next generation of compiler for c/c++
  • clang++ uses llvm and is the compiler for c/c++
  • a c/c++ file needs to be compiled with the -g flag to enable debugger metadata, which is then used by lldb
  • the launch config does just what a terminal session would do but dynamically for the active file in the ide and it runs the build task (clang++ with -g flag) beforehand to enable debugging in the window for active file.

Config

  • .vscode/launch.json
// launch config
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "lldb",
      "type": "lldb",
      "request": "launch",
      "program": "${fileDirname}/${fileBasenameNoExtension}",
      "cwd": "${workspaceFolder}",
      "preLaunchTask": "clang++"
    }
  ]
}
  • .vscode/tasks.json
// build task
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "clang++",
      "type": "shell",
      "command": "/usr/bin/clang++",
      "args": [
        "-std=c++17",
        "-g",
        "${file}",
        "-o",
        "${fileDirname}/${fileBasenameNoExtension}"
      ]
    }
  ]
}
  • Dockerfile
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    build-essential \
    clang \
    gdb \
    cmake \
    git \
    curl \
    vim \
    lldb \
    python3 \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

RUN pip3 install --upgrade pip

RUN pip3 install matplotlib numpy

RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 10

CMD ["bash"]
  • .devcontainer/devcontainer.json
// https://code.visualstudio.com/docs/devcontainers/containers#_create-a-devcontainerjson-file
{
  "name": "Existing Dockerfile",
  "build": {
    "context": "..",
    "dockerfile": "../Dockerfile"
  },
  "customizations": {
    "vscode": {
      "extensions": ["vadimcn.vscode-lldb"]
    }
  }
}

Kubernetes

Prerequisites

  1. Kubernetes Extension
  2. Kustomize

Setup

  1. Install Kubernetes Extension in VS Code
  2. Install Kustomize
  3. Make Kustomize script executable and run it to setup the kubernetes cluster
  4. Attach VS Code to the running container (pod in the deployment) via the Kubernetes extension

Kustomize

chmod +x ./kustomize-deploy.sh
./kustomize-deploy.sh

Kubectl

Commands Reference

kubectl apply -f <filename>.yaml
kubectl exec -it <pod-name> -- /bin/sh
kubectl describe pod <pod-name>
kubectl get deployments
kubectl scale deployment <deployment-name> --replicas=0
kubectl get pods
kubectl delete pod <pod-name>
kubectl delete deployment <deployment-name>
kubectl delete pods --all

Sourcing images from Github (GHCRIO Registry)

Generate a Personal Access Token (PAT): Navigate to GitHub Settings. Click on "Generate new token". Select Scopes: For public repositories: read:packages write:packages delete:packages (optional) For private repositories: All the above scopes. Example Name: ghcr-access-token Click "Generate token" and copy the token. Note: You won't be able to see it again. Login to GHCR via Docker CLI: Open your terminal and execute:

echo <YOUR_PAT> | docker login ghcr.io -u <github-username> --password-stdin

Build Image from Dockerfile

docker build -t <image-name>:<tag> .

Tag and Push Image to GHCRIO Registry

docker tag <image-name>:<tag> ghcr.io/<username>/<image-name>:<tag>
docker push ghcr.io/<username>/<image-name>:<tag>

Reference the ghcr.io registry in the kubernetes deployment manifest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: busy-knuth-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: busy-knuth
  template:
    metadata:
      labels:
        app: busy-knuth
    spec:
      containers:
        - name: busy-knuth-container
          image: ghcr.io/bronifty/busy-knuth:latest
          imagePullPolicy: IfNotPresent
          command: ["/bin/sh"]
          args:
            - "-c"
            - |
              echo Container started
              trap "exit 0" 15

              exec "$@"
              while sleep 1 & wait $!; do :; done
            - "-"
          env:
            - name: PATH
              value: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            - name: DEBIAN_FRONTEND
              value: "noninteractive"
          volumeMounts:
            - name: c-debugging
              mountPath: /workspaces/c-debugging
            - name: vscode
              mountPath: /vscode
      volumes:
        - name: c-debugging
          hostPath:
            path: /Users/bronifty/codes/temp/c-debugging
            type: Directory
        - name: vscode
          persistentVolumeClaim:
            claimName: vscode-pvc

Run GHCRIO Image Locally to Validate

docker run -it ghcr.io/<username>/<image-name>:<tag>

Apply Deployment Update

kubectl apply -f deployment.yaml
kubectl get pods
kubectl describe pod busy-knuth-deployment-xxxxxxxxxx-yyyyy
kubectl exec -it busy-knuth-deployment-xxxxxxxxxx-yyyyy -- /bin/bash
kubectl logs busy-knuth-deployment-xxxxxxxxxx-yyyyy

Verify and Check Logs

kubectl get pods
kubectl describe pod busy-knuth-deployment-xxxxxxxxxx-yyyyy
kubectl exec -it busy-knuth-deployment-xxxxxxxxxx-yyyyy -- /bin/bash
kubectl logs busy-knuth-deployment-xxxxxxxxxx-yyyyy