Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Hyperlight KVM guest debugging using gdb #111

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/dep_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
# make sure certain cargo features compile
cargo check -p hyperlight-host --features crashdump
cargo check -p hyperlight-host --features print_debug
cargo check -p hyperlight-host --features gdb

# without any driver (shouldn't compile)
just test-rust-feature-compilation-fail ${{ matrix.config }}
Expand Down
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This project is composed internally of several internal components, depicted in

* [Security guidance for developers](./security-guidance-for-developers.md)
* [Paging Development Notes](./paging-development-notes.md)
* [How to debug a Hyperlight guest](./how-to-debug-a-hyperlight-guest.md)
* [How to use Flatbuffers in Hyperlight](./how-to-use-flatbuffers.md)
* [How to make a Hyperlight release](./how-to-make-releases.md)
* [Getting Hyperlight Metrics, Logs, and Traces](./hyperlight-metrics-logs-and-traces.md)
Expand Down
193 changes: 193 additions & 0 deletions docs/how-to-debug-a-hyperlight-guest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# How to debug a Hyperlight **KVM** guest using gdb

Hyperlight supports gdb debugging of a **KVM** guest running inside a Hyperlight sandbox.
When Hyperlight is compiled with the `gdb` feature enabled, a Hyperlight KVM sandbox can be configured
to start listening for a gdb connection.

## Supported features

The Hyperlight `gdb` feature enables **KVM** guest debugging:
- an entry point breakpoint is automatically set for the guest to stop
- add and remove HW breakpoints (maximum 4 set breakpoints at a time)
- add and remove SW breakpoints
- read and write registers
- read and write addresses
- step/continue
- get code offset from target

## Expected behavior

Below is a list describing some cases of expected behavior from a gdb debug
session of a guest binary running inside a KVM Hyperlight sandbox.

- when the `gdb` feature is enabled and a SandboxConfiguration is provided a
debug port, the created sandbox will wait for a gdb client to connect on the
configured port
- when the gdb client attaches, the guest vCPU is expected to be stopped at the
entry point
- if a gdb client disconnects unexpectedly, the debug session will be closed and
the guest will continue executing disregarding any prior breakpoints
- if multiple sandbox instances are created, each instance will have its own
gdb thread listening on the configured port
- if two sandbox instances are created with the same debug port, the second
instance logs an error and the gdb thread will not be created, but the sandbox
will continue to run without gdb debugging

## Example

### Sandbox configuration

The `guest-debugging` example in Hyperlight demonstrates how to configure a Hyperlight
sandbox to listen for a gdb client on a specific port.

### CLI Gdb configuration

One can use a gdb config file to provide the symbols and desired configuration.

The below contents of the `.gdbinit` file can be used to provide a basic configuration
to gdb startup.

```gdb
# Path to symbols
file path/to/symbols.elf
# The port on which Hyperlight listens for a connection
target remote :8080
set disassembly-flavor intel
set disassemble-next-line on
enable pretty-printer
layout src
```
One can find more information about the `.gdbinit` file at [gdbinit(5)](https://www.man7.org/linux/man-pages/man5/gdbinit.5.html).

### End to end example

Using the example mentioned at [Sandbox configuration](#sandbox-configuration)
one can run the below commands to debug the guest binary:

```bash
# Terminal 1
$ cargo run --example guest-debugging --features gdb
```

```bash
# Terminal 2
$ cat .gdbinit
file src/tests/rust_guests/bin/debug/simpleguest
target remote :8080
set disassembly-flavor intel
set disassemble-next-line on
enable pretty-printer
layout src

$ gdb
dblnz marked this conversation as resolved.
Show resolved Hide resolved
```
dblnz marked this conversation as resolved.
Show resolved Hide resolved

### Using VSCode to debug a Hyperlight guest

To replicate the above behavior using VSCode follow the below steps:
- install the `gdb` package on the host machine
- install the `C/C++` extension in VSCode to add debugging capabilities
- create a `.vscode/launch.json` file in the project directory with the below content:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "GDB",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/src/tests/rust_guests/bin/debug/simpleguest",
"args": [],
"stopAtEntry": true,
"hardwareBreakpoints": {"require": false, "limit": 4},
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"miDebuggerServerAddress": "localhost:8080",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
```
- in `Run and Debug` tab, select the `GDB` configuration and click on the `Run`
button to start the debugging session.
The gdb client will connect to the Hyperlight sandbox and the guest vCPU will
stop at the entry point.


## How it works

The gdb feature is designed to work like a Request - Response protocol between
a thread that accepts commands from a gdb client and the hypervisor handler over
a communication channel.

All the functionality is implemented on the hypervisor side so it has access to
the shared memory and the vCPU.

The gdb thread uses the `gdbstub` crate to handle the communication with the gdb client.
When the gdb client requests one of the supported features mentioned above, a request
is sent over the communication channel to the hypervisor handler for the sandbox
to resolve.

Below is a sequence diagram that shows the interaction between the entities
involved in the gdb debugging of a Hyperlight guest running inside a KVM sandbox.

```
┌───────────────────────────────────────────────────────────────────────────────────────────────┐
│ Hyperlight Sandbox │
USER │ │
┌────────────┐ │ ┌──────────────┐ ┌───────────────────────────┐ ┌────────┐ │
│ gdb client │ │ │ gdb thread │ │ hypervisor handler thread │ │ vCPU │ │
└────────────┘ │ └──────────────┘ └───────────────────────────┘ └────────┘ │
| │ | create_gdb_thread | | │
| │ |◄─────────────────────────────────────────┌─┐ vcpu stopped ┌─┐ │
| attach │ ┌─┐ │ │◄──────────────────────────────┴─┘ │
┌─┐───────────────────────┼────────►│ │ │ │ entrypoint breakpoint | │
│ │ attach response │ │ │ │ │ | │
│ │◄──────────────────────┼─────────│ │ │ │ | │
│ │ │ │ │ │ │ | │
│ │ add_breakpoint │ │ │ │ │ | │
│ │───────────────────────┼────────►│ │ add_breakpoint │ │ | │
│ │ │ │ │────────────────────────────────────────►│ │ add_breakpoint | │
│ │ │ │ │ │ │────┐ | │
│ │ │ │ │ │ │ │ | │
│ │ │ │ │ │ │◄───┘ | │
│ │ │ │ │ add_breakpoint response │ │ | │
│ │ add_breakpoint response │ │◄────────────────────────────────────────│ │ | │
│ │◄──────────────────────┬─────────│ │ │ │ | │
│ │ continue │ │ │ │ │ | │
│ │───────────────────────┼────────►│ │ continue │ │ | │
│ │ │ │ │────────────────────────────────────────►│ │ resume vcpu | │
│ │ │ │ │ │ │──────────────────────────────►┌─┐ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ vcpu stopped │ │ │
│ │ │ │ │ notify vcpu stop reason │ │◄──────────────────────────────┴─┘ │
│ │ notify vcpu stop reason │ │◄────────────────────────────────────────│ │ | │
│ │◄──────────────────────┬─────────│ │ │ │ | │
│ │ continue until end │ │ │ │ │ | │
│ │───────────────────────┼────────►│ │ continue │ │ resume vcpu | │
│ │ │ │ │────────────────────────────────────────►│ │──────────────────────────────►┌─┐ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ comm channel disconnected │ │ vcpu halted │ │ │
│ │ target finished exec│ │ │◄────────────────────────────────────────┤ │◄──────────────────────────────┴─┘ │
│ │◄──────────────────────┼─────────┴─┘ target finished exec └─┘ | │
│ │ │ | | | │
└─┘ │ | | | │
| └───────────────────────────────────────────────────────────────────────────────────────────────┘
```
4 changes: 4 additions & 0 deletions src/hyperlight_host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ sha256 = "1.4.0"
windows-version = "0.1"

[target.'cfg(unix)'.dependencies]
gdbstub = { version = "0.7.3", optional = true }
gdbstub_arch = { version = "0.3.1", optional = true }
seccompiler = { version = "0.4.0", optional = true }
kvm-bindings = { version = "0.11", features = ["fam-wrappers"], optional = true }
kvm-ioctls = { version = "0.20", optional = true }
Expand Down Expand Up @@ -128,6 +130,8 @@ kvm = ["dep:kvm-bindings", "dep:kvm-ioctls"]
mshv2 = ["dep:mshv-bindings2", "dep:mshv-ioctls2"]
mshv3 = ["dep:mshv-bindings3", "dep:mshv-ioctls3"]
inprocess = []
# This enables easy debug in the guest
gdb = ["dep:gdbstub", "dep:gdbstub_arch"]

[[bench]]
name = "benchmarks"
Expand Down
4 changes: 2 additions & 2 deletions src/hyperlight_host/benches/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ fn guest_call_benchmark(c: &mut Criterion) {
fn sandbox_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("sandboxes");

// Benchmarks the time to create a new uninintialized sandbox.
// Benchmarks the time to create a new uninitialized sandbox.
// Does **not** include the time to drop the sandbox.
group.bench_function("create_uninitialized_sandbox", |b| {
b.iter_with_large_drop(create_uninit_sandbox);
});

// Benchmarks the time to create a new uninintialized sandbox and drop it.
// Benchmarks the time to create a new uninitialized sandbox and drop it.
group.bench_function("create_uninitialized_sandbox_and_drop", |b| {
b.iter(create_uninit_sandbox);
});
Expand Down
1 change: 1 addition & 0 deletions src/hyperlight_host/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ fn main() -> Result<()> {
// Essentially the kvm and mshv features are ignored on windows as long as you use #[cfg(kvm)] and not #[cfg(feature = "kvm")].
// You should never use #[cfg(feature = "kvm")] or #[cfg(feature = "mshv")] in the codebase.
cfg_aliases::cfg_aliases! {
gdb: { all(feature = "gdb", debug_assertions, feature = "kvm", target_os = "linux") },
kvm: { all(feature = "kvm", target_os = "linux") },
mshv: { all(any(feature = "mshv2", feature = "mshv3"), target_os = "linux") },
// inprocess feature is aliased with debug_assertions to make it only available in debug-builds.
Expand Down
Loading
Loading